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