X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fext%2Frbvi%2Fchimera%2FChimeraCommands.java;fp=src%2Fjalview%2Fext%2Frbvi%2Fchimera%2FChimeraCommands.java;h=c4aa2e7efeb17862c53ba88abaf9fb6cc3623897;hb=4762b29420ed4df01b55a8afe5ab05467aaf41a9;hp=8f6b2f14eaf1a2bf1bd9c966211924142ed4aa0f;hpb=29c183b0d0de9f6f9f94d2b528d10e20b1891684;p=jalview.git diff --git a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java index 8f6b2f1..c4aa2e7 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java @@ -23,6 +23,7 @@ package jalview.ext.rbvi.chimera; import jalview.api.FeatureRenderer; import jalview.api.SequenceRenderer; import jalview.datamodel.AlignmentI; +import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.structure.StructureMapping; import jalview.structure.StructureMappingcommandSet; @@ -32,11 +33,10 @@ import jalview.util.Comparison; import java.awt.Color; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; /** * Routines for generating Chimera commands for Jalview/Chimera binding @@ -47,26 +47,32 @@ import java.util.TreeMap; 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 alignment + * @return */ public static StructureMappingcommandSet[] getColourBySequenceCommand( StructureSelectionManager ssm, String[] files, SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr, AlignmentI alignment) { - Map>>> colourMap = buildColoursMap( - ssm, files, sequence, sr, fr, alignment); + Map colourMap = buildColoursMap(ssm, files, + sequence, sr, fr, alignment); List colourCommands = buildColourCommands(colourMap); StructureMappingcommandSet cs = new StructureMappingcommandSet( ChimeraCommands.class, null, - colourCommands.toArray(new String[0])); + colourCommands.toArray(new String[colourCommands.size()])); return new StructureMappingcommandSet[] { cs }; } @@ -76,18 +82,18 @@ public class ChimeraCommands * 'color' commands (one per distinct colour used). The format of each command * is * - *
color colorname #modelnumber:range.chain e.g. color #00ff00 - * #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,... - * - * @see http - * ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec - * .html + *
+   * 
+ * color colorname #modelnumber:range.chain + * e.g. color #00ff00 #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,... + *
+ *
* * @param colourMap * @return */ protected static List buildColourCommands( - Map>>> colourMap) + Map colourMap) { /* * This version concatenates all commands into a single String (semi-colon @@ -97,8 +103,9 @@ public class ChimeraCommands 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,48 +113,62 @@ public class ChimeraCommands } sb.append("color ").append(colourCode).append(" "); firstColour = false; - boolean firstModelForColour = true; - final Map>> colourData = colourMap - .get(colour); - for (Integer model : colourData.keySet()) + final AtomSpecModel colourData = colourMap.get(colour); + sb.append(colourData.getAtomSpec()); + } + commands.add(sb.toString()); + return commands; + } + + /** + * Traverses a map of { modelNumber, {chain, {list of from-to ranges} } } and + * builds a Chimera format atom spec + * + * @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) { - boolean firstPositionForModel = true; - if (!firstModelForColour) - { - sb.append("|"); - } - firstModelForColour = false; - sb.append("#").append(model).append(":"); + sb.append("|"); + } + firstModelForColour = false; + sb.append("#").append(model).append(":"); - final Map> modelData = colourData.get(model); - for (String chain : modelData.keySet()) + final Map> modelData = modelAndChainRanges + .get(model); + for (String chain : modelData.keySet()) + { + boolean hasChain = !"".equals(chain.trim()); + for (int[] range : modelData.get(chain)) { - boolean hasChain = !"".equals(chain.trim()); - for (int[] range : modelData.get(chain)) + if (!firstPositionForModel) { - 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; + 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; } } } - commands.add(sb.toString()); - return commands; + return sb.toString(); } /** @@ -162,12 +183,12 @@ 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) { - Map>>> colourMap = new LinkedHashMap>>>(); + Map colourMap = new LinkedHashMap(); Color lastColour = null; for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { @@ -230,8 +251,8 @@ public class ChimeraCommands // final colour range if (lastColour != null) { - addColourRange(colourMap, lastColour, pdbfnum, startPos, - lastPos, lastChain); + addColourRange(colourMap, lastColour, pdbfnum, startPos, lastPos, + lastChain); } // break; } @@ -244,51 +265,258 @@ public class ChimeraCommands /** * Helper method to add one contiguous colour range to the colour map. * - * @param colourMap - * @param colour + * @param map + * @param key * @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 addColourRange(Map map, + Object key, int model, int startPos, int endPos, String chain) { /* * Get/initialize map of data for the colour */ - SortedMap>> colourData = colourMap - .get(colour); - if (colourData == null) + AtomSpecModel atomSpec = map.get(key); + if (atomSpec == null) { - colourMap - .put(colour, - colourData = new TreeMap>>()); + atomSpec = new AtomSpecModel(); + map.put(key, atomSpec); } - /* - * Get/initialize map of data for the colour and model - */ - Map> modelData = colourData.get(model); - if (modelData == null) + atomSpec.addRange(model, startPos, endPos, chain); + } + + /** + * 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 + * @return + */ + public static StructureMappingcommandSet getSetAttributeCommandsForFeatures( + StructureSelectionManager ssm, String[] files, + SequenceI[][] seqs, FeatureRenderer fr, AlignmentI alignment) + { + Map> featureMap = buildFeaturesMap( + ssm, files, seqs, fr, alignment); + + List commands = buildSetAttributeCommands(featureMap); + + StructureMappingcommandSet cs = new StructureMappingcommandSet( + ChimeraCommands.class, null, + commands.toArray(new String[commands.size()])); + + return cs; + } + + /** + *
+   * Helper method to build a map of 
+   *   { featureType, { feature value, AtomSpecModel } }
+   * 
+ * + * @param ssm + * @param files + * @param seqs + * @param fr + * @param alignment + * @return + */ + protected static Map> buildFeaturesMap( + StructureSelectionManager ssm, String[] files, + SequenceI[][] seqs, FeatureRenderer fr, AlignmentI alignment) + { + Map> theMap = new LinkedHashMap>(); + + List visibleFeatures = fr.getDisplayedFeatureTypes(); + if (visibleFeatures.isEmpty()) { - colourData.put(model, modelData = new TreeMap>()); + return theMap; } - /* - * Get/initialize map of data for colour, model and chain - */ - List chainData = modelData.get(chain); - if (chainData == null) + for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) + { + StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]); + + if (mapping == null || mapping.length < 1) + { + continue; + } + + 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) + { + /* + * 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); + } + } + } + } + return theMap; + } + + /** + * 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) + { + SequenceFeature[] sfs = seq.getSequenceFeatures(); + if (sfs == null) + { + return; + } + + for (SequenceFeature sf : sfs) + { + String type = sf.getType(); + + /* + * 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 || !visibleFeatures.contains(type)) + { + continue; + } + 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) + { + addColourRange(featureValues, value, modelNumber, range[0], range[1], + mapping.getChain()); + } + } + } + } + + /** + * 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,... + *
+ *
+ * + * @param featureMap + * @return + */ + protected static List buildSetAttributeCommands( + Map> featureMap) + { + List commands = new ArrayList(); + for (String featureType : featureMap.keySet()) { - modelData.put(chain, chainData = new ArrayList()); + 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 + */ + StringBuilder sb = new StringBuilder(128); + sb.append("setattr r ").append(attributeName).append(" \"") + .append(value.toString()).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(); + /* - * Add the start/end positions + * Chimera treats an attribute name ending in 'color' as colour-valued; + * Jalview doesn't, so prevent this by appending an underscore */ - chainData.add(new int[] { startPos, endPos }); + if (attName.toUpperCase().endsWith("COLOR")) + { + attName += "_"; + } + + return attName; } }