Bladeren bron

added basic volume support to plugin

Alexander Rose 6 jaren geleden
bovenliggende
commit
33ec88b6ed

+ 2 - 4
src/mol-io/reader/_spec/ccp4.spec.ts

@@ -4,15 +4,13 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import CCP4 from '../ccp4/parser'
-import { FileHandle } from '../../common/file-handle';
+import * as CCP4 from '../ccp4/parser'
 
 const ccp4Buffer = new Uint8Array(4 * 64)
 
 describe('ccp4 reader', () => {
     it('basic', async () => {
-        const file = FileHandle.fromBuffer(ccp4Buffer)
-        const parsed = await CCP4(file).run();
+        const parsed = await CCP4.parse(ccp4Buffer).run();
 
         if (parsed.isError) {
             console.log(parsed)

+ 10 - 7
src/mol-io/reader/ccp4/parser.ts

@@ -5,11 +5,11 @@
  */
 
 import { Task, RuntimeContext } from 'mol-task';
-import * as Schema from './schema'
+import { Ccp4File, Ccp4Header } from './schema'
 import Result from '../result'
 import { FileHandle } from '../../common/file-handle';
 
-async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Result<Schema.Ccp4File>> {
+async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Result<Ccp4File>> {
     await ctx.update({ message: 'Parsing CCP4 file...' });
 
     const { buffer } = await file.readBuffer(0, file.length)
@@ -39,7 +39,7 @@ async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Res
         }
     }
 
-    const header: Schema.Ccp4Header = {
+    const header: Ccp4Header = {
         NC: intView[0],
         NR: intView[1],
         NS: intView[2],
@@ -116,12 +116,15 @@ async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Res
         }
     }
 
-    const result: Schema.Ccp4File = { header, values };
+    const result: Ccp4File = { header, values };
     return Result.success(result);
 }
 
-export function parse(file: FileHandle) {
-    return Task.create<Result<Schema.Ccp4File>>('Parse CCP4', ctx => parseInternal(file, ctx));
+export function parseFile(file: FileHandle) {
+    return Task.create<Result<Ccp4File>>('Parse CCP4', ctx => parseInternal(file, ctx));
 }
 
-export default parse;
+export function parse(buffer: Uint8Array) {
+    const file = FileHandle.fromBuffer(buffer)
+    return Task.create<Result<Ccp4File>>('Parse CCP4', ctx => parseInternal(file, ctx));
+}

+ 6 - 0
src/mol-plugin/context.ts

@@ -25,6 +25,7 @@ import { Color } from 'mol-util/color';
 import { LociLabelEntry, LociLabelManager } from './util/loci-label-manager';
 import { ajaxGet } from 'mol-util/data-source';
 import { CustomPropertyRegistry } from './util/custom-prop-registry';
+import { VolumeRepresentationRegistry } from 'mol-repr/volume/registry';
 
 export class PluginContext {
     private disposed = false;
@@ -76,6 +77,11 @@ export class PluginContext {
         themeCtx: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext
     }
 
+    readonly volumeRepresentation = {
+        registry: new VolumeRepresentationRegistry(),
+        themeCtx: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext
+    }
+
     readonly customModelProperties = new CustomPropertyRegistry();
 
     initViewer(canvas: HTMLCanvasElement, container: HTMLDivElement) {

+ 6 - 2
src/mol-plugin/index.ts

@@ -11,7 +11,7 @@ import * as React from 'react';
 import * as ReactDOM from 'react-dom';
 import { PluginCommands } from './command';
 import { PluginSpec } from './spec';
-import { DownloadStructure, CreateComplexRepresentation, OpenStructure } from './state/actions/basic';
+import { DownloadStructure, CreateComplexRepresentation, OpenStructure, OpenVolume } from './state/actions/basic';
 import { StateTransforms } from './state/transforms';
 import { PluginBehaviors } from './behavior';
 
@@ -24,14 +24,18 @@ const DefaultSpec: PluginSpec = {
     actions: [
         PluginSpec.Action(DownloadStructure),
         PluginSpec.Action(OpenStructure),
+        PluginSpec.Action(OpenVolume),
         PluginSpec.Action(CreateComplexRepresentation),
         PluginSpec.Action(StateTransforms.Data.Download),
         PluginSpec.Action(StateTransforms.Data.ParseCif),
+        PluginSpec.Action(StateTransforms.Data.ParseCcp4),
         PluginSpec.Action(StateTransforms.Model.StructureAssemblyFromModel),
         PluginSpec.Action(StateTransforms.Model.StructureFromModel),
         PluginSpec.Action(StateTransforms.Model.ModelFromTrajectory),
+        PluginSpec.Action(StateTransforms.Model.VolumeFromCcp4),
         PluginSpec.Action(StateTransforms.Representation.StructureRepresentation3D),
-        PluginSpec.Action(StateTransforms.Representation.ExplodeStructureRepresentation3D)
+        PluginSpec.Action(StateTransforms.Representation.ExplodeStructureRepresentation3D),
+        PluginSpec.Action(StateTransforms.Representation.VolumeRepresentation3D),
     ],
     behaviors: [
         PluginSpec.Behavior(PluginBehaviors.Representation.HighlightLoci),

+ 28 - 2
src/mol-plugin/state/actions/basic.ts

@@ -1,7 +1,8 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { PluginContext } from 'mol-plugin/context';
@@ -13,7 +14,7 @@ import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { PluginStateObject } from '../objects';
 import { StateTransforms } from '../transforms';
 import { Download } from '../transforms/data';
-import { StructureRepresentation3DHelpers } from '../transforms/representation';
+import { StructureRepresentation3DHelpers, VolumeRepresentation3DHelpers } from '../transforms/representation';
 
 // TODO: "structure parser provider"
 
@@ -156,4 +157,29 @@ export const UpdateTrajectory = StateAction.build({
     }
 
     return state.update(update);
+});
+
+//
+
+function createVolumeTree(ctx: PluginContext, b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>): StateTree {
+    let root = b
+        .apply(StateTransforms.Data.ParseCcp4)
+        .apply(StateTransforms.Model.VolumeFromCcp4)
+        .apply(StateTransforms.Representation.VolumeRepresentation3D,
+            VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface'));
+
+    return root.getTree();
+}
+
+export const OpenVolume = StateAction.build({
+    display: { name: 'Open Volume', description: 'Load a volume from file and create its default visual' },
+    from: PluginStateObject.Root,
+    params: {
+        file: PD.File({ accept: '.ccp4,.mrc'}),
+        // format: PD.Select('auto', [['auto', 'Automatic'], ['ccp4', 'CCP4'], ['mrc', 'MRC']]),
+    }
+})(({ params, state }, ctx: PluginContext) => {
+    const b = state.build();
+    const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: true });
+    return state.update(createVolumeTree(ctx, data));
 });

+ 4 - 1
src/mol-plugin/state/objects.ts

@@ -1,7 +1,8 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { CifFile } from 'mol-io/reader/cif';
@@ -12,6 +13,7 @@ import { Representation } from 'mol-repr/representation';
 import { StructureRepresentation } from 'mol-repr/structure/representation';
 import { VolumeRepresentation } from 'mol-repr/volume/representation';
 import { StateObject, Transformer } from 'mol-state';
+import { Ccp4File } from 'mol-io/reader/ccp4/schema';
 
 export type TypeClass = 'root' | 'data' | 'prop'
 
@@ -56,6 +58,7 @@ export namespace PluginStateObject {
     export namespace Format {
         export class Json extends Create<any>({ name: 'JSON Data', typeClass: 'Data' }) { }
         export class Cif extends Create<CifFile>({ name: 'CIF File', typeClass: 'Data' }) { }
+        export class Ccp4 extends Create<Ccp4File>({ name: 'CCP4 File', typeClass: 'Data' }) { }
     }
 
     export namespace Molecule {

+ 20 - 1
src/mol-plugin/state/transforms/data.ts

@@ -1,7 +1,8 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { PluginStateTransform } from '../objects';
@@ -12,6 +13,7 @@ import { PluginContext } from 'mol-plugin/context';
 import { ParamDefinition as PD } from 'mol-util/param-definition';
 import { Transformer } from 'mol-state';
 import { readFromFile } from 'mol-util/data-source';
+import * as CCP4 from 'mol-io/reader/ccp4/parser'
 
 export { Download }
 type Download = typeof Download
@@ -90,4 +92,21 @@ const ParseCif = PluginStateTransform.BuiltIn({
             return new SO.Format.Cif(parsed.result);
         });
     }
+});
+
+export { ParseCcp4 }
+type ParseCcp4 = typeof ParseCcp4
+const ParseCcp4 = PluginStateTransform.BuiltIn({
+    name: 'parse-ccp4',
+    display: { name: 'Parse CCP4', description: 'Parse CCP4 from Binary data' },
+    from: [SO.Data.Binary],
+    to: SO.Format.Ccp4
+})({
+    apply({ a }) {
+        return Task.create('Parse CCP4', async ctx => {
+            const parsed = await CCP4.parse(a.data).runInContext(ctx);
+            if (parsed.isError) throw new Error(parsed.message);
+            return new SO.Format.Ccp4(parsed.result);
+        });
+    }
 });

+ 39 - 12
src/mol-plugin/state/transforms/model.ts

@@ -1,7 +1,8 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 import { PluginStateTransform } from '../objects';
@@ -15,6 +16,8 @@ import { MolScriptBuilder } from 'mol-script/language/builder';
 import { StateObject } from 'mol-state';
 import { PluginContext } from 'mol-plugin/context';
 import { stringToWords } from 'mol-util/string';
+import { volumeFromCcp4 } from 'mol-model/volume/formats/ccp4';
+import { Vec3 } from 'mol-math/linear-algebra';
 
 export { TrajectoryFromMmCif }
 type TrajectoryFromMmCif = typeof TrajectoryFromMmCif
@@ -38,8 +41,8 @@ const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({
             if (!block) throw new Error(`Data block '${[header]}' not found.`);
             const models = await Model.create(Format.mmCIF(block)).runInContext(ctx);
             if (models.length === 0) throw new Error('No models found.');
-            const label = { label: models[0].label, description: `${models.length} model${models.length === 1 ? '' : 's'}` };
-            return new SO.Molecule.Trajectory(models, label);
+            const props = { label: models[0].label, description: `${models.length} model${models.length === 1 ? '' : 's'}` };
+            return new SO.Molecule.Trajectory(models, props);
         });
     }
 });
@@ -58,8 +61,8 @@ const ModelFromTrajectory = PluginStateTransform.BuiltIn({
     apply({ a, params }) {
         if (params.modelIndex < 0 || params.modelIndex >= a.data.length) throw new Error(`Invalid modelIndex ${params.modelIndex}`);
         const model = a.data[params.modelIndex];
-        const label = { label: `Model ${model.modelNum}` };
-        return new SO.Molecule.Model(model, label);
+        const props = { label: `Model ${model.modelNum}` };
+        return new SO.Molecule.Model(model, props);
     }
 });
 
@@ -73,8 +76,8 @@ const StructureFromModel = PluginStateTransform.BuiltIn({
 })({
     apply({ a }) {
         let s = Structure.ofModel(a.data);
-        const label = { label: a.data.label, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` };
-        return new SO.Molecule.Structure(s, label);
+        const props = { label: a.data.label, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` };
+        return new SO.Molecule.Structure(s, props);
     }
 });
 
@@ -111,8 +114,8 @@ const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({
             }
 
             const s = await StructureSymmetry.buildAssembly(base, id!).runInContext(ctx);
-            const label = { label: `Assembly ${id}`, description: structureDesc(s) };
-            return new SO.Molecule.Structure(s, label);
+            const props = { label: `Assembly ${id}`, description: structureDesc(s) };
+            return new SO.Molecule.Structure(s, props);
         })
     }
 });
@@ -134,8 +137,8 @@ const StructureSelection = PluginStateTransform.BuiltIn({
         const compiled = compile<Sel>(params.query);
         const result = compiled(new QueryContext(a.data));
         const s = Sel.unionStructure(result);
-        const label = { label: `${params.label || 'Selection'}`, description: structureDesc(s) };
-        return new SO.Molecule.Structure(s, label);
+        const props = { label: `${params.label || 'Selection'}`, description: structureDesc(s) };
+        return new SO.Molecule.Structure(s, props);
     }
 });
 
@@ -190,4 +193,28 @@ async function attachProps(model: Model, ctx: PluginContext, taskCtx: RuntimeCon
         const p = ctx.customModelProperties.get(name);
         await p.attach(model).runInContext(taskCtx);
     }
-}
+}
+
+//
+
+export { VolumeFromCcp4 }
+type VolumeFromCcp4 = typeof VolumeFromCcp4
+const VolumeFromCcp4 = PluginStateTransform.BuiltIn({
+    name: 'volume-from-ccp4',
+    display: { name: 'Volume from CCP4/MRC', description: 'Create Volume from CCP4/MRC data' },
+    from: SO.Format.Ccp4,
+    to: SO.Volume.Data,
+    params(a) {
+        return {
+            voxelSize: PD.Vec3(Vec3.create(1, 1, 1))
+        };
+    }
+})({
+    apply({ a, params }) {
+        return Task.create('Create volume from CCP4/MRC', async ctx => {
+            const volume = await volumeFromCcp4(a.data, params).runInContext(ctx)
+            const props = { label: 'Volume' };
+            return new SO.Volume.Data(volume, props);
+        });
+    }
+});

+ 87 - 1
src/mol-plugin/state/transforms/representation.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -16,6 +16,9 @@ import { BuiltInStructureRepresentationsName } from 'mol-repr/structure/registry
 import { Structure } from 'mol-model/structure';
 import { StructureParams } from 'mol-repr/structure/representation';
 import { ExplodeRepresentation3D } from 'mol-plugin/behavior/dynamic/representation';
+import { VolumeData } from 'mol-model/volume';
+import { BuiltInVolumeRepresentationsName } from 'mol-repr/volume/registry';
+import { VolumeParams } from 'mol-repr/volume/representation';
 
 export namespace StructureRepresentation3DHelpers {
     export function getDefaultParams(ctx: PluginContext, name: BuiltInStructureRepresentationsName, structure: Structure, structureParams?: Partial<PD.Values<StructureParams>>): Transformer.Params<StructureRepresentation3D> {
@@ -120,4 +123,87 @@ const ExplodeStructureRepresentation3D = PluginStateTransform.BuiltIn({
             return updated ? Transformer.UpdateResult.Updated : Transformer.UpdateResult.Unchanged;
         });
     }
+});
+
+//
+
+export namespace VolumeRepresentation3DHelpers {
+    export function getDefaultParams(ctx: PluginContext, name: BuiltInVolumeRepresentationsName, volume: VolumeData, volumeParams?: Partial<PD.Values<VolumeParams>>): Transformer.Params<VolumeRepresentation3D> {
+        const type = ctx.volumeRepresentation.registry.get(name);
+
+        const themeDataCtx = { volume };
+        const colorParams = ctx.volumeRepresentation.themeCtx.colorThemeRegistry.get(type.defaultColorTheme).getParams(themeDataCtx);
+        const sizeParams = ctx.volumeRepresentation.themeCtx.sizeThemeRegistry.get(type.defaultSizeTheme).getParams(themeDataCtx)
+        const volumeDefaultParams = PD.getDefaultValues(type.getParams(ctx.volumeRepresentation.themeCtx, volume))
+        return ({
+            type: { name, params: volumeParams ? { ...volumeDefaultParams, ...volumeParams } : volumeDefaultParams },
+            colorTheme: { name: type.defaultColorTheme, params: PD.getDefaultValues(colorParams) },
+            sizeTheme: { name: type.defaultSizeTheme, params: PD.getDefaultValues(sizeParams) }
+        })
+    }
+
+    export function getDefaultParamsStatic(ctx: PluginContext, name: BuiltInVolumeRepresentationsName, volumeParams?: Partial<PD.Values<VolumeParams>>): Transformer.Params<VolumeRepresentation3D> {
+        const type = ctx.volumeRepresentation.registry.get(name);
+        const colorParams = ctx.volumeRepresentation.themeCtx.colorThemeRegistry.get(type.defaultColorTheme).defaultValues;
+        const sizeParams = ctx.volumeRepresentation.themeCtx.sizeThemeRegistry.get(type.defaultSizeTheme).defaultValues
+        return ({
+            type: { name, params: volumeParams ? { ...type.defaultValues, ...volumeParams } : type.defaultValues },
+            colorTheme: { name: type.defaultColorTheme, params: colorParams },
+            sizeTheme: { name: type.defaultSizeTheme, params: sizeParams }
+        })
+    }
+}
+export { VolumeRepresentation3D }
+type VolumeRepresentation3D = typeof VolumeRepresentation3D
+const VolumeRepresentation3D = PluginStateTransform.BuiltIn({
+    name: 'volume-representation-3d',
+    display: '3D Representation',
+    from: SO.Volume.Data,
+    to: SO.Volume.Representation3D,
+    params: (a, ctx: PluginContext) => {
+        const { registry, themeCtx } = ctx.volumeRepresentation
+        const type = registry.get(registry.default.name);
+        const dataCtx = { volume: a.data }
+        return ({
+            type: PD.Mapped<any>(
+                registry.default.name,
+                registry.types,
+                name => PD.Group<any>(registry.get(name).getParams(themeCtx, a.data))),
+            colorTheme: PD.Mapped<any>(
+                type.defaultColorTheme,
+                themeCtx.colorThemeRegistry.getApplicableTypes(dataCtx),
+                name => PD.Group<any>(themeCtx.colorThemeRegistry.get(name).getParams(dataCtx))
+            ),
+            sizeTheme: PD.Mapped<any>(
+                type.defaultSizeTheme,
+                themeCtx.sizeThemeRegistry.types,
+                name => PD.Group<any>(themeCtx.sizeThemeRegistry.get(name).getParams(dataCtx))
+            )
+        })
+    }
+})({
+    canAutoUpdate({ oldParams, newParams }) {
+        // TODO: allow for small molecules
+        return oldParams.type.name === newParams.type.name;
+    },
+    apply({ a, params }, plugin: PluginContext) {
+        return Task.create('Volume Representation', async ctx => {
+            const provider = plugin.volumeRepresentation.registry.get(params.type.name)
+            const props = params.type.params || {}
+            const repr = provider.factory({ webgl: plugin.canvas3d.webgl, ...plugin.volumeRepresentation.themeCtx }, provider.getParams)
+            repr.setTheme(createTheme(plugin.volumeRepresentation.themeCtx, { volume: a.data }, params))
+            // TODO set initial state, repr.setState({})
+            await repr.createOrUpdate(props, a.data).runInContext(ctx);
+            return new SO.Volume.Representation3D(repr, { label: provider.label });
+        });
+    },
+    update({ a, b, oldParams, newParams }, plugin: PluginContext) {
+        return Task.create('Volume Representation', async ctx => {
+            if (newParams.type.name !== oldParams.type.name) return Transformer.UpdateResult.Recreate;
+            const props = { ...b.data.props, ...newParams.type.params }
+            b.data.setTheme(createTheme(plugin.volumeRepresentation.themeCtx, { volume: a.data }, newParams))
+            await b.data.createOrUpdate(props, a.data).runInContext(ctx);
+            return Transformer.UpdateResult.Updated;
+        });
+    }
 });