diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 0a3904c8f29753a9e5cd940569fa9ced98a8f7e1..d592a2ef37742a39bdc79c15dfaf0ba1804dbdde 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -29,7 +29,7 @@ import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version'; import { PluginLayout } from './layout'; import { List } from 'immutable'; import { StateTransformParameters } from './ui/state/common'; -import { DataFormatRegistry } from './state/actions/volume'; +import { DataFormatRegistry } from './state/actions/data-format'; import { PluginBehavior } from './behavior/behavior'; import { CustomPropertyRegistry } from 'mol-model-props/common/custom-property-registry'; diff --git a/src/mol-plugin/index.ts b/src/mol-plugin/index.ts index b701c7828ba25bf5fe1b6a3d4044059c4c5780b4..e54645b3e1ac94d76370f804e44dc9045d46f450 100644 --- a/src/mol-plugin/index.ts +++ b/src/mol-plugin/index.ts @@ -25,8 +25,7 @@ export const DefaultPluginSpec: PluginSpec = { actions: [ PluginSpec.Action(StateActions.Structure.DownloadStructure), PluginSpec.Action(StateActions.Volume.DownloadDensity), - PluginSpec.Action(StateActions.Structure.OpenStructure), - PluginSpec.Action(StateActions.Volume.OpenVolume), + PluginSpec.Action(StateActions.DataFormat.OpenFile), PluginSpec.Action(StateActions.Structure.CreateComplexRepresentation), PluginSpec.Action(StateActions.Structure.EnableModelCustomProps), @@ -35,6 +34,7 @@ export const DefaultPluginSpec: PluginSpec = { PluginSpec.Action(StateTransforms.Data.Download), PluginSpec.Action(StateTransforms.Data.ParseCif), PluginSpec.Action(StateTransforms.Data.ParseCcp4), + PluginSpec.Action(StateTransforms.Data.ParseDsn6), PluginSpec.Action(StateTransforms.Model.StructureAssemblyFromModel), PluginSpec.Action(StateTransforms.Model.StructureSymmetryFromModel), PluginSpec.Action(StateTransforms.Model.StructureFromModel), diff --git a/src/mol-plugin/state/actions.ts b/src/mol-plugin/state/actions.ts index c5b1fb6a04dfc810abadefeab19d3ef266a90826..5491ef5cfdcb1f0e9c4c7575c888199f9e38f4cc 100644 --- a/src/mol-plugin/state/actions.ts +++ b/src/mol-plugin/state/actions.ts @@ -6,8 +6,10 @@ import * as Structure from './actions/structure' import * as Volume from './actions/volume' +import * as DataFormat from './actions/data-format' export const StateActions = { Structure, - Volume + Volume, + DataFormat } \ No newline at end of file diff --git a/src/mol-plugin/state/actions/data-format.ts b/src/mol-plugin/state/actions/data-format.ts new file mode 100644 index 0000000000000000000000000000000000000000..eb9ad461229f839602f393d4a1f9829ec9b8fd03 --- /dev/null +++ b/src/mol-plugin/state/actions/data-format.ts @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { PluginContext } from 'mol-plugin/context'; +import { State, StateBuilder, StateAction } from 'mol-state'; +import { Task } from 'mol-task'; +import { FileInfo, getFileInfo } from 'mol-util/file-info'; +import { PluginStateObject } from '../objects'; +import { ParamDefinition as PD } from 'mol-util/param-definition'; +import { Ccp4Provider, Dsn6Provider, DscifProvider } from './volume'; +import { StateTransforms } from '../transforms'; +import { MmcifProvider, PdbProvider } from './structure'; + +export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> { + private _list: { name: string, provider: DataFormatProvider<D> }[] = [] + private _map = new Map<string, DataFormatProvider<D>>() + private _extensions: Set<string> | undefined = undefined + private _binaryExtensions: Set<string> | undefined = undefined + private _options: [string, string][] | undefined = undefined + + get types(): [string, string][] { + return this._list.map(e => [e.name, e.provider.label] as [string, string]); + } + + get extensions() { + if (this._extensions) return this._extensions + const extensions = new Set<string>() + this._list.forEach(({ provider }) => { + provider.stringExtensions.forEach(ext => extensions.add(ext)) + provider.binaryExtensions.forEach(ext => extensions.add(ext)) + }) + this._extensions = extensions + return extensions + } + + get binaryExtensions() { + if (this._binaryExtensions) return this._binaryExtensions + const binaryExtensions = new Set<string>() + this._list.forEach(({ provider }) => provider.binaryExtensions.forEach(ext => binaryExtensions.add(ext))) + this._binaryExtensions = binaryExtensions + return binaryExtensions + } + + get options() { + if (this._options) return this._options + const options: [string, string][] = [['auto', 'Automatic']] + this._list.forEach(({ name, provider }) => options.push([ name, provider.label ])) + this._options = options + return options + } + + constructor() { + this.add('ccp4', Ccp4Provider) + this.add('dscif', DscifProvider) + this.add('dsn6', Dsn6Provider) + this.add('mmcif', MmcifProvider) + this.add('pdb', PdbProvider) + }; + + private _clear() { + this._extensions = undefined + this._binaryExtensions = undefined + this._options = undefined + } + + add(name: string, provider: DataFormatProvider<D>) { + this._clear() + this._list.push({ name, provider }) + this._map.set(name, provider) + } + + remove(name: string) { + this._clear() + this._list.splice(this._list.findIndex(e => e.name === name), 1) + this._map.delete(name) + } + + auto(info: FileInfo, dataStateObject: D) { + for (let i = 0, il = this.list.length; i < il; ++i) { + const { provider } = this._list[i] + if (provider.isApplicable(info, dataStateObject.data)) return provider + } + throw new Error('no compatible data format provider available') + } + + get(name: string): DataFormatProvider<D> { + if (this._map.has(name)) { + return this._map.get(name)! + } else { + throw new Error(`unknown data format name '${name}'`) + } + } + + get list() { + return this._list + } +} + +export interface DataFormatProvider<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> { + label: string + description: string + stringExtensions: string[] + binaryExtensions: string[] + isApplicable(info: FileInfo, data: string | Uint8Array): boolean + getDefaultBuilder(ctx: PluginContext, data: StateBuilder.To<D>, state?: State): Task<void> +} + +// + +export const OpenFile = StateAction.build({ + display: { name: 'Open File', description: 'Load a file and create its default visual' }, + from: PluginStateObject.Root, + params: (a, ctx: PluginContext) => { + const { extensions, options } = ctx.dataFormat.registry + return { + file: PD.File({ accept: Array.from(extensions).map(e => `.${e}`).join(',')}), + format: PD.Select('auto', options), + } + } +})(({ params, state }, ctx: PluginContext) => Task.create('Open File', async taskCtx => { + const info = getFileInfo(params.file) + const data = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: ctx.dataFormat.registry.binaryExtensions.has(info.ext) }); + const dataStateObject = await state.updateTree(data).runInContext(taskCtx); + + // Alternative for more complex states where the builder is not a simple StateBuilder.To<>: + /* + const dataRef = dataTree.ref; + await state.updateTree(dataTree).runInContext(taskCtx); + const dataCell = state.select(dataRef)[0]; + */ + + // const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) }); + + const provider = params.format === 'auto' ? ctx.dataFormat.registry.auto(info, dataStateObject) : ctx.dataFormat.registry.get(params.format) + const b = state.build().to(data.ref); + // need to await the 2nd update the so that the enclosing Task finishes after the update is done. + await provider.getDefaultBuilder(ctx, b, state).runInContext(taskCtx) +})); \ No newline at end of file diff --git a/src/mol-plugin/state/actions/structure.ts b/src/mol-plugin/state/actions/structure.ts index b96e2d60fe0f0435c9079d7048d514f5f754a32b..53696f1eb294477cfbfe9157333d2187949a62e7 100644 --- a/src/mol-plugin/state/actions/structure.ts +++ b/src/mol-plugin/state/actions/structure.ts @@ -6,15 +6,50 @@ */ import { PluginContext } from 'mol-plugin/context'; -import { StateAction, StateBuilder, StateSelection, StateTransformer } from 'mol-state'; +import { StateAction, StateBuilder, StateSelection, StateTransformer, State } from 'mol-state'; 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 { CustomModelProperties } from '../transforms/model'; +import { DataFormatProvider } from './data-format'; +import { FileInfo } from 'mol-util/file-info'; +import { Task } from 'mol-task'; + +export const MmcifProvider: DataFormatProvider<any> = { + label: 'mmCIF', + description: 'mmCIF', + stringExtensions: ['cif', 'mmcif', 'mcif'], + binaryExtensions: ['bcif'], + isApplicable: (info: FileInfo, data: Uint8Array) => { + return info.ext === 'cif' || info.ext === 'mmcif' || info.ext === 'mcif' || info.ext === 'bcif' + }, + getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, state: State) => { + return Task.create('mmCIF default builder', async taskCtx => { + const traj = createModelTree(data, 'cif'); + await state.updateTree(createStructureTree(ctx, traj, false)).runInContext(taskCtx) + }) + } +} + +export const PdbProvider: DataFormatProvider<any> = { + label: 'PDB', + description: 'PDB', + stringExtensions: ['pdb', 'ent'], + binaryExtensions: [], + isApplicable: (info: FileInfo, data: Uint8Array) => { + return info.ext === 'pdb' || info.ext === 'ent' + }, + getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.String>, state: State) => { + return Task.create('mmCIF default builder', async taskCtx => { + const traj = createModelTree(data, 'pdb'); + await state.updateTree(createStructureTree(ctx, traj, false)).runInContext(taskCtx) + }) + } +} -// TODO: "structure/volume parser provider" +// export { DownloadStructure }; type DownloadStructure = typeof DownloadStructure @@ -76,17 +111,6 @@ const DownloadStructure = StateAction.build({ return state.updateTree(createStructureTree(ctx, traj, params.source.params.supportProps)); }); -export const OpenStructure = StateAction.build({ - display: { name: 'Open Structure', description: 'Load a structure from file and create its default Assembly and visual' }, - from: PluginStateObject.Root, - params: { file: PD.File({ accept: '.cif,.bcif' }) } -})(({ params, state }, ctx: PluginContext) => { - const b = state.build(); - const data = b.toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: /\.bcif$/i.test(params.file.name) }); - const traj = createModelTree(data, 'cif'); - return state.updateTree(createStructureTree(ctx, traj, false)); -}); - function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>, format: 'pdb' | 'cif' = 'cif') { const parsed = format === 'cif' ? b.apply(StateTransforms.Data.ParseCif, void 0, { props: { isGhost: true }}).apply(StateTransforms.Model.TrajectoryFromMmCif, void 0, { props: { isGhost: true }}) diff --git a/src/mol-plugin/state/actions/volume.ts b/src/mol-plugin/state/actions/volume.ts index c8bbc9c892081968e785ebec860dd76516eae3f1..ab07fd80a3098c5d9b303dbcb1fa4d26a1148ee7 100644 --- a/src/mol-plugin/state/actions/volume.ts +++ b/src/mol-plugin/state/actions/volume.ts @@ -7,7 +7,7 @@ import { VolumeIsoValue } from 'mol-model/volume'; import { PluginContext } from 'mol-plugin/context'; -import { State, StateAction, StateBuilder, StateObject, StateTransformer } from 'mol-state'; +import { State, StateAction, StateBuilder, StateTransformer } from 'mol-state'; import { Task } from 'mol-task'; import { ColorNames } from 'mol-util/color/tables'; import { FileInfo, getFileInfo } from 'mol-util/file-info'; @@ -17,65 +17,13 @@ import { StateTransforms } from '../transforms'; import { Download } from '../transforms/data'; import { VolumeRepresentation3DHelpers } from '../transforms/representation'; import { VolumeStreaming } from 'mol-plugin/behavior/dynamic/volume'; +import { DataFormatProvider } from './data-format'; -export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String, M extends StateObject> { - private _list: { name: string, provider: DataFormatProvider<D> }[] = [] - private _map = new Map<string, DataFormatProvider<D>>() - - get default() { return this._list[0]; } - get types(): [string, string][] { - return this._list.map(e => [e.name, e.provider.label] as [string, string]); - } - - constructor() { - this.add('ccp4', Ccp4Provider) - this.add('dsn6', Dsn6Provider) - this.add('dscif', DscifProvider) - }; - - add(name: string, provider: DataFormatProvider<D>) { - this._list.push({ name, provider }) - this._map.set(name, provider) - } - - remove(name: string) { - this._list.splice(this._list.findIndex(e => e.name === name), 1) - this._map.delete(name) - } - - auto(info: FileInfo, dataStateObject: D) { - for (let i = 0, il = this.list.length; i < il; ++i) { - const { provider } = this._list[i] - if (provider.isApplicable(info, dataStateObject.data)) return provider - } - throw new Error('no compatible data format provider available') - } - - get(name: string): DataFormatProvider<D> { - if (this._map.has(name)) { - return this._map.get(name)! - } else { - throw new Error(`unknown data format name '${name}'`) - } - } - - get list() { - return this._list - } -} - -interface DataFormatProvider<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String> { - label: string - description: string - fileExtensions: string[] - isApplicable(info: FileInfo, data: string | Uint8Array): boolean - getDefaultBuilder(ctx: PluginContext, data: StateBuilder.To<D>, state?: State): Task<void> -} - -const Ccp4Provider: DataFormatProvider<any> = { +export const Ccp4Provider: DataFormatProvider<any> = { label: 'CCP4/MRC/BRIX', description: 'CCP4/MRC/BRIX', - fileExtensions: ['ccp4', 'mrc', 'map'], + stringExtensions: [], + binaryExtensions: ['ccp4', 'mrc', 'map'], isApplicable: (info: FileInfo, data: Uint8Array) => { return info.ext === 'ccp4' || info.ext === 'mrc' || info.ext === 'map' }, @@ -89,10 +37,11 @@ const Ccp4Provider: DataFormatProvider<any> = { } } -const Dsn6Provider: DataFormatProvider<any> = { +export const Dsn6Provider: DataFormatProvider<any> = { label: 'DSN6/BRIX', description: 'DSN6/BRIX', - fileExtensions: ['dsn6', 'brix'], + stringExtensions: [], + binaryExtensions: ['dsn6', 'brix'], isApplicable: (info: FileInfo, data: Uint8Array) => { return info.ext === 'dsn6' || info.ext === 'brix' }, @@ -106,12 +55,13 @@ const Dsn6Provider: DataFormatProvider<any> = { } } -const DscifProvider: DataFormatProvider<any> = { +export const DscifProvider: DataFormatProvider<any> = { label: 'DensityServer CIF', description: 'DensityServer CIF', - fileExtensions: ['cif'], + stringExtensions: ['cif'], + binaryExtensions: ['bcif'], isApplicable: (info: FileInfo, data: Uint8Array) => { - return info.ext === 'cif' + return info.ext === 'cif' || info.ext === 'bcif' }, getDefaultBuilder: (ctx: PluginContext, data: StateBuilder.To<PluginStateObject.Data.Binary>, state: State) => { return Task.create('DensityServer CIF default builder', async taskCtx => { @@ -143,53 +93,13 @@ const DscifProvider: DataFormatProvider<any> = { } } -// - -function getDataFormatExtensionsOptions(dataFormatRegistry: DataFormatRegistry<any, any>) { - const extensions: string[] = [] - const options: [string, string][] = [['auto', 'Automatic']] - dataFormatRegistry.list.forEach(({ name, provider }) => { - extensions.push(...provider.fileExtensions) - options.push([ name, provider.label ]) - }) - return { extensions, options } -} - -export const OpenVolume = StateAction.build({ - display: { name: 'Open Volume', description: 'Load a volume from file and create its default visual' }, - from: PluginStateObject.Root, - params: (a, ctx: PluginContext) => { - const { extensions, options } = getDataFormatExtensionsOptions(ctx.dataFormat.registry) - return { - file: PD.File({ accept: extensions.map(e => `.${e}`).join(',')}), - format: PD.Select('auto', options), - isBinary: PD.Boolean(true), // TOOD should take selected format into account - } - } -})(({ params, state }, ctx: PluginContext) => Task.create('Open Volume', async taskCtx => { - const data = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: params.isBinary }); - const dataStateObject = await state.updateTree(data).runInContext(taskCtx); - - // Alternative for more complex states where the builder is not a simple StateBuilder.To<>: - /* - const dataRef = dataTree.ref; - await state.updateTree(dataTree).runInContext(taskCtx); - const dataCell = state.select(dataRef)[0]; - */ - - const provider = params.format === 'auto' ? ctx.dataFormat.registry.auto(getFileInfo(params.file), dataStateObject) : ctx.dataFormat.registry.get(params.format) - const b = state.build().to(data.ref); - // need to await the 2nd update the so that the enclosing Task finishes after the update is done. - await provider.getDefaultBuilder(ctx, b, state).runInContext(taskCtx) -})); - export { DownloadDensity }; type DownloadDensity = typeof DownloadDensity const DownloadDensity = StateAction.build({ from: PluginStateObject.Root, display: { name: 'Download Density', description: 'Load a density from the provided source and create its default visual.' }, params: (a, ctx: PluginContext) => { - const { options } = getDataFormatExtensionsOptions(ctx.dataFormat.registry) + const { options } = ctx.dataFormat.registry return { source: PD.MappedStatic('rcsb', { 'pdbe': PD.Group({