Class |
Line # |
Actions |
|||
---|---|---|---|---|---|
FeatureRendererTest | 59 | 335 | 10 |
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.renderer.seqfeatures; | |
22 | ||
23 | import static org.testng.Assert.assertEquals; | |
24 | import static org.testng.Assert.assertFalse; | |
25 | import static org.testng.Assert.assertNotNull; | |
26 | import static org.testng.Assert.assertNull; | |
27 | import static org.testng.Assert.assertSame; | |
28 | import static org.testng.Assert.assertTrue; | |
29 | ||
30 | import java.awt.Color; | |
31 | import java.util.ArrayList; | |
32 | import java.util.Arrays; | |
33 | import java.util.HashMap; | |
34 | import java.util.List; | |
35 | import java.util.Map; | |
36 | ||
37 | import org.testng.annotations.BeforeMethod; | |
38 | import org.testng.annotations.Test; | |
39 | ||
40 | import jalview.analysis.GeneticCodes; | |
41 | import jalview.api.AlignViewportI; | |
42 | import jalview.api.FeatureColourI; | |
43 | import jalview.bin.Jalview; | |
44 | import jalview.datamodel.MappedFeatures; | |
45 | import jalview.datamodel.SequenceFeature; | |
46 | import jalview.datamodel.SequenceI; | |
47 | import jalview.datamodel.features.FeatureMatcher; | |
48 | import jalview.datamodel.features.FeatureMatcherSet; | |
49 | import jalview.datamodel.features.FeatureMatcherSetI; | |
50 | import jalview.gui.AlignFrame; | |
51 | import jalview.gui.AlignViewport; | |
52 | import jalview.gui.Desktop; | |
53 | import jalview.io.DataSourceType; | |
54 | import jalview.io.FileLoader; | |
55 | import jalview.schemes.FeatureColour; | |
56 | import jalview.util.matcher.Condition; | |
57 | import jalview.viewmodel.seqfeatures.FeatureRendererModel.FeatureSettingsBean; | |
58 | ||
59 | public class FeatureRendererTest | |
60 | { | |
61 | 6 | @BeforeMethod(alwaysRun = true) |
62 | public void closeAll() | |
63 | { | |
64 | 6 | if (Desktop.instance != null) |
65 | 6 | Desktop.instance.closeAll_actionPerformed(null); |
66 | } | |
67 | ||
68 | 1 | @Test(groups = "Functional") |
69 | public void testFindAllFeatures() | |
70 | { | |
71 | 1 | String seqData = ">s1\nabcdef\n>s2\nabcdef\n>s3\nabcdef\n>s4\nabcdef\n"; |
72 | 1 | AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData, |
73 | DataSourceType.PASTE); | |
74 | 1 | AlignViewportI av = af.getViewport(); |
75 | 1 | FeatureRenderer fr = new FeatureRenderer(av); |
76 | ||
77 | /* | |
78 | * with no features | |
79 | */ | |
80 | 1 | fr.findAllFeatures(true); |
81 | 1 | assertTrue(fr.getRenderOrder().isEmpty()); |
82 | 1 | assertTrue(fr.getFeatureGroups().isEmpty()); |
83 | ||
84 | 1 | List<SequenceI> seqs = av.getAlignment().getSequences(); |
85 | ||
86 | // add a non-positional feature - should be ignored by FeatureRenderer | |
87 | 1 | SequenceFeature sf1 = new SequenceFeature("Type", "Desc", 0, 0, 1f, |
88 | "Group"); | |
89 | 1 | seqs.get(0).addSequenceFeature(sf1); |
90 | 1 | fr.findAllFeatures(true); |
91 | // ? bug - types and groups added for non-positional features | |
92 | 1 | List<String> types = fr.getRenderOrder(); |
93 | 1 | List<String> groups = fr.getFeatureGroups(); |
94 | 1 | assertEquals(types.size(), 0); |
95 | 1 | assertFalse(types.contains("Type")); |
96 | 1 | assertEquals(groups.size(), 0); |
97 | 1 | assertFalse(groups.contains("Group")); |
98 | ||
99 | // add some positional features | |
100 | 1 | seqs.get(1).addSequenceFeature( |
101 | new SequenceFeature("Pfam", "Desc", 5, 9, 1f, "PfamGroup")); | |
102 | 1 | seqs.get(2).addSequenceFeature( |
103 | new SequenceFeature("Pfam", "Desc", 14, 22, 2f, "RfamGroup")); | |
104 | // bug in findAllFeatures - group not checked for a known feature type | |
105 | 1 | seqs.get(2).addSequenceFeature(new SequenceFeature("Rfam", "Desc", 5, 9, |
106 | Float.NaN, "RfamGroup")); | |
107 | // existing feature type with null group | |
108 | 1 | seqs.get(3).addSequenceFeature( |
109 | new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null)); | |
110 | // new feature type with null group | |
111 | 1 | seqs.get(3).addSequenceFeature( |
112 | new SequenceFeature("Scop", "Desc", 5, 9, Float.NaN, null)); | |
113 | // null value for type produces NullPointerException | |
114 | 1 | fr.findAllFeatures(true); |
115 | 1 | types = fr.getRenderOrder(); |
116 | 1 | groups = fr.getFeatureGroups(); |
117 | 1 | assertEquals(types.size(), 3); |
118 | 1 | assertFalse(types.contains("Type")); |
119 | 1 | assertTrue(types.contains("Pfam")); |
120 | 1 | assertTrue(types.contains("Rfam")); |
121 | 1 | assertTrue(types.contains("Scop")); |
122 | 1 | assertEquals(groups.size(), 2); |
123 | 1 | assertFalse(groups.contains("Group")); |
124 | 1 | assertTrue(groups.contains("PfamGroup")); |
125 | 1 | assertTrue(groups.contains("RfamGroup")); |
126 | 1 | assertFalse(groups.contains(null)); // null group is ignored |
127 | ||
128 | /* | |
129 | * check min-max values | |
130 | */ | |
131 | 1 | Map<String, float[][]> minMax = fr.getMinMax(); |
132 | 1 | assertEquals(minMax.size(), 1); // non-positional and NaN not stored |
133 | 1 | assertEquals(minMax.get("Pfam")[0][0], 1f); // positional min |
134 | 1 | assertEquals(minMax.get("Pfam")[0][1], 2f); // positional max |
135 | ||
136 | // increase max for Pfam, add scores for Rfam | |
137 | 1 | seqs.get(0).addSequenceFeature( |
138 | new SequenceFeature("Pfam", "Desc", 14, 22, 8f, "RfamGroup")); | |
139 | 1 | seqs.get(1).addSequenceFeature( |
140 | new SequenceFeature("Rfam", "Desc", 5, 9, 6f, "RfamGroup")); | |
141 | 1 | fr.findAllFeatures(true); |
142 | // note minMax is not a defensive copy, shouldn't expose this | |
143 | 1 | assertEquals(minMax.size(), 2); |
144 | 1 | assertEquals(minMax.get("Pfam")[0][0], 1f); |
145 | 1 | assertEquals(minMax.get("Pfam")[0][1], 8f); |
146 | 1 | assertEquals(minMax.get("Rfam")[0][0], 6f); |
147 | 1 | assertEquals(minMax.get("Rfam")[0][1], 6f); |
148 | ||
149 | /* | |
150 | * check render order (last is on top) | |
151 | */ | |
152 | 1 | List<String> renderOrder = fr.getRenderOrder(); |
153 | 1 | assertEquals(renderOrder, Arrays.asList("Scop", "Rfam", "Pfam")); |
154 | ||
155 | /* | |
156 | * change render order (todo: an easier way) | |
157 | * nb here last comes first in the data array | |
158 | */ | |
159 | 1 | FeatureSettingsBean[] data = new FeatureSettingsBean[3]; |
160 | 1 | FeatureColourI colour = new FeatureColour(Color.RED); |
161 | 1 | data[0] = new FeatureSettingsBean("Rfam", colour, null, true); |
162 | 1 | data[1] = new FeatureSettingsBean("Pfam", colour, null, false); |
163 | 1 | data[2] = new FeatureSettingsBean("Scop", colour, null, false); |
164 | 1 | fr.setFeaturePriority(data); |
165 | 1 | assertEquals(fr.getRenderOrder(), |
166 | Arrays.asList("Scop", "Pfam", "Rfam")); | |
167 | 1 | assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam")); |
168 | ||
169 | /* | |
170 | * add a new feature type: should go on top of render order as visible, | |
171 | * other feature ordering and visibility should be unchanged | |
172 | */ | |
173 | 1 | seqs.get(2).addSequenceFeature( |
174 | new SequenceFeature("Metal", "Desc", 14, 22, 8f, "MetalGroup")); | |
175 | 1 | fr.findAllFeatures(true); |
176 | 1 | assertEquals(fr.getRenderOrder(), |
177 | Arrays.asList("Scop", "Pfam", "Rfam", "Metal")); | |
178 | 1 | assertEquals(fr.getDisplayedFeatureTypes(), |
179 | Arrays.asList("Rfam", "Metal")); | |
180 | } | |
181 | ||
182 | 1 | @Test(groups = "Functional") |
183 | public void testFindFeaturesAtColumn() | |
184 | { | |
185 | 1 | String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n"; |
186 | 1 | AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData, |
187 | DataSourceType.PASTE); | |
188 | 1 | AlignViewportI av = af.getViewport(); |
189 | 1 | FeatureRenderer fr = new FeatureRenderer(av); |
190 | 1 | SequenceI seq = av.getAlignment().getSequenceAt(0); |
191 | ||
192 | /* | |
193 | * with no features | |
194 | */ | |
195 | 1 | List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, 3); |
196 | 1 | assertTrue(features.isEmpty()); |
197 | ||
198 | /* | |
199 | * add features | |
200 | */ | |
201 | 1 | SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f, |
202 | "Group"); // non-positional | |
203 | 1 | seq.addSequenceFeature(sf1); |
204 | 1 | SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f, |
205 | "Group1"); | |
206 | 1 | seq.addSequenceFeature(sf2); |
207 | 1 | SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f, |
208 | "Group2"); | |
209 | 1 | seq.addSequenceFeature(sf3); |
210 | 1 | SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 8, 18, 1f, |
211 | null); // null group is always treated as visible | |
212 | 1 | seq.addSequenceFeature(sf4); |
213 | ||
214 | /* | |
215 | * add contact features | |
216 | */ | |
217 | 1 | SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7, |
218 | 15, 1f, "Group1"); | |
219 | 1 | seq.addSequenceFeature(sf5); |
220 | 1 | SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7, |
221 | 15, 1f, "Group2"); | |
222 | 1 | seq.addSequenceFeature(sf6); |
223 | 1 | SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7, |
224 | 15, 1f, null); | |
225 | 1 | seq.addSequenceFeature(sf7); |
226 | ||
227 | // feature spanning B--C | |
228 | 1 | SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f, |
229 | "Group"); | |
230 | 1 | seq.addSequenceFeature(sf8); |
231 | // contact feature B/C | |
232 | 1 | SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5, |
233 | 6, 1f, "Group"); | |
234 | 1 | seq.addSequenceFeature(sf9); |
235 | ||
236 | /* | |
237 | * let feature renderer discover features (and make visible) | |
238 | */ | |
239 | 1 | fr.findAllFeatures(true); |
240 | 1 | features = fr.findFeaturesAtColumn(seq, 15); // all positional |
241 | 1 | assertEquals(features.size(), 6); |
242 | 1 | assertTrue(features.contains(sf2)); |
243 | 1 | assertTrue(features.contains(sf3)); |
244 | 1 | assertTrue(features.contains(sf4)); |
245 | 1 | assertTrue(features.contains(sf5)); |
246 | 1 | assertTrue(features.contains(sf6)); |
247 | 1 | assertTrue(features.contains(sf7)); |
248 | ||
249 | /* | |
250 | * at a non-contact position | |
251 | */ | |
252 | 1 | features = fr.findFeaturesAtColumn(seq, 14); |
253 | 1 | assertEquals(features.size(), 3); |
254 | 1 | assertTrue(features.contains(sf2)); |
255 | 1 | assertTrue(features.contains(sf3)); |
256 | 1 | assertTrue(features.contains(sf4)); |
257 | ||
258 | /* | |
259 | * make "Type2" not displayed | |
260 | */ | |
261 | 1 | FeatureColourI colour = new FeatureColour(Color.RED); |
262 | 1 | FeatureSettingsBean[] data = new FeatureSettingsBean[4]; |
263 | 1 | data[0] = new FeatureSettingsBean("Type1", colour, null, true); |
264 | 1 | data[1] = new FeatureSettingsBean("Type2", colour, null, false); |
265 | 1 | data[2] = new FeatureSettingsBean("Type3", colour, null, true); |
266 | 1 | data[3] = new FeatureSettingsBean("Disulphide Bond", colour, null, |
267 | true); | |
268 | 1 | fr.setFeaturePriority(data); |
269 | ||
270 | 1 | features = fr.findFeaturesAtColumn(seq, 15); |
271 | 1 | assertEquals(features.size(), 5); // no sf2 |
272 | 1 | assertTrue(features.contains(sf3)); |
273 | 1 | assertTrue(features.contains(sf4)); |
274 | 1 | assertTrue(features.contains(sf5)); |
275 | 1 | assertTrue(features.contains(sf6)); |
276 | 1 | assertTrue(features.contains(sf7)); |
277 | ||
278 | /* | |
279 | * make "Group2" not displayed | |
280 | */ | |
281 | 1 | fr.setGroupVisibility("Group2", false); |
282 | ||
283 | 1 | features = fr.findFeaturesAtColumn(seq, 15); |
284 | 1 | assertEquals(features.size(), 3); // no sf2, sf3, sf6 |
285 | 1 | assertTrue(features.contains(sf4)); |
286 | 1 | assertTrue(features.contains(sf5)); |
287 | 1 | assertTrue(features.contains(sf7)); |
288 | ||
289 | // features 'at' a gap between b and c | |
290 | // - returns enclosing feature BC but not contact feature B/C | |
291 | 1 | features = fr.findFeaturesAtColumn(seq, 4); |
292 | 1 | assertEquals(features.size(), 1); |
293 | 1 | assertTrue(features.contains(sf8)); |
294 | 1 | features = fr.findFeaturesAtColumn(seq, 5); |
295 | 1 | assertEquals(features.size(), 1); |
296 | 1 | assertTrue(features.contains(sf8)); |
297 | ||
298 | /* | |
299 | * give "Type3" features a graduated colour scheme | |
300 | * - first with no threshold | |
301 | */ | |
302 | 1 | FeatureColourI gc = new FeatureColour(Color.green, Color.yellow, |
303 | Color.red, null, 0f, 10f); | |
304 | 1 | fr.getFeatureColours().put("Type3", gc); |
305 | 1 | features = fr.findFeaturesAtColumn(seq, 8); |
306 | 1 | assertTrue(features.contains(sf4)); |
307 | // now with threshold > 2f - feature score of 1f is excluded | |
308 | 1 | gc.setAboveThreshold(true); |
309 | 1 | gc.setThreshold(2f); |
310 | 1 | features = fr.findFeaturesAtColumn(seq, 8); |
311 | 1 | assertFalse(features.contains(sf4)); |
312 | ||
313 | /* | |
314 | * make "Type3" graduated colour by attribute "AF" | |
315 | * - first with no attribute held - feature should be excluded | |
316 | */ | |
317 | 1 | gc.setAttributeName("AF"); |
318 | 1 | features = fr.findFeaturesAtColumn(seq, 8); |
319 | 1 | assertFalse(features.contains(sf4)); |
320 | // now with the attribute above threshold - should be included | |
321 | 1 | sf4.setValue("AF", "2.4"); |
322 | 1 | features = fr.findFeaturesAtColumn(seq, 8); |
323 | 1 | assertTrue(features.contains(sf4)); |
324 | // now with the attribute below threshold - should be excluded | |
325 | 1 | sf4.setValue("AF", "1.4"); |
326 | 1 | features = fr.findFeaturesAtColumn(seq, 8); |
327 | 1 | assertFalse(features.contains(sf4)); |
328 | } | |
329 | ||
330 | 1 | @Test(groups = "Functional") |
331 | public void testFilterFeaturesForDisplay() | |
332 | { | |
333 | 1 | String seqData = ">s1\nabcdef\n"; |
334 | 1 | AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData, |
335 | DataSourceType.PASTE); | |
336 | 1 | AlignViewportI av = af.getViewport(); |
337 | 1 | FeatureRenderer fr = new FeatureRenderer(av); |
338 | ||
339 | 1 | List<SequenceFeature> features = new ArrayList<>(); |
340 | 1 | fr.filterFeaturesForDisplay(features); // empty list, does nothing |
341 | ||
342 | 1 | SequenceI seq = av.getAlignment().getSequenceAt(0); |
343 | 1 | SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN, |
344 | "group1"); | |
345 | 1 | SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f, |
346 | "group2"); | |
347 | 1 | SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f, |
348 | "group3"); | |
349 | 1 | SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f, |
350 | "group4"); | |
351 | 1 | SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f, |
352 | "group4"); | |
353 | 1 | seq.addSequenceFeature(sf1); |
354 | 1 | seq.addSequenceFeature(sf2); |
355 | 1 | seq.addSequenceFeature(sf3); |
356 | 1 | seq.addSequenceFeature(sf4); |
357 | 1 | seq.addSequenceFeature(sf5); |
358 | ||
359 | 1 | fr.findAllFeatures(true); |
360 | ||
361 | 1 | features = seq.getSequenceFeatures(); |
362 | 1 | assertEquals(features.size(), 5); |
363 | 1 | assertTrue(features.contains(sf1)); |
364 | 1 | assertTrue(features.contains(sf2)); |
365 | 1 | assertTrue(features.contains(sf3)); |
366 | 1 | assertTrue(features.contains(sf4)); |
367 | 1 | assertTrue(features.contains(sf5)); |
368 | ||
369 | /* | |
370 | * filter out duplicate (co-located) features | |
371 | * note: which gets removed is not guaranteed | |
372 | */ | |
373 | 1 | fr.filterFeaturesForDisplay(features); |
374 | 1 | assertEquals(features.size(), 3); |
375 | 1 | assertTrue(features.contains(sf1) || features.contains(sf4)); |
376 | 1 | assertFalse(features.contains(sf1) && features.contains(sf4)); |
377 | 1 | assertTrue(features.contains(sf2) || features.contains(sf3)); |
378 | 1 | assertFalse(features.contains(sf2) && features.contains(sf3)); |
379 | 1 | assertTrue(features.contains(sf5)); |
380 | ||
381 | /* | |
382 | * features in hidden groups are removed | |
383 | */ | |
384 | 1 | fr.setGroupVisibility("group2", false); |
385 | 1 | fr.setGroupVisibility("group3", false); |
386 | 1 | features = seq.getSequenceFeatures(); |
387 | 1 | fr.filterFeaturesForDisplay(features); |
388 | 1 | assertEquals(features.size(), 2); |
389 | 1 | assertTrue(features.contains(sf1) || features.contains(sf4)); |
390 | 1 | assertFalse(features.contains(sf1) && features.contains(sf4)); |
391 | 1 | assertFalse(features.contains(sf2)); |
392 | 1 | assertFalse(features.contains(sf3)); |
393 | 1 | assertTrue(features.contains(sf5)); |
394 | ||
395 | /* | |
396 | * no filtering if transparency is applied | |
397 | */ | |
398 | 1 | fr.setTransparency(0.5f); |
399 | 1 | features = seq.getSequenceFeatures(); |
400 | 1 | fr.filterFeaturesForDisplay(features); |
401 | 1 | assertEquals(features.size(), 5); |
402 | } | |
403 | ||
404 | 1 | @Test(groups = "Functional") |
405 | public void testGetColour() | |
406 | { | |
407 | 1 | AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(">s1\nABCD\n", |
408 | DataSourceType.PASTE); | |
409 | 1 | AlignViewportI av = af.getViewport(); |
410 | 1 | FeatureRenderer fr = new FeatureRenderer(av); |
411 | ||
412 | /* | |
413 | * simple colour, feature type and group displayed | |
414 | */ | |
415 | 1 | FeatureColourI fc = new FeatureColour(Color.red); |
416 | 1 | fr.getFeatureColours().put("Cath", fc); |
417 | 1 | SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN, |
418 | "group1"); | |
419 | 1 | assertEquals(fr.getColour(sf1), Color.red); |
420 | ||
421 | /* | |
422 | * hide feature type, then unhide | |
423 | * - feature type visibility should not affect the result | |
424 | */ | |
425 | 1 | FeatureSettingsBean[] data = new FeatureSettingsBean[1]; |
426 | 1 | data[0] = new FeatureSettingsBean("Cath", fc, null, false); |
427 | 1 | fr.setFeaturePriority(data); |
428 | 1 | assertEquals(fr.getColour(sf1), Color.red); |
429 | 1 | data[0] = new FeatureSettingsBean("Cath", fc, null, true); |
430 | 1 | fr.setFeaturePriority(data); |
431 | 1 | assertEquals(fr.getColour(sf1), Color.red); |
432 | ||
433 | /* | |
434 | * hide feature group, then unhide | |
435 | */ | |
436 | 1 | fr.setGroupVisibility("group1", false); |
437 | 1 | assertNull(fr.getColour(sf1)); |
438 | 1 | fr.setGroupVisibility("group1", true); |
439 | 1 | assertEquals(fr.getColour(sf1), Color.red); |
440 | ||
441 | /* | |
442 | * graduated colour by score, no threshold, no score | |
443 | * | |
444 | */ | |
445 | 1 | FeatureColourI gc = new FeatureColour(Color.red, Color.yellow, |
446 | Color.red, Color.green, 1f, 11f); | |
447 | 1 | fr.getFeatureColours().put("Cath", gc); |
448 | 1 | assertEquals(fr.getColour(sf1), Color.green); |
449 | ||
450 | /* | |
451 | * graduated colour by score, no threshold, with score value | |
452 | */ | |
453 | 1 | SequenceFeature sf2 = new SequenceFeature("Cath", "", 6, 8, 6f, |
454 | "group1"); | |
455 | // score 6 is half way from yellow(255, 255, 0) to red(255, 0, 0) | |
456 | 1 | Color expected = new Color(255, 128, 0); |
457 | 1 | assertEquals(fr.getColour(sf2), expected); |
458 | ||
459 | /* | |
460 | * above threshold, score is above threshold - no change | |
461 | */ | |
462 | 1 | gc.setAboveThreshold(true); |
463 | 1 | gc.setThreshold(5f); |
464 | 1 | assertEquals(fr.getColour(sf2), expected); |
465 | ||
466 | /* | |
467 | * threshold is min-max; now score 6 is 1/6 of the way from 5 to 11 | |
468 | * or from yellow(255, 255, 0) to red(255, 0, 0) | |
469 | */ | |
470 | 1 | gc = new FeatureColour(Color.red, Color.yellow, Color.red, Color.green, |
471 | 5f, 11f); | |
472 | 1 | fr.getFeatureColours().put("Cath", gc); |
473 | 1 | gc.setAutoScaled(false); // this does little other than save a checkbox |
474 | // setting! | |
475 | 1 | assertEquals(fr.getColour(sf2), new Color(255, 213, 0)); |
476 | ||
477 | /* | |
478 | * feature score is below threshold - no colour | |
479 | */ | |
480 | 1 | gc.setAboveThreshold(true); |
481 | 1 | gc.setThreshold(7f); |
482 | 1 | assertNull(fr.getColour(sf2)); |
483 | ||
484 | /* | |
485 | * feature score is above threshold - no colour | |
486 | */ | |
487 | 1 | gc.setBelowThreshold(true); |
488 | 1 | gc.setThreshold(3f); |
489 | 1 | assertNull(fr.getColour(sf2)); |
490 | ||
491 | /* | |
492 | * colour by feature attribute value | |
493 | * first with no value held | |
494 | */ | |
495 | 1 | gc = new FeatureColour(Color.red, Color.yellow, Color.red, Color.green, |
496 | 1f, 11f); | |
497 | 1 | fr.getFeatureColours().put("Cath", gc); |
498 | 1 | gc.setAttributeName("AF"); |
499 | 1 | assertEquals(fr.getColour(sf2), Color.green); |
500 | ||
501 | // with non-numeric attribute value | |
502 | 1 | sf2.setValue("AF", "Five"); |
503 | 1 | assertEquals(fr.getColour(sf2), Color.green); |
504 | ||
505 | // with numeric attribute value | |
506 | 1 | sf2.setValue("AF", "6"); |
507 | 1 | assertEquals(fr.getColour(sf2), expected); |
508 | ||
509 | // with numeric value outwith threshold | |
510 | 1 | gc.setAboveThreshold(true); |
511 | 1 | gc.setThreshold(10f); |
512 | 1 | assertNull(fr.getColour(sf2)); |
513 | ||
514 | // with filter on AF < 4 | |
515 | 1 | gc.setAboveThreshold(false); |
516 | 1 | assertEquals(fr.getColour(sf2), expected); |
517 | 1 | FeatureMatcherSetI filter = new FeatureMatcherSet(); |
518 | 1 | filter.and(FeatureMatcher.byAttribute(Condition.LT, "4.0", "AF")); |
519 | 1 | fr.setFeatureFilter("Cath", filter); |
520 | 1 | assertNull(fr.getColour(sf2)); |
521 | ||
522 | // with filter on 'Consequence contains missense' | |
523 | 1 | filter = new FeatureMatcherSet(); |
524 | 1 | filter.and(FeatureMatcher.byAttribute(Condition.Contains, "missense", |
525 | "Consequence")); | |
526 | 1 | fr.setFeatureFilter("Cath", filter); |
527 | // if feature has no Consequence attribute, no colour | |
528 | 1 | assertNull(fr.getColour(sf2)); |
529 | // if attribute does not match filter, no colour | |
530 | 1 | sf2.setValue("Consequence", "Synonymous"); |
531 | 1 | assertNull(fr.getColour(sf2)); |
532 | // attribute matches filter | |
533 | 1 | sf2.setValue("Consequence", "Missense variant"); |
534 | 1 | assertEquals(fr.getColour(sf2), expected); |
535 | ||
536 | // with filter on CSQ:Feature contains "ENST01234" | |
537 | 1 | filter = new FeatureMatcherSet(); |
538 | 1 | filter.and(FeatureMatcher.byAttribute(Condition.Matches, "ENST01234", |
539 | "CSQ", "Feature")); | |
540 | 1 | fr.setFeatureFilter("Cath", filter); |
541 | // if feature has no CSQ data, no colour | |
542 | 1 | assertNull(fr.getColour(sf2)); |
543 | // if CSQ data does not include Feature, no colour | |
544 | 1 | Map<String, String> csqData = new HashMap<>(); |
545 | 1 | csqData.put("BIOTYPE", "Transcript"); |
546 | 1 | sf2.setValue("CSQ", csqData); |
547 | 1 | assertNull(fr.getColour(sf2)); |
548 | // if attribute does not match filter, no colour | |
549 | 1 | csqData.put("Feature", "ENST9876"); |
550 | 1 | assertNull(fr.getColour(sf2)); |
551 | // attribute matches filter | |
552 | 1 | csqData.put("Feature", "ENST01234"); |
553 | 1 | assertEquals(fr.getColour(sf2), expected); |
554 | } | |
555 | ||
556 | 1 | @Test(groups = "Functional") |
557 | public void testIsVisible() | |
558 | { | |
559 | 1 | String seqData = ">s1\nMLQGIFPRS\n"; |
560 | 1 | AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData, |
561 | DataSourceType.PASTE); | |
562 | 1 | AlignViewportI av = af.getViewport(); |
563 | 1 | FeatureRenderer fr = new FeatureRenderer(av); |
564 | 1 | SequenceI seq = av.getAlignment().getSequenceAt(0); |
565 | 1 | SequenceFeature sf = new SequenceFeature("METAL", "Desc", 10, 10, 1f, |
566 | "Group"); | |
567 | 1 | sf.setValue("AC", "11"); |
568 | 1 | sf.setValue("CLIN_SIG", "Likely Pathogenic"); |
569 | 1 | seq.addSequenceFeature(sf); |
570 | ||
571 | 1 | assertFalse(fr.isVisible(null)); |
572 | ||
573 | /* | |
574 | * initial state FeatureRenderer hasn't 'found' feature | |
575 | * and so its feature type has not yet been set visible | |
576 | */ | |
577 | 1 | assertFalse(fr.getDisplayedFeatureCols().containsKey("METAL")); |
578 | 1 | assertFalse(fr.isVisible(sf)); |
579 | ||
580 | 1 | fr.findAllFeatures(true); |
581 | 1 | assertTrue(fr.isVisible(sf)); |
582 | ||
583 | /* | |
584 | * feature group not visible | |
585 | */ | |
586 | 1 | fr.setGroupVisibility("Group", false); |
587 | 1 | assertFalse(fr.isVisible(sf)); |
588 | 1 | fr.setGroupVisibility("Group", true); |
589 | 1 | assertTrue(fr.isVisible(sf)); |
590 | ||
591 | /* | |
592 | * feature score outwith colour threshold (score > 2) | |
593 | */ | |
594 | 1 | FeatureColourI fc = new FeatureColour(null, Color.white, Color.black, |
595 | Color.white, 0, 10); | |
596 | 1 | fc.setAboveThreshold(true); |
597 | 1 | fc.setThreshold(2f); |
598 | 1 | fr.setColour("METAL", fc); |
599 | 1 | assertFalse(fr.isVisible(sf)); // score 1 is not above threshold 2 |
600 | 1 | fc.setBelowThreshold(true); |
601 | 1 | assertTrue(fr.isVisible(sf)); // score 1 is below threshold 2 |
602 | ||
603 | /* | |
604 | * colour with threshold on attribute AC (value is 11) | |
605 | */ | |
606 | 1 | fc.setAttributeName("AC"); |
607 | 1 | assertFalse(fr.isVisible(sf)); // value 11 is not below threshold 2 |
608 | 1 | fc.setAboveThreshold(true); |
609 | 1 | assertTrue(fr.isVisible(sf)); // value 11 is above threshold 2 |
610 | ||
611 | 1 | fc.setAttributeName("AF"); // attribute AF is absent in sf |
612 | 1 | assertTrue(fr.isVisible(sf)); // feature is not excluded by threshold |
613 | ||
614 | 1 | FeatureMatcherSetI filter = new FeatureMatcherSet(); |
615 | 1 | filter.and(FeatureMatcher.byAttribute(Condition.Contains, "pathogenic", |
616 | "CLIN_SIG")); | |
617 | 1 | fr.setFeatureFilter("METAL", filter); |
618 | 1 | assertTrue(fr.isVisible(sf)); // feature matches filter |
619 | 1 | filter.and(FeatureMatcher.byScore(Condition.LE, "0.4")); |
620 | 1 | assertFalse(fr.isVisible(sf)); // feature doesn't match filter |
621 | } | |
622 | ||
623 | 1 | @Test(groups = "Functional") |
624 | public void testFindComplementFeaturesAtResidue() | |
625 | { | |
626 | 1 | Jalview.main( |
627 | new String[] | |
628 | { "--nonews", "--props", "test/jalview/testProps.jvprops" }); | |
629 | ||
630 | // codons for MCWHSE | |
631 | 1 | String cdsSeq = ">cds\nATGtgtTGGcacTCAgaa"; |
632 | 1 | AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(cdsSeq, |
633 | DataSourceType.PASTE); | |
634 | 1 | af.showTranslation_actionPerformed( |
635 | GeneticCodes.getInstance().getStandardCodeTable()); | |
636 | 1 | af.closeMenuItem_actionPerformed(true); |
637 | ||
638 | /* | |
639 | * find the complement frames (ugly) | |
640 | */ | |
641 | 1 | AlignFrame[] frames = Desktop.getDesktopAlignFrames(); |
642 | 1 | assertEquals(frames.length, 2); |
643 | 1 | AlignViewport av1 = frames[0].getViewport(); |
644 | 1 | AlignViewport av2 = frames[1].getViewport(); |
645 | 1 | AlignViewport cds = av1.getAlignment().isNucleotide() ? av1 : av2; |
646 | 1 | AlignViewport peptide = cds == av1 ? av2 : av1; |
647 | 1 | assertNotNull(cds); |
648 | 1 | assertNotNull(peptide); |
649 | ||
650 | /* | |
651 | * add features to CDS at first codon, positions 2-3 | |
652 | */ | |
653 | 1 | SequenceI seq1 = cds.getAlignment().getSequenceAt(0); |
654 | 1 | SequenceFeature sf1 = new SequenceFeature("sequence_variant", "G,GT", 2, |
655 | 2, "ensembl"); | |
656 | 1 | seq1.addSequenceFeature(sf1); |
657 | 1 | SequenceFeature sf2 = new SequenceFeature("sequence_variant", "C, CA", |
658 | 3, 3, "ensembl"); | |
659 | 1 | seq1.addSequenceFeature(sf2); |
660 | ||
661 | /* | |
662 | * 'find' mapped features from the peptide position | |
663 | * - first with CDS features _not_ shown on peptide alignment | |
664 | */ | |
665 | 1 | SequenceI seq2 = peptide.getAlignment().getSequenceAt(0); |
666 | 1 | FeatureRenderer frC = new FeatureRenderer(cds); |
667 | 1 | frC.featuresAdded(); |
668 | 1 | MappedFeatures mf = frC.findComplementFeaturesAtResidue(seq2, 1); |
669 | 1 | assertNotNull(mf); |
670 | 1 | assertEquals(mf.features.size(), 2); |
671 | 1 | assertSame(mf.features.get(0), sf1); |
672 | 1 | assertSame(mf.features.get(1), sf2); |
673 | ||
674 | /* | |
675 | * add exon feature and verify it is only returned once for a | |
676 | * peptide position, even though it is on all 3 codon positions | |
677 | */ | |
678 | 1 | SequenceFeature sf3 = new SequenceFeature("exon", "exon1", 4, 12, |
679 | "ensembl"); | |
680 | 1 | seq1.addSequenceFeature(sf3); |
681 | 1 | frC.featuresAdded(); |
682 | 1 | mf = frC.findComplementFeaturesAtResidue(seq2, 3); |
683 | 1 | assertNotNull(mf); |
684 | 1 | assertEquals(mf.features.size(), 1); |
685 | 1 | assertSame(mf.features.get(0), sf3); |
686 | } | |
687 | } |