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