X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=test%2Fjalview%2Frenderer%2Fseqfeatures%2FFeatureRendererTest.java;h=723f3b8c9dcf3cc7e8404cc1d0bf0908bfda2801;hb=3c8a25936a2d805e7e3d7ab82f83b13135406d18;hp=5bda3162c88b7d9a2009a35b6f5245aa61955c35;hpb=fa6001e965a3c35eaa93496d661a1f515dc0a426;p=jalview.git diff --git a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java index 5bda316..723f3b8 100644 --- a/test/jalview/renderer/seqfeatures/FeatureRendererTest.java +++ b/test/jalview/renderer/seqfeatures/FeatureRendererTest.java @@ -1,20 +1,48 @@ +/* + * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) + * Copyright (C) $$Year-Rel$$ The Jalview Authors + * + * This file is part of Jalview. + * + * Jalview is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 + * of the License, or (at your option) any later version. + * + * Jalview is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Jalview. If not, see . + * The Jalview Authors are detailed in the 'AUTHORS' file. + */ package jalview.renderer.seqfeatures; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import jalview.api.AlignViewportI; import jalview.api.FeatureColourI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.datamodel.features.FeatureMatcher; +import jalview.datamodel.features.FeatureMatcherSet; +import jalview.datamodel.features.FeatureMatcherSetI; import jalview.gui.AlignFrame; import jalview.io.DataSourceType; import jalview.io.FileLoader; import jalview.schemes.FeatureColour; +import jalview.util.matcher.Condition; +import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean; import java.awt.Color; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -60,19 +88,23 @@ public class FeatureRendererTest 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")); + 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(), 2); + 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")); @@ -104,18 +136,20 @@ public class FeatureRendererTest * check render order (last is on top) */ List renderOrder = fr.getRenderOrder(); - assertEquals(renderOrder, Arrays.asList("Rfam", "Pfam")); + 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[2][]; + FeatureSettingsBean[] data = new FeatureSettingsBean[3]; FeatureColourI colour = new FeatureColour(Color.RED); - data[0] = new Object[] { "Rfam", colour, true }; - data[1] = new Object[] { "Pfam", colour, false }; + data[0] = new FeatureSettingsBean("Rfam", colour, null, true); + data[1] = new FeatureSettingsBean("Pfam", colour, null, false); + data[2] = new FeatureSettingsBean("Scop", colour, null, false); fr.setFeaturePriority(data); - assertEquals(fr.getRenderOrder(), Arrays.asList("Pfam", "Rfam")); + assertEquals(fr.getRenderOrder(), + Arrays.asList("Scop", "Pfam", "Rfam")); assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam")); /* @@ -126,15 +160,15 @@ public class FeatureRendererTest new SequenceFeature("Metal", "Desc", 14, 22, 8f, "MetalGroup")); fr.findAllFeatures(true); assertEquals(fr.getRenderOrder(), - Arrays.asList("Pfam", "Rfam", "Metal")); + Arrays.asList("Scop", "Pfam", "Rfam", "Metal")); assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam", "Metal")); } @Test(groups = "Functional") - public void testFindFeaturesAtRes() + public void testFindFeaturesAtColumn() { - String seqData = ">s1\nabcdefghijklmnopqrstuvwxyz\n"; + String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n"; AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData, DataSourceType.PASTE); AlignViewportI av = af.getViewport(); @@ -144,7 +178,7 @@ public class FeatureRendererTest /* * with no features */ - List features = fr.findFeaturesAtRes(seq, 3); + List features = fr.findFeaturesAtColumn(seq, 3); assertTrue(features.isEmpty()); /* @@ -153,34 +187,43 @@ public class FeatureRendererTest SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f, "Group"); // non-positional seq.addSequenceFeature(sf1); - SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 5, 15, 1f, + SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f, "Group1"); seq.addSequenceFeature(sf2); - SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 5, 15, 1f, + SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f, "Group2"); seq.addSequenceFeature(sf3); - SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 5, 15, 1f, + 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", 4, - 12, 1f, "Group1"); + SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7, + 15, 1f, "Group1"); seq.addSequenceFeature(sf5); - SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 4, - 12, 1f, "Group2"); + SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7, + 15, 1f, "Group2"); seq.addSequenceFeature(sf6); - SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 4, - 12, 1f, null); + 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.findFeaturesAtRes(seq, 12); // all positional + features = fr.findFeaturesAtColumn(seq, 15); // all positional assertEquals(features.size(), 6); assertTrue(features.contains(sf2)); assertTrue(features.contains(sf3)); @@ -192,7 +235,7 @@ public class FeatureRendererTest /* * at a non-contact position */ - features = fr.findFeaturesAtRes(seq, 11); + features = fr.findFeaturesAtColumn(seq, 14); assertEquals(features.size(), 3); assertTrue(features.contains(sf2)); assertTrue(features.contains(sf3)); @@ -201,14 +244,16 @@ public class FeatureRendererTest /* * 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 }; + FeatureSettingsBean[] data = new FeatureSettingsBean[4]; + data[0] = new FeatureSettingsBean("Type1", colour, null, true); + data[1] = new FeatureSettingsBean("Type2", colour, null, false); + data[2] = new FeatureSettingsBean("Type3", colour, null, true); + data[3] = new FeatureSettingsBean("Disulphide Bond", colour, null, + true); fr.setFeaturePriority(data); - features = fr.findFeaturesAtRes(seq, 12); + + features = fr.findFeaturesAtColumn(seq, 15); assertEquals(features.size(), 5); // no sf2 assertTrue(features.contains(sf3)); assertTrue(features.contains(sf4)); @@ -220,10 +265,343 @@ public class FeatureRendererTest * make "Group2" not displayed */ fr.setGroupVisibility("Group2", false); - features = fr.findFeaturesAtRes(seq, 12); + + 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)); + + /* + * give "Type3" features a graduated colour scheme + * - first with no threshold + */ + FeatureColourI gc = new FeatureColour(Color.green, Color.yellow, + Color.red, null, 0f, 10f); + fr.getFeatureColours().put("Type3", gc); + features = fr.findFeaturesAtColumn(seq, 8); + assertTrue(features.contains(sf4)); + // now with threshold > 2f - feature score of 1f is excluded + gc.setAboveThreshold(true); + gc.setThreshold(2f); + features = fr.findFeaturesAtColumn(seq, 8); + assertFalse(features.contains(sf4)); + + /* + * make "Type3" graduated colour by attribute "AF" + * - first with no attribute held - feature should be excluded + */ + gc.setAttributeName("AF"); + features = fr.findFeaturesAtColumn(seq, 8); + assertFalse(features.contains(sf4)); + // now with the attribute above threshold - should be included + sf4.setValue("AF", "2.4"); + features = fr.findFeaturesAtColumn(seq, 8); + assertTrue(features.contains(sf4)); + // now with the attribute below threshold - should be excluded + sf4.setValue("AF", "1.4"); + features = fr.findFeaturesAtColumn(seq, 8); + assertFalse(features.contains(sf4)); + } + + @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); // empty list, does nothing + + SequenceI seq = av.getAlignment().getSequenceAt(0); + SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN, + "group1"); + SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f, + "group2"); + SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f, + "group3"); + SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f, + "group4"); + SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f, + "group4"); + seq.addSequenceFeature(sf1); + seq.addSequenceFeature(sf2); + seq.addSequenceFeature(sf3); + seq.addSequenceFeature(sf4); + 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); + 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)); + + /* + * features in hidden groups are removed + */ + fr.setGroupVisibility("group2", false); + fr.setGroupVisibility("group3", false); + features = seq.getSequenceFeatures(); + fr.filterFeaturesForDisplay(features); + assertEquals(features.size(), 2); + assertTrue(features.contains(sf1) || features.contains(sf4)); + assertFalse(features.contains(sf1) && features.contains(sf4)); + assertFalse(features.contains(sf2)); + assertFalse(features.contains(sf3)); + assertTrue(features.contains(sf5)); + + /* + * no filtering if transparency is applied + */ + fr.setTransparency(0.5f); + features = seq.getSequenceFeatures(); + fr.filterFeaturesForDisplay(features); + assertEquals(features.size(), 5); + } + + @Test(groups = "Functional") + public void testGetColour() + { + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(">s1\nABCD\n", + DataSourceType.PASTE); + AlignViewportI av = af.getViewport(); + FeatureRenderer fr = new FeatureRenderer(av); + + /* + * simple colour, feature type and group displayed + */ + FeatureColourI fc = new FeatureColour(Color.red); + fr.getFeatureColours().put("Cath", fc); + SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN, + "group1"); + assertEquals(fr.getColour(sf1), Color.red); + + /* + * hide feature type, then unhide + * - feature type visibility should not affect the result + */ + FeatureSettingsBean[] data = new FeatureSettingsBean[1]; + data[0] = new FeatureSettingsBean("Cath", fc, null, false); + fr.setFeaturePriority(data); + assertEquals(fr.getColour(sf1), Color.red); + data[0] = new FeatureSettingsBean("Cath", fc, null, true); + fr.setFeaturePriority(data); + assertEquals(fr.getColour(sf1), Color.red); + + /* + * hide feature group, then unhide + */ + fr.setGroupVisibility("group1", false); + assertNull(fr.getColour(sf1)); + fr.setGroupVisibility("group1", true); + assertEquals(fr.getColour(sf1), Color.red); + + /* + * graduated colour by score, no threshold, no score + * + */ + FeatureColourI gc = new FeatureColour(Color.red, Color.yellow, + Color.red, Color.green, 1f, 11f); + fr.getFeatureColours().put("Cath", gc); + assertEquals(fr.getColour(sf1), Color.green); + + /* + * graduated colour by score, no threshold, with score value + */ + SequenceFeature sf2 = new SequenceFeature("Cath", "", 6, 8, 6f, + "group1"); + // score 6 is half way from yellow(255, 255, 0) to red(255, 0, 0) + Color expected = new Color(255, 128, 0); + assertEquals(fr.getColour(sf2), expected); + + /* + * above threshold, score is above threshold - no change + */ + gc.setAboveThreshold(true); + gc.setThreshold(5f); + assertEquals(fr.getColour(sf2), expected); + + /* + * threshold is min-max; now score 6 is 1/6 of the way from 5 to 11 + * or from yellow(255, 255, 0) to red(255, 0, 0) + */ + gc = new FeatureColour(Color.red, Color.yellow, Color.red, Color.green, + 5f, 11f); + fr.getFeatureColours().put("Cath", gc); + gc.setAutoScaled(false); // this does little other than save a checkbox setting! + assertEquals(fr.getColour(sf2), new Color(255, 213, 0)); + + /* + * feature score is below threshold - no colour + */ + gc.setAboveThreshold(true); + gc.setThreshold(7f); + assertNull(fr.getColour(sf2)); + + /* + * feature score is above threshold - no colour + */ + gc.setBelowThreshold(true); + gc.setThreshold(3f); + assertNull(fr.getColour(sf2)); + + /* + * colour by feature attribute value + * first with no value held + */ + gc = new FeatureColour(Color.red, Color.yellow, Color.red, Color.green, + 1f, 11f); + fr.getFeatureColours().put("Cath", gc); + gc.setAttributeName("AF"); + assertEquals(fr.getColour(sf2), Color.green); + + // with non-numeric attribute value + sf2.setValue("AF", "Five"); + assertEquals(fr.getColour(sf2), Color.green); + + // with numeric attribute value + sf2.setValue("AF", "6"); + assertEquals(fr.getColour(sf2), expected); + + // with numeric value outwith threshold + gc.setAboveThreshold(true); + gc.setThreshold(10f); + assertNull(fr.getColour(sf2)); + + // with filter on AF < 4 + gc.setAboveThreshold(false); + assertEquals(fr.getColour(sf2), expected); + FeatureMatcherSetI filter = new FeatureMatcherSet(); + filter.and(FeatureMatcher.byAttribute(Condition.LT, "4.0", "AF")); + fr.setFeatureFilter("Cath", filter); + assertNull(fr.getColour(sf2)); + + // with filter on 'Consequence contains missense' + filter = new FeatureMatcherSet(); + filter.and(FeatureMatcher.byAttribute(Condition.Contains, "missense", + "Consequence")); + fr.setFeatureFilter("Cath", filter); + // if feature has no Consequence attribute, no colour + assertNull(fr.getColour(sf2)); + // if attribute does not match filter, no colour + sf2.setValue("Consequence", "Synonymous"); + assertNull(fr.getColour(sf2)); + // attribute matches filter + sf2.setValue("Consequence", "Missense variant"); + assertEquals(fr.getColour(sf2), expected); + + // with filter on CSQ:Feature contains "ENST01234" + filter = new FeatureMatcherSet(); + filter.and(FeatureMatcher.byAttribute(Condition.Matches, "ENST01234", + "CSQ", "Feature")); + fr.setFeatureFilter("Cath", filter); + // if feature has no CSQ data, no colour + assertNull(fr.getColour(sf2)); + // if CSQ data does not include Feature, no colour + Map csqData = new HashMap<>(); + csqData.put("BIOTYPE", "Transcript"); + sf2.setValue("CSQ", csqData); + assertNull(fr.getColour(sf2)); + // if attribute does not match filter, no colour + csqData.put("Feature", "ENST9876"); + assertNull(fr.getColour(sf2)); + // attribute matches filter + csqData.put("Feature", "ENST01234"); + assertEquals(fr.getColour(sf2), expected); + } + + @Test(groups = "Functional") + public void testIsVisible() + { + String seqData = ">s1\nMLQGIFPRS\n"; + AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData, + DataSourceType.PASTE); + AlignViewportI av = af.getViewport(); + FeatureRenderer fr = new FeatureRenderer(av); + SequenceI seq = av.getAlignment().getSequenceAt(0); + SequenceFeature sf = new SequenceFeature("METAL", "Desc", 10, 10, 1f, + "Group"); + sf.setValue("AC", "11"); + sf.setValue("CLIN_SIG", "Likely Pathogenic"); + seq.addSequenceFeature(sf); + + assertFalse(fr.isVisible(null)); + + /* + * initial state FeatureRenderer hasn't 'found' feature + * and so its feature type has not yet been set visible + */ + assertFalse(fr.getDisplayedFeatureCols().containsKey("METAL")); + assertFalse(fr.isVisible(sf)); + + fr.findAllFeatures(true); + assertTrue(fr.isVisible(sf)); + + /* + * feature group not visible + */ + fr.setGroupVisibility("Group", false); + assertFalse(fr.isVisible(sf)); + fr.setGroupVisibility("Group", true); + assertTrue(fr.isVisible(sf)); + + /* + * feature score outwith colour threshold (score > 2) + */ + FeatureColourI fc = new FeatureColour(null, Color.white, Color.black, + Color.white, 0, 10); + fc.setAboveThreshold(true); + fc.setThreshold(2f); + fr.setColour("METAL", fc); + assertFalse(fr.isVisible(sf)); // score 1 is not above threshold 2 + fc.setBelowThreshold(true); + assertTrue(fr.isVisible(sf)); // score 1 is below threshold 2 + + /* + * colour with threshold on attribute AC (value is 11) + */ + fc.setAttributeName("AC"); + assertFalse(fr.isVisible(sf)); // value 11 is not below threshold 2 + fc.setAboveThreshold(true); + assertTrue(fr.isVisible(sf)); // value 11 is above threshold 2 + + fc.setAttributeName("AF"); // attribute AF is absent in sf + assertTrue(fr.isVisible(sf)); // feature is not excluded by threshold + + FeatureMatcherSetI filter = new FeatureMatcherSet(); + filter.and(FeatureMatcher.byAttribute(Condition.Contains, "pathogenic", + "CLIN_SIG")); + fr.setFeatureFilter("METAL", filter); + assertTrue(fr.isVisible(sf)); // feature matches filter + filter.and(FeatureMatcher.byScore(Condition.LE, "0.4")); + assertFalse(fr.isVisible(sf)); // feature doesn't match filter } }