Merge branch 'develop' into features/JAL-2295setChimeraAttributes
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 16 Nov 2016 12:23:33 +0000 (12:23 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 16 Nov 2016 12:23:33 +0000 (12:23 +0000)
Conflicts:
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/gui/ChimeraViewFrame.java

1  2 
src/jalview/datamodel/SequenceFeature.java
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/gui/JalviewChimeraBindingModel.java
test/jalview/datamodel/SequenceFeatureTest.java

@@@ -208,7 -208,7 +208,9 @@@ public class SequenceFeatur
      }
  
      SequenceFeature sf = (SequenceFeature) o;
--    if (begin != sf.begin || end != sf.end || score != sf.score)
++    boolean sameScore = Float.isNaN(score) ? Float.isNaN(sf.score)
++            : score == sf.score;
++    if (begin != sf.begin || end != sf.end || !sameScore)
      {
        return false;
      }
@@@ -41,13 -38,9 +41,14 @@@ import jalview.structures.models.AAStru
  import jalview.util.MessageManager;
  
  import java.awt.Color;
 +import java.io.File;
 +import java.io.FileOutputStream;
 +import java.io.IOException;
 +import java.io.PrintWriter;
  import java.net.BindException;
  import java.util.ArrayList;
 +import java.util.Collections;
+ import java.util.Hashtable;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
@@@ -59,10 -52,6 +60,8 @@@ import ext.edu.ucsf.rbvi.strucviz2.Stru
  
  public abstract class JalviewChimeraBinding extends AAStructureBindingModel
  {
 +  public static final String CHIMERA_FEATURE_GROUP = "Chimera";
 +
-   private static final String CHIMERA_FEATURE_PREFIX = "chim_";
    // Chimera clause to exclude alternate locations in atom selection
    private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9";
  
  
    private static final String ALPHACARBON = "CA";
  
+   private List<String> chainNames = new ArrayList<String>();
+   private Hashtable<String, String> chainFile = new Hashtable<String, String>();
++  
    /*
     * Object through which we talk to Chimera
     */
     * @param ssm
     * @param pdbentry
     * @param sequenceIs
--   * @param chains
     * @param protocol
     */
    public JalviewChimeraBinding(StructureSelectionManager ssm,
     */
    public abstract void refreshPdbEntries();
  
--  private int getModelNum(String modelFileName)
--  {
--    String[] mfn = getPdbFile();
--    if (mfn == null)
--    {
--      return -1;
--    }
--    for (int i = 0; i < mfn.length; i++)
--    {
--      if (mfn[i].equalsIgnoreCase(modelFileName))
--      {
--        return i;
--      }
--    }
--    return -1;
--  }
--
--  /**
--   * map between index of model filename returned from getPdbFile and the first
--   * index of models from this file in the viewer. Note - this is not trimmed -
--   * use getPdbFile to get number of unique models.
--   */
--  private int _modelFileNameMap[];
--
    // ////////////////////////////////
    // /StructureListener
    @Override
     * 
     * @return
     */
++  @Override
 +  public List<String> getChainNames()
 +  {
-     List<String> names = new ArrayList<String>();
-     String[][] allNames = getChains();
-     if (allNames != null)
-     {
-       for (String[] chainsForPdb : allNames)
-       {
-         if (chainsForPdb != null)
-         {
-           for (String chain : chainsForPdb)
-           {
-             if (chain != null && !names.contains(chain))
-             {
-               names.add(chain);
-             }
-           }
-         }
-       }
-     }
-     return names;
++    return chainNames;
 +  }
  
    /**
     * Send a 'focus' command to Chimera to recentre the visible display
      }
    }
  
 +  /**
 +   * Constructs and send commands to Chimera to set attributes on residues for
 +   * features visible in Jalview
 +   * 
 +   * @param avp
 +   */
 +  public void sendFeaturesToViewer(AlignmentViewPanel avp)
 +  {
 +    // TODO refactor as required to pull up to an interface
 +    AlignmentI alignment = avp.getAlignment();
 +    FeatureRenderer fr = getFeatureRenderer(avp);
  
 -  @Override
 -  public List<String> getChainNames()
 +    /*
 +     * fr is null if feature display is turned off
 +     */
 +    if (fr == null)
 +    {
 +      return;
 +    }
 +
 +    String[] files = getPdbFile();
 +    if (files == null)
 +    {
 +      return;
 +    }
 +
 +    StructureMappingcommandSet commandSet = ChimeraCommands
 +            .getSetAttributeCommandsForFeatures(getSsm(), files,
 +                    getSequence(), fr, alignment);
 +    String[] commands = commandSet.commands;
 +    if (commands.length > 10)
 +    {
 +      sendCommandsByFile(commands);
 +    }
 +    else
 +    {
 +      for (String command : commands)
 +      {
 +        sendAsynchronousCommand(command, null);
 +      }
 +    }
 +  }
 +
 +  /**
 +   * Write commands to a temporary file, and send a command to Chimera to open
 +   * the file as a commands script. For use when sending a large number of
 +   * separate commands would overload the REST interface mechanism.
 +   * 
 +   * @param commands
 +   */
 +  protected void sendCommandsByFile(String[] commands)
    {
 -    return chainNames;
 +    try
 +    {
 +      File tmp = File.createTempFile("chim", ".com");
 +      tmp.deleteOnExit();
 +      PrintWriter out = new PrintWriter(new FileOutputStream(tmp));
 +      for (String command : commands)
 +      {
 +        out.println(command);
 +      }
 +      out.flush();
 +      out.close();
 +      String path = tmp.getAbsolutePath();
 +      sendAsynchronousCommand("open cmd:" + path, null);
 +    } catch (IOException e)
 +    {
 +      System.err
 +              .println("Sending commands to Chimera via file failed with "
 +                      + e.getMessage());
 +    }
    }
  
 +  /**
 +   * Get Chimera residues which have the named attribute, find the mapped
 +   * positions in the Jalview sequence(s), and set as sequence features
 +   * 
 +   * @param attName
 +   * @param alignmentPanel
 +   */
 +  public void copyStructureAttributesToFeatures(String attName,
 +          AlignmentViewPanel alignmentPanel)
 +  {
 +    // todo pull up to AAStructureBindingModel (and interface?)
 +
 +    /*
 +     * ask Chimera to list residues with the attribute, reporting its value
 +     */
 +    // this alternative command
 +    // list residues spec ':*/attName' attr attName
 +    // doesn't report 'None' values (which is good), but
 +    // fails for 'average.bfactor' (which is bad):
 +
 +    String cmd = "list residues attr '" + attName + "'";
 +    List<String> residues = sendChimeraCommand(cmd, true);
-     /*
-      * TODO check if Jalview already has this feature name, if so give it a 
-      * distinguishing prefix e.g. chim_
-      */
-     FeatureRenderer fr = alignmentPanel.getFeatureRenderer();
-     // todo should getRenderOrder be in api.FeatureRenderer?
-     // FIXME this is empty if feature display is turned off
-     List<String> existingFeatures = ((jalview.gui.FeatureRenderer) fr)
-             .getRenderOrder();
-     if (existingFeatures.contains(attName))
-     {
-       // TODO check if feature of this name is in group Chimera
-       // if so don't create a new feature name!
-       // problem: needs a lookup of features for feature group
-       attName = getStructureFeaturePrefix() + attName;
-     }
 +    /*
 +     * Expect 0, 1 or more reply lines of the format (chi2 is attName):
 +     * residue id #0:5.A chi2 -155.000836316 index 5
 +     * or
 +     * residue id #0:6.A chi3 None
 +     * 
 +     * We assume here that attributes on structure do not naturally convert
 +     * to ranges on sequence, i.e. we just set one feature per mapped position.
 +     * 
 +     * To conflate positions, would need to first build a map 
 +     * Map<String value, Map<Sequence seq, List<Integer position>>>
 +     * and then traverse it to find feature ranges.
 +     */
 +    boolean featureAdded = false;
 +    String featureGroup = getViewerFeatureGroup();
 +
 +    for (String residue : residues)
 +    {
 +      AtomSpec spec = null;
 +      String[] tokens = residue.split(" ");
 +      if (tokens.length < 5)
 +      {
 +        continue;
 +      }
 +      String atomSpec = tokens[2];
 +      String attValue = tokens[4];
 +
 +      /*
 +       * ignore 'None' (e.g. for phi) or 'False' (e.g. for isHelix)
 +       */
 +      if ("None".equalsIgnoreCase(attValue)
 +              || "False".equalsIgnoreCase(attValue))
 +      {
 +        continue;
 +      }
 +
 +      try
 +      {
 +        spec = AtomSpec.fromChimeraAtomspec(atomSpec);
 +      } catch (IllegalArgumentException e)
 +      {
 +        System.err.println("Problem parsing atomspec " + atomSpec);
 +        continue;
 +      }
 +
 +      String chainId = spec.getChain();
 +      String description = attValue;
 +      float score = Float.NaN;
 +      try
 +      {
 +        score = Float.valueOf(attValue);
 +        description = chainId;
 +      } catch (NumberFormatException e)
 +      {
 +        // was not a float value
 +      }
 +
 +      String pdbFile = getPdbFileForModel(spec.getModelNumber());
 +      spec.setPdbFile(pdbFile);
 +
 +      List<AtomSpec> atoms = Collections.singletonList(spec);
 +      SearchResults sr = getSsm()
 +              .findAlignmentPositionsForStructurePositions(atoms);
 +
 +      /*
 +       * expect one matched alignment position, or none 
 +       * (if the structure position is not mapped)
 +       */
 +      for (Match m : sr.getResults())
 +      {
 +        SequenceI seq = m.getSequence();
 +        int start = m.getStart();
 +        int end = m.getEnd();
 +        SequenceFeature sf = new SequenceFeature(attName, description,
 +                start, end, score, featureGroup);
 +        // todo: should SequenceFeature have an explicit property for chain?
 +        // note: repeating the action shouldn't duplicate features
 +        featureAdded |= seq.addSequenceFeature(sf);
 +      }
 +    }
 +    if (featureAdded)
 +    {
-       fr.featuresAdded();
++      alignmentPanel.getFeatureRenderer().featuresAdded();
 +    }
 +  }
 +
 +  /**
-    * Answers a 'namespace' prefix to use for features created in Jalview from
-    * attributes in the structure viewer
-    * 
-    * @return
-    */
-   protected String getStructureFeaturePrefix()
-   {
-     // TODO pull up as abstract
-     return CHIMERA_FEATURE_PREFIX;
-   }
-   /**
 +   * Answers the feature group name to apply to features created in Jalview from
 +   * Chimera attributes
 +   * 
 +   * @return
 +   */
 +  protected String getViewerFeatureGroup()
 +  {
 +    // todo pull up to interface
 +    return CHIMERA_FEATURE_GROUP;
 +  }
++
++
+   public Hashtable<String, String> getChainFile()
+   {
+     return chainFile;
+   }
+   public List<ChimeraModel> getChimeraModelByChain(String chain)
+   {
+     return chimeraMaps.get(chainFile.get(chain));
+   }
+   public int getModelNoForChain(String chain)
+   {
+     List<ChimeraModel> foundModels = getChimeraModelByChain(chain);
+     if (foundModels != null && !foundModels.isEmpty())
+     {
+       return foundModels.get(0).getModelNumber();
+     }
+     return -1;
+   }
  }
@@@ -342,11 -246,9 +343,8 @@@ public class ChimeraViewFrame extends S
            SequenceI[][] seqs)
    {
      createProgressBar();
--    // FIXME extractChains needs pdbentries to match IDs to PDBEntry(s) on seqs
-     String[][] chains = extractChains(seqs);
      jmb = new JalviewChimeraBindingModel(this,
-             ap.getStructureSelectionManager(), pdbentrys, seqs, chains,
-             null);
+             ap.getStructureSelectionManager(), pdbentrys, seqs, null);
      addAlignmentPanel(ap);
      useAlignmentPanelForColourbyseq(ap);
      if (pdbentrys.length > 1)
  
    }
  
 -
 -
    /**
-    * Retrieve chains for sequences by inspecting their PDB refs. The hope is
-    * that the first will be to the sequence's own chain. Really need a more
-    * managed way of doing this.
-    * 
-    * @param seqs
-    * @return
-    */
-   protected String[][] extractChains(SequenceI[][] seqs)
-   {
-     String[][] chains = new String[seqs.length][];
-     for (int i = 0; i < seqs.length; i++)
-     {
-       chains[i] = new String[seqs[i].length];
-       int seqno = 0;
-       for (SequenceI seq : seqs[i])
-       {
-         String chain = null;
-         if (seq.getDatasetSequence() != null)
-         {
-           Vector<PDBEntry> pdbrefs = seq.getDatasetSequence()
-                   .getAllPDBEntries();
-           if (pdbrefs != null && pdbrefs.size() > 0)
-           {
-             // FIXME: SequenceI.PDBEntry[0] chain mapping used for
-             // ChimeraViewFrame. Is this even used ???
-             chain = pdbrefs.get(0).getChainCode();
-           }
-         }
-         chains[i][seqno++] = chain;
-       }
-     }
-     return chains;
-   }
-   /**
     * Create a new viewer from saved session state data including Chimera session
     * file
     * 
     */
    void initChimera()
    {
-     jalview.gui.Desktop.addInternalFrame(this,
-             jmb.getViewerTitle("Chimera", true), getBounds().width,
-             getBounds().height);
 -    jmb.setFinishedInit(false);
 -    jalview.gui.Desktop.addInternalFrame(this,
 -            jmb.getViewerTitle("Chimera", true), getBounds().width,
 -            getBounds().height);
++    Desktop.addInternalFrame(this, jmb.getViewerTitle("Chimera", true),
++            getBounds().width, getBounds().height);
  
      if (!jmb.launchChimera())
      {
      jmb.startChimeraListener();
    }
  
 +  /**
 +   * If the list is not empty, add menu items for 'All' and each individual
 +   * chain to the "View | Show Chain" sub-menu. Multiple selections are allowed.
 +   * 
 +   * @param chainNames
 +   */
++  @Override
 +  void setChainMenuItems(List<String> chainNames)
 +  {
 +    chainMenu.removeAll();
 +    if (chainNames == null || chainNames.isEmpty())
 +    {
 +      return;
 +    }
 +    JMenuItem menuItem = new JMenuItem(
 +            MessageManager.getString("label.all"));
 +    menuItem.addActionListener(new ActionListener()
 +    {
 +      @Override
 +      public void actionPerformed(ActionEvent evt)
 +      {
 +        allChainsSelected = true;
 +        for (int i = 0; i < chainMenu.getItemCount(); i++)
 +        {
 +          if (chainMenu.getItem(i) instanceof JCheckBoxMenuItem)
 +          {
 +            ((JCheckBoxMenuItem) chainMenu.getItem(i)).setSelected(true);
 +          }
 +        }
 +        showSelectedChains();
 +        allChainsSelected = false;
 +      }
 +    });
 +
 +    chainMenu.add(menuItem);
 +
 +    for (String chainName : chainNames)
 +    {
 +      menuItem = new JCheckBoxMenuItem(chainName, true);
 +      menuItem.addItemListener(new ItemListener()
 +      {
 +        @Override
 +        public void itemStateChanged(ItemEvent evt)
 +        {
 +          if (!allChainsSelected)
 +          {
 +            showSelectedChains();
 +          }
 +        }
 +      });
 +
 +      chainMenu.add(menuItem);
 +    }
 +  }
  
    /**
     * Show only the selected chain(s) in the viewer
            }
          }
        }
 +
+       jmb.refreshGUI();
        jmb.setFinishedInit(true);
        jmb.setLoadingFromArchive(false);
  
@@@ -112,56 -112,56 +112,83 @@@ public class SequenceFeatureTes
      assertEquals(sf1.hashCode(), sf2.hashCode());
  
      // changing type breaks equals:
++    String restores = sf2.getType();
      sf2.setType("Type");
      assertFalse(sf1.equals(sf2));
++    sf2.setType(restores);
  
      // changing description breaks equals:
--    sf2.setType("type");
++    restores = sf2.getDescription();
      sf2.setDescription("Desc");
      assertFalse(sf1.equals(sf2));
++    sf2.setDescription(restores);
++
++    // changing score breaks equals:
++    float restoref = sf2.getScore();
++    sf2.setScore(12.4f);
++    assertFalse(sf1.equals(sf2));
++    sf2.setScore(restoref);
++
++    // NaN doesn't match a number
++    restoref = sf2.getScore();
++    sf2.setScore(Float.NaN);
++    assertFalse(sf1.equals(sf2));
++
++    // NaN matches NaN
++    sf1.setScore(Float.NaN);
++    assertTrue(sf1.equals(sf2));
++    sf1.setScore(restoref);
++    sf2.setScore(restoref);
  
      // changing start position breaks equals:
--    sf2.setDescription("desc");
++    int restorei = sf2.getBegin();
      sf2.setBegin(21);
      assertFalse(sf1.equals(sf2));
++    sf2.setBegin(restorei);
  
      // changing end position breaks equals:
--    sf2.setBegin(22);
++    restorei = sf2.getEnd();
      sf2.setEnd(32);
      assertFalse(sf1.equals(sf2));
++    sf2.setEnd(restorei);
  
      // changing feature group breaks equals:
--    sf2.setEnd(33);
++    restores = sf2.getFeatureGroup();
      sf2.setFeatureGroup("Group");
      assertFalse(sf1.equals(sf2));
++    sf2.setFeatureGroup(restores);
  
      // changing ID breaks equals:
--    sf2.setFeatureGroup("group");
++    restores = (String) sf2.getValue("ID");
      sf2.setValue("ID", "id2");
      assertFalse(sf1.equals(sf2));
++    sf2.setValue("ID", restores);
  
      // changing Name breaks equals:
--    sf2.setValue("ID", "id");
++    restores = (String) sf2.getValue("Name");
      sf2.setValue("Name", "Name");
      assertFalse(sf1.equals(sf2));
++    sf2.setValue("Name", restores);
  
      // changing Parent breaks equals:
--    sf2.setValue("Name", "name");
++    restores = (String) sf1.getValue("Parent");
      sf1.setValue("Parent", "Parent");
      assertFalse(sf1.equals(sf2));
++    sf1.setValue("Parent", restores);
  
      // changing strand breaks equals:
--    sf1.setValue("Parent", "parent");
++    restorei = sf2.getStrand();
      sf2.setStrand("-");
      assertFalse(sf1.equals(sf2));
++    sf2.setStrand(restorei == 1 ? "+" : "-");
  
      // changing phase breaks equals:
--    sf2.setStrand("+");
++    restores = sf1.getPhase();
      sf1.setPhase("2");
      assertFalse(sf1.equals(sf2));
++    sf1.setPhase(restores);
  
      // restore equality as sanity check:
--    sf1.setPhase("1");
      assertTrue(sf1.equals(sf2));
      assertTrue(sf2.equals(sf1));
      assertEquals(sf1.hashCode(), sf2.hashCode());