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