JAL-629 refactoring TFType. Remove i18n identification of annotation.
[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.Annotation;
44 import jalview.datamodel.PDBEntry;
45 import jalview.datamodel.SequenceI;
46 import jalview.datamodel.annotations.AlphaFoldAnnotationRowBuilder;
47 import jalview.datamodel.annotations.AnnotationRowBuilder;
48 import jalview.io.DataSourceType;
49 import jalview.io.FileParse;
50 import jalview.io.StructureFile;
51 import jalview.schemes.ResidueProperties;
52 import jalview.structure.StructureImportSettings;
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           StructureImportSettings.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() || this
255                   .getTemperatureFactorType() == StructureImportSettings.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   public void setAlphafoldModel(boolean afm)
325   {
326     alphaFoldModel = afm;
327   }
328
329   private boolean isAlphafoldModel()
330   {
331     return alphaFoldModel;
332   }
333
334   private List<Atom> convertSignificantAtoms(ModelSet ms)
335   {
336     List<Atom> significantAtoms = new ArrayList<Atom>();
337     HashMap<String, org.jmol.modelset.Atom> chainTerMap = new HashMap<String, org.jmol.modelset.Atom>();
338     org.jmol.modelset.Atom prevAtom = null;
339     for (org.jmol.modelset.Atom atom : ms.at)
340     {
341       if (atom.getAtomName().equalsIgnoreCase("CA")
342               || atom.getAtomName().equalsIgnoreCase("P"))
343       {
344         if (!atomValidated(atom, prevAtom, chainTerMap))
345         {
346           continue;
347         }
348         Atom curAtom = new Atom(atom.x, atom.y, atom.z);
349         curAtom.atomIndex = atom.getIndex();
350         curAtom.chain = atom.getChainIDStr();
351         curAtom.insCode = atom.group.getInsertionCode() == '\000' ? ' '
352                 : atom.group.getInsertionCode();
353         curAtom.name = atom.getAtomName();
354         curAtom.number = atom.getAtomNumber();
355         curAtom.resName = atom.getGroup3(true);
356         curAtom.resNumber = atom.getResno();
357         curAtom.occupancy = ms.occupancies != null
358                 ? ms.occupancies[atom.getIndex()]
359                 : Float.valueOf(atom.getOccupancy100());
360         String fmt = new Format("%4i").form(curAtom.resNumber);
361         curAtom.resNumIns = (fmt + curAtom.insCode);
362         curAtom.tfactor = atom.getBfactor100() / 100f;
363         curAtom.type = 0;
364         // significantAtoms.add(curAtom);
365         // ignore atoms from subsequent models
366         if (!significantAtoms.contains(curAtom))
367         {
368           significantAtoms.add(curAtom);
369         }
370         prevAtom = atom;
371       }
372     }
373     return significantAtoms;
374   }
375
376   private boolean atomValidated(org.jmol.modelset.Atom curAtom,
377           org.jmol.modelset.Atom prevAtom,
378           HashMap<String, org.jmol.modelset.Atom> chainTerMap)
379   {
380     // System.out.println("Atom: " + curAtom.getAtomNumber()
381     // + " Last atom index " + curAtom.group.lastAtomIndex);
382     if (chainTerMap == null || prevAtom == null)
383     {
384       return true;
385     }
386     String curAtomChId = curAtom.getChainIDStr();
387     String prevAtomChId = prevAtom.getChainIDStr();
388     // new chain encoutered
389     if (!prevAtomChId.equals(curAtomChId))
390     {
391       // On chain switch add previous chain termination to xTerMap if not exists
392       if (!chainTerMap.containsKey(prevAtomChId))
393       {
394         chainTerMap.put(prevAtomChId, prevAtom);
395       }
396       // if current atom belongs to an already terminated chain and the resNum
397       // diff < 5 then mark as valid and update termination Atom
398       if (chainTerMap.containsKey(curAtomChId))
399       {
400         if (curAtom.getResno() < chainTerMap.get(curAtomChId).getResno())
401         {
402           return false;
403         }
404         if ((curAtom.getResno()
405                 - chainTerMap.get(curAtomChId).getResno()) < 5)
406         {
407           chainTerMap.put(curAtomChId, curAtom);
408           return true;
409         }
410         return false;
411       }
412     }
413     // atom with previously terminated chain encountered
414     else if (chainTerMap.containsKey(curAtomChId))
415     {
416       if (curAtom.getResno() < chainTerMap.get(curAtomChId).getResno())
417       {
418         return false;
419       }
420       if ((curAtom.getResno()
421               - chainTerMap.get(curAtomChId).getResno()) < 5)
422       {
423         chainTerMap.put(curAtomChId, curAtom);
424         return true;
425       }
426       return false;
427     }
428     // HETATM with resNum jump > 2
429     return !(curAtom.isHetero()
430             && ((curAtom.getResno() - prevAtom.getResno()) > 2));
431   }
432
433   private void createAnnotation(SequenceI sequence, PDBChain chain,
434           org.jmol.modelset.Atom[] jmolAtoms)
435   {
436     char[] secstr = new char[sequence.getLength()];
437     char[] secstrcode = new char[sequence.getLength()];
438
439     // Ensure Residue size equals Seq size
440     if (chain.residues.size() != sequence.getLength())
441     {
442       return;
443     }
444     int annotIndex = 0;
445     for (Residue residue : chain.residues)
446     {
447       Atom repAtom = residue.getAtoms().get(0);
448       STR proteinStructureSubType = jmolAtoms[repAtom.atomIndex].group
449               .getProteinStructureSubType();
450       setSecondaryStructure(proteinStructureSubType, annotIndex, secstr,
451               secstrcode);
452       ++annotIndex;
453     }
454     addSecondaryStructureAnnotation(chain.pdbid, sequence, secstr,
455             secstrcode, chain.id, sequence.getStart());
456   }
457
458   /**
459    * Helper method that adds an AlignmentAnnotation for secondary structure to
460    * the sequence, provided at least one secondary structure assignment has been
461    * made
462    * 
463    * @param modelTitle
464    * @param seq
465    * @param secstr
466    * @param secstrcode
467    * @param chainId
468    * @param firstResNum
469    * @return
470    */
471   protected void addSecondaryStructureAnnotation(String modelTitle,
472           SequenceI sq, char[] secstr, char[] secstrcode, String chainId,
473           int firstResNum)
474   {
475     int length = sq.getLength();
476     boolean ssFound = false;
477     Annotation asecstr[] = new Annotation[length + firstResNum - 1];
478     for (int p = 0; p < length; p++)
479     {
480       if (secstr[p] >= 'A' && secstr[p] <= 'z')
481       {
482         try
483         {
484           asecstr[p] = new Annotation(null, null, secstrcode[p], Float.NaN);
485           ssFound = true;
486         } catch (Exception e)
487         {
488           // e.printStackTrace();
489         }
490       }
491     }
492
493     if (ssFound)
494     {
495       String mt = modelTitle == null ? getDataName() : modelTitle;
496       mt += chainId;
497       AlignmentAnnotation ann = new AlignmentAnnotation(
498               "Secondary Structure", "Secondary Structure for " + mt,
499               asecstr);
500       ann.belowAlignment = true;
501       ann.visible = true;
502       ann.autoCalculated = false;
503       ann.setCalcId(getClass().getName());
504       ann.adjustForAlignment();
505       ann.validateRangeAndDisplay();
506       annotations.add(ann);
507       sq.addAlignmentAnnotation(ann);
508     }
509   }
510
511   private void waitForScript(Viewer jmd)
512   {
513     while (jmd.isScriptExecuting())
514     {
515       try
516       {
517         Thread.sleep(50);
518
519       } catch (InterruptedException x)
520       {
521       }
522     }
523   }
524
525   /**
526    * Convert Jmol's secondary structure code to Jalview's, and stored it in the
527    * secondary structure arrays at the given sequence position
528    * 
529    * @param proteinStructureSubType
530    * @param pos
531    * @param secstr
532    * @param secstrcode
533    */
534   protected void setSecondaryStructure(STR proteinStructureSubType, int pos,
535           char[] secstr, char[] secstrcode)
536   {
537     switch (proteinStructureSubType)
538     {
539     case HELIX310:
540       secstr[pos] = '3';
541       break;
542     case HELIX:
543     case HELIXALPHA:
544       secstr[pos] = 'H';
545       break;
546     case HELIXPI:
547       secstr[pos] = 'P';
548       break;
549     case SHEET:
550       secstr[pos] = 'E';
551       break;
552     default:
553       secstr[pos] = 0;
554     }
555
556     switch (proteinStructureSubType)
557     {
558     case HELIX310:
559     case HELIXALPHA:
560     case HELIXPI:
561     case HELIX:
562       secstrcode[pos] = 'H';
563       break;
564     case SHEET:
565       secstrcode[pos] = 'E';
566       break;
567     default:
568       secstrcode[pos] = 0;
569     }
570   }
571
572   /**
573    * Convert any non-standard peptide codes to their standard code table
574    * equivalent. (Initial version only does Selenomethionine MSE->MET.)
575    * 
576    * @param threeLetterCode
577    * @param seq
578    * @param pos
579    */
580   protected void replaceNonCanonicalResidue(String threeLetterCode,
581           char[] seq, int pos)
582   {
583     String canonical = ResidueProperties
584             .getCanonicalAminoAcid(threeLetterCode);
585     if (canonical != null && !canonical.equalsIgnoreCase(threeLetterCode))
586     {
587       seq[pos] = ResidueProperties.getSingleCharacterCode(canonical);
588     }
589   }
590
591   /**
592    * Not implemented - returns null
593    */
594   @Override
595   public String print(SequenceI[] seqs, boolean jvSuffix)
596   {
597     return null;
598   }
599
600   /**
601    * Not implemented
602    */
603   @Override
604   public void setCallbackFunction(String callbackType,
605           String callbackFunction)
606   {
607   }
608
609   @Override
610   public void notifyCallback(CBK cbType, Object[] data)
611   {
612     String strInfo = (data == null || data[1] == null ? null
613             : data[1].toString());
614     switch (cbType)
615     {
616     case ECHO:
617       sendConsoleEcho(strInfo);
618       break;
619     case SCRIPT:
620       notifyScriptTermination((String) data[2],
621               ((Integer) data[3]).intValue());
622       break;
623     case MEASURE:
624       String mystatus = (String) data[3];
625       if (mystatus.indexOf("Picked") >= 0
626               || mystatus.indexOf("Sequence") >= 0)
627       {
628         // Picking mode
629         sendConsoleMessage(strInfo);
630       }
631       else if (mystatus.indexOf("Completed") >= 0)
632       {
633         sendConsoleEcho(strInfo.substring(strInfo.lastIndexOf(",") + 2,
634                 strInfo.length() - 1));
635       }
636       break;
637     case MESSAGE:
638       sendConsoleMessage(data == null ? null : strInfo);
639       break;
640     case PICK:
641       sendConsoleMessage(strInfo);
642       break;
643     default:
644       break;
645     }
646   }
647
648   String lastConsoleEcho = "";
649
650   private void sendConsoleEcho(String string)
651   {
652     lastConsoleEcho += string;
653     lastConsoleEcho += "\n";
654   }
655
656   String lastConsoleMessage = "";
657
658   private void sendConsoleMessage(String string)
659   {
660     lastConsoleMessage += string;
661     lastConsoleMessage += "\n";
662   }
663
664   int lastScriptTermination = -1;
665
666   String lastScriptMessage = "";
667
668   private void notifyScriptTermination(String string, int intValue)
669   {
670     lastScriptMessage += string;
671     lastScriptMessage += "\n";
672     lastScriptTermination = intValue;
673   }
674
675   @Override
676   public boolean notifyEnabled(CBK callbackPick)
677   {
678     switch (callbackPick)
679     {
680     case MESSAGE:
681     case SCRIPT:
682     case ECHO:
683     case LOADSTRUCT:
684     case ERROR:
685       return true;
686     default:
687       return false;
688     }
689   }
690
691   /**
692    * Not implemented - returns null
693    */
694   @Override
695   public String eval(String strEval)
696   {
697     return null;
698   }
699
700   /**
701    * Not implemented - returns null
702    */
703   @Override
704   public float[][] functionXY(String functionName, int x, int y)
705   {
706     return null;
707   }
708
709   /**
710    * Not implemented - returns null
711    */
712   @Override
713   public float[][][] functionXYZ(String functionName, int nx, int ny,
714           int nz)
715   {
716     return null;
717   }
718
719   /**
720    * Not implemented - returns null
721    */
722   @Override
723   public String createImage(String fileName, String imageType,
724           Object text_or_bytes, int quality)
725   {
726     return null;
727   }
728
729   /**
730    * Not implemented - returns null
731    */
732   @Override
733   public Map<String, Object> getRegistryInfo()
734   {
735     return null;
736   }
737
738   /**
739    * Not implemented
740    */
741   @Override
742   public void showUrl(String url)
743   {
744   }
745
746   /**
747    * Not implemented - returns null
748    */
749   @Override
750   public int[] resizeInnerPanel(String data)
751   {
752     return null;
753   }
754
755   @Override
756   public Map<String, Object> getJSpecViewProperty(String arg0)
757   {
758     return null;
759   }
760
761   public boolean isPredictSecondaryStructure()
762   {
763     return predictSecondaryStructure;
764   }
765
766   public void setPredictSecondaryStructure(
767           boolean predictSecondaryStructure)
768   {
769     this.predictSecondaryStructure = predictSecondaryStructure;
770   }
771
772   public boolean isVisibleChainAnnotation()
773   {
774     return visibleChainAnnotation;
775   }
776
777   public void setVisibleChainAnnotation(boolean visibleChainAnnotation)
778   {
779     this.visibleChainAnnotation = visibleChainAnnotation;
780   }
781
782 }