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