X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fstructures%2Fmodels%2FAAStructureBindingModel.java;h=5a7b4484cd728b78f51d21b0a6ef9dd7e735dcb0;hb=bed0c230be450b16213e30ee1dcaf3e76ddfc669;hp=3c4041413282c840e9631e2262e086c21321cbd1;hpb=4d7f98a6dd54d9863ba449ec79dcd95d25ed863d;p=jalview.git
diff --git a/src/jalview/structures/models/AAStructureBindingModel.java b/src/jalview/structures/models/AAStructureBindingModel.java
index 3c40414..5a7b448 100644
--- a/src/jalview/structures/models/AAStructureBindingModel.java
+++ b/src/jalview/structures/models/AAStructureBindingModel.java
@@ -1,20 +1,70 @@
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ * PURPOSE. See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview. If not, see .
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
package jalview.structures.models;
+import java.awt.Color;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.SwingUtilities;
+
+import jalview.api.AlignViewportI;
+import jalview.api.AlignmentViewPanel;
+import jalview.api.FeatureRenderer;
+import jalview.api.SequenceRenderer;
import jalview.api.StructureSelectionManagerProvider;
+import jalview.api.structures.JalviewStructureDisplayI;
+import jalview.bin.Cache;
import jalview.datamodel.AlignmentI;
+import jalview.datamodel.HiddenColumns;
+import jalview.datamodel.MappedFeatures;
import jalview.datamodel.PDBEntry;
+import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
+import jalview.ext.rbvi.chimera.JalviewChimeraBinding;
+import jalview.gui.AlignmentPanel;
+import jalview.gui.Desktop;
+import jalview.gui.StructureViewer.ViewerType;
+import jalview.io.DataSourceType;
+import jalview.io.StructureFile;
+import jalview.renderer.seqfeatures.FeatureColourFinder;
+import jalview.schemes.ColourSchemeI;
+import jalview.schemes.ResidueProperties;
import jalview.structure.AtomSpec;
+import jalview.structure.AtomSpecModel;
+import jalview.structure.StructureCommandI;
+import jalview.structure.StructureCommandsI;
import jalview.structure.StructureListener;
import jalview.structure.StructureMapping;
import jalview.structure.StructureSelectionManager;
import jalview.util.Comparison;
import jalview.util.MessageManager;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
/**
*
* A base class to hold common function for protein structure model binding.
@@ -24,13 +74,78 @@ import java.util.List;
* @author gmcarstairs
*
*/
-public abstract class AAStructureBindingModel extends
- SequenceStructureBindingModel implements StructureListener,
- StructureSelectionManagerProvider
+public abstract class AAStructureBindingModel
+ extends SequenceStructureBindingModel
+ implements StructureListener, StructureSelectionManagerProvider
{
+ /**
+ * Data bean class to simplify parameterisation in superposeStructures
+ */
+ public static class SuperposeData
+ {
+ public String filename;
+
+ public String pdbId;
+
+ public String chain = "";
+
+ public boolean isRna;
+
+ /*
+ * The pdb residue number (if any) mapped to columns of the alignment
+ */
+ public int[] pdbResNo; // or use SparseIntArray?
+
+ public String modelId;
+
+ /**
+ * Constructor
+ *
+ * @param width
+ * width of alignment (number of columns that may potentially
+ * participate in superposition)
+ * @param model
+ * structure viewer model number
+ */
+ public SuperposeData(int width, String model)
+ {
+ pdbResNo = new int[width];
+ modelId = model;
+ }
+ }
+
+ private static final int MIN_POS_TO_SUPERPOSE = 4;
+
+ private static final String COLOURING_STRUCTURES = MessageManager
+ .getString("status.colouring_structures");
+
+ /*
+ * the Jalview panel through which the user interacts
+ * with the structure viewer
+ */
+ private JalviewStructureDisplayI viewer;
+
+ /*
+ * helper that generates command syntax
+ */
+ private StructureCommandsI commandGenerator;
private StructureSelectionManager ssm;
+ /*
+ * modelled chains, formatted as "pdbid:chainCode"
+ */
+ private List chainNames;
+
+ /*
+ * lookup of pdb file name by key "pdbid:chainCode"
+ */
+ private Map chainFile;
+
+ /*
+ * distinct PDB entries (pdb files) associated
+ * with sequences
+ */
private PDBEntry[] pdbEntry;
/*
@@ -46,7 +161,7 @@ public abstract class AAStructureBindingModel extends
/*
* datasource protocol for access to PDBEntrylatest
*/
- String protocol = null;
+ DataSourceType protocol = null;
protected boolean colourBySequence = true;
@@ -55,33 +170,13 @@ public abstract class AAStructureBindingModel extends
private boolean finishedInit = false;
/**
- * Data bean class to simplify parameterisation in superposeStructures
+ * current set of model filenames loaded in the viewer
*/
- protected class SuperposeData
- {
- /**
- * Constructor with alignment width argument
- *
- * @param width
- */
- public SuperposeData(int width)
- {
- pdbResNo = new int[width];
- }
-
- public String filename;
+ protected String[] modelFileNames = null;
- public String pdbId;
-
- public String chain = "";
-
- public boolean isRna;
+ public String fileLoadingError;
- /*
- * The pdb residue number (if any) mapped to each column of the alignment
- */
- public int[] pdbResNo;
- }
+ protected Thread externalViewerMonitor;
/**
* Constructor
@@ -94,6 +189,8 @@ public abstract class AAStructureBindingModel extends
{
this.ssm = ssm;
this.sequence = seqs;
+ chainNames = new ArrayList<>();
+ chainFile = new HashMap<>();
}
/**
@@ -102,23 +199,69 @@ public abstract class AAStructureBindingModel extends
* @param ssm
* @param pdbentry
* @param sequenceIs
- * @param chains
* @param protocol
*/
public AAStructureBindingModel(StructureSelectionManager ssm,
- PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains,
- String protocol)
+ PDBEntry[] pdbentry, SequenceI[][] sequenceIs,
+ DataSourceType protocol)
{
- this.ssm = ssm;
- this.sequence = sequenceIs;
+ this(ssm, sequenceIs);
this.nucleotide = Comparison.isNucleotide(sequenceIs);
- this.chains = chains;
this.pdbEntry = pdbentry;
this.protocol = protocol;
- if (chains == null)
+ resolveChains();
+ }
+
+ private boolean resolveChains()
+ {
+ /**
+ * final count of chain mappings discovered
+ */
+ int chainmaps = 0;
+ // JBPNote: JAL-2693 - this should be a list of chain mappings per
+ // [pdbentry][sequence]
+ String[][] newchains = new String[pdbEntry.length][];
+ int pe = 0;
+ for (PDBEntry pdb : pdbEntry)
{
- this.chains = new String[pdbentry.length][];
+ SequenceI[] seqsForPdb = sequence[pe];
+ if (seqsForPdb != null)
+ {
+ newchains[pe] = new String[seqsForPdb.length];
+ int se = 0;
+ for (SequenceI asq : seqsForPdb)
+ {
+ String chain = (chains != null && chains[pe] != null)
+ ? chains[pe][se]
+ : null;
+ SequenceI sq = (asq.getDatasetSequence() == null) ? asq
+ : asq.getDatasetSequence();
+ if (sq.getAllPDBEntries() != null)
+ {
+ for (PDBEntry pdbentry : sq.getAllPDBEntries())
+ {
+ if (pdb.getFile() != null && pdbentry.getFile() != null
+ && pdb.getFile().equals(pdbentry.getFile()))
+ {
+ String chaincode = pdbentry.getChainCode();
+ if (chaincode != null && chaincode.length() > 0)
+ {
+ chain = chaincode;
+ chainmaps++;
+ break;
+ }
+ }
+ }
+ }
+ newchains[pe][se] = chain;
+ se++;
+ }
+ pe++;
+ }
}
+
+ chains = newchains;
+ return chainmaps > 0;
}
public StructureSelectionManager getSsm()
@@ -178,7 +321,7 @@ public abstract class AAStructureBindingModel extends
return chains;
}
- public String getProtocol()
+ public DataSourceType getProtocol()
{
return protocol;
}
@@ -219,24 +362,21 @@ public abstract class AAStructureBindingModel extends
// TODO: give a more informative title when multiple structures are
// displayed.
StringBuilder title = new StringBuilder(64);
- final PDBEntry pdbEntry = getPdbEntry(0);
+ final PDBEntry pdbe = getPdbEntry(0);
title.append(viewerName + " view for " + getSequence()[0][0].getName()
- + ":" + pdbEntry.getId());
+ + ":" + pdbe.getId());
if (verbose)
{
- if (pdbEntry.getProperty() != null)
+ String method = (String) pdbe.getProperty("method");
+ if (method != null)
{
- if (pdbEntry.getProperty().get("method") != null)
- {
- title.append(" Method: ");
- title.append(pdbEntry.getProperty().get("method"));
- }
- if (pdbEntry.getProperty().get("chains") != null)
- {
- title.append(" Chain:");
- title.append(pdbEntry.getProperty().get("chains"));
- }
+ title.append(" Method: ").append(method);
+ }
+ String chain = (String) pdbe.getProperty("chains");
+ if (chain != null)
+ {
+ title.append(" Chain:").append(chain);
}
}
return title.toString();
@@ -248,7 +388,11 @@ public abstract class AAStructureBindingModel extends
*/
protected void releaseUIResources()
{
+ }
+ @Override
+ public void releaseReferences(Object svl)
+ {
}
public boolean isColourBySequence()
@@ -256,6 +400,25 @@ public abstract class AAStructureBindingModel extends
return colourBySequence;
}
+ /**
+ * Called when the binding thinks the UI needs to be refreshed after a
+ * structure viewer state change. This could be because structures were
+ * loaded, or because an error has occurred. Default does nothing, override as
+ * required.
+ */
+ public void refreshGUI()
+ {
+ }
+
+ /**
+ * Instruct the Jalview binding to update the pdbentries vector if necessary
+ * prior to matching the jmol view's contents to the list of structure files
+ * Jalview knows about. By default does nothing, override as required.
+ */
+ public void refreshPdbEntries()
+ {
+ }
+
public void setColourBySequence(boolean colourBySequence)
{
this.colourBySequence = colourBySequence;
@@ -268,11 +431,12 @@ public abstract class AAStructureBindingModel extends
{
throw new Error(MessageManager.formatMessage(
"error.implementation_error_no_pdbentry_from_index",
- new Object[] { Integer.valueOf(pe).toString() }));
+ new Object[]
+ { Integer.valueOf(pe).toString() }));
}
final String nullChain = "TheNullChain";
- List s = new ArrayList();
- List c = new ArrayList();
+ List s = new ArrayList<>();
+ List c = new ArrayList<>();
if (getChains() == null)
{
setChains(new String[getPdbCount()][]);
@@ -341,8 +505,8 @@ public abstract class AAStructureBindingModel extends
public synchronized PDBEntry[] addSequenceAndChain(PDBEntry[] pdbe,
SequenceI[][] seq, String[][] chns)
{
- List v = new ArrayList();
- List rtn = new ArrayList();
+ List v = new ArrayList<>();
+ List rtn = new ArrayList<>();
for (int i = 0; i < getPdbCount(); i++)
{
v.add(getPdbEntry(i));
@@ -489,18 +653,23 @@ public abstract class AAStructureBindingModel extends
* the sequence alignment which is the basis of structure
* superposition
* @param matched
- * an array of booleans, indexed by alignment column, where true
- * indicates that every structure has a mapped residue present in the
- * column (so the column can participate in structure alignment)
+ * a BitSet, where bit j is set to indicate that every structure has
+ * a mapped residue present in column j (so the column can
+ * participate in structure alignment)
* @param structures
* an array of data beans corresponding to pdb file index
* @return
*/
protected int findSuperposableResidues(AlignmentI alignment,
- boolean[] matched, SuperposeData[] structures)
+ BitSet matched,
+ AAStructureBindingModel.SuperposeData[] structures)
{
int refStructure = -1;
- String[] files = getPdbFile();
+ String[] files = getStructureFiles();
+ if (files == null)
+ {
+ return -1;
+ }
for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
{
StructureMapping[] mappings = getSsm().getMapping(files[pdbfnum]);
@@ -523,16 +692,16 @@ public abstract class AAStructureBindingModel extends
{
refStructure = pdbfnum;
}
- for (int r = 0; r < matched.length; r++)
+ for (int r = 0; r < alignment.getWidth(); r++)
{
- if (!matched[r])
+ if (!matched.get(r))
{
continue;
}
int pos = getMappedPosition(theSequence, r, mapping);
if (pos < 1 || pos == lastPos)
{
- matched[r] = false;
+ matched.clear(r);
continue;
}
lastPos = pos;
@@ -545,9 +714,13 @@ public abstract class AAStructureBindingModel extends
}
structures[pdbfnum].pdbId = mapping.getPdbId();
structures[pdbfnum].isRna = theSequence.getRNA() != null;
- // move on to next pdb file
+
+ /*
+ * move on to next pdb file (ignore sequences for other chains
+ * for the same structure)
+ */
s = seqCountForPdbFile;
- break;
+ break; // fixme break out of two loops here!
}
}
}
@@ -578,6 +751,10 @@ public abstract class AAStructureBindingModel extends
for (String file : files)
{
notLoaded = file;
+ if (file == null)
+ {
+ continue;
+ }
try
{
StructureMapping[] sm = getSsm().getMapping(file);
@@ -594,8 +771,8 @@ public abstract class AAStructureBindingModel extends
if (waiting)
{
- System.err
- .println("Timed out waiting for structure viewer to load file "
+ System.err.println(
+ "Timed out waiting for structure viewer to load file "
+ notLoaded);
return false;
}
@@ -613,7 +790,8 @@ public abstract class AAStructureBindingModel extends
{
for (SequenceI s : seqs)
{
- if (s == seq)
+ if (s == seq || (s.getDatasetSequence() != null
+ && s.getDatasetSequence() == seq.getDatasetSequence()))
{
return true;
}
@@ -633,4 +811,1133 @@ public abstract class AAStructureBindingModel extends
{
this.finishedInit = fi;
}
-}
\ No newline at end of file
+
+ /**
+ * Returns a list of chains mapped in this viewer, formatted as
+ * "pdbid:chainCode"
+ *
+ * @return
+ */
+ public List getChainNames()
+ {
+ return chainNames;
+ }
+
+ /**
+ * Returns the Jalview panel hosting the structure viewer (if any)
+ *
+ * @return
+ */
+ public JalviewStructureDisplayI getViewer()
+ {
+ return viewer;
+ }
+
+ public void setViewer(JalviewStructureDisplayI v)
+ {
+ viewer = v;
+ }
+
+ /**
+ * Constructs and sends a command to align structures against a reference
+ * structure, based on one or more sequence alignments. May optionally return
+ * an error or warning message for the alignment command(s).
+ *
+ * @param alignWith
+ * an array of one or more alignment views to process
+ * @return
+ */
+ public String superposeStructures(List alignWith)
+ {
+ String error = "";
+ String[] files = getStructureFiles();
+
+ if (!waitForFileLoad(files))
+ {
+ return null;
+ }
+ refreshPdbEntries();
+
+ for (AlignmentViewPanel view : alignWith)
+ {
+ AlignmentI alignment = view.getAlignment();
+ HiddenColumns hiddenCols = alignment.getHiddenColumns();
+
+ /*
+ * 'matched' bit i will be set for visible alignment columns i where
+ * all sequences have a residue with a mapping to their PDB structure
+ */
+ BitSet matched = new BitSet();
+ final int width = alignment.getWidth();
+ for (int m = 0; m < width; m++)
+ {
+ if (hiddenCols == null || hiddenCols.isVisible(m))
+ {
+ matched.set(m);
+ }
+ }
+
+ AAStructureBindingModel.SuperposeData[] structures = new AAStructureBindingModel.SuperposeData[files.length];
+ for (int f = 0; f < files.length; f++)
+ {
+ structures[f] = new AAStructureBindingModel.SuperposeData(width,
+ getModelIdForFile(files[f]));
+ }
+
+ /*
+ * Calculate the superposable alignment columns ('matched'), and the
+ * corresponding structure residue positions (structures.pdbResNo)
+ */
+ int refStructure = findSuperposableResidues(alignment, matched,
+ structures);
+
+ /*
+ * require at least 4 positions to be able to execute superposition
+ */
+ int nmatched = matched.cardinality();
+ if (nmatched < MIN_POS_TO_SUPERPOSE)
+ {
+ String msg = MessageManager
+ .formatMessage("label.insufficient_residues", nmatched);
+ error += view.getViewName() + ": " + msg + "; ";
+ continue;
+ }
+
+ /*
+ * get a model of the superposable residues in the reference structure
+ */
+ AtomSpecModel refAtoms = getAtomSpec(structures[refStructure],
+ matched);
+
+ /*
+ * Show all as backbone before doing superposition(s)
+ * (residues used for matching will be shown as ribbon)
+ */
+ // todo better way to ensure synchronous than setting getReply true!!
+ executeCommands(commandGenerator.showBackbone(), true, null);
+
+ /*
+ * superpose each (other) structure to the reference in turn
+ */
+ for (int i = 0; i < structures.length; i++)
+ {
+ if (i != refStructure)
+ {
+ AtomSpecModel atomSpec = getAtomSpec(structures[i], matched);
+ List commands = commandGenerator
+ .superposeStructures(refAtoms, atomSpec);
+ List replies = executeCommands(commands, true, null);
+ for (String reply : replies)
+ {
+ // return this error (Chimera only) to the user
+ if (reply.toLowerCase().contains("unequal numbers of atoms"))
+ {
+ error += "; " + reply;
+ }
+ }
+ }
+ }
+ }
+
+ return error;
+ }
+
+ private AtomSpecModel getAtomSpec(
+ AAStructureBindingModel.SuperposeData superposeData,
+ BitSet matched)
+ {
+ AtomSpecModel model = new AtomSpecModel();
+ int nextColumnMatch = matched.nextSetBit(0);
+ while (nextColumnMatch != -1)
+ {
+ int pdbResNum = superposeData.pdbResNo[nextColumnMatch];
+ model.addRange(superposeData.modelId, pdbResNum, pdbResNum,
+ superposeData.chain);
+ nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1);
+ }
+
+ return model;
+ }
+
+ /**
+ * returns the current sequenceRenderer that should be used to colour the
+ * structures
+ *
+ * @param alignment
+ *
+ * @return
+ */
+ public abstract SequenceRenderer getSequenceRenderer(
+ AlignmentViewPanel alignment);
+
+ /**
+ * Sends a command to the structure viewer to colour each chain with a
+ * distinct colour (to the extent supported by the viewer)
+ */
+ public void colourByChain()
+ {
+ colourBySequence = false;
+
+ // TODO: JAL-628 colour chains distinctly across all visible models
+
+ executeCommand(false, COLOURING_STRUCTURES,
+ commandGenerator.colourByChain());
+ }
+
+ /**
+ * Sends a command to the structure viewer to colour each chain with a
+ * distinct colour (to the extent supported by the viewer)
+ */
+ public void colourByCharge()
+ {
+ colourBySequence = false;
+
+ executeCommands(commandGenerator.colourByCharge(), false,
+ COLOURING_STRUCTURES);
+ }
+
+ /**
+ * Sends a command to the structure to apply a colour scheme (defined in
+ * Jalview but not necessarily applied to the alignment), which defines a
+ * colour per residue letter. More complex schemes (e.g. that depend on
+ * consensus) cannot be used here and are ignored.
+ *
+ * @param cs
+ */
+ public void colourByJalviewColourScheme(ColourSchemeI cs)
+ {
+ colourBySequence = false;
+
+ if (cs == null || !cs.isSimple())
+ {
+ return;
+ }
+
+ /*
+ * build a map of {Residue3LetterCode, Color}
+ */
+ Map colours = new HashMap<>();
+ List residues = ResidueProperties.getResidues(isNucleotide(),
+ false);
+ for (String resName : residues)
+ {
+ char res = resName.length() == 3
+ ? ResidueProperties.getSingleCharacterCode(resName)
+ : resName.charAt(0);
+ Color colour = cs.findColour(res, 0, null, null, 0f);
+ colours.put(resName, colour);
+ }
+
+ /*
+ * pass to the command constructor, and send the command
+ */
+ List cmd = commandGenerator
+ .colourByResidues(colours);
+ executeCommands(cmd, false, COLOURING_STRUCTURES);
+ }
+
+ public void setBackgroundColour(Color col)
+ {
+ StructureCommandI cmd = commandGenerator.setBackgroundColour(col);
+ executeCommand(false, null, cmd);
+ }
+
+ /**
+ * Execute one structure viewer command. If {@code getReply} is true, may
+ * optionally return one or more reply messages, else returns null.
+ *
+ * @param cmd
+ * @param getReply
+ */
+ protected abstract List executeCommand(StructureCommandI cmd,
+ boolean getReply);
+
+ /**
+ * Executes one or more structure viewer commands
+ *
+ * @param commands
+ * @param getReply
+ * @param msg
+ */
+ protected List executeCommands(List commands,
+ boolean getReply, String msg)
+ {
+ return executeCommand(getReply, msg,
+ commands.toArray(new StructureCommandI[commands.size()]));
+ }
+
+ /**
+ * Executes one or more structure viewer commands, optionally returning the
+ * reply, and optionally showing a status message while the command is being
+ * executed.
+ *
+ * If a reply is wanted, the execution is done synchronously (waits),
+ * otherwise it is done in a separate thread (doesn't wait).
+ *
+ * @param getReply
+ * @param msg
+ * @param cmds
+ * @return
+ */
+ protected List executeCommand(boolean getReply, String msg,
+ StructureCommandI... cmds)
+ {
+ JalviewStructureDisplayI theViewer = getViewer();
+ final long handle = msg == null ? 0 : theViewer.startProgressBar(msg);
+
+ if (getReply)
+ {
+ /*
+ * execute and wait for reply
+ */
+ List response = new ArrayList<>();
+ try
+ {
+ for (StructureCommandI cmd : cmds)
+ {
+ List replies = executeCommand(cmd, true);
+ if (replies != null)
+ {
+ response.addAll(replies);
+ }
+ }
+ return response;
+ } finally
+ {
+ if (msg != null)
+ {
+ theViewer.stopProgressBar(null, handle);
+ }
+ }
+ }
+
+ /*
+ * fire and forget
+ */
+ String threadName = msg == null ? "StructureCommand" : msg;
+ new Thread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ try
+ {
+ for (StructureCommandI cmd : cmds)
+ {
+ executeCommand(cmd, false);
+ }
+ } finally
+ {
+ if (msg != null)
+ {
+ SwingUtilities.invokeLater(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ theViewer.stopProgressBar(null, handle);
+ }
+ });
+ }
+ }
+ }
+ }, threadName).start();
+ return null;
+ }
+
+ /**
+ * Colours any structures associated with sequences in the given alignment as
+ * coloured in the alignment view, provided colourBySequence is enabled
+ */
+ public void colourBySequence(AlignmentViewPanel alignmentv)
+ {
+ if (!colourBySequence || !isLoadingFinished() || getSsm() == null)
+ {
+ return;
+ }
+ Map