X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fext%2Fjmol%2FPDBFileWithJmol.java;h=49cb6d55b587abd19d55c17b47f6f3d378cdc814;hb=d8d34047253ad28418f5478d9185a9b840114072;hp=92dce3632f552208517b2ebb873255113e2753a2;hpb=f8cde294a098c1f729b8c7e95c7e238a17cd0581;p=jalview.git diff --git a/src/jalview/ext/jmol/PDBFileWithJmol.java b/src/jalview/ext/jmol/PDBFileWithJmol.java index 92dce36..49cb6d5 100644 --- a/src/jalview/ext/jmol/PDBFileWithJmol.java +++ b/src/jalview/ext/jmol/PDBFileWithJmol.java @@ -1,6 +1,6 @@ /* - * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2) - * Copyright (C) 2014 The Jalview Authors + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * @@ -28,22 +28,28 @@ import jalview.datamodel.SequenceI; import jalview.io.AlignFile; import jalview.io.FileParse; import jalview.schemes.ResidueProperties; +import jalview.util.Comparison; import jalview.util.MessageManager; import java.io.IOException; +import java.util.ArrayList; import java.util.Hashtable; +import java.util.List; import java.util.Map; +import javajs.awt.Dimension; + import org.jmol.api.JmolStatusListener; import org.jmol.api.JmolViewer; -import org.jmol.constant.EnumCallback; +import org.jmol.c.CBK; +import org.jmol.c.STR; import org.jmol.modelset.Group; import org.jmol.modelset.Model; import org.jmol.modelset.ModelSet; -import org.jmol.modelset.Polymer; +import org.jmol.modelsetbio.BioModel; import org.jmol.modelsetbio.BioPolymer; +import org.jmol.modelsetbio.Monomer; import org.jmol.viewer.Viewer; -import org.openscience.jmol.app.JmolApp; /** * Import and process PDB files with Jmol @@ -54,9 +60,6 @@ import org.openscience.jmol.app.JmolApp; public class PDBFileWithJmol extends AlignFile implements JmolStatusListener { - - JmolApp jmolApp = null; - Viewer viewer = null; public PDBFileWithJmol(String inFile, String type) throws IOException @@ -71,7 +74,6 @@ public class PDBFileWithJmol extends AlignFile implements public PDBFileWithJmol() { - // TODO Auto-generated constructor stub } /** @@ -82,23 +84,18 @@ public class PDBFileWithJmol extends AlignFile implements private Viewer getJmolData() { if (viewer == null) - { // note that -o -n -x are all implied - jmolApp = new JmolApp(); - jmolApp.isDataOnly = true; - jmolApp.haveConsole = false; - jmolApp.haveDisplay = false; - jmolApp.exitUponCompletion = true; + { try { viewer = (Viewer) JmolViewer.allocateViewer(null, null, null, null, - null, jmolApp.commandOptions, this); - viewer.setScreenDimension(jmolApp.startupWidth, - jmolApp.startupHeight); - jmolApp.startViewer(viewer, null); + null, "-x -o -n", this); + // ensure the 'new' (DSSP) not 'old' (Ramachandran) SS method is used + viewer.setBooleanProperty("defaultStructureDSSP", true); } catch (ClassCastException x) { - throw new Error(MessageManager.formatMessage("error.jmol_version_not_compatible_with_jalview_version", new String[]{JmolViewer.getJmolVersion()}), - x); + throw new Error(MessageManager.formatMessage( + "error.jmol_version_not_compatible_with_jalview_version", + new String[] { JmolViewer.getJmolVersion() }), x); } } return viewer; @@ -118,197 +115,95 @@ public class PDBFileWithJmol extends AlignFile implements } } - /* - * (non-Javadoc) + /** + * Convert Jmol's secondary structure code to Jalview's, and stored it in the + * secondary structure arrays at the given sequence position * - * @see jalview.io.AlignFile#parse() + * @param proteinStructureSubType + * @param pos + * @param secstr + * @param secstrcode */ - @Override - public void parse() throws IOException + protected void setSecondaryStructure(STR proteinStructureSubType, + int pos, char[] secstr, char[] secstrcode) { - Viewer jmd = getJmolData(); - jmd.openReader(getDataName(), getDataName(), getReader()); - waitForScript(jmd); - if (jmd.getModelCount() > 0) + switch (proteinStructureSubType) { - ModelSet ms = jmd.getModelSet(); - String structs = ms.calculateStructures(null, true, false, true); - // System.out.println("Structs\n"+structs); - for (Model model : ms.getModels()) + case HELIX310: + if (secstr[pos] == 0) { - for (int _bp = 0, _bpc = model.getBioPolymerCount(); _bp < _bpc; _bp++) - { - Polymer bp = model.getBioPolymer(_bp); - if (bp instanceof BioPolymer) - { - BioPolymer biopoly = (BioPolymer) bp; - char _lastChainId = 0; - int[] groups = biopoly.getLeadAtomIndices(); - Group[] bpgrp = biopoly.getGroups(); - char seq[] = new char[groups.length], secstr[] = new char[groups.length], secstrcode[] = new char[groups.length]; - int groupc = 0, len = 0, firstrnum = 1, lastrnum = 0; - do - { - if (groupc >= groups.length - || ms.atoms[groups[groupc]].getChainID() != _lastChainId) - { - if (len > 0) - { - boolean isNa = (biopoly.isDna() || biopoly.isRna()); - // normalise sequence from Jmol to jalview - int[] cinds = isNa ? ResidueProperties.nucleotideIndex : ResidueProperties.aaIndex; - int nonGap = isNa ? ResidueProperties.maxNucleotideIndex - : ResidueProperties.maxProteinIndex; - char ngc = 'X'; - char newseq[] = new char[len]; - Annotation asecstr[] = new Annotation[len+firstrnum-1]; - for (int p = 0; p < len; p++) - { - newseq[p] = cinds[seq[p]] == nonGap ? ngc : seq[p]; - if (secstr[p] >= 'A' && secstr[p] <= 'z') - { - asecstr[p] = new Annotation("" + secstr[p], null, - secstrcode[p], Float.NaN); - } - } - SequenceI sq = new Sequence("" + getDataName() + "|" - + model.getModelTitle() + "|" + _lastChainId, - newseq, firstrnum, lastrnum); - PDBEntry pdbe = new PDBEntry(); - pdbe.setFile(getDataName()); - pdbe.setId(getDataName()); - sq.addPDBId(pdbe); - pdbe.setProperty(new Hashtable()); - pdbe.getProperty().put("CHAIN", "" + _lastChainId); - // JAL-1533 - // Need to put the number of models for this polymer somewhere for Chimera/others to grab - // pdbe.getProperty().put("PDBMODELS", biopoly.) - seqs.add(sq); - if (!isNa) - { - String mt = model.getModelTitle() == null ? getDataName() - : model.getModelTitle(); - if (_lastChainId >= ' ') - { - mt += _lastChainId; - } - AlignmentAnnotation ann = new AlignmentAnnotation( - "Secondary Structure", - "Secondary Structure for " + mt, asecstr); - ann.belowAlignment=true; - ann.visible=true; - ann.autoCalculated=false; - ann.setCalcId(getClass().getName()); - sq.addAlignmentAnnotation(ann); - ann.adjustForAlignment(); - ann.validateRangeAndDisplay(); - annotations.add(ann); - } - } - len = 0; - firstrnum = 1; - lastrnum = 0; - } - if (groupc < groups.length) - { - if (len == 0) - { - firstrnum = bpgrp[groupc].getResno(); - _lastChainId = bpgrp[groupc].getChainID(); - } - else - { - lastrnum = bpgrp[groupc].getResno(); - } - seq[len] = bpgrp[groupc].getGroup1(); - switch (bpgrp[groupc].getProteinStructureSubType()) - { - case HELIX_310: - if (secstr[len] == 0) - { - secstr[len] = '3'; - } - case HELIX_ALPHA: - if (secstr[len] == 0) - { - secstr[len] = 'H'; - } - case HELIX_PI: - if (secstr[len] == 0) - { - secstr[len] = 'P'; - } - case HELIX: - if (secstr[len] == 0) - { - secstr[len] = 'H'; - } - secstrcode[len] = 'H'; - break; - case SHEET: - secstr[len] = 'E'; - secstrcode[len] = 'E'; - break; - default: - secstr[len] = 0; - secstrcode[len] = 0; - } - len++; - } - } while (groupc++ < groups.length); - - } - } + secstr[pos] = '3'; } - - /* - * lastScriptTermination = -9465; String dsspOut = - * jmd.evalString("calculate STRUCTURE"); if (dsspOut.equals("pending")) { - * while (lastScriptTermination == -9465) { try { Thread.sleep(50); } - * catch (Exception x) { } ; } } System.out.println(lastConsoleEcho); - */ + case HELIXALPHA: + if (secstr[pos] == 0) + { + secstr[pos] = 'H'; + } + case HELIXPI: + if (secstr[pos] == 0) + { + secstr[pos] = 'P'; + } + case HELIX: + if (secstr[pos] == 0) + { + secstr[pos] = 'H'; + } + secstrcode[pos] = 'H'; + break; + case SHEET: + secstr[pos] = 'E'; + secstrcode[pos] = 'E'; + break; + default: + secstr[pos] = 0; + secstrcode[pos] = 0; } } - /* - * (non-Javadoc) + /** + * Convert any non-standard peptide codes to their standard code table + * equivalent. (Initial version only does Selenomethionine MSE->MET.) * - * @see jalview.io.AlignFile#print() + * @param threeLetterCode + * @param seq + * @param pos + */ + protected void replaceNonCanonicalResidue(String threeLetterCode, + char[] seq, int pos) + { + String canonical = ResidueProperties + .getCanonicalAminoAcid(threeLetterCode); + if (canonical != null && !canonical.equalsIgnoreCase(threeLetterCode)) + { + seq[pos] = ResidueProperties.getSingleCharacterCode(canonical); + } + } + + /** + * Not implemented - returns null */ @Override public String print() { - // TODO Auto-generated method stub return null; } + /** + * Not implemented + */ @Override public void setCallbackFunction(String callbackType, String callbackFunction) { - // TODO Auto-generated method stub - } - /* - * @Override public void notifyCallback(EnumCallback type, Object[] data) { - * try { switch (type) { case ERROR: case SCRIPT: - * notifyScriptTermination((String) data[2], ((Integer) data[3]).intValue()); - * break; case MESSAGE: sendConsoleMessage((data == null) ? ((String) null) : - * (String) data[1]); break; case LOADSTRUCT: notifyFileLoaded((String) - * data[1], (String) data[2], (String) data[3], (String) data[4], ((Integer) - * data[5]).intValue()); - * - * break; default: // System.err.println("Unhandled callback " + type + " " // - * + data[1].toString()); break; } } catch (Exception e) { - * System.err.println("Squashed Jmol callback handler error:"); - * e.printStackTrace(); } } - */ - public void notifyCallback(EnumCallback type, Object[] data) + @Override + public void notifyCallback(CBK cbType, Object[] data) { String strInfo = (data == null || data[1] == null ? null : data[1] .toString()); - switch (type) + switch (cbType) { case ECHO: sendConsoleEcho(strInfo); @@ -342,13 +237,6 @@ public class PDBFileWithJmol extends AlignFile implements } } - private void notifyFileLoaded(String string, String string2, - String string3, String string4, int intValue) - { - // TODO Auto-generated method stub - - } - String lastConsoleEcho = ""; private void sendConsoleEcho(String string) @@ -377,7 +265,7 @@ public class PDBFileWithJmol extends AlignFile implements } @Override - public boolean notifyEnabled(EnumCallback callbackPick) + public boolean notifyEnabled(CBK callbackPick) { switch (callbackPick) { @@ -387,66 +275,397 @@ public class PDBFileWithJmol extends AlignFile implements case LOADSTRUCT: case ERROR: return true; - case MEASURE: - case PICK: - case HOVER: - case RESIZE: - case SYNC: - case CLICK: - case ANIMFRAME: - case MINIMIZATION: + default: + return false; } - return false; } + /** + * Not implemented - returns null + */ @Override public String eval(String strEval) { - // TODO Auto-generated method stub return null; } + /** + * Not implemented - returns null + */ @Override public float[][] functionXY(String functionName, int x, int y) { - // TODO Auto-generated method stub return null; } + /** + * Not implemented - returns null + */ @Override public float[][][] functionXYZ(String functionName, int nx, int ny, int nz) { - // TODO Auto-generated method stub return null; } + /** + * Not implemented - returns null + */ @Override - public String createImage(String fileName, String type, + public String createImage(String fileName, String imageType, Object text_or_bytes, int quality) { - // TODO Auto-generated method stub return null; } + /** + * Not implemented - returns null + */ @Override public Map getRegistryInfo() { - // TODO Auto-generated method stub return null; } + /** + * Not implemented + */ @Override public void showUrl(String url) { - // TODO Auto-generated method stub + } + /** + * Not implemented - returns null + */ + @Override + public Dimension resizeInnerPanel(String data) + { + return null; } @Override - public void resizeInnerPanel(String data) + public Map getJSpecViewProperty(String arg0) { - // TODO Auto-generated method stub + return null; + } + /** + * Calls the Jmol library to parse the PDB file, and then inspects the + * resulting object model to generate Jalview-style sequences, with secondary + * structure annotation added where available (i.e. where it has been computed + * by Jmol using DSSP). + * + * @see jalview.io.AlignFile#parse() + */ + @Override + public void parse() throws IOException + { + Viewer jmolModel = getJmolData(); + jmolModel.openReader(getDataName(), getDataName(), getReader()); + waitForScript(jmolModel); + + /* + * Convert one or more Jmol Model objects to Jalview objects. + */ + if (jmolModel.ms.mc > 0) + { + parseBiopolymers(jmolModel.ms); + } + } + + /** + * Process the Jmol BioPolymer array and generate a Jalview sequence for each + * chain found (including any secondary structure annotation from DSSP) + * + * @param ms + * @throws IOException + */ + public void parseBiopolymers(ModelSet ms) throws IOException + { + int modelIndex = -1; + for (Model model : ms.am) + { + modelIndex++; + String modelTitle = (String) ms.getInfo(modelIndex, "title"); + + /* + * as chains can span BioPolymers, we first make a flattened list, + * and then work out the lengths of chains present + */ + List monomers = getMonomers(ms, (BioModel) model); + List chainLengths = getChainLengths(monomers); + + /* + * now chop up the Monomer list to make Jalview Sequences + */ + int from = 0; + for (int length : chainLengths) + { + buildSequenceFromChain(monomers.subList(from, from + length), modelTitle); + from += length; + } + } + } + + /** + * Helper method to construct a sequence for one chain and add it to the seqs + * list + * + * @param monomers + * a list of all monomers in the chain + * @param modelTitle + */ + protected void buildSequenceFromChain(List monomers, String modelTitle) + { + final int length = monomers.size(); + + /* + * arrays to hold sequence and secondary structure + */ + char[] seq = new char[length]; + char[] secstr = new char[length]; + char[] secstrcode = new char[length]; + + /* + * populate the sequence and secondary structure arrays + */ + extractJmolChainData(monomers, seq, secstr, secstrcode); + + /* + * grab chain code and start position from first residue; + */ + String chainId = monomers.get(0).chain.getIDStr(); + int firstResNum = monomers.get(0).getResno(); + if (firstResNum < 1) + { + // Jalview doesn't like residue < 1, so force this to 1 + System.err.println("Converting chain " + chainId + " first RESNUM (" + + firstResNum + ") to 1"); + firstResNum = 1; + } + + /* + * convert any non-gap unknown residues to 'X' + */ + convertNonGapCharacters(seq); + + /* + * construct and add the Jalview sequence + */ + SequenceI sq = new Sequence("" + getDataName() + "|" + modelTitle + "|" + + chainId, seq, firstResNum, firstResNum + length - 1); + seqs.add(sq); + + /* + * add secondary structure predictions (if any) + */ + addSecondaryStructureAnnotation(modelTitle, sq, secstr, secstrcode, + chainId, firstResNum); + + /* + * record the PDB id for the sequence + */ + addPdbid(sq, chainId); + } + + /** + * Scans the list of (Jmol) Monomer objects, and adds the residue for each to + * the sequence array, and any converted secondary structure prediction to the + * secondary structure arrays + * + * @param monomers + * @param seq + * @param secstr + * @param secstrcode + */ + protected void extractJmolChainData(List monomers, char[] seq, + char[] secstr, char[] secstrcode) + { + int pos = 0; + for (Monomer monomer : monomers) + { + seq[pos] = monomer.getGroup1(); + + /* + * JAL-1828 replace a modified amino acid with its standard + * equivalent (e.g. MSE with MET->M) to maximise sequence matching + */ + replaceNonCanonicalResidue(monomer.getGroup3(), seq, pos); + + /* + * if Jmol has derived a secondary structure prediction for + * this position, convert it to Jalview equivalent and save it + */ + setSecondaryStructure(monomer.getProteinStructureSubType(), pos, + secstr, secstrcode); + pos++; + } + } + + /** + * Helper method that adds an AlignmentAnnotation for secondary structure to + * the sequence, provided at least one secondary structure prediction has been + * made + * + * @param modelTitle + * @param seq + * @param secstr + * @param secstrcode + * @param chainId + * @param firstResNum + * @return + */ + protected void addSecondaryStructureAnnotation(String modelTitle, + SequenceI sq, char[] secstr, char[] secstrcode, + String chainId, int firstResNum) + { + char[] seq = sq.getSequence(); + boolean ssFound = false; + Annotation asecstr[] = new Annotation[seq.length + firstResNum - 1]; + for (int p = 0; p < seq.length; p++) + { + if (secstr[p] >= 'A' && secstr[p] <= 'z') + { + asecstr[p] = new Annotation(String.valueOf(secstr[p]), null, + secstrcode[p], Float.NaN); + ssFound = true; + } + } + + if (ssFound) + { + String mt = modelTitle == null ? getDataName() : modelTitle; + mt += chainId; + AlignmentAnnotation ann = new AlignmentAnnotation( + "Secondary Structure", "Secondary Structure for " + mt, + asecstr); + ann.belowAlignment = true; + ann.visible = true; + ann.autoCalculated = false; + ann.setCalcId(getClass().getName()); + ann.adjustForAlignment(); + ann.validateRangeAndDisplay(); + annotations.add(ann); + sq.addAlignmentAnnotation(ann); + } + } + + /** + * Replace any non-gap miscellaneous characters with 'X' + * + * @param seq + * @return + */ + protected void convertNonGapCharacters(char[] seq) + { + boolean isNa = Comparison.areNucleotide(new char[][] { seq }); + int[] cinds = isNa ? ResidueProperties.nucleotideIndex + : ResidueProperties.aaIndex; + int nonGap = isNa ? ResidueProperties.maxNucleotideIndex + : ResidueProperties.maxProteinIndex; + + for (int p = 0; p < seq.length; p++) + { + if (cinds[seq[p]] == nonGap) + { + seq[p] = 'X'; + } + } + } + + /** + * @param sq + * @param chainId + */ + protected void addPdbid(SequenceI sq, String chainId) + { + PDBEntry pdbe = new PDBEntry(); + pdbe.setFile(getDataName()); + pdbe.setId(getDataName()); + pdbe.setProperty(new Hashtable()); + // pdbe.getProperty().put("CHAIN", "" + _lastChainId); + pdbe.setChainCode(chainId); + sq.addPDBId(pdbe); + } + + /** + * Scans the list of Monomers (residue models), inspecting the chain id for + * each, and returns an array whose length is the number of chains, and values + * the length of each chain + * + * @param monomers + * @return + */ + protected List getChainLengths(List monomers) + { + List chainLengths = new ArrayList(); + int lastChainId = -1; + int length = 0; + + for (Monomer monomer : monomers) + { + int chainId = monomer.chain.chainID; + if (chainId != lastChainId && length > 0) + { + /* + * change of chain - record the length of the last one + */ + chainLengths.add(length); + length = 0; + } + lastChainId = chainId; + length++; + } + if (length > 0) + { + /* + * record the length of the final chain + */ + chainLengths.add(length); + } + + return chainLengths; + } + + /** + * Returns a flattened list of Monomer (residue) in order, across all + * BioPolymers in the model. This simplifies assembling chains which span + * BioPolymers. The result does not include any alternate residues reported + * for the same sequence position (RESNUM value). + * + * @param ms + * @param model + * @return + */ + protected List getMonomers(ModelSet ms, BioModel model) + { + List result = new ArrayList(); + String lastSeqCode = ""; + + for (BioPolymer bp : model.bioPolymers) { + for (int groupLeadAtoms : bp.getLeadAtomIndices()) + { + Group group = ms.at[groupLeadAtoms].group; + if (group instanceof Monomer) + { + /* + * ignore alternate residue at same position + * example: 1ejg has residues A:LEU, B:ILE, C:ILE at RESNUM=25 + */ + String seqcodeString = group.getSeqcodeString(); + if (!lastSeqCode.equals(seqcodeString)) + { + result.add((Monomer) group); + } + else + { + System.out.println("skipping"); + } + lastSeqCode = seqcodeString; + } + } + } + return result; } }