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