JAL-1551 spotlessApply
[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,
173             0f, null);
174     seq.addSequenceFeature(sf1);
175     SequenceFeature sf2 = new SequenceFeature("domain", "binding", 10, 20,
176             0f, null);
177     seq.addSequenceFeature(sf2);
178     FeatureColourI fc = new FeatureColour(Color.red);
179     fc.setColourByLabel(true);
180     fr.setColour("domain", fc);
181     fr.featuresAdded();
182     av.setShowSequenceFeatures(true);
183     Color c = finder.findFeatureColour(null, seq, 15);
184     assertEquals(c, fr.getColor(sf2, fc));
185   }
186
187   @Test(groups = "Functional")
188   public void testFindFeatureColour_multipleFeaturesAtPositionNoTransparency()
189   {
190     /*
191      * featuresAdded -> FeatureRendererModel.updateRenderOrder which adds any
192      * new features 'on top' (but reverses the order of any added features)
193      */
194     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
195             Float.NaN, "MetalGroup"));
196     FeatureColour red = new FeatureColour(Color.red);
197     fr.setColour("Metal", red);
198     fr.featuresAdded();
199     seq.addSequenceFeature(new SequenceFeature("Domain", "Domain", 4, 15,
200             Float.NaN, "DomainGroup"));
201     FeatureColour green = new FeatureColour(Color.green);
202     fr.setColour("Domain", green);
203     fr.featuresAdded();
204     av.setShowSequenceFeatures(true);
205
206     /*
207      * expect Domain (green) to be rendered above Metal (red)
208      */
209     Color c = finder.findFeatureColour(Color.blue, seq, 10);
210     assertEquals(c, Color.green);
211
212     /*
213      * now promote Metal above Domain
214      * - currently no way other than mimicking reordering of
215      * table in Feature Settings
216      */
217     FeatureSettingsBean[] data = new FeatureSettingsBean[2];
218     data[0] = new FeatureSettingsBean("Metal", red, null, true);
219     data[1] = new FeatureSettingsBean("Domain", green, null, true);
220     fr.setFeaturePriority(data);
221     c = finder.findFeatureColour(Color.blue, seq, 10);
222     assertEquals(c, Color.red);
223
224     /*
225      * ..and turn off display of Metal
226      */
227     data[0] = new FeatureSettingsBean("Metal", red, null, false);
228     fr.setFeaturePriority(data);
229     c = finder.findFeatureColour(Color.blue, seq, 10);
230     assertEquals(c, Color.green);
231   }
232
233   @Test(groups = "Functional")
234   public void testFindFeatureColour_singleFeatureNotAtPosition()
235   {
236     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 8, 12,
237             Float.NaN, "MetalGroup"));
238     fr.setColour("Metal", new FeatureColour(Color.red));
239     fr.featuresAdded();
240     av.setShowSequenceFeatures(true);
241     // column 2 = sequence position 3
242     Color c = finder.findFeatureColour(Color.blue, seq, 2);
243     assertEquals(c, Color.blue);
244   }
245
246   @Test(groups = "Functional")
247   public void testFindFeatureColour_featureTypeNotDisplayed()
248   {
249     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
250             Float.NaN, "MetalGroup"));
251     FeatureColour red = new FeatureColour(Color.red);
252     fr.setColour("Metal", red);
253     fr.featuresAdded();
254     av.setShowSequenceFeatures(true);
255     Color c = finder.findFeatureColour(Color.blue, seq, 10);
256     assertEquals(c, Color.red);
257
258     /*
259      * turn off display of Metal - is this the easiest way to do it??
260      */
261     FeatureSettingsBean[] data = new FeatureSettingsBean[1];
262     data[0] = new FeatureSettingsBean("Metal", red, null, false);
263     fr.setFeaturePriority(data);
264     c = finder.findFeatureColour(Color.blue, seq, 10);
265     assertEquals(c, Color.blue);
266
267     /*
268      * turn display of Metal back on
269      */
270     data[0] = new FeatureSettingsBean("Metal", red, null, true);
271     fr.setFeaturePriority(data);
272     c = finder.findFeatureColour(Color.blue, seq, 10);
273     assertEquals(c, Color.red);
274   }
275
276   @Test(groups = "Functional")
277   public void testFindFeatureColour_featureGroupNotDisplayed()
278   {
279     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
280             Float.NaN, "MetalGroup"));
281     FeatureColour red = new FeatureColour(Color.red);
282     fr.setColour("Metal", red);
283     fr.featuresAdded();
284     av.setShowSequenceFeatures(true);
285     Color c = finder.findFeatureColour(Color.blue, seq, 10);
286     assertEquals(c, Color.red);
287
288     /*
289      * turn off display of MetalGroup
290      */
291     fr.setGroupVisibility("MetalGroup", false);
292     c = finder.findFeatureColour(Color.blue, seq, 10);
293     assertEquals(c, Color.blue);
294
295     /*
296      * turn display of MetalGroup back on
297      */
298     fr.setGroupVisibility("MetalGroup", true);
299     c = finder.findFeatureColour(Color.blue, seq, 10);
300     assertEquals(c, Color.red);
301   }
302
303   @Test(groups = "Functional")
304   public void testFindFeatureColour_contactFeature()
305   {
306     /*
307      * currently contact feature == type "Disulphide Bond" or "Disulfide Bond" !!
308      */
309     seq.addSequenceFeature(new SequenceFeature("Disulphide Bond", "Contact",
310             2, 12, Float.NaN, "Disulphide"));
311     fr.setColour("Disulphide Bond", new FeatureColour(Color.red));
312     fr.featuresAdded();
313     av.setShowSequenceFeatures(true);
314
315     /*
316      * Contact positions are residues 2 and 12
317      * which are columns 1 and 14
318      * positions in between don't count for a contact feature!
319      */
320     Color c = finder.findFeatureColour(Color.blue, seq, 10);
321     assertEquals(c, Color.blue);
322     c = finder.findFeatureColour(Color.blue, seq, 8);
323     assertEquals(c, Color.blue);
324     c = finder.findFeatureColour(Color.blue, seq, 1);
325     assertEquals(c, Color.red);
326     c = finder.findFeatureColour(Color.blue, seq, 14);
327     assertEquals(c, Color.red);
328   }
329
330   @Test(groups = "Functional")
331   public void testFindFeatureAtEnd()
332   {
333     /*
334      * terminal residue feature
335      */
336     seq.addSequenceFeature(new SequenceFeature("PDBRESNUM", "pdb res 1",
337             seq.getEnd(), seq.getEnd(), Float.NaN, "1seq.pdb"));
338     fr.setColour("PDBRESNUM", new FeatureColour(Color.red));
339     fr.featuresAdded();
340     av.setShowSequenceFeatures(true);
341
342     /*
343      * final column should have PDBRESNUM feature, the others not
344      */
345     Color c = finder.findFeatureColour(Color.blue, seq,
346             seq.getLength() - 2);
347     assertNotEquals(c, Color.red);
348     c = finder.findFeatureColour(Color.blue, seq, seq.getLength() - 1);
349     assertEquals(c, Color.red);
350   }
351
352   @Test(groups = "Functional")
353   public void testFindFeatureColour_graduatedFeatureColour()
354   {
355     seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 2, 2,
356             0f, "KdGroup"));
357     seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 4, 4,
358             5f, "KdGroup"));
359     seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 7, 7,
360             10f, "KdGroup"));
361
362     /*
363      * graduated colour from 0 to 10
364      */
365     Color min = new Color(100, 50, 150);
366     Color max = new Color(200, 0, 100);
367     FeatureColourI fc = new FeatureColour(null, min, max, null, 0, 10);
368     fr.setColour("kd", fc);
369     fr.featuresAdded();
370     av.setShowSequenceFeatures(true);
371
372     /*
373      * position 2, column 1, score 0 - minimum colour in range
374      */
375     Color c = finder.findFeatureColour(Color.blue, seq, 1);
376     assertEquals(c, min);
377
378     /*
379      * position 7, column 9, score 10 - maximum colour in range
380      */
381     c = finder.findFeatureColour(Color.blue, seq, 9);
382     assertEquals(c, max);
383
384     /*
385      * position 4, column 3, score 5 - half way from min to max
386      */
387     c = finder.findFeatureColour(Color.blue, seq, 3);
388     assertEquals(c, new Color(150, 25, 125));
389   }
390
391   @Test(groups = "Functional")
392   public void testFindFeatureColour_transparencySingleFeature()
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     av.setShowSequenceFeatures(true);
400
401     /*
402      * the FeatureSettings transparency slider has range 0-70 which
403      * corresponds to a transparency value of 1 - 0.3
404      * A value of 0.4 gives a combination of
405      * 0.4 * red(255, 0, 0) + 0.6 * cyan(0, 255, 255) = (102, 153, 153)
406      */
407     fr.setTransparency(0.4f);
408     Color c = finder.findFeatureColour(Color.cyan, seq, 10);
409     assertEquals(c, new Color(102, 153, 153));
410   }
411
412   @Test(groups = "Functional")
413   public void testFindFeatureColour_transparencyTwoFeatures()
414   {
415     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
416             Float.NaN, "MetalGroup"));
417     FeatureColour red = new FeatureColour(Color.red);
418     fr.setColour("Metal", red);
419     fr.featuresAdded();
420     seq.addSequenceFeature(new SequenceFeature("Domain", "Domain", 4, 15,
421             Float.NaN, "DomainGroup"));
422     FeatureColour green = new FeatureColour(Color.green);
423     fr.setColour("Domain", green);
424     fr.featuresAdded();
425     av.setShowSequenceFeatures(true);
426
427     /*
428      * Domain (green) rendered above Metal (red) above background (cyan)
429      * 1) 0.6 * red(255, 0, 0) + 0.4 * cyan(0, 255, 255) = (153, 102, 102)
430      * 2) 0.6* green(0, 255, 0) + 0.4 * (153, 102, 102) = (61, 194, 41) rounded
431      */
432     fr.setTransparency(0.6f);
433     Color c = finder.findFeatureColour(Color.cyan, seq, 10);
434     assertEquals(c, new Color(61, 194, 41));
435
436     /*
437      * now promote Metal above Domain
438      * - currently no way other than mimicking reordering of
439      * table in Feature Settings
440      * Metal (red) rendered above Domain (green) above background (cyan)
441      * 1) 0.6 * green(0, 255, 0) + 0.4 * cyan(0, 255, 255) = (0, 255, 102)
442      * 2) 0.6* red(255, 0, 0) + 0.4 * (0, 255, 102) = (153, 102, 41) rounded
443      */
444     FeatureSettingsBean[] data = new FeatureSettingsBean[2];
445     data[0] = new FeatureSettingsBean("Metal", red, null, true);
446     data[1] = new FeatureSettingsBean("Domain", green, null, true);
447     fr.setFeaturePriority(data);
448     c = finder.findFeatureColour(Color.cyan, seq, 10);
449     assertEquals(c, new Color(153, 102, 41));
450
451     /*
452      * ..and turn off display of Metal
453      * Domain (green) above background (pink)
454      * 0.6 * green(0, 255, 0) + 0.4 * pink(255, 175, 175) = (102, 223, 70)
455      */
456     data[0] = new FeatureSettingsBean("Metal", red, null, false);
457     fr.setFeaturePriority(data);
458     c = finder.findFeatureColour(Color.pink, seq, 10);
459     assertEquals(c, new Color(102, 223, 70));
460   }
461
462   @Test(groups = "Functional")
463   public void testNoFeaturesDisplayed()
464   {
465     /*
466      * no features on alignment to render
467      */
468     assertTrue(finder.noFeaturesDisplayed());
469
470     /*
471      * add a feature
472      * it will be automatically set visible but we leave
473      * the viewport configured not to show features
474      */
475     av.setShowSequenceFeatures(false);
476     seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12,
477             Float.NaN, "MetalGroup"));
478     FeatureColour red = new FeatureColour(Color.red);
479     fr.setColour("Metal", red);
480     fr.featuresAdded();
481     assertTrue(finder.noFeaturesDisplayed());
482
483     /*
484      * turn on feature display
485      */
486     av.setShowSequenceFeatures(true);
487     assertFalse(finder.noFeaturesDisplayed());
488
489     /*
490      * turn off display of Metal
491      */
492     FeatureSettingsBean[] data = new FeatureSettingsBean[1];
493     data[0] = new FeatureSettingsBean("Metal", red, null, false);
494     fr.setFeaturePriority(data);
495     assertTrue(finder.noFeaturesDisplayed());
496
497     /*
498      * turn display of Metal back on
499      */
500     fr.setVisible("Metal");
501     assertFalse(finder.noFeaturesDisplayed());
502
503     /*
504      * turn off MetalGroup - has no effect here since the group of a
505      * sequence feature instance is independent of its type
506      */
507     fr.setGroupVisibility("MetalGroup", false);
508     assertFalse(finder.noFeaturesDisplayed());
509
510     /*
511      * a finder with no feature renderer
512      */
513     FeatureColourFinder finder2 = new FeatureColourFinder(null);
514     assertTrue(finder2.noFeaturesDisplayed());
515   }
516
517   @Test(groups = "Functional")
518   public void testFindFeatureColour_graduatedWithThreshold()
519   {
520     String kdFeature = "kd";
521     String metalFeature = "Metal";
522     seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity",
523             2, 2, 0f, "KdGroup"));
524     seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity",
525             4, 4, 5f, "KdGroup"));
526     seq.addSequenceFeature(new SequenceFeature(metalFeature, "Fe", 4, 4, 5f,
527             "MetalGroup"));
528     seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity",
529             7, 7, 10f, "KdGroup"));
530
531     /*
532      * kd feature has graduated colour from 0 to 10
533      * above threshold value of 5
534      */
535     Color min = new Color(100, 50, 150);
536     Color max = new Color(200, 0, 100);
537     FeatureColourI fc = new FeatureColour(null, min, max, null, 0, 10);
538     fc.setAboveThreshold(true);
539     fc.setThreshold(5f);
540     fr.setColour(kdFeature, fc);
541     FeatureColour green = new FeatureColour(Color.green);
542     fr.setColour(metalFeature, green);
543     fr.featuresAdded();
544
545     /*
546      * render order is kd above Metal
547      */
548     FeatureSettingsBean[] data = new FeatureSettingsBean[2];
549     data[0] = new FeatureSettingsBean(kdFeature, fc, null, true);
550     data[1] = new FeatureSettingsBean(metalFeature, green, null, true);
551     fr.setFeaturePriority(data);
552
553     av.setShowSequenceFeatures(true);
554
555     /*
556      * position 2, column 1, score 0 - below threshold - default colour
557      */
558     Color c = finder.findFeatureColour(Color.blue, seq, 1);
559     assertEquals(c, Color.blue);
560
561     /*
562      * position 4, column 3, score 5 - at threshold
563      * should return Green (colour of Metal feature)
564      */
565     c = finder.findFeatureColour(Color.blue, seq, 3);
566     assertEquals(c, Color.green);
567
568     /*
569      * position 7, column 9, score 10 - maximum colour in range
570      */
571     c = finder.findFeatureColour(Color.blue, seq, 9);
572     assertEquals(c, max);
573
574     /*
575      * now colour below threshold of 5
576      */
577     fc.setBelowThreshold(true);
578
579     /*
580      * position 2, column 1, score 0 - min colour
581      */
582     c = finder.findFeatureColour(Color.blue, seq, 1);
583     assertEquals(c, min);
584
585     /*
586      * position 4, column 3, score 5 - at threshold
587      * should return Green (colour of Metal feature)
588      */
589     c = finder.findFeatureColour(Color.blue, seq, 3);
590     assertEquals(c, Color.green);
591
592     /*
593      * position 7, column 9, score 10 - above threshold - default colour
594      */
595     c = finder.findFeatureColour(Color.blue, seq, 9);
596     assertEquals(c, Color.blue);
597   }
598 }