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