X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fext%2Frbvi%2Fchimera%2FChimeraCommands.java;h=c52b9a217412d522dfa1cf8b641789bd51bfc033;hb=2d62933ef95beb94c1ec2444bcced4a3a7ec42c0;hp=3415815ba3e12e2b74ac4135b75d07d529e0d5d2;hpb=3634aa61b96d7ce541c4abb83502fc67d72e3f27;p=jalview.git diff --git a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java index 3415815..c52b9a2 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java @@ -20,26 +20,32 @@ */ package jalview.ext.rbvi.chimera; +import jalview.api.AlignViewportI; +import jalview.api.AlignmentViewPanel; import jalview.api.FeatureRenderer; import jalview.api.SequenceRenderer; import jalview.datamodel.AlignmentI; +import jalview.datamodel.HiddenColumns; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.structure.StructureMapping; import jalview.structure.StructureMappingcommandSet; import jalview.structure.StructureSelectionManager; +import jalview.structures.models.AAStructureBindingModel; import jalview.util.ColorUtils; import jalview.util.Comparison; +import jalview.util.IntRangeComparator; import java.awt.Color; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import MCview.PDBChain; - /** * Routines for generating Chimera commands for Jalview/Chimera binding * @@ -48,35 +54,42 @@ import MCview.PDBChain; */ public class ChimeraCommands { + public static final String NAMESPACE_PREFIX = "jv_"; - private static final String NAMESPACE_PREFIX = "jv_"; + /* + * colour for residues shown in structure but hidden in alignment + */ + private static final String COLOR_GRAY_HEX = "color " + + ColorUtils.toTkCode(Color.GRAY); /** * Constructs Chimera commands to colour residues as per the Jalview alignment * - * @param ssm * @param files - * @param sequence - * @param sr - * @param fr - * @param alignment + * @param viewPanel + * @param binding * @return */ - public static StructureMappingcommandSet getColourBySequenceCommand( - StructureSelectionManager ssm, String[] files, - SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr, - AlignmentI alignment) + public static StructureMappingcommandSet[] getColourBySequenceCommand( + String[] files, AlignmentViewPanel viewPanel, + AAStructureBindingModel binding) { - Map colourMap = buildColoursMap( - ssm, files, sequence, sr, fr, alignment); + StructureSelectionManager ssm = binding.getSsm(); + SequenceRenderer sr = binding.getSequenceRenderer(viewPanel); + SequenceI[][] sequence = binding.getSequence(); + boolean hideHiddenRegions = binding.isShowAlignmentOnly() + && binding.isHideHiddenRegions(); + + Map colourMap = buildColoursMap(ssm, files, + sequence, sr, hideHiddenRegions, viewPanel); - List colourCommands = buildColourCommands(colourMap); + List colourCommands = buildColourCommands(colourMap, binding); StructureMappingcommandSet cs = new StructureMappingcommandSet( ChimeraCommands.class, null, colourCommands.toArray(new String[colourCommands.size()])); - return cs; + return new StructureMappingcommandSet[] { cs }; } /** @@ -92,107 +105,70 @@ public class ChimeraCommands * * * @param colourMap + * @param binding * @return */ protected static List buildColourCommands( - Map colourMap) + Map colourMap, + AAStructureBindingModel binding) { /* * This version concatenates all commands into a single String (semi-colon * delimited). If length limit issues arise, refactor to return one color * command per colour. */ - List commands = new ArrayList(); + List commands = new ArrayList<>(); StringBuilder sb = new StringBuilder(256); - boolean firstColour = true; + sb.append(COLOR_GRAY_HEX); + for (Object key : colourMap.keySet()) { Color colour = (Color) key; String colourCode = ColorUtils.toTkCode(colour); - if (!firstColour) - { - sb.append("; "); - } + sb.append("; "); sb.append("color ").append(colourCode).append(" "); - firstColour = false; - final AtomSpecModel colourData = colourMap - .get(colour); - sb.append(colourData.getAtomSpec()); + final AtomSpecModel colourData = colourMap.get(colour); + sb.append(getAtomSpec(colourData, binding)); } commands.add(sb.toString()); return commands; } /** - * Traverses a map of { modelNumber, {chain, {list of from-to ranges} } } and - * builds a Chimera format atom spec + * Build a data structure which records contiguous subsequences for each colour. + * From this we can easily generate the Chimera command for colour by sequence. * - * @param modelAndChainRanges - */ - protected static String getAtomSpec( - Map>> modelAndChainRanges) - { - StringBuilder sb = new StringBuilder(128); - boolean firstModelForColour = true; - for (Integer model : modelAndChainRanges.keySet()) - { - boolean firstPositionForModel = true; - if (!firstModelForColour) - { - sb.append("|"); - } - firstModelForColour = false; - sb.append("#").append(model).append(":"); - - final Map> modelData = modelAndChainRanges - .get(model); - for (String chain : modelData.keySet()) - { - boolean hasChain = !"".equals(chain.trim()); - for (int[] range : modelData.get(chain)) - { - if (!firstPositionForModel) - { - sb.append(","); - } - if (range[0] == range[1]) - { - sb.append(range[0]); - } - else - { - sb.append(range[0]).append("-").append(range[1]); - } - if (hasChain) - { - sb.append(".").append(chain); - } - firstPositionForModel = false; - } - } - } - return sb.toString(); - } - - /** *
-   * Build a data structure which maps contiguous subsequences for each colour. 
-   * This generates a data structure from which we can easily generate the 
-   * Chimera command for colour by sequence.
    * Color
    *     Model number
    *         Chain
    *             list of start/end ranges
-   * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
    * 
+ * + * Ordering is by order of addition (for colours and positions), natural + * ordering (for models and chains) + * + * @param ssm + * @param files + * @param sequence + * @param sr + * @param hideHiddenRegions + * @param viewPanel + * @return */ protected static Map buildColoursMap( StructureSelectionManager ssm, String[] files, - SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr, - AlignmentI alignment) + SequenceI[][] sequence, SequenceRenderer sr, + boolean hideHiddenRegions, AlignmentViewPanel viewPanel) { - Map colourMap = new LinkedHashMap(); + FeatureRenderer fr = viewPanel.getFeatureRenderer(); + FeatureColourFinder finder = new FeatureColourFinder(fr); + AlignViewportI viewport = viewPanel.getAlignViewport(); + HiddenColumns cs = viewport.getAlignment().getHiddenColumns(); + AlignmentI al = viewport.getAlignment(); + Map colourMap = new LinkedHashMap<>(); Color lastColour = null; + for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]); @@ -210,9 +186,9 @@ public class ChimeraCommands { final SequenceI seq = sequence[pdbfnum][s]; if (mapping[m].getSequence() == seq - && (sp = alignment.findIndex(seq)) > -1) + && (sp = al.findIndex(seq)) > -1) { - SequenceI asp = alignment.getSequenceAt(sp); + SequenceI asp = al.getSequenceAt(sp); for (int r = 0; r < asp.getLength(); r++) { // no mapping to gaps in sequence @@ -227,7 +203,23 @@ public class ChimeraCommands continue; } - Color colour = sr.getResidueColour(seq, r, fr); + Color colour = sr.getResidueColour(seq, r, finder); + + /* + * hidden regions are shown gray or, optionally, ignored + */ + if (!cs.isVisible(r)) + { + if (hideHiddenRegions) + { + continue; + } + else + { + colour = Color.GRAY; + } + } + final String chain = mapping[m].getChain(); /* @@ -242,7 +234,7 @@ public class ChimeraCommands { if (startPos != -1) { - addRange(colourMap, lastColour, pdbfnum, startPos, + addColourRange(colourMap, lastColour, pdbfnum, startPos, lastPos, lastChain); } startPos = pos; @@ -254,7 +246,7 @@ public class ChimeraCommands // final colour range if (lastColour != null) { - addRange(colourMap, lastColour, pdbfnum, startPos, + addColourRange(colourMap, lastColour, pdbfnum, startPos, lastPos, lastChain); } // break; @@ -275,7 +267,7 @@ public class ChimeraCommands * @param endPos * @param chain */ - protected static void addRange(Map map, + protected static void addColourRange(Map map, Object key, int model, int startPos, int endPos, String chain) { /* @@ -293,24 +285,27 @@ public class ChimeraCommands /** * Constructs and returns Chimera commands to set attributes on residues - * corresponding to features in Jalview. Attribute names are the Jalview - * feature type, with a "jv_" prefix. + * corresponding to features in Jalview. Attribute names are the Jalview feature + * type, with a "jv_" prefix. * * @param ssm * @param files * @param seqs - * @param fr - * @param alignment + * @param viewPanel + * @param binding * @return */ public static StructureMappingcommandSet getSetAttributeCommandsForFeatures( - StructureSelectionManager ssm, String[] files, - SequenceI[][] seqs, FeatureRenderer fr, AlignmentI alignment) + AlignmentViewPanel viewPanel, AAStructureBindingModel binding) { + StructureSelectionManager ssm = binding.getSsm(); + String[] files = binding.getStructureFiles(); + SequenceI[][] seqs = binding.getSequence(); + Map> featureMap = buildFeaturesMap( - ssm, files, seqs, fr, alignment); + ssm, files, seqs, viewPanel); - List commands = buildSetAttributeCommands(featureMap); + List commands = buildSetAttributeCommands(featureMap, binding); StructureMappingcommandSet cs = new StructureMappingcommandSet( ChimeraCommands.class, null, @@ -328,22 +323,28 @@ public class ChimeraCommands * @param ssm * @param files * @param seqs - * @param fr - * @param alignment + * @param viewPanel * @return */ protected static Map> buildFeaturesMap( - StructureSelectionManager ssm, String[] files, - SequenceI[][] seqs, FeatureRenderer fr, AlignmentI alignment) + StructureSelectionManager ssm, String[] files, SequenceI[][] seqs, + AlignmentViewPanel viewPanel) { - Map> theMap = new LinkedHashMap>(); + Map> theMap = new LinkedHashMap<>(); + + FeatureRenderer fr = viewPanel.getFeatureRenderer(); + if (fr == null) + { + return theMap; + } List visibleFeatures = fr.getDisplayedFeatureTypes(); if (visibleFeatures.isEmpty()) { return theMap; } - + + AlignmentI alignment = viewPanel.getAlignment(); for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]); @@ -391,16 +392,19 @@ public class ChimeraCommands StructureMapping mapping, SequenceI seq, Map> theMap, int modelNumber) { - SequenceFeature[] sfs = seq.getSequenceFeatures(); - if (sfs == null) - { - return; - } - + List sfs = seq.getFeatures().getPositionalFeatures( + visibleFeatures.toArray(new String[visibleFeatures.size()])); for (SequenceFeature sf : sfs) { String type = sf.getType(); - if (!visibleFeatures.contains(type) || suppressFeature(type)) + + /* + * Only copy visible features, don't copy any which originated + * from Chimera, and suppress uninteresting ones (e.g. RESNUM) + */ + boolean isFromViewer = JalviewChimeraBinding.CHIMERA_FEATURE_GROUP + .equals(sf.getFeatureGroup()); + if (isFromViewer) { continue; } @@ -422,31 +426,19 @@ public class ChimeraCommands Map featureValues = theMap.get(type); if (featureValues == null) { - featureValues = new HashMap(); + featureValues = new HashMap<>(); theMap.put(type, featureValues); } for (int[] range : mappedRanges) { - addRange(featureValues, value, modelNumber, range[0], range[1], - mapping.getChain()); + addColourRange(featureValues, value, modelNumber, range[0], + range[1], mapping.getChain()); } } } } /** - * Answers true if the feature type is one we don't wish to propagate to - * Chimera - for now, RESNUM - * - * @param type - * @return - */ - static boolean suppressFeature(String type) - { - return PDBChain.RESNUM_FEATURE.equals(type); - } - - /** * Traverse the map of features/values/models/chains/positions to construct a * list of 'setattr' commands (one per distinct feature type and value). *

@@ -459,12 +451,14 @@ public class ChimeraCommands * * * @param featureMap + * @param binding * @return */ protected static List buildSetAttributeCommands( - Map> featureMap) + Map> featureMap, + AAStructureBindingModel binding) { - List commands = new ArrayList(); + List commands = new ArrayList<>(); for (String featureType : featureMap.keySet()) { String attributeName = makeAttributeName(featureType); @@ -481,11 +475,14 @@ public class ChimeraCommands /* * for each distinct value recorded for this feature type, * add a command to set the attribute on the mapped residues + * Put values in single quotes, encoding any embedded single quotes */ StringBuilder sb = new StringBuilder(128); - sb.append("setattr r ").append(attributeName).append(" \"") - .append(value.toString()).append("\" "); - sb.append(values.get(value).getAtomSpec()); + String featureValue = value.toString(); + featureValue = featureValue.replaceAll("\\'", "'"); + sb.append("setattr r ").append(attributeName).append(" '") + .append(featureValue).append("' "); + sb.append(getAtomSpec(values.get(value), binding)); commands.add(sb.toString()); } } @@ -499,9 +496,11 @@ public class ChimeraCommands * to an underscore. * * @param featureType - * @return

-   * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
-   * 
+ * @return + * + *
+   * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
+   *         
*/ protected static String makeAttributeName(String featureType) { @@ -527,4 +526,111 @@ public class ChimeraCommands return attName; } + /** + * Returns the range(s) formatted as a Chimera atomspec + * + * @return + */ + public static String getAtomSpec(AtomSpecModel atomSpec, + AAStructureBindingModel binding) + { + StringBuilder sb = new StringBuilder(128); + boolean firstModel = true; + for (Integer model : atomSpec.getModels()) + { + if (!firstModel) + { + sb.append("|"); + } + firstModel = false; + // todo use JalviewChimeraBinding.getModelSpec(model) + // which means this cannot be static + sb.append(binding.getModelSpec(model)).append(":"); + + boolean firstPositionForModel = true; + + for (String chain : atomSpec.getChains(model)) + { + chain = " ".equals(chain) ? chain : chain.trim(); + + List rangeList = atomSpec.getRanges(model, chain); + + /* + * sort ranges into ascending start position order + */ + Collections.sort(rangeList, IntRangeComparator.ASCENDING); + + int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0]; + int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1]; + + Iterator iterator = rangeList.iterator(); + while (iterator.hasNext()) + { + int[] range = iterator.next(); + if (range[0] <= end + 1) + { + /* + * range overlaps or is contiguous with the last one + * - so just extend the end position, and carry on + * (unless this is the last in the list) + */ + end = Math.max(end, range[1]); + } + else + { + /* + * we have a break so append the last range + */ + appendRange(sb, start, end, chain, firstPositionForModel); + firstPositionForModel = false; + start = range[0]; + end = range[1]; + } + } + + /* + * and append the last range + */ + if (!rangeList.isEmpty()) + { + appendRange(sb, start, end, chain, firstPositionForModel); + firstPositionForModel = false; + } + } + } + return sb.toString(); + } + + /** + * A helper method that appends one start-end range to a Chimera atomspec + * + * @param sb + * @param start + * @param end + * @param chain + * @param firstPositionForModel + */ + static void appendRange(StringBuilder sb, int start, int end, + String chain, boolean firstPositionForModel) + { + if (!firstPositionForModel) + { + sb.append(","); + } + if (end == start) + { + sb.append(start); + } + else + { + sb.append(start).append("-").append(end); + } + + sb.append("."); + if (!" ".equals(chain)) + { + sb.append(chain); + } + } + }