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.AlignViewportI;
24 import jalview.api.AlignmentViewPanel;
25 import jalview.api.FeatureRenderer;
26 import jalview.datamodel.AlignmentI;
27 import jalview.datamodel.MappedFeatures;
28 import jalview.datamodel.SequenceFeature;
29 import jalview.datamodel.SequenceI;
30 import jalview.gui.Desktop;
31 import jalview.structure.StructureMapping;
32 import jalview.structure.StructureMappingcommandSet;
33 import jalview.structure.StructureSelectionManager;
34 import jalview.structures.models.AAStructureBindingModel;
35 import jalview.util.ColorUtils;
36 import jalview.util.StructureCommands;
38 import java.awt.Color;
39 import java.util.ArrayList;
40 import java.util.HashMap;
41 import java.util.LinkedHashMap;
42 import java.util.List;
46 * Routines for generating Chimera commands for Jalview/Chimera binding
51 public class ChimeraCommands extends StructureCommands
53 public static final String NAMESPACE_PREFIX = "jv_";
56 * colour for residues shown in structure but hidden in alignment
58 private static final String COLOR_GRAY_HEX = "color "
59 + ColorUtils.toTkCode(Color.GRAY);
62 * Constructs Chimera commands to colour residues as per the Jalview alignment
68 public static String[] getColourBySequenceCommand(
69 Map<Object, AtomSpecModel> colourMap,
70 AAStructureBindingModel binding)
72 List<String> colourCommands = buildColourCommands(colourMap, binding);
74 return colourCommands.toArray(new String[colourCommands.size()]);
78 * Traverse the map of colours/models/chains/positions to construct a list of
79 * 'color' commands (one per distinct colour used). The format of each command
84 * color colorname #modelnumber:range.chain
85 * 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,
95 AAStructureBindingModel binding)
98 * This version concatenates all commands into a single String (semi-colon
99 * delimited). If length limit issues arise, refactor to return one color
100 * command per colour.
102 List<String> commands = new ArrayList<>();
103 StringBuilder sb = new StringBuilder(256);
104 sb.append(COLOR_GRAY_HEX);
106 for (Object key : colourMap.keySet())
108 Color colour = (Color) key;
109 String colourCode = ColorUtils.toTkCode(colour);
111 sb.append("color ").append(colourCode).append(" ");
112 final AtomSpecModel colourData = colourMap.get(colour);
113 sb.append(getAtomSpec(colourData, binding));
115 commands.add(sb.toString());
120 * Constructs and returns Chimera commands to set attributes on residues
121 * corresponding to features in Jalview. Attribute names are the Jalview feature
122 * type, with a "jv_" prefix.
131 public static StructureMappingcommandSet getSetAttributeCommandsForFeatures(
132 AlignmentViewPanel viewPanel, AAStructureBindingModel binding)
134 StructureSelectionManager ssm = binding.getSsm();
135 String[] files = binding.getStructureFiles();
136 SequenceI[][] seqs = binding.getSequence();
138 Map<String, Map<Object, AtomSpecModel>> featureMap = buildFeaturesMap(
139 ssm, files, seqs, viewPanel);
141 List<String> commands = buildSetAttributeCommands(featureMap, binding);
143 StructureMappingcommandSet cs = new StructureMappingcommandSet(
144 ChimeraCommands.class, null,
145 commands.toArray(new String[commands.size()]));
152 * Helper method to build a map of
153 * { featureType, { feature value, AtomSpecModel } }
162 protected static Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
163 StructureSelectionManager ssm, String[] files, SequenceI[][] seqs,
164 AlignmentViewPanel viewPanel)
166 Map<String, Map<Object, AtomSpecModel>> theMap = new LinkedHashMap<>();
168 FeatureRenderer fr = viewPanel.getFeatureRenderer();
174 AlignViewportI viewport = viewPanel.getAlignViewport();
175 List<String> visibleFeatures = fr.getDisplayedFeatureTypes();
178 * if alignment is showing features from complement, we also transfer
179 * these features to the corresponding mapped structure residues
181 boolean showLinkedFeatures = viewport.isShowComplementFeatures();
182 List<String> complementFeatures = new ArrayList<>();
183 FeatureRenderer complementRenderer = null;
184 if (showLinkedFeatures)
186 AlignViewportI comp = fr.getViewport().getCodingComplement();
189 complementRenderer = Desktop.getAlignFrameFor(comp)
190 .getFeatureRenderer();
191 complementFeatures = complementRenderer.getDisplayedFeatureTypes();
194 if (visibleFeatures.isEmpty() && complementFeatures.isEmpty())
199 AlignmentI alignment = viewPanel.getAlignment();
200 for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
202 StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
204 if (mapping == null || mapping.length < 1)
209 for (int seqNo = 0; seqNo < seqs[pdbfnum].length; seqNo++)
211 for (int m = 0; m < mapping.length; m++)
213 final SequenceI seq = seqs[pdbfnum][seqNo];
214 int sp = alignment.findIndex(seq);
215 StructureMapping structureMapping = mapping[m];
216 if (structureMapping.getSequence() == seq && sp > -1)
219 * found a sequence with a mapping to a structure;
220 * now scan its features
222 if (!visibleFeatures.isEmpty())
224 scanSequenceFeatures(visibleFeatures, structureMapping, seq,
227 if (showLinkedFeatures)
229 scanComplementFeatures(complementRenderer, structureMapping,
230 seq, theMap, pdbfnum);
240 * Scans visible features in mapped positions of the CDS/peptide complement, and
241 * adds any found to the map of attribute values/structure positions
243 * @param complementRenderer
244 * @param structureMapping
249 protected static void scanComplementFeatures(
250 FeatureRenderer complementRenderer,
251 StructureMapping structureMapping, SequenceI seq,
252 Map<String, Map<Object, AtomSpecModel>> theMap, int modelNumber)
255 * for each sequence residue mapped to a structure position...
257 for (int seqPos : structureMapping.getMapping().keySet())
260 * find visible complementary features at mapped position(s)
262 MappedFeatures mf = complementRenderer
263 .findComplementFeaturesAtResidue(seq, seqPos);
266 for (SequenceFeature sf : mf.features)
268 String type = sf.getType();
271 * Don't copy features which originated from Chimera
273 if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
274 .equals(sf.getFeatureGroup()))
280 * record feature 'value' (score/description/type) as at the
281 * corresponding structure position
283 List<int[]> mappedRanges = structureMapping
284 .getPDBResNumRanges(seqPos, seqPos);
286 if (!mappedRanges.isEmpty())
288 String value = sf.getDescription();
289 if (value == null || value.length() == 0)
293 float score = sf.getScore();
294 if (score != 0f && !Float.isNaN(score))
296 value = Float.toString(score);
298 Map<Object, AtomSpecModel> featureValues = theMap.get(type);
299 if (featureValues == null)
301 featureValues = new HashMap<>();
302 theMap.put(type, featureValues);
304 for (int[] range : mappedRanges)
306 addAtomSpecRange(featureValues, value, modelNumber, range[0],
307 range[1], structureMapping.getChain());
316 * Inspect features on the sequence; for each feature that is visible, determine
317 * its mapped ranges in the structure (if any) according to the given mapping,
318 * and add them to the map.
320 * @param visibleFeatures
326 protected static void scanSequenceFeatures(List<String> visibleFeatures,
327 StructureMapping mapping, SequenceI seq,
328 Map<String, Map<Object, AtomSpecModel>> theMap, int modelNumber)
330 List<SequenceFeature> sfs = seq.getFeatures().getPositionalFeatures(
331 visibleFeatures.toArray(new String[visibleFeatures.size()]));
332 for (SequenceFeature sf : sfs)
334 String type = sf.getType();
337 * Don't copy features which originated from Chimera
339 if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
340 .equals(sf.getFeatureGroup()))
345 List<int[]> mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(),
348 if (!mappedRanges.isEmpty())
350 String value = sf.getDescription();
351 if (value == null || value.length() == 0)
355 float score = sf.getScore();
356 if (score != 0f && !Float.isNaN(score))
358 value = Float.toString(score);
360 Map<Object, AtomSpecModel> featureValues = theMap.get(type);
361 if (featureValues == null)
363 featureValues = new HashMap<>();
364 theMap.put(type, featureValues);
366 for (int[] range : mappedRanges)
368 addAtomSpecRange(featureValues, value, modelNumber, range[0],
369 range[1], mapping.getChain());
376 * Traverse the map of features/values/models/chains/positions to construct a
377 * list of 'setattr' commands (one per distinct feature type and value).
379 * The format of each command is
382 * <blockquote> setattr r <featureName> " " #modelnumber:range.chain
383 * e.g. setattr r jv:chain <value> #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
391 protected static List<String> buildSetAttributeCommands(
392 Map<String, Map<Object, AtomSpecModel>> featureMap,
393 AAStructureBindingModel binding)
395 List<String> commands = new ArrayList<>();
396 for (String featureType : featureMap.keySet())
398 String attributeName = makeAttributeName(featureType);
401 * clear down existing attributes for this feature
403 // 'problem' - sets attribute to None on all residues - overkill?
404 // commands.add("~setattr r " + attributeName + " :*");
406 Map<Object, AtomSpecModel> values = featureMap.get(featureType);
407 for (Object value : values.keySet())
410 * for each distinct value recorded for this feature type,
411 * add a command to set the attribute on the mapped residues
412 * Put values in single quotes, encoding any embedded single quotes
414 StringBuilder sb = new StringBuilder(128);
415 String featureValue = value.toString();
416 featureValue = featureValue.replaceAll("\\'", "'");
417 sb.append("setattr r ").append(attributeName).append(" '")
418 .append(featureValue).append("' ");
419 sb.append(getAtomSpec(values.get(value), binding));
420 commands.add(sb.toString());
428 * Makes a prefixed and valid Chimera attribute name. A jv_ prefix is applied
429 * for a 'Jalview' namespace, and any non-alphanumeric character is converted
436 * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
439 protected static String makeAttributeName(String featureType)
441 StringBuilder sb = new StringBuilder();
442 if (featureType != null)
444 for (char c : featureType.toCharArray())
446 sb.append(Character.isLetterOrDigit(c) ? c : '_');
449 String attName = NAMESPACE_PREFIX + sb.toString();
452 * Chimera treats an attribute name ending in 'color' as colour-valued;
453 * Jalview doesn't, so prevent this by appending an underscore
455 if (attName.toUpperCase().endsWith("COLOR"))
464 * Returns the range(s) formatted as a Chimera atomspec
468 public static String getAtomSpec(AtomSpecModel atomSpec,
469 AAStructureBindingModel binding)
471 StringBuilder sb = new StringBuilder(128);
472 boolean firstModel = true;
473 for (Integer model : atomSpec.getModels())
480 sb.append(binding.getModelSpec(model)).append(":");
482 boolean firstPositionForModel = true;
484 for (String chain : atomSpec.getChains(model))
486 chain = " ".equals(chain) ? chain : chain.trim();
488 List<int[]> rangeList = atomSpec.getRanges(model, chain);
490 String chainToken = " ".equals(chain) ? "." : "." + chain;
491 appendResidueRange(sb, rangeList, chainToken,
492 firstPositionForModel);
493 firstPositionForModel = false;
496 return sb.toString();
500 * Chimera atomspec requires chain to be specified for each start-end residue
501 * range, otherwise it will apply to all chains
506 protected static void appendChainToRange(StringBuilder sb, String chain)
509 if (!" ".equals(chain))