JAL-653 GFF new/refactored helper classes
[jalview.git] / test / jalview / io / FeaturesFileTest.java
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.io;
22
23 import static org.testng.AssertJUnit.assertEquals;
24 import static org.testng.AssertJUnit.assertFalse;
25 import static org.testng.AssertJUnit.assertNotNull;
26 import static org.testng.AssertJUnit.assertNull;
27 import static org.testng.AssertJUnit.assertSame;
28 import static org.testng.AssertJUnit.assertTrue;
29 import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
30
31 import jalview.datamodel.AlignedCodonFrame;
32 import jalview.datamodel.Alignment;
33 import jalview.datamodel.AlignmentI;
34 import jalview.datamodel.Mapping;
35 import jalview.datamodel.SequenceDummy;
36 import jalview.datamodel.SequenceFeature;
37 import jalview.datamodel.SequenceI;
38 import jalview.gui.AlignFrame;
39 import jalview.schemes.AnnotationColourGradient;
40 import jalview.schemes.GraduatedColor;
41
42 import java.awt.Color;
43 import java.io.File;
44 import java.io.IOException;
45 import java.util.Iterator;
46 import java.util.Map;
47 import java.util.Set;
48
49 import org.testng.annotations.Test;
50
51 public class FeaturesFileTest
52 {
53
54   private static String simpleGffFile = "examples/testdata/simpleGff3.gff";
55
56   @Test(groups = { "Functional" })
57   public void testParse() throws Exception
58   {
59     File f = new File("examples/uniref50.fa");
60     AlignmentI al = readAlignmentFile(f);
61     AlignFrame af = new AlignFrame(al, 500, 500);
62     Map<String, Object> colours = af.getFeatureRenderer()
63             .getFeatureColours();
64     FeaturesFile featuresFile = new FeaturesFile(
65             "examples/exampleFeatures.txt", FormatAdapter.FILE);
66     assertTrue("Test " + "Features file test"
67             + "\nFailed to parse features file.",
68             featuresFile.parse(al.getDataset(), colours, true));
69
70     /*
71      * Refetch the colour map from the FeatureRenderer (to confirm it has been
72      * updated - JAL-1904), and verify (some) feature group colours
73      */
74     colours = af.getFeatureRenderer().getFeatureColours();
75     assertEquals("26 feature group colours not found", 26, colours.size());
76     assertEquals(colours.get("Cath"), new Color(0x93b1d1));
77     assertEquals(colours.get("ASX-MOTIF"), new Color(0x6addbb));
78
79     /*
80      * verify (some) features on sequences
81      */
82     SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
83             .getSequenceFeatures(); // FER_CAPAA
84     assertEquals(7, sfs.length);
85     SequenceFeature sf = sfs[0];
86     assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
87     assertEquals(39, sf.begin);
88     assertEquals(39, sf.end);
89     assertEquals("uniprot", sf.featureGroup);
90     assertEquals("METAL", sf.type);
91     sf = sfs[1];
92     assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
93     assertEquals(44, sf.begin);
94     assertEquals(44, sf.end);
95     assertEquals("uniprot", sf.featureGroup);
96     assertEquals("METAL", sf.type);
97     sf = sfs[2];
98     assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
99     assertEquals(47, sf.begin);
100     assertEquals(47, sf.end);
101     assertEquals("uniprot", sf.featureGroup);
102     assertEquals("METAL", sf.type);
103     sf = sfs[3];
104     assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
105     assertEquals(77, sf.begin);
106     assertEquals(77, sf.end);
107     assertEquals("uniprot", sf.featureGroup);
108     assertEquals("METAL", sf.type);
109     sf = sfs[4];
110     assertEquals("Fer2 Status: True Positive Pfam 8_8%LINK%",
111             sf.description);
112     assertEquals("Pfam 8_8|http://pfam.sanger.ac.uk/family/PF00111",
113             sf.links.get(0).toString());
114     assertEquals(8, sf.begin);
115     assertEquals(83, sf.end);
116     assertEquals("uniprot", sf.featureGroup);
117     assertEquals("Pfam", sf.type);
118     sf = sfs[5];
119     assertEquals("Ferredoxin_fold Status: True Positive ", sf.description);
120     assertEquals(3, sf.begin);
121     assertEquals(93, sf.end);
122     assertEquals("uniprot", sf.featureGroup);
123     assertEquals("Cath", sf.type);
124     sf = sfs[6];
125     assertEquals(
126             "High confidence server. Only hits with scores over 0.8 are reported. PHOSPHORYLATION (T) 89_8%LINK%",
127             sf.description);
128     assertEquals(
129             "PHOSPHORYLATION (T) 89_8|http://www.cbs.dtu.dk/cgi-bin/proview/webface-link?seqid=P83527&amp;service=NetPhos-2.0",
130             sf.links.get(0).toString());
131     assertEquals(89, sf.begin);
132     assertEquals(89, sf.end);
133     assertEquals("netphos", sf.featureGroup);
134     assertEquals("PHOSPHORYLATION (T)", sf.type);
135   }
136
137   /**
138    * Test parsing a features file with a mix of Jalview and GFF formatted
139    * content
140    * 
141    * @throws Exception
142    */
143   @Test(groups = { "Functional" })
144   public void testParse_mixedJalviewGff() throws Exception
145   {
146     File f = new File("examples/uniref50.fa");
147     AlignmentI al = readAlignmentFile(f);
148     AlignFrame af = new AlignFrame(al, 500, 500);
149     Map<String, Object> colours = af.getFeatureRenderer()
150             .getFeatureColours();
151     // GFF2 uses space as name/value separator in column 9
152     String gffData = "METAL\tcc9900\n" + "GFF\n"
153             + "FER_CAPAA\tuniprot\tMETAL\t44\t45\t4.0\t.\t.\tNote Iron-sulfur; Note 2Fe-2S\n"
154             + "FER1_SOLLC\tuniprot\tPfam\t55\t130\t2.0\t.\t.";
155     FeaturesFile featuresFile = new FeaturesFile(gffData,
156             FormatAdapter.PASTE);
157     assertTrue("Failed to parse features file",
158             featuresFile.parse(al.getDataset(), colours, true));
159
160     // verify colours read or synthesized
161     colours = af.getFeatureRenderer().getFeatureColours();
162     assertEquals("1 feature group colours not found", 1, colours.size());
163     assertEquals(colours.get("METAL"), new Color(0xcc9900));
164
165     // verify feature on FER_CAPAA
166     SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
167             .getSequenceFeatures();
168     assertEquals(1, sfs.length);
169     SequenceFeature sf = sfs[0];
170     assertEquals("Iron-sulfur; 2Fe-2S", sf.description);
171     assertEquals(44, sf.begin);
172     assertEquals(45, sf.end);
173     assertEquals("uniprot", sf.featureGroup);
174     assertEquals("METAL", sf.type);
175     assertEquals(4f, sf.getScore(), 0.001f);
176
177     // verify feature on FER1_SOLLC
178     sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
179     assertEquals(1, sfs.length);
180     sf = sfs[0];
181     assertEquals("uniprot", sf.description);
182     assertEquals(55, sf.begin);
183     assertEquals(130, sf.end);
184     assertEquals("uniprot", sf.featureGroup);
185     assertEquals("Pfam", sf.type);
186     assertEquals(2f, sf.getScore(), 0.001f);
187   }
188
189   public static AlignmentI readAlignmentFile(File f) throws IOException
190   {
191     System.out.println("Reading file: " + f);
192     String ff = f.getPath();
193     FormatAdapter rf = new FormatAdapter();
194
195     AlignmentI al = rf.readFile(ff, FormatAdapter.FILE,
196             new IdentifyFile().identify(ff, FormatAdapter.FILE));
197
198     al.setDataset(null); // creates dataset sequences
199     assertNotNull("Couldn't read supplied alignment data.", al);
200     return al;
201   }
202
203   /**
204    * Test various ways of describing a feature colour scheme
205    * 
206    * @throws Exception
207    */
208   @Test(groups = { "Functional" })
209   public void testParseGraduatedColourScheme() throws Exception
210   {
211     FeaturesFile ff = new FeaturesFile();
212
213     // colour by label:
214     GraduatedColor gc = ff.parseGraduatedColourScheme(
215             "BETA-TURN-IR\t9a6a94", "label");
216     assertTrue(gc.isColourByLabel());
217     assertEquals(Color.white, gc.getMinColor());
218     assertEquals(Color.black, gc.getMaxColor());
219     assertTrue(gc.isAutoScale());
220
221     // using colour name, rgb, etc:
222     String spec = "blue|255,0,255|absolute|20.0|95.0|below|66.0";
223     gc = ff.parseGraduatedColourScheme("BETA-TURN-IR\t" + spec, spec);
224     assertFalse(gc.isColourByLabel());
225     assertEquals(Color.blue, gc.getMinColor());
226     assertEquals(new Color(255, 0, 255), gc.getMaxColor());
227     assertFalse(gc.isAutoScale());
228     assertFalse(gc.getTolow());
229     assertEquals(20.0f, gc.getMin(), 0.001f);
230     assertEquals(95.0f, gc.getMax(), 0.001f);
231     assertEquals(AnnotationColourGradient.BELOW_THRESHOLD,
232             gc.getThreshType());
233     assertEquals(66.0f, gc.getThresh(), 0.001f);
234
235     // inverse gradient high to low:
236     spec = "blue|255,0,255|95.0|20.0|below|66.0";
237     gc = ff.parseGraduatedColourScheme("BETA-TURN-IR\t" + spec, spec);
238     assertTrue(gc.isAutoScale());
239     assertTrue(gc.getTolow());
240   }
241
242   /**
243    * Test parsing a features file with GFF formatted content only
244    * 
245    * @throws Exception
246    */
247   @Test(groups = { "Functional" })
248   public void testParse_pureGff3() throws Exception
249   {
250     File f = new File("examples/uniref50.fa");
251     AlignmentI al = readAlignmentFile(f);
252     AlignFrame af = new AlignFrame(al, 500, 500);
253     Map<String, Object> colours = af.getFeatureRenderer()
254             .getFeatureColours();
255     // GFF3 uses '=' separator for name/value pairs in colum 9
256     String gffData = "##gff-version 3\n"
257             + "FER_CAPAA\tuniprot\tMETAL\t39\t39\t0.0\t.\t.\t"
258             + "Note=Iron-sulfur (2Fe-2S);Note=another note;evidence=ECO:0000255|PROSITE-ProRule:PRU00465\n"
259             + "FER1_SOLLC\tuniprot\tPfam\t55\t130\t3.0\t.\t.\tID=$23";
260     FeaturesFile featuresFile = new FeaturesFile(gffData,
261             FormatAdapter.PASTE);
262     assertTrue("Failed to parse features file",
263             featuresFile.parse(al.getDataset(), colours, true));
264
265     // verify feature on FER_CAPAA
266     SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
267             .getSequenceFeatures();
268     assertEquals(1, sfs.length);
269     SequenceFeature sf = sfs[0];
270     // description parsed from Note attribute
271     assertEquals("Iron-sulfur (2Fe-2S); another note", sf.description);
272     assertEquals(39, sf.begin);
273     assertEquals(39, sf.end);
274     assertEquals("uniprot", sf.featureGroup);
275     assertEquals("METAL", sf.type);
276     assertEquals(
277             "Note=Iron-sulfur (2Fe-2S);Note=another note;evidence=ECO:0000255|PROSITE-ProRule:PRU00465",
278             sf.getValue("ATTRIBUTES"));
279
280     // verify feature on FER1_SOLLC1
281     sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
282     assertEquals(1, sfs.length);
283     sf = sfs[0];
284     assertEquals("uniprot", sf.description);
285     assertEquals(55, sf.begin);
286     assertEquals(130, sf.end);
287     assertEquals("uniprot", sf.featureGroup);
288     assertEquals("Pfam", sf.type);
289     assertEquals(3f, sf.getScore(), 0.001f);
290   }
291
292   /**
293    * Test parsing a features file with Jalview format features (but no colour
294    * descriptors or startgroup to give the hint not to parse as GFF)
295    * 
296    * @throws Exception
297    */
298   @Test(groups = { "Functional" })
299   public void testParse_jalviewFeaturesOnly() throws Exception
300   {
301     File f = new File("examples/uniref50.fa");
302     AlignmentI al = readAlignmentFile(f);
303     AlignFrame af = new AlignFrame(al, 500, 500);
304     Map<String, Object> colours = af.getFeatureRenderer()
305             .getFeatureColours();
306
307     /*
308      * one feature on FER_CAPAA and one on sequence 3 (index 2) FER1_SOLLC
309      */
310     String featureData = "Iron-sulfur (2Fe-2S)\tFER_CAPAA\t-1\t39\t39\tMETAL\n"
311             + "Iron-phosphorus (2Fe-P)\tID_NOT_SPECIFIED\t2\t86\t87\tMETALLIC\n";
312     FeaturesFile featuresFile = new FeaturesFile(featureData,
313             FormatAdapter.PASTE);
314     assertTrue("Failed to parse features file",
315             featuresFile.parse(al.getDataset(), colours, true));
316
317     // verify FER_CAPAA feature
318     SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
319             .getSequenceFeatures();
320     assertEquals(1, sfs.length);
321     SequenceFeature sf = sfs[0];
322     assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
323     assertEquals(39, sf.begin);
324     assertEquals(39, sf.end);
325     assertEquals("METAL", sf.type);
326
327     // verify FER1_SOLLC feature
328     sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
329     assertEquals(1, sfs.length);
330     sf = sfs[0];
331     assertEquals("Iron-phosphorus (2Fe-P)", sf.description);
332     assertEquals(86, sf.begin);
333     assertEquals(87, sf.end);
334     assertEquals("METALLIC", sf.type);
335   }
336
337   private void checkDatasetfromSimpleGff3(AlignmentI dataset)
338   {
339     assertEquals("no sequences extracted from GFF3 file", 2,
340             dataset.getHeight());
341   
342     SequenceI seq1 = dataset.findName("seq1");
343     SequenceI seq2 = dataset.findName("seq2");
344     assertNotNull(seq1);
345     assertNotNull(seq2);
346     assertFalse(
347             "Failed to replace dummy seq1 with real sequence",
348             seq1 instanceof SequenceDummy
349                     && ((SequenceDummy) seq1).isDummy());
350     assertFalse(
351             "Failed to replace dummy seq2 with real sequence",
352             seq2 instanceof SequenceDummy
353                     && ((SequenceDummy) seq2).isDummy());
354     String placeholderseq = new SequenceDummy("foo").getSequenceAsString();
355     assertFalse("dummy replacement buggy for seq1",
356             placeholderseq.equals(seq1.getSequenceAsString()));
357     assertFalse("dummy replacement buggy for seq2",
358             placeholderseq.equals(seq2.getSequenceAsString()));
359     assertNotNull("No features added to seq1", seq1.getSequenceFeatures());
360     assertEquals("Wrong number of features", 3,
361             seq1.getSequenceFeatures().length);
362     assertNull(seq2.getSequenceFeatures());
363     assertEquals(
364             "Wrong number of features",
365             0,
366             seq2.getSequenceFeatures() == null ? 0 : seq2
367                     .getSequenceFeatures().length);
368     assertTrue(
369             "Expected at least one CDNA/Protein mapping for seq1",
370             dataset.getCodonFrame(seq1) != null
371                     && dataset.getCodonFrame(seq1).size() > 0);
372   
373   }
374
375   @Test(groups = { "Functional" })
376   public void readGff3File() throws IOException
377   {
378     FeaturesFile gffreader = new FeaturesFile(true, simpleGffFile,
379             FormatAdapter.FILE);
380     Alignment dataset = new Alignment(gffreader.getSeqsAsArray());
381     gffreader.addProperties(dataset);
382     checkDatasetfromSimpleGff3(dataset);
383   }
384
385   @Test(groups = { "Functional" })
386   public void simpleGff3FileClass() throws IOException
387   {
388     AlignmentI dataset = new Alignment(new SequenceI[] {});
389     FeaturesFile ffile = new FeaturesFile(simpleGffFile,
390             FormatAdapter.FILE);
391   
392     boolean parseResult = ffile.parse(dataset, null, false, false);
393     assertTrue("return result should be true", parseResult);
394     checkDatasetfromSimpleGff3(dataset);
395   }
396
397   @Test(groups = { "Functional" })
398   public void simpleGff3FileLoader() throws IOException
399   {
400     AlignFrame af = new FileLoader(false).LoadFileWaitTillLoaded(
401             simpleGffFile, FormatAdapter.FILE);
402     assertTrue(
403             "Didn't read the alignment into an alignframe from Gff3 File",
404             af != null);
405     checkDatasetfromSimpleGff3(af.getViewport().getAlignment());
406   }
407
408   @Test(groups = { "Functional" })
409   public void simpleGff3RelaxedIdMatching() throws IOException
410   {
411     AlignmentI dataset = new Alignment(new SequenceI[] {});
412     FeaturesFile ffile = new FeaturesFile(simpleGffFile,
413             FormatAdapter.FILE);
414   
415     boolean parseResult = ffile.parse(dataset, null, false, true);
416     assertTrue("return result (relaxedID matching) should be true",
417             parseResult);
418     checkDatasetfromSimpleGff3(dataset);
419   }
420 }