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