JAL-1919 Added improvement to ensure consistence of annotations parsed via JmolParser...
[jalview.git] / src / jalview / ext / jmol / JmolParser.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.ext.jmol;
22
23 import jalview.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.Annotation;
25 import jalview.datamodel.SequenceI;
26 import jalview.io.FileParse;
27 import jalview.io.StructureFile;
28 import jalview.schemes.ResidueProperties;
29 import jalview.structure.StructureImportSettings;
30 import jalview.util.MessageManager;
31
32 import java.io.IOException;
33 import java.util.ArrayList;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Vector;
38
39 import javajs.awt.Dimension;
40
41 import org.jmol.api.JmolStatusListener;
42 import org.jmol.api.JmolViewer;
43 import org.jmol.c.CBK;
44 import org.jmol.c.STR;
45 import org.jmol.modelset.ModelSet;
46 import org.jmol.viewer.Viewer;
47
48 import MCview.Atom;
49 import MCview.PDBChain;
50 import MCview.Residue;
51
52 /**
53  * Import and process files with Jmol for file like PDB, mmCIF
54  * 
55  * @author jprocter
56  * 
57  */
58 public class JmolParser extends StructureFile implements JmolStatusListener
59 {
60   Viewer viewer = null;
61
62   public JmolParser(boolean addAlignmentAnnotations, boolean predictSecStr,
63           boolean externalSecStr, String inFile, String type)
64           throws IOException
65   {
66     super(inFile, type);
67   }
68
69   public JmolParser(boolean addAlignmentAnnotations, boolean predictSecStr,
70           boolean externalSecStr, FileParse fp) throws IOException
71   {
72     super(fp);
73   }
74
75   public JmolParser()
76   {
77   }
78
79   /**
80    * Calls the Jmol library to parse the PDB/mmCIF file, and then inspects the
81    * resulting object model to generate Jalview-style sequences, with secondary
82    * structure annotation added where available (i.e. where it has been computed
83    * by Jmol using DSSP).
84    * 
85    * @see jalview.io.AlignFile#parse()
86    */
87   @Override
88   public void parse() throws IOException
89   {
90     setChains(new Vector<PDBChain>());
91     Viewer jmolModel = getJmolData();
92     jmolModel.openReader(getDataName(), getDataName(), getReader());
93     waitForScript(jmolModel);
94
95     /*
96      * Convert one or more Jmol Model objects to Jalview sequences
97      */
98     if (jmolModel.ms.mc > 0)
99     {
100       transformJmolModelToJalview(jmolModel.ms);
101     }
102   }
103
104   /**
105    * create a headless jmol instance for dataprocessing
106    * 
107    * @return
108    */
109   private Viewer getJmolData()
110   {
111     if (viewer == null)
112     {
113       try
114       {
115         viewer = (Viewer) JmolViewer.allocateViewer(null, null, null, null,
116                 null, "-x -o -n", this);
117         // ensure the 'new' (DSSP) not 'old' (Ramachandran) SS method is used
118         viewer.setBooleanProperty("defaultStructureDSSP", true);
119       } catch (ClassCastException x)
120       {
121         throw new Error(MessageManager.formatMessage(
122                 "error.jmol_version_not_compatible_with_jalview_version",
123                 new String[] { JmolViewer.getJmolVersion() }), x);
124       }
125     }
126     return viewer;
127   }
128
129   public void transformJmolModelToJalview(ModelSet ms) throws IOException
130   {
131     try
132     {
133       String lastID = "";
134       List<SequenceI> rna = new ArrayList<SequenceI>();
135       List<SequenceI> prot = new ArrayList<SequenceI>();
136       PDBChain tmpchain;
137       String pdbId = (String) ms.getInfo(0, "title");
138       setId(pdbId);
139       List<Atom> significantAtoms = convertSignificantAtoms(ms);
140       for (Atom tmpatom : significantAtoms)
141       {
142         try
143         {
144           tmpchain = findChain(tmpatom.chain);
145           if (tmpatom.resNumIns.trim().equals(lastID))
146           {
147             // phosphorylated protein - seen both CA and P..
148             continue;
149           }
150           tmpchain.atoms.addElement(tmpatom);
151         } catch (Exception e)
152         {
153           tmpchain = new PDBChain(pdbId, tmpatom.chain);
154           getChains().add(tmpchain);
155           tmpchain.atoms.addElement(tmpatom);
156         }
157         lastID = tmpatom.resNumIns.trim();
158       }
159       xferSettings();
160
161       makeResidueList();
162       makeCaBondList();
163
164       if (getId() == null)
165       {
166         setId(safeName(getDataName()));
167       }
168       for (PDBChain chain : getChains())
169       {
170         SequenceI chainseq = postProcessChain(chain);
171         if (isRNA(chainseq))
172         {
173           rna.add(chainseq);
174         }
175         else
176         {
177           prot.add(chainseq);
178         }
179
180         if (StructureImportSettings.isProcessSecondaryStructure())
181         {
182           createAnnotation(chainseq, chain, ms.at);
183         }
184       }
185     } catch (OutOfMemoryError er)
186     {
187       System.out
188               .println("OUT OF MEMORY LOADING TRANSFORMING JMOL MODEL TO JALVIEW MODEL");
189       throw new IOException(
190               MessageManager
191                       .getString("exception.outofmemory_loading_mmcif_file"));
192     }
193   }
194
195   private List<Atom> convertSignificantAtoms(ModelSet ms)
196   {
197     List<Atom> significantAtoms = new ArrayList<Atom>();
198     HashMap<String, org.jmol.modelset.Atom> chainTerMap = new HashMap<String, org.jmol.modelset.Atom>();
199     org.jmol.modelset.Atom prevAtom = null;
200     for (org.jmol.modelset.Atom atom : ms.at)
201     {
202       if (atom.getAtomName().equalsIgnoreCase("CA")
203               || atom.getAtomName().equalsIgnoreCase("P"))
204       {
205         if (!atomValidated(atom, prevAtom, chainTerMap))
206         {
207           continue;
208         }
209         Atom curAtom = new Atom(atom.x, atom.y, atom.z);
210         curAtom.atomIndex = atom.getIndex();
211         curAtom.chain = atom.getChainIDStr();
212         curAtom.insCode = atom.group.getInsertionCode() == '\000' ? ' '
213                 : atom.group.getInsertionCode();
214         curAtom.name = atom.getAtomName();
215         curAtom.number = atom.getAtomNumber();
216         curAtom.resName = atom.getGroup3(true);
217         curAtom.resNumber = atom.getResno();
218         curAtom.occupancy = ms.occupancies != null ? ms.occupancies[atom
219                 .getIndex()] : Float.valueOf(atom.getOccupancy100());
220         curAtom.resNumIns = ("" + curAtom.resNumber + curAtom.insCode)
221                 .trim();
222         curAtom.tfactor = atom.getBfactor100() / 100f;
223         curAtom.type = 0;
224         // significantAtoms.add(curAtom);
225         // ignore atoms from subsequent models
226         if (!significantAtoms.contains(curAtom))
227         {
228           significantAtoms.add(curAtom);
229         }
230         prevAtom = atom;
231       }
232     }
233     return significantAtoms;
234   }
235
236   private boolean atomValidated(org.jmol.modelset.Atom curAtom,
237           org.jmol.modelset.Atom prevAtom,
238           HashMap<String, org.jmol.modelset.Atom> chainTerMap)
239   {
240     // System.out.println("Atom: " + curAtom.getAtomNumber()
241     // + "   Last atom index " + curAtom.group.lastAtomIndex);
242     if (chainTerMap == null || prevAtom == null)
243     {
244       return true;
245     }
246     String curAtomChId = curAtom.getChainIDStr();
247     String prevAtomChId = prevAtom.getChainIDStr();
248     // new chain encoutered
249     if (!prevAtomChId.equals(curAtomChId))
250     {
251       // On chain switch add previous chain termination to xTerMap if not exists
252       if (!chainTerMap.containsKey(prevAtomChId))
253       {
254         chainTerMap.put(prevAtomChId, prevAtom);
255       }
256       // if current atom belongs to an already terminated chain and the resNum
257       // diff < 5 then mark as valid and update termination Atom
258       if (chainTerMap.containsKey(curAtomChId))
259       {
260         if ((curAtom.getResno() - chainTerMap.get(curAtomChId).getResno()) < 5)
261         {
262           chainTerMap.put(curAtomChId, curAtom);
263           return true;
264         }
265         return false;
266       }
267     }
268     // atom with previously terminated chain encountered
269     else if (chainTerMap.containsKey(curAtomChId))
270     {
271       if ((curAtom.getResno() - chainTerMap.get(curAtomChId).getResno()) < 5)
272       {
273         chainTerMap.put(curAtomChId, curAtom);
274         return true;
275       }
276       return false;
277     }
278     // HETATM with resNum jump > 2
279     return !(curAtom.isHetero() && ((curAtom.getResno() - prevAtom
280             .getResno()) > 2));
281   }
282
283   private void createAnnotation(SequenceI sequence, PDBChain chain,
284           org.jmol.modelset.Atom[] jmolAtoms)
285   {
286     char[] secstr = new char[sequence.getLength()];
287     char[] secstrcode = new char[sequence.getLength()];
288
289     // Ensure Residue size equals Seq size
290     if (chain.residues.size() != sequence.getLength())
291     {
292       return;
293     }
294     int annotIndex = 0;
295     for (Residue residue : chain.residues)
296     {
297       Atom repAtom = residue.getAtoms().get(0);
298       STR proteinStructureSubType = jmolAtoms[repAtom.atomIndex].group
299               .getProteinStructureSubType();
300       setSecondaryStructure(proteinStructureSubType, annotIndex, secstr,
301               secstrcode);
302       ++annotIndex;
303     }
304     addSecondaryStructureAnnotation(chain.pdbid, sequence, secstr,
305             secstrcode, chain.id, sequence.getStart());
306   }
307
308   /**
309    * Helper method that adds an AlignmentAnnotation for secondary structure to
310    * the sequence, provided at least one secondary structure prediction has been
311    * made
312    * 
313    * @param modelTitle
314    * @param seq
315    * @param secstr
316    * @param secstrcode
317    * @param chainId
318    * @param firstResNum
319    * @return
320    */
321   protected void addSecondaryStructureAnnotation(String modelTitle,
322           SequenceI sq, char[] secstr, char[] secstrcode, String chainId,
323           int firstResNum)
324   {
325     char[] seq = sq.getSequence();
326     boolean ssFound = false;
327     Annotation asecstr[] = new Annotation[seq.length + firstResNum - 1];
328     for (int p = 0; p < seq.length; p++)
329     {
330       if (secstr[p] >= 'A' && secstr[p] <= 'z')
331       {
332         try
333         {
334         asecstr[p] = new Annotation(String.valueOf(secstr[p]), null,
335                 secstrcode[p], Float.NaN);
336         ssFound = true;
337         } catch (Exception e)
338         {
339           // e.printStackTrace();
340         }
341       }
342     }
343
344     if (ssFound)
345     {
346       String mt = modelTitle == null ? getDataName() : modelTitle;
347       mt += chainId;
348       AlignmentAnnotation ann = new AlignmentAnnotation(
349               "Secondary Structure", "Secondary Structure for " + mt,
350               asecstr);
351       ann.belowAlignment = true;
352       ann.visible = true;
353       ann.autoCalculated = false;
354       ann.setCalcId(getClass().getName());
355       ann.adjustForAlignment();
356       ann.validateRangeAndDisplay();
357       annotations.add(ann);
358       sq.addAlignmentAnnotation(ann);
359     }
360   }
361
362   private void waitForScript(Viewer jmd)
363   {
364     while (jmd.isScriptExecuting())
365     {
366       try
367       {
368         Thread.sleep(50);
369
370       } catch (InterruptedException x)
371       {
372       }
373     }
374   }
375
376   /**
377    * Convert Jmol's secondary structure code to Jalview's, and stored it in the
378    * secondary structure arrays at the given sequence position
379    * 
380    * @param proteinStructureSubType
381    * @param pos
382    * @param secstr
383    * @param secstrcode
384    */
385   protected void setSecondaryStructure(STR proteinStructureSubType,
386           int pos, char[] secstr, char[] secstrcode)
387   {
388     switch (proteinStructureSubType)
389     {
390     case HELIX310:
391       secstr[pos] = '3';
392       break;
393     case HELIX:
394     case HELIXALPHA:
395       secstr[pos] = 'H';
396       break;
397     case HELIXPI:
398       secstr[pos] = 'P';
399       break;
400     case SHEET:
401       secstr[pos] = 'E';
402       break;
403     default:
404       secstr[pos] = 0;
405     }
406
407     switch (proteinStructureSubType)
408     {
409     case HELIX310:
410     case HELIXALPHA:
411     case HELIXPI:
412     case HELIX:
413       secstrcode[pos] = 'H';
414       break;
415     case SHEET:
416       secstrcode[pos] = 'E';
417       break;
418     default:
419       secstrcode[pos] = 0;
420     }
421   }
422
423   /**
424    * Convert any non-standard peptide codes to their standard code table
425    * equivalent. (Initial version only does Selenomethionine MSE->MET.)
426    * 
427    * @param threeLetterCode
428    * @param seq
429    * @param pos
430    */
431   protected void replaceNonCanonicalResidue(String threeLetterCode,
432           char[] seq, int pos)
433   {
434     String canonical = ResidueProperties
435             .getCanonicalAminoAcid(threeLetterCode);
436     if (canonical != null && !canonical.equalsIgnoreCase(threeLetterCode))
437     {
438       seq[pos] = ResidueProperties.getSingleCharacterCode(canonical);
439     }
440   }
441
442   /**
443    * Not implemented - returns null
444    */
445   @Override
446   public String print()
447   {
448     return null;
449   }
450
451   /**
452    * Not implemented
453    */
454   @Override
455   public void setCallbackFunction(String callbackType,
456           String callbackFunction)
457   {
458   }
459
460   @Override
461   public void notifyCallback(CBK cbType, Object[] data)
462   {
463     String strInfo = (data == null || data[1] == null ? null : data[1]
464             .toString());
465     switch (cbType)
466     {
467     case ECHO:
468       sendConsoleEcho(strInfo);
469       break;
470     case SCRIPT:
471       notifyScriptTermination((String) data[2],
472               ((Integer) data[3]).intValue());
473       break;
474     case MEASURE:
475       String mystatus = (String) data[3];
476       if (mystatus.indexOf("Picked") >= 0
477               || mystatus.indexOf("Sequence") >= 0)
478       {
479         // Picking mode
480         sendConsoleMessage(strInfo);
481       }
482       else if (mystatus.indexOf("Completed") >= 0)
483       {
484         sendConsoleEcho(strInfo.substring(strInfo.lastIndexOf(",") + 2,
485                 strInfo.length() - 1));
486       }
487       break;
488     case MESSAGE:
489       sendConsoleMessage(data == null ? null : strInfo);
490       break;
491     case PICK:
492       sendConsoleMessage(strInfo);
493       break;
494     default:
495       break;
496     }
497   }
498
499   String lastConsoleEcho = "";
500
501   private void sendConsoleEcho(String string)
502   {
503     lastConsoleEcho += string;
504     lastConsoleEcho += "\n";
505   }
506
507   String lastConsoleMessage = "";
508
509   private void sendConsoleMessage(String string)
510   {
511     lastConsoleMessage += string;
512     lastConsoleMessage += "\n";
513   }
514
515   int lastScriptTermination = -1;
516
517   String lastScriptMessage = "";
518
519   private void notifyScriptTermination(String string, int intValue)
520   {
521     lastScriptMessage += string;
522     lastScriptMessage += "\n";
523     lastScriptTermination = intValue;
524   }
525
526   @Override
527   public boolean notifyEnabled(CBK callbackPick)
528   {
529     switch (callbackPick)
530     {
531     case MESSAGE:
532     case SCRIPT:
533     case ECHO:
534     case LOADSTRUCT:
535     case ERROR:
536       return true;
537     default:
538       return false;
539     }
540   }
541
542   /**
543    * Not implemented - returns null
544    */
545   @Override
546   public String eval(String strEval)
547   {
548     return null;
549   }
550
551   /**
552    * Not implemented - returns null
553    */
554   @Override
555   public float[][] functionXY(String functionName, int x, int y)
556   {
557     return null;
558   }
559
560   /**
561    * Not implemented - returns null
562    */
563   @Override
564   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
565   {
566     return null;
567   }
568
569   /**
570    * Not implemented - returns null
571    */
572   @Override
573   public String createImage(String fileName, String imageType,
574           Object text_or_bytes, int quality)
575   {
576     return null;
577   }
578
579   /**
580    * Not implemented - returns null
581    */
582   @Override
583   public Map<String, Object> getRegistryInfo()
584   {
585     return null;
586   }
587
588   /**
589    * Not implemented
590    */
591   @Override
592   public void showUrl(String url)
593   {
594   }
595
596   /**
597    * Not implemented - returns null
598    */
599   @Override
600   public Dimension resizeInnerPanel(String data)
601   {
602     return null;
603   }
604
605   @Override
606   public Map<String, Object> getJSpecViewProperty(String arg0)
607   {
608     return null;
609   }
610
611   public boolean isPredictSecondaryStructure()
612   {
613     return predictSecondaryStructure;
614   }
615
616   public void setPredictSecondaryStructure(boolean predictSecondaryStructure)
617   {
618     this.predictSecondaryStructure = predictSecondaryStructure;
619   }
620
621   public boolean isVisibleChainAnnotation()
622   {
623     return visibleChainAnnotation;
624   }
625
626   public void setVisibleChainAnnotation(boolean visibleChainAnnotation)
627   {
628     this.visibleChainAnnotation = visibleChainAnnotation;
629   }
630
631 }