Skip to content
Snippets Groups Projects
Commit a340a024 authored by David Sehnal's avatar David Sehnal
Browse files

mol-plugin: initial volume streaming support

parent 5a22b99f
No related branches found
No related tags found
No related merge requests found
...@@ -8,35 +8,41 @@ import CIF from 'mol-io/reader/cif'; ...@@ -8,35 +8,41 @@ import CIF from 'mol-io/reader/cif';
import { Box3D } from 'mol-math/geometry'; import { Box3D } from 'mol-math/geometry';
import { Vec3 } from 'mol-math/linear-algebra'; import { Vec3 } from 'mol-math/linear-algebra';
import { volumeFromDensityServerData } from 'mol-model-formats/volume/density-server'; 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 { PluginContext } from 'mol-plugin/context';
import { PluginStateObject } from 'mol-plugin/state/objects'; 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 { Color } from 'mol-util/color';
import { ColorNames } from 'mol-util/color/tables';
import { LRUCache } from 'mol-util/lru-cache'; import { LRUCache } from 'mol-util/lru-cache';
import { ParamDefinition as PD } from 'mol-util/param-definition'; import { ParamDefinition as PD } from 'mol-util/param-definition';
import { PluginBehavior } from '../behavior'; import { PluginBehavior } from '../behavior';
export namespace VolumeStreamingBehavior { export namespace VolumeStreaming {
function channelParam(label: string) { return PD.Group({ color: PD.Color(Color(ColorNames.teal)), isoValue: IsoValueParam }, { label }); } function channelParam(label: string, color: Color, defaultValue: number) {
return PD.Group({
color: PD.Color(color),
isoValue: createIsoValueParam(VolumeIsoValue.relative(defaultValue))
}, { label });
}
export const Params = { export const Params = {
id: PD.Text(''), id: PD.Text('1tqn'),
levels: PD.MappedStatic('em', { levels: PD.MappedStatic('x-ray', {
'em': channelParam('EM'), 'em': channelParam('EM', Color(0x638F8F), 1.5),
'x-ray': PD.Group({ 'x-ray': PD.Group({
'2fo-fc': channelParam('2Fo-Fc'), '2fo-fc': channelParam('2Fo-Fc', Color(0x3362B2), 1.5),
'fo-fc(+ve)': channelParam('Fo-Fc(+ve)'), 'fo-fc(+ve)': channelParam('Fo-Fc(+ve)', Color(0x33BB33), 3),
'fo-fc(-ve)': channelParam('Fo-Fc(-ve)'), 'fo-fc(-ve)': channelParam('Fo-Fc(-ve)', Color(0xBB3333), -3),
}) })
}), }),
box: PD.MappedStatic('static-box', { box: PD.MappedStatic('static-box', {
'static-box': PD.Group({ 'static-box': PD.Group({
bottomLeft: PD.Vec3(Vec3.create(0, 0, 0)), bottomLeft: PD.Vec3(Vec3.create(-22.4, -33.4, -21.6)),
topRight: PD.Vec3(Vec3.create(1, 1, 1)) topRight: PD.Vec3(Vec3.create(-7.1, -10, -0.9))
}, { description: 'Static box defined by cartesian coords.' }), }, { description: 'Static box defined by cartesian coords.' }),
// 'around-selection': PD.Group({ radius: PD.Numeric(5, { min: 0, max: 10 }) }), // '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 }), detailLevel: PD.Numeric(3, { min: 0, max: 7 }),
serverUrl: PD.Text('https://webchem.ncbr.muni.cz/DensityServer'), serverUrl: PD.Text('https://webchem.ncbr.muni.cz/DensityServer'),
...@@ -47,6 +53,7 @@ export namespace VolumeStreamingBehavior { ...@@ -47,6 +53,7 @@ export namespace VolumeStreamingBehavior {
export type LevelType = 'em' | '2fo-fc' | 'fo-fc(+ve)' | 'fo-fc(-ve)' export type LevelType = 'em' | '2fo-fc' | 'fo-fc(+ve)' | 'fo-fc(-ve)'
export class Behavior implements PluginBehavior<Params> { export class Behavior implements PluginBehavior<Params> {
// TODO: have special value for "cell"?
private cache = LRUCache.create<ChannelData>(25); private cache = LRUCache.create<ChannelData>(25);
currentData: ChannelData = { } currentData: ChannelData = { }
...@@ -95,13 +102,14 @@ export namespace VolumeStreamingBehavior { ...@@ -95,13 +102,14 @@ export namespace VolumeStreamingBehavior {
const block = parsed.result.blocks[i]; const block = parsed.result.blocks[i];
const densityServerCif = CIF.schema.densityServer(block); 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; (ret as any)[block.header as any] = volume;
} }
return ret; return ret;
} }
register(ref: string): void { register(ref: string): void {
// TODO: registr camera movement/loci so that "around selection box works"
this.update(this.params); this.update(this.params);
} }
...@@ -116,6 +124,7 @@ export namespace VolumeStreamingBehavior { ...@@ -116,6 +124,7 @@ export namespace VolumeStreamingBehavior {
} }
unregister(): void { unregister(): void {
// TODO unsubscribe to events
} }
constructor(public ctx: PluginContext, public params: Params) { constructor(public ctx: PluginContext, public params: Params) {
......
...@@ -29,6 +29,9 @@ export const DefaultPluginSpec: PluginSpec = { ...@@ -29,6 +29,9 @@ export const DefaultPluginSpec: PluginSpec = {
PluginSpec.Action(StateActions.Volume.OpenVolume), PluginSpec.Action(StateActions.Volume.OpenVolume),
PluginSpec.Action(StateActions.Structure.CreateComplexRepresentation), PluginSpec.Action(StateActions.Structure.CreateComplexRepresentation),
PluginSpec.Action(StateActions.Structure.EnableModelCustomProps), PluginSpec.Action(StateActions.Structure.EnableModelCustomProps),
PluginSpec.Action(StateActions.Volume.InitVolumeStreaming),
PluginSpec.Action(StateTransforms.Data.Download), PluginSpec.Action(StateTransforms.Data.Download),
PluginSpec.Action(StateTransforms.Data.ParseCif), PluginSpec.Action(StateTransforms.Data.ParseCif),
PluginSpec.Action(StateTransforms.Data.ParseCcp4), PluginSpec.Action(StateTransforms.Data.ParseCcp4),
...@@ -36,7 +39,7 @@ export const DefaultPluginSpec: PluginSpec = { ...@@ -36,7 +39,7 @@ export const DefaultPluginSpec: PluginSpec = {
PluginSpec.Action(StateTransforms.Model.StructureSymmetryFromModel), PluginSpec.Action(StateTransforms.Model.StructureSymmetryFromModel),
PluginSpec.Action(StateTransforms.Model.StructureFromModel), PluginSpec.Action(StateTransforms.Model.StructureFromModel),
PluginSpec.Action(StateTransforms.Model.ModelFromTrajectory), PluginSpec.Action(StateTransforms.Model.ModelFromTrajectory),
PluginSpec.Action(StateTransforms.Model.VolumeFromCcp4), PluginSpec.Action(StateTransforms.Volume.VolumeFromCcp4),
PluginSpec.Action(StateTransforms.Representation.StructureRepresentation3D), PluginSpec.Action(StateTransforms.Representation.StructureRepresentation3D),
PluginSpec.Action(StateTransforms.Representation.ExplodeStructureRepresentation3D), PluginSpec.Action(StateTransforms.Representation.ExplodeStructureRepresentation3D),
PluginSpec.Action(StateTransforms.Representation.VolumeRepresentation3D), PluginSpec.Action(StateTransforms.Representation.VolumeRepresentation3D),
......
...@@ -16,6 +16,7 @@ import { PluginStateObject } from '../objects'; ...@@ -16,6 +16,7 @@ import { PluginStateObject } from '../objects';
import { StateTransforms } from '../transforms'; import { StateTransforms } from '../transforms';
import { Download } from '../transforms/data'; import { Download } from '../transforms/data';
import { VolumeRepresentation3DHelpers } from '../transforms/representation'; 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> { export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String, M extends StateObject> {
private _list: { name: string, provider: DataFormatProvider<D> }[] = [] private _list: { name: string, provider: DataFormatProvider<D> }[] = []
...@@ -290,4 +291,24 @@ const DownloadDensity = StateAction.build({ ...@@ -290,4 +291,24 @@ const DownloadDensity = StateAction.build({
const b = state.build().to(data.ref); const b = state.build().to(data.ref);
await provider.getDefaultBuilder(ctx, b, state).runInContext(taskCtx) await provider.getDefaultBuilder(ctx, b, state).runInContext(taskCtx)
})); }));
\ No newline at end of file
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);
});
\ No newline at end of file
...@@ -13,12 +13,14 @@ import { volumeFromDsn6 } from 'mol-model-formats/volume/dsn6'; ...@@ -13,12 +13,14 @@ import { volumeFromDsn6 } from 'mol-model-formats/volume/dsn6';
import { Task } from 'mol-task'; import { Task } from 'mol-task';
import { ParamDefinition as PD } from 'mol-util/param-definition'; import { ParamDefinition as PD } from 'mol-util/param-definition';
import { PluginStateObject as SO, PluginStateTransform } from '../objects'; 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 { PluginContext } from 'mol-plugin/context';
import { StateTransformer } from 'mol-state'; import { StateTransformer } from 'mol-state';
import { VolumeData, VolumeIsoValue } from 'mol-model/volume'; import { VolumeData, VolumeIsoValue } from 'mol-model/volume';
import { BuiltInVolumeRepresentations } from 'mol-repr/volume/registry'; import { BuiltInVolumeRepresentations } from 'mol-repr/volume/registry';
import { createTheme } from 'mol-theme/theme'; import { createTheme } from 'mol-theme/theme';
import { VolumeRepresentation3DHelpers } from './representation';
import { Color } from 'mol-util/color';
export { VolumeFromCcp4 }; export { VolumeFromCcp4 };
export { VolumeFromDsn6 }; export { VolumeFromDsn6 };
...@@ -106,10 +108,12 @@ const VolumeStreamingBehavior = PluginStateTransform.BuiltIn({ ...@@ -106,10 +108,12 @@ const VolumeStreamingBehavior = PluginStateTransform.BuiltIn({
to: VolumeStreaming.Obj, to: VolumeStreaming.Obj,
params: VolumeStreaming.Params 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); 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' }); return new VolumeStreaming.Obj(behavior, { label: 'Volume Streaming' });
}, }),
update({ b, newParams }) { update({ b, newParams }) {
return Task.create('Update Volume Streaming', async _ => { return Task.create('Update Volume Streaming', async _ => {
await b.data.update(newParams); await b.data.update(newParams);
...@@ -118,6 +122,25 @@ const VolumeStreamingBehavior = PluginStateTransform.BuiltIn({ ...@@ -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 } export { VolumeStreamingVisual }
type VolumeStreamingVisual = typeof VolumeStreamingVisual type VolumeStreamingVisual = typeof VolumeStreamingVisual
const VolumeStreamingVisual = PluginStateTransform.BuiltIn({ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
...@@ -130,19 +153,21 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({ ...@@ -130,19 +153,21 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
level: PD.Text<VolumeStreaming.LevelType>('em') level: PD.Text<VolumeStreaming.LevelType>('em')
} }
})({ })({
apply: ({ a, params }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => { apply: ({ a, params: srcParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
const { data, props, theme } = createVolumeProps(a.data, params.channel, params.level) const { data, params } = createVolumeProps(a.data, srcParams.channel, srcParams.level);
const repr = BuiltInVolumeRepresentations.isosurface.factory({ webgl: plugin.canvas3d.webgl, ...plugin.volumeRepresentation.themeCtx }, BuiltInVolumeRepresentations.isosurface.getParams);
repr.setTheme(theme);
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); 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 => { update: ({ a, b, oldParams, newParams }, plugin: PluginContext) => Task.create('Volume Representation', async ctx => {
// TODO : check if params have changed // 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, props, theme } = createVolumeProps(a.data, newParams.channel, newParams.level); const { data, params } = createVolumeProps(a.data, newParams.channel, newParams.level);
b.data.setTheme(theme); 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); await b.data.createOrUpdate(props, data).runInContext(ctx);
return StateTransformer.UpdateResult.Updated; return StateTransformer.UpdateResult.Updated;
}) })
...@@ -150,22 +175,20 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({ ...@@ -150,22 +175,20 @@ const VolumeStreamingVisual = PluginStateTransform.BuiltIn({
function createVolumeProps(streaming: VolumeStreaming.Behavior, channel: keyof VolumeStreaming.ChannelData, level: VolumeStreaming.LevelType) { function createVolumeProps(streaming: VolumeStreaming.Behavior, channel: keyof VolumeStreaming.ChannelData, level: VolumeStreaming.LevelType) {
const data = streaming.currentData[channel] || VolumeData.Empty; 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, color: Color;
let isoValue: VolumeIsoValue;
if (level === 'em' && streaming.params.levels.name === 'em') { if (level === 'em' && streaming.params.levels.name === 'em') {
isoValue = streaming.params.levels.params.isoValue; isoValue = streaming.params.levels.params.isoValue;
color = streaming.params.levels.params.color;
} else if (level !== 'em' && streaming.params.levels.name === 'x-ray') { } else if (level !== 'em' && streaming.params.levels.name === 'x-ray') {
isoValue = streaming.params.levels.params[level].isoValue; isoValue = streaming.params.levels.params[level].isoValue;
color = streaming.params.levels.params[level].color;
} else { } else {
throw new Error(`Unsupported iso level ${level}.`); throw new Error(`Unsupported iso level ${level}.`);
} }
props.isoValue = isoValue; const params = VolumeRepresentation3DHelpers.getDefaultParamsStatic(streaming.ctx, 'isosurface', { isoValue, alpha: 0.3 }, 'uniform', { value: color });
return { data, params };
const theme = createTheme(streaming.ctx.volumeRepresentation.themeCtx, { volume: data }, props);
return { data, props, theme };
} }
\ No newline at end of file
...@@ -19,23 +19,27 @@ import { VisualContext } from 'mol-repr/visual'; ...@@ -19,23 +19,27 @@ import { VisualContext } from 'mol-repr/visual';
import { NullLocation } from 'mol-model/location'; import { NullLocation } from 'mol-model/location';
import { Lines } from 'mol-geo/geometry/lines/lines'; import { Lines } from 'mol-geo/geometry/lines/lines';
export const IsoValueParam = PD.Conditioned( export function createIsoValueParam(defaultValue: VolumeIsoValue) {
VolumeIsoValue.relative(2), return PD.Conditioned(
{ defaultValue,
'absolute': PD.Converted( {
(v: VolumeIsoValue) => VolumeIsoValue.toAbsolute(v, VolumeData.Empty.dataStats).absoluteValue, 'absolute': PD.Converted(
(v: number) => VolumeIsoValue.absolute(v), (v: VolumeIsoValue) => VolumeIsoValue.toAbsolute(v, VolumeData.Empty.dataStats).absoluteValue,
PD.Numeric(0.5, { min: -1, max: 1, step: 0.01 }) (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, 'relative': PD.Converted(
(v: number) => VolumeIsoValue.relative(v), (v: VolumeIsoValue) => VolumeIsoValue.toRelative(v, VolumeData.Empty.dataStats).relativeValue,
PD.Numeric(2, { min: -10, max: 10, step: 0.01 }) (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) (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 type IsoValueParam = typeof IsoValueParam
export const VolumeIsosurfaceParams = { export const VolumeIsosurfaceParams = {
...@@ -154,6 +158,7 @@ export function getIsosurfaceParams(ctx: ThemeRegistryContext, volume: VolumeDat ...@@ -154,6 +158,7 @@ export function getIsosurfaceParams(ctx: ThemeRegistryContext, volume: VolumeDat
(v: VolumeIsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative', (v: VolumeIsoValue) => v.kind === 'absolute' ? 'absolute' : 'relative',
(v: VolumeIsoValue, c: 'absolute' | 'relative') => c === 'absolute' ? VolumeIsoValue.toAbsolute(v, stats) : VolumeIsoValue.toRelative(v, stats) (v: VolumeIsoValue, c: 'absolute' | 'relative') => c === 'absolute' ? VolumeIsoValue.toAbsolute(v, stats) : VolumeIsoValue.toRelative(v, stats)
) )
return p return p
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment