dbba0f4cad6b18711cb3824c1022c7d40255a00f
[jalview.git] / src / jalview / analysis / StructureFrequency.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8)
3  * Copyright (C) 2012 J Procter, AM Waterhouse, LM Lui, J Engelhardt, 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
19 package jalview.analysis;
20
21 import java.util.*;
22
23 import jalview.datamodel.*;
24
25 /**
26  * Takes in a vector or array of sequences and column start and column end and
27  * returns a new Hashtable[] of size maxSeqLength, if Hashtable not supplied.
28  * This class is used extensively in calculating alignment colourschemes that
29  * depend on the amount of conservation in each alignment column.
30  * 
31  * @author $author$
32  * @version $Revision$
33  */
34 public class StructureFrequency
35 {
36   // No need to store 1000s of strings which are not
37   // visible to the user.
38   public static final String MAXCOUNT = "C";
39
40   public static final String MAXRESIDUE = "R";
41
42   public static final String PID_GAPS = "G";
43
44   public static final String PID_NOGAPS = "N";
45
46   public static final String PROFILE = "P";
47
48   public static final String PAIRPROFILE = "B";
49
50   /**
51    * Returns the 3' position of a base pair
52    * 
53    * @param pairs
54    *          Secondary structure annotation
55    * @param indice
56    *          5' position of a base pair
57    * @return 3' position of a base pair
58    */
59   public static int findPair(SequenceFeature[] pairs, int indice)
60   {
61
62     for (int i = 0; i < pairs.length; i++)
63     {
64       if (pairs[i].getBegin() == indice)
65
66       {
67
68         return pairs[i].getEnd();
69
70       }
71     }
72     return -1;
73   }
74
75   /**
76    * Method to calculate a 'base pair consensus row', very similar to nucleotide
77    * consensus but takes into account a given structure
78    * 
79    * @param sequences
80    * @param start
81    * @param end
82    * @param result
83    * @param profile
84    * @param rnaStruc
85    */
86   public static final void calculate(SequenceI[] sequences, int start,
87           int end, Hashtable[] result, boolean profile,
88           AlignmentAnnotation rnaStruc)
89   {
90
91     Hashtable residueHash;
92     String maxResidue;
93     char[] struc = rnaStruc.getRNAStruc().toCharArray();
94
95     SequenceFeature[] rna = rnaStruc._rnasecstr;
96     char c, s, cEnd;
97     int count = 0, nonGap = 0, i, bpEnd = -1, j, jSize = sequences.length;
98     int[] values;
99     int[][] pairs;
100     float percentage;
101     boolean wooble = true;
102     for (i = start; i < end; i++) // foreach column
103     {
104       residueHash = new Hashtable();
105       maxResidue = "-";
106       values = new int[255];
107       pairs = new int[255][255];
108       bpEnd = -1;
109       // System.out.println("s="+struc[i]);
110       if (i < struc.length)
111       {
112         s = struc[i];
113
114       }
115       else
116       {
117         s = '-';
118       }
119       if (s == '.' || s == ' ')
120       {
121         s = '-';
122       }
123
124       if (s != '(' && s != '[')
125       {
126         if (s == '-')
127         {
128           values['-']++;
129         }
130       }
131       else
132       {
133
134         bpEnd = findPair(rna, i);
135
136         if (bpEnd > -1)
137         {
138           for (j = 0; j < jSize; j++) // foreach row
139           {
140             if (sequences[j] == null)
141             {
142               System.err
143                       .println("WARNING: Consensus skipping null sequence - possible race condition.");
144               continue;
145             }
146             c = sequences[j].getCharAt(i);
147             // System.out.println("c="+c);
148
149             // standard representation for gaps in sequence and structure
150             if (c == '.' || c == ' ')
151             {
152               c = '-';
153             }
154
155             if (c == '-')
156             {
157               values['-']++;
158               continue;
159             }
160             cEnd = sequences[j].getCharAt(bpEnd);
161
162             // System.out.println("pairs ="+c+","+cEnd);
163             if (checkBpType(c, cEnd) == true)
164             {
165               values['(']++; // H means it's a helix (structured)
166               maxResidue = "(";
167               wooble = true;
168               // System.out.println("It's a pair wc");
169
170             }
171             if (checkBpType(c, cEnd) == false)
172             {
173               wooble = false;
174               values['[']++; // H means it's a helix (structured)
175               maxResidue = "[";
176
177             }
178             pairs[c][cEnd]++;
179
180           }
181         }
182         // nonGap++;
183       }
184       // UPDATE this for new values
185       if (profile)
186       {
187         residueHash.put(PROFILE, new int[][]
188         { values, new int[]
189         { jSize, (jSize - values['-']) } });
190
191         residueHash.put(PAIRPROFILE, pairs);
192       }
193       if (wooble == true)
194       {
195         count = values['('];
196       }
197       if (wooble == false)
198       {
199         count = values['['];
200       }
201       residueHash.put(MAXCOUNT, new Integer(count));
202       residueHash.put(MAXRESIDUE, maxResidue);
203
204       percentage = ((float) count * 100) / jSize;
205       residueHash.put(PID_GAPS, new Float(percentage));
206
207       // percentage = ((float) count * 100) / (float) nongap;
208       // residueHash.put(PID_NOGAPS, new Float(percentage));
209       if (result[i] == null)
210       {
211         result[i] = residueHash;
212       }
213       if (bpEnd > 0)
214       {
215         values[')'] = values['('];
216         values[']'] = values['['];
217         values['('] = 0;
218         values['['] = 0;
219         residueHash = new Hashtable();
220         if (wooble == true)
221         {
222           // System.out.println(maxResidue+","+wooble);
223           maxResidue = ")";
224         }
225         if (wooble == false)
226         {
227           // System.out.println(maxResidue+","+wooble);
228           maxResidue = "]";
229         }
230         if (profile)
231         {
232           residueHash.put(PROFILE, new int[][]
233           { values, new int[]
234           { jSize, (jSize - values['-']) } });
235
236           residueHash.put(PAIRPROFILE, pairs);
237         }
238
239         residueHash.put(MAXCOUNT, new Integer(count));
240         residueHash.put(MAXRESIDUE, maxResidue);
241
242         percentage = ((float) count * 100) / jSize;
243         residueHash.put(PID_GAPS, new Float(percentage));
244
245         result[bpEnd] = residueHash;
246
247       }
248     }
249   }
250
251   /**
252    * Method to check if a base-pair is a canonical or a wobble bp
253    * 
254    * @param up
255    *          5' base
256    * @param down
257    *          3' base
258    * @return True if it is a canonical/wobble bp
259    */
260   public static boolean checkBpType(char up, char down)
261   {
262     if (up > 'Z')
263     {
264       up -= 32;
265     }
266     if (down > 'Z')
267     {
268       down -= 32;
269     }
270
271     switch (up)
272     {
273     case 'A':
274       switch (down)
275       {
276       case 'T':
277         return true;
278       case 'U':
279         return true;
280       }
281       break;
282     case 'C':
283       switch (down)
284       {
285       case 'G':
286         return true;
287       }
288       break;
289     case 'T':
290       switch (down)
291       {
292       case 'A':
293         return true;
294       case 'G':
295         return true;
296       }
297       break;
298     case 'G':
299       switch (down)
300       {
301       case 'C':
302         return true;
303       case 'T':
304         return true;
305       case 'U':
306         return true;
307       }
308       break;
309     case 'U':
310       switch (down)
311       {
312       case 'A':
313         return true;
314       case 'G':
315         return true;
316       }
317       break;
318     }
319     return false;
320   }
321
322   /**
323    * Compute all or part of the annotation row from the given consensus
324    * hashtable
325    * 
326    * @param consensus
327    *          - pre-allocated annotation row
328    * @param hconsensus
329    * @param iStart
330    * @param width
331    * @param ignoreGapsInConsensusCalculation
332    * @param includeAllConsSymbols
333    */
334   public static void completeConsensus(AlignmentAnnotation consensus,
335           Hashtable[] hconsensus, int iStart, int width,
336           boolean ignoreGapsInConsensusCalculation,
337           boolean includeAllConsSymbols)
338   {
339     float tval, value;
340     if (consensus == null || consensus.annotations == null
341             || consensus.annotations.length < width)
342     {
343       // called with a bad alignment annotation row - wait for it to be
344       // initialised properly
345       return;
346     }
347     for (int i = iStart; i < width; i++)
348     {
349       Hashtable hci;
350       if (i >= hconsensus.length || ((hci = hconsensus[i]) == null))
351       {
352         // happens if sequences calculated over were shorter than alignment
353         // width
354         consensus.annotations[i] = null;
355         continue;
356       }
357       value = 0;
358       Float fv;
359       if (ignoreGapsInConsensusCalculation)
360       {
361         fv = (Float) hci.get(StructureFrequency.PID_NOGAPS);
362       }
363       else
364       {
365         fv = (Float) hci.get(StructureFrequency.PID_GAPS);
366       }
367       if (fv == null)
368       {
369         consensus.annotations[i] = null;
370         // data has changed below us .. give up and
371         continue;
372       }
373       value = fv.floatValue();
374       String maxRes = hci.get(StructureFrequency.MAXRESIDUE).toString();
375       String mouseOver = hci.get(StructureFrequency.MAXRESIDUE) + " ";
376       if (maxRes.length() > 1)
377       {
378         mouseOver = "[" + maxRes + "] ";
379         maxRes = "+";
380       }
381       int[][] profile = (int[][]) hci.get(StructureFrequency.PROFILE);
382       int[][] pairs = (int[][]) hci.get(StructureFrequency.PAIRPROFILE);
383
384       if (pairs != null && includeAllConsSymbols) // Just responsible for the
385       // tooltip
386       // TODO Update tooltips for Structure row
387       {
388         mouseOver = "";
389
390         /*
391          * TODO It's not sure what is the purpose of the alphabet and wheter it
392          * is useful for structure?
393          * 
394          * if (alphabet != null) { for (int c = 0; c < alphabet.length; c++) {
395          * tval = ((float) profile[0][alphabet[c]]) 100f / (float)
396          * profile[1][ignoreGapsInConsensusCalculation ? 1 : 0]; mouseOver +=
397          * ((c == 0) ? "" : "; ") + alphabet[c] + " " + ((int) tval) + "%"; } }
398          * else {
399          */
400         Object[] ca = new Object[625];
401         float[] vl = new float[625];
402         int x = 0;
403         for (int c = 65; c < 90; c++)
404         {
405           for (int d = 65; d < 90; d++)
406           {
407             ca[x] = new int[]
408             { c, d };
409             vl[x] = pairs[c][d];
410             x++;
411           }
412         }
413         jalview.util.QuickSort.sort(vl, ca);
414         int p = 0;
415
416         for (int c = 624; c > 0; c--)
417         {
418           if (vl[c] > 0)
419           {
420             tval = (vl[c] * 100f / profile[1][ignoreGapsInConsensusCalculation ? 1
421                     : 0]);
422             mouseOver += ((p == 0) ? "" : "; ") + (char) ((int[]) ca[c])[0]
423                     + (char) ((int[]) ca[c])[1] + " " + ((int) tval) + "%";
424             p++;
425
426           }
427         }
428
429         // }
430       }
431       else
432       {
433         mouseOver += ((int) value + "%");
434       }
435       consensus.annotations[i] = new Annotation(maxRes, mouseOver, ' ',
436               value);
437     }
438   }
439
440   /**
441    * get the sorted base-pair profile for the given position of the consensus
442    * 
443    * @param hconsensus
444    * @return profile of the given column
445    */
446   public static int[] extractProfile(Hashtable hconsensus,
447           boolean ignoreGapsInConsensusCalculation)
448   {
449     int[] rtnval = new int[74]; // 2*(5*5)+2
450     int[][] profile = (int[][]) hconsensus.get(StructureFrequency.PROFILE);
451     int[][] pairs = (int[][]) hconsensus
452             .get(StructureFrequency.PAIRPROFILE);
453
454     if (profile == null)
455       return null;
456
457     // TODO fix the object length, also do it in completeConsensus
458     Object[] ca = new Object[625];
459     float[] vl = new float[625];
460     int x = 0;
461     for (int c = 65; c < 90; c++)
462     {
463       for (int d = 65; d < 90; d++)
464       {
465         ca[x] = new int[]
466         { c, d };
467         vl[x] = pairs[c][d];
468         x++;
469       }
470     }
471     jalview.util.QuickSort.sort(vl, ca);
472
473     rtnval[0] = 2;
474     rtnval[1] = 0;
475     for (int c = 624; c > 0; c--)
476     {
477       if (vl[c] > 0)
478       {
479         rtnval[rtnval[0]++] = ((int[]) ca[c])[0];
480         rtnval[rtnval[0]++] = ((int[]) ca[c])[1];
481         rtnval[rtnval[0]] = (int) (vl[c] * 100f / profile[1][ignoreGapsInConsensusCalculation ? 1
482                 : 0]);
483         rtnval[1] += rtnval[rtnval[0]++];
484       }
485     }
486
487     return rtnval;
488   }
489
490   public static void main(String args[])
491   {
492     // Short test to see if checkBpType works
493     ArrayList<String> test = new ArrayList<String>();
494     test.add("A");
495     test.add("c");
496     test.add("g");
497     test.add("T");
498     test.add("U");
499     for (String i : test)
500     {
501       for (String j : test)
502       {
503         System.out.println(i + "-" + j + ": "
504                 + StructureFrequency.checkBpType(i.charAt(0), j.charAt(0)));
505       }
506     }
507   }
508 }