Browse Source

dx parser

David Sehnal 5 years ago
parent
commit
1a23cb672e

+ 99 - 0
src/mol-io/reader/dx/parser.ts

@@ -0,0 +1,99 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * Adapted from NGL.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { Vec3 } from '../../../mol-math/linear-algebra';
+import { chunkedSubtask, RuntimeContext, Task } from '../../../mol-task';
+import { parseFloat as fastParseFloat } from '../common/text/number-parser';
+import { Tokenizer } from '../common/text/tokenizer';
+import { ReaderResult as Result } from '../result';
+
+// http://apbs-pdb2pqr.readthedocs.io/en/latest/formats/opendx.html
+
+export interface DxFile {
+    header: DxFile.Header,
+    values: Float64Array
+}
+
+export namespace DxFile {
+    export interface Header {
+        dim: Vec3,
+        min: Vec3,
+        h: Vec3
+    }
+}
+
+function readHeader(tokenizer: Tokenizer): { header: DxFile.Header, headerByteCount: number } {
+    const header: Partial<DxFile.Header> = { h: Vec3() };
+    let headerByteCount = 0;
+    let deltaLineCount = 0;
+
+    const reWhitespace = /\s+/g;
+
+    while (true) {
+        const line = Tokenizer.readLine(tokenizer);
+        let ls;
+
+        if (line.startsWith('object 1')) {
+            ls = line.split(reWhitespace);
+            header.dim = Vec3.create(parseInt(ls[5]), parseInt(ls[6]), parseInt(ls[7]));
+        } else if (line.startsWith('origin')) {
+            ls = line.split(reWhitespace);
+            header.min = Vec3.create(parseFloat(ls[1]), parseFloat(ls[2]), parseFloat(ls[3]));
+        } else if (line.startsWith('delta')) {
+            ls = line.split(reWhitespace);
+
+            if (deltaLineCount === 0) {
+                (header.h as any)[0] = parseFloat(ls[1]);
+            } else if (deltaLineCount === 1) {
+                (header.h as any)[1] = parseFloat(ls[2]);
+            } else if (deltaLineCount === 2) {
+                (header.h as any)[2] = parseFloat(ls[3]);
+            }
+
+            deltaLineCount += 1;
+        } else if (line.startsWith('object 3')) {
+            headerByteCount += line.length + 1;
+            break;
+        }
+
+        headerByteCount += line.length + 1;
+    }
+
+    return { header: header as DxFile.Header, headerByteCount };
+}
+
+function readValuesText(ctx: RuntimeContext, tokenizer: Tokenizer, header: DxFile.Header) {
+    const N = header.dim[0] * header.dim[1] * header.dim[2];
+    const chunkSize = 100 * 100 * 100;
+    const data = new Float64Array(N);
+    let offset = 0;
+
+    return chunkedSubtask(ctx, chunkSize, data, (count, data) => {
+        const max = Math.min(N, offset + count);
+        for (let i = offset; i < max; i++) {
+            Tokenizer.skipWhitespace(tokenizer);
+            tokenizer.tokenStart = tokenizer.position;
+            Tokenizer.eatValue(tokenizer);
+            data[i] = fastParseFloat(tokenizer.data, tokenizer.tokenStart, tokenizer.tokenEnd);
+        }
+        offset = max;
+        return max === N ? 0 : chunkSize;
+    }, (ctx, _, i) => ctx.update({ current: Math.min(i, N), max: N }));
+}
+
+export function parseDx(data: string | Uint8Array) {
+    return Task.create<Result<DxFile>>('Parse Cube', async taskCtx => {
+        await taskCtx.update('Reading header...');
+        const tokenizer = Tokenizer(data as string);
+        const { header } = readHeader(tokenizer);
+        const values = await readValuesText(taskCtx, tokenizer, header);
+        console.log(values);
+        return Result.success({ header, values });
+    });
+}

+ 33 - 0
src/mol-model-formats/volume/dx.ts

@@ -0,0 +1,33 @@
+/**
+ * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { DxFile } from '../../mol-io/reader/dx/parser';
+import { Mat4, Tensor } from '../../mol-math/linear-algebra';
+import { VolumeData } from '../../mol-model/volume/data';
+import { Task } from '../../mol-task';
+import { arrayMax, arrayMean, arrayMin, arrayRms } from '../../mol-util/array';
+
+export function volumeFromDx(source: DxFile): Task<VolumeData> {
+    return Task.create<VolumeData>('Create Volume Data', async () => {
+        const { header, values } = source;
+        const space = Tensor.Space(header.dim, [0, 1, 2], Float64Array);
+        const data = Tensor.create(space, Tensor.Data1(values));
+        const matrix = Mat4.fromTranslation(Mat4(), header.min);
+        const basis = Mat4.fromScaling(Mat4(), header.h);
+        Mat4.mul(matrix, matrix, basis);
+
+        return {
+            transform: { kind: 'matrix', matrix },
+            data,
+            dataStats: {
+                min: arrayMin(values),
+                max: arrayMax(values),
+                mean: arrayMean(values),
+                sigma: arrayRms(values)
+            }
+        };
+    });
+}

+ 19 - 0
src/mol-plugin-state/formats/volume.ts

@@ -61,6 +61,24 @@ export const Dsn6Provider = DataFormatProvider({
     visuals: defaultVisuals
 });
 
+export const DxProvider = DataFormatProvider({
+    label: 'DX',
+    description: 'DX',
+    category: Category,
+    stringExtensions: ['dx'],
+    binaryExtensions: ['dxbin'],
+    parse: async (plugin, data) => {
+        const volume = plugin.build()
+            .to(data)
+            .apply(StateTransforms.Volume.VolumeFromDx, {}, { state: { isGhost: true } });
+
+        await volume.commit({ revertOnError: true });
+
+        return { volume: volume.selector };
+    },
+    visuals: defaultVisuals
+});
+
 export const CubeProvider = DataFormatProvider({
     label: 'Cube',
     description: 'Cube',
@@ -165,6 +183,7 @@ export const BuiltInVolumeFormats = [
     ['ccp4', Ccp4Provider] as const,
     ['dns6', Dsn6Provider] as const,
     ['cube', CubeProvider] as const,
+    ['dx', DxProvider] as const,
     ['dscif', DscifProvider] as const,
 ] as const;
 

+ 22 - 0
src/mol-plugin-state/transforms/volume.ts

@@ -14,10 +14,13 @@ import { Task } from '../../mol-task';
 import { ParamDefinition as PD } from '../../mol-util/param-definition';
 import { PluginStateObject as SO, PluginStateTransform } from '../objects';
 import { volumeFromCube } from '../../mol-model-formats/volume/cube';
+import { parseDx } from '../../mol-io/reader/dx/parser';
+import { volumeFromDx } from '../../mol-model-formats/volume/dx';
 
 export { VolumeFromCcp4 };
 export { VolumeFromDsn6 };
 export { VolumeFromCube };
+export { VolumeFromDx };
 export { VolumeFromDensityServerCif };
 
 type VolumeFromCcp4 = typeof VolumeFromCcp4
@@ -85,6 +88,25 @@ const VolumeFromCube = PluginStateTransform.BuiltIn({
     }
 });
 
+type VolumeFromDx = typeof VolumeFromDx
+const VolumeFromDx = PluginStateTransform.BuiltIn({
+    name: 'volume-from-dx',
+    display: { name: 'Parse PX', description: 'Parse DX string/binary and create volume.' },
+    from: [SO.Data.String, SO.Data.Binary],
+    to: SO.Volume.Data
+})({
+    apply({ a }) {
+        return Task.create('Parse DX', async ctx => {
+            const parsed = await parseDx(a.data).runInContext(ctx);
+            if (parsed.isError) throw new Error(parsed.message);
+            const volume = await volumeFromDx(parsed.result).runInContext(ctx);
+            const props = { label: `Volume` };
+            return new SO.Volume.Data(volume, props);
+        });
+    }
+});
+
+
 type VolumeFromDensityServerCif = typeof VolumeFromDensityServerCif
 const VolumeFromDensityServerCif = PluginStateTransform.BuiltIn({
     name: 'volume-from-density-server-cif',

+ 1 - 0
src/mol-plugin/index.ts

@@ -62,6 +62,7 @@ export const DefaultPluginSpec: PluginSpec = {
         PluginSpec.Action(StateTransforms.Volume.VolumeFromCcp4),
         PluginSpec.Action(StateTransforms.Volume.VolumeFromDsn6),
         PluginSpec.Action(StateTransforms.Volume.VolumeFromCube),
+        PluginSpec.Action(StateTransforms.Volume.VolumeFromDx),
         PluginSpec.Action(StateTransforms.Representation.VolumeRepresentation3D),
     ],
     behaviors: [