From f10a135deac9bf1200b85c6c844f9cadc798429a Mon Sep 17 00:00:00 2001 From: Alexander Rose <alexander.rose@weirdbyte.de> Date: Sat, 22 Jun 2019 07:24:01 -0700 Subject: [PATCH] sequence view, better handling of missing residues, full structure loci --- src/mol-data/int/ordered-set.ts | 1 + src/mol-data/int/sorted-array.ts | 8 +-- src/mol-plugin/ui/sequence/polymer.ts | 76 ++++++++++++++++----------- src/mol-plugin/ui/sequence/util.ts | 14 ++--- 4 files changed, 56 insertions(+), 43 deletions(-) diff --git a/src/mol-data/int/ordered-set.ts b/src/mol-data/int/ordered-set.ts index 7f671e22a..4ec45b17f 100644 --- a/src/mol-data/int/ordered-set.ts +++ b/src/mol-data/int/ordered-set.ts @@ -37,6 +37,7 @@ namespace OrderedSet { export const union: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.union as any; export const intersect: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.intersect as any; + /** Returns elements of `a` that are not in `b`, i.e `a` - `b` */ export const subtract: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.subtract as any; export const findPredecessorIndex: <T extends number = number>(set: OrderedSet<T>, x: number) => number = Base.findPredecessorIndex as any; diff --git a/src/mol-data/int/sorted-array.ts b/src/mol-data/int/sorted-array.ts index bd1c202b0..8858aed3d 100644 --- a/src/mol-data/int/sorted-array.ts +++ b/src/mol-data/int/sorted-array.ts @@ -12,9 +12,9 @@ namespace SortedArray { export const ofUnsortedArray: <T extends number = number>(xs: ArrayLike<number>) => SortedArray<T> = Impl.ofUnsortedArray as any; export const ofSingleton: <T extends number = number>(v: number) => SortedArray<T> = Impl.ofSingleton as any; export const ofSortedArray: <T extends number = number>(xs: ArrayLike<number>) => SortedArray<T> = Impl.ofSortedArray as any; - // create sorted array [min, max] (it DOES contain the max value) + /** create sorted array [min, max] (it DOES contain the max value) */ export const ofRange: <T extends number = number>(min: T, max: T) => SortedArray<T> = Impl.ofRange as any; - // create sorted array [min, max) (it does NOT contain the max value) + /** create sorted array [min, max) (it does NOT contain the max value) */ export const ofBounds: <T extends number = number>(min: T, max: T) => SortedArray<T> = (min, max) => Impl.ofRange(min, max - 1) as any; export const is: <T extends number = number>(v: any) => v is SortedArray<T> = Impl.is as any; @@ -23,9 +23,9 @@ namespace SortedArray { export const indexOf: <T extends number = number>(array: SortedArray<T>, x: T) => number = Impl.indexOf as any; export const indexOfInInterval: <T extends number = number>(array: SortedArray<T>, x: number, bounds: Interval) => number = Impl.indexOfInInterval as any; - // array[0] + /** Returns `array[0]` */ export const start: <T extends number = number>(array: SortedArray<T>) => T = Impl.start as any; - // array[array.length - 1] + 1 + /** Returns `array[array.length - 1] + 1` */ export const end: <T extends number = number>(array: SortedArray<T>) => T = Impl.end as any; export const min: <T extends number = number>(array: SortedArray<T>) => T = Impl.min as any; export const max: <T extends number = number>(array: SortedArray<T>) => T = Impl.max as any; diff --git a/src/mol-plugin/ui/sequence/polymer.ts b/src/mol-plugin/ui/sequence/polymer.ts index 03b1f60d0..7ccd38272 100644 --- a/src/mol-plugin/ui/sequence/polymer.ts +++ b/src/mol-plugin/ui/sequence/polymer.ts @@ -6,7 +6,7 @@ import { StructureSelection, StructureQuery, Structure, Queries, StructureProperties as SP, StructureElement, Unit } from '../../../mol-model/structure'; import { SequenceWrapper } from './util'; -import { OrderedSet, Interval } from '../../../mol-data/int'; +import { OrderedSet, Interval, SortedArray } from '../../../mol-data/int'; import { Loci } from '../../../mol-model/loci'; import { Sequence } from '../../../mol-model/sequence'; import { MissingResidues } from '../../../mol-model/structure/model/properties/common'; @@ -18,46 +18,52 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> { private readonly location: StructureElement private readonly sequence: Sequence private readonly missing: MissingResidues + private readonly observed: OrderedSet // sequences indices private readonly modelNum: number private readonly asymId: string - seqId(i: number) { - return this.sequence.offset + i + 1 + seqId(seqIdx: number) { + return this.sequence.offset + seqIdx + 1 } - residueLabel(i: number) { - return this.sequence.sequence[i] + residueLabel(seqIdx: number) { + return this.sequence.sequence[seqIdx] } - residueColor(i: number) { - return this.missing.has(this.modelNum, this.asymId, this.seqId(i)) + residueColor(seqIdx: number) { + return this.missing.has(this.modelNum, this.asymId, this.seqId(seqIdx)) ? ColorNames.grey : ColorNames.black } - eachResidue(loci: Loci, apply: (interval: Interval) => boolean) { + eachResidue(loci: Loci, apply: (set: OrderedSet) => boolean) { let changed = false const { structure, unit } = this.data - if (!StructureElement.isLoci(loci)) return false - if (!Structure.areParentsEquivalent(loci.structure, structure)) return false - - const { location } = this - for (const e of loci.elements) { - let rIprev = -1 - location.unit = e.unit - - const { index: residueIndex } = e.unit.model.atomicHierarchy.residueAtomSegments - - OrderedSet.forEach(e.indices, v => { - location.element = e.unit.elements[v] - const rI = residueIndex[location.element] - // avoid checking for the same residue multiple times - if (rI !== rIprev) { - if (SP.unit.id(location) !== unit.id) return - - if (apply(getSeqIdInterval(location))) changed = true - rIprev = rI - } - }) + if (StructureElement.isLoci(loci)) { + if (!Structure.areParentsEqual(loci.structure, structure)) return false + + const { location } = this + for (const e of loci.elements) { + let rIprev = -1 + location.unit = e.unit + + const { index: residueIndex } = e.unit.model.atomicHierarchy.residueAtomSegments + + OrderedSet.forEach(e.indices, v => { + location.element = e.unit.elements[v] + const rI = residueIndex[location.element] + // avoid checking for the same residue multiple times + if (rI !== rIprev) { + if (SP.unit.id(location) !== unit.id) return + + if (apply(getSeqIndices(location))) changed = true + rIprev = rI + } + }) + } + } else if (Structure.isLoci(loci)) { + if (!Structure.areParentsEqual(loci.structure, structure)) return false + + if (apply(this.observed)) changed = true } return changed } @@ -70,7 +76,8 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> { constructor(data: StructureUnit) { const l = StructureElement.create(data.unit, data.unit.elements[0]) const sequence = data.unit.model.sequence.byEntityKey[SP.entity.key(l)].sequence - const markerArray = new Uint8Array(sequence.sequence.length) + const length = sequence.sequence.length + const markerArray = new Uint8Array(length) super(data, markerArray, sequence.sequence.length) @@ -80,6 +87,12 @@ export class PolymerSequenceWrapper extends SequenceWrapper<StructureUnit> { this.modelNum = data.unit.model.modelNum this.asymId = SP.chain.label_asym_id(l) + + const missing: number[] = [] + for (let i = 0; i < length; ++i) { + if (this.missing.has(this.modelNum, this.asymId, this.seqId(i))) missing.push(i) + } + this.observed = OrderedSet.subtract(Interval.ofBounds(0, length), SortedArray.ofSortedArray(missing)) } } @@ -101,8 +114,7 @@ function createResidueQuery(unitId: number, label_seq_id: number) { }); } -/** Zero-indexed */ -function getSeqIdInterval(location: StructureElement): Interval { +function getSeqIndices(location: StructureElement): Interval { const { unit, element } = location const { model } = unit switch (unit.kind) { diff --git a/src/mol-plugin/ui/sequence/util.ts b/src/mol-plugin/ui/sequence/util.ts index 91813be7a..158b47294 100644 --- a/src/mol-plugin/ui/sequence/util.ts +++ b/src/mol-plugin/ui/sequence/util.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Interval } from '../../../mol-data/int'; +import { OrderedSet } from '../../../mol-data/int'; import { Loci } from '../../../mol-model/loci'; import { MarkerAction, applyMarkerAction } from '../../../mol-util/marker-action'; import { StructureElement } from '../../../mol-model/structure'; @@ -13,16 +13,16 @@ import { Color } from '../../../mol-util/color'; export { SequenceWrapper } abstract class SequenceWrapper<D> { - abstract seqId(i: number): number - abstract residueLabel(i: number): string - abstract residueColor(i: number): Color + abstract seqId(seqIdx: number): number + abstract residueLabel(seqIdx: number): string + abstract residueColor(seqIdx: number): Color - abstract eachResidue(loci: Loci, apply: (interval: Interval) => boolean): boolean + abstract eachResidue(loci: Loci, apply: (set: OrderedSet) => boolean): boolean abstract getLoci(seqId: number): StructureElement.Loci markResidue(loci: Loci, action: MarkerAction) { - return this.eachResidue(loci, (i: Interval) => { - return applyMarkerAction(this.markerArray, i, action) + return this.eachResidue(loci, (set: OrderedSet) => { + return applyMarkerAction(this.markerArray, set, action) }) } -- GitLab