diff --git a/src/mol-io/common/simple-buffer.ts b/src/mol-io/common/simple-buffer.ts index 3ecbaecbc5c7498fa901b27d96e38813f80ae49a..8825388f1439581955f41a1854be9d942f91ec2f 100644 --- a/src/mol-io/common/simple-buffer.ts +++ b/src/mol-io/common/simple-buffer.ts @@ -103,4 +103,12 @@ export namespace SimpleBuffer { } } } + + export function flipByteOrderInPlace2(buffer: ArrayBuffer, byteOffset = 0, length?: number) { + const intView = new Int16Array(buffer, byteOffset, length) + for (let i = 0, n = intView.length; i < n; ++i) { + const val = intView[i] + intView[i] = ((val & 0xff) << 8) | ((val >> 8) & 0xff) + } + } } \ No newline at end of file diff --git a/src/mol-io/reader/ccp4/parser.ts b/src/mol-io/reader/ccp4/parser.ts index 388c22a879d556d33e1a0fba9de3896576a15db5..b81f9cca34a4558ca82da86cf768b07308ab396c 100644 --- a/src/mol-io/reader/ccp4/parser.ts +++ b/src/mol-io/reader/ccp4/parser.ts @@ -10,16 +10,14 @@ import { ReaderResult as Result } from '../result' import { FileHandle } from '../../common/file-handle'; import { SimpleBuffer } from 'mol-io/common/simple-buffer'; -export async function readCcp4Header(file: FileHandle) { +export async function readCcp4Header(file: FileHandle): Promise<{ header: Ccp4Header, littleEndian: boolean }> { const headerSize = 1024; const { buffer } = await file.readBuffer(0, headerSize) - const bin = buffer.buffer - const dv = new DataView(bin) // 53 MAP Character string 'MAP ' to identify file type const MAP = String.fromCharCode( - dv.getUint8(52 * 4), dv.getUint8(52 * 4 + 1), - dv.getUint8(52 * 4 + 2), dv.getUint8(52 * 4 + 3) + buffer.readUInt8(52 * 4), buffer.readUInt8(52 * 4 + 1), + buffer.readUInt8(52 * 4 + 2), buffer.readUInt8(52 * 4 + 3) ) if (MAP !== 'MAP ') { throw new Error('ccp4 format error, missing "MAP " string'); @@ -27,15 +25,15 @@ export async function readCcp4Header(file: FileHandle) { // 54 MACHST Machine stamp indicating machine type which wrote file // 17 and 17 for big-endian or 68 and 65 for little-endian - const MACHST = [ dv.getUint8(53 * 4), dv.getUint8(53 * 4 + 1) ] + const MACHST = [ buffer.readUInt8(53 * 4), buffer.readUInt8(53 * 4 + 1) ] let littleEndian = true // found MRC files that don't have the MACHST stamp set and are big-endian if (MACHST[0] !== 68 && MACHST[1] !== 65) { littleEndian = false; } - const readInt = (o: number) => dv.getInt32(o * 4, littleEndian) - const readFloat = (o: number) => dv.getFloat32(o * 4, littleEndian) + const readInt = littleEndian ? (o: number) => buffer.readInt32LE(o * 4) : (o: number) => buffer.readInt32BE(o * 4) + const readFloat = littleEndian ? (o: number) => buffer.readFloatLE(o * 4) : (o: number) => buffer.readFloatBE(o * 4) const header: Ccp4Header = { NC: readInt(0), @@ -98,15 +96,12 @@ export async function readCcp4Header(file: FileHandle) { } function getElementByteSize(mode: number) { - if (mode === 2) { - return 4 - } else if (mode === 1) { - return 2 - } else if (mode === 0) { - return 1 - } else { - throw new Error(`ccp4 mode '${mode}' unsupported`); + switch (mode) { + case 2: return 4 + case 1: return 2 + case 0: return 1 } + throw new Error(`ccp4 mode '${mode}' unsupported`); } async function parseInternal(file: FileHandle, size: number, ctx: RuntimeContext): Promise<Ccp4File> { @@ -115,17 +110,23 @@ async function parseInternal(file: FileHandle, size: number, ctx: RuntimeContext const { header, littleEndian } = await readCcp4Header(file) const offset = 256 * 4 + header.NSYMBT + const { buffer, bytesRead } = await file.readBuffer(offset, size - offset) + const count = header.NC * header.NR * header.NS const elementByteSize = getElementByteSize(header.MODE) const byteCount = count * elementByteSize - const { buffer } = await file.readBuffer(offset, size) + if (byteCount !== bytesRead) { + console.warn(`byteCount ${byteCount} and bytesRead ${bytesRead} differ`) + } let values if (header.MODE === 2) { - values = new Float32Array(buffer, offset, count) + values = new Float32Array(buffer.buffer, offset, count) + } else if (header.MODE === 1) { + values = new Int16Array(buffer.buffer, offset, count) } else if (header.MODE === 0) { - values = new Int8Array(buffer, offset, count) + values = new Int8Array(buffer.buffer, offset, count) } else { throw new Error(`ccp4 mode '${header.MODE}' unsupported`); } @@ -136,7 +137,7 @@ async function parseInternal(file: FileHandle, size: number, ctx: RuntimeContext // if the file was converted by mapmode2to0 - scale the data // based on uglymol (https://github.com/uglymol/uglymol) by Marcin Wojdyr (wojdyr) - if (header.userFlag1 -128 && header.userFlag2 === 127) { + if (header.userFlag1 === -128 && header.userFlag2 === 127) { values = new Float32Array(values) // scaling f(x)=b1*x+b0 such that f(-128)=min and f(127)=max const b1 = (header.AMAX - header.AMIN) / 255.0 diff --git a/src/mol-io/reader/ccp4/schema.ts b/src/mol-io/reader/ccp4/schema.ts index 1263b590dec34dfa51fedd529ca999fdcd319dc7..f896f81d7df462218a164bb293308f313dc74180 100644 --- a/src/mol-io/reader/ccp4/schema.ts +++ b/src/mol-io/reader/ccp4/schema.ts @@ -115,5 +115,5 @@ export interface Ccp4Header { */ export interface Ccp4File { header: Ccp4Header - values: Float32Array | Int8Array + values: Float32Array | Int16Array | Int8Array } \ No newline at end of file diff --git a/src/mol-io/reader/dsn6/parser.ts b/src/mol-io/reader/dsn6/parser.ts index 737e7461b32b6e09b7882a2a46dd99e11c6d5d19..2dcfbe060c84e120ba3f8e70212675796fd3624b 100644 --- a/src/mol-io/reader/dsn6/parser.ts +++ b/src/mol-io/reader/dsn6/parser.ts @@ -10,6 +10,8 @@ import { ReaderResult as Result } from '../result' import { FileHandle } from '../../common/file-handle'; import { SimpleBuffer } from 'mol-io/common/simple-buffer'; +export const dsn6HeaderSize = 512; + function parseBrixHeader(str: string): Dsn6Header { return { xStart: parseInt(str.substr(10, 5)), @@ -33,61 +35,69 @@ function parseBrixHeader(str: string): Dsn6Header { } } -function parseDsn6Header(int: Int16Array): Dsn6Header { - const factor = 1 / int[ 17 ] +function parseDsn6Header(buffer: SimpleBuffer, littleEndian: boolean): Dsn6Header { + const readInt = littleEndian ? (o: number) => buffer.readInt16LE(o * 2) : (o: number) => buffer.readInt16BE(o * 2) + const factor = 1 / readInt(17) return { - xStart: int[ 0 ], - yStart: int[ 1 ], - zStart: int[ 2 ], - xExtent: int[ 3 ], - yExtent: int[ 4 ], - zExtent: int[ 5 ], - xRate: int[ 6 ], - yRate: int[ 7 ], - zRate: int[ 8 ], - xlen: int[ 9 ] * factor, - ylen: int[ 10 ] * factor, - zlen: int[ 11 ] * factor, - alpha: int[ 12 ] * factor, - beta: int[ 13 ] * factor, - gamma: int[ 14 ] * factor, - divisor: int[ 15 ] / 100, - summand: int[ 16 ], + xStart: readInt(0), + yStart: readInt(1), + zStart: readInt(2), + xExtent: readInt(3), + yExtent: readInt(4), + zExtent: readInt(5), + xRate: readInt(6), + yRate: readInt(7), + zRate: readInt(8), + xlen: readInt(9) * factor, + ylen: readInt(10) * factor, + zlen: readInt(11) * factor, + alpha: readInt(12) * factor, + beta: readInt(13) * factor, + gamma: readInt(14) * factor, + divisor: readInt(15) / 100, + summand: readInt(16), sigma: undefined } } -async function parseInternal(file: FileHandle, size: number, ctx: RuntimeContext): Promise<Dsn6File> { - await ctx.update({ message: 'Parsing DSN6/BRIX file...' }); - - const { buffer } = await file.readBuffer(0, size) - const bin = buffer.buffer - - const intView = new Int16Array(bin) - const byteView = new Uint8Array(bin) - const brixStr = String.fromCharCode.apply(null, byteView.subarray(0, 512)) +export async function readDsn6Header(file: FileHandle): Promise<{ header: Dsn6Header, littleEndian: boolean }> { + const { buffer } = await file.readBuffer(0, dsn6HeaderSize) + const brixStr = String.fromCharCode.apply(null, buffer) as string const isBrix = brixStr.startsWith(':-)') + const littleEndian = isBrix || buffer.readInt16LE(18 * 2) === 100 + const header = isBrix ? parseBrixHeader(brixStr) : parseDsn6Header(buffer, littleEndian) + return { header, littleEndian } +} - if (!isBrix) { - // for DSN6, swap byte order when big endian - 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) - } - } - } +async function parseInternal(file: FileHandle, size: number, ctx: RuntimeContext): Promise<Dsn6File> { + await ctx.update({ message: 'Parsing DSN6/BRIX file...' }); - const header = isBrix ? parseBrixHeader(brixStr) : parseDsn6Header(intView) + const { header, littleEndian } = await readDsn6Header(file) const { divisor, summand } = header - const values = new Float32Array(header.xExtent * header.yExtent * header.zExtent) + const { buffer, bytesRead } = await file.readBuffer(dsn6HeaderSize, size - dsn6HeaderSize) - let offset = 512 const xBlocks = Math.ceil(header.xExtent / 8) const yBlocks = Math.ceil(header.yExtent / 8) const zBlocks = Math.ceil(header.zExtent / 8) + const valueCount = header.xExtent * header.yExtent * header.zExtent + + const count = xBlocks * 8 * yBlocks * 8 * zBlocks * 8 + const elementByteSize = 1 + const byteCount = count * elementByteSize + + if (byteCount !== bytesRead) { + console.warn(`byteCount ${byteCount} and bytesRead ${bytesRead} differ`) + } + + const values = new Float32Array(valueCount) + + if (!littleEndian) { + // even though the values are one byte they need to be swapped like they are 2 + SimpleBuffer.flipByteOrderInPlace2(buffer.buffer) + } + let offset = 0 // loop over blocks for (let zz = 0; zz < zBlocks; ++zz) { for (let yy = 0; yy < yBlocks; ++yy) { @@ -102,7 +112,7 @@ async function parseInternal(file: FileHandle, size: number, ctx: RuntimeContext // check if remaining slice-part contains values if (x < header.xExtent && y < header.yExtent && z < header.zExtent) { const idx = ((((x * header.yExtent) + y) * header.zExtent) + z) - values[ idx ] = (byteView[ offset ] - summand) / divisor + values[idx] = (buffer[offset] - summand) / divisor ++offset } else { offset += 8 - i