JAL-2759 Moved propagateInsertions out of HiddenColumns
[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, canonical = 0;
116       int otherPairCount = 0;
117       int nongap = 0;
118       maxResidue = "-";
119       values = new int[255];
120       pairs = new int[255][255];
121       bpEnd = -1;
122       if (i < struc.length)
123       {
124         s = struc[i];
125       }
126       else
127       {
128         s = '-';
129       }
130       if (s == '.' || s == ' ')
131       {
132         s = '-';
133       }
134
135       if (!Rna.isOpeningParenthesis(s))
136       {
137         if (s == '-')
138         {
139           values['-']++;
140         }
141       }
142       else
143       {
144         bpEnd = findPair(rna, i);
145
146         if (bpEnd > -1)
147         {
148           for (int j = 0; j < jSize; j++) // foreach row
149           {
150             if (sequences[j] == null)
151             {
152               System.err.println(
153                       "WARNING: Consensus skipping null sequence - possible race condition.");
154               continue;
155             }
156
157             c = sequences[j].getCharAt(i);
158             cEnd = sequences[j].getCharAt(bpEnd);
159
160             if (Comparison.isGap(c) || Comparison.isGap(cEnd))
161             {
162               values['-']++;
163               continue;
164             }
165             nongap++;
166             /*
167              * ensure upper-case for counting purposes
168              */
169             if ('a' <= c && 'z' >= c)
170             {
171               c += 'A' - 'a';
172             }
173             if ('a' <= cEnd && 'z' >= cEnd)
174             {
175               cEnd += 'A' - 'a';
176             }
177             if (Rna.isCanonicalOrWobblePair(c, cEnd))
178             {
179               canonicalOrWobblePairCount++;
180               if (Rna.isCanonicalPair(c, cEnd))
181               {
182                 canonical++;
183               }
184             }
185             else
186             {
187               otherPairCount++;
188             }
189             pairs[c][cEnd]++;
190           }
191         }
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,
199                 new int[][]
200                 { values, new int[] { jSize, (jSize - values['-']) } });
201
202         residueHash.put(PAIRPROFILE, pairs);
203       }
204       values['('] = canonicalOrWobblePairCount;
205       values['['] = canonical;
206       values['{'] = otherPairCount;
207       /*
208        * the count is the number of valid pairs (as a percentage, determines
209        * the relative size of the profile logo)
210        */
211       int count = canonicalOrWobblePairCount;
212
213       /*
214        * display '(' if most pairs are canonical, or as
215        * '[' if there are more wobble pairs. 
216        */
217       if (canonicalOrWobblePairCount > 0 || otherPairCount > 0)
218       {
219         if (canonicalOrWobblePairCount >= otherPairCount)
220         {
221           maxResidue = (canonicalOrWobblePairCount - canonical) < canonical
222                   ? "("
223                   : "[";
224         }
225         else
226         {
227           maxResidue = "{";
228         }
229       }
230       residueHash.put(MAXCOUNT, new Integer(count));
231       residueHash.put(MAXRESIDUE, maxResidue);
232
233       percentage = ((float) count * 100) / jSize;
234       residueHash.put(PID_GAPS, new Float(percentage));
235
236       percentage = ((float) count * 100) / nongap;
237       residueHash.put(PID_NOGAPS, new Float(percentage));
238
239       if (result[i] == null)
240       {
241         result[i] = residueHash;
242       }
243       if (bpEnd > 0)
244       {
245         values[')'] = values['('];
246         values[']'] = values['['];
247         values['}'] = values['{'];
248         values['('] = 0;
249         values['['] = 0;
250         values['{'] = 0;
251         maxResidue = maxResidue.equals("(") ? ")"
252                 : maxResidue.equals("[") ? "]" : "}";
253
254         residueHash = new Hashtable();
255         if (profile)
256         {
257           residueHash.put(PROFILE,
258                   new int[][]
259                   { values, new int[] { jSize, (jSize - values['-']) } });
260
261           residueHash.put(PAIRPROFILE, pairs);
262         }
263
264         residueHash.put(MAXCOUNT, new Integer(count));
265         residueHash.put(MAXRESIDUE, maxResidue);
266
267         percentage = ((float) count * 100) / jSize;
268         residueHash.put(PID_GAPS, new Float(percentage));
269
270         percentage = ((float) count * 100) / nongap;
271         residueHash.put(PID_NOGAPS, new Float(percentage));
272
273         result[bpEnd] = residueHash;
274       }
275     }
276   }
277
278   /**
279    * Compute all or part of the annotation row from the given consensus
280    * hashtable
281    * 
282    * @param consensus
283    *          - pre-allocated annotation row
284    * @param hconsensus
285    * @param iStart
286    * @param width
287    * @param ignoreGapsInConsensusCalculation
288    * @param includeAllConsSymbols
289    */
290   public static void completeConsensus(AlignmentAnnotation consensus,
291           Hashtable[] hconsensus, int iStart, int width,
292           boolean ignoreGapsInConsensusCalculation,
293           boolean includeAllConsSymbols, long nseq)
294   {
295     float tval, value;
296     if (consensus == null || consensus.annotations == null
297             || consensus.annotations.length < width)
298     {
299       // called with a bad alignment annotation row - wait for it to be
300       // initialised properly
301       return;
302     }
303     String fmtstr = "%3.1f";
304     int precision = 2;
305     while (nseq > 100)
306     {
307       precision++;
308       nseq /= 10;
309     }
310     if (precision > 2)
311     {
312       fmtstr = "%" + (2 + precision) + "." + precision + "f";
313     }
314     Format fmt = new Format(fmtstr);
315
316     for (int i = iStart; i < width; i++)
317     {
318       Hashtable hci;
319       if (i >= hconsensus.length || ((hci = hconsensus[i]) == null))
320       {
321         // happens if sequences calculated over were shorter than alignment
322         // width
323         consensus.annotations[i] = null;
324         continue;
325       }
326       value = 0;
327       Float fv;
328       if (ignoreGapsInConsensusCalculation)
329       {
330         fv = (Float) hci.get(StructureFrequency.PID_NOGAPS);
331       }
332       else
333       {
334         fv = (Float) hci.get(StructureFrequency.PID_GAPS);
335       }
336       if (fv == null)
337       {
338         consensus.annotations[i] = null;
339         // data has changed below us .. give up and
340         continue;
341       }
342       value = fv.floatValue();
343       String maxRes = hci.get(StructureFrequency.MAXRESIDUE).toString();
344       String mouseOver = hci.get(StructureFrequency.MAXRESIDUE) + " ";
345       if (maxRes.length() > 1)
346       {
347         mouseOver = "[" + maxRes + "] ";
348         maxRes = "+";
349       }
350       int[][] profile = (int[][]) hci.get(StructureFrequency.PROFILE);
351       int[][] pairs = (int[][]) hci.get(StructureFrequency.PAIRPROFILE);
352
353       if (pairs != null && includeAllConsSymbols) // Just responsible for the
354       // tooltip
355       // TODO Update tooltips for Structure row
356       {
357         mouseOver = "";
358
359         /*
360          * TODO It's not sure what is the purpose of the alphabet and wheter it
361          * is useful for structure?
362          * 
363          * if (alphabet != null) { for (int c = 0; c < alphabet.length; c++) {
364          * tval = ((float) profile[0][alphabet[c]]) 100f / (float)
365          * profile[1][ignoreGapsInConsensusCalculation ? 1 : 0]; mouseOver +=
366          * ((c == 0) ? "" : "; ") + alphabet[c] + " " + ((int) tval) + "%"; } }
367          * else {
368          */
369         int[][] ca = new int[625][];
370         float[] vl = new float[625];
371         int x = 0;
372         for (int c = 65; c < 90; c++)
373         {
374           for (int d = 65; d < 90; d++)
375           {
376             ca[x] = new int[] { c, d };
377             vl[x] = pairs[c][d];
378             x++;
379           }
380         }
381         jalview.util.QuickSort.sort(vl, ca);
382         int p = 0;
383
384         /*
385          * profile[1] is {total, ungappedTotal}
386          */
387         final int divisor = profile[1][ignoreGapsInConsensusCalculation ? 1
388                 : 0];
389         for (int c = 624; c > 0; c--)
390         {
391           if (vl[c] > 0)
392           {
393             tval = (vl[c] * 100f / divisor);
394             mouseOver += ((p == 0) ? "" : "; ") + (char) ca[c][0]
395                     + (char) ca[c][1] + " " + fmt.form(tval) + "%";
396             p++;
397
398           }
399         }
400
401         // }
402       }
403       else
404       {
405         mouseOver += (fmt.form(value) + "%");
406       }
407       consensus.annotations[i] = new Annotation(maxRes, mouseOver, ' ',
408               value);
409     }
410   }
411
412   /**
413    * get the sorted base-pair profile for the given position of the consensus
414    * 
415    * @param hconsensus
416    * @return profile of the given column
417    */
418   public static int[] extractProfile(Hashtable hconsensus,
419           boolean ignoreGapsInConsensusCalculation)
420   {
421     int[] rtnval = new int[STRUCTURE_PROFILE_LENGTH]; // 2*(5*5)+2
422     int[][] profile = (int[][]) hconsensus.get(StructureFrequency.PROFILE);
423     int[][] pairs = (int[][]) hconsensus
424             .get(StructureFrequency.PAIRPROFILE);
425
426     if (profile == null)
427     {
428       return null;
429     }
430
431     // TODO fix the object length, also do it in completeConsensus
432     // Object[] ca = new Object[625];
433     int[][] ca = new int[625][];
434     float[] vl = new float[625];
435     int x = 0;
436     for (int c = 65; c < 90; c++)
437     {
438       for (int d = 65; d < 90; d++)
439       {
440         ca[x] = new int[] { c, d };
441         vl[x] = pairs[c][d];
442         x++;
443       }
444     }
445     jalview.util.QuickSort.sort(vl, ca);
446
447     int valuesCount = 0;
448     rtnval[1] = 0;
449     int offset = 2;
450     final int divisor = profile[1][ignoreGapsInConsensusCalculation ? 1
451             : 0];
452     for (int c = 624; c > 0; c--)
453     {
454       if (vl[c] > 0)
455       {
456         rtnval[offset++] = ca[c][0];
457         rtnval[offset++] = ca[c][1];
458         rtnval[offset] = (int) (vl[c] * 100f / divisor);
459         rtnval[1] += rtnval[offset++];
460         valuesCount++;
461       }
462     }
463     rtnval[0] = valuesCount;
464
465     // insert profile type code in position 0
466     int[] result = new int[rtnval.length + 1];
467     result[0] = AlignmentAnnotation.STRUCTURE_PROFILE;
468     System.arraycopy(rtnval, 0, result, 1, rtnval.length);
469     return result;
470   }
471
472   public static void main(String args[])
473   {
474     // Short test to see if checkBpType works
475     ArrayList<String> test = new ArrayList<String>();
476     test.add("A");
477     test.add("c");
478     test.add("g");
479     test.add("T");
480     test.add("U");
481     for (String i : test)
482     {
483       for (String j : test)
484       {
485         System.out.println(i + "-" + j + ": "
486                 + Rna.isCanonicalOrWobblePair(i.charAt(0), j.charAt(0)));
487       }
488     }
489   }
490 }