packages/miew/src/io/parsers/CCP4Parser.js

Summary

Maintainability
A
3 hrs
Test Coverage
import Parser from './Parser'
import VolumeModel, { valueType } from './VolumeModel'

const CCP4Header = {
  extent: [valueType.array, 'u32', 0],
  type: [valueType.singular, 'u32', 3],
  nstart: [valueType.array, 'i32', 4],
  grid: [valueType.array, 'u32', 7],
  cellDims: [valueType.vector, 'f32', 10],
  angles: [valueType.array, 'f32', 13],
  crs2xyz: [valueType.array, 'i32', 16],
  dmin: [valueType.singular, 'f32', 19],
  dmax: [valueType.singular, 'f32', 20],
  dmean: [valueType.singular, 'f32', 21],
  ispg: [valueType.singular, 'u32', 22],
  nsymbt: [valueType.singular, 'u32', 23],
  lksflg: [valueType.singular, 'u32', 24],
  customData: [valueType.buffer, 'buffer', 25, 9],
  origin: [valueType.vector, 'f32', 34],
  map: [valueType.buffer, 'buffer', 52, 1],
  machine: [valueType.singular, 'u32', 53],
  sd: [valueType.singular, 'f32', 54],
  nlabel: [valueType.singular, 'f32', 55],
  label: [valueType.buffer, 'buffer', 56, 200]
}

class Ccp4Model extends VolumeModel {
  // read header (http://www.ccp4.ac.uk/html/maplib.html)
  _parseHeader(_buffer) {
    this._buff = _buffer
    this._typedCheck()
    const arrays = {}
    arrays.u32 = new Uint32Array(this._buff, 0, 56)
    arrays.i32 = new Int32Array(this._buff, 0, 56)
    arrays.f32 = new Float32Array(this._buff, 0, 56)
    arrays.buffer = this._buff
    const header = this._header

    this._fillHeader(CCP4Header, arrays)

    // calculate non-orthogonal unit cell coordinates
    header.angles.forEach((angle, i, a) => {
      a[i] *= Math.PI / 180.0
    })
  }

  _setAxisIndices() {
    const header = this._header

    if (
      header.cellDims.x === 0.0 &&
      header.cellDims.y === 0.0 &&
      header.cellDims.z === 0.0
    ) {
      header.cellDims.set(1.0, 1.0, 1.0)
    }
    // Apply header conversion
    // Mapping between CCP4 column, row, section and VMD x, y, z.
    const { crs2xyz } = this._header
    if (crs2xyz[0] === 0 && crs2xyz[1] === 0 && crs2xyz[2] === 0) {
      crs2xyz[0] = 1
      crs2xyz[1] = 2
      crs2xyz[2] = 3
    }

    const xyz2crs = this._xyz2crs
    xyz2crs[crs2xyz[0] - 1] = 0 // column
    xyz2crs[crs2xyz[1] - 1] = 1 // row
    xyz2crs[crs2xyz[2] - 1] = 2 // section
  }

  _setOrigins() {
    const [xaxis, yaxis, zaxis] = this._getAxis()
    this._setAxisIndices()

    const header = this._header
    const xyz2crs = this._xyz2crs
    // Handle both MRC-2000 and older format maps
    if (
      header.origin.x === 0.0 &&
      header.origin.y === 0.0 &&
      header.origin.z === 0.0
    ) {
      this._origin.addScaledVector(xaxis, header.nstart[xyz2crs[0]])
      this._origin.addScaledVector(yaxis, header.nstart[xyz2crs[1]])
      this._origin.addScaledVector(zaxis, header.nstart[xyz2crs[2]])
    } else {
      this._origin = header.origin
      // Use ORIGIN records rather than old n[xyz]start records
      //   http://www2.mrc-lmb.cam.ac.uk/image2000.html
      // XXX the ORIGIN field is only used by the EM community, and
      // has undefined meaning for non-orthogonal maps and/or non-cubic voxels, etc.
    }
    xaxis.multiplyScalar(header.extent[xyz2crs[0]] - 1)
    yaxis.multiplyScalar(header.extent[xyz2crs[1]] - 1)
    zaxis.multiplyScalar(header.extent[xyz2crs[2]] - 1)

    if (header.type === 2) {
      this._data = new Float32Array(
        this._buff,
        1024 + header.nsymbt,
        header.extent[0] * header.extent[1] * header.extent[2]
      )
    } else {
      throw new Error(`CCP4: Unsupported format ${header.type}`)
    }

    this._setBoxParams(xaxis, yaxis, zaxis)
  }

  _toXYZData() {
    const header = this._header
    const data = this._data
    const xyz2crs = this._xyz2crs
    const xyzData = new Float32Array(data.length)

    const dim = this._getXYZdim()
    const xSize = dim[0]
    const ySize = dim[1]

    let crsIdx = 0
    const coord = []
    let x
    let y
    let z
    for (coord[2] = 0; coord[2] < header.extent[2]; coord[2]++) {
      // Site
      for (coord[1] = 0; coord[1] < header.extent[1]; coord[1]++) {
        // Row
        for (coord[0] = 0; coord[0] < header.extent[0]; coord[0]++, crsIdx++) {
          // Column
          x = coord[xyz2crs[0]]
          y = coord[xyz2crs[1]]
          z = coord[xyz2crs[2]]
          xyzData[x + xSize * (y + ySize * z)] = data[crsIdx]
        }
      }
    }

    return xyzData
  }
}

class CCP4Parser extends Parser {
  constructor(data, options) {
    super(data, options)
    this._options.fileType = 'ccp4'
    this.model = new Ccp4Model()
  }

  static canProbablyParse(_data) {
    return false // Autodetection is not implemented yet
  }

  parseSync() {
    return this.model.parse(this._data)
  }
}

CCP4Parser.formats = ['ccp4']
CCP4Parser.extensions = ['.ccp4', '.map', '.mrc']
CCP4Parser.binary = true

export default CCP4Parser