package jalview.ext.rbvi.chimera;
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.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.HashMap;
+import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
public abstract class JalviewChimeraBinding extends AAStructureBindingModel
{
+ public static final String CHIMERA_FEATURE_GROUP = "Chimera";
+
// Chimera clause to exclude alternate locations in atom selection
private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9";
private static final String ALPHACARBON = "CA";
+ private List<String> chainNames = new ArrayList<>();
+
+ private Hashtable<String, String> chainFile = new Hashtable<>();
+
/*
* 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.
*
- * We check if the PDB model id is already loaded in Chimera, if so don't
- * reopen it. This is the case if Chimera has opened a saved session file.
+ * We check if the PDB model id is already loaded in Chimera, if so don't reopen
+ * it. This is the case if Chimera has opened a saved session file.
*
* @param pe
* @return
String file = pe.getFile();
try
{
- List<ChimeraModel> modelsToMap = new ArrayList<ChimeraModel>();
- List<ChimeraModel> oldList = viewer.getModelList();
+ List<ChimeraModel> modelsToMap = new ArrayList<>();
+ List<ChimeraModel> oldList = viewer.isChimeraX() ? new ArrayList<>()
+ : viewer.getModelList();
boolean alreadyOpen = false;
/*
if (!alreadyOpen)
{
viewer.openModel(file, pe.getId(), ModelType.PDB_MODEL);
- List<ChimeraModel> newList = viewer.getModelList();
- // JAL-1728 newList.removeAll(oldList) does not work
- for (ChimeraModel cm : newList)
+ if (viewer.isChimeraX())
+ {
+ /*
+ * ChimeraX hack: force chimera model name to pdbId
+ */
+ int modelNumber = chimeraMaps.size() + 1;
+ String command = "setattr #" + modelNumber + " models name "
+ + pe.getId();
+ sendChimeraCommand(command, false);
+ modelsToMap.add(new ChimeraModel(pe.getId(), ModelType.PDB_MODEL,
+ modelNumber, 0));
+ }
+ else
{
- if (cm.getModelName().equals(pe.getId()))
+ /*
+ * Chimera: query for actual models and find the one with
+ * matching model name - set in viewer.openModel()
+ */
+ List<ChimeraModel> newList = viewer.getModelList();
+ // JAL-1728 newList.removeAll(oldList) does not work
+ for (ChimeraModel cm : newList)
{
- modelsToMap.add(cm);
+ if (cm.getModelName().equals(pe.getId()))
+ {
+ modelsToMap.add(cm);
+ }
}
}
}
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 ssm
* @param pdbentry
* @param sequenceIs
- * @param chains
* @param protocol
*/
public JalviewChimeraBinding(StructureSelectionManager ssm,
- PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
- String protocol)
+ PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
+ DataSourceType protocol)
{
- super(ssm, pdbentry, sequenceIs, chains, protocol);
- viewer = new ChimeraManager(
- new ext.edu.ucsf.rbvi.strucviz2.StructureManager(true));
+ super(ssm, pdbentry, sequenceIs, protocol);
+ viewer = new ChimeraManager(new StructureManager(true));
}
/**
- * Start a dedicated HttpServer to listen for Chimera notifications, and tell
- * it to start listening
+ * 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();
+ }
+
+ /**
+ * Start a dedicated HttpServer to listen for Chimera notifications, and tell it
+ * to start listening
*/
public void startChimeraListener()
{
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
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(":.").append(chain);
+ cmd.append(showChainCmd);
first = false;
}
* window, but it looks more helpful not to (easier to relate chains to the
* whole)
*/
- final String command = "~display #*; ~ribbon #*; ribbon "
+ final String command = "~display #*; ~ribbon #*; ribbon :"
+ cmd.toString();
sendChimeraCommand(command, false);
}
*/
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);
}
/*
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())
.append(";" + command.toString());
}
}
+
+ String error = null;
if (selectioncom.length() > 0)
{
// TODO: visually distinguish regions that were superposed
}
allComs.append("; ~display all; chain @CA|P; ribbon ")
.append(selectioncom.toString()).append("; focus");
- sendChimeraCommand(allComs.toString(), false);
+ List<String> chimeraReplies = sendChimeraCommand(allComs.toString(),
+ true);
+ for (String reply : chimeraReplies)
+ {
+ if (reply.toLowerCase().contains("unequal numbers of atoms"))
+ {
+ error = reply;
+ }
+ }
}
-
+ return error;
}
/**
* 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" : "");
}
/**
* 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 colourBySequenceCommands
*/
- public void colourBySequence(boolean showFeatures,
- jalview.api.AlignmentViewPanel alignmentv)
+ @Override
+ protected void colourBySequence(
+ StructureMappingcommandSet[] colourBySequenceCommands)
{
- if (!colourBySequence || !loadingFinished)
+ for (StructureMappingcommandSet cpdbbyseq : colourBySequenceCommands)
{
- return;
- }
- if (getSsm() == null)
- {
- return;
- }
- String[] files = getPdbFile();
-
- SequenceRenderer sr = getSequenceRenderer(alignmentv);
-
- FeatureRenderer fr = null;
- if (showFeatures)
- {
- fr = getFeatureRenderer(alignmentv);
+ for (String command : cpdbbyseq.commands)
+ {
+ sendAsynchronousCommand(command, COLOURING_CHIMERA);
+ }
}
- AlignmentI alignment = alignmentv.getAlignment();
+ }
- StructureMappingcommandSet colourBySequenceCommands = ChimeraCommands
- .getColourBySequenceCommand(getSsm(), files, getSequence(), sr,
- fr, alignment);
- for (String command : colourBySequenceCommands.commands)
- {
- sendAsynchronousCommand(command, COLOURING_CHIMERA);
- }
+ /**
+ * @param files
+ * @param sr
+ * @param viewPanel
+ * @return
+ */
+ @Override
+ protected StructureMappingcommandSet[] getColourBySequenceCommands(
+ String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel)
+ {
+ return ChimeraCommands.getColourBySequenceCommand(getSsm(), files,
+ getSequence(), sr, viewPanel, viewer.isChimeraX());
}
/**
// //////////////////////////
/**
- * 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();
- private int getModelNum(String modelFileName)
- {
- String[] mfn = getPdbFile();
- if (mfn == null)
- {
- return -1;
- }
- for (int i = 0; i < mfn.length; i++)
- {
- if (mfn[i].equalsIgnoreCase(modelFileName))
- {
- return i;
- }
- }
- return -1;
- }
-
/**
* 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 -
// ////////////////////////////////
// /StructureListener
@Override
- public synchronized String[] getPdbFile()
+ public synchronized String[] getStructureFiles()
{
if (viewer == null)
{
return new String[0];
}
- // if (modelFileNames == null)
- // {
- // Collection<ChimeraModel> chimodels = viewer.getChimeraModels();
- // _modelFileNameMap = new int[chimodels.size()];
- // int j = 0;
- // for (ChimeraModel chimodel : chimodels)
- // {
- // String mdlName = chimodel.getModelName();
- // }
- // modelFileNames = new String[j];
- // // System.arraycopy(mset, 0, modelFileNames, 0, j);
- // }
-
- 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);
+ return chimeraMaps.keySet()
+ .toArray(modelFileNames = new String[chimeraMaps.size()]);
+ }
/**
- * 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.
+ * 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.
*/
@Override
public void highlightAtoms(List<AtomSpec> atoms)
return;
}
+ boolean forChimeraX = viewer.isChimeraX();
StringBuilder cmd = new StringBuilder(128);
boolean first = true;
boolean found = false;
{
if (first)
{
- cmd.append("rlabel #").append(cms.get(0).getModelNumber())
- .append(":");
+ cmd.append(forChimeraX ? "label #" : "rlabel #");
}
else
{
cmd.append(",");
}
first = false;
- cmd.append(pdbResNum);
- if (!chain.equals(" "))
+ if (forChimeraX)
{
- cmd.append(".").append(chain);
+ cmd.append(cms.get(0).getModelNumber())
+ .append("/").append(chain).append(":").append(pdbResNum);
+ }
+ else
+ {
+ cmd.append(cms.get(0).getModelNumber())
+ .append(":").append(pdbResNum);
+ if (!chain.equals(" ") && !forChimeraX)
+ {
+ cmd.append(".").append(chain);
+ }
}
found = true;
}
* 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)
{
- int colonPos = atomSpec.indexOf(":");
- if (colonPos == -1)
- {
- continue; // malformed
- }
-
- int hashPos = atomSpec.indexOf("#");
- String modelSubmodel = atomSpec.substring(hashPos + 1, colonPos);
- int dotPos = modelSubmodel.indexOf(".");
- int modelId = 0;
try
{
- modelId = Integer.valueOf(dotPos == -1 ? modelSubmodel
- : modelSubmodel.substring(0, dotPos));
- } catch (NumberFormatException e)
+ AtomSpec spec = AtomSpec.fromChimeraAtomspec(atomSpec);
+ String pdbfilename = getPdbFileForModel(spec.getModelNumber());
+ spec.setPdbFile(pdbfilename);
+ atomSpecs.add(spec);
+ } catch (IllegalArgumentException e)
{
- // ignore, default to model 0
+ System.err.println("Failed to parse atomspec: " + atomSpec);
}
+ }
+ return atomSpecs;
+ }
- String residueChain = atomSpec.substring(colonPos + 1);
- dotPos = residueChain.indexOf(".");
- int pdbResNum = Integer.parseInt(dotPos == -1 ? residueChain
- : residueChain.substring(0, dotPos));
-
- String chainId = dotPos == -1 ? "" : residueChain
- .substring(dotPos + 1);
-
- /*
- * Work out the pdbfilename from the model number
- */
- String pdbfilename = modelFileNames[0];
- findfileloop: for (String pdbfile : this.chimeraMaps.keySet())
+ /**
+ * @param modelId
+ * @return
+ */
+ protected String getPdbFileForModel(int modelId)
+ {
+ /*
+ * Work out the pdbfilename from the model number
+ */
+ String pdbfilename = modelFileNames[0];
+ findfileloop: for (String pdbfile : this.chimeraMaps.keySet())
+ {
+ for (ChimeraModel cm : chimeraMaps.get(pdbfile))
{
- for (ChimeraModel cm : chimeraMaps.get(pdbfile))
+ if (cm.getModelNumber() == modelId)
{
- if (cm.getModelNumber() == modelId)
- {
- pdbfilename = pdbfile;
- break findfileloop;
- }
+ pdbfilename = pdbfile;
+ break findfileloop;
}
}
- atomSpecs.add(new AtomSpec(pdbfilename, chainId, pdbResNum, 0));
}
- return atomSpecs;
+ return pdbfilename;
}
private void log(String message)
return loadNotifiesHandled;
}
+ @Override
public void setJalviewColourScheme(ColourSchemeI cs)
{
colourBySequence = false;
List<String> residueSet = ResidueProperties.getResidues(isNucleotide(),
false);
- for (String res : residueSet)
+ for (String resName : residueSet)
{
- Color col = cs.findColour(res.charAt(0));
+ char res = resName.length() == 3
+ ? ResidueProperties.getSingleCharacterCode(resName)
+ : resName.charAt(0);
+ Color col = cs.findColour(res, 0, null, null, 0f);
command.append("color " + col.getRed() / normalise + ","
- + col.getGreen() / normalise + "," + col.getBlue()
- / normalise + " ::" + res + ";");
+ + col.getGreen() / normalise + "," + col.getBlue() / normalise
+ + " ::" + resName + ";");
}
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);
}
*
* @return
*/
+ @Override
public List<String> getChainNames()
{
- List<String> names = new ArrayList<String>();
- String[][] allNames = getChains();
- if (allNames != null)
- {
- for (String[] chainsForPdb : allNames)
- {
- if (chainsForPdb != null)
- {
- for (String chain : chainsForPdb)
- {
- if (chain != null && !names.contains(chain))
- {
- names.add(chain);
- }
- }
- }
- }
- }
- return names;
+ return chainNames;
}
/**
* 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);
+ getSequence(), avp, viewer.isChimeraX());
String[] commands = commandSet.commands;
if (commands.length > 10)
{
sendAsynchronousCommand(command, null);
}
}
+ return commands.length;
}
/**
- * Write commands to a temporary file, and send a command to Chimera to open
- * the file as a commands script. For use when sending a large number of
- * separate commands would overload the REST interface mechanism.
+ * Write commands to a temporary file, and send a command to Chimera to open the
+ * file as a commands script. For use when sending a large number of separate
+ * commands would overload the REST interface mechanism.
*
* @param commands
*/
protected void sendCommandsByFile(String[] commands)
{
+ boolean toChimeraX = viewer.isChimeraX();
try
{
- File tmp = File.createTempFile("chim", ".com");
+ File tmp = File.createTempFile("chim", toChimeraX ? ".cxc" : ".com");
tmp.deleteOnExit();
PrintWriter out = new PrintWriter(new FileOutputStream(tmp));
for (String command : commands)
out.flush();
out.close();
String path = tmp.getAbsolutePath();
- sendAsynchronousCommand("open cmd:" + path, null);
+ String command = "open " + (toChimeraX ? "" : "cmd:") + path;
+ sendAsynchronousCommand(command, 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());
}
}
AlignmentViewPanel alignmentPanel)
{
// todo pull up to AAStructureBindingModel (and interface?)
-
+
/*
* ask Chimera to list residues with the attribute, reporting its value
*/
- String cmd = "list residues spec ':*/" + attName + "' attr " + attName;
+ // this alternative command
+ // list residues spec ':*/attName' attr attName
+ // doesn't report 'None' values (which is good), but
+ // fails for 'average.bfactor' (which is bad):
+
+ String cmd = "list residues attr '" + attName + "'";
List<String> residues = sendChimeraCommand(cmd, true);
- /*
- * TODO check if Jalview already has this feature name, if so give it a
- * distinguishing prefix e.g. chim_
- */
- FeatureRenderer fr = alignmentPanel.getFeatureRenderer();
- fr.getFeaturesDisplayed().getVisibleFeatureCount();
+ 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();
- /*
- * Expect 0, 1 or more reply lines of the format (chi2 is attName):
- * residue id #0:5.A chi2 -155.000836316 index 5
- */
- Map<String, Map<SequenceI, List<Integer>>> attsMap = new HashMap<String, Map<SequenceI, List<Integer>>>();
for (String residue : residues)
{
+ AtomSpec spec = null;
String[] tokens = residue.split(" ");
- if (tokens.length > 4)
+ if (tokens.length < 5)
{
- String atomSpec = tokens[2];
- String attValue = tokens[4];
- // TODO find mapping of atomspec to Jalview residue if any
- // build a map of { attValue, Map<SequenceI, List<Integer>> }
- // sort the integer lists
- // and create a feature for each contiguous integer range
- SequenceI seq = null; // mapped-to sequence
- int seqPos = 0; // mapped-to sequence position
+ continue;
+ }
+ String atomSpec = tokens[2];
+ String attValue = tokens[4];
- /*
- * record attribute value / sequence / sequence position
- */
- Map<SequenceI, List<Integer>> seqMap = attsMap.get(attValue);
- if (seqMap == null)
- {
- seqMap = new HashMap<SequenceI, List<Integer>>();
- attsMap.put(attValue, seqMap);
- }
- List<Integer> seqPositions = seqMap.get(seq);
- if (seqPositions == null)
- {
- seqPositions = new ArrayList<Integer>();
- seqMap.put(seq, seqPositions);
- }
- seqPositions.add(seqPos);
+ /*
+ * ignore 'None' (e.g. for phi) or 'False' (e.g. for isHelix)
+ */
+ if ("None".equalsIgnoreCase(attValue)
+ || "False".equalsIgnoreCase(attValue))
+ {
+ continue;
}
- }
- /*
- * traverse values and sequences
- */
- for (String val : attsMap.keySet())
- {
- for (SequenceI seq : attsMap.get(val).keySet())
+ try
{
- List<Integer> positions = attsMap.get(val).get(seq);
- Collections.sort(positions);
- // TODO find reusable code that compacts the list
- List<int[]> ranges = null;// compacted list
- for (int[] range : ranges)
- {
- float score = Float.NaN;
- try
- {
- score = Float.valueOf(val);
- } catch (NumberFormatException e)
- {
- // was not a float value
- }
- String featureGroup = getViewerFeatureGroup();
- SequenceFeature sf = new SequenceFeature(attName, val, range[0],
- range[1], score, featureGroup);
- // note: repeating the action shouldn't duplicate features
- seq.addSequenceFeature(sf);
- }
+ spec = AtomSpec.fromChimeraAtomspec(atomSpec);
+ } catch (IllegalArgumentException e)
+ {
+ System.err.println("Problem parsing atomspec " + atomSpec);
+ continue;
+ }
+
+ String chainId = spec.getChain();
+ String description = attValue;
+ float score = Float.NaN;
+ try
+ {
+ score = Float.valueOf(attValue);
+ description = chainId;
+ } catch (NumberFormatException e)
+ {
+ // was not a float value
+ }
+
+ String pdbFile = getPdbFileForModel(spec.getModelNumber());
+ spec.setPdbFile(pdbFile);
+
+ List<AtomSpec> atoms = Collections.singletonList(spec);
+
+ /*
+ * 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 (SearchResultMatchI m : sr.getResults())
+ {
+ SequenceI seq = m.getSequence();
+ int start = m.getStart();
+ int end = m.getEnd();
+ SequenceFeature sf = new SequenceFeature(attName, description,
+ start, end, score, featureGroup);
+ // todo: should SequenceFeature have an explicit property for chain?
+ // note: repeating the action shouldn't duplicate features
+ featureAdded |= seq.addSequenceFeature(sf);
}
}
+ return featureAdded;
}
/**
protected String getViewerFeatureGroup()
{
// todo pull up to interface
- return "Chimera";
+ return CHIMERA_FEATURE_GROUP;
+ }
+
+ public Hashtable<String, String> getChainFile()
+ {
+ return chainFile;
+ }
+
+ public List<ChimeraModel> getChimeraModelByChain(String chain)
+ {
+ return chimeraMaps.get(chainFile.get(chain));
+ }
+
+ public int getModelNoForChain(String chain)
+ {
+ List<ChimeraModel> foundModels = getChimeraModelByChain(chain);
+ if (foundModels != null && !foundModels.isEmpty())
+ {
+ return foundModels.get(0).getModelNumber();
+ }
+ return -1;
}
}