/** * Imports ***/
import ParsersVolume from './parsers.volume';
import { Vector3 } from 'three/src/math/Vector3';
/**
* @module parsers/mgh
*/
export default class ParsersMgh extends ParsersVolume {
constructor(data, id) {
super();
/**
* @member
* @type {arraybuffer}
*/
this._id = id;
this._url = data.url;
this._buffer = null;
this._bufferPos = 0;
this._dataPos = 0;
this._pixelData = null;
// Default MGH Header as described at:
// https://surfer.nmr.mgh.harvard.edu/fswiki/FsTutorial/MghFormat
// Image "header" with default values
this._version = 1;
this._width = 0;
this._height = 0;
this._depth = 0;
this._nframes = 0;
this._type = ParsersMgh.MRI_UCHAR; // 0-UCHAR, 4-SHORT, 1-INT, 3-FLOAT
this._dof = 0;
this._goodRASFlag = 0; // True: Use directional cosines, false assume CORONAL
this._spacingXYZ = [1, 1, 1];
this._Xras = [-1, 0, 0];
this._Yras = [0, 0, -1];
this._Zras = [0, 1, 0];
this._Cras = [0, 0, 0];
// Image "footer"
this._tr = 0; // ms
this._flipAngle = 0; // radians
this._te = 0; // ms
this._ti = 0; // ms
this._fov = 0; // from doc: IGNORE THIS FIELD (data is inconsistent)
this._tags = []; // Will then contain variable length char strings
// Other misc
this._origin = [0, 0, 0];
this._imageOrient = [0, 0, 0, 0, 0, 0];
// Read header
// ArrayBuffer in data.buffer may need endian swap
this._buffer = data.buffer;
this._version = this._readInt();
this._swapEndian = false;
if (this._version == 1) {
// Life is good
} else if (this._version == 16777216) {
this._swapEndian = true;
this._version = this._swap32(this._version);
} else {
const error = new Error('MGH/MGZ parser: Unknown Endian. Version reports: ' + this._version);
throw error;
}
this._width = this._readInt();
this._height = this._readInt();
this._depth = this._readInt(); // AMI calls this frames
this._nframes = this._readInt();
this._type = this._readInt();
this._dof = this._readInt();
this._goodRASFlag = this._readShort();
this._spacingXYZ = this._readFloat(3);
this._Xras = this._readFloat(3);
this._Yras = this._readFloat(3);
this._Zras = this._readFloat(3);
this._Cras = this._readFloat(3);
this._bufferPos = 284;
let dataSize = this._width * this._height * this._depth * this._nframes;
let vSize = this._width * this._height * this._depth;
switch (this._type) {
case ParsersMgh.MRI_UCHAR:
this._pixelData = this._readUChar(dataSize);
break;
case ParsersMgh.MRI_INT:
this._pixelData = this._readInt(dataSize);
break;
case ParsersMgh.MRI_FLOAT:
this._pixelData = this._readFloat(dataSize);
break;
case ParsersMgh.MRI_SHORT:
this._pixelData = this._readShort(dataSize);
break;
default:
throw Error('MGH/MGZ parser: Unknown _type. _type reports: ' + this._type);
}
this._tr = this._readFloat(1);
this._flipAngle = this._readFloat(1);
this._te = this._readFloat(1);
this._ti = this._readFloat(1);
this._fov = this._readFloat(1);
let enc = new TextDecoder();
let t = this._tagReadStart();
while (t[0] != undefined) {
let tagType = t[0];
let tagLen = t[1];
let tagValue = undefined;
switch (tagType) {
case ParsersMgh.TAG_OLD_MGH_XFORM:
case ParsersMgh.TAG_MGH_XFORM:
tagValue = this._readChar(tagLen);
break;
default:
tagValue = this._readChar(tagLen);
}
tagValue = enc.decode(tagValue);
this._tags.push({ tagType: tagType, tagValue: tagValue });
// read for next loop
t = this._tagReadStart();
}
// detect if we are in a right handed coordinate system
const first = new Vector3().fromArray(this._Xras);
const second = new Vector3().fromArray(this._Yras);
const crossFirstSecond = new Vector3().crossVectors(first, second);
const third = new Vector3().fromArray(this._Zras);
if (crossFirstSecond.angleTo(third) > Math.PI / 2) {
this._rightHanded = false;
}
// - sign to move to LPS space
this._imageOrient = [
-this._Xras[0],
-this._Xras[1],
this._Xras[2],
-this._Yras[0],
-this._Yras[1],
this._Yras[2],
];
// Calculate origin
let fcx = this._width / 2.0;
let fcy = this._height / 2.0;
let fcz = this._depth / 2.0;
for (let ui = 0; ui < 3; ++ui) {
this._origin[ui] =
this._Cras[ui] -
(this._Xras[ui] * this._spacingXYZ[0] * fcx +
this._Yras[ui] * this._spacingXYZ[1] * fcy +
this._Zras[ui] * this._spacingXYZ[2] * fcz);
}
// - sign to move to LPS space
this._origin = [-this._origin[0], -this._origin[1], this._origin[2]];
}
seriesInstanceUID() {
// use filename + timestamp..?
return this._url;
}
numberOfFrames() {
// AMI calls Z component frames, not T (_nframes)
return this._depth;
}
sopInstanceUID(frameIndex = 0) {
return frameIndex;
}
rows(frameIndex = 0) {
return this._width;
}
columns(frameIndex = 0) {
return this._height;
}
pixelType(frameIndex = 0) {
// Return: 0 integer, 1 float
switch (this._type) {
case ParsersMgh.MRI_UCHAR:
case ParsersMgh.MRI_INT:
case ParsersMgh.MRI_SHORT:
return 0;
case ParsersMgh.MRI_FLOAT:
return 1;
default:
throw Error('MGH/MGZ parser: Unknown _type. _type reports: ' + this._type);
}
}
bitsAllocated(frameIndex = 0) {
switch (this._type) {
case ParsersMgh.MRI_UCHAR:
return 8;
case ParsersMgh.MRI_SHORT:
return 16;
case ParsersMgh.MRI_INT:
case ParsersMgh.MRI_FLOAT:
return 32;
default:
throw Error('MGH/MGZ parser: Unknown _type. _type reports: ' + this._type);
}
}
pixelSpacing(frameIndex = 0) {
return this._spacingXYZ;
}
imageOrientation(frameIndex = 0) {
return this._imageOrient;
}
imagePosition(frameIndex = 0) {
return this._origin;
}
extractPixelData(frameIndex = 0) {
let sliceSize = this._width * this._height;
return this._pixelData.slice(frameIndex * sliceSize, (frameIndex + 1) * sliceSize);
}
// signed int32
_readInt(len = 1) {
let tempBuff = new DataView(this._buffer.slice(this._bufferPos, this._bufferPos + len * 4));
this._bufferPos += len * 4;
let v = undefined;
if (len == 1) {
v = tempBuff.getInt32(0, this._swapEndian);
} else {
v = new Int32Array(len);
for (let i = 0; i < len; i++) {
v[i] = tempBuff.getInt32(i * 4, this._swapEndian);
}
}
return v;
}
// signed int16
_readShort(len = 1) {
let tempBuff = new DataView(this._buffer.slice(this._bufferPos, this._bufferPos + len * 2));
this._bufferPos += len * 2;
let v = undefined;
if (len == 1) {
v = tempBuff.getInt16(0, this._swapEndian);
} else {
v = new Int16Array(len);
for (let i = 0; i < len; i++) {
v[i] = tempBuff.getInt16(i * 2, this._swapEndian);
}
}
return v;
}
// signed int64
_readLong(len = 1) {
let tempBuff = new DataView(this._buffer.slice(this._bufferPos, this._bufferPos + len * 8));
this._bufferPos += len * 8;
let v = new Uint16Array(len);
for (let i = 0; i < len; i++) {
/* DataView doesn't have Int64.
* This work around based off Scalajs
* (https://github.com/scala-js/scala-js/blob/master/library/src/main/scala/scala/scalajs/js/typedarray/DataViewExt.scala)
* v[i]=tempBuff.getInt64(i*8,this._swapEndian);
*/
let shiftHigh = 0;
let shiftLow = 0;
if (this._swapendian) {
shiftHigh = 4;
} else {
shiftLow = 4;
}
let high = tempBuff.getInt32(i * 8 + shiftHigh, this._swapEndian);
let low = tempBuff.getInt32(i * 8 + shiftLow, this._swapEndian);
if (high != 0) {
console.log('Unable to read Int64 with high word: ' + high + 'low word: ' + low);
low = undefined;
}
v[i] = low;
}
if (len == 0) {
return undefined;
} else if (len == 1) {
return v[0];
} else {
return v;
}
}
// signed int8
_readChar(len = 1) {
let tempBuff = new DataView(this._buffer.slice(this._bufferPos, this._bufferPos + len));
this._bufferPos += len;
let v = undefined;
if (len == 1) {
v = tempBuff.getInt8(0, this._swapEndian);
} else {
v = new Int8Array(len);
for (let i = 0; i < len; i++) {
v[i] = tempBuff.getInt8(i, this._swapEndian);
}
}
return v;
}
// unsigned int8
_readUChar(len = 1) {
let tempBuff = new DataView(this._buffer.slice(this._bufferPos, this._bufferPos + len));
this._bufferPos += len;
let v = undefined;
if (len == 1) {
v = tempBuff.getUint8(0, this._swapEndian);
} else {
v = new Uint8Array(len);
for (let i = 0; i < len; i++) {
v[i] = tempBuff.getUint8(i, this._swapEndian);
}
}
return v;
}
// float32
_readFloat(len = 1) {
let tempBuff = new DataView(this._buffer.slice(this._bufferPos, this._bufferPos + len * 4));
this._bufferPos += len * 4;
let v = undefined;
if (len == 1) {
v = tempBuff.getFloat32(0, this._swapEndian);
} else {
v = new Float32Array(len);
for (let i = 0; i < len; i++) {
v[i] = tempBuff.getFloat32(i * 4, this._swapEndian);
}
}
return v;
}
_tagReadStart() {
if (this._bufferPos >= this._buffer.byteLength) {
return [undefined, undefined];
}
let tagType = this._readInt();
let tagLen = undefined;
switch (tagType) {
case ParsersMgh.TAG_OLD_MGH_XFORM:
tagLen = this._readInt();
tagLen -= 1;
break;
case ParsersMgh.TAG_OLD_SURF_GEOM:
case ParsersMgh.TAG_OLD_USEREALRAS:
case ParsersMgh.TAG_OLD_COLORTABLE:
tagLen = 0;
break;
default:
tagLen = this._readLong();
}
if (tagLen == undefined) {
tagType = undefined;
}
return [tagType, tagLen];
}
}
// https://github.com/freesurfer/freesurfer/
// See include/mri.h
ParsersMgh.MRI_UCHAR = 0;
ParsersMgh.MRI_INT = 1;
ParsersMgh.MRI_LONG = 2;
ParsersMgh.MRI_FLOAT = 3;
ParsersMgh.MRI_SHORT = 4;
ParsersMgh.MRI_BITMAP = 5;
ParsersMgh.MRI_TENSOR = 6;
ParsersMgh.MRI_FLOAT_COMPLEX = 7;
ParsersMgh.MRI_DOUBLE_COMPLEX = 8;
ParsersMgh.MRI_RGB = 9;
// https://github.com/freesurfer/freesurfer/
// See include/tags.h
ParsersMgh.TAG_OLD_COLORTABLE = 1;
ParsersMgh.TAG_OLD_USEREALRAS = 2;
ParsersMgh.TAG_CMDLINE = 3;
ParsersMgh.TAG_USEREALRAS = 4;
ParsersMgh.TAG_COLORTABLE = 5;
ParsersMgh.TAG_GCAMORPH_GEOM = 10;
ParsersMgh.TAG_GCAMORPH_TYPE = 11;
ParsersMgh.TAG_GCAMORPH_LABELS = 12;
ParsersMgh.TAG_OLD_SURF_GEOM = 20;
ParsersMgh.TAG_SURF_GEOM = 21;
ParsersMgh.TAG_OLD_MGH_XFORM = 30;
ParsersMgh.TAG_MGH_XFORM = 31;
ParsersMgh.TAG_GROUP_AVG_SURFACE_AREA = 32;
ParsersMgh.TAG_AUTO_ALIGN = 33;
ParsersMgh.TAG_SCALAR_DOUBLE = 40;
ParsersMgh.TAG_PEDIR = 41;
ParsersMgh.TAG_MRI_FRAME = 42;
ParsersMgh.TAG_FIELDSTRENGTH = 43;