AlignmentAnnotation: added annotation score attribute and allowed for annotation...
[jalview.git] / src / jalview / datamodel / AlignmentAnnotation.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer
3  * Copyright (C) 2007 AM Waterhouse, J Procter, G Barton, M Clamp, S Searle
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
18  */
19 package jalview.datamodel;
20
21 import java.util.Enumeration;
22 import java.util.Hashtable;
23
24 /**
25  * DOCUMENT ME!
26  *
27  * @author $author$
28  * @version $Revision$
29  */
30 public class AlignmentAnnotation
31 {
32   /** If true, this annotations is calculated every edit,
33    * eg consensus, quality or conservation graphs */
34   public boolean autoCalculated = false;
35
36   public String annotationId;
37
38   public SequenceI sequenceRef;
39
40   /** DOCUMENT ME!! */
41   public String label;
42
43   /** DOCUMENT ME!! */
44   public String description;
45
46   /** DOCUMENT ME!! */
47   public Annotation[] annotations;
48
49   public java.util.Hashtable sequenceMapping;
50
51   /** DOCUMENT ME!! */
52   public float graphMin;
53
54   /** DOCUMENT ME!! */
55   public float graphMax;
56
57   /**
58    * Score associated with label and description.
59    */
60   public float score;
61   /**
62    * flag indicating if annotation has a score.
63    */
64   public boolean hasScore=false;
65   
66   public GraphLine threshold;
67
68   // Graphical hints and tips
69
70   /** DOCUMENT ME!! */
71   public boolean editable = false;
72
73   /** DOCUMENT ME!! */
74   public boolean hasIcons; //
75
76   /** DOCUMENT ME!! */
77   public boolean hasText;
78
79   /** DOCUMENT ME!! */
80   public boolean visible = true;
81
82   public int graphGroup = -1;
83
84   /** DOCUMENT ME!! */
85   public int height = 0;
86
87   public int graph = 0;
88
89   public int graphHeight = 40;
90
91   public boolean padGaps = true;
92
93   public static final int NO_GRAPH = 0;
94
95   public static final int BAR_GRAPH = 1;
96
97   public static final int LINE_GRAPH = 2;
98
99
100   public static int getGraphValueFromString(String string)
101   {
102     if (string.equalsIgnoreCase("BAR_GRAPH"))
103     {
104       return BAR_GRAPH;
105     }
106     else if (string.equalsIgnoreCase("LINE_GRAPH"))
107     {
108       return LINE_GRAPH;
109     }
110     else
111     {
112       return NO_GRAPH;
113     }
114   }
115
116   /**
117    * Creates a new AlignmentAnnotation object.
118    *
119    * @param label DOCUMENT ME!
120    * @param description DOCUMENT ME!
121    * @param annotations DOCUMENT ME!
122    */
123   public AlignmentAnnotation(String label, String description,
124                              Annotation[] annotations)
125   {
126     // always editable?
127     editable = true;
128     this.label = label;
129     this.description = description;
130     this.annotations = annotations;
131
132      validateRangeAndDisplay();
133   }
134
135   void areLabelsSecondaryStructure()
136   {
137     boolean nonSSLabel = false;
138     for (int i = 0; i < annotations.length; i++)
139     {
140       if (annotations[i] == null)
141       {
142         padGaps = false;
143         continue;
144       }
145       if (annotations[i].secondaryStructure == 'H' ||
146           annotations[i].secondaryStructure == 'E')
147       {
148           hasIcons = true;
149       }
150
151       if(annotations[i].displayCharacter==null)
152         continue;
153
154
155       if (annotations[i].displayCharacter.length() == 1
156           && !annotations[i].displayCharacter.equals("H")
157           && !annotations[i].displayCharacter.equals("E")
158           && !annotations[i].displayCharacter.equals("-")
159           && !annotations[i].displayCharacter.equals("."))
160         {
161           if (jalview.schemes.ResidueProperties.aaIndex
162                   [annotations[i].displayCharacter.charAt(0)] < 23)
163           {
164             nonSSLabel = true;
165           }
166         }
167
168         if (annotations[i].displayCharacter.length() > 0)
169         {
170           hasText = true;
171         }
172         else
173           padGaps = false;
174       }
175
176     if (nonSSLabel)
177     {
178       hasIcons = false;
179       for (int j = 0; j < annotations.length; j++)
180       {
181         if (annotations[j] != null && annotations[j].secondaryStructure != ' ')
182         {
183           annotations[j].displayCharacter
184               = String.valueOf(annotations[j].secondaryStructure);
185           annotations[j].secondaryStructure = ' ';
186         }
187
188       }
189     }
190
191     annotationId = this.hashCode() + "";
192   }
193   /**
194    * Creates a new AlignmentAnnotation object.
195    *
196    * @param label DOCUMENT ME!
197    * @param description DOCUMENT ME!
198    * @param annotations DOCUMENT ME!
199    * @param min DOCUMENT ME!
200    * @param max DOCUMENT ME!
201    * @param winLength DOCUMENT ME!
202    */
203   public AlignmentAnnotation(String label, String description,
204                              Annotation[] annotations, float min, float max,
205                              int graphType)
206   {
207     // graphs are not editable
208     editable = graphType==0;
209
210     this.label = label;
211     this.description = description;
212     this.annotations = annotations;
213     graph = graphType;
214     graphMin = min;
215     graphMax = max;
216     validateRangeAndDisplay();
217   }
218   /**
219    * checks graphMin and graphMax,
220    * secondary structure symbols,
221    * sets graphType appropriately,
222    * sets null labels to the empty string
223    * if appropriate.
224    */
225   private void validateRangeAndDisplay() {
226     if (annotations==null)
227     {
228       visible=false; // try to prevent renderer from displaying.
229       return; // this is a non-annotation row annotation - ie a sequence score.
230     }
231     int graphType = graph;
232     float min = graphMin;
233     float max = graphMax;
234     boolean drawValues = true;
235
236     if (min == max)
237     {
238       min = 999999999;
239       for (int i = 0; i < annotations.length; i++)
240       {
241         if (annotations[i] == null)
242         {
243           continue;
244         }
245
246         if (drawValues
247             && annotations[i].displayCharacter!=null
248             && annotations[i].displayCharacter.length() > 1)
249         {
250           drawValues = false;
251         }
252
253         if (annotations[i].value > max)
254         {
255           max = annotations[i].value;
256         }
257
258         if (annotations[i].value < min)
259         {
260           min = annotations[i].value;
261         }
262       }
263     }
264
265     graphMin = min;
266     graphMax = max;
267
268     areLabelsSecondaryStructure();
269
270     if (!drawValues && graphType != NO_GRAPH)
271     {
272       for (int i = 0; i < annotations.length; i++)
273       {
274         if (annotations[i] != null)
275         {
276           annotations[i].displayCharacter = "";
277         }
278       }
279     }
280   }
281
282   /**
283    * Copy constructor
284    * creates a new independent annotation row with the same associated sequenceRef
285    * @param annotation
286    */
287   public AlignmentAnnotation(AlignmentAnnotation annotation)
288   {
289     this.label = new String(annotation.label);
290     if (annotation.description != null)
291       this.description = new String(annotation.description);
292     this.graphMin = annotation.graphMin;
293     this.graphMax = annotation.graphMax;
294     this.graph = annotation.graph;
295     this.graphHeight = annotation.graphHeight;
296     this.graphGroup = annotation.graphGroup;
297     this.editable = annotation.editable;
298     this.autoCalculated = annotation.autoCalculated;
299     this.hasIcons = annotation.hasIcons;
300     this.hasText = annotation.hasText;
301     this.height = annotation.height;
302     this.label = annotation.label;
303     this.padGaps = annotation.padGaps;
304     if (threshold!=null) {
305       threshold = new GraphLine(annotation.threshold);
306     }
307     if (annotation.annotations!=null) {
308       Annotation[] ann = annotation.annotations;
309       this.annotations = new Annotation[ann.length];
310       for (int i=0; i<ann.length; i++) {
311         annotations[i] = new Annotation(ann[i]);
312       };
313       if (annotation.sequenceRef!=null) {
314         this.sequenceRef = annotation.sequenceRef;
315         if (annotation.sequenceMapping!=null)
316         {
317           Integer p=null;
318           sequenceMapping = new Hashtable();
319           Enumeration pos=annotation.sequenceMapping.keys();
320           while (pos.hasMoreElements()) {
321             // could optimise this!
322             p = (Integer) pos.nextElement();
323             Annotation a = (Annotation) annotation.sequenceMapping.get(p);
324             if (a==null)
325             {
326               continue;
327             }
328             for (int i=0; i<ann.length; i++)
329             {
330               if (ann[i]==a)
331               {
332                 sequenceMapping.put(p, annotations[i]);
333               }
334             }
335           }
336         } else {
337           this.sequenceMapping = null;
338         }
339       }
340     }
341     validateRangeAndDisplay(); // construct hashcodes, etc.
342   }
343
344   /**
345    * clip the annotation to the columns given by startRes and endRes (inclusive)
346    * and prune any existing sequenceMapping to just those columns.
347    * @param startRes
348    * @param endRes
349    */
350   public void restrict(int startRes, int endRes)
351   {
352     Annotation[] temp = new Annotation[endRes-startRes+1];
353     if (startRes<annotations.length)
354     {
355       System.arraycopy(annotations, startRes, temp, 0, Math.min(endRes, annotations.length-1)-startRes+1);
356     }
357     if (sequenceRef!=null) {
358       // Clip the mapping, if it exists.
359       int spos = sequenceRef.findPosition(startRes);
360       int epos = sequenceRef.findPosition(endRes);
361       if (sequenceMapping!=null)
362       {
363         Hashtable newmapping = new Hashtable();
364         Enumeration e = sequenceMapping.keys();
365         while (e.hasMoreElements())
366         {
367           Integer pos = (Integer) e.nextElement();
368           if (pos.intValue()>=spos && pos.intValue()<=epos)
369           {
370             newmapping.put(pos, sequenceMapping.get(pos));
371           }
372         }
373         sequenceMapping.clear();
374         sequenceMapping = newmapping;
375       }
376     }
377     annotations=temp;
378   }
379   /**
380    * set the annotation row to be at least length Annotations
381    * @param length minimum number of columns required in the annotation row
382    * @return false if the annotation row is greater than length
383    */
384   public boolean padAnnotation(int length) {
385     if (annotations==null)
386     {
387       annotations = new Annotation[length];
388       return true;
389     }
390     if (annotations.length<length)
391     {
392       Annotation[] na = new Annotation[length];
393       System.arraycopy(annotations, 0, na, 0, annotations.length);
394       annotations = na;
395       return true;
396     }
397     return annotations.length>length;
398
399   }
400
401   /**
402    * DOCUMENT ME!
403    *
404    * @return DOCUMENT ME!
405    */
406   public String toString()
407   {
408     StringBuffer buffer = new StringBuffer();
409
410     for (int i = 0; i < annotations.length; i++)
411     {
412       if (annotations[i] != null)
413       {
414         if (graph != 0)
415         {
416           buffer.append(annotations[i].value);
417         }
418         else if (hasIcons)
419         {
420           buffer.append(annotations[i].secondaryStructure);
421         }
422         else
423         {
424           buffer.append(annotations[i].displayCharacter);
425         }
426       }
427
428       buffer.append(", ");
429     }
430
431     if (label.equals("Consensus"))
432     {
433       buffer.append("\n");
434
435       for (int i = 0; i < annotations.length; i++)
436       {
437         if (annotations[i] != null)
438         {
439           buffer.append(annotations[i].description);
440         }
441
442         buffer.append(", ");
443       }
444     }
445
446     return buffer.toString();
447   }
448
449   public void setThreshold(GraphLine line)
450   {
451     threshold = line;
452   }
453
454   public GraphLine getThreshold()
455   {
456     return threshold;
457   }
458
459   /**
460    * Attach the annotation to seqRef, starting from startRes position. If alreadyMapped is true then the indices of the annotation[] array are sequence positions rather than alignment column positions.
461    * @param seqRef
462    * @param startRes
463    * @param alreadyMapped
464    */
465   public void createSequenceMapping(SequenceI seqRef,
466                                     int startRes,
467                                     boolean alreadyMapped)
468   {
469
470     if (seqRef == null)
471     {
472       return;
473     }
474     if (annotations==null)
475     {
476       return;
477     }
478     sequenceMapping = new java.util.Hashtable();
479
480     sequenceRef = seqRef;
481     int seqPos;
482
483     for (int i = 0; i < annotations.length; i++)
484     {
485       if (annotations[i] != null)
486       {
487         if (alreadyMapped)
488         {
489           seqPos = seqRef.findPosition(i);
490         }
491         else
492         {
493           seqPos = i + startRes;
494         }
495
496         sequenceMapping.put(new Integer(seqPos), annotations[i]);
497       }
498     }
499
500   }
501
502   public void adjustForAlignment()
503   {
504     if (sequenceRef==null)
505       return;
506
507     if (annotations==null)
508     {
509       return;
510     }
511
512     int a = 0, aSize = sequenceRef.getLength();
513
514     if (aSize == 0)
515     {
516       //Its been deleted
517       return;
518     }
519
520     int position;
521     Annotation[] temp = new Annotation[aSize];
522     Integer index;
523
524     for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++)
525     {
526       index = new Integer(a);
527       if (sequenceMapping.containsKey(index))
528       {
529         position = sequenceRef.findIndex(a) - 1;
530
531         temp[position] = (Annotation) sequenceMapping.get(index);
532       }
533     }
534
535     annotations = temp;
536   }
537   /**
538    * remove any null entries in annotation row and return the
539    * number of non-null annotation elements.
540    * @return
541    */
542   private int compactAnnotationArray() {
543     int j=0;
544     for (int i=0;i<annotations.length; i++) {
545       if (annotations[i]!=null && j!=i) {
546         annotations[j++] = annotations[i];
547       }
548     }
549     Annotation[] ann = annotations;
550     annotations = new Annotation[j];
551     System.arraycopy(ann, 0, annotations, 0, j);
552     ann = null;
553     return j;
554   }
555
556   /**
557    * Associate this annotion with the aligned residues of a particular sequence.
558    * sequenceMapping will be updated in the following way:
559    *   null sequenceI - existing mapping will be discarded but annotations left in mapped positions.
560    *   valid sequenceI not equal to current sequenceRef: mapping is discarded and rebuilt assuming 1:1 correspondence
561    *   TODO: overload with parameter to specify correspondence between current and new sequenceRef
562    * @param sequenceI
563    */
564   public void setSequenceRef(SequenceI sequenceI)
565   {
566     if (sequenceI != null)
567     {
568       if (sequenceRef != null)
569       {
570         if (sequenceRef != sequenceI && !sequenceRef.equals(sequenceI) && sequenceRef.getDatasetSequence()!=sequenceI.getDatasetSequence())
571         {
572           // if sequenceRef isn't intersecting with sequenceI
573           // throw away old mapping and reconstruct.
574           sequenceRef = null;
575           if (sequenceMapping != null)
576           {
577             sequenceMapping = null;
578             // compactAnnotationArray();
579           }
580           createSequenceMapping(sequenceI, 1, true);
581           adjustForAlignment();
582         }
583         else
584         {
585           // Mapping carried over
586           sequenceRef = sequenceI;
587         }
588       }
589       else
590       {
591         // No mapping exists
592         createSequenceMapping(sequenceI, 1, true);
593         adjustForAlignment();
594       }
595     }
596     else
597     {
598       // throw away the mapping without compacting.
599       sequenceMapping = null;
600       sequenceRef = null;
601     }
602   }
603
604   /**
605    * @return the score
606    */
607   public float getScore()
608   {
609     return score;
610   }
611
612   /**
613    * @param score the score to set
614    */
615   public void setScore(float score)
616   {
617     this.score = score;
618   }
619   /**
620    * 
621    * @return true if annotation has an associated score
622    */
623   public boolean hasScore()
624   {
625     return hasScore;
626   }
627 }