JAL-891; sequence logos are available on structure conservation row
[jalview.git] / src / jalview / analysis / StructureFrequency.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.analysis;
19
20 import java.util.*;
21
22 import jalview.datamodel.*;
23
24 /**
25  * Takes in a vector or array of sequences and column start and column end and
26  * returns a new Hashtable[] of size maxSeqLength, if Hashtable not supplied.
27  * This class is used extensively in calculating alignment colourschemes that
28  * depend on the amount of conservation in each alignment column.
29  * 
30  * @author $author$
31  * @version $Revision$
32  */
33 public class StructureFrequency
34 {
35   // No need to store 1000s of strings which are not
36   // visible to the user.
37   public static final String MAXCOUNT = "C";
38
39   public static final String MAXRESIDUE = "R";
40
41   public static final String PID_GAPS = "G";
42
43   public static final String PID_NOGAPS = "N";
44
45   public static final String PROFILE = "P";
46
47   public static final Hashtable[] calculate(Vector sequences, int start,
48           int end)
49   {
50     return calculate(sequences, start, end, false);
51   }
52
53   public static final Hashtable[] calculate(Vector sequences, int start,
54           int end, boolean profile)
55   {
56     SequenceI[] seqs = new SequenceI[sequences.size()];
57     int width = 0;
58     for (int i = 0; i < sequences.size(); i++)
59     {
60       seqs[i] = (SequenceI) sequences.elementAt(i);
61       if (seqs[i].getLength() > width)
62       {
63         width = seqs[i].getLength();
64       }
65     }
66
67     Hashtable[] reply = new Hashtable[width];
68
69     if (end >= width)
70     {
71       end = width;
72     }
73
74     calculate(seqs, start, end, reply, profile);
75
76     return reply;
77   }
78
79   public static final void calculate(SequenceI[] sequences, int start,
80           int end, Hashtable[] result)
81   {
82     calculate(sequences, start, end, result, false);
83   }
84
85   public static final void calculate(SequenceI[] sequences, int start,
86           int end, Hashtable[] result, boolean profile)
87   {
88     Hashtable residueHash;
89     int maxCount, nongap, i, j, v, jSize = sequences.length;
90     String maxResidue;
91     char c;
92     float percentage;
93
94     int[] values = new int[255];
95
96     char[] seq;
97
98     for (i = start; i < end; i++)
99     {
100       residueHash = new Hashtable();
101       maxCount = 0;
102       maxResidue = "";
103       nongap = 0;
104       values = new int[255];
105       
106
107       for (j = 0; j < jSize; j++)
108       {
109         if (sequences[j]==null)
110         {
111           System.err.println("WARNING: Consensus skipping null sequence - possible race condition.");
112           continue;
113         }
114         seq = sequences[j].getSequence();
115         if (seq.length > i)
116         {
117           c = seq[i];
118
119           if (c == '.' || c == ' ')
120           {
121             c = '-';
122           }
123
124           if (c == '-')
125           {
126             values['-']++;
127             continue;
128           }
129           else if ('a' <= c && c <= 'z')
130           {
131             c -= 32; // ('a' - 'A');
132           }
133
134           nongap++;
135           values[c]++;
136
137         }
138         else
139         {
140           values['-']++;
141         }
142       }
143
144       for (v = 'A'; v < 'Z'; v++)
145       {
146         if (values[v] < 2 || values[v] < maxCount)
147         {
148           continue;
149         }
150
151         if (values[v] > maxCount)
152         {
153           maxResidue = String.valueOf((char) v);
154         }
155         else if (values[v] == maxCount)
156         {
157           maxResidue += String.valueOf((char) v);
158         }
159         maxCount = values[v];
160       }
161
162       if (maxResidue.length() == 0)
163       {
164         maxResidue = "-";
165       }
166       if (profile)
167       {
168         residueHash.put(PROFILE, new int[][]
169         { values, new int[]
170         { jSize, nongap } });
171       }
172       residueHash.put(MAXCOUNT, new Integer(maxCount));
173       residueHash.put(MAXRESIDUE, maxResidue);
174
175       percentage = ((float) maxCount * 100) / (float) jSize;
176       residueHash.put(PID_GAPS, new Float(percentage));
177
178       percentage = ((float) maxCount * 100) / (float) nongap;
179       residueHash.put(PID_NOGAPS, new Float(percentage));
180       result[i] = residueHash;
181     }
182   }
183   
184   public static int findPair(SequenceFeature[] pairs,int indice){
185           for(int i=0; i<pairs.length; i++){
186                   if(pairs[i].getBegin()==indice){
187                           return pairs[i].getEnd();
188                   }
189           }
190           return -1;
191   }
192   
193   /**
194    * Method to calculate a 'base pair consensus row', very similar 
195    * to nucleotide consensus but takes into account a given structure
196    * @param sequences
197    * @param start
198    * @param end
199    * @param result
200    * @param profile
201    * @param rnaStruc
202    */
203   public static final void calculate(SequenceI[] sequences, int start,
204                   int end, Hashtable[] result, boolean profile, AlignmentAnnotation rnaStruc){
205           //System.out.println("StructureFrequency.calculateNEW4");
206           Hashtable residueHash;
207           String maxResidue;
208           char[] seq, struc=rnaStruc.getRNAStruc().toCharArray();
209           SequenceFeature[] rna =rnaStruc._rnasecstr;
210           char c,s,cEnd;
211           int count,nonGap=0,i,bpEnd=-1,j,jSize = sequences.length;
212           int[] values = new int[255];
213           float percentage;
214                   
215           for (i = start; i < end; i++) //foreach column
216           {
217                   residueHash = new Hashtable();
218                   maxResidue="-";
219                   values = new int[255];
220                   bpEnd=-1;
221
222                   s = struc[i];
223                   if (s == '.' || s == ' ')
224                   {
225                           s = '-';
226                   }
227
228                   if(s != '('){
229                           if(s == '-'){
230                                   values['-']++;
231                           }
232                   }
233                   else
234                   {
235                           for (j = 0; j < jSize; j++) //foreach row
236                           {
237                                   if (sequences[j]==null)
238                                   {
239                                           System.err.println("WARNING: Consensus skipping null sequence - possible race condition.");
240                                           continue;
241                                   }
242                                   seq = sequences[j].getSequence();
243                                   
244                                   if (seq.length > i)
245                                   {
246                                           c = seq[i];
247
248                                           //standard representation for gaps in sequence and structure
249                                           if (c == '.' || c == ' ')
250                                           {
251                                                   c = '-';
252                                           }
253
254                                           if (c == '-')
255                                           {
256                                                   values['-']++;
257                                                   continue;
258                                           }
259                                           bpEnd=findPair(rna,i);
260                                           cEnd=seq[bpEnd];
261                                           if(checkBpType(c,cEnd)){
262                                                   values['H']++; //H means it's a helix (structured)
263                                           }
264                                           maxResidue="H";
265                                   }
266                           }
267 //                        nonGap++;
268                   }
269                   //UPDATE this for new values
270               if (profile)
271               {
272                   //System.out.println("profile");
273                 residueHash.put(PROFILE, new int[][]
274                 { values, new int[]
275                 { jSize, values['H'] } });
276               }
277                    
278
279                   count=values['H'];
280                                   
281                   residueHash.put(MAXCOUNT, new Integer(count));
282               residueHash.put(MAXRESIDUE, maxResidue);
283               
284                   percentage = ((float) count * 100) / (float) jSize;
285                   residueHash.put(PID_GAPS, new Float(percentage));
286
287                   //percentage = ((float) count * 100) / (float) nongap;
288                   //residueHash.put(PID_NOGAPS, new Float(percentage));
289                   if(result[i]==null){
290                           result[i] = residueHash;
291                   }
292                   if(bpEnd>0){
293                           result[bpEnd]=residueHash;
294                   }
295           }
296   }
297
298          
299   /**
300    * Method to check if a base-pair is a canonical or a wobble bp 
301    * @param up 5' base
302    * @param down 3' base
303    * @return True if it is a canonical/wobble bp
304    */
305   public static boolean checkBpType(char up, char down){
306           if(up>'Z'){up-=32;}
307           if(down>'Z'){down-=32;}
308           
309           switch (up){
310                 case 'A': 
311                         switch (down){
312                                 case 'T': return true;
313                                 case 'U': return true;
314                         }
315                 break;
316                 case 'C': 
317                         switch (down){
318                                 case 'G': return true;
319                         }
320                 break;
321                 case 'T': 
322                         switch (down){
323                                 case 'A': return true;
324                                 case 'G': return true;
325                                 }
326                 break;
327                 case 'G': 
328                         switch (down){
329                                 case 'C': return true;
330                                 case 'T': return true;
331                                 case 'U': return true;
332                         }
333                 break;
334                 case 'U': 
335                         switch (down){
336                                 case 'A': return true;
337                                 case 'G': return true;
338                         }
339                 break;
340           }       
341           return false;
342   }
343   
344   /**
345    * Compute all or part of the annotation row from the given consensus
346    * hashtable
347    * 
348    * @param consensus
349    *          - pre-allocated annotation row
350    * @param hconsensus
351    * @param iStart
352    * @param width
353    * @param ignoreGapsInConsensusCalculation
354    * @param includeAllConsSymbols
355    */
356   public static void completeConsensus(AlignmentAnnotation consensus,
357           Hashtable[] hconsensus, int iStart, int width,
358           boolean ignoreGapsInConsensusCalculation,
359           boolean includeAllConsSymbols)
360   {
361     completeConsensus(consensus, hconsensus, iStart, width,
362             ignoreGapsInConsensusCalculation, includeAllConsSymbols, null); // new
363                                                                             // char[]
364     // { 'A', 'C', 'G', 'T', 'U' });
365   }
366
367   public static void completeConsensus(AlignmentAnnotation consensus,
368           Hashtable[] hconsensus, int iStart, int width,
369           boolean ignoreGapsInConsensusCalculation,
370           boolean includeAllConsSymbols, char[] alphabet)
371   {
372         System.out.println("StructureFrequency.completeConsensus "+includeAllConsSymbols);
373     float tval, value;
374     if (consensus == null || consensus.annotations == null
375             || consensus.annotations.length < width)
376     {
377       // called with a bad alignment annotation row - wait for it to be
378       // initialised properly
379       return;
380     }
381     for (int i = iStart; i < width; i++)
382     {
383       if (i >= hconsensus.length)
384       {
385         // happens if sequences calculated over were shorter than alignment
386         // width
387         consensus.annotations[i] = null;
388         continue;
389       }
390       value = 0;
391       if (ignoreGapsInConsensusCalculation)
392       {
393         value = ((Float) hconsensus[i].get(StructureFrequency.PID_NOGAPS))
394                 .floatValue();
395       }
396       else
397       {
398         value = ((Float) hconsensus[i].get(StructureFrequency.PID_GAPS))
399                 .floatValue();
400       }
401       
402       String maxRes = hconsensus[i].get(StructureFrequency.MAXRESIDUE).toString();
403       String mouseOver = hconsensus[i].get(StructureFrequency.MAXRESIDUE) + " ";
404       if (maxRes.length() > 1)
405       {
406         mouseOver = "[" + maxRes + "] ";
407         maxRes = "+";
408       }
409       int[][] profile = (int[][]) hconsensus[i].get(StructureFrequency.PROFILE);
410       if (profile != null && includeAllConsSymbols) //Just responsible for the tooltip
411       {
412         //System.out.println("StructureFrequency.includeAllConsSymbols");
413         mouseOver = "";
414         if (alphabet != null)
415         {
416           for (int c = 0; c < alphabet.length; c++)
417           {
418             tval = ((float) profile[0][alphabet[c]])
419                     * 100f
420                     / (float) profile[1][ignoreGapsInConsensusCalculation ? 1
421                             : 0];
422             mouseOver += ((c == 0) ? "" : "; ") + alphabet[c] + " "
423                     + ((int) tval) + "%";
424           }
425         }
426         else
427         {
428           //System.out.println("StructureFrequency.NOTincludeAllConsSymbols");
429           Object[] ca = new Object[profile[0].length];
430           float[] vl = new float[profile[0].length];
431           for (int c = 0; c < ca.length; c++)
432           {
433             ca[c] = new char[]
434             { (char) c };
435             vl[c] = (float) profile[0][c];
436           }
437           ;
438           jalview.util.QuickSort.sort(vl, ca);
439           for (int p = 0, c = ca.length - 1; profile[0][((char[]) ca[c])[0]] > 0; c--)
440           {
441             if (((char[]) ca[c])[0] != '-')
442             {
443               tval = ((float) profile[0][((char[]) ca[c])[0]])
444                       * 100f
445                       / (float) profile[1][ignoreGapsInConsensusCalculation ? 1
446                               : 0];
447               mouseOver += ((p == 0) ? "" : "; ") + ((char[]) ca[c])[0]
448                       + " " + ((int) tval) + "%";
449               p++;
450
451             }
452           }
453
454         }
455       }
456       else
457       {
458         mouseOver += ((int) value + "%");
459       }
460       consensus.annotations[i] = new Annotation(maxRes, mouseOver, ' ',
461               value);
462     }
463   }
464
465   /**
466    * get the sorted profile for the given position of the consensus
467    * 
468    * @param hconsensus
469    * @return
470    */
471   public static int[] extractProfile(Hashtable hconsensus,
472                   boolean ignoreGapsInConsensusCalculation)
473   {
474           //System.out.println("StructureFrequency.extractProfile");
475           int[] rtnval = new int[11];
476           int[][] profile = (int[][]) hconsensus.get(StructureFrequency.PROFILE);
477           if (profile == null)
478                   return null;
479           
480           Object[] ca = new Object[profile[0].length];
481           float[] vl = new float[profile[0].length];
482           for (int c = 0; c < ca.length; c++)
483           {
484                   ca[c] = new char[]
485                                    { (char) c };
486                   vl[c] = (float) profile[0][c];
487           }
488           ;
489           jalview.util.QuickSort.sort(vl, ca);
490           rtnval[0] = 1;
491           for (int c = ca.length - 1; profile[0][((char[]) ca[c])[0]] > 0; c--)
492           {
493                   if (((char[]) ca[c])[0] != '-')
494                   {
495                           rtnval[rtnval[0]++] = ((char[]) ca[c])[0];
496                           rtnval[rtnval[0]++] = (int) (((float) profile[0][((char[]) ca[c])[0]]) * 100f / (float) profile[1][ignoreGapsInConsensusCalculation ? 1
497                                           : 0]);
498                   }
499           }
500           return rtnval;
501   }
502
503   public static void main(String args[]){
504           //Short test to see if checkBpType works
505           ArrayList<String> test = new ArrayList<String>();
506           test.add("A");
507           test.add("c");
508           test.add("g");
509           test.add("T");
510           test.add("U");
511           for (String i : test) {
512                   for (String j : test) {
513                           System.out.println(i+"-"+j+": "+StructureFrequency.checkBpType(i.charAt(0),j.charAt(0)));
514                   }
515           }
516   }
517 }