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