package jalview.renderer.seqfeatures; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotEquals; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import jalview.api.FeatureColourI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.gui.AlignFrame; import jalview.gui.AlignViewport; import jalview.gui.FeatureRenderer; import jalview.io.DataSourceType; import jalview.io.FileLoader; import jalview.schemes.FeatureColour; import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean; import java.awt.Color; import java.util.List; import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; /** * Unit tests for feature colour determination, including but not limited to * */ public class FeatureColourFinderTest { private AlignViewport av; private SequenceI seq; private FeatureColourFinder finder; private AlignFrame af; private FeatureRenderer fr; @BeforeTest(alwaysRun = true) public void setUp() { // aligned column 8 is sequence position 6 String s = ">s1\nABCDE---FGHIJKLMNOPQRSTUVWXYZ\n"; af = new FileLoader().LoadFileWaitTillLoaded(s, DataSourceType.PASTE); av = af.getViewport(); seq = av.getAlignment().getSequenceAt(0); fr = af.getFeatureRenderer(); finder = new FeatureColourFinder(fr); } /** * Clear down any sequence features before each test (not as easy as it * sounds...) */ @BeforeMethod(alwaysRun = true) public void setUpBeforeTest() { List sfs = seq.getSequenceFeatures(); for (SequenceFeature sf : sfs) { seq.deleteFeature(sf); } fr.findAllFeatures(true); /* * reset all feature groups to visible */ for (String group : fr.getGroups(false)) { fr.setGroupVisibility(group, true); } fr.clearRenderOrder(); av.setShowSequenceFeatures(true); } @Test(groups = "Functional") public void testFindFeatureColour_noFeatures() { av.setShowSequenceFeatures(false); Color c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.blue); av.setShowSequenceFeatures(true); c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.blue); } @Test(groups = "Functional") public void testFindFeatureColour_noFeaturesShown() { seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12, Float.NaN, "MetalGroup")); fr.featuresAdded(); av.setShowSequenceFeatures(false); Color c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.blue); } @Test(groups = "Functional") public void testFindFeatureColour_singleFeatureAtPosition() { seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12, Float.NaN, "MetalGroup")); fr.setColour("Metal", new FeatureColour(Color.red)); fr.featuresAdded(); av.setShowSequenceFeatures(true); Color c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.red); } /** * feature colour at a gap is null (not white) - a user defined colour scheme * can then provide a bespoke gap colour if configured to do so */ @Test(groups = "Functional") public void testFindFeatureColour_gapPosition() { seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12, 0f, null)); fr.setColour("Metal", new FeatureColour(Color.red)); fr.featuresAdded(); av.setShowSequenceFeatures(true); Color c = finder.findFeatureColour(null, seq, 6); assertNull(c); } @Test(groups = "Functional") public void testFindFeatureColour_multipleFeaturesAtPositionNoTransparency() { /* * featuresAdded -> FeatureRendererModel.updateRenderOrder which adds any * new features 'on top' (but reverses the order of any added features) */ seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12, Float.NaN, "MetalGroup")); FeatureColour red = new FeatureColour(Color.red); fr.setColour("Metal", red); fr.featuresAdded(); seq.addSequenceFeature(new SequenceFeature("Domain", "Domain", 4, 15, Float.NaN, "DomainGroup")); FeatureColour green = new FeatureColour(Color.green); fr.setColour("Domain", green); fr.featuresAdded(); av.setShowSequenceFeatures(true); /* * expect Domain (green) to be rendered above Metal (red) */ Color c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.green); /* * now promote Metal above Domain * - currently no way other than mimicking reordering of * table in Feature Settings */ FeatureSettingsBean[] data = new FeatureSettingsBean[2]; data[0] = new FeatureSettingsBean("Metal", red, null, true); data[1] = new FeatureSettingsBean("Domain", green, null, true); fr.setFeaturePriority(data); c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.red); /* * ..and turn off display of Metal */ data[0] = new FeatureSettingsBean("Metal", red, null, false); fr.setFeaturePriority(data); c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.green); } @Test(groups = "Functional") public void testFindFeatureColour_singleFeatureNotAtPosition() { seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 8, 12, Float.NaN, "MetalGroup")); fr.setColour("Metal", new FeatureColour(Color.red)); fr.featuresAdded(); av.setShowSequenceFeatures(true); // column 2 = sequence position 3 Color c = finder.findFeatureColour(Color.blue, seq, 2); assertEquals(c, Color.blue); } @Test(groups = "Functional") public void testFindFeatureColour_featureTypeNotDisplayed() { seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12, Float.NaN, "MetalGroup")); FeatureColour red = new FeatureColour(Color.red); fr.setColour("Metal", red); fr.featuresAdded(); av.setShowSequenceFeatures(true); Color c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.red); /* * turn off display of Metal - is this the easiest way to do it?? */ FeatureSettingsBean[] data = new FeatureSettingsBean[1]; data[0] = new FeatureSettingsBean("Metal", red, null, false); fr.setFeaturePriority(data); c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.blue); /* * turn display of Metal back on */ data[0] = new FeatureSettingsBean("Metal", red, null, true); fr.setFeaturePriority(data); c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.red); } @Test(groups = "Functional") public void testFindFeatureColour_featureGroupNotDisplayed() { seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12, Float.NaN, "MetalGroup")); FeatureColour red = new FeatureColour(Color.red); fr.setColour("Metal", red); fr.featuresAdded(); av.setShowSequenceFeatures(true); Color c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.red); /* * turn off display of MetalGroup */ fr.setGroupVisibility("MetalGroup", false); c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.blue); /* * turn display of MetalGroup back on */ fr.setGroupVisibility("MetalGroup", true); c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.red); } @Test(groups = "Functional") public void testFindFeatureColour_contactFeature() { /* * currently contact feature == type "Disulphide Bond" or "Disulfide Bond" !! */ seq.addSequenceFeature(new SequenceFeature("Disulphide Bond", "Contact", 2, 12, Float.NaN, "Disulphide")); fr.setColour("Disulphide Bond", new FeatureColour(Color.red)); fr.featuresAdded(); av.setShowSequenceFeatures(true); /* * Contact positions are residues 2 and 12 * which are columns 1 and 14 * positions in between don't count for a contact feature! */ Color c = finder.findFeatureColour(Color.blue, seq, 10); assertEquals(c, Color.blue); c = finder.findFeatureColour(Color.blue, seq, 8); assertEquals(c, Color.blue); c = finder.findFeatureColour(Color.blue, seq, 1); assertEquals(c, Color.red); c = finder.findFeatureColour(Color.blue, seq, 14); assertEquals(c, Color.red); } @Test(groups = "Functional") public void testFindFeatureAtEnd() { /* * terminal residue feature */ seq.addSequenceFeature(new SequenceFeature("PDBRESNUM", "pdb res 1", seq.getEnd(), seq.getEnd(), Float.NaN, "1seq.pdb")); fr.setColour("PDBRESNUM", new FeatureColour(Color.red)); fr.featuresAdded(); av.setShowSequenceFeatures(true); /* * final column should have PDBRESNUM feature, the others not */ Color c = finder.findFeatureColour(Color.blue, seq, seq.getLength() - 2); assertNotEquals(c, Color.red); c = finder.findFeatureColour(Color.blue, seq, seq.getLength() - 1); assertEquals(c, Color.red); } @Test(groups = "Functional") public void testFindFeatureColour_graduatedFeatureColour() { seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 2, 2, 0f, "KdGroup")); seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 4, 4, 5f, "KdGroup")); seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 7, 7, 10f, "KdGroup")); /* * graduated colour from 0 to 10 */ Color min = new Color(100, 50, 150); Color max = new Color(200, 0, 100); FeatureColourI fc = new FeatureColour(min, max, 0, 10); fr.setColour("kd", fc); fr.featuresAdded(); av.setShowSequenceFeatures(true); /* * position 2, column 1, score 0 - minimum colour in range */ Color c = finder.findFeatureColour(Color.blue, seq, 1); assertEquals(c, min); /* * position 7, column 9, score 10 - maximum colour in range */ c = finder.findFeatureColour(Color.blue, seq, 9); assertEquals(c, max); /* * position 4, column 3, score 5 - half way from min to max */ c = finder.findFeatureColour(Color.blue, seq, 3); assertEquals(c, new Color(150, 25, 125)); } @Test(groups = "Functional") public void testFindFeatureColour_transparencySingleFeature() { seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12, Float.NaN, "MetalGroup")); FeatureColour red = new FeatureColour(Color.red); fr.setColour("Metal", red); fr.featuresAdded(); av.setShowSequenceFeatures(true); /* * the FeatureSettings transparency slider has range 0-70 which * corresponds to a transparency value of 1 - 0.3 * A value of 0.4 gives a combination of * 0.4 * red(255, 0, 0) + 0.6 * cyan(0, 255, 255) = (102, 153, 153) */ fr.setTransparency(0.4f); Color c = finder.findFeatureColour(Color.cyan, seq, 10); assertEquals(c, new Color(102, 153, 153)); } @Test(groups = "Functional") public void testFindFeatureColour_transparencyTwoFeatures() { seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12, Float.NaN, "MetalGroup")); FeatureColour red = new FeatureColour(Color.red); fr.setColour("Metal", red); fr.featuresAdded(); seq.addSequenceFeature(new SequenceFeature("Domain", "Domain", 4, 15, Float.NaN, "DomainGroup")); FeatureColour green = new FeatureColour(Color.green); fr.setColour("Domain", green); fr.featuresAdded(); av.setShowSequenceFeatures(true); /* * Domain (green) rendered above Metal (red) above background (cyan) * 1) 0.6 * red(255, 0, 0) + 0.4 * cyan(0, 255, 255) = (153, 102, 102) * 2) 0.6* green(0, 255, 0) + 0.4 * (153, 102, 102) = (61, 194, 41) rounded */ fr.setTransparency(0.6f); Color c = finder.findFeatureColour(Color.cyan, seq, 10); assertEquals(c, new Color(61, 194, 41)); /* * now promote Metal above Domain * - currently no way other than mimicking reordering of * table in Feature Settings * Metal (red) rendered above Domain (green) above background (cyan) * 1) 0.6 * green(0, 255, 0) + 0.4 * cyan(0, 255, 255) = (0, 255, 102) * 2) 0.6* red(255, 0, 0) + 0.4 * (0, 255, 102) = (153, 102, 41) rounded */ FeatureSettingsBean[] data = new FeatureSettingsBean[2]; data[0] = new FeatureSettingsBean("Metal", red, null, true); data[1] = new FeatureSettingsBean("Domain", green, null, true); fr.setFeaturePriority(data); c = finder.findFeatureColour(Color.cyan, seq, 10); assertEquals(c, new Color(153, 102, 41)); /* * ..and turn off display of Metal * Domain (green) above background (pink) * 0.6 * green(0, 255, 0) + 0.4 * pink(255, 175, 175) = (102, 223, 70) */ data[0] = new FeatureSettingsBean("Metal", red, null, false); fr.setFeaturePriority(data); c = finder.findFeatureColour(Color.pink, seq, 10); assertEquals(c, new Color(102, 223, 70)); } @Test(groups = "Functional") public void testNoFeaturesDisplayed() { /* * no features on alignment to render */ assertTrue(finder.noFeaturesDisplayed()); /* * add a feature * it will be automatically set visible but we leave * the viewport configured not to show features */ av.setShowSequenceFeatures(false); seq.addSequenceFeature(new SequenceFeature("Metal", "Metal", 2, 12, Float.NaN, "MetalGroup")); FeatureColour red = new FeatureColour(Color.red); fr.setColour("Metal", red); fr.featuresAdded(); assertTrue(finder.noFeaturesDisplayed()); /* * turn on feature display */ av.setShowSequenceFeatures(true); assertFalse(finder.noFeaturesDisplayed()); /* * turn off display of Metal */ FeatureSettingsBean[] data = new FeatureSettingsBean[1]; data[0] = new FeatureSettingsBean("Metal", red, null, false); fr.setFeaturePriority(data); assertTrue(finder.noFeaturesDisplayed()); /* * turn display of Metal back on */ fr.setVisible("Metal"); assertFalse(finder.noFeaturesDisplayed()); /* * turn off MetalGroup - has no effect here since the group of a * sequence feature instance is independent of its type */ fr.setGroupVisibility("MetalGroup", false); assertFalse(finder.noFeaturesDisplayed()); /* * a finder with no feature renderer */ FeatureColourFinder finder2 = new FeatureColourFinder(null); assertTrue(finder2.noFeaturesDisplayed()); } @Test(groups = "Functional") public void testFindFeatureColour_graduatedWithThreshold() { String kdFeature = "kd"; String metalFeature = "Metal"; seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity", 2, 2, 0f, "KdGroup")); seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity", 4, 4, 5f, "KdGroup")); seq.addSequenceFeature(new SequenceFeature(metalFeature, "Fe", 4, 4, 5f, "MetalGroup")); seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity", 7, 7, 10f, "KdGroup")); /* * kd feature has graduated colour from 0 to 10 * above threshold value of 5 */ Color min = new Color(100, 50, 150); Color max = new Color(200, 0, 100); FeatureColourI fc = new FeatureColour(min, max, 0, 10); fc.setAboveThreshold(true); fc.setThreshold(5f); fr.setColour(kdFeature, fc); FeatureColour green = new FeatureColour(Color.green); fr.setColour(metalFeature, green); fr.featuresAdded(); /* * render order is kd above Metal */ FeatureSettingsBean[] data = new FeatureSettingsBean[2]; data[0] = new FeatureSettingsBean(kdFeature, fc, null, true); data[1] = new FeatureSettingsBean(metalFeature, green, null, true); fr.setFeaturePriority(data); av.setShowSequenceFeatures(true); /* * position 2, column 1, score 0 - below threshold - default colour */ Color c = finder.findFeatureColour(Color.blue, seq, 1); assertEquals(c, Color.blue); /* * position 4, column 3, score 5 - at threshold * should return Green (colour of Metal feature) */ c = finder.findFeatureColour(Color.blue, seq, 3); assertEquals(c, Color.green); /* * position 7, column 9, score 10 - maximum colour in range */ c = finder.findFeatureColour(Color.blue, seq, 9); assertEquals(c, max); /* * now colour below threshold of 5 */ fc.setBelowThreshold(true); /* * position 2, column 1, score 0 - min colour */ c = finder.findFeatureColour(Color.blue, seq, 1); assertEquals(c, min); /* * position 4, column 3, score 5 - at threshold * should return Green (colour of Metal feature) */ c = finder.findFeatureColour(Color.blue, seq, 3); assertEquals(c, Color.green); /* * position 7, column 9, score 10 - above threshold - default colour */ c = finder.findFeatureColour(Color.blue, seq, 9); assertEquals(c, Color.blue); } }