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