import jalview.api.AlignViewportI;
import jalview.api.AlignmentViewPanel;
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.renderer.seqfeatures.FeatureColourFinder;
+import jalview.gui.Desktop;
import jalview.structure.StructureMapping;
import jalview.structure.StructureMappingcommandSet;
import jalview.structure.StructureSelectionManager;
import jalview.structures.models.AAStructureBindingModel;
import jalview.util.ColorUtils;
-import jalview.util.Comparison;
-import jalview.util.IntRangeComparator;
+import jalview.util.StructureCommands;
import java.awt.Color;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
* @author JimP
*
*/
-public class ChimeraCommands
+public class ChimeraCommands extends StructureCommands
{
public static final String NAMESPACE_PREFIX = "jv_";
}
/**
- * Build a data structure which records contiguous subsequences for each colour.
- * From this we can easily generate the Chimera command for colour by sequence.
- *
- * <pre>
- * Color
- * Model number
- * Chain
- * list of start/end ranges
- * </pre>
- *
- * Ordering is by order of addition (for colours and positions), natural
- * ordering (for models and chains)
- *
- * @param ssm
- * @param files
- * @param sequence
- * @param sr
- * @param hideHiddenRegions
- * @param viewPanel
- * @return
- */
- protected static Map<Object, AtomSpecModel> buildColoursMap(
- StructureSelectionManager ssm, String[] files,
- SequenceI[][] sequence, SequenceRenderer sr,
- boolean hideHiddenRegions, AlignmentViewPanel viewPanel)
- {
- FeatureRenderer fr = viewPanel.getFeatureRenderer();
- FeatureColourFinder finder = new FeatureColourFinder(fr);
- AlignViewportI viewport = viewPanel.getAlignViewport();
- HiddenColumns cs = viewport.getAlignment().getHiddenColumns();
- AlignmentI al = viewport.getAlignment();
- Map<Object, AtomSpecModel> colourMap = new LinkedHashMap<>();
- Color lastColour = null;
-
- for (int pdbfnum = 0; pdbfnum < files.length; pdbfnum++)
- {
- StructureMapping[] mapping = ssm.getMapping(files[pdbfnum]);
-
- if (mapping == null || mapping.length < 1)
- {
- continue;
- }
-
- int startPos = -1, lastPos = -1;
- String lastChain = "";
- for (int s = 0; s < sequence[pdbfnum].length; s++)
- {
- for (int sp, m = 0; m < mapping.length; m++)
- {
- final SequenceI seq = sequence[pdbfnum][s];
- if (mapping[m].getSequence() == seq
- && (sp = al.findIndex(seq)) > -1)
- {
- SequenceI asp = al.getSequenceAt(sp);
- for (int r = 0; r < asp.getLength(); r++)
- {
- // no mapping to gaps in sequence
- if (Comparison.isGap(asp.getCharAt(r)))
- {
- continue;
- }
- int pos = mapping[m].getPDBResNum(asp.findPosition(r));
-
- if (pos < 1 || pos == lastPos)
- {
- continue;
- }
-
- Color colour = sr.getResidueColour(seq, r, finder);
-
- /*
- * hidden regions are shown gray or, optionally, ignored
- */
- if (!cs.isVisible(r))
- {
- if (hideHiddenRegions)
- {
- continue;
- }
- else
- {
- colour = Color.GRAY;
- }
- }
-
- final String chain = mapping[m].getChain();
-
- /*
- * Just keep incrementing the end position for this colour range
- * _unless_ colour, PDB model or chain has changed, or there is a
- * gap in the mapped residue sequence
- */
- final boolean newColour = !colour.equals(lastColour);
- final boolean nonContig = lastPos + 1 != pos;
- final boolean newChain = !chain.equals(lastChain);
- if (newColour || nonContig || newChain)
- {
- if (startPos != -1)
- {
- addMapRange(colourMap, lastColour, pdbfnum, startPos,
- lastPos, lastChain);
- }
- startPos = pos;
- }
- lastColour = colour;
- lastPos = pos;
- lastChain = chain;
- }
- // final colour range
- if (lastColour != null)
- {
- addMapRange(colourMap, lastColour, pdbfnum, startPos,
- lastPos, lastChain);
- }
- // break;
- }
- }
- }
- }
- return colourMap;
- }
-
- /**
- * Helper method to add one contiguous range to the map, for a given value key
- * (e.g. colour or feature type), structure model number, and chain
- *
- * @param map
- * @param key
- * @param model
- * @param startPos
- * @param endPos
- * @param chain
- */
- public static void addMapRange(Map<Object, AtomSpecModel> map,
- Object key, int model, int startPos, int endPos, String chain)
- {
- /*
- * Get/initialize map of data for the colour
- */
- AtomSpecModel atomSpec = map.get(key);
- if (atomSpec == null)
- {
- atomSpec = new AtomSpecModel();
- map.put(key, atomSpec);
- }
-
- atomSpec.addRange(model, startPos, endPos, chain);
- }
-
- /**
* Constructs and returns Chimera commands to set attributes on residues
* corresponding to features in Jalview. Attribute names are the Jalview feature
* type, with a "jv_" prefix.
return theMap;
}
+ AlignViewportI viewport = viewPanel.getAlignViewport();
List<String> 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<String> 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;
}
{
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);
+ }
}
}
}
}
/**
- * 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<String, Map<Object, AtomSpecModel>> 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<int[]> 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<Object, AtomSpecModel> 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
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<int[]> mappedRanges = mapping.getPDBResNumRanges(sf.getBegin(),
sf.getEnd());
}
for (int[] range : mappedRanges)
{
- addMapRange(featureValues, value, modelNumber, range[0],
+ addAtomSpecRange(featureValues, value, modelNumber, range[0],
range[1], mapping.getChain());
}
}
sb.append("|");
}
firstModel = false;
- // todo use JalviewChimeraBinding.getModelSpec(model)
- // which means this cannot be static
sb.append(binding.getModelSpec(model)).append(":");
boolean firstPositionForModel = true;
List<int[]> rangeList = atomSpec.getRanges(model, chain);
- /*
- * sort ranges into ascending start position order
- */
- Collections.sort(rangeList, IntRangeComparator.ASCENDING);
-
- int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
- int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
-
- Iterator<int[]> iterator = rangeList.iterator();
- while (iterator.hasNext())
- {
- int[] range = iterator.next();
- if (range[0] <= end + 1)
- {
- /*
- * range overlaps or is contiguous with the last one
- * - so just extend the end position, and carry on
- * (unless this is the last in the list)
- */
- end = Math.max(end, range[1]);
- }
- else
- {
- /*
- * we have a break so append the last range
- */
- appendRange(sb, start, end, chain, firstPositionForModel);
- firstPositionForModel = false;
- start = range[0];
- end = range[1];
- }
- }
-
- /*
- * and append the last range
- */
- if (!rangeList.isEmpty())
- {
- appendRange(sb, start, end, chain, firstPositionForModel);
- firstPositionForModel = false;
- }
+ String chainToken = " ".equals(chain) ? "." : "." + chain;
+ appendResidueRange(sb, rangeList, chainToken,
+ firstPositionForModel);
+ firstPositionForModel = false;
}
}
return sb.toString();
}
/**
- * A helper method that appends one start-end range to a Chimera atomspec
+ * Chimera atomspec requires chain to be specified for each start-end residue
+ * range, otherwise it will apply to all chains
*
* @param sb
- * @param start
- * @param end
* @param chain
- * @param firstPositionForModel
*/
- static void appendRange(StringBuilder sb, int start, int end,
- String chain, boolean firstPositionForModel)
+ protected static void appendChainToRange(StringBuilder sb, String chain)
{
- if (!firstPositionForModel)
- {
- sb.append(",");
- }
- if (end == start)
- {
- sb.append(start);
- }
- else
- {
- sb.append(start).append("-").append(end);
- }
-
sb.append(".");
if (!" ".equals(chain))
{