JAL-3612 use _last_ drawn colour per feature type in Overview
[jalview.git] / test / jalview / renderer / seqfeatures / FeatureColourFinderTest.java
1 package jalview.renderer.seqfeatures;
2
3 import static org.testng.Assert.assertEquals;
4 import static org.testng.Assert.assertFalse;
5 import static org.testng.Assert.assertNotEquals;
6 import static org.testng.Assert.assertNull;
7 import static org.testng.Assert.assertTrue;
8
9 import java.awt.Color;
10 import java.util.List;
11
12 import org.testng.annotations.BeforeMethod;
13 import org.testng.annotations.BeforeTest;
14 import org.testng.annotations.Test;
15
16 import jalview.api.FeatureColourI;
17 import jalview.datamodel.SequenceFeature;
18 import jalview.datamodel.SequenceI;
19 import jalview.gui.AlignFrame;
20 import jalview.gui.AlignViewport;
21 import jalview.io.DataSourceType;
22 import jalview.io.FileLoader;
23 import jalview.schemes.FeatureColour;
24 import jalview.viewmodel.seqfeatures.FeatureRendererModel;
25 import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean;
26
27 /**
28  * Unit tests for feature colour determination, including but not limited to
29  * <ul>
30  * <li>gap position</li>
31  * <li>no features present</li>
32  * <li>features present but show features turned off</li>
33  * <li>features displayed but selected feature turned off</li>
34  * <li>features displayed but feature group turned off</li>
35  * <li>feature displayed but none at the specified position</li>
36  * <li>multiple features at position, with no transparency</li>
37  * <li>multiple features at position, with transparency</li>
38  * <li>score graduated feature colour</li>
39  * <li>contact feature start at the selected position</li>
40  * <li>contact feature end at the selected position</li>
41  * <li>contact feature straddling the selected position (not shown)</li>
42  * </ul>
43  */
44 public class FeatureColourFinderTest
45 {
46   private AlignViewport av;
47
48   private SequenceI seq;
49
50   private FeatureColourFinder finder;
51
52   private AlignFrame af;
53
54   private FeatureRendererModel fr;
55
56   @BeforeTest(alwaysRun = true)
57   public void setUp()
58   {
59     // aligned column 8 is sequence position 6
60     String s = ">s1\nABCDE---FGHIJKLMNOPQRSTUVWXYZ\n";
61     af = new FileLoader().LoadFileWaitTillLoaded(s, DataSourceType.PASTE);
62     av = af.getViewport();
63     seq = av.getAlignment().getSequenceAt(0);
64     fr = af.getFeatureRenderer();
65     finder = new FeatureColourFinder(fr);
66   }
67
68   /**
69    * Clear down any sequence features before each test (not as easy as it
70    * sounds...)
71    */
72   @BeforeMethod(alwaysRun = true)
73   public void setUpBeforeTest()
74   {
75     List<SequenceFeature> sfs = seq.getSequenceFeatures();
76     for (SequenceFeature sf : sfs)
77     {
78       seq.deleteFeature(sf);
79     }
80     fr.findAllFeatures(true);
81
82     /*
83      * reset all feature groups to visible
84      */
85     for (String group : fr.getGroups(false))
86     {
87       fr.setGroupVisibility(group, true);
88     }
89
90     fr.clearRenderOrder();
91     av.setShowSequenceFeatures(true);
92   }
93
94   @Test(groups = "Functional")
95   public void testFindFeatureColour_noFeatures()
96   {
97     av.setShowSequenceFeatures(false);
98     Color c = finder.findFeatureColour(Color.blue, seq, 10);
99     assertEquals(c, Color.blue);
100
101     av.setShowSequenceFeatures(true);
102     c = finder.findFeatureColour(Color.blue, seq, 10);
103     assertEquals(c, Color.blue);
104   }
105
106   @Test(groups = "Functional")
107   public void testFindFeatureColour_noFeaturesShown()
108   {
109     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
110             Float.NaN, "MetalGroup"));
111     fr.featuresAdded();
112     av.setShowSequenceFeatures(false);
113     Color c = finder.findFeatureColour(Color.blue, seq, 10);
114     assertEquals(c, Color.blue);
115   }
116
117   @Test(groups = "Functional")
118   public void testFindFeatureColour_singleFeatureAtPosition()
119   {
120     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
121             Float.NaN, "MetalGroup"));
122     fr.setColour("Metal", new FeatureColour(Color.red));
123     fr.featuresAdded();
124     av.setShowSequenceFeatures(true);
125     Color c = finder.findFeatureColour(Color.blue, seq, 10);
126     assertEquals(c, Color.red);
127   }
128
129   /**
130    * feature colour at a gap is null (not white) - a user defined colour scheme
131    * can then provide a bespoke gap colour if configured to do so
132    */
133   @Test(groups = "Functional")
134   public void testFindFeatureColour_gapPosition()
135   {
136     seq.addSequenceFeature(
137             new SequenceFeature("Metal", "Metal", 2, 12, 0f, null));
138     fr.setColour("Metal", new FeatureColour(Color.red));
139     fr.featuresAdded();
140     av.setShowSequenceFeatures(true);
141     Color c = finder.findFeatureColour(null, seq, 6);
142     assertNull(c);
143   }
144
145   /**
146    * Nested features coloured by label - expect the colour of the enclosed
147    * feature
148    */
149   @Test(groups = "Functional")
150   public void testFindFeatureColour_nestedFeatures()
151   {
152     SequenceFeature sf1 = new SequenceFeature("domain", "peptide", 1, 120, 0f, null);
153     seq.addSequenceFeature(sf1);
154     SequenceFeature sf2 = new SequenceFeature("domain", "binding", 10, 20,
155             0f, null);
156     seq.addSequenceFeature(sf2);
157     FeatureColourI fc = new FeatureColour(Color.red);
158     fc.setColourByLabel(true);
159     fr.setColour("domain", fc);
160     fr.featuresAdded();
161     av.setShowSequenceFeatures(true);
162     Color c = finder.findFeatureColour(null, seq, 15);
163     assertEquals(c, fr.getColor(sf2, fc));
164   }
165
166   @Test(groups = "Functional")
167   public void testFindFeatureColour_multipleFeaturesAtPositionNoTransparency()
168   {
169     /*
170      * featuresAdded -> FeatureRendererModel.updateRenderOrder which adds any
171      * new features 'on top' (but reverses the order of any added features)
172      */
173     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
174             Float.NaN, "MetalGroup"));
175     FeatureColour red = new FeatureColour(Color.red);
176     fr.setColour("Metal", red);
177     fr.featuresAdded();
178     seq.addSequenceFeature(new SequenceFeature("Domain", "Domain", 4, 15,
179             Float.NaN, "DomainGroup"));
180     FeatureColour green = new FeatureColour(Color.green);
181     fr.setColour("Domain", green);
182     fr.featuresAdded();
183     av.setShowSequenceFeatures(true);
184
185     /*
186      * expect Domain (green) to be rendered above Metal (red)
187      */
188     Color c = finder.findFeatureColour(Color.blue, seq, 10);
189     assertEquals(c, Color.green);
190
191     /*
192      * now promote Metal above Domain
193      * - currently no way other than mimicking reordering of
194      * table in Feature Settings
195      */
196     FeatureSettingsBean[] data = new FeatureSettingsBean[2];
197     data[0] = new FeatureSettingsBean("Metal", red, null, true);
198     data[1] = new FeatureSettingsBean("Domain", green, null, true);
199     fr.setFeaturePriority(data);
200     c = finder.findFeatureColour(Color.blue, seq, 10);
201     assertEquals(c, Color.red);
202
203     /*
204      * ..and turn off display of Metal
205      */
206     data[0] = new FeatureSettingsBean("Metal", red, null, false);
207     fr.setFeaturePriority(data);
208     c = finder.findFeatureColour(Color.blue, seq, 10);
209     assertEquals(c, Color.green);
210   }
211
212   @Test(groups = "Functional")
213   public void testFindFeatureColour_singleFeatureNotAtPosition()
214   {
215     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 8, 12,
216             Float.NaN, "MetalGroup"));
217     fr.setColour("Metal", new FeatureColour(Color.red));
218     fr.featuresAdded();
219     av.setShowSequenceFeatures(true);
220     // column 2 = sequence position 3
221     Color c = finder.findFeatureColour(Color.blue, seq, 2);
222     assertEquals(c, Color.blue);
223   }
224
225   @Test(groups = "Functional")
226   public void testFindFeatureColour_featureTypeNotDisplayed()
227   {
228     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
229             Float.NaN, "MetalGroup"));
230     FeatureColour red = new FeatureColour(Color.red);
231     fr.setColour("Metal", red);
232     fr.featuresAdded();
233     av.setShowSequenceFeatures(true);
234     Color c = finder.findFeatureColour(Color.blue, seq, 10);
235     assertEquals(c, Color.red);
236
237     /*
238      * turn off display of Metal - is this the easiest way to do it??
239      */
240     FeatureSettingsBean[] data = new FeatureSettingsBean[1];
241     data[0] = new FeatureSettingsBean("Metal", red, null, false);
242     fr.setFeaturePriority(data);
243     c = finder.findFeatureColour(Color.blue, seq, 10);
244     assertEquals(c, Color.blue);
245
246     /*
247      * turn display of Metal back on
248      */
249     data[0] = new FeatureSettingsBean("Metal", red, null, true);
250     fr.setFeaturePriority(data);
251     c = finder.findFeatureColour(Color.blue, seq, 10);
252     assertEquals(c, Color.red);
253   }
254
255   @Test(groups = "Functional")
256   public void testFindFeatureColour_featureGroupNotDisplayed()
257   {
258     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
259             Float.NaN, "MetalGroup"));
260     FeatureColour red = new FeatureColour(Color.red);
261     fr.setColour("Metal", red);
262     fr.featuresAdded();
263     av.setShowSequenceFeatures(true);
264     Color c = finder.findFeatureColour(Color.blue, seq, 10);
265     assertEquals(c, Color.red);
266
267     /*
268      * turn off display of MetalGroup
269      */
270     fr.setGroupVisibility("MetalGroup", false);
271     c = finder.findFeatureColour(Color.blue, seq, 10);
272     assertEquals(c, Color.blue);
273
274     /*
275      * turn display of MetalGroup back on
276      */
277     fr.setGroupVisibility("MetalGroup", true);
278     c = finder.findFeatureColour(Color.blue, seq, 10);
279     assertEquals(c, Color.red);
280   }
281
282   @Test(groups = "Functional")
283   public void testFindFeatureColour_contactFeature()
284   {
285     /*
286      * currently contact feature == type "Disulphide Bond" or "Disulfide Bond" !!
287      */
288     seq.addSequenceFeature(new SequenceFeature("Disulphide Bond", "Contact",
289             2, 12, Float.NaN, "Disulphide"));
290     fr.setColour("Disulphide Bond", new FeatureColour(Color.red));
291     fr.featuresAdded();
292     av.setShowSequenceFeatures(true);
293
294     /*
295      * Contact positions are residues 2 and 12
296      * which are columns 1 and 14
297      * positions in between don't count for a contact feature!
298      */
299     Color c = finder.findFeatureColour(Color.blue, seq, 10);
300     assertEquals(c, Color.blue);
301     c = finder.findFeatureColour(Color.blue, seq, 8);
302     assertEquals(c, Color.blue);
303     c = finder.findFeatureColour(Color.blue, seq, 1);
304     assertEquals(c, Color.red);
305     c = finder.findFeatureColour(Color.blue, seq, 14);
306     assertEquals(c, Color.red);
307   }
308
309   @Test(groups = "Functional")
310   public void testFindFeatureAtEnd()
311   {
312     /*
313      * terminal residue feature
314      */
315     seq.addSequenceFeature(new SequenceFeature("PDBRESNUM", "pdb res 1",
316             seq.getEnd(), seq.getEnd(), Float.NaN, "1seq.pdb"));
317     fr.setColour("PDBRESNUM", new FeatureColour(Color.red));
318     fr.featuresAdded();
319     av.setShowSequenceFeatures(true);
320
321     /*
322      * final column should have PDBRESNUM feature, the others not
323      */
324     Color c = finder.findFeatureColour(Color.blue, seq,
325             seq.getLength() - 2);
326     assertNotEquals(c, Color.red);
327     c = finder.findFeatureColour(Color.blue, seq, seq.getLength() - 1);
328     assertEquals(c, Color.red);
329   }
330
331   @Test(groups = "Functional")
332   public void testFindFeatureColour_graduatedFeatureColour()
333   {
334     seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 2, 2,
335             0f, "KdGroup"));
336     seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 4, 4,
337             5f, "KdGroup"));
338     seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 7, 7,
339             10f, "KdGroup"));
340
341     /*
342      * graduated colour from 0 to 10
343      */
344     Color min = new Color(100, 50, 150);
345     Color max = new Color(200, 0, 100);
346     FeatureColourI fc = new FeatureColour(null, min, max, null, 0, 10);
347     fr.setColour("kd", fc);
348     fr.featuresAdded();
349     av.setShowSequenceFeatures(true);
350
351     /*
352      * position 2, column 1, score 0 - minimum colour in range
353      */
354     Color c = finder.findFeatureColour(Color.blue, seq, 1);
355     assertEquals(c, min);
356
357     /*
358      * position 7, column 9, score 10 - maximum colour in range
359      */
360     c = finder.findFeatureColour(Color.blue, seq, 9);
361     assertEquals(c, max);
362
363     /*
364      * position 4, column 3, score 5 - half way from min to max
365      */
366     c = finder.findFeatureColour(Color.blue, seq, 3);
367     assertEquals(c, new Color(150, 25, 125));
368   }
369
370   @Test(groups = "Functional")
371   public void testFindFeatureColour_transparencySingleFeature()
372   {
373     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
374             Float.NaN, "MetalGroup"));
375     FeatureColour red = new FeatureColour(Color.red);
376     fr.setColour("Metal", red);
377     fr.featuresAdded();
378     av.setShowSequenceFeatures(true);
379
380     /*
381      * the FeatureSettings transparency slider has range 0-70 which
382      * corresponds to a transparency value of 1 - 0.3
383      * A value of 0.4 gives a combination of
384      * 0.4 * red(255, 0, 0) + 0.6 * cyan(0, 255, 255) = (102, 153, 153)
385      */
386     fr.setTransparency(0.4f);
387     Color c = finder.findFeatureColour(Color.cyan, seq, 10);
388     assertEquals(c, new Color(102, 153, 153));
389   }
390
391   @Test(groups = "Functional")
392   public void testFindFeatureColour_transparencyTwoFeatures()
393   {
394     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
395             Float.NaN, "MetalGroup"));
396     FeatureColour red = new FeatureColour(Color.red);
397     fr.setColour("Metal", red);
398     fr.featuresAdded();
399     seq.addSequenceFeature(new SequenceFeature("Domain", "Domain", 4, 15,
400             Float.NaN, "DomainGroup"));
401     FeatureColour green = new FeatureColour(Color.green);
402     fr.setColour("Domain", green);
403     fr.featuresAdded();
404     av.setShowSequenceFeatures(true);
405
406     /*
407      * Domain (green) rendered above Metal (red) above background (cyan)
408      * 1) 0.6 * red(255, 0, 0) + 0.4 * cyan(0, 255, 255) = (153, 102, 102)
409      * 2) 0.6* green(0, 255, 0) + 0.4 * (153, 102, 102) = (61, 194, 41) rounded
410      */
411     fr.setTransparency(0.6f);
412     Color c = finder.findFeatureColour(Color.cyan, seq, 10);
413     assertEquals(c, new Color(61, 194, 41));
414
415     /*
416      * now promote Metal above Domain
417      * - currently no way other than mimicking reordering of
418      * table in Feature Settings
419      * Metal (red) rendered above Domain (green) above background (cyan)
420      * 1) 0.6 * green(0, 255, 0) + 0.4 * cyan(0, 255, 255) = (0, 255, 102)
421      * 2) 0.6* red(255, 0, 0) + 0.4 * (0, 255, 102) = (153, 102, 41) rounded
422      */
423     FeatureSettingsBean[] data = new FeatureSettingsBean[2];
424     data[0] = new FeatureSettingsBean("Metal", red, null, true);
425     data[1] = new FeatureSettingsBean("Domain", green, null, true);
426     fr.setFeaturePriority(data);
427     c = finder.findFeatureColour(Color.cyan, seq, 10);
428     assertEquals(c, new Color(153, 102, 41));
429
430     /*
431      * ..and turn off display of Metal
432      * Domain (green) above background (pink)
433      * 0.6 * green(0, 255, 0) + 0.4 * pink(255, 175, 175) = (102, 223, 70)
434      */
435     data[0] = new FeatureSettingsBean("Metal", red, null, false);
436     fr.setFeaturePriority(data);
437     c = finder.findFeatureColour(Color.pink, seq, 10);
438     assertEquals(c, new Color(102, 223, 70));
439   }
440
441   @Test(groups = "Functional")
442   public void testNoFeaturesDisplayed()
443   {
444     /*
445      * no features on alignment to render
446      */
447     assertTrue(finder.noFeaturesDisplayed());
448
449     /*
450      * add a feature
451      * it will be automatically set visible but we leave
452      * the viewport configured not to show features
453      */
454     av.setShowSequenceFeatures(false);
455     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
456             Float.NaN, "MetalGroup"));
457     FeatureColour red = new FeatureColour(Color.red);
458     fr.setColour("Metal", red);
459     fr.featuresAdded();
460     assertTrue(finder.noFeaturesDisplayed());
461
462     /*
463      * turn on feature display
464      */
465     av.setShowSequenceFeatures(true);
466     assertFalse(finder.noFeaturesDisplayed());
467
468     /*
469      * turn off display of Metal
470      */
471     FeatureSettingsBean[] data = new FeatureSettingsBean[1];
472     data[0] = new FeatureSettingsBean("Metal", red, null, false);
473     fr.setFeaturePriority(data);
474     assertTrue(finder.noFeaturesDisplayed());
475
476     /*
477      * turn display of Metal back on
478      */
479     fr.setVisible("Metal");
480     assertFalse(finder.noFeaturesDisplayed());
481
482     /*
483      * turn off MetalGroup - has no effect here since the group of a
484      * sequence feature instance is independent of its type
485      */
486     fr.setGroupVisibility("MetalGroup", false);
487     assertFalse(finder.noFeaturesDisplayed());
488
489     /*
490      * a finder with no feature renderer
491      */
492     FeatureColourFinder finder2 = new FeatureColourFinder(null);
493     assertTrue(finder2.noFeaturesDisplayed());
494   }
495
496   @Test(groups = "Functional")
497   public void testFindFeatureColour_graduatedWithThreshold()
498   {
499     String kdFeature = "kd";
500     String metalFeature = "Metal";
501     seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity",
502             2, 2, 0f, "KdGroup"));
503     seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity",
504             4, 4, 5f, "KdGroup"));
505     seq.addSequenceFeature(new SequenceFeature(metalFeature, "Fe", 4, 4, 5f,
506             "MetalGroup"));
507     seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity",
508             7, 7, 10f, "KdGroup"));
509
510     /*
511      * kd feature has graduated colour from 0 to 10
512      * above threshold value of 5
513      */
514     Color min = new Color(100, 50, 150);
515     Color max = new Color(200, 0, 100);
516     FeatureColourI fc = new FeatureColour(null, min, max, null, 0, 10);
517     fc.setAboveThreshold(true);
518     fc.setThreshold(5f);
519     fr.setColour(kdFeature, fc);
520     FeatureColour green = new FeatureColour(Color.green);
521     fr.setColour(metalFeature, green);
522     fr.featuresAdded();
523
524     /*
525      * render order is kd above Metal
526      */
527     FeatureSettingsBean[] data = new FeatureSettingsBean[2];
528     data[0] = new FeatureSettingsBean(kdFeature, fc, null, true);
529     data[1] = new FeatureSettingsBean(metalFeature, green, null, true);
530     fr.setFeaturePriority(data);
531
532     av.setShowSequenceFeatures(true);
533
534     /*
535      * position 2, column 1, score 0 - below threshold - default colour
536      */
537     Color c = finder.findFeatureColour(Color.blue, seq, 1);
538     assertEquals(c, Color.blue);
539
540     /*
541      * position 4, column 3, score 5 - at threshold
542      * should return Green (colour of Metal feature)
543      */
544     c = finder.findFeatureColour(Color.blue, seq, 3);
545     assertEquals(c, Color.green);
546
547     /*
548      * position 7, column 9, score 10 - maximum colour in range
549      */
550     c = finder.findFeatureColour(Color.blue, seq, 9);
551     assertEquals(c, max);
552
553     /*
554      * now colour below threshold of 5
555      */
556     fc.setBelowThreshold(true);
557
558     /*
559      * position 2, column 1, score 0 - min colour
560      */
561     c = finder.findFeatureColour(Color.blue, seq, 1);
562     assertEquals(c, min);
563
564     /*
565      * position 4, column 3, score 5 - at threshold
566      * should return Green (colour of Metal feature)
567      */
568     c = finder.findFeatureColour(Color.blue, seq, 3);
569     assertEquals(c, Color.green);
570
571     /*
572      * position 7, column 9, score 10 - above threshold - default colour
573      */
574     c = finder.findFeatureColour(Color.blue, seq, 9);
575     assertEquals(c, Color.blue);
576   }
577 }