From 5a22b99f3d391617dc9ab30a9ffd6e92b0e55b45 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Sat, 23 Feb 2019 17:23:07 +0100 Subject: [PATCH] wip: volume streaming --- src/mol-plugin/behavior/dynamic/volume.ts | 126 ++++++++++++++++ src/mol-plugin/state/actions/volume.ts | 10 +- src/mol-plugin/state/transforms.ts | 2 + src/mol-plugin/state/transforms/model.ts | 126 +++------------- src/mol-plugin/state/transforms/volume.ts | 171 ++++++++++++++++++++++ src/mol-repr/volume/isosurface.ts | 2 +- src/mol-util/lru-cache.ts | 53 +++++++ 7 files changed, 379 insertions(+), 111 deletions(-) create mode 100644 src/mol-plugin/behavior/dynamic/volume.ts create mode 100644 src/mol-plugin/state/transforms/volume.ts create mode 100644 src/mol-util/lru-cache.ts diff --git a/src/mol-plugin/behavior/dynamic/volume.ts b/src/mol-plugin/behavior/dynamic/volume.ts new file mode 100644 index 000000000..bffbfce39 --- /dev/null +++ b/src/mol-plugin/behavior/dynamic/volume.ts @@ -0,0 +1,126 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import CIF from 'mol-io/reader/cif'; +import { Box3D } from 'mol-math/geometry'; +import { Vec3 } from 'mol-math/linear-algebra'; +import { volumeFromDensityServerData } from 'mol-model-formats/volume/density-server'; +import { VolumeData } from 'mol-model/volume'; +import { PluginContext } from 'mol-plugin/context'; +import { PluginStateObject } from 'mol-plugin/state/objects'; +import { IsoValueParam } from 'mol-repr/volume/isosurface'; +import { Color } from 'mol-util/color'; +import { ColorNames } from 'mol-util/color/tables'; +import { LRUCache } from 'mol-util/lru-cache'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { PluginBehavior } from '../behavior'; + +export namespace VolumeStreamingBehavior { + function channelParam(label: string) { return PD.Group({ color: PD.Color(Color(ColorNames.teal)), isoValue: IsoValueParam }, { label }); } + + export const Params = { + id: PD.Text(''), + levels: PD.MappedStatic('em', { + 'em': channelParam('EM'), + 'x-ray': PD.Group({ + '2fo-fc': channelParam('2Fo-Fc'), + 'fo-fc(+ve)': channelParam('Fo-Fc(+ve)'), + 'fo-fc(-ve)': channelParam('Fo-Fc(-ve)'), + }) + }), + box: PD.MappedStatic('static-box', { + 'static-box': PD.Group({ + bottomLeft: PD.Vec3(Vec3.create(0, 0, 0)), + topRight: PD.Vec3(Vec3.create(1, 1, 1)) + }, { description: 'Static box defined by cartesian coords.' }), + // 'around-selection': PD.Group({ radius: PD.Numeric(5, { min: 0, max: 10 }) }), + }), + detailLevel: PD.Numeric(3, { min: 0, max: 7 }), + serverUrl: PD.Text('https://webchem.ncbr.muni.cz/DensityServer'), + } + export type Params = PD.Values<typeof Params> + + export type ChannelData = { [name in 'EM' | '2FO-FC' | 'FO-FC']?: VolumeData } + export type LevelType = 'em' | '2fo-fc' | 'fo-fc(+ve)' | 'fo-fc(-ve)' + + export class Behavior implements PluginBehavior<Params> { + private cache = LRUCache.create<ChannelData>(25); + + currentData: ChannelData = { } + + private async queryData(box?: Box3D) { + let url = `${this.params.serverUrl}/${this.params.levels.name}/${this.params.id}` + + if (box) { + const { min: a, max: b } = box; + url += `/box` + + `/${a.map(v => Math.round(1000 * v) / 1000).join(',')}` + + `/${b.map(v => Math.round(1000 * v) / 1000).join(',')}`; + } else { + url += `/cell`; + } + url += `?detail=${this.params.detailLevel}`; + + let data = LRUCache.get(this.cache, url); + if (data) { + return data; + } + + const cif = await this.ctx.runTask(this.ctx.fetch(url, 'binary')); + data = await this.parseCif(cif as Uint8Array); + if (!data) { + return; + } + + LRUCache.set(this.cache, url, data); + return data; + } + + private async parseCif(data: Uint8Array): Promise<ChannelData | undefined> { + const parsed = await this.ctx.runTask(CIF.parseBinary(data)); + if (parsed.isError) { + this.ctx.log.error('VolumeStreaming, parsing CIF: ' + parsed.toString()); + return; + } + if (parsed.result.blocks.length < 2) { + this.ctx.log.error('VolumeStreaming: Invalid data.'); + return; + } + + const ret: ChannelData = { }; + for (let i = 1; i < parsed.result.blocks.length; i++) { + const block = parsed.result.blocks[i]; + + const densityServerCif = CIF.schema.densityServer(block); + const volume = this.ctx.runTask(await volumeFromDensityServerData(densityServerCif)); + (ret as any)[block.header as any] = volume; + } + return ret; + } + + register(ref: string): void { + this.update(this.params); + } + + async update(params: Params): Promise<boolean> { + this.params = params; + + const box: Box3D = Box3D.create(params.box.params.bottomLeft, params.box.params.topRight); + const data = await this.queryData(box); + this.currentData = data || { }; + + return true; + } + + unregister(): void { + } + + constructor(public ctx: PluginContext, public params: Params) { + } + } + + export class Obj extends PluginStateObject.CreateBehavior<Behavior>({ name: 'Volume Streaming' }) { } +} \ No newline at end of file diff --git a/src/mol-plugin/state/actions/volume.ts b/src/mol-plugin/state/actions/volume.ts index 14b041322..7009c2de7 100644 --- a/src/mol-plugin/state/actions/volume.ts +++ b/src/mol-plugin/state/actions/volume.ts @@ -81,7 +81,7 @@ const Ccp4Provider: DataFormatProvider<any> = { getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.Binary>, state: State) => { return Task.create('CCP4/MRC/BRIX default builder', async taskCtx => { const tree = data.apply(StateTransforms.Data.ParseCcp4) - .apply(StateTransforms.Model.VolumeFromCcp4) + .apply(StateTransforms.Volume.VolumeFromCcp4) .apply(StateTransforms.Representation.VolumeRepresentation3D) await state.updateTree(tree).runInContext(taskCtx) }) @@ -98,7 +98,7 @@ const Dsn6Provider: DataFormatProvider<any> = { getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.Binary>, state: State) => { return Task.create('DSN6/BRIX default builder', async taskCtx => { const tree = data.apply(StateTransforms.Data.ParseDsn6) - .apply(StateTransforms.Model.VolumeFromDsn6) + .apply(StateTransforms.Volume.VolumeFromDsn6) .apply(StateTransforms.Representation.VolumeRepresentation3D) await state.updateTree(tree).runInContext(taskCtx) }) @@ -121,14 +121,14 @@ const DscifProvider: DataFormatProvider<any> = { let tree: StateBuilder.To<any> if (blocks.length === 1) { tree = b - .apply(StateTransforms.Model.VolumeFromDensityServerCif, { blockHeader: blocks[0].header }) + .apply(StateTransforms.Volume.VolumeFromDensityServerCif, { blockHeader: blocks[0].header }) .apply(StateTransforms.Representation.VolumeRepresentation3D, VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface', { isoValue: VolumeIsoValue.relative(1.5), alpha: 0.3 }, 'uniform', { value: ColorNames.teal })) } else if (blocks.length === 2) { tree = b - .apply(StateTransforms.Model.VolumeFromDensityServerCif, { blockHeader: blocks[0].header }) + .apply(StateTransforms.Volume.VolumeFromDensityServerCif, { blockHeader: blocks[0].header }) .apply(StateTransforms.Representation.VolumeRepresentation3D, VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface', { isoValue: VolumeIsoValue.relative(1.5), alpha: 0.3 }, 'uniform', { value: ColorNames.blue })) const vol = tree.to(cifBuilder.ref) - .apply(StateTransforms.Model.VolumeFromDensityServerCif, { blockHeader: blocks[1].header }) + .apply(StateTransforms.Volume.VolumeFromDensityServerCif, { blockHeader: blocks[1].header }) const posParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface', { isoValue: VolumeIsoValue.relative(3), alpha: 0.3 }, 'uniform', { value: ColorNames.green }) tree = vol.apply(StateTransforms.Representation.VolumeRepresentation3D, posParams) const negParams = VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface', { isoValue: VolumeIsoValue.relative(-3), alpha: 0.3 }, 'uniform', { value: ColorNames.red }) diff --git a/src/mol-plugin/state/transforms.ts b/src/mol-plugin/state/transforms.ts index c6914174c..6b1c5884c 100644 --- a/src/mol-plugin/state/transforms.ts +++ b/src/mol-plugin/state/transforms.ts @@ -6,10 +6,12 @@ import * as Data from './transforms/data' import * as Model from './transforms/model' +import * as Volume from './transforms/volume' import * as Representation from './transforms/representation' export const StateTransforms = { Data, Model, + Volume, Representation } \ No newline at end of file diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts index ea17a2729..622dc32fa 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -5,28 +5,31 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { PluginStateTransform } from '../objects'; -import { PluginStateObject as SO } from '../objects'; -import { Task, RuntimeContext } from 'mol-task'; -import { Model, Structure, ModelSymmetry, StructureSymmetry, QueryContext, StructureSelection as Sel, StructureQuery, Queries } from 'mol-model/structure'; -import { ParamDefinition as PD } from 'mol-util/param-definition'; -import Expression from 'mol-script/language/expression'; -import { compile } from 'mol-script/runtime/query/compiler'; -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-formats/volume/ccp4'; +import { parsePDB } from 'mol-io/reader/pdb/parser'; import { Vec3 } from 'mol-math/linear-algebra'; -import CIF from 'mol-io/reader/cif'; -import { volumeFromDsn6 } from 'mol-model-formats/volume/dsn6'; -import { volumeFromDensityServerData } from 'mol-model-formats/volume/density-server'; import { trajectoryFromMmCIF } from 'mol-model-formats/structure/mmcif'; -import { parsePDB } from 'mol-io/reader/pdb/parser'; import { trajectoryFromPDB } from 'mol-model-formats/structure/pdb'; +import { Model, ModelSymmetry, Queries, QueryContext, Structure, StructureQuery, StructureSelection as Sel, StructureSymmetry } from 'mol-model/structure'; import { Assembly } from 'mol-model/structure/model/properties/symmetry'; +import { PluginContext } from 'mol-plugin/context'; +import { MolScriptBuilder } from 'mol-script/language/builder'; +import Expression from 'mol-script/language/expression'; +import { compile } from 'mol-script/runtime/query/compiler'; +import { StateObject } from 'mol-state'; +import { RuntimeContext, Task } from 'mol-task'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { stringToWords } from 'mol-util/string'; +import { PluginStateObject as SO, PluginStateTransform } from '../objects'; -export { TrajectoryFromMmCif } +export { TrajectoryFromMmCif }; +export { TrajectoryFromPDB }; +export { ModelFromTrajectory }; +export { StructureFromModel }; +export { StructureAssemblyFromModel }; +export { StructureSymmetryFromModel }; +export { StructureSelection }; +export { StructureComplexElement }; +export { CustomModelProperties }; type TrajectoryFromMmCif = typeof TrajectoryFromMmCif const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({ name: 'trajectory-from-mmcif', @@ -60,7 +63,6 @@ const TrajectoryFromMmCif = PluginStateTransform.BuiltIn({ }); -export { TrajectoryFromPDB } type TrajectoryFromPDB = typeof TrajectoryFromPDB const TrajectoryFromPDB = PluginStateTransform.BuiltIn({ name: 'trajectory-from-pdb', @@ -80,7 +82,6 @@ const TrajectoryFromPDB = PluginStateTransform.BuiltIn({ }); -export { ModelFromTrajectory } const plus1 = (v: number) => v + 1, minus1 = (v: number) => v - 1; type ModelFromTrajectory = typeof ModelFromTrajectory const ModelFromTrajectory = PluginStateTransform.BuiltIn({ @@ -106,7 +107,6 @@ const ModelFromTrajectory = PluginStateTransform.BuiltIn({ } }); -export { StructureFromModel } type StructureFromModel = typeof StructureFromModel const StructureFromModel = PluginStateTransform.BuiltIn({ name: 'structure-from-model', @@ -125,7 +125,6 @@ function structureDesc(s: Structure) { return s.elementCount === 1 ? '1 element' : `${s.elementCount} elements`; } -export { StructureAssemblyFromModel } type StructureAssemblyFromModel = typeof StructureAssemblyFromModel const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({ name: 'structure-assembly-from-model', @@ -178,7 +177,6 @@ const StructureAssemblyFromModel = PluginStateTransform.BuiltIn({ } }); -export { StructureSymmetryFromModel } type StructureSymmetryFromModel = typeof StructureSymmetryFromModel const StructureSymmetryFromModel = PluginStateTransform.BuiltIn({ name: 'structure-symmetry-from-model', @@ -204,7 +202,6 @@ const StructureSymmetryFromModel = PluginStateTransform.BuiltIn({ } }); -export { StructureSelection } type StructureSelection = typeof StructureSelection const StructureSelection = PluginStateTransform.BuiltIn({ name: 'structure-selection', @@ -226,7 +223,6 @@ const StructureSelection = PluginStateTransform.BuiltIn({ } }); -export { StructureComplexElement } namespace StructureComplexElement { export type Types = 'atomic-sequence' | 'water' | 'atomic-het' | 'spheres' } @@ -259,7 +255,6 @@ const StructureComplexElement = PluginStateTransform.BuiltIn({ } }); -export { CustomModelProperties } type CustomModelProperties = typeof CustomModelProperties const CustomModelProperties = PluginStateTransform.BuiltIn({ name: 'custom-model-properties', @@ -283,83 +278,4 @@ 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/MAP', description: 'Create Volume from CCP4/MRC/MAP 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/MAP', async ctx => { - const volume = await volumeFromCcp4(a.data, params).runInContext(ctx) - const props = { label: 'Volume' }; - return new SO.Volume.Data(volume, props); - }); - } -}); - -export { VolumeFromDsn6 } -type VolumeFromDsn6 = typeof VolumeFromDsn6 -const VolumeFromDsn6 = PluginStateTransform.BuiltIn({ - name: 'volume-from-dsn6', - display: { name: 'Volume from DSN6/BRIX', description: 'Create Volume from DSN6/BRIX data' }, - from: SO.Format.Dsn6, - to: SO.Volume.Data, - params(a) { - return { - voxelSize: PD.Vec3(Vec3.create(1, 1, 1)) - }; - } -})({ - apply({ a, params }) { - return Task.create('Create volume from DSN6/BRIX', async ctx => { - const volume = await volumeFromDsn6(a.data, params).runInContext(ctx) - const props = { label: 'Volume' }; - return new SO.Volume.Data(volume, props); - }); - } -}); - -export { VolumeFromDensityServerCif } -type VolumeFromDensityServerCif = typeof VolumeFromDensityServerCif -const VolumeFromDensityServerCif = PluginStateTransform.BuiltIn({ - name: 'volume-from-density-server-cif', - display: { name: 'Volume from density-server CIF', description: 'Identify and create all separate models in the specified CIF data block' }, - from: SO.Format.Cif, - to: SO.Volume.Data, - params(a) { - if (!a) { - return { - blockHeader: PD.makeOptional(PD.Text(void 0, { description: 'Header of the block to parse. If none is specifed, the 1st data block in the file is used.' })) - }; - } - const blocks = a.data.blocks.slice(1); // zero block contains query meta-data - return { - blockHeader: PD.makeOptional(PD.Select(blocks[0] && blocks[0].header, blocks.map(b => [b.header, b.header] as [string, string]), { description: 'Header of the block to parse' })) - }; - } -})({ - isApplicable: a => a.data.blocks.length > 0, - apply({ a, params }) { - return Task.create('Parse density-server CIF', async ctx => { - const header = params.blockHeader || a.data.blocks[1].header; // zero block contains query meta-data - const block = a.data.blocks.find(b => b.header === header); - if (!block) throw new Error(`Data block '${[header]}' not found.`); - const densityServerCif = CIF.schema.densityServer(block) - const volume = await volumeFromDensityServerData(densityServerCif).runInContext(ctx) - const props = { label: densityServerCif.volume_data_3d_info.name.value(0), description: `${densityServerCif.volume_data_3d_info.name.value(0)}` }; - return new SO.Volume.Data(volume, props); - }); - } -}); \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-plugin/state/transforms/volume.ts b/src/mol-plugin/state/transforms/volume.ts new file mode 100644 index 000000000..7c1aa3aab --- /dev/null +++ b/src/mol-plugin/state/transforms/volume.ts @@ -0,0 +1,171 @@ +/** + * 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 CIF from 'mol-io/reader/cif'; +import { Vec3 } from 'mol-math/linear-algebra'; +import { volumeFromCcp4 } from 'mol-model-formats/volume/ccp4'; +import { volumeFromDensityServerData } from 'mol-model-formats/volume/density-server'; +import { volumeFromDsn6 } from 'mol-model-formats/volume/dsn6'; +import { Task } from 'mol-task'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { PluginStateObject as SO, PluginStateTransform } from '../objects'; +import { VolumeStreamingBehavior as VolumeStreaming } from 'mol-plugin/behavior/dynamic/volume'; +import { PluginContext } from 'mol-plugin/context'; +import { StateTransformer } from 'mol-state'; +import { VolumeData, VolumeIsoValue } from 'mol-model/volume'; +import { BuiltInVolumeRepresentations } from 'mol-repr/volume/registry'; +import { createTheme } from 'mol-theme/theme'; + +export { VolumeFromCcp4 }; +export { VolumeFromDsn6 }; +export { VolumeFromDensityServerCif }; +type VolumeFromCcp4 = typeof VolumeFromCcp4 +const VolumeFromCcp4 = PluginStateTransform.BuiltIn({ + name: 'volume-from-ccp4', + display: { name: 'Volume from CCP4/MRC/MAP', description: 'Create Volume from CCP4/MRC/MAP 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/MAP', async ctx => { + const volume = await volumeFromCcp4(a.data, params).runInContext(ctx) + const props = { label: 'Volume' }; + return new SO.Volume.Data(volume, props); + }); + } +}); + +type VolumeFromDsn6 = typeof VolumeFromDsn6 +const VolumeFromDsn6 = PluginStateTransform.BuiltIn({ + name: 'volume-from-dsn6', + display: { name: 'Volume from DSN6/BRIX', description: 'Create Volume from DSN6/BRIX data' }, + from: SO.Format.Dsn6, + to: SO.Volume.Data, + params(a) { + return { + voxelSize: PD.Vec3(Vec3.create(1, 1, 1)) + }; + } +})({ + apply({ a, params }) { + return Task.create('Create volume from DSN6/BRIX', async ctx => { + const volume = await volumeFromDsn6(a.data, params).runInContext(ctx) + const props = { label: 'Volume' }; + return new SO.Volume.Data(volume, props); + }); + } +}); + +type VolumeFromDensityServerCif = typeof VolumeFromDensityServerCif +const VolumeFromDensityServerCif = PluginStateTransform.BuiltIn({ + name: 'volume-from-density-server-cif', + display: { name: 'Volume from density-server CIF', description: 'Identify and create all separate models in the specified CIF data block' }, + from: SO.Format.Cif, + to: SO.Volume.Data, + params(a) { + if (!a) { + return { + blockHeader: PD.makeOptional(PD.Text(void 0, { description: 'Header of the block to parse. If none is specifed, the 1st data block in the file is used.' })) + }; + } + const blocks = a.data.blocks.slice(1); // zero block contains query meta-data + return { + blockHeader: PD.makeOptional(PD.Select(blocks[0] && blocks[0].header, blocks.map(b => [b.header, b.header] as [string, string]), { description: 'Header of the block to parse' })) + }; + } +})({ + isApplicable: a => a.data.blocks.length > 0, + apply({ a, params }) { + return Task.create('Parse density-server CIF', async ctx => { + const header = params.blockHeader || a.data.blocks[1].header; // zero block contains query meta-data + const block = a.data.blocks.find(b => b.header === header); + if (!block) throw new Error(`Data block '${[header]}' not found.`); + const densityServerCif = CIF.schema.densityServer(block) + const volume = await volumeFromDensityServerData(densityServerCif).runInContext(ctx) + const props = { label: densityServerCif.volume_data_3d_info.name.value(0), description: `${densityServerCif.volume_data_3d_info.name.value(0)}` }; + return new SO.Volume.Data(volume, props); + }); + } +}); + +export { VolumeStreamingBehavior } +type VolumeStreamingBehavior = typeof VolumeStreamingBehavior +const VolumeStreamingBehavior = PluginStateTransform.BuiltIn({ + name: 'volume-streaming-behavior', + display: { name: 'Volume Streaming Behavior', description: 'Create Volume Streaming behavior.' }, + from: SO.Molecule.Model, + to: VolumeStreaming.Obj, + params: VolumeStreaming.Params +})({ + apply({ a, params }, plugin: PluginContext) { + const behavior = new VolumeStreaming.Behavior(plugin, params); + return new VolumeStreaming.Obj(behavior, { label: 'Volume Streaming' }); + }, + update({ b, newParams }) { + return Task.create('Update Volume Streaming', async _ => { + await b.data.update(newParams); + return StateTransformer.UpdateResult.Updated; + }); + } +}); + +export { VolumeStreamingVisual } +type VolumeStreamingVisual = typeof VolumeStreamingVisual +const VolumeStreamingVisual = PluginStateTransform.BuiltIn({ + name: 'volume-streaming-visual', + display: { name: 'Volume Streaming Visual' }, + from: VolumeStreaming.Obj, + to: SO.Volume.Representation3D, + params: { + channel: PD.Select<keyof VolumeStreaming.ChannelData>('EM', [['EM', 'EM'], ['FO-FC', 'Fo-Fc'], ['2FO-FC', '2Fo-Fc']], { isHidden: true }), + level: PD.Text<VolumeStreaming.LevelType>('em') + } +})({ + apply: ({ a, params }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => { + const { data, props, theme } = createVolumeProps(a.data, params.channel, params.level) + + const repr = BuiltInVolumeRepresentations.isosurface.factory({ webgl: plugin.canvas3d.webgl, ...plugin.volumeRepresentation.themeCtx }, BuiltInVolumeRepresentations.isosurface.getParams); + repr.setTheme(theme); + + await repr.createOrUpdate(props, data).runInContext(ctx); + return new SO.Volume.Representation3D(repr, { label: params.level }); + }), + update: ({ a, b, oldParams, newParams }) => Task.create('Volume Representation', async ctx => { + // TODO : check if params have changed + const { data, props, theme } = createVolumeProps(a.data, newParams.channel, newParams.level); + b.data.setTheme(theme); + await b.data.createOrUpdate(props, data).runInContext(ctx); + return StateTransformer.UpdateResult.Updated; + }) +}); + +function createVolumeProps(streaming: VolumeStreaming.Behavior, channel: keyof VolumeStreaming.ChannelData, level: VolumeStreaming.LevelType) { + const data = streaming.currentData[channel] || VolumeData.Empty; + const { themeCtx } = streaming.ctx.volumeRepresentation; + + const props = PD.getDefaultValues(BuiltInVolumeRepresentations.isosurface.getParams(themeCtx, data)); + let isoValue: VolumeIsoValue; + + if (level === 'em' && streaming.params.levels.name === 'em') { + isoValue = streaming.params.levels.params.isoValue; + } else if (level !== 'em' && streaming.params.levels.name === 'x-ray') { + isoValue = streaming.params.levels.params[level].isoValue; + } else { + throw new Error(`Unsupported iso level ${level}.`); + } + + props.isoValue = isoValue; + + const theme = createTheme(streaming.ctx.volumeRepresentation.themeCtx, { volume: data }, props); + + return { data, props, theme }; +} \ No newline at end of file diff --git a/src/mol-repr/volume/isosurface.ts b/src/mol-repr/volume/isosurface.ts index db801ed3d..5fa96603e 100644 --- a/src/mol-repr/volume/isosurface.ts +++ b/src/mol-repr/volume/isosurface.ts @@ -19,7 +19,7 @@ import { VisualContext } from 'mol-repr/visual'; import { NullLocation } from 'mol-model/location'; import { Lines } from 'mol-geo/geometry/lines/lines'; -const IsoValueParam = PD.Conditioned( +export const IsoValueParam = PD.Conditioned( VolumeIsoValue.relative(2), { 'absolute': PD.Converted( diff --git a/src/mol-util/lru-cache.ts b/src/mol-util/lru-cache.ts new file mode 100644 index 000000000..38d950981 --- /dev/null +++ b/src/mol-util/lru-cache.ts @@ -0,0 +1,53 @@ + +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol. + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { LinkedList } from 'mol-data/generic'; + +export { LRUCache } + +interface LRUCache<T> { + entries: LinkedList<LRUCache.Entry<T>>, + capacity: number +} + +namespace LRUCache { + export interface Entry<T> { + key: string, + data: T + } + + function entry<T>(key: string, data: T): Entry<T> { + return { key, data }; + } + + export function create<T>(capacity: number): LRUCache<T> { + return { + entries: LinkedList<Entry<T>>(), + capacity: Math.max(1, capacity) + }; + } + + export function get<T>(cache: LRUCache<T>, key: string) { + for (let e = cache.entries.first; e; e = e.next) { + if (e.value.key === key) { + cache.entries.remove(e); + cache.entries.addLast(e.value); + return e.value.data; + } + } + return void 0; + } + + export function set<T>(cache: LRUCache<T>, key: string, data: T): T { + if (cache.entries.count >= cache.capacity) { + cache.entries.remove(cache.entries.first!); + } + cache.entries.addLast(entry(key, data)); + return data; + } +} \ No newline at end of file -- GitLab