Skip to content
Snippets Groups Projects
Commit 7bf4fe13 authored by Alexander Rose's avatar Alexander Rose
Browse files

wip, data format registry

parent a2ba753a
No related branches found
No related tags found
No related merge requests found
...@@ -30,6 +30,7 @@ import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version'; ...@@ -30,6 +30,7 @@ import { PLUGIN_VERSION, PLUGIN_VERSION_DATE } from './version';
import { PluginLayout } from './layout'; import { PluginLayout } from './layout';
import { List } from 'immutable'; import { List } from 'immutable';
import { StateTransformParameters } from './ui/state/common'; import { StateTransformParameters } from './ui/state/common';
import { DataFormatRegistry } from './state/actions/basic';
export class PluginContext { export class PluginContext {
private disposed = false; private disposed = false;
...@@ -87,6 +88,10 @@ export class PluginContext { ...@@ -87,6 +88,10 @@ export class PluginContext {
themeCtx: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext themeCtx: { colorThemeRegistry: ColorTheme.createRegistry(), sizeThemeRegistry: SizeTheme.createRegistry() } as ThemeRegistryContext
} }
readonly dataFormat = {
registry: new DataFormatRegistry()
}
readonly customModelProperties = new CustomPropertyRegistry(); readonly customModelProperties = new CustomPropertyRegistry();
readonly customParamEditors = new Map<string, StateTransformParameters.Class>(); readonly customParamEditors = new Map<string, StateTransformParameters.Class>();
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
*/ */
import { PluginContext } from 'mol-plugin/context'; import { PluginContext } from 'mol-plugin/context';
import { StateTree, Transformer } from 'mol-state'; import { StateTree, Transformer, StateObject } from 'mol-state';
import { StateAction } from 'mol-state/action'; import { StateAction } from 'mol-state/action';
import { StateSelection } from 'mol-state/state/selection'; import { StateSelection } from 'mol-state/state/selection';
import { StateTreeBuilder } from 'mol-state/tree/builder'; import { StateTreeBuilder } from 'mol-state/tree/builder';
...@@ -15,7 +15,7 @@ import { PluginStateObject } from '../objects'; ...@@ -15,7 +15,7 @@ import { PluginStateObject } from '../objects';
import { StateTransforms } from '../transforms'; import { StateTransforms } from '../transforms';
import { Download } from '../transforms/data'; import { Download } from '../transforms/data';
import { StructureRepresentation3DHelpers } from '../transforms/representation'; import { StructureRepresentation3DHelpers } from '../transforms/representation';
import { getFileInfo, FileInput } from 'mol-util/file-info'; import { getFileInfo, FileInfo } from 'mol-util/file-info';
import { Task } from 'mol-task'; import { Task } from 'mol-task';
// TODO: "structure/volume parser provider" // TODO: "structure/volume parser provider"
...@@ -170,55 +170,128 @@ export const UpdateTrajectory = StateAction.build({ ...@@ -170,55 +170,128 @@ export const UpdateTrajectory = StateAction.build({
// //
const VolumeFormats = { 'ccp4': '', 'mrc': '', 'map': '', 'dsn6': '', 'brix': '', 'dscif': '' } export class DataFormatRegistry<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String, M extends StateObject> {
type VolumeFormat = keyof typeof VolumeFormats private _list: { name: string, provider: DataFormatProvider<D, M> }[] = []
private _map = new Map<string, DataFormatProvider<D, M>>()
function getVolumeData(format: VolumeFormat, b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>) {
switch (format) { get default() { return this._list[0]; }
case 'ccp4': case 'mrc': case 'map': get types(): [string, string][] {
return b.apply(StateTransforms.Data.ParseCcp4).apply(StateTransforms.Model.VolumeFromCcp4); return this._list.map(e => [e.name, e.provider.label] as [string, string]);
case 'dsn6': case 'brix':
return b.apply(StateTransforms.Data.ParseDsn6).apply(StateTransforms.Model.VolumeFromDsn6);
case 'dscif':
return b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.VolumeFromDensityServerCif);
} }
}
function createVolumeTree(format: VolumeFormat, ctx: PluginContext, b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>): StateTree { constructor() {
return getVolumeData(format, b) this.add('ccp4', Ccp4Provider)
.apply(StateTransforms.Representation.VolumeRepresentation3D) this.add('dsn6', Dsn6Provider)
// the parameters will be used automatically by the reconciler and the IsoValue object this.add('dscif', DscifProvider)
// will get the correct Stats object instead of the empty one };
// VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface'))
.getTree(); add(name: string, provider: DataFormatProvider<D, M>) {
} 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)
}
function getFileFormat(format: VolumeFormat | 'auto', file: FileInput, data?: Uint8Array): VolumeFormat { auto(info: FileInfo, dataStateObject: D) {
if (format === 'auto') { for (let i = 0, il = this.list.length; i < il; ++i) {
const fileFormat = getFileInfo(file).ext const { provider } = this._list[i]
if (fileFormat in VolumeFormats) { if (provider.isApplicable(info, dataStateObject.data)) return provider
return fileFormat as VolumeFormat }
throw new Error('no compatible data format provider available')
}
get(name: string): DataFormatProvider<D, M> {
if (this._map.has(name)) {
return this._map.get(name)!
} else { } else {
throw new Error('unsupported format') throw new Error(`unknown data format name '${name}'`)
} }
} else { }
return format
get list() {
return this._list
}
}
interface DataFormatProvider<D extends PluginStateObject.Data.Binary | PluginStateObject.Data.String, M extends StateObject> {
label: string
description: string
fileExtensions: string[]
isApplicable(info: FileInfo, data: string | Uint8Array): boolean
getDefaultBuilder(b: StateTreeBuilder.To<D>): StateTreeBuilder.To<M>
}
const Ccp4Provider: DataFormatProvider<any, any> = {
label: 'CCP4/MRC/BRIX',
description: 'CCP4/MRC/BRIX',
fileExtensions: ['ccp4', 'mrc', 'map'],
isApplicable: (info: FileInfo, data: Uint8Array) => {
return info.ext === 'ccp4' || info.ext === 'mrc' || info.ext === 'map'
},
getDefaultBuilder: (b: StateTreeBuilder.To<PluginStateObject.Data.Binary>) => {
return b.apply(StateTransforms.Data.ParseCcp4)
.apply(StateTransforms.Model.VolumeFromCcp4)
.apply(StateTransforms.Representation.VolumeRepresentation3D)
}
}
const Dsn6Provider: DataFormatProvider<any, any> = {
label: 'DSN6/BRIX',
description: 'DSN6/BRIX',
fileExtensions: ['dsn6', 'brix'],
isApplicable: (info: FileInfo, data: Uint8Array) => {
return info.ext === 'dsn6' || info.ext === 'brix'
},
getDefaultBuilder: (b: StateTreeBuilder.To<PluginStateObject.Data.Binary>) => {
return b.apply(StateTransforms.Data.ParseDsn6)
.apply(StateTransforms.Model.VolumeFromDsn6)
.apply(StateTransforms.Representation.VolumeRepresentation3D)
} }
} }
const DscifProvider: DataFormatProvider<any, any> = {
label: 'DensityServer CIF',
description: 'DensityServer CIF',
fileExtensions: ['cif'],
isApplicable: (info: FileInfo, data: Uint8Array) => {
return info.ext === 'cif'
},
getDefaultBuilder: (b: StateTreeBuilder.To<PluginStateObject.Data.Binary>) => {
return b.apply(StateTransforms.Data.ParseCif, { })
.apply(StateTransforms.Model.VolumeFromDensityServerCif)
.apply(StateTransforms.Representation.VolumeRepresentation3D)
}
}
//
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({ export const OpenVolume = StateAction.build({
display: { name: 'Open Volume', description: 'Load a volume from file and create its default visual' }, display: { name: 'Open Volume', description: 'Load a volume from file and create its default visual' },
from: PluginStateObject.Root, from: PluginStateObject.Root,
params: { params: (a, ctx: PluginContext) => {
file: PD.File({ accept: '.ccp4,.mrc,.map,.dsn6,.brix,.cif'}), const { extensions, options } = getDataFormatExtensionsOptions(ctx.dataFormat.registry)
isBinary: PD.Boolean(true), return {
format: PD.Select('auto', [ file: PD.File({ accept: extensions.map(e => `.${e}`).join(',')}),
['auto', 'Automatic'], ['ccp4', 'CCP4'], ['mrc', 'MRC'], ['map', 'MAP'], ['dsn6', 'DSN6'], ['brix', 'BRIX'], ['dscif', 'densityServerCIF'] 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 => { })(({ params, state }, ctx: PluginContext) => Task.create('Open Volume', async taskCtx => {
const dataTree = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: true }); const data = state.build().toRoot().apply(StateTransforms.Data.ReadFile, { file: params.file, isBinary: params.isBinary });
const volumeData = await state.updateTree(dataTree).runInContext(taskCtx); const dataStateObject = await state.updateTree(data).runInContext(taskCtx);
// Alternative for more complex states where the builder is not a simple StateTreeBuilder.To<>: // Alternative for more complex states where the builder is not a simple StateTreeBuilder.To<>:
/* /*
...@@ -227,10 +300,11 @@ export const OpenVolume = StateAction.build({ ...@@ -227,10 +300,11 @@ export const OpenVolume = StateAction.build({
const dataCell = state.select(dataRef)[0]; const dataCell = state.select(dataRef)[0];
*/ */
const format = getFileFormat(params.format, params.file, volumeData.data as Uint8Array); const provider = params.format === 'auto' ? ctx.dataFormat.registry.auto(getFileInfo(params.file), dataStateObject) : ctx.dataFormat.registry.get(params.format)
const volumeTree = state.build().to(dataTree.ref); const b = state.build().to(data.ref);
const tree = provider.getDefaultBuilder(b).getTree()
// need to await the 2nd update the so that the enclosing Task finishes after the update is done. // need to await the 2nd update the so that the enclosing Task finishes after the update is done.
await state.updateTree(createVolumeTree(format, ctx, volumeTree)).runInContext(taskCtx); await state.updateTree(tree).runInContext(taskCtx);
})); }));
export { DownloadDensity }; export { DownloadDensity };
...@@ -238,41 +312,40 @@ type DownloadDensity = typeof DownloadDensity ...@@ -238,41 +312,40 @@ type DownloadDensity = typeof DownloadDensity
const DownloadDensity = StateAction.build({ const DownloadDensity = StateAction.build({
from: PluginStateObject.Root, from: PluginStateObject.Root,
display: { name: 'Download Density', description: 'Load a density from the provided source and create its default visual.' }, display: { name: 'Download Density', description: 'Load a density from the provided source and create its default visual.' },
params: { params: (a, ctx: PluginContext) => {
source: PD.MappedStatic('rcsb', { const { options } = getDataFormatExtensionsOptions(ctx.dataFormat.registry)
'pdbe': PD.Group({ return {
id: PD.Text('1tqn', { label: 'Id' }), source: PD.MappedStatic('rcsb', {
type: PD.Select('2fofc', [['2fofc', '2Fo-Fc'], ['fofc', 'Fo-Fc']]), 'pdbe': PD.Group({
}, { isFlat: true }), id: PD.Text('1tqn', { label: 'Id' }),
'rcsb': PD.Group({ type: PD.Select('2fofc', [['2fofc', '2Fo-Fc'], ['fofc', 'Fo-Fc']]),
id: PD.Text('1tqn', { label: 'Id' }), }, { isFlat: true }),
type: PD.Select('2fofc', [['2fofc', '2Fo-Fc'], ['fofc', 'Fo-Fc']]), 'rcsb': PD.Group({
}, { isFlat: true }), id: PD.Text('1tqn', { label: 'Id' }),
'url': PD.Group({ type: PD.Select('2fofc', [['2fofc', '2Fo-Fc'], ['fofc', 'Fo-Fc']]),
url: PD.Text(''), }, { isFlat: true }),
isBinary: PD.Boolean(true), 'url': PD.Group({
format: PD.Select('auto', [ url: PD.Text(''),
['auto', 'Automatic'], ['ccp4', 'CCP4'], ['mrc', 'MRC'], ['map', 'MAP'], ['dsn6', 'DSN6'], ['brix', 'BRIX'], ['dscif', 'densityServerCIF'] isBinary: PD.Boolean(false),
]), format: PD.Select('auto', options),
}, { isFlat: true }) }, { isFlat: true })
}, { }, {
options: [ options: [
['pdbe', 'PDBe X-ray maps'], ['pdbe', 'PDBe X-ray maps'],
['rcsb', 'RCSB X-ray maps'], ['rcsb', 'RCSB X-ray maps'],
['url', 'URL'] ['url', 'URL']
] ]
}) })
}
} }
})(({ params, state }, ctx: PluginContext) => { })(({ params, state }, ctx: PluginContext) => Task.create('Download Density', async taskCtx => {
const b = state.build();
const src = params.source; const src = params.source;
let downloadParams: Transformer.Params<Download>; let downloadParams: Transformer.Params<Download>;
let format: VolumeFormat let provider: DataFormatProvider<any, any>
switch (src.name) { switch (src.name) {
case 'url': case 'url':
downloadParams = src.params; downloadParams = src.params;
format = getFileFormat(src.params.format, src.params.url)
break; break;
case 'pdbe': case 'pdbe':
downloadParams = { downloadParams = {
...@@ -282,7 +355,6 @@ const DownloadDensity = StateAction.build({ ...@@ -282,7 +355,6 @@ const DownloadDensity = StateAction.build({
isBinary: true, isBinary: true,
label: `PDBe X-ray map: ${src.params.id}` label: `PDBe X-ray map: ${src.params.id}`
}; };
format = 'ccp4'
break; break;
case 'rcsb': case 'rcsb':
downloadParams = { downloadParams = {
...@@ -292,11 +364,28 @@ const DownloadDensity = StateAction.build({ ...@@ -292,11 +364,28 @@ const DownloadDensity = StateAction.build({
isBinary: true, isBinary: true,
label: `RCSB X-ray map: ${src.params.id}` label: `RCSB X-ray map: ${src.params.id}`
}; };
format = 'dsn6'
break; break;
default: throw new Error(`${(src as any).name} not supported.`); default: throw new Error(`${(src as any).name} not supported.`);
} }
const data = b.toRoot().apply(StateTransforms.Data.Download, downloadParams); const data = state.build().toRoot().apply(StateTransforms.Data.Download, downloadParams);
return state.updateTree(createVolumeTree(format, ctx, data)); const dataStateObject = await state.updateTree(data).runInContext(taskCtx);
});
\ No newline at end of file switch (src.name) {
case 'url':
downloadParams = src.params;
provider = src.params.format === 'auto' ? ctx.dataFormat.registry.auto(getFileInfo(downloadParams.url), dataStateObject) : ctx.dataFormat.registry.get(src.params.format)
break;
case 'pdbe':
provider = ctx.dataFormat.registry.get('ccp4')
break;
case 'rcsb':
provider = ctx.dataFormat.registry.get('dsn6')
break;
default: throw new Error(`${(src as any).name} not supported.`);
}
const b = state.build().to(data.ref);
const tree = provider.getDefaultBuilder(b).getTree()
await state.updateTree(tree).runInContext(taskCtx);
}));
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment