X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fext%2Frbvi%2Fchimera%2FJalviewChimeraBinding.java;fp=src%2Fjalview%2Fext%2Frbvi%2Fchimera%2FJalviewChimeraBinding.java;h=2042ac4f7398df8d0b64b355414c99c016ef6858;hb=4762b29420ed4df01b55a8afe5ab05467aaf41a9;hp=b05c168c50f1649db40a440e68116dabdd09c8c5;hpb=29c183b0d0de9f6f9f94d2b528d10e20b1891684;p=jalview.git diff --git a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java index b05c168..2042ac4 100644 --- a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java +++ b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java @@ -28,6 +28,9 @@ import jalview.bin.Cache; import jalview.datamodel.AlignmentI; import jalview.datamodel.ColumnSelection; import jalview.datamodel.PDBEntry; +import jalview.datamodel.SearchResultMatchI; +import jalview.datamodel.SearchResults; +import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.httpserver.AbstractRequestHandler; import jalview.io.DataSourceType; @@ -40,8 +43,13 @@ import jalview.structures.models.AAStructureBindingModel; import jalview.util.MessageManager; import java.awt.Color; +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.Collections; import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.List; @@ -54,6 +62,8 @@ import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType; 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"; @@ -98,14 +108,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel */ private Map> chimeraMaps = new LinkedHashMap>(); - /* - * the default or current model displayed if the model cannot be identified - * from the selection message - */ - private int frameNo = 0; - - private String lastCommand; - String lastHighlightCommand; /* @@ -304,7 +306,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel chimeraListener.shutdown(); chimeraListener = null; } - lastCommand = null; viewer = null; if (chimeraMonitor != null) @@ -631,31 +632,42 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } /** - * Send a command to Chimera, and optionally log any responses. + * Send a command to Chimera, and optionally log and return any responses. + *

+ * Does nothing, and returns null, if the command is the same as the last one + * sent [why?]. * * @param command - * @param logResponse + * @param getResponse */ - public void sendChimeraCommand(final String command, boolean logResponse) + public List sendChimeraCommand(final String command, + boolean getResponse) { if (viewer == null) { // ? thread running after viewer shut down - return; + return null; } + List reply = null; viewerCommandHistory(false); - if (lastCommand == null || !lastCommand.equals(command)) + if (true /*lastCommand == null || !lastCommand.equals(command)*/) { // trim command or it may never find a match in the replyLog!! List lastReply = viewer.sendChimeraCommand(command.trim(), - logResponse); - if (logResponse && debug) + getResponse); + if (getResponse) { - log("Response from command ('" + command + "') was:\n" + lastReply); + reply = lastReply; + if (debug) + { + log("Response from command ('" + command + "') was:\n" + + lastReply); + } } } viewerCommandHistory(true); - lastCommand = command; + + return reply; } /** @@ -742,6 +754,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel */ private int _modelFileNameMap[]; + // //////////////////////////////// // /StructureListener @Override @@ -837,59 +850,64 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * 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 atomSpecs = convertStructureResiduesToAlignment(selection); + + /* + * Broadcast the selection (which may be empty, if the user just cleared all + * selections) + */ + getSsm().mouseOverStructure(atomSpecs); + } + + /** + * Converts a list of Chimera atomspecs to a list of AtomSpec representing the + * corresponding residues (if any) in Jalview + * + * @param structureSelection + * @return + */ + protected List convertStructureResiduesToAlignment( + List structureSelection) + { List atomSpecs = new ArrayList(); - for (String atomSpec : selection) + 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[frameNo]; - 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)); } - - /* - * Broadcast the selection (which may be empty, if the user just cleared all - * selections) - */ - getSsm().mouseOverStructure(atomSpecs); + return pdbfilename; } private void log(String message) @@ -1036,6 +1054,18 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } /** + * Returns a list of chains mapped in this viewer. Note this list is not + * currently scoped per structure. + * + * @return + */ + @Override + public List getChainNames() + { + return chainNames; + } + + /** * Send a 'focus' command to Chimera to recentre the visible display */ public void focusView() @@ -1071,13 +1101,214 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } } + /** + * Constructs and send commands to Chimera to set attributes on residues for + * features visible in Jalview + * + * @param avp + */ + public void sendFeaturesToViewer(AlignmentViewPanel avp) + { + // TODO refactor as required to pull up to an interface + AlignmentI alignment = avp.getAlignment(); + FeatureRenderer fr = getFeatureRenderer(avp); - @Override - public List getChainNames() + /* + * fr is null if feature display is turned off + */ + if (fr == null) + { + return; + } + + String[] files = getPdbFile(); + if (files == null) + { + return; + } + + StructureMappingcommandSet commandSet = ChimeraCommands + .getSetAttributeCommandsForFeatures(getSsm(), files, + getSequence(), fr, alignment); + String[] commands = commandSet.commands; + if (commands.length > 10) + { + sendCommandsByFile(commands); + } + else + { + for (String command : commands) + { + sendAsynchronousCommand(command, null); + } + } + } + + /** + * 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) { - return chainNames; + try + { + File tmp = File.createTempFile("chim", ".com"); + tmp.deleteOnExit(); + PrintWriter out = new PrintWriter(new FileOutputStream(tmp)); + for (String command : commands) + { + out.println(command); + } + out.flush(); + out.close(); + String path = tmp.getAbsolutePath(); + sendAsynchronousCommand("open cmd:" + path, null); + } catch (IOException e) + { + System.err + .println("Sending commands to Chimera via file failed with " + + e.getMessage()); + } } + /** + * Get Chimera residues which have the named attribute, find the mapped + * positions in the Jalview sequence(s), and set as sequence features + * + * @param attName + * @param alignmentPanel + */ + public void copyStructureAttributesToFeatures(String attName, + AlignmentViewPanel alignmentPanel) + { + // todo pull up to AAStructureBindingModel (and interface?) + + /* + * ask Chimera to list residues with the attribute, reporting its value + */ + // 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 residues = sendChimeraCommand(cmd, true); + + boolean featureAdded = createFeaturesForAttributes(attName, residues); + if (featureAdded) + { + alignmentPanel.getFeatureRenderer().featuresAdded(); + } + } + + /** + * Create features in Jalview for the given attribute name and structure + * residues. + * + *

+   * 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
+   * 
+ * + * @param attName + * @param residues + * @return + */ + protected boolean createFeaturesForAttributes(String attName, + List residues) + { + boolean featureAdded = false; + String featureGroup = getViewerFeatureGroup(); + + for (String residue : residues) + { + AtomSpec spec = null; + String[] tokens = residue.split(" "); + if (tokens.length < 5) + { + continue; + } + String atomSpec = tokens[2]; + String attValue = tokens[4]; + + /* + * ignore 'None' (e.g. for phi) or 'False' (e.g. for isHelix) + */ + if ("None".equalsIgnoreCase(attValue) + || "False".equalsIgnoreCase(attValue)) + { + continue; + } + + try + { + 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 atoms = Collections.singletonList(spec); + + /* + * locate the mapped position in the alignment (if any) + */ + SearchResults 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; + } + + /** + * Answers the feature group name to apply to features created in Jalview from + * Chimera attributes + * + * @return + */ + protected String getViewerFeatureGroup() + { + // todo pull up to interface + return CHIMERA_FEATURE_GROUP; + } + + public Hashtable getChainFile() { return chainFile;