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.List;
27 import org.testng.annotations.Test;
29 public class FeatureRendererTest
32 @Test(groups = "Functional")
33 public void testFindAllFeatures()
35 String seqData = ">s1\nabcdef\n>s2\nabcdef\n>s3\nabcdef\n>s4\nabcdef\n";
36 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
37 DataSourceType.PASTE);
38 AlignViewportI av = af.getViewport();
39 FeatureRenderer fr = new FeatureRenderer(av);
44 fr.findAllFeatures(true);
45 assertTrue(fr.getRenderOrder().isEmpty());
46 assertTrue(fr.getFeatureGroups().isEmpty());
48 List<SequenceI> seqs = av.getAlignment().getSequences();
50 // add a non-positional feature - should be ignored by FeatureRenderer
51 SequenceFeature sf1 = new SequenceFeature("Type", "Desc", 0, 0, 1f,
53 seqs.get(0).addSequenceFeature(sf1);
54 fr.findAllFeatures(true);
55 // ? bug - types and groups added for non-positional features
56 List<String> types = fr.getRenderOrder();
57 List<String> groups = fr.getFeatureGroups();
58 assertEquals(types.size(), 0);
59 assertFalse(types.contains("Type"));
60 assertEquals(groups.size(), 0);
61 assertFalse(groups.contains("Group"));
63 // add some positional features
64 seqs.get(1).addSequenceFeature(
65 new SequenceFeature("Pfam", "Desc", 5, 9, 1f, "PfamGroup"));
66 seqs.get(2).addSequenceFeature(
67 new SequenceFeature("Pfam", "Desc", 14, 22, 2f, "RfamGroup"));
68 // bug in findAllFeatures - group not checked for a known feature type
69 seqs.get(2).addSequenceFeature(
70 new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN,
72 // existing feature type with null group
73 seqs.get(3).addSequenceFeature(
74 new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null));
75 // new feature type with null group
76 seqs.get(3).addSequenceFeature(
77 new SequenceFeature("Scop", "Desc", 5, 9, Float.NaN, null));
78 // null value for type produces NullPointerException
79 fr.findAllFeatures(true);
80 types = fr.getRenderOrder();
81 groups = fr.getFeatureGroups();
82 assertEquals(types.size(), 3);
83 assertFalse(types.contains("Type"));
84 assertTrue(types.contains("Pfam"));
85 assertTrue(types.contains("Rfam"));
86 assertTrue(types.contains("Scop"));
87 assertEquals(groups.size(), 2);
88 assertFalse(groups.contains("Group"));
89 assertTrue(groups.contains("PfamGroup"));
90 assertTrue(groups.contains("RfamGroup"));
91 assertFalse(groups.contains(null)); // null group is ignored
94 * check min-max values
96 Map<String, float[][]> minMax = fr.getMinMax();
97 assertEquals(minMax.size(), 1); // non-positional and NaN not stored
98 assertEquals(minMax.get("Pfam")[0][0], 1f); // positional min
99 assertEquals(minMax.get("Pfam")[0][1], 2f); // positional max
101 // increase max for Pfam, add scores for Rfam
102 seqs.get(0).addSequenceFeature(
103 new SequenceFeature("Pfam", "Desc", 14, 22, 8f, "RfamGroup"));
104 seqs.get(1).addSequenceFeature(
105 new SequenceFeature("Rfam", "Desc", 5, 9, 6f, "RfamGroup"));
106 fr.findAllFeatures(true);
107 // note minMax is not a defensive copy, shouldn't expose this
108 assertEquals(minMax.size(), 2);
109 assertEquals(minMax.get("Pfam")[0][0], 1f);
110 assertEquals(minMax.get("Pfam")[0][1], 8f);
111 assertEquals(minMax.get("Rfam")[0][0], 6f);
112 assertEquals(minMax.get("Rfam")[0][1], 6f);
115 * check render order (last is on top)
117 List<String> renderOrder = fr.getRenderOrder();
118 assertEquals(renderOrder, Arrays.asList("Scop", "Rfam", "Pfam"));
121 * change render order (todo: an easier way)
122 * nb here last comes first in the data array
124 Object[][] data = new Object[3][];
125 FeatureColourI colour = new FeatureColour(Color.RED);
126 data[0] = new Object[] { "Rfam", colour, true };
127 data[1] = new Object[] { "Pfam", colour, false };
128 data[2] = new Object[] { "Scop", colour, false };
129 fr.setFeaturePriority(data);
130 assertEquals(fr.getRenderOrder(), Arrays.asList("Scop", "Pfam", "Rfam"));
131 assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam"));
134 * add a new feature type: should go on top of render order as visible,
135 * other feature ordering and visibility should be unchanged
137 seqs.get(2).addSequenceFeature(
138 new SequenceFeature("Metal", "Desc", 14, 22, 8f, "MetalGroup"));
139 fr.findAllFeatures(true);
140 assertEquals(fr.getRenderOrder(),
141 Arrays.asList("Scop", "Pfam", "Rfam", "Metal"));
142 assertEquals(fr.getDisplayedFeatureTypes(),
143 Arrays.asList("Rfam", "Metal"));
146 @Test(groups = "Functional")
147 public void testFindFeaturesAtColumn()
149 String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n";
150 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
151 DataSourceType.PASTE);
152 AlignViewportI av = af.getViewport();
153 FeatureRenderer fr = new FeatureRenderer(av);
154 SequenceI seq = av.getAlignment().getSequenceAt(0);
159 List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, 3);
160 assertTrue(features.isEmpty());
165 SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f,
166 "Group"); // non-positional
167 seq.addSequenceFeature(sf1);
168 SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f,
170 seq.addSequenceFeature(sf2);
171 SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
173 seq.addSequenceFeature(sf3);
174 SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
175 null); // null group is always treated as visible
176 seq.addSequenceFeature(sf4);
179 * add contact features
181 SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7,
183 seq.addSequenceFeature(sf5);
184 SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7,
186 seq.addSequenceFeature(sf6);
187 SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7,
189 seq.addSequenceFeature(sf7);
191 // feature spanning B--C
192 SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f,
194 seq.addSequenceFeature(sf8);
195 // contact feature B/C
196 SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5,
198 seq.addSequenceFeature(sf9);
201 * let feature renderer discover features (and make visible)
203 fr.findAllFeatures(true);
204 features = fr.findFeaturesAtColumn(seq, 15); // all positional
205 assertEquals(features.size(), 6);
206 assertTrue(features.contains(sf2));
207 assertTrue(features.contains(sf3));
208 assertTrue(features.contains(sf4));
209 assertTrue(features.contains(sf5));
210 assertTrue(features.contains(sf6));
211 assertTrue(features.contains(sf7));
214 * at a non-contact position
216 features = fr.findFeaturesAtColumn(seq, 14);
217 assertEquals(features.size(), 3);
218 assertTrue(features.contains(sf2));
219 assertTrue(features.contains(sf3));
220 assertTrue(features.contains(sf4));
223 * make "Type2" not displayed
225 Object[][] data = new Object[4][];
226 FeatureColourI colour = new FeatureColour(Color.RED);
227 data[0] = new Object[] { "Type1", colour, true };
228 data[1] = new Object[] { "Type2", colour, false };
229 data[2] = new Object[] { "Type3", colour, true };
230 data[3] = new Object[] { "Disulphide Bond", colour, true };
231 fr.setFeaturePriority(data);
233 features = fr.findFeaturesAtColumn(seq, 15);
234 assertEquals(features.size(), 5); // no sf2
235 assertTrue(features.contains(sf3));
236 assertTrue(features.contains(sf4));
237 assertTrue(features.contains(sf5));
238 assertTrue(features.contains(sf6));
239 assertTrue(features.contains(sf7));
242 * make "Group2" not displayed
244 fr.setGroupVisibility("Group2", false);
246 features = fr.findFeaturesAtColumn(seq, 15);
247 assertEquals(features.size(), 3); // no sf2, sf3, sf6
248 assertTrue(features.contains(sf4));
249 assertTrue(features.contains(sf5));
250 assertTrue(features.contains(sf7));
252 // features 'at' a gap between b and c
253 // - returns enclosing feature BC but not contact feature B/C
254 features = fr.findFeaturesAtColumn(seq, 4);
255 assertEquals(features.size(), 1);
256 assertTrue(features.contains(sf8));
257 features = fr.findFeaturesAtColumn(seq, 5);
258 assertEquals(features.size(), 1);
259 assertTrue(features.contains(sf8));
262 * give "Type3" features a graduated colour scheme
263 * - first with no threshold
265 FeatureColourI gc = new FeatureColour(Color.yellow, Color.red, null, 0f,
267 fr.getFeatureColours().put("Type3", gc);
268 features = fr.findFeaturesAtColumn(seq, 8);
269 assertTrue(features.contains(sf4));
270 // now with threshold > 2f - feature score of 1f is excluded
271 gc.setAboveThreshold(true);
273 features = fr.findFeaturesAtColumn(seq, 8);
274 assertFalse(features.contains(sf4));
277 * make "Type3" graduated colour by attribute "AF"
278 * - first with no attribute held - feature should be excluded
280 gc.setAttributeName("AF");
281 features = fr.findFeaturesAtColumn(seq, 8);
282 assertFalse(features.contains(sf4));
283 // now with the attribute above threshold - should be included
284 sf4.setValue("AF", "2.4");
285 features = fr.findFeaturesAtColumn(seq, 8);
286 assertTrue(features.contains(sf4));
287 // now with the attribute below threshold - should be excluded
288 sf4.setValue("AF", "1.4");
289 features = fr.findFeaturesAtColumn(seq, 8);
290 assertFalse(features.contains(sf4));
293 @Test(groups = "Functional")
294 public void testFilterFeaturesForDisplay()
296 String seqData = ">s1\nabcdef\n";
297 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
298 DataSourceType.PASTE);
299 AlignViewportI av = af.getViewport();
300 FeatureRenderer fr = new FeatureRenderer(av);
302 List<SequenceFeature> features = new ArrayList<>();
303 fr.filterFeaturesForDisplay(features); // empty list, does nothing
305 SequenceI seq = av.getAlignment().getSequenceAt(0);
306 SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
308 seq.addSequenceFeature(sf1);
309 SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
311 seq.addSequenceFeature(sf2);
312 SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
314 seq.addSequenceFeature(sf3);
315 SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
317 seq.addSequenceFeature(sf4);
318 SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
320 seq.addSequenceFeature(sf5);
322 fr.findAllFeatures(true);
324 features = seq.getSequenceFeatures();
325 assertEquals(features.size(), 5);
326 assertTrue(features.contains(sf1));
327 assertTrue(features.contains(sf2));
328 assertTrue(features.contains(sf3));
329 assertTrue(features.contains(sf4));
330 assertTrue(features.contains(sf5));
333 * filter out duplicate (co-located) features
334 * note: which gets removed is not guaranteed
336 fr.filterFeaturesForDisplay(features);
337 assertEquals(features.size(), 3);
338 assertTrue(features.contains(sf1) || features.contains(sf4));
339 assertFalse(features.contains(sf1) && features.contains(sf4));
340 assertTrue(features.contains(sf2) || features.contains(sf3));
341 assertFalse(features.contains(sf2) && features.contains(sf3));
342 assertTrue(features.contains(sf5));
345 * hide groups 2 and 3 makes no difference to this method
347 fr.setGroupVisibility("group2", false);
348 fr.setGroupVisibility("group3", false);
349 features = seq.getSequenceFeatures();
350 fr.filterFeaturesForDisplay(features);
351 assertEquals(features.size(), 3);
352 assertTrue(features.contains(sf1) || features.contains(sf4));
353 assertFalse(features.contains(sf1) && features.contains(sf4));
354 assertTrue(features.contains(sf2) || features.contains(sf3));
355 assertFalse(features.contains(sf2) && features.contains(sf3));
356 assertTrue(features.contains(sf5));
359 @Test(groups = "Functional")
360 public void testGetColour()
362 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(">s1\nABCD\n",
363 DataSourceType.PASTE);
364 AlignViewportI av = af.getViewport();
365 FeatureRenderer fr = new FeatureRenderer(av);
368 * simple colour, feature type and group displayed
370 FeatureColourI fc = new FeatureColour(Color.red);
371 fr.getFeatureColours().put("Cath", fc);
372 SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
374 assertEquals(fr.getColour(sf1), Color.red);
377 * hide feature type, then unhide
379 Object[][] data = new Object[1][];
380 data[0] = new Object[] { "Cath", fc, false };
381 fr.setFeaturePriority(data);
382 assertNull(fr.getColour(sf1));
383 data[0] = new Object[] { "Cath", fc, true };
384 fr.setFeaturePriority(data);
385 assertEquals(fr.getColour(sf1), Color.red);
388 * hide feature group, then unhide
390 fr.setGroupVisibility("group1", false);
391 assertNull(fr.getColour(sf1));
392 fr.setGroupVisibility("group1", true);
393 assertEquals(fr.getColour(sf1), Color.red);
396 * graduated colour by score, no threshold, no score
399 FeatureColourI gc = new FeatureColour(Color.yellow, Color.red,
400 Color.green, 1f, 11f);
401 fr.getFeatureColours().put("Cath", gc);
402 assertEquals(fr.getColour(sf1), Color.green);
405 * graduated colour by score, no threshold, with score value
407 SequenceFeature sf2 = new SequenceFeature("Cath", "", 6, 8, 6f,
409 // score 6 is half way from yellow(255, 255, 0) to red(255, 0, 0)
410 assertEquals(fr.getColour(sf2), new Color(255, 128, 0));
413 * above threshold, score is above threshold - no change
415 gc.setAboveThreshold(true);
417 assertEquals(fr.getColour(sf2), new Color(255, 128, 0));
420 * threshold is min-max; now score 6 is 1/6 of the way from 5 to 11
421 * or from yellow(255, 255, 0) to red(255, 0, 0)
423 gc = new FeatureColour(Color.yellow, Color.red, Color.green, 5f, 11f);
424 fr.getFeatureColours().put("Cath", gc);
425 gc.setAutoScaled(false); // this does little other than save a checkbox setting!
426 assertEquals(fr.getColour(sf2), new Color(255, 213, 0));
429 * feature score is below threshold - no colour
431 gc.setAboveThreshold(true);
433 assertNull(fr.getColour(sf2));
436 * feature score is above threshold - no colour
438 gc.setBelowThreshold(true);
440 assertNull(fr.getColour(sf2));
443 * colour by feature attribute value
444 * first with no value held
446 gc = new FeatureColour(Color.yellow, Color.red, Color.green, 1f, 11f);
447 fr.getFeatureColours().put("Cath", gc);
448 gc.setAttributeName("AF");
449 assertEquals(fr.getColour(sf2), Color.green);
451 // with non-numeric attribute value
452 sf2.setValue("AF", "Five");
453 assertEquals(fr.getColour(sf2), Color.green);
455 // with numeric attribute value
456 sf2.setValue("AF", "6");
457 assertEquals(fr.getColour(sf2), new Color(255, 128, 0));
459 // with numeric value outwith threshold
460 gc.setAboveThreshold(true);
461 gc.setThreshold(10f);
462 assertNull(fr.getColour(sf2));
464 // with filter on AF < 4
465 gc.setAboveThreshold(false);
466 assertEquals(fr.getColour(sf2), new Color(255, 128, 0));
467 KeyedMatcherSetI filter = new KeyedMatcherSet();
468 filter.and(new KeyedMatcher(Condition.LT, 4f, "AF"));
469 fr.setFeatureFilter("Cath", filter);
470 assertNull(fr.getColour(sf2));
472 // with filter on 'Consequence contains missense'
473 filter = new KeyedMatcherSet();
474 filter.and(new KeyedMatcher(Condition.Contains, "missense",
476 fr.setFeatureFilter("Cath", filter);
477 // if feature has no Consequence attribute, no colour
478 assertNull(fr.getColour(sf2));
479 // if attribute does not match filter, no colour
480 sf2.setValue("Consequence", "Synonymous");
481 assertNull(fr.getColour(sf2));
482 // attribute matches filter
483 sf2.setValue("Consequence", "Missense variant");
484 assertEquals(fr.getColour(sf2), new Color(255, 128, 0));