/*
- * 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.
*
*/
package jalview.ext.jmol;
+import jalview.datamodel.AlignmentAnnotation;
+import jalview.datamodel.Annotation;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.Sequence;
+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 jalview.datamodel.AlignmentAnnotation;
-import jalview.datamodel.Annotation;
-import jalview.datamodel.PDBEntry;
-import jalview.datamodel.Sequence;
-import jalview.datamodel.SequenceI;
-import jalview.io.AlignFile;
-import jalview.io.FileParse;
/**
* Import and process PDB files with Jmol
public class PDBFileWithJmol extends AlignFile implements
JmolStatusListener
{
-
- JmolApp jmolApp = null;
-
Viewer viewer = null;
public PDBFileWithJmol(String inFile, String type) throws IOException
public PDBFileWithJmol()
{
- // TODO Auto-generated constructor stub
}
/**
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(
- "Jmol version "
- + JmolViewer.getJmolVersion()
- + " is not compatible with this version of Jalview. Report this problem at issues.jalview.org",
- x);
+ throw new Error(MessageManager.formatMessage(
+ "error.jmol_version_not_compatible_with_jalview_version",
+ new String[] { JmolViewer.getJmolVersion() }), x);
}
}
return viewer;
}
}
- /*
- * (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)
- {
- char newseq[] = new char[len];
- System.arraycopy(seq, 0, newseq, 0, len);
- Annotation asecstr[] = new Annotation[len+firstrnum-1];
- for (int p = 0; p < len; 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 (!(biopoly.isDna() || biopoly.isRna()))
- {
- AlignmentAnnotation ann = new AlignmentAnnotation(
- "Secondary Structure",
- "Secondary Structure from PDB File", 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);
case MEASURE:
String mystatus = (String) data[3];
if (mystatus.indexOf("Picked") >= 0
- || mystatus.indexOf("Sequence") >= 0) // picking mode
+ || mystatus.indexOf("Sequence") >= 0)
+ {
+ // Picking mode
sendConsoleMessage(strInfo);
+ }
else if (mystatus.indexOf("Completed") >= 0)
+ {
sendConsoleEcho(strInfo.substring(strInfo.lastIndexOf(",") + 2,
strInfo.length() - 1));
+ }
break;
case MESSAGE:
sendConsoleMessage(data == null ? null : strInfo);
}
}
- 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)
}
@Override
- public boolean notifyEnabled(EnumCallback callbackPick)
+ public boolean notifyEnabled(CBK callbackPick)
{
switch (callbackPick)
{
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<String, Object> 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<String, Object> 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<Monomer> monomers = getMonomers(ms, (BioModel) model);
+ List<Integer> 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<Monomer> 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<Monomer> 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<Integer> getChainLengths(List<Monomer> monomers)
+ {
+ List<Integer> chainLengths = new ArrayList<Integer>();
+ 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<Monomer> getMonomers(ModelSet ms, BioModel model)
+ {
+ List<Monomer> result = new ArrayList<Monomer>();
+ 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;
}
}