*/
package jalview.ext.rbvi.chimera;
+import jalview.api.AlignViewportI;
import jalview.api.AlignmentViewPanel;
-import jalview.api.FeatureRenderer;
-import jalview.api.SequenceRenderer;
+import jalview.api.structures.JalviewStructureDisplayI;
import jalview.bin.Cache;
import jalview.datamodel.AlignmentI;
-import jalview.datamodel.ColumnSelection;
+import jalview.datamodel.HiddenColumns;
import jalview.datamodel.PDBEntry;
-import jalview.datamodel.SearchResults;
-import jalview.datamodel.SearchResults.Match;
+import jalview.datamodel.SearchResultMatchI;
+import jalview.datamodel.SearchResultsI;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
import jalview.httpserver.AbstractRequestHandler;
+import jalview.io.DataSourceType;
import jalview.schemes.ColourSchemeI;
import jalview.schemes.ResidueProperties;
import jalview.structure.AtomSpec;
import java.io.PrintWriter;
import java.net.BindException;
import java.util.ArrayList;
+import java.util.BitSet;
import java.util.Collections;
import java.util.Hashtable;
import java.util.LinkedHashMap;
private static final String ALPHACARBON = "CA";
- private List<String> chainNames = new ArrayList<String>();
+ private List<String> chainNames = new ArrayList<>();
+
+ private Hashtable<String, String> chainFile = new Hashtable<>();
- private Hashtable<String, String> chainFile = new Hashtable<String, String>();
-
/*
* Object through which we talk to Chimera
*/
*/
private boolean loadingFinished = true;
- public String fileLoadingError;
-
/*
* Map of ChimeraModel objects keyed by PDB full local file name
*/
- private Map<String, List<ChimeraModel>> chimeraMaps = new LinkedHashMap<String, List<ChimeraModel>>();
+ private Map<String, List<ChimeraModel>> chimeraMaps = new LinkedHashMap<>();
String lastHighlightCommand;
*/
private long loadNotifiesHandled = 0;
+ private Thread chimeraMonitor;
+
/**
* Open a PDB structure file in Chimera and set up mappings from Jalview.
*
String file = pe.getFile();
try
{
- List<ChimeraModel> modelsToMap = new ArrayList<ChimeraModel>();
+ List<ChimeraModel> modelsToMap = new ArrayList<>();
List<ChimeraModel> oldList = viewer.getModelList();
boolean alreadyOpen = false;
if (getSsm() != null)
{
getSsm().addStructureViewerListener(this);
- // ssm.addSelectionListener(this);
- FeatureRenderer fr = getFeatureRenderer(null);
- if (fr != null)
- {
- fr.featuresAdded();
- }
- refreshGUI();
}
return true;
} catch (Exception q)
* @param protocol
*/
public JalviewChimeraBinding(StructureSelectionManager ssm,
- PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String protocol)
+ PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
+ DataSourceType protocol)
{
super(ssm, pdbentry, sequenceIs, protocol);
- viewer = new ChimeraManager(
- new ext.edu.ucsf.rbvi.strucviz2.StructureManager(true));
+ viewer = new ChimeraManager(new StructureManager(true));
+ }
+
+ /**
+ * 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 viewer panels in Jalview if the user closes Chimera.
+ */
+ protected void startChimeraProcessMonitor()
+ {
+ final Process p = viewer.getChimeraProcess();
+ chimeraMonitor = new Thread(new Runnable()
+ {
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ p.waitFor();
+ JalviewStructureDisplayI display = getViewer();
+ if (display != null)
+ {
+ display.closeViewer(false);
+ }
+ } catch (InterruptedException e)
+ {
+ // exit thread if Chimera Viewer is closed in Jalview
+ }
+ }
+ });
+ chimeraMonitor.start();
}
/**
viewer.startListening(chimeraListener.getUri());
} catch (BindException e)
{
- System.err.println("Failed to start Chimera listener: "
- + e.getMessage());
+ System.err.println(
+ "Failed to start Chimera listener: " + e.getMessage());
}
}
/**
- * Construct a title string for the viewer window based on the data Jalview
- * knows about
- *
- * @param verbose
- * @return
- */
- public String getViewerTitle(boolean verbose)
- {
- return getViewerTitle("Chimera", verbose);
- }
-
- /**
- * Tells Chimera to display only the specified chains
- *
- * @param toshow
- */
- public void showChains(List<String> toshow)
- {
- /*
- * Construct a chimera command like
- *
- * ~display #*;~ribbon #*;ribbon :.A,:.B
- */
- StringBuilder cmd = new StringBuilder(64);
- boolean first = true;
- for (String chain : toshow)
- {
- int modelNumber = getModelNoForChain(chain);
- String showChainCmd = modelNumber == -1 ? "" : modelNumber + ":."
- + chain.split(":")[1];
- if (!first)
- {
- cmd.append(",");
- }
- cmd.append(showChainCmd);
- first = false;
- }
-
- /*
- * could append ";focus" to this command to resize the display to fill the
- * window, but it looks more helpful not to (easier to relate chains to the
- * whole)
- */
- final String command = "~display #*; ~ribbon #*; ribbon :"
- + cmd.toString();
- sendChimeraCommand(command, false);
- }
-
- /**
* Close down the Jalview viewer and listener, and (optionally) the associated
* Chimera window.
*/
public void closeViewer(boolean closeChimera)
{
- getSsm().removeStructureViewerListener(this, this.getPdbFile());
+ getSsm().removeStructureViewerListener(this, this.getStructureFiles());
if (closeChimera)
{
viewer.exitChimera();
}
viewer = null;
+ if (chimeraMonitor != null)
+ {
+ chimeraMonitor.interrupt();
+ }
releaseUIResources();
}
+ @Override
public void colourByChain()
{
colourBySequence = false;
* <li>all others - white</li>
* </ul>
*/
+ @Override
public void colourByCharge()
{
colourBySequence = false;
}
/**
- * Construct and send a command to align structures against a reference
- * structure, based on one or more sequence alignments
- *
- * @param _alignment
- * an array of alignments to process
- * @param _refStructure
- * 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
+ * {@inheritDoc}
*/
- public void superposeStructures(AlignmentI[] _alignment,
- int[] _refStructure, ColumnSelection[] _hiddenCols)
+ @Override
+ public String superposeStructures(AlignmentI[] _alignment,
+ int[] _refStructure, HiddenColumns[] _hiddenCols)
{
StringBuilder allComs = new StringBuilder(128);
- String[] files = getPdbFile();
+ String[] files = getStructureFiles();
if (!waitForFileLoad(files))
{
- return;
+ return null;
}
refreshPdbEntries();
{
int refStructure = _refStructure[a];
AlignmentI alignment = _alignment[a];
- ColumnSelection hiddenCols = _hiddenCols[a];
+ HiddenColumns hiddenCols = _hiddenCols[a];
if (refStructure >= files.length)
{
}
/*
- * 'matched' array will hold 'true' for visible alignment columns where
+ * 'matched' bit i will be set for visible alignment columns i where
* all sequences have a residue with a mapping to the PDB structure
*/
- boolean matched[] = new boolean[alignment.getWidth()];
- for (int m = 0; m < matched.length; m++)
+ BitSet matched = new BitSet();
+ for (int m = 0; m < alignment.getWidth(); m++)
{
- matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true;
+ if (hiddenCols == null || hiddenCols.isVisible(m))
+ {
+ matched.set(m);
+ }
}
SuperposeData[] structures = new SuperposeData[files.length];
refStructure = candidateRefStructure;
}
- int nmatched = 0;
- for (boolean b : matched)
- {
- if (b)
- {
- nmatched++;
- }
- }
+ int nmatched = matched.cardinality();
if (nmatched < 4)
{
- // TODO: bail out here because superposition illdefined?
+ return MessageManager.formatMessage("label.insufficient_residues",
+ nmatched);
}
/*
int lpos = -1;
boolean run = false;
StringBuilder molsel = new StringBuilder();
- for (int r = 0; r < matched.length; r++)
+
+ int nextColumnMatch = matched.nextSetBit(0);
+ while (nextColumnMatch != -1)
{
- if (matched[r])
+ int pdbResNum = structures[pdbfnum].pdbResNo[nextColumnMatch];
+ if (lpos != pdbResNum - 1)
{
- int pdbResNum = structures[pdbfnum].pdbResNo[r];
- if (lpos != pdbResNum - 1)
+ /*
+ * discontiguous - append last residue now
+ */
+ if (lpos != -1)
{
- /*
- * discontiguous - append last residue now
- */
- if (lpos != -1)
- {
- molsel.append(String.valueOf(lpos));
- molsel.append(chainCd);
- molsel.append(",");
- }
- run = false;
+ molsel.append(String.valueOf(lpos));
+ molsel.append(chainCd);
+ molsel.append(",");
}
- else
+ run = false;
+ }
+ else
+ {
+ /*
+ * extending a contiguous run
+ */
+ if (!run)
{
/*
- * extending a contiguous run
+ * start the range selection
*/
- if (!run)
- {
- /*
- * start the range selection
- */
- molsel.append(String.valueOf(lpos));
- molsel.append("-");
- }
- run = true;
+ molsel.append(String.valueOf(lpos));
+ molsel.append("-");
}
- lpos = pdbResNum;
+ run = true;
}
+ lpos = pdbResNum;
+ nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
}
/*
* @see
* https://www.cgl.ucsf.edu/chimera/docs/UsersGuide/midas/match.html
*/
- command.append("match ").append(getModelSpec(pdbfnum)).append(":");
+ command.append("match ").append(getModelSpec(pdbfnum))
+ .append(":");
command.append(selcom[pdbfnum]);
command.append("@").append(
structures[pdbfnum].isRna ? PHOSPHORUS : ALPHACARBON);
if (debug)
{
System.out.println("Select regions:\n" + selectioncom.toString());
- System.out.println("Superimpose command(s):\n"
- + command.toString());
+ System.out.println(
+ "Superimpose command(s):\n" + command.toString());
}
- allComs.append("~display all; chain @CA|P; ribbon ")
- .append(selectioncom.toString())
+ allComs/*.append("~display all; chain @CA|P; ribbon ")
+ .append(selectioncom.toString())*/
.append(";" + command.toString());
}
}
+
+ String error = null;
if (selectioncom.length() > 0)
{
// TODO: visually distinguish regions that were superposed
{
System.out.println("Select regions:\n" + selectioncom.toString());
}
- allComs.append("; ~display all; chain @CA|P; ribbon ")
- .append(selectioncom.toString()).append("; focus");
- sendChimeraCommand(allComs.toString(), false);
+ allComs.append("; ~display "); // all");
+ if (!isShowAlignmentOnly())
+ {
+ allComs.append("; ribbon; chain @CA|P");
+ }
+ else
+ {
+ allComs.append("; ~ribbon");
+ }
+ allComs.append("; ribbon ").append(selectioncom.toString())
+ .append("; focus");
+ List<String> chimeraReplies = sendChimeraCommand(allComs.toString(),
+ true);
+ for (String reply : chimeraReplies)
+ {
+ String lowerCase = reply.toLowerCase();
+ if (lowerCase.contains("unequal numbers of atoms")
+ || lowerCase.contains("at least"))
+ {
+ error = reply;
+ }
+ }
}
-
+ return error;
}
/**
* @param pdbfnum
* @return
*/
- protected String getModelSpec(int pdbfnum)
+ @Override
+ public String getModelSpec(int pdbfnum)
{
if (pdbfnum < 0 || pdbfnum >= getPdbCount())
{
* to the Chimera command 'list models type molecule', see
* ChimeraManager.getModelList().
*/
- List<ChimeraModel> maps = chimeraMaps.get(getPdbFile()[pdbfnum]);
+ List<ChimeraModel> maps = chimeraMaps.get(getStructureFiles()[pdbfnum]);
boolean hasSubModels = maps != null && maps.size() > 1;
- return "#" + String.valueOf(pdbfnum) + (hasSubModels ? ".1" : "");
+ String spec = "#" + String.valueOf(pdbfnum);
+ return hasSubModels ? spec + ".1" : spec;
}
/**
* Launch Chimera, unless an instance linked to this object is already
- * running. Returns true if chimera is successfully launched, or already
+ * running. Returns true if Chimera is successfully launched, or already
* running, else false.
*
* @return
*/
public boolean launchChimera()
{
- if (!viewer.isChimeraLaunched())
- {
- return viewer.launchChimera(StructureManager.getChimeraPaths());
- }
if (viewer.isChimeraLaunched())
{
return true;
}
- log("Failed to launch Chimera!");
- return false;
+
+ boolean launched = viewer
+ .launchChimera(StructureManager.getChimeraPaths());
+ if (launched)
+ {
+ startChimeraProcessMonitor();
+ }
+ else
+ {
+ log("Failed to launch Chimera!");
+ }
+ return launched;
}
/**
String progressMsg);
/**
- * colour any structures associated with sequences in the given alignment
- * using the getFeatureRenderer() and getSequenceRenderer() renderers but only
- * if colourBySequence is enabled.
+ * Sends a set of colour commands to the structure viewer
+ *
+ * @param commands
*/
- public void colourBySequence(boolean showFeatures,
- jalview.api.AlignmentViewPanel alignmentv)
+ @Override
+ protected void colourBySequence(String[] commands)
{
- if (!colourBySequence || !loadingFinished)
+ for (String command : commands)
{
- return;
- }
- if (getSsm() == null)
- {
- return;
+ sendAsynchronousCommand(command, COLOURING_CHIMERA);
}
- String[] files = getPdbFile();
-
- SequenceRenderer sr = getSequenceRenderer(alignmentv);
+ }
- FeatureRenderer fr = null;
- if (showFeatures)
- {
- fr = getFeatureRenderer(alignmentv);
- }
- AlignmentI alignment = alignmentv.getAlignment();
+ /**
+ * Computes and returns a set of commands to colour residues in Chimera the same
+ * as mapped residues in the alignment
+ *
+ * @param files
+ * @param viewPanel
+ * @return
+ */
+ @Override
+ protected String[] getColourBySequenceCommands(
+ String[] files, AlignmentViewPanel viewPanel)
+ {
+ Map<Object, AtomSpecModel> colourMap = buildColoursMap(viewPanel);
- StructureMappingcommandSet colourBySequenceCommands = ChimeraCommands
- .getColourBySequenceCommand(getSsm(), files, getSequence(), sr,
- fr, alignment);
- for (String command : colourBySequenceCommands.commands)
- {
- sendAsynchronousCommand(command, COLOURING_CHIMERA);
- }
+ return ChimeraCommands.getColourBySequenceCommand(colourMap, this);
}
/**
// //////////////////////////
/**
- * returns the current featureRenderer that should be used to colour the
- * structures
- *
- * @param alignment
- *
- * @return
- */
- public abstract FeatureRenderer getFeatureRenderer(
- AlignmentViewPanel alignment);
-
- /**
* instruct the Jalview binding to update the pdbentries vector if necessary
* prior to matching the viewer's contents to the list of structure files
* Jalview knows about.
*/
public abstract void refreshPdbEntries();
+ /**
+ * map between index of model filename returned from getPdbFile and the first
+ * index of models from this file in the viewer. Note - this is not trimmed -
+ * use getPdbFile to get number of unique models.
+ */
+ private int _modelFileNameMap[];
+
// ////////////////////////////////
// /StructureListener
@Override
- public synchronized String[] getPdbFile()
+ public synchronized String[] getStructureFiles()
{
if (viewer == null)
{
return new String[0];
}
- return chimeraMaps.keySet().toArray(
- modelFileNames = new String[chimeraMaps.size()]);
+ return chimeraMaps.keySet()
+ .toArray(modelFileNames = new String[chimeraMaps.size()]);
}
/**
- * returns the current sequenceRenderer that should be used to colour the
- * structures
- *
- * @param alignment
- *
- * @return
- */
- public abstract SequenceRenderer getSequenceRenderer(
- AlignmentViewPanel alignment);
-
- /**
* Construct and send a command to highlight zero, one or more atoms. We do
* this by sending an "rlabel" command to show the residue label at that
* position.
* Parse model number, residue and chain for each selected position,
* formatted as #0:123.A or #1.2:87.B (#model.submodel:residue.chain)
*/
- List<AtomSpec> atomSpecs = convertStructureResiduesToAlignment(selection);
+ List<AtomSpec> atomSpecs = convertStructureResiduesToAlignment(
+ selection);
/*
* Broadcast the selection (which may be empty, if the user just cleared all
protected List<AtomSpec> convertStructureResiduesToAlignment(
List<String> structureSelection)
{
- List<AtomSpec> atomSpecs = new ArrayList<AtomSpec>();
+ List<AtomSpec> atomSpecs = new ArrayList<>();
for (String atomSpec : structureSelection)
{
try
return loadNotifiesHandled;
}
+ @Override
public void setJalviewColourScheme(ColourSchemeI cs)
{
colourBySequence = false;
return;
}
- // Chimera expects RBG values in the range 0-1
+ // Chimera expects RGB values in the range 0-1
final double normalise = 255D;
viewerCommandHistory(false);
StringBuilder command = new StringBuilder(128);
List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
false);
- for (String res : residueSet)
+ for (String resName : residueSet)
{
- Color col = cs.findColour(res.charAt(0));
- command.append("color " + col.getRed() / normalise + ","
- + col.getGreen() / normalise + "," + col.getBlue()
- / normalise + " ::" + res + ";");
+ char res = resName.length() == 3
+ ? ResidueProperties.getSingleCharacterCode(resName)
+ : resName.charAt(0);
+ Color col = cs.findColour(res, 0, null, null, 0f);
+ command.append("color ")
+ .append(String.valueOf(col.getRed() / normalise)).append(",")
+ .append(String.valueOf(col.getGreen() / normalise))
+ .append(",").append(String.valueOf(col.getBlue() / normalise))
+ .append(" ::").append(resName).append(";");
}
sendAsynchronousCommand(command.toString(), COLOURING_CHIMERA);
/**
* Send the Chimera 'background solid <color>" command.
*
- * @see https
+ * @see https
* ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/background
* .html
* @param col
*/
+ @Override
public void setBackgroundColour(Color col)
{
viewerCommandHistory(false);
double normalise = 255D;
final String command = "background solid " + col.getRed() / normalise
- + "," + col.getGreen() / normalise + "," + col.getBlue()
- / normalise + ";";
+ + "," + col.getGreen() / normalise + ","
+ + col.getBlue() / normalise + ";";
viewer.sendChimeraCommand(command, false);
viewerCommandHistory(true);
}
* features visible in Jalview
*
* @param avp
+ * @return
*/
- public void sendFeaturesToViewer(AlignmentViewPanel avp)
+ public int sendFeaturesToViewer(AlignmentViewPanel avp)
{
// TODO refactor as required to pull up to an interface
AlignmentI alignment = avp.getAlignment();
- FeatureRenderer fr = getFeatureRenderer(avp);
- /*
- * fr is null if feature display is turned off
- */
- if (fr == null)
- {
- return;
- }
-
- String[] files = getPdbFile();
+ String[] files = getStructureFiles();
if (files == null)
{
- return;
+ return 0;
}
StructureMappingcommandSet commandSet = ChimeraCommands
- .getSetAttributeCommandsForFeatures(getSsm(), files,
- getSequence(), fr, alignment);
+ .getSetAttributeCommandsForFeatures(avp, this);
String[] commands = commandSet.commands;
if (commands.length > 10)
{
sendAsynchronousCommand(command, null);
}
}
+ return commands.length;
}
/**
sendAsynchronousCommand("open cmd:" + path, null);
} catch (IOException e)
{
- System.err
- .println("Sending commands to Chimera via file failed with "
- + e.getMessage());
+ System.err.println("Sending commands to Chimera via file failed with "
+ + e.getMessage());
}
}
String cmd = "list residues attr '" + attName + "'";
List<String> residues = sendChimeraCommand(cmd, true);
- /*
- * Expect 0, 1 or more reply lines of the format (chi2 is attName):
- * residue id #0:5.A chi2 -155.000836316 index 5
- * or
- * residue id #0:6.A chi3 None
- *
- * We assume here that attributes on structure do not naturally convert
- * to ranges on sequence, i.e. we just set one feature per mapped position.
- *
- * To conflate positions, would need to first build a map
- * Map<String value, Map<Sequence seq, List<Integer position>>>
- * and then traverse it to find feature ranges.
- */
+
+ boolean featureAdded = createFeaturesForAttributes(attName, residues);
+ if (featureAdded)
+ {
+ alignmentPanel.getFeatureRenderer().featuresAdded();
+ }
+ }
+
+ /**
+ * Create features in Jalview for the given attribute name and structure
+ * residues.
+ *
+ * <pre>
+ * The residue list should be 0, 1 or more reply lines of the format:
+ * residue id #0:5.A isHelix -155.000836316 index 5
+ * or
+ * residue id #0:6.A isHelix None
+ * </pre>
+ *
+ * @param attName
+ * @param residues
+ * @return
+ */
+ protected boolean createFeaturesForAttributes(String attName,
+ List<String> residues)
+ {
boolean featureAdded = false;
String featureGroup = getViewerFeatureGroup();
spec.setPdbFile(pdbFile);
List<AtomSpec> atoms = Collections.singletonList(spec);
- SearchResults sr = getSsm()
+
+ /*
+ * locate the mapped position in the alignment (if any)
+ */
+ SearchResultsI sr = getSsm()
.findAlignmentPositionsForStructurePositions(atoms);
/*
* expect one matched alignment position, or none
* (if the structure position is not mapped)
*/
- for (Match m : sr.getResults())
+ for (SearchResultMatchI m : sr.getResults())
{
SequenceI seq = m.getSequence();
int start = m.getStart();
featureAdded |= seq.addSequenceFeature(sf);
}
}
- if (featureAdded)
- {
- alignmentPanel.getFeatureRenderer().featuresAdded();
- }
+ return featureAdded;
}
/**
return CHIMERA_FEATURE_GROUP;
}
-
public Hashtable<String, String> getChainFile()
{
return chainFile;
}
return -1;
}
+
+ @Override
+ public void showStructures(AlignViewportI av, boolean refocus)
+ {
+ StringBuilder cmd = new StringBuilder(128);
+ cmd.append("~display; ~ribbon;");
+
+ AtomSpecModel model = getShownResidues(av);
+ String atomSpec = ChimeraCommands.getAtomSpec(model, this);
+
+ cmd.append("ribbon ").append(atomSpec);
+ if (!isShowAlignmentOnly())
+ {
+ cmd.append("chain @CA|P; ribbon");
+ }
+ if (refocus)
+ {
+ cmd.append("; focus");
+ }
+ sendChimeraCommand(cmd.toString(), false);
+ }
}