Merge branch 'patch/JAL-2976_vaqua4_fallback' into releases/Release_2_10_4_Branch
[jalview.git] / test / jalview / renderer / seqfeatures / FeatureRendererTest.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.renderer.seqfeatures;
22
23 import static org.testng.Assert.assertEquals;
24 import static org.testng.Assert.assertFalse;
25 import static org.testng.Assert.assertTrue;
26
27 import jalview.api.AlignViewportI;
28 import jalview.api.FeatureColourI;
29 import jalview.datamodel.SequenceFeature;
30 import jalview.datamodel.SequenceI;
31 import jalview.gui.AlignFrame;
32 import jalview.io.DataSourceType;
33 import jalview.io.FileLoader;
34 import jalview.schemes.FeatureColour;
35
36 import java.awt.Color;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.List;
40 import java.util.Map;
41
42 import org.testng.annotations.Test;
43
44 public class FeatureRendererTest
45 {
46
47   @Test(groups = "Functional")
48   public void testFindAllFeatures()
49   {
50     String seqData = ">s1\nabcdef\n>s2\nabcdef\n>s3\nabcdef\n>s4\nabcdef\n";
51     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
52             DataSourceType.PASTE);
53     AlignViewportI av = af.getViewport();
54     FeatureRenderer fr = new FeatureRenderer(av);
55
56     /*
57      * with no features
58      */
59     fr.findAllFeatures(true);
60     assertTrue(fr.getRenderOrder().isEmpty());
61     assertTrue(fr.getFeatureGroups().isEmpty());
62
63     List<SequenceI> seqs = av.getAlignment().getSequences();
64
65     // add a non-positional feature - should be ignored by FeatureRenderer
66     SequenceFeature sf1 = new SequenceFeature("Type", "Desc", 0, 0, 1f,
67             "Group");
68     seqs.get(0).addSequenceFeature(sf1);
69     fr.findAllFeatures(true);
70     // ? bug - types and groups added for non-positional features
71     List<String> types = fr.getRenderOrder();
72     List<String> groups = fr.getFeatureGroups();
73     assertEquals(types.size(), 0);
74     assertFalse(types.contains("Type"));
75     assertEquals(groups.size(), 0);
76     assertFalse(groups.contains("Group"));
77
78     // add some positional features
79     seqs.get(1).addSequenceFeature(
80             new SequenceFeature("Pfam", "Desc", 5, 9, 1f, "PfamGroup"));
81     seqs.get(2).addSequenceFeature(
82             new SequenceFeature("Pfam", "Desc", 14, 22, 2f, "RfamGroup"));
83     // bug in findAllFeatures - group not checked for a known feature type
84     seqs.get(2).addSequenceFeature(
85             new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN,
86                     "RfamGroup"));
87     // existing feature type with null group
88     seqs.get(3).addSequenceFeature(
89             new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null));
90     // new feature type with null group
91     seqs.get(3).addSequenceFeature(
92             new SequenceFeature("Scop", "Desc", 5, 9, Float.NaN, null));
93     // null value for type produces NullPointerException
94     fr.findAllFeatures(true);
95     types = fr.getRenderOrder();
96     groups = fr.getFeatureGroups();
97     assertEquals(types.size(), 3);
98     assertFalse(types.contains("Type"));
99     assertTrue(types.contains("Pfam"));
100     assertTrue(types.contains("Rfam"));
101     assertTrue(types.contains("Scop"));
102     assertEquals(groups.size(), 2);
103     assertFalse(groups.contains("Group"));
104     assertTrue(groups.contains("PfamGroup"));
105     assertTrue(groups.contains("RfamGroup"));
106     assertFalse(groups.contains(null)); // null group is ignored
107
108     /*
109      * check min-max values
110      */
111     Map<String, float[][]> minMax = fr.getMinMax();
112     assertEquals(minMax.size(), 1); // non-positional and NaN not stored
113     assertEquals(minMax.get("Pfam")[0][0], 1f); // positional min
114     assertEquals(minMax.get("Pfam")[0][1], 2f); // positional max
115
116     // increase max for Pfam, add scores for Rfam
117     seqs.get(0).addSequenceFeature(
118             new SequenceFeature("Pfam", "Desc", 14, 22, 8f, "RfamGroup"));
119     seqs.get(1).addSequenceFeature(
120             new SequenceFeature("Rfam", "Desc", 5, 9, 6f, "RfamGroup"));
121     fr.findAllFeatures(true);
122     // note minMax is not a defensive copy, shouldn't expose this
123     assertEquals(minMax.size(), 2);
124     assertEquals(minMax.get("Pfam")[0][0], 1f);
125     assertEquals(minMax.get("Pfam")[0][1], 8f);
126     assertEquals(minMax.get("Rfam")[0][0], 6f);
127     assertEquals(minMax.get("Rfam")[0][1], 6f);
128
129     /*
130      * check render order (last is on top)
131      */
132     List<String> renderOrder = fr.getRenderOrder();
133     assertEquals(renderOrder, Arrays.asList("Scop", "Rfam", "Pfam"));
134
135     /*
136      * change render order (todo: an easier way)
137      * nb here last comes first in the data array
138      */
139     Object[][] data = new Object[3][];
140     FeatureColourI colour = new FeatureColour(Color.RED);
141     data[0] = new Object[] { "Rfam", colour, true };
142     data[1] = new Object[] { "Pfam", colour, false };
143     data[2] = new Object[] { "Scop", colour, false };
144     fr.setFeaturePriority(data);
145     assertEquals(fr.getRenderOrder(), Arrays.asList("Scop", "Pfam", "Rfam"));
146     assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam"));
147
148     /*
149      * add a new feature type: should go on top of render order as visible,
150      * other feature ordering and visibility should be unchanged
151      */
152     seqs.get(2).addSequenceFeature(
153             new SequenceFeature("Metal", "Desc", 14, 22, 8f, "MetalGroup"));
154     fr.findAllFeatures(true);
155     assertEquals(fr.getRenderOrder(),
156             Arrays.asList("Scop", "Pfam", "Rfam", "Metal"));
157     assertEquals(fr.getDisplayedFeatureTypes(),
158             Arrays.asList("Rfam", "Metal"));
159   }
160
161   @Test(groups = "Functional")
162   public void testFindFeaturesAtColumn()
163   {
164     String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n";
165     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
166             DataSourceType.PASTE);
167     AlignViewportI av = af.getViewport();
168     FeatureRenderer fr = new FeatureRenderer(av);
169     SequenceI seq = av.getAlignment().getSequenceAt(0);
170
171     /*
172      * with no features
173      */
174     List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, 3);
175     assertTrue(features.isEmpty());
176
177     /*
178      * add features
179      */
180     SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f,
181             "Group"); // non-positional
182     seq.addSequenceFeature(sf1);
183     SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f,
184             "Group1");
185     seq.addSequenceFeature(sf2);
186     SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
187             "Group2");
188     seq.addSequenceFeature(sf3);
189     SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
190             null); // null group is always treated as visible
191     seq.addSequenceFeature(sf4);
192
193     /*
194      * add contact features
195      */
196     SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7,
197             15, 1f, "Group1");
198     seq.addSequenceFeature(sf5);
199     SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7,
200             15, 1f, "Group2");
201     seq.addSequenceFeature(sf6);
202     SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7,
203             15, 1f, null);
204     seq.addSequenceFeature(sf7);
205
206     // feature spanning B--C
207     SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f,
208             "Group");
209     seq.addSequenceFeature(sf8);
210     // contact feature B/C
211     SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5,
212             6, 1f, "Group");
213     seq.addSequenceFeature(sf9);
214
215     /*
216      * let feature renderer discover features (and make visible)
217      */
218     fr.findAllFeatures(true);
219     features = fr.findFeaturesAtColumn(seq, 15); // all positional
220     assertEquals(features.size(), 6);
221     assertTrue(features.contains(sf2));
222     assertTrue(features.contains(sf3));
223     assertTrue(features.contains(sf4));
224     assertTrue(features.contains(sf5));
225     assertTrue(features.contains(sf6));
226     assertTrue(features.contains(sf7));
227
228     /*
229      * at a non-contact position
230      */
231     features = fr.findFeaturesAtColumn(seq, 14);
232     assertEquals(features.size(), 3);
233     assertTrue(features.contains(sf2));
234     assertTrue(features.contains(sf3));
235     assertTrue(features.contains(sf4));
236
237     /*
238      * make "Type2" not displayed
239      */
240     Object[][] data = new Object[4][];
241     FeatureColourI colour = new FeatureColour(Color.RED);
242     data[0] = new Object[] { "Type1", colour, true };
243     data[1] = new Object[] { "Type2", colour, false };
244     data[2] = new Object[] { "Type3", colour, true };
245     data[3] = new Object[] { "Disulphide Bond", colour, true };
246     fr.setFeaturePriority(data);
247
248     features = fr.findFeaturesAtColumn(seq, 15);
249     assertEquals(features.size(), 5); // no sf2
250     assertTrue(features.contains(sf3));
251     assertTrue(features.contains(sf4));
252     assertTrue(features.contains(sf5));
253     assertTrue(features.contains(sf6));
254     assertTrue(features.contains(sf7));
255
256     /*
257      * make "Group2" not displayed
258      */
259     fr.setGroupVisibility("Group2", false);
260
261     features = fr.findFeaturesAtColumn(seq, 15);
262     assertEquals(features.size(), 3); // no sf2, sf3, sf6
263     assertTrue(features.contains(sf4));
264     assertTrue(features.contains(sf5));
265     assertTrue(features.contains(sf7));
266
267     // features 'at' a gap between b and c
268     // - returns enclosing feature BC but not contact feature B/C
269     features = fr.findFeaturesAtColumn(seq, 4);
270     assertEquals(features.size(), 1);
271     assertTrue(features.contains(sf8));
272     features = fr.findFeaturesAtColumn(seq, 5);
273     assertEquals(features.size(), 1);
274     assertTrue(features.contains(sf8));
275   }
276
277   @Test(groups = "Functional")
278   public void testFilterFeaturesForDisplay()
279   {
280     String seqData = ">s1\nabcdef\n";
281     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
282             DataSourceType.PASTE);
283     AlignViewportI av = af.getViewport();
284     FeatureRenderer fr = new FeatureRenderer(av);
285
286     List<SequenceFeature> features = new ArrayList<>();
287     fr.filterFeaturesForDisplay(features, null); // empty list, does nothing
288
289     SequenceI seq = av.getAlignment().getSequenceAt(0);
290     SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
291             "group1");
292     seq.addSequenceFeature(sf1);
293     SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
294             "group2");
295     seq.addSequenceFeature(sf2);
296     SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
297             "group3");
298     seq.addSequenceFeature(sf3);
299     SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
300             "group4");
301     seq.addSequenceFeature(sf4);
302     SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
303             "group4");
304     seq.addSequenceFeature(sf5);
305
306     fr.findAllFeatures(true);
307
308     features = seq.getSequenceFeatures();
309     assertEquals(features.size(), 5);
310     assertTrue(features.contains(sf1));
311     assertTrue(features.contains(sf2));
312     assertTrue(features.contains(sf3));
313     assertTrue(features.contains(sf4));
314     assertTrue(features.contains(sf5));
315
316     /*
317      * filter out duplicate (co-located) features
318      * note: which gets removed is not guaranteed
319      */
320     fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue));
321     assertEquals(features.size(), 3);
322     assertTrue(features.contains(sf1) || features.contains(sf4));
323     assertFalse(features.contains(sf1) && features.contains(sf4));
324     assertTrue(features.contains(sf2) || features.contains(sf3));
325     assertFalse(features.contains(sf2) && features.contains(sf3));
326     assertTrue(features.contains(sf5));
327
328     /*
329      * hide group 3 - sf3 is removed, sf2 is retained
330      */
331     fr.setGroupVisibility("group3", false);
332     features = seq.getSequenceFeatures();
333     fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue));
334     assertEquals(features.size(), 3);
335     assertTrue(features.contains(sf1) || features.contains(sf4));
336     assertFalse(features.contains(sf1) && features.contains(sf4));
337     assertTrue(features.contains(sf2));
338     assertFalse(features.contains(sf3));
339     assertTrue(features.contains(sf5));
340
341     /*
342      * hide group 2, show group 3 - sf2 is removed, sf3 is retained
343      */
344     fr.setGroupVisibility("group2", false);
345     fr.setGroupVisibility("group3", true);
346     features = seq.getSequenceFeatures();
347     fr.filterFeaturesForDisplay(features, null);
348     assertEquals(features.size(), 3);
349     assertTrue(features.contains(sf1) || features.contains(sf4));
350     assertFalse(features.contains(sf1) && features.contains(sf4));
351     assertFalse(features.contains(sf2));
352     assertTrue(features.contains(sf3));
353     assertTrue(features.contains(sf5));
354
355     /*
356      * no filtering of co-located features with graduated colour scheme
357      * filterFeaturesForDisplay does _not_ check colour threshold
358      * sf2 is removed as its group is hidden
359      */
360     features = seq.getSequenceFeatures();
361     fr.filterFeaturesForDisplay(features, new FeatureColour(Color.black,
362             Color.white, 0f, 1f));
363     assertEquals(features.size(), 4);
364     assertFalse(features.contains(sf2));
365
366     /*
367      * co-located features with colour by label
368      * should not get filtered
369      */
370     features = seq.getSequenceFeatures();
371     FeatureColour fc = new FeatureColour(Color.black);
372     fc.setColourByLabel(true);
373     fr.filterFeaturesForDisplay(features, fc);
374     assertEquals(features.size(), 4);
375     assertTrue(features.contains(sf1));
376     assertTrue(features.contains(sf3));
377     assertTrue(features.contains(sf4));
378     assertTrue(features.contains(sf5));
379
380     /*
381      * no filtering if transparency is applied
382      */
383     fr.setTransparency(0.5f);
384     features = seq.getSequenceFeatures();
385     fr.setGroupVisibility("group2", true);
386     fr.filterFeaturesForDisplay(features, new FeatureColour(Color.RED));
387     assertEquals(features.size(), 5);
388   }
389 }