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