diff --git a/src/mol-io/reader/_spec/ccp4.spec.ts b/src/mol-io/reader/_spec/ccp4.spec.ts index fc57da2ad58e4c009ed85cc338dbf22b557c2e81..1fe2202d9d01aa5f19a3138bdac800ec8adf4058 100644 --- a/src/mol-io/reader/_spec/ccp4.spec.ts +++ b/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) diff --git a/src/mol-io/reader/ccp4/parser.ts b/src/mol-io/reader/ccp4/parser.ts index 30e7c9e9db807ed6210956cdd1f2599e7cdbb976..cf56ab484723e963f27051225a2d242b1c15094f 100644 --- a/src/mol-io/reader/ccp4/parser.ts +++ b/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; \ No newline at end of file +export function parse(buffer: Uint8Array) { + const file = FileHandle.fromBuffer(buffer) + return Task.create<Result<Ccp4File>>('Parse CCP4', ctx => parseInternal(file, ctx)); +} \ No newline at end of file diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 6de25de940cab524258ede72007a227af87d05a7..5325d1856d130bf9b100a1a5549cb82324d53a2d 100644 --- a/src/mol-plugin/context.ts +++ b/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) { diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index 64c38518ecf5609517378fc4c679d4b2d6e29646..122d6ab7e2ed358dd800b67822d74ed91e1a3442 100644 --- a/src/mol-plugin/index.ts +++ b/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), diff --git a/src/mol-plugin/state/actions/basic.ts b/src/mol-plugin/state/actions/basic.ts index 13a43c24e53a214869eb60581a98d43403383bd4..01f22b2b4b83d0655e24815ade299d18d997544f 100644 --- a/src/mol-plugin/state/actions/basic.ts +++ b/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)); }); \ No newline at end of file diff --git a/src/mol-plugin/state/objects.ts b/src/mol-plugin/state/objects.ts index 8f90acb581763d535695903fb3e84aea9c0f5623..38495fe64612eea479ca7e82d87cf9f0643909b4 100644 --- a/src/mol-plugin/state/objects.ts +++ b/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 { diff --git a/src/mol-plugin/state/transforms/data.ts b/src/mol-plugin/state/transforms/data.ts index 0878ceb369e3b4567db73515b131ea54e8f22409..a32194ef65b7755933c08951798b56a5d9639dae 100644 --- a/src/mol-plugin/state/transforms/data.ts +++ b/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); + }); + } }); \ 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 51c5cfc6da08f2c10523aae1d5ddb8ebbd2f9f75..72a8d63f13c3e1ff3e77f564728817df78438b63 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/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); } -} \ No newline at end of file +} + +// + +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); + }); + } +}); \ No newline at end of file diff --git a/src/mol-plugin/state/transforms/representation.ts b/src/mol-plugin/state/transforms/representation.ts index 7536742a7329b43e710f3303b5c966e27f07c6da..83c33de0663d184c2fab2fe0576058d226d40320 100644 --- a/src/mol-plugin/state/transforms/representation.ts +++ b/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; + }); + } }); \ No newline at end of file