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