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