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