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