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;
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;
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";
*/
public String getViewerTitle(boolean verbose)
{
- return getViewerTitle("Chimera", verbose);
+ return getViewerTitle(CHIMERA_FEATURE_GROUP, verbose);
}
/**
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)
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);
/*
* 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
*
protected String getViewerFeatureGroup()
{
// todo pull up to interface
- return "Chimera";
+ return CHIMERA_FEATURE_GROUP;
}
}