From e987d73d2b9e660dcf7de8a4739b9b0ff4f3e794 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Tue, 3 Oct 2017 15:39:28 +0200 Subject: [PATCH] BinaryCIF support --- examples/1cbs_full.bcif | Bin 0 -> 66851 bytes src/reader/cif/binary/decoder.ts | 215 ++++++++++++++++++++++++++++++ src/reader/cif/binary/encoding.ts | 152 +++++++++++++++++++++ src/reader/cif/binary/field.ts | 54 +++++++- src/reader/cif/binary/parser.ts | 50 ++++++- src/reader/cif/index.ts | 6 +- src/script.ts | 55 +++++--- 7 files changed, 510 insertions(+), 22 deletions(-) create mode 100644 examples/1cbs_full.bcif create mode 100644 src/reader/cif/binary/decoder.ts create mode 100644 src/reader/cif/binary/encoding.ts diff --git a/examples/1cbs_full.bcif b/examples/1cbs_full.bcif new file mode 100644 index 0000000000000000000000000000000000000000..38628c3dafaae093d7f5f6689f4bc471ef826b27 GIT binary patch literal 66851 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 Y^@)I5YqlfOyAaAz!|L`L+S@byKQ|;e=l}o! literal 0 HcmV?d00001 diff --git a/src/reader/cif/binary/decoder.ts b/src/reader/cif/binary/decoder.ts new file mode 100644 index 000000000..f6c587f37 --- /dev/null +++ b/src/reader/cif/binary/decoder.ts @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * From CIFTools.js + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Encoding, EncodedData } from './encoding' + +/** + * Fixed point, delta, RLE, integer packing adopted from https://github.com/rcsb/mmtf-javascript/ + * by Alexander Rose <alexander.rose@weirdbyte.de>, MIT License, Copyright (c) 2016 + */ + +export default function decode(data: EncodedData): any[] { + let current: any = data.data; + for (let i = data.encoding.length - 1; i >= 0; i--) { + current = decodeStep(current, data.encoding[i]); + } + return current as any[]; +} + +function decodeStep(data: any, encoding: Encoding): any { + switch (encoding.kind) { + case 'ByteArray': { + switch (encoding.type) { + case Encoding.IntDataType.Uint8: return data; + case Encoding.IntDataType.Int8: return int8(data); + case Encoding.IntDataType.Int16: return int16(data); + case Encoding.IntDataType.Uint16: return uint16(data); + case Encoding.IntDataType.Int32: return int32(data); + case Encoding.IntDataType.Uint32: return uint32(data); + case Encoding.FloatDataType.Float32: return float32(data); + case Encoding.FloatDataType.Float64: return float64(data); + default: throw new Error('Unsupported ByteArray type.') + } + } + case 'FixedPoint': return fixedPoint(data, encoding); + case 'IntervalQuantization': return intervalQuantization(data, encoding); + case 'RunLength': return runLength(data, encoding); + case 'Delta': return delta(data, encoding); + case 'IntegerPacking': return integerPacking(data, encoding); + case 'StringArray': return stringArray(data, encoding); + } +} + +function getIntArray(type: Encoding.IntDataType, size: number) { + switch (type) { + case Encoding.IntDataType.Int8: return new Int8Array(size); + case Encoding.IntDataType.Int16: return new Int16Array(size); + case Encoding.IntDataType.Int32: return new Int32Array(size); + case Encoding.IntDataType.Uint8: return new Uint8Array(size); + case Encoding.IntDataType.Uint16: return new Uint16Array(size); + case Encoding.IntDataType.Uint32: return new Uint32Array(size); + default: throw new Error('Unsupported integer data type.'); + } +} + +function getFloatArray(type: Encoding.FloatDataType, size: number) { + switch (type) { + case Encoding.FloatDataType.Float32: return new Float32Array(size); + case Encoding.FloatDataType.Float64: return new Float64Array(size); + default: throw new Error('Unsupported floating data type.'); + } +} + +/* http://stackoverflow.com/questions/7869752/javascript-typed-arrays-and-endianness */ +const isLittleEndian = (function () { + const arrayBuffer = new ArrayBuffer(2); + const uint8Array = new Uint8Array(arrayBuffer); + const uint16array = new Uint16Array(arrayBuffer); + uint8Array[0] = 0xAA; + uint8Array[1] = 0xBB; + if (uint16array[0] === 0xBBAA) return true; + return false; +})(); + +function int8(data: Uint8Array) { return new Int8Array(data.buffer, data.byteOffset); } + +function flipByteOrder(data: Uint8Array, bytes: number) { + let buffer = new ArrayBuffer(data.length); + let ret = new Uint8Array(buffer); + for (let i = 0, n = data.length; i < n; i += bytes) { + for (let j = 0; j < bytes; j++) { + ret[i + bytes - j - 1] = data[i + j]; + } + } + return buffer; +} + +function view<T>(data: Uint8Array, byteSize: number, c: new (buffer: ArrayBuffer) => T) { + if (isLittleEndian) return new c(data.buffer); + return new c(flipByteOrder(data, byteSize)); +} + +function int16(data: Uint8Array) { return view(data, 2, Int16Array); } +function uint16(data: Uint8Array) { return view(data, 2, Uint16Array); } +function int32(data: Uint8Array) { return view(data, 4, Int32Array); } +function uint32(data: Uint8Array) { return view(data, 4, Uint32Array); } +function float32(data: Uint8Array) { return view(data, 4, Float32Array); } +function float64(data: Uint8Array) { return view(data, 8, Float64Array); } + +function fixedPoint(data: Int32Array, encoding: Encoding.FixedPoint) { + let n = data.length; + let output = getFloatArray(encoding.srcType, n); + let f = 1 / encoding.factor; + for (let i = 0; i < n; i++) { + output[i] = f * data[i]; + } + return output; +} + +function intervalQuantization(data: Int32Array, encoding: Encoding.IntervalQuantization) { + let n = data.length; + let output = getFloatArray(encoding.srcType, n); + let delta = (encoding.max - encoding.min) / (encoding.numSteps - 1) + let min = encoding.min; + for (let i = 0; i < n; i++) { + output[i] = min + delta * data[i]; + } + return output; +} + +function runLength(data: Int32Array, encoding: Encoding.RunLength) { + let output = getIntArray(encoding.srcType, encoding.srcSize); + let dataOffset = 0; + for (let i = 0, il = data.length; i < il; i += 2) { + let value = data[i]; // value to be repeated + let length = data[i + 1]; // number of repeats + for (let j = 0; j < length; ++j) { + output[dataOffset++] = value; + } + } + return output; +} + +function delta(data: (Int8Array | Int16Array | Int32Array), encoding: Encoding.Delta) { + let n = data.length; + let output = getIntArray(encoding.srcType, n); + if (!n) return output; + output[0] = data[0] + (encoding.origin | 0); + for (let i = 1; i < n; ++i) { + output[i] = data[i] + output[i - 1]; + } + return output; +} + +function integerPackingSigned(data: (Int8Array | Int16Array), encoding: Encoding.IntegerPacking) { + let upperLimit = encoding.byteCount === 1 ? 0x7F : 0x7FFF; + let lowerLimit = -upperLimit - 1; + let n = data.length; + let output = new Int32Array(encoding.srcSize); + let i = 0; + let j = 0; + while (i < n) { + let value = 0, t = data[i]; + while (t === upperLimit || t === lowerLimit) { + value += t; + i++; + t = data[i]; + } + value += t; + output[j] = value; + i++; + j++; + } + return output; +} + +function integerPackingUnsigned(data: (Int8Array | Int16Array), encoding: Encoding.IntegerPacking) { + let upperLimit = encoding.byteCount === 1 ? 0xFF : 0xFFFF; + let n = data.length; + let output = new Int32Array(encoding.srcSize); + let i = 0; + let j = 0; + while (i < n) { + let value = 0, t = data[i]; + while (t === upperLimit) { + value += t; + i++; + t = data[i]; + } + value += t; + output[j] = value; + i++; + j++; + } + return output; +} + +function integerPacking(data: (Int8Array | Int16Array), encoding: Encoding.IntegerPacking) { + return encoding.isUnsigned ? integerPackingUnsigned(data, encoding) : integerPackingSigned(data, encoding); +} + +function stringArray(data: Uint8Array, encoding: Encoding.StringArray) { + let str = encoding.stringData; + let offsets = decode({ encoding: encoding.offsetEncoding, data: encoding.offsets }); + let indices = decode({ encoding: encoding.dataEncoding, data }); + let cache: any = Object.create(null); + let result = new Array(indices.length); + let offset = 0; + for (let i of indices) { + if (i < 0) { + result[offset++] = null; + continue; + } + let v = cache[i]; + if (v === void 0) { + v = str.substring(offsets[i], offsets[i + 1]); + cache[i] = v; + } + result[offset++] = v; + } + return result; +} \ No newline at end of file diff --git a/src/reader/cif/binary/encoding.ts b/src/reader/cif/binary/encoding.ts new file mode 100644 index 000000000..c9369e964 --- /dev/null +++ b/src/reader/cif/binary/encoding.ts @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * From CIFTools.js + * @author David Sehnal <david.sehnal@gmail.com> + */ + +export const VERSION = '0.3.0'; + +export type Encoding = + | Encoding.ByteArray + | Encoding.FixedPoint + | Encoding.RunLength + | Encoding.Delta + | Encoding.IntervalQuantization + | Encoding.IntegerPacking + | Encoding.StringArray; + +export interface EncodedFile { + version: string, + encoder: string, + dataBlocks: EncodedDataBlock[] +} + +export interface EncodedDataBlock { + header: string, + categories: EncodedCategory[], +} + +export interface EncodedCategory { + name: string, + rowCount: number, + columns: EncodedColumn[], +} + +export interface EncodedColumn { + name: string, + data: EncodedData, + + /** + * The mask represents the presence or absent of particular "CIF value". + * If the mask is not set, every value is present. + * + * 0 = Value is present + * 1 = . = value not specified + * 2 = ? = value unknown + */ + mask?: EncodedData +} + +export interface EncodedData { + encoding: Encoding[], + data: Uint8Array +} + +export namespace Encoding { + + export const enum IntDataType { + Int8 = 1, + Int16 = 2, + Int32 = 3, + Uint8 = 4, + Uint16 = 5, + Uint32 = 6, + } + + export const enum FloatDataType { + Float32 = 32, + Float64 = 33 + } + + export type DataType = IntDataType | FloatDataType + + export type IntArray = Int8Array | Int16Array | Int32Array | Uint8Array | Uint16Array | Uint32Array + export type FloatArray = Float32Array | Float64Array + + export function getDataType(data: IntArray | FloatArray): DataType { + let srcType: DataType; + if (data instanceof Int8Array) srcType = Encoding.IntDataType.Int8; + else if (data instanceof Int16Array) srcType = Encoding.IntDataType.Int16; + else if (data instanceof Int32Array) srcType = Encoding.IntDataType.Int32; + else if (data instanceof Uint8Array) srcType = Encoding.IntDataType.Uint8; + else if (data instanceof Uint16Array) srcType = Encoding.IntDataType.Uint16; + else if (data instanceof Uint32Array) srcType = Encoding.IntDataType.Uint32; + else if (data instanceof Float32Array) srcType = Encoding.FloatDataType.Float32; + else if (data instanceof Float64Array) srcType = Encoding.FloatDataType.Float64; + else throw new Error('Unsupported integer data type.'); + return srcType; + } + + export function isSignedIntegerDataType(data: IntArray) { + return data instanceof Int8Array || data instanceof Int16Array || data instanceof Int32Array; + } + + // type[] -> Uint8[] + export interface ByteArray { + kind: 'ByteArray', + type: DataType + } + + // (Float32 | Float64)[] -> Int32[] + export interface FixedPoint { + kind: 'FixedPoint', + factor: number, + srcType: FloatDataType + } + + // (Float32|Float64)[] -> Int32 + export interface IntervalQuantization { + kind: 'IntervalQuantization', + min: number, + max: number, + numSteps: number, + srcType: FloatDataType + } + + // (Uint8 | Int8 | Int16 | Int32)[] -> Int32[] + export interface RunLength { + kind: 'RunLength', + srcType: IntDataType, + srcSize: number + } + + // T=(Int8Array | Int16Array | Int32Array)[] -> T[] + export interface Delta { + kind: 'Delta', + origin: number, + srcType: IntDataType + } + + // Int32[] -> (Int8 | Int16 | Uint8 | Uint16)[] + export interface IntegerPacking { + kind: 'IntegerPacking', + byteCount: number, + isUnsigned: boolean, + srcSize: number + } + + // string[] -> Uint8[] + // stores 0 and indices of ends of strings: + // stringData = '123456' + // offsets = [0,2,5,6] + // encodes ['12','345','6'] + export interface StringArray { + kind: 'StringArray', + dataEncoding: Encoding[], + stringData: string, + offsetEncoding: Encoding[], + offsets: Uint8Array + } + +} \ No newline at end of file diff --git a/src/reader/cif/binary/field.ts b/src/reader/cif/binary/field.ts index 0ffdd02fc..974dd522b 100644 --- a/src/reader/cif/binary/field.ts +++ b/src/reader/cif/binary/field.ts @@ -1 +1,53 @@ -// TODO \ No newline at end of file +/* + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import * as Column from '../../common/column' +import * as Data from '../data-model' +import { EncodedColumn } from './encoding' +import decode from './decoder' +import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../../common/text/number-parser' + +export default function Field(column: EncodedColumn): Data.Field { + const mask = column.mask ? decode(column.mask) as number[] : void 0; + const data = decode(column.data); + const isNumeric = (data as any).buffer && (data as any).byteLength && (data as any).BYTES_PER_ELEMENT; + + const str: Data.Field['str'] = isNumeric + ? mask + ? row => mask[row] === Data.ValuePresence.Present ? '' + data[row] : '' + : row => '' + data[row] + : mask + ? row => mask[row] === Data.ValuePresence.Present ? data[row] : '' + : row => data[row]; + + const int: Data.Field['int'] = isNumeric + ? row => data[row] + : row => { const v = data[row]; return fastParseInt(v, 0, v.length); }; + + const float: Data.Field['float'] = isNumeric + ? row => data[row] + : row => { const v = data[row]; return fastParseFloat(v, 0, v.length); }; + + const presence: Data.Field['presence'] = mask + ? row => mask[row] + : row => Data.ValuePresence.Present; + + const rowCount = data.length; + + return { + isDefined: true, + rowCount, + str, + int, + float, + presence, + areValuesEqual: (rowA, rowB) => data[rowA] === data[rowB], + stringEquals(row, v) { return str(row) === v; }, + toStringArray(params) { return Column.createAndFillArray(rowCount, str, params); }, + toIntArray(params) { return Column.createAndFillArray(rowCount, int, params); }, + toFloatArray(params) { return Column.createAndFillArray(rowCount, float, params); } + }; +} \ No newline at end of file diff --git a/src/reader/cif/binary/parser.ts b/src/reader/cif/binary/parser.ts index 0ffdd02fc..1cdbd04b1 100644 --- a/src/reader/cif/binary/parser.ts +++ b/src/reader/cif/binary/parser.ts @@ -1 +1,49 @@ -// TODO \ No newline at end of file +/* + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import * as Data from '../data-model' +import * as Encoding from './encoding' +import Field from './field' +import Result from '../../result' +import decodeMsgPack from '../../../utils/msgpack/decode' + +function checkVersions(min: number[], current: number[]) { + for (let i = 0; i < 2; i++) { + if (min[i] > current[i]) return false; + } + return true; +} + +function Category(data: Encoding.EncodedCategory): Data.Category { + const map = Object.create(null); + for (const col of data.columns) map[col.name] = col; + return { + rowCount: data.rowCount, + getField(name) { + const col = map[name]; + return col ? Field(col) : Data.DefaultUndefinedField(data.rowCount); + } + } +} + +export default function parse(data: Uint8Array): Result<Data.File> { + const minVersion = [0, 3]; + + try { + const unpacked = decodeMsgPack(data) as Encoding.EncodedFile; + if (!checkVersions(minVersion, unpacked.version.match(/(\d)\.(\d)\.\d/)!.slice(1).map(v => +v))) { + return Result.error<Data.File>(`Unsupported format version. Current ${unpacked.version}, required ${minVersion.join('.')}.`); + } + const file = Data.File(unpacked.dataBlocks.map(block => { + const cats = Object.create(null); + for (const cat of block.categories) cats[cat.name] = Category(cat); + return Data.Block(cats, block.header); + })); + return Result.success(file); + } catch (e) { + return Result.error<Data.File>('' + e); + } +} \ No newline at end of file diff --git a/src/reader/cif/index.ts b/src/reader/cif/index.ts index c3882b9e8..e7dcc0f88 100644 --- a/src/reader/cif/index.ts +++ b/src/reader/cif/index.ts @@ -5,14 +5,18 @@ */ import parseText from './text/parser' +import parseBinary from './binary/parser' import { Block } from './data-model' import { apply as applySchema } from './schema' import mmCIF from './schema/mmcif' export default { parseText, + parseBinary, applySchema, schema: { mmCIF: (block: Block) => applySchema(mmCIF, block) } -} \ No newline at end of file +} + +export * from './data-model' \ No newline at end of file diff --git a/src/script.ts b/src/script.ts index 0ac94deb9..ba82c95d2 100644 --- a/src/script.ts +++ b/src/script.ts @@ -72,32 +72,49 @@ export function _gro() { }); } +function runCIF(input: string | Uint8Array) { + console.time('parseCIF'); + const parsed = typeof input === 'string' ? CIF.parseText(input) : CIF.parseBinary(input); + console.timeEnd('parseCIF'); + if (parsed.isError) { + console.log(parsed); + return; + } + + const data = parsed.result.blocks[0]; + const atom_site = data.categories._atom_site; + console.log(atom_site.getField('Cartn_x')!.float(0)); + //console.log(atom_site.getField('label_atom_id')!.toStringArray()); + + const mmcif = CIF.schema.mmCIF(data); + console.log(mmcif.atom_site.Cartn_x.value(0)); + console.log(mmcif.entity.type.toArray()); + console.log(mmcif.pdbx_struct_oper_list.matrix.value(0)); +} + export function _cif() { - const path = `./examples/1cbs_updated.cif`; - //const path = 'c:/test/quick/3j3q.cif'; + let path = `./examples/1cbs_updated.cif`; + //path = 'c:/test/quick/3j3q.cif'; fs.readFile(path, 'utf8', function (err, input) { if (err) { return console.log(err); } + console.log('------------------'); + console.log('Text CIF:'); + runCIF(input); + }); - console.time('parseCIF'); - const parsed = CIF.parseText(input); - console.timeEnd('parseCIF'); - if (parsed.isError) { - console.log(parsed); - return; + path = `./examples/1cbs_full.bcif`; + //const path = 'c:/test/quick/3j3q.cif'; + fs.readFile(path, function (err, input) { + if (err) { + return console.log(err); } - - const data = parsed.result.blocks[0]; - - const atom_site = data.categories._atom_site; - console.log(atom_site.getField('Cartn_x')!.float(0)); - //console.log(atom_site.getField('label_atom_id')!.toStringArray()); - - const mmcif = CIF.schema.mmCIF(data); - console.log(mmcif.atom_site.Cartn_x.value(0)); - console.log(mmcif.entity.type.toArray()); - console.log(mmcif.pdbx_struct_oper_list.matrix.value(0)); + console.log('------------------'); + console.log('BinaryCIF:'); + const data = new Uint8Array(input.byteLength); + for (let i = 0; i < input.byteLength; i++) data[i] = input[i]; + runCIF(input); }); } -- GitLab