ソースを参照

mol-plugin: initial volume streaming support

David Sehnal 6 年 前
コミット
a340a02456

+ 23 - 14
src/mol-plugin/behavior/dynamic/volume.ts

@@ -8,35 +8,41 @@ 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 { VolumeData, VolumeIsoValue } 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 { createIsoValueParam } 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 namespace VolumeStreaming {
+    function channelParam(label: string, color: Color, defaultValue: number) {
+        return PD.Group({
+            color: PD.Color(color),
+            isoValue: createIsoValueParam(VolumeIsoValue.relative(defaultValue))
+        }, { label });
+    }
 
     export const Params = {
-        id: PD.Text(''),
-        levels: PD.MappedStatic('em', {
-            'em': channelParam('EM'),
+        id: PD.Text('1tqn'),
+        levels: PD.MappedStatic('x-ray', {
+            'em': channelParam('EM', Color(0x638F8F), 1.5),
             'x-ray': PD.Group({
-                '2fo-fc': channelParam('2Fo-Fc'),
-                'fo-fc(+ve)': channelParam('Fo-Fc(+ve)'),
-                'fo-fc(-ve)': channelParam('Fo-Fc(-ve)'),
+                '2fo-fc': channelParam('2Fo-Fc', Color(0x3362B2), 1.5),
+                'fo-fc(+ve)': channelParam('Fo-Fc(+ve)', Color(0x33BB33), 3),
+                'fo-fc(-ve)': channelParam('Fo-Fc(-ve)', Color(0xBB3333), -3),
             })
         }),
         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))
+                bottomLeft: PD.Vec3(Vec3.create(-22.4, -33.4, -21.6)),
+                topRight: PD.Vec3(Vec3.create(-7.1, -10, -0.9))
             }, { description: 'Static box defined by cartesian coords.' }),
             // 'around-selection': PD.Group({ radius: PD.Numeric(5, { min: 0, max: 10 }) }),
+            // 'whole-structure': PD.Group({  }),
+            // 'auto': PD.Group({  }), // based on camera distance/active selection/whatever, show whole structure or slice.
         }),
         detailLevel: PD.Numeric(3, { min: 0, max: 7 }),
         serverUrl: PD.Text('https://webchem.ncbr.muni.cz/DensityServer'),
@@ -47,6 +53,7 @@ export namespace VolumeStreamingBehavior {
     export type LevelType = 'em' | '2fo-fc' | 'fo-fc(+ve)' | 'fo-fc(-ve)'
 
     export class Behavior implements PluginBehavior<Params> {
+        // TODO: have special value for "cell"?
         private cache = LRUCache.create<ChannelData>(25);
 
         currentData: ChannelData = { }
@@ -95,13 +102,14 @@ export namespace VolumeStreamingBehavior {
                 const block = parsed.result.blocks[i];
 
                 const densityServerCif = CIF.schema.densityServer(block);
-                const volume = this.ctx.runTask(await volumeFromDensityServerData(densityServerCif));
+                const volume = await this.ctx.runTask(await volumeFromDensityServerData(densityServerCif));
                 (ret as any)[block.header as any] = volume;
             }
             return ret;
         }
 
         register(ref: string): void {
+            // TODO: registr camera movement/loci so that "around selection box works"
             this.update(this.params);
         }
 
@@ -116,6 +124,7 @@ export namespace VolumeStreamingBehavior {
         }
 
         unregister(): void {
+            // TODO unsubscribe to events
         }
 
         constructor(public ctx: PluginContext, public params: Params) {

+ 4 - 1
src/mol-plugin/index.ts

@@ -29,6 +29,9 @@ export const DefaultPluginSpec: PluginSpec = {
         PluginSpec.Action(StateActions.Volume.OpenVolume),
         PluginSpec.Action(StateActions.Structure.CreateComplexRepresentation),
         PluginSpec.Action(StateActions.Structure.EnableModelCustomProps),
+
+        PluginSpec.Action(StateActions.Volume.InitVolumeStreaming),
+
         PluginSpec.Action(StateTransforms.Data.Download),
         PluginSpec.Action(StateTransforms.Data.ParseCif),
         PluginSpec.Action(StateTransforms.Data.ParseCcp4),
@@ -36,7 +39,7 @@ export const DefaultPluginSpec: PluginSpec = {
         PluginSpec.Action(StateTransforms.Model.StructureSymmetryFromModel),
         PluginSpec.Action(StateTransforms.Model.StructureFromModel),
         PluginSpec.Action(StateTransforms.Model.ModelFromTrajectory),
-        PluginSpec.Action(StateTransforms.Model.VolumeFromCcp4),
+        PluginSpec.Action(StateTransforms.Volume.VolumeFromCcp4),
         PluginSpec.Action(StateTransforms.Representation.StructureRepresentation3D),
         PluginSpec.Action(StateTransforms.Representation.ExplodeStructureRepresentation3D),
         PluginSpec.Action(StateTransforms.Representation.VolumeRepresentation3D),

+ 22 - 1
src/mol-plugin/state/actions/volume.ts

@@ -16,6 +16,7 @@ import { PluginStateObject } from '../objects';
 import { StateTransforms } from '../transforms';
 import { Download } from '../transforms/data';
 import { VolumeRepresentation3DHelpers } from '../transforms/representation';
+import { VolumeStreaming } from 'mol-plugin/behavior/dynamic/volume';
 
 export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String, M extends StateObject> {
     private _list: { name: string, provider: DataFormatProvider<D> }[] = []
@@ -290,4 +291,24 @@ const DownloadDensity = StateAction.build({
 
     const b = state.build().to(data.ref);
     await provider.getDefaultBuilder(ctx, b, state).runInContext(taskCtx)
-}));
+}));
+
+export const InitVolumeStreaming = StateAction.build({
+    display: { name: 'Volume Streaming' },
+    from: PluginStateObject.Molecule.Model,
+    params: VolumeStreaming.Params
+})(({ ref, state, params }, ctx: PluginContext) => {
+    // TODO: specify simpler params
+    // TODO: try to determine if the input is x-ray or emd (in params provider)
+    // TODO: for EMD, use PDBe API to determine controur level https://github.com/dsehnal/LiteMol/blob/master/src/Viewer/Extensions/DensityStreaming/Entity.ts#L168
+    // TODO: custom react view for this and the VolumeStreamingBehavior transformer
+
+    const root = state.build().to(ref)
+        .apply(StateTransforms.Volume.VolumeStreamingBehavior, params);
+
+    root.apply(StateTransforms.Volume.VolumeStreamingVisual, { channel: '2FO-FC', level: '2fo-fc' });
+    root.apply(StateTransforms.Volume.VolumeStreamingVisual, { channel: 'FO-FC', level: 'fo-fc(+ve)' });
+    root.apply(StateTransforms.Volume.VolumeStreamingVisual, { channel: 'FO-FC', level: 'fo-fc(-ve)' });
+
+    return state.updateTree(root);
+});

+ 44 - 21
src/mol-plugin/state/transforms/volume.ts

@@ -13,12 +13,14 @@ 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 { 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';
+import { VolumeRepresentation3DHelpers } from './representation';
+import { Color } from 'mol-util/color';
 
 export { VolumeFromCcp4 };
 export { VolumeFromDsn6 };
@@ -106,10 +108,12 @@ const VolumeStreamingBehavior = PluginStateTransform.BuiltIn({
     to: VolumeStreaming.Obj,
     params: VolumeStreaming.Params
 })({
-    apply({ a, params }, plugin: PluginContext) {
+    apply: ({ params }, plugin: PluginContext) => Task.create('Volume Streaming', async ctx => {
         const behavior = new VolumeStreaming.Behavior(plugin, params);
+        // get the initial data now so that the child projections dont get empty volumes.
+        await behavior.update(behavior.params);
         return new VolumeStreaming.Obj(behavior, { label: 'Volume Streaming' });
-    },
+    }),
     update({ b, newParams }) {
         return Task.create('Update Volume Streaming', async _ => {
             await b.data.update(newParams);
@@ -118,6 +122,25 @@ const VolumeStreamingBehavior = PluginStateTransform.BuiltIn({
     }
 });
 
+// export { VolumeStreamingData }
+// type VolumeStreamingData = typeof VolumeStreamingData
+// const VolumeStreamingData = PluginStateTransform.BuiltIn({
+//     name: 'volume-streaming-data',
+//     display: { name: 'Volume Streaming Data' },
+//     from: VolumeStreaming.Obj,
+//     to: SO.Volume.Data,
+//     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) {
+//         const data = a.data.currentData[params.channel] || VolumeData.Empty;
+//         console.log({ data });
+//         return new SO.Volume.Data(a.data.currentData[params.channel] || VolumeData.Empty, { label: params.level });
+//     }
+// });
+
 export { VolumeStreamingVisual }
 type VolumeStreamingVisual = typeof VolumeStreamingVisual
 const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
@@ -130,19 +153,21 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
         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);
+    apply: ({ a, params: srcParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
+        const { data, params } = createVolumeProps(a.data, srcParams.channel, srcParams.level);
 
+        const provider = BuiltInVolumeRepresentations.isosurface;
+        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: data }, params))
         await repr.createOrUpdate(props, data).runInContext(ctx);
-        return new SO.Volume.Representation3D(repr, { label: params.level });
+        return new SO.Volume.Representation3D(repr, { label: srcParams.level, description: VolumeRepresentation3DHelpers.getDescription(props) });
     }),
-    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);
+    update: ({ a, b, oldParams, newParams }, 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 { data, params } = createVolumeProps(a.data, newParams.channel, newParams.level);
+        const props = { ...b.data.props, ...params.type.params };
+        b.data.setTheme(createTheme(plugin.volumeRepresentation.themeCtx, { volume: data }, params))
         await b.data.createOrUpdate(props, data).runInContext(ctx);
         return StateTransformer.UpdateResult.Updated;
     })
@@ -150,22 +175,20 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
 
 function createVolumeProps(streaming: VolumeStreaming.Behavior, channel: keyof VolumeStreaming.ChannelData, level: VolumeStreaming.LevelType) {
     const data = streaming.currentData[channel] || VolumeData.Empty;
-    const { themeCtx } = streaming.ctx.volumeRepresentation;
+    // TODO: createTheme fails when VolumeData.Empty is used for some reason.
 
-    const props = PD.getDefaultValues(BuiltInVolumeRepresentations.isosurface.getParams(themeCtx, data));
-    let isoValue: VolumeIsoValue;
+    let isoValue: VolumeIsoValue, color: Color;
 
     if (level === 'em' && streaming.params.levels.name === 'em') {
         isoValue = streaming.params.levels.params.isoValue;
+        color = streaming.params.levels.params.color;
     } else if (level !== 'em' && streaming.params.levels.name === 'x-ray') {
         isoValue = streaming.params.levels.params[level].isoValue;
+        color = streaming.params.levels.params[level].color;
     } 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 };
+    const params = VolumeRepresentation3DHelpers.getDefaultParamsStatic(streaming.ctx, 'isosurface', { isoValue, alpha: 0.3 }, 'uniform', { value: color });
+    return { data, params };
 }

+ 22 - 17
src/mol-repr/volume/isosurface.ts

@@ -19,23 +19,27 @@ import { VisualContext } from 'mol-repr/visual';
 import { NullLocation } from 'mol-model/location';
 import { Lines } from 'mol-geo/geometry/lines/lines';
 
-export const IsoValueParam = PD.Conditioned(
-    VolumeIsoValue.relative(2),
-    {
-        'absolute': PD.Converted(
-            (v: VolumeIsoValue) => VolumeIsoValue.toAbsolute(v, VolumeData.Empty.dataStats).absoluteValue,
-            (v: number) => VolumeIsoValue.absolute(v),
-            PD.Numeric(0.5, { min: -1, max: 1, step: 0.01 })
-        ),
-        'relative': PD.Converted(
-            (v: VolumeIsoValue) => VolumeIsoValue.toRelative(v, VolumeData.Empty.dataStats).relativeValue,
-            (v: number) => VolumeIsoValue.relative(v),
-            PD.Numeric(2, { min: -10, max: 10, step: 0.01 })
-        )
-    },
-    (v: VolumeIsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative',
-    (v: VolumeIsoValue, c: 'absolute' | 'relative') => c === 'absolute' ? VolumeIsoValue.toAbsolute(v, VolumeData.Empty.dataStats) : VolumeIsoValue.toRelative(v, VolumeData.Empty.dataStats)
-)
+export function createIsoValueParam(defaultValue: VolumeIsoValue) {
+    return PD.Conditioned(
+        defaultValue,
+        {
+            'absolute': PD.Converted(
+                (v: VolumeIsoValue) => VolumeIsoValue.toAbsolute(v, VolumeData.Empty.dataStats).absoluteValue,
+                (v: number) => VolumeIsoValue.absolute(v),
+                PD.Numeric(0.5, { min: -1, max: 1, step: 0.01 })
+            ),
+            'relative': PD.Converted(
+                (v: VolumeIsoValue) => VolumeIsoValue.toRelative(v, VolumeData.Empty.dataStats).relativeValue,
+                (v: number) => VolumeIsoValue.relative(v),
+                PD.Numeric(2, { min: -10, max: 10, step: 0.01 })
+            )
+        },
+        (v: VolumeIsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative',
+        (v: VolumeIsoValue, c: 'absolute' | 'relative') => c === 'absolute' ? VolumeIsoValue.toAbsolute(v, VolumeData.Empty.dataStats) : VolumeIsoValue.toRelative(v, VolumeData.Empty.dataStats)
+    )
+}
+
+export const IsoValueParam = createIsoValueParam(VolumeIsoValue.relative(2));
 type IsoValueParam = typeof IsoValueParam
 
 export const VolumeIsosurfaceParams = {
@@ -154,6 +158,7 @@ export function getIsosurfaceParams(ctx: ThemeRegistryContext, volume: VolumeDat
         (v: VolumeIsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative',
         (v: VolumeIsoValue, c: 'absolute' | 'relative') => c === 'absolute' ? VolumeIsoValue.toAbsolute(v, stats) : VolumeIsoValue.toRelative(v, stats)
     )
+
     return p
 }