diff --git a/src/mol-io/reader/ccp4/parser.ts b/src/mol-io/reader/ccp4/parser.ts index 3a9266e9c03c0b945d5296179ab554dcc4eb5722..43cc24a8302bba97fc893f7eaee26ebcdabfb277 100644 --- a/src/mol-io/reader/ccp4/parser.ts +++ b/src/mol-io/reader/ccp4/parser.ts @@ -10,7 +10,7 @@ import Result from '../result' import { FileHandle } from '../../common/file-handle'; async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Result<Ccp4File>> { - await ctx.update({ message: 'Parsing CCP4 file...' }); + await ctx.update({ message: 'Parsing CCP4/MRC file...' }); const { buffer } = await file.readBuffer(0, file.length) const bin = buffer.buffer diff --git a/src/mol-io/reader/dsn6/parser.ts b/src/mol-io/reader/dsn6/parser.ts index 3e21bb7ba4e8e93918de065934d0f7c49b535dc4..a77c968662036b64dab1f21d0df9a90edd3d117d 100644 --- a/src/mol-io/reader/dsn6/parser.ts +++ b/src/mol-io/reader/dsn6/parser.ts @@ -26,9 +26,9 @@ function parseBrixHeader(str: string): Dsn6Header { alpha: parseFloat(str.substr(103, 10)), beta: parseFloat(str.substr(113, 10)), gamma: parseFloat(str.substr(123, 10)), - divisor: parseFloat(str.substr(138, 12)) / 100, + divisor: parseFloat(str.substr(138, 12)), summand: parseInt(str.substr(155, 8)), - sigma: parseFloat(str.substr(170, 12)) * 100 + sigma: parseFloat(str.substr(170, 12)) } } @@ -57,7 +57,7 @@ function parseDsn6Header(int: Int16Array): Dsn6Header { } async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Result<Dsn6File>> { - await ctx.update({ message: 'Parsing CCP4 file...' }); + await ctx.update({ message: 'Parsing DSN6/BRIX file...' }); const { buffer } = await file.readBuffer(0, file.length) const bin = buffer.buffer @@ -67,18 +67,19 @@ async function parseInternal(file: FileHandle, ctx: RuntimeContext): Promise<Res const brixStr = String.fromCharCode.apply(null, byteView.subarray(0, 512)) const isBrix = brixStr.startsWith(':-)') - const header = isBrix ? parseBrixHeader(brixStr) : parseDsn6Header(intView) - const { divisor, summand } = header - if (!isBrix) { // for DSN6, swap byte order when big endian - if (intView[ 18 ] !== 100) { + if (intView[18] !== 100) { for (let i = 0, n = intView.length; i < n; ++i) { - const val = intView[ i ] - intView[ i ] = ((val & 0xff) << 8) | ((val >> 8) & 0xff) + const val = intView[i] + intView[i] = ((val & 0xff) << 8) | ((val >> 8) & 0xff) } } } + + const header = isBrix ? parseBrixHeader(brixStr) : parseDsn6Header(intView) + const { divisor, summand } = header + const values = new Float32Array(header.xExtent * header.yExtent * header.zExtent) let offset = 512 diff --git a/src/mol-model/volume/formats/dsn6.ts b/src/mol-model/volume/formats/dsn6.ts index 1a578ce14f3dd8cb0378468dfead083f93d31cb6..d945c19cfb03a6458fa57acd78138369c772c236 100644 --- a/src/mol-model/volume/formats/dsn6.ts +++ b/src/mol-model/volume/formats/dsn6.ts @@ -28,7 +28,7 @@ function volumeFromDsn6(source: Dsn6File, params?: { voxelSize?: Vec3 }): Task<V const origin_frac = Vec3.create(gridOrigin[0] / grid[0], gridOrigin[1] / grid[1], gridOrigin[2] / grid[2]); const dimensions_frac = Vec3.create(extent[0] / grid[0], extent[1] / grid[1], extent[2] / grid[2]); - const space = Tensor.Space(extent, [2, 1, 0], Float32Array); + const space = Tensor.Space(extent, [0, 1, 2], Float32Array); const data = Tensor.create(space, Tensor.Data1(values)); return { diff --git a/src/mol-plugin/state/actions/basic.ts b/src/mol-plugin/state/actions/basic.ts index 01f22b2b4b83d0655e24815ade299d18d997544f..4ddb9e25d026d7ee193c5e141d345c6dface33ee 100644 --- a/src/mol-plugin/state/actions/basic.ts +++ b/src/mol-plugin/state/actions/basic.ts @@ -15,6 +15,7 @@ import { PluginStateObject } from '../objects'; import { StateTransforms } from '../transforms'; import { Download } from '../transforms/data'; import { StructureRepresentation3DHelpers, VolumeRepresentation3DHelpers } from '../transforms/representation'; +import { getFileInfo } from 'mol-util/file-info'; // TODO: "structure parser provider" @@ -161,25 +162,52 @@ export const UpdateTrajectory = StateAction.build({ // -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) +const VolumeFormats = { 'ccp4': '', 'mrc': '', 'dsn6': '', 'brix': '' } +type VolumeFormat = keyof typeof VolumeFormats + +function getVolumeData(format: VolumeFormat, b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>) { + switch (format) { + case 'ccp4': case 'mrc': + return b.apply(StateTransforms.Data.ParseCcp4).apply(StateTransforms.Model.VolumeFromCcp4); + case 'dsn6': case 'brix': + return b.apply(StateTransforms.Data.ParseDsn6).apply(StateTransforms.Model.VolumeFromDsn6); + } +} + +function createVolumeTree(format: VolumeFormat, ctx: PluginContext, b: StateTreeBuilder.To<PluginStateObject.Data.Binary | PluginStateObject.Data.String>): StateTree { + + const root = getVolumeData(format, b) .apply(StateTransforms.Representation.VolumeRepresentation3D, VolumeRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'isosurface')); return root.getTree(); } +function getFileFormat(format: VolumeFormat | 'auto', file: File): VolumeFormat { + if (format === 'auto') { + const fileFormat = getFileInfo(file).ext + if (fileFormat in VolumeFormats) { + return fileFormat as VolumeFormat + } else { + throw new Error('unsupported format') + } + } else { + return format + } +} + 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']]), + file: PD.File({ accept: '.ccp4,.mrc,.dsn6,.brix'}), + format: PD.Select('auto', [ + ['auto', 'Automatic'], ['ccp4', 'CCP4'], ['mrc', 'MRC'], ['dsn6', 'DSN6'], ['brix', 'BRIX'] + ]), } })(({ 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)); + const format = getFileFormat(params.format, params.file) + return state.update(createVolumeTree(format, 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 38495fe64612eea479ca7e82d87cf9f0643909b4..a47bb8ed1794b5823ce010e966e729b6e9b519c7 100644 --- a/src/mol-plugin/state/objects.ts +++ b/src/mol-plugin/state/objects.ts @@ -14,6 +14,7 @@ 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'; +import { Dsn6File } from 'mol-io/reader/dsn6/schema'; export type TypeClass = 'root' | 'data' | 'prop' @@ -58,7 +59,8 @@ 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 class Ccp4 extends Create<Ccp4File>({ name: 'CCP4/MRC File', typeClass: 'Data' }) { } + export class Dsn6 extends Create<Dsn6File>({ name: 'DSN6/BRIX 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 a32194ef65b7755933c08951798b56a5d9639dae..8cb114dcb34da78c494762a8c664f932370b3453 100644 --- a/src/mol-plugin/state/transforms/data.ts +++ b/src/mol-plugin/state/transforms/data.ts @@ -14,6 +14,7 @@ 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' +import * as DSN6 from 'mol-io/reader/dsn6/parser' export { Download } type Download = typeof Download @@ -98,15 +99,32 @@ export { ParseCcp4 } type ParseCcp4 = typeof ParseCcp4 const ParseCcp4 = PluginStateTransform.BuiltIn({ name: 'parse-ccp4', - display: { name: 'Parse CCP4', description: 'Parse CCP4 from Binary data' }, + display: { name: 'Parse CCP4/MRC', description: 'Parse CCP4/MRC from Binary data' }, from: [SO.Data.Binary], to: SO.Format.Ccp4 })({ apply({ a }) { - return Task.create('Parse CCP4', async ctx => { + return Task.create('Parse CCP4/MRC', 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); }); } +}); + +export { ParseDsn6 } +type ParseDsn6 = typeof ParseDsn6 +const ParseDsn6 = PluginStateTransform.BuiltIn({ + name: 'parse-dsn6', + display: { name: 'Parse DSN6/BRIX', description: 'Parse CCP4/BRIX from Binary data' }, + from: [SO.Data.Binary], + to: SO.Format.Dsn6 +})({ + apply({ a }) { + return Task.create('Parse DSN6/BRIX', async ctx => { + const parsed = await DSN6.parse(a.data).runInContext(ctx); + if (parsed.isError) throw new Error(parsed.message); + return new SO.Format.Dsn6(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 e0ac192283859f0b5ee745b275407970b16249dd..12cf4ec4d536cc6c86528e2fb7264d098d760f46 100644 --- a/src/mol-plugin/state/transforms/model.ts +++ b/src/mol-plugin/state/transforms/model.ts @@ -18,6 +18,7 @@ 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'; +import { volumeFromDsn6 } from 'mol-model/volume/formats/dsn6'; export { TrajectoryFromMmCif } type TrajectoryFromMmCif = typeof TrajectoryFromMmCif @@ -233,4 +234,26 @@ const VolumeFromCcp4 = PluginStateTransform.BuiltIn({ return new SO.Volume.Data(volume, props); }); } +}); + +export { VolumeFromDsn6 } +type VolumeFromDsn6 = typeof VolumeFromDsn6 +const VolumeFromDsn6 = PluginStateTransform.BuiltIn({ + name: 'volume-from-dsn6', + display: { name: 'Volume from DSN6/BRIX', description: 'Create Volume from DSN6/BRIX data' }, + from: SO.Format.Dsn6, + to: SO.Volume.Data, + params(a) { + return { + voxelSize: PD.Vec3(Vec3.create(1, 1, 1)) + }; + } +})({ + apply({ a, params }) { + return Task.create('Create volume from DSN6/BRIX', async ctx => { + const volume = await volumeFromDsn6(a.data, params).runInContext(ctx) + const props = { label: 'Volume' }; + return new SO.Volume.Data(volume, props); + }); + } }); \ No newline at end of file