ccp4.ts 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  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 { Volume } from '../../mol-model/volume';
  7. import { Task } from '../../mol-task';
  8. import { SpacegroupCell, Box3D } from '../../mol-math/geometry';
  9. import { Tensor, Vec3 } from '../../mol-math/linear-algebra';
  10. import { Ccp4File, Ccp4Header } from '../../mol-io/reader/ccp4/schema';
  11. import { degToRad } from '../../mol-math/misc';
  12. import { getCcp4ValueType } from '../../mol-io/reader/ccp4/parser';
  13. import { TypedArrayValueType } from '../../mol-io/common/typed-array';
  14. import { arrayMin, arrayRms, arrayMean, arrayMax } from '../../mol-util/array';
  15. import { ModelFormat } from '../format';
  16. import { CustomProperties } from '../../mol-model/custom-property';
  17. /** When available (e.g. in MRC files) use ORIGIN records instead of N[CRS]START */
  18. export function getCcp4Origin(header: Ccp4Header): Vec3 {
  19. if (header.originX === 0.0 && header.originY === 0.0 && header.originZ === 0.0) {
  20. return Vec3.create(header.NCSTART, header.NRSTART, header.NSSTART);
  21. } else {
  22. return Vec3.create(
  23. header.originX / (header.xLength / header.NX),
  24. header.originY / (header.yLength / header.NY),
  25. header.originZ / (header.zLength / header.NZ)
  26. );
  27. }
  28. }
  29. function getTypedArrayCtor(header: Ccp4Header) {
  30. const valueType = getCcp4ValueType(header);
  31. switch (valueType) {
  32. case TypedArrayValueType.Float32: return Float32Array;
  33. case TypedArrayValueType.Int8: return Int8Array;
  34. case TypedArrayValueType.Int16: return Int16Array;
  35. case TypedArrayValueType.Uint16: return Uint16Array;
  36. }
  37. throw Error(`${valueType} is not a supported value format.`);
  38. }
  39. export function volumeFromCcp4(source: Ccp4File, params?: { voxelSize?: Vec3, offset?: Vec3, label?: string }): Task<Volume> {
  40. return Task.create<Volume>('Create Volume', async ctx => {
  41. const { header, values } = source;
  42. const size = Vec3.create(header.xLength, header.yLength, header.zLength);
  43. if (params && params.voxelSize) Vec3.mul(size, size, params.voxelSize);
  44. const angles = Vec3.create(degToRad(header.alpha), degToRad(header.beta), degToRad(header.gamma));
  45. const spacegroup = header.ISPG > 65536 ? 0 : header.ISPG;
  46. const cell = SpacegroupCell.create(spacegroup || 'P 1', size, angles);
  47. const axis_order_fast_to_slow = Vec3.create(header.MAPC - 1, header.MAPR - 1, header.MAPS - 1);
  48. const normalizeOrder = Tensor.convertToCanonicalAxisIndicesFastToSlow(axis_order_fast_to_slow);
  49. const grid = [header.NX, header.NY, header.NZ];
  50. const extent = normalizeOrder([header.NC, header.NR, header.NS]);
  51. const origin = getCcp4Origin(header);
  52. if (params?.offset) Vec3.add(origin, origin, params.offset);
  53. const gridOrigin = normalizeOrder(origin);
  54. const origin_frac = Vec3.create(gridOrigin[0] / grid[0], gridOrigin[1] / grid[1], gridOrigin[2] / grid[2]);
  55. const dimensions_frac = Vec3.create(extent[0] / grid[0], extent[1] / grid[1], extent[2] / grid[2]);
  56. const space = Tensor.Space(extent, Tensor.invertAxisOrder(axis_order_fast_to_slow), getTypedArrayCtor(header));
  57. const data = Tensor.create(space, Tensor.Data1(values));
  58. // TODO Calculate stats? When to trust header data?
  59. // Min/max/mean are reliable (based on LiteMol/DensityServer usage)
  60. // These, however, calculate sigma, so no data on that.
  61. // always calculate stats when all stats related values are zero
  62. const calcStats = header.AMIN === 0 && header.AMAX === 0 && header.AMEAN === 0 && header.ARMS === 0;
  63. return {
  64. label: params?.label,
  65. grid: {
  66. transform: { kind: 'spacegroup', cell, fractionalBox: Box3D.create(origin_frac, Vec3.add(Vec3.zero(), origin_frac, dimensions_frac)) },
  67. cells: data,
  68. stats: {
  69. min: (isNaN(header.AMIN) || calcStats) ? arrayMin(values) : header.AMIN,
  70. max: (isNaN(header.AMAX) || calcStats) ? arrayMax(values) : header.AMAX,
  71. mean: (isNaN(header.AMEAN) || calcStats) ? arrayMean(values) : header.AMEAN,
  72. sigma: (isNaN(header.ARMS) || header.ARMS === 0) ? arrayRms(values) : header.ARMS
  73. },
  74. },
  75. sourceData: Ccp4Format.create(source),
  76. customProperties: new CustomProperties(),
  77. _propertyData: Object.create(null),
  78. };
  79. });
  80. }
  81. //
  82. export { Ccp4Format };
  83. type Ccp4Format = ModelFormat<Ccp4File>
  84. namespace Ccp4Format {
  85. export function is(x: ModelFormat): x is Ccp4Format {
  86. return x.kind === 'ccp4';
  87. }
  88. export function create(ccp4: Ccp4File): Ccp4Format {
  89. return { kind: 'ccp4', name: ccp4.name, data: ccp4 };
  90. }
  91. }