Merge branch 'releases/Release_2_10_4_Branch' into develop
[jalview.git] / test / jalview / renderer / seqfeatures / FeatureRendererTest.java
1 package jalview.renderer.seqfeatures;
2
3 import static org.testng.Assert.assertEquals;
4 import static org.testng.Assert.assertFalse;
5 import static org.testng.Assert.assertNull;
6 import static org.testng.Assert.assertTrue;
7
8 import jalview.api.AlignViewportI;
9 import jalview.api.FeatureColourI;
10 import jalview.datamodel.SequenceFeature;
11 import jalview.datamodel.SequenceI;
12 import jalview.datamodel.features.FeatureMatcher;
13 import jalview.datamodel.features.FeatureMatcherSet;
14 import jalview.datamodel.features.FeatureMatcherSetI;
15 import jalview.gui.AlignFrame;
16 import jalview.io.DataSourceType;
17 import jalview.io.FileLoader;
18 import jalview.schemes.FeatureColour;
19 import jalview.util.matcher.Condition;
20 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
21
22 import java.awt.Color;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28
29 import org.testng.annotations.Test;
30
31 public class FeatureRendererTest
32 {
33
34   @Test(groups = "Functional")
35   public void testFindAllFeatures()
36   {
37     String seqData = ">s1\nabcdef\n>s2\nabcdef\n>s3\nabcdef\n>s4\nabcdef\n";
38     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
39             DataSourceType.PASTE);
40     AlignViewportI av = af.getViewport();
41     FeatureRenderer fr = new FeatureRenderer(av);
42
43     /*
44      * with no features
45      */
46     fr.findAllFeatures(true);
47     assertTrue(fr.getRenderOrder().isEmpty());
48     assertTrue(fr.getFeatureGroups().isEmpty());
49
50     List<SequenceI> seqs = av.getAlignment().getSequences();
51
52     // add a non-positional feature - should be ignored by FeatureRenderer
53     SequenceFeature sf1 = new SequenceFeature("Type", "Desc", 0, 0, 1f,
54             "Group");
55     seqs.get(0).addSequenceFeature(sf1);
56     fr.findAllFeatures(true);
57     // ? bug - types and groups added for non-positional features
58     List<String> types = fr.getRenderOrder();
59     List<String> groups = fr.getFeatureGroups();
60     assertEquals(types.size(), 0);
61     assertFalse(types.contains("Type"));
62     assertEquals(groups.size(), 0);
63     assertFalse(groups.contains("Group"));
64
65     // add some positional features
66     seqs.get(1).addSequenceFeature(
67             new SequenceFeature("Pfam", "Desc", 5, 9, 1f, "PfamGroup"));
68     seqs.get(2).addSequenceFeature(
69             new SequenceFeature("Pfam", "Desc", 14, 22, 2f, "RfamGroup"));
70     // bug in findAllFeatures - group not checked for a known feature type
71     seqs.get(2).addSequenceFeature(new SequenceFeature("Rfam", "Desc", 5, 9,
72             Float.NaN, "RfamGroup"));
73     // existing feature type with null group
74     seqs.get(3).addSequenceFeature(
75             new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null));
76     // new feature type with null group
77     seqs.get(3).addSequenceFeature(
78             new SequenceFeature("Scop", "Desc", 5, 9, Float.NaN, null));
79     // null value for type produces NullPointerException
80     fr.findAllFeatures(true);
81     types = fr.getRenderOrder();
82     groups = fr.getFeatureGroups();
83     assertEquals(types.size(), 3);
84     assertFalse(types.contains("Type"));
85     assertTrue(types.contains("Pfam"));
86     assertTrue(types.contains("Rfam"));
87     assertTrue(types.contains("Scop"));
88     assertEquals(groups.size(), 2);
89     assertFalse(groups.contains("Group"));
90     assertTrue(groups.contains("PfamGroup"));
91     assertTrue(groups.contains("RfamGroup"));
92     assertFalse(groups.contains(null)); // null group is ignored
93
94     /*
95      * check min-max values
96      */
97     Map<String, float[][]> minMax = fr.getMinMax();
98     assertEquals(minMax.size(), 1); // non-positional and NaN not stored
99     assertEquals(minMax.get("Pfam")[0][0], 1f); // positional min
100     assertEquals(minMax.get("Pfam")[0][1], 2f); // positional max
101
102     // increase max for Pfam, add scores for Rfam
103     seqs.get(0).addSequenceFeature(
104             new SequenceFeature("Pfam", "Desc", 14, 22, 8f, "RfamGroup"));
105     seqs.get(1).addSequenceFeature(
106             new SequenceFeature("Rfam", "Desc", 5, 9, 6f, "RfamGroup"));
107     fr.findAllFeatures(true);
108     // note minMax is not a defensive copy, shouldn't expose this
109     assertEquals(minMax.size(), 2);
110     assertEquals(minMax.get("Pfam")[0][0], 1f);
111     assertEquals(minMax.get("Pfam")[0][1], 8f);
112     assertEquals(minMax.get("Rfam")[0][0], 6f);
113     assertEquals(minMax.get("Rfam")[0][1], 6f);
114
115     /*
116      * check render order (last is on top)
117      */
118     List<String> renderOrder = fr.getRenderOrder();
119     assertEquals(renderOrder, Arrays.asList("Scop", "Rfam", "Pfam"));
120
121     /*
122      * change render order (todo: an easier way)
123      * nb here last comes first in the data array
124      */
125     FeatureSettingsBean[] data = new FeatureSettingsBean[3];
126     FeatureColourI colour = new FeatureColour(Color.RED);
127     data[0] = new FeatureSettingsBean("Rfam", colour, null, true);
128     data[1] = new FeatureSettingsBean("Pfam", colour, null, false);
129     data[2] = new FeatureSettingsBean("Scop", colour, null, false);
130     fr.setFeaturePriority(data);
131     assertEquals(fr.getRenderOrder(),
132             Arrays.asList("Scop", "Pfam", "Rfam"));
133     assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam"));
134
135     /*
136      * add a new feature type: should go on top of render order as visible,
137      * other feature ordering and visibility should be unchanged
138      */
139     seqs.get(2).addSequenceFeature(
140             new SequenceFeature("Metal", "Desc", 14, 22, 8f, "MetalGroup"));
141     fr.findAllFeatures(true);
142     assertEquals(fr.getRenderOrder(),
143             Arrays.asList("Scop", "Pfam", "Rfam", "Metal"));
144     assertEquals(fr.getDisplayedFeatureTypes(),
145             Arrays.asList("Rfam", "Metal"));
146   }
147
148   @Test(groups = "Functional")
149   public void testFindFeaturesAtColumn()
150   {
151     String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n";
152     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
153             DataSourceType.PASTE);
154     AlignViewportI av = af.getViewport();
155     FeatureRenderer fr = new FeatureRenderer(av);
156     SequenceI seq = av.getAlignment().getSequenceAt(0);
157
158     /*
159      * with no features
160      */
161     List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, 3);
162     assertTrue(features.isEmpty());
163
164     /*
165      * add features
166      */
167     SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f,
168             "Group"); // non-positional
169     seq.addSequenceFeature(sf1);
170     SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f,
171             "Group1");
172     seq.addSequenceFeature(sf2);
173     SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
174             "Group2");
175     seq.addSequenceFeature(sf3);
176     SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
177             null); // null group is always treated as visible
178     seq.addSequenceFeature(sf4);
179
180     /*
181      * add contact features
182      */
183     SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7,
184             15, 1f, "Group1");
185     seq.addSequenceFeature(sf5);
186     SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7,
187             15, 1f, "Group2");
188     seq.addSequenceFeature(sf6);
189     SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7,
190             15, 1f, null);
191     seq.addSequenceFeature(sf7);
192
193     // feature spanning B--C
194     SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f,
195             "Group");
196     seq.addSequenceFeature(sf8);
197     // contact feature B/C
198     SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5,
199             6, 1f, "Group");
200     seq.addSequenceFeature(sf9);
201
202     /*
203      * let feature renderer discover features (and make visible)
204      */
205     fr.findAllFeatures(true);
206     features = fr.findFeaturesAtColumn(seq, 15); // all positional
207     assertEquals(features.size(), 6);
208     assertTrue(features.contains(sf2));
209     assertTrue(features.contains(sf3));
210     assertTrue(features.contains(sf4));
211     assertTrue(features.contains(sf5));
212     assertTrue(features.contains(sf6));
213     assertTrue(features.contains(sf7));
214
215     /*
216      * at a non-contact position
217      */
218     features = fr.findFeaturesAtColumn(seq, 14);
219     assertEquals(features.size(), 3);
220     assertTrue(features.contains(sf2));
221     assertTrue(features.contains(sf3));
222     assertTrue(features.contains(sf4));
223
224     /*
225      * make "Type2" not displayed
226      */
227     FeatureColourI colour = new FeatureColour(Color.RED);
228     FeatureSettingsBean[] data = new FeatureSettingsBean[4];
229     data[0] = new FeatureSettingsBean("Type1", colour, null, true);
230     data[1] = new FeatureSettingsBean("Type2", colour, null, false);
231     data[2] = new FeatureSettingsBean("Type3", colour, null, true);
232     data[3] = new FeatureSettingsBean("Disulphide Bond", colour, null,
233             true);
234     fr.setFeaturePriority(data);
235
236     features = fr.findFeaturesAtColumn(seq, 15);
237     assertEquals(features.size(), 5); // no sf2
238     assertTrue(features.contains(sf3));
239     assertTrue(features.contains(sf4));
240     assertTrue(features.contains(sf5));
241     assertTrue(features.contains(sf6));
242     assertTrue(features.contains(sf7));
243
244     /*
245      * make "Group2" not displayed
246      */
247     fr.setGroupVisibility("Group2", false);
248
249     features = fr.findFeaturesAtColumn(seq, 15);
250     assertEquals(features.size(), 3); // no sf2, sf3, sf6
251     assertTrue(features.contains(sf4));
252     assertTrue(features.contains(sf5));
253     assertTrue(features.contains(sf7));
254
255     // features 'at' a gap between b and c
256     // - returns enclosing feature BC but not contact feature B/C
257     features = fr.findFeaturesAtColumn(seq, 4);
258     assertEquals(features.size(), 1);
259     assertTrue(features.contains(sf8));
260     features = fr.findFeaturesAtColumn(seq, 5);
261     assertEquals(features.size(), 1);
262     assertTrue(features.contains(sf8));
263
264     /*
265      * give "Type3" features a graduated colour scheme
266      * - first with no threshold
267      */
268     FeatureColourI gc = new FeatureColour(Color.yellow, Color.red, null, 0f,
269             10f);
270     fr.getFeatureColours().put("Type3", gc);
271     features = fr.findFeaturesAtColumn(seq, 8);
272     assertTrue(features.contains(sf4));
273     // now with threshold > 2f - feature score of 1f is excluded
274     gc.setAboveThreshold(true);
275     gc.setThreshold(2f);
276     features = fr.findFeaturesAtColumn(seq, 8);
277     assertFalse(features.contains(sf4));
278
279     /*
280      * make "Type3" graduated colour by attribute "AF"
281      * - first with no attribute held - feature should be excluded
282      */
283     gc.setAttributeName("AF");
284     features = fr.findFeaturesAtColumn(seq, 8);
285     assertFalse(features.contains(sf4));
286     // now with the attribute above threshold - should be included
287     sf4.setValue("AF", "2.4");
288     features = fr.findFeaturesAtColumn(seq, 8);
289     assertTrue(features.contains(sf4));
290     // now with the attribute below threshold - should be excluded
291     sf4.setValue("AF", "1.4");
292     features = fr.findFeaturesAtColumn(seq, 8);
293     assertFalse(features.contains(sf4));
294   }
295
296   @Test(groups = "Functional")
297   public void testFilterFeaturesForDisplay()
298   {
299     String seqData = ">s1\nabcdef\n";
300     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
301             DataSourceType.PASTE);
302     AlignViewportI av = af.getViewport();
303     FeatureRenderer fr = new FeatureRenderer(av);
304
305     List<SequenceFeature> features = new ArrayList<>();
306     fr.filterFeaturesForDisplay(features); // empty list, does nothing
307
308     SequenceI seq = av.getAlignment().getSequenceAt(0);
309     SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
310             "group1");
311     seq.addSequenceFeature(sf1);
312     SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
313             "group2");
314     seq.addSequenceFeature(sf2);
315     SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
316             "group3");
317     seq.addSequenceFeature(sf3);
318     SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
319             "group4");
320     seq.addSequenceFeature(sf4);
321     SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
322             "group4");
323     seq.addSequenceFeature(sf5);
324
325     fr.findAllFeatures(true);
326
327     features = seq.getSequenceFeatures();
328     assertEquals(features.size(), 5);
329     assertTrue(features.contains(sf1));
330     assertTrue(features.contains(sf2));
331     assertTrue(features.contains(sf3));
332     assertTrue(features.contains(sf4));
333     assertTrue(features.contains(sf5));
334
335     /*
336      * filter out duplicate (co-located) features
337      * note: which gets removed is not guaranteed
338      */
339     fr.filterFeaturesForDisplay(features);
340     assertEquals(features.size(), 3);
341     assertTrue(features.contains(sf1) || features.contains(sf4));
342     assertFalse(features.contains(sf1) && features.contains(sf4));
343     assertTrue(features.contains(sf2) || features.contains(sf3));
344     assertFalse(features.contains(sf2) && features.contains(sf3));
345     assertTrue(features.contains(sf5));
346
347     /*
348      * hide groups 2 and 3 makes no difference to this method
349      */
350     fr.setGroupVisibility("group2", false);
351     fr.setGroupVisibility("group3", false);
352     features = seq.getSequenceFeatures();
353     fr.filterFeaturesForDisplay(features);
354     assertEquals(features.size(), 3);
355     assertTrue(features.contains(sf1) || features.contains(sf4));
356     assertFalse(features.contains(sf1) && features.contains(sf4));
357     assertTrue(features.contains(sf2) || features.contains(sf3));
358     assertFalse(features.contains(sf2) && features.contains(sf3));
359     assertTrue(features.contains(sf5));
360
361     /*
362      * no filtering if transparency is applied
363      */
364     fr.setTransparency(0.5f);
365     features = seq.getSequenceFeatures();
366     fr.filterFeaturesForDisplay(features);
367     assertEquals(features.size(), 5);
368   }
369
370   @Test(groups = "Functional")
371   public void testGetColour()
372   {
373     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(">s1\nABCD\n",
374             DataSourceType.PASTE);
375     AlignViewportI av = af.getViewport();
376     FeatureRenderer fr = new FeatureRenderer(av);
377
378     /*
379      * simple colour, feature type and group displayed
380      */
381     FeatureColourI fc = new FeatureColour(Color.red);
382     fr.getFeatureColours().put("Cath", fc);
383     SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
384             "group1");
385     assertEquals(fr.getColour(sf1), Color.red);
386
387     /*
388      * hide feature type, then unhide
389      * - feature type visibility should not affect the result
390      */
391     FeatureSettingsBean[] data = new FeatureSettingsBean[1];
392     data[0] = new FeatureSettingsBean("Cath", fc, null, false);
393     fr.setFeaturePriority(data);
394     assertEquals(fr.getColour(sf1), Color.red);
395     data[0] = new FeatureSettingsBean("Cath", fc, null, true);
396     fr.setFeaturePriority(data);
397     assertEquals(fr.getColour(sf1), Color.red);
398
399     /*
400      * hide feature group, then unhide
401      */
402     fr.setGroupVisibility("group1", false);
403     assertNull(fr.getColour(sf1));
404     fr.setGroupVisibility("group1", true);
405     assertEquals(fr.getColour(sf1), Color.red);
406
407     /*
408      * graduated colour by score, no threshold, no score
409      * 
410      */
411     FeatureColourI gc = new FeatureColour(Color.yellow, Color.red,
412             Color.green, 1f, 11f);
413     fr.getFeatureColours().put("Cath", gc);
414     assertEquals(fr.getColour(sf1), Color.green);
415
416     /*
417      * graduated colour by score, no threshold, with score value
418      */
419     SequenceFeature sf2 = new SequenceFeature("Cath", "", 6, 8, 6f,
420             "group1");
421     // score 6 is half way from yellow(255, 255, 0) to red(255, 0, 0)
422     Color expected = new Color(255, 128, 0);
423     assertEquals(fr.getColour(sf2), expected);
424
425     /*
426      * above threshold, score is above threshold - no change
427      */
428     gc.setAboveThreshold(true);
429     gc.setThreshold(5f);
430     assertEquals(fr.getColour(sf2), expected);
431
432     /*
433      * threshold is min-max; now score 6 is 1/6 of the way from 5 to 11
434      * or from yellow(255, 255, 0) to red(255, 0, 0)
435      */
436     gc = new FeatureColour(Color.yellow, Color.red, Color.green, 5f, 11f);
437     fr.getFeatureColours().put("Cath", gc);
438     gc.setAutoScaled(false); // this does little other than save a checkbox setting!
439     assertEquals(fr.getColour(sf2), new Color(255, 213, 0));
440
441     /*
442      * feature score is below threshold - no colour
443      */
444     gc.setAboveThreshold(true);
445     gc.setThreshold(7f);
446     assertNull(fr.getColour(sf2));
447
448     /*
449      * feature score is above threshold - no colour
450      */
451     gc.setBelowThreshold(true);
452     gc.setThreshold(3f);
453     assertNull(fr.getColour(sf2));
454
455     /*
456      * colour by feature attribute value
457      * first with no value held
458      */
459     gc = new FeatureColour(Color.yellow, Color.red, Color.green, 1f, 11f);
460     fr.getFeatureColours().put("Cath", gc);
461     gc.setAttributeName("AF");
462     assertEquals(fr.getColour(sf2), Color.green);
463
464     // with non-numeric attribute value
465     sf2.setValue("AF", "Five");
466     assertEquals(fr.getColour(sf2), Color.green);
467
468     // with numeric attribute value
469     sf2.setValue("AF", "6");
470     assertEquals(fr.getColour(sf2), expected);
471
472     // with numeric value outwith threshold
473     gc.setAboveThreshold(true);
474     gc.setThreshold(10f);
475     assertNull(fr.getColour(sf2));
476
477     // with filter on AF < 4
478     gc.setAboveThreshold(false);
479     assertEquals(fr.getColour(sf2), expected);
480     FeatureMatcherSetI filter = new FeatureMatcherSet();
481     filter.and(FeatureMatcher.byAttribute(Condition.LT, "4.0", "AF"));
482     fr.setFeatureFilter("Cath", filter);
483     assertNull(fr.getColour(sf2));
484
485     // with filter on 'Consequence contains missense'
486     filter = new FeatureMatcherSet();
487     filter.and(FeatureMatcher.byAttribute(Condition.Contains, "missense",
488             "Consequence"));
489     fr.setFeatureFilter("Cath", filter);
490     // if feature has no Consequence attribute, no colour
491     assertNull(fr.getColour(sf2));
492     // if attribute does not match filter, no colour
493     sf2.setValue("Consequence", "Synonymous");
494     assertNull(fr.getColour(sf2));
495     // attribute matches filter
496     sf2.setValue("Consequence", "Missense variant");
497     assertEquals(fr.getColour(sf2), expected);
498
499     // with filter on CSQ:Feature contains "ENST01234"
500     filter = new FeatureMatcherSet();
501     filter.and(FeatureMatcher.byAttribute(Condition.Matches, "ENST01234",
502             "CSQ", "Feature"));
503     fr.setFeatureFilter("Cath", filter);
504     // if feature has no CSQ data, no colour
505     assertNull(fr.getColour(sf2));
506     // if CSQ data does not include Feature, no colour
507     Map<String, String> csqData = new HashMap<>();
508     csqData.put("BIOTYPE", "Transcript");
509     sf2.setValue("CSQ", csqData);
510     assertNull(fr.getColour(sf2));
511     // if attribute does not match filter, no colour
512     csqData.put("Feature", "ENST9876");
513     assertNull(fr.getColour(sf2));
514     // attribute matches filter
515     csqData.put("Feature", "ENST01234");
516     assertEquals(fr.getColour(sf2), expected);
517   }
518 }