X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fext%2Frbvi%2Fchimera%2FJalviewChimeraBinding.java;h=bc4eef4914cf6a76223326be2b63c4097fbe964a;hb=2fb924ec0d110eb3ca6c3fb06efa27acd34b2750;hp=731ffeaa1c7a0a47d30563f5e0ed4bf325671dce;hpb=9c1a9d682a2664d525bfd0f38bae861292dc3921;p=jalview.git diff --git a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java index 731ffea..bc4eef4 100644 --- a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java +++ b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java @@ -20,32 +20,12 @@ */ package jalview.ext.rbvi.chimera; -import jalview.api.AlignmentViewPanel; -import jalview.api.structures.JalviewStructureDisplayI; -import jalview.bin.Cache; -import jalview.datamodel.AlignmentI; -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.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; - import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.net.BindException; import java.util.ArrayList; -import java.util.BitSet; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; @@ -56,19 +36,29 @@ import ext.edu.ucsf.rbvi.strucviz2.ChimeraManager; import ext.edu.ucsf.rbvi.strucviz2.ChimeraModel; import ext.edu.ucsf.rbvi.strucviz2.StructureManager; import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType; +import jalview.api.AlignmentViewPanel; +import jalview.bin.Cache; +import jalview.datamodel.AlignmentI; +import jalview.datamodel.PDBEntry; +import jalview.datamodel.SearchResultMatchI; +import jalview.datamodel.SearchResultsI; +import jalview.datamodel.SequenceFeature; +import jalview.datamodel.SequenceI; +import jalview.gui.StructureViewer.ViewerType; +import jalview.httpserver.AbstractRequestHandler; +import jalview.io.DataSourceType; +import jalview.structure.AtomSpec; +import jalview.structure.AtomSpecModel; +import jalview.structure.StructureCommand; +import jalview.structure.StructureCommandI; +import jalview.structure.StructureSelectionManager; +import jalview.structures.models.AAStructureBindingModel; 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 boolean debug = false; - - private static final String PHOSPHORUS = "P"; + public static final String CHIMERA_SESSION_EXTENSION = ".py"; - private static final String ALPHACARBON = "CA"; + public static final String CHIMERA_FEATURE_GROUP = "Chimera"; /* * Object through which we talk to Chimera @@ -87,8 +77,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel String lastHighlightCommand; - private Thread chimeraMonitor; - /** * Open a PDB structure file in Chimera and set up mappings from Jalview. * @@ -195,37 +183,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } /** - * 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 = chimeraManager.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 */ @@ -246,299 +203,17 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * Close down the Jalview viewer and listener, and (optionally) the associated * Chimera window. */ + @Override public void closeViewer(boolean closeChimera) { - getSsm().removeStructureViewerListener(this, this.getStructureFiles()); - if (closeChimera) - { - chimeraManager.exitChimera(); - } + super.closeViewer(closeChimera); if (this.chimeraListener != null) { chimeraListener.shutdown(); chimeraListener = null; } + chimeraManager.clearOnChimeraExit(); chimeraManager = null; - - if (chimeraMonitor != null) - { - chimeraMonitor.interrupt(); - } - releaseUIResources(); - } - - /** - * {@inheritDoc} - */ - public String superposeStructures(AlignmentI[] _alignment, - int[] _refStructure, HiddenColumns[] _hiddenCols) - { - StringBuilder allComs = new StringBuilder(128); - String[] files = getStructureFiles(); - - if (!waitForFileLoad(files)) - { - return null; - } - - refreshPdbEntries(); - StringBuilder selectioncom = new StringBuilder(256); - boolean chimeraX = chimeraManager.isChimeraX(); - for (int a = 0; a < _alignment.length; a++) - { - int refStructure = _refStructure[a]; - AlignmentI alignment = _alignment[a]; - HiddenColumns hiddenCols = _hiddenCols[a]; - - if (refStructure >= files.length) - { - System.err.println("Ignoring invalid reference structure value " - + refStructure); - refStructure = -1; - } - - /* - * 'matched' bit i will be set for visible alignment columns i where - * all sequences have a residue with a mapping to the PDB structure - */ - BitSet matched = new BitSet(); - for (int m = 0; m < alignment.getWidth(); 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(alignment.getWidth(), f); - } - - /* - * Calculate the superposable alignment columns ('matched'), and the - * corresponding structure residue positions (structures.pdbResNo) - */ - int candidateRefStructure = findSuperposableResidues(alignment, - matched, structures); - if (refStructure < 0) - { - /* - * If no reference structure was specified, pick the first one that has - * a mapping in the alignment - */ - refStructure = candidateRefStructure; - } - - int nmatched = matched.cardinality(); - if (nmatched < 4) - { - return MessageManager.formatMessage("label.insufficient_residues", - nmatched); - } - - /* - * Generate select statements to select regions to superimpose structures - */ - String[] selcom = new String[files.length]; - for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) - { - final int modelNo = pdbfnum + (chimeraX ? 1 : 0); - // todo correct resolution to model number - String chainCd = "." + structures[pdbfnum].chain; - int lpos = -1; - boolean run = false; - StringBuilder molsel = new StringBuilder(); - if (chimeraX) - { - molsel.append("/" + structures[pdbfnum].chain + ":"); - } - - int nextColumnMatch = matched.nextSetBit(0); - while (nextColumnMatch != -1) - { - int pdbResNum = structures[pdbfnum].pdbResNo[nextColumnMatch]; - if (lpos != pdbResNum - 1) - { - /* - * discontiguous - append last residue now - */ - if (lpos != -1) - { - molsel.append(String.valueOf(lpos)); - if (!chimeraX) - { - molsel.append(chainCd); - } - molsel.append(","); - } - run = false; - } - else - { - /* - * extending a contiguous run - */ - if (!run) - { - /* - * start the range selection - */ - molsel.append(String.valueOf(lpos)); - molsel.append("-"); - } - run = true; - } - lpos = pdbResNum; - nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1); - } - - /* - * and terminate final selection - */ - if (lpos != -1) - { - molsel.append(String.valueOf(lpos)); - if (!chimeraX) - { - molsel.append(chainCd); - } - } - if (molsel.length() > 1) - { - selcom[pdbfnum] = molsel.toString(); - selectioncom.append("#").append(String.valueOf(modelNo)); - if (!chimeraX) - { - selectioncom.append(":"); - } - selectioncom.append(selcom[pdbfnum]); - // selectioncom.append(" "); - if (pdbfnum < files.length - 1) - { - selectioncom.append("|"); - } - } - else - { - selcom[pdbfnum] = null; - } - } - - StringBuilder command = new StringBuilder(256); - for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) - { - final int modelNo = pdbfnum + (chimeraX ? 1 : 0); - if (pdbfnum == refStructure || selcom[pdbfnum] == null - || selcom[refStructure] == null) - { - continue; - } - if (command.length() > 0) - { - command.append(";"); - } - - /* - * Form Chimera match command, from the 'new' structure to the - * 'reference' structure e.g. (50 residues, chain B/A, alphacarbons): - * - * 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 - */ - command.append(chimeraX ? "align " : "match "); - command.append(getModelSpec(modelNo)); - if (!chimeraX) - { - command.append(":"); - } - command.append(selcom[pdbfnum]); - command.append("@").append( - structures[pdbfnum].isRna ? PHOSPHORUS : ALPHACARBON); - // JAL-1757 exclude alternate CA locations - ChimeraX syntax tbd - if (!chimeraX) - { - command.append(NO_ALTLOCS); - } - command.append(chimeraX ? " toAtoms " : " ") - .append(getModelSpec(refStructure + (chimeraX ? 1 : 0))); - if (!chimeraX) - { - command.append(":"); - } - command.append(selcom[refStructure]); - command.append("@").append( - structures[refStructure].isRna ? PHOSPHORUS : ALPHACARBON); - if (!chimeraX) - { - command.append(NO_ALTLOCS); - } - } - if (selectioncom.length() > 0) - { - if (debug) - { - System.out.println("Select regions:\n" + selectioncom.toString()); - System.out.println( - "Superimpose command(s):\n" + command.toString()); - } - // allComs.append("~display all; "); - // if (chimeraX) - // { - // allComs.append("show ").append(selectioncom.toString()) - // .append(" pbonds"); - // } - // else - // { - // allComs.append("chain @CA|P; ribbon "); - // allComs.append(selectioncom.toString()); - // } - if (allComs.length() > 0) { - allComs.append(";"); - } - allComs.append(command.toString()); - } - } - - String error = null; - if (selectioncom.length() > 0) - { - // TODO: visually distinguish regions that were superposed - if (selectioncom.substring(selectioncom.length() - 1).equals("|")) - { - selectioncom.setLength(selectioncom.length() - 1); - } - if (debug) - { - System.out.println("Select regions:\n" + selectioncom.toString()); - } - allComs.append(";~display all; "); - if (chimeraX) - { - allComs.append("show @CA|P pbonds; show ") - .append(selectioncom.toString()).append(" ribbons; view"); - } - else - { - 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"); - List chimeraReplies = executeCommand(allComs.toString(), - true); - for (String reply : chimeraReplies) - { - if (reply.toLowerCase().contains("unequal numbers of atoms")) - { - error = reply; - } - } - } - return error; } /** @@ -589,7 +264,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel boolean launched = chimeraManager.launchChimera(getChimeraPaths()); if (launched) { - startChimeraProcessMonitor(); + startExternalViewerMonitor(chimeraManager.getChimeraProcess()); } else { @@ -614,7 +289,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * * @return */ - public boolean isChimeraRunning() + @Override + public boolean isViewerRunning() { return chimeraManager.isChimeraLaunched(); } @@ -626,7 +302,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * @param getResponse */ @Override - public List executeCommand(final String command, + public List executeCommand(final StructureCommandI command, boolean getResponse) { if (chimeraManager == null || command == null) @@ -636,43 +312,19 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } List reply = null; // trim command or it may never find a match in the replyLog!! + String cmd = command.getCommand().trim(); List lastReply = chimeraManager - .sendChimeraCommand(command.trim(), getResponse); + .sendChimeraCommand(cmd, getResponse); if (getResponse) { reply = lastReply; - if (debug) - { - log("Response from command ('" + command + "') was:\n" + lastReply); - } + Cache.log.debug( + "Response from command ('" + cmd + "') was:\n" + lastReply); } return reply; } - /** - * @param command - */ - protected void executeWhenReady(String command) - { - waitForChimera(); - executeCommand(command, false); - waitForChimera(); - } - - private void waitForChimera() - { - while (chimeraManager != null && chimeraManager.isBusy()) - { - try - { - Thread.sleep(15); - } catch (InterruptedException q) - { - } - } - } - @Override public synchronized String[] getStructureFiles() { @@ -806,7 +458,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel atomSpecs.add(spec); } catch (IllegalArgumentException e) { - System.err.println("Failed to parse atomspec: " + atomSpec); + Cache.log.error("Failed to parse atomspec: " + atomSpec); } } return atomSpecs; @@ -842,55 +494,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } /** - * Ask Chimera to save its session to the given file. Returns true if - * successful, else false. - * - * @param filepath - * @return - */ - public boolean saveSession(String filepath) - { - if (isChimeraRunning()) - { - /* - * 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 = getCommandGenerator().saveSession(filepath); - List reply = chimeraManager.sendChimeraCommand(command, true); - if (reply.contains("Session written")) - { - return true; - } - else - { - Cache.log - .error("Error saving Chimera session: " + reply.toString()); - } - } - return false; - } - - /** - * 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. - * - * @param filepath - * @return - */ - public boolean openSession(String filepath) - { - /* - * Chimera: https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/open.html - * ChimeraX: https://www.cgl.ucsf.edu/chimerax/docs/user/commands/open.html - */ - executeCommand("open " + filepath, true); - // todo: test for failure - how? - return true; - } - - /** * Send a 'show' command for all atoms in the currently selected columns * * TODO: pull up to abstract structure viewer interface @@ -920,7 +523,11 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel /** * Constructs and send commands to Chimera to set attributes on residues for - * features visible in Jalview + * features visible in Jalview. + *

+ * The syntax is: setattr r <attName> <attValue> <atomSpec> + *

+ * For example: setattr r jv_chain "Ferredoxin-1, Chloroplastic" #0:94.A * * @param avp * @return @@ -928,26 +535,23 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel public int sendFeaturesToViewer(AlignmentViewPanel avp) { // TODO refactor as required to pull up to an interface - String[] files = getStructureFiles(); - if (files == null) - { - return 0; - } - String[] commands = getCommandGenerator() - .setAttributesForFeatures(getSsm(), files, getSequence(), avp); - if (commands.length > 10) + Map> featureValues = buildFeaturesMap( + avp); + List commands = getCommandGenerator() + .setAttributes(featureValues); + if (commands.size() > 10) { sendCommandsByFile(commands); } else { - for (String command : commands) + for (StructureCommandI command : commands) { sendAsynchronousCommand(command, null); } } - return commands.length; + return commands.size(); } /** @@ -957,21 +561,22 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * * @param commands */ - protected void sendCommandsByFile(String[] commands) + protected void sendCommandsByFile(List commands) { try { File tmp = File.createTempFile("chim", getCommandFileExtension()); tmp.deleteOnExit(); PrintWriter out = new PrintWriter(new FileOutputStream(tmp)); - for (String command : commands) + for (StructureCommandI command : commands) { - out.println(command); + out.println(command.getCommand()); } out.flush(); out.close(); String path = tmp.getAbsolutePath(); - String command = getCommandGenerator().openCommandFile(path); + StructureCommandI command = getCommandGenerator() + .openCommandFile(path); sendAsynchronousCommand(command, null); } catch (IOException e) { @@ -1011,7 +616,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel // fails for 'average.bfactor' (which is bad): String cmd = "list residues attr '" + attName + "'"; - List residues = executeCommand(cmd, true); + List residues = executeCommand(new StructureCommand(cmd), true); boolean featureAdded = createFeaturesForAttributes(attName, residues); if (featureAdded) @@ -1126,14 +731,14 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } @Override - public int getModelNoForFile(String pdbFile) + public String getModelIdForFile(String pdbFile) { List foundModels = chimeraMaps.get(pdbFile); if (foundModels != null && !foundModels.isEmpty()) { - return foundModels.get(0).getModelNumber(); + return String.valueOf(foundModels.get(0).getModelNumber()); } - return -1; + return ""; } /** @@ -1160,15 +765,17 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } /** - * Returns the file extension to use for a saved viewer session file + * Returns the file extension to use for a saved viewer session file (.py) * * @return */ + @Override public String getSessionFileExtension() { - return ".py"; + return CHIMERA_SESSION_EXTENSION; } + @Override public String getHelpURL() { return "https://www.cgl.ucsf.edu/chimera/docs/UsersGuide";