JAL-2296 new code and refactoring for 'copy Chimera attribute to
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 14 Nov 2016 12:31:30 +0000 (12:31 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 14 Nov 2016 12:31:30 +0000 (12:31 +0000)
features'

src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/structure/StructureSelectionManager.java

index a74b3ab..54adf87 100644 (file)
@@ -27,6 +27,8 @@ import jalview.bin.Cache;
 import jalview.datamodel.AlignmentI;
 import jalview.datamodel.ColumnSelection;
 import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SearchResults;
+import jalview.datamodel.SearchResults.Match;
 import jalview.datamodel.SequenceFeature;
 import jalview.datamodel.SequenceI;
 import jalview.httpserver.AbstractRequestHandler;
@@ -46,7 +48,6 @@ 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;
@@ -58,6 +59,10 @@ import ext.edu.ucsf.rbvi.strucviz2.StructureManager.ModelType;
 
 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";
 
@@ -224,7 +229,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
    */
   public String getViewerTitle(boolean verbose)
   {
-    return getViewerTitle("Chimera", verbose);
+    return getViewerTitle(CHIMERA_FEATURE_GROUP, verbose);
   }
 
   /**
@@ -882,51 +887,42 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
     List<AtomSpec> atomSpecs = new ArrayList<AtomSpec>();
     for (String atomSpec : structureSelection)
     {
-      int colonPos = atomSpec.indexOf(":");
-      if (colonPos == -1)
-      {
-        continue; // malformed
-      }
-
-      int hashPos = atomSpec.indexOf("#");
-      String modelSubmodel = atomSpec.substring(hashPos + 1, colonPos);
-      int dotPos = modelSubmodel.indexOf(".");
-      int modelId = 0;
       try
       {
-        modelId = Integer.valueOf(dotPos == -1 ? modelSubmodel
-                : modelSubmodel.substring(0, dotPos));
-      } catch (NumberFormatException e)
+        AtomSpec spec = AtomSpec.fromChimeraAtomspec(atomSpec);
+        String pdbfilename = getPdbFileForModel(spec.getModelNumber());
+        spec.setPdbFile(pdbfilename);
+        atomSpecs.add(spec);
+      } catch (IllegalArgumentException e)
       {
-        // ignore, default to model 0
+        System.err.println("Failed to parse atomspec: " + atomSpec);
       }
+    }
+    return atomSpecs;
+  }
 
-      String residueChain = atomSpec.substring(colonPos + 1);
-      dotPos = residueChain.indexOf(".");
-      int pdbResNum = Integer.parseInt(dotPos == -1 ? residueChain
-              : residueChain.substring(0, dotPos));
-
-      String chainId = dotPos == -1 ? "" : residueChain
-              .substring(dotPos + 1);
-
-      /*
-       * Work out the pdbfilename from the model number
-       */
-      String pdbfilename = modelFileNames[0];
-      findfileloop: for (String pdbfile : this.chimeraMaps.keySet())
+  /**
+   * @param modelId
+   * @return
+   */
+  protected String getPdbFileForModel(int modelId)
+  {
+    /*
+     * Work out the pdbfilename from the model number
+     */
+    String pdbfilename = modelFileNames[0];
+    findfileloop: for (String pdbfile : this.chimeraMaps.keySet())
+    {
+      for (ChimeraModel cm : chimeraMaps.get(pdbfile))
       {
-        for (ChimeraModel cm : chimeraMaps.get(pdbfile))
+        if (cm.getModelNumber() == modelId)
         {
-          if (cm.getModelNumber() == modelId)
-          {
-            pdbfilename = pdbfile;
-            break findfileloop;
-          }
+          pdbfilename = pdbfile;
+          break findfileloop;
         }
       }
-      atomSpecs.add(new AtomSpec(pdbfilename, chainId, pdbResNum, 0));
     }
-    return atomSpecs;
+    return pdbfilename;
   }
 
   private void log(String message)
@@ -1217,11 +1213,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
           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;
+    // 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);
 
     /*
@@ -1229,78 +1230,102 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
      * distinguishing prefix e.g. chim_
      */
     FeatureRenderer fr = alignmentPanel.getFeatureRenderer();
-    fr.getFeaturesDisplayed().getVisibleFeatureCount();
+    // 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))
+    {
+      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.
      */
-    Map<String, Map<SequenceI, List<Integer>>> attsMap = new HashMap<String, Map<SequenceI, List<Integer>>>();
+    boolean featureAdded = false;
     for (String residue : residues)
     {
+      AtomSpec spec = null;
       String[] tokens = residue.split(" ");
-      if (tokens.length > 4)
+      if (tokens.length < 5)
+      {
+        continue;
+      }
+      String atomSpec = tokens[2];
+      String attValue = tokens[4];
+      if ("None".equals(attValue))
       {
-        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
+        continue;
+      }
+      try
+      {
+        spec = AtomSpec.fromChimeraAtomspec(atomSpec);
+      } catch (IllegalArgumentException e)
+      {
+        System.err.println("Problem parsing atomspec " + atomSpec);
+        continue;
+      }
+      String pdbFile = getPdbFileForModel(spec.getModelNumber());
+      spec.setPdbFile(pdbFile);
 
-        /*
-         * record attribute value / sequence / sequence position
-         */
-        Map<SequenceI, List<Integer>> seqMap = attsMap.get(attValue);
-        if (seqMap == null)
+      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();
+
+        float score = Float.NaN;
+        try
         {
-          seqMap = new HashMap<SequenceI, List<Integer>>();
-          attsMap.put(attValue, seqMap);
-        }
-        List<Integer> seqPositions = seqMap.get(seq);
-        if (seqPositions == null)
+          score = Float.valueOf(attValue);
+        } catch (NumberFormatException e)
         {
-          seqPositions = new ArrayList<Integer>();
-          seqMap.put(seq, seqPositions);
+          // was not a float value
         }
-        seqPositions.add(seqPos);
+        String featureGroup = getViewerFeatureGroup();
+        SequenceFeature sf = new SequenceFeature(attName, attValue, start,
+                end, score, featureGroup);
+        // note: repeating the action shouldn't duplicate features
+        featureAdded |= seq.addSequenceFeature(sf);
       }
     }
-
-    /*
-     * traverse values and sequences
-     */
-    for (String val : attsMap.keySet())
+    if (featureAdded)
     {
-      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);
-        }
-      }
+      fr.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
    * 
@@ -1309,6 +1334,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel
   protected String getViewerFeatureGroup()
   {
     // todo pull up to interface
-    return "Chimera";
+    return CHIMERA_FEATURE_GROUP;
   }
 }
index cad2303..c8708bd 100644 (file)
@@ -805,6 +805,27 @@ public class StructureSelectionManager
       return;
     }
 
+    SearchResults results = findAlignmentPositionsForStructurePositions(atoms);
+    for (Object li : listeners)
+    {
+      if (li instanceof SequenceListener)
+      {
+        ((SequenceListener) li).highlightSequence(results);
+      }
+    }
+  }
+
+  /**
+   * Constructs a SearchResults object holding regions (if any) in the Jalview
+   * alignment which have a mapping to the structure viewer positions in the
+   * supplied list
+   * 
+   * @param atoms
+   * @return
+   */
+  public SearchResults findAlignmentPositionsForStructurePositions(
+          List<AtomSpec> atoms)
+  {
     SearchResults results = new SearchResults();
     for (AtomSpec atom : atoms)
     {
@@ -830,13 +851,7 @@ public class StructureSelectionManager
         }
       }
     }
-    for (Object li : listeners)
-    {
-      if (li instanceof SequenceListener)
-      {
-        ((SequenceListener) li).highlightSequence(results);
-      }
-    }
+    return results;
   }
 
   /**