2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.ext.rbvi.chimera;
23 import jalview.api.FeatureRenderer;
24 import jalview.api.SequenceRenderer;
25 import jalview.datamodel.AlignmentI;
26 import jalview.datamodel.SequenceFeature;
27 import jalview.datamodel.SequenceI;
28 import jalview.gui.StructureViewerBase;
29 import jalview.structure.StructureMapping;
30 import jalview.structure.StructureMappingcommandSet;
31 import jalview.structure.StructureSelectionManager;
32 import jalview.util.ColorUtils;
33 import jalview.util.Comparison;
35 import java.awt.Color;
36 import java.util.ArrayList;
37 import java.util.HashMap;
38 import java.util.LinkedHashMap;
39 import java.util.List;
43 * Routines for generating Chimera commands for Jalview/Chimera binding
48 public class ChimeraCommands
51 * Constructs Chimera commands to colour residues as per the Jalview alignment
61 public static StructureMappingcommandSet getColourBySequenceCommand(
62 StructureSelectionManager ssm, String[] files,
63 SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
66 Map<Object, AtomSpecModel> colourMap = buildColoursMap(
67 ssm, files, sequence, sr, fr, alignment);
69 List<String> colourCommands = buildColourCommands(colourMap);
71 StructureMappingcommandSet cs = new StructureMappingcommandSet(
72 ChimeraCommands.class, null,
73 colourCommands.toArray(new String[colourCommands.size()]));
79 * Traverse the map of colours/models/chains/positions to construct a list of
80 * 'color' commands (one per distinct colour used). The format of each command
85 * color colorname #modelnumber:range.chain
86 * e.g. color #00ff00 #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
93 protected static List<String> buildColourCommands(
94 Map<Object, AtomSpecModel> colourMap)
97 * This version concatenates all commands into a single String (semi-colon
98 * delimited). If length limit issues arise, refactor to return one color
101 List<String> commands = new ArrayList<String>();
102 StringBuilder sb = new StringBuilder(256);
103 boolean firstColour = true;
104 for (Object key : colourMap.keySet())
106 Color colour = (Color) key;
107 String colourCode = ColorUtils.toTkCode(colour);
112 sb.append("color ").append(colourCode).append(" ");
114 final AtomSpecModel colourData = colourMap
116 sb.append(colourData.getAtomSpec());
118 commands.add(sb.toString());
123 * Traverses a map of { modelNumber, {chain, {list of from-to ranges} } } and
124 * builds a Chimera format atom spec
126 * @param modelAndChainRanges
128 protected static String getAtomSpec(
129 Map<Integer, Map<String, List<int[]>>> modelAndChainRanges)
131 StringBuilder sb = new StringBuilder(128);
132 boolean firstModelForColour = true;
133 for (Integer model : modelAndChainRanges.keySet())
135 boolean firstPositionForModel = true;
136 if (!firstModelForColour)
140 firstModelForColour = false;
141 sb.append("#").append(model).append(":");
143 final Map<String, List<int[]>> modelData = modelAndChainRanges
145 for (String chain : modelData.keySet())
147 boolean hasChain = !"".equals(chain.trim());
148 for (int[] range : modelData.get(chain))
150 if (!firstPositionForModel)
154 if (range[0] == range[1])
160 sb.append(range[0]).append("-").append(range[1]);
164 sb.append(".").append(chain);
166 firstPositionForModel = false;
170 return sb.toString();
175 * Build a data structure which maps contiguous subsequences for each colour.
176 * This generates a data structure from which we can easily generate the
177 * Chimera command for colour by sequence.
181 * list of start/end ranges
182 * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
185 protected static Map<Object, AtomSpecModel> buildColoursMap(
186 StructureSelectionManager ssm, String[] files,
187 SequenceI[][] sequence, SequenceRenderer sr, FeatureRenderer fr,
188 AlignmentI alignment)
190 Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<Object, AtomSpecModel>();
191 Color lastColour = null;
192 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
194 StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
196 if (mapping == null || mapping.length < 1)
201 int startPos = -1, lastPos = -1;
202 String lastChain = "";
203 for (int s = 0; s < sequence[pdbfnum].length; s++)
205 for (int sp, m = 0; m < mapping.length; m++)
207 final SequenceI seq = sequence[pdbfnum][s];
208 if (mapping[m].getSequence() == seq
209 && (sp = alignment.findIndex(seq)) > -1)
211 SequenceI asp = alignment.getSequenceAt(sp);
212 for (int r = 0; r < asp.getLength(); r++)
214 // no mapping to gaps in sequence
215 if (Comparison.isGap(asp.getCharAt(r)))
219 int pos = mapping[m].getPDBResNum(asp.findPosition(r));
221 if (pos < 1 || pos == lastPos)
226 Color colour = sr.getResidueColour(seq, r, fr);
227 final String chain = mapping[m].getChain();
230 * Just keep incrementing the end position for this colour range
231 * _unless_ colour, PDB model or chain has changed, or there is a
232 * gap in the mapped residue sequence
234 final boolean newColour = !colour.equals(lastColour);
235 final boolean nonContig = lastPos + 1 != pos;
236 final boolean newChain = !chain.equals(lastChain);
237 if (newColour || nonContig || newChain)
241 addRange(colourMap, lastColour, pdbfnum, startPos,
250 // final colour range
251 if (lastColour != null)
253 addRange(colourMap, lastColour, pdbfnum, startPos,
265 * Helper method to add one contiguous colour range to the colour map.
274 protected static void addRange(Map<Object, AtomSpecModel> map,
275 Object key, int model, int startPos, int endPos, String chain)
278 * Get/initialize map of data for the colour
280 AtomSpecModel atomSpec = map.get(key);
281 if (atomSpec == null)
283 atomSpec = new AtomSpecModel();
284 map.put(key, atomSpec);
287 atomSpec.addRange(model, startPos, endPos, chain);
291 * Constructs and returns Chimera commands to set attributes on residues
292 * corresponding to features in Jalview. Attribute names are the Jalview
293 * feature type, with a "jv_" prefix.
302 public static StructureMappingcommandSet getSetAttributeCommandsForFeatures(
303 StructureSelectionManager ssm, String[] files,
304 SequenceI[][] seqs, FeatureRenderer fr, AlignmentI alignment)
306 Map<String, Map<Object, AtomSpecModel>> featureMap = buildFeaturesMap(
307 ssm, files, seqs, fr, alignment);
309 List<String> commands = buildSetAttributeCommands(featureMap);
311 StructureMappingcommandSet cs = new StructureMappingcommandSet(
312 ChimeraCommands.class, null,
313 commands.toArray(new String[commands.size()]));
320 * Helper method to build a map of
321 * { featureType, { feature value, AtomSpecModel } }
331 protected static Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
332 StructureSelectionManager ssm, String[] files,
333 SequenceI[][] seqs, FeatureRenderer fr, AlignmentI alignment)
335 Map<String, Map<Object, AtomSpecModel>> theMap = new LinkedHashMap<String, Map<Object, AtomSpecModel>>();
337 List<String> visibleFeatures = fr.getDisplayedFeatureTypes();
338 if (visibleFeatures.isEmpty())
343 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
345 StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
347 if (mapping == null || mapping.length < 1)
352 for (int seqNo = 0; seqNo < seqs[pdbfnum].length; seqNo++)
354 for (int m = 0; m < mapping.length; m++)
356 final SequenceI seq = seqs[pdbfnum][seqNo];
357 int sp = alignment.findIndex(seq);
358 if (mapping[m].getSequence() == seq && sp > -1)
361 * found a sequence with a mapping to a structure;
362 * now scan its features
364 SequenceI asp = alignment.getSequenceAt(sp);
366 scanSequenceFeatures(visibleFeatures, mapping[m], asp, theMap,
376 * Inspect features on the sequence; for each feature that is visible,
377 * determine its mapped ranges in the structure (if any) according to the
378 * given mapping, and add them to the map
380 * @param visibleFeatures
386 protected static void scanSequenceFeatures(List<String> visibleFeatures,
387 StructureMapping mapping, SequenceI seq,
388 Map<String, Map<Object, AtomSpecModel>> theMap, int modelNumber)
390 SequenceFeature[] sfs = seq.getSequenceFeatures();
396 for (SequenceFeature sf : sfs)
398 String type = sf.getType();
401 * Only copy visible features, don't copy any which originated
402 * from Chimera, and suppress uninteresting ones (e.g. RESNUM)
404 boolean isFromViewer = JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
405 .equals(sf.getFeatureGroup());
406 if (isFromViewer || !visibleFeatures.contains(type))
410 List<int[]> mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(),
413 if (!mappedRanges.isEmpty())
415 String value = sf.getDescription();
416 if (value == null || value.length() == 0)
420 float score = sf.getScore();
421 if (score != 0f && !Float.isNaN(score))
423 value = Float.toString(score);
425 Map<Object, AtomSpecModel> featureValues = theMap.get(type);
426 if (featureValues == null)
428 featureValues = new HashMap<Object, AtomSpecModel>();
429 theMap.put(type, featureValues);
431 for (int[] range : mappedRanges)
433 addRange(featureValues, value, modelNumber, range[0], range[1],
441 * Traverse the map of features/values/models/chains/positions to construct a
442 * list of 'setattr' commands (one per distinct feature type and value).
444 * The format of each command is
447 * <blockquote> setattr r <featureName> " " #modelnumber:range.chain
448 * e.g. setattr r jv:chain <value> #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
455 protected static List<String> buildSetAttributeCommands(
456 Map<String, Map<Object, AtomSpecModel>> featureMap)
458 List<String> commands = new ArrayList<String>();
459 for (String featureType : featureMap.keySet())
461 String attributeName = makeAttributeName(featureType);
464 * clear down existing attributes for this feature
466 // 'problem' - sets attribute to None on all residues - overkill?
467 // commands.add("~setattr r " + attributeName + " :*");
469 Map<Object, AtomSpecModel> values = featureMap.get(featureType);
470 for (Object value : values.keySet())
473 * for each distinct value recorded for this feature type,
474 * add a command to set the attribute on the mapped residues
476 StringBuilder sb = new StringBuilder(128);
477 sb.append("setattr r ").append(attributeName).append(" \"")
478 .append(value.toString()).append("\" ");
479 sb.append(values.get(value).getAtomSpec());
480 commands.add(sb.toString());
488 * Makes a prefixed and valid Chimera attribute name. A jv_ prefix is applied
489 * for a 'Jalview' namespace, and any non-alphanumeric character is converted
494 * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
497 protected static String makeAttributeName(String featureType)
499 StringBuilder sb = new StringBuilder();
500 if (featureType != null)
502 for (char c : featureType.toCharArray())
504 sb.append(Character.isLetterOrDigit(c) ? c : '_');
507 String attName = StructureViewerBase.NAMESPACE_PREFIX + sb.toString();
510 * Chimera treats an attribute name ending in 'color' as colour-valued;
511 * Jalview doesn't, so prevent this by appending an underscore
513 if (attName.toUpperCase().endsWith("COLOR"))