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