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