Browse Source

mol-model: Model.customData & G3d support improvements

David Sehnal 4 years ago
parent
commit
4cd7f0575e

+ 1 - 1
src/extensions/g3d/data.ts

@@ -22,7 +22,7 @@ export type G3dDataBlock = {
     header: G3dHeader,
     resolution: number,
     data: {
-        [HTMLBRElement in 'paternal' | 'maternal']: {
+        [haplotype: string]: {
             [ch: string]: {
                 start: number[]
                 x: number[],

+ 28 - 28
src/extensions/g3d/format.ts

@@ -15,7 +15,11 @@ import { StateAction, StateObjectRef } from '../../mol-state';
 import { Task } from '../../mol-task';
 import { ParamDefinition } from '../../mol-util/param-definition';
 import { G3dHeader, getG3dDataBlock, getG3dHeader } from './data';
-import { g3dHaplotypeQuery, G3dInfoPropertyProvider, G3dLabelProvider, trajectoryFromG3D } from './model';
+import { g3dHaplotypeQuery, G3dLabelProvider, trajectoryFromG3D, G3dSymbols, getG3dInfoData } from './model';
+import { StateTransforms } from '../../mol-plugin-state/transforms';
+import { createStructureRepresentationParams } from '../../mol-plugin-state/helpers/structure-representation-params';
+import { stringToWords } from '../../mol-util/string';
+import { objectForEach } from '../../mol-util/object';
 
 export const G3dProvider: TrajectoryFormatProvider = {
     label: 'G3D',
@@ -37,29 +41,29 @@ export const G3dProvider: TrajectoryFormatProvider = {
 async function defaultStructure(plugin: PluginContext, data: { trajectory: StateObjectRef<SO.Molecule.Trajectory> }) {
     const builder = plugin.builders.structure;
     const model = await builder.createModel(data.trajectory);
-    const modelProperties = await builder.insertModelProperties(model);
-    const structure = await builder.createStructure(modelProperties);
-
-    const m = await builder.tryCreateComponentFromExpression(structure, g3dHaplotypeQuery('maternal'), 'maternal', { label: 'Maternal' });
-    const p = await builder.tryCreateComponentFromExpression(structure, g3dHaplotypeQuery('paternal'), 'paternal', { label: 'Paternal' });
-
-    if (m) {
-        await builder.representation.addRepresentation(m, {
-            type: 'cartoon',
-            color: 'polymer-index',
-            size: 'uniform',
-            sizeParams: { value: 2 }
-        });
-    }
 
-    if (p) {
-        await builder.representation.addRepresentation(p, {
-            type: 'cartoon',
-            color: 'polymer-index',
-            size: 'uniform',
-            sizeParams: { value: 2 }
-        });
+    if (!model) return;
+    const structure = await builder.createStructure(model);
+
+    const info = getG3dInfoData(model.data!);
+    if (!info) return;
+
+    const components = plugin.build().to(structure);
+
+    const repr = createStructureRepresentationParams(plugin, void 0, {
+        type: 'cartoon',
+        color: 'polymer-index',
+        size: 'uniform',
+        sizeParams: { value: 0.25 }
+    });
+
+    for (const h of info.haplotypes) {
+        components
+            .apply(StateTransforms.Model.StructureSelectionFromExpression, { expression: g3dHaplotypeQuery(h), label: stringToWords(h) })
+            .apply(StateTransforms.Representation.StructureRepresentation3D, repr);
     }
+
+    await components.commit();
 }
 
 export class G3dHeaderObject extends SO.Create<{
@@ -157,16 +161,12 @@ export const G3DFormat = PluginBehavior.create<{ autoAttach: boolean, showToolti
     ctor: class extends PluginBehavior.Handler<{ autoAttach: boolean, showTooltip: boolean }> {
         register() {
             this.ctx.state.data.actions.add(LoadG3D);
-
-            DefaultQueryRuntimeTable.addCustomProp(G3dInfoPropertyProvider.descriptor);
-            this.ctx.customModelProperties.register(G3dInfoPropertyProvider, false);
+            objectForEach(G3dSymbols, s => DefaultQueryRuntimeTable.addSymbol(s));
             this.ctx.managers.lociLabels.addProvider(G3dLabelProvider);
         }
         unregister() {
             this.ctx.state.data.actions.remove(LoadG3D);
-
-            DefaultQueryRuntimeTable.removeCustomProp(G3dInfoPropertyProvider.descriptor);
-            this.ctx.customModelProperties.unregister(G3dInfoPropertyProvider.descriptor.name);
+            objectForEach(G3dSymbols, s => DefaultQueryRuntimeTable.removeSymbol(s));
             this.ctx.managers.lociLabels.removeProvider(G3dLabelProvider);
         }
     }

+ 60 - 35
src/extensions/g3d/model.ts

@@ -22,6 +22,7 @@ import { G3dDataBlock } from './data';
 import { Loci } from '../../mol-model/loci';
 import { LociLabelProvider } from '../../mol-plugin-state/manager/loci-label';
 import { OrderedSet } from '../../mol-data/int';
+import { Vec3 } from '../../mol-math/linear-algebra';
 
 interface NormalizedData {
     entity_id: string[],
@@ -32,6 +33,7 @@ interface NormalizedData {
     x: Float32Array,
     y: Float32Array,
     z: Float32Array,
+    r: Float32Array,
     haplotype: string[]
 }
 
@@ -50,25 +52,56 @@ function getColumns(block: G3dDataBlock) {
         x: new Float32Array(size),
         y: new Float32Array(size),
         z: new Float32Array(size),
+        r: new Float32Array(size),
         haplotype: new Array(size)
     };
 
+    const p = [Vec3(), Vec3(), Vec3()];
+
     let o = 0;
     objectForEach(data, (hs, h) => {
         objectForEach(hs, (chs, ch) => {
             const entity_id = `${ch}-${h}`;
-            for (let i = 0, _i = chs.start.length; i < _i; i++) {
+            const l =  chs.start.length;
+            if (l === 0) return;
+
+            let x = chs.x[0];
+            let y = chs.y[0];
+            let z = chs.z[0];
+
+            Vec3.set(p[0], x, y, z);
+            Vec3.set(p[2], x, y, z);
+
+            for (let i = 0; i < l; i++) {
                 normalized.entity_id[o] = entity_id;
                 normalized.chromosome[o] = ch;
                 normalized.start[o] = chs.start[i];
                 normalized.seq_id_begin[o] = o;
                 normalized.seq_id_end[o] = o;
-                normalized.x[o] = 10 * chs.x[i];
-                normalized.y[o] = 10 * chs.y[i];
-                normalized.z[o] = 10 * chs.z[i];
+
+                x = chs.x[i];
+                y = chs.y[i];
+                z = chs.z[i];
+
+                Vec3.set(p[1], x, y, z);
+                if (i + 1 < l) Vec3.set(p[2], chs.x[i + 1], chs.y[i + 1], chs.z[i + 1]);
+                else Vec3.set(p[2], x, y, z);
+
+                normalized.x[o] = x;
+                normalized.y[o] = y;
+                normalized.z[o] = z;
+                normalized.r[o] = 2 / 3 * Math.min(Vec3.distance(p[0], p[1]), Vec3.distance(p[1], p[2]));
                 normalized.haplotype[o] = h;
+
+                const _p = p[0];
+                p[0] = p[1];
+                p[1] = _p;
                 o++;
             }
+
+            if (l === 1) {
+                normalized.r[o - 1] = 1;
+            }
         });
     });
 
@@ -82,9 +115,6 @@ async function getTraj(ctx: RuntimeContext, data: G3dDataBlock) {
     const entityIds = new Array<string>(rowCount);
     const entityBuilder = new EntityBuilder();
 
-    const stride = normalized.seq_id_begin[1] - normalized.seq_id_begin[0];
-    const objectRadius = stride / 3500;
-
     const eName = { customName: '' };
     for (let i = 0; i < rowCount; ++i) {
         const e = normalized.entity_id[i];
@@ -104,7 +134,7 @@ async function getTraj(ctx: RuntimeContext, data: G3dDataBlock) {
         Cartn_y: Column.ofFloatArray(normalized.y),
         Cartn_z: Column.ofFloatArray(normalized.z),
 
-        object_radius: Column.ofConst(objectRadius, rowCount, Column.Schema.float),
+        object_radius: Column.ofFloatArray(normalized.r),
         rmsf: Column.ofConst(0, rowCount, Column.Schema.float),
         model_id: Column.ofConst(1, rowCount, Column.Schema.int),
     }, rowCount);
@@ -120,11 +150,12 @@ async function getTraj(ctx: RuntimeContext, data: G3dDataBlock) {
 
     const models = await createModels(basic, { kind: 'g3d', name: 'G3D', data }, ctx);
 
-    await G3dInfoPropertyProvider.attach({ runtime: ctx, assetManager: void 0 as any }, models.representative, {
-        resolution: data.resolution,
+    models.representative.customData.g3dInfo = {
+        haplotypes: Object.keys(data.data),
         haplotype: normalized.haplotype,
+        resolution: data.resolution,
         start: normalized.start
-    });
+    } as G3dInfoData;
 
     return models;
 }
@@ -139,40 +170,34 @@ export const G3dSymbols = {
     haplotype: QuerySymbolRuntime.Dynamic(CustomPropSymbol('g3d', 'haplotype', Type.Str),
         ctx => {
             if (Unit.isAtomic(ctx.element.unit)) return '';
-            const info = G3dInfoPropertyProvider.get(ctx.element.unit.model);
+            const info =  getG3dInfoData(ctx.element.unit.model);
+            if (!info) return '';
             const seqId = ctx.element.unit.model.coarseHierarchy.spheres.seq_id_begin.value(ctx.element.element);
-            return info.value?.haplotype[seqId] || '';
+            return info.haplotype[seqId] || '';
         }
     )
 };
 
-export function g3dHaplotypeQuery(haplotype: 'maternal' | 'paternal') {
+export function g3dHaplotypeQuery(haplotype: string) {
     return MS.struct.generator.atomGroups({
         'chain-test': MS.core.rel.eq([G3dSymbols.haplotype.symbol(), haplotype]),
     });
 }
 
-export const G3dInfoPropertyParams = {
-    haplotype: ParamDefinition.Value<string[]>([]),
-    start: ParamDefinition.Value<Int32Array>(new Int32Array(0)),
-    resolution: ParamDefinition.Numeric(0)
+export interface G3dInfoData {
+    haplotypes: string[],
+    haplotype: string[],
+    start: Int32Array,
+    resolution: number
 };
 
-export type G3dInfoPropertyParams = typeof G3dInfoPropertyParams
-export type G3dInfoPropertyParamsProps = ParamDefinition.Values<G3dInfoPropertyParams>
-
-export const G3dInfoPropertyProvider: CustomModelProperty.Provider<G3dInfoPropertyParams, G3dInfoPropertyParamsProps> = CustomModelProperty.createProvider({
-    label: 'G3d Info',
-    type: 'static',
-    isHidden: true,
-    defaultParams: G3dInfoPropertyParams,
-    descriptor: { name: 'g3d_info', symbols: G3dSymbols },
-    getParams: (data: Model) => G3dInfoPropertyParams,
-    isApplicable: (data: Model) => true,
-    obtain: async (ctx, data, props) => {
-        return { value: props as any };
-    }
-});
+export function setG3dInfoData(model: Model, data: G3dInfoData) {
+    model.customData.g3dInfo = data;
+}
+
+export function getG3dInfoData(model: Model): G3dInfoData | undefined {
+    return model.customData.g3dInfo;
+}
 
 export const G3dLabelProvider: LociLabelProvider = {
     label: (e: Loci): string | undefined => {
@@ -180,11 +205,11 @@ export const G3dLabelProvider: LociLabelProvider = {
 
         const first = e.elements[0];
         if (e.elements.length !== 1 || Unit.isAtomic(first.unit)) return;
-        const info = G3dInfoPropertyProvider.get(first.unit.model);
+        const info = getG3dInfoData(first.unit.model);
         if (!info) return;
 
         const eI = first.unit.elements[OrderedSet.getAt(first.indices, 0)];
         const seqId = first.unit.model.coarseHierarchy.spheres.seq_id_begin.value(eI);
-        return `<b>Start:</b> ${info.value?.start[seqId]} <small>| resolution ${info.value?.resolution}<small>`;
+        return `<b>Start:</b> ${info.start[seqId]} <small>| resolution ${info.resolution}<small>`;
     }
 };

+ 2 - 0
src/mol-model-formats/structure/basic/parser.ts

@@ -79,6 +79,7 @@ function createStandardModel(data: BasicData, atom_site: AtomSite, sourceIndex:
         coarseHierarchy: coarse.hierarchy,
         coarseConformation: coarse.conformation,
         properties,
+        customData: Object.create(null),
         customProperties: new CustomProperties(),
         _staticPropertyData: Object.create(null),
         _dynamicPropertyData: Object.create(null)
@@ -118,6 +119,7 @@ function createIntegrativeModel(data: BasicData, ihm: CoarseData, properties: Mo
         coarseHierarchy: coarse.hierarchy,
         coarseConformation: coarse.conformation,
         properties,
+        customData: Object.create(null),
         customProperties: new CustomProperties(),
         _staticPropertyData: Object.create(null),
         _dynamicPropertyData: Object.create(null)

+ 6 - 0
src/mol-model/structure/model/model.ts

@@ -72,6 +72,12 @@ export interface Model extends Readonly<{
         readonly structAsymMap: StructAsymMap
     },
 
+    /**
+     * Object that allows to store any custom related to the model,
+     * This is different from the custom properties to bypass having to store the data
+     * in the CustomProperty props.
+     */
+    customData: { [key: string]: any },
     customProperties: CustomProperties,
 
     /**

+ 7 - 1
src/mol-plugin-state/transforms/model.ts

@@ -421,7 +421,11 @@ const StructureFromModel = PluginStateTransform.BuiltIn({
     update: ({ a, b, oldParams, newParams }) => {
         if (!deepEqual(oldParams, newParams)) return StateTransformer.UpdateResult.Recreate;
         if (b.data.model === a.data) return StateTransformer.UpdateResult.Unchanged;
-        if (Model.getRoot(b.data.model) !== Model.getRoot(a.data) && a.data.atomicHierarchy !== b.data.model.atomicHierarchy) return StateTransformer.UpdateResult.Recreate;
+        if (Model.getRoot(b.data.model) !== Model.getRoot(a.data)
+            && (a.data.atomicHierarchy !== b.data.model.atomicHierarchy
+                || a.data.coarseHierarchy !== b.data.model.coarseHierarchy)) {
+            return StateTransformer.UpdateResult.Recreate;
+        }
 
         b.data = Structure.remapModel(b.data, a.data);
 
@@ -955,6 +959,8 @@ const CustomStructureProperties = PluginStateTransform.BuiltIn({
         });
     },
     update({ a, b, oldParams, newParams }, ctx: PluginContext) {
+        if (a.data !== b.data) return StateTransformer.UpdateResult.Recreate;
+
         return Task.create('Custom Props', async taskCtx => {
             b.data = a.data;
             b.label = a.label;