JAL-2629 add ability to add background frequencies to a HMM
[jalview.git] / src / jalview / schemes / HMMMatchScoreColourScheme.java
1 package jalview.schemes;
2
3 import jalview.api.AlignViewportI;
4 import jalview.datamodel.AnnotatedCollectionI;
5 import jalview.datamodel.HiddenMarkovModel;
6 import jalview.datamodel.SequenceI;
7 import jalview.util.ColorUtils;
8 import jalview.util.Comparison;
9
10 import java.awt.Color;
11 import java.io.BufferedReader;
12 import java.io.FileNotFoundException;
13 import java.io.FileReader;
14 import java.io.IOException;
15 import java.util.HashMap;
16 import java.util.List;
17 import java.util.Map;
18
19
20 public class HMMMatchScoreColourScheme extends ResidueColourScheme
21 {
22   
23   private Map<Character, Map<String, Double>> probabilities;
24
25   public class MatchProbReader
26   {
27     private BufferedReader reader;
28     
29     MatchProbReader() throws FileNotFoundException
30     {
31       reader = new BufferedReader(
32               new FileReader("resources/ProbabilityOfMatch"));
33     }
34     
35     public Map<Character, Map<String, Double>>  getProbabilities() throws IOException
36     {
37       Map<Character, Map<String, Double>> probabilities = new HashMap<>();
38       
39       String[] alphabet = reader.readLine().split("\\,");
40       for (int i = 1; i < alphabet.length - 1; i++)
41       {
42         probabilities.put(alphabet[i].replaceAll("\\ ", "").charAt(0),
43                 new HashMap<>());
44       }
45       
46       String line = reader.readLine();
47       while(line != null)
48       {
49         String[] contents = line.split("\\,");
50         
51         for(int i = 1; i < contents.length; i++)
52         {
53           probabilities.get(alphabet[i].replaceAll("\\ ", "").charAt(0))
54                   .put(contents[0], Double
55                           .valueOf(contents[i].replaceAll("\\ ", "")));
56         }
57         line = reader.readLine();
58
59       }
60       reader.close();
61       return probabilities;
62     }
63     
64     
65   }
66
67   private static final Color INSERTION_COLOUR = Color.white;
68
69   /*
70    * the aligned HMM consensus sequence to use as reference for colouring
71    */
72   private SequenceI hmmSeq;
73
74   private HiddenMarkovModel hmm;
75
76   private Map<Character, Float> frequencies;
77
78   /**
79    * Constructor given a list of Hidden Markov Model consensus sequences. The
80    * first sequence provides the HMM profile from which we can read the emission
81    * probabilities that determine the colour.
82    * 
83    * @param hmmSeqs
84    * @throws IOException
85    */
86   public HMMMatchScoreColourScheme(List<SequenceI> hmmSeqs)
87   {
88     hmmSeq = hmmSeqs.isEmpty() ? null : hmmSeqs.get(0);
89     hmm = hmmSeq == null ? null : hmmSeq.getHMM();
90
91     try
92     {
93       MatchProbReader probabilityReader = new MatchProbReader();
94       probabilities = probabilityReader.getProbabilities();
95     } catch (IOException e)
96     {
97       System.out.println(e.getStackTrace());
98     }
99   }
100
101   /**
102    * Default constructor (required by ColourSchemes.loadColourSchemes)
103    */
104   public HMMMatchScoreColourScheme()
105   {
106   }
107
108   @Override
109   public Color findColour(char symbol, int column, SequenceI seq,
110           String consensusResidue, float pid)
111   {
112     return findColour(symbol, column);
113   }
114
115   // TODO change
116   /**
117    * Returns the colour at a particular symbol at a column in the alignment:
118    * <ul>
119    * <li>white for a gap</li>
120    * <li>red for an insertion</li>
121    * <li>orange for negative information content</li>
122    * <li>white to blue for increasing information content</li>
123    * </ul>
124    * 
125    * @param symbol
126    * @param column
127    * @return
128    */
129   private Color findColour(char symbol, int column)
130   {
131     if (getHmm() == null || Comparison.isGap(symbol))
132     {
133       return Color.white;
134     }
135     if (Comparison.isGap(hmmSeq.getCharAt(column)))
136     {
137       return INSERTION_COLOUR;
138     }
139     if (Character.isLowerCase(symbol))
140     {
141       symbol = Character.toUpperCase(symbol);
142     }
143
144     double prob = 0;
145     if (hmm.getBackgroundFrequencies().containsKey(symbol))
146     {
147       double llr = Math
148               .log(getHmm().getMatchEmissionProbability(column, symbol)
149                       / hmm.getBackgroundFrequencies().get(symbol));
150
151       if (!probabilities.get(symbol).containsKey(format(llr)))
152       {
153         return Color.green;
154       }
155
156       prob = probabilities.get(symbol).get(format(llr));
157     }
158     else
159     {
160       return new Color(140, 140, 140);
161     }
162
163     Color colour = Color.ORANGE;
164     if (prob >= 0.5)
165     {
166
167       colour = ColorUtils.getGraduatedColour((float) prob, 0.5f,
168               Color.WHITE, 1f,
169               Color.blue);
170     }
171     else
172     {
173       colour = ColorUtils.getGraduatedColour((float) prob, 0f, Color.red,
174               0.5f, Color.WHITE);
175     }
176
177     return colour;
178   }
179
180   public static String format(Double d)
181   {
182     String formatted = String.format("%.1f", d);
183     if (Double.valueOf(formatted) == 0)
184     {
185       formatted = "0";
186     }
187     return formatted;
188   }
189
190   /**
191    * Answers the maximum possible value of information score (log ratio), for use
192    * in scaling a graduated colour range
193    * 
194    * @return
195    */
196   protected float getMaxInformationScore()
197   {
198     return 0f;
199   }
200
201   /**
202    * Answers a new colour scheme instance based on the HMM of the first sequence
203    * in ac that has an HMM
204    */
205   @Override
206   public ColourSchemeI getInstance(AlignViewportI viewport,
207           AnnotatedCollectionI ac)
208   {
209     return newInstance(ac);
210   }
211
212   /**
213    * Constructor given a sequence collection
214    * 
215    * @param ac
216    */
217   public HMMMatchScoreColourScheme(AnnotatedCollectionI ac)
218   {
219     this(ac.getHmmSequences());
220   }
221
222   /**
223    * Answers a new instance of the colour scheme for the given HMM
224    * 
225    * @param ac
226    * @return
227    */
228   protected HMMMatchScoreColourScheme newInstance(AnnotatedCollectionI ac)
229   {
230     return new HMMMatchScoreColourScheme(ac);
231   }
232
233   @Override
234   public boolean isSimple()
235   {
236     return false;
237   }
238
239   /**
240    * Answers true if the sequence collection has an HMM consensus sequence and
241    * that the first HMM sequence contains background frequencies, else false
242    */
243   @Override
244   public boolean isApplicableTo(AnnotatedCollectionI ac)
245   {
246     return !ac.getHmmSequences().isEmpty() && ac.getHmmSequences().get(0)
247             .getHMM().getBackgroundFrequencies() != null;
248   }
249
250   protected Map<Character, Float> getFrequencies()
251   {
252     return frequencies;
253   }
254
255   protected void setFrequencies(Map<Character, Float> frequencies)
256   {
257     this.frequencies = frequencies;
258   }
259
260   protected HiddenMarkovModel getHmm()
261   {
262     return hmm;
263   }
264
265   protected SequenceI getHmmSequence()
266   {
267     return hmmSeq;
268   }
269
270   @Override
271   public String getSchemeName()
272   {
273     return JalviewColourScheme.HMMMatchScore.toString();
274   }
275
276   
277 }
278
279