JAL-2629 add hmmalign command
[jalview.git] / src / jalview / datamodel / HiddenMarkovModel.java
1 package jalview.datamodel;
2
3 import jalview.gui.AlignFrame;
4 import jalview.schemes.ResidueProperties;
5
6 import java.util.ArrayList;
7 import java.util.HashMap;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.Scanner;
11
12 /**
13  * Data structure which stores a hidden Markov model. Currently contains file
14  * properties as well, not sure whether these should be transferred to the
15  * HMMFile class
16  * 
17  * @author TZVanaalten
18  * 
19  */
20 public class HiddenMarkovModel
21 {
22
23
24   // Stores file properties. Do not directly access this field as it contains
25   // only string value - use the getter methods. For example, to find the length
26   // of theHMM, use getModelLength()to return an int value
27   Map<String, String> fileProperties = new HashMap<>();
28   
29   // contains all of the symbols used in this model. The index of each symbol
30   // represents its lookup value
31   List<Character> symbols = new ArrayList<>();
32
33   // contains information for each node in the model. The begin node is at index
34   // 0. Node 0 contains average emission probabilities for each symbol
35   List<HMMNode> nodes = new ArrayList<>();
36
37   // contains the HMM node for each alignment column
38   Map<Integer, Integer> nodeLookup = new HashMap<>();
39   
40   // contains the symbol index for each symbol
41   Map<Character, Integer> symbolIndexLookup = new HashMap<>();
42
43   final static String YES = "yes";
44
45   final static String NO = "no";
46
47   int numberOfSymbols;
48   
49   // keys for file properties hashmap
50   private final String NAME = "NAME";
51
52   private final String ACCESSION_NUMBER = "ACC";
53
54   private final String DESCRIPTION = "DESC";
55
56   private final String LENGTH = "LENG";
57
58   private final String MAX_LENGTH = "MAXL";
59
60   private final String ALPHABET = "ALPH";
61
62   private final String DATE = "DATE";
63
64   private final String COMMAND_LOG = "COM";
65
66   private final String NUMBER_OF_SEQUENCES = "NSEQ";
67
68   private final String EFF_NUMBER_OF_SEQUENCES = "EFFN";
69
70   private final String CHECK_SUM = "CKSUM";
71
72   private final String GATHERING_THRESHOLDS = "GA";
73
74   private final String TRUSTED_CUTOFFS = "TC";
75
76   private final String NOISE_CUTOFFS = "NC";
77
78   private final String STATISTICS = "STATS";
79
80   private final String COMPO = "COMPO";
81   
82   private final String GATHERING_THRESHOLD = "GA";
83
84   private final String TRUSTED_CUTOFF = "TC";
85
86   private final String NOISE_CUTOFF = "NC";
87
88   private final String VITERBI = "VITERBI";
89
90   private final String MSV = "MSV";
91
92   private final String FORWARD = "FORWARD";
93
94   private final String MAP = "MAP";
95
96   private final String REFERENCE_ANNOTATION = "RF";
97
98   private final String CONSENSUS_RESIDUE = "CONS";
99
100   private final String CONSENSUS_STRUCTURE = "CS";
101
102   private final String MASKED_VALUE = "MM";
103   
104   public static final int MATCHTOMATCH = 0;
105
106   public static final int MATCHTOINSERT = 1;
107
108   public static final int MATCHTODELETE = 2;
109
110   public static final int INSERTTOMATCH = 3;
111
112   public static final int INSERTTOINSERT = 4;
113
114   public static final int DELETETOMATCH = 5;
115
116   public static final int DELETETODELETE = 6;
117
118   String fileHeader;
119
120   public HiddenMarkovModel()
121   {
122
123   }
124
125   public HiddenMarkovModel(HiddenMarkovModel hmm)
126   {
127     super();
128     this.fileProperties = hmm.fileProperties;
129     this.symbols = hmm.symbols;
130     this.nodes = hmm.nodes;
131     this.nodeLookup = hmm.nodeLookup;
132     this.symbolIndexLookup = hmm.symbolIndexLookup;
133     this.numberOfSymbols = hmm.numberOfSymbols;
134     this.fileHeader = hmm.fileHeader;
135   }
136
137   /**
138    * Gets the file header of the .hmm file this model came from.
139    * 
140    * @return
141    */
142   public String getFileHeader()
143   {
144     return fileHeader;
145   }
146
147   /**
148    * Sets the file header of this model.
149    * 
150    * @param header
151    */
152   public void setFileHeader(String header)
153   {
154     fileHeader = header;
155   }
156
157   /**
158    * Returns the map containing the matches between nodes and alignment column
159    * indexes.
160    * 
161    * @return
162    * 
163    */
164   public Map<Integer, Integer> getNodeLookup()
165   {
166     return nodeLookup;
167   }
168
169   /**
170    * Returns the list of symbols used in this hidden Markov model.
171    * 
172    * @return
173    */
174   public List<Character> getSymbols()
175   {
176     return symbols;
177   }
178   
179   /**
180    * Returns the file properties.
181    * 
182    * @return
183    */
184   public Map<String, String> getFileProperties()
185   {
186     return fileProperties;
187   }
188
189   /**
190    * Gets the node in the hidden Markov model at the specified position.
191    * 
192    * @param nodeIndex
193    *          The index of the node requested. Node 0 optionally contains the
194    *          average match emission probabilities across the entire model, and
195    *          always contains the insert emission probabilities and state
196    *          transition probabilities for the begin node. Node 1 contains the
197    *          first node in the HMM that can correspond to a column in the
198    *          alignment.
199    * @return
200    */
201   public HMMNode getNode(int nodeIndex)
202   {
203     return getNodes().get(nodeIndex);
204   }
205
206   /**
207    * Sets the list of symbols used in the hidden Markov model to the list
208    * specified.
209    * 
210    * @param symbolsL
211    *          The list of symbols to which the current list is to be changed.
212    * 
213    */
214   public void setSymbols(List<Character> symbolsL)
215   {
216     this.symbols = symbolsL;
217   }
218
219   /**
220    * Returns the name of the sequence alignment on which the HMM is based.
221    * 
222    * @return
223    */
224   public String getName()
225   {
226     return fileProperties.get(NAME);
227   }
228   
229   /**
230    * Returns the accession number.
231    * @return
232    */
233   public String getAccessionNumber()
234   {
235     return fileProperties.get(ACCESSION_NUMBER);
236   }
237
238   /**
239    * Returns a description of the sequence alignment on which the hidden Markov
240    * model is based.
241    * 
242    * @return
243    */
244   public String getDescription()
245   {
246     return fileProperties.get(DESCRIPTION);
247   }
248
249   /**
250    * Returns the length of the hidden Markov model.
251    * 
252    * @return
253    */
254   public Integer getLength()
255   {
256     if (fileProperties.get(LENGTH) == null)
257     {
258       return null;
259     }
260     return Integer.parseInt(fileProperties.get(LENGTH));
261   }
262
263   /**
264    * Returns the max instance length within the hidden Markov model.
265    * 
266    * @return
267    */
268   public Integer getMaxInstanceLength()
269   {
270     if (fileProperties.get(MAX_LENGTH) == null)
271     {
272       return null;
273     }
274     return Integer.parseInt(fileProperties.get(MAX_LENGTH));
275   }
276
277   /**
278    * Returns the type of symbol alphabet - "amino", "DNA", "RNA" are the
279    * options. Other alphabets may be added.
280    * 
281    * @return
282    */
283   public String getAlphabetType()
284   {
285     return fileProperties.get(ALPHABET);
286   }
287
288   /**
289    * Returns the date as a String.
290    * 
291    * @return
292    */
293   public String getDate()
294   {
295     return fileProperties.get(DATE);
296   }
297
298   /**
299    * Returns the command line log.
300    * 
301    * @return
302    */
303   public String getCommandLineLog()
304   {
305     return fileProperties.get(COMMAND_LOG);
306   }
307
308   /**
309    * Returns the number of sequences on which the HMM was trained.
310    * 
311    * @return
312    */
313   public Integer getNumberOfSequences()
314   {
315     if (fileProperties.get(NUMBER_OF_SEQUENCES) == null)
316     {
317       return null;
318     }
319     return Integer.parseInt(fileProperties.get(NUMBER_OF_SEQUENCES));
320   }
321
322   /**
323    * Returns the effective number of sequences on which the HMM was based.
324    * 
325    * @param value
326    */
327   public Double getEffectiveNumberOfSequences()
328   {
329     if (fileProperties.get(LENGTH) == null)
330     {
331       return null;
332     }
333     return Double.parseDouble(fileProperties.get(EFF_NUMBER_OF_SEQUENCES));
334   }
335
336   /**
337    * Returns the checksum.
338    * 
339    * @return
340    */
341   public Long getCheckSum()
342   {
343     if (fileProperties.get(LENGTH) == null)
344     {
345       return null;
346     }
347     return Long.parseLong(fileProperties.get(CHECK_SUM));
348   }
349
350   /**
351    * Returns the list of nodes in this HMM.
352    * 
353    * @return
354    */
355   public List<HMMNode> getNodes()
356   {
357     return nodes;
358   }
359
360   /**
361    * Sets the list of nodes in this HMM to the given list.
362    * 
363    * @param nodes
364    *          The list of nodes to which the current list of nodes is being
365    *          changed.
366    */
367   public void setNodes(List<HMMNode> nodes)
368   {
369     this.nodes = nodes;
370   }
371   
372   /**
373    * Gets the match emission probability for a given symbol at a column in the
374    * alignment.
375    * 
376    * @param alignColumn
377    *          The index of the alignment column, starting at index 0. Index 0
378    *          usually corresponds to index 1 in the HMM.
379    * @param symbol
380    *          The symbol for which the desired probability is being requested.
381    * @return
382    * 
383    */
384   public Double getMatchEmissionProbability(int alignColumn, char symbol)
385   {
386     int symbolIndex;
387     int nodeIndex;
388     Double probability;
389     if (!symbolIndexLookup.containsKey(symbol))
390     {
391       return 0d;
392     }
393     symbolIndex = symbolIndexLookup.get(symbol);
394     if (nodeLookup.containsKey(alignColumn + 1))
395     {
396       nodeIndex = nodeLookup.get(alignColumn + 1);
397       probability = getNode(nodeIndex).getMatchEmissions().get(symbolIndex);
398       return probability;
399     }
400     else
401     {
402       return 0d;
403     }
404
405   }
406
407   /**
408    * Gets the insert emission probability for a given symbol at a column in the
409    * alignment.
410    * 
411    * @param alignColumn
412    *          The index of the alignment column, starting at index 0. Index 0
413    *          usually corresponds to index 1 in the HMM.
414    * @param symbol
415    *          The symbol for which the desired probability is being requested.
416    * @return
417    * 
418    */
419   public Double getInsertEmissionProbability(int alignColumn, char symbol)
420   {
421     int symbolIndex;
422     int nodeIndex;
423     Double probability;
424     if (!symbolIndexLookup.containsKey(symbol))
425     {
426       return 0d;
427     }
428     symbolIndex = symbolIndexLookup.get(symbol);
429     if (nodeLookup.containsKey(alignColumn + 1))
430     {
431       nodeIndex = nodeLookup.get(alignColumn + 1);
432       probability = getNode(nodeIndex).getInsertEmissions()
433               .get(symbolIndex);
434       return probability;
435     }
436     else
437     {
438       return 0d;
439     }
440
441   }
442   
443   /**
444    * Gets the state transition probability for a given symbol at a column in the
445    * alignment.
446    * 
447    * @param alignColumn
448    *          The index of the alignment column, starting at index 0. Index 0
449    *          usually corresponds to index 1 in the HMM.
450    * @param symbol
451    *          The symbol for which the desired probability is being requested.
452    * @return
453    * 
454    */
455   public Double getStateTransitionProbability(int alignColumn,
456           int transition)
457   {
458     int transitionIndex;
459     int nodeIndex;
460     Double probability;
461     if (nodeLookup.containsKey(alignColumn + 1))
462     {
463       nodeIndex = nodeLookup.get(alignColumn + 1);
464       probability = getNode(nodeIndex).getStateTransitions()
465               .get(transition);
466       return probability;
467     }
468     else
469     {
470       return 0d;
471     }
472
473   }
474   
475   /**
476    * Returns the alignment column linked to the node at the given index.
477    * 
478    * @param nodeIndex
479    *          The index of the node, starting from index 1. Index 0 is the begin
480    *          node, which does not correspond to a column in the alignment.
481    * @return
482    */
483   public Integer getNodeAlignmentColumn(int nodeIndex)
484   {
485     Integer value = nodes.get(nodeIndex).getAlignmentColumn();
486     return value - 1;
487   }
488   
489   /**
490    * Returns the consensus residue at the specified node.
491    * 
492    * @param nodeIndex
493    *          The index of the specified node.
494    * @return
495    */
496   public char getConsensusResidue(int nodeIndex)
497   {
498    char value = nodes.get(nodeIndex).getConsensusResidue();
499    return value;
500   }
501   
502   /**
503    * Returns the consensus at a given alignment column.
504    * 
505    * @param columnIndex
506    *          The index of the column in the alignment for which the consensus
507    *          is desired. The list of columns starts at index 0.
508    * @return
509    */
510   public char getConsensusAtAlignColumn(int columnIndex)
511   {
512     char mostLikely = '-';
513     if (consensusResidueIsActive())
514     {
515
516     Integer index = findNodeIndex(columnIndex);
517     if (index == null)
518     {
519       return '-';
520     }
521       mostLikely = getNodes().get(index).getConsensusResidue();
522       return mostLikely;
523     }
524     else
525     {
526       double highestProb = 0;
527       for (char character : symbols)
528       {
529         Double prob = getMatchEmissionProbability(columnIndex, character);
530         if (prob > highestProb)
531         {
532           highestProb = prob;
533           mostLikely = character;
534         }
535       }
536       return mostLikely;
537     }
538
539   }
540
541   /**
542    * Returns the reference annotation at the specified node.
543    * 
544    * @param nodeIndex
545    *          The index of the specified node.
546    * @return
547    */
548   public char getReferenceAnnotation(int nodeIndex)
549   {
550    char value = nodes.get(nodeIndex).getReferenceAnnotation();
551    return value;
552   }
553   
554   /**
555    * Returns the mask value at the specified node.
556    * 
557    * @param nodeIndex
558    *          The index of the specified node.
559    * @return
560    */
561   public char getMaskedValue(int nodeIndex)
562   {
563    char value = nodes.get(nodeIndex).getMaskValue();
564    return value;
565   }
566   
567   /**
568    * Returns the consensus structure at the specified node.
569    * 
570    * @param nodeIndex
571    *          The index of the specified node.
572    * @return
573    */
574   public char getConsensusStructure(int nodeIndex)
575   {
576    char value = nodes.get(nodeIndex).getConsensusStructure();
577    return value;
578   }
579   
580   /**
581    * Returns the average match emission probability for a given symbol
582    * 
583    * @param symbolIndex
584    *          The index of the symbol.
585    * @return
586    * 
587    */
588   public double getAverageMatchEmission(int symbolIndex)
589   {
590     double value = nodes.get(0).getMatchEmissions().get(symbolIndex);
591     return value;
592   }
593
594   /**
595    * Returns the number of symbols in the alphabet used in this HMM.
596    * 
597    * @return
598    */
599   public int getNumberOfSymbols()
600   {
601     return numberOfSymbols;
602   }
603
604   /**
605    * Fills symbol array and whilst doing so, updates the value of the number of
606    * symbols.
607    * 
608    * @param parser
609    *          The scanner scanning the symbol line in the file.
610    */
611   public void fillSymbols(Scanner parser)
612   {
613     int i = 0;
614     while (parser.hasNext())
615     {
616       String strSymbol = parser.next();
617       char[] symbol = strSymbol.toCharArray();
618       symbols.add(symbol[0]);
619       symbolIndexLookup.put(symbol[0], i);
620       i++;
621     }
622     numberOfSymbols = symbols.size();
623   }
624
625   /**
626    * Adds a file property.
627    * 
628    * @param key
629    * @param value
630    */
631   public void addFileProperty(String key, String value)
632   {
633     fileProperties.put(key, value);
634   }
635
636   /**
637    * Returns a boolean indicating whether the reference annotation is active.
638    * 
639    * @return
640    */
641   public boolean referenceAnnotationIsActive()
642   {
643     String status;
644     status = fileProperties.get(REFERENCE_ANNOTATION);
645     if (status == null)
646     {
647       return false;
648     }
649     switch (status)
650     {
651     case YES:
652       return true;
653     case NO:
654       return false;
655     default:
656       return false;
657     }
658
659   }
660
661   /**
662    * Returns a boolean indicating whether the mask value annotation is active.
663    * 
664    * @return
665    */
666   public boolean maskValueIsActive()
667   {
668     String status;
669     status = fileProperties.get(MASKED_VALUE);
670     if (status == null)
671     {
672       return false;
673     }
674     switch (status)
675     {
676     case YES:
677       return true;
678     case NO:
679       return false;
680     default:
681       return false;
682     }
683
684   }
685
686   /**
687    * Returns a boolean indicating whether the consensus residue annotation is
688    * active.
689    * 
690    * @return
691    */
692   public boolean consensusResidueIsActive()
693   {
694     String status;
695     status = fileProperties.get(CONSENSUS_RESIDUE);
696     if (status == null)
697     {
698       return false;
699     }
700     switch (status)
701     {
702     case YES:
703       return true;
704     case NO:
705       return false;
706     default:
707       return false;
708     }
709
710   }
711
712   /**
713    * Returns a boolean indicating whether the consensus structure annotation is
714    * active.
715    * 
716    * @return
717    */
718   public boolean consensusStructureIsActive()
719   {
720     String status;
721     status = fileProperties.get(CONSENSUS_STRUCTURE);
722     if (status == null)
723     {
724       return false;
725     }
726     switch (status)
727     {
728     case YES:
729       return true;
730     case NO:
731       return false;
732     default:
733       return false;
734     }
735
736   }
737
738   /**
739    * Returns a boolean indicating whether the MAP annotation is active.
740    * 
741    * @return
742    */
743   public boolean mapIsActive()
744   {
745     String status;
746     status = fileProperties.get(MAP);
747     if (status == null)
748     {
749       return false;
750     }
751     switch (status)
752     {
753     case YES:
754       return true;
755     case NO:
756       return false;
757     default:
758       return false;
759     }
760
761   }
762
763   /**
764    * Sets the alignment column of the specified node.
765    * 
766    * @param nodeIndex
767    * 
768    * @param column
769    * 
770    */
771   public void setAlignmentColumn(int nodeIndex, int column)
772   {
773     nodes.get(nodeIndex).setAlignmentColumn(column);
774   }
775
776   /**
777    * Sets the reference annotation at a given node.
778    * 
779    * @param nodeIndex
780    * @param value
781    */
782   public void setReferenceAnnotation(int nodeIndex, char value)
783   {
784     nodes.get(nodeIndex).setReferenceAnnotation(value);
785   }
786
787   /**
788    * Sets the consensus residue at a given node.
789    * 
790    * @param nodeIndex
791    * @param value
792    */
793   public void setConsensusResidue(int nodeIndex, char value)
794   {
795     nodes.get(nodeIndex).setConsensusResidue(value);
796   }
797
798   /**
799    * Sets the consensus structure at a given node.
800    * 
801    * @param nodeIndex
802    * @param value
803    */
804   public void setConsensusStructure(int nodeIndex, char value)
805   {
806     nodes.get(nodeIndex).setConsensusStructure(value);
807   }
808
809   /**
810    * Sets the mask value at a given node.
811    * 
812    * @param nodeIndex
813    * @param value
814    */
815   public void setMaskValue(int nodeIndex, char value)
816   {
817     nodes.get(nodeIndex).setMaskValue(value);
818   }
819
820   /**
821    * Temporary implementation, should not be used.
822    * 
823    * @return
824    */
825   public String getGatheringThreshold()
826   {
827     String value;
828     value = fileProperties.get("GA");
829     return value;
830   }
831
832   /**
833    * Temporary implementation, should not be used.
834    * 
835    * @return
836    */
837   public String getNoiseCutoff()
838   {
839     String value;
840     value = fileProperties.get("NC");
841     return value;
842   }
843
844   /**
845    * Temporary implementation, should not be used.
846    * 
847    * @return
848    */
849   public String getTrustedCutoff()
850   {
851     String value;
852     value = fileProperties.get("TC");
853     return value;
854   }
855
856   /**
857    * Temporary implementation, should not be used.
858    * 
859    * @return
860    */
861   public String getViterbi()
862   {
863     String value;
864     value = fileProperties.get(VITERBI);
865     return value;
866   }
867
868   /**
869    * Temporary implementation, should not be used.
870    * 
871    * @return
872    */
873   public String getMSV()
874   {
875     String value;
876     value = fileProperties.get(MSV);
877     return value;
878   }
879
880   /**
881    * Temporary implementation, should not be used.
882    * 
883    * @return
884    */
885   public String getForward()
886   {
887     String value;
888     value = fileProperties.get(FORWARD);
889     return value;
890   }
891
892   /**
893    * Sets the activation status of the MAP annotation.
894    * 
895    * @param status
896    */
897   public void setMAPStatus(boolean status)
898   {
899     fileProperties.put(MAP, status ? YES : NO);
900   }
901
902   /**
903    * Sets the activation status of the reference annotation.
904    * 
905    * @param status
906    */
907   public void setReferenceAnnotationStatus(boolean status)
908   {
909     fileProperties.put(REFERENCE_ANNOTATION, status ? YES : NO);
910   }
911
912   /**
913    * Sets the activation status of the mask value annotation.
914    * 
915    * @param status
916    */
917   public void setMaskedValueStatus(boolean status)
918   {
919     fileProperties.put(MASKED_VALUE, status ? YES : NO);
920   }
921
922   /**
923    * Sets the activation status of the consensus residue annotation.
924    * 
925    * @param status
926    */
927   public void setConsensusResidueStatus(boolean status)
928   {
929     fileProperties.put(CONSENSUS_RESIDUE, status ? YES : NO);
930   }
931
932   /**
933    * Sets the activation status of the consensus structure annotation.
934    * 
935    * @param status
936    */
937   public void setConsensusStructureStatus(boolean status)
938   {
939     fileProperties.put(CONSENSUS_STRUCTURE, status ? YES : NO);
940   }
941
942   /**
943    * Finds the index of the node in a hidden Markov model based on the column in
944    * the alignment
945    * 
946    * @param alignmentColumn
947    *          The index of the column in the alignment, with the indexes
948    *          starting from 0.
949    */
950
951   public Integer findNodeIndex(int alignmentColumn)
952   {
953     Integer index;
954     index = nodeLookup.get(alignmentColumn + 1);
955     return index;
956   }
957
958   /**
959    * Finds the String values of a boolean. "yes" for true and "no" for false.
960    * 
961    * @param value
962    * @return
963    */
964   public static String findStringFromBoolean(boolean value)
965   {
966     if (value)
967     {
968       return YES;
969     }
970     else
971     {
972       return NO;
973     }
974   }
975
976   /**
977    * Creates the HMM Logo alignment annotation, and populates it with
978    * information content data.
979    * 
980    * @return The alignment annotation.
981    */
982   public AlignmentAnnotation createAnnotation(int length)
983   {
984     Annotation[] annotations = new Annotation[length];
985     float max = 0f;
986     for (int alignPos = 0; alignPos < length; alignPos++)
987     {
988       Float content = getInformationContent(alignPos);
989       if (content > max)
990       {
991         max = content;
992       }
993
994       Character cons;
995
996       cons = getConsensusAtAlignColumn(alignPos);
997
998       cons = Character.toUpperCase(cons);
999
1000       String description = String.format("%.3f", content);
1001       description += " bits";
1002       annotations[alignPos] = new Annotation(cons.toString(), description,
1003               ' ',
1004               content);
1005
1006     }
1007     AlignmentAnnotation annotation = new AlignmentAnnotation(
1008             "Information",
1009             "The information content of each column, measured in bits",
1010             annotations,
1011             0f, max, AlignmentAnnotation.BAR_GRAPH);
1012     annotation.setHMM(this);
1013     return annotation;
1014   }
1015
1016   /**
1017    * Returns the information content at a specified column.
1018    * 
1019    * @param column
1020    *          Index of the column, starting from 0.
1021    * @return
1022    */
1023   public float getInformationContent(int column)
1024   {
1025     float informationContent = 0f;
1026
1027     for (char symbol : symbols)
1028     {
1029       float freq = 0f;
1030       if ("amino".equals(getAlphabetType()))
1031       {
1032         freq = ResidueProperties.aminoBackgroundFrequencies.get(symbol);
1033       }
1034       if ("DNA".equals(getAlphabetType()))
1035       {
1036         freq = ResidueProperties.dnaBackgroundFrequencies.get(symbol);
1037       }
1038       if ("RNA".equals(getAlphabetType()))
1039       {
1040         freq = ResidueProperties.rnaBackgroundFrequencies
1041                 .get(symbol);
1042       }
1043       Double hmmProb = getMatchEmissionProbability(column, symbol);
1044       float prob = hmmProb.floatValue();
1045       informationContent += prob * (Math.log(prob / freq) / Math.log(2));
1046
1047     }
1048
1049     return informationContent;
1050   }
1051
1052   /**
1053    * Returns the consensus sequence based on the most probable symbol at each
1054    * position. The sequence is adjusted to match the length of the existing
1055    * sequence alignment. Gap characters are used as padding.
1056    * 
1057    * @param length
1058    *          The length of the longest sequence in the existing alignment.
1059    * @return
1060    */
1061   public Sequence getConsensusSequence(int length)
1062   {
1063     int start;
1064     int end;
1065     int modelLength;
1066     start = getNodeAlignmentColumn(1);
1067     modelLength = getLength();
1068     end = getNodeAlignmentColumn(modelLength);
1069     char[] sequence = new char[length];
1070     for (int index = 0; index < length; index++)
1071     {
1072       Character character;
1073
1074         character = getConsensusAtAlignColumn(index);
1075
1076       if (character == null || character == '-')
1077       {
1078         sequence[index] = '-';
1079       }
1080       else
1081       {
1082         sequence[index] = Character.toUpperCase(character);
1083       }
1084       }
1085
1086
1087     Sequence seq = new Sequence("HMM CONSENSUS", sequence, start, end);
1088     return seq;
1089   }
1090
1091
1092   /**
1093    * Maps the nodes of the hidden Markov model to the reference annotation and
1094    * then deletes this annotation.
1095    */
1096   public void mapToReferenceAnnotation(AlignFrame af)
1097   {
1098     AlignmentAnnotation annotArray[] = af.getViewport().getAlignment()
1099             .getAlignmentAnnotation();
1100
1101     AlignmentAnnotation reference = null;
1102     for (AlignmentAnnotation annot : annotArray)
1103     {
1104       if (annot.label.contains("Reference"))
1105       {
1106         reference = annot;
1107       }
1108     }
1109
1110     if (reference == null)
1111     {
1112       return;
1113     }
1114
1115     Annotation[] annots = reference.annotations;
1116     {
1117       int nodeIndex = 0;
1118       for (int col = 0; col < annots.length; col++)
1119       {
1120         String character = annots[col].displayCharacter;
1121         if ("x".equals(character) || "X".equals(character))
1122         {
1123           nodeIndex++;
1124           if (nodeIndex < nodes.size())
1125           {
1126             nodes.get(nodeIndex).setAlignmentColumn(col + 1);
1127             nodeLookup.put(col + 1, nodeIndex);
1128           }
1129           else
1130           {
1131             System.out.println(
1132                     "The reference annotation contains more consensus columns than the hidden Markov model");
1133             break;
1134           }
1135         }
1136         else
1137         {
1138           nodeLookup.remove(col + 1);
1139         }
1140       }
1141
1142     }
1143     af.getViewport().getAlignment().deleteAnnotation(reference);
1144   }
1145
1146   public void initPlaceholder(AlignFrame af)
1147   {
1148     AlignmentI alignment = af.getViewport().getAlignment();
1149     int length = alignment.getWidth();
1150     Sequence consensus = getConsensusSequence(length);
1151     consensus.setHMM(this);
1152     SequenceI[] consensusArr = new Sequence[] { consensus };
1153     AlignmentI newAlignment = new Alignment(consensusArr);
1154     newAlignment.append(alignment);
1155     af.getViewport().setAlignment(newAlignment);
1156   }
1157
1158 }
1159