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