Merge branch 'develop' into features/hmmer
[jalview.git] / src / jalview / io / HMMFile.java
1 package jalview.io;
2
3 import jalview.api.AlignExportSettingI;
4 import jalview.api.AlignmentViewPanel;
5 import jalview.datamodel.HMMNode;
6 import jalview.datamodel.HiddenMarkovModel;
7 import jalview.datamodel.SequenceI;
8
9 import java.io.BufferedReader;
10 import java.io.IOException;
11 import java.util.ArrayList;
12 import java.util.List;
13 import java.util.Scanner;
14
15
16 /**
17  * Adds capability to read in and write out HMMER3 files. .
18  * 
19  * 
20  * @author TZVanaalten
21  *
22  */
23 public class HMMFile extends AlignFile
24         implements AlignmentFileReaderI, AlignmentFileWriterI
25 {
26   // HMM to store file data
27   private HiddenMarkovModel hmm;
28
29   // number of possible transitions
30   private static final int NUMBER_OF_TRANSITIONS = 7;
31
32   private String NL = "\n";
33
34   //number of symbols in the alphabet used in the hidden Markov model
35   int numberOfSymbols;
36
37   private final String SPACE = " ";
38
39   private final String COMPO = "COMPO";
40
41   private final String EMPTY = "";
42
43   //This is a line that needs to be added to each HMMER� file. It is purely for readability.
44   private static final String TRANSITIONTYPELINE = "            m->m     m->i     m->d     i->m     i->i     d->m     d->d";
45
46   /**
47    * Parses immediately.
48    * 
49    * @param inFile
50    * @param type
51    * @throws IOException
52    */
53   public HMMFile(String inFile, DataSourceType type) throws IOException
54   {
55     super(inFile, type);
56   }
57
58   /**
59    * Parses immediately.
60    * 
61    * @param source
62    * @throws IOException
63    */
64   public HMMFile(FileParse source) throws IOException
65   {
66     super(source);
67   }
68
69   /**
70    * Default constructor, do not use!
71    */
72   public HMMFile()
73   {
74
75   }
76
77   /**
78    * Constructor for HMMFile used for exporting.
79    * 
80    * @param hmm
81    * @param exportImmediately
82    */
83   public HMMFile(HiddenMarkovModel markov)
84   {
85     hmm = markov;
86   }
87
88   /**
89    * For testing, do not use.
90    * 
91    * @param br
92    */
93   HMMFile(BufferedReader br)
94   {
95     dataIn = br;
96   }
97
98   /**
99    * Returns the HMM produced by reading in a HMMER3 file.
100    * 
101    * @return
102    */
103   public HiddenMarkovModel getHMM()
104   {
105     return hmm;
106   }
107
108   /**
109    * Sets the HMM used in this file.
110    * 
111    * @param model
112    */
113   public void setHMM(HiddenMarkovModel model)
114   {
115     this.hmm = model;
116   }
117
118   /**
119    * Gets the name of the hidden Markov model.
120    * 
121    * @return
122    */
123   public String getName()
124   {
125     return hmm.getName();
126   }
127
128   /**
129    * Reads the data from HMM file into the HMM field on this object.
130    * 
131    * @throws IOException
132    */
133   @Override
134   public void parse() throws IOException
135   {
136     hmm = new HiddenMarkovModel();
137     parseFileProperties(dataIn);
138     parseModel(dataIn);
139   }
140
141   /**
142    * Reads the data from HMM file into the HMM field on this object.
143    * 
144    * @throws IOException
145    */
146
147   public void parse(BufferedReader br) throws IOException
148   {
149     hmm = new HiddenMarkovModel();
150     parseFileProperties(br);
151     parseModel(br);
152   }
153
154
155
156   /**
157    * Imports the file properties from a HMMER3 file.
158    * 
159    * @param input
160    *          The buffered reader used to read in the file.
161    * @throws IOException
162    */
163   void parseFileProperties(BufferedReader input) throws IOException
164   {
165     boolean readingFile = true;
166     hmm.setFileHeader(input.readLine());
167     String line = input.readLine();
168     while (readingFile)
169     {
170       if (line != null)
171       {
172         Scanner parser = new Scanner(line);
173         String next = parser.next();
174         if ("HMM".equals(next)) // indicates start of HMM data (end of file
175                               // properties)
176         {
177           readingFile = false;
178           fillSymbols(parser);
179           numberOfSymbols = hmm.getNumberOfSymbols();
180         }
181         else if ("STATS".equals(next))
182         {
183           parser.next();
184           String key;
185           String value;
186           key = parser.next();
187           value = parser.next() + SPACE + SPACE + parser.next();
188           hmm.addFileProperty(key, value);
189         }
190         else
191         {
192           String key = next;
193           String value = parser.next();
194           while (parser.hasNext())
195           {
196             value = value + SPACE + parser.next();
197           }
198           hmm.addFileProperty(key, value);
199         }
200         parser.close();
201       }
202       line = input.readLine();
203       if (line == null)
204       {
205         readingFile = false;
206       }
207     }
208
209   }
210
211   /**
212    * Parses the model data from the HMMER3 file
213    * 
214    * @param input
215    *          The buffered reader used to read the file.
216    * @throws IOException
217    */
218   void parseModel(BufferedReader input) throws IOException
219   {
220     String line = input.readLine();
221     int node = 0;
222     while (!"//".equals(line))
223     {
224       hmm.getNodes().add(new HMMNode());
225       String next;
226       Scanner matchReader = new Scanner(line);
227       next = matchReader.next();
228       if (next.equals(COMPO) || node > 0)
229       {
230         // stores match emission line in list
231         List<Double> matches = new ArrayList<>();
232         matches = fillList(matchReader, numberOfSymbols);
233         hmm.getNodes().get(node).setMatchEmissions(matches);
234         if (node > 0)
235         {
236           parseAnnotations(matchReader, node);
237         }
238       }
239       matchReader.close();
240       // stores insert emission line in list
241       line = input.readLine();
242       Scanner insertReader = new Scanner(line);
243       List<Double> inserts = new ArrayList<>();
244       inserts = fillList(insertReader, numberOfSymbols);
245       hmm.getNodes().get(node).setInsertEmissions(inserts);
246       insertReader.close();
247
248       // stores state transition line in list
249       line = input.readLine();
250       Scanner transitionReader = new Scanner(line);
251       List<Double> transitions = new ArrayList<>();
252       transitions = fillList(transitionReader, NUMBER_OF_TRANSITIONS);
253       hmm.getNodes().get(node).setStateTransitions(transitions);
254       transitionReader.close();
255       line = input.readLine();
256       node++;
257     }
258
259   }
260
261   /**
262    * Parses the annotations on the match emission line.
263    * 
264    * @param scanner
265    *          The scanner which is processing match emission line.
266    * @param index
267    *          The index of node which is being scanned.
268    */
269   void parseAnnotations(Scanner scanner, int index)
270   {
271     if (hmm.mapIsActive() && scanner.hasNext())
272     {
273       int column;
274       column = scanner.nextInt();
275       hmm.getNodes().get(index).setAlignmentColumn(column - 1);
276       hmm.getNodeLookup().put(column - 1, index);
277     }
278     else
279     {
280       scanner.next();
281     }
282
283     if (scanner.hasNext())
284     {
285       char consensusR;
286       consensusR = charValue(scanner.next());
287       hmm.getNodes().get(index).setConsensusResidue(consensusR);
288     }
289
290     if (scanner.hasNext())
291     {
292       char reference;
293       reference = charValue(scanner.next());
294       hmm.getNodes().get(index).setReferenceAnnotation(reference);
295     }
296
297     if (scanner.hasNext())
298     {
299       char value;
300       value = charValue(scanner.next());
301       hmm.getNodes().get(index).setMaskValue(value);
302     }
303     if (scanner.hasNext())
304     {
305       char consensusS;
306       consensusS = charValue(scanner.next());
307       hmm.getNodes().get(index).setConsensusStructure(consensusS);
308     }
309   }
310
311
312
313   /**
314    * Fills a list of doubles based on an input line.
315    * 
316    * @param input
317    *          The scanner for the line containing the data to be transferred to
318    *          the list.
319    * @param numberOfElements
320    *          The number of elements in the list to be filled.
321    * @return filled list Returns the list of doubles.
322    * @throws IOException
323    */
324   static List<Double> fillList(Scanner input,
325           int numberOfElements) throws IOException
326   {
327     List<Double> list = new ArrayList<>();
328     for (int i = 0; i < numberOfElements; i++)
329     {
330
331       String next = input.next();
332       if (next.contains("*")) // state transitions to or from delete states
333                               // occasionally have values of -infinity. These
334                               // values are represented by an * in the .hmm
335                               // file.
336       {
337         list.add(Double.NEGATIVE_INFINITY);
338       }
339       else
340       {
341         double prob = Double.valueOf(next);
342         prob = Math.pow(Math.E, -prob);
343         list.add(prob);
344       }
345     }
346     if (list.size() < numberOfElements)
347     {
348       throw new IOException("Incomplete data");
349     }
350     return list;
351   }
352
353   /**
354    * Returns a string to be added to the StringBuilder containing the entire
355    * output String.
356    * 
357    * @param initialColumnSeparation
358    *          The initial whitespace separation between the left side of the
359    *          file and first character.
360    * @param columnSeparation
361    *          The separation between subsequent data entries.
362    * @param data
363    *          The list fo data to be added to the String.
364    * @return
365    */
366   String addData(int initialColumnSeparation,
367           int columnSeparation, List<String> data)
368   {
369     String line = EMPTY;
370     int index = 0;
371     for (String value : data)
372     {
373       if (index == 0)
374       {
375         line += String.format("%" + initialColumnSeparation + "s", value);
376       }
377       else
378       {
379         line += String.format("%" + columnSeparation + "s", value);
380       }
381       index++;
382     }
383     return line;
384   }
385
386   /**
387    * Converts list of characters into a list of Strings.
388    * 
389    * @param list
390    * @return Returns the list of Strings.
391    */
392   List<String> charListToStringList(List<Character> list)
393   {
394     List<String> strList = new ArrayList<>();
395     for (char value : list)
396     {
397       String strValue = Character.toString(value);
398       strList.add(strValue);
399     }
400     return strList;
401   }
402
403   /**
404    * Converts a list of doubles into a list of Strings, rounded to the nearest
405    * 5th decimal place.
406    * 
407    * @param list
408    * @param noOfDecimals
409    * @return
410    */
411   List<String> doubleListToStringList(List<Double> list)
412   {
413     List<String> strList = new ArrayList<>();
414     for (double value : list)
415     {
416       String strValue;
417       if (value > 0)
418       {
419         strValue = String.format("%.5f", value);
420
421       }
422       else if (value == -0.00000d)
423       {
424         strValue = "0.00000";
425       }
426       else
427       {
428         strValue = "*";
429       }
430
431       strList.add(strValue);
432     }
433     return strList;
434   }
435
436   /**
437    * Converts a primitive array of Strings to a list of Strings.
438    * 
439    * @param array
440    * @return
441    */
442   List<String> stringArrayToStringList(String[] array)
443   {
444     List<String> list = new ArrayList<>();
445     for (String value : array)
446     {
447       list.add(value);
448     }
449
450     return list;
451   }
452
453   /**
454    * Returns a string containing the model data.
455    */
456   String getModelAsString()
457   {
458     StringBuffer output = new StringBuffer();
459     String symbolLine = "HMM";
460     List<Character> charSymbols = hmm.getSymbols();
461     List<String> strSymbols;
462     strSymbols = charListToStringList(charSymbols);
463     symbolLine += addData(11, 9, strSymbols);
464     output.append(symbolLine);
465     output.append(NL + TRANSITIONTYPELINE);
466
467     int length = hmm.getLength();
468
469     for (int node = 0; node <= length; node++)
470     {
471       String matchLine;
472       if (node == 0)
473       {
474         matchLine = String.format("%7s", "COMPO");
475       }
476       else
477       {
478         matchLine = String.format("%7s", node);
479       }
480
481       List<String> strMatches;
482       List<Double> doubleMatches;
483       doubleMatches = convertListToLogSpace(
484               hmm.getNode(node).getMatchEmissions());
485       strMatches = doubleListToStringList(doubleMatches);
486       matchLine += addData(10, 9, strMatches);
487
488
489       if (node != 0)
490       {
491         matchLine += SPACE + (hmm.getNodeAlignmentColumn(node) + 1);
492         matchLine += SPACE + hmm.getConsensusResidue(node);
493         matchLine += SPACE + hmm.getReferenceAnnotation(node);
494         if (hmm.getFileHeader().contains("HMMER3/f"))
495         {
496           matchLine += SPACE + hmm.getMaskedValue(node);
497           matchLine += SPACE + hmm.getConsensusStructure(node);
498         }
499
500       }
501
502       output.append(NL + matchLine);
503       
504       String insertLine = EMPTY;
505       List<String> strInserts;
506       List<Double> doubleInserts;
507       doubleInserts = convertListToLogSpace(
508               hmm.getNode(node).getInsertEmissions());
509       strInserts = doubleListToStringList(doubleInserts);
510       insertLine += addData(17, 9, strInserts);
511
512       output.append(NL + insertLine);
513
514       String transitionLine = EMPTY;
515       List<String> strTransitions;
516       List<Double> doubleTransitions;
517       doubleTransitions = convertListToLogSpace(
518               hmm.getNode(node).getStateTransitions());
519       strTransitions = doubleListToStringList(doubleTransitions);
520       transitionLine += addData(17, 9, strTransitions);
521
522       output.append(NL + transitionLine);
523     }
524     return output.toString();
525   }
526
527   /**
528    * Returns a String containing the HMM file properties
529    */
530   String getFilePropertiesAsString()
531   {
532     StringBuffer output = new StringBuffer();
533     String line;
534
535     output.append(hmm.getFileHeader());
536     
537     line = String.format("%-5s %1s", "NAME", hmm.getName());
538     output.append(NL + line);
539
540     if (hmm.getAccessionNumber() != null)
541     {
542     line = String.format("%-5s %1s", "ACC", hmm.getAccessionNumber());
543       output.append(NL + line);
544     }
545
546     if (hmm.getDescription() != null)
547     {
548     line = String.format("%-5s %1s", "DESC", hmm.getDescription());
549       output.append(NL + line);
550     }
551     line = String.format("%-5s %1s", "LENG", hmm.getLength());
552     output.append(NL + line);
553
554     if (hmm.getMaxInstanceLength() != null)
555     {
556     line = String.format("%-5s %1s", "MAXL", hmm.getMaxInstanceLength());
557       output.append(NL + line);
558     }
559     line = String.format("%-5s %1s", "ALPH", hmm.getAlphabetType());
560     output.append(NL + line);
561
562     boolean status;
563     String statusStr;
564
565     status = hmm.referenceAnnotationIsActive();
566     statusStr = HiddenMarkovModel.findStringFromBoolean(status);
567     line = String.format("%-5s %1s", "RF",
568             statusStr);
569     output.append(NL + line);
570
571     status = hmm.maskValueIsActive();
572     statusStr = HiddenMarkovModel.findStringFromBoolean(status);
573     line = String.format("%-5s %1s", "MM",
574             statusStr);
575     output.append(NL + line);
576     
577     status = hmm.consensusResidueIsActive();
578     statusStr = HiddenMarkovModel.findStringFromBoolean(status);
579     line = String.format("%-5s %1s", "CONS",
580             statusStr);
581     output.append(NL + line);
582
583     status = hmm.consensusStructureIsActive();
584     statusStr = HiddenMarkovModel.findStringFromBoolean(status);
585     line = String.format("%-5s %1s", "CS",
586             statusStr);
587     output.append(NL + line);
588
589     status = hmm.mapIsActive();
590     statusStr = HiddenMarkovModel.findStringFromBoolean(status);
591     line = String.format("%-5s %1s", "MAP",
592             statusStr);
593     output.append(NL + line);
594
595
596     if (hmm.getDate() != null)
597     {
598     line = String.format("%-5s %1s", "DATE", hmm.getDate());
599       output.append(NL + line);
600     }
601     if (hmm.getNumberOfSequences() != null)
602     {
603     line = String.format("%-5s %1s", "NSEQ", hmm.getNumberOfSequences());
604       output.append(NL + line);
605     }
606     if (hmm.getEffectiveNumberOfSequences() != null)
607     {
608     line = String.format("%-5s %1s", "EFFN",
609             hmm.getEffectiveNumberOfSequences());
610       output.append(NL + line);
611     }
612     if (hmm.getCheckSum() != null)
613     {
614     line = String.format("%-5s %1s", "CKSUM", hmm.getCheckSum());
615       output.append(NL + line);
616     }
617     if (hmm.getGatheringThreshold() != null)
618     {
619     line = String.format("%-5s %1s", "GA", hmm.getGatheringThreshold());
620       output.append(NL + line);
621     }
622
623     if (hmm.getTrustedCutoff() != null)
624     {
625     line = String.format("%-5s %1s", "TC", hmm.getTrustedCutoff());
626       output.append(NL + line);
627     }
628     if (hmm.getNoiseCutoff() != null)
629     {
630     line = String.format("%-5s %1s", "NC", hmm.getNoiseCutoff());
631       output.append(NL + line);
632     }
633     if (hmm.getMSV() != null)
634     {
635       line = String.format("%-19s %18s", "STATS LOCAL MSV", hmm.getMSV());
636       output.append(NL + line);
637
638       line = String.format("%-19s %18s", "STATS LOCAL VITERBI",
639               hmm.getViterbi());
640       output.append(NL + line);
641     
642       line = String.format("%-19s %18s", "STATS LOCAL FORWARD",
643               hmm.getForward());
644       output.append(NL + line);
645     }
646     return output.toString();
647   }
648
649
650   /**
651    * Returns the char value of a single lettered String.
652    * 
653    * @param string
654    * @return
655    */
656   char charValue(String string)
657   {
658     char character;
659     character = string.charAt(0);
660     return character;
661
662   }
663
664   @Override
665   public String print(SequenceI[] seqs, boolean jvsuffix)
666   {
667     if (seqs[0].getHMM() != null)
668     {
669       hmm = seqs[0].getHMM();
670     }
671     return print();
672   }
673
674   /**
675    * Prints the .hmm file to a String.
676    * 
677    * @return
678    */
679   public String print()
680   {
681     StringBuffer output = new StringBuffer();
682     output.append(getFilePropertiesAsString());
683     output.append(NL);
684     output.append(getModelAsString());
685     output.append(NL + "//");
686     return output.toString();
687   }
688
689   /**
690    * Converts the probabilities contained in a list into log space.
691    * 
692    * @param list
693    */
694   List<Double> convertListToLogSpace(List<Double> list)
695   {
696
697     List<Double> convertedList = new ArrayList<>();
698     for (int i = 0; i < list.size(); i++)
699     {
700       double prob = list.get(i);
701       double logProb = -1 * Math.log(prob);
702
703       convertedList.add(logProb);
704     }
705     return convertedList;
706
707
708   }
709
710   /**
711    * Returns the HMM sequence produced by reading a .hmm file.
712    */
713   @Override
714   public SequenceI[] getSeqsAsArray()
715   {
716     SequenceI hmmSeq = hmm.initHMMSequence();
717     SequenceI[] seq = new SequenceI[1];
718     seq[0] = hmmSeq;
719     return seq;
720
721   }
722
723   /**
724    * Fills symbol array and adds each symbol to an index lookup
725    * 
726    * @param parser
727    *          The scanner scanning the symbol line in the file.
728    */
729   public void fillSymbols(Scanner parser)
730   {
731     int i = 0;
732     while (parser.hasNext())
733     {
734       String strSymbol = parser.next();
735       char[] symbol = strSymbol.toCharArray();
736       hmm.getSymbols().add(symbol[0]);
737       hmm.setSymbolIndex(symbol[0], i);
738       i++;
739     }
740   }
741
742   @Override
743   public void setNewlineString(String newLine)
744   {
745     NL = newLine;
746   }
747
748   @Override
749   public void setExportSettings(AlignExportSettingI exportSettings)
750   {
751
752   }
753
754   @Override
755   public void configureForView(AlignmentViewPanel viewpanel)
756   {
757
758   }
759
760   @Override
761   public boolean hasWarningMessage()
762   {
763     return false;
764   }
765
766   @Override
767   public String getWarningMessage()
768   {
769     return "warning message";
770   }
771
772 }
773