0f538825c0f9d05b182577295c0137b6a192d3d7
[jalview.git] / src / jalview / analysis / AAFrequency.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
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       } else {for (v = 'A'; v < 'Z'; v++)
154       {
155         if (values[v] < 2 || values[v] < maxCount)
156         {
157           continue;
158         }
159
160         if (values[v] > maxCount)
161         {
162           maxResidue = String.valueOf((char) v);
163         }
164         else if (values[v] == maxCount)
165         {
166           maxResidue += String.valueOf((char) v);
167         }
168         maxCount = values[v];
169       }
170       }
171       if (maxResidue.length() == 0)
172       {
173         maxResidue = "-";
174       }
175       if (profile)
176       {
177         residueHash.put(PROFILE, new int[][]
178         { values, new int[]
179         { jSize, nongap } });
180       }
181       residueHash.put(MAXCOUNT, new Integer(maxCount));
182       residueHash.put(MAXRESIDUE, maxResidue);
183
184       percentage = ((float) maxCount * 100) / jSize;
185       residueHash.put(PID_GAPS, new Float(percentage));
186
187       if (nongap>0) {
188         // calculate for non-gapped too
189         percentage = ((float) maxCount * 100) / nongap;
190       }
191       residueHash.put(PID_NOGAPS, new Float(percentage));
192       
193       result[i] = residueHash;
194     }
195   }
196
197   /**
198    * Compute all or part of the annotation row from the given consensus
199    * hashtable
200    * 
201    * @param consensus
202    *          - pre-allocated annotation row
203    * @param hconsensus
204    * @param iStart
205    * @param width
206    * @param ignoreGapsInConsensusCalculation
207    * @param includeAllConsSymbols
208    * @param nseq 
209    */
210   public static void completeConsensus(AlignmentAnnotation consensus,
211           Hashtable[] hconsensus, int iStart, int width,
212           boolean ignoreGapsInConsensusCalculation,
213           boolean includeAllConsSymbols, long nseq)
214   {
215     completeConsensus(consensus, hconsensus, iStart, width,
216             ignoreGapsInConsensusCalculation, includeAllConsSymbols, null, nseq); // new
217                                                                             // char[]
218     // { 'A', 'C', 'G', 'T', 'U' });
219   }
220
221   public static void completeConsensus(AlignmentAnnotation consensus,
222           Hashtable[] hconsensus, int iStart, int width,
223           boolean ignoreGapsInConsensusCalculation,
224           boolean includeAllConsSymbols, char[] alphabet, long nseq)
225   {
226     float tval, value;
227     if (consensus == null || consensus.annotations == null
228             || consensus.annotations.length < width)
229     {
230       // called with a bad alignment annotation row - wait for it to be
231       // initialised properly
232       return;
233     }
234     String fmtstr="%3.1f";
235     int precision=0;
236     while (nseq>=10) {
237       precision++;
238       nseq/=10;
239     }
240     final Format fmt;
241     if (precision>1)
242     {
243       //if (precision>2)
244       {
245         fmtstr = "%"+(2+precision)+"."+(precision)+"f";
246       }
247       fmt = new Format(fmtstr);
248     } else {
249       fmt = null;
250     }
251     for (int i = iStart; i < width; i++)
252     {
253       Hashtable hci;
254       if (i >= hconsensus.length || ((hci = hconsensus[i]) == null))
255       {
256         // happens if sequences calculated over were shorter than alignment
257         // width
258         consensus.annotations[i] = null;
259         continue;
260       }
261       value = 0;
262       Float fv;
263       if (ignoreGapsInConsensusCalculation)
264       {
265         fv = (Float) hci.get(AAFrequency.PID_NOGAPS);
266       }
267       else
268       {
269         fv = (Float) hci.get(AAFrequency.PID_GAPS);
270       }
271       if (fv == null)
272       {
273         consensus.annotations[i] = null;
274         // data has changed below us .. give up and
275         continue;
276       }
277       value = fv.floatValue();
278       String maxRes = hci.get(AAFrequency.MAXRESIDUE).toString();
279       String mouseOver = hci.get(AAFrequency.MAXRESIDUE) + " ";
280       if (maxRes.length() > 1)
281       {
282         mouseOver = "[" + maxRes + "] ";
283         maxRes = "+";
284       }
285       int[][] profile = (int[][]) hci.get(AAFrequency.PROFILE);
286       if (profile != null && includeAllConsSymbols)
287       {
288         mouseOver = "";
289         if (alphabet != null)
290         {
291           for (int c = 0; c < alphabet.length; c++)
292           {
293             tval = profile[0][alphabet[c]] * 100f
294                     / profile[1][ignoreGapsInConsensusCalculation ? 1 : 0];
295             mouseOver += ((c == 0) ? "" : "; ") + alphabet[c] + " "
296                     + ((fmt!=null) ? fmt.form(tval) : ((int) tval)) + "%";
297           }
298         }
299         else
300         {
301           Object[] ca = new Object[profile[0].length];
302           float[] vl = new float[profile[0].length];
303           for (int c = 0; c < ca.length; c++)
304           {
305             ca[c] = new char[]
306             { (char) c };
307             vl[c] = profile[0][c];
308           }
309           ;
310           jalview.util.QuickSort.sort(vl, ca);
311           for (int p = 0, c = ca.length - 1; profile[0][((char[]) ca[c])[0]] > 0; c--)
312           {
313             if (((char[]) ca[c])[0] != '-')
314             {
315               tval = profile[0][((char[]) ca[c])[0]]
316                       * 100f
317                       / profile[1][ignoreGapsInConsensusCalculation ? 1 : 0];
318               mouseOver += ((p == 0) ? "" : "; ") + ((char[]) ca[c])[0]
319                       + " " + ((fmt!=null) ? fmt.form(tval) : ((int) tval)) + "%";
320               p++;
321
322             }
323           }
324
325         }
326       }
327       else
328       {
329         mouseOver += ((fmt!=null) ? fmt.form(value) : ((int) value)) + "%";
330       }
331       consensus.annotations[i] = new Annotation(maxRes, mouseOver, ' ',
332               value);
333     }
334   }
335
336   /**
337    * get the sorted profile for the given position of the consensus
338    * 
339    * @param hconsensus
340    * @return
341    */
342   public static int[] extractProfile(Hashtable hconsensus,
343           boolean ignoreGapsInConsensusCalculation)
344   {
345     int[] rtnval = new int[64];
346     int[][] profile = (int[][]) hconsensus.get(AAFrequency.PROFILE);
347     if (profile == null)
348       return null;
349     Object[] ca = new Object[profile[0].length];
350     float[] vl = new float[profile[0].length];
351     for (int c = 0; c < ca.length; c++)
352     {
353       ca[c] = new char[]
354       { (char) c };
355       vl[c] = profile[0][c];
356     }
357     ;
358     jalview.util.QuickSort.sort(vl, ca);
359     rtnval[0] = 2;
360     rtnval[1] = 0;
361     for (int c = ca.length - 1; profile[0][((char[]) ca[c])[0]] > 0; c--)
362     {
363       if (((char[]) ca[c])[0] != '-')
364       {
365         rtnval[rtnval[0]++] = ((char[]) ca[c])[0];
366         rtnval[rtnval[0]] = (int) (profile[0][((char[]) ca[c])[0]] * 100f / profile[1][ignoreGapsInConsensusCalculation ? 1
367                 : 0]);
368         rtnval[1] += rtnval[rtnval[0]++];
369       }
370     }
371     return rtnval;
372   }
373 }