package jalview.renderer.seqfeatures; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import jalview.api.AlignViewportI; import jalview.api.FeatureColourI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.gui.AlignFrame; import jalview.io.DataSourceType; import jalview.io.FileLoader; import jalview.schemes.FeatureColour; import java.awt.Color; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import org.testng.annotations.Test; public class FeatureRendererTest { @Test(groups = "Functional") public void testFindAllFeatures() { String seqData = ">s1\nabcdef\n>s2\nabcdef\n>s3\nabcdef\n>s4\nabcdef\n"; AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData, DataSourceType.PASTE); AlignViewportI av = af.getViewport(); FeatureRenderer fr = new FeatureRenderer(av); /* * with no features */ fr.findAllFeatures(true); assertTrue(fr.getRenderOrder().isEmpty()); assertTrue(fr.getFeatureGroups().isEmpty()); List seqs = av.getAlignment().getSequences(); // add a non-positional feature - should be ignored by FeatureRenderer SequenceFeature sf1 = new SequenceFeature("Type", "Desc", 0, 0, 1f, "Group"); seqs.get(0).addSequenceFeature(sf1); fr.findAllFeatures(true); // ? bug - types and groups added for non-positional features List types = fr.getRenderOrder(); List groups = fr.getFeatureGroups(); assertEquals(types.size(), 0); assertFalse(types.contains("Type")); assertEquals(groups.size(), 0); assertFalse(groups.contains("Group")); // add some positional features seqs.get(1).addSequenceFeature( new SequenceFeature("Pfam", "Desc", 5, 9, 1f, "PfamGroup")); seqs.get(2).addSequenceFeature( new SequenceFeature("Pfam", "Desc", 14, 22, 2f, "RfamGroup")); // bug in findAllFeatures - group not checked for a known feature type seqs.get(2).addSequenceFeature( new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, "RfamGroup")); // existing feature type with null group seqs.get(3).addSequenceFeature( new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null)); // new feature type with null group seqs.get(3).addSequenceFeature( new SequenceFeature("Scop", "Desc", 5, 9, Float.NaN, null)); // null value for type produces NullPointerException fr.findAllFeatures(true); types = fr.getRenderOrder(); groups = fr.getFeatureGroups(); assertEquals(types.size(), 3); assertFalse(types.contains("Type")); assertTrue(types.contains("Pfam")); assertTrue(types.contains("Rfam")); assertTrue(types.contains("Scop")); assertEquals(groups.size(), 2); assertFalse(groups.contains("Group")); assertTrue(groups.contains("PfamGroup")); assertTrue(groups.contains("RfamGroup")); assertFalse(groups.contains(null)); // null group is ignored /* * check min-max values */ Map minMax = fr.getMinMax(); assertEquals(minMax.size(), 1); // non-positional and NaN not stored assertEquals(minMax.get("Pfam")[0][0], 1f); // positional min assertEquals(minMax.get("Pfam")[0][1], 2f); // positional max // increase max for Pfam, add scores for Rfam seqs.get(0).addSequenceFeature( new SequenceFeature("Pfam", "Desc", 14, 22, 8f, "RfamGroup")); seqs.get(1).addSequenceFeature( new SequenceFeature("Rfam", "Desc", 5, 9, 6f, "RfamGroup")); fr.findAllFeatures(true); // note minMax is not a defensive copy, shouldn't expose this assertEquals(minMax.size(), 2); assertEquals(minMax.get("Pfam")[0][0], 1f); assertEquals(minMax.get("Pfam")[0][1], 8f); assertEquals(minMax.get("Rfam")[0][0], 6f); assertEquals(minMax.get("Rfam")[0][1], 6f); /* * check render order (last is on top) */ List renderOrder = fr.getRenderOrder(); assertEquals(renderOrder, Arrays.asList("Scop", "Rfam", "Pfam")); /* * change render order (todo: an easier way) * nb here last comes first in the data array */ Object[][] data = new Object[3][]; FeatureColourI colour = new FeatureColour(Color.RED); data[0] = new Object[] { "Rfam", colour, true }; data[1] = new Object[] { "Pfam", colour, false }; data[2] = new Object[] { "Scop", colour, false }; fr.setFeaturePriority(data); assertEquals(fr.getRenderOrder(), Arrays.asList("Scop", "Pfam", "Rfam")); assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam")); /* * add a new feature type: should go on top of render order as visible, * other feature ordering and visibility should be unchanged */ seqs.get(2).addSequenceFeature( new SequenceFeature("Metal", "Desc", 14, 22, 8f, "MetalGroup")); fr.findAllFeatures(true); assertEquals(fr.getRenderOrder(), Arrays.asList("Scop", "Pfam", "Rfam", "Metal")); assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam", "Metal")); } @Test(groups = "Functional") public void testFindFeaturesAtColumn() { String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n"; AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData, DataSourceType.PASTE); AlignViewportI av = af.getViewport(); FeatureRenderer fr = new FeatureRenderer(av); SequenceI seq = av.getAlignment().getSequenceAt(0); /* * with no features */ List features = fr.findFeaturesAtColumn(seq, 3); assertTrue(features.isEmpty()); /* * add features */ SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f, "Group"); // non-positional seq.addSequenceFeature(sf1); SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f, "Group1"); seq.addSequenceFeature(sf2); SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f, "Group2"); seq.addSequenceFeature(sf3); SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 8, 18, 1f, null); // null group is always treated as visible seq.addSequenceFeature(sf4); /* * add contact features */ SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7, 15, 1f, "Group1"); seq.addSequenceFeature(sf5); SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7, 15, 1f, "Group2"); seq.addSequenceFeature(sf6); SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7, 15, 1f, null); seq.addSequenceFeature(sf7); // feature spanning B--C SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f, "Group"); seq.addSequenceFeature(sf8); // contact feature B/C SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5, 6, 1f, "Group"); seq.addSequenceFeature(sf9); /* * let feature renderer discover features (and make visible) */ fr.findAllFeatures(true); features = fr.findFeaturesAtColumn(seq, 15); // all positional assertEquals(features.size(), 6); assertTrue(features.contains(sf2)); assertTrue(features.contains(sf3)); assertTrue(features.contains(sf4)); assertTrue(features.contains(sf5)); assertTrue(features.contains(sf6)); assertTrue(features.contains(sf7)); /* * at a non-contact position */ features = fr.findFeaturesAtColumn(seq, 14); assertEquals(features.size(), 3); assertTrue(features.contains(sf2)); assertTrue(features.contains(sf3)); assertTrue(features.contains(sf4)); /* * make "Type2" not displayed */ Object[][] data = new Object[4][]; FeatureColourI colour = new FeatureColour(Color.RED); data[0] = new Object[] { "Type1", colour, true }; data[1] = new Object[] { "Type2", colour, false }; data[2] = new Object[] { "Type3", colour, true }; data[3] = new Object[] { "Disulphide Bond", colour, true }; fr.setFeaturePriority(data); features = fr.findFeaturesAtColumn(seq, 15); assertEquals(features.size(), 5); // no sf2 assertTrue(features.contains(sf3)); assertTrue(features.contains(sf4)); assertTrue(features.contains(sf5)); assertTrue(features.contains(sf6)); assertTrue(features.contains(sf7)); /* * make "Group2" not displayed */ fr.setGroupVisibility("Group2", false); features = fr.findFeaturesAtColumn(seq, 15); assertEquals(features.size(), 3); // no sf2, sf3, sf6 assertTrue(features.contains(sf4)); assertTrue(features.contains(sf5)); assertTrue(features.contains(sf7)); // features 'at' a gap between b and c // - returns enclosing feature BC but not contact feature B/C features = fr.findFeaturesAtColumn(seq, 4); assertEquals(features.size(), 1); assertTrue(features.contains(sf8)); features = fr.findFeaturesAtColumn(seq, 5); assertEquals(features.size(), 1); assertTrue(features.contains(sf8)); } @Test(groups = "Functional") public void testFilterFeaturesForDisplay() { String seqData = ">s1\nabcdef\n"; AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData, DataSourceType.PASTE); AlignViewportI av = af.getViewport(); FeatureRenderer fr = new FeatureRenderer(av); List features = new ArrayList<>(); fr.filterFeaturesForDisplay(features, null); // empty list, does nothing SequenceI seq = av.getAlignment().getSequenceAt(0); SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN, "group1"); seq.addSequenceFeature(sf1); SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f, "group2"); seq.addSequenceFeature(sf2); SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f, "group3"); seq.addSequenceFeature(sf3); SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f, "group4"); seq.addSequenceFeature(sf4); SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f, "group4"); seq.addSequenceFeature(sf5); fr.findAllFeatures(true); features = seq.getSequenceFeatures(); assertEquals(features.size(), 5); assertTrue(features.contains(sf1)); assertTrue(features.contains(sf2)); assertTrue(features.contains(sf3)); assertTrue(features.contains(sf4)); assertTrue(features.contains(sf5)); /* * filter out duplicate (co-located) features * note: which gets removed is not guaranteed */ fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue)); assertEquals(features.size(), 3); assertTrue(features.contains(sf1) || features.contains(sf4)); assertFalse(features.contains(sf1) && features.contains(sf4)); assertTrue(features.contains(sf2) || features.contains(sf3)); assertFalse(features.contains(sf2) && features.contains(sf3)); assertTrue(features.contains(sf5)); /* * hide group 3 - sf3 is removed, sf2 is retained */ fr.setGroupVisibility("group3", false); features = seq.getSequenceFeatures(); fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue)); assertEquals(features.size(), 3); assertTrue(features.contains(sf1) || features.contains(sf4)); assertFalse(features.contains(sf1) && features.contains(sf4)); assertTrue(features.contains(sf2)); assertFalse(features.contains(sf3)); assertTrue(features.contains(sf5)); /* * hide group 2, show group 3 - sf2 is removed, sf3 is retained */ fr.setGroupVisibility("group2", false); fr.setGroupVisibility("group3", true); features = seq.getSequenceFeatures(); fr.filterFeaturesForDisplay(features, null); assertEquals(features.size(), 3); assertTrue(features.contains(sf1) || features.contains(sf4)); assertFalse(features.contains(sf1) && features.contains(sf4)); assertFalse(features.contains(sf2)); assertTrue(features.contains(sf3)); assertTrue(features.contains(sf5)); /* * no filtering of co-located features with graduated colour scheme * filterFeaturesForDisplay does _not_ check colour threshold * sf2 is removed as its group is hidden */ features = seq.getSequenceFeatures(); fr.filterFeaturesForDisplay(features, new FeatureColour(Color.black, Color.white, 0f, 1f)); assertEquals(features.size(), 4); assertTrue(features.contains(sf1)); assertTrue(features.contains(sf3)); assertTrue(features.contains(sf4)); assertTrue(features.contains(sf5)); /* * co-located features with colour by label * should not get filtered */ features = seq.getSequenceFeatures(); FeatureColour fc = new FeatureColour(Color.black); fc.setColourByLabel(true); fr.filterFeaturesForDisplay(features, fc); assertEquals(features.size(), 4); assertTrue(features.contains(sf1)); assertTrue(features.contains(sf3)); assertTrue(features.contains(sf4)); assertTrue(features.contains(sf5)); } }