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