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