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