Merge branch 'develop' into features/JAL-2295setChimeraAttributes
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 29 Nov 2016 14:20:41 +0000 (14:20 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Tue, 29 Nov 2016 14:20:41 +0000 (14:20 +0000)
Conflicts:
test/jalview/ext/rbvi/chimera/JalviewChimeraView.java

1  2 
resources/lang/Messages.properties
src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/JalviewChimeraBindingModel.java
src/jalview/structure/StructureSelectionManager.java
test/jalview/ext/jmol/JmolViewerTest.java
test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java
test/jalview/ext/rbvi/chimera/JalviewChimeraView.java

Simple merge
@@@ -27,11 -27,9 +27,12 @@@ import jalview.bin.Cache
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.PDBEntry;
 +import jalview.datamodel.SearchResultMatchI;
 +import jalview.datamodel.SearchResults;
 +import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
  import jalview.httpserver.AbstractRequestHandler;
+ import jalview.io.DataSourceType;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.ResidueProperties;
  import jalview.structure.AtomSpec;
@@@ -26,10 -26,9 +26,10 @@@ import jalview.datamodel.AlignmentI
  import jalview.datamodel.ColumnSelection;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SequenceI;
 +import jalview.ext.rbvi.chimera.ChimeraCommands;
  import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
  import jalview.gui.StructureViewer.ViewerType;
- import jalview.io.AppletFormatAdapter;
+ import jalview.io.DataSourceType;
  import jalview.io.JalviewFileChooser;
  import jalview.io.JalviewFileView;
  import jalview.io.StructureFile;
@@@ -24,9 -24,9 +24,10 @@@ import static org.testng.AssertJUnit.as
  
  import jalview.api.structures.JalviewStructureDisplayI;
  import jalview.bin.Cache;
 +import jalview.bin.Jalview;
  import jalview.datamodel.SequenceI;
  import jalview.gui.AlignFrame;
+ import jalview.gui.JvOptionPane;
  import jalview.gui.Preferences;
  import jalview.gui.StructureViewer;
  import jalview.gui.StructureViewer.ViewerType;
   */
  package jalview.ext.rbvi.chimera;
  
 -import static org.testng.AssertJUnit.assertEquals;
 -import static org.testng.AssertJUnit.assertTrue;
 +import static org.testng.Assert.assertEquals;
 +import static org.testng.Assert.assertTrue;
  
+ import jalview.gui.JvOptionPane;
  import java.awt.Color;
 -import java.util.Arrays;
 +import java.util.HashMap;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
@@@ -33,7 -36,63 +36,15 @@@ import org.testng.annotations.Test
  
  public class ChimeraCommandsTest
  {
+   @BeforeClass(alwaysRun = true)
+   public void setUpJvOptionPane()
+   {
+     JvOptionPane.setInteractiveMode(false);
+     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+   }
    @Test(groups = { "Functional" })
 -  public void testAddColourRange()
 -  {
 -    Map<Color, Map<Integer, Map<String, List<int[]>>>> map = new LinkedHashMap<Color, Map<Integer, Map<String, List<int[]>>>>();
 -    ChimeraCommands.addColourRange(map, Color.pink, 1, 2, 4, "A");
 -    ChimeraCommands.addColourRange(map, Color.pink, 1, 8, 8, "A");
 -    ChimeraCommands.addColourRange(map, Color.pink, 1, 5, 7, "B");
 -    ChimeraCommands.addColourRange(map, Color.red, 1, 3, 5, "A");
 -    ChimeraCommands.addColourRange(map, Color.red, 0, 1, 4, "B");
 -    ChimeraCommands.addColourRange(map, Color.orange, 0, 5, 9, "C");
 -
 -    // three colours mapped
 -    assertEquals(3, map.keySet().size());
 -
 -    // Red has two models, Pink and Orange one each
 -    assertEquals(2, map.get(Color.red).keySet().size());
 -    assertEquals(1, map.get(Color.orange).keySet().size());
 -    assertEquals(1, map.get(Color.pink).keySet().size());
 -
 -    // pink model 1 has two chains, red.0 / red.1 / orange.0 one each
 -    assertEquals(2, map.get(Color.pink).get(1).keySet().size());
 -    assertEquals(1, map.get(Color.red).get(0).keySet().size());
 -    assertEquals(1, map.get(Color.red).get(1).keySet().size());
 -    assertEquals(1, map.get(Color.orange).get(0).keySet().size());
 -
 -    // inspect positions
 -    List<int[]> posList = map.get(Color.pink).get(1).get("A");
 -    assertEquals(2, posList.size());
 -    assertTrue(Arrays.equals(new int[] { 2, 4 }, posList.get(0)));
 -    assertTrue(Arrays.equals(new int[] { 8, 8 }, posList.get(1)));
 -
 -    posList = map.get(Color.pink).get(1).get("B");
 -    assertEquals(1, posList.size());
 -    assertTrue(Arrays.equals(new int[] { 5, 7 }, posList.get(0)));
 -
 -    posList = map.get(Color.red).get(0).get("B");
 -    assertEquals(1, posList.size());
 -    assertTrue(Arrays.equals(new int[] { 1, 4 }, posList.get(0)));
 -
 -    posList = map.get(Color.red).get(1).get("A");
 -    assertEquals(1, posList.size());
 -    assertTrue(Arrays.equals(new int[] { 3, 5 }, posList.get(0)));
 -
 -    posList = map.get(Color.orange).get(0).get("C");
 -    assertEquals(1, posList.size());
 -    assertTrue(Arrays.equals(new int[] { 5, 9 }, posList.get(0)));
 -  }
 -
 -  @Test(groups = { "Functional" })
    public void testBuildColourCommands()
    {
  
   */
  package jalview.ext.rbvi.chimera;
  
 -import static org.testng.AssertJUnit.assertEquals;
 -import static org.testng.AssertJUnit.assertTrue;
 +import static org.testng.Assert.assertEquals;
 +import static org.testng.Assert.assertFalse;
 +import static org.testng.Assert.assertNotNull;
 +import static org.testng.Assert.assertTrue;
  
 +import jalview.api.FeatureRenderer;
  import jalview.api.structures.JalviewStructureDisplayI;
  import jalview.bin.Cache;
 +import jalview.bin.Jalview;
 +import jalview.datamodel.DBRefEntry;
 +import jalview.datamodel.PDBEntry;
 +import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
  import jalview.gui.AlignFrame;
 +import jalview.gui.Desktop;
+ import jalview.gui.JvOptionPane;
  import jalview.gui.Preferences;
  import jalview.gui.StructureViewer;
  import jalview.gui.StructureViewer.ViewerType;
 +import jalview.io.FileLoader;
- import jalview.io.FormatAdapter;
 +import jalview.structure.StructureMapping;
 +import jalview.structure.StructureSelectionManager;
 +import jalview.ws.sifts.SiftsClient;
 +import jalview.ws.sifts.SiftsException;
 +import jalview.ws.sifts.SiftsSettings;
 +
 +import java.io.File;
 +import java.io.IOException;
 +import java.util.List;
 +import java.util.Vector;
+ import jalview.io.DataSourceType;
  
  import org.testng.annotations.AfterClass;
 +import org.testng.annotations.AfterMethod;
  import org.testng.annotations.BeforeClass;
  import org.testng.annotations.Test;
  
  public class JalviewChimeraView
  {
  
+   @BeforeClass(alwaysRun = true)
+   public void setUpJvOptionPane()
+   {
+     JvOptionPane.setInteractiveMode(false);
+     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
+   }
 +  private JalviewStructureDisplayI chimeraViewer;
 +
    /**
     * @throws java.lang.Exception
     */
    @AfterClass(alwaysRun = true)
    public static void tearDownAfterClass() throws Exception
    {
 -    jalview.gui.Desktop.instance.closeAll_actionPerformed(null);
 +    Desktop.instance.closeAll_actionPerformed(null);
 +  }
 +
 +  @AfterMethod(alwaysRun = true)
 +  public void tearDownAfterTest() throws Exception
 +  {
 +    SiftsClient.setMockSiftsFile(null);
 +    if (chimeraViewer != null)
 +    {
 +      chimeraViewer.closeViewer(true);
 +    }
    }
  
 -  @Test(groups = { "Functional" })
 +  /**
 +   * Load 1GAQ and view the first structure for which a PDB id is found. Note no
 +   * network connection is needed - PDB file is read locally, SIFTS fetch fails
 +   * so mapping falls back to Needleman-Wunsch - ok for this test.
 +   */
 +  // External as local install of Chimera required
 +  @Test(groups = { "External" })
    public void testSingleSeqViewChimera()
    {
 -    Cache.setProperty(Preferences.STRUCTURE_DISPLAY,
 -            ViewerType.CHIMERA.name());
      String inFile = "examples/1gaq.txt";
 -    AlignFrame af = new jalview.io.FileLoader().LoadFileWaitTillLoaded(
 -            inFile, DataSourceType.FILE);
 -    assertTrue("Didn't read input file " + inFile, af != null);
 -    for (SequenceI sq : af.getViewport().getAlignment().getSequences())
 +    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
-             FormatAdapter.FILE);
++            DataSourceType.FILE);
 +    assertNotNull(af, "Failed to create AlignFrame");
 +    SequenceI sq = af.getViewport().getAlignment().getSequenceAt(0);
 +    assertEquals(sq.getName(), "1GAQ|A");
 +    SequenceI dsq = sq.getDatasetSequence();
 +    Vector<PDBEntry> pdbIds = dsq.getAllPDBEntries();
 +    assertEquals(pdbIds.size(), 1);
 +    PDBEntry pdbEntry = pdbIds.get(0);
 +    assertEquals(pdbEntry.getId(), "1GAQ");
 +    StructureViewer structureViewer = new StructureViewer(af.getViewport()
 +            .getStructureSelectionManager());
 +    chimeraViewer = structureViewer.viewStructures(pdbEntry,
 +            new SequenceI[] { sq }, af.getCurrentView().getAlignPanel());
 +    JalviewChimeraBinding binding = (JalviewChimeraBinding) chimeraViewer
 +            .getBinding();
 +
 +    /*
 +     * Wait for viewer load thread to complete
 +     */
 +    while (!binding.isFinishedInit())
      {
 -      System.out.println("** sq=" + sq.getName());
 -      SequenceI dsq = sq.getDatasetSequence();
 -      while (dsq.getDatasetSequence() != null)
 +      try
 +      {
 +        Thread.sleep(500);
 +      } catch (InterruptedException e)
        {
 -        dsq = dsq.getDatasetSequence();
        }
 -      if (dsq.getAllPDBEntries() != null
 -              && dsq.getAllPDBEntries().size() > 0)
 +    }
 +
 +    assertTrue(binding.isChimeraRunning(), "Failed to start Chimera");
 +
 +    assertEquals(chimeraViewer.getBinding().getPdbCount(), 1);
 +    chimeraViewer.closeViewer(true);
 +    chimeraViewer = null;
 +    return;
 +  }
 +
 +  /**
 +   * Test for writing Jalview features as attributes on mapped residues in
 +   * Chimera. Note this uses local copies of PDB and SIFTS file, no network
 +   * connection required.
 +   * 
 +   * @throws IOException
 +   * @throws SiftsException
 +   */
 +  // External as this requires a local install of Chimera
 +  @Test(groups = { "External" })
 +  public void testTransferFeatures() throws IOException, SiftsException
 +  {
 +    String inFile = "examples/uniref50.fa";
 +    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
-             FormatAdapter.FILE);
++            DataSourceType.FILE);
 +    assertNotNull(af, "Failed to create AlignFrame");
 +    SequenceI sq = af.getViewport().getAlignment().findName("FER2_ARATH");
 +    assertNotNull(sq, "Didn't find FER2_ARATH");
 +
 +    /*
 +     * need a Uniprot dbref for SIFTS mapping to work!!
 +     */
 +    sq.addDBRef(new DBRefEntry("UNIPROT", "0", "P16972", null));
 +
 +    /*
 +     * use local test PDB and SIFTS files
 +     */
 +    String pdbFilePath = new File(
 +            "test/jalview/ext/rbvi/chimera/4zho.pdb").getPath();
 +    PDBEntry pdbEntry = new PDBEntry("4ZHO", null, null, pdbFilePath);
 +    String siftsFilePath = new File(
 +            "test/jalview/ext/rbvi/chimera/4zho.xml.gz")
 +            .getPath();
 +    SiftsClient.setMockSiftsFile(new File(siftsFilePath));
 +
 +    StructureViewer structureViewer = new StructureViewer(af.getViewport()
 +            .getStructureSelectionManager());
 +    chimeraViewer = structureViewer.viewStructures(pdbEntry,
 +            new SequenceI[] { sq }, af.getCurrentView().getAlignPanel());
 +
 +    JalviewChimeraBinding binding = (JalviewChimeraBinding) chimeraViewer
 +            .getBinding();
 +    do
 +    {
 +      try
 +      {
 +        Thread.sleep(500);
 +      } catch (InterruptedException e)
 +      {
 +      }
 +    } while (!binding.isFinishedInit());
 +
 +    assertTrue(binding.isChimeraRunning(), "Failed to launch Chimera");
 +
 +    assertEquals(binding.getPdbCount(), 1);
 +
 +    /*
 +     * check mapping is (sequence) 53-145 to (structure) 2-94 A/B
 +     * (or possibly 52-145 to 1-94 - see JAL-2319)
 +     */
 +    StructureSelectionManager ssm = binding.getSsm();
 +    String pdbFile = binding.getPdbFile()[0];
 +    StructureMapping[] mappings = ssm.getMapping(pdbFile);
 +    assertTrue(mappings[0].getMappingDetailsOutput().contains("SIFTS"),
 +            "Failed to perform SIFTS mapping");
 +    assertEquals(mappings.length, 2);
 +    assertEquals(mappings[0].getChain(), "A");
 +    assertEquals(mappings[0].getPDBResNum(53), 2);
 +    assertEquals(mappings[0].getPDBResNum(145), 94);
 +    assertEquals(mappings[1].getChain(), "B");
 +    assertEquals(mappings[1].getPDBResNum(53), 2);
 +    assertEquals(mappings[1].getPDBResNum(145), 94);
 +
 +    /*
 +     * now add some features to FER2_ARATH 
 +     */
 +    // feature on a sequence region not mapped to structure:
 +    sq.addSequenceFeature(new SequenceFeature("transit peptide",
 +            "chloroplast", 1, 51, Float.NaN, null));
 +    // feature on a region mapped to structure:
 +    sq.addSequenceFeature(new SequenceFeature("domain",
 +            "2Fe-2S ferredoxin-type", 55, 145, Float.NaN, null));
 +    // on sparse positions of the sequence
 +    sq.addSequenceFeature(new SequenceFeature("metal ion-binding site",
 +            "Iron-Sulfur (2Fe-2S)", 91, 91, Float.NaN, null));
 +    sq.addSequenceFeature(new SequenceFeature("metal ion-binding site",
 +            "Iron-Sulfur (2Fe-2S)", 96, 96, Float.NaN, null));
 +    // on a sequence region that is partially mapped to structure:
 +    sq.addSequenceFeature(new SequenceFeature("helix", null, 50, 60,
 +            Float.NaN, null));
 +    // and again:
 +    sq.addSequenceFeature(new SequenceFeature("chain", null, 50, 70,
 +            Float.NaN, null));
 +    // add numeric valued features - score is set as attribute value
 +    sq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 62,
 +            62, -2.1f, null));
 +    sq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 65,
 +            65, 3.6f, null));
 +    sq.addSequenceFeature(new SequenceFeature("RESNUM", "ALA:   2  4zhoA",
 +            53, 53, Float.NaN, null));
 +
 +    /*
 +     * set all features visible except for chain
 +     */
 +    af.setShowSeqFeatures(true);
 +    FeatureRenderer fr = af.getFeatureRenderer();
 +    fr.setVisible("transit peptide");
 +    fr.setVisible("domain");
 +    fr.setVisible("metal ion-binding site");
 +    fr.setVisible("helix");
 +    fr.setVisible("kd");
 +    fr.setVisible("RESNUM");
 +
 +    /*
 +     * 'perform' menu action to copy visible features to
 +     * attributes in Chimera
 +     */
 +    // TODO rename and pull up method to binding interface
 +    // once functionality is added for Jmol as well
 +    binding.sendFeaturesToViewer(af.getViewport().getAlignPanel());
 +
 +    /*
 +     * give Chimera time to open the commands file and execute it
 +     */
 +    try
 +    {
 +      Thread.sleep(1000);
 +    } catch (InterruptedException e)
 +    {
 +    }
 +
 +    /*
 +     * ask Chimera for its residue attribute names
 +     */
 +    List<String> reply = binding.sendChimeraCommand("list resattr", true);
 +    // prefixed and sanitised attribute names for Jalview features:
 +    assertTrue(reply.contains("resattr jv_domain"));
 +    assertTrue(reply.contains("resattr jv_metal_ion_binding_site"));
 +    assertTrue(reply.contains("resattr jv_helix"));
 +    assertTrue(reply.contains("resattr jv_kd"));
 +    assertTrue(reply.contains("resattr jv_RESNUM"));
 +    // feature is not on a mapped region - no attribute created
 +    assertFalse(reply.contains("resattr jv_transit_peptide"));
 +    // feature is not visible - no attribute created
 +    assertFalse(reply.contains("resattr jv_chain"));
 +
 +    /*
 +     * ask Chimera for residues with an attribute
 +     * 91 and 96 on sequence --> residues 40 and 45 on chains A and B
 +     */
 +    reply = binding.sendChimeraCommand(
 +            "list resi att jv_metal_ion_binding_site", true);
 +    assertEquals(reply.size(), 4);
 +    assertTrue(reply
 +            .contains("residue id #0:40.A jv_metal_ion_binding_site \"Iron-Sulfur (2Fe-2S)\" index 40"));
 +    assertTrue(reply
 +            .contains("residue id #0:45.A jv_metal_ion_binding_site \"Iron-Sulfur (2Fe-2S)\" index 45"));
 +    assertTrue(reply
 +            .contains("residue id #0:40.B jv_metal_ion_binding_site \"Iron-Sulfur (2Fe-2S)\" index 40"));
 +    assertTrue(reply
 +            .contains("residue id #0:45.B jv_metal_ion_binding_site \"Iron-Sulfur (2Fe-2S)\" index 45"));
 +
 +    /*
 +     * check attributes with score values
 +     * sequence positions 62 and 65 --> residues 11 and 14 on chains A and B
 +     */
 +    reply = binding.sendChimeraCommand("list resi att jv_kd", true);
 +    assertEquals(reply.size(), 4);
 +    assertTrue(reply.contains("residue id #0:11.A jv_kd -2.1 index 11"));
 +    assertTrue(reply.contains("residue id #0:14.A jv_kd 3.6 index 14"));
 +    assertTrue(reply.contains("residue id #0:11.B jv_kd -2.1 index 11"));
 +    assertTrue(reply.contains("residue id #0:14.B jv_kd 3.6 index 14"));
 +
 +    /*
 +     * list residues with positive kd score 
 +     */
 +    reply = binding.sendChimeraCommand(
 +            "list resi spec :*/jv_kd>0 attr jv_kd", true);
 +    assertEquals(reply.size(), 2);
 +    assertTrue(reply.contains("residue id #0:14.A jv_kd 3.6 index 14"));
 +    assertTrue(reply.contains("residue id #0:14.B jv_kd 3.6 index 14"));
 +
 +    SiftsClient.setMockSiftsFile(null);
 +    chimeraViewer.closeViewer(true);
 +    chimeraViewer = null;
 +  }
 +
 +  /**
 +   * Test for creating Jalview features from attributes on mapped residues in
 +   * Chimera. Note this uses local copies of PDB and SIFTS file, no network
 +   * connection required.
 +   * 
 +   * @throws IOException
 +   * @throws SiftsException
 +   */
 +  // External as this requires a local install of Chimera
 +  @Test(groups = { "External" })
 +  public void testGetAttributes() throws IOException, SiftsException
 +  {
 +    String inFile = "examples/uniref50.fa";
 +    AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(inFile,
-             FormatAdapter.FILE);
++            DataSourceType.FILE);
 +    assertNotNull(af, "Failed to create AlignFrame");
 +    SequenceI fer2Arath = af.getViewport().getAlignment()
 +            .findName("FER2_ARATH");
 +    assertNotNull(fer2Arath, "Didn't find FER2_ARATH");
 +  
 +    /*
 +     * need a Uniprot dbref for SIFTS mapping to work!!
 +     */
 +    fer2Arath.addDBRef(new DBRefEntry("UNIPROT", "0", "P16972", null));
 +  
 +    /*
 +     * use local test PDB and SIFTS files
 +     */
 +    String pdbFilePath = new File(
 +            "test/jalview/ext/rbvi/chimera/4zho.pdb").getPath();
 +    PDBEntry pdbEntry = new PDBEntry("4ZHO", null, null, pdbFilePath);
 +    String siftsFilePath = new File(
 +            "test/jalview/ext/rbvi/chimera/4zho.xml.gz")
 +            .getPath();
 +    SiftsClient.setMockSiftsFile(new File(siftsFilePath));
 +  
 +    StructureViewer structureViewer = new StructureViewer(af.getViewport()
 +            .getStructureSelectionManager());
 +    chimeraViewer = structureViewer.viewStructures(pdbEntry,
 +            new SequenceI[] { fer2Arath }, af.getCurrentView()
 +                    .getAlignPanel());
 +  
 +    JalviewChimeraBinding binding = (JalviewChimeraBinding) chimeraViewer
 +            .getBinding();
 +    do
 +    {
 +      try
 +      {
 +        Thread.sleep(500);
 +      } catch (InterruptedException e)
        {
 -        for (int q = 0; q < dsq.getAllPDBEntries().size(); q++)
 -        {
 -          final StructureViewer structureViewer = new StructureViewer(af
 -                  .getViewport().getStructureSelectionManager());
 -          structureViewer.setViewerType(ViewerType.CHIMERA);
 -          JalviewStructureDisplayI chimeraViewer = structureViewer
 -                  .viewStructures(dsq.getAllPDBEntries().elementAt(q),
 -                          new SequenceI[] { sq }, af.getCurrentView()
 -                                  .getAlignPanel());
 -          /*
 -           * Wait for viewer load thread to complete
 -           */
 -          while (!chimeraViewer.getBinding().isFinishedInit())
 -          {
 -            try
 -            {
 -              Thread.sleep(500);
 -            } catch (InterruptedException e)
 -            {
 -            }
 -          }
 -          assertEquals(1, chimeraViewer.getBinding().getPdbCount());
 -          chimeraViewer.closeViewer(true);
 -          // todo: break here means only once through this loop?
 -          break;
 -        }
 -        break;
        }
 +    } while (!binding.isFinishedInit());
 +  
 +    assertTrue(binding.isChimeraRunning(), "Failed to launch Chimera");
 +  
 +    assertEquals(binding.getPdbCount(), 1);
 +  
 +    /*
 +     * 'perform' menu action to copy visible features to
 +     * attributes in Chimera
 +     */
 +    // TODO rename and pull up method to binding interface
 +    // once functionality is added for Jmol as well
 +    binding.copyStructureAttributesToFeatures("isHelix", af.getViewport()
 +            .getAlignPanel());
 +
 +    /*
 +     * verify 22 residues have isHelix feature
 +     * (may merge into ranges in future)
 +     */
 +    af.setShowSeqFeatures(true);
 +    FeatureRenderer fr = af.getFeatureRenderer();
 +    fr.setVisible("isHelix");
 +    for (int res = 75; res <= 83; res++)
 +    {
 +      checkFeaturesAtRes(fer2Arath, fr, res, "isHelix");
      }
 +    for (int res = 117; res <= 123; res++)
 +    {
 +      checkFeaturesAtRes(fer2Arath, fr, res, "isHelix");
 +    }
 +    for (int res = 129; res <= 131; res++)
 +    {
 +      checkFeaturesAtRes(fer2Arath, fr, res, "isHelix");
 +    }
 +    for (int res = 143; res <= 145; res++)
 +    {
 +      checkFeaturesAtRes(fer2Arath, fr, res, "isHelix");
 +    }
 +
 +    /*
 +     * fetch a numeric valued attribute
 +     */
 +    binding.copyStructureAttributesToFeatures("phi", af.getViewport()
 +            .getAlignPanel());
 +    fr.setVisible("phi");
 +    List<SequenceFeature> fs = fr.findFeaturesAtRes(fer2Arath, 54);
 +    assertEquals(fs.size(), 3);
 +    assertEquals(fs.get(0).getType(), "RESNUM");
 +    assertEquals(fs.get(1).getType(), "phi");
 +    assertEquals(fs.get(2).getType(), "phi");
 +    assertEquals(fs.get(1).getDescription(), "A"); // chain
 +    assertEquals(fs.get(2).getDescription(), "B");
 +    assertEquals(fs.get(1).getScore(), -131.0713f, 0.001f);
 +    assertEquals(fs.get(2).getScore(), -127.39512, 0.001f);
 +
 +    /*
 +     * tear down - also in AfterMethod
 +     */
 +    SiftsClient.setMockSiftsFile(null);
 +    chimeraViewer.closeViewer(true);
 +    chimeraViewer = null;
 +  }
 +
 +  /**
 +   * Helper method to verify new feature at a sequence position
 +   * 
 +   * @param seq
 +   * @param fr
 +   * @param res
 +   * @param featureType
 +   */
 +  protected void checkFeaturesAtRes(SequenceI seq, FeatureRenderer fr,
 +          int res, String featureType)
 +  {
 +    String where = "at position " + res;
 +    List<SequenceFeature> fs = fr.findFeaturesAtRes(seq, res);
 +    assertEquals(fs.size(), 2, where);
 +    assertEquals(fs.get(0).getType(), "RESNUM", where);
 +    SequenceFeature sf = fs.get(1);
 +    assertEquals(sf.getType(), featureType, where);
 +    assertEquals(sf.getFeatureGroup(), "Chimera", where);
 +    assertEquals(sf.getDescription(), "True", where);
 +    assertEquals(sf.getScore(), Float.NaN, where);
    }
  }