Merge branch 'develop' into features/JAL-845splitPaneMergeDevelop
[jalview.git] / src / jalview / analysis / AAFrequency.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.analysis;
22
23 import jalview.datamodel.AlignmentAnnotation;
24 import jalview.datamodel.Annotation;
25 import jalview.datamodel.SequenceI;
26 import jalview.util.Format;
27
28 import java.util.Hashtable;
29 import java.util.List;
30
31 /**
32  * Takes in a vector or array of sequences and column start and column end and
33  * returns a new Hashtable[] of size maxSeqLength, if Hashtable not supplied.
34  * This class is used extensively in calculating alignment colourschemes that
35  * depend on the amount of conservation in each alignment column.
36  * 
37  * @author $author$
38  * @version $Revision$
39  */
40 public class AAFrequency
41 {
42   private static final int TO_UPPER_CASE = 'A' - 'a'; // -32
43
44   public static final String MAXCOUNT = "C";
45
46   public static final String MAXRESIDUE = "R";
47
48   public static final String PID_GAPS = "G";
49
50   public static final String PID_NOGAPS = "N";
51
52   public static final String PROFILE = "P";
53
54   /*
55    * Quick look-up of String value of char 'A' to 'Z'
56    */
57   private static final String[] CHARS = new String['Z' - 'A' + 1];
58
59   static
60   {
61     for (char c = 'A'; c <= 'Z'; c++)
62     {
63       CHARS[c - 'A'] = String.valueOf(c);
64     }
65   }
66
67   public static final Hashtable[] calculate(List<SequenceI> list,
68           int start, int end)
69   {
70     return calculate(list, start, end, false);
71   }
72
73   public static final Hashtable[] calculate(List<SequenceI> sequences,
74           int start, int end, boolean profile)
75   {
76     SequenceI[] seqs = new SequenceI[sequences.size()];
77     int width = 0;
78     synchronized (sequences)
79     {
80       for (int i = 0; i < sequences.size(); i++)
81       {
82         seqs[i] = sequences.get(i);
83         if (seqs[i].getLength() > width)
84         {
85           width = seqs[i].getLength();
86         }
87       }
88
89       Hashtable[] reply = new Hashtable[width];
90
91       if (end >= width)
92       {
93         end = width;
94       }
95
96       calculate(seqs, start, end, reply, profile);
97       return reply;
98     }
99   }
100
101   public static final void calculate(SequenceI[] sequences, int start,
102           int end, Hashtable[] result, boolean profile)
103   {
104     Hashtable residueHash;
105     int maxCount, nongap, i, j, v;
106     int jSize = sequences.length;
107     String maxResidue;
108     char c = '-';
109     float percentage;
110
111     int[] values = new int[255];
112
113     char[] seq;
114
115     for (i = start; i < end; i++)
116     {
117       residueHash = new Hashtable();
118       maxCount = 0;
119       maxResidue = "";
120       nongap = 0;
121       values = new int[255];
122
123       for (j = 0; j < jSize; j++)
124       {
125         if (sequences[j] == null)
126         {
127           System.err
128                   .println("WARNING: Consensus skipping null sequence - possible race condition.");
129           continue;
130         }
131         seq = sequences[j].getSequence();
132         if (seq.length > i)
133         {
134           c = seq[i];
135
136           if (c == '.' || c == ' ')
137           {
138             c = '-';
139           }
140
141           if (c == '-')
142           {
143             values['-']++;
144             continue;
145           }
146           else if ('a' <= c && c <= 'z')
147           {
148             c += TO_UPPER_CASE;
149           }
150
151           nongap++;
152           values[c]++;
153
154         }
155         else
156         {
157           values['-']++;
158         }
159       }
160       if (jSize == 1)
161       {
162         maxResidue = String.valueOf(c);
163         maxCount = 1;
164       }
165       else
166       {
167         for (v = 'A'; v <= 'Z'; v++)
168         {
169           if (values[v] < 2 || values[v] < maxCount)
170           {
171             continue;
172           }
173
174           if (values[v] > maxCount)
175           {
176             maxResidue = CHARS[v - 'A'];
177           }
178           else if (values[v] == maxCount)
179           {
180             maxResidue += CHARS[v - 'A'];
181           }
182           maxCount = values[v];
183         }
184       }
185       if (maxResidue.length() == 0)
186       {
187         maxResidue = "-";
188       }
189       if (profile)
190       {
191         residueHash.put(PROFILE, new int[][]
192         { values, new int[]
193         { jSize, nongap } });
194       }
195       residueHash.put(MAXCOUNT, new Integer(maxCount));
196       residueHash.put(MAXRESIDUE, maxResidue);
197
198       percentage = ((float) maxCount * 100) / jSize;
199       residueHash.put(PID_GAPS, new Float(percentage));
200
201       if (nongap > 0)
202       {
203         // calculate for non-gapped too
204         percentage = ((float) maxCount * 100) / nongap;
205       }
206       residueHash.put(PID_NOGAPS, new Float(percentage));
207
208       result[i] = residueHash;
209     }
210   }
211
212   /**
213    * Compute all or part of the annotation row from the given consensus
214    * hashtable
215    * 
216    * @param consensus
217    *          - pre-allocated annotation row
218    * @param hconsensus
219    * @param iStart
220    * @param width
221    * @param ignoreGapsInConsensusCalculation
222    * @param includeAllConsSymbols
223    * @param nseq
224    */
225   public static void completeConsensus(AlignmentAnnotation consensus,
226           Hashtable[] hconsensus, int iStart, int width,
227           boolean ignoreGapsInConsensusCalculation,
228           boolean includeAllConsSymbols, long nseq)
229   {
230     completeConsensus(consensus, hconsensus, iStart, width,
231             ignoreGapsInConsensusCalculation, includeAllConsSymbols, null,
232             nseq);
233   }
234
235   public static void completeConsensus(AlignmentAnnotation consensus,
236           Hashtable[] hconsensus, int iStart, int width,
237           boolean ignoreGapsInConsensusCalculation,
238           boolean includeAllConsSymbols, char[] alphabet, long nseq)
239   {
240     float tval, value;
241     if (consensus == null || consensus.annotations == null
242             || consensus.annotations.length < width)
243     {
244       // called with a bad alignment annotation row - wait for it to be
245       // initialised properly
246       return;
247     }
248     String fmtstr = "%3.1f";
249     int precision = 0;
250     while (nseq >= 10)
251     {
252       precision++;
253       nseq /= 10;
254     }
255     final Format fmt;
256     if (precision > 1)
257     {
258       // if (precision>2)
259       {
260         fmtstr = "%" + (2 + precision) + "." + (precision) + "f";
261       }
262       fmt = new Format(fmtstr);
263     }
264     else
265     {
266       fmt = null;
267     }
268     for (int i = iStart; i < width; i++)
269     {
270       Hashtable hci;
271       if (i >= hconsensus.length || ((hci = hconsensus[i]) == null))
272       {
273         // happens if sequences calculated over were shorter than alignment
274         // width
275         consensus.annotations[i] = null;
276         continue;
277       }
278       value = 0;
279       Float fv;
280       if (ignoreGapsInConsensusCalculation)
281       {
282         fv = (Float) hci.get(AAFrequency.PID_NOGAPS);
283       }
284       else
285       {
286         fv = (Float) hci.get(AAFrequency.PID_GAPS);
287       }
288       if (fv == null)
289       {
290         consensus.annotations[i] = null;
291         // data has changed below us .. give up and
292         continue;
293       }
294       value = fv.floatValue();
295       String maxRes = hci.get(AAFrequency.MAXRESIDUE).toString();
296       String mouseOver = hci.get(AAFrequency.MAXRESIDUE) + " ";
297       if (maxRes.length() > 1)
298       {
299         mouseOver = "[" + maxRes + "] ";
300         maxRes = "+";
301       }
302       int[][] profile = (int[][]) hci.get(AAFrequency.PROFILE);
303       if (profile != null && includeAllConsSymbols)
304       {
305         mouseOver = "";
306         if (alphabet != null)
307         {
308           for (int c = 0; c < alphabet.length; c++)
309           {
310             tval = profile[0][alphabet[c]] * 100f
311                     / profile[1][ignoreGapsInConsensusCalculation ? 1 : 0];
312             mouseOver += ((c == 0) ? "" : "; ") + alphabet[c] + " "
313                     + ((fmt != null) ? fmt.form(tval) : ((int) tval)) + "%";
314           }
315         }
316         else
317         {
318           Object[] ca = new Object[profile[0].length];
319           float[] vl = new float[profile[0].length];
320           for (int c = 0; c < ca.length; c++)
321           {
322             ca[c] = new char[]
323             { (char) c };
324             vl[c] = profile[0][c];
325           }
326           ;
327           jalview.util.QuickSort.sort(vl, ca);
328           for (int p = 0, c = ca.length - 1; profile[0][((char[]) ca[c])[0]] > 0; c--)
329           {
330             if (((char[]) ca[c])[0] != '-')
331             {
332               tval = profile[0][((char[]) ca[c])[0]]
333                       * 100f
334                       / profile[1][ignoreGapsInConsensusCalculation ? 1 : 0];
335               mouseOver += ((p == 0) ? "" : "; ") + ((char[]) ca[c])[0]
336                       + " "
337                       + ((fmt != null) ? fmt.form(tval) : ((int) tval))
338                       + "%";
339               p++;
340
341             }
342           }
343
344         }
345       }
346       else
347       {
348         mouseOver += ((fmt != null) ? fmt.form(value) : ((int) value))
349                 + "%";
350       }
351       consensus.annotations[i] = new Annotation(maxRes, mouseOver, ' ',
352               value);
353     }
354   }
355
356   /**
357    * get the sorted profile for the given position of the consensus
358    * 
359    * @param hconsensus
360    * @return
361    */
362   public static int[] extractProfile(Hashtable hconsensus,
363           boolean ignoreGapsInConsensusCalculation)
364   {
365     int[] rtnval = new int[64];
366     int[][] profile = (int[][]) hconsensus.get(AAFrequency.PROFILE);
367     if (profile == null)
368     {
369       return null;
370     }
371     char[][] ca = new char[profile[0].length][];
372     float[] vl = new float[profile[0].length];
373     for (int c = 0; c < ca.length; c++)
374     {
375       ca[c] = new char[]
376       { (char) c };
377       vl[c] = profile[0][c];
378     }
379     jalview.util.QuickSort.sort(vl, ca);
380     rtnval[0] = 2;
381     rtnval[1] = 0;
382     for (int c = ca.length - 1; profile[0][ca[c][0]] > 0; c--)
383     {
384       if (ca[c][0] != '-')
385       {
386         rtnval[rtnval[0]++] = ca[c][0];
387         rtnval[rtnval[0]] = (int) (profile[0][ca[c][0]] * 100f / profile[1][ignoreGapsInConsensusCalculation ? 1
388                 : 0]);
389         rtnval[1] += rtnval[rtnval[0]++];
390       }
391     }
392     return rtnval;
393   }
394 }