JAL-2089 patch broken merge to master for Release 2.10.0b1
[jalview.git] / src / jalview / analysis / Conservation.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.Sequence;
26 import jalview.datamodel.SequenceI;
27 import jalview.schemes.ResidueProperties;
28
29 import java.awt.Color;
30 import java.util.Hashtable;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Vector;
34
35 /**
36  * Calculates conservation values for a given set of sequences
37  * 
38  * @author $author$
39  * @version $Revision$
40  */
41 public class Conservation
42 {
43   SequenceI[] sequences;
44
45   int start;
46
47   int end;
48
49   Vector<int[]> seqNums; // vector of int vectors where first is sequence
50                          // checksum
51
52   int maxLength = 0; // used by quality calcs
53
54   boolean seqNumsChanged = false; // updated after any change via calcSeqNum;
55
56   Map<String, Integer>[] total;
57
58   boolean canonicaliseAa = true; // if true then conservation calculation will
59
60   // map all symbols to canonical aa numbering
61   // rather than consider conservation of that
62   // symbol
63
64   /** Stores calculated quality values */
65   private Vector<Double> quality;
66
67   /** Stores maximum and minimum values of quality values */
68   private double[] qualityRange = new double[2];
69
70   private Sequence consSequence;
71
72   private int threshold;
73
74   private String name = "";
75
76   private int[][] cons2;
77
78   private String[] consSymbs;
79
80   /**
81    * Creates a new Conservation object.
82    * 
83    * @param name
84    *          Name of conservation
85    * @param threshold
86    *          to count the residues in residueHash(). commonly used value is 3
87    * @param sequences
88    *          sequences to be used in calculation
89    * @param start
90    *          start residue position
91    * @param end
92    *          end residue position
93    */
94   public Conservation(String name, int threshold,
95           List<SequenceI> sequences, int start, int end)
96   {
97     this.name = name;
98     this.threshold = threshold;
99     this.start = start;
100     this.end = end;
101
102     maxLength = end - start + 1; // default width includes bounds of
103     // calculation
104
105     int s, sSize = sequences.size();
106     SequenceI[] sarray = new SequenceI[sSize];
107     this.sequences = sarray;
108     try
109     {
110       for (s = 0; s < sSize; s++)
111       {
112         sarray[s] = sequences.get(s);
113         if (sarray[s].getLength() > maxLength)
114         {
115           maxLength = sarray[s].getLength();
116         }
117       }
118     } catch (ArrayIndexOutOfBoundsException ex)
119     {
120       // bail - another thread has modified the sequence array, so the current
121       // calculation is probably invalid.
122       this.sequences = new SequenceI[0];
123       maxLength = 0;
124     }
125   }
126
127   /**
128    * Translate sequence i into a numerical representation and store it in the
129    * i'th position of the seqNums array.
130    * 
131    * @param i
132    */
133   private void calcSeqNum(int i)
134   {
135     String sq = null; // for dumb jbuilder not-inited exception warning
136     int[] sqnum = null;
137
138     int sSize = sequences.length;
139
140     if ((i > -1) && (i < sSize))
141     {
142       sq = sequences[i].getSequenceAsString();
143
144       if (seqNums.size() <= i)
145       {
146         seqNums.addElement(new int[sq.length() + 1]);
147       }
148
149       if (sq.hashCode() != seqNums.elementAt(i)[0])
150       {
151         int j;
152         int len;
153         seqNumsChanged = true;
154         len = sq.length();
155
156         if (maxLength < len)
157         {
158           maxLength = len;
159         }
160
161         sqnum = new int[len + 1]; // better to always make a new array -
162         // sequence can change its length
163         sqnum[0] = sq.hashCode();
164
165         for (j = 1; j <= len; j++)
166         {
167           sqnum[j] = jalview.schemes.ResidueProperties.aaIndex[sq
168                   .charAt(j - 1)];
169         }
170
171         seqNums.setElementAt(sqnum, i);
172       }
173       else
174       {
175         System.out.println("SEQUENCE HAS BEEN DELETED!!!");
176       }
177     }
178     else
179     {
180       // JBPNote INFO level debug
181       System.err
182               .println("ERROR: calcSeqNum called with out of range sequence index for Alignment\n");
183     }
184   }
185
186   /**
187    * Calculates the conservation values for given set of sequences
188    */
189   public void calculate()
190   {
191     int thresh, j, jSize = sequences.length;
192     int[] values; // Replaces residueHash
193     char c;
194
195     total = new Hashtable[maxLength];
196
197     for (int i = start; i <= end; i++)
198     {
199       values = new int[255];
200
201       for (j = 0; j < jSize; j++)
202       {
203         if (sequences[j].getLength() > i)
204         {
205           c = sequences[j].getCharAt(i);
206
207           if (canonicaliseAa)
208           { // lookup the base aa code symbol
209             c = (char) ResidueProperties.aaIndex[sequences[j].getCharAt(i)];
210             if (c > 20)
211             {
212               c = '-';
213             }
214             else
215             {
216               // recover canonical aa symbol
217               c = ResidueProperties.aa[c].charAt(0);
218             }
219           }
220           else
221           {
222             // original behaviour - operate on ascii symbols directly
223             // No need to check if its a '-'
224             if (c == '.' || c == ' ')
225             {
226               c = '-';
227             }
228
229             c = toUpperCase(c);
230           }
231           values[c]++;
232         }
233         else
234         {
235           values['-']++;
236         }
237       }
238
239       // What is the count threshold to count the residues in residueHash()
240       thresh = (threshold * (jSize)) / 100;
241
242       // loop over all the found residues
243       Hashtable<String, Integer> resultHash = new Hashtable<String, Integer>();
244       for (char v = '-'; v < 'Z'; v++)
245       {
246
247         if (values[v] > thresh)
248         {
249           String res = String.valueOf(v);
250
251           // Now loop over the properties
252           for (String type : ResidueProperties.propHash.keySet())
253           {
254             Map<String, Integer> ht = ResidueProperties.propHash.get(type);
255
256             // Have we ticked this before?
257             if (!resultHash.containsKey(type))
258             {
259               if (ht.containsKey(res))
260               {
261                 resultHash.put(type, ht.get(res));
262               }
263               else
264               {
265                 resultHash.put(type, ht.get("-"));
266               }
267             }
268             else if (!resultHash.get(type).equals(ht.get(res)))
269             {
270               resultHash.put(type, new Integer(-1));
271             }
272           }
273         }
274       }
275
276       if (total.length > 0)
277       {
278         total[i - start] = resultHash;
279       }
280     }
281   }
282
283   /*****************************************************************************
284    * count conservation for the j'th column of the alignment
285    * 
286    * @return { gap count, conserved residue count}
287    */
288   public int[] countConsNGaps(int j)
289   {
290     int count = 0;
291     int cons = 0;
292     int nres = 0;
293     int[] r = new int[2];
294     char f = '$';
295     int i, iSize = sequences.length;
296     char c;
297
298     for (i = 0; i < iSize; i++)
299     {
300       if (j >= sequences[i].getLength())
301       {
302         count++;
303         continue;
304       }
305
306       c = sequences[i].getCharAt(j); // gaps do not have upper/lower case
307
308       if (jalview.util.Comparison.isGap((c)))
309       {
310         count++;
311       }
312       else
313       {
314         c = toUpperCase(c);
315         nres++;
316
317         if (nres == 1)
318         {
319           f = c;
320           cons++;
321         }
322         else if (f == c)
323         {
324           cons++;
325         }
326       }
327     }
328
329     r[0] = (nres == cons) ? 1 : 0;
330     r[1] = count;
331
332     return r;
333   }
334
335   /**
336    * Returns the upper-cased character if between 'a' and 'z', else the
337    * unchanged value
338    * 
339    * @param c
340    * @return
341    */
342   char toUpperCase(char c)
343   {
344     if ('a' <= c && c <= 'z')
345     {
346       c -= (32); // 32 = 'a' - 'A'
347     }
348     return c;
349   }
350
351   /**
352    * Calculates the conservation sequence
353    * 
354    * @param consflag
355    *          if true, positive conservation; false calculates negative
356    *          conservation
357    * @param percentageGaps
358    *          commonly used value is 25
359    */
360   public void verdict(boolean consflag, float percentageGaps)
361   {
362     StringBuffer consString = new StringBuffer();
363
364     // NOTE THIS SHOULD CHECK IF THE CONSEQUENCE ALREADY
365     // EXISTS AND NOT OVERWRITE WITH '-', BUT THIS CASE
366     // DOES NOT EXIST IN JALVIEW 2.1.2
367     for (int i = 0; i < start; i++)
368     {
369       consString.append('-');
370     }
371     consSymbs = new String[end - start + 1];
372     for (int i = start; i <= end; i++)
373     {
374       int[] gapcons = countConsNGaps(i);
375       int totGaps = gapcons[1];
376       float pgaps = ((float) totGaps * 100) / sequences.length;
377       consSymbs[i - start] = new String();
378
379       if (percentageGaps > pgaps)
380       {
381         Map<String, Integer> resultHash = total[i - start];
382         // Now find the verdict
383         int count = 0;
384         for (String type : resultHash.keySet())
385         {
386           int result = resultHash.get(type).intValue();
387           // Do we want to count +ve conservation or +ve and -ve cons.?
388           if (consflag)
389           {
390             if (result == 1)
391             {
392               consSymbs[i - start] = type + " " + consSymbs[i - start];
393               count++;
394             }
395           }
396           else
397           {
398             if (result != -1)
399             {
400               if (result == 0)
401               {
402                 consSymbs[i - start] = consSymbs[i - start] + " !" + type;
403               }
404               else
405               {
406                 consSymbs[i - start] = type + " " + consSymbs[i - start];
407               }
408               count++;
409             }
410           }
411         }
412
413         if (count < 10)
414         {
415           consString.append(count); // Conserved props!=Identity
416         }
417         else
418         {
419           consString.append((gapcons[0] == 1) ? "*" : "+");
420         }
421       }
422       else
423       {
424         consString.append('-');
425       }
426     }
427
428     consSequence = new Sequence(name, consString.toString(), start, end);
429   }
430
431   /**
432    * 
433    * 
434    * @return Conservation sequence
435    */
436   public Sequence getConsSequence()
437   {
438     return consSequence;
439   }
440
441   // From Alignment.java in jalview118
442   public void findQuality()
443   {
444     findQuality(0, maxLength - 1);
445   }
446
447   /**
448    * DOCUMENT ME!
449    */
450   private void percentIdentity2()
451   {
452     seqNums = new Vector<int[]>();
453     // calcSeqNum(s);
454     int i = 0, iSize = sequences.length;
455     // Do we need to calculate this again?
456     for (i = 0; i < iSize; i++)
457     {
458       calcSeqNum(i);
459     }
460
461     if ((cons2 == null) || seqNumsChanged)
462     {
463       cons2 = new int[maxLength][24];
464
465       // Initialize the array
466       for (int j = 0; j < 24; j++)
467       {
468         for (i = 0; i < maxLength; i++)
469         {
470           cons2[i][j] = 0;
471         }
472       }
473
474       int[] sqnum;
475       int j = 0;
476
477       while (j < sequences.length)
478       {
479         sqnum = seqNums.elementAt(j);
480
481         for (i = 1; i < sqnum.length; i++)
482         {
483           cons2[i - 1][sqnum[i]]++;
484         }
485
486         for (i = sqnum.length - 1; i < maxLength; i++)
487         {
488           cons2[i][23]++; // gap count
489         }
490
491         j++;
492       }
493
494       // unnecessary ?
495
496       /*
497        * for (int i=start; i <= end; i++) { int max = -1000; int maxi = -1; int
498        * maxj = -1;
499        * 
500        * for (int j=0;j<24;j++) { if (cons2[i][j] > max) { max = cons2[i][j];
501        * maxi = i; maxj = j; } } }
502        */
503     }
504   }
505
506   /**
507    * Calculates the quality of the set of sequences
508    * 
509    * @param startRes
510    *          Start residue
511    * @param endRes
512    *          End residue
513    */
514   public void findQuality(int startRes, int endRes)
515   {
516     quality = new Vector<Double>();
517
518     double max = -10000;
519     int[][] BLOSUM62 = ResidueProperties.getBLOSUM62();
520
521     // Loop over columns // JBPNote Profiling info
522     // long ts = System.currentTimeMillis();
523     // long te = System.currentTimeMillis();
524     percentIdentity2();
525
526     int size = seqNums.size();
527     int[] lengths = new int[size];
528     double tot, bigtot, sr, tmp;
529     double[] x, xx;
530     int l, j, i, ii, i2, k, seqNum;
531
532     for (l = 0; l < size; l++)
533     {
534       lengths[l] = seqNums.elementAt(l).length - 1;
535     }
536
537     for (j = startRes; j <= endRes; j++)
538     {
539       bigtot = 0;
540
541       // First Xr = depends on column only
542       x = new double[24];
543
544       for (ii = 0; ii < 24; ii++)
545       {
546         x[ii] = 0;
547
548         for (i2 = 0; i2 < 24; i2++)
549         {
550           x[ii] += (((double) cons2[j][i2] * BLOSUM62[ii][i2]) + 4);
551         }
552
553         x[ii] /= size;
554       }
555
556       // Now calculate D for each position and sum
557       for (k = 0; k < size; k++)
558       {
559         tot = 0;
560         xx = new double[24];
561         seqNum = (j < lengths[k]) ? seqNums.elementAt(k)[j + 1] : 23; // Sequence,
562                                                                       // or gap
563                                                                       // at the
564                                                                       // end
565
566         // This is a loop over r
567         for (i = 0; i < 23; i++)
568         {
569           sr = 0;
570
571           sr = (double) BLOSUM62[i][seqNum] + 4;
572
573           // Calculate X with another loop over residues
574           // System.out.println("Xi " + i + " " + x[i] + " " + sr);
575           xx[i] = x[i] - sr;
576
577           tot += (xx[i] * xx[i]);
578         }
579
580         bigtot += Math.sqrt(tot);
581       }
582
583       // This is the quality for one column
584       if (max < bigtot)
585       {
586         max = bigtot;
587       }
588
589       // bigtot = bigtot * (size-cons2[j][23])/size;
590       quality.addElement(new Double(bigtot));
591
592       // Need to normalize by gaps
593     }
594
595     double newmax = -10000;
596
597     for (j = startRes; j <= endRes; j++)
598     {
599       tmp = quality.elementAt(j).doubleValue();
600       tmp = ((max - tmp) * (size - cons2[j][23])) / size;
601
602       // System.out.println(tmp+ " " + j);
603       quality.setElementAt(new Double(tmp), j);
604
605       if (tmp > newmax)
606       {
607         newmax = tmp;
608       }
609     }
610
611     // System.out.println("Quality " + s);
612     qualityRange[0] = 0D;
613     qualityRange[1] = newmax;
614   }
615
616   /**
617    * Complete the given consensus and quuality annotation rows. Note: currently
618    * this method will enlarge the given annotation row if it is too small,
619    * otherwise will leave its length unchanged.
620    * 
621    * @param conservation
622    *          conservation annotation row
623    * @param quality2
624    *          (optional - may be null)
625    * @param istart
626    *          first column for conservation
627    * @param alWidth
628    *          extent of conservation
629    */
630   public void completeAnnotations(AlignmentAnnotation conservation,
631           AlignmentAnnotation quality2, int istart, int alWidth)
632   {
633     char[] sequence = getConsSequence().getSequence();
634     float minR;
635     float minG;
636     float minB;
637     float maxR;
638     float maxG;
639     float maxB;
640     minR = 0.3f;
641     minG = 0.0f;
642     minB = 0f;
643     maxR = 1.0f - minR;
644     maxG = 0.9f - minG;
645     maxB = 0f - minB; // scalable range for colouring both Conservation and
646     // Quality
647
648     float min = 0f;
649     float max = 11f;
650     float qmin = 0f;
651     float qmax = 0f;
652
653     char c;
654
655     if (conservation != null && conservation.annotations != null
656             && conservation.annotations.length < alWidth)
657     {
658       conservation.annotations = new Annotation[alWidth];
659     }
660
661     if (quality2 != null)
662     {
663       quality2.graphMax = (float) qualityRange[1];
664       if (quality2.annotations != null
665               && quality2.annotations.length < alWidth)
666       {
667         quality2.annotations = new Annotation[alWidth];
668       }
669       qmin = (float) qualityRange[0];
670       qmax = (float) qualityRange[1];
671     }
672
673     for (int i = istart; i < alWidth; i++)
674     {
675       float value = 0;
676
677       c = sequence[i];
678
679       if (Character.isDigit(c))
680       {
681         value = c - '0';
682       }
683       else if (c == '*')
684       {
685         value = 11;
686       }
687       else if (c == '+')
688       {
689         value = 10;
690       }
691
692       if (conservation != null)
693       {
694         float vprop = value - min;
695         vprop /= max;
696         int consp = i - start;
697         String conssym = (value > 0 && consp > -1 && consp < consSymbs.length) ? consSymbs[consp]
698                 : "";
699         conservation.annotations[i] = new Annotation(String.valueOf(c),
700                 conssym, ' ', value, new Color(minR + (maxR * vprop), minG
701                         + (maxG * vprop), minB + (maxB * vprop)));
702       }
703
704       // Quality calc
705       if (quality2 != null)
706       {
707         value = quality.elementAt(i).floatValue();
708         float vprop = value - qmin;
709         vprop /= qmax;
710         quality2.annotations[i] = new Annotation(" ",
711                 String.valueOf(value), ' ', value, new Color(minR
712                         + (maxR * vprop), minG + (maxG * vprop), minB
713                         + (maxB * vprop)));
714       }
715     }
716   }
717
718   /**
719    * construct and call the calculation methods on a new Conservation object
720    * 
721    * @param name
722    *          - name of conservation
723    * @param threshold
724    *          - minimum number of conserved residues needed to indicate
725    *          conservation (typically 3)
726    * @param seqs
727    * @param start
728    *          first column in calculation window
729    * @param end
730    *          last column in calculation window
731    * @param posOrNeg
732    *          positive (true) or negative (false) conservation
733    * @param consPercGaps
734    *          percentage of gaps tolerated in column
735    * @param calcQuality
736    *          flag indicating if alignment quality should be calculated
737    * @return Conservation object ready for use in visualization
738    */
739   public static Conservation calculateConservation(String name,
740           int threshold, List<SequenceI> seqs, int start, int end,
741           boolean posOrNeg, int consPercGaps, boolean calcQuality)
742   {
743     Conservation cons = new Conservation(name, threshold, seqs, start, end);
744     cons.calculate();
745     cons.verdict(posOrNeg, consPercGaps);
746
747     if (calcQuality)
748     {
749       cons.findQuality();
750     }
751
752     return cons;
753   }
754 }