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