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=9ef89fd8d538c4a4ebefbe27454121d5496aa091;hpb=fb315d08a73717eebc3d3a8832a9456fcef52484;p=jalview.git diff --git a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java index 9ef89fd..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; @@ -38,8 +44,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import MCview.PDBChain; - /** * Routines for generating Chimera commands for Jalview/Chimera binding * @@ -49,7 +53,7 @@ import MCview.PDBChain; public class ChimeraCommands { - private static final String NAMESPACE_PREFIX = "jv_"; + public static final String NAMESPACE_PREFIX = "jv_"; /** * Constructs Chimera commands to colour residues as per the Jalview alignment @@ -59,16 +63,16 @@ public class ChimeraCommands * @param sequence * @param sr * @param fr - * @param alignment + * @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); @@ -76,7 +80,7 @@ public class ChimeraCommands ChimeraCommands.class, null, colourCommands.toArray(new String[colourCommands.size()])); - return cs; + return new StructureMappingcommandSet[] { cs }; } /** @@ -102,7 +106,7 @@ public class ChimeraCommands * 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 (Object key : colourMap.keySet()) @@ -115,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()); @@ -176,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
@@ -188,11 +190,17 @@ public class ChimeraCommands
    */
   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]);
@@ -210,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
@@ -227,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();
 
               /*
@@ -242,7 +259,7 @@ public class ChimeraCommands
               {
                 if (startPos != -1)
                 {
-                  addRange(colourMap, lastColour, pdbfnum, startPos,
+                  addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos,
                           lastPos, lastChain);
                 }
                 startPos = pos;
@@ -254,7 +271,7 @@ public class ChimeraCommands
             // final colour range
             if (lastColour != null)
             {
-              addRange(colourMap, lastColour, pdbfnum, startPos,
+              addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos,
                       lastPos, lastChain);
             }
             // break;
@@ -266,26 +283,32 @@ 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 map - * @param key + * @param value * @param model * @param startPos * @param endPos * @param chain */ - protected static void addRange(Map map, - Object key, int model, int startPos, int endPos, String chain) + protected static void addAtomSpecRange(Map map, + Object value, int model, int startPos, int endPos, String chain) { /* * Get/initialize map of data for the colour */ - AtomSpecModel atomSpec = map.get(key); + AtomSpecModel atomSpec = map.get(value); if (atomSpec == null) { atomSpec = new AtomSpecModel(); - map.put(key, atomSpec); + map.put(value, atomSpec); } atomSpec.addRange(model, startPos, endPos, chain); @@ -299,16 +322,15 @@ public class ChimeraCommands * @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); + ssm, files, seqs, viewPanel); List commands = buildSetAttributeCommands(featureMap); @@ -328,22 +350,47 @@ 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; + } + AlignViewportI viewport = viewPanel.getAlignViewport(); List visibleFeatures = fr.getDisplayedFeatureTypes(); - if (visibleFeatures.isEmpty()) + + /* + * 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]); @@ -359,16 +406,23 @@ public class ChimeraCommands { 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) { /* * found a sequence with a mapping to a structure; * now scan its features */ - SequenceI asp = alignment.getSequenceAt(sp); - - scanSequenceFeatures(visibleFeatures, mapping[m], asp, theMap, - pdbfnum); + if (!visibleFeatures.isEmpty()) + { + scanSequenceFeatures(visibleFeatures, structureMapping, seq, + theMap, pdbfnum); + } + if (showLinkedFeatures) + { + scanComplementFeatures(complementRenderer, structureMapping, + seq, theMap, pdbfnum); + } } } } @@ -377,9 +431,85 @@ public class ChimeraCommands } /** - * 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 + * 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(); + + /* + * 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 @@ -391,19 +521,21 @@ 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)) + + /* + * Don't copy features which originated from Chimera + */ + if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP + .equals(sf.getFeatureGroup())) { continue; } + List mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(), sf.getEnd()); @@ -415,38 +547,26 @@ public class ChimeraCommands value = type; } float score = sf.getScore(); - if (score != 0f && score != Float.NaN) + if (score != 0f && !Float.isNaN(score)) { value = Float.toString(score); } 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()); + addAtomSpecRange(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). *

@@ -464,7 +584,7 @@ public class ChimeraCommands protected static List buildSetAttributeCommands( Map> featureMap) { - List commands = new ArrayList(); + List commands = new ArrayList<>(); for (String featureType : featureMap.keySet()) { String attributeName = makeAttributeName(featureType); @@ -481,10 +601,13 @@ 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("\" "); + 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()); } @@ -499,9 +622,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) {