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