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);