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