Browse Source

GlobalModelTransformInfo
- support in volume streaming
- export in ModelServer if transform param is present

David Sehnal 4 years ago
parent
commit
5b4c6743e7

+ 3 - 0
src/mol-model-formats/structure/mmcif.ts

@@ -18,6 +18,7 @@ import { AtomSiteAnisotrop } from './property/anisotropic';
 import { ComponentBond } from './property/bonds/chem_comp';
 import { StructConn } from './property/bonds/struct_conn';
 import { Trajectory } from '../../mol-model/structure';
+import { GlobalModelTransformInfo } from '../../mol-model/structure/model/properties/global-transform';
 
 function modelSymmetryFromMmcif(model: Model) {
     if (!MmcifFormat.is(model.sourceData)) return;
@@ -69,6 +70,8 @@ function structConnFromMmcif(model: Model) {
 }
 StructConn.Provider.formatRegistry.add('mmCIF', structConnFromMmcif);
 
+GlobalModelTransformInfo.Provider.formatRegistry.add('mmCIF', GlobalModelTransformInfo.fromMmCif, GlobalModelTransformInfo.hasData);
+
 //
 
 export { MmcifFormat };

+ 22 - 4
src/mol-model/structure/export/mmcif.ts

@@ -132,7 +132,8 @@ function getCustomPropCategories(customProp: CustomPropertyDescriptor, ctx: CifE
 type encode_mmCIF_categories_Params = {
     skipCategoryNames?: Set<string>,
     exportCtx?: CifExportContext,
-    copyAllCategories?: boolean
+    copyAllCategories?: boolean,
+    customProperties?: CustomPropertyDescriptor[]
 }
 
 /** Doesn't start a data block */
@@ -144,7 +145,7 @@ export function encode_mmCIF_categories(encoder: CifWriter.Encoder, structures:
     const ctx: CifExportContext = params?.exportCtx || CifExportContext.create(structures);
 
     if (params?.copyAllCategories && MmcifFormat.is(models[0].sourceData)) {
-        encode_mmCIF_categories_copyAll(encoder, ctx);
+        encode_mmCIF_categories_copyAll(encoder, ctx, params);
     } else {
         encode_mmCIF_categories_default(encoder, ctx, params);
     }
@@ -168,6 +169,14 @@ function encode_mmCIF_categories_default(encoder: CifWriter.Encoder, ctx: CifExp
         }
     }
 
+    if (params?.customProperties) {
+        for (const customProp of params?.customProperties) {
+            for (const [cat, propCtx] of getCustomPropCategories(customProp, ctx, _params)) {
+                encoder.writeCategory(cat, propCtx);
+            }
+        }
+    }
+
     for (const s of ctx.structures) {
         if (!s.hasCustomProperties) continue;
         for (const customProp of s.customPropertyDescriptors.all) {
@@ -178,7 +187,7 @@ function encode_mmCIF_categories_default(encoder: CifWriter.Encoder, ctx: CifExp
     }
 }
 
-function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExportContext) {
+function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExportContext, params?: encode_mmCIF_categories_Params) {
     const providedCategories = new Map<string, CifExportCategoryInfo>();
 
     for (const cat of Categories) {
@@ -188,12 +197,21 @@ function encode_mmCIF_categories_copyAll(encoder: CifWriter.Encoder, ctx: CifExp
     const mapping = atom_site_operator_mapping(ctx);
     if (mapping) providedCategories.set(mapping[0].name, mapping);
 
+    const _params = params || { };
     for (const customProp of ctx.firstModel.customProperties.all) {
-        for (const info of getCustomPropCategories(customProp, ctx)) {
+        for (const info of getCustomPropCategories(customProp, ctx, _params)) {
             providedCategories.set(info[0].name, info);
         }
     }
 
+    if (params?.customProperties) {
+        for (const customProp of params?.customProperties) {
+            for (const info of getCustomPropCategories(customProp, ctx, _params)) {
+                providedCategories.set(info[0].name, info);
+            }
+        }
+    }
+
     for (const s of ctx.structures) {
         if (!s.hasCustomProperties) continue;
         for (const customProp of s.customPropertyDescriptors.all) {

+ 0 - 7
src/mol-model/structure/model/properties/format-specific.ts

@@ -1,7 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-// TODO add access to things like MOL2 charge ...

+ 80 - 0
src/mol-model/structure/model/properties/global-transform.ts

@@ -0,0 +1,80 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Mat4, Tensor } from '../../../../mol-math/linear-algebra';
+import { FormatPropertyProvider } from '../../../../mol-model-formats/structure/common/property';
+import { CustomPropertyDescriptor } from '../../../custom-property';
+import { CifExportContext } from '../../structure';
+import { Model } from '../model';
+import { Column, Table } from '../../../../mol-data/db';
+import { CifWriter } from '../../../../mol-io/writer/cif';
+import { MmcifFormat } from '../../../../mol-model-formats/structure/mmcif';
+import { toTable } from '../../../../mol-io/reader/cif/schema';
+
+export namespace GlobalModelTransformInfo {
+    const CategoryName = 'molstar_global_model_transform_info' as const;
+    export const Schema = {
+        [CategoryName]: {
+            matrix: Column.Schema.Matrix(4, 4, Column.Schema.float)
+        }
+    };
+    export type Schema = typeof Schema
+
+    export const Descriptor = CustomPropertyDescriptor({
+        name: CategoryName,
+        cifExport: {
+            categories: [{
+                name: CategoryName,
+                instance(ctx: CifExportContext) {
+                    const mat = get(ctx.firstModel);
+                    if (!mat) return CifWriter.Category.Empty;
+                    const table = Table.ofRows(Schema.molstar_global_model_transform_info, [{ matrix: mat as unknown as Tensor.Data }]);
+                    return CifWriter.Category.ofTable(table);
+                }
+            }],
+            prefix: 'molstar'
+        }
+    });
+
+    export const Provider = FormatPropertyProvider.create<Mat4>(Descriptor);
+
+    export function attach(model: Model, matrix: Mat4) {
+        if (!model.customProperties.has(Descriptor)) {
+            model.customProperties.add(Descriptor);
+        }
+        Provider.set(model, matrix);
+    }
+
+    export function get(model: Model): Mat4 | undefined {
+        return Provider.get(model);
+    }
+
+    export function fromMmCif(model: Model) {
+        if (!MmcifFormat.is(model.sourceData)) return;
+
+        const cat = model.sourceData.data.frame.categories[CategoryName];
+        if (!cat) return;
+        const table = toTable(Schema[CategoryName], cat);
+        if (table._rowCount === 0) return;
+        return table.matrix.value(0) as unknown as Mat4;
+    }
+
+    export function hasData(model: Model) {
+        if (!MmcifFormat.is(model.sourceData)) return false;
+        const cat = model.sourceData.data.frame.categories[CategoryName];
+        return !!cat && cat.rowCount > 0;
+    }
+
+    export function writeMmCif(encoder: CifWriter.Encoder, matrix: Mat4) {
+        encoder.writeCategory({
+            name: CategoryName,
+            instance() {
+                const table = Table.ofRows(Schema.molstar_global_model_transform_info, [{ matrix: matrix as unknown as Tensor.Data }]);
+                return CifWriter.Category.ofTable(table);
+            }
+        });
+    }
+}

+ 4 - 2
src/mol-model/structure/structure/element/loci.ts

@@ -7,7 +7,7 @@
 
 import { UniqueArray } from '../../../../mol-data/generic';
 import { OrderedSet, SortedArray, Interval } from '../../../../mol-data/int';
-import { Vec3 } from '../../../../mol-math/linear-algebra';
+import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra';
 import { MolScriptBuilder as MS } from '../../../../mol-script/language/builder';
 import Structure from '../structure';
 import Unit from '../unit';
@@ -489,7 +489,7 @@ export namespace Loci {
 
     const boundaryHelper = new BoundaryHelper('98');
     const tempPosBoundary = Vec3();
-    export function getBoundary(loci: Loci): Boundary {
+    export function getBoundary(loci: Loci, transform?: Mat4): Boundary {
         boundaryHelper.reset();
 
         for (const e of loci.elements) {
@@ -499,6 +499,7 @@ export namespace Loci {
             for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
                 const eI = elements[OrderedSet.getAt(indices, i)];
                 pos(eI, tempPosBoundary);
+                if (transform) Vec3.transformMat4(tempPosBoundary, tempPosBoundary, transform);
                 boundaryHelper.includePositionRadius(tempPosBoundary, r(eI));
             }
         }
@@ -510,6 +511,7 @@ export namespace Loci {
             for (let i = 0, _i = OrderedSet.size(indices); i < _i; i++) {
                 const eI = elements[OrderedSet.getAt(indices, i)];
                 pos(eI, tempPosBoundary);
+                if (transform) Vec3.transformMat4(tempPosBoundary, tempPosBoundary, transform);
                 boundaryHelper.radiusPositionRadius(tempPosBoundary, r(eI));
             }
         }

+ 9 - 3
src/mol-plugin/behavior/dynamic/volume-streaming/behavior.ts

@@ -10,7 +10,7 @@ import { PluginStateObject } from '../../../../mol-plugin-state/objects';
 import { Volume, Grid } from '../../../../mol-model/volume';
 import { VolumeServerHeader, VolumeServerInfo } from './model';
 import { Box3D } from '../../../../mol-math/geometry';
-import { Vec3 } from '../../../../mol-math/linear-algebra';
+import { Mat4, Vec3 } from '../../../../mol-math/linear-algebra';
 import { Color } from '../../../../mol-util/color';
 import { PluginBehavior } from '../../behavior';
 import { LRUCache } from '../../../../mol-util/lru-cache';
@@ -23,6 +23,7 @@ import { StructureElement, Structure } from '../../../../mol-model/structure';
 import { PluginContext } from '../../../context';
 import { EmptyLoci, Loci, isEmptyLoci } from '../../../../mol-model/loci';
 import { Asset } from '../../../../mol-util/assets';
+import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform';
 
 export class VolumeStreaming extends PluginStateObject.CreateBehavior<VolumeStreaming.Behavior>({ name: 'Volume Streaming' }) { }
 
@@ -302,6 +303,7 @@ export namespace VolumeStreaming {
             }
         }
 
+        private _invTransform: Mat4 = Mat4();
         private getBoxFromLoci(loci: StructureElement.Loci | EmptyLoci): Box3D {
             if (Loci.isEmpty(loci)) {
                 return Box3D();
@@ -312,11 +314,16 @@ export namespace VolumeStreaming {
             const root = this.getStructureRoot();
             if (!root || root.obj?.data !== parent.obj?.data) return Box3D();
 
+            const transform = GlobalModelTransformInfo.get(root.obj?.data.models[0]!);
+            if (transform) Mat4.invert(this._invTransform, transform);
+
             const extendedLoci = StructureElement.Loci.extendToWholeResidues(loci);
-            const box = StructureElement.Loci.getBoundary(extendedLoci).box;
+            const box = StructureElement.Loci.getBoundary(extendedLoci, transform && !Number.isNaN(this._invTransform[0]) ? this._invTransform : void 0).box;
+
             if (StructureElement.Loci.size(extendedLoci) === 1) {
                 Box3D.expand(box, box, Vec3.create(1, 1, 1));
             }
+
             return box;
         }
 
@@ -360,7 +367,6 @@ export namespace VolumeStreaming {
             const switchedToSelection = params.entry.params.view.name === 'selection-box' && this.params && this.params.entry && this.params.entry.params && this.params.entry.params.view && this.params.entry.params.view.name !== 'selection-box';
 
             this.params = params;
-
             let box: Box3D | undefined = void 0, emptyData = false;
 
             switch (params.entry.params.view.name) {

+ 13 - 3
src/mol-plugin/behavior/dynamic/volume-streaming/transformers.ts

@@ -22,6 +22,7 @@ import { Box3D } from '../../../../mol-math/geometry';
 import { Vec3 } from '../../../../mol-math/linear-algebra';
 import { PluginConfig } from '../../../config';
 import { Model } from '../../../../mol-model/structure';
+import { GlobalModelTransformInfo } from '../../../../mol-model/structure/model/properties/global-transform';
 
 function addEntry(entries: InfoEntryProps[], method: VolumeServerInfo.Kind, dataId: string, emDefaultContourLevel: number) {
     entries.push({
@@ -253,20 +254,22 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
         channel: PD.Select<VolumeStreaming.ChannelType>('em', VolumeStreaming.ChannelTypeOptions, { isHidden: true })
     }
 })({
-    apply: ({ a, params: srcParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
+    apply: ({ a, params: srcParams, spine }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
         const channel = a.data.channels[srcParams.channel];
         if (!channel) return StateObject.Null;
 
         const params = createVolumeProps(a.data, srcParams.channel);
-
         const provider = VolumeRepresentationRegistry.BuiltIn.isosurface;
         const props = params.type.params || {};
         const repr = provider.factory({ webgl: plugin.canvas3d?.webgl, ...plugin.representation.volume.themes }, provider.getParams);
         repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params));
+        const structure = spine.getAncestorOfType(SO.Molecule.Structure)?.data;
+        const transform = structure?.models.length === 0 ? void 0 : GlobalModelTransformInfo.get(structure?.models[0]!);
         await repr.createOrUpdate(props, channel.data).runInContext(ctx);
+        if (transform) repr.setState({ transform });
         return new SO.Volume.Representation3D({ repr, source: a }, { label: `${Math.round(channel.isoValue.relativeValue * 100) / 100} σ [${srcParams.channel}]` });
     }),
-    update: ({ a, b, oldParams, newParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
+    update: ({ a, b, newParams, spine }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
         // TODO : check if params/underlying data/etc have changed; maybe will need to export "data" or some other "tag" in the Representation for this to work
 
         const channel = a.data.channels[newParams.channel];
@@ -277,6 +280,13 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
         const props = { ...b.data.repr.props, ...params.type.params };
         b.data.repr.setTheme(Theme.create(plugin.representation.volume.themes, { volume: channel.data }, params));
         await b.data.repr.createOrUpdate(props, channel.data).runInContext(ctx);
+
+        // TODO: set the transform here as well in case the structure moves?
+        //       doing this here now breaks the code for some reason...
+        // const structure = spine.getAncestorOfType(SO.Molecule.Structure)?.data;
+        // const transform = structure?.models.length === 0 ? void 0 : GlobalModelTransformInfo.get(structure?.models[0]!);
+        // if (transform) b.data.repr.setState({ transform });
+
         return StateTransformer.UpdateResult.Updated;
     })
 });

+ 3 - 0
src/servers/model/CHANGELOG.md

@@ -1,3 +1,6 @@
+# 0.9.5
+* Support molstar_global_model_transform_info category.
+
 # 0.9.4
 * bug fix for /ligand queries on metal ions
 

+ 2 - 0
src/servers/model/server/query.ts

@@ -30,6 +30,7 @@ import { MolEncoder } from '../../../mol-io/writer/mol/encoder';
 import { Mol2Encoder } from '../../../mol-io/writer/mol2/encoder';
 import { ComponentAtom } from '../../../mol-model-formats/structure/property/atoms/chem_comp';
 import { Mat4 } from '../../../mol-math/linear-algebra';
+import { GlobalModelTransformInfo } from '../../../mol-model/structure/model/properties/global-transform';
 
 export interface Stats {
     structure: StructureWrapper,
@@ -247,6 +248,7 @@ async function resolveJobEntry(entry: JobEntry, structure: StructureWrapper, enc
 
         if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter(entry.queryDefinition.filter);
         if (result.length > 0) encode_mmCIF_categories(encoder, result, { copyAllCategories: entry.copyAllCategories });
+        if (entry.transform && !Mat4.isIdentity(entry.transform)) GlobalModelTransformInfo.writeMmCif(encoder, entry.transform);
         if (!entry.copyAllCategories && entry.queryDefinition.filter) encoder.setFilter();
         perf.end('encode');
 

+ 1 - 1
src/servers/model/version.ts

@@ -4,4 +4,4 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-export default '0.9.4';
+export default '0.9.5';