/* * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * * Jalview is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * Jalview is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Jalview. If not, see . * The Jalview Authors are detailed in the 'AUTHORS' file. */ package jalview.ext.jmol; import jalview.datamodel.AlignmentAnnotation; import jalview.datamodel.Annotation; import jalview.datamodel.DBRefEntry; import jalview.datamodel.DBRefSource; import jalview.datamodel.PDBEntry; import jalview.datamodel.Sequence; import jalview.datamodel.SequenceI; import jalview.io.FileParse; import jalview.io.StructureFile; 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 java.util.Vector; import javajs.awt.Dimension; import org.jmol.api.JmolStatusListener; import org.jmol.api.JmolViewer; 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.modelsetbio.BioModel; import org.jmol.modelsetbio.BioPolymer; import org.jmol.modelsetbio.Monomer; import org.jmol.viewer.Viewer; import MCview.Atom; import MCview.PDBChain; import MCview.Residue; /** * Import and process files with Jmol for file like PDB, mmCIF * * @author jprocter * */ public class JmolParser extends StructureFile implements JmolStatusListener { Viewer viewer = null; public JmolParser(boolean addAlignmentAnnotations, boolean predictSecondaryStructure, boolean externalSecStr, String inFile, String type) throws IOException { super(inFile, type); this.visibleChainAnnotation = addAlignmentAnnotations; this.predictSecondaryStructure = predictSecondaryStructure; this.externalSecondaryStructure = externalSecStr; } public JmolParser(boolean addAlignmentAnnotations, boolean predictSecondaryStructure, boolean externalSecStr, FileParse fp) throws IOException { super(fp); this.visibleChainAnnotation = addAlignmentAnnotations; this.predictSecondaryStructure = predictSecondaryStructure; this.externalSecondaryStructure = externalSecStr; } public JmolParser(FileParse fp) throws IOException { super(fp); } public JmolParser(String inFile, String type) throws IOException { super(inFile, type); } public JmolParser() { } /** * Calls the Jmol library to parse the PDB/mmCIF 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 { setChains(new Vector()); Viewer jmolModel = getJmolData(); jmolModel.openReader(getDataName(), getDataName(), getReader()); waitForScript(jmolModel); /* * Convert one or more Jmol Model objects to Jalview sequences */ if (jmolModel.ms.mc > 0) { parseBiopolymer(jmolModel.ms); // transformJmolModelToJalview(jmolModel.ms); } } /** * create a headless jmol instance for dataprocessing * * @return */ private Viewer getJmolData() { if (viewer == null) { try { viewer = (Viewer) JmolViewer.allocateViewer(null, null, null, 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); } } return viewer; } public void transformJmolModelToJalview(ModelSet ms) throws IOException { try { String lastID = ""; List rna = new ArrayList(); List prot = new ArrayList(); PDBChain tmpchain; String pdbId = (String) ms.getInfo(0, "title"); setId(pdbId); List significantAtoms = convertSignificantAtoms(ms); for (Atom tmpatom : significantAtoms) { try { tmpchain = findChain(tmpatom.chain); if (tmpatom.resNumIns.trim().equals(lastID)) { // phosphorylated protein - seen both CA and P.. continue; } tmpchain.atoms.addElement(tmpatom); } catch (Exception e) { tmpchain = new PDBChain(pdbId, tmpatom.chain); getChains().add(tmpchain); tmpchain.atoms.addElement(tmpatom); } lastID = tmpatom.resNumIns.trim(); } makeResidueList(); makeCaBondList(); if (getId() == null) { setId(inFile.getName()); } for (PDBChain chain : getChains()) { SequenceI chainseq = postProcessChain(chain); createAnnotation(chainseq, chain, ms.at); if (isRNA(chainseq)) { rna.add(chainseq); } else { prot.add(chainseq); } } } catch (OutOfMemoryError er) { System.out .println("OUT OF MEMORY LOADING TRANSFORMING JMOL MODEL TO JALVIEW MODEL"); throw new IOException( MessageManager .getString("exception.outofmemory_loading_mmcif_file")); } } private List convertSignificantAtoms(ModelSet ms) { List significantAtoms = new ArrayList(); for (org.jmol.modelset.Atom atom : ms.at) { if (atom.getAtomName().equalsIgnoreCase("CA") || atom.getAtomName().equalsIgnoreCase("P")) { Atom curAtom = new Atom(atom.x, atom.y, atom.z); curAtom.atomIndex = atom.getIndex(); curAtom.chain = atom.getChainIDStr(); curAtom.insCode = atom.group.getInsertionCode(); curAtom.name = atom.getAtomName(); curAtom.number = atom.getAtomNumber(); curAtom.resName = atom.getGroup3(true); curAtom.resNumber = atom.getResno(); curAtom.ss = getSecondayStructure(atom.group .getProteinStructureSubType()); curAtom.occupancy = ms.occupancies != null ? ms.occupancies[atom .getIndex()] : Float.valueOf(atom.getOccupancy100()); curAtom.resNumIns = "" + curAtom.resNumber + curAtom.insCode; // curAtom.tfactor = atom.group.; curAtom.type = 0; significantAtoms.add(curAtom); } } return significantAtoms; } private void createAnnotation(SequenceI sequence, PDBChain chain, org.jmol.modelset.Atom[] jmolAtoms) { char[] secstr = new char[sequence.getLength()]; char[] secstrcode = new char[sequence.getLength()]; for (Residue residue : chain.residues) { } addSecondaryStructureAnnotation(chain.pdbid, sequence, secstr, secstrcode, chain.id, sequence.getStart()); } /** * 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 parseBiopolymer(ModelSet ms) throws IOException { int modelIndex = -1; for (Model model : ms.am) { modelIndex++; String modelTitle = (String) ms.getInfo(modelIndex, "title"); /* * Chains can span BioPolymers, so 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; } } } /** * Returns a flattened list of Monomer (residues) in order, across all * BioPolymers in the model. This simplifies assembling chains which span * BioPolymers. The result omits 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(); int lastResNo = Integer.MIN_VALUE; 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 at RESNUM=25 */ int resNo = group.getResno(); if (lastResNo != resNo) { result.add((Monomer) group); } lastResNo = resNo; } } } return result; } /** * 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; } /** * 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 */ String seqName = "" + modelTitle + "|" + chainId; int start = firstResNum; int end = firstResNum + length - 1; SequenceI sq = new Sequence(seqName, seq, start, end); addPdbid(sq, modelTitle, chainId); addSourceDBref(sq, modelTitle, start, end); seqs.add(sq); /* * add secondary structure predictions (if any) */ addSecondaryStructureAnnotation(modelTitle, sq, secstr, secstrcode, chainId, firstResNum); } /** * 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++; } } /** * 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'; } } } /** * Add a source db ref entry for the given sequence. * * @param sq * @param accessionId * @param start * @param end */ protected void addSourceDBref(SequenceI sq, String accessionId, int start, int end) { DBRefEntry sourceDBRef = new DBRefEntry(); sourceDBRef.setAccessionId(accessionId); sourceDBRef.setSource(DBRefSource.MMCIF); sourceDBRef.setStartRes(start); sourceDBRef.setEndRes(end); sq.setSourceDBRef(sourceDBRef); sq.addDBRef(sourceDBRef); } /** * Add a PDBEntry giving the source of PDB data to the sequence * * @param sq * @param id * @param chainId */ protected void addPdbid(SequenceI sq, String id, String chainId) { PDBEntry entry = new PDBEntry(); entry.setId(id); entry.setType(PDBEntry.Type.MMCIF); entry.setProperty(new Hashtable()); if (chainId != null) { // entry.getProperty().put("CHAIN", chains.elementAt(i).id); entry.setChainCode(String.valueOf(chainId)); } if (inFile != null) { entry.setFile(inFile.getAbsolutePath()); } else { // TODO: decide if we should dump the datasource to disk entry.setFile(getDataName()); } sq.addPDBId(entry); } /** * 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); } } private void waitForScript(Viewer jmd) { while (jmd.isScriptExecuting()) { try { Thread.sleep(50); } catch (InterruptedException x) { } } } /** * Convert Jmol's secondary structure code to Jalview's, and stored it in the * secondary structure arrays at the given sequence position * * @param proteinStructureSubType * @param pos * @param secstr * @param secstrcode */ protected void setSecondaryStructure(STR proteinStructureSubType, int pos, char[] secstr, char[] secstrcode) { switch (proteinStructureSubType) { case HELIX310: secstr[pos] = '3'; break; case HELIX: case HELIXALPHA: secstr[pos] = 'H'; break; case HELIXPI: secstr[pos] = 'P'; break; case SHEET: secstr[pos] = 'E'; break; default: secstr[pos] = 0; } switch (proteinStructureSubType) { case HELIX310: case HELIXALPHA: case HELIXPI: case HELIX: secstrcode[pos] = 'H'; break; case SHEET: secstrcode[pos] = 'E'; break; default: secstrcode[pos] = 0; } } private char getSecondayStructure(STR proteinStructureSubType) { switch (proteinStructureSubType) { case HELIX310: return '3'; case HELIX: case HELIXALPHA: return 'H'; case HELIXPI: return 'P'; case SHEET: return 'E'; default: return 0; } } /** * Convert any non-standard peptide codes to their standard code table * equivalent. (Initial version only does Selenomethionine MSE->MET.) * * @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() { return null; } /** * Not implemented */ @Override public void setCallbackFunction(String callbackType, String callbackFunction) { } @Override public void notifyCallback(CBK cbType, Object[] data) { String strInfo = (data == null || data[1] == null ? null : data[1] .toString()); switch (cbType) { case ECHO: sendConsoleEcho(strInfo); break; case SCRIPT: notifyScriptTermination((String) data[2], ((Integer) data[3]).intValue()); break; case MEASURE: String mystatus = (String) data[3]; if (mystatus.indexOf("Picked") >= 0 || 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); break; case PICK: sendConsoleMessage(strInfo); break; default: break; } } String lastConsoleEcho = ""; private void sendConsoleEcho(String string) { lastConsoleEcho += string; lastConsoleEcho += "\n"; } String lastConsoleMessage = ""; private void sendConsoleMessage(String string) { lastConsoleMessage += string; lastConsoleMessage += "\n"; } int lastScriptTermination = -1; String lastScriptMessage = ""; private void notifyScriptTermination(String string, int intValue) { lastScriptMessage += string; lastScriptMessage += "\n"; lastScriptTermination = intValue; } @Override public boolean notifyEnabled(CBK callbackPick) { switch (callbackPick) { case MESSAGE: case SCRIPT: case ECHO: case LOADSTRUCT: case ERROR: return true; default: return false; } } /** * Not implemented - returns null */ @Override public String eval(String strEval) { return null; } /** * Not implemented - returns null */ @Override public float[][] functionXY(String functionName, int x, int y) { return null; } /** * Not implemented - returns null */ @Override public float[][][] functionXYZ(String functionName, int nx, int ny, int nz) { return null; } /** * Not implemented - returns null */ @Override public String createImage(String fileName, String imageType, Object text_or_bytes, int quality) { return null; } /** * Not implemented - returns null */ @Override public Map getRegistryInfo() { return null; } /** * Not implemented */ @Override public void showUrl(String url) { } /** * Not implemented - returns null */ @Override public Dimension resizeInnerPanel(String data) { return null; } @Override public Map getJSpecViewProperty(String arg0) { return null; } public boolean isPredictSecondaryStructure() { return predictSecondaryStructure; } public void setPredictSecondaryStructure(boolean predictSecondaryStructure) { this.predictSecondaryStructure = predictSecondaryStructure; } }