18d8e05555d4101a2d9c101badf9c3f164609ea7
[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   PDBEntry.Type jmolFiletype=null;
164   /**
165    * resolve a jmol filetype string and update the jmolFiletype field accordingly
166    * @param jmolIdentifiedFileType
167    * @return true if filetype was identified as MMCIF, PDB
168    */
169   public boolean updateFileType(String jmolIdentifiedFileType)
170   {
171     if (jmolIdentifiedFileType == null 
172             || jmolIdentifiedFileType.trim().equals(""))
173     {
174       return false;
175     }
176     if ("mmcif".equalsIgnoreCase(jmolIdentifiedFileType)) {
177       jmolFiletype = PDBEntry.Type.MMCIF;
178       return true;
179     }
180     if ("pdb".equalsIgnoreCase(jmolIdentifiedFileType))
181     {
182       jmolFiletype = PDBEntry.Type.PDB;
183       return true;
184     } 
185     return false;
186   }
187   
188   public void transformJmolModelToJalview(ModelSet ms) throws IOException
189   {
190     try
191     {
192       Regex alphaFold = getNewAlphafoldValidator();
193       String lastID = "";
194       List<SequenceI> rna = new ArrayList<SequenceI>();
195       List<SequenceI> prot = new ArrayList<SequenceI>();
196       PDBChain tmpchain;
197       String pdbId = (String) ms.getInfo(0, "title");
198       boolean isMMCIF = false;
199       String jmolFileType_String = (String) ms.getInfo(0, "fileType");
200       if (updateFileType(jmolFileType_String))
201       {
202         setStructureFileType(jmolFiletype.toString());
203       }
204       
205       isMMCIF = PDBEntry.Type.MMCIF.equals(jmolFiletype);
206       
207       if (pdbId == null)
208       {
209         setId(safeName(getDataName()));
210         setPDBIdAvailable(false);
211       }
212       else
213       {
214         setId(pdbId);
215         setPDBIdAvailable(true);
216         alphaFoldModel = alphaFold.search(pdbId) && isMMCIF;  
217
218       }
219       List<Atom> significantAtoms = convertSignificantAtoms(ms);
220       for (Atom tmpatom : significantAtoms)
221       {
222         if (tmpatom.resNumIns.trim().equals(lastID))
223         {
224           // phosphorylated protein - seen both CA and P..
225           continue;
226         }
227         tmpchain = findChain(tmpatom.chain);
228         if (tmpchain != null)
229         {
230           tmpchain.atoms.addElement(tmpatom);
231         } else
232         {
233           String tempFString=null;
234           if (isAlphafoldModel())
235           {
236             tempFString = "Alphafold Reliability";
237           }
238
239           tmpchain = new PDBChain(getId(), tmpatom.chain,tempFString);
240           getChains().add(tmpchain);
241           tmpchain.atoms.addElement(tmpatom);
242         }
243         lastID = tmpatom.resNumIns.trim();
244       }
245       if (isParseImmediately())
246       {
247         // configure parsing settings from the static singleton
248         xferSettings();
249       }
250
251       makeResidueList();
252       makeCaBondList();
253
254       for (PDBChain chain : getChains())
255       {
256         SequenceI chainseq = postProcessChain(chain);
257         if (isRNA(chainseq))
258         {
259           rna.add(chainseq);
260         }
261         else
262         {
263           prot.add(chainseq);
264         }
265
266         // look at local setting for adding secondary tructure
267         if (predictSecondaryStructure)
268         {
269           createAnnotation(chainseq, chain, ms.at);
270         }
271       }
272     } catch (OutOfMemoryError er)
273     {
274       System.out.println(
275               "OUT OF MEMORY LOADING TRANSFORMING JMOL MODEL TO JALVIEW MODEL");
276       throw new IOException(MessageManager
277               .getString("exception.outofmemory_loading_mmcif_file"));
278     }
279   }
280
281   private boolean isAlphafoldModel()
282   {
283     return alphaFoldModel;
284   }
285
286   private List<Atom> convertSignificantAtoms(ModelSet ms)
287   {
288     List<Atom> significantAtoms = new ArrayList<Atom>();
289     HashMap<String, org.jmol.modelset.Atom> chainTerMap = new HashMap<String, org.jmol.modelset.Atom>();
290     org.jmol.modelset.Atom prevAtom = null;
291     for (org.jmol.modelset.Atom atom : ms.at)
292     {
293       if (atom.getAtomName().equalsIgnoreCase("CA")
294               || atom.getAtomName().equalsIgnoreCase("P"))
295       {
296         if (!atomValidated(atom, prevAtom, chainTerMap))
297         {
298           continue;
299         }
300         Atom curAtom = new Atom(atom.x, atom.y, atom.z);
301         curAtom.atomIndex = atom.getIndex();
302         curAtom.chain = atom.getChainIDStr();
303         curAtom.insCode = atom.group.getInsertionCode() == '\000' ? ' '
304                 : atom.group.getInsertionCode();
305         curAtom.name = atom.getAtomName();
306         curAtom.number = atom.getAtomNumber();
307         curAtom.resName = atom.getGroup3(true);
308         curAtom.resNumber = atom.getResno();
309         curAtom.occupancy = ms.occupancies != null
310                 ? ms.occupancies[atom.getIndex()]
311                 : Float.valueOf(atom.getOccupancy100());
312         String fmt = new Format("%4i").form(curAtom.resNumber);
313         curAtom.resNumIns = (fmt + curAtom.insCode);
314         curAtom.tfactor = atom.getBfactor100() / 100f;
315         curAtom.type = 0;
316         // significantAtoms.add(curAtom);
317         // ignore atoms from subsequent models
318         if (!significantAtoms.contains(curAtom))
319         {
320           significantAtoms.add(curAtom);
321         }
322         prevAtom = atom;
323       }
324     }
325     return significantAtoms;
326   }
327
328   private boolean atomValidated(org.jmol.modelset.Atom curAtom,
329           org.jmol.modelset.Atom prevAtom,
330           HashMap<String, org.jmol.modelset.Atom> chainTerMap)
331   {
332     // System.out.println("Atom: " + curAtom.getAtomNumber()
333     // + " Last atom index " + curAtom.group.lastAtomIndex);
334     if (chainTerMap == null || prevAtom == null)
335     {
336       return true;
337     }
338     String curAtomChId = curAtom.getChainIDStr();
339     String prevAtomChId = prevAtom.getChainIDStr();
340     // new chain encoutered
341     if (!prevAtomChId.equals(curAtomChId))
342     {
343       // On chain switch add previous chain termination to xTerMap if not exists
344       if (!chainTerMap.containsKey(prevAtomChId))
345       {
346         chainTerMap.put(prevAtomChId, prevAtom);
347       }
348       // if current atom belongs to an already terminated chain and the resNum
349       // diff < 5 then mark as valid and update termination Atom
350       if (chainTerMap.containsKey(curAtomChId))
351       {
352         if (curAtom.getResno() < chainTerMap.get(curAtomChId).getResno())
353         {
354           return false;
355         }
356         if ((curAtom.getResno()
357                 - chainTerMap.get(curAtomChId).getResno()) < 5)
358         {
359           chainTerMap.put(curAtomChId, curAtom);
360           return true;
361         }
362         return false;
363       }
364     }
365     // atom with previously terminated chain encountered
366     else if (chainTerMap.containsKey(curAtomChId))
367     {
368       if (curAtom.getResno() < chainTerMap.get(curAtomChId).getResno())
369       {
370         return false;
371       }
372       if ((curAtom.getResno()
373               - chainTerMap.get(curAtomChId).getResno()) < 5)
374       {
375         chainTerMap.put(curAtomChId, curAtom);
376         return true;
377       }
378       return false;
379     }
380     // HETATM with resNum jump > 2
381     return !(curAtom.isHetero()
382             && ((curAtom.getResno() - prevAtom.getResno()) > 2));
383   }
384
385   private void createAnnotation(SequenceI sequence, PDBChain chain,
386           org.jmol.modelset.Atom[] jmolAtoms)
387   {
388     char[] secstr = new char[sequence.getLength()];
389     char[] secstrcode = new char[sequence.getLength()];
390
391     // Ensure Residue size equals Seq size
392     if (chain.residues.size() != sequence.getLength())
393     {
394       return;
395     }
396     int annotIndex = 0;
397     for (Residue residue : chain.residues)
398     {
399       Atom repAtom = residue.getAtoms().get(0);
400       STR proteinStructureSubType = jmolAtoms[repAtom.atomIndex].group
401               .getProteinStructureSubType();
402       setSecondaryStructure(proteinStructureSubType, annotIndex, secstr,
403               secstrcode);
404       ++annotIndex;
405     }
406     addSecondaryStructureAnnotation(chain.pdbid, sequence, secstr,
407             secstrcode, chain.id, sequence.getStart());
408   }
409
410   /**
411    * Helper method that adds an AlignmentAnnotation for secondary structure to
412    * the sequence, provided at least one secondary structure assignment has been
413    * made
414    * 
415    * @param modelTitle
416    * @param seq
417    * @param secstr
418    * @param secstrcode
419    * @param chainId
420    * @param firstResNum
421    * @return
422    */
423   protected void addSecondaryStructureAnnotation(String modelTitle,
424           SequenceI sq, char[] secstr, char[] secstrcode, String chainId,
425           int firstResNum)
426   {
427     int length = sq.getLength();
428     boolean ssFound = false;
429     Annotation asecstr[] = new Annotation[length + firstResNum - 1];
430     for (int p = 0; p < length; p++)
431     {
432       if (secstr[p] >= 'A' && secstr[p] <= 'z')
433       {
434         try
435         {
436           asecstr[p] = new Annotation(String.valueOf(secstr[p]), null,
437                   secstrcode[p], Float.NaN);
438           ssFound = true;
439         } catch (Exception e)
440         {
441           // e.printStackTrace();
442         }
443       }
444     }
445
446     if (ssFound)
447     {
448       String mt = modelTitle == null ? getDataName() : modelTitle;
449       mt += chainId;
450       AlignmentAnnotation ann = new AlignmentAnnotation(
451               "Secondary Structure", "Secondary Structure for " + mt,
452               asecstr);
453       ann.belowAlignment = true;
454       ann.visible = true;
455       ann.autoCalculated = false;
456       ann.setCalcId(getClass().getName());
457       ann.adjustForAlignment();
458       ann.validateRangeAndDisplay();
459       annotations.add(ann);
460       sq.addAlignmentAnnotation(ann);
461     }
462   }
463
464   private void waitForScript(Viewer jmd)
465   {
466     while (jmd.isScriptExecuting())
467     {
468       try
469       {
470         Thread.sleep(50);
471
472       } catch (InterruptedException x)
473       {
474       }
475     }
476   }
477
478   /**
479    * Convert Jmol's secondary structure code to Jalview's, and stored it in the
480    * secondary structure arrays at the given sequence position
481    * 
482    * @param proteinStructureSubType
483    * @param pos
484    * @param secstr
485    * @param secstrcode
486    */
487   protected void setSecondaryStructure(STR proteinStructureSubType, int pos,
488           char[] secstr, char[] secstrcode)
489   {
490     switch (proteinStructureSubType)
491     {
492     case HELIX310:
493       secstr[pos] = '3';
494       break;
495     case HELIX:
496     case HELIXALPHA:
497       secstr[pos] = 'H';
498       break;
499     case HELIXPI:
500       secstr[pos] = 'P';
501       break;
502     case SHEET:
503       secstr[pos] = 'E';
504       break;
505     default:
506       secstr[pos] = 0;
507     }
508
509     switch (proteinStructureSubType)
510     {
511     case HELIX310:
512     case HELIXALPHA:
513     case HELIXPI:
514     case HELIX:
515       secstrcode[pos] = 'H';
516       break;
517     case SHEET:
518       secstrcode[pos] = 'E';
519       break;
520     default:
521       secstrcode[pos] = 0;
522     }
523   }
524
525   /**
526    * Convert any non-standard peptide codes to their standard code table
527    * equivalent. (Initial version only does Selenomethionine MSE->MET.)
528    * 
529    * @param threeLetterCode
530    * @param seq
531    * @param pos
532    */
533   protected void replaceNonCanonicalResidue(String threeLetterCode,
534           char[] seq, int pos)
535   {
536     String canonical = ResidueProperties
537             .getCanonicalAminoAcid(threeLetterCode);
538     if (canonical != null && !canonical.equalsIgnoreCase(threeLetterCode))
539     {
540       seq[pos] = ResidueProperties.getSingleCharacterCode(canonical);
541     }
542   }
543
544   /**
545    * Not implemented - returns null
546    */
547   @Override
548   public String print(SequenceI[] seqs, boolean jvSuffix)
549   {
550     return null;
551   }
552
553   /**
554    * Not implemented
555    */
556   @Override
557   public void setCallbackFunction(String callbackType,
558           String callbackFunction)
559   {
560   }
561
562   @Override
563   public void notifyCallback(CBK cbType, Object[] data)
564   {
565     String strInfo = (data == null || data[1] == null ? null
566             : data[1].toString());
567     switch (cbType)
568     {
569     case ECHO:
570       sendConsoleEcho(strInfo);
571       break;
572     case SCRIPT:
573       notifyScriptTermination((String) data[2],
574               ((Integer) data[3]).intValue());
575       break;
576     case MEASURE:
577       String mystatus = (String) data[3];
578       if (mystatus.indexOf("Picked") >= 0
579               || mystatus.indexOf("Sequence") >= 0)
580       {
581         // Picking mode
582         sendConsoleMessage(strInfo);
583       }
584       else if (mystatus.indexOf("Completed") >= 0)
585       {
586         sendConsoleEcho(strInfo.substring(strInfo.lastIndexOf(",") + 2,
587                 strInfo.length() - 1));
588       }
589       break;
590     case MESSAGE:
591       sendConsoleMessage(data == null ? null : strInfo);
592       break;
593     case PICK:
594       sendConsoleMessage(strInfo);
595       break;
596     default:
597       break;
598     }
599   }
600
601   String lastConsoleEcho = "";
602
603   private void sendConsoleEcho(String string)
604   {
605     lastConsoleEcho += string;
606     lastConsoleEcho += "\n";
607   }
608
609   String lastConsoleMessage = "";
610
611   private void sendConsoleMessage(String string)
612   {
613     lastConsoleMessage += string;
614     lastConsoleMessage += "\n";
615   }
616
617   int lastScriptTermination = -1;
618
619   String lastScriptMessage = "";
620
621   private void notifyScriptTermination(String string, int intValue)
622   {
623     lastScriptMessage += string;
624     lastScriptMessage += "\n";
625     lastScriptTermination = intValue;
626   }
627
628   @Override
629   public boolean notifyEnabled(CBK callbackPick)
630   {
631     switch (callbackPick)
632     {
633     case MESSAGE:
634     case SCRIPT:
635     case ECHO:
636     case LOADSTRUCT:
637     case ERROR:
638       return true;
639     default:
640       return false;
641     }
642   }
643
644   /**
645    * Not implemented - returns null
646    */
647   @Override
648   public String eval(String strEval)
649   {
650     return null;
651   }
652
653   /**
654    * Not implemented - returns null
655    */
656   @Override
657   public float[][] functionXY(String functionName, int x, int y)
658   {
659     return null;
660   }
661
662   /**
663    * Not implemented - returns null
664    */
665   @Override
666   public float[][][] functionXYZ(String functionName, int nx, int ny,
667           int nz)
668   {
669     return null;
670   }
671
672   /**
673    * Not implemented - returns null
674    */
675   @Override
676   public String createImage(String fileName, String imageType,
677           Object text_or_bytes, int quality)
678   {
679     return null;
680   }
681
682   /**
683    * Not implemented - returns null
684    */
685   @Override
686   public Map<String, Object> getRegistryInfo()
687   {
688     return null;
689   }
690
691   /**
692    * Not implemented
693    */
694   @Override
695   public void showUrl(String url)
696   {
697   }
698
699   /**
700    * Not implemented - returns null
701    */
702   @Override
703   public int[] resizeInnerPanel(String data)
704   {
705     return null;
706   }
707
708   @Override
709   public Map<String, Object> getJSpecViewProperty(String arg0)
710   {
711     return null;
712   }
713
714   public boolean isPredictSecondaryStructure()
715   {
716     return predictSecondaryStructure;
717   }
718
719   public void setPredictSecondaryStructure(
720           boolean predictSecondaryStructure)
721   {
722     this.predictSecondaryStructure = predictSecondaryStructure;
723   }
724
725   public boolean isVisibleChainAnnotation()
726   {
727     return visibleChainAnnotation;
728   }
729
730   public void setVisibleChainAnnotation(boolean visibleChainAnnotation)
731   {
732     this.visibleChainAnnotation = visibleChainAnnotation;
733   }
734
735 }