7b97a8dd3691a4ac179dfaf4ae0a180360dccad3
[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       if(i<struc.length){
100         s = struc[i];
101       }else{
102         s = '-';
103       }
104       if (s == '.' || s == ' ')
105       {
106         s = '-';
107       }
108
109       if (s != '(')
110       {
111         if (s == '-')
112         {
113           values['-']++;
114         }
115       }
116       else
117       {
118         for (j = 0; j < jSize; j++) // foreach row
119         {
120           if (sequences[j] == null)
121           {
122             System.err
123                     .println("WARNING: Consensus skipping null sequence - possible race condition.");
124             continue;
125           }
126           seq = sequences[j].getSequence();
127
128           if (seq.length > i)
129           {
130             c = seq[i];
131
132             // standard representation for gaps in sequence and structure
133             if (c == '.' || c == ' ')
134             {
135               c = '-';
136             }
137
138             if (c == '-')
139             {
140               values['-']++;
141               continue;
142             }
143             bpEnd = findPair(rna, i);
144             cEnd = seq[bpEnd];
145             if (checkBpType(c, cEnd))
146             {
147               values['(']++; // H means it's a helix (structured)
148             }
149             pairs[c][cEnd]++;
150
151             maxResidue = "(";
152           }
153         }
154         // nonGap++;
155       }
156       // UPDATE this for new values
157       if (profile)
158       {
159         residueHash.put(PROFILE, new int[][]
160         { values, new int[]
161         { jSize, (jSize - values['-']) } });
162
163         residueHash.put(PAIRPROFILE, pairs);
164       }
165
166       count = values['('];
167
168       residueHash.put(MAXCOUNT, new Integer(count));
169       residueHash.put(MAXRESIDUE, maxResidue);
170
171       percentage = ((float) count * 100) / (float) jSize;
172       residueHash.put(PID_GAPS, new Float(percentage));
173
174       // percentage = ((float) count * 100) / (float) nongap;
175       // residueHash.put(PID_NOGAPS, new Float(percentage));
176       if (result[i] == null)
177       {
178         result[i] = residueHash;
179       }
180       if (bpEnd > 0)
181       {     
182         values[')']=values['('];
183         values['(']=0;
184         
185         residueHash = new Hashtable();
186         maxResidue = ")";
187         
188         if (profile)
189         {
190           residueHash.put(PROFILE, new int[][]
191           { values, new int[]
192           { jSize, (jSize - values['-']) } });
193
194           residueHash.put(PAIRPROFILE, pairs);
195         }
196         
197         residueHash.put(MAXCOUNT, new Integer(count));
198         residueHash.put(MAXRESIDUE, maxResidue);
199
200         percentage = ((float) count * 100) / (float) jSize;
201         residueHash.put(PID_GAPS, new Float(percentage));
202
203         
204         result[bpEnd] = residueHash;
205       }
206     }
207   }
208
209   /**
210    * Method to check if a base-pair is a canonical or a wobble bp
211    * 
212    * @param up
213    *          5' base
214    * @param down
215    *          3' base
216    * @return True if it is a canonical/wobble bp
217    */
218   public static boolean checkBpType(char up, char down)
219   {
220     if (up > 'Z')
221     {
222       up -= 32;
223     }
224     if (down > 'Z')
225     {
226       down -= 32;
227     }
228
229     switch (up)
230     {
231     case 'A':
232       switch (down)
233       {
234       case 'T':
235         return true;
236       case 'U':
237         return true;
238       }
239       break;
240     case 'C':
241       switch (down)
242       {
243       case 'G':
244         return true;
245       }
246       break;
247     case 'T':
248       switch (down)
249       {
250       case 'A':
251         return true;
252       case 'G':
253         return true;
254       }
255       break;
256     case 'G':
257       switch (down)
258       {
259       case 'C':
260         return true;
261       case 'T':
262         return true;
263       case 'U':
264         return true;
265       }
266       break;
267     case 'U':
268       switch (down)
269       {
270       case 'A':
271         return true;
272       case 'G':
273         return true;
274       }
275       break;
276     }
277     return false;
278   }
279
280   /**
281    * Compute all or part of the annotation row from the given consensus
282    * hashtable
283    * 
284    * @param consensus
285    *          - pre-allocated annotation row
286    * @param hconsensus
287    * @param iStart
288    * @param width
289    * @param ignoreGapsInConsensusCalculation
290    * @param includeAllConsSymbols
291    */
292   public static void completeConsensus(AlignmentAnnotation consensus,
293           Hashtable[] hconsensus, int iStart, int width,
294           boolean ignoreGapsInConsensusCalculation,
295           boolean includeAllConsSymbols)
296   {
297     completeConsensus(consensus, hconsensus, iStart, width,
298             ignoreGapsInConsensusCalculation, includeAllConsSymbols, null); // new
299     // char[]
300     // { 'A', 'C', 'G', 'T', 'U' });
301   }
302
303   public static void completeConsensus(AlignmentAnnotation consensus,
304           Hashtable[] hconsensus, int iStart, int width,
305           boolean ignoreGapsInConsensusCalculation,
306           boolean includeAllConsSymbols, char[] alphabet)
307   {
308     float tval, value;
309     if (consensus == null || consensus.annotations == null
310             || consensus.annotations.length < width)
311     {
312       // called with a bad alignment annotation row - wait for it to be
313       // initialised properly
314       return;
315     }
316     for (int i = iStart; i < width; i++)
317     {
318       if (i >= hconsensus.length)
319       {
320         // happens if sequences calculated over were shorter than alignment
321         // width
322         consensus.annotations[i] = null;
323         continue;
324       }
325       value = 0;
326       if (ignoreGapsInConsensusCalculation)
327       {
328         value = ((Float) hconsensus[i].get(StructureFrequency.PID_NOGAPS))
329                 .floatValue();
330       }
331       else
332       {
333         value = ((Float) hconsensus[i].get(StructureFrequency.PID_GAPS))
334                 .floatValue();
335       }
336
337       String maxRes = hconsensus[i].get(StructureFrequency.MAXRESIDUE)
338               .toString();
339       String mouseOver = hconsensus[i].get(StructureFrequency.MAXRESIDUE)
340               + " ";
341       if (maxRes.length() > 1)
342       {
343         mouseOver = "[" + maxRes + "] ";
344         maxRes = "+";
345       }
346       int[][] profile = (int[][]) hconsensus[i]
347               .get(StructureFrequency.PROFILE);
348       if (profile != null && includeAllConsSymbols) // Just responsible for the
349       // tooltip
350       //TODO Update tooltips for Structure row
351       {
352         mouseOver = "";
353         if (alphabet != null)
354         {
355           for (int c = 0; c < alphabet.length; c++)
356           {
357             tval = ((float) profile[0][alphabet[c]])
358                     * 100f
359                     / (float) profile[1][ignoreGapsInConsensusCalculation ? 1
360                             : 0];
361             mouseOver += ((c == 0) ? "" : "; ") + alphabet[c] + " "
362                     + ((int) tval) + "%";
363           }
364         }
365         else
366         {
367           Object[] ca = new Object[profile[0].length];
368           float[] vl = new float[profile[0].length];
369           for (int c = 0; c < ca.length; c++)
370           {
371             ca[c] = new char[]
372             { (char) c };
373             vl[c] = (float) profile[0][c];
374           }
375           ;
376           jalview.util.QuickSort.sort(vl, ca);
377           for (int p = 0, c = ca.length - 1; profile[0][((char[]) ca[c])[0]] > 0; c--)
378           {
379             if (((char[]) ca[c])[0] != '-')
380             {
381               tval = ((float) profile[0][((char[]) ca[c])[0]])
382                       * 100f
383                       / (float) profile[1][ignoreGapsInConsensusCalculation ? 1
384                               : 0];
385               mouseOver += ((p == 0) ? "" : "; ") + ((char[]) ca[c])[0]
386                       + " " + ((int) tval) + "%";
387               p++;
388
389             }
390           }
391
392         }
393       }
394       else
395       {
396         mouseOver += ((int) value + "%");
397       }
398       consensus.annotations[i] = new Annotation(maxRes, mouseOver, ' ',
399               value);
400     }
401   }
402
403   /**
404    * get the sorted base-pair profile for the given position of the consensus
405    * 
406    * @param hconsensus
407    * @return profile of the given column
408    */
409   public static int[] extractProfile(Hashtable hconsensus,
410           boolean ignoreGapsInConsensusCalculation, int column)
411   {
412     // TODO is there a more elegant way to acces the column number?
413     /*
414      * calculate the frequence of the 16 bp variations for this column 'somehow'
415      * transfer this via getProfile and let it correctly draw
416      */
417     int[] rtnval = new int[51]; // 2*(5*5)+1
418     int[][] profile = (int[][]) hconsensus.get(StructureFrequency.PROFILE);
419     int[][] pairs = (int[][]) hconsensus
420             .get(StructureFrequency.PAIRPROFILE);
421
422     if (profile == null)
423       return null;
424     
425     Object[] ca = new Object[625];
426     float[] vl = new float[625];
427     int x=0;
428     for (int c = 65; c < 90; c++)
429     {
430       for(int d = 65; d< 90; d++)
431       {
432       ca[x] = new int[]{ c, d};
433       vl[x] = (float) pairs[c][d];
434       x++;
435       }
436     }
437     jalview.util.QuickSort.sort(vl, ca);
438     
439     rtnval[0] = 1;
440     for (int c=624; c>0; c--)
441     {
442       if (vl[c]>0)
443       {
444         rtnval[rtnval[0]++] = ((int[]) ca[c])[0];
445         rtnval[rtnval[0]++] = ((int[]) ca[c])[1];
446         rtnval[rtnval[0]++] = (int) ((float) vl[c] * 100f / (float) profile[1][ignoreGapsInConsensusCalculation ? 1
447                 : 0]);
448        }
449     }
450     
451     return rtnval;
452   }
453
454   public static void main(String args[])
455   {
456     // Short test to see if checkBpType works
457     ArrayList<String> test = new ArrayList<String>();
458     test.add("A");
459     test.add("c");
460     test.add("g");
461     test.add("T");
462     test.add("U");
463     for (String i : test)
464     {
465       for (String j : test)
466       {
467         System.out.println(i + "-" + j + ": "
468                 + StructureFrequency.checkBpType(i.charAt(0), j.charAt(0)));
469       }
470     }
471   }
472 }