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