123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
- import { Task, RuntimeContext } from '../../../mol-task';
- import { Ccp4File, Ccp4Header } from './schema';
- import { ReaderResult as Result } from '../result';
- import { FileHandle } from '../../common/file-handle';
- import { SimpleBuffer } from '../../../mol-io/common/simple-buffer';
- import { TypedArrayValueType, getElementByteSize, TypedArrayBufferContext, readTypedArray, createTypedArrayBufferContext } from '../../../mol-io/common/typed-array';
- export async function readCcp4Header(file: FileHandle): Promise<{ header: Ccp4Header, littleEndian: boolean }> {
- const headerSize = 1024;
- const { buffer } = await file.readBuffer(0, headerSize);
- // 53 MAP Character string 'MAP ' to identify file type
- const MAP = String.fromCharCode(
- 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');
- }
- // 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 = [buffer.readUInt8(53 * 4), buffer.readUInt8(53 * 4 + 1)];
- let littleEndian = false;
- if (MACHST[0] === 68 && MACHST[1] === 65) {
- littleEndian = true;
- } else if (MACHST[0] === 17 && MACHST[1] === 17) {
- littleEndian = false;
- } else {
- const modeLE = buffer.readInt32LE(3 * 4);
- if (modeLE <= 16) littleEndian = true;
- }
- 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),
- NR: readInt(1),
- NS: readInt(2),
- MODE: readInt(3),
- NCSTART: readInt(4),
- NRSTART: readInt(5),
- NSSTART: readInt(6),
- NX: readInt(7),
- NY: readInt(8),
- NZ: readInt(9),
- xLength: readFloat(10),
- yLength: readFloat(11),
- zLength: readFloat(12),
- alpha: readFloat(13),
- beta: readFloat(14),
- gamma: readFloat(15),
- MAPC: readInt(16),
- MAPR: readInt(17),
- MAPS: readInt(18),
- AMIN: readFloat(19),
- AMAX: readFloat(20),
- AMEAN: readFloat(21),
- ISPG: readInt(22),
- NSYMBT: readInt(23),
- LSKFLG: readInt(24),
- SKWMAT: [], // TODO bytes 26-34
- SKWTRN: [], // TODO bytes 35-37
- userFlag1: readInt(39),
- userFlag2: readInt(40),
- // bytes 50-52 origin in X,Y,Z used for transforms
- originX: readFloat(49),
- originY: readFloat(50),
- originZ: readFloat(51),
- MAP, // bytes 53 MAP
- MACHST, // bytes 54 MACHST
- ARMS: readFloat(54),
- // TODO bytes 56 NLABL
- // TODO bytes 57-256 LABEL
- };
- return { header, littleEndian };
- }
- export async function readCcp4Slices(header: Ccp4Header, buffer: TypedArrayBufferContext, file: FileHandle, byteOffset: number, length: number, littleEndian: boolean) {
- if (isMapmode2to0(header)) {
- // data from mapmode2to0 is in MODE 0 (Int8) and needs to be scaled and written as float32
- const valueByteOffset = 3 * length;
- // read int8 data to last quarter of the read buffer
- await file.readBuffer(byteOffset, buffer.readBuffer, length, valueByteOffset);
- // get int8 view of last quarter of the read buffer
- const int8 = new Int8Array(buffer.valuesBuffer.buffer, valueByteOffset);
- // scaling f(x)=b1*x+b0 such that f(-128)=min and f(127)=max
- const b1 = (header.AMAX - header.AMIN) / 255.0;
- const b0 = 0.5 * (header.AMIN + header.AMAX + b1);
- for (let j = 0, jl = length; j < jl; ++j) {
- buffer.values[j] = b1 * int8[j] + b0;
- }
- } else {
- await readTypedArray(buffer, file, byteOffset, length, 0, littleEndian);
- }
- }
- function getCcp4DataType(mode: number) {
- switch (mode) {
- case 0: return TypedArrayValueType.Int8;
- case 1: return TypedArrayValueType.Int16;
- case 2: return TypedArrayValueType.Float32;
- case 3: throw new Error('mode 3 unsupported, complex 16-bit integers');
- case 4: throw new Error('mode 4 unsupported, complex 32-bit reals');
- case 6: TypedArrayValueType.Uint16;
- case 16: throw new Error('mode 16 unsupported, unsigned char * 3 (for rgb data, non-standard)');
- }
- throw new Error(`unknown mode '${mode}'`);
- }
- /** check if the file was converted by mapmode2to0, see https://github.com/uglymol/uglymol */
- function isMapmode2to0(header: Ccp4Header) {
- return header.userFlag1 === -128 && header.userFlag2 === 127;
- }
- export function getCcp4ValueType(header: Ccp4Header) {
- return isMapmode2to0(header) ? TypedArrayValueType.Float32 : getCcp4DataType(header.MODE);
- }
- export function getCcp4DataOffset(header: Ccp4Header) {
- return 256 * 4 + header.NSYMBT;
- }
- async function parseInternal(file: FileHandle, size: number, ctx: RuntimeContext): Promise<Ccp4File> {
- await ctx.update({ message: 'Parsing CCP4/MRC/MAP file...' });
- const { header, littleEndian } = await readCcp4Header(file);
- const offset = getCcp4DataOffset(header);
- const dataType = getCcp4DataType(header.MODE);
- const valueType = getCcp4ValueType(header);
- const count = header.NC * header.NR * header.NS;
- const elementByteSize = getElementByteSize(dataType);
- const byteCount = count * elementByteSize;
- const buffer = createTypedArrayBufferContext(count, valueType);
- readCcp4Slices(header, buffer, file, offset, byteCount, littleEndian);
- const result: Ccp4File = { header, values: buffer.values, name: file.name };
- return result;
- }
- export function parseFile(file: FileHandle, size: number) {
- return Task.create<Result<Ccp4File>>('Parse CCP4/MRC/MAP', async ctx => {
- try {
- return Result.success(await parseInternal(file, size, ctx));
- } catch (e) {
- return Result.error(e);
- }
- });
- }
- export function parse(buffer: Uint8Array, name: string) {
- return parseFile(FileHandle.fromBuffer(SimpleBuffer.fromUint8Array(buffer), name), buffer.length);
- }
|