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 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
*
* - gap position
* - no features present
* - features present but show features turned off
* - features displayed but selected feature turned off
* - features displayed but feature group turned off
* - feature displayed but none at the specified position
* - multiple features at position, with no transparency
* - multiple features at position, with transparency
* - score graduated feature colour
* - contact feature start at the selected position
* - contact feature end at the selected position
* - contact feature straddling the selected position (not shown)
*
*/
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
*/
Object[][] data = new Object[2][];
data[0] = new Object[] { "Metal", red, true };
data[1] = new Object[] { "Domain", green, true };
fr.setFeaturePriority(data);
c = finder.findFeatureColour(Color.blue, seq, 10);
assertEquals(c, Color.red);
/*
* ..and turn off display of Metal
*/
data[0][2] = 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??
*/
Object[][] data = new Object[1][];
data[0] = new Object[] { "Metal", red, false };
fr.setFeaturePriority(data);
c = finder.findFeatureColour(Color.blue, seq, 10);
assertEquals(c, Color.blue);
/*
* turn display of Metal back on
*/
data[0] = new Object[] { "Metal", red, 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
*/
Object[][] data = new Object[2][];
data[0] = new Object[] { "Metal", red, true };
data[1] = new Object[] { "Domain", green, 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][2] = 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
*/
Object[][] data = new Object[1][];
data[0] = new Object[] { "Metal", red, 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
*/
Object[][] data = new Object[2][];
data[0] = new Object[] { kdFeature, fc, true };
data[1] = new Object[] { metalFeature, green, 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);
}
}