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