c3191e83c4d76d97d4439c881ec7ab84b0183524
[jalview.git] / test / jalview / analysis / AlignmentAnnotationUtilsTest.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 static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertFalse;
25 import static org.testng.AssertJUnit.assertTrue;
26
27 import jalview.datamodel.AlignmentAnnotation;
28 import jalview.datamodel.AlignmentI;
29 import jalview.datamodel.Annotation;
30 import jalview.datamodel.SequenceI;
31 import jalview.io.AppletFormatAdapter;
32
33 import java.io.IOException;
34 import java.util.ArrayList;
35 import java.util.BitSet;
36 import java.util.Collection;
37 import java.util.HashMap;
38 import java.util.List;
39 import java.util.Map;
40
41 import org.testng.annotations.BeforeMethod;
42 import org.testng.annotations.Test;
43
44 public class AlignmentAnnotationUtilsTest
45 {
46   // 4 sequences x 13 positions
47   final static String EOL = "\n";
48
49   // @formatter:off
50   final static String TEST_DATA = 
51           ">FER_CAPAA Ferredoxin" + EOL +
52           "TIETHKEAELVG-" + EOL +
53           ">FER_CAPAN Ferredoxin, chloroplast precursor" + EOL +
54           "TIETHKEAELVG-" + EOL +
55           ">FER1_SOLLC Ferredoxin-1, chloroplast precursor" + EOL +
56           "TIETHKEEELTA-" + EOL + 
57           ">Q93XJ9_SOLTU Ferredoxin I precursor" + EOL +
58           "TIETHKEEELTA-" + EOL;
59   // @formatter:on
60
61   private static final int SEQ_ANN_COUNT = 12;
62
63   private AlignmentI alignment;
64
65   /**
66    * Test method that converts a (possibly null) array to a list.
67    */
68   @Test(groups = { "Functional" })
69   public void testAsList()
70   {
71     // null array
72     Collection<AlignmentAnnotation> c1 = AlignmentAnnotationUtils
73             .asList(null);
74     assertTrue(c1.isEmpty());
75
76     // empty array
77     AlignmentAnnotation[] anns = new AlignmentAnnotation[0];
78     c1 = AlignmentAnnotationUtils.asList(anns);
79     assertTrue(c1.isEmpty());
80
81     // non-empty array
82     anns = new AlignmentAnnotation[2];
83     anns[0] = new AlignmentAnnotation("label0", "desc0", 0.0f);
84     anns[1] = new AlignmentAnnotation("label1", "desc1", 1.0f);
85     c1 = AlignmentAnnotationUtils.asList(anns);
86     assertEquals(2, c1.size());
87     assertTrue(c1.contains(anns[0]));
88     assertTrue(c1.contains(anns[1]));
89   }
90
91   /**
92    * This output is not part of the test but may help make sense of it...
93    * 
94    * @param shownTypes
95    * @param hiddenTypes
96    */
97   protected void consoleDebug(Map<String, List<List<String>>> shownTypes,
98           Map<String, List<List<String>>> hiddenTypes)
99   {
100     for (String calcId : shownTypes.keySet())
101     {
102       System.out.println("Visible annotation types for calcId=" + calcId);
103       for (List<String> type : shownTypes.get(calcId))
104       {
105         System.out.println("   " + type);
106       }
107     }
108     for (String calcId : hiddenTypes.keySet())
109     {
110       System.out.println("Hidden annotation types for calcId=" + calcId);
111       for (List<String> type : hiddenTypes.get(calcId))
112       {
113         System.out.println("   " + type);
114       }
115     }
116   }
117
118   /**
119    * Add a sequence group to the alignment with the specified sequences (base 0)
120    * in it
121    * 
122    * @param i
123    * @param more
124    */
125   private List<SequenceI> selectSequences(int... selected)
126   {
127     List<SequenceI> result = new ArrayList<SequenceI>();
128     SequenceI[] seqs = alignment.getSequencesArray();
129     for (int i : selected)
130     {
131       result.add(seqs[i]);
132     }
133     return result;
134   }
135
136   /**
137    * Load the test alignment and generate annotations on it
138    * 
139    * @throws IOException
140    */
141   @BeforeMethod(alwaysRun = true)
142   public void setUp() throws IOException
143   {
144     alignment = new jalview.io.FormatAdapter().readFile(TEST_DATA,
145             AppletFormatAdapter.PASTE, "FASTA");
146
147     AlignmentAnnotation[] anns = new AlignmentAnnotation[SEQ_ANN_COUNT];
148     for (int i = 0; i < anns.length; i++)
149     {
150       /*
151        * Use the constructor for a positional annotation (with an Annotation
152        * array)
153        */
154       anns[i] = new AlignmentAnnotation("Label" + i, "Desc " + i,
155               new Annotation[] {});
156       anns[i].setCalcId("CalcId" + i);
157       anns[i].visible = true;
158       alignment.addAnnotation(anns[i]);
159     }
160   }
161
162   /**
163    * Test a mixture of show/hidden annotations in/outside selection group.
164    */
165   @Test(groups = { "Functional" })
166   public void testGetShownHiddenTypes_forSelectionGroup()
167   {
168     Map<String, List<List<String>>> shownTypes = new HashMap<String, List<List<String>>>();
169     Map<String, List<List<String>>> hiddenTypes = new HashMap<String, List<List<String>>>();
170     AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
171     SequenceI[] seqs = alignment.getSequencesArray();
172
173     /*
174      * Configure annotation properties for test
175      */
176     // not in selection group (should be ignored):
177     // hidden annotation Label4 not in selection group
178     anns[4].sequenceRef = seqs[2];
179     anns[4].visible = false;
180     anns[7].sequenceRef = seqs[1];
181     anns[7].visible = true;
182
183     /*
184      * in selection group, hidden:
185      */
186     anns[2].sequenceRef = seqs[3]; // CalcId2/Label2
187     anns[2].visible = false;
188     anns[3].sequenceRef = seqs[3]; // CalcId3/Label2
189     anns[3].visible = false;
190     anns[3].label = "Label2";
191     anns[4].sequenceRef = seqs[3]; // CalcId2/Label3
192     anns[4].visible = false;
193     anns[4].label = "Label3";
194     anns[4].setCalcId("CalcId2");
195     anns[8].sequenceRef = seqs[0]; // CalcId9/Label9
196     anns[8].visible = false;
197     anns[8].label = "Label9";
198     anns[8].setCalcId("CalcId9");
199     /*
200      * in selection group, visible
201      */
202     anns[6].sequenceRef = seqs[0]; // CalcId6/Label6
203     anns[6].visible = true;
204     anns[9].sequenceRef = seqs[3]; // CalcId9/Label9
205     anns[9].visible = true;
206
207     List<SequenceI> selected = selectSequences(0, 3);
208     AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
209             AlignmentAnnotationUtils.asList(anns), selected);
210
211     // check results; note CalcId9/Label9 is both hidden and shown (for
212     // different sequences) so should be in both
213     // shown: CalcId6/Label6 and CalcId9/Label9
214     assertEquals(2, shownTypes.size());
215     assertEquals(1, shownTypes.get("CalcId6").size());
216     assertEquals(1, shownTypes.get("CalcId6").get(0).size());
217     assertEquals("Label6", shownTypes.get("CalcId6").get(0).get(0));
218     assertEquals(1, shownTypes.get("CalcId9").size());
219     assertEquals(1, shownTypes.get("CalcId9").get(0).size());
220     assertEquals("Label9", shownTypes.get("CalcId9").get(0).get(0));
221
222     // hidden: CalcId2/Label2, CalcId2/Label3, CalcId3/Label2, CalcId9/Label9
223     assertEquals(3, hiddenTypes.size());
224     assertEquals(2, hiddenTypes.get("CalcId2").size());
225     assertEquals(1, hiddenTypes.get("CalcId2").get(0).size());
226     assertEquals("Label2", hiddenTypes.get("CalcId2").get(0).get(0));
227     assertEquals(1, hiddenTypes.get("CalcId2").get(1).size());
228     assertEquals("Label3", hiddenTypes.get("CalcId2").get(1).get(0));
229     assertEquals(1, hiddenTypes.get("CalcId3").size());
230     assertEquals(1, hiddenTypes.get("CalcId3").get(0).size());
231     assertEquals("Label2", hiddenTypes.get("CalcId3").get(0).get(0));
232     assertEquals(1, hiddenTypes.get("CalcId9").size());
233     assertEquals(1, hiddenTypes.get("CalcId9").get(0).size());
234     assertEquals("Label9", hiddenTypes.get("CalcId9").get(0).get(0));
235
236     consoleDebug(shownTypes, hiddenTypes);
237   }
238
239   /**
240    * Test case where there are 'grouped' annotations, visible and hidden, within
241    * and without the selection group.
242    */
243   @Test(groups = { "Functional" })
244   public void testGetShownHiddenTypes_withGraphGroups()
245   {
246     final int GROUP_3 = 3;
247     final int GROUP_4 = 4;
248     final int GROUP_5 = 5;
249     final int GROUP_6 = 6;
250
251     Map<String, List<List<String>>> shownTypes = new HashMap<String, List<List<String>>>();
252     Map<String, List<List<String>>> hiddenTypes = new HashMap<String, List<List<String>>>();
253     AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
254     SequenceI[] seqs = alignment.getSequencesArray();
255
256     /*
257      * Annotations for selection group and graph group
258      * 
259      * Hidden annotations Label2, Label3, in (hidden) group 5
260      */
261     anns[2].sequenceRef = seqs[3];
262     anns[2].visible = false;
263     anns[2].graph = AlignmentAnnotation.LINE_GRAPH;
264     anns[2].graphGroup = GROUP_5; // not a visible group
265     anns[3].sequenceRef = seqs[0];
266     anns[3].visible = false;
267     anns[3].graph = AlignmentAnnotation.LINE_GRAPH;
268     anns[3].graphGroup = GROUP_5;
269     // need to ensure annotations have the same calcId as well
270     anns[3].setCalcId("CalcId2");
271     // annotations for a different hidden group generating the same group label
272     anns[10].sequenceRef = seqs[0];
273     anns[10].visible = false;
274     anns[10].graph = AlignmentAnnotation.LINE_GRAPH;
275     anns[10].graphGroup = GROUP_3;
276     anns[10].label = "Label3";
277     anns[10].setCalcId("CalcId2");
278     anns[11].sequenceRef = seqs[3];
279     anns[11].visible = false;
280     anns[11].graph = AlignmentAnnotation.LINE_GRAPH;
281     anns[11].graphGroup = GROUP_3;
282     anns[11].label = "Label2";
283     anns[11].setCalcId("CalcId2");
284
285     // annotations Label1 (hidden), Label5 (visible) in group 6 (visible)
286     anns[1].sequenceRef = seqs[3];
287     // being in a visible group should take precedence over this visibility
288     anns[1].visible = false;
289     anns[1].graph = AlignmentAnnotation.LINE_GRAPH;
290     anns[1].graphGroup = GROUP_6;
291     anns[5].sequenceRef = seqs[0];
292     anns[5].visible = true;
293     anns[5].graph = AlignmentAnnotation.LINE_GRAPH;
294     anns[5].graphGroup = GROUP_6;
295     anns[5].setCalcId("CalcId1");
296     /*
297      * Annotations 0 and 4 are visible, for a different CalcId and graph group.
298      * They produce the same label as annotations 1 and 5, which should not be
299      * duplicated in the results. This case corresponds to (e.g.) many
300      * occurrences of an IUPred Short/Long annotation group, one per sequence.
301      */
302     anns[4].sequenceRef = seqs[0];
303     anns[4].visible = false;
304     anns[4].graph = AlignmentAnnotation.LINE_GRAPH;
305     anns[4].graphGroup = GROUP_4;
306     anns[4].label = "Label1";
307     anns[4].setCalcId("CalcId1");
308     anns[0].sequenceRef = seqs[0];
309     anns[0].visible = true;
310     anns[0].graph = AlignmentAnnotation.LINE_GRAPH;
311     anns[0].graphGroup = GROUP_4;
312     anns[0].label = "Label5";
313     anns[0].setCalcId("CalcId1");
314
315     /*
316      * Annotations outwith selection group - should be ignored.
317      */
318     // Hidden grouped annotations
319     anns[6].sequenceRef = seqs[2];
320     anns[6].visible = false;
321     anns[6].graph = AlignmentAnnotation.LINE_GRAPH;
322     anns[6].graphGroup = GROUP_4;
323     anns[8].sequenceRef = seqs[1];
324     anns[8].visible = false;
325     anns[8].graph = AlignmentAnnotation.LINE_GRAPH;
326     anns[8].graphGroup = GROUP_4;
327
328     // visible grouped annotations Label7, Label9
329     anns[7].sequenceRef = seqs[2];
330     anns[7].visible = true;
331     anns[7].graph = AlignmentAnnotation.LINE_GRAPH;
332     anns[7].graphGroup = GROUP_4;
333     anns[9].sequenceRef = seqs[1];
334     anns[9].visible = true;
335     anns[9].graph = AlignmentAnnotation.LINE_GRAPH;
336     anns[9].graphGroup = GROUP_4;
337
338     /*
339      * Generate annotations[] arrays to match aligned columns
340      */
341     // adjustForAlignment(anns);
342
343     List<SequenceI> selected = selectSequences(0, 3);
344     AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
345             AlignmentAnnotationUtils.asList(anns), selected);
346
347     consoleDebug(shownTypes, hiddenTypes);
348
349     // CalcId1 / Label1, Label5 (only) should be 'shown', once, as a compound
350     // type
351     assertEquals(1, shownTypes.size());
352     assertEquals(1, shownTypes.get("CalcId1").size());
353     assertEquals(2, shownTypes.get("CalcId1").get(0).size());
354     assertEquals("Label1", shownTypes.get("CalcId1").get(0).get(0));
355     assertEquals("Label5", shownTypes.get("CalcId1").get(0).get(1));
356
357     // CalcId2 / Label2, Label3 (only) should be 'hidden'
358     assertEquals(1, hiddenTypes.size());
359     assertEquals(1, hiddenTypes.get("CalcId2").size());
360     assertEquals(2, hiddenTypes.get("CalcId2").get(0).size());
361     assertEquals("Label2", hiddenTypes.get("CalcId2").get(0).get(0));
362     assertEquals("Label3", hiddenTypes.get("CalcId2").get(0).get(1));
363   }
364
365   /**
366    * Test method that determines visible graph groups.
367    */
368   @Test(groups = { "Functional" })
369   public void testGetVisibleGraphGroups()
370   {
371     AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
372     /*
373      * a bar graph group is not included
374      */
375     anns[0].graph = AlignmentAnnotation.BAR_GRAPH;
376     anns[0].graphGroup = 1;
377     anns[0].visible = true;
378
379     /*
380      * a line graph group is included as long as one of its members is visible
381      */
382     anns[1].graph = AlignmentAnnotation.LINE_GRAPH;
383     anns[1].graphGroup = 5;
384     anns[1].visible = false;
385     anns[2].graph = AlignmentAnnotation.LINE_GRAPH;
386     anns[2].graphGroup = 5;
387     anns[2].visible = true;
388
389     /*
390      * a line graph group with no visible rows is not included
391      */
392     anns[3].graph = AlignmentAnnotation.LINE_GRAPH;
393     anns[3].graphGroup = 3;
394     anns[3].visible = false;
395
396     // a visible line graph with no graph group is not included
397     anns[4].graph = AlignmentAnnotation.LINE_GRAPH;
398     anns[4].graphGroup = -1;
399     anns[4].visible = true;
400
401     BitSet result = AlignmentAnnotationUtils
402             .getVisibleLineGraphGroups(AlignmentAnnotationUtils
403                     .asList(anns));
404     assertTrue(result.get(5));
405     assertFalse(result.get(0));
406     assertFalse(result.get(1));
407     assertFalse(result.get(2));
408     assertFalse(result.get(3));
409   }
410
411   /**
412    * Test for case where no sequence is selected. Shouldn't normally arise but
413    * check it handles it gracefully.
414    */
415   @Test(groups = { "Functional" })
416   public void testGetShownHiddenTypes_noSequenceSelected()
417   {
418     Map<String, List<List<String>>> shownTypes = new HashMap<String, List<List<String>>>();
419     Map<String, List<List<String>>> hiddenTypes = new HashMap<String, List<List<String>>>();
420     AlignmentAnnotation[] anns = alignment.getAlignmentAnnotation();
421     // selected sequences null
422     AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
423             AlignmentAnnotationUtils.asList(anns), null);
424     assertTrue(shownTypes.isEmpty());
425     assertTrue(hiddenTypes.isEmpty());
426
427     List<SequenceI> sequences = new ArrayList<SequenceI>();
428     // selected sequences empty list
429     AlignmentAnnotationUtils.getShownHiddenTypes(shownTypes, hiddenTypes,
430             AlignmentAnnotationUtils.asList(anns), sequences);
431     assertTrue(shownTypes.isEmpty());
432     assertTrue(hiddenTypes.isEmpty());
433   }
434 }