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