Merge branch 'develop' into features/JAL-2295setChimeraAttributes
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 23 Nov 2016 10:03:07 +0000 (10:03 +0000)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Wed, 23 Nov 2016 10:03:07 +0000 (10:03 +0000)
Conflicts:
src/jalview/structure/StructureSelectionManager.java

1  2 
resources/lang/Messages.properties
src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java
src/jalview/structure/StructureSelectionManager.java

@@@ -125,6 -125,8 +125,8 @@@ action.change_font_tree_panel = Change 
  action.colour = Colour
  action.calculate = Calculate
  action.select_all = Select all
+ action.select_highlighted_columns = Select Highlighted Columns
+ tooltip.select_highlighted_columns = Press B to mark highlighted columns, Ctrl-B to toggle, and Alt-B to mark all but highlighted columns 
  action.deselect_all = Deselect all
  action.invert_selection = Invert selection
  action.using_jmol = Using Jmol
@@@ -714,8 -716,6 +716,8 @@@ label.colour_with_chimera = Colour wit
  label.align_structures = Align Structures
  label.jmol = Jmol
  label.chimera = Chimera
 +label.create_chimera_attributes = Write Jalview features
 +label.create_chimera_attributes_tip = Set Chimera residue attributes for visible features
  label.sort_alignment_by_tree = Sort Alignment By Tree
  label.mark_unlinked_leaves = Mark Unlinked Leaves
  label.associate_leaves_with = Associate Leaves With
@@@ -1274,3 -1274,4 +1276,4 @@@ label.SEQUENCE_ID_no_longer_used = $SEQ
  label.SEQUENCE_ID_for_DB_ACCESSION1 = Please review your URL links in the 'Connections' tab of the Preferences window:
  label.SEQUENCE_ID_for_DB_ACCESSION2 = URL links using '$SEQUENCE_ID$' for DB accessions now use '$DB_ACCESSION$'.
  label.do_not_display_again = Do not display this message again
+ label.output_seq_details = Output Sequence Details to list all database references
@@@ -27,9 -27,6 +27,9 @@@ 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.SearchResults.Match;
 +import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
  import jalview.httpserver.AbstractRequestHandler;
  import jalview.schemes.ColourSchemeI;
@@@ -41,13 -38,8 +41,13 @@@ 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;
@@@ -60,8 -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";
 +
    // Chimera clause to exclude alternate locations in atom selection
    private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9";
  
@@@ -77,7 -67,6 +77,7 @@@
    private List<String> chainNames = new ArrayList<String>();
  
    private Hashtable<String, String> chainFile = new Hashtable<String, String>();
 +  
    /*
     * Object through which we talk to Chimera
     */
     */
    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;
  
    /*
     * @param ssm
     * @param pdbentry
     * @param sequenceIs
 -   * @param chains
     * @param protocol
     */
    public JalviewChimeraBinding(StructureSelectionManager ssm,
        chimeraListener.shutdown();
        chimeraListener = null;
      }
 -    lastCommand = null;
      viewer = null;
  
      releaseUIResources();
    }
  
    /**
 -   * Send a command to Chimera, and optionally log 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 logResponse
 +   * @param getResponse
     */
 -  public void sendChimeraCommand(final String command, boolean logResponse)
 +  public List<String> sendChimeraCommand(final String command,
 +          boolean getResponse)
    {
      if (viewer == null)
      {
        // ? thread running after viewer shut down
 -      return;
 +      return null;
      }
 +    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(),
 -              logResponse);
 -      if (logResponse && debug)
 +              getResponse);
 +      if (getResponse)
        {
 -        log("Response from command ('" + command + "') was:\n" + lastReply);
 +        reply = lastReply;
 +        if (debug)
 +        {
 +          log("Response from command ('" + command + "') was:\n"
 +                  + lastReply);
 +        }
        }
      }
      viewerCommandHistory(true);
 -    lastCommand = command;
 +
 +    return reply;
    }
  
    /**
      }
      AlignmentI alignment = alignmentv.getAlignment();
  
 -    for (jalview.structure.StructureMappingcommandSet cpdbbyseq : getColourBySequenceCommands(
 -            files, sr, fr, alignment))
 +    StructureMappingcommandSet colourBySequenceCommands = ChimeraCommands
 +            .getColourBySequenceCommand(getSsm(), files, getSequence(), sr,
 +                    fr, alignment);
 +    for (String command : colourBySequenceCommands.commands)
      {
 -      for (String command : cpdbbyseq.commands)
 -      {
 -        sendAsynchronousCommand(command, COLOURING_CHIMERA);
 -      }
 +      sendAsynchronousCommand(command, COLOURING_CHIMERA);
      }
    }
  
    /**
 -   * @param files
 -   * @param sr
 -   * @param fr
 -   * @param alignment
 -   * @return
 -   */
 -  protected StructureMappingcommandSet[] getColourBySequenceCommands(
 -          String[] files, SequenceRenderer sr, FeatureRenderer fr,
 -          AlignmentI alignment)
 -  {
 -    return ChimeraCommands.getColourBySequenceCommand(getSsm(), files,
 -            getSequence(), sr, fr, alignment);
 -  }
 -
 -  /**
     * @param command
     */
    protected void executeWhenReady(String command)
     */
    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
       * 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)
 -      {
 -        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[frameNo];
 -      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));
      }
 -
 -    /*
 -     * Broadcast the selection (which may be empty, if the user just cleared all
 -     * selections)
 -     */
 -    getSsm().mouseOverStructure(atomSpecs);
 +    return pdbfilename;
    }
  
    private void log(String message)
     * 
     * @return
     */
 +  @Override
 +  public List<String> getChainNames()
 +  {
 +    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);
 +
 +    boolean featureAdded = createFeaturesForAttributes(attName, residues);
 +    if (featureAdded)
 +    {
 +      alignmentPanel.getFeatureRenderer().featuresAdded();
 +    }
 +  }
 +
 +  /**
 +   * Create features in Jalview for the given attribute name and structure
 +   * residues.
 +   * 
 +   * <pre>
 +   * The residue list should be 0, 1 or more reply lines of the format: 
 +   *     residue id #0:5.A isHelix -155.000836316 index 5 
 +   * or 
 +   *     residue id #0:6.A isHelix None
 +   * </pre>
 +   * 
 +   * @param attName
 +   * @param residues
 +   * @return
 +   */
 +  protected boolean createFeaturesForAttributes(String attName,
 +          List<String> residues)
 +  {
 +    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);
 +
 +      /*
 +       * locate the mapped position in the alignment (if any)
 +       */
 +      SearchResults sr = getSsm()
 +              .findAlignmentPositionsForStructurePositions(atoms);
 +
 +      /*
 +       * expect one matched alignment position, or none 
 +       * (if the structure position is not mapped)
 +       */
-       for (Match m : sr.getResults())
++      for (SearchResultMatchI 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);
 +      }
 +    }
 +    return featureAdded;
 +  }
 +
 +  /**
 +   * 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;
@@@ -31,6 -31,7 +31,7 @@@ import jalview.datamodel.AlignmentI
  import jalview.datamodel.Annotation;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.SearchResults;
+ import jalview.datamodel.SearchResultsI;
  import jalview.datamodel.SequenceI;
  import jalview.ext.jmol.JmolParser;
  import jalview.gui.IProgressIndicator;
@@@ -805,28 -806,7 +806,28 @@@ public class StructureSelectionManage
        return;
      }
  
-     SearchResults results = findAlignmentPositionsForStructurePositions(atoms);
 -    SearchResultsI results = new SearchResults();
++    SearchResultsI 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)
      {
        SequenceI lastseq = null;
          }
        }
      }
 -    for (Object li : listeners)
 -    {
 -      if (li instanceof SequenceListener)
 -      {
 -        ((SequenceListener) li).highlightSequence(results);
 -      }
 -    }
 +    return results;
    }
  
    /**
    {
      boolean hasSequenceListeners = handlingVamsasMo
              || !seqmappings.isEmpty();
-     SearchResults results = null;
+     SearchResultsI results = null;
      if (seqPos == -1)
      {
        seqPos = seq.findPosition(indexpos);