X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fext%2Frbvi%2Fchimera%2FChimeraCommands.java;h=d34ac255a8622c5a2fab2cb978725e62e287466e;hb=8c0955545a1a10062467e2d6dc7feb1e855655c0;hp=4ee74aa99e68e1958725e84c5ad15bc01101d651;hpb=2dd1164b5450a3b1b3b17420543fadece6e5fcbe;p=jalview.git diff --git a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java index 4ee74aa..d34ac25 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java @@ -1,6 +1,6 @@ /* - * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2) - * Copyright (C) 2014 The Jalview Authors + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * @@ -23,14 +23,18 @@ 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; import jalview.structure.StructureSelectionManager; +import jalview.util.ColorUtils; +import jalview.util.Comparison; import java.awt.Color; import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /** @@ -49,18 +53,137 @@ public class ChimeraCommands * @returns Object[] { Object[] { , * */ - public static StructureMappingcommandSet[] getColourBySequenceCommand( + 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); - ArrayList cset = new ArrayList(); + List colourCommands = buildColourCommands(colourMap); + StructureMappingcommandSet cs = new StructureMappingcommandSet( + ChimeraCommands.class, null, + colourCommands.toArray(new String[colourCommands.size()])); + + return cs; + } + + /** + * Traverse the map of colours/models/chains/positions to construct a list of + * '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,... + *
+ *
+ * + * @param colourMap + * @return + */ + protected static List buildColourCommands( + Map colourMap) + { /* - * Map of { colour, positionSpecs} + * 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. */ - Map colranges = new LinkedHashMap(); + List commands = new ArrayList(); + StringBuilder sb = new StringBuilder(256); + boolean firstColour = true; + for (Object key : colourMap.keySet()) + { + Color colour = (Color) key; + String colourCode = ColorUtils.toTkCode(colour); + if (!firstColour) + { + sb.append("; "); + } + sb.append("color ").append(colourCode).append(" "); + firstColour = false; + 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) + { + 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)
+   * 
+ */ + protected static Map buildColoursMap( + StructureSelectionManager ssm, String[] files, + SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr, + AlignmentI alignment) + { + Map colourMap = new LinkedHashMap(); + Color lastColour = null; for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) { StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]); @@ -70,21 +193,21 @@ public class ChimeraCommands continue; } - int startPos = -1, lastPos = -1, startModel = -1, lastModel = -1; + int startPos = -1, lastPos = -1; String lastChain = ""; - Color lastCol = null; for (int s = 0; s < sequence[pdbfnum].length; s++) { for (int sp, m = 0; m < mapping.length; m++) { - if (mapping[m].getSequence() == sequence[pdbfnum][s] - && (sp = alignment.findIndex(sequence[pdbfnum][s])) > -1) + final SequenceI seq = sequence[pdbfnum][s]; + if (mapping[m].getSequence() == seq + && (sp = alignment.findIndex(seq)) > -1) { SequenceI asp = alignment.getSequenceAt(sp); for (int r = 0; r < asp.getLength(); r++) { // no mapping to gaps in sequence - if (jalview.util.Comparison.isGap(asp.getCharAt(r))) + if (Comparison.isGap(asp.getCharAt(r))) { continue; } @@ -95,78 +218,231 @@ public class ChimeraCommands continue; } - Color col = sr.getResidueBoxColour(sequence[pdbfnum][s], r); + Color colour = sr.getResidueColour(seq, r, fr); + final String chain = mapping[m].getChain(); - if (fr != null) - { - col = fr.findFeatureColour(col, sequence[pdbfnum][s], r); - } - if (lastCol != col || lastPos + 1 != pos - || pdbfnum != lastModel - || !mapping[m].getChain().equals(lastChain)) + /* + * Just keep incrementing the end position for this colour range + * _unless_ colour, PDB model or chain has changed, or there is a + * gap in the mapped residue sequence + */ + final boolean newColour = !colour.equals(lastColour); + final boolean nonContig = lastPos + 1 != pos; + final boolean newChain = !chain.equals(lastChain); + if (newColour || nonContig || newChain) { - if (lastCol != null) + if (startPos != -1) { - addColourRange(colranges, lastCol,startModel,startPos,lastPos,lastChain); + addRange(colourMap, lastColour, pdbfnum, startPos, + lastPos, lastChain); } - lastCol = null; startPos = pos; - startModel = pdbfnum; } - lastCol = col; + lastColour = colour; lastPos = pos; - lastModel = pdbfnum; - lastChain = mapping[m].getChain(); + lastChain = chain; } // final colour range - if (lastCol != null) + if (lastColour != null) { - addColourRange(colranges, lastCol,startModel,startPos,lastPos,lastChain); + addRange(colourMap, lastColour, pdbfnum, startPos, + lastPos, lastChain); } - break; + // break; } } } - // Finally, add the command set ready to be returned. - StringBuilder coms = new StringBuilder(256); - for (String cr:colranges.keySet()) - { - coms.append("color #"+cr+" "+colranges.get(cr)+";"); - } - cset.add(new StructureMappingcommandSet(ChimeraCommands.class, - files[pdbfnum], new String[] { coms.toString() })); } - return cset.toArray(new StructureMappingcommandSet[cset.size()]); + return colourMap; } /** - * Helper method to record a range of positions of the same colour. + * Helper method to add one contiguous colour range to the colour map. * - * @param colranges - * @param colour + * @param map + * @param key * @param model * @param startPos * @param endPos * @param chain */ - private static void addColourRange(Map colranges, - Color colour, int model, - int startPos, int endPos, String chain) + protected static void addRange(Map map, + Object key, int model, int startPos, int endPos, String chain) { - String colstring = ((colour.getRed()< 16) ? "0":"")+Integer.toHexString(colour.getRed()) - + ((colour.getGreen()< 16) ? "0":"")+Integer.toHexString(colour.getGreen()) - + ((colour.getBlue()< 16) ? "0":"")+Integer.toHexString(colour.getBlue()); - StringBuilder currange = colranges.get(colstring); - if (currange == null) + /* + * Get/initialize map of data for the colour + */ + AtomSpecModel atomSpec = map.get(key); + if (atomSpec == null) { - colranges.put(colstring, currange = new StringBuilder(256)); + atomSpec = new AtomSpecModel(); + map.put(key, atomSpec); } - if (currange.length() > 0) + + atomSpec.addRange(model, startPos, endPos, chain); + } + + /** + * Constructs and returns a set of Chimera commands to set attributes on + * residues corresponding to features in Jalview + * + * @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, 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()) + { + return theMap; + } + + 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) { - currange.append("|"); + return; } - currange.append("#" + model + ":" + ((startPos==endPos) ? startPos : startPos + "-" - + endPos) + "." + chain); + + for (SequenceFeature sf : sfs) + { + String type = sf.getType(); + if (!visibleFeatures.contains(type)) + { + continue; + } + List mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(), + sf.getEnd()); + + if (!mappedRanges.isEmpty()) + { + AtomSpecModel atomSpec = theMap.get(type); + if (atomSpec == null) + { + atomSpec = new AtomSpecModel(); + theMap.put(type, atomSpec); + } + for (int[] range : mappedRanges) + { + atomSpec.addRange(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 + * + *
+   * 
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,... + *
+ *
+ *

+ * 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 + */ + protected static List buildSetAttributeCommands( + Map featureMap) + { + List commands = new ArrayList(); + for (String featureType : featureMap.keySet()) + { + StringBuilder sb = new StringBuilder(128); + String sanitised = featureType.replace(" ", "_").replace("-", "_"); + sb.append("setattr r jv_").append(sanitised).append(" \" \" "); + sb.append(featureMap.get(featureType).getAtomSpec()); + commands.add(sb.toString()); + } + + return commands; } }