diff --git a/src/mol-geo/representation/structure/cartoon.ts b/src/mol-geo/representation/structure/cartoon.ts index 3e56c507a491bc014221ef1811db9b94e7d00aa1..6296fce4978ef5d1795770ebb30baae35f74a9af 100644 --- a/src/mol-geo/representation/structure/cartoon.ts +++ b/src/mol-geo/representation/structure/cartoon.ts @@ -14,6 +14,7 @@ import { PolymerTraceVisual, DefaultPolymerTraceProps } from './visual/polymer-t import { PolymerGapVisual, DefaultPolymerGapProps } from './visual/polymer-gap-cylinder'; import { NucleotideBlockVisual, DefaultNucleotideBlockProps } from './visual/nucleotide-block-mesh'; import { PolymerDirectionVisual, DefaultPolymerDirectionProps } from './visual/polymer-direction-wedge'; +import { CarbohydrateSymbolVisual } from './visual/carbohydrate-symbol-mesh'; export const DefaultCartoonProps = { ...DefaultPolymerTraceProps, @@ -28,13 +29,14 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> { const gapRepr = StructureUnitsRepresentation(PolymerGapVisual) const blockRepr = StructureUnitsRepresentation(NucleotideBlockVisual) const directionRepr = StructureUnitsRepresentation(PolymerDirectionVisual) + const carbohydrateRepr = StructureRepresentation(CarbohydrateSymbolVisual) return { get renderObjects() { - return [ ...traceRepr.renderObjects, ...gapRepr.renderObjects, ...blockRepr.renderObjects, ...directionRepr.renderObjects ] + return [ ...traceRepr.renderObjects, ...gapRepr.renderObjects, ...blockRepr.renderObjects, ...directionRepr.renderObjects, ...carbohydrateRepr.renderObjects ] }, get props() { - return { ...traceRepr.props, ...gapRepr.props, ...blockRepr.props, ...directionRepr.props } + return { ...traceRepr.props, ...gapRepr.props, ...blockRepr.props, ...carbohydrateRepr.props } }, create: (structure: Structure, props: CartoonProps = {} as CartoonProps) => { const p = Object.assign({}, DefaultCartoonProps, props) @@ -43,6 +45,7 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> { await gapRepr.create(structure, p).runInContext(ctx) await blockRepr.create(structure, p).runInContext(ctx) await directionRepr.create(structure, p).runInContext(ctx) + await carbohydrateRepr.create(structure, p).runInContext(ctx) }) }, update: (props: CartoonProps) => { @@ -52,6 +55,7 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> { await gapRepr.update(p).runInContext(ctx) await blockRepr.update(p).runInContext(ctx) await directionRepr.update(p).runInContext(ctx) + await carbohydrateRepr.update(p).runInContext(ctx) }) }, getLoci: (pickingId: PickingId) => { @@ -59,22 +63,26 @@ export function CartoonRepresentation(): StructureRepresentation<CartoonProps> { const gapLoci = gapRepr.getLoci(pickingId) const blockLoci = blockRepr.getLoci(pickingId) const directionLoci = directionRepr.getLoci(pickingId) + const carbohydrateRepr = directionRepr.getLoci(pickingId) return !isEmptyLoci(traceLoci) ? traceLoci : !isEmptyLoci(gapLoci) ? gapLoci : !isEmptyLoci(blockLoci) ? blockLoci - : directionLoci + : !isEmptyLoci(directionLoci) ? directionLoci + : carbohydrateRepr }, mark: (loci: Loci, action: MarkerAction) => { traceRepr.mark(loci, action) gapRepr.mark(loci, action) blockRepr.mark(loci, action) directionRepr.mark(loci, action) + carbohydrateRepr.mark(loci, action) }, destroy() { traceRepr.destroy() gapRepr.destroy() blockRepr.destroy() directionRepr.destroy() + carbohydrateRepr.destroy() } } } \ No newline at end of file diff --git a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ff36dd809382fb496132b8a7e5b9c2e6cf81b71 --- /dev/null +++ b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { ValueCell } from 'mol-util/value-cell' + +import { createMeshRenderObject, MeshRenderObject } from 'mol-gl/render-object' +import { Unit, Structure } from 'mol-model/structure'; +import { DefaultStructureProps, StructureVisual } from '..'; +import { RuntimeContext } from 'mol-task' +import { createIdentityTransform } from './util/common'; +import { MeshValues } from 'mol-gl/renderable'; +import { getMeshData } from '../../../util/mesh-data'; +import { Mesh } from '../../../shape/mesh'; +import { PickingId } from '../../../util/picking'; +import { createMarkers, MarkerAction } from '../../../util/marker-data'; +import { Loci, EmptyLoci } from 'mol-model/loci'; +import { SizeTheme } from '../../../theme'; +import { createMeshValues, updateMeshValues, updateRenderableState, createRenderableState, DefaultMeshProps } from '../../util'; +import { MeshBuilder } from '../../../shape/mesh-builder'; +import { Vec3, Mat4 } from 'mol-math/linear-algebra'; +import { createUniformColor } from '../../../util/color-data'; + +async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Structure, mesh?: Mesh) { + const builder = MeshBuilder.create(256, 128, mesh) + + const t = Mat4.identity() + const p = Vec3.zero() + const { carbohydrates } = structure + + const linkParams = { radiusTop: 0.2, radiusBottom: 0.2 } + + for (let i = 0, il = carbohydrates.elements.length; i < il; ++i) { + const c = carbohydrates.elements[i] + Mat4.setTranslation(t, c.center) + builder.addBox(t, { width: 2, height: 2, depth: 2 }) + } + + for (let i = 0, il = carbohydrates.links.length; i < il; ++i) { + const l = carbohydrates.links[i] + const centerA = carbohydrates.elements[l.carbohydrateIndexA].center + const centerB = carbohydrates.elements[l.carbohydrateIndexB].center + builder.addCylinder(centerA, centerB, 0.5, linkParams) + } + + for (let i = 0, il = carbohydrates.terminalLinks.length; i < il; ++i) { + const tl = carbohydrates.terminalLinks[i] + const center = carbohydrates.elements[tl.carbohydrateIndex].center + tl.elementUnit.conformation.position(tl.elementUnit.elements[tl.elementIndex], p) + if (tl.fromCarbohydrate) { + builder.addCylinder(center, p, 0.5, linkParams) + } else { + builder.addCylinder(p, center, 0.5, linkParams) + } + } + + return builder.getMesh() +} + +export const DefaultCarbohydrateSymbolProps = { + ...DefaultMeshProps, + ...DefaultStructureProps, + sizeTheme: { name: 'physical', factor: 1 } as SizeTheme, + detail: 0, + unitKinds: [ Unit.Kind.Atomic, Unit.Kind.Spheres ] as Unit.Kind[] +} +export type CarbohydrateSymbolProps = Partial<typeof DefaultCarbohydrateSymbolProps> + +export function CarbohydrateSymbolVisual(): StructureVisual<CarbohydrateSymbolProps> { + let renderObject: MeshRenderObject + let currentProps: typeof DefaultCarbohydrateSymbolProps + let mesh: Mesh + let currentStructure: Structure + + return { + get renderObject () { return renderObject }, + async create(ctx: RuntimeContext, structure: Structure, props: CarbohydrateSymbolProps = {}) { + currentProps = Object.assign({}, DefaultCarbohydrateSymbolProps, props) + currentStructure = structure + + const instanceCount = 1 + const elementCount = currentStructure.elementCount + + mesh = await createCarbohydrateSymbolMesh(ctx, currentStructure, mesh) + // console.log(mesh) + + const transforms = createIdentityTransform() + const color = createUniformColor({ value: 0x999911 }) // TODO + const marker = createMarkers(instanceCount * elementCount) + + const counts = { drawCount: mesh.triangleCount * 3, elementCount, instanceCount } + + const values: MeshValues = { + ...getMeshData(mesh), + ...color, + ...marker, + aTransform: transforms, + elements: mesh.indexBuffer, + ...createMeshValues(currentProps, counts), + aColor: ValueCell.create(new Float32Array(mesh.vertexCount * 3)) + } + const state = createRenderableState(currentProps) + + renderObject = createMeshRenderObject(values, state) + }, + async update(ctx: RuntimeContext, props: CarbohydrateSymbolProps) { + const newProps = Object.assign({}, currentProps, props) + + if (!renderObject) return false + + updateMeshValues(renderObject.values, newProps) + updateRenderableState(renderObject.state, newProps) + + return false + }, + getLoci(pickingId: PickingId) { + return EmptyLoci + }, + mark(loci: Loci, action: MarkerAction) { + // TODO + }, + destroy() { + // TODO + } + } +} diff --git a/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts b/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts index 69fe8db7a0e47b3d9847764f367b94df952a2557..cbb68b1d1ba0e2ff36dad1260e4f466e540d3576 100644 --- a/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts +++ b/src/mol-geo/representation/structure/visual/nucleotide-block-mesh.ts @@ -33,8 +33,8 @@ async function createNucleotideBlockMesh(ctx: RuntimeContext, unit: Unit, mesh?: const builder = MeshBuilder.create(256, 128, mesh) const { elements, model } = unit - const { chemicalComponentMap, modifiedResidues } = unit.model.properties - const { chainAtomSegments, residueAtomSegments, residues } = unit.model.atomicHierarchy + const { chemicalComponentMap, modifiedResidues } = model.properties + const { chainAtomSegments, residueAtomSegments, residues } = model.atomicHierarchy const { label_comp_id } = residues const pos = unit.conformation.invariantPosition diff --git a/src/mol-math/linear-algebra/3d/vec3.ts b/src/mol-math/linear-algebra/3d/vec3.ts index 4b8efbb13edcadf19dbb83b5fc1838fc79c713d6..659e35acc0c005e151f43ace20d3e0ffc5c13e01 100644 --- a/src/mol-math/linear-algebra/3d/vec3.ts +++ b/src/mol-math/linear-algebra/3d/vec3.ts @@ -162,6 +162,26 @@ namespace Vec3 { return out; } + /** + * Returns the minimum of two Vec3's + */ + export function min(out: Vec3, a: Vec3, b: Vec3) { + out[0] = Math.min(a[0], b[0]); + out[1] = Math.min(a[1], b[1]); + out[2] = Math.min(a[2], b[2]); + return out; + } + + /** + * Returns the maximum of two Vec3's + */ + export function max(out: Vec3, a: Vec3, b: Vec3) { + out[0] = Math.max(a[0], b[0]); + out[1] = Math.max(a[1], b[1]); + out[2] = Math.max(a[2], b[2]); + return out; + } + export function distance(a: Vec3, b: Vec3) { const x = b[0] - a[0], y = b[1] - a[1], diff --git a/src/mol-model/structure/structure/carbohydrates/compute.ts b/src/mol-model/structure/structure/carbohydrates/compute.ts new file mode 100644 index 0000000000000000000000000000000000000000..1b9ad695d50117af9442eaade7cda6396d7cb0bc --- /dev/null +++ b/src/mol-model/structure/structure/carbohydrates/compute.ts @@ -0,0 +1,127 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import Unit from '../unit'; +import { ResidueIndex } from '../../model'; +import { Interval, Segmentation } from 'mol-data/int'; +import Structure from '../structure'; +import { Carbohydrates, CarbohydrateLink, CarbohydrateTerminalLink, CarbohydrateElement } from './data'; +import { SaccharideNameMap } from './constants'; +import { Vec3 } from 'mol-math/linear-algebra'; +import { getCenterAndRadius } from '../../util'; + +function getResidueIndex(elementIndex: number, unit: Unit.Atomic) { + return unit.model.atomicHierarchy.residueAtomSegments.index[unit.elements[elementIndex]] +} + +function getRingIndices(unit: Unit.Atomic, rI: ResidueIndex) { + const { offsets } = unit.model.atomicHierarchy.residueAtomSegments + const { elements } = unit + const interval = Interval.ofBounds(offsets[rI], offsets[rI + 1]) + const rings = unit.rings.byFingerprint.get('C-C-C-C-C-O') || unit.rings.byFingerprint.get('C-C-C-C-O') + if (rings) { + for (let i = 0, il = rings.length; i < il; ++i) { + let withinIntervalCount = 0 + const ring = unit.rings.all[rings[i]] + for (let j = 0, jl = ring.length; j < jl; ++j) { + if (Interval.has(interval, elements[ring[j]])) ++withinIntervalCount + } + if (withinIntervalCount === ring.length) return ring + } + } +} + +export function computeCarbohydrates(structure: Structure): Carbohydrates { + const links: CarbohydrateLink[] = [] + const terminalLinks: CarbohydrateTerminalLink[] = [] + const elements: CarbohydrateElement[] = [] + + const elementsMap = new Map<string, number>() + + function elementKey(residueIndex: number, unitId: number) { + return `${residueIndex}|${unitId}` + } + + for (let i = 0, il = structure.units.length; i < il; ++i) { + const unit = structure.units[i] + if (!Unit.isAtomic(unit)) continue + + const { model } = unit + const { chainAtomSegments, residueAtomSegments, residues } = model.atomicHierarchy + const { label_comp_id } = residues + + const chainIt = Segmentation.transientSegments(chainAtomSegments, unit.elements) + const residueIt = Segmentation.transientSegments(residueAtomSegments, unit.elements) + + while (chainIt.hasNext) { + residueIt.setSegment(chainIt.move()); + + while (residueIt.hasNext) { + const { index: residueIndex } = residueIt.move(); + + const saccharideComp = SaccharideNameMap.get(label_comp_id.value(residueIndex)) + if (!saccharideComp) continue + + const ringIndices = getRingIndices(unit, residueIndex) + if (ringIndices) { + const center = Vec3.zero() + const normal = Vec3.zero() + const direction = Vec3.zero() + const ringRadius = getCenterAndRadius(center, unit, ringIndices) + console.log(ringRadius, center) + + elementsMap.set(elementKey(residueIndex, unit.id), elements.length) + elements.push({ center, normal, direction, unit, residueIndex, component: saccharideComp }) + } else { + console.warn('No ring found for carbohydrate') + } + } + } + } + + elementsMap.forEach((elementIndex, key) => { + const unit = elements[elementIndex].unit + structure.links.getLinkedUnits(unit).forEach(pairBonds => { + pairBonds.linkedElementIndices.forEach(indexA => { + pairBonds.getBonds(indexA).forEach(bondInfo => { + let { unitA, unitB } = pairBonds + let indexB = bondInfo.indexB + let residueIndexA = getResidueIndex(indexA, unitA) + let residueIndexB = getResidueIndex(indexB, unitB) + let keyA = elementKey(residueIndexA, unitA.id) + let keyB = elementKey(residueIndexB, unitB.id) + if (key === keyB) { + [keyB, keyA] = [keyA, keyB]; + [indexB, indexA] = [indexA, indexB]; + [unitB, unitA] = [unitA, unitB]; + } + const elementIndexB = elementsMap.get(keyB) + if (elementIndexB !== undefined) { + links.push({ + carbohydrateIndexA: elementIndex, + carbohydrateIndexB: elementIndexB + }) + } else { + terminalLinks.push({ + carbohydrateIndex: elementIndex, + elementIndex: indexB, + elementUnit: unitB, + fromCarbohydrate: true + }) + terminalLinks.push({ + carbohydrateIndex: elementIndex, + elementIndex: indexB, + elementUnit: unitB, + fromCarbohydrate: false + }) + } + }) + }) + }) + }) + + return { links, terminalLinks, elements } +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/carbohydrates/constants.ts b/src/mol-model/structure/structure/carbohydrates/constants.ts new file mode 100644 index 0000000000000000000000000000000000000000..8c71acb0f1333ff4d14904b24e9fd288e0f04ed4 --- /dev/null +++ b/src/mol-model/structure/structure/carbohydrates/constants.ts @@ -0,0 +1,270 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author David Sehnal <david.sehnal@gmail.com> + */ + +// https://www.ncbi.nlm.nih.gov/glycans/snfg.html + +export const enum SaccharideShapes { + FilledSphere, FilledCube, CrossedCube, DividedDiamond, FilledCone, DevidedCone, + FlatBox, FilledStar, FilledDiamond, FlatDiamond, FlatHexagon, Pentagon +} + +// TODO move to theme +const enum SaccharideColors { + Blue = 0x0090bc, + Green = 0x00a651, + Yellow = 0xffd400, + Orange = 0xf47920, + Pink = 0xf69ea1, + Purple = 0xa54399, + LightBlue = 0x8fcce9, + Brown = 0xa17a4d, + Red = 0xed1c24, + + Secondary = 0xf1ece1 +} + +export const enum SaccharideType { + Hexose, HexNAc, Hexosamine, Hexuronate, Deoxyhexose, DeoxyhexNAc, DiDeoxyhexose, + Pentose, Deoxynonulosonate, DiDeoxynonulosonate, Unknown, Assigned +} + +const SaccharideTypeNameMap = { + [SaccharideType.Hexose]: 'Hexose', + [SaccharideType.HexNAc]: 'HexNAc', + [SaccharideType.Hexosamine]: 'Hexosamine', + [SaccharideType.Hexuronate]: 'Hexuronate', + [SaccharideType.Deoxyhexose]: 'Deoxyhexose', + [SaccharideType.DeoxyhexNAc]: 'DeoxyhexNAc', + [SaccharideType.DiDeoxyhexose]: 'Di-deoxyhexose', + [SaccharideType.Pentose]: 'Pentose', + [SaccharideType.Deoxynonulosonate]: 'Deoxynonulosonate', + [SaccharideType.DiDeoxynonulosonate]: 'Di-deoxynonulosonate', + [SaccharideType.Unknown]: 'Unknown', + [SaccharideType.Assigned]: 'Assigned', +} + +export function getSaccharideName(type: SaccharideType) { + return SaccharideTypeNameMap[type] +} + +const SaccharideTypeShapeMap = { + [SaccharideType.Hexose]: SaccharideShapes.FilledSphere, + [SaccharideType.HexNAc]: SaccharideShapes.FilledCube, + [SaccharideType.Hexosamine]: SaccharideShapes.CrossedCube, + [SaccharideType.Hexuronate]: SaccharideShapes.DividedDiamond, + [SaccharideType.Deoxyhexose]: SaccharideShapes.FilledCone, + [SaccharideType.DeoxyhexNAc]: SaccharideShapes.DevidedCone, + [SaccharideType.DiDeoxyhexose]: SaccharideShapes.FlatBox, + [SaccharideType.Pentose]: SaccharideShapes.FilledStar, + [SaccharideType.Deoxynonulosonate]: SaccharideShapes.FilledDiamond, + [SaccharideType.DiDeoxynonulosonate]: SaccharideShapes.FlatDiamond, + [SaccharideType.Unknown]: SaccharideShapes.FlatHexagon, + [SaccharideType.Assigned]: SaccharideShapes.Pentagon, +} + +export function getSaccharideShape(type: SaccharideType) { + return SaccharideTypeShapeMap[type] +} + +export type SaccharideComponent = { + abbr: string + name: string + color: SaccharideColors + type: SaccharideType +} + +const Monosaccharides: SaccharideComponent[] = [ + { abbr: 'Glc', name: 'Glucose', color: SaccharideColors.Blue, type: SaccharideType.Hexose }, + { abbr: 'Man', name: 'Mannose', color: SaccharideColors.Green, type: SaccharideType.Hexose }, + { abbr: 'Gal', name: 'Galactose', color: SaccharideColors.Yellow, type: SaccharideType.Hexose }, + { abbr: 'Gul', name: 'Gulose', color: SaccharideColors.Orange, type: SaccharideType.Hexose }, + { abbr: 'Alt', name: 'Altrose', color: SaccharideColors.Pink, type: SaccharideType.Hexose }, + { abbr: 'All', name: 'Allose', color: SaccharideColors.Purple, type: SaccharideType.Hexose }, + { abbr: 'Tal', name: 'Talose', color: SaccharideColors.LightBlue, type: SaccharideType.Hexose }, + { abbr: 'Ido', name: 'Idose', color: SaccharideColors.Brown, type: SaccharideType.Hexose }, + + { abbr: 'GlcNAc', name: 'N-Acetyl Glucosamine', color: SaccharideColors.Blue, type: SaccharideType.HexNAc }, + { abbr: 'ManNAc', name: 'N-Acetyl Mannosamine', color: SaccharideColors.Green, type: SaccharideType.HexNAc }, + { abbr: 'GalNAc', name: 'N-Acetyl Galactosamine', color: SaccharideColors.Yellow, type: SaccharideType.HexNAc }, + { abbr: 'GulNAc', name: 'N-Acetyl Gulosamine', color: SaccharideColors.Orange, type: SaccharideType.HexNAc }, + { abbr: 'AltNAc', name: 'N-Acetyl Altrosamine', color: SaccharideColors.Pink, type: SaccharideType.HexNAc }, + { abbr: 'AllNAc', name: 'N-Acetyl Allosamine', color: SaccharideColors.Purple, type: SaccharideType.HexNAc }, + { abbr: 'TalNAc', name: 'N-Acetyl Talosamine', color: SaccharideColors.LightBlue, type: SaccharideType.HexNAc }, + { abbr: 'IdoNAc', name: 'N-Acetyl Idosamine', color: SaccharideColors.Brown, type: SaccharideType.HexNAc }, + + { abbr: 'GlcN', name: 'Glucosamine', color: SaccharideColors.Blue, type: SaccharideType.Hexosamine }, + { abbr: 'ManN', name: 'Mannosamine', color: SaccharideColors.Green, type: SaccharideType.Hexosamine }, + { abbr: 'GalN', name: 'Galactosamine', color: SaccharideColors.Yellow, type: SaccharideType.Hexosamine }, + { abbr: 'GulN', name: 'Gulosamine', color: SaccharideColors.Orange, type: SaccharideType.Hexosamine }, + { abbr: 'AltN', name: 'Altrosamine', color: SaccharideColors.Pink, type: SaccharideType.Hexosamine }, + { abbr: 'AllN', name: 'Allosamine', color: SaccharideColors.Purple, type: SaccharideType.Hexosamine }, + { abbr: 'TalN', name: 'Talosamine', color: SaccharideColors.LightBlue, type: SaccharideType.Hexosamine }, + { abbr: 'IdoN', name: 'Idosamine', color: SaccharideColors.Brown, type: SaccharideType.Hexosamine }, + + { abbr: 'GlcA', name: 'Glucuronic Acid', color: SaccharideColors.Blue, type: SaccharideType.Hexuronate }, + { abbr: 'ManA', name: 'Mannuronic Acid', color: SaccharideColors.Green, type: SaccharideType.Hexuronate }, + { abbr: 'GalA', name: 'Galacturonic Acid', color: SaccharideColors.Yellow, type: SaccharideType.Hexuronate }, + { abbr: 'GulA', name: 'Guluronic Acid', color: SaccharideColors.Orange, type: SaccharideType.Hexuronate }, + { abbr: 'AltA', name: 'Altruronic Acid', color: SaccharideColors.Pink, type: SaccharideType.Hexuronate }, + { abbr: 'AllA', name: 'Alluronic Acid', color: SaccharideColors.Purple, type: SaccharideType.Hexuronate }, + { abbr: 'TalA', name: 'Taluronic Acid', color: SaccharideColors.LightBlue, type: SaccharideType.Hexuronate }, + { abbr: 'IdoA', name: 'Iduronic Acid', color: SaccharideColors.Brown, type: SaccharideType.Hexuronate }, + + { abbr: 'Qui', name: 'Quinovose', color: SaccharideColors.Blue, type: SaccharideType.Deoxyhexose }, + { abbr: 'Rha', name: 'Rhamnose', color: SaccharideColors.Green, type: SaccharideType.Deoxyhexose }, + { abbr: '6dGul', name: '6-Deoxy Gulose', color: SaccharideColors.Orange, type: SaccharideType.Deoxyhexose }, + { abbr: '6dAlt', name: '6-Deoxy Altrose', color: SaccharideColors.Pink, type: SaccharideType.Deoxyhexose }, + { abbr: '6dTal', name: '6-Deoxy Talose', color: SaccharideColors.LightBlue, type: SaccharideType.Deoxyhexose }, + { abbr: 'Fuc', name: 'Fucose', color: SaccharideColors.Red, type: SaccharideType.Deoxyhexose }, + + { abbr: 'QuiNAc', name: 'N-Acetyl Quinovosamine', color: SaccharideColors.Blue, type: SaccharideType.DeoxyhexNAc }, + { abbr: 'RhaNAc', name: 'N-Acetyl Rhamnosamine', color: SaccharideColors.Green, type: SaccharideType.DeoxyhexNAc }, + { abbr: '6dAltNAc', name: 'N-Acetyl 6-Deoxy Altrosamine', color: SaccharideColors.Pink, type: SaccharideType.DeoxyhexNAc }, + { abbr: '6dTalNAc', name: 'N-Acetyl 6-Deoxy Talosamine', color: SaccharideColors.LightBlue, type: SaccharideType.DeoxyhexNAc }, + { abbr: 'FucNAc', name: 'N-Acetyl Fucosamine', color: SaccharideColors.Red, type: SaccharideType.DeoxyhexNAc }, + + { abbr: 'Oli', name: 'Olivose', color: SaccharideColors.Blue, type: SaccharideType.DiDeoxyhexose }, + { abbr: 'Tyv', name: 'Tyvelose', color: SaccharideColors.Green, type: SaccharideType.DiDeoxyhexose }, + { abbr: 'Abe', name: 'Abequose', color: SaccharideColors.Orange, type: SaccharideType.DiDeoxyhexose }, + { abbr: 'Par', name: 'Paratose', color: SaccharideColors.Pink, type: SaccharideType.DiDeoxyhexose }, + { abbr: 'Dig', name: 'Digitoxose', color: SaccharideColors.Purple, type: SaccharideType.DiDeoxyhexose }, + { abbr: 'Col', name: 'Colitose', color: SaccharideColors.LightBlue, type: SaccharideType.DiDeoxyhexose }, + + { abbr: 'Ara', name: 'Arabinose', color: SaccharideColors.Green, type: SaccharideType.Pentose }, + { abbr: 'Lyx', name: 'Lyxose', color: SaccharideColors.Yellow, type: SaccharideType.Pentose }, + { abbr: 'Xyl', name: 'Xylose', color: SaccharideColors.Orange, type: SaccharideType.Pentose }, + { abbr: 'Rib', name: 'Ribose', color: SaccharideColors.Pink, type: SaccharideType.Pentose }, + + { abbr: 'Kdn', name: 'Keto-Deoxy Nonulonic Acid', color: SaccharideColors.Green, type: SaccharideType.Deoxynonulosonate }, + { abbr: 'Neu5Ac', name: 'N-Acetyl Neuraminic Acid', color: SaccharideColors.Purple, type: SaccharideType.Deoxynonulosonate }, + { abbr: 'Neu5Gc', name: 'N-Glycolyl Neuraminic Acid', color: SaccharideColors.LightBlue, type: SaccharideType.Deoxynonulosonate }, + { abbr: 'Neu', name: 'Neuraminic Acid', color: SaccharideColors.Brown, type: SaccharideType.Deoxynonulosonate }, + { abbr: 'Sia', name: 'Sialic acid', color: SaccharideColors.Red, type: SaccharideType.Deoxynonulosonate }, + + { abbr: 'Pse', name: 'Pseudaminic Acid', color: SaccharideColors.Green, type: SaccharideType.DiDeoxynonulosonate }, + { abbr: 'Leg', name: 'Legionaminic Acid', color: SaccharideColors.Yellow, type: SaccharideType.DiDeoxynonulosonate }, + { abbr: 'Aci', name: 'Acinetaminic Acid', color: SaccharideColors.Pink, type: SaccharideType.DiDeoxynonulosonate }, + { abbr: '4eLeg', name: '4-Epilegionaminic Acid', color: SaccharideColors.LightBlue, type: SaccharideType.DiDeoxynonulosonate }, + + { abbr: 'Bac', name: 'Bacillosamine', color: SaccharideColors.Blue, type: SaccharideType.Unknown }, + { abbr: 'LDManHep', name: 'L-Glycero-D-Manno Heptose', color: SaccharideColors.Green, type: SaccharideType.Unknown }, + { abbr: 'Kdo', name: 'Keto-Deoxy Octulonic Acid', color: SaccharideColors.Yellow, type: SaccharideType.Unknown }, + { abbr: 'Dha', name: '3-Deoxy Lyxo-Heptulosaric Acid', color: SaccharideColors.Orange, type: SaccharideType.Unknown }, + { abbr: 'DDManHep', name: 'D-Glycero-D-Manno-Heptose', color: SaccharideColors.Pink, type: SaccharideType.Unknown }, + { abbr: 'MurNAc', name: 'N-Acetyl Muramic Acid', color: SaccharideColors.Purple, type: SaccharideType.Unknown }, + { abbr: 'MurNGc', name: 'N-Glycolyl Muramic Acid', color: SaccharideColors.LightBlue, type: SaccharideType.Unknown }, + { abbr: 'Mur', name: 'Muramic Acid', color: SaccharideColors.Brown, type: SaccharideType.Unknown }, + + { abbr: 'Api', name: 'Apicose', color: SaccharideColors.Green, type: SaccharideType.Assigned }, + { abbr: 'Fru', name: 'Fructose', color: SaccharideColors.Green, type: SaccharideType.Assigned }, + { abbr: 'Tag', name: 'Tagatose', color: SaccharideColors.Yellow, type: SaccharideType.Assigned }, + { abbr: 'Sor', name: 'Sorbose', color: SaccharideColors.Orange, type: SaccharideType.Assigned }, + { abbr: 'Psi', name: 'Psicose', color: SaccharideColors.Pink, type: SaccharideType.Assigned }, +] + +const CommonSaccharideNames: { [k: string]: string[] } = { + // Hexose + Glc: ['GLC', 'BGC'], + Man: ['MAN', 'BMA'], + Gal: ['GAL', 'GLA'], + Gul: ['GUP', 'GL0'], + Alt: ['ALT'], + All: ['ALL', 'AFD'], + Tal: ['TAL'], + Ido: ['4N2'], + // HexNAc + GlcNAc: ['NAG', 'NDG'], + ManNAc: ['NGA', 'A2G'], + GulNAc: [], + AltNAc: [], + AllNAc: ['NAA'], + TalNAc: [], + IdoNAc: ['HSQ'], + // Hexosamine + GlcN: ['GCS', 'PA1'], + ManN: ['95Z'], + GalN: ['X6X', '1GN'], + GulN: [], + AltN: [], + AllN: [], + TalN: [], + IdoN: [], + // Hexuronate + GlcA: ['GCU', 'BDP'], + ManA: ['MAV', 'BEM'], + GalA: ['ADA', 'GTR'], + GulA: ['LGU'], + AltA: [], + AllA: [], + TalA: ['X0X', 'X1X'], + IdoA: ['IDR'], + // Deoxyhexose + Qui: ['G6D'], + Rha: ['RAM', 'RM4'], + '6dGul': [], + '6dAlt': [], + '6dTal': [], + Fuc: ['FUC', 'FUL'], + // DeoxyhexNAc + QuiNAc: [], + RhaNAc: [], + '6dAltNAc': [], + '6dTalNAc': [], + FucNAc: [], + // Di-deoxyhexose + Oli: ['DDA'], + Tyv: ['TYV'], + Abe: ['ABE'], + Par: ['PZU'], + Dig: [], + Col: [], + // Pentose + Ara: ['ARA', 'ARB'], + Lyx: ['LDY'], + Xyl: ['XYS', 'XYP'], + Rib: ['RIP', '0MK'], + // Deoxynonulosonate + Kdn: ['KDN', 'KDM'], + Neu5Ac: ['SIA', 'SLB'], + Neu5Gc: ['NGC', 'NGE'], + Neu: [], + Sia: [], + // Di-deoxynonulosonate + Pse: ['6PZ'], + Leg: [], + Aci: [], + '4eLeg': [], + // Unknown + Bac: ['B6D'], + LDManHep: ['GMH'], + Kdo: ['KDO'], + Dha: [], + DDManHep: [], + MurNAc: ['AMU'], + MurNGc: [], + Mur: ['MUR'], + // Assigned + Api: ['XXM'], + Fru: ['BDF'], + Tag: ['T6T'], + Sor: ['SOE'], + Psi: [], +} + +export const SaccharideNameMap = (function () { + const map = new Map<string, SaccharideComponent>() + for (let i = 0, il = Monosaccharides.length; i < il; ++i) { + const saccharide = Monosaccharides[i] + const names = CommonSaccharideNames[saccharide.abbr] + if (names) { + for (let j = 0, jl = names.length; j < jl; ++j) { + map.set(names[j], saccharide) + } + } + } + return map +})() \ 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 new file mode 100644 index 0000000000000000000000000000000000000000..c8f22a1fe31c26c5155a26071c1002a52cc39dd8 --- /dev/null +++ b/src/mol-model/structure/structure/carbohydrates/data.ts @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import Unit from '../unit'; +import { Vec3 } from 'mol-math/linear-algebra'; +import { ResidueIndex } from '../../model'; +import { SaccharideComponent } from './constants'; + +export interface CarbohydrateLink { + readonly carbohydrateIndexA: number + readonly carbohydrateIndexB: number +} + +export interface CarbohydrateTerminalLink { + readonly carbohydrateIndex: number + readonly elementIndex: number + readonly elementUnit: Unit + /** specifies direction of the link */ + readonly fromCarbohydrate: boolean +} + +export interface CarbohydrateElement { + readonly center: Vec3, + readonly normal: Vec3, + readonly direction: Vec3, + readonly unit: Unit.Atomic + readonly residueIndex: ResidueIndex + readonly component: SaccharideComponent +} + +export interface Carbohydrates { + links: ReadonlyArray<CarbohydrateLink> + terminalLinks: ReadonlyArray<CarbohydrateTerminalLink> + elements: ReadonlyArray<CarbohydrateElement> +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index 73cd16ee0b9c7cb62fb1fc7bc2ba9e84fe2b2769..348e8a7d9f650ba6bdd88b352fad774087d0d99f 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -19,6 +19,8 @@ import { CrossLinkRestraints, extractCrossLinkRestraints } from './unit/pair-res import StructureSymmetry from './symmetry'; import StructureProperties from './properties'; import { ResidueIndex } from '../model/indexing'; +import { Carbohydrates } from './carbohydrates/data'; +import { computeCarbohydrates } from './carbohydrates/compute'; class Structure { readonly unitMap: IntMap<Unit>; @@ -86,6 +88,13 @@ class Structure { return this._unitSymmetryGroups; } + private _carbohydrates?: Carbohydrates = void 0; + get carbohydrates(): Carbohydrates { + if (this._carbohydrates) return this._carbohydrates; + this._carbohydrates = computeCarbohydrates(this); + return this._carbohydrates; + } + constructor(units: ArrayLike<Unit>) { const map = IntMap.Mutable<Unit>(); let elementCount = 0; diff --git a/src/mol-model/structure/util.ts b/src/mol-model/structure/util.ts index fbe525649c4e876ffe7eb21d39c805d030e33af7..bac2778ca445ea5ed1d17fb7103cc17c5748f9a2 100644 --- a/src/mol-model/structure/util.ts +++ b/src/mol-model/structure/util.ts @@ -6,6 +6,8 @@ import { Model, ResidueIndex, ElementIndex } from './model'; import { MoleculeType, AtomRole, MoleculeTypeAtomRoleId } from './model/types'; +import { Vec3 } from 'mol-math/linear-algebra'; +import { Unit } from './structure'; export function getMoleculeType(model: Model, rI: ResidueIndex) { const compId = model.atomicHierarchy.residues.label_comp_id.value(rI) @@ -43,4 +45,19 @@ export function residueLabel(model: Model, rI: number) { const { label_asym_id } = chains const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]] return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}` +} + +const centerPos = Vec3.zero() +const centerMin = Vec3.zero() +export function getCenterAndRadius(centroid: Vec3, unit: Unit, indices: ArrayLike<number>) { + const pos = unit.conformation.position + const { elements } = unit + Vec3.set(centroid, 0, 0, 0) + for (let i = 0, il = indices.length; i < il; ++i) { + pos(elements[indices[i]], centerPos) + Vec3.add(centroid, centroid, centerPos) + Vec3.min(centerMin, centerMin, centerPos) + } + Vec3.scale(centroid, centroid, 1/indices.length) + return Vec3.distance(centerMin, centroid) } \ No newline at end of file diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index 1fc6ce73a9455caa7e57163f148da127e53dba7c..a886525f57931616070ec6e17c4c17c03b158604 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -27,8 +27,8 @@ const spacefillProps: SpacefillProps = { const ballAndStickProps: BallAndStickProps = { doubleSided: true, colorTheme: { name: 'chain-id' }, - sizeTheme: { name: 'uniform', value: 0.05 }, - linkRadius: 0.05, + sizeTheme: { name: 'uniform', value: 0.15 }, + linkRadius: 0.15, quality: 'auto', useFog: false } @@ -78,7 +78,7 @@ export class Stage { // this.loadPdbid('1hrv') // viral assembly // this.loadPdbid('1rb8') // virus // this.loadPdbid('1blu') // metal coordination - // this.loadPdbid('3pqr') // inter unit bonds, two polymer chains, ligands, water + this.loadPdbid('3pqr') // inter unit bonds, two polymer chains, ligands, water, carbohydrates linked to protein // this.loadPdbid('4v5a') // ribosome // this.loadPdbid('3j3q') // ... // this.loadPdbid('2np2') // dna @@ -92,8 +92,10 @@ export class Stage { // this.loadPdbid('1xv6') // rna, modified nucleotides // this.loadPdbid('3bbm') // rna with linker // this.loadPdbid('1gfl') // GFP, flourophore has carbonyl oxygen removed - this.loadPdbid('1sfi') // contains cyclic peptid + // this.loadPdbid('1sfi') // contains cyclic peptid // this.loadPdbid('3sn6') // discontinuous chains + // this.loadPdbid('2zex') // small, contains carbohydrate polymer + // this.loadPdbid('2b5t') // contains large carbohydrate polymer // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`) // this.loadMmcifUrl(`../../examples/1cbs_updated.cif`) // this.loadMmcifUrl(`../../examples/1crn.cif`) @@ -119,10 +121,10 @@ export class Stage { console.log(modelEntity.value) const structureEntity = await ModelToStructure.apply(this.ctx, modelEntity) - StructureToBallAndStick.apply(this.ctx, structureEntity, { ...ballAndStickProps, visible: false }) + StructureToBallAndStick.apply(this.ctx, structureEntity, { ...ballAndStickProps, visible: true }) StructureToSpacefill.apply(this.ctx, structureEntity, { ...spacefillProps, visible: false }) StructureToDistanceRestraint.apply(this.ctx, structureEntity, { ...distanceRestraintProps, visible: false }) - StructureToBackbone.apply(this.ctx, structureEntity, { ...backboneProps, visible: true }) + StructureToBackbone.apply(this.ctx, structureEntity, { ...backboneProps, visible: false }) StructureToCartoon.apply(this.ctx, structureEntity, { ...cartoonProps, visible: true }) StructureCenter.apply(this.ctx, structureEntity)