ccp4.ts 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer)
  5. *
  6. * @author David Sehnal <david.sehnal@gmail.com>
  7. */
  8. import * as File from '../common/file'
  9. import * as DataFormat from '../common/data-format'
  10. export const enum Mode { Int8 = 0, Int16 = 1, Float32 = 2 }
  11. export interface Header {
  12. name: string,
  13. mode: Mode,
  14. grid: number[], // grid is converted to the axis order!!
  15. axisOrder: number[],
  16. extent: number[],
  17. origin: number[],
  18. spacegroupNumber: number,
  19. cellSize: number[],
  20. cellAngles: number[],
  21. littleEndian: boolean,
  22. dataOffset: number
  23. }
  24. /** Represents a circular buffer for 2 * blockSize layers */
  25. export interface SliceBuffer {
  26. buffer: File.TypedArrayBufferContext,
  27. sliceCapacity: number,
  28. slicesRead: number,
  29. values: DataFormat.ValueArray,
  30. sliceCount: number,
  31. /** Have all the input slice been read? */
  32. isFinished: boolean
  33. }
  34. export interface Data {
  35. header: Header,
  36. file: number,
  37. slices: SliceBuffer
  38. }
  39. export function getValueType(header: Header) {
  40. if (header.mode === Mode.Float32) return DataFormat.ValueType.Float32;
  41. if (header.mode === Mode.Int16) return DataFormat.ValueType.Int16;
  42. return DataFormat.ValueType.Int8;
  43. }
  44. export function assignSliceBuffer(data: Data, blockSize: number) {
  45. const { extent } = data.header;
  46. const valueType = getValueType(data.header);
  47. const sliceSize = extent[0] * extent[1] * DataFormat.getValueByteSize(valueType);
  48. const sliceCapacity = Math.max(1, Math.floor(Math.min(64 * 1024 * 1024, sliceSize * extent[2]) / sliceSize));
  49. const buffer = File.createTypedArrayBufferContext(sliceCapacity * extent[0] * extent[1], valueType);
  50. data.slices = {
  51. buffer,
  52. sliceCapacity,
  53. slicesRead: 0,
  54. values: buffer.values,
  55. sliceCount: 0,
  56. isFinished: false
  57. };
  58. }
  59. function compareProp(a: any, b: any) {
  60. if (a instanceof Array && b instanceof Array) {
  61. if (a.length !== b.length) return false;
  62. for (let i = 0; i < a.length; i++) {
  63. if (a[i] !== b[i]) return false;
  64. }
  65. return true;
  66. }
  67. return a === b;
  68. }
  69. export function compareHeaders(a: Header, b: Header) {
  70. for (const p of ['grid', 'axisOrder', 'extent', 'origin', 'spacegroupNumber', 'cellSize', 'cellAngles', 'mode']) {
  71. if (!compareProp((a as any)[p], (b as any)[p])) return false;
  72. }
  73. return true;
  74. }
  75. function getArray(r: (offset: number) => number, offset: number, count: number) {
  76. const ret: number[] = [];
  77. for (let i = 0; i < count; i++) {
  78. ret[i] = r(offset + i);
  79. }
  80. return ret;
  81. }
  82. async function readHeader(name: string, file: number) {
  83. const headerSize = 1024;
  84. const { buffer: data } = await File.readBuffer(file, 0, headerSize);
  85. let littleEndian = true;
  86. let mode = data.readInt32LE(3 * 4);
  87. if (mode < 0 || mode > 2) {
  88. littleEndian = false;
  89. mode = data.readInt32BE(3 * 4, true);
  90. if (mode < 0 || mode > 2) {
  91. throw Error('Only CCP4 modes 0, 1, and 2 are supported.');
  92. }
  93. }
  94. const readInt = littleEndian ? (o: number) => data.readInt32LE(o * 4) : (o: number) => data.readInt32BE(o * 4);
  95. const readFloat = littleEndian ? (o: number) => data.readFloatLE(o * 4) : (o: number) => data.readFloatBE(o * 4);
  96. const origin2k = getArray(readFloat, 49, 3);
  97. const nxyzStart = getArray(readInt, 4, 3);
  98. const header: Header = {
  99. name,
  100. mode,
  101. grid: getArray(readInt, 7, 3),
  102. axisOrder: getArray(readInt, 16, 3).map(i => i - 1),
  103. extent: getArray(readInt, 0, 3),
  104. origin: origin2k[0] === 0.0 && origin2k[1] === 0.0 && origin2k[2] === 0.0 ? nxyzStart : origin2k,
  105. spacegroupNumber: readInt(22),
  106. cellSize: getArray(readFloat, 10, 3),
  107. cellAngles: getArray(readFloat, 13, 3),
  108. // mean: readFloat(21),
  109. littleEndian,
  110. dataOffset: headerSize + readInt(23) /* symBytes */
  111. };
  112. // "normalize" the grid axis order
  113. header.grid = [header.grid[header.axisOrder[0]], header.grid[header.axisOrder[1]], header.grid[header.axisOrder[2]]];
  114. return header;
  115. }
  116. export async function readSlices(data: Data) {
  117. const { slices, header } = data;
  118. if (slices.isFinished) {
  119. return;
  120. }
  121. const { extent } = header;
  122. const sliceSize = extent[0] * extent[1];
  123. const sliceByteOffset = slices.buffer.elementByteSize * sliceSize * slices.slicesRead;
  124. const sliceCount = Math.min(slices.sliceCapacity, extent[2] - slices.slicesRead);
  125. const sliceByteCount = sliceCount * sliceSize;
  126. await File.readTypedArray(slices.buffer, data.file, header.dataOffset + sliceByteOffset, sliceByteCount, 0, header.littleEndian);
  127. slices.slicesRead += sliceCount;
  128. slices.sliceCount = sliceCount;
  129. if (slices.slicesRead >= extent[2]) {
  130. slices.isFinished = true;
  131. }
  132. }
  133. export async function open(name: string, filename: string): Promise<Data> {
  134. const file = await File.openRead(filename);
  135. const header = await readHeader(name, file);
  136. return {
  137. header,
  138. file,
  139. slices: void 0 as any
  140. };
  141. }