JAL-2295 JAL-2296 skeleton 'attributes to features'; renamed methods
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 14 Nov 2016 09:43:28 +0000 (09:43 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 14 Nov 2016 09:43:28 +0000 (09:43 +0000)
with a view to pull-up

src/ext/edu/ucsf/rbvi/strucviz2/ChimeraManager.java
src/jalview/ext/rbvi/chimera/ChimeraCommands.java
src/jalview/ext/rbvi/chimera/ChimeraListener.java
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/gui/ChimeraViewFrame.java
src/jalview/structure/AtomSpec.java
test/jalview/ext/rbvi/chimera/JalviewChimeraView.java

index fdcf34f..21ae914 100644 (file)
@@ -389,6 +389,11 @@ public class ChimeraManager
             "list selection level residue", true);
     if (chimeraReply != null)
     {
+      /*
+       * expect 0, 1 or more lines of the format
+       * residue id #0:43.A type GLY
+       * where we are only interested in the atomspec #0.43.A
+       */
       for (String inputLine : chimeraReply)
       {
         String[] inputLineParts = inputLine.split("\\s+");
index 3415815..7020f90 100644 (file)
@@ -49,7 +49,7 @@ import MCview.PDBChain;
 public class ChimeraCommands
 {
 
-  private static final String NAMESPACE_PREFIX = "jv_";
+  public static final String NAMESPACE_PREFIX = "jv_";
 
   /**
    * Constructs Chimera commands to colour residues as per the Jalview alignment
index ba5e332..507ddbc 100644 (file)
@@ -129,7 +129,7 @@ public class ChimeraListener extends AbstractRequestHandler implements
   }
 
   /**
-   * Handler a ModelChanged notification from Chimera
+   * Handle a ModelChanged notification from Chimera
    * 
    * @param substring
    */
index 3d6e239..a74b3ab 100644 (file)
@@ -27,6 +27,7 @@ import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.httpserver.AbstractRequestHandler;
 import jalview.schemes.ColourSchemeI;
@@ -44,6 +45,8 @@ import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.BindException;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -97,14 +100,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    */
   private Map<String, List<ChimeraModel>> chimeraMaps = new LinkedHashMap<String, List<ChimeraModel>>();
 
-  /*
-   * the default or current model displayed if the model cannot be identified
-   * from the selection message
-   */
-  private int frameNo = 0;
-
-  private String lastCommand;
-
   String lastHighlightCommand;
 
   /*
@@ -282,7 +277,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       chimeraListener.shutdown();
       chimeraListener = null;
     }
-    lastCommand = null;
     viewer = null;
 
     releaseUIResources();
@@ -596,7 +590,10 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   }
 
   /**
-   * Send a command to Chimera, and optionally log and return any responses
+   * Send a command to Chimera, and optionally log and return any responses.
+   * <p>
+   * Does nothing, and returns null, if the command is the same as the last one
+   * sent [why?].
    * 
    * @param command
    * @param getResponse
@@ -611,7 +608,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     }
     List<String> reply = null;
     viewerCommandHistory(false);
-    if (lastCommand == null || !lastCommand.equals(command))
+    if (true /*lastCommand == null || !lastCommand.equals(command)*/)
     {
       // trim command or it may never find a match in the replyLog!!
       List<String> lastReply = viewer.sendChimeraCommand(command.trim(),
@@ -627,7 +624,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       }
     }
     viewerCommandHistory(true);
-    lastCommand = command;
 
     return reply;
   }
@@ -864,8 +860,27 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
      * Parse model number, residue and chain for each selected position,
      * formatted as #0:123.A or #1.2:87.B (#model.submodel:residue.chain)
      */
+    List<AtomSpec> atomSpecs = convertStructureResiduesToAlignment(selection);
+
+    /*
+     * Broadcast the selection (which may be empty, if the user just cleared all
+     * selections)
+     */
+    getSsm().mouseOverStructure(atomSpecs);
+  }
+
+  /**
+   * Converts a list of Chimera atomspecs to a list of AtomSpec representing the
+   * corresponding residues (if any) in Jalview
+   * 
+   * @param structureSelection
+   * @return
+   */
+  protected List<AtomSpec> convertStructureResiduesToAlignment(
+          List<String> structureSelection)
+  {
     List<AtomSpec> atomSpecs = new ArrayList<AtomSpec>();
-    for (String atomSpec : selection)
+    for (String atomSpec : structureSelection)
     {
       int colonPos = atomSpec.indexOf(":");
       if (colonPos == -1)
@@ -897,7 +912,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       /*
        * Work out the pdbfilename from the model number
        */
-      String pdbfilename = modelFileNames[frameNo];
+      String pdbfilename = modelFileNames[0];
       findfileloop: for (String pdbfile : this.chimeraMaps.keySet())
       {
         for (ChimeraModel cm : chimeraMaps.get(pdbfile))
@@ -911,12 +926,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
       }
       atomSpecs.add(new AtomSpec(pdbfilename, chainId, pdbResNum, 0));
     }
-
-    /*
-     * Broadcast the selection (which may be empty, if the user just cleared all
-     * selections)
-     */
-    getSsm().mouseOverStructure(atomSpecs);
+    return atomSpecs;
   }
 
   private void log(String message)
@@ -1129,8 +1139,9 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    * 
    * @param avp
    */
-  public void sendFeaturesToChimera(AlignmentViewPanel avp)
+  public void sendFeaturesToViewer(AlignmentViewPanel avp)
   {
+    // TODO refactor as required to pull up to an interface
     AlignmentI alignment = avp.getAlignment();
     FeatureRenderer fr = getFeatureRenderer(avp);
 
@@ -1151,11 +1162,18 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     StructureMappingcommandSet commandSet = ChimeraCommands
             .getSetAttributeCommandsForFeatures(getSsm(), files,
                     getSequence(), fr, alignment);
-    // for (String command : commandSet.commands)
-    // {
-    // sendAsynchronousCommand(command, null);
-    // }
-    sendCommandsByFile(commandSet.commands);
+    String[] commands = commandSet.commands;
+    if (commands.length > 10)
+    {
+      sendCommandsByFile(commands);
+    }
+    else
+    {
+      for (String command : commands)
+      {
+        sendAsynchronousCommand(command, null);
+      }
+    }
   }
 
   /**
@@ -1187,4 +1205,110 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
                       + 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
+     */
+    String cmd = "list residues spec ':*/" + attName + "' 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();
+    fr.getFeaturesDisplayed().getVisibleFeatureCount();
+
+    /*
+     * Expect 0, 1 or more reply lines of the format (chi2 is attName):
+     * residue id #0:5.A chi2 -155.000836316 index 5
+     */
+    Map<String, Map<SequenceI, List<Integer>>> attsMap = new HashMap<String, Map<SequenceI, List<Integer>>>();
+    for (String residue : residues)
+    {
+      String[] tokens = residue.split(" ");
+      if (tokens.length > 4)
+      {
+        String atomSpec = tokens[2];
+        String attValue = tokens[4];
+        // TODO find mapping of atomspec to Jalview residue if any
+        // build a map of { attValue, Map<SequenceI, List<Integer>> }
+        // sort the integer lists
+        // and create a feature for each contiguous integer range
+        SequenceI seq = null; // mapped-to sequence
+        int seqPos = 0; // mapped-to sequence position
+
+        /*
+         * record attribute value / sequence / sequence position
+         */
+        Map<SequenceI, List<Integer>> seqMap = attsMap.get(attValue);
+        if (seqMap == null)
+        {
+          seqMap = new HashMap<SequenceI, List<Integer>>();
+          attsMap.put(attValue, seqMap);
+        }
+        List<Integer> seqPositions = seqMap.get(seq);
+        if (seqPositions == null)
+        {
+          seqPositions = new ArrayList<Integer>();
+          seqMap.put(seq, seqPositions);
+        }
+        seqPositions.add(seqPos);
+      }
+    }
+
+    /*
+     * traverse values and sequences
+     */
+    for (String val : attsMap.keySet())
+    {
+      for (SequenceI seq : attsMap.get(val).keySet())
+      {
+        List<Integer> positions = attsMap.get(val).get(seq);
+        Collections.sort(positions);
+        // TODO find reusable code that compacts the list
+        List<int[]> ranges = null;// compacted list
+        for (int[] range : ranges)
+        {
+          float score = Float.NaN;
+          try
+          {
+            score = Float.valueOf(val);
+          } catch (NumberFormatException e)
+          {
+            // was not a float value
+          }
+          String featureGroup = getViewerFeatureGroup();
+          SequenceFeature sf = new SequenceFeature(attName, val, range[0],
+                  range[1], score, featureGroup);
+          // note: repeating the action shouldn't duplicate features
+          seq.addSequenceFeature(sf);
+        }
+      }
+    }
+  }
+
+  /**
+   * 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";
+  }
 }
index d68cce8..d43702c 100644 (file)
@@ -26,6 +26,7 @@ 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;
@@ -49,6 +50,8 @@ import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import java.awt.event.ItemEvent;
 import java.awt.event.ItemListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
@@ -58,6 +61,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Random;
 import java.util.Vector;
@@ -181,6 +185,7 @@ public class ChimeraViewFrame extends StructureViewerBase
         // TODO Auto-generated method stub
       }
     });
+
     JMenuItem writeFeatures = new JMenuItem(
             MessageManager.getString("label.create_chimera_attributes"));
     writeFeatures.setToolTipText(MessageManager
@@ -194,6 +199,71 @@ public class ChimeraViewFrame extends StructureViewerBase
       }
     });
     viewerActionMenu.add(writeFeatures);
+
+    final JMenu fetchAttributes = new JMenu("Fetch Chimera attributes");
+    fetchAttributes
+            .setToolTipText("Copy Chimera attribute to Jalview feature");
+    fetchAttributes.addMouseListener(new MouseAdapter()
+    {
+
+      @Override
+      public void mouseEntered(MouseEvent e)
+      {
+        buildAttributesMenu(fetchAttributes);
+      }
+    });
+    viewerActionMenu.add(fetchAttributes);
+
+  }
+
+  /**
+   * Query Chimera for its residue attribute names and add them as items off the
+   * attributes menu
+   * 
+   * @param attributesMenu
+   */
+  protected void buildAttributesMenu(JMenu attributesMenu)
+  {
+    List<String> atts = jmb.sendChimeraCommand("list resattr", true);
+    if (atts == null)
+    {
+      return;
+    }
+    attributesMenu.removeAll();
+    Collections.sort(atts);
+    for (String att : atts)
+    {
+      final String attName = att.split(" ")[1];
+
+      /*
+       * ignore 'jv_*' attributes, as these are Jalview features that have
+       * been transferred to residue attributes in Chimera!
+       */
+      if (!attName.startsWith(ChimeraCommands.NAMESPACE_PREFIX))
+      {
+        JMenuItem menuItem = new JMenuItem(attName);
+        menuItem.addActionListener(new ActionListener()
+        {
+          @Override
+          public void actionPerformed(ActionEvent e)
+          {
+            getChimeraAttributes(attName);
+          }
+        });
+        attributesMenu.add(menuItem);
+      }
+    }
+  }
+
+  /**
+   * Read residues in Chimera with the given attribute name, and set as features
+   * on the corresponding sequence positions (if any)
+   * 
+   * @param attName
+   */
+  protected void getChimeraAttributes(String attName)
+  {
+    jmb.copyStructureAttributesToFeatures(attName, getAlignmentPanel());
   }
 
   /**
@@ -205,7 +275,7 @@ public class ChimeraViewFrame extends StructureViewerBase
    */
   protected void sendFeaturesToChimera()
   {
-    jmb.sendFeaturesToChimera(getAlignmentPanel());
+    jmb.sendFeaturesToViewer(getAlignmentPanel());
   }
 
   /**
index a19acef..271bf1d 100644 (file)
@@ -29,9 +29,6 @@ package jalview.structure;
  */
 public class AtomSpec
 {
-  // TODO clarify do we want pdbFile here, or pdbId?
-  // compare highlightAtom in 2.8.2 for JalviewJmolBinding and
-  // javascript.MouseOverStructureListener
   private String pdbFile;
 
   private String chain;
index 6ea377f..1b7d9d0 100644 (file)
@@ -264,7 +264,7 @@ public class JalviewChimeraView
      */
     // TODO rename and pull up method to binding interface
     // once functionality is added for Jmol as well
-    binding.sendFeaturesToChimera(af.getViewport().getAlignPanel());
+    binding.sendFeaturesToViewer(af.getViewport().getAlignPanel());
 
     /*
      * give Chimera time to open the commands file and execute it