Browse Source

Best Database Sequence Mapping property
- assigned based on atom_site.db_name/_acc/_num/_res CIF fields
- added basic color theme

dsehnal 3 years ago
parent
commit
69024152cb

+ 95 - 0
src/mol-model-props/sequence/best-database-mapping.ts

@@ -0,0 +1,95 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Column } from '../../mol-data/db';
+import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
+import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
+import { Model } from '../../mol-model/structure';
+import { StructureElement } from '../../mol-model/structure/structure';
+import { CustomModelProperty } from '../common/custom-model-property';
+
+export { BestDatabaseSequenceMapping };
+
+interface BestDatabaseSequenceMapping {
+    readonly dbName: string[],
+    readonly accession: string[],
+    readonly num: number[],
+    readonly residue: string[]
+}
+
+namespace BestDatabaseSequenceMapping {
+    export const Provider: CustomModelProperty.Provider<{}, BestDatabaseSequenceMapping> = CustomModelProperty.createProvider({
+        label: 'Best Database Sequence Mapping',
+        descriptor: CustomPropertyDescriptor({
+            name: 'molstar_best_database_sequence_mapping'
+        }),
+        type: 'static',
+        defaultParams: {},
+        getParams: () => ({}),
+        isApplicable: (data: Model) => MmcifFormat.is(data.sourceData) && data.sourceData.data.frame.categories?.atom_site.fieldNames.indexOf('db_name') >= 0,
+        obtain: async (ctx, data) => {
+            return { value: fromCif(data) };
+        }
+    });
+
+    export function getKey(loc: StructureElement.Location) {
+        const model = loc.unit.model;
+        const data = Provider.get(model).value;
+        if (!data) return '';
+        const eI = loc.unit.elements[loc.element];
+        const rI = model.atomicHierarchy.residueAtomSegments.offsets[eI];
+        return data.accession[rI];
+    }
+
+    export function getLabel(loc: StructureElement.Location) {
+        const model = loc.unit.model;
+        const data = Provider.get(model).value;
+        if (!data) return;
+        const eI = loc.unit.elements[loc.element];
+        const rI = model.atomicHierarchy.residueAtomSegments.offsets[eI];
+        const dbName = data.dbName[rI];
+        if (!dbName) return;
+        return `${dbName} ${data.accession[rI]} ${data.num[rI]} ${data.residue[rI]}`;
+    }
+
+    function fromCif(model: Model): BestDatabaseSequenceMapping | undefined {
+        if (!MmcifFormat.is(model.sourceData)) return;
+
+        const { atom_site } = model.sourceData.data.frame.categories;
+        const db_name = atom_site.getField('db_name');
+        const db_acc = atom_site.getField('db_acc');
+        const db_num = atom_site.getField('db_num');
+        const db_res = atom_site.getField('db_res');
+
+        if (!db_name || !db_acc || !db_num || !db_res) return;
+
+        const { atomSourceIndex } = model.atomicHierarchy;
+        const { count, offsets: residueOffsets } = model.atomicHierarchy.residueAtomSegments;
+        const dbName = new Array<string>(count);
+        const accession = new Array<string>(count);
+        const num = new Array<number>(count);
+        const residue = new Array<string>(count);
+
+        for (let i = 0; i < count; i++) {
+            const row = atomSourceIndex.value(residueOffsets[i]);
+
+            if (db_name.valueKind(row) !== Column.ValueKind.Present) {
+                dbName[row] = '';
+                accession[row] = '';
+                num[row] = 0;
+                residue[row] = '';
+                continue;
+            }
+
+            dbName[row] = db_name.str(row);
+            accession[row] = db_acc.str(row);
+            num[row] = db_num.int(row);
+            residue[row] = db_res.str(row);
+        }
+
+        return { dbName, accession, num, residue };
+    }
+}

+ 105 - 0
src/mol-model-props/sequence/themes/best-database-mapping.ts

@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Location } from '../../../mol-model/location';
+import { Bond, StructureElement, Unit } from '../../../mol-model/structure';
+import { ColorTheme, LocationColor } from '../../../mol-theme/color';
+import { ThemeDataContext } from '../../../mol-theme/theme';
+import { Color } from '../../../mol-util/color';
+import { getPalette, getPaletteParams } from '../../../mol-util/color/palette';
+import { ParamDefinition as PD } from '../../../mol-util/param-definition';
+import { CustomProperty } from '../../common/custom-property';
+import { BestDatabaseSequenceMapping } from '../best-database-mapping';
+
+const DefaultColor = Color(0xFAFAFA);
+const Description = 'Assigns a color based on best dababase sequence mapping.';
+
+// same colors for same accessions
+const globalAccessionMap = new Map<string, number>();
+
+export const BestDatabaseSequenceMappingColorThemeParams = {
+    ...getPaletteParams({ type: 'colors', colorList: 'set-3' }),
+};
+export type BestDatabaseSequenceMappingColorThemeParams = typeof BestDatabaseSequenceMappingColorThemeParams
+export function getBestDatabaseSequenceMappingColorThemeParams(ctx: ThemeDataContext) {
+    return BestDatabaseSequenceMappingColorThemeParams; // TODO return copy
+}
+export function BestDatabaseSequenceMappingColorTheme(ctx: ThemeDataContext, props: PD.Values<BestDatabaseSequenceMappingColorThemeParams>): ColorTheme<BestDatabaseSequenceMappingColorThemeParams> {
+    let color: LocationColor;
+
+    if (ctx.structure) {
+        for (const m of ctx.structure.models) {
+            const mapping = BestDatabaseSequenceMapping.Provider.get(m).value;
+            if (!mapping) continue;
+            for (const acc of mapping.accession) {
+                if (!acc) continue;
+                globalAccessionMap.set(acc, globalAccessionMap.size);
+            }
+        }
+
+        const l = StructureElement.Location.create(ctx.structure);
+        const palette = getPalette(globalAccessionMap.size + 1, props, { valueLabel: i => `${i}` });
+        const colorMap = new Map<string, Color>();
+
+        const getColor = (location: StructureElement.Location) => {
+            const key = BestDatabaseSequenceMapping.getKey(location);
+            if (!key) return DefaultColor;
+
+            if (colorMap.has(key)) return colorMap.get(key)!;
+
+            const color = palette.color(globalAccessionMap.get(key)!);
+            colorMap.set(key, color);
+            return color;
+        };
+
+        color = (location: Location): Color => {
+            if (StructureElement.Location.is(location) && Unit.isAtomic(location.unit)) {
+                return getColor(location);
+            } else if (Bond.isLocation(location)) {
+                l.unit = location.aUnit;
+                l.element = location.aUnit.elements[location.aIndex];
+                return getColor(l);
+            }
+            return DefaultColor;
+        };
+    } else {
+        color = () => DefaultColor;
+    }
+
+    return {
+        factory: BestDatabaseSequenceMappingColorTheme,
+        granularity: 'group',
+        preferSmoothing: true,
+        color,
+        props,
+        description: Description,
+    };
+}
+
+export const BestDatabaseSequenceMappingColorThemeProvider: ColorTheme.Provider<BestDatabaseSequenceMappingColorThemeParams, 'best-sequence-database-mapping'> = {
+    name: 'best-sequence-database-mapping',
+    label: 'Best Database Sequence Mapping',
+    category: ColorTheme.Category.Residue,
+    factory: BestDatabaseSequenceMappingColorTheme,
+    getParams: getBestDatabaseSequenceMappingColorThemeParams,
+    defaultValues: PD.getDefaultValues(BestDatabaseSequenceMappingColorThemeParams),
+    isApplicable: (ctx: ThemeDataContext) => !!ctx.structure?.models.some(m => BestDatabaseSequenceMapping.Provider.isApplicable(m)),
+    ensureCustomProperties: {
+        attach: async (ctx: CustomProperty.Context, data: ThemeDataContext) => {
+            if (!data.structure) return;
+
+            for (const m of data.structure.models) {
+                await BestDatabaseSequenceMapping.Provider.attach(ctx, m, void 0, true);
+            }
+        },
+        detach: (data) => {
+            if (!data.structure) return;
+            for (const m of data.structure.models) {
+                m.customProperties.reference(BestDatabaseSequenceMapping.Provider.descriptor, false);
+            }
+        }
+    }
+};

+ 1 - 0
src/mol-plugin/behavior/dynamic/custom-props.ts

@@ -11,5 +11,6 @@ export { AccessibleSurfaceArea } from './custom-props/computed/accessible-surfac
 export { Interactions } from './custom-props/computed/interactions';
 export { SecondaryStructure } from './custom-props/computed/secondary-structure';
 export { ValenceModel } from './custom-props/computed/valence-model';
+export { BestDatabaseSequenceMapping } from './custom-props/sequence/best-database-mapping';
 
 export { CrossLinkRestraint } from './custom-props/integrative/cross-link-restraint';

+ 69 - 0
src/mol-plugin/behavior/dynamic/custom-props/sequence/best-database-mapping.ts

@@ -0,0 +1,69 @@
+/**
+ * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { OrderedSet } from '../../../../../mol-data/int';
+import { BestDatabaseSequenceMapping as BestDatabaseSequenceMappingProp } from '../../../../../mol-model-props/sequence/best-database-mapping';
+import { BestDatabaseSequenceMappingColorThemeProvider } from '../../../../../mol-model-props/sequence/themes/best-database-mapping';
+import { Loci } from '../../../../../mol-model/loci';
+import { StructureElement } from '../../../../../mol-model/structure';
+import { ParamDefinition as PD } from '../../../../../mol-util/param-definition';
+import { PluginBehavior } from '../../../behavior';
+
+export const BestDatabaseSequenceMapping = PluginBehavior.create<{ autoAttach: boolean, showTooltip: boolean }>({
+    name: 'best-sequence-database-mapping-prop',
+    category: 'custom-props',
+    display: { name: 'Best Database Sequence Mapping' },
+    ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
+        private provider = BestDatabaseSequenceMappingProp.Provider
+
+        private labelProvider = {
+            label: (loci: Loci): string | undefined => {
+                if (!this.params.showTooltip) return;
+                return bestDatabaseSequenceMappingLabel(loci);
+            }
+        }
+
+        update(p: { autoAttach: boolean, showTooltip: boolean }) {
+            let updated = (
+                this.params.autoAttach !== p.autoAttach ||
+                this.params.showTooltip !== p.showTooltip
+            );
+            this.params.autoAttach = p.autoAttach;
+            this.params.showTooltip = p.showTooltip;
+            this.ctx.customStructureProperties.setDefaultAutoAttach(this.provider.descriptor.name, this.params.autoAttach);
+            return updated;
+        }
+
+        register(): void {
+            this.ctx.customModelProperties.register(this.provider, this.params.autoAttach);
+            this.ctx.representation.structure.themes.colorThemeRegistry.add(BestDatabaseSequenceMappingColorThemeProvider);
+            this.ctx.managers.lociLabels.addProvider(this.labelProvider);
+        }
+
+        unregister() {
+            this.ctx.customModelProperties.unregister(this.provider.descriptor.name);
+            this.ctx.representation.structure.themes.colorThemeRegistry.remove(BestDatabaseSequenceMappingColorThemeProvider);
+            this.ctx.managers.lociLabels.removeProvider(this.labelProvider);
+        }
+    },
+    params: () => ({
+        autoAttach: PD.Boolean(true),
+        showTooltip: PD.Boolean(true)
+    })
+});
+
+//
+
+function bestDatabaseSequenceMappingLabel(loci: Loci): string | undefined {
+    if(loci.kind === 'element-loci') {
+        if (loci.elements.length === 0) return;
+
+        const e = loci.elements[0];
+        const u = e.unit;
+        const se = StructureElement.Location.create(loci.structure, u, u.elements[OrderedSet.getAt(e.indices, 0)]);
+        return BestDatabaseSequenceMappingProp.getLabel(se);
+    }
+}

+ 1 - 0
src/mol-plugin/spec.ts

@@ -120,6 +120,7 @@ export const DefaultPluginSpec = (): PluginSpec => ({
 
         PluginSpec.Behavior(PluginBehaviors.CustomProps.StructureInfo),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.AccessibleSurfaceArea),
+        PluginSpec.Behavior(PluginBehaviors.CustomProps.BestDatabaseSequenceMapping),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.Interactions),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.SecondaryStructure),
         PluginSpec.Behavior(PluginBehaviors.CustomProps.ValenceModel),