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