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

volume parser tweaks and fixes

parent 173e108e
No related branches found
No related tags found
No related merge requests found
......@@ -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
......@@ -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
......
......@@ -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
......@@ -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
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment