JAL-885; Structure conservation row is just added if alignment
[jalview.git] / src / jalview / analysis / StructureFrequency.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.analysis;
19
20 import java.util.*;
21
22 import jalview.datamodel.*;
23
24 /**
25  * Takes in a vector or array of sequences and column start and column end and
26  * returns a new Hashtable[] of size maxSeqLength, if Hashtable not supplied.
27  * This class is used extensively in calculating alignment colourschemes that
28  * depend on the amount of conservation in each alignment column.
29  * 
30  * @author $author$
31  * @version $Revision$
32  */
33 public class StructureFrequency
34 {
35   // No need to store 1000s of strings which are not
36   // visible to the user.
37   public static final String MAXCOUNT = "C";
38
39   public static final String MAXRESIDUE = "R";
40
41   public static final String PID_GAPS = "G";
42
43   public static final String PID_NOGAPS = "N";
44
45   public static final String PROFILE = "P";
46
47   public static final String PAIRPROFILE = "B";
48
49 /**
50  * Returns the 3' position of a base pair
51  * @param pairs Secondary structure annotation
52  * @param indice 5' position of a base pair
53  * @return 3' position of a base pair
54  */
55   public static int findPair(SequenceFeature[] pairs, int indice)
56   {
57     for (int i = 0; i < pairs.length; i++)
58     {
59       if (pairs[i].getBegin() == indice)
60       {
61         return pairs[i].getEnd();
62       }
63     }
64     return -1;
65   }
66
67   /**
68    * Method to calculate a 'base pair consensus row', very similar to nucleotide
69    * consensus but takes into account a given structure
70    * 
71    * @param sequences
72    * @param start
73    * @param end
74    * @param result
75    * @param profile
76    * @param rnaStruc
77    */
78   public static final void calculate(SequenceI[] sequences, int start,
79           int end, Hashtable[] result, boolean profile,
80           AlignmentAnnotation rnaStruc)
81   {
82     Hashtable residueHash;
83     String maxResidue;
84     char[] seq, struc = rnaStruc.getRNAStruc().toCharArray();
85     SequenceFeature[] rna = rnaStruc._rnasecstr;
86     char c, s, cEnd;
87     int count, nonGap = 0, i, bpEnd = -1, j, jSize = sequences.length;
88     int[] values;
89     int[][] pairs;
90     float percentage;
91
92     for (i = start; i < end; i++) // foreach column
93     {
94       residueHash = new Hashtable();
95       maxResidue = "-";
96       values = new int[255];
97       pairs = new int[255][255];
98       bpEnd = -1;
99
100       s = struc[i];
101       if (s == '.' || s == ' ')
102       {
103         s = '-';
104       }
105
106       if (s != '(')
107       {
108         if (s == '-')
109         {
110           values['-']++;
111         }
112       }
113       else
114       {
115         for (j = 0; j < jSize; j++) // foreach row
116         {
117           if (sequences[j] == null)
118           {
119             System.err
120                     .println("WARNING: Consensus skipping null sequence - possible race condition.");
121             continue;
122           }
123           seq = sequences[j].getSequence();
124
125           if (seq.length > i)
126           {
127             c = seq[i];
128
129             // standard representation for gaps in sequence and structure
130             if (c == '.' || c == ' ')
131             {
132               c = '-';
133             }
134
135             if (c == '-')
136             {
137               values['-']++;
138               continue;
139             }
140             bpEnd = findPair(rna, i);
141             cEnd = seq[bpEnd];
142             if (checkBpType(c, cEnd))
143             {
144               values['H']++; // H means it's a helix (structured)
145             }
146             pairs[c][cEnd]++;
147
148             maxResidue = "H";
149           }
150         }
151         // nonGap++;
152       }
153       // UPDATE this for new values
154       if (profile)
155       {
156         residueHash.put(PROFILE, new int[][]
157         { values, new int[]
158         { jSize, (jSize - values['-']) } });
159
160         residueHash.put(PAIRPROFILE, pairs);
161       }
162
163       count = values['H'];
164
165       residueHash.put(MAXCOUNT, new Integer(count));
166       residueHash.put(MAXRESIDUE, maxResidue);
167
168       percentage = ((float) count * 100) / (float) jSize;
169       residueHash.put(PID_GAPS, new Float(percentage));
170
171       // percentage = ((float) count * 100) / (float) nongap;
172       // residueHash.put(PID_NOGAPS, new Float(percentage));
173       if (result[i] == null)
174       {
175         result[i] = residueHash;
176       }
177       if (bpEnd > 0)
178       {
179         result[bpEnd] = residueHash;
180       }
181     }
182   }
183
184   /**
185    * Method to check if a base-pair is a canonical or a wobble bp
186    * 
187    * @param up
188    *          5' base
189    * @param down
190    *          3' base
191    * @return True if it is a canonical/wobble bp
192    */
193   public static boolean checkBpType(char up, char down)
194   {
195     if (up > 'Z')
196     {
197       up -= 32;
198     }
199     if (down > 'Z')
200     {
201       down -= 32;
202     }
203
204     switch (up)
205     {
206     case 'A':
207       switch (down)
208       {
209       case 'T':
210         return true;
211       case 'U':
212         return true;
213       }
214       break;
215     case 'C':
216       switch (down)
217       {
218       case 'G':
219         return true;
220       }
221       break;
222     case 'T':
223       switch (down)
224       {
225       case 'A':
226         return true;
227       case 'G':
228         return true;
229       }
230       break;
231     case 'G':
232       switch (down)
233       {
234       case 'C':
235         return true;
236       case 'T':
237         return true;
238       case 'U':
239         return true;
240       }
241       break;
242     case 'U':
243       switch (down)
244       {
245       case 'A':
246         return true;
247       case 'G':
248         return true;
249       }
250       break;
251     }
252     return false;
253   }
254
255   /**
256    * Compute all or part of the annotation row from the given consensus
257    * hashtable
258    * 
259    * @param consensus
260    *          - pre-allocated annotation row
261    * @param hconsensus
262    * @param iStart
263    * @param width
264    * @param ignoreGapsInConsensusCalculation
265    * @param includeAllConsSymbols
266    */
267   public static void completeConsensus(AlignmentAnnotation consensus,
268           Hashtable[] hconsensus, int iStart, int width,
269           boolean ignoreGapsInConsensusCalculation,
270           boolean includeAllConsSymbols)
271   {
272     completeConsensus(consensus, hconsensus, iStart, width,
273             ignoreGapsInConsensusCalculation, includeAllConsSymbols, null); // new
274     // char[]
275     // { 'A', 'C', 'G', 'T', 'U' });
276   }
277
278   public static void completeConsensus(AlignmentAnnotation consensus,
279           Hashtable[] hconsensus, int iStart, int width,
280           boolean ignoreGapsInConsensusCalculation,
281           boolean includeAllConsSymbols, char[] alphabet)
282   {
283     float tval, value;
284     if (consensus == null || consensus.annotations == null
285             || consensus.annotations.length < width)
286     {
287       // called with a bad alignment annotation row - wait for it to be
288       // initialised properly
289       return;
290     }
291     for (int i = iStart; i < width; i++)
292     {
293       if (i >= hconsensus.length)
294       {
295         // happens if sequences calculated over were shorter than alignment
296         // width
297         consensus.annotations[i] = null;
298         continue;
299       }
300       value = 0;
301       if (ignoreGapsInConsensusCalculation)
302       {
303         value = ((Float) hconsensus[i].get(StructureFrequency.PID_NOGAPS))
304                 .floatValue();
305       }
306       else
307       {
308         value = ((Float) hconsensus[i].get(StructureFrequency.PID_GAPS))
309                 .floatValue();
310       }
311
312       String maxRes = hconsensus[i].get(StructureFrequency.MAXRESIDUE)
313               .toString();
314       String mouseOver = hconsensus[i].get(StructureFrequency.MAXRESIDUE)
315               + " ";
316       if (maxRes.length() > 1)
317       {
318         mouseOver = "[" + maxRes + "] ";
319         maxRes = "+";
320       }
321       int[][] profile = (int[][]) hconsensus[i]
322               .get(StructureFrequency.PROFILE);
323       if (profile != null && includeAllConsSymbols) // Just responsible for the
324       // tooltip
325       {
326         mouseOver = "";
327         if (alphabet != null)
328         {
329           for (int c = 0; c < alphabet.length; c++)
330           {
331             tval = ((float) profile[0][alphabet[c]])
332                     * 100f
333                     / (float) profile[1][ignoreGapsInConsensusCalculation ? 1
334                             : 0];
335             mouseOver += ((c == 0) ? "" : "; ") + alphabet[c] + " "
336                     + ((int) tval) + "%";
337           }
338         }
339         else
340         {
341           Object[] ca = new Object[profile[0].length];
342           float[] vl = new float[profile[0].length];
343           for (int c = 0; c < ca.length; c++)
344           {
345             ca[c] = new char[]
346             { (char) c };
347             vl[c] = (float) profile[0][c];
348           }
349           ;
350           jalview.util.QuickSort.sort(vl, ca);
351           for (int p = 0, c = ca.length - 1; profile[0][((char[]) ca[c])[0]] > 0; c--)
352           {
353             if (((char[]) ca[c])[0] != '-')
354             {
355               tval = ((float) profile[0][((char[]) ca[c])[0]])
356                       * 100f
357                       / (float) profile[1][ignoreGapsInConsensusCalculation ? 1
358                               : 0];
359               mouseOver += ((p == 0) ? "" : "; ") + ((char[]) ca[c])[0]
360                       + " " + ((int) tval) + "%";
361               p++;
362
363             }
364           }
365
366         }
367       }
368       else
369       {
370         mouseOver += ((int) value + "%");
371       }
372       consensus.annotations[i] = new Annotation(maxRes, mouseOver, ' ',
373               value);
374     }
375   }
376
377   /**
378    * get the sorted base-pair profile for the given position of the consensus
379    * 
380    * @param hconsensus
381    * @return profile of the given column
382    */
383   public static int[] extractProfile(Hashtable hconsensus,
384           boolean ignoreGapsInConsensusCalculation, int column)
385   {
386     // TODO is there a more elegant way to acces the column number?
387     /*
388      * calculate the frequence of the 16 bp variations for this column 'somehow'
389      * transfer this via getProfile and let it correctly draw
390      */
391     int[] rtnval = new int[51]; // 2*(5*5)+1
392     int[][] profile = (int[][]) hconsensus.get(StructureFrequency.PROFILE);
393     int[][] pairs = (int[][]) hconsensus
394             .get(StructureFrequency.PAIRPROFILE);
395
396     if (profile == null)
397       return null;
398     
399     rtnval[0] = 1;
400     for (int j = 65; j <= 90; j++)
401     {
402       for (int k = 65; k <= 90; k++)
403       {
404         if (pairs[j][k] > 0)
405         {
406           rtnval[rtnval[0]++] = j;
407           rtnval[rtnval[0]++] = k;
408           rtnval[rtnval[0]++] = (int) ((float) pairs[j][k] * 100f / (float) profile[1][ignoreGapsInConsensusCalculation ? 1
409                   : 0]);
410         }
411       }
412     }
413
414     return rtnval;
415   }
416
417   public static void main(String args[])
418   {
419     // Short test to see if checkBpType works
420     ArrayList<String> test = new ArrayList<String>();
421     test.add("A");
422     test.add("c");
423     test.add("g");
424     test.add("T");
425     test.add("U");
426     for (String i : test)
427     {
428       for (String j : test)
429       {
430         System.out.println(i + "-" + j + ": "
431                 + StructureFrequency.checkBpType(i.charAt(0), j.charAt(0)));
432       }
433     }
434   }
435 }