JAL-2295 JAL-2296 skeleton 'attributes to features'; renamed methods
[jalview.git] / src / jalview / ext / rbvi / chimera / JalviewChimeraBinding.java
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";
+  }
 }