From 8c9822da28cb7c8c7a232f96aa30a50b4d290e87 Mon Sep 17 00:00:00 2001 From: Alexander Rose <alex.rose@rcsb.org> Date: Fri, 3 May 2019 15:56:42 -0700 Subject: [PATCH] improved carbohydrate/anomeric carbon picking/loci --- .../structure/carbohydrates/compute.ts | 16 +++++++---- .../structure/structure/carbohydrates/data.ts | 4 +-- .../visual/carbohydrate-symbol-mesh.ts | 20 +++++++------- src/mol-repr/structure/visual/util/common.ts | 27 +++++++++++++++++++ src/mol-theme/color/carbohydrate-symbol.ts | 8 +++--- 5 files changed, 54 insertions(+), 21 deletions(-) diff --git a/src/mol-model/structure/structure/carbohydrates/compute.ts b/src/mol-model/structure/structure/carbohydrates/compute.ts index 8fc4688a5..342175438 100644 --- a/src/mol-model/structure/structure/carbohydrates/compute.ts +++ b/src/mol-model/structure/structure/carbohydrates/compute.ts @@ -421,16 +421,22 @@ function buildLookups (elements: CarbohydrateElement[], links: CarbohydrateLink[ return `${unit.id}|${residueIndex}` } - const anomericCarbonMap = new Map<string, ElementIndex>() + const anomericCarbonMap = new Map<string, ElementIndex[]>() for (let i = 0, il = elements.length; i < il; ++i) { const { unit, anomericCarbon } = elements[i] const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index[anomericCarbon] - anomericCarbonMap.set(anomericCarbonKey(unit, residueIndex), anomericCarbon) + const k = anomericCarbonKey(unit, residueIndex) + if (anomericCarbonMap.has(k)) { + anomericCarbonMap.get(k)!.push(anomericCarbon) + } else { + anomericCarbonMap.set(k, [anomericCarbon]) + } } - function getAnomericCarbon(unit: Unit, residueIndex: ResidueIndex) { - return anomericCarbonMap.get(anomericCarbonKey(unit, residueIndex)) + const EmptyArray: ReadonlyArray<any> = [] + function getAnomericCarbons(unit: Unit, residueIndex: ResidueIndex) { + return anomericCarbonMap.get(anomericCarbonKey(unit, residueIndex)) || EmptyArray } - return { getElementIndex, getLinkIndex, getLinkIndices, getTerminalLinkIndex, getTerminalLinkIndices, getAnomericCarbon } + return { getElementIndex, getLinkIndex, getLinkIndices, getTerminalLinkIndex, getTerminalLinkIndices, getAnomericCarbons } } \ No newline at end of file diff --git a/src/mol-model/structure/structure/carbohydrates/data.ts b/src/mol-model/structure/structure/carbohydrates/data.ts index 1eaae487c..a75337837 100644 --- a/src/mol-model/structure/structure/carbohydrates/data.ts +++ b/src/mol-model/structure/structure/carbohydrates/data.ts @@ -49,7 +49,7 @@ export interface Carbohydrates { getLinkIndices: (unit: Unit, anomericCarbon: ElementIndex) => ReadonlyArray<number> getTerminalLinkIndex: (unitA: Unit, elementA: ElementIndex, unitB: Unit, elementB: ElementIndex) => number | undefined getTerminalLinkIndices: (unit: Unit, element: ElementIndex) => ReadonlyArray<number> - getAnomericCarbon: (unit: Unit, residueIndex: ResidueIndex) => ElementIndex | undefined + getAnomericCarbons: (unit: Unit, residueIndex: ResidueIndex) => ReadonlyArray<ElementIndex> } const EmptyArray: ReadonlyArray<any> = [] @@ -63,5 +63,5 @@ export const EmptyCarbohydrates: Carbohydrates = { getLinkIndices: () => EmptyArray, getTerminalLinkIndex: () => undefined, getTerminalLinkIndices: () => EmptyArray, - getAnomericCarbon: () => undefined, + getAnomericCarbons: () => [], } \ No newline at end of file diff --git a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts index c1c2b046f..075ffa575 100644 --- a/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-repr/structure/visual/carbohydrate-symbol-mesh.ts @@ -25,7 +25,7 @@ import { OrderedSet, Interval } from 'mol-data/int'; import { EmptyLoci, Loci } from 'mol-model/loci'; import { VisualContext } from 'mol-repr/visual'; import { Theme } from 'mol-theme/theme'; -import { getResidueLoci } from './util/common'; +import { getAltResidueLoci } from './util/common'; const t = Mat4.identity() const sVec = Vec3.zero() @@ -186,28 +186,28 @@ function getCarbohydrateLoci(pickingId: PickingId, structure: Structure, id: num const { objectId, groupId } = pickingId if (id === objectId) { const carb = structure.carbohydrates.elements[Math.floor(groupId / 2)] - return getResidueLoci(structure, carb.unit, carb.anomericCarbon) + return getAltResidueLoci(structure, carb.unit, carb.anomericCarbon) } return EmptyLoci } /** For each carbohydrate (usually a monosaccharide) when all its residue's elements are in a loci. */ function eachCarbohydrate(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean) { - const { getElementIndex, getAnomericCarbon } = structure.carbohydrates + const { getElementIndex, getAnomericCarbons } = structure.carbohydrates let changed = false if (!StructureElement.isLoci(loci)) return false if (!Structure.areEquivalent(loci.structure, structure)) return false for (const e of loci.elements) { + // TODO make more efficient by handling/grouping `e.indices` by residue index + // TODO only call apply when the full alt-residue of the unit is part of `e` OrderedSet.forEach(e.indices, v => { const { model, elements } = e.unit - const { index, offsets } = model.atomicHierarchy.residueAtomSegments + const { index } = model.atomicHierarchy.residueAtomSegments const rI = index[elements[v]] - const unitIndexMin = OrderedSet.findPredecessorIndex(elements, offsets[rI]) - const unitIndexMax = OrderedSet.findPredecessorIndex(elements, offsets[rI + 1] - 1) - const unitIndexInterval = Interval.ofRange(unitIndexMin, unitIndexMax) - if (!OrderedSet.isSubset(e.indices, unitIndexInterval)) return - const eI = getAnomericCarbon(e.unit, rI) - if (eI !== undefined) { + const eIndices = getAnomericCarbons(e.unit, rI) + for (let i = 0, il = eIndices.length; i < il; ++i) { + const eI = eIndices[i] + if (!OrderedSet.has(e.indices, OrderedSet.indexOf(elements, eI))) continue const idx = getElementIndex(e.unit, eI) if (idx !== undefined) { if (apply(Interval.ofBounds(idx * 2, idx * 2 + 2))) changed = true diff --git a/src/mol-repr/structure/visual/util/common.ts b/src/mol-repr/structure/visual/util/common.ts index f2f8faa6d..7aaa308da 100644 --- a/src/mol-repr/structure/visual/util/common.ts +++ b/src/mol-repr/structure/visual/util/common.ts @@ -28,6 +28,33 @@ export function getResidueLoci(structure: Structure, unit: Unit.Atomic, elementI return EmptyLoci } +/** + * Return a Loci for the elements of a whole residue the elementIndex belongs to but + * restrict to elements that have the same label_alt_id or none + */ +export function getAltResidueLoci(structure: Structure, unit: Unit.Atomic, elementIndex: ElementIndex): Loci { + const { elements, model } = unit + const { label_alt_id } = model.atomicHierarchy.atoms + const elementAltId = label_alt_id.value(elementIndex) + if (OrderedSet.indexOf(elements, elementIndex) !== -1) { + const { index, offsets } = model.atomicHierarchy.residueAtomSegments + const rI = index[elementIndex] + const _indices: number[] = [] + for (let i = offsets[rI], il = offsets[rI + 1]; i < il; ++i) { + const unitIndex = OrderedSet.indexOf(elements, i) + if (unitIndex !== -1) { + const altId = label_alt_id.value(i) + if (elementAltId === altId || altId === '') { + _indices.push(unitIndex) + } + } + } + const indices = OrderedSet.ofSortedArray<StructureElement.UnitIndex>(SortedArray.ofSortedArray(_indices)) + return StructureElement.Loci(structure, [{ unit, indices }]) + } + return EmptyLoci +} + // export function createUnitsTransform({ units }: Unit.SymmetryGroup, transformData?: TransformData) { diff --git a/src/mol-theme/color/carbohydrate-symbol.ts b/src/mol-theme/color/carbohydrate-symbol.ts index a92d183e0..f76bd90ed 100644 --- a/src/mol-theme/color/carbohydrate-symbol.ts +++ b/src/mol-theme/color/carbohydrate-symbol.ts @@ -27,13 +27,13 @@ export function CarbohydrateSymbolColorTheme(ctx: ThemeDataContext, props: PD.Va let color: LocationColor if (ctx.structure) { - const { elements, getElementIndex, getAnomericCarbon } = ctx.structure.carbohydrates + const { elements, getElementIndex, getAnomericCarbons } = ctx.structure.carbohydrates const getColor = (unit: Unit, index: ElementIndex) => { const residueIndex = unit.model.atomicHierarchy.residueAtomSegments.index[index] - const anomericCarbon = getAnomericCarbon(unit, residueIndex) - if (anomericCarbon !== undefined) { - const idx = getElementIndex(unit, anomericCarbon) + const anomericCarbons = getAnomericCarbons(unit, residueIndex) + if (anomericCarbons.length > 0) { + const idx = getElementIndex(unit, anomericCarbons[0]) if (idx !== undefined) return elements[idx].component.color } return DefaultColor -- GitLab