From 0d1529f5a1f02b9cb959f0fe0d3e7f468723b83a Mon Sep 17 00:00:00 2001 From: gmungoc Date: Fri, 14 Jun 2019 11:54:50 +0100 Subject: [PATCH] JAL-3302 transfer visible 'linked' features to Chimera --- src/jalview/api/FeatureRenderer.java | 14 ++ src/jalview/ext/rbvi/chimera/ChimeraCommands.java | 165 ++++++++++++++++---- .../seqfeatures/FeatureRendererModel.java | 12 +- .../ext/rbvi/chimera/ChimeraCommandsTest.java | 32 ++-- 4 files changed, 168 insertions(+), 55 deletions(-) diff --git a/src/jalview/api/FeatureRenderer.java b/src/jalview/api/FeatureRenderer.java index a9bbe31..8aa2858 100644 --- a/src/jalview/api/FeatureRenderer.java +++ b/src/jalview/api/FeatureRenderer.java @@ -20,6 +20,7 @@ */ package jalview.api; +import jalview.datamodel.MappedFeatures; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.datamodel.features.FeatureMatcherSetI; @@ -283,4 +284,17 @@ public interface FeatureRenderer * @return */ boolean isVisible(SequenceFeature feature); + + /** + * Answers a bean containing a mapping, and a list of visible features in this + * alignment at a position (or range) which is mappable from the given sequence + * residue position in a mapped alignment. Features are returned in render order + * of feature type (on top last), with order within feature type undefined. If + * no features or mapping are found, answers null. + * + * @param sequence + * @param pos + * @return + */ + MappedFeatures findComplementFeaturesAtResidue(SequenceI sequence, int pos); } diff --git a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java index dad8511..3caaac3 100644 --- a/src/jalview/ext/rbvi/chimera/ChimeraCommands.java +++ b/src/jalview/ext/rbvi/chimera/ChimeraCommands.java @@ -26,8 +26,10 @@ import jalview.api.FeatureRenderer; import jalview.api.SequenceRenderer; import jalview.datamodel.AlignmentI; import jalview.datamodel.HiddenColumns; +import jalview.datamodel.MappedFeatures; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; +import jalview.gui.Desktop; import jalview.renderer.seqfeatures.FeatureColourFinder; import jalview.structure.StructureMapping; import jalview.structure.StructureMappingcommandSet; @@ -104,7 +106,7 @@ public class ChimeraCommands * delimited). If length limit issues arise, refactor to return one color * command per colour. */ - List commands = new ArrayList(); + List commands = new ArrayList<>(); StringBuilder sb = new StringBuilder(256); boolean firstColour = true; for (Object key : colourMap.keySet()) @@ -196,7 +198,7 @@ public class ChimeraCommands AlignViewportI viewport = viewPanel.getAlignViewport(); HiddenColumns cs = viewport.getAlignment().getHiddenColumns(); AlignmentI al = viewport.getAlignment(); - Map colourMap = new LinkedHashMap(); + Map colourMap = new LinkedHashMap<>(); Color lastColour = null; for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++) @@ -257,7 +259,7 @@ public class ChimeraCommands { if (startPos != -1) { - addColourRange(colourMap, lastColour, pdbfnum, startPos, + addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos, lastPos, lastChain); } startPos = pos; @@ -269,7 +271,7 @@ public class ChimeraCommands // final colour range if (lastColour != null) { - addColourRange(colourMap, lastColour, pdbfnum, startPos, + addAtomSpecRange(colourMap, lastColour, pdbfnum, startPos, lastPos, lastChain); } // break; @@ -281,26 +283,32 @@ public class ChimeraCommands } /** - * Helper method to add one contiguous colour range to the colour map. + * Helper method to add one contiguous range to the AtomSpec model for the given + * value (creating the model if necessary). As used by Jalview, {@code value} is + *
    + *
  • a colour, when building a 'colour structure by sequence' command
  • + *
  • a feature value, when building a 'set Chimera attributes from features' + * command
  • + *
* * @param map - * @param key + * @param value * @param model * @param startPos * @param endPos * @param chain */ - protected static void addColourRange(Map map, - Object key, int model, int startPos, int endPos, String chain) + protected static void addAtomSpecRange(Map map, + Object value, int model, int startPos, int endPos, String chain) { /* * Get/initialize map of data for the colour */ - AtomSpecModel atomSpec = map.get(key); + AtomSpecModel atomSpec = map.get(value); if (atomSpec == null) { atomSpec = new AtomSpecModel(); - map.put(key, atomSpec); + map.put(value, atomSpec); } atomSpec.addRange(model, startPos, endPos, chain); @@ -349,7 +357,7 @@ public class ChimeraCommands StructureSelectionManager ssm, String[] files, SequenceI[][] seqs, AlignmentViewPanel viewPanel) { - Map> theMap = new LinkedHashMap>(); + Map> theMap = new LinkedHashMap<>(); FeatureRenderer fr = viewPanel.getFeatureRenderer(); if (fr == null) @@ -357,8 +365,27 @@ public class ChimeraCommands return theMap; } + AlignViewportI viewport = viewPanel.getAlignViewport(); List visibleFeatures = fr.getDisplayedFeatureTypes(); - if (visibleFeatures.isEmpty()) + + /* + * if alignment is showing features from complement, we also transfer + * these features to the corresponding mapped structure residues + */ + boolean showLinkedFeatures = viewport.isShowComplementFeatures(); + List complementFeatures = new ArrayList<>(); + FeatureRenderer complementRenderer = null; + if (showLinkedFeatures) + { + AlignViewportI comp = fr.getViewport().getCodingComplement(); + if (comp != null) + { + complementRenderer = Desktop.getAlignFrameFor(comp) + .getFeatureRenderer(); + complementFeatures = complementRenderer.getDisplayedFeatureTypes(); + } + } + if (visibleFeatures.isEmpty() && complementFeatures.isEmpty()) { return theMap; } @@ -379,16 +406,23 @@ public class ChimeraCommands { final SequenceI seq = seqs[pdbfnum][seqNo]; int sp = alignment.findIndex(seq); - if (mapping[m].getSequence() == seq && sp > -1) + StructureMapping structureMapping = mapping[m]; + if (structureMapping.getSequence() == seq && sp > -1) { /* * found a sequence with a mapping to a structure; * now scan its features */ - SequenceI asp = alignment.getSequenceAt(sp); - - scanSequenceFeatures(visibleFeatures, mapping[m], asp, theMap, - pdbfnum); + if (!visibleFeatures.isEmpty()) + { + scanSequenceFeatures(visibleFeatures, structureMapping, seq, + theMap, pdbfnum); + } + if (showLinkedFeatures) + { + scanComplementFeatures(complementRenderer, structureMapping, + seq, theMap, pdbfnum); + } } } } @@ -397,9 +431,85 @@ public class ChimeraCommands } /** - * Inspect features on the sequence; for each feature that is visible, - * determine its mapped ranges in the structure (if any) according to the - * given mapping, and add them to the map + * Scans visible features in mapped positions of the CDS/peptide complement, and + * adds any found to the map of attribute values/structure positions + * + * @param complementRenderer + * @param structureMapping + * @param seq + * @param theMap + * @param modelNumber + */ + protected static void scanComplementFeatures( + FeatureRenderer complementRenderer, + StructureMapping structureMapping, SequenceI seq, + Map> theMap, int modelNumber) + { + /* + * for each sequence residue mapped to a structure position... + */ + for (int seqPos : structureMapping.getMapping().keySet()) + { + /* + * find visible complementary features at mapped position(s) + */ + MappedFeatures mf = complementRenderer + .findComplementFeaturesAtResidue(seq, seqPos); + if (mf != null) + { + for (SequenceFeature sf : mf.features) + { + String type = sf.getType(); + + /* + * Don't copy features which originated from Chimera + */ + if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP + .equals(sf.getFeatureGroup())) + { + continue; + } + + /* + * record feature 'value' (score/description/type) as at the + * corresponding structure position + */ + List mappedRanges = structureMapping + .getPDBResNumRanges(seqPos, seqPos); + + if (!mappedRanges.isEmpty()) + { + String value = sf.getDescription(); + if (value == null || value.length() == 0) + { + value = type; + } + float score = sf.getScore(); + if (score != 0f && !Float.isNaN(score)) + { + value = Float.toString(score); + } + Map featureValues = theMap.get(type); + if (featureValues == null) + { + featureValues = new HashMap<>(); + theMap.put(type, featureValues); + } + for (int[] range : mappedRanges) + { + addAtomSpecRange(featureValues, value, modelNumber, range[0], + range[1], structureMapping.getChain()); + } + } + } + } + } + } + + /** + * Inspect features on the sequence; for each feature that is visible, determine + * its mapped ranges in the structure (if any) according to the given mapping, + * and add them to the map. * * @param visibleFeatures * @param mapping @@ -418,15 +528,14 @@ public class ChimeraCommands String type = sf.getType(); /* - * Only copy visible features, don't copy any which originated - * from Chimera, and suppress uninteresting ones (e.g. RESNUM) + * Don't copy features which originated from Chimera */ - boolean isFromViewer = JalviewChimeraBinding.CHIMERA_FEATURE_GROUP - .equals(sf.getFeatureGroup()); - if (isFromViewer) + if (JalviewChimeraBinding.CHIMERA_FEATURE_GROUP + .equals(sf.getFeatureGroup())) { continue; } + List mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(), sf.getEnd()); @@ -445,12 +554,12 @@ public class ChimeraCommands Map featureValues = theMap.get(type); if (featureValues == null) { - featureValues = new HashMap(); + featureValues = new HashMap<>(); theMap.put(type, featureValues); } for (int[] range : mappedRanges) { - addColourRange(featureValues, value, modelNumber, range[0], + addAtomSpecRange(featureValues, value, modelNumber, range[0], range[1], mapping.getChain()); } } @@ -475,7 +584,7 @@ public class ChimeraCommands protected static List buildSetAttributeCommands( Map> featureMap) { - List commands = new ArrayList(); + List commands = new ArrayList<>(); for (String featureType : featureMap.keySet()) { String attributeName = makeAttributeName(featureType); diff --git a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java index 838c41a..f9c9782 100644 --- a/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java +++ b/src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java @@ -1161,17 +1161,7 @@ public abstract class FeatureRendererModel return filter == null ? true : filter.matches(sf); } - /** - * Answers a bean containing a mapping, and a list of features in this - * alignment at a position (or range) which is mappable from the given - * sequence residue position in a mapped alignment. Features are returned in - * render order of feature type (on top last), with order within feature type - * undefined. If no features or mapping are found, answers null. - * - * @param sequence - * @param pos - * @return - */ + @Override public MappedFeatures findComplementFeaturesAtResidue(SequenceI sequence, int pos) { diff --git a/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java b/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java index 2c973ca..06a09df 100644 --- a/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java +++ b/test/jalview/ext/rbvi/chimera/ChimeraCommandsTest.java @@ -60,15 +60,15 @@ public class ChimeraCommandsTest { Map map = new LinkedHashMap(); - ChimeraCommands.addColourRange(map, Color.blue, 0, 2, 5, "A"); - ChimeraCommands.addColourRange(map, Color.blue, 0, 7, 7, "B"); - ChimeraCommands.addColourRange(map, Color.blue, 0, 9, 23, "A"); - ChimeraCommands.addColourRange(map, Color.blue, 1, 1, 1, "A"); - ChimeraCommands.addColourRange(map, Color.blue, 1, 4, 7, "B"); - ChimeraCommands.addColourRange(map, Color.yellow, 1, 8, 8, "A"); - ChimeraCommands.addColourRange(map, Color.yellow, 1, 3, 5, "A"); - ChimeraCommands.addColourRange(map, Color.red, 0, 3, 5, "A"); - ChimeraCommands.addColourRange(map, Color.red, 0, 6, 9, "A"); + ChimeraCommands.addAtomSpecRange(map, Color.blue, 0, 2, 5, "A"); + ChimeraCommands.addAtomSpecRange(map, Color.blue, 0, 7, 7, "B"); + ChimeraCommands.addAtomSpecRange(map, Color.blue, 0, 9, 23, "A"); + ChimeraCommands.addAtomSpecRange(map, Color.blue, 1, 1, 1, "A"); + ChimeraCommands.addAtomSpecRange(map, Color.blue, 1, 4, 7, "B"); + ChimeraCommands.addAtomSpecRange(map, Color.yellow, 1, 8, 8, "A"); + ChimeraCommands.addAtomSpecRange(map, Color.yellow, 1, 3, 5, "A"); + ChimeraCommands.addAtomSpecRange(map, Color.red, 0, 3, 5, "A"); + ChimeraCommands.addAtomSpecRange(map, Color.red, 0, 6, 9, "A"); // Colours should appear in the Chimera command in the order in which // they were added; within colour, by model, by chain, ranges in start order @@ -91,7 +91,7 @@ public class ChimeraCommandsTest * start with just one feature/value... */ featuresMap.put("chain", featureValues); - ChimeraCommands.addColourRange(featureValues, "X", 0, 8, 20, "A"); + ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 8, 20, "A"); List commands = ChimeraCommands .buildSetAttributeCommands(featuresMap); @@ -104,24 +104,24 @@ public class ChimeraCommandsTest assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:8-20.A"); // add same feature value, overlapping range - ChimeraCommands.addColourRange(featureValues, "X", 0, 3, 9, "A"); + ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 3, 9, "A"); // same feature value, contiguous range - ChimeraCommands.addColourRange(featureValues, "X", 0, 21, 25, "A"); + ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 21, 25, "A"); commands = ChimeraCommands.buildSetAttributeCommands(featuresMap); assertEquals(1, commands.size()); assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:3-25.A"); // same feature value and model, different chain - ChimeraCommands.addColourRange(featureValues, "X", 0, 21, 25, "B"); + ChimeraCommands.addAtomSpecRange(featureValues, "X", 0, 21, 25, "B"); // same feature value and chain, different model - ChimeraCommands.addColourRange(featureValues, "X", 1, 26, 30, "A"); + ChimeraCommands.addAtomSpecRange(featureValues, "X", 1, 26, 30, "A"); commands = ChimeraCommands.buildSetAttributeCommands(featuresMap); assertEquals(1, commands.size()); assertEquals(commands.get(0), "setattr r jv_chain 'X' #0:3-25.A,21-25.B|#1:26-30.A"); // same feature, different value - ChimeraCommands.addColourRange(featureValues, "Y", 0, 40, 50, "A"); + ChimeraCommands.addAtomSpecRange(featureValues, "Y", 0, 40, 50, "A"); commands = ChimeraCommands.buildSetAttributeCommands(featuresMap); assertEquals(2, commands.size()); // commands are ordered by feature type but not by value @@ -133,7 +133,7 @@ public class ChimeraCommandsTest featuresMap.clear(); featureValues.clear(); featuresMap.put("side-chain binding!", featureValues); - ChimeraCommands.addColourRange(featureValues, + ChimeraCommands.addAtomSpecRange(featureValues, "metal 'ion!", 0, 7, 15, "A"); // feature names are sanitised to change non-alphanumeric to underscore -- 1.7.10.2