1 package jalview.renderer.seqfeatures;
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;
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;
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;
28 import org.testng.annotations.Test;
30 public class FeatureRendererTest
33 @Test(groups = "Functional")
34 public void testFindAllFeatures()
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);
45 fr.findAllFeatures(true);
46 assertTrue(fr.getRenderOrder().isEmpty());
47 assertTrue(fr.getFeatureGroups().isEmpty());
49 List<SequenceI> seqs = av.getAlignment().getSequences();
51 // add a non-positional feature - should be ignored by FeatureRenderer
52 SequenceFeature sf1 = new SequenceFeature("Type", "Desc", 0, 0, 1f,
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"));
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,
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
95 * check min-max values
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
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);
116 * check render order (last is on top)
118 List<String> renderOrder = fr.getRenderOrder();
119 assertEquals(renderOrder, Arrays.asList("Scop", "Rfam", "Pfam"));
122 * change render order (todo: an easier way)
123 * nb here last comes first in the data array
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"));
135 * add a new feature type: should go on top of render order as visible,
136 * other feature ordering and visibility should be unchanged
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"));
147 @Test(groups = "Functional")
148 public void testFindFeaturesAtColumn()
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);
160 List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, 3);
161 assertTrue(features.isEmpty());
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,
171 seq.addSequenceFeature(sf2);
172 SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
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);
180 * add contact features
182 SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7,
184 seq.addSequenceFeature(sf5);
185 SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7,
187 seq.addSequenceFeature(sf6);
188 SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7,
190 seq.addSequenceFeature(sf7);
192 // feature spanning B--C
193 SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f,
195 seq.addSequenceFeature(sf8);
196 // contact feature B/C
197 SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5,
199 seq.addSequenceFeature(sf9);
202 * let feature renderer discover features (and make visible)
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));
215 * at a non-contact position
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));
224 * make "Type2" not displayed
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);
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));
243 * make "Group2" not displayed
245 fr.setGroupVisibility("Group2", false);
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));
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));
263 * give "Type3" features a graduated colour scheme
264 * - first with no threshold
266 FeatureColourI gc = new FeatureColour(Color.yellow, Color.red, null, 0f,
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);
274 features = fr.findFeaturesAtColumn(seq, 8);
275 assertFalse(features.contains(sf4));
278 * make "Type3" graduated colour by attribute "AF"
279 * - first with no attribute held - feature should be excluded
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));
294 @Test(groups = "Functional")
295 public void testFilterFeaturesForDisplay()
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);
303 List<SequenceFeature> features = new ArrayList<>();
304 fr.filterFeaturesForDisplay(features); // empty list, does nothing
306 SequenceI seq = av.getAlignment().getSequenceAt(0);
307 SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
309 seq.addSequenceFeature(sf1);
310 SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
312 seq.addSequenceFeature(sf2);
313 SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
315 seq.addSequenceFeature(sf3);
316 SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
318 seq.addSequenceFeature(sf4);
319 SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
321 seq.addSequenceFeature(sf5);
323 fr.findAllFeatures(true);
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));
334 * filter out duplicate (co-located) features
335 * note: which gets removed is not guaranteed
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));
346 * hide groups 2 and 3 makes no difference to this method
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));
360 @Test(groups = "Functional")
361 public void testGetColour()
363 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(">s1\nABCD\n",
364 DataSourceType.PASTE);
365 AlignViewportI av = af.getViewport();
366 FeatureRenderer fr = new FeatureRenderer(av);
369 * simple colour, feature type and group displayed
371 FeatureColourI fc = new FeatureColour(Color.red);
372 fr.getFeatureColours().put("Cath", fc);
373 SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
375 assertEquals(fr.getColour(sf1), Color.red);
378 * hide feature type, then unhide
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);
389 * hide feature group, then unhide
391 fr.setGroupVisibility("group1", false);
392 assertNull(fr.getColour(sf1));
393 fr.setGroupVisibility("group1", true);
394 assertEquals(fr.getColour(sf1), Color.red);
397 * graduated colour by score, no threshold, no score
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);
406 * graduated colour by score, no threshold, with score value
408 SequenceFeature sf2 = new SequenceFeature("Cath", "", 6, 8, 6f,
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);
415 * above threshold, score is above threshold - no change
417 gc.setAboveThreshold(true);
419 assertEquals(fr.getColour(sf2), expected);
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)
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));
431 * feature score is below threshold - no colour
433 gc.setAboveThreshold(true);
435 assertNull(fr.getColour(sf2));
438 * feature score is above threshold - no colour
440 gc.setBelowThreshold(true);
442 assertNull(fr.getColour(sf2));
445 * colour by feature attribute value
446 * first with no value held
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);
453 // with non-numeric attribute value
454 sf2.setValue("AF", "Five");
455 assertEquals(fr.getColour(sf2), Color.green);
457 // with numeric attribute value
458 sf2.setValue("AF", "6");
459 assertEquals(fr.getColour(sf2), expected);
461 // with numeric value outwith threshold
462 gc.setAboveThreshold(true);
463 gc.setThreshold(10f);
464 assertNull(fr.getColour(sf2));
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));
474 // with filter on 'Consequence contains missense'
475 filter = new KeyedMatcherSet();
476 filter.and(new KeyedMatcher(Condition.Contains, "missense",
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);
488 // with filter on CSQ.Feature contains "ENST01234"
489 filter = new KeyedMatcherSet();
490 filter.and(new KeyedMatcher(Condition.Matches, "ENST01234", "CSQ",
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);