JAL-2148 Bugfix in structureImportSettings string-to-enum conversion and further...
[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.SequenceI;
26 import jalview.io.FileParse;
27 import jalview.io.StructureFile;
28 import jalview.schemes.ResidueProperties;
29 import jalview.structure.StructureImportSettings;
30 import jalview.util.MessageManager;
31
32 import java.io.IOException;
33 import java.util.ArrayList;
34 import java.util.HashMap;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Vector;
38
39 import javajs.awt.Dimension;
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 MCview.Atom;
49 import MCview.PDBChain;
50 import MCview.Residue;
51
52 /**
53  * Import and process files with Jmol for file like PDB, mmCIF
54  * 
55  * @author jprocter
56  * 
57  */
58 public class JmolParser extends StructureFile implements JmolStatusListener
59 {
60   Viewer viewer = null;
61
62   public JmolParser(boolean addAlignmentAnnotations, boolean predictSecStr,
63           boolean externalSecStr, String inFile, String type)
64           throws IOException
65   {
66     super(inFile, type);
67   }
68
69   public JmolParser(boolean addAlignmentAnnotations, boolean predictSecStr,
70           boolean externalSecStr, FileParse fp) throws IOException
71   {
72     super(fp);
73   }
74
75   public JmolParser()
76   {
77   }
78
79   /**
80    * Calls the Jmol library to parse the PDB/mmCIF file, and then inspects the
81    * resulting object model to generate Jalview-style sequences, with secondary
82    * structure annotation added where available (i.e. where it has been computed
83    * by Jmol using DSSP).
84    * 
85    * @see jalview.io.AlignFile#parse()
86    */
87   @Override
88   public void parse() throws IOException
89   {
90     setChains(new Vector<PDBChain>());
91     Viewer jmolModel = getJmolData();
92     jmolModel.openReader(getDataName(), getDataName(), getReader());
93     waitForScript(jmolModel);
94
95     /*
96      * Convert one or more Jmol Model objects to Jalview sequences
97      */
98     if (jmolModel.ms.mc > 0)
99     {
100       transformJmolModelToJalview(jmolModel.ms);
101     }
102   }
103
104   /**
105    * create a headless jmol instance for dataprocessing
106    * 
107    * @return
108    */
109   private Viewer getJmolData()
110   {
111     if (viewer == null)
112     {
113       try
114       {
115         viewer = (Viewer) JmolViewer.allocateViewer(null, null, null, null,
116                 null, "-x -o -n", this);
117         // ensure the 'new' (DSSP) not 'old' (Ramachandran) SS method is used
118         viewer.setBooleanProperty("defaultStructureDSSP", true);
119       } catch (ClassCastException x)
120       {
121         throw new Error(MessageManager.formatMessage(
122                 "error.jmol_version_not_compatible_with_jalview_version",
123                 new String[] { JmolViewer.getJmolVersion() }), x);
124       }
125     }
126     return viewer;
127   }
128
129   public void transformJmolModelToJalview(ModelSet ms) throws IOException
130   {
131     try
132     {
133       String lastID = "";
134       List<SequenceI> rna = new ArrayList<SequenceI>();
135       List<SequenceI> prot = new ArrayList<SequenceI>();
136       PDBChain tmpchain;
137       String pdbId = (String) ms.getInfo(0, "title");
138       setId(pdbId);
139       List<Atom> significantAtoms = convertSignificantAtoms(ms);
140       for (Atom tmpatom : significantAtoms)
141       {
142         try
143         {
144           tmpchain = findChain(tmpatom.chain);
145           if (tmpatom.resNumIns.trim().equals(lastID))
146           {
147             // phosphorylated protein - seen both CA and P..
148             continue;
149           }
150           tmpchain.atoms.addElement(tmpatom);
151         } catch (Exception e)
152         {
153           tmpchain = new PDBChain(pdbId, tmpatom.chain);
154           getChains().add(tmpchain);
155           tmpchain.atoms.addElement(tmpatom);
156         }
157         lastID = tmpatom.resNumIns.trim();
158       }
159       xferSettings();
160
161       makeResidueList();
162       makeCaBondList();
163
164       if (getId() == null)
165       {
166         setId(safeName(getDataName()));
167       }
168       for (PDBChain chain : getChains())
169       {
170         SequenceI chainseq = postProcessChain(chain);
171         if (isRNA(chainseq))
172         {
173           rna.add(chainseq);
174         }
175         else
176         {
177           prot.add(chainseq);
178         }
179
180         if (StructureImportSettings.isProcessSecondaryStructure())
181         {
182           createAnnotation(chainseq, chain, ms.at);
183         }
184       }
185     } catch (OutOfMemoryError er)
186     {
187       System.out
188               .println("OUT OF MEMORY LOADING TRANSFORMING JMOL MODEL TO JALVIEW MODEL");
189       throw new IOException(
190               MessageManager
191                       .getString("exception.outofmemory_loading_mmcif_file"));
192     }
193   }
194
195   private List<Atom> convertSignificantAtoms(ModelSet ms)
196   {
197     List<Atom> significantAtoms = new ArrayList<Atom>();
198     HashMap<String, org.jmol.modelset.Atom> chainTerMap = new HashMap<String, org.jmol.modelset.Atom>();
199     org.jmol.modelset.Atom prevAtom = null;
200     for (org.jmol.modelset.Atom atom : ms.at)
201     {
202       if (atom.getAtomName().equalsIgnoreCase("CA")
203               || atom.getAtomName().equalsIgnoreCase("P"))
204       {
205         if (!atomValidated(atom, prevAtom, chainTerMap))
206         {
207           continue;
208         }
209         Atom curAtom = new Atom(atom.x, atom.y, atom.z);
210         curAtom.atomIndex = atom.getIndex();
211         curAtom.chain = atom.getChainIDStr();
212         curAtom.insCode = atom.group.getInsertionCode();
213         curAtom.name = atom.getAtomName();
214         curAtom.number = atom.getAtomNumber();
215         curAtom.resName = atom.getGroup3(true);
216         curAtom.resNumber = atom.getResno();
217         curAtom.occupancy = ms.occupancies != null ? ms.occupancies[atom
218                 .getIndex()] : Float.valueOf(atom.getOccupancy100());
219         curAtom.resNumIns = "" + curAtom.resNumber + curAtom.insCode;
220         curAtom.tfactor = atom.getBfactor100() / 100f;
221         curAtom.type = 0;
222         // significantAtoms.add(curAtom);
223         // ignore atoms from subsequent models
224         if (!significantAtoms.contains(curAtom))
225         {
226           significantAtoms.add(curAtom);
227         }
228         prevAtom = atom;
229       }
230     }
231     return significantAtoms;
232   }
233
234   private boolean atomValidated(org.jmol.modelset.Atom curAtom,
235           org.jmol.modelset.Atom prevAtom,
236           HashMap<String, org.jmol.modelset.Atom> chainTerMap)
237   {
238     // System.out.println("Atom: " + curAtom.getAtomNumber()
239     // + "   Last atom index " + curAtom.group.lastAtomIndex);
240     if (chainTerMap == null || prevAtom == null)
241     {
242       return true;
243     }
244     String curAtomChId = curAtom.getChainIDStr();
245     String prevAtomChId = prevAtom.getChainIDStr();
246     // new chain encoutered
247     if (!prevAtomChId.equals(curAtomChId))
248     {
249       // On chain switch add previous chain termination to xTerMap if not exists
250       if (!chainTerMap.containsKey(prevAtomChId))
251       {
252         chainTerMap.put(prevAtomChId, prevAtom);
253       }
254       // if current atom belongs to an already terminated chain and the resNum
255       // diff < 5 then mark as valid and update termination Atom
256       if (chainTerMap.containsKey(curAtomChId))
257       {
258         if ((curAtom.getResno() - chainTerMap.get(curAtomChId).getResno()) < 5)
259         {
260           chainTerMap.put(curAtomChId, curAtom);
261           return true;
262         }
263         return false;
264       }
265     }
266     // atom with previously terminated chain encountered
267     else if (chainTerMap.containsKey(curAtomChId))
268     {
269       if ((curAtom.getResno() - chainTerMap.get(curAtomChId).getResno()) < 5)
270       {
271         chainTerMap.put(curAtomChId, curAtom);
272         return true;
273       }
274       return false;
275     }
276     // HETATM with resNum jump > 2
277     return !(curAtom.isHetero() && ((curAtom.getResno() - prevAtom
278             .getResno()) > 2));
279   }
280
281   private void createAnnotation(SequenceI sequence, PDBChain chain,
282           org.jmol.modelset.Atom[] jmolAtoms)
283   {
284     char[] secstr = new char[sequence.getLength()];
285     char[] secstrcode = new char[sequence.getLength()];
286
287     // Ensure Residue size equals Seq size
288     if (chain.residues.size() != sequence.getLength())
289     {
290       return;
291     }
292     int annotIndex = 0;
293     for (Residue residue : chain.residues)
294     {
295       Atom repAtom = residue.getAtoms().get(0);
296       STR proteinStructureSubType = jmolAtoms[repAtom.atomIndex].group
297               .getProteinStructureSubType();
298       setSecondaryStructure(proteinStructureSubType, annotIndex, secstr,
299               secstrcode);
300       ++annotIndex;
301     }
302     addSecondaryStructureAnnotation(chain.pdbid, sequence, secstr,
303             secstrcode, chain.id, sequence.getStart());
304   }
305
306   /**
307    * Helper method that adds an AlignmentAnnotation for secondary structure to
308    * the sequence, provided at least one secondary structure prediction has been
309    * made
310    * 
311    * @param modelTitle
312    * @param seq
313    * @param secstr
314    * @param secstrcode
315    * @param chainId
316    * @param firstResNum
317    * @return
318    */
319   protected void addSecondaryStructureAnnotation(String modelTitle,
320           SequenceI sq, char[] secstr, char[] secstrcode, String chainId,
321           int firstResNum)
322   {
323     char[] seq = sq.getSequence();
324     boolean ssFound = false;
325     Annotation asecstr[] = new Annotation[seq.length + firstResNum - 1];
326     for (int p = 0; p < seq.length; p++)
327     {
328       if (secstr[p] >= 'A' && secstr[p] <= 'z')
329       {
330         try
331         {
332         asecstr[p] = new Annotation(String.valueOf(secstr[p]), null,
333                 secstrcode[p], Float.NaN);
334         ssFound = true;
335         } catch (Exception e)
336         {
337           // e.printStackTrace();
338         }
339       }
340     }
341
342     if (ssFound)
343     {
344       String mt = modelTitle == null ? getDataName() : modelTitle;
345       mt += chainId;
346       AlignmentAnnotation ann = new AlignmentAnnotation(
347               "Secondary Structure", "Secondary Structure for " + mt,
348               asecstr);
349       ann.belowAlignment = true;
350       ann.visible = true;
351       ann.autoCalculated = false;
352       ann.setCalcId(getClass().getName());
353       ann.adjustForAlignment();
354       ann.validateRangeAndDisplay();
355       annotations.add(ann);
356       sq.addAlignmentAnnotation(ann);
357     }
358   }
359
360   private void waitForScript(Viewer jmd)
361   {
362     while (jmd.isScriptExecuting())
363     {
364       try
365       {
366         Thread.sleep(50);
367
368       } catch (InterruptedException x)
369       {
370       }
371     }
372   }
373
374   /**
375    * Convert Jmol's secondary structure code to Jalview's, and stored it in the
376    * secondary structure arrays at the given sequence position
377    * 
378    * @param proteinStructureSubType
379    * @param pos
380    * @param secstr
381    * @param secstrcode
382    */
383   protected void setSecondaryStructure(STR proteinStructureSubType,
384           int pos, char[] secstr, char[] secstrcode)
385   {
386     switch (proteinStructureSubType)
387     {
388     case HELIX310:
389       secstr[pos] = '3';
390       break;
391     case HELIX:
392     case HELIXALPHA:
393       secstr[pos] = 'H';
394       break;
395     case HELIXPI:
396       secstr[pos] = 'P';
397       break;
398     case SHEET:
399       secstr[pos] = 'E';
400       break;
401     default:
402       secstr[pos] = 0;
403     }
404
405     switch (proteinStructureSubType)
406     {
407     case HELIX310:
408     case HELIXALPHA:
409     case HELIXPI:
410     case HELIX:
411       secstrcode[pos] = 'H';
412       break;
413     case SHEET:
414       secstrcode[pos] = 'E';
415       break;
416     default:
417       secstrcode[pos] = 0;
418     }
419   }
420
421   /**
422    * Convert any non-standard peptide codes to their standard code table
423    * equivalent. (Initial version only does Selenomethionine MSE->MET.)
424    * 
425    * @param threeLetterCode
426    * @param seq
427    * @param pos
428    */
429   protected void replaceNonCanonicalResidue(String threeLetterCode,
430           char[] seq, int pos)
431   {
432     String canonical = ResidueProperties
433             .getCanonicalAminoAcid(threeLetterCode);
434     if (canonical != null && !canonical.equalsIgnoreCase(threeLetterCode))
435     {
436       seq[pos] = ResidueProperties.getSingleCharacterCode(canonical);
437     }
438   }
439
440   /**
441    * Not implemented - returns null
442    */
443   @Override
444   public String print()
445   {
446     return null;
447   }
448
449   /**
450    * Not implemented
451    */
452   @Override
453   public void setCallbackFunction(String callbackType,
454           String callbackFunction)
455   {
456   }
457
458   @Override
459   public void notifyCallback(CBK cbType, Object[] data)
460   {
461     String strInfo = (data == null || data[1] == null ? null : data[1]
462             .toString());
463     switch (cbType)
464     {
465     case ECHO:
466       sendConsoleEcho(strInfo);
467       break;
468     case SCRIPT:
469       notifyScriptTermination((String) data[2],
470               ((Integer) data[3]).intValue());
471       break;
472     case MEASURE:
473       String mystatus = (String) data[3];
474       if (mystatus.indexOf("Picked") >= 0
475               || mystatus.indexOf("Sequence") >= 0)
476       {
477         // Picking mode
478         sendConsoleMessage(strInfo);
479       }
480       else if (mystatus.indexOf("Completed") >= 0)
481       {
482         sendConsoleEcho(strInfo.substring(strInfo.lastIndexOf(",") + 2,
483                 strInfo.length() - 1));
484       }
485       break;
486     case MESSAGE:
487       sendConsoleMessage(data == null ? null : strInfo);
488       break;
489     case PICK:
490       sendConsoleMessage(strInfo);
491       break;
492     default:
493       break;
494     }
495   }
496
497   String lastConsoleEcho = "";
498
499   private void sendConsoleEcho(String string)
500   {
501     lastConsoleEcho += string;
502     lastConsoleEcho += "\n";
503   }
504
505   String lastConsoleMessage = "";
506
507   private void sendConsoleMessage(String string)
508   {
509     lastConsoleMessage += string;
510     lastConsoleMessage += "\n";
511   }
512
513   int lastScriptTermination = -1;
514
515   String lastScriptMessage = "";
516
517   private void notifyScriptTermination(String string, int intValue)
518   {
519     lastScriptMessage += string;
520     lastScriptMessage += "\n";
521     lastScriptTermination = intValue;
522   }
523
524   @Override
525   public boolean notifyEnabled(CBK callbackPick)
526   {
527     switch (callbackPick)
528     {
529     case MESSAGE:
530     case SCRIPT:
531     case ECHO:
532     case LOADSTRUCT:
533     case ERROR:
534       return true;
535     default:
536       return false;
537     }
538   }
539
540   /**
541    * Not implemented - returns null
542    */
543   @Override
544   public String eval(String strEval)
545   {
546     return null;
547   }
548
549   /**
550    * Not implemented - returns null
551    */
552   @Override
553   public float[][] functionXY(String functionName, int x, int y)
554   {
555     return null;
556   }
557
558   /**
559    * Not implemented - returns null
560    */
561   @Override
562   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
563   {
564     return null;
565   }
566
567   /**
568    * Not implemented - returns null
569    */
570   @Override
571   public String createImage(String fileName, String imageType,
572           Object text_or_bytes, int quality)
573   {
574     return null;
575   }
576
577   /**
578    * Not implemented - returns null
579    */
580   @Override
581   public Map<String, Object> getRegistryInfo()
582   {
583     return null;
584   }
585
586   /**
587    * Not implemented
588    */
589   @Override
590   public void showUrl(String url)
591   {
592   }
593
594   /**
595    * Not implemented - returns null
596    */
597   @Override
598   public Dimension resizeInnerPanel(String data)
599   {
600     return null;
601   }
602
603   @Override
604   public Map<String, Object> getJSpecViewProperty(String arg0)
605   {
606     return null;
607   }
608
609   public boolean isPredictSecondaryStructure()
610   {
611     return predictSecondaryStructure;
612   }
613
614   public void setPredictSecondaryStructure(boolean predictSecondaryStructure)
615   {
616     this.predictSecondaryStructure = predictSecondaryStructure;
617   }
618
619   public boolean isVisibleChainAnnotation()
620   {
621     return visibleChainAnnotation;
622   }
623
624   public void setVisibleChainAnnotation(boolean visibleChainAnnotation)
625   {
626     this.visibleChainAnnotation = visibleChainAnnotation;
627   }
628
629 }