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.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;
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;
29 import org.testng.annotations.Test;
31 public class FeatureRendererTest
34 @Test(groups = "Functional")
35 public void testFindAllFeatures()
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);
46 fr.findAllFeatures(true);
47 assertTrue(fr.getRenderOrder().isEmpty());
48 assertTrue(fr.getFeatureGroups().isEmpty());
50 List<SequenceI> seqs = av.getAlignment().getSequences();
52 // add a non-positional feature - should be ignored by FeatureRenderer
53 SequenceFeature sf1 = new SequenceFeature("Type", "Desc", 0, 0, 1f,
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"));
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
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 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"));
136 * add a new feature type: should go on top of render order as visible,
137 * other feature ordering and visibility should be unchanged
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"));
148 @Test(groups = "Functional")
149 public void testFindFeaturesAtColumn()
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);
161 List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, 3);
162 assertTrue(features.isEmpty());
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,
172 seq.addSequenceFeature(sf2);
173 SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
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);
181 * add contact features
183 SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7,
185 seq.addSequenceFeature(sf5);
186 SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7,
188 seq.addSequenceFeature(sf6);
189 SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7,
191 seq.addSequenceFeature(sf7);
193 // feature spanning B--C
194 SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f,
196 seq.addSequenceFeature(sf8);
197 // contact feature B/C
198 SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5,
200 seq.addSequenceFeature(sf9);
203 * let feature renderer discover features (and make visible)
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));
216 * at a non-contact position
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));
225 * make "Type2" not displayed
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,
234 fr.setFeaturePriority(data);
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));
245 * make "Group2" not displayed
247 fr.setGroupVisibility("Group2", false);
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));
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));
265 * give "Type3" features a graduated colour scheme
266 * - first with no threshold
268 FeatureColourI gc = new FeatureColour(Color.yellow, Color.red, null, 0f,
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);
276 features = fr.findFeaturesAtColumn(seq, 8);
277 assertFalse(features.contains(sf4));
280 * make "Type3" graduated colour by attribute "AF"
281 * - first with no attribute held - feature should be excluded
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));
296 @Test(groups = "Functional")
297 public void testFilterFeaturesForDisplay()
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);
305 List<SequenceFeature> features = new ArrayList<>();
306 fr.filterFeaturesForDisplay(features); // empty list, does nothing
308 SequenceI seq = av.getAlignment().getSequenceAt(0);
309 SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
311 seq.addSequenceFeature(sf1);
312 SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
314 seq.addSequenceFeature(sf2);
315 SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
317 seq.addSequenceFeature(sf3);
318 SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
320 seq.addSequenceFeature(sf4);
321 SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
323 seq.addSequenceFeature(sf5);
325 fr.findAllFeatures(true);
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));
336 * filter out duplicate (co-located) features
337 * note: which gets removed is not guaranteed
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));
348 * hide groups 2 and 3 makes no difference to this method
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));
362 * no filtering if transparency is applied
364 fr.setTransparency(0.5f);
365 features = seq.getSequenceFeatures();
366 fr.filterFeaturesForDisplay(features);
367 assertEquals(features.size(), 5);
370 @Test(groups = "Functional")
371 public void testGetColour()
373 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(">s1\nABCD\n",
374 DataSourceType.PASTE);
375 AlignViewportI av = af.getViewport();
376 FeatureRenderer fr = new FeatureRenderer(av);
379 * simple colour, feature type and group displayed
381 FeatureColourI fc = new FeatureColour(Color.red);
382 fr.getFeatureColours().put("Cath", fc);
383 SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
385 assertEquals(fr.getColour(sf1), Color.red);
388 * hide feature type, then unhide
389 * - feature type visibility should not affect the result
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);
400 * hide feature group, then unhide
402 fr.setGroupVisibility("group1", false);
403 assertNull(fr.getColour(sf1));
404 fr.setGroupVisibility("group1", true);
405 assertEquals(fr.getColour(sf1), Color.red);
408 * graduated colour by score, no threshold, no score
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);
417 * graduated colour by score, no threshold, with score value
419 SequenceFeature sf2 = new SequenceFeature("Cath", "", 6, 8, 6f,
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);
426 * above threshold, score is above threshold - no change
428 gc.setAboveThreshold(true);
430 assertEquals(fr.getColour(sf2), expected);
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)
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));
442 * feature score is below threshold - no colour
444 gc.setAboveThreshold(true);
446 assertNull(fr.getColour(sf2));
449 * feature score is above threshold - no colour
451 gc.setBelowThreshold(true);
453 assertNull(fr.getColour(sf2));
456 * colour by feature attribute value
457 * first with no value held
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);
464 // with non-numeric attribute value
465 sf2.setValue("AF", "Five");
466 assertEquals(fr.getColour(sf2), Color.green);
468 // with numeric attribute value
469 sf2.setValue("AF", "6");
470 assertEquals(fr.getColour(sf2), expected);
472 // with numeric value outwith threshold
473 gc.setAboveThreshold(true);
474 gc.setThreshold(10f);
475 assertNull(fr.getColour(sf2));
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));
485 // with filter on 'Consequence contains missense'
486 filter = new FeatureMatcherSet();
487 filter.and(FeatureMatcher.byAttribute(Condition.Contains, "missense",
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);
499 // with filter on CSQ:Feature contains "ENST01234"
500 filter = new FeatureMatcherSet();
501 filter.and(FeatureMatcher.byAttribute(Condition.Matches, "ENST01234",
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);