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