2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.renderer.seqfeatures;
23 import static org.testng.Assert.assertEquals;
24 import static org.testng.Assert.assertFalse;
25 import static org.testng.Assert.assertNull;
26 import static org.testng.Assert.assertTrue;
28 import jalview.api.AlignViewportI;
29 import jalview.api.FeatureColourI;
30 import jalview.datamodel.SequenceFeature;
31 import jalview.datamodel.SequenceI;
32 import jalview.datamodel.features.FeatureMatcher;
33 import jalview.datamodel.features.FeatureMatcherSet;
34 import jalview.datamodel.features.FeatureMatcherSetI;
35 import jalview.gui.AlignFrame;
36 import jalview.io.DataSourceType;
37 import jalview.io.FileLoader;
38 import jalview.schemes.FeatureColour;
39 import jalview.util.matcher.Condition;
40 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
42 import java.awt.Color;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.HashMap;
46 import java.util.List;
49 import org.testng.annotations.Test;
51 public class FeatureRendererTest
54 @Test(groups = "Functional")
55 public void testFindAllFeatures()
57 String seqData = ">s1\nabcdef\n>s2\nabcdef\n>s3\nabcdef\n>s4\nabcdef\n";
58 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
59 DataSourceType.PASTE);
60 AlignViewportI av = af.getViewport();
61 FeatureRenderer fr = new FeatureRenderer(av);
66 fr.findAllFeatures(true);
67 assertTrue(fr.getRenderOrder().isEmpty());
68 assertTrue(fr.getFeatureGroups().isEmpty());
70 List<SequenceI> seqs = av.getAlignment().getSequences();
72 // add a non-positional feature - should be ignored by FeatureRenderer
73 SequenceFeature sf1 = new SequenceFeature("Type", "Desc", 0, 0, 1f,
75 seqs.get(0).addSequenceFeature(sf1);
76 fr.findAllFeatures(true);
77 // ? bug - types and groups added for non-positional features
78 List<String> types = fr.getRenderOrder();
79 List<String> groups = fr.getFeatureGroups();
80 assertEquals(types.size(), 0);
81 assertFalse(types.contains("Type"));
82 assertEquals(groups.size(), 0);
83 assertFalse(groups.contains("Group"));
85 // add some positional features
86 seqs.get(1).addSequenceFeature(
87 new SequenceFeature("Pfam", "Desc", 5, 9, 1f, "PfamGroup"));
88 seqs.get(2).addSequenceFeature(
89 new SequenceFeature("Pfam", "Desc", 14, 22, 2f, "RfamGroup"));
90 // bug in findAllFeatures - group not checked for a known feature type
91 seqs.get(2).addSequenceFeature(new SequenceFeature("Rfam", "Desc", 5, 9,
92 Float.NaN, "RfamGroup"));
93 // existing feature type with null group
94 seqs.get(3).addSequenceFeature(
95 new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null));
96 // new feature type with null group
97 seqs.get(3).addSequenceFeature(
98 new SequenceFeature("Scop", "Desc", 5, 9, Float.NaN, null));
99 // null value for type produces NullPointerException
100 fr.findAllFeatures(true);
101 types = fr.getRenderOrder();
102 groups = fr.getFeatureGroups();
103 assertEquals(types.size(), 3);
104 assertFalse(types.contains("Type"));
105 assertTrue(types.contains("Pfam"));
106 assertTrue(types.contains("Rfam"));
107 assertTrue(types.contains("Scop"));
108 assertEquals(groups.size(), 2);
109 assertFalse(groups.contains("Group"));
110 assertTrue(groups.contains("PfamGroup"));
111 assertTrue(groups.contains("RfamGroup"));
112 assertFalse(groups.contains(null)); // null group is ignored
115 * check min-max values
117 Map<String, float[][]> minMax = fr.getMinMax();
118 assertEquals(minMax.size(), 1); // non-positional and NaN not stored
119 assertEquals(minMax.get("Pfam")[0][0], 1f); // positional min
120 assertEquals(minMax.get("Pfam")[0][1], 2f); // positional max
122 // increase max for Pfam, add scores for Rfam
123 seqs.get(0).addSequenceFeature(
124 new SequenceFeature("Pfam", "Desc", 14, 22, 8f, "RfamGroup"));
125 seqs.get(1).addSequenceFeature(
126 new SequenceFeature("Rfam", "Desc", 5, 9, 6f, "RfamGroup"));
127 fr.findAllFeatures(true);
128 // note minMax is not a defensive copy, shouldn't expose this
129 assertEquals(minMax.size(), 2);
130 assertEquals(minMax.get("Pfam")[0][0], 1f);
131 assertEquals(minMax.get("Pfam")[0][1], 8f);
132 assertEquals(minMax.get("Rfam")[0][0], 6f);
133 assertEquals(minMax.get("Rfam")[0][1], 6f);
136 * check render order (last is on top)
138 List<String> renderOrder = fr.getRenderOrder();
139 assertEquals(renderOrder, Arrays.asList("Scop", "Rfam", "Pfam"));
142 * change render order (todo: an easier way)
143 * nb here last comes first in the data array
145 FeatureSettingsBean[] data = new FeatureSettingsBean[3];
146 FeatureColourI colour = new FeatureColour(Color.RED);
147 data[0] = new FeatureSettingsBean("Rfam", colour, null, true);
148 data[1] = new FeatureSettingsBean("Pfam", colour, null, false);
149 data[2] = new FeatureSettingsBean("Scop", colour, null, false);
150 fr.setFeaturePriority(data);
151 assertEquals(fr.getRenderOrder(),
152 Arrays.asList("Scop", "Pfam", "Rfam"));
153 assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam"));
156 * add a new feature type: should go on top of render order as visible,
157 * other feature ordering and visibility should be unchanged
159 seqs.get(2).addSequenceFeature(
160 new SequenceFeature("Metal", "Desc", 14, 22, 8f, "MetalGroup"));
161 fr.findAllFeatures(true);
162 assertEquals(fr.getRenderOrder(),
163 Arrays.asList("Scop", "Pfam", "Rfam", "Metal"));
164 assertEquals(fr.getDisplayedFeatureTypes(),
165 Arrays.asList("Rfam", "Metal"));
168 @Test(groups = "Functional")
169 public void testFindFeaturesAtColumn()
171 String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n";
172 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
173 DataSourceType.PASTE);
174 AlignViewportI av = af.getViewport();
175 FeatureRenderer fr = new FeatureRenderer(av);
176 SequenceI seq = av.getAlignment().getSequenceAt(0);
181 List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, 3);
182 assertTrue(features.isEmpty());
187 SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f,
188 "Group"); // non-positional
189 seq.addSequenceFeature(sf1);
190 SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f,
192 seq.addSequenceFeature(sf2);
193 SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
195 seq.addSequenceFeature(sf3);
196 SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
197 null); // null group is always treated as visible
198 seq.addSequenceFeature(sf4);
201 * add contact features
203 SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7,
205 seq.addSequenceFeature(sf5);
206 SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7,
208 seq.addSequenceFeature(sf6);
209 SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7,
211 seq.addSequenceFeature(sf7);
213 // feature spanning B--C
214 SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f,
216 seq.addSequenceFeature(sf8);
217 // contact feature B/C
218 SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5,
220 seq.addSequenceFeature(sf9);
223 * let feature renderer discover features (and make visible)
225 fr.findAllFeatures(true);
226 features = fr.findFeaturesAtColumn(seq, 15); // all positional
227 assertEquals(features.size(), 6);
228 assertTrue(features.contains(sf2));
229 assertTrue(features.contains(sf3));
230 assertTrue(features.contains(sf4));
231 assertTrue(features.contains(sf5));
232 assertTrue(features.contains(sf6));
233 assertTrue(features.contains(sf7));
236 * at a non-contact position
238 features = fr.findFeaturesAtColumn(seq, 14);
239 assertEquals(features.size(), 3);
240 assertTrue(features.contains(sf2));
241 assertTrue(features.contains(sf3));
242 assertTrue(features.contains(sf4));
245 * make "Type2" not displayed
247 FeatureColourI colour = new FeatureColour(Color.RED);
248 FeatureSettingsBean[] data = new FeatureSettingsBean[4];
249 data[0] = new FeatureSettingsBean("Type1", colour, null, true);
250 data[1] = new FeatureSettingsBean("Type2", colour, null, false);
251 data[2] = new FeatureSettingsBean("Type3", colour, null, true);
252 data[3] = new FeatureSettingsBean("Disulphide Bond", colour, null,
254 fr.setFeaturePriority(data);
256 features = fr.findFeaturesAtColumn(seq, 15);
257 assertEquals(features.size(), 5); // no sf2
258 assertTrue(features.contains(sf3));
259 assertTrue(features.contains(sf4));
260 assertTrue(features.contains(sf5));
261 assertTrue(features.contains(sf6));
262 assertTrue(features.contains(sf7));
265 * make "Group2" not displayed
267 fr.setGroupVisibility("Group2", false);
269 features = fr.findFeaturesAtColumn(seq, 15);
270 assertEquals(features.size(), 3); // no sf2, sf3, sf6
271 assertTrue(features.contains(sf4));
272 assertTrue(features.contains(sf5));
273 assertTrue(features.contains(sf7));
275 // features 'at' a gap between b and c
276 // - returns enclosing feature BC but not contact feature B/C
277 features = fr.findFeaturesAtColumn(seq, 4);
278 assertEquals(features.size(), 1);
279 assertTrue(features.contains(sf8));
280 features = fr.findFeaturesAtColumn(seq, 5);
281 assertEquals(features.size(), 1);
282 assertTrue(features.contains(sf8));
285 * give "Type3" features a graduated colour scheme
286 * - first with no threshold
288 FeatureColourI gc = new FeatureColour(Color.yellow, Color.red, null, 0f,
290 fr.getFeatureColours().put("Type3", gc);
291 features = fr.findFeaturesAtColumn(seq, 8);
292 assertTrue(features.contains(sf4));
293 // now with threshold > 2f - feature score of 1f is excluded
294 gc.setAboveThreshold(true);
296 features = fr.findFeaturesAtColumn(seq, 8);
297 assertFalse(features.contains(sf4));
300 * make "Type3" graduated colour by attribute "AF"
301 * - first with no attribute held - feature should be excluded
303 gc.setAttributeName("AF");
304 features = fr.findFeaturesAtColumn(seq, 8);
305 assertFalse(features.contains(sf4));
306 // now with the attribute above threshold - should be included
307 sf4.setValue("AF", "2.4");
308 features = fr.findFeaturesAtColumn(seq, 8);
309 assertTrue(features.contains(sf4));
310 // now with the attribute below threshold - should be excluded
311 sf4.setValue("AF", "1.4");
312 features = fr.findFeaturesAtColumn(seq, 8);
313 assertFalse(features.contains(sf4));
316 @Test(groups = "Functional")
317 public void testFilterFeaturesForDisplay()
319 String seqData = ">s1\nabcdef\n";
320 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
321 DataSourceType.PASTE);
322 AlignViewportI av = af.getViewport();
323 FeatureRenderer fr = new FeatureRenderer(av);
325 List<SequenceFeature> features = new ArrayList<>();
326 fr.filterFeaturesForDisplay(features); // empty list, does nothing
328 SequenceI seq = av.getAlignment().getSequenceAt(0);
329 SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
331 seq.addSequenceFeature(sf1);
332 SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
334 seq.addSequenceFeature(sf2);
335 SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
337 seq.addSequenceFeature(sf3);
338 SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
340 seq.addSequenceFeature(sf4);
341 SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
343 seq.addSequenceFeature(sf5);
345 fr.findAllFeatures(true);
347 features = seq.getSequenceFeatures();
348 assertEquals(features.size(), 5);
349 assertTrue(features.contains(sf1));
350 assertTrue(features.contains(sf2));
351 assertTrue(features.contains(sf3));
352 assertTrue(features.contains(sf4));
353 assertTrue(features.contains(sf5));
356 * filter out duplicate (co-located) features
357 * note: which gets removed is not guaranteed
359 fr.filterFeaturesForDisplay(features);
360 assertEquals(features.size(), 3);
361 assertTrue(features.contains(sf1) || features.contains(sf4));
362 assertFalse(features.contains(sf1) && features.contains(sf4));
363 assertTrue(features.contains(sf2) || features.contains(sf3));
364 assertFalse(features.contains(sf2) && features.contains(sf3));
365 assertTrue(features.contains(sf5));
368 * hide groups 2 and 3 makes no difference to this method
370 fr.setGroupVisibility("group2", false);
371 fr.setGroupVisibility("group3", false);
372 features = seq.getSequenceFeatures();
373 fr.filterFeaturesForDisplay(features);
374 assertEquals(features.size(), 3);
375 assertTrue(features.contains(sf1) || features.contains(sf4));
376 assertFalse(features.contains(sf1) && features.contains(sf4));
377 assertTrue(features.contains(sf2) || features.contains(sf3));
378 assertFalse(features.contains(sf2) && features.contains(sf3));
379 assertTrue(features.contains(sf5));
382 * no filtering if transparency is applied
384 fr.setTransparency(0.5f);
385 features = seq.getSequenceFeatures();
386 fr.filterFeaturesForDisplay(features);
387 assertEquals(features.size(), 5);
390 @Test(groups = "Functional")
391 public void testGetColour()
393 AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(">s1\nABCD\n",
394 DataSourceType.PASTE);
395 AlignViewportI av = af.getViewport();
396 FeatureRenderer fr = new FeatureRenderer(av);
399 * simple colour, feature type and group displayed
401 FeatureColourI fc = new FeatureColour(Color.red);
402 fr.getFeatureColours().put("Cath", fc);
403 SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
405 assertEquals(fr.getColour(sf1), Color.red);
408 * hide feature type, then unhide
409 * - feature type visibility should not affect the result
411 FeatureSettingsBean[] data = new FeatureSettingsBean[1];
412 data[0] = new FeatureSettingsBean("Cath", fc, null, false);
413 fr.setFeaturePriority(data);
414 assertEquals(fr.getColour(sf1), Color.red);
415 data[0] = new FeatureSettingsBean("Cath", fc, null, true);
416 fr.setFeaturePriority(data);
417 assertEquals(fr.getColour(sf1), Color.red);
420 * hide feature group, then unhide
422 fr.setGroupVisibility("group1", false);
423 assertNull(fr.getColour(sf1));
424 fr.setGroupVisibility("group1", true);
425 assertEquals(fr.getColour(sf1), Color.red);
428 * graduated colour by score, no threshold, no score
431 FeatureColourI gc = new FeatureColour(Color.yellow, Color.red,
432 Color.green, 1f, 11f);
433 fr.getFeatureColours().put("Cath", gc);
434 assertEquals(fr.getColour(sf1), Color.green);
437 * graduated colour by score, no threshold, with score value
439 SequenceFeature sf2 = new SequenceFeature("Cath", "", 6, 8, 6f,
441 // score 6 is half way from yellow(255, 255, 0) to red(255, 0, 0)
442 Color expected = new Color(255, 128, 0);
443 assertEquals(fr.getColour(sf2), expected);
446 * above threshold, score is above threshold - no change
448 gc.setAboveThreshold(true);
450 assertEquals(fr.getColour(sf2), expected);
453 * threshold is min-max; now score 6 is 1/6 of the way from 5 to 11
454 * or from yellow(255, 255, 0) to red(255, 0, 0)
456 gc = new FeatureColour(Color.yellow, Color.red, Color.green, 5f, 11f);
457 fr.getFeatureColours().put("Cath", gc);
458 gc.setAutoScaled(false); // this does little other than save a checkbox setting!
459 assertEquals(fr.getColour(sf2), new Color(255, 213, 0));
462 * feature score is below threshold - no colour
464 gc.setAboveThreshold(true);
466 assertNull(fr.getColour(sf2));
469 * feature score is above threshold - no colour
471 gc.setBelowThreshold(true);
473 assertNull(fr.getColour(sf2));
476 * colour by feature attribute value
477 * first with no value held
479 gc = new FeatureColour(Color.yellow, Color.red, Color.green, 1f, 11f);
480 fr.getFeatureColours().put("Cath", gc);
481 gc.setAttributeName("AF");
482 assertEquals(fr.getColour(sf2), Color.green);
484 // with non-numeric attribute value
485 sf2.setValue("AF", "Five");
486 assertEquals(fr.getColour(sf2), Color.green);
488 // with numeric attribute value
489 sf2.setValue("AF", "6");
490 assertEquals(fr.getColour(sf2), expected);
492 // with numeric value outwith threshold
493 gc.setAboveThreshold(true);
494 gc.setThreshold(10f);
495 assertNull(fr.getColour(sf2));
497 // with filter on AF < 4
498 gc.setAboveThreshold(false);
499 assertEquals(fr.getColour(sf2), expected);
500 FeatureMatcherSetI filter = new FeatureMatcherSet();
501 filter.and(FeatureMatcher.byAttribute(Condition.LT, "4.0", "AF"));
502 fr.setFeatureFilter("Cath", filter);
503 assertNull(fr.getColour(sf2));
505 // with filter on 'Consequence contains missense'
506 filter = new FeatureMatcherSet();
507 filter.and(FeatureMatcher.byAttribute(Condition.Contains, "missense",
509 fr.setFeatureFilter("Cath", filter);
510 // if feature has no Consequence attribute, no colour
511 assertNull(fr.getColour(sf2));
512 // if attribute does not match filter, no colour
513 sf2.setValue("Consequence", "Synonymous");
514 assertNull(fr.getColour(sf2));
515 // attribute matches filter
516 sf2.setValue("Consequence", "Missense variant");
517 assertEquals(fr.getColour(sf2), expected);
519 // with filter on CSQ:Feature contains "ENST01234"
520 filter = new FeatureMatcherSet();
521 filter.and(FeatureMatcher.byAttribute(Condition.Matches, "ENST01234",
523 fr.setFeatureFilter("Cath", filter);
524 // if feature has no CSQ data, no colour
525 assertNull(fr.getColour(sf2));
526 // if CSQ data does not include Feature, no colour
527 Map<String, String> csqData = new HashMap<>();
528 csqData.put("BIOTYPE", "Transcript");
529 sf2.setValue("CSQ", csqData);
530 assertNull(fr.getColour(sf2));
531 // if attribute does not match filter, no colour
532 csqData.put("Feature", "ENST9876");
533 assertNull(fr.getColour(sf2));
534 // attribute matches filter
535 csqData.put("Feature", "ENST01234");
536 assertEquals(fr.getColour(sf2), expected);