Browse Source

added sequence-id and secondary-structure color schemes

Alexander Rose 6 years ago
parent
commit
fd0e62da09

+ 2 - 2
src/apps/canvas/component/structure-representation.tsx

@@ -220,8 +220,8 @@ export class StructureRepresentationComponent extends React.Component<StructureR
                                     background: `linear-gradient(to right, ${ct.legend.colors.map(c => Color.toStyle(c)).join(', ')})`
                                 }}
                             >
-                                <span style={{float: 'left', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.min}</span>
-                                <span style={{float: 'right', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.max}</span>
+                                <span style={{float: 'left', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.minLabel}</span>
+                                <span style={{float: 'right', padding: '6px', color: 'white', fontWeight: 'bold', backgroundColor: 'rgba(0, 0, 0, 0.2)'}}>{ct.legend.maxLabel}</span>
                             </div>
                         : ct.legend && ct.legend.kind === 'table-legend'
                             ? <div>

+ 2 - 2
src/mol-geo/representation/structure/visual/util/polymer/trace-iterator.ts

@@ -11,7 +11,7 @@ import Iterator from 'mol-data/iterator';
 import { Vec3 } from 'mol-math/linear-algebra';
 import SortedRanges from 'mol-data/int/sorted-ranges';
 import { CoarseSphereConformation, CoarseGaussianConformation } from 'mol-model/structure/model/properties/coarse';
-import { getMoleculeType, getElementIndexForAtomRole } from 'mol-model/structure/util';
+import { getAtomicMoleculeType, getElementIndexForAtomRole } from 'mol-model/structure/util';
 import { getPolymerRanges } from '../polymer';
 
 /**
@@ -165,7 +165,7 @@ export class AtomicPolymerTraceIterator implements Iterator<PolymerTraceElement>
             value.first = residueIndex === this.residueSegmentMin
             value.last = residueIndex === this.residueSegmentMax
             value.secStrucChange = this.unit.model.properties.secondaryStructure.key[residueIndex] !== this.unit.model.properties.secondaryStructure.key[residueIndex + 1]
-            value.moleculeType = getMoleculeType(this.unit.model, residueIndex)
+            value.moleculeType = getAtomicMoleculeType(this.unit.model, residueIndex)
 
             if (!residueIt.hasNext) {
                 this.state = AtomicPolymerTraceIteratorState.nextPolymer

+ 2 - 2
src/mol-model/structure/structure/carbohydrates/compute.ts

@@ -13,7 +13,7 @@ import PrincipalAxes from 'mol-math/linear-algebra/matrix/principal-axes';
 import { fillSerial } from 'mol-util/array';
 import { ResidueIndex } from '../../model';
 import { ElementSymbol, MoleculeType } from '../../model/types';
-import { getMoleculeType, getPositionMatrix } from '../../util';
+import { getAtomicMoleculeType, getPositionMatrix } from '../../util';
 import StructureElement from '../element';
 import Structure from '../structure';
 import Unit from '../unit';
@@ -164,7 +164,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates {
 
                 const saccharideComp = SaccharideNameMap.get(label_comp_id.value(residueIndex)) || UnknownSaccharideComponent
                 if (saccharideComp === UnknownSaccharideComponent) {
-                    if (getMoleculeType(unit.model, residueIndex) !== MoleculeType.saccharide) continue
+                    if (getAtomicMoleculeType(unit.model, residueIndex) !== MoleculeType.saccharide) continue
                 }
 
                 if (!sugarResidueMap) {

+ 25 - 2
src/mol-model/structure/util.ts

@@ -10,7 +10,30 @@ import { Vec3 } from 'mol-math/linear-algebra';
 import { Unit } from './structure';
 import Matrix from 'mol-math/linear-algebra/matrix/matrix';
 
-export function getMoleculeType(model: Model, rI: ResidueIndex) {
+export function getCoarseBegCompId(unit: Unit.Spheres | Unit.Gaussians, element: ElementIndex) {
+    const entityKey = unit.coarseElements.entityKey[element]
+    const seq = unit.model.sequence.byEntityKey[entityKey]
+    const seq_id_begin = unit.coarseElements.seq_id_begin.value(element)
+    return seq.compId.value(seq_id_begin - 1) // 1-indexed
+}
+
+export function getElementMoleculeType(unit: Unit, element: ElementIndex) {
+    let compId = ''
+    switch (unit.kind) {
+        case Unit.Kind.Atomic:
+            compId = unit.model.atomicHierarchy.residues.label_comp_id.value(unit.residueIndex[element])
+            break
+        case Unit.Kind.Spheres:
+        case Unit.Kind.Gaussians:
+            compId = getCoarseBegCompId(unit, element)
+            break
+    }
+    const chemCompMap = unit.model.properties.chemicalComponentMap
+    const cc = chemCompMap.get(compId)
+    return cc ? cc.moleculeType : MoleculeType.unknown
+}
+
+export function getAtomicMoleculeType(model: Model, rI: ResidueIndex) {
     const compId = model.atomicHierarchy.residues.label_comp_id.value(rI)
     const chemCompMap = model.properties.chemicalComponentMap
     const cc = chemCompMap.get(compId)
@@ -36,7 +59,7 @@ export function getElementIndexForAtomId(model: Model, rI: ResidueIndex, atomId:
 }
 
 export function getElementIndexForAtomRole(model: Model, rI: ResidueIndex, atomRole: AtomRole) {
-    const atomId = getAtomIdForAtomRole(getMoleculeType(model, rI), atomRole)
+    const atomId = getAtomIdForAtomRole(getAtomicMoleculeType(model, rI), atomRole)
     return getElementIndexForAtomId(model, rI, atomId)
 }
 

+ 1 - 1
src/mol-util/color/tables.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ColorMap, ColorTable, Color } from './color';
+import { ColorMap, ColorTable } from './color';
 
 /**
  * Brewer Color Lists

+ 14 - 14
src/mol-view/label.ts

@@ -53,24 +53,24 @@ export function linkLabel(link: Link.Location) {
     return `${elementLabel(elementLocA)} - ${elementLabel(elementLocB)}`
 }
 
-export function elementLabel(element: StructureElement) {
-    const model = element.unit.model.label
-    const instance = element.unit.conformation.operator.name
+export function elementLabel(location: StructureElement) {
+    const model = location.unit.model.label
+    const instance = location.unit.conformation.operator.name
     let label = ''
 
-    if (Unit.isAtomic(element.unit)) {
-        const asym_id = Props.chain.auth_asym_id(element)
-        const seq_id = Props.residue.auth_seq_id(element)
-        const comp_id = Props.residue.auth_comp_id(element)
-        const atom_id = Props.atom.auth_atom_id(element)
+    if (Unit.isAtomic(location.unit)) {
+        const asym_id = Props.chain.auth_asym_id(location)
+        const seq_id = Props.residue.auth_seq_id(location)
+        const comp_id = Props.residue.auth_comp_id(location)
+        const atom_id = Props.atom.auth_atom_id(location)
         label = `[${comp_id}]${seq_id}:${asym_id}.${atom_id}`
-    } else if (Unit.isCoarse(element.unit)) {
-        const asym_id = Props.coarse.asym_id(element)
-        const seq_id_begin = Props.coarse.seq_id_begin(element)
-        const seq_id_end = Props.coarse.seq_id_end(element)
+    } else if (Unit.isCoarse(location.unit)) {
+        const asym_id = Props.coarse.asym_id(location)
+        const seq_id_begin = Props.coarse.seq_id_begin(location)
+        const seq_id_end = Props.coarse.seq_id_end(location)
         if (seq_id_begin === seq_id_end) {
-            const entityKey = Props.coarse.entityKey(element)
-            const seq = element.unit.model.sequence.byEntityKey[entityKey]
+            const entityIndex = Props.coarse.entityKey(location)
+            const seq = location.unit.model.sequence.byEntityKey[entityIndex]
             const comp_id = seq.compId.value(seq_id_begin - 1) // 1-indexed
             label = `[${comp_id}]${seq_id_begin}:${asym_id}`
         } else {

+ 14 - 8
src/mol-view/theme/color.ts

@@ -19,6 +19,8 @@ import { CrossLinkColorTheme } from './color/cross-link';
 import { ShapeGroupColorTheme } from './color/shape-group';
 import { CustomColorTheme } from './color/custom';
 import { ResidueNameColorTheme } from './color/residue-name';
+import { SequenceIdColorTheme } from './color/sequence-id';
+import { SecondaryStructureColorTheme } from './color/secondary-structure';
 
 export type LocationColor = (location: Location, isSecondary: boolean) => Color
 
@@ -49,16 +51,18 @@ export interface ColorTheme {
 
 export function ColorTheme(props: ColorThemeProps): ColorTheme {
     switch (props.name) {
-        case 'element-index': return ElementIndexColorTheme(props)
         case 'carbohydrate-symbol': return CarbohydrateSymbolColorTheme(props)
-        case 'cross-link': return CrossLinkColorTheme(props)
         case 'chain-id': return ChainIdColorTheme(props)
+        case 'cross-link': return CrossLinkColorTheme(props)
+        case 'custom': return CustomColorTheme(props)
+        case 'element-index': return ElementIndexColorTheme(props)
         case 'element-symbol': return ElementSymbolColorTheme(props)
         case 'residue-name': return ResidueNameColorTheme(props)
+        case 'secondary-structure': return SecondaryStructureColorTheme(props)
+        case 'sequence-id': return SequenceIdColorTheme(props)
+        case 'shape-group': return ShapeGroupColorTheme(props)
         case 'unit-index': return UnitIndexColorTheme(props)
         case 'uniform': return UniformColorTheme(props)
-        case 'shape-group': return ShapeGroupColorTheme(props)
-        case 'custom': return CustomColorTheme(props)
     }
 }
 
@@ -74,16 +78,18 @@ export interface ColorThemeProps {
 }
 
 export const ColorThemeInfo = {
-    'element-index': {},
     'carbohydrate-symbol': {},
-    'cross-link': {},
     'chain-id': {},
+    'cross-link': {},
+    'custom': {},
+    'element-index': {},
     'element-symbol': {},
     'residue-name': {},
+    'secondary-structure': {},
+    'sequence-id': {},
+    'shape-group': {},
     'unit-index': {},
     'uniform': {},
-    'shape-group': {},
-    'custom': {}
 }
 export type ColorThemeName = keyof typeof ColorThemeInfo
 export const ColorThemeNames = Object.keys(ColorThemeInfo)

+ 82 - 0
src/mol-view/theme/color/secondary-structure.ts

@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Color, ColorMap } from 'mol-util/color';
+import { StructureElement, Unit, Link, ElementIndex } from 'mol-model/structure';
+import { Location } from 'mol-model/location';
+import { ColorThemeProps, ColorTheme, TableLegend } from '../color';
+import { SecondaryStructureType, MoleculeType } from 'mol-model/structure/model/types';
+import { getElementMoleculeType } from 'mol-model/structure/util';
+
+// from Jmol http://jmol.sourceforge.net/jscolors/ (shapely)
+const SecondaryStructureColors = ColorMap({
+    'alphaHelix': 0xFF0080,
+    'threeTenHelix': 0xA00080,
+    'piHelix': 0x600080,
+    'betaTurn': 0x6080FF,
+    'betaStrand': 0xFFC800,
+    'coil': 0xFFFFFF,
+
+    'dna': 0xAE00FE,
+    'rna': 0xFD0162,
+
+    'carbohydrate': 0xA6A6FA
+})
+
+const DefaultSecondaryStructureColor = Color(0x808080)
+const Description = 'Assigns a color based on the type of secondary structure and basic molecule type.'
+
+export function secondaryStructureColor(unit: Unit, element: ElementIndex): Color {
+    let secStrucType = SecondaryStructureType.create(SecondaryStructureType.Flag.None)
+    if (Unit.isAtomic(unit)) {
+        secStrucType = unit.model.properties.secondaryStructure.type[unit.residueIndex[element]]
+    }
+
+    if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Helix)) {
+        if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Helix3Ten)) {
+            return SecondaryStructureColors.threeTenHelix
+        } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.HelixPi)) {
+            return SecondaryStructureColors.piHelix
+        }
+        return SecondaryStructureColors.alphaHelix
+    } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Beta)) {
+        return SecondaryStructureColors.betaStrand
+    } else if (SecondaryStructureType.is(secStrucType, SecondaryStructureType.Flag.Turn)) {
+        return SecondaryStructureColors.coil
+    } else {
+        const moleculeType = getElementMoleculeType(unit, element)
+        if (moleculeType === MoleculeType.DNA) {
+            return SecondaryStructureColors.dna
+        } else if (moleculeType === MoleculeType.RNA) {
+            return SecondaryStructureColors.rna
+        } else if (moleculeType === MoleculeType.saccharide) {
+            return SecondaryStructureColors.carbohydrate
+        } else if (moleculeType === MoleculeType.protein) {
+            return SecondaryStructureColors.coil
+        }
+    }
+    return DefaultSecondaryStructureColor
+}
+
+export function SecondaryStructureColorTheme(props: ColorThemeProps): ColorTheme {
+    function color(location: Location): Color {
+        if (StructureElement.isLocation(location)) {
+            return secondaryStructureColor(location.unit, location.element)
+        } else if (Link.isLocation(location)) {
+            return secondaryStructureColor(location.aUnit, location.aUnit.elements[location.aIndex])
+        }
+        return DefaultSecondaryStructureColor
+    }
+
+    return {
+        granularity: 'group',
+        color,
+        description: Description,
+        legend: TableLegend(Object.keys(SecondaryStructureColors).map(name => {
+            return [name, (SecondaryStructureColors as any)[name] as Color] as [string, Color]
+        }).concat([[ 'Other', DefaultSecondaryStructureColor ]]))
+    }
+}

+ 92 - 0
src/mol-view/theme/color/sequence-id.ts

@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Unit, StructureElement, Link, ElementIndex } from 'mol-model/structure';
+
+import { ColorScale, Color } from 'mol-util/color';
+import { Location } from 'mol-model/location';
+import { ColorThemeProps, ColorTheme } from '../color';
+import { ColorOther } from 'mol-util/color/tables';
+
+const DefaultColor = Color(0xCCCCCC)
+const Description = 'Gives every polymer residue a color based on its `seq_id` value.'
+
+function getSeqId(unit: Unit, element: ElementIndex): number {
+    const { model } = unit
+    switch (unit.kind) {
+        case Unit.Kind.Atomic:
+            const residueIndex = model.atomicHierarchy.residueAtomSegments.index[element]
+            return model.atomicHierarchy.residues.label_seq_id.value(residueIndex)
+        case Unit.Kind.Spheres:
+            return Math.round(
+                (model.coarseHierarchy.spheres.seq_id_begin.value(element) +
+                    model.coarseHierarchy.spheres.seq_id_end.value(element)) / 2
+            )
+        case Unit.Kind.Gaussians:
+            return Math.round(
+                (model.coarseHierarchy.gaussians.seq_id_begin.value(element) +
+                    model.coarseHierarchy.gaussians.seq_id_end.value(element)) / 2
+            )
+    }
+}
+
+function getSequenceLength(unit: Unit, element: ElementIndex) {
+    const { model } = unit
+    let entityId = ''
+    switch (unit.kind) {
+        case Unit.Kind.Atomic:
+            const chainIndex = model.atomicHierarchy.chainAtomSegments.index[element]
+            entityId = model.atomicHierarchy.chains.label_entity_id.value(chainIndex)
+            break
+        case Unit.Kind.Spheres:
+            entityId = model.coarseHierarchy.spheres.entity_id.value(element)
+            break
+        case Unit.Kind.Gaussians:
+            entityId = model.coarseHierarchy.gaussians.entity_id.value(element)
+            break
+    }
+    if (entityId === '') return 0
+    const entityIndex = model.entities.getEntityIndex(entityId)
+    if (entityIndex === -1) return 0
+    return model.sequence.byEntityKey[entityIndex].sequence.sequence.length
+}
+
+export function SequenceIdColorTheme(props: ColorThemeProps): ColorTheme {
+    const p = {
+        ...props,
+        colors: ColorOther.rainbow,
+        minLabel: 'Start',
+        maxLabel: 'End',
+    }
+
+    const scale = ColorScale.create(p)
+    const color = (location: Location): Color => {
+        if (StructureElement.isLocation(location)) {
+            const { unit, element } = location
+            const seq_id = getSeqId(unit, element)
+            if (seq_id > 0) {
+                scale.setDomain(0, getSequenceLength(unit, element) - 1)
+                return scale.color(seq_id)
+            }
+        } else if (Link.isLocation(location)) {
+            const { aUnit, aIndex } = location
+            const seq_id = getSeqId(aUnit, aUnit.elements[aIndex])
+            if (seq_id > 0) {
+                scale.setDomain(0, getSequenceLength(aUnit, aUnit.elements[aIndex]) - 1)
+                return scale.color(seq_id)
+            }
+        }
+        return DefaultColor
+    }
+
+    return {
+        granularity: 'group',
+        color,
+        description: Description,
+        // legend: scale ? TableLegend(table) : undefined
+        legend: scale ? scale.legend : undefined
+    }
+}