From 627b2c7bab30bf3bce78972c1e1bc8854e6e3cb2 Mon Sep 17 00:00:00 2001
From: Alexander Rose <alex.rose@rcsb.org>
Date: Thu, 21 Feb 2019 14:13:02 -0800
Subject: [PATCH] volume parser tweaks and fixes

---
 src/mol-io/common/simple-buffer.ts |  8 +++
 src/mol-io/reader/ccp4/parser.ts   | 41 ++++++-------
 src/mol-io/reader/ccp4/schema.ts   |  2 +-
 src/mol-io/reader/dsn6/parser.ts   | 92 +++++++++++++++++-------------
 4 files changed, 81 insertions(+), 62 deletions(-)

diff --git a/src/mol-io/common/simple-buffer.ts b/src/mol-io/common/simple-buffer.ts
index 3ecbaecbc..8825388f1 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 388c22a87..b81f9cca3 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 1263b590d..f896f81d7 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 737e7461b..2dcfbe060 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
-- 
GitLab