{
return null;
}
+
+ @Override
+ protected void sendAsynchronousCommand(String command, String progressMsg)
+ {
+ // TODO Auto-generated method stub
+
+ }
}
return null;
}
+ @Override
+ protected void sendAsynchronousCommand(String command, String progressMsg)
+ {
+ // TODO Auto-generated method stub
+
+ }
+
}
import jalview.datamodel.PDBEntry;
import jalview.datamodel.SequenceI;
import jalview.gui.IProgressIndicator;
+import jalview.gui.StructureViewer.ViewerType;
import jalview.io.DataSourceType;
import jalview.io.StructureFile;
import jalview.structure.AtomSpec;
+import jalview.structure.StructureCommandsI.SuperposeData;
import jalview.structure.StructureSelectionManager;
import jalview.structures.models.AAStructureBindingModel;
import jalview.util.MessageManager;
/**
* {@inheritDoc}
*/
- @Override
public String superposeStructures(AlignmentI[] _alignment,
int[] _refStructure, HiddenColumns[] _hiddenCols)
{
SuperposeData[] structures = new SuperposeData[files.length];
for (int f = 0; f < files.length; f++)
{
- structures[f] = new SuperposeData(alignment.getWidth());
+ structures[f] = new SuperposeData(alignment.getWidth(), f);
}
/*
}
}
return -1;
+ }
+ @Override
+ protected ViewerType getViewerType()
+ {
+ return ViewerType.JMOL;
}
}
import jalview.datamodel.HiddenColumns;
import jalview.datamodel.SequenceI;
import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.structure.AtomSpecModel;
import jalview.structure.StructureCommandsBase;
import jalview.structure.StructureMapping;
import jalview.structure.StructureSelectionManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
/**
* Routines for generating Jmol commands for Jalview/Jmol binding
private static final String CMD_COLOUR_BY_CHAIN = "select *;color chain";
- private static String formatRGB(Color c) {
+ private static final String PIPE = "|";
+
+ private static final String HYPHEN = "-";
+
+ private static final String COLON = ":";
+
+ private static final String SLASH = "/";
+
+ /**
+ * {@inheritDoc}
+ *
+ * @return
+ */
+ @Override
+ public int getModelStartNo()
+ {
+ return 1;
+ }
+
+ @Override
+ protected String getColourString(Color c)
+ {
return c == null ? null
: String.format("[%d,%d,%d]", c.getRed(), c.getGreen(),
c.getBlue());
}
+ /**
+ * Returns commands (one per colour key in the map) like
+ *
+ * <pre>
+ * select 2:A/1.1|3-27:B/1.1|9-12:A/2.1;color[173,0,82]
+ * </pre>
+ */
@Override
- public String[] colourBySequence(
- StructureSelectionManager ssm, String[] files,
+ public String[] colourBySequence(Map<Object, AtomSpecModel> colourMap)
+ {
+ List<String> colourCommands = buildColourCommands(colourMap);
+
+ return colourCommands.toArray(new String[colourCommands.size()]);
+ }
+
+ public String[] colourBySequence(StructureSelectionManager ssm,
+ String[] files,
SequenceI[][] sequence, SequenceRenderer sr,
AlignmentViewPanel viewPanel)
{
String newSelcom = (mapping[m].getChain() != " "
? ":" + mapping[m].getChain()
- : "") + "/" + (pdbfnum + 1) + ".1" + ";color["
- + col.getRed() + "," + col.getGreen() + ","
- + col.getBlue() + "]";
+ : "") + "/" + (pdbfnum + 1) + ".1" + ";color"
+ + getColourString(col);
if (command.length() > newSelcom.length() && command
.substring(command.length() - newSelcom.length())
.equals(newSelcom))
{
StringBuilder cmd = new StringBuilder(128);
cmd.append("select *;color white;");
-
- /*
- * concatenate commands like
- * select VAL;color[100,215,55]
- */
- for (Entry<String, Color> entry : colours.entrySet())
- {
- Color col = entry.getValue();
- String resCode = entry.getKey();
- cmd.append("select ").append(resCode).append(";");
- cmd.append("color[");
- cmd.append(formatRGB(col));
- cmd.append("];");
- }
+ cmd.append(super.colourByResidues(colours));
return cmd.toString();
}
@Override
public String setBackgroundColour(Color col)
{
- return "background " + formatRGB(col);
+ return "background " + getColourString(col);
}
@Override
return command;
}
+ /**
+ * Returns a command to superpose atoms in {@code atomSpec} to those in
+ * {@code refAtoms}, restricted to alpha carbons only (Phosphorous for rna).
+ * For example
+ *
+ * <pre>
+ * compare {2.1} {1.1} SUBSET {(*.CA | *.P) and conformation=1}
+ * ATOMS {1-87:A}{2-54:A|61-94:A} ROTATE TRANSLATE 1.0;
+ * </pre>
+ *
+ * where {@code conformation=1} excludes ALTLOC atom locations, and 1.0 is the
+ * time in seconds to animate the action. For this example, atoms in model 2
+ * are moved towards atoms in model 1.
+ * <p>
+ * The two atomspecs should each be for one model only, but may have more than
+ * one chain. The number of atoms specified should be the same for both
+ * models, though if not, Jmol may make a 'best effort' at superposition.
+ *
+ * @see https://chemapps.stolaf.edu/jmol/docs/#compare
+ */
+ @Override
+ public String superposeStructures(AtomSpecModel refAtoms,
+ AtomSpecModel atomSpec)
+ {
+ StringBuilder sb = new StringBuilder(64);
+ int refModel = refAtoms.getModels().iterator().next();
+ int model2 = atomSpec.getModels().iterator().next();
+ sb.append(String.format("compare {%d.1} {%d.1}", model2, refModel));
+ sb.append(" SUBSET {(*.CA | *.P) and conformation=1} ATOMS {");
+
+ /*
+ * command examples don't include modelspec with atoms, getAtomSpec does;
+ * it works, so leave it as it is for simplicity
+ */
+ sb.append(getAtomSpec(atomSpec, true)).append("}{");
+ sb.append(getAtomSpec(refAtoms, true)).append("}");
+ sb.append(" ROTATE TRANSLATE ");
+ sb.append(getCommandSeparator());
+
+ /*
+ * show residues used for superposition as ribbon
+ */
+ sb.append("select ").append(getAtomSpec(atomSpec, false)).append("|");
+ sb.append(getAtomSpec(refAtoms, false)).append(getCommandSeparator())
+ .append("cartoons");
+
+ return sb.toString();
+ }
+
+ @Override
+ public String openCommandFile(String path)
+ {
+ /*
+ * https://chemapps.stolaf.edu/jmol/docs/#script
+ * not currently used in Jalview
+ */
+ return "script " + path;
+ }
+
+ @Override
+ public String saveSession(String filepath)
+ {
+ /*
+ * https://chemapps.stolaf.edu/jmol/docs/#write
+ * not currently used in Jalview
+ */
+ return "write \"" + filepath + "\"";
+ }
+
+ @Override
+ protected String getColourCommand(String atomSpec, Color colour)
+ {
+ StringBuilder sb = new StringBuilder(atomSpec.length()+20);
+ sb.append("select ").append(atomSpec).append(getCommandSeparator())
+ .append("color").append(getColourString(colour));
+ return sb.toString();
+ }
+
+ @Override
+ protected String getResidueSpec(String residue)
+ {
+ return residue;
+ }
+
+ /**
+ * Generates a Jmol atomspec string like
+ *
+ * <pre>
+ * 2-5:A/1.1,8:A/1.1,5-10:B/2.1
+ * </pre>
+ *
+ * Parameter {@code alphaOnly} is not used here - this restriction is made by
+ * a separate clause in the {@code compare} (superposition) command.
+ */
+ @Override
+ public String getAtomSpec(AtomSpecModel model, boolean alphaOnly)
+ {
+ StringBuilder sb = new StringBuilder(128);
+
+ boolean first = true;
+ for (int modelNo : model.getModels())
+ {
+ for (String chain : model.getChains(modelNo))
+ {
+ for (int[] range : model.getRanges(modelNo, chain))
+ {
+ if (!first)
+ {
+ sb.append(PIPE);
+ }
+ first = false;
+ if (range[0] == range[1])
+ {
+ sb.append(range[0]);
+ }
+ else
+ {
+ sb.append(range[0]).append(HYPHEN).append(range[1]);
+ }
+ sb.append(COLON).append(chain.trim()).append(SLASH);
+ sb.append(String.valueOf(modelNo)).append(".1");
+ }
+ }
+ }
+
+ return sb.toString();
+ }
+
+ @Override
+ public String showBackbone()
+ {
+ return "select *; cartoons off; backbone";
+ }
}
+++ /dev/null
-/*
- * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
- * Copyright (C) $$Year-Rel$$ The Jalview Authors
- *
- * This file is part of Jalview.
- *
- * Jalview is free software: you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation, either version 3
- * of the License, or (at your option) any later version.
- *
- * Jalview is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
- * PURPOSE. See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
- * The Jalview Authors are detailed in the 'AUTHORS' file.
- */
-package jalview.ext.rbvi.chimera;
-
-import jalview.util.IntRangeComparator;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-/**
- * A class to model a Chimera atomspec pattern, for example
- *
- * <pre>
- * #0:15.A,28.A,54.A,63.A,70-72.A,83-84.A,97-98.A|#1:2.A,6.A,11.A,13-14.A,70.A,82.A,96-97.A
- * </pre>
- *
- * where
- * <ul>
- * <li>#0 is a model number</li>
- * <li>15 or 70-72 is a residue number, or range of residue numbers</li>
- * <li>.A is a chain identifier</li>
- * <li>residue ranges are separated by comma</li>
- * <li>atomspecs for distinct models are separated by | (or)</li>
- * </ul>
- *
- * <pre>
- * @see http://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
- * </pre>
- */
-public class AtomSpecModel
-{
- private Map<Integer, Map<String, List<int[]>>> atomSpec;
-
- /**
- * Constructor
- */
- public AtomSpecModel()
- {
- atomSpec = new TreeMap<>();
- }
-
- /**
- * Adds one contiguous range to this atom spec
- *
- * @param model
- * @param startPos
- * @param endPos
- * @param chain
- */
- public void addRange(int model, int startPos, int endPos, String chain)
- {
- /*
- * Get/initialize map of data for the colour and model
- */
- Map<String, List<int[]>> modelData = atomSpec.get(model);
- if (modelData == null)
- {
- atomSpec.put(model, modelData = new TreeMap<>());
- }
-
- /*
- * Get/initialize map of data for colour, model and chain
- */
- List<int[]> chainData = modelData.get(chain);
- if (chainData == null)
- {
- chainData = new ArrayList<>();
- modelData.put(chain, chainData);
- }
-
- /*
- * Add the start/end positions
- */
- chainData.add(new int[] { startPos, endPos });
- // TODO add intelligently, using a RangeList class
- }
-
- /**
- * Returns the range(s) formatted as a Chimera atomspec
- *
- * @return
- */
- public String getAtomSpec()
- {
- StringBuilder sb = new StringBuilder(128);
- boolean firstModel = true;
- for (Integer model : atomSpec.keySet())
- {
- if (!firstModel)
- {
- sb.append("|");
- }
- firstModel = false;
- sb.append("#").append(model).append(":");
-
- boolean firstPositionForModel = true;
- final Map<String, List<int[]>> modelData = atomSpec.get(model);
-
- for (String chain : modelData.keySet())
- {
- chain = " ".equals(chain) ? chain : chain.trim();
-
- List<int[]> rangeList = modelData.get(chain);
-
- /*
- * sort ranges into ascending start position order
- */
- Collections.sort(rangeList, IntRangeComparator.ASCENDING);
-
- int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
- int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
-
- Iterator<int[]> iterator = rangeList.iterator();
- while (iterator.hasNext())
- {
- int[] range = iterator.next();
- if (range[0] <= end + 1)
- {
- /*
- * 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]);
- }
- else
- {
- /*
- * we have a break so append the last range
- */
- appendRange(sb, start, end, chain, firstPositionForModel,
- false);
- firstPositionForModel = false;
- start = range[0];
- end = range[1];
- }
- }
-
- /*
- * and append the last range
- */
- if (!rangeList.isEmpty())
- {
- appendRange(sb, start, end, chain, firstPositionForModel, false);
- firstPositionForModel = false;
- }
- }
- }
- return sb.toString();
- }
-
- /**
- * @param sb
- * @param start
- * @param end
- * @param chain
- * @param firstPositionForModel
- */
- protected void appendRange(StringBuilder sb, int start, int end,
- String chain, boolean firstPositionForModel, boolean isChimeraX)
- {
- if (!firstPositionForModel)
- {
- sb.append(",");
- }
- if (end == start)
- {
- sb.append(start);
- }
- else
- {
- sb.append(start).append("-").append(end);
- }
-
- if (!isChimeraX)
- {
- sb.append(".");
- if (!" ".equals(chain))
- {
- sb.append(chain);
- }
- }
- }
-
- /**
- * 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
- *
- * @return
- */
- public String getAtomSpecX()
- {
- StringBuilder sb = new StringBuilder(128);
- boolean firstModel = true;
- for (Integer model : atomSpec.keySet())
- {
- if (!firstModel)
- {
- sb.append("|");
- }
- firstModel = false;
- sb.append("#").append(model);
-
- final Map<String, List<int[]>> modelData = atomSpec.get(model);
-
- for (String chain : modelData.keySet())
- {
- boolean firstPositionForChain = true;
- chain = " ".equals(chain) ? chain : chain.trim();
- sb.append("/").append(chain).append(":");
- List<int[]> rangeList = modelData.get(chain);
-
- /*
- * sort ranges into ascending start position order
- */
- Collections.sort(rangeList, IntRangeComparator.ASCENDING);
-
- int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
- int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
-
- Iterator<int[]> iterator = rangeList.iterator();
- while (iterator.hasNext())
- {
- int[] range = iterator.next();
- if (range[0] <= end + 1)
- {
- /*
- * 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]);
- }
- else
- {
- /*
- * we have a break so append the last range
- */
- appendRange(sb, start, end, chain, firstPositionForChain, true);
- start = range[0];
- end = range[1];
- firstPositionForChain = false;
- }
- }
-
- /*
- * and append the last range
- */
- if (!rangeList.isEmpty())
- {
- appendRange(sb, start, end, chain, firstPositionForChain, true);
- }
- firstPositionForChain = false;
- }
- }
- return sb.toString();
- }
-}
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.AtomSpecModel;
import jalview.structure.StructureCommandsBase;
import jalview.structure.StructureMapping;
import jalview.structure.StructureSelectionManager;
import jalview.util.ColorUtils;
-import jalview.util.Comparison;
+import jalview.util.IntRangeComparator;
import java.awt.Color;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
/**
* Routines for generating Chimera commands for Jalview/Chimera binding
{
public static final String NAMESPACE_PREFIX = "jv_";
- protected static final String CMD_SEPARATOR = ";";
-
private static final String CMD_COLOUR_BY_CHARGE = "color white;color red ::ASP;color red ::GLU;color blue ::LYS;color blue ::ARG;color yellow ::CYS";
private static final String CMD_COLOUR_BY_CHAIN = "rainbow chain";
+ // Chimera clause to exclude alternate locations in atom selection
+ private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9";
+
@Override
- public String[] colourBySequence(
- StructureSelectionManager ssm, String[] files,
- SequenceI[][] sequence, SequenceRenderer sr,
- AlignmentViewPanel viewPanel)
+ public String[] colourBySequence(Map<Object, AtomSpecModel> colourMap)
{
- Map<Object, AtomSpecModel> colourMap = buildColoursMap(ssm, files,
- sequence, sr, viewPanel);
-
List<String> colourCommands = buildColourCommands(colourMap);
return colourCommands.toArray(new String[colourCommands.size()]);
}
- /**
- * 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
- *
- * <pre>
- * <blockquote>
- * color colorname #modelnumber:range.chain
- * e.g. color #00ff00 #0:2.B,4.B,9-12.B|#1:1.A,2-6.A,...
- * </blockquote>
- * </pre>
- *
- * @param colourMap
- * @return
- */
- protected List<String> buildColourCommands(
- Map<Object, AtomSpecModel> colourMap)
- {
- /*
- * 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.
- */
- List<String> 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("; ");
- }
- firstColour = false;
- final AtomSpecModel colourData = colourMap.get(colour);
- sb.append(getColourCommand(colourData, colourCode));
- }
- commands.add(sb.toString());
- return commands;
- }
-
- protected String getColourCommand(AtomSpecModel colourData,
- String colourCode)
- {
- return "color " + colourCode + " " + colourData.getAtomSpec();
- }
-
- /**
- * 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<Integer, Map<String, List<int[]>>> 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<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();
- }
-
- /**
- * <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>
- *
- * @param ssm
- * @param files
- * @param sequence
- * @param sr
- * @param viewPanel
- * @return
- */
- protected static Map<Object, AtomSpecModel> buildColoursMap(
- StructureSelectionManager ssm, String[] files,
- SequenceI[][] sequence, SequenceRenderer sr,
- AlignmentViewPanel viewPanel)
+ @Override
+ public String getColourCommand(String atomSpec, Color colour)
{
- 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++)
- {
- final int modelNumber = pdbfnum + getModelStartNo();
- 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));
-
- 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, modelNumber,
- startPos, lastPos, lastChain);
- }
- startPos = pos;
- }
- lastColour = colour;
- lastPos = pos;
- lastChain = chain;
- }
- // final colour range
- if (lastColour != null)
- {
- addAtomSpecRange(colourMap, lastColour, modelNumber, startPos,
- lastPos, lastChain);
- }
- // break;
- }
- }
- }
- }
- return colourMap;
+ // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/color.html
+ String colourCode = getColourString(colour);
+ return "color " + colourCode + " " + atomSpec;
}
/**
- * Returns the lowest model number used by the structure viewer
+ * Returns a colour formatted suitable for use in viewer command syntax
*
+ * @param colour
* @return
*/
- protected static int getModelStartNo()
+ @Override
+ protected String getColourString(Color colour)
{
- return 0;
- }
-
- /**
- * Helper method to add one contiguous range to the AtomSpec model for the given
- * value (creating the model if necessary). As used by Jalview, {@code value} is
- * <ul>
- * <li>a colour, when building a 'colour structure by sequence' command</li>
- * <li>a feature value, when building a 'set Chimera attributes from features'
- * command</li>
- * </ul>
- *
- * @param map
- * @param value
- * @param model
- * @param startPos
- * @param endPos
- * @param chain
- */
- protected static final void addAtomSpecRange(
- Map<Object, AtomSpecModel> map,
- Object value, int model, int startPos, int endPos, String chain)
- {
- /*
- * Get/initialize map of data for the colour
- */
- AtomSpecModel atomSpec = map.get(value);
- if (atomSpec == null)
- {
- atomSpec = new AtomSpecModel();
- map.put(value, atomSpec);
- }
-
- atomSpec.addRange(model, startPos, endPos, chain);
+ return ColorUtils.toTkCode(colour);
}
/**
* @param viewPanel
* @return
*/
- protected static Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
+ protected Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
StructureSelectionManager ssm, String[] files, SequenceI[][] seqs,
AlignmentViewPanel viewPanel)
{
StringBuilder sb = new StringBuilder(128);
sb.append("setattr res ").append(attributeName).append(" '")
.append(attributeValue).append("' ");
- sb.append(atomSpecModel.getAtomSpec());
+ sb.append(getAtomSpec(atomSpecModel, false));
return sb.toString();
}
}
@Override
- public String colourByResidues(Map<String, Color> colours)
+ public String getResidueSpec(String residue)
{
- StringBuilder cmd = new StringBuilder(12 * colours.size());
-
- /*
- * concatenate commands like
- * color #4949b6 ::VAL
- */
- for (Entry<String, Color> entry : colours.entrySet())
- {
- String colorSpec = ColorUtils.toTkCode(entry.getValue());
- String resCode = entry.getKey();
- cmd.append("color ").append(colorSpec).append(" ::").append(resCode)
- .append(CMD_SEPARATOR);
- }
- return cmd.toString();
+ return "::" + residue;
}
@Override
public String setBackgroundColour(Color col)
{
+ // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/set.html#bgcolor
return "set bgColor " + ColorUtils.toTkCode(col);
}
@Override
public String focusView()
{
+ // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/focus.html
return "focus";
}
return command;
}
+ @Override
+ public String superposeStructures(AtomSpecModel spec, AtomSpecModel ref)
+ {
+ /*
+ * Form Chimera match command to match spec to ref
+ *
+ * match #1:1-30.B,81-100.B@CA #0:21-40.A,61-90.A@CA
+ *
+ * @see
+ * https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/midas/match.html
+ */
+ StringBuilder cmd = new StringBuilder();
+ String atomSpec = getAtomSpec(spec, true);
+ String refSpec = getAtomSpec(ref, true);
+ cmd.append("match ").append(atomSpec).append(" ").append(refSpec);
+
+ /*
+ * show superposed residues as ribbon, others as chain
+ */
+ // fixme this should precede the loop over all alignments/structures
+ cmd.append(";~display all; chain @CA|P");
+ cmd.append("; ribbon ");
+ cmd.append(atomSpec).append("|").append(refSpec).append("; focus");
+
+ return cmd.toString();
+ }
+
+ @Override
+ public String openCommandFile(String path)
+ {
+ // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/filetypes.html
+ return "open cmd:" + path;
+ }
+
+ @Override
+ public String saveSession(String filepath)
+ {
+ // https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/save.html
+ return "save " + filepath;
+ }
+
+ /**
+ * Returns the range(s) modelled by {@code atomSpec} formatted as a Chimera
+ * atomspec string, e.g.
+ *
+ * <pre>
+ * #0:15.A,28.A,54.A,70-72.A|#1:2.A,6.A,11.A,13-14.A
+ * </pre>
+ *
+ * where
+ * <ul>
+ * <li>#0 is a model number</li>
+ * <li>15 or 70-72 is a residue number, or range of residue numbers</li>
+ * <li>.A is a chain identifier</li>
+ * <li>residue ranges are separated by comma</li>
+ * <li>atomspecs for distinct models are separated by | (or)</li>
+ * </ul>
+ *
+ * <pre>
+ *
+ * @param model
+ * @param alphaOnly
+ * @return
+ * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
+ */
+ @Override
+ public String getAtomSpec(AtomSpecModel atomSpec, boolean alphaOnly)
+ {
+ StringBuilder sb = new StringBuilder(128);
+ boolean firstModel = true;
+ for (Integer model : atomSpec.getModels())
+ {
+ if (!firstModel)
+ {
+ sb.append("|");
+ }
+ firstModel = false;
+ appendModel(sb, model, atomSpec, alphaOnly);
+ }
+ return sb.toString();
+ }
+
+ /**
+ * A helper method to append an atomSpec string for atoms in the given model
+ *
+ * @param sb
+ * @param model
+ * @param atomSpec
+ * @param alphaOnly
+ */
+ protected void appendModel(StringBuilder sb, Integer model,
+ AtomSpecModel atomSpec, boolean alphaOnly)
+ {
+ sb.append("#").append(model).append(":");
+
+ boolean firstPositionForModel = true;
+
+ for (String chain : atomSpec.getChains(model))
+ {
+ chain = " ".equals(chain) ? chain : chain.trim();
+
+ List<int[]> rangeList = atomSpec.getRanges(model, chain);
+
+ /*
+ * sort ranges into ascending start position order
+ */
+ Collections.sort(rangeList, IntRangeComparator.ASCENDING);
+
+ int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
+ int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
+
+ Iterator<int[]> iterator = rangeList.iterator();
+ while (iterator.hasNext())
+ {
+ int[] range = iterator.next();
+ if (range[0] <= end + 1)
+ {
+ /*
+ * 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]);
+ }
+ else
+ {
+ /*
+ * we have a break so append the last range
+ */
+ appendRange(sb, start, end, chain, firstPositionForModel, false);
+ firstPositionForModel = false;
+ start = range[0];
+ end = range[1];
+ }
+ }
+
+ /*
+ * and append the last range
+ */
+ if (!rangeList.isEmpty())
+ {
+ appendRange(sb, start, end, chain, firstPositionForModel, false);
+ firstPositionForModel = false;
+ }
+ }
+ if (alphaOnly)
+ {
+ /*
+ * restrict to alpha carbon, no alternative locations
+ * (needed to ensuring matching atom counts for superposition)
+ */
+ sb.append("@CA|P").append(NO_ALTLOCS);
+ }
+ }
+
+ @Override
+ public String showBackbone()
+ {
+ return "~display all;chain @CA|P";
+ }
+
}
*/
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();
}
}
import jalview.datamodel.SearchResultsI;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
-import jalview.gui.Preferences;
import jalview.gui.StructureViewer.ViewerType;
import jalview.httpserver.AbstractRequestHandler;
import jalview.io.DataSourceType;
import jalview.structure.AtomSpec;
+import jalview.structure.StructureCommandsI.SuperposeData;
import jalview.structure.StructureSelectionManager;
import jalview.structures.models.AAStructureBindingModel;
import jalview.util.MessageManager;
{
super(ssm, pdbentry, sequenceIs, protocol);
chimeraManager = new ChimeraManager(new StructureManager(true));
- String viewerType = Cache.getProperty(Preferences.STRUCTURE_DISPLAY);
- chimeraManager.setChimeraX(ViewerType.CHIMERAX.name().equals(viewerType));
+ chimeraManager.setChimeraX(ViewerType.CHIMERAX.equals(getViewerType()));
setStructureCommands(new ChimeraCommands());
}
+ @Override
+ protected ViewerType getViewerType()
+ {
+ return ViewerType.CHIMERA;
+ }
+
/**
* Starts a thread that waits for the Chimera process to finish, so that we can
* then close the associated resources. This avoids leaving orphaned Chimera
/**
* {@inheritDoc}
*/
- @Override
public String superposeStructures(AlignmentI[] _alignment,
int[] _refStructure, HiddenColumns[] _hiddenCols)
{
SuperposeData[] structures = new SuperposeData[files.length];
for (int f = 0; f < files.length; f++)
{
- structures[f] = new SuperposeData(alignment.getWidth());
+ structures[f] = new SuperposeData(alignment.getWidth(), f);
}
/*
}
else
{
- allComs.append("chain @CA|P; ribbon ; focus");
- allComs.append(selectioncom.toString());
+ allComs.append("chain @CA|P; ribbon ");
+ allComs.append(selectioncom.toString()).append("; focus");
}
// allComs.append("; ~display all; chain @CA|P; ribbon ")
// .append(selectioncom.toString()).append("; focus");
}
/**
- * Send a Chimera command asynchronously in a new thread. If the progress
- * message is not null, display this message while the command is executing.
- *
- * @param command
- * @param progressMsg
- */
- protected abstract void sendAsynchronousCommand(String command,
- String progressMsg);
-
- /**
* @param command
*/
protected void executeWhenReady(String command)
* Chimera: https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/save.html
* ChimeraX: https://www.cgl.ucsf.edu/chimerax/docs/user/commands/save.html
*/
- String command = getSaveSessionCommand(filepath);
+ String command = getCommandGenerator().saveSession(filepath);
List<String> reply = chimeraManager.sendChimeraCommand(command, true);
if (reply.contains("Session written"))
{
}
/**
- * Returns the command to save the viewer session to the given file path
- *
- * @param filepath
- * @return
- */
- protected String getSaveSessionCommand(String filepath)
- {
- return "save " + filepath;
- }
-
- /**
* Ask Chimera to open a session file. Returns true if successful, else false.
* The filename must have a .py (Chimera) or .cxs (ChimeraX) extension for
* this command to work.
out.flush();
out.close();
String path = tmp.getAbsolutePath();
- String command = getOpenCommandFileCommand(path);
+ String command = getCommandGenerator().openCommandFile(path);
sendAsynchronousCommand(command, null);
} catch (IOException e)
{
}
/**
- * Returns the command for the structure viewer to open a file of commands at
- * the given file path
- *
- * @param path
- * @return
- */
- protected String getOpenCommandFileCommand(String path)
- {
- return "open cmd:" + path;
- }
-
- /**
* Returns the file extension required for a file of commands to be read by
* the structure viewer
* @return
SequenceI[][] seqs)
{
createProgressBar();
- jmb = new JalviewChimeraBindingModel(this,
- ap.getStructureSelectionManager(), pdbentrys, seqs, null);
+ jmb = newBindingModel(ap, pdbentrys, seqs);
addAlignmentPanel(ap);
useAlignmentPanelForColourbyseq(ap);
}
+ protected JalviewChimeraBindingModel newBindingModel(AlignmentPanel ap,
+ PDBEntry[] pdbentrys, SequenceI[][] seqs)
+ {
+ return new JalviewChimeraBindingModel(this,
+ ap.getStructureSelectionManager(), pdbentrys, seqs, null);
+ }
+
/**
* Create a new viewer from saved session state data including Chimera session
* file
{
return "Chimera";
}
-
- /**
- * Sends commands to align structures according to associated alignment(s).
- *
- * @return
- */
- @Override
- protected String alignStructsWithAllAlignPanels()
- {
- String reply = super.alignStructsWithAllAlignPanels();
- if (reply != null)
- {
- statusBar.setText("Superposition failed: " + reply);
- }
- return reply;
- }
}
package jalview.gui;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceI;
import jalview.gui.StructureViewer.ViewerType;
/**
public class ChimeraXViewFrame extends ChimeraViewFrame
{
+ public ChimeraXViewFrame(PDBEntry pdb, SequenceI[] seqsForPdb,
+ String[] chains, AlignmentPanel ap)
+ {
+ super(pdb, seqsForPdb, chains, ap);
+ }
+
+ public ChimeraXViewFrame(PDBEntry[] pdbsForFile, boolean superposeAdded,
+ SequenceI[][] theSeqs, AlignmentPanel ap)
+ {
+ super(pdbsForFile, superposeAdded, theSeqs, ap);
+ }
+
@Override
public ViewerType getViewerType()
{
- return null;// ViewerType.CHIMERAX;
+ return ViewerType.CHIMERAX;
}
@Override
return "ChimeraX";
}
+ @Override
+ protected JalviewChimeraBindingModel newBindingModel(AlignmentPanel ap,
+ PDBEntry[] pdbentrys, SequenceI[][] seqs)
+ {
+ return new JalviewChimeraXBindingModel(this,
+ ap.getStructureSelectionManager(), pdbentrys, seqs, null);
+ }
+
}
}
});
}
-
- /**
- * Send an asynchronous command to Chimera, in a new thread, optionally with
- * an 'in progress' message in a progress bar somewhere
- */
- @Override
- protected void sendAsynchronousCommand(final String command,
- final String progressMsg)
- {
- final JalviewStructureDisplayI theViewer = getViewer();
- final long handle = progressMsg == null ? 0
- : theViewer.startProgressBar(progressMsg);
- SwingUtilities.invokeLater(new Runnable()
- {
- @Override
- public void run()
- {
- try
- {
- executeCommand(command, false);
- } finally
- {
- if (progressMsg != null)
- {
- theViewer.stopProgressBar(null, handle);
- }
- }
- }
- });
- }
}
import jalview.datamodel.PDBEntry;
import jalview.datamodel.SequenceI;
+import jalview.ext.rbvi.chimera.ChimeraXCommands;
+import jalview.gui.StructureViewer.ViewerType;
import jalview.io.DataSourceType;
import jalview.structure.StructureSelectionManager;
SequenceI[][] sequenceIs, DataSourceType protocol)
{
super(chimeraViewFrame, ssm, pdbentry, sequenceIs, protocol);
+ setStructureCommands(new ChimeraXCommands());
}
@Override
}
/**
- * {@inheritDoc}
- *
- * @return
- */
- @Override
- protected String getOpenCommandFileCommand(String path)
- {
- return "open " + path;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- protected String getSaveSessionCommand(String filepath)
- {
- return "save session " + filepath;
- }
-
- /**
* Returns the file extension to use for a saved viewer session file
*
* @return
return "http://www.rbvi.ucsf.edu/chimerax/docs/user/index.html";
}
+ @Override
+ protected ViewerType getViewerType()
+ {
+ return ViewerType.CHIMERAX;
+ }
+
}
{
sview = new AppJmol(ap, superposeAdded, pdbsForFile, theSeqs);
}
- else if (viewerType.equals(ViewerType.CHIMERA)
- || viewerType.equals(ViewerType.CHIMERAX))
+ else if (viewerType.equals(ViewerType.CHIMERA))
{
sview = new ChimeraViewFrame(pdbsForFile, superposeAdded, theSeqs,
ap);
}
+ else if (viewerType.equals(ViewerType.CHIMERAX))
+ {
+ sview = new ChimeraXViewFrame(pdbsForFile, superposeAdded, theSeqs,
+ ap);
+ }
else
{
Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
{
sview = new AppJmol(pdb, seqsForPdb, null, ap);
}
- else if (viewerType.equals(ViewerType.CHIMERA)
- || viewerType.equals(ViewerType.CHIMERAX))
+ else if (viewerType.equals(ViewerType.CHIMERA))
{
sview = new ChimeraViewFrame(pdb, seqsForPdb, null, ap);
}
+ else if (viewerType.equals(ViewerType.CHIMERAX))
+ {
+ sview = new ChimeraXViewFrame(pdb, seqsForPdb, null, ap);
+ }
else
{
Cache.log.error(UNKNOWN_VIEWER_TYPE + getViewerType().toString());
import jalview.api.AlignmentViewPanel;
import jalview.bin.Cache;
-import jalview.datamodel.Alignment;
import jalview.datamodel.AlignmentI;
-import jalview.datamodel.HiddenColumns;
import jalview.datamodel.PDBEntry;
import jalview.datamodel.SequenceI;
import jalview.gui.StructureViewer.ViewerType;
String reply = null;
try
{
- AlignmentI[] als = new Alignment[_alignwith.size()];
- HiddenColumns[] alc = new HiddenColumns[_alignwith.size()];
- int[] alm = new int[_alignwith.size()];
- int a = 0;
-
- for (AlignmentViewPanel alignPanel : _alignwith)
- {
- als[a] = alignPanel.getAlignment();
- alm[a] = -1;
- alc[a++] = alignPanel.getAlignment().getHiddenColumns();
- }
- reply = getBinding().superposeStructures(als, alm, alc);
- if (reply != null)
+ reply = getBinding().superposeStructures(_alignwith);
+ if (reply != null && !reply.isEmpty())
{
String text = MessageManager
.formatMessage("error.superposition_failed", reply);
--- /dev/null
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.structure;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ * A class to model a set of models, chains and atom range positions
+ *
+ */
+public class AtomSpecModel
+{
+ /*
+ * { modelNo, {chainCode, List<from-to> ranges} }
+ */
+ private Map<Integer, Map<String, BitSet>> atomSpec;
+
+ /**
+ * Constructor
+ */
+ public AtomSpecModel()
+ {
+ atomSpec = new TreeMap<>();
+ }
+
+ /**
+ * Adds one contiguous range to this atom spec
+ *
+ * @param model
+ * @param startPos
+ * @param endPos
+ * @param chain
+ */
+ public void addRange(int model, int startPos, int endPos, String chain)
+ {
+ /*
+ * Get/initialize map of data for the colour and model
+ */
+ Map<String, BitSet> modelData = atomSpec.get(model);
+ if (modelData == null)
+ {
+ atomSpec.put(model, modelData = new TreeMap<>());
+ }
+
+ /*
+ * Get/initialize map of data for colour, model and chain
+ */
+ BitSet chainData = modelData.get(chain);
+ if (chainData == null)
+ {
+ chainData = new BitSet();
+ modelData.put(chain, chainData);
+ }
+
+ /*
+ * Add the start/end positions
+ */
+ chainData.set(startPos, endPos + 1);
+ }
+
+ public Iterable<Integer> getModels()
+ {
+ return atomSpec.keySet();
+ }
+
+ public Iterable<String> getChains(Integer model)
+ {
+ return atomSpec.containsKey(model) ? atomSpec.get(model).keySet()
+ : null;
+ }
+
+ /**
+ * Returns a (possibly empty) ordered list of contiguous atom ranges for the
+ * given model and chain.
+ *
+ * @param model
+ * @param chain
+ * @return
+ */
+ public List<int[]> getRanges(Integer model, String chain)
+ {
+ List<int[]> ranges = new ArrayList<>();
+ if (atomSpec.containsKey(model))
+ {
+ BitSet bs = atomSpec.get(model).get(chain);
+ int start = 0;
+ if (bs != null)
+ {
+ start = bs.nextSetBit(start);
+ int end = 0;
+ while (start != -1)
+ {
+ end = bs.nextClearBit(start);
+ ranges.add(new int[] { start, end - 1 });
+ start = bs.nextSetBit(end);
+ }
+ }
+ }
+ return ranges;
+ }
+}
package jalview.structure;
+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.SequenceI;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.util.Comparison;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
/**
* A base class holding methods useful to all classes that implement commands
*/
public abstract class StructureCommandsBase implements StructureCommandsI
{
+ private static final String CMD_SEPARATOR = ";";
+
+ /**
+ * Returns something that separates concatenated commands
+ *
+ * @return
+ */
+ protected static String getCommandSeparator()
+ {
+ return CMD_SEPARATOR;
+ }
@Override
public String[] setAttributesForFeatures(StructureSelectionManager ssm,
// default does nothing, override where this is implemented
return null;
}
+
+ /**
+ * Returns the lowest model number used by the structure viewer
+ *
+ * @return
+ */
+ @Override
+ public int getModelStartNo()
+ {
+ return 0;
+ }
+
+ /**
+ * <pre>
+ * Build a data structure which records contiguous subsequences for each colour.
+ * From this we can easily generate the viewer 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>
+ *
+ * @param ssm
+ * @param files
+ * @param sequence
+ * @param sr
+ * @param viewPanel
+ * @return
+ */
+ protected Map<Object, AtomSpecModel> buildColoursMap(
+ StructureSelectionManager ssm, String[] files,
+ SequenceI[][] sequence, SequenceRenderer sr, AlignmentViewPanel viewPanel)
+ {
+ 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++)
+ {
+ final int modelNumber = pdbfnum + getModelStartNo();
+ 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));
+
+ 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, modelNumber,
+ startPos, lastPos, lastChain);
+ }
+ startPos = pos;
+ }
+ lastColour = colour;
+ lastPos = pos;
+ lastChain = chain;
+ }
+ // final colour range
+ if (lastColour != null)
+ {
+ addAtomSpecRange(colourMap, lastColour, modelNumber, startPos,
+ lastPos, lastChain);
+ }
+ // break;
+ }
+ }
+ }
+ }
+ return colourMap;
+ }
+
+ /**
+ * Helper method to add one contiguous range to the AtomSpec model for the given
+ * value (creating the model if necessary). As used by Jalview, {@code value} is
+ * <ul>
+ * <li>a colour, when building a 'colour structure by sequence' command</li>
+ * <li>a feature value, when building a 'set Chimera attributes from features'
+ * command</li>
+ * </ul>
+ *
+ * @param map
+ * @param value
+ * @param model
+ * @param startPos
+ * @param endPos
+ * @param chain
+ */
+ public static final void addAtomSpecRange(Map<Object, AtomSpecModel> map,
+ Object value,
+ int model, int startPos, int endPos, String chain)
+ {
+ /*
+ * Get/initialize map of data for the colour
+ */
+ AtomSpecModel atomSpec = map.get(value);
+ if (atomSpec == null)
+ {
+ atomSpec = new AtomSpecModel();
+ map.put(value, atomSpec);
+ }
+
+ atomSpec.addRange(model, startPos, endPos, chain);
+ }
+
+ /**
+ * Returns a colour formatted suitable for use in viewer command syntax
+ *
+ * @param colour
+ * @return
+ */
+ protected abstract String getColourString(Color colour);
+
+ /**
+ * 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 specific to the structure viewer.
+ *
+ * @param colourMap
+ * @return
+ */
+ public List<String> buildColourCommands(
+ Map<Object, AtomSpecModel> colourMap)
+ {
+ /*
+ * 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.
+ */
+ List<String> commands = new ArrayList<>();
+ StringBuilder sb = new StringBuilder(256);
+ boolean firstColour = true;
+ for (Object key : colourMap.keySet())
+ {
+ Color colour = (Color) key;
+ if (!firstColour)
+ {
+ sb.append(getCommandSeparator()).append(" ");
+ }
+ firstColour = false;
+ final AtomSpecModel colourData = colourMap.get(colour);
+ sb.append(getColourCommand(colourData, colour));
+ }
+ commands.add(sb.toString());
+ return commands;
+ }
+
+ /**
+ * Returns a command to colour the atoms represented by {@code atomSpecModel}
+ * with the colour specified by {@code colourCode}.
+ *
+ * @param atomSpecModel
+ * @param colour
+ * @return
+ */
+ protected String getColourCommand(AtomSpecModel atomSpecModel, Color colour)
+ {
+ String atomSpec = getAtomSpec(atomSpecModel, false);
+ return getColourCommand(atomSpec, colour);
+ }
+
+ /**
+ * Returns a command to colour the atoms described (in viewer command syntax)
+ * by {@code atomSpec} with the colour specified by {@code colourCode}
+ *
+ * @param atomSpec
+ * @param colour
+ * @return
+ */
+ protected abstract String getColourCommand(String atomSpec, Color colour);
+
+ @Override
+ public String colourByResidues(Map<String, Color> colours)
+ {
+ StringBuilder cmd = new StringBuilder(12 * colours.size());
+
+ for (Entry<String, Color> entry : colours.entrySet())
+ {
+ String residue = entry.getKey();
+ String atomSpec = getResidueSpec(residue);
+ cmd.append(getColourCommand(atomSpec, entry.getValue()));
+ cmd.append(getCommandSeparator());
+ }
+ return cmd.toString();
+ }
+
+ /**
+ * Helper method to append one start-end range to an atomspec string
+ *
+ * @param sb
+ * @param start
+ * @param end
+ * @param chain
+ * @param firstPositionForModel
+ */
+ protected void appendRange(StringBuilder sb, int start, int end,
+ String chain, boolean firstPositionForModel, boolean isChimeraX)
+ {
+ if (!firstPositionForModel)
+ {
+ sb.append(",");
+ }
+ if (end == start)
+ {
+ sb.append(start);
+ }
+ else
+ {
+ sb.append(start).append("-").append(end);
+ }
+
+ if (!isChimeraX)
+ {
+ sb.append(".");
+ if (!" ".equals(chain))
+ {
+ sb.append(chain);
+ }
+ }
+ }
+
+ /**
+ * Returns the atom specifier meaning all occurrences of the given residue
+ *
+ * @param residue
+ * @return
+ */
+ protected abstract String getResidueSpec(String residue);
}
package jalview.structure;
import jalview.api.AlignmentViewPanel;
-import jalview.api.SequenceRenderer;
import jalview.datamodel.SequenceI;
import java.awt.Color;
*/
public interface StructureCommandsI
{
+ /**
+ * Data bean class to simplify parameterisation in superposeStructures
+ */
+ public class SuperposeData
+ {
+ public String filename;
+
+ public String pdbId;
+
+ public String chain = "";
+
+ public boolean isRna;
+
+ /*
+ * The pdb residue number (if any) mapped to columns of the alignment
+ */
+ public int[] pdbResNo; // or use SparseIntArray?
+
+ public int modelNo;
+
+ /**
+ * Constructor
+ *
+ * @param width
+ * width of alignment (number of columns that may potentially
+ * participate in superposition)
+ * @param model
+ * structure viewer model number
+ */
+ public SuperposeData(int width, int model)
+ {
+ pdbResNo = new int[width];
+ modelNo = model;
+ }
+ }
/**
* Returns the command to colour by chain
/**
* Returns commands to colour mapped residues of structures according to
- * Jalview's colouring (including feature colouring if applied)
+ * Jalview's colouring (including feature colouring if applied). Parameter is
+ * a map from Color to a model of all residues assigned that colour.
*
- * @param structureSelectionManager
- * @param files
- * @param seqs
- * @param sr
- * @param alignmentv
+ * @param colourMap
* @return
*/
- String[] colourBySequence(
- StructureSelectionManager structureSelectionManager,
- String[] files, SequenceI[][] seqs, SequenceRenderer sr,
- AlignmentViewPanel alignmentv);
+
+ String[] colourBySequence(Map<Object, AtomSpecModel> colourMap);
/**
* Returns a command to centre the display in the structure viewer
String[] setAttributesForFeatures(StructureSelectionManager ssm,
String[] files, SequenceI[][] sequence, AlignmentViewPanel avp);
+ /**
+ * Returns a command to superpose structures by closest positioning of
+ * residues in {@code atomSpec} to the corresponding residues in {@ refAtoms}.
+ * If wanted, this may include commands to visually highlight the residues
+ * that were used for the superposition.
+ *
+ * @param refAtoms
+ * @param atomSpec
+ * @return
+ */
+ String superposeStructures(AtomSpecModel refAtoms,
+ AtomSpecModel atomSpec);
+
+ /**
+ * Returns a command to open a file of commands at the given path
+ *
+ * @param path
+ * @return
+ */
+ String openCommandFile(String path);
+
+ /**
+ * Returns a command to save the current viewer session state to the given
+ * file
+ *
+ * @param filepath
+ * @return
+ */
+ String saveSession(String filepath);
+
+ /**
+ * Returns a representation of the atom set represented by the model, in
+ * viewer syntax format. If {@code alphaOnly} is true, this is restricted to
+ * Alpha Carbon (peptide) or Phosphorous (rna) only
+ *
+ * @param model
+ * @param alphaOnly
+ * @return
+ */
+ String getAtomSpec(AtomSpecModel model, boolean alphaOnly);
+
+ /**
+ * Returns the lowest model number used by the structure viewer (likely 0 or
+ * 1)
+ *
+ * @return
+ */
+ // TODO remove by refactoring so command generation is purely driven by
+ // AtomSpecModel objects derived in the binding classes?
+ int getModelStartNo();
+
+ /**
+ * Show only the backbone of the peptide (cartoons in Jmol, chain in Chimera)
+ *
+ * @return
+ */
+ String showBackbone();
}
*/
package jalview.structures.models;
+import jalview.api.AlignViewportI;
import jalview.api.AlignmentViewPanel;
import jalview.api.FeatureRenderer;
import jalview.api.SequenceRenderer;
import jalview.datamodel.HiddenColumns;
import jalview.datamodel.PDBEntry;
import jalview.datamodel.SequenceI;
+import jalview.gui.StructureViewer.ViewerType;
import jalview.io.DataSourceType;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
import jalview.schemes.ColourSchemeI;
import jalview.schemes.ResidueProperties;
import jalview.structure.AtomSpec;
+import jalview.structure.AtomSpecModel;
import jalview.structure.StructureCommandsI;
+import jalview.structure.StructureCommandsI.SuperposeData;
import jalview.structure.StructureListener;
import jalview.structure.StructureMapping;
import jalview.structure.StructureSelectionManager;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
extends SequenceStructureBindingModel
implements StructureListener, StructureSelectionManagerProvider
{
+ private static final int MIN_POS_TO_SUPERPOSE = 4;
+
private static final String COLOURING_STRUCTURES = MessageManager
.getString("status.colouring_structures");
public String fileLoadingError;
/**
- * Data bean class to simplify parameterisation in superposeStructures
- */
- protected class SuperposeData
- {
- /**
- * Constructor with alignment width argument
- *
- * @param width
- */
- public SuperposeData(int width)
- {
- pdbResNo = new int[width];
- }
-
- public String filename;
-
- public String pdbId;
-
- public String chain = "";
-
- public boolean isRna;
-
- /*
- * The pdb residue number (if any) mapped to each column of the alignment
- */
- public int[] pdbResNo;
- }
-
- /**
* Constructor
*
* @param ssm
* for the same structure)
*/
s = seqCountForPdbFile;
- break;
+ break; // fixme break out of two loops here!
}
}
}
/**
* Constructs and sends a command to align structures against a reference
* structure, based on one or more sequence alignments. May optionally return
- * an error or warning message for the alignment command.
+ * an error or warning message for the alignment command(s).
*
- * @param alignments
- * an array of alignments to process
- * @param structureIndices
- * an array of corresponding reference structures (index into pdb
- * file array); if a negative value is passed, the first PDB file
- * mapped to an alignment sequence is used as the reference for
- * superposition
- * @param hiddenCols
- * an array of corresponding hidden columns for each alignment
+ * @param alignWith
+ * an array of one or more alignment views to process
* @return
*/
- public abstract String superposeStructures(AlignmentI[] alignments,
- int[] structureIndices, HiddenColumns[] hiddenCols);
+ public String superposeStructures(List<AlignmentViewPanel> alignWith)
+ {
+ String error = "";
+ String[] files = getStructureFiles();
+
+ if (!waitForFileLoad(files))
+ {
+ return null;
+ }
+ refreshPdbEntries();
+
+ for (AlignmentViewPanel view : alignWith)
+ {
+ AlignmentI alignment = view.getAlignment();
+ HiddenColumns hiddenCols = alignment.getHiddenColumns();
+
+ /*
+ * 'matched' bit i will be set for visible alignment columns i where
+ * all sequences have a residue with a mapping to their PDB structure
+ */
+ BitSet matched = new BitSet();
+ final int width = alignment.getWidth();
+ for (int m = 0; m < width; m++)
+ {
+ if (hiddenCols == null || hiddenCols.isVisible(m))
+ {
+ matched.set(m);
+ }
+ }
+
+ SuperposeData[] structures = new SuperposeData[files.length];
+ for (int f = 0; f < files.length; f++)
+ {
+ structures[f] = new SuperposeData(width,
+ f + commandGenerator.getModelStartNo());
+ }
+
+ /*
+ * Calculate the superposable alignment columns ('matched'), and the
+ * corresponding structure residue positions (structures.pdbResNo)
+ */
+ int refStructure = findSuperposableResidues(alignment,
+ matched, structures);
+
+ /*
+ * require at least 4 positions to be able to execute superposition
+ */
+ int nmatched = matched.cardinality();
+ if (nmatched < MIN_POS_TO_SUPERPOSE)
+ {
+ String msg = MessageManager.formatMessage("label.insufficient_residues",
+ nmatched);
+ error += view.getViewName() + ": " + msg + "; ";
+ continue;
+ }
+
+ /*
+ * get a model of the superposable residues in the reference structure
+ */
+ AtomSpecModel refAtoms = getAtomSpec(structures[refStructure],
+ matched);
+
+ /*
+ * Show all as backbone before doing superposition(s)
+ * (residues used for matching will be shown as ribbon)
+ */
+ executeCommand(commandGenerator.showBackbone(), false);
+
+ /*
+ * superpose each (other) sequence to it in turn
+ */
+ for (int i = 0; i < structures.length; i++)
+ {
+ if (i != refStructure)
+ {
+ AtomSpecModel atomSpec = getAtomSpec(structures[i], matched);
+ String commands = commandGenerator.superposeStructures(refAtoms,
+ atomSpec);
+ List<String> replies = executeCommands(true, commands);
+ for (String reply : replies)
+ {
+ // return this error (Chimera only) to the user
+ if (reply.toLowerCase().contains("unequal numbers of atoms"))
+ {
+ error += "; " + reply;
+ }
+ }
+ }
+ }
+ }
+
+ return error;
+ }
+
+ private AtomSpecModel getAtomSpec(SuperposeData superposeData,
+ BitSet matched)
+ {
+ AtomSpecModel model = new AtomSpecModel();
+ int nextColumnMatch = matched.nextSetBit(0);
+ while (nextColumnMatch != -1)
+ {
+ int pdbResNum = superposeData.pdbResNo[nextColumnMatch];
+ model.addRange(superposeData.modelNo, pdbResNum, pdbResNum,
+ superposeData.chain);
+ nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
+ }
+
+ return model;
+ }
/**
* returns the current sequenceRenderer that should be used to colour the
protected List<String> executeCommands(boolean getReply,
String... commands)
{
- List<String> response = null;
+ // todo: tidy this up
+ List<String> response = getReply ? new ArrayList<>() : null;
for (String cmd : commands)
{
- response = executeCommand(cmd, getReply);
+ List<String> replies = executeCommand(cmd, getReply);
+ if (getReply && replies != null)
+ {
+ response.addAll(replies);
+ }
}
return response;
}
String[] files = getStructureFiles();
SequenceRenderer sr = getSequenceRenderer(alignmentv);
+ Map<Object, AtomSpecModel> colourMap = buildColoursMap(ssm, files,
+ sequence, sr, alignmentv);
String[] colourBySequenceCommands = commandGenerator
- .colourBySequence(getSsm(), files, getSequence(), sr,
- alignmentv);
+ .colourBySequence(colourMap);
executeCommands(false, colourBySequenceCommands);
}
{
return commandGenerator;
}
+
+ protected abstract ViewerType getViewerType();
+
+ /**
+ * Send a structure viewer command asynchronously in a new thread. If the
+ * progress message is not null, display this message while the command is
+ * executing.
+ *
+ * @param command
+ * @param progressMsg
+ */
+ protected void sendAsynchronousCommand(String command, String progressMsg)
+ {
+ final JalviewStructureDisplayI theViewer = getViewer();
+ final long handle = progressMsg == null ? 0
+ : theViewer.startProgressBar(progressMsg);
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ executeCommand(command, false);
+ } finally
+ {
+ if (progressMsg != null)
+ {
+ theViewer.stopProgressBar(null, handle);
+ }
+ }
+ }
+ });
+
+ }
+
+ /**
+ * <pre>
+ * Build a data structure which records residues for each colour.
+ * From this we can easily generate the viewer command for colour by sequence.
+ * Color
+ * Model number
+ * Chain
+ * Residue positions
+ * Ordering is by order of addition (for colours and positions), natural ordering (for models and chains)
+ * </pre>
+ *
+ * @param ssm
+ * @param files
+ * @param sequence
+ * @param sr
+ * @param viewPanel
+ * @return
+ */
+ protected Map<Object, AtomSpecModel> buildColoursMap(
+ StructureSelectionManager ssm, String[] files,
+ SequenceI[][] sequence, SequenceRenderer sr, AlignmentViewPanel viewPanel)
+ {
+ 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++)
+ {
+ final int modelNumber = pdbfnum + commandGenerator.getModelStartNo();
+ 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));
+
+ 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, modelNumber,
+ startPos, lastPos, lastChain);
+ }
+ startPos = pos;
+ }
+ lastColour = colour;
+ lastPos = pos;
+ lastChain = chain;
+ }
+ // final colour range
+ if (lastColour != null)
+ {
+ addAtomSpecRange(colourMap, lastColour, modelNumber, startPos,
+ lastPos, lastChain);
+ }
+ // break;
+ }
+ }
+ }
+ }
+ return colourMap;
+ }
+
+ /**
+ * Helper method to add one contiguous range to the AtomSpec model for the given
+ * value (creating the model if necessary). As used by Jalview, {@code value} is
+ * <ul>
+ * <li>a colour, when building a 'colour structure by sequence' command</li>
+ * <li>a feature value, when building a 'set Chimera attributes from features'
+ * command</li>
+ * </ul>
+ *
+ * @param map
+ * @param value
+ * @param model
+ * @param startPos
+ * @param endPos
+ * @param chain
+ */
+ public static final void addAtomSpecRange(Map<Object, AtomSpecModel> map,
+ Object value,
+ int model, int startPos, int endPos, String chain)
+ {
+ /*
+ * Get/initialize map of data for the colour
+ */
+ AtomSpecModel atomSpec = map.get(value);
+ if (atomSpec == null)
+ {
+ atomSpec = new AtomSpecModel();
+ map.put(value, atomSpec);
+ }
+
+ atomSpec.addRange(model, startPos, endPos, chain);
+ }
}
import jalview.gui.JvOptionPane;
import jalview.gui.SequenceRenderer;
import jalview.schemes.JalviewColourScheme;
+import jalview.structure.AtomSpecModel;
+import jalview.structure.StructureCommandsI;
import jalview.structure.StructureMapping;
import jalview.structure.StructureSelectionManager;
assertTrue(
chainBCommand.contains(";select 26-30:B/2.1;color[73,73,182]"));
}
+
+ @Test(groups = "Functional")
+ public void testGetAtomSpec()
+ {
+ StructureCommandsI testee = new JmolCommands();
+ AtomSpecModel model = new AtomSpecModel();
+ assertEquals(testee.getAtomSpec(model, false), "");
+ model.addRange(1, 2, 4, "A");
+ assertEquals(testee.getAtomSpec(model, false), "2-4:A/1.1");
+ model.addRange(1, 8, 8, "A");
+ assertEquals(testee.getAtomSpec(model, false), "2-4:A/1.1|8:A/1.1");
+ model.addRange(1, 5, 7, "B");
+ assertEquals(testee.getAtomSpec(model, false),
+ "2-4:A/1.1|8:A/1.1|5-7:B/1.1");
+ model.addRange(1, 3, 5, "A");
+ assertEquals(testee.getAtomSpec(model, false),
+ "2-5:A/1.1|8:A/1.1|5-7:B/1.1");
+ model.addRange(2, 1, 4, "B");
+ assertEquals(testee.getAtomSpec(model, false),
+ "2-5:A/1.1|8:A/1.1|5-7:B/1.1|1-4:B/2.1");
+ model.addRange(2, 5, 9, "C");
+ assertEquals(testee.getAtomSpec(model, false),
+ "2-5:A/1.1|8:A/1.1|5-7:B/1.1|1-4:B/2.1|5-9:C/2.1");
+ model.addRange(1, 8, 10, "B");
+ assertEquals(testee.getAtomSpec(model, false),
+ "2-5:A/1.1|8:A/1.1|5-10:B/1.1|1-4:B/2.1|5-9:C/2.1");
+ model.addRange(1, 8, 9, "B");
+ assertEquals(testee.getAtomSpec(model, false),
+ "2-5:A/1.1|8:A/1.1|5-10:B/1.1|1-4:B/2.1|5-9:C/2.1");
+ model.addRange(2, 3, 10, "C"); // subsumes 5-9
+ assertEquals(testee.getAtomSpec(model, false),
+ "2-5:A/1.1|8:A/1.1|5-10:B/1.1|1-4:B/2.1|3-10:C/2.1");
+ model.addRange(5, 25, 35, " ");
+ assertEquals(testee.getAtomSpec(model, false),
+ "2-5:A/1.1|8:A/1.1|5-10:B/1.1|1-4:B/2.1|3-10:C/2.1|25-35:/5.1");
+
+ }
+
+ @Test(groups = { "Functional" })
+ public void testSuperposeStructures()
+ {
+ StructureCommandsI testee = new JmolCommands();
+ AtomSpecModel ref = new AtomSpecModel();
+ ref.addRange(1, 12, 14, "A");
+ ref.addRange(1, 18, 18, "B");
+ ref.addRange(1, 22, 23, "B");
+ AtomSpecModel toAlign = new AtomSpecModel();
+ toAlign.addRange(2, 15, 17, "B");
+ toAlign.addRange(2, 20, 21, "B");
+ toAlign.addRange(2, 22, 22, "C");
+ String command = testee.superposeStructures(ref, toAlign);
+ String refSpec = "12-14:A/1.1|18:B/1.1|22-23:B/1.1";
+ String toAlignSpec = "15-17:B/2.1|20-21:B/2.1|22:C/2.1";
+ String expected = String.format(
+ "compare {2.1} {1.1} SUBSET {(*.CA | *.P) and conformation=1} ATOMS {%s}{%s} ROTATE TRANSLATE ;select %s|%s;cartoons",
+ toAlignSpec, refSpec, toAlignSpec, refSpec);
+ assertEquals(command, expected);
+ }
}
+++ /dev/null
-package jalview.ext.rbvi.chimera;
-
-import static org.testng.Assert.assertEquals;
-
-import org.testng.annotations.Test;
-
-public class AtomSpecModelTest
-{
- @Test(groups = "Functional")
- public void testGetAtomSpec()
- {
- AtomSpecModel model = new AtomSpecModel();
- assertEquals(model.getAtomSpec(), "");
- model.addRange(1, 2, 4, "A");
- assertEquals(model.getAtomSpec(), "#1:2-4.A");
- model.addRange(1, 8, 8, "A");
- assertEquals(model.getAtomSpec(), "#1:2-4.A,8.A");
- model.addRange(1, 5, 7, "B");
- assertEquals(model.getAtomSpec(), "#1:2-4.A,8.A,5-7.B");
- model.addRange(1, 3, 5, "A");
- assertEquals(model.getAtomSpec(), "#1:2-5.A,8.A,5-7.B");
- model.addRange(0, 1, 4, "B");
- assertEquals(model.getAtomSpec(), "#0:1-4.B|#1:2-5.A,8.A,5-7.B");
- model.addRange(0, 5, 9, "C");
- assertEquals(model.getAtomSpec(), "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-7.B");
- model.addRange(1, 8, 10, "B");
- assertEquals(model.getAtomSpec(), "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
- model.addRange(1, 8, 9, "B");
- assertEquals(model.getAtomSpec(), "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
- model.addRange(0, 3, 10, "C"); // subsumes 5-9
- assertEquals(model.getAtomSpec(), "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B");
- model.addRange(5, 25, 35, " "); // empty chain code - e.g. from homology
- // modelling
- assertEquals(model.getAtomSpec(),
- "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B|#5:25-35.");
-
- }
-
-}
import jalview.gui.AlignFrame;
import jalview.gui.SequenceRenderer;
import jalview.schemes.JalviewColourScheme;
+import jalview.structure.AtomSpecModel;
+import jalview.structure.StructureCommandsI;
import jalview.structure.StructureMapping;
import jalview.structure.StructureSelectionManager;
// S and G are both coloured #4949b6
assertTrue(theCommand.contains("color #4949b6 #0:26-30.A|#1:26-30.B"));
}
+
+ @Test(groups = "Functional")
+ public void testGetAtomSpec()
+ {
+ StructureCommandsI testee = new ChimeraCommands();
+ AtomSpecModel model = new AtomSpecModel();
+ assertEquals(testee.getAtomSpec(model, false), "");
+ model.addRange(1, 2, 4, "A");
+ assertEquals(testee.getAtomSpec(model, false), "#1:2-4.A");
+ model.addRange(1, 8, 8, "A");
+ assertEquals(testee.getAtomSpec(model, false), "#1:2-4.A,8.A");
+ model.addRange(1, 5, 7, "B");
+ assertEquals(testee.getAtomSpec(model, false), "#1:2-4.A,8.A,5-7.B");
+ model.addRange(1, 3, 5, "A");
+ assertEquals(testee.getAtomSpec(model, false), "#1:2-5.A,8.A,5-7.B");
+ model.addRange(0, 1, 4, "B");
+ assertEquals(testee.getAtomSpec(model, false),
+ "#0:1-4.B|#1:2-5.A,8.A,5-7.B");
+ model.addRange(0, 5, 9, "C");
+ assertEquals(testee.getAtomSpec(model, false),
+ "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-7.B");
+ model.addRange(1, 8, 10, "B");
+ assertEquals(testee.getAtomSpec(model, false),
+ "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
+ model.addRange(1, 8, 9, "B");
+ assertEquals(testee.getAtomSpec(model, false),
+ "#0:1-4.B,5-9.C|#1:2-5.A,8.A,5-10.B");
+ model.addRange(0, 3, 10, "C"); // subsumes 5-9
+ assertEquals(testee.getAtomSpec(model, false),
+ "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B");
+ model.addRange(5, 25, 35, " "); // empty chain code - e.g. from homology
+ // modelling
+ assertEquals(testee.getAtomSpec(model, false),
+ "#0:1-4.B,3-10.C|#1:2-5.A,8.A,5-10.B|#5:25-35.");
+
+ }
+
+ @Test(groups = "Functional")
+ public void testGetAtomSpec_alphaOnly()
+ {
+ StructureCommandsI testee = new ChimeraCommands();
+ AtomSpecModel model = new AtomSpecModel();
+ assertEquals(testee.getAtomSpec(model, true), "");
+ model.addRange(1, 2, 4, "A");
+ assertEquals(testee.getAtomSpec(model, true), "#1:2-4.A@CA|P");
+ model.addRange(1, 8, 8, "A");
+ assertEquals(testee.getAtomSpec(model, true), "#1:2-4.A,8.A@CA|P");
+ model.addRange(1, 5, 7, "B");
+ assertEquals(testee.getAtomSpec(model, true),
+ "#1:2-4.A,8.A,5-7.B@CA|P");
+ model.addRange(1, 3, 5, "A");
+ assertEquals(testee.getAtomSpec(model, true),
+ "#1:2-5.A,8.A,5-7.B@CA|P");
+ model.addRange(0, 1, 4, "B");
+ assertEquals(testee.getAtomSpec(model, true),
+ "#0:1-4.B@CA|P|#1:2-5.A,8.A,5-7.B@CA|P");
+ model.addRange(0, 5, 9, "C");
+ assertEquals(testee.getAtomSpec(model, true),
+ "#0:1-4.B,5-9.C@CA|P|#1:2-5.A,8.A,5-7.B@CA|P");
+ model.addRange(1, 8, 10, "B");
+ assertEquals(testee.getAtomSpec(model, true),
+ "#0:1-4.B,5-9.C@CA|P|#1:2-5.A,8.A,5-10.B@CA|P");
+ model.addRange(1, 8, 9, "B");
+ assertEquals(testee.getAtomSpec(model, true),
+ "#0:1-4.B,5-9.C@CA|P|#1:2-5.A,8.A,5-10.B@CA|P");
+ model.addRange(0, 3, 10, "C"); // subsumes 5-9
+ assertEquals(testee.getAtomSpec(model, true),
+ "#0:1-4.B,3-10.C@CA|P|#1:2-5.A,8.A,5-10.B@CA|P");
+ model.addRange(5, 25, 35, " "); // empty chain code
+ assertEquals(testee.getAtomSpec(model, true),
+ "#0:1-4.B,3-10.C@CA|P|#1:2-5.A,8.A,5-10.B@CA|P|#5:25-35.@CA|P");
+
+ }
+
+ @Test(groups = { "Functional" })
+ public void testSuperposeStructures()
+ {
+ StructureCommandsI testee = new ChimeraCommands();
+ AtomSpecModel ref = new AtomSpecModel();
+ ref.addRange(1, 12, 14, "A");
+ ref.addRange(1, 18, 18, "B");
+ ref.addRange(1, 22, 23, "B");
+ AtomSpecModel toAlign = new AtomSpecModel();
+ toAlign.addRange(2, 15, 17, "B");
+ toAlign.addRange(2, 20, 21, "B");
+ toAlign.addRange(2, 22, 22, "C");
+ String command = testee.superposeStructures(ref, toAlign);
+ String refSpec = "#1:12-14.A,18.B,22-23.B@CA|P&~@.B-Z&~@.2-9";
+ String toAlignSpec = "#2:15-17.B,20-21.B,22.C@CA|P&~@.B-Z&~@.2-9";
+ String expected = String.format(
+ "match %s %s;~display all; chain @CA|P; ribbon %s|%s; focus",
+ refSpec, toAlignSpec, refSpec, toAlignSpec);
+ assertEquals(command, expected);
+ }
}
import jalview.gui.AlignFrame;
import jalview.gui.SequenceRenderer;
import jalview.schemes.JalviewColourScheme;
+import jalview.structure.AtomSpecModel;
+import jalview.structure.StructureCommandsI;
import jalview.structure.StructureMapping;
import jalview.structure.StructureSelectionManager;
// S and G are both coloured #4949b6
assertTrue(theCommand.contains("color #0/A:26-30|#1/B:26-30"));
}
+
+ @Test(groups = { "Functional" })
+ public void testSuperposeStructures()
+ {
+ StructureCommandsI testee = new ChimeraXCommands();
+ AtomSpecModel ref = new AtomSpecModel();
+ ref.addRange(1, 12, 14, "A");
+ ref.addRange(1, 18, 18, "B");
+ ref.addRange(1, 22, 23, "B");
+ AtomSpecModel toAlign = new AtomSpecModel();
+ toAlign.addRange(2, 15, 17, "B");
+ toAlign.addRange(2, 20, 21, "B");
+ toAlign.addRange(2, 22, 22, "C");
+ String command = testee.superposeStructures(ref, toAlign);
+ String refSpec = "#1/A:12-14/B:18,22-23";
+ String toAlignSpec = "#2/B:15-17,20-21/C:22";
+ String expected = String.format(
+ "align %s %s;~display all; chain @CA|P; ribbon %s|%s; focus",
+ refSpec, toAlignSpec, refSpec, toAlignSpec);
+ assertEquals(command, expected);
+ }
}
--- /dev/null
+package jalview.structure;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import java.util.List;
+
+import org.testng.annotations.Test;
+
+public class AtomSpecModelTest
+{
+ @Test(groups="Functional")
+ public void testGetRanges()
+ {
+ AtomSpecModel model = new AtomSpecModel();
+ assertFalse(model.getModels().iterator().hasNext());
+ List<int[]> ranges = model.getRanges(1, "A");
+ assertTrue(ranges.isEmpty());
+
+ model.addRange(1, 12, 14, "A");
+ assertTrue(model.getRanges(1, "B").isEmpty());
+ assertTrue(model.getRanges(2, "A").isEmpty());
+ ranges = model.getRanges(1, "A");
+ assertEquals(ranges.size(), 1);
+ int[] range = ranges.get(0);
+ assertEquals(range[0], 12);
+ assertEquals(range[1], 14);
+
+ /*
+ * add some ranges; they should be coalesced and
+ * ordered when retrieved
+ */
+ model.addRange(1, 25, 25, "A");
+ model.addRange(1, 20, 24, "A");
+ model.addRange(1, 6, 8, "A");
+ model.addRange(1, 13, 18, "A");
+ model.addRange(1, 5, 6, "A");
+ ranges = model.getRanges(1, "A");
+ assertEquals(ranges.size(), 3);
+ range = ranges.get(0);
+ assertEquals(range[0], 5);
+ assertEquals(range[1], 8);
+ range = ranges.get(1);
+ assertEquals(range[0], 12);
+ assertEquals(range[1], 18);
+ range = ranges.get(2);
+ assertEquals(range[0], 20);
+ assertEquals(range[1], 25);
+ }
+}
import jalview.api.SequenceRenderer;
import jalview.datamodel.Alignment;
import jalview.datamodel.AlignmentI;
-import jalview.datamodel.HiddenColumns;
import jalview.datamodel.PDBEntry;
import jalview.datamodel.PDBEntry.Type;
import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceI;
import jalview.gui.JvOptionPane;
+import jalview.gui.StructureViewer.ViewerType;
import jalview.io.DataSourceType;
import jalview.io.FileFormats;
import jalview.structure.AtomSpec;
+import jalview.structure.StructureCommandsI.SuperposeData;
import jalview.structure.StructureSelectionManager;
-import jalview.structures.models.AAStructureBindingModel.SuperposeData;
import java.io.IOException;
import java.util.Arrays;
}
@Override
- public String superposeStructures(AlignmentI[] alignments,
- int[] structureIndices, HiddenColumns[] hiddenCols)
- {
- return null;
- }
-
- @Override
public void highlightAtoms(List<AtomSpec> atoms)
{
}
{
return 0;
}
+
+ @Override
+ protected ViewerType getViewerType()
+ {
+ return null;
+ }
};
String[][] chains = binder.getChains();
assertFalse(chains == null || chains[0] == null,
}
@Override
- public String superposeStructures(AlignmentI[] als, int[] alm,
- HiddenColumns[] alc)
- {
- return null;
- }
-
- @Override
public SequenceRenderer getSequenceRenderer(
AlignmentViewPanel alignment)
{
{
return 0;
}
+
+ @Override
+ protected ViewerType getViewerType()
+ {
+ return null;
+ }
};
}
SuperposeData[] structs = new SuperposeData[testee.getStructureFiles().length];
for (int i = 0; i < structs.length; i++)
{
- structs[i] = testee.new SuperposeData(al.getWidth());
+ structs[i] = new SuperposeData(al.getWidth(), 0);
}
/*
* initialise BitSet of 'superposable columns' to true (would be false for
SuperposeData[] structs = new SuperposeData[al.getHeight()];
for (int i = 0; i < structs.length; i++)
{
- structs[i] = testee.new SuperposeData(al.getWidth());
+ structs[i] = new SuperposeData(al.getWidth(), 0);
}
/*
* initialise BitSet of 'superposable columns' to true (would be false for
assertFalse(matched.get(4)); // superposable, but hidden, column
assertTrue(matched.get(5));
}
-}
+}
\ No newline at end of file