*/
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.util.ColorUtils;
-import jalview.util.Comparison;
-
import java.awt.Color;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
+import java.util.Arrays;
import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
+
+import jalview.structure.AtomSpecModel;
+import jalview.structure.StructureCommand;
+import jalview.structure.StructureCommandI;
+import jalview.structure.StructureCommandsI.AtomSpecType;
/**
* Routines for generating ChimeraX commands for Jalview/ChimeraX binding
*/
public class ChimeraXCommands extends ChimeraCommands
{
- private static final String CMD_COLOUR_BY_CHARGE = "color white;color :ASP,GLU red;color :LYS,ARG blue;color :CYS yellow";
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/info.html#resattr
+ private static final StructureCommand LIST_RESIDUE_ATTRIBUTES = new StructureCommand(
+ "info resattr");
+
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/exit.html
+ private static final StructureCommand CLOSE_CHIMERAX = new StructureCommand(
+ "exit");
+
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/info.html#notify
+ private static final StructureCommand STOP_NOTIFY_SELECTION = new StructureCommand(
+ "info notify stop selection jalview");
+
+ private static final StructureCommand STOP_NOTIFY_MODELS = new StructureCommand(
+ "info notify stop models jalview");
+
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/info.html#selection
+ private static final StructureCommand GET_SELECTION = new StructureCommand(
+ "info selection level residue");
+
+ private static final StructureCommand SHOW_BACKBONE = new StructureCommand(
+ "~display all;~ribbon;show @CA|P atoms");
+
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/view.html
+ private static final StructureCommand FOCUS_VIEW = new StructureCommand(
+ "view");
+
+ private static final StructureCommandI COLOUR_BY_CHARGE = new StructureCommand(
+ "color white;color :ASP,GLU red;color :LYS,ARG blue;color :CYS yellow");
+
+ @Override
+ public List<StructureCommandI> colourByCharge()
+ {
+ return Arrays.asList(COLOUR_BY_CHARGE);
+ }
+
+ @Override
+ public String getResidueSpec(String residue)
+ {
+ return ":" + residue;
+ }
+
+ @Override
+ public StructureCommandI colourResidues(String atomSpec, Color colour)
+ {
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/color.html
+ String colourCode = getColourString(colour);
+
+ return new StructureCommand("color " + atomSpec + " " + colourCode);
+ }
+
+ @Override
+ public StructureCommandI focusView()
+ {
+ return FOCUS_VIEW;
+ }
/**
- * Traverses a map of { modelNumber, {chain, {list of from-to ranges} } } and
- * builds a ChimeraX format atom spec
+ * {@inheritDoc}
*
- * @param modelAndChainRanges
+ * @return
*/
- protected static String getAtomSpec(
- Map<Integer, Map<String, List<int[]>>> modelAndChainRanges)
+ @Override
+ public int getModelStartNo()
{
- 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 1;
}
/**
+ * Returns a viewer command to set the given residue attribute value on
+ * residues specified by the AtomSpecModel, for example
+ *
* <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)
+ * setattr #0/A:3-9,14-20,39-43 res jv_strand 'strand' create true
* </pre>
+ *
+ * @param attributeName
+ * @param attributeValue
+ * @param atomSpecModel
+ * @return
*/
- protected static Map<Object, AtomSpecModel> buildColoursMap(
- StructureSelectionManager ssm, String[] files,
- SequenceI[][] sequence, SequenceRenderer sr,
- AlignmentViewPanel viewPanel)
+ @Override
+ protected StructureCommandI setAttribute(String attributeName,
+ String attributeValue, AtomSpecModel atomSpecModel)
{
- 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]);
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("setattr ")
+ .append(getAtomSpec(atomSpecModel, AtomSpecType.RESIDUE_ONLY));
+ sb.append(" res ").append(attributeName).append(" '")
+ .append(attributeValue).append("'");
+ sb.append(" create true");
+ return new StructureCommand(sb.toString());
+ }
- if (mapping == null || mapping.length < 1)
- {
- continue;
- }
+ @Override
+ public StructureCommandI openCommandFile(String path)
+ {
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/open.html
+ return new StructureCommand("open " + path);
+ }
- 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));
-
- if (pos < 1 || pos == lastPos)
- {
- continue;
- }
-
- Color colour = sr.getResidueColour(seq, r, finder);
-
- /*
- * darker colour for hidden regions
- */
- if (!cs.isVisible(r))
- {
- colour = Color.GRAY;
- }
-
- final String chain = mapping[m].getChain();
-
- /*
- * 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;
+ @Override
+ public StructureCommandI saveSession(String filepath)
+ {
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/save.html
+ // note ChimeraX will append ".cxs" to the filepath!
+ return new StructureCommand("save " + filepath + " format session");
}
/**
- * <pre>
- * Helper method to build a map of
- * { featureType, { feature value, AtomSpecModel } }
- * </pre>
+ * 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
+ * <p>
+ * Note there is no need to explicitly exclude ALTLOC atoms when
+ * {@code alphaOnly == true}, as this is the default behaviour of ChimeraX (a
+ * change from Chimera)
*
- * @param ssm
- * @param files
- * @param seqs
- * @param viewPanel
* @return
*/
- protected static Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
- StructureSelectionManager ssm, String[] files, SequenceI[][] seqs,
- AlignmentViewPanel viewPanel)
+ @Override
+ public String getAtomSpec(AtomSpecModel atomSpec, AtomSpecType specType)
{
- 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)
+ StringBuilder sb = new StringBuilder(128);
+ boolean firstModel = true;
+ for (String model : atomSpec.getModels())
{
- AlignViewportI comp = fr.getViewport().getCodingComplement();
- if (comp != null)
+ if (!firstModel)
{
- complementRenderer = Desktop.getAlignFrameFor(comp)
- .getFeatureRenderer();
- complementFeatures = complementRenderer.getDisplayedFeatureTypes();
+ sb.append("|");
}
- }
- 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]);
-
- if (mapping == null || mapping.length < 1)
+ firstModel = false;
+ appendModel(sb, model, atomSpec);
+ if (specType == AtomSpecType.ALPHA)
{
- continue;
+ sb.append("@CA");
}
-
- for (int seqNo = 0; seqNo < seqs[pdbfnum].length; seqNo++)
+ if (specType == AtomSpecType.PHOSPHATE)
{
- 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);
- }
- }
- }
+ sb.append("@P");
}
}
- return theMap;
+ return sb.toString();
}
/**
- * Scans visible features in mapped positions of the CDS/peptide complement, and
- * adds any found to the map of attribute values/structure positions
+ * A helper method to append an atomSpec string for atoms in the given model
*
- * @param complementRenderer
- * @param structureMapping
- * @param seq
- * @param theMap
- * @param modelNumber
+ * @param sb
+ * @param model
+ * @param atomSpec
*/
- protected static void scanComplementFeatures(
- FeatureRenderer complementRenderer,
- StructureMapping structureMapping, SequenceI seq,
- Map<String, Map<Object, AtomSpecModel>> theMap, int modelNumber)
+ protected void appendModel(StringBuilder sb, String model,
+ AtomSpecModel atomSpec)
{
- /*
- * for each sequence residue mapped to a structure position...
- */
- for (int seqPos : structureMapping.getMapping().keySet())
+ sb.append("#").append(model);
+
+ for (String chain : atomSpec.getChains(model))
{
- /*
- * find visible complementary features at mapped position(s)
- */
- MappedFeatures mf = complementRenderer
- .findComplementFeaturesAtResidue(seq, seqPos);
- if (mf != null)
+ boolean firstPositionForChain = true;
+ sb.append("/").append(chain.trim()).append(":");
+ List<int[]> rangeList = atomSpec.getRanges(model, chain);
+ boolean first = true;
+ for (int[] range : rangeList)
{
- for (SequenceFeature sf : mf.features)
+ if (!first)
{
- 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(",");
}
+ first = false;
+ appendRange(sb, range[0], range[1], chain, firstPositionForChain,
+ true);
}
}
}
- /**
- * 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<String> visibleFeatures,
- StructureMapping mapping, SequenceI seq,
- Map<String, Map<Object, AtomSpecModel>> theMap, int modelNumber)
+ @Override
+ public List<StructureCommandI> showBackbone()
{
- List<SequenceFeature> sfs = seq.getFeatures().getPositionalFeatures(
- visibleFeatures.toArray(new String[visibleFeatures.size()]));
- for (SequenceFeature sf : sfs)
- {
- String type = sf.getType();
-
- /*
- * Don't copy features which originated from Chimera
- */
- if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
- .equals(sf.getFeatureGroup()))
- {
- continue;
- }
-
- List<int[]> 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<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], mapping.getChain());
- }
- }
- }
+ return Arrays.asList(SHOW_BACKBONE);
}
- /**
- * 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)
+ @Override
+ public List<StructureCommandI> superposeStructures(AtomSpecModel ref,
+ AtomSpecModel spec, AtomSpecType backbone)
{
- StringBuilder sb = new StringBuilder();
- if (featureType != null)
- {
- for (char c : featureType.toCharArray())
- {
- sb.append(Character.isLetterOrDigit(c) ? c : '_');
- }
- }
- String attName = NAMESPACE_PREFIX + sb.toString();
+ /*
+ * 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/chimerax/docs/user/commands/align.html
+ */
+ StringBuilder cmd = new StringBuilder();
+ String atomSpec = getAtomSpec(spec, backbone);
+ String refSpec = getAtomSpec(ref, backbone);
+ cmd.append("align ").append(atomSpec).append(" toAtoms ")
+ .append(refSpec);
/*
- * Chimera treats an attribute name ending in 'color' as colour-valued;
- * Jalview doesn't, so prevent this by appending an underscore
+ * show superposed residues as ribbon, others as chain
*/
- if (attName.toUpperCase().endsWith("COLOR"))
- {
- attName += "_";
- }
+ cmd.append("; ribbon ");
+ cmd.append(getAtomSpec(spec, AtomSpecType.RESIDUE_ONLY)).append("|");
+ cmd.append(getAtomSpec(ref, AtomSpecType.RESIDUE_ONLY))
+ .append("; view");
- return attName;
+ return Arrays.asList(new StructureCommand(cmd.toString()));
}
@Override
- public String colourByCharge()
+ public StructureCommandI openSession(String filepath)
{
- return CMD_COLOUR_BY_CHARGE;
+ // https://www.cgl.ucsf.edu/chimerax/docs/user/commands/open.html#composite
+ // this version of the command has no dependency on file extension
+ return new StructureCommand("open " + filepath + " format session");
}
@Override
- public String colourByResidues(Map<String, Color> colours)
+ public StructureCommandI closeViewer()
{
- StringBuilder cmd = new StringBuilder(12 * colours.size());
-
- /*
- * concatenate commands like
- * color :VAL #4949b6
- */
- 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();
+ return CLOSE_CHIMERAX;
}
@Override
- public String setBackgroundColour(Color col)
+ public List<StructureCommandI> startNotifications(String uri)
{
- return "set bgColor " + ColorUtils.toTkCode(col);
+ List<StructureCommandI> cmds = new ArrayList<>();
+ cmds.add(new StructureCommand(
+ "info notify start models jalview prefix ModelChanged url "
+ + uri));
+ cmds.add(new StructureCommand(
+ "info notify start selection jalview prefix SelectionChanged url "
+ + uri));
+ return cmds;
}
@Override
- protected String getColourCommand(AtomSpecModel colourData,
- String colourCode)
+ public List<StructureCommandI> stopNotifications()
{
- return "color " + colourData.getAtomSpecX() + " " + colourCode;
+ List<StructureCommandI> cmds = new ArrayList<>();
+ cmds.add(STOP_NOTIFY_MODELS);
+ cmds.add(STOP_NOTIFY_SELECTION);
+ return cmds;
}
@Override
- public String focusView()
- {
- return "view";
- }
-
- /**
- * {@inheritDoc}
- *
- * @return
- */
- protected static int getModelStartNo()
+ public StructureCommandI getSelectedResidues()
{
- return 1;
+ return GET_SELECTION;
}
- /**
- * 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)
+ public StructureCommandI listResidueAttributes()
{
- 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 LIST_RESIDUE_ATTRIBUTES;
}
-
}