JAL-2069 update spike branch with latest
[jalview.git] / test / jalview / schemes / FeatureColourTest.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.schemes;
22
23 import static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertFalse;
25 import static org.testng.AssertJUnit.assertNull;
26 import static org.testng.AssertJUnit.assertTrue;
27 import static org.testng.AssertJUnit.fail;
28
29 import jalview.datamodel.SequenceFeature;
30 import jalview.gui.JvOptionPane;
31 import jalview.util.ColorUtils;
32 import jalview.util.Format;
33
34 import java.awt.Color;
35
36 import junit.extensions.PA;
37
38 import org.testng.annotations.BeforeClass;
39 import org.testng.annotations.Test;
40
41 public class FeatureColourTest
42 {
43
44   @BeforeClass(alwaysRun = true)
45   public void setUpJvOptionPane()
46   {
47     JvOptionPane.setInteractiveMode(false);
48     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
49   }
50
51   @Test(groups = { "Functional" })
52   public void testCopyConstructor()
53   {
54     /*
55      * plain colour
56      */
57     FeatureColour fc = new FeatureColour(Color.RED);
58     FeatureColour fc1 = new FeatureColour(fc);
59     assertTrue(fc1.getColour().equals(Color.RED));
60     assertFalse(fc1.isGraduatedColour());
61     assertFalse(fc1.isColourByLabel());
62     assertFalse(fc1.isColourByAttribute());
63     assertNull(fc1.getAttributeName());
64
65     /*
66      * min-max colour
67      */
68     fc = new FeatureColour(Color.gray, Color.black, 10f, 20f);
69     fc.setAboveThreshold(true);
70     fc.setThreshold(12f);
71     fc1 = new FeatureColour(fc);
72     assertTrue(fc1.isGraduatedColour());
73     assertFalse(fc1.isColourByLabel());
74     assertTrue(fc1.isAboveThreshold());
75     assertFalse(fc1.isColourByAttribute());
76     assertNull(fc1.getAttributeName());
77     assertEquals(12f, fc1.getThreshold());
78     assertEquals(Color.gray, fc1.getMinColour());
79     assertEquals(Color.black, fc1.getMaxColour());
80     assertEquals(Color.gray, fc1.getNoColour());
81     assertEquals(10f, fc1.getMin());
82     assertEquals(20f, fc1.getMax());
83
84     /*
85      * min-max-noValue colour
86      */
87     fc = new FeatureColour(Color.gray, Color.black, Color.green, 10f, 20f);
88     fc.setAboveThreshold(true);
89     fc.setThreshold(12f);
90     fc1 = new FeatureColour(fc);
91     assertTrue(fc1.isGraduatedColour());
92     assertFalse(fc1.isColourByLabel());
93     assertFalse(fc1.isColourByAttribute());
94     assertNull(fc1.getAttributeName());
95     assertTrue(fc1.isAboveThreshold());
96     assertEquals(12f, fc1.getThreshold());
97     assertEquals(Color.gray, fc1.getMinColour());
98     assertEquals(Color.black, fc1.getMaxColour());
99     assertEquals(Color.green, fc1.getNoColour());
100     assertEquals(10f, fc1.getMin());
101     assertEquals(20f, fc1.getMax());
102
103     /*
104      * colour by label
105      */
106     fc = new FeatureColour();
107     fc.setColourByLabel(true);
108     fc1 = new FeatureColour(fc);
109     assertTrue(fc1.isColourByLabel());
110     assertFalse(fc1.isGraduatedColour());
111     assertFalse(fc1.isColourByAttribute());
112     assertNull(fc1.getAttributeName());
113
114     /*
115      * colour by attribute (label)
116      */
117     fc = new FeatureColour();
118     fc.setColourByLabel(true);
119     fc.setAttributeName("AF");
120     fc1 = new FeatureColour(fc);
121     assertTrue(fc1.isColourByLabel());
122     assertFalse(fc1.isGraduatedColour());
123     assertTrue(fc1.isColourByAttribute());
124     assertEquals("AF", fc1.getAttributeName());
125
126     /*
127      * colour by attribute (value)
128      */
129     fc = new FeatureColour(Color.gray, Color.black, Color.green, 10f, 20f);
130     fc.setAboveThreshold(true);
131     fc.setThreshold(12f);
132     fc.setAttributeName("AF");
133     fc1 = new FeatureColour(fc);
134     assertTrue(fc1.isGraduatedColour());
135     assertFalse(fc1.isColourByLabel());
136     assertTrue(fc1.isColourByAttribute());
137     assertEquals("AF", fc1.getAttributeName());
138     assertTrue(fc1.isAboveThreshold());
139     assertEquals(12f, fc1.getThreshold());
140     assertEquals(Color.gray, fc1.getMinColour());
141     assertEquals(Color.black, fc1.getMaxColour());
142     assertEquals(Color.green, fc1.getNoColour());
143     assertEquals(10f, fc1.getMin());
144     assertEquals(20f, fc1.getMax());
145   }
146
147   @Test(groups = { "Functional" })
148   public void testCopyConstructor_minMax()
149   {
150     /*
151      * graduated colour
152      */
153     FeatureColour fc = new FeatureColour(Color.BLUE, Color.RED, 1f, 5f);
154     assertTrue(fc.isGraduatedColour());
155     assertFalse(fc.isColourByLabel());
156     assertFalse(fc.isColourByAttribute());
157     assertNull(fc.getAttributeName());
158     assertEquals(1f, fc.getMin());
159     assertEquals(5f, fc.getMax());
160
161     /*
162      * update min-max bounds
163      */
164     FeatureColour fc1 = new FeatureColour(fc, 2f, 6f);
165     assertTrue(fc1.isGraduatedColour());
166     assertFalse(fc1.isColourByLabel());
167     assertFalse(fc1.isColourByAttribute());
168     assertNull(fc1.getAttributeName());
169     assertEquals(2f, fc1.getMin());
170     assertEquals(6f, fc1.getMax());
171     assertFalse((boolean) PA.getValue(fc1, "isHighToLow"));
172
173     /*
174      * update min-max bounds - high to low
175      */
176     fc1 = new FeatureColour(fc, 23f, 16f);
177     assertTrue(fc1.isGraduatedColour());
178     assertFalse(fc1.isColourByLabel());
179     assertFalse(fc1.isColourByAttribute());
180     assertNull(fc1.getAttributeName());
181     assertEquals(23f, fc1.getMin());
182     assertEquals(16f, fc1.getMax());
183     assertTrue((boolean) PA.getValue(fc1, "isHighToLow"));
184
185     /*
186      * colour by label
187      */
188     fc = new FeatureColour(Color.BLUE, Color.RED, 1f, 5f);
189     fc.setColourByLabel(true);
190     assertFalse(fc.isGraduatedColour());
191     assertTrue(fc.isColourByLabel());
192     assertFalse(fc.isColourByAttribute());
193     assertNull(fc.getAttributeName());
194     assertEquals(1f, fc.getMin());
195     assertEquals(5f, fc.getMax());
196
197     /*
198      * update min-max bounds - converts to graduated colour
199      */
200     fc1 = new FeatureColour(fc, 2f, 6f);
201     assertTrue(fc1.isGraduatedColour());
202     assertFalse(fc1.isColourByLabel());
203     assertFalse(fc1.isColourByAttribute());
204     assertNull(fc1.getAttributeName());
205     assertEquals(2f, fc1.getMin());
206     assertEquals(6f, fc1.getMax());
207   }
208
209   @Test(groups = { "Functional" })
210   public void testGetColor_simpleColour()
211   {
212     FeatureColour fc = new FeatureColour(Color.RED);
213     assertEquals(Color.RED,
214             fc.getColor(new SequenceFeature("Cath", "", 1, 2, 0f, null)));
215   }
216
217   @Test(groups = { "Functional" })
218   public void testGetColor_colourByLabel()
219   {
220     FeatureColour fc = new FeatureColour();
221     fc.setColourByLabel(true);
222     SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 1f,
223             null);
224     Color expected = ColorUtils.createColourFromName("desc");
225     assertEquals(expected, fc.getColor(sf));
226   }
227
228   @Test(groups = { "Functional" })
229   public void testGetColor_Graduated()
230   {
231     /*
232      * graduated colour from 
233      * score 0 to 100
234      * gray(128, 128, 128) to red(255, 0, 0)
235      */
236     FeatureColour fc = new FeatureColour(Color.GRAY, Color.RED, 0f, 100f);
237     // feature score is 75 which is 3/4 of the way from GRAY to RED
238     SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
239             null);
240     // the colour gradient is computed in float values from 0-1 (where 1 == 255)
241     float red = 128 / 255f + 3 / 4f * (255 - 128) / 255f;
242     float green = 128 / 255f + 3 / 4f * (0 - 128) / 255f;
243     float blue = 128 / 255f + 3 / 4f * (0 - 128) / 255f;
244     Color expected = new Color(red, green, blue);
245     assertEquals(expected, fc.getColor(sf));
246   }
247
248   @Test(groups = { "Functional" })
249   public void testGetColor_aboveBelowThreshold()
250   {
251     // gradient from [50, 150] from WHITE(255, 255, 255) to BLACK(0, 0, 0)
252     FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 50f,
253             150f);
254     SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 70f,
255             null);
256
257     /*
258      * feature with score of Float.NaN is always assigned minimum colour
259      */
260     SequenceFeature sf2 = new SequenceFeature("type", "desc", 0, 20,
261             Float.NaN, null);
262
263     fc.setThreshold(100f); // ignore for now
264     assertEquals(new Color(204, 204, 204), fc.getColor(sf));
265     assertEquals(Color.white, fc.getColor(sf2));
266
267     fc.setAboveThreshold(true); // feature lies below threshold
268     assertNull(fc.getColor(sf));
269     assertEquals(Color.white, fc.getColor(sf2));
270
271     fc.setBelowThreshold(true);
272     fc.setThreshold(70f);
273     assertNull(fc.getColor(sf)); // feature score == threshold - hidden
274     assertEquals(Color.white, fc.getColor(sf2));
275     fc.setThreshold(69f);
276     assertNull(fc.getColor(sf)); // feature score > threshold - hidden
277     assertEquals(Color.white, fc.getColor(sf2));
278   }
279
280   /**
281    * Test output of feature colours to Jalview features file format
282    */
283   @Test(groups = { "Functional" })
284   public void testToJalviewFormat()
285   {
286     /*
287      * plain colour - to RGB hex code
288      */
289     FeatureColour fc = new FeatureColour(Color.RED);
290     String redHex = Format.getHexString(Color.RED);
291     String hexColour = redHex;
292     assertEquals("domain\t" + hexColour, fc.toJalviewFormat("domain"));
293
294     /*
295      * colour by label (no threshold)
296      */
297     fc = new FeatureColour();
298     fc.setColourByLabel(true);
299     assertEquals("domain\tlabel", fc.toJalviewFormat("domain"));
300
301     /*
302      * colour by label (autoscaled) (an odd state you can reach by selecting
303      * 'above threshold', then deselecting 'threshold is min/max' then 'colour
304      * by label')
305      */
306     fc.setAutoScaled(true);
307     assertEquals("domain\tlabel", fc.toJalviewFormat("domain"));
308
309     /*
310      * colour by label (above threshold) (min/max values are output though not
311      * used by this scheme)
312      */
313     fc.setAutoScaled(false);
314     fc.setThreshold(12.5f);
315     fc.setAboveThreshold(true);
316     assertEquals("domain\tlabel|||0.0|0.0|above|12.5",
317             fc.toJalviewFormat("domain"));
318
319     /*
320      * colour by label (below threshold)
321      */
322     fc.setBelowThreshold(true);
323     assertEquals("domain\tlabel|||0.0|0.0|below|12.5",
324             fc.toJalviewFormat("domain"));
325
326     /*
327      * graduated colour, no threshold
328      */
329     fc = new FeatureColour(Color.GREEN, Color.RED, 12f, 25f);
330     String greenHex = Format.getHexString(Color.GREEN);
331     String expected = String.format("domain\t%s|%s|abso|12.0|25.0|none",
332             greenHex, redHex);
333     assertEquals(expected, fc.toJalviewFormat("domain"));
334
335     /*
336      * colour ranges over the actual score ranges (not min/max)
337      */
338     fc.setAutoScaled(true);
339     expected = String.format("domain\t%s|%s|12.0|25.0|none", greenHex,
340             redHex);
341     assertEquals(expected, fc.toJalviewFormat("domain"));
342
343     /*
344      * graduated colour below threshold
345      */
346     fc.setThreshold(12.5f);
347     fc.setBelowThreshold(true);
348     expected = String.format("domain\t%s|%s|12.0|25.0|below|12.5",
349             greenHex, redHex);
350     assertEquals(expected, fc.toJalviewFormat("domain"));
351
352     /*
353      * graduated colour above threshold
354      */
355     fc.setThreshold(12.5f);
356     fc.setAboveThreshold(true);
357     fc.setAutoScaled(false);
358     expected = String.format("domain\t%s|%s|abso|12.0|25.0|above|12.5",
359             greenHex, redHex);
360     assertEquals(expected, fc.toJalviewFormat("domain"));
361   }
362
363   /**
364    * Test parsing of feature colours from Jalview features file format
365    */
366   @Test(groups = { "Functional" })
367   public void testParseJalviewFeatureColour()
368   {
369     /*
370      * simple colour by name
371      */
372     FeatureColour fc = FeatureColour.parseJalviewFeatureColour("red");
373     assertTrue(fc.isSimpleColour());
374     assertEquals(Color.RED, fc.getColour());
375
376     /*
377      * simple colour by hex code
378      */
379     fc = FeatureColour.parseJalviewFeatureColour(Format
380             .getHexString(Color.RED));
381     assertTrue(fc.isSimpleColour());
382     assertEquals(Color.RED, fc.getColour());
383
384     /*
385      * simple colour by rgb triplet
386      */
387     fc = FeatureColour.parseJalviewFeatureColour("255,0,0");
388     assertTrue(fc.isSimpleColour());
389     assertEquals(Color.RED, fc.getColour());
390
391     /*
392      * malformed colour
393      */
394     try
395     {
396       fc = FeatureColour.parseJalviewFeatureColour("oops");
397       fail("expected exception");
398     } catch (IllegalArgumentException e)
399     {
400       assertEquals("Invalid colour descriptor: oops", e.getMessage());
401     }
402
403     /*
404      * colour by label (no threshold)
405      */
406     fc = FeatureColour.parseJalviewFeatureColour("label");
407     assertTrue(fc.isColourByLabel());
408     assertFalse(fc.hasThreshold());
409
410     /*
411      * colour by label (with threshold)
412      */
413     fc = FeatureColour
414             .parseJalviewFeatureColour("label|||0.0|0.0|above|12.0");
415     assertTrue(fc.isColourByLabel());
416     assertTrue(fc.isAboveThreshold());
417     assertEquals(12.0f, fc.getThreshold());
418
419     /*
420      * graduated colour (by name) (no threshold)
421      */
422     fc = FeatureColour.parseJalviewFeatureColour("red|green|10.0|20.0");
423     assertTrue(fc.isGraduatedColour());
424     assertFalse(fc.hasThreshold());
425     assertEquals(Color.RED, fc.getMinColour());
426     assertEquals(Color.GREEN, fc.getMaxColour());
427     assertEquals(10f, fc.getMin());
428     assertEquals(20f, fc.getMax());
429     assertTrue(fc.isAutoScaled());
430
431     /*
432      * graduated colour (by hex code) (above threshold)
433      */
434     String descriptor = String.format("%s|%s|10.0|20.0|above|15",
435             Format.getHexString(Color.RED),
436             Format.getHexString(Color.GREEN));
437     fc = FeatureColour.parseJalviewFeatureColour(descriptor);
438     assertTrue(fc.isGraduatedColour());
439     assertTrue(fc.hasThreshold());
440     assertTrue(fc.isAboveThreshold());
441     assertEquals(15f, fc.getThreshold());
442     assertEquals(Color.RED, fc.getMinColour());
443     assertEquals(Color.GREEN, fc.getMaxColour());
444     assertEquals(10f, fc.getMin());
445     assertEquals(20f, fc.getMax());
446     assertTrue(fc.isAutoScaled());
447
448     /*
449      * graduated colour (by RGB triplet) (below threshold), absolute scale
450      */
451     descriptor = String.format("255,0,0|0,255,0|abso|10.0|20.0|below|15");
452     fc = FeatureColour.parseJalviewFeatureColour(descriptor);
453     assertTrue(fc.isGraduatedColour());
454     assertFalse(fc.isAutoScaled());
455     assertTrue(fc.hasThreshold());
456     assertTrue(fc.isBelowThreshold());
457     assertEquals(15f, fc.getThreshold());
458     assertEquals(Color.RED, fc.getMinColour());
459     assertEquals(Color.GREEN, fc.getMaxColour());
460     assertEquals(10f, fc.getMin());
461     assertEquals(20f, fc.getMax());
462
463     descriptor = String
464             .format("blue|255,0,255|absolute|20.0|95.0|below|66.0");
465     fc = FeatureColour.parseJalviewFeatureColour(descriptor);
466     assertTrue(fc.isGraduatedColour());
467   }
468
469   @Test(groups = { "Functional" })
470   public void testGetColor_colourByAttributeText()
471   {
472     FeatureColour fc = new FeatureColour();
473     fc.setColourByLabel(true);
474     fc.setAttributeName("consequence");
475     SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 1f,
476             null);
477
478     /*
479      * if feature has no such attribute, use 'no value' colour
480      */
481     assertEquals(FeatureColour.DEFAULT_NO_COLOUR, fc.getColor(sf));
482
483     /*
484      * if feature has attribute, generate colour from value
485      */
486     sf.setValue("consequence", "benign");
487     Color expected = ColorUtils.createColourFromName("benign");
488     assertEquals(expected, fc.getColor(sf));
489   }
490
491   @Test(groups = { "Functional" })
492   public void testGetColor_GraduatedByAttributeValue()
493   {
494     /*
495      * graduated colour based on attribute value for AF
496      * given a min-max range of 0-100
497      */
498     FeatureColour fc = new FeatureColour(new Color(50, 100, 150),
499             new Color(150, 200, 250), Color.yellow, 0f, 100f);
500     String attName = "AF";
501     fc.setAttributeName(attName);
502
503     /*
504      * first case: feature lacks the attribute - use 'no value' colour
505      */
506     SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 75f,
507             null);
508     assertEquals(Color.yellow, fc.getColor(sf));
509
510     /*
511      * second case: attribute present but not numeric - treat as if absent
512      */
513     sf.setValue(attName, "twelve");
514     assertEquals(Color.yellow, fc.getColor(sf));
515
516     /*
517      * third case: valid attribute value
518      */
519     sf.setValue(attName, "20.0");
520     Color expected = new Color(70, 120, 170);
521     assertEquals(expected, fc.getColor(sf));
522   }
523 }