2e04ecb663b96a353cfe0bcd700fa4829f28ad95
[jalview.git] / test / jalview / renderer / seqfeatures / FeatureRendererTest.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
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.
11  *  
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.
16  * 
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.
20  */
21 package jalview.renderer.seqfeatures;
22
23 import static org.testng.Assert.assertEquals;
24 import static org.testng.Assert.assertFalse;
25 import static org.testng.Assert.assertNotNull;
26 import static org.testng.Assert.assertNull;
27 import static org.testng.Assert.assertSame;
28 import static org.testng.Assert.assertTrue;
29
30 import jalview.analysis.GeneticCodes;
31 import jalview.api.AlignViewportI;
32 import jalview.api.FeatureColourI;
33 import jalview.bin.Jalview;
34 import jalview.datamodel.MappedFeatures;
35 import jalview.datamodel.SequenceFeature;
36 import jalview.datamodel.SequenceI;
37 import jalview.datamodel.features.FeatureMatcher;
38 import jalview.datamodel.features.FeatureMatcherSet;
39 import jalview.datamodel.features.FeatureMatcherSetI;
40 import jalview.gui.AlignFrame;
41 import jalview.gui.AlignViewport;
42 import jalview.gui.Desktop;
43 import jalview.io.DataSourceType;
44 import jalview.io.FileLoader;
45 import jalview.schemes.FeatureColour;
46 import jalview.util.matcher.Condition;
47 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
48
49 import java.awt.Color;
50 import java.util.ArrayList;
51 import java.util.Arrays;
52 import java.util.HashMap;
53 import java.util.List;
54 import java.util.Map;
55
56 import org.testng.annotations.Test;
57
58 public class FeatureRendererTest
59 {
60
61   @Test(groups = "Functional")
62   public void testFindAllFeatures()
63   {
64     String seqData = ">s1\nabcdef\n>s2\nabcdef\n>s3\nabcdef\n>s4\nabcdef\n";
65     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
66             DataSourceType.PASTE);
67     AlignViewportI av = af.getViewport();
68     FeatureRenderer fr = new FeatureRenderer(av);
69
70     /*
71      * with no features
72      */
73     fr.findAllFeatures(true);
74     assertTrue(fr.getRenderOrder().isEmpty());
75     assertTrue(fr.getFeatureGroups().isEmpty());
76
77     List<SequenceI> seqs = av.getAlignment().getSequences();
78
79     // add a non-positional feature - should be ignored by FeatureRenderer
80     SequenceFeature sf1 = new SequenceFeature("Type", "Desc", 0, 0, 1f,
81             "Group");
82     seqs.get(0).addSequenceFeature(sf1);
83     fr.findAllFeatures(true);
84     // ? bug - types and groups added for non-positional features
85     List<String> types = fr.getRenderOrder();
86     List<String> groups = fr.getFeatureGroups();
87     assertEquals(types.size(), 0);
88     assertFalse(types.contains("Type"));
89     assertEquals(groups.size(), 0);
90     assertFalse(groups.contains("Group"));
91
92     // add some positional features
93     seqs.get(1).addSequenceFeature(
94             new SequenceFeature("Pfam", "Desc", 5, 9, 1f, "PfamGroup"));
95     seqs.get(2).addSequenceFeature(
96             new SequenceFeature("Pfam", "Desc", 14, 22, 2f, "RfamGroup"));
97     // bug in findAllFeatures - group not checked for a known feature type
98     seqs.get(2).addSequenceFeature(new SequenceFeature("Rfam", "Desc", 5, 9,
99             Float.NaN, "RfamGroup"));
100     // existing feature type with null group
101     seqs.get(3).addSequenceFeature(
102             new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null));
103     // new feature type with null group
104     seqs.get(3).addSequenceFeature(
105             new SequenceFeature("Scop", "Desc", 5, 9, Float.NaN, null));
106     // null value for type produces NullPointerException
107     fr.findAllFeatures(true);
108     types = fr.getRenderOrder();
109     groups = fr.getFeatureGroups();
110     assertEquals(types.size(), 3);
111     assertFalse(types.contains("Type"));
112     assertTrue(types.contains("Pfam"));
113     assertTrue(types.contains("Rfam"));
114     assertTrue(types.contains("Scop"));
115     assertEquals(groups.size(), 2);
116     assertFalse(groups.contains("Group"));
117     assertTrue(groups.contains("PfamGroup"));
118     assertTrue(groups.contains("RfamGroup"));
119     assertFalse(groups.contains(null)); // null group is ignored
120
121     /*
122      * check min-max values
123      */
124     Map<String, float[][]> minMax = fr.getMinMax();
125     assertEquals(minMax.size(), 1); // non-positional and NaN not stored
126     assertEquals(minMax.get("Pfam")[0][0], 1f); // positional min
127     assertEquals(minMax.get("Pfam")[0][1], 2f); // positional max
128
129     // increase max for Pfam, add scores for Rfam
130     seqs.get(0).addSequenceFeature(
131             new SequenceFeature("Pfam", "Desc", 14, 22, 8f, "RfamGroup"));
132     seqs.get(1).addSequenceFeature(
133             new SequenceFeature("Rfam", "Desc", 5, 9, 6f, "RfamGroup"));
134     fr.findAllFeatures(true);
135     // note minMax is not a defensive copy, shouldn't expose this
136     assertEquals(minMax.size(), 2);
137     assertEquals(minMax.get("Pfam")[0][0], 1f);
138     assertEquals(minMax.get("Pfam")[0][1], 8f);
139     assertEquals(minMax.get("Rfam")[0][0], 6f);
140     assertEquals(minMax.get("Rfam")[0][1], 6f);
141
142     /*
143      * check render order (last is on top)
144      */
145     List<String> renderOrder = fr.getRenderOrder();
146     assertEquals(renderOrder, Arrays.asList("Scop", "Rfam", "Pfam"));
147
148     /*
149      * change render order (todo: an easier way)
150      * nb here last comes first in the data array
151      */
152     FeatureSettingsBean[] data = new FeatureSettingsBean[3];
153     FeatureColourI colour = new FeatureColour(Color.RED);
154     data[0] = new FeatureSettingsBean("Rfam", colour, null, true);
155     data[1] = new FeatureSettingsBean("Pfam", colour, null, false);
156     data[2] = new FeatureSettingsBean("Scop", colour, null, false);
157     fr.setFeaturePriority(data);
158     assertEquals(fr.getRenderOrder(),
159             Arrays.asList("Scop", "Pfam", "Rfam"));
160     assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam"));
161
162     /*
163      * add a new feature type: should go on top of render order as visible,
164      * other feature ordering and visibility should be unchanged
165      */
166     seqs.get(2).addSequenceFeature(
167             new SequenceFeature("Metal", "Desc", 14, 22, 8f, "MetalGroup"));
168     fr.findAllFeatures(true);
169     assertEquals(fr.getRenderOrder(),
170             Arrays.asList("Scop", "Pfam", "Rfam", "Metal"));
171     assertEquals(fr.getDisplayedFeatureTypes(),
172             Arrays.asList("Rfam", "Metal"));
173   }
174
175   @Test(groups = "Functional")
176   public void testFindFeaturesAtColumn()
177   {
178     String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n";
179     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
180             DataSourceType.PASTE);
181     AlignViewportI av = af.getViewport();
182     FeatureRenderer fr = new FeatureRenderer(av);
183     SequenceI seq = av.getAlignment().getSequenceAt(0);
184
185     /*
186      * with no features
187      */
188     List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, 3);
189     assertTrue(features.isEmpty());
190
191     /*
192      * add features
193      */
194     SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f,
195             "Group"); // non-positional
196     seq.addSequenceFeature(sf1);
197     SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f,
198             "Group1");
199     seq.addSequenceFeature(sf2);
200     SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
201             "Group2");
202     seq.addSequenceFeature(sf3);
203     SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
204             null); // null group is always treated as visible
205     seq.addSequenceFeature(sf4);
206
207     /*
208      * add contact features
209      */
210     SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7,
211             15, 1f, "Group1");
212     seq.addSequenceFeature(sf5);
213     SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7,
214             15, 1f, "Group2");
215     seq.addSequenceFeature(sf6);
216     SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7,
217             15, 1f, null);
218     seq.addSequenceFeature(sf7);
219
220     // feature spanning B--C
221     SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f,
222             "Group");
223     seq.addSequenceFeature(sf8);
224     // contact feature B/C
225     SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5,
226             6, 1f, "Group");
227     seq.addSequenceFeature(sf9);
228
229     /*
230      * let feature renderer discover features (and make visible)
231      */
232     fr.findAllFeatures(true);
233     features = fr.findFeaturesAtColumn(seq, 15); // all positional
234     assertEquals(features.size(), 6);
235     assertTrue(features.contains(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      * at a non-contact position
244      */
245     features = fr.findFeaturesAtColumn(seq, 14);
246     assertEquals(features.size(), 3);
247     assertTrue(features.contains(sf2));
248     assertTrue(features.contains(sf3));
249     assertTrue(features.contains(sf4));
250
251     /*
252      * make "Type2" not displayed
253      */
254     FeatureColourI colour = new FeatureColour(Color.RED);
255     FeatureSettingsBean[] data = new FeatureSettingsBean[4];
256     data[0] = new FeatureSettingsBean("Type1", colour, null, true);
257     data[1] = new FeatureSettingsBean("Type2", colour, null, false);
258     data[2] = new FeatureSettingsBean("Type3", colour, null, true);
259     data[3] = new FeatureSettingsBean("Disulphide Bond", colour, null,
260             true);
261     fr.setFeaturePriority(data);
262
263     features = fr.findFeaturesAtColumn(seq, 15);
264     assertEquals(features.size(), 5); // no sf2
265     assertTrue(features.contains(sf3));
266     assertTrue(features.contains(sf4));
267     assertTrue(features.contains(sf5));
268     assertTrue(features.contains(sf6));
269     assertTrue(features.contains(sf7));
270
271     /*
272      * make "Group2" not displayed
273      */
274     fr.setGroupVisibility("Group2", false);
275
276     features = fr.findFeaturesAtColumn(seq, 15);
277     assertEquals(features.size(), 3); // no sf2, sf3, sf6
278     assertTrue(features.contains(sf4));
279     assertTrue(features.contains(sf5));
280     assertTrue(features.contains(sf7));
281
282     // features 'at' a gap between b and c
283     // - returns enclosing feature BC but not contact feature B/C
284     features = fr.findFeaturesAtColumn(seq, 4);
285     assertEquals(features.size(), 1);
286     assertTrue(features.contains(sf8));
287     features = fr.findFeaturesAtColumn(seq, 5);
288     assertEquals(features.size(), 1);
289     assertTrue(features.contains(sf8));
290
291     /*
292      * give "Type3" features a graduated colour scheme
293      * - first with no threshold
294      */
295     FeatureColourI gc = new FeatureColour(Color.green, Color.yellow,
296             Color.red, null, 0f, 10f);
297     fr.getFeatureColours().put("Type3", gc);
298     features = fr.findFeaturesAtColumn(seq, 8);
299     assertTrue(features.contains(sf4));
300     // now with threshold > 2f - feature score of 1f is excluded
301     gc.setAboveThreshold(true);
302     gc.setThreshold(2f);
303     features = fr.findFeaturesAtColumn(seq, 8);
304     assertFalse(features.contains(sf4));
305
306     /*
307      * make "Type3" graduated colour by attribute "AF"
308      * - first with no attribute held - feature should be excluded
309      */
310     gc.setAttributeName("AF");
311     features = fr.findFeaturesAtColumn(seq, 8);
312     assertFalse(features.contains(sf4));
313     // now with the attribute above threshold - should be included
314     sf4.setValue("AF", "2.4");
315     features = fr.findFeaturesAtColumn(seq, 8);
316     assertTrue(features.contains(sf4));
317     // now with the attribute below threshold - should be excluded
318     sf4.setValue("AF", "1.4");
319     features = fr.findFeaturesAtColumn(seq, 8);
320     assertFalse(features.contains(sf4));
321   }
322
323   @Test(groups = "Functional")
324   public void testFilterFeaturesForDisplay()
325   {
326     String seqData = ">s1\nabcdef\n";
327     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
328             DataSourceType.PASTE);
329     AlignViewportI av = af.getViewport();
330     FeatureRenderer fr = new FeatureRenderer(av);
331
332     List<SequenceFeature> features = new ArrayList<>();
333     fr.filterFeaturesForDisplay(features); // empty list, does nothing
334
335     SequenceI seq = av.getAlignment().getSequenceAt(0);
336     SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
337             "group1");
338     SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
339             "group2");
340     SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
341             "group3");
342     SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
343             "group4");
344     SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
345             "group4");
346     seq.addSequenceFeature(sf1);
347     seq.addSequenceFeature(sf2);
348     seq.addSequenceFeature(sf3);
349     seq.addSequenceFeature(sf4);
350     seq.addSequenceFeature(sf5);
351
352     fr.findAllFeatures(true);
353
354     features = seq.getSequenceFeatures();
355     assertEquals(features.size(), 5);
356     assertTrue(features.contains(sf1));
357     assertTrue(features.contains(sf2));
358     assertTrue(features.contains(sf3));
359     assertTrue(features.contains(sf4));
360     assertTrue(features.contains(sf5));
361
362     /*
363      * filter out duplicate (co-located) features
364      * note: which gets removed is not guaranteed
365      */
366     fr.filterFeaturesForDisplay(features);
367     assertEquals(features.size(), 3);
368     assertTrue(features.contains(sf1) || features.contains(sf4));
369     assertFalse(features.contains(sf1) && features.contains(sf4));
370     assertTrue(features.contains(sf2) || features.contains(sf3));
371     assertFalse(features.contains(sf2) && features.contains(sf3));
372     assertTrue(features.contains(sf5));
373
374     /*
375      * features in hidden groups are removed
376      */
377     fr.setGroupVisibility("group2", false);
378     fr.setGroupVisibility("group3", false);
379     features = seq.getSequenceFeatures();
380     fr.filterFeaturesForDisplay(features);
381     assertEquals(features.size(), 2);
382     assertTrue(features.contains(sf1) || features.contains(sf4));
383     assertFalse(features.contains(sf1) && features.contains(sf4));
384     assertFalse(features.contains(sf2));
385     assertFalse(features.contains(sf3));
386     assertTrue(features.contains(sf5));
387
388     /*
389      * no filtering if transparency is applied
390      */
391     fr.setTransparency(0.5f);
392     features = seq.getSequenceFeatures();
393     fr.filterFeaturesForDisplay(features);
394     assertEquals(features.size(), 5);
395   }
396
397   @Test(groups = "Functional")
398   public void testGetColour()
399   {
400     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(">s1\nABCD\n",
401             DataSourceType.PASTE);
402     AlignViewportI av = af.getViewport();
403     FeatureRenderer fr = new FeatureRenderer(av);
404
405     /*
406      * simple colour, feature type and group displayed
407      */
408     FeatureColourI fc = new FeatureColour(Color.red);
409     fr.getFeatureColours().put("Cath", fc);
410     SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
411             "group1");
412     assertEquals(fr.getColour(sf1), Color.red);
413
414     /*
415      * hide feature type, then unhide
416      * - feature type visibility should not affect the result
417      */
418     FeatureSettingsBean[] data = new FeatureSettingsBean[1];
419     data[0] = new FeatureSettingsBean("Cath", fc, null, false);
420     fr.setFeaturePriority(data);
421     assertEquals(fr.getColour(sf1), Color.red);
422     data[0] = new FeatureSettingsBean("Cath", fc, null, true);
423     fr.setFeaturePriority(data);
424     assertEquals(fr.getColour(sf1), Color.red);
425
426     /*
427      * hide feature group, then unhide
428      */
429     fr.setGroupVisibility("group1", false);
430     assertNull(fr.getColour(sf1));
431     fr.setGroupVisibility("group1", true);
432     assertEquals(fr.getColour(sf1), Color.red);
433
434     /*
435      * graduated colour by score, no threshold, no score
436      * 
437      */
438     FeatureColourI gc = new FeatureColour(Color.red, Color.yellow,
439             Color.red, Color.green, 1f, 11f);
440     fr.getFeatureColours().put("Cath", gc);
441     assertEquals(fr.getColour(sf1), Color.green);
442
443     /*
444      * graduated colour by score, no threshold, with score value
445      */
446     SequenceFeature sf2 = new SequenceFeature("Cath", "", 6, 8, 6f,
447             "group1");
448     // score 6 is half way from yellow(255, 255, 0) to red(255, 0, 0)
449     Color expected = new Color(255, 128, 0);
450     assertEquals(fr.getColour(sf2), expected);
451
452     /*
453      * above threshold, score is above threshold - no change
454      */
455     gc.setAboveThreshold(true);
456     gc.setThreshold(5f);
457     assertEquals(fr.getColour(sf2), expected);
458
459     /*
460      * threshold is min-max; now score 6 is 1/6 of the way from 5 to 11
461      * or from yellow(255, 255, 0) to red(255, 0, 0)
462      */
463     gc = new FeatureColour(Color.red, Color.yellow, Color.red, Color.green,
464             5f, 11f);
465     fr.getFeatureColours().put("Cath", gc);
466     gc.setAutoScaled(false); // this does little other than save a checkbox setting!
467     assertEquals(fr.getColour(sf2), new Color(255, 213, 0));
468
469     /*
470      * feature score is below threshold - no colour
471      */
472     gc.setAboveThreshold(true);
473     gc.setThreshold(7f);
474     assertNull(fr.getColour(sf2));
475
476     /*
477      * feature score is above threshold - no colour
478      */
479     gc.setBelowThreshold(true);
480     gc.setThreshold(3f);
481     assertNull(fr.getColour(sf2));
482
483     /*
484      * colour by feature attribute value
485      * first with no value held
486      */
487     gc = new FeatureColour(Color.red, Color.yellow, Color.red, Color.green,
488             1f, 11f);
489     fr.getFeatureColours().put("Cath", gc);
490     gc.setAttributeName("AF");
491     assertEquals(fr.getColour(sf2), Color.green);
492
493     // with non-numeric attribute value
494     sf2.setValue("AF", "Five");
495     assertEquals(fr.getColour(sf2), Color.green);
496
497     // with numeric attribute value
498     sf2.setValue("AF", "6");
499     assertEquals(fr.getColour(sf2), expected);
500
501     // with numeric value outwith threshold
502     gc.setAboveThreshold(true);
503     gc.setThreshold(10f);
504     assertNull(fr.getColour(sf2));
505
506     // with filter on AF < 4
507     gc.setAboveThreshold(false);
508     assertEquals(fr.getColour(sf2), expected);
509     FeatureMatcherSetI filter = new FeatureMatcherSet();
510     filter.and(FeatureMatcher.byAttribute(Condition.LT, "4.0", "AF"));
511     fr.setFeatureFilter("Cath", filter);
512     assertNull(fr.getColour(sf2));
513
514     // with filter on 'Consequence contains missense'
515     filter = new FeatureMatcherSet();
516     filter.and(FeatureMatcher.byAttribute(Condition.Contains, "missense",
517             "Consequence"));
518     fr.setFeatureFilter("Cath", filter);
519     // if feature has no Consequence attribute, no colour
520     assertNull(fr.getColour(sf2));
521     // if attribute does not match filter, no colour
522     sf2.setValue("Consequence", "Synonymous");
523     assertNull(fr.getColour(sf2));
524     // attribute matches filter
525     sf2.setValue("Consequence", "Missense variant");
526     assertEquals(fr.getColour(sf2), expected);
527
528     // with filter on CSQ:Feature contains "ENST01234"
529     filter = new FeatureMatcherSet();
530     filter.and(FeatureMatcher.byAttribute(Condition.Matches, "ENST01234",
531             "CSQ", "Feature"));
532     fr.setFeatureFilter("Cath", filter);
533     // if feature has no CSQ data, no colour
534     assertNull(fr.getColour(sf2));
535     // if CSQ data does not include Feature, no colour
536     Map<String, String> csqData = new HashMap<>();
537     csqData.put("BIOTYPE", "Transcript");
538     sf2.setValue("CSQ", csqData);
539     assertNull(fr.getColour(sf2));
540     // if attribute does not match filter, no colour
541     csqData.put("Feature", "ENST9876");
542     assertNull(fr.getColour(sf2));
543     // attribute matches filter
544     csqData.put("Feature", "ENST01234");
545     assertEquals(fr.getColour(sf2), expected);
546   }
547
548   @Test(groups = "Functional")
549   public void testIsVisible()
550   {
551     String seqData = ">s1\nMLQGIFPRS\n";
552     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
553             DataSourceType.PASTE);
554     AlignViewportI av = af.getViewport();
555     FeatureRenderer fr = new FeatureRenderer(av);
556     SequenceI seq = av.getAlignment().getSequenceAt(0);
557     SequenceFeature sf = new SequenceFeature("METAL", "Desc", 10, 10, 1f,
558             "Group");
559     sf.setValue("AC", "11");
560     sf.setValue("CLIN_SIG", "Likely Pathogenic");
561     seq.addSequenceFeature(sf);
562
563     assertFalse(fr.isVisible(null));
564
565     /*
566      * initial state FeatureRenderer hasn't 'found' feature
567      * and so its feature type has not yet been set visible
568      */
569     assertFalse(fr.getDisplayedFeatureCols().containsKey("METAL"));
570     assertFalse(fr.isVisible(sf));
571
572     fr.findAllFeatures(true);
573     assertTrue(fr.isVisible(sf));
574
575     /*
576      * feature group not visible
577      */
578     fr.setGroupVisibility("Group", false);
579     assertFalse(fr.isVisible(sf));
580     fr.setGroupVisibility("Group", true);
581     assertTrue(fr.isVisible(sf));
582
583     /*
584      * feature score outwith colour threshold (score > 2)
585      */
586     FeatureColourI fc = new FeatureColour(null, Color.white, Color.black,
587             Color.white, 0, 10);
588     fc.setAboveThreshold(true);
589     fc.setThreshold(2f);
590     fr.setColour("METAL", fc);
591     assertFalse(fr.isVisible(sf)); // score 1 is not above threshold 2
592     fc.setBelowThreshold(true);
593     assertTrue(fr.isVisible(sf)); // score 1 is below threshold 2
594
595     /*
596      * colour with threshold on attribute AC (value is 11)
597      */
598     fc.setAttributeName("AC");
599     assertFalse(fr.isVisible(sf)); // value 11 is not below threshold 2
600     fc.setAboveThreshold(true);
601     assertTrue(fr.isVisible(sf)); // value 11 is above threshold 2
602
603     fc.setAttributeName("AF"); // attribute AF is absent in sf
604     assertTrue(fr.isVisible(sf)); // feature is not excluded by threshold
605
606     FeatureMatcherSetI filter = new FeatureMatcherSet();
607     filter.and(FeatureMatcher.byAttribute(Condition.Contains, "pathogenic",
608             "CLIN_SIG"));
609     fr.setFeatureFilter("METAL", filter);
610     assertTrue(fr.isVisible(sf)); // feature matches filter
611     filter.and(FeatureMatcher.byScore(Condition.LE, "0.4"));
612     assertFalse(fr.isVisible(sf)); // feature doesn't match filter
613   }
614
615   @Test(groups = "Functional")
616   public void testFindComplementFeaturesAtResidue()
617   {
618     Jalview.main(
619             new String[]
620             { "-nonews", "-props", "test/jalview/testProps.jvprops" });
621
622     // codons for MCWHSE
623     String cdsSeq = ">cds\nATGtgtTGGcacTCAgaa";
624     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(cdsSeq,
625             DataSourceType.PASTE);
626     af.showTranslation_actionPerformed(
627             GeneticCodes.getInstance().getStandardCodeTable());
628     af.closeMenuItem_actionPerformed(true);
629
630     /*
631      * find the complement frames (ugly)
632      */
633     AlignFrame[] frames = Desktop.getAlignFrames();
634     assertEquals(frames.length, 2);
635     AlignViewport av1 = frames[0].getViewport();
636     AlignViewport av2 = frames[1].getViewport();
637     AlignViewport cds = av1.getAlignment().isNucleotide() ? av1 : av2;
638     AlignViewport peptide = cds == av1 ? av2 : av1;
639     assertNotNull(cds);
640     assertNotNull(peptide);
641
642     /*
643      * add features to CDS at first codon, positions 2-3
644      */
645     SequenceI seq1 = cds.getAlignment().getSequenceAt(0);
646     SequenceFeature sf1 = new SequenceFeature("sequence_variant", "G,GT", 2,
647             2, "ensembl");
648     seq1.addSequenceFeature(sf1);
649     SequenceFeature sf2 = new SequenceFeature("sequence_variant", "C, CA",
650             3, 3, "ensembl");
651     seq1.addSequenceFeature(sf2);
652     
653     /*
654      * 'find' mapped features from the peptide position
655      * - first with CDS features _not_ shown on peptide alignment
656      */
657     SequenceI seq2 = peptide.getAlignment().getSequenceAt(0);
658     FeatureRenderer frC = new FeatureRenderer(cds);
659     frC.featuresAdded();
660     MappedFeatures mf = frC.findComplementFeaturesAtResidue(seq2, 1);
661     assertNotNull(mf);
662     assertEquals(mf.features.size(), 2);
663     assertSame(mf.features.get(0), sf1);
664     assertSame(mf.features.get(1), sf2);
665
666     /*
667      * add exon feature and verify it is only returned once for a
668      * peptide position, even though it is on all 3 codon positions
669      */
670     SequenceFeature sf3 = new SequenceFeature("exon", "exon1", 4, 12,
671             "ensembl");
672     seq1.addSequenceFeature(sf3);
673     frC.featuresAdded();
674     mf = frC.findComplementFeaturesAtResidue(seq2, 3);
675     assertNotNull(mf);
676     assertEquals(mf.features.size(), 1);
677     assertSame(mf.features.get(0), sf3);
678   }
679 }