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