X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fext%2Frbvi%2Fchimera%2FChimeraCommands.java;h=3caaac3532367e30f3f2d11003cbdb23c9560437;hb=refs%2Fheads%2FJAL-3253-applet-SwingJS-omnibus;hp=93262aa98b97b16b125794691abb79be1f98c1d5;hpb=c0d803f0d4b7374d93f91055007a9a1f16f8c8dd;p=jalview.git diff --git a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java index 93262aa..3caaac3 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java @@ -20,11 +20,17 @@ */ 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.MappedFeatures; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.gui.Desktop; +import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.structure.StructureMapping; import jalview.structure.StructureMappingcommandSet; import jalview.structure.StructureSelectionManager; @@ -47,20 +53,26 @@ import java.util.Map; public class ChimeraCommands { + public static final String NAMESPACE_PREFIX = "jv_"; + /** - * utility to construct the commands to colour chains by the given alignment - * for passing to Chimera - * - * @returns Object[] { Object[] { , + * Constructs Chimera commands to colour residues as per the Jalview alignment * + * @param ssm + * @param files + * @param sequence + * @param sr + * @param fr + * @param viewPanel + * @return */ - public static StructureMappingcommandSet getColourBySequenceCommand( + public static StructureMappingcommandSet[] getColourBySequenceCommand( StructureSelectionManager ssm, String[] files, - SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr, - AlignmentI alignment) + SequenceI[][] sequence, SequenceRenderer sr, + AlignmentViewPanel viewPanel) { - Map colourMap = buildColoursMap( - ssm, files, sequence, sr, fr, alignment); + Map colourMap = buildColoursMap(ssm, files, + sequence, sr, viewPanel); List colourCommands = buildColourCommands(colourMap); @@ -68,7 +80,7 @@ public class ChimeraCommands ChimeraCommands.class, null, colourCommands.toArray(new String[colourCommands.size()])); - return cs; + return new StructureMappingcommandSet[] { cs }; } /** @@ -87,18 +99,19 @@ public class ChimeraCommands * @return */ protected static List buildColourCommands( - Map colourMap) + Map colourMap) { /* * 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; - for (Color colour : colourMap.keySet()) + for (Object key : colourMap.keySet()) { + Color colour = (Color) key; String colourCode = ColorUtils.toTkCode(colour); if (!firstColour) { @@ -106,8 +119,7 @@ public class ChimeraCommands } sb.append("color ").append(colourCode).append(" "); firstColour = false; - final AtomSpecModel colourData = colourMap - .get(colour); + final AtomSpecModel colourData = colourMap.get(colour); sb.append(colourData.getAtomSpec()); } commands.add(sb.toString()); @@ -167,9 +179,8 @@ public class ChimeraCommands /** *
-   * 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.
+   * Build a data structure which records contiguous subsequences for each colour. 
+   * From this we can easily generate the Chimera command for colour by sequence.
    * Color
    *     Model number
    *         Chain
@@ -177,13 +188,19 @@ public class ChimeraCommands
    * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
    * 
*/ - protected static Map buildColoursMap( + protected static Map buildColoursMap( StructureSelectionManager ssm, String[] files, - SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr, - AlignmentI alignment) + SequenceI[][] sequence, SequenceRenderer sr, + 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]); @@ -201,9 +218,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 @@ -218,7 +235,16 @@ public class ChimeraCommands continue; } - Color colour = sr.getResidueColour(seq, r, fr); + Color colour = sr.getResidueColour(seq, r, finder); + + /* + * darker colour for hidden regions + */ + if (!cs.isVisible(r)) + { + colour = Color.GRAY; + } + final String chain = mapping[m].getChain(); /* @@ -233,7 +259,7 @@ public class ChimeraCommands { if (startPos != -1) { - addColourRange(colourMap, lastColour, pdbfnum, startPos, + addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos, lastPos, lastChain); } startPos = pos; @@ -245,7 +271,7 @@ public class ChimeraCommands // final colour range if (lastColour != null) { - addColourRange(colourMap, lastColour, pdbfnum, startPos, + addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos, lastPos, lastChain); } // break; @@ -257,56 +283,60 @@ public class ChimeraCommands } /** - * Helper method to add one contiguous colour range to the colour map. + * Helper method to add one contiguous range to the AtomSpec model for the given + * value (creating the model if necessary). As used by Jalview, {@code value} is + *
    + *
  • a colour, when building a 'colour structure by sequence' command
  • + *
  • a feature value, when building a 'set Chimera attributes from features' + * command
  • + *
* - * @param colourMap - * @param colour + * @param map + * @param value * @param model * @param startPos * @param endPos * @param chain */ - protected static void addColourRange( -Map colourMap, - Color colour, int model, int startPos, int endPos, String chain) + protected static void addAtomSpecRange(Map map, + Object value, int model, int startPos, int endPos, String chain) { - // refactor for reuse as addRange /* * Get/initialize map of data for the colour */ - AtomSpecModel colourData = colourMap.get(colour); - if (colourData == null) + AtomSpecModel atomSpec = map.get(value); + if (atomSpec == null) { - colourData = new AtomSpecModel(); - colourMap.put(colour, colourData); + atomSpec = new AtomSpecModel(); + map.put(value, atomSpec); } - colourData.addRange(model, startPos, endPos, chain); + atomSpec.addRange(model, startPos, endPos, chain); } /** - * Constructs and returns a set of Chimera commands to set attributes on - * residues corresponding to features in Jalview. + * 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. * * @param ssm * @param files * @param seqs - * @param fr - * @param alignment + * @param viewPanel * @return */ public static StructureMappingcommandSet getSetAttributeCommandsForFeatures( - StructureSelectionManager ssm, String[] files, - SequenceI[][] seqs, FeatureRenderer fr, AlignmentI alignment) + StructureSelectionManager ssm, String[] files, SequenceI[][] seqs, + AlignmentViewPanel viewPanel) { - Map>>> featureMap = buildFeaturesMap( - ssm, files, seqs, fr, alignment); + Map> featureMap = buildFeaturesMap( + ssm, files, seqs, viewPanel); - List colourCommands = buildSetAttributeCommands(featureMap); + List commands = buildSetAttributeCommands(featureMap); StructureMappingcommandSet cs = new StructureMappingcommandSet( ChimeraCommands.class, null, - colourCommands.toArray(new String[colourCommands.size()])); + commands.toArray(new String[commands.size()])); return cs; } @@ -314,31 +344,53 @@ Map colourMap, /** *
    * Helper method to build a map of 
-   * { featureType, {modelNumber, {chain, {list of from-to ranges} } } }
+   *   { featureType, { feature value, AtomSpecModel } }
    * 
* * @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) + protected static Map> buildFeaturesMap( + StructureSelectionManager ssm, String[] files, SequenceI[][] seqs, + AlignmentViewPanel viewPanel) { - Map>>> theMap = new HashMap>>>(); + Map> theMap = new LinkedHashMap<>(); - List visibleFeatures = fr.getDisplayedFeatureTypes(); - if (visibleFeatures.isEmpty()) + FeatureRenderer fr = viewPanel.getFeatureRenderer(); + if (fr == null) { return theMap; } - + + AlignViewportI viewport = viewPanel.getAlignViewport(); + List visibleFeatures = fr.getDisplayedFeatureTypes(); + /* - * traverse mappings to structures + * if alignment is showing features from complement, we also transfer + * these features to the corresponding mapped structure residues */ + boolean showLinkedFeatures = viewport.isShowComplementFeatures(); + List complementFeatures = new ArrayList<>(); + FeatureRenderer complementRenderer = null; + if (showLinkedFeatures) + { + AlignViewportI comp = fr.getViewport().getCodingComplement(); + if (comp != null) + { + complementRenderer = Desktop.getAlignFrameFor(comp) + .getFeatureRenderer(); + complementFeatures = complementRenderer.getDisplayedFeatureTypes(); + } + } + if (visibleFeatures.isEmpty() && complementFeatures.isEmpty()) + { + return theMap; + } + + AlignmentI alignment = viewPanel.getAlignment(); for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]); @@ -348,94 +400,256 @@ Map colourMap, continue; } - int lastPos = -1; for (int seqNo = 0; seqNo < seqs[pdbfnum].length; seqNo++) { for (int m = 0; m < mapping.length; m++) { final SequenceI seq = seqs[pdbfnum][seqNo]; int sp = alignment.findIndex(seq); - if (mapping[m].getSequence() == seq && sp > -1) + StructureMapping structureMapping = mapping[m]; + if (structureMapping.getSequence() == seq && sp > -1) { - SequenceI asp = alignment.getSequenceAt(sp); - /* - * traverse each sequence for its mapped positions + * found a sequence with a mapping to a structure; + * now scan its features */ - for (int r = 0; r < asp.getLength(); r++) + if (!visibleFeatures.isEmpty()) { - // no mapping to gaps in sequence - if (Comparison.isGap(asp.getCharAt(r))) - { - continue; - } - int residuePos = asp.findPosition(r); - int pos = mapping[m].getPDBResNum(residuePos); + scanSequenceFeatures(visibleFeatures, structureMapping, seq, + theMap, pdbfnum); + } + if (showLinkedFeatures) + { + scanComplementFeatures(complementRenderer, structureMapping, + seq, theMap, pdbfnum); + } + } + } + } + } + return theMap; + } - if (pos < 1 || pos == lastPos) - { - continue; - } - final String chain = mapping[m].getChain(); + /** + * Scans visible features in mapped positions of the CDS/peptide complement, and + * adds any found to the map of attribute values/structure positions + * + * @param complementRenderer + * @param structureMapping + * @param seq + * @param theMap + * @param modelNumber + */ + protected static void scanComplementFeatures( + FeatureRenderer complementRenderer, + StructureMapping structureMapping, SequenceI seq, + Map> theMap, int modelNumber) + { + /* + * for each sequence residue mapped to a structure position... + */ + for (int seqPos : structureMapping.getMapping().keySet()) + { + /* + * find visible complementary features at mapped position(s) + */ + MappedFeatures mf = complementRenderer + .findComplementFeaturesAtResidue(seq, seqPos); + if (mf != null) + { + for (SequenceFeature sf : mf.features) + { + String type = sf.getType(); - /* - * record any features at this position, with the model, chain - * and residue number they map to - */ - List features = fr.findFeaturesAtRes(asp, - residuePos); - for (SequenceFeature feature : features) - { - if (!visibleFeatures.contains(feature)) - { - continue; - } - } + /* + * Don't copy features which originated from Chimera + */ + if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP + .equals(sf.getFeatureGroup())) + { + continue; + } + + /* + * record feature 'value' (score/description/type) as at the + * corresponding structure position + */ + List mappedRanges = structureMapping + .getPDBResNumRanges(seqPos, seqPos); + + if (!mappedRanges.isEmpty()) + { + String value = sf.getDescription(); + if (value == null || value.length() == 0) + { + value = type; + } + float score = sf.getScore(); + if (score != 0f && !Float.isNaN(score)) + { + value = Float.toString(score); + } + Map featureValues = theMap.get(type); + if (featureValues == null) + { + featureValues = new HashMap<>(); + theMap.put(type, featureValues); + } + for (int[] range : mappedRanges) + { + addAtomSpecRange(featureValues, value, modelNumber, range[0], + range[1], structureMapping.getChain()); } } } } + } + } + + /** + * Inspect features on the sequence; for each feature that is visible, determine + * its mapped ranges in the structure (if any) according to the given mapping, + * and add them to the map. + * + * @param visibleFeatures + * @param mapping + * @param seq + * @param theMap + * @param modelNumber + */ + protected static void scanSequenceFeatures(List visibleFeatures, + StructureMapping mapping, SequenceI seq, + Map> theMap, int modelNumber) + { + List sfs = seq.getFeatures().getPositionalFeatures( + visibleFeatures.toArray(new String[visibleFeatures.size()])); + for (SequenceFeature sf : sfs) + { + String type = sf.getType(); + + /* + * Don't copy features which originated from Chimera + */ + if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP + .equals(sf.getFeatureGroup())) + { + continue; } - return theMap; + + List mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(), + sf.getEnd()); + + if (!mappedRanges.isEmpty()) + { + String value = sf.getDescription(); + if (value == null || value.length() == 0) + { + value = type; + } + float score = sf.getScore(); + if (score != 0f && !Float.isNaN(score)) + { + value = Float.toString(score); + } + Map featureValues = theMap.get(type); + if (featureValues == null) + { + featureValues = new HashMap<>(); + theMap.put(type, featureValues); + } + for (int[] range : mappedRanges) + { + addAtomSpecRange(featureValues, value, modelNumber, range[0], + range[1], mapping.getChain()); + } + } + } } /** - * Traverse the map of features/models/chains/positions to construct a list of - * 'setattr' commands (one per feature type). The format of each command is + * Traverse the map of features/values/models/chains/positions to construct a + * list of 'setattr' commands (one per distinct feature type and value). + *

+ * The format of each command is * *

    * 
setattr r " " #modelnumber:range.chain - * e.g. setattr r jv:chain " " #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,... + * e.g. setattr r jv:chain #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,... *
*
- *

- * Note we are not (currently) setting attribute values, only the type - * (presence) of each attribute. This is to avoid overloading the Chimera REST - * interface by sending too many distinct commands. Analysis by feature values - * may still be performed in Jalview, on selections created in Chimera. * * @param featureMap * @return - * @see http - * ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec - * .html */ protected static List buildSetAttributeCommands( - Map>>> featureMap) + Map> featureMap) { - List commands = new ArrayList(); + List commands = new ArrayList<>(); for (String featureType : featureMap.keySet()) { - StringBuilder sb = new StringBuilder(128); - featureType = featureType.replace(" ", "_"); - sb.append("setattr r jv:").append(featureType).append(" \" \" "); - final Map>> featureData = featureMap - .get(featureType); - sb.append(getAtomSpec(featureData)); - commands.add(sb.toString()); + String attributeName = makeAttributeName(featureType); + + /* + * clear down existing attributes for this feature + */ + // 'problem' - sets attribute to None on all residues - overkill? + // commands.add("~setattr r " + attributeName + " :*"); + + Map values = featureMap.get(featureType); + for (Object value : values.keySet()) + { + /* + * 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); + String featureValue = value.toString(); + featureValue = featureValue.replaceAll("\\'", "'"); + sb.append("setattr r ").append(attributeName).append(" '") + .append(featureValue).append("' "); + sb.append(values.get(value).getAtomSpec()); + commands.add(sb.toString()); + } } return commands; } + /** + * Makes a prefixed and valid Chimera attribute name. A jv_ prefix is applied + * for a 'Jalview' namespace, and any non-alphanumeric character is converted + * to an underscore. + * + * @param featureType + * @return + * + *

+   * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
+   *         
+ */ + protected static String makeAttributeName(String featureType) + { + StringBuilder sb = new StringBuilder(); + if (featureType != null) + { + for (char c : featureType.toCharArray()) + { + sb.append(Character.isLetterOrDigit(c) ? c : '_'); + } + } + String attName = NAMESPACE_PREFIX + sb.toString(); + + /* + * Chimera treats an attribute name ending in 'color' as colour-valued; + * Jalview doesn't, so prevent this by appending an underscore + */ + if (attName.toUpperCase().endsWith("COLOR")) + { + attName += "_"; + } + + return attName; + } + }