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