*/
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.StructureSelectionManager;
+import jalview.structure.AtomSpecModel;
import jalview.util.ColorUtils;
-import jalview.util.Comparison;
+import jalview.util.IntRangeComparator;
import java.awt.Color;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
+import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
/**
* Routines for generating ChimeraX commands for Jalview/ChimeraX binding
{
private static final String CMD_COLOUR_BY_CHARGE = "color white;color :ASP,GLU red;color :LYS,ARG blue;color :CYS yellow";
- /**
- * Traverses a map of { modelNumber, {chain, {list of from-to ranges} } } and
- * builds a ChimeraX format atom spec
- *
- * @param modelAndChainRanges
- */
- protected static String getAtomSpec(
- Map<Integer, Map<String, List<int[]>>> modelAndChainRanges)
+ @Override
+ public String colourByCharge()
{
- 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<String, List<int[]>> 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();
+ return CMD_COLOUR_BY_CHARGE;
}
- /**
- * <pre>
- * 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
- * list of start/end ranges
- * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
- * </pre>
- */
- protected static Map<Object, AtomSpecModel> buildColoursMap(
- StructureSelectionManager ssm, String[] files,
- SequenceI[][] sequence, SequenceRenderer sr,
- AlignmentViewPanel viewPanel)
+ @Override
+ public String getResidueSpec(String residue)
{
- FeatureRenderer fr = viewPanel.getFeatureRenderer();
- FeatureColourFinder finder = new FeatureColourFinder(fr);
- AlignViewportI viewport = viewPanel.getAlignViewport();
- HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
- AlignmentI al = viewport.getAlignment();
- Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
- Color lastColour = null;
-
- for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
- {
- StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
-
- if (mapping == null || mapping.length < 1)
- {
- continue;
- }
-
- int startPos = -1, lastPos = -1;
- String lastChain = "";
- for (int s = 0; s < sequence[pdbfnum].length; s++)
- {
- for (int sp, m = 0; m < mapping.length; m++)
- {
- final SequenceI seq = sequence[pdbfnum][s];
- if (mapping[m].getSequence() == seq
- && (sp = al.findIndex(seq)) > -1)
- {
- SequenceI asp = al.getSequenceAt(sp);
- for (int r = 0; r < asp.getLength(); r++)
- {
- // no mapping to gaps in sequence
- if (Comparison.isGap(asp.getCharAt(r)))
- {
- continue;
- }
- int pos = mapping[m].getPDBResNum(asp.findPosition(r));
+ return ":" + residue;
+ }
- if (pos < 1 || pos == lastPos)
- {
- continue;
- }
+ @Override
+ public String setBackgroundColour(Color col)
+ {
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/set.html
+ return "set bgColor " + ColorUtils.toTkCode(col);
+ }
- Color colour = sr.getResidueColour(seq, r, finder);
+ @Override
+ protected String getColourCommand(AtomSpecModel colourData,
+ Color colour)
+ {
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/color.html
+ String colourCode = getColourString(colour);
- /*
- * darker colour for hidden regions
- */
- if (!cs.isVisible(r))
- {
- colour = Color.GRAY;
- }
+ return "color " + getAtomSpec(colourData, false) + " " + colourCode;
+ }
- final String chain = mapping[m].getChain();
+ @Override
+ public String focusView()
+ {
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/view.html
+ return "view";
+ }
- /*
- * 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 (startPos != -1)
- {
- addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos,
- lastPos, lastChain);
- }
- startPos = pos;
- }
- lastColour = colour;
- lastPos = pos;
- lastChain = chain;
- }
- // final colour range
- if (lastColour != null)
- {
- addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos,
- lastPos, lastChain);
- }
- // break;
- }
- }
- }
- }
- return colourMap;
+ /**
+ * {@inheritDoc}
+ *
+ * @return
+ */
+ @Override
+ public int getModelStartNo()
+ {
+ return 1;
}
/**
+ * Returns a viewer command to set the given residue attribute value on
+ * residues specified by the AtomSpecModel, for example
+ *
* <pre>
- * Helper method to build a map of
- * { featureType, { feature value, AtomSpecModel } }
+ * setattr #0/A:3-9,14-20,39-43 res jv_strand 'strand' create true
* </pre>
*
- * @param ssm
- * @param files
- * @param seqs
- * @param viewPanel
+ * @param attributeName
+ * @param attributeValue
+ * @param atomSpecModel
* @return
*/
- protected static Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
- StructureSelectionManager ssm, String[] files, SequenceI[][] seqs,
- AlignmentViewPanel viewPanel)
+ @Override
+ protected String getSetAttributeCommand(String attributeName,
+ String attributeValue, AtomSpecModel atomSpecModel)
{
- Map<String, Map<Object, AtomSpecModel>> theMap = new LinkedHashMap<>();
-
- FeatureRenderer fr = viewPanel.getFeatureRenderer();
- if (fr == null)
- {
- return theMap;
- }
-
- AlignViewportI viewport = viewPanel.getAlignViewport();
- List<String> visibleFeatures = fr.getDisplayedFeatureTypes();
-
- /*
- * if alignment is showing features from complement, we also transfer
- * these features to the corresponding mapped structure residues
- */
- boolean showLinkedFeatures = viewport.isShowComplementFeatures();
- List<String> 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]);
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("setattr ").append(getAtomSpec(atomSpecModel, false));
+ sb.append(" res ").append(attributeName).append(" '")
+ .append(attributeValue).append("'");
+ sb.append(" create true");
+ return sb.toString();
+ }
- if (mapping == null || mapping.length < 1)
- {
- continue;
- }
+ @Override
+ public String openCommandFile(String path)
+ {
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/open.html
+ return "open " + path;
+ }
- 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);
- StructureMapping structureMapping = mapping[m];
- if (structureMapping.getSequence() == seq && sp > -1)
- {
- /*
- * found a sequence with a mapping to a structure;
- * now scan its features
- */
- if (!visibleFeatures.isEmpty())
- {
- scanSequenceFeatures(visibleFeatures, structureMapping, seq,
- theMap, pdbfnum);
- }
- if (showLinkedFeatures)
- {
- scanComplementFeatures(complementRenderer, structureMapping,
- seq, theMap, pdbfnum);
- }
- }
- }
- }
- }
- return theMap;
+ @Override
+ public String saveSession(String filepath)
+ {
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/save.html
+ return "save session " + filepath;
}
/**
- * Scans visible features in mapped positions of the CDS/peptide complement, and
- * adds any found to the map of attribute values/structure positions
+ * Returns the range(s) formatted as a ChimeraX atomspec, for example
+ * <p>
+ * #1/A:2-20,30-40/B:10-20|#2/A:12-30
*
- * @param complementRenderer
- * @param structureMapping
- * @param seq
- * @param theMap
- * @param modelNumber
+ * @return
*/
- protected static void scanComplementFeatures(
- FeatureRenderer complementRenderer,
- StructureMapping structureMapping, SequenceI seq,
- Map<String, Map<Object, AtomSpecModel>> theMap, int modelNumber)
+ @Override
+ public String getAtomSpec(AtomSpecModel atomSpec, boolean alphaOnly)
{
- /*
- * for each sequence residue mapped to a structure position...
- */
- for (int seqPos : structureMapping.getMapping().keySet())
+ StringBuilder sb = new StringBuilder(128);
+ boolean firstModel = true;
+ for (Integer model : atomSpec.getModels())
{
- /*
- * find visible complementary features at mapped position(s)
- */
- MappedFeatures mf = complementRenderer
- .findComplementFeaturesAtResidue(seq, seqPos);
- if (mf != null)
+ if (!firstModel)
{
- 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<int[]> 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<Object, AtomSpecModel> 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());
- }
- }
- }
+ sb.append("|");
+ }
+ firstModel = false;
+ appendModel(sb, model, atomSpec);
+ if (alphaOnly)
+ {
+ sb.append("@CA|P");
}
+ // todo: is there ChimeraX syntax to exclude altlocs?
}
+ return sb.toString();
}
/**
- * 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.
+ * A helper method to append an atomSpec string for atoms in the given model
*
- * @param visibleFeatures
- * @param mapping
- * @param seq
- * @param theMap
- * @param modelNumber
+ * @param sb
+ * @param model
+ * @param atomSpec
*/
- protected static void scanSequenceFeatures(List<String> visibleFeatures,
- StructureMapping mapping, SequenceI seq,
- Map<String, Map<Object, AtomSpecModel>> theMap, int modelNumber)
+ protected void appendModel(StringBuilder sb, Integer model,
+ AtomSpecModel atomSpec)
{
- List<SequenceFeature> sfs = seq.getFeatures().getPositionalFeatures(
- visibleFeatures.toArray(new String[visibleFeatures.size()]));
- for (SequenceFeature sf : sfs)
+ sb.append("#").append(model);
+
+ for (String chain : atomSpec.getChains(model))
{
- String type = sf.getType();
+ boolean firstPositionForChain = true;
+ chain = " ".equals(chain) ? chain : chain.trim();
+ sb.append("/").append(chain).append(":");
+ List<int[]> rangeList = atomSpec.getRanges(model, chain);
/*
- * Don't copy features which originated from Chimera
+ * sort ranges into ascending start position order
*/
- if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
- .equals(sf.getFeatureGroup()))
- {
- continue;
- }
+ Collections.sort(rangeList, IntRangeComparator.ASCENDING);
- List<int[]> mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(),
- sf.getEnd());
+ int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
+ int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
- if (!mappedRanges.isEmpty())
+ Iterator<int[]> iterator = rangeList.iterator();
+ while (iterator.hasNext())
{
- String value = sf.getDescription();
- if (value == null || value.length() == 0)
+ int[] range = iterator.next();
+ if (range[0] <= end + 1)
{
- value = type;
- }
- float score = sf.getScore();
- if (score != 0f && !Float.isNaN(score))
- {
- value = Float.toString(score);
- }
- Map<Object, AtomSpecModel> featureValues = theMap.get(type);
- if (featureValues == null)
- {
- featureValues = new HashMap<>();
- theMap.put(type, featureValues);
+ /*
+ * range overlaps or is contiguous with the last one
+ * - so just extend the end position, and carry on
+ * (unless this is the last in the list)
+ */
+ end = Math.max(end, range[1]);
}
- for (int[] range : mappedRanges)
+ else
{
- addAtomSpecRange(featureValues, value, modelNumber, range[0],
- range[1], mapping.getChain());
+ /*
+ * we have a break so append the last range
+ */
+ appendRange(sb, start, end, chain, firstPositionForChain, true);
+ start = range[0];
+ end = range[1];
+ firstPositionForChain = false;
}
}
- }
- }
- /**
- * 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
- *
- * <pre>
- * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
- * </pre>
- */
- protected static String makeAttributeName(String featureType)
- {
- StringBuilder sb = new StringBuilder();
- if (featureType != null)
- {
- for (char c : featureType.toCharArray())
+ /*
+ * and append the last range
+ */
+ if (!rangeList.isEmpty())
{
- sb.append(Character.isLetterOrDigit(c) ? c : '_');
+ appendRange(sb, start, end, chain, firstPositionForChain, true);
}
+ firstPositionForChain = false;
}
- String attName = NAMESPACE_PREFIX + sb.toString();
-
- /*
- * Chimera treats an attribute name ending in 'color' as colour-valued;
- * Jalview doesn't, so prevent this by appending an underscore
- */
- if (attName.toUpperCase().endsWith("COLOR"))
- {
- attName += "_";
- }
-
- return attName;
}
@Override
- public String colourByCharge()
+ public String showBackbone()
{
- return CMD_COLOUR_BY_CHARGE;
+ return "~display all;show @CA|P pbonds";
}
@Override
- public String colourByResidues(Map<String, Color> colours)
+ public String superposeStructures(AtomSpecModel spec, AtomSpecModel ref)
{
- StringBuilder cmd = new StringBuilder(12 * colours.size());
-
/*
- * concatenate commands like
- * color :VAL #4949b6
+ * Form ChimeraX match command to match spec to ref
+ *
+ * match #1/A:2-94 toAtoms #2/A:1-93
+ *
+ * @see
+ * https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/midas/match.html
*/
- for (Entry<String, Color> entry : colours.entrySet())
- {
- String colorSpec = ColorUtils.toTkCode(entry.getValue());
- String resCode = entry.getKey();
- cmd.append("color :").append(resCode).append(" ").append(colorSpec)
- .append(CMD_SEPARATOR);
- }
- return cmd.toString();
- }
-
- @Override
- public String setBackgroundColour(Color col)
- {
- return "set bgColor " + ColorUtils.toTkCode(col);
- }
-
- @Override
- protected String getColourCommand(AtomSpecModel colourData,
- String colourCode)
- {
- return "color " + colourData.getAtomSpecX() + " " + colourCode;
- }
-
- @Override
- public String focusView()
- {
- return "view";
- }
+ StringBuilder cmd = new StringBuilder();
+ String atomSpec = getAtomSpec(spec, true);
+ String refSpec = getAtomSpec(ref, true);
+ cmd.append("align ").append(atomSpec).append(" toAtoms ")
+ .append(refSpec);
- /**
- * {@inheritDoc}
- *
- * @return
- */
- protected static int getModelStartNo()
- {
- return 1;
- }
+ /*
+ * show superposed residues as ribbon, others as chain
+ */
+ cmd.append("; ribbon ");
+ cmd.append(getAtomSpec(spec, false)).append("|");
+ cmd.append(getAtomSpec(ref, false)).append("; view");
- /**
- * Returns a viewer command to set the given residue attribute value on
- * residues specified by the AtomSpecModel, for example
- *
- * <pre>
- * setattr #0/A:3-9,14-20,39-43 res jv_strand 'strand' create true
- * </pre>
- *
- * @param attributeName
- * @param attributeValue
- * @param atomSpecModel
- * @return
- */
- @Override
- protected String getSetAttributeCommand(String attributeName,
- String attributeValue, AtomSpecModel atomSpecModel)
- {
- StringBuilder sb = new StringBuilder(128);
- sb.append("setattr ").append(atomSpecModel.getAtomSpecX());
- sb.append(" res ").append(attributeName).append(" '")
- .append(attributeValue).append("'");
- sb.append(" create true");
- return sb.toString();
+ return cmd.toString();
}
}