JAL-2777 test for the whitespace chain
[jalview.git] / test / jalview / ext / rbvi / chimera / JalviewChimeraView.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.ext.rbvi.chimera;
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.assertTrue;
27
28 import jalview.api.FeatureRenderer;
29 import jalview.api.structures.JalviewStructureDisplayI;
30 import jalview.bin.Cache;
31 import jalview.bin.Jalview;
32 import jalview.datamodel.DBRefEntry;
33 import jalview.datamodel.PDBEntry;
34 import jalview.datamodel.SequenceFeature;
35 import jalview.datamodel.SequenceI;
36 import jalview.gui.AlignFrame;
37 import jalview.gui.Desktop;
38 import jalview.gui.JvOptionPane;
39 import jalview.gui.Preferences;
40 import jalview.gui.StructureViewer;
41 import jalview.gui.StructureViewer.ViewerType;
42 import jalview.io.DataSourceType;
43 import jalview.io.FileLoader;
44 import jalview.structure.StructureMapping;
45 import jalview.structure.StructureSelectionManager;
46 import jalview.ws.sifts.SiftsClient;
47 import jalview.ws.sifts.SiftsException;
48 import jalview.ws.sifts.SiftsSettings;
49
50 import java.io.File;
51 import java.io.IOException;
52 import java.util.List;
53 import java.util.Vector;
54
55 import org.testng.annotations.AfterClass;
56 import org.testng.annotations.AfterMethod;
57 import org.testng.annotations.BeforeClass;
58 import org.testng.annotations.Test;
59
60 @Test(singleThreaded = true)
61 public class JalviewChimeraView
62 {
63
64   @BeforeClass(alwaysRun = true)
65   public void setUpJvOptionPane()
66   {
67     JvOptionPane.setInteractiveMode(false);
68     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
69   }
70
71   private JalviewStructureDisplayI chimeraViewer;
72
73   /**
74    * @throws java.lang.Exception
75    */
76   @BeforeClass(alwaysRun = true)
77   public static void setUpBeforeClass() throws Exception
78   {
79     Jalview.main(new String[] { "-noquestionnaire", "-nonews", "-props",
80         "test/jalview/ext/rbvi/chimera/testProps.jvprops" });
81     Cache.setProperty(Preferences.STRUCTURE_DISPLAY,
82             ViewerType.CHIMERA.name());
83     Cache.setProperty("SHOW_ANNOTATIONS", "false");
84     Cache.setProperty(Preferences.STRUCT_FROM_PDB, "false");
85     Cache.setProperty(Preferences.STRUCTURE_DISPLAY,
86             ViewerType.CHIMERA.name());
87     Cache.setProperty("MAP_WITH_SIFTS", "true");
88     // TODO this should not be necessary!
89     SiftsSettings.setMapWithSifts(true);
90   }
91
92   /**
93    * @throws java.lang.Exception
94    */
95   @AfterClass(alwaysRun = true)
96   public static void tearDownAfterClass() throws Exception
97   {
98     Desktop.instance.closeAll_actionPerformed(null);
99   }
100
101   @AfterMethod(alwaysRun = true)
102   public void tearDownAfterTest() throws Exception
103   {
104     SiftsClient.setMockSiftsFile(null);
105     if (chimeraViewer != null)
106     {
107       chimeraViewer.closeViewer(true);
108     }
109   }
110
111   /**
112    * Load 1GAQ and view the first structure for which a PDB id is found. Note no
113    * network connection is needed - PDB file is read locally, SIFTS fetch fails
114    * so mapping falls back to Needleman-Wunsch - ok for this test.
115    */
116   // External as local install of Chimera required
117   @Test(groups = { "External" })
118   public void testSingleSeqViewChimera()
119   {
120     String inFile = "examples/1gaq.txt";
121     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
122             DataSourceType.FILE);
123     assertNotNull(af, "Failed to create AlignFrame");
124     SequenceI sq = af.getViewport().getAlignment().getSequenceAt(0);
125     assertEquals(sq.getName(), "1GAQ|A");
126     SequenceI dsq = sq.getDatasetSequence();
127     Vector<PDBEntry> pdbIds = dsq.getAllPDBEntries();
128     assertEquals(pdbIds.size(), 1);
129     PDBEntry pdbEntry = pdbIds.get(0);
130     assertEquals(pdbEntry.getId(), "1GAQ");
131     StructureViewer structureViewer = new StructureViewer(af.getViewport()
132             .getStructureSelectionManager());
133     chimeraViewer = structureViewer.viewStructures(pdbEntry,
134             new SequenceI[] { sq }, af.getCurrentView().getAlignPanel());
135     JalviewChimeraBinding binding = (JalviewChimeraBinding) chimeraViewer
136             .getBinding();
137
138     /*
139      * Wait for viewer load thread to complete
140      */
141     while (!binding.isFinishedInit())
142     {
143       try
144       {
145         Thread.sleep(500);
146       } catch (InterruptedException e)
147       {
148       }
149     }
150
151     assertTrue(binding.isChimeraRunning(), "Failed to start Chimera");
152
153     assertEquals(chimeraViewer.getBinding().getPdbCount(), 1);
154     chimeraViewer.closeViewer(true);
155     chimeraViewer = null;
156     return;
157   }
158
159   /**
160    * Test for writing Jalview features as attributes on mapped residues in
161    * Chimera. Note this uses local copies of PDB and SIFTS file, no network
162    * connection required.
163    * 
164    * @throws IOException
165    * @throws SiftsException
166    */
167   // External as this requires a local install of Chimera
168   @Test(groups = { "External" })
169   public void testTransferFeatures() throws IOException, SiftsException
170   {
171     String inFile = "examples/uniref50.fa";
172     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
173             DataSourceType.FILE);
174     assertNotNull(af, "Failed to create AlignFrame");
175     SequenceI sq = af.getViewport().getAlignment().findName("FER2_ARATH");
176     assertNotNull(sq, "Didn't find FER2_ARATH");
177
178     /*
179      * need a Uniprot dbref for SIFTS mapping to work!!
180      */
181     sq.addDBRef(new DBRefEntry("UNIPROT", "0", "P16972", null));
182
183     /*
184      * use local test PDB and SIFTS files
185      */
186     String pdbFilePath = new File(
187             "test/jalview/ext/rbvi/chimera/4zho.pdb").getPath();
188     PDBEntry pdbEntry = new PDBEntry("4ZHO", null, null, pdbFilePath);
189     String siftsFilePath = new File(
190             "test/jalview/ext/rbvi/chimera/4zho.xml.gz")
191             .getPath();
192     SiftsClient.setMockSiftsFile(new File(siftsFilePath));
193
194     StructureViewer structureViewer = new StructureViewer(af.getViewport()
195             .getStructureSelectionManager());
196     chimeraViewer = structureViewer.viewStructures(pdbEntry,
197             new SequenceI[] { sq }, af.getCurrentView().getAlignPanel());
198
199     JalviewChimeraBinding binding = (JalviewChimeraBinding) chimeraViewer
200             .getBinding();
201     do
202     {
203       try
204       {
205         Thread.sleep(500);
206       } catch (InterruptedException e)
207       {
208       }
209     } while (!binding.isFinishedInit());
210
211     assertTrue(binding.isChimeraRunning(), "Failed to launch Chimera");
212
213     assertEquals(binding.getPdbCount(), 1);
214
215     /*
216      * check mapping is (sequence) 53-145 to (structure) 2-94 A/B
217      * (or possibly 52-145 to 1-94 - see JAL-2319)
218      */
219     StructureSelectionManager ssm = binding.getSsm();
220     String pdbFile = binding.getStructureFiles()[0];
221     StructureMapping[] mappings = ssm.getMapping(pdbFile);
222     assertTrue(mappings[0].getMappingDetailsOutput().contains("SIFTS"),
223             "Failed to perform SIFTS mapping");
224     assertEquals(mappings.length, 2);
225     assertEquals(mappings[0].getChain(), "A");
226     assertEquals(mappings[0].getPDBResNum(53), 2);
227     assertEquals(mappings[0].getPDBResNum(145), 94);
228     assertEquals(mappings[1].getChain(), "B");
229     assertEquals(mappings[1].getPDBResNum(53), 2);
230     assertEquals(mappings[1].getPDBResNum(145), 94);
231
232     /*
233      * now add some features to FER2_ARATH 
234      */
235     // feature on a sequence region not mapped to structure:
236     sq.addSequenceFeature(new SequenceFeature("transit peptide",
237             "chloroplast", 1, 51, Float.NaN, null));
238     // feature on a region mapped to structure:
239     sq.addSequenceFeature(new SequenceFeature("domain",
240             "2Fe-2S ferredoxin-type", 55, 145, Float.NaN, null));
241     // on sparse positions of the sequence
242     sq.addSequenceFeature(new SequenceFeature("metal ion-binding site",
243             "Iron-Sulfur (2Fe-2S)", 91, 91, Float.NaN, null));
244     sq.addSequenceFeature(new SequenceFeature("metal ion-binding site",
245             "Iron-Sulfur (2Fe-2S)", 96, 96, Float.NaN, null));
246     // on a sequence region that is partially mapped to structure:
247     sq.addSequenceFeature(new SequenceFeature("helix", null, 50, 60,
248             Float.NaN, null));
249     // and again:
250     sq.addSequenceFeature(new SequenceFeature("chain", null, 50, 70,
251             Float.NaN, null));
252     // add numeric valued features - score is set as attribute value
253     sq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 62,
254             62, -2.1f, null));
255     sq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 65,
256             65, 3.6f, null));
257     sq.addSequenceFeature(new SequenceFeature("RESNUM", "ALA:   2  4zhoA",
258             53, 53, Float.NaN, null));
259
260     /*
261      * set all features visible except for chain
262      */
263     af.setShowSeqFeatures(true);
264     FeatureRenderer fr = af.getFeatureRenderer();
265     fr.setVisible("transit peptide");
266     fr.setVisible("domain");
267     fr.setVisible("metal ion-binding site");
268     fr.setVisible("helix");
269     fr.setVisible("kd");
270     fr.setVisible("RESNUM");
271
272     /*
273      * 'perform' menu action to copy visible features to
274      * attributes in Chimera
275      */
276     // TODO rename and pull up method to binding interface
277     // once functionality is added for Jmol as well
278     binding.sendFeaturesToViewer(af.getViewport().getAlignPanel());
279
280     /*
281      * give Chimera time to open the commands file and execute it
282      */
283     try
284     {
285       Thread.sleep(1000);
286     } catch (InterruptedException e)
287     {
288     }
289
290     /*
291      * ask Chimera for its residue attribute names
292      */
293     List<String> reply = binding.sendChimeraCommand("list resattr", true);
294     // prefixed and sanitised attribute names for Jalview features:
295     assertTrue(reply.contains("resattr jv_domain"));
296     assertTrue(reply.contains("resattr jv_metal_ion_binding_site"));
297     assertTrue(reply.contains("resattr jv_helix"));
298     assertTrue(reply.contains("resattr jv_kd"));
299     assertTrue(reply.contains("resattr jv_RESNUM"));
300     // feature is not on a mapped region - no attribute created
301     assertFalse(reply.contains("resattr jv_transit_peptide"));
302     // feature is not visible - no attribute created
303     assertFalse(reply.contains("resattr jv_chain"));
304
305     /*
306      * ask Chimera for residues with an attribute
307      * 91 and 96 on sequence --> residues 40 and 45 on chains A and B
308      */
309     reply = binding.sendChimeraCommand(
310             "list resi att jv_metal_ion_binding_site", true);
311     assertEquals(reply.size(), 4);
312     assertTrue(reply
313             .contains("residue id #0:40.A jv_metal_ion_binding_site \"Iron-Sulfur (2Fe-2S)\" index 40"));
314     assertTrue(reply
315             .contains("residue id #0:45.A jv_metal_ion_binding_site \"Iron-Sulfur (2Fe-2S)\" index 45"));
316     assertTrue(reply
317             .contains("residue id #0:40.B jv_metal_ion_binding_site \"Iron-Sulfur (2Fe-2S)\" index 40"));
318     assertTrue(reply
319             .contains("residue id #0:45.B jv_metal_ion_binding_site \"Iron-Sulfur (2Fe-2S)\" index 45"));
320
321     /*
322      * check attributes with score values
323      * sequence positions 62 and 65 --> residues 11 and 14 on chains A and B
324      */
325     reply = binding.sendChimeraCommand("list resi att jv_kd", true);
326     assertEquals(reply.size(), 4);
327     assertTrue(reply.contains("residue id #0:11.A jv_kd -2.1 index 11"));
328     assertTrue(reply.contains("residue id #0:14.A jv_kd 3.6 index 14"));
329     assertTrue(reply.contains("residue id #0:11.B jv_kd -2.1 index 11"));
330     assertTrue(reply.contains("residue id #0:14.B jv_kd 3.6 index 14"));
331
332     /*
333      * list residues with positive kd score 
334      */
335     reply = binding.sendChimeraCommand(
336             "list resi spec :*/jv_kd>0 attr jv_kd", true);
337     assertEquals(reply.size(), 2);
338     assertTrue(reply.contains("residue id #0:14.A jv_kd 3.6 index 14"));
339     assertTrue(reply.contains("residue id #0:14.B jv_kd 3.6 index 14"));
340
341     SiftsClient.setMockSiftsFile(null);
342     chimeraViewer.closeViewer(true);
343     chimeraViewer = null;
344   }
345
346   /**
347    * Test for creating Jalview features from attributes on mapped residues in
348    * Chimera. Note this uses local copies of PDB and SIFTS file, no network
349    * connection required.
350    * 
351    * @throws IOException
352    * @throws SiftsException
353    */
354   // External as this requires a local install of Chimera
355   @Test(groups = { "External" })
356   public void testGetAttributes() throws IOException, SiftsException
357   {
358     String inFile = "examples/uniref50.fa";
359     AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
360             DataSourceType.FILE);
361     assertNotNull(af, "Failed to create AlignFrame");
362     SequenceI fer2Arath = af.getViewport().getAlignment()
363             .findName("FER2_ARATH");
364     assertNotNull(fer2Arath, "Didn't find FER2_ARATH");
365   
366     /*
367      * need a Uniprot dbref for SIFTS mapping to work!!
368      */
369     fer2Arath.addDBRef(new DBRefEntry("UNIPROT", "0", "P16972", null));
370   
371     /*
372      * use local test PDB and SIFTS files
373      */
374     String pdbFilePath = new File(
375             "test/jalview/ext/rbvi/chimera/4zho.pdb").getPath();
376     PDBEntry pdbEntry = new PDBEntry("4ZHO", null, null, pdbFilePath);
377     String siftsFilePath = new File(
378             "test/jalview/ext/rbvi/chimera/4zho.xml.gz")
379             .getPath();
380     SiftsClient.setMockSiftsFile(new File(siftsFilePath));
381   
382     StructureViewer structureViewer = new StructureViewer(af.getViewport()
383             .getStructureSelectionManager());
384     chimeraViewer = structureViewer.viewStructures(pdbEntry,
385             new SequenceI[] { fer2Arath }, af.getCurrentView()
386                     .getAlignPanel());
387   
388     JalviewChimeraBinding binding = (JalviewChimeraBinding) chimeraViewer
389             .getBinding();
390     do
391     {
392       try
393       {
394         Thread.sleep(500);
395       } catch (InterruptedException e)
396       {
397       }
398     } while (!binding.isFinishedInit());
399   
400     assertTrue(binding.isChimeraRunning(), "Failed to launch Chimera");
401   
402     assertEquals(binding.getPdbCount(), 1);
403   
404     /*
405      * 'perform' menu action to copy visible features to
406      * attributes in Chimera
407      */
408     // TODO rename and pull up method to binding interface
409     // once functionality is added for Jmol as well
410     binding.copyStructureAttributesToFeatures("isHelix", af.getViewport()
411             .getAlignPanel());
412
413     /*
414      * verify 22 residues have isHelix feature
415      * (may merge into ranges in future)
416      */
417     af.setShowSeqFeatures(true);
418     FeatureRenderer fr = af.getFeatureRenderer();
419     fr.setVisible("isHelix");
420     for (int res = 75; res <= 83; res++)
421     {
422       checkFeaturesAtRes(fer2Arath, fr, res, "isHelix");
423     }
424     for (int res = 117; res <= 123; res++)
425     {
426       checkFeaturesAtRes(fer2Arath, fr, res, "isHelix");
427     }
428     for (int res = 129; res <= 131; res++)
429     {
430       checkFeaturesAtRes(fer2Arath, fr, res, "isHelix");
431     }
432     for (int res = 143; res <= 145; res++)
433     {
434       checkFeaturesAtRes(fer2Arath, fr, res, "isHelix");
435     }
436
437     /*
438      * fetch a numeric valued attribute
439      */
440     binding.copyStructureAttributesToFeatures("phi", af.getViewport()
441             .getAlignPanel());
442     fr.setVisible("phi");
443     List<SequenceFeature> fs = fer2Arath.getFeatures().findFeatures(54, 54);
444     assertEquals(fs.size(), 3);
445     /*
446      * order of returned features is not guaranteed
447      */
448     assertTrue("RESNUM".equals(fs.get(0).getType())
449             || "RESNUM".equals(fs.get(1).getType())
450             || "RESNUM".equals(fs.get(2).getType()));
451     assertTrue(fs.contains(new SequenceFeature("phi", "A", 54, 54,
452             -131.0713f, "Chimera")));
453     assertTrue(fs.contains(new SequenceFeature("phi", "B", 54, 54,
454             -127.39512f, "Chimera")));
455
456     /*
457      * tear down - also in AfterMethod
458      */
459     SiftsClient.setMockSiftsFile(null);
460     chimeraViewer.closeViewer(true);
461     chimeraViewer = null;
462   }
463
464   /**
465    * Helper method to verify new feature at a sequence position
466    * 
467    * @param seq
468    * @param fr
469    * @param res
470    * @param featureType
471    */
472   protected void checkFeaturesAtRes(SequenceI seq, FeatureRenderer fr,
473           int res, String featureType)
474   {
475     String where = "at position " + res;
476     List<SequenceFeature> fs = seq.getFeatures().findFeatures(res, res);
477
478     assertEquals(fs.size(), 2, where);
479     assertEquals(fs.get(0).getType(), "RESNUM", where);
480     SequenceFeature sf = fs.get(1);
481     assertEquals(sf.getType(), featureType, where);
482     assertEquals(sf.getFeatureGroup(), "Chimera", where);
483     assertEquals(sf.getDescription(), "True", where);
484     assertEquals(sf.getScore(), Float.NaN, where);
485   }
486 }