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