Get NIFTI Headers using itk-wasm and vtk.js

Hi there,

Is it at all possible to access the full headers after reading a .nii file using itk-wasm and getting the vtkImageData? I see that we have some limited options available (e.g. getDirection, getOrigin), but what about the rest of the headers? In particular, I am looking for cal_min and cal_max to be able to dynamically set the color range of the image. Also would appreciate insights on how to do that regardless of whether it involves the headers!

Thanks!

itk-wasm currently doesn’t expose this extra metadata through vtkImageData. It would be interesting to see if itk-wasm itself can have facilities to return extra image metadata, maybe as a key/value map. @thewtex

Thanks for the response. We ended up writing a helper function that parses the ArrayBuffer directly. I’ll put that here in case it helps someone else in the future:

const struct = require('@aksel/structjs');

static parseHeader(buffer) {
    const niftiSpec = struct('<i35xB8h3f4h11fh2B4f8x104c2h18f20c4B');

    const nullTerm = (s) => {
      for (let i = 0; i < s.length; i += 1) {
        if (s[i] === '\0') {
          return s.slice(0, i).join('');
        }
      }
      return s.join('');
    };
    const v = niftiSpec.unpack(buffer);

    return {
      sizeof_hdr: v[0],
      dim_info: v[1],
      dim: v.slice(2, 10),
      intent_p1: v[10],
      intent_p2: v[11],
      intent_p3: v[12],
      intent_code: v[13],
      datatype: v[14],
      bitpix: v[15],
      slice_start: v[16],
      pixdim: v.slice(17, 25),
      vox_offset: v[25],
      scl_slope: v[26],
      scl_inter: v[27],
      slice_end: v[28],
      slice_code: v[29],
      xyzt_units: v[30],
      cal_max: v[31],
      cal_min: v[32],
      slice_duration: v[33],
      toffset: v[34],
      descrip: nullTerm(v.slice(35, 115)),
      aux_file: nullTerm(v.slice(115, 139)),
      qform_code: v[139],
      sform_code: v[140],
      quatern: v.slice(141, 144),
      quatern_offset: v.slice(144, 147),
      srow_x: v.slice(147, 151),
      srow_y: v.slice(151, 155),
      srow_z: v.slice(155, 159),
      intent_name: nullTerm(v.slice(159, 175)),
      magic: nullTerm(v.slice(175, 179)),
      ext: v.slice(179, 183),
    };
  }

 const arr = new Uint8Array([
      92, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 16, 0, 16, 0, 1, 0,
      1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0,
      32, 0, 0, 0, 0, 0, 128, 63, 0, 0, 92, 65, 0, 0, 92, 65, 0, 0, 160, 64, 0,
      0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 128, 63, 0, 0, 176, 67, 0,
      0, 128, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 193, 0, 0, 0, 193, 0, 0, 0, 0, 0, 0, 92, 65, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 193, 0, 0, 0, 0, 0, 0, 92, 65, 0, 0, 0, 0,
      0, 0, 0, 193, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160, 64, 0, 0, 0, 0, 0, 0, 0,
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 110, 43, 49, 0, 0, 0, 0, 0,
    ]);
const buf = arr.buffer;

const headers = {
      sizeof_hdr: 348,
      dim_info: 0,
      dim: [2, 16, 16, 1, 1, 1, 1, 1],
      intent_p1: 0,
      intent_p2: 0,
      intent_p3: 0,
      intent_code: 0,
      datatype: 16,
      bitpix: 32,
      slice_start: 0,
      pixdim: [1, 13.75, 13.75, 5, 1, 1, 1, 1],
      vox_offset: 352,
      scl_slope: 1,
      scl_inter: 0,
      slice_end: 0,
      slice_code: 0,
      xyzt_units: 0,
      cal_max: 0,
      cal_min: 0,
      slice_duration: 0,
      toffset: 0,
      descrip: '',
      aux_file: '',
      qform_code: 0,
      sform_code: 2,
      quatern: [0, 0, 0],
      quatern_offset: [-8, -8, 0],
      srow_x: [13.75, 0, 0, -8],
      srow_y: [0, 13.75, 0, -8],
      srow_z: [0, 0, 5, 0],
      intent_name: '',
      magic: 'n+1',
      ext: [0, 0, 0, 0],
};

expect(NiftiUtils.parseHeader(buf)).to.deep.equal(headers);
1 Like