parser.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. /**
  2. * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { Task, RuntimeContext } from '../../../mol-task';
  7. import { Ccp4File, Ccp4Header } from './schema';
  8. import { ReaderResult as Result } from '../result';
  9. import { FileHandle } from '../../common/file-handle';
  10. import { SimpleBuffer } from '../../../mol-io/common/simple-buffer';
  11. import { TypedArrayValueType, getElementByteSize, TypedArrayBufferContext, readTypedArray, createTypedArrayBufferContext } from '../../../mol-io/common/typed-array';
  12. export async function readCcp4Header(file: FileHandle): Promise<{ header: Ccp4Header, littleEndian: boolean }> {
  13. const headerSize = 1024;
  14. const { buffer } = await file.readBuffer(0, headerSize);
  15. // 53 MAP Character string 'MAP ' to identify file type
  16. const MAP = String.fromCharCode(
  17. buffer.readUInt8(52 * 4), buffer.readUInt8(52 * 4 + 1),
  18. buffer.readUInt8(52 * 4 + 2), buffer.readUInt8(52 * 4 + 3)
  19. );
  20. if (MAP !== 'MAP ') {
  21. throw new Error('ccp4 format error, missing "MAP " string');
  22. }
  23. // 54 MACHST Machine stamp indicating machine type which wrote file
  24. // 17 and 17 for big-endian or 68 and 65 for little-endian
  25. const MACHST = [buffer.readUInt8(53 * 4), buffer.readUInt8(53 * 4 + 1)];
  26. let littleEndian = false;
  27. if (MACHST[0] === 68 && MACHST[1] === 65) {
  28. littleEndian = true;
  29. } else if (MACHST[0] === 17 && MACHST[1] === 17) {
  30. littleEndian = false;
  31. } else {
  32. const modeLE = buffer.readInt32LE(3 * 4);
  33. if (modeLE <= 16) littleEndian = true;
  34. }
  35. const readInt = littleEndian ? (o: number) => buffer.readInt32LE(o * 4) : (o: number) => buffer.readInt32BE(o * 4);
  36. const readFloat = littleEndian ? (o: number) => buffer.readFloatLE(o * 4) : (o: number) => buffer.readFloatBE(o * 4);
  37. const header: Ccp4Header = {
  38. NC: readInt(0),
  39. NR: readInt(1),
  40. NS: readInt(2),
  41. MODE: readInt(3),
  42. NCSTART: readInt(4),
  43. NRSTART: readInt(5),
  44. NSSTART: readInt(6),
  45. NX: readInt(7),
  46. NY: readInt(8),
  47. NZ: readInt(9),
  48. xLength: readFloat(10),
  49. yLength: readFloat(11),
  50. zLength: readFloat(12),
  51. alpha: readFloat(13),
  52. beta: readFloat(14),
  53. gamma: readFloat(15),
  54. MAPC: readInt(16),
  55. MAPR: readInt(17),
  56. MAPS: readInt(18),
  57. AMIN: readFloat(19),
  58. AMAX: readFloat(20),
  59. AMEAN: readFloat(21),
  60. ISPG: readInt(22),
  61. NSYMBT: readInt(23),
  62. LSKFLG: readInt(24),
  63. SKWMAT: [], // TODO bytes 26-34
  64. SKWTRN: [], // TODO bytes 35-37
  65. userFlag1: readInt(39),
  66. userFlag2: readInt(40),
  67. // bytes 50-52 origin in X,Y,Z used for transforms
  68. originX: readFloat(49),
  69. originY: readFloat(50),
  70. originZ: readFloat(51),
  71. MAP, // bytes 53 MAP
  72. MACHST, // bytes 54 MACHST
  73. ARMS: readFloat(54),
  74. // TODO bytes 56 NLABL
  75. // TODO bytes 57-256 LABEL
  76. };
  77. return { header, littleEndian };
  78. }
  79. export async function readCcp4Slices(header: Ccp4Header, buffer: TypedArrayBufferContext, file: FileHandle, byteOffset: number, length: number, littleEndian: boolean) {
  80. if (isMapmode2to0(header)) {
  81. // data from mapmode2to0 is in MODE 0 (Int8) and needs to be scaled and written as float32
  82. const valueByteOffset = 3 * length;
  83. // read int8 data to last quarter of the read buffer
  84. await file.readBuffer(byteOffset, buffer.readBuffer, length, valueByteOffset);
  85. // get int8 view of last quarter of the read buffer
  86. const int8 = new Int8Array(buffer.valuesBuffer.buffer, valueByteOffset);
  87. // scaling f(x)=b1*x+b0 such that f(-128)=min and f(127)=max
  88. const b1 = (header.AMAX - header.AMIN) / 255.0;
  89. const b0 = 0.5 * (header.AMIN + header.AMAX + b1);
  90. for (let j = 0, jl = length; j < jl; ++j) {
  91. buffer.values[j] = b1 * int8[j] + b0;
  92. }
  93. } else {
  94. await readTypedArray(buffer, file, byteOffset, length, 0, littleEndian);
  95. }
  96. }
  97. function getCcp4DataType(mode: number) {
  98. switch (mode) {
  99. case 0: return TypedArrayValueType.Int8;
  100. case 1: return TypedArrayValueType.Int16;
  101. case 2: return TypedArrayValueType.Float32;
  102. case 3: throw new Error('mode 3 unsupported, complex 16-bit integers');
  103. case 4: throw new Error('mode 4 unsupported, complex 32-bit reals');
  104. case 6: TypedArrayValueType.Uint16;
  105. case 16: throw new Error('mode 16 unsupported, unsigned char * 3 (for rgb data, non-standard)');
  106. }
  107. throw new Error(`unknown mode '${mode}'`);
  108. }
  109. /** check if the file was converted by mapmode2to0, see https://github.com/uglymol/uglymol */
  110. function isMapmode2to0(header: Ccp4Header) {
  111. return header.userFlag1 === -128 && header.userFlag2 === 127;
  112. }
  113. export function getCcp4ValueType(header: Ccp4Header) {
  114. return isMapmode2to0(header) ? TypedArrayValueType.Float32 : getCcp4DataType(header.MODE);
  115. }
  116. export function getCcp4DataOffset(header: Ccp4Header) {
  117. return 256 * 4 + header.NSYMBT;
  118. }
  119. async function parseInternal(file: FileHandle, size: number, ctx: RuntimeContext): Promise<Ccp4File> {
  120. await ctx.update({ message: 'Parsing CCP4/MRC/MAP file...' });
  121. const { header, littleEndian } = await readCcp4Header(file);
  122. const offset = getCcp4DataOffset(header);
  123. const dataType = getCcp4DataType(header.MODE);
  124. const valueType = getCcp4ValueType(header);
  125. const count = header.NC * header.NR * header.NS;
  126. const elementByteSize = getElementByteSize(dataType);
  127. const byteCount = count * elementByteSize;
  128. const buffer = createTypedArrayBufferContext(count, valueType);
  129. readCcp4Slices(header, buffer, file, offset, byteCount, littleEndian);
  130. const result: Ccp4File = { header, values: buffer.values, name: file.name };
  131. return result;
  132. }
  133. export function parseFile(file: FileHandle, size: number) {
  134. return Task.create<Result<Ccp4File>>('Parse CCP4/MRC/MAP', async ctx => {
  135. try {
  136. return Result.success(await parseInternal(file, size, ctx));
  137. } catch (e) {
  138. return Result.error(e);
  139. }
  140. });
  141. }
  142. export function parse(buffer: Uint8Array, name: string) {
  143. return parseFile(FileHandle.fromBuffer(SimpleBuffer.fromUint8Array(buffer), name), buffer.length);
  144. }