// namespaces
var dwv = dwv || {};
/** @namespace */
dwv.dicom = dwv.dicom || {};
/**
* Get the version of the library.
*
* @returns {string} The version of the library.
*/
dwv.getVersion = function () {
return '0.30.5';
};
/**
* Check that an input buffer includes the DICOM prefix 'DICM'
* after the 128 bytes preamble.
* Ref: [DICOM File Meta]{@link https://dicom.nema.org/dicom/2013/output/chtml/part10/chapter_7.html#sect_7.1}
*
* @param {ArrayBuffer} buffer The buffer to check.
* @returns {boolean} True if the buffer includes the prefix.
*/
dwv.dicom.hasDicomPrefix = function (buffer) {
var prefixArray = new Uint8Array(buffer, 128, 4);
var stringReducer = function (previous, current) {
return previous += String.fromCharCode(current);
};
return prefixArray.reduce(stringReducer, '') === 'DICM';
};
/**
* Clean string: trim and remove ending.
*
* @param {string} inputStr The string to clean.
* @returns {string} The cleaned string.
*/
dwv.dicom.cleanString = function (inputStr) {
var res = inputStr;
if (inputStr) {
// trim spaces
res = inputStr.trim();
// get rid of ending zero-width space (u200B)
if (res[res.length - 1] === String.fromCharCode('u200B')) {
res = res.substring(0, res.length - 1);
}
}
return res;
};
/**
* Get the utfLabel (used by the TextDecoder) from a character set term
* References:
* - DICOM [Value Encoding]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/chapter_6.html}
* - DICOM [Specific Character Set]{@link http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.12.html#sect_C.12.1.1.2}
* - [TextDecoder#Parameters]{@link https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/TextDecoder#Parameters}
*
* @param {string} charSetTerm The DICOM character set.
* @returns {string} The corresponding UTF label.
*/
dwv.dicom.getUtfLabel = function (charSetTerm) {
var label = 'utf-8';
if (charSetTerm === 'ISO_IR 100') {
label = 'iso-8859-1';
} else if (charSetTerm === 'ISO_IR 101') {
label = 'iso-8859-2';
} else if (charSetTerm === 'ISO_IR 109') {
label = 'iso-8859-3';
} else if (charSetTerm === 'ISO_IR 110') {
label = 'iso-8859-4';
} else if (charSetTerm === 'ISO_IR 144') {
label = 'iso-8859-5';
} else if (charSetTerm === 'ISO_IR 127') {
label = 'iso-8859-6';
} else if (charSetTerm === 'ISO_IR 126') {
label = 'iso-8859-7';
} else if (charSetTerm === 'ISO_IR 138') {
label = 'iso-8859-8';
} else if (charSetTerm === 'ISO_IR 148') {
label = 'iso-8859-9';
} else if (charSetTerm === 'ISO_IR 13') {
label = 'shift-jis';
} else if (charSetTerm === 'ISO_IR 166') {
label = 'iso-8859-11';
} else if (charSetTerm === 'ISO 2022 IR 87') {
label = 'iso-2022-jp';
} else if (charSetTerm === 'ISO 2022 IR 149') {
// not supported by TextDecoder when it says it should...
//label = "iso-2022-kr";
} else if (charSetTerm === 'ISO 2022 IR 58') {
// not supported by TextDecoder...
//label = "iso-2022-cn";
} else if (charSetTerm === 'ISO_IR 192') {
label = 'utf-8';
} else if (charSetTerm === 'GB18030') {
label = 'gb18030';
} else if (charSetTerm === 'GB2312') {
label = 'gb2312';
} else if (charSetTerm === 'GBK') {
label = 'chinese';
}
return label;
};
/**
* Get patient orientation label in the reverse direction.
*
* @param {string} ori Patient Orientation value.
* @returns {string} Reverse Orientation Label.
*/
dwv.dicom.getReverseOrientation = function (ori) {
if (!ori) {
return null;
}
// reverse labels
var rlabels = {
L: 'R',
R: 'L',
A: 'P',
P: 'A',
H: 'F',
F: 'H'
};
var rori = '';
for (var n = 0; n < ori.length; n++) {
var o = ori.substr(n, 1);
var r = rlabels[o];
if (r) {
rori += r;
}
}
// return
return rori;
};
/**
* Tell if a given syntax is an implicit one (element with no VR).
*
* @param {string} syntax The transfer syntax to test.
* @returns {boolean} True if an implicit syntax.
*/
dwv.dicom.isImplicitTransferSyntax = function (syntax) {
return syntax === '1.2.840.10008.1.2';
};
/**
* Tell if a given syntax is a big endian syntax.
*
* @param {string} syntax The transfer syntax to test.
* @returns {boolean} True if a big endian syntax.
*/
dwv.dicom.isBigEndianTransferSyntax = function (syntax) {
return syntax === '1.2.840.10008.1.2.2';
};
/**
* Tell if a given syntax is a JPEG baseline one.
*
* @param {string} syntax The transfer syntax to test.
* @returns {boolean} True if a jpeg baseline syntax.
*/
dwv.dicom.isJpegBaselineTransferSyntax = function (syntax) {
return syntax === '1.2.840.10008.1.2.4.50' ||
syntax === '1.2.840.10008.1.2.4.51';
};
/**
* Tell if a given syntax is a retired JPEG one.
*
* @param {string} syntax The transfer syntax to test.
* @returns {boolean} True if a retired jpeg syntax.
*/
dwv.dicom.isJpegRetiredTransferSyntax = function (syntax) {
return (syntax.match(/1.2.840.10008.1.2.4.5/) !== null &&
!dwv.dicom.isJpegBaselineTransferSyntax() &&
!dwv.dicom.isJpegLosslessTransferSyntax()) ||
syntax.match(/1.2.840.10008.1.2.4.6/) !== null;
};
/**
* Tell if a given syntax is a JPEG Lossless one.
*
* @param {string} syntax The transfer syntax to test.
* @returns {boolean} True if a jpeg lossless syntax.
*/
dwv.dicom.isJpegLosslessTransferSyntax = function (syntax) {
return syntax === '1.2.840.10008.1.2.4.57' ||
syntax === '1.2.840.10008.1.2.4.70';
};
/**
* Tell if a given syntax is a JPEG-LS one.
*
* @param {string} syntax The transfer syntax to test.
* @returns {boolean} True if a jpeg-ls syntax.
*/
dwv.dicom.isJpeglsTransferSyntax = function (syntax) {
return syntax.match(/1.2.840.10008.1.2.4.8/) !== null;
};
/**
* Tell if a given syntax is a JPEG 2000 one.
*
* @param {string} syntax The transfer syntax to test.
* @returns {boolean} True if a jpeg 2000 syntax.
*/
dwv.dicom.isJpeg2000TransferSyntax = function (syntax) {
return syntax.match(/1.2.840.10008.1.2.4.9/) !== null;
};
/**
* Tell if a given syntax is a RLE (Run-length encoding) one.
*
* @param {string} syntax The transfer syntax to test.
* @returns {boolean} True if a RLE syntax.
*/
dwv.dicom.isRleTransferSyntax = function (syntax) {
return syntax.match(/1.2.840.10008.1.2.5/) !== null;
};
/**
* Tell if a given syntax needs decompression.
*
* @param {string} syntax The transfer syntax to test.
* @returns {string} The name of the decompression algorithm.
*/
dwv.dicom.getSyntaxDecompressionName = function (syntax) {
var algo = null;
if (dwv.dicom.isJpeg2000TransferSyntax(syntax)) {
algo = 'jpeg2000';
} else if (dwv.dicom.isJpegBaselineTransferSyntax(syntax)) {
algo = 'jpeg-baseline';
} else if (dwv.dicom.isJpegLosslessTransferSyntax(syntax)) {
algo = 'jpeg-lossless';
} else if (dwv.dicom.isRleTransferSyntax(syntax)) {
algo = 'rle';
}
return algo;
};
/**
* Tell if a given syntax is supported for reading.
*
* @param {string} syntax The transfer syntax to test.
* @returns {boolean} True if a supported syntax.
*/
dwv.dicom.isReadSupportedTransferSyntax = function (syntax) {
// Unsupported:
// "1.2.840.10008.1.2.1.99": Deflated Explicit VR - Little Endian
// "1.2.840.10008.1.2.4.100": MPEG2 Image Compression
// dwv.dicom.isJpegRetiredTransferSyntax(syntax): non supported JPEG
// dwv.dicom.isJpeglsTransferSyntax(syntax): JPEG-LS
return (syntax === '1.2.840.10008.1.2' || // Implicit VR - Little Endian
syntax === '1.2.840.10008.1.2.1' || // Explicit VR - Little Endian
syntax === '1.2.840.10008.1.2.2' || // Explicit VR - Big Endian
dwv.dicom.isJpegBaselineTransferSyntax(syntax) || // JPEG baseline
dwv.dicom.isJpegLosslessTransferSyntax(syntax) || // JPEG Lossless
dwv.dicom.isJpeg2000TransferSyntax(syntax) || // JPEG 2000
dwv.dicom.isRleTransferSyntax(syntax)); // RLE
};
/**
* Get the transfer syntax name.
* Reference: [UID Values]{@link http://dicom.nema.org/dicom/2013/output/chtml/part06/chapter_A.html}.
*
* @param {string} syntax The transfer syntax.
* @returns {string} The name of the transfer syntax.
*/
dwv.dicom.getTransferSyntaxName = function (syntax) {
var name = 'Unknown';
if (syntax === '1.2.840.10008.1.2') {
// Implicit VR - Little Endian
name = 'Little Endian Implicit';
} else if (syntax === '1.2.840.10008.1.2.1') {
// Explicit VR - Little Endian
name = 'Little Endian Explicit';
} else if (syntax === '1.2.840.10008.1.2.1.99') {
// Deflated Explicit VR - Little Endian
name = 'Little Endian Deflated Explicit';
} else if (syntax === '1.2.840.10008.1.2.2') {
// Explicit VR - Big Endian
name = 'Big Endian Explicit';
} else if (dwv.dicom.isJpegBaselineTransferSyntax(syntax)) {
// JPEG baseline
if (syntax === '1.2.840.10008.1.2.4.50') {
name = 'JPEG Baseline';
} else { // *.51
name = 'JPEG Extended, Process 2+4';
}
} else if (dwv.dicom.isJpegLosslessTransferSyntax(syntax)) {
// JPEG Lossless
if (syntax === '1.2.840.10008.1.2.4.57') {
name = 'JPEG Lossless, Nonhierarchical (Processes 14)';
} else { // *.70
name = 'JPEG Lossless, Non-hierarchical, 1st Order Prediction';
}
} else if (dwv.dicom.isJpegRetiredTransferSyntax(syntax)) {
// Retired JPEG
name = 'Retired JPEG';
} else if (dwv.dicom.isJpeglsTransferSyntax(syntax)) {
// JPEG-LS
name = 'JPEG-LS';
} else if (dwv.dicom.isJpeg2000TransferSyntax(syntax)) {
// JPEG 2000
if (syntax === '1.2.840.10008.1.2.4.91') {
name = 'JPEG 2000 (Lossless or Lossy)';
} else { // *.90
name = 'JPEG 2000 (Lossless only)';
}
} else if (syntax === '1.2.840.10008.1.2.4.100') {
// MPEG2 Image Compression
name = 'MPEG2';
} else if (dwv.dicom.isRleTransferSyntax(syntax)) {
// RLE (lossless)
name = 'RLE';
}
// return
return name;
};
/**
* Guess the transfer syntax from the first data element.
* See https://github.com/ivmartel/dwv/issues/188
* (Allow to load DICOM with no DICM preamble) for more details.
*
* @param {object} firstDataElement The first data element of the DICOM header.
* @returns {object} The transfer syntax data element.
*/
dwv.dicom.guessTransferSyntax = function (firstDataElement) {
var oEightGroupBigEndian = '0x0800';
var oEightGroupLittleEndian = '0x0008';
// check that group is 0x0008
var group = firstDataElement.tag.group;
if (group !== oEightGroupBigEndian &&
group !== oEightGroupLittleEndian) {
throw new Error(
'Not a valid DICOM file (no magic DICM word found' +
'and first element not in 0x0008 group)'
);
}
// reasonable assumption: 2 uppercase characters => explicit vr
var vr = firstDataElement.vr;
var vr0 = vr.charCodeAt(0);
var vr1 = vr.charCodeAt(1);
var implicit = (vr0 >= 65 && vr0 <= 90 && vr1 >= 65 && vr1 <= 90)
? false : true;
// guess transfer syntax
var syntax = null;
if (group === oEightGroupLittleEndian) {
if (implicit) {
// ImplicitVRLittleEndian
syntax = '1.2.840.10008.1.2';
} else {
// ExplicitVRLittleEndian
syntax = '1.2.840.10008.1.2.1';
}
} else {
if (implicit) {
// ImplicitVRBigEndian: impossible
throw new Error(
'Not a valid DICOM file (no magic DICM word found' +
'and implicit VR big endian detected)'
);
} else {
// ExplicitVRBigEndian
syntax = '1.2.840.10008.1.2.2';
}
}
// set transfer syntax data element
var dataElement = {
tag: {
group: '0x0002',
element: '0x0010',
name: 'x00020010',
endOffset: 4
},
vr: 'UI'
};
dataElement.value = [syntax + ' ']; // even length
dataElement.vl = dataElement.value[0].length;
dataElement.startOffset = firstDataElement.startOffset;
dataElement.endOffset = dataElement.startOffset + dataElement.vl;
return dataElement;
};
/**
* Get the appropriate TypedArray in function of arguments.
*
* @param {number} bitsAllocated The number of bites used to store
* the data: [8, 16, 32].
* @param {number} pixelRepresentation The pixel representation,
* 0:unsigned;1:signed.
* @param {dwv.image.Size} size The size of the new array.
* @returns {Array} The good typed array.
*/
dwv.dicom.getTypedArray = function (bitsAllocated, pixelRepresentation, size) {
var res = null;
try {
if (bitsAllocated === 8) {
if (pixelRepresentation === 0) {
res = new Uint8Array(size);
} else {
res = new Int8Array(size);
}
} else if (bitsAllocated === 16) {
if (pixelRepresentation === 0) {
res = new Uint16Array(size);
} else {
res = new Int16Array(size);
}
} else if (bitsAllocated === 32) {
if (pixelRepresentation === 0) {
res = new Uint32Array(size);
} else {
res = new Int32Array(size);
}
}
} catch (error) {
if (error instanceof RangeError) {
var powerOf2 = Math.floor(Math.log(size) / Math.log(2));
dwv.logger.error('Cannot allocate array of size: ' +
size + ' (>2^' + powerOf2 + ').');
}
}
return res;
};
/**
* Does this Value Representation (VR) have a 32bit Value Length (VL).
* Ref: [Data Element explicit]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/chapter_7.html#table_7.1-1}.
*
* @param {string} vr The data Value Representation (VR).
* @returns {boolean} True if this VR has a 32-bit VL.
*/
dwv.dicom.is32bitVLVR = function (vr) {
// added locally used 'ox'
return (vr === 'OB' ||
vr === 'OW' ||
vr === 'OF' ||
vr === 'ox' ||
vr === 'UT' ||
vr === 'SQ' ||
vr === 'UN');
};
/**
* Get the number of bytes occupied by a data element prefix,
* i.e. without its value.
*
* @param {string} vr The Value Representation of the element.
* @param {boolean} isImplicit Does the data use implicit VR?
* @returns {number} The size of the element prefix.
* WARNING: this is valid for tags with a VR, if not sure use
* the 'isTagWithVR' function first.
* Reference:
* - [Data Element explicit]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/chapter_7.html#table_7.1-1},
* - [Data Element implicit]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_7.5.html#table_7.5-1}.
*
* | Tag | VR | VL | Value |
* | 4 | 2 | 2 | X | -> regular explicit: 8 + X
* | 4 | 2+2 | 4 | X | -> 32bit VL: 12 + X
*
* | Tag | VL | Value |
* | 4 | 4 | X | -> implicit (32bit VL): 8 + X
*
* | Tag | Len | Value |
* | 4 | 4 | X | -> item: 8 + X
*/
dwv.dicom.getDataElementPrefixByteSize = function (vr, isImplicit) {
return isImplicit ? 8 : dwv.dicom.is32bitVLVR(vr) ? 12 : 8;
};
/**
* DicomParser class.
*
* @class
*/
dwv.dicom.DicomParser = function () {
/**
* The list of DICOM elements.
*
* @type {Array}
*/
this.dicomElements = {};
/**
* Default character set (optional).
*
* @private
* @type {string}
*/
var defaultCharacterSet;
/**
* Get the default character set.
*
* @returns {string} The default character set.
*/
this.getDefaultCharacterSet = function () {
return defaultCharacterSet;
};
/**
* Set the default character set.
* param {String} The character set.
*
* @param {string} characterSet The input character set.
*/
this.setDefaultCharacterSet = function (characterSet) {
defaultCharacterSet = characterSet;
};
};
/**
* Get the raw DICOM data elements.
*
* @returns {object} The raw DICOM elements.
*/
dwv.dicom.DicomParser.prototype.getRawDicomElements = function () {
return this.dicomElements;
};
/**
* Get the DICOM data elements.
*
* @returns {object} The DICOM elements.
*/
dwv.dicom.DicomParser.prototype.getDicomElements = function () {
return new dwv.dicom.DicomElementsWrapper(this.dicomElements);
};
/**
* Read a DICOM tag.
*
* @param {dwv.dicom.DataReader} reader The raw data reader.
* @param {number} offset The offset where to start to read.
* @returns {object} An object containing the tags 'group',
* 'element' and 'name'.
*/
dwv.dicom.DicomParser.prototype.readTag = function (reader, offset) {
// group
var group = reader.readHex(offset);
offset += Uint16Array.BYTES_PER_ELEMENT;
// element
var element = reader.readHex(offset);
offset += Uint16Array.BYTES_PER_ELEMENT;
// name
var name = new dwv.dicom.Tag(group, element).getKey();
// return
return {
group: group,
element: element,
name: name,
endOffset: offset
};
};
/**
* Read an explicit item data element.
*
* @param {dwv.dicom.DataReader} reader The raw data reader.
* @param {number} offset The offset where to start to read.
* @param {boolean} implicit Is the DICOM VR implicit?
* @returns {object} The item data as a list of data elements.
*/
dwv.dicom.DicomParser.prototype.readExplicitItemDataElement = function (
reader, offset, implicit) {
var itemData = {};
// read the first item
var item = this.readDataElement(reader, offset, implicit);
offset = item.endOffset;
itemData[item.tag.name] = item;
// read until the end offset
var endOffset = offset;
offset -= item.vl;
while (offset < endOffset) {
item = this.readDataElement(reader, offset, implicit);
offset = item.endOffset;
itemData[item.tag.name] = item;
}
return {
data: itemData,
endOffset: offset,
isSeqDelim: false
};
};
/**
* Read an implicit item data element.
*
* @param {dwv.dicom.DataReader} reader The raw data reader.
* @param {number} offset The offset where to start to read.
* @param {boolean} implicit Is the DICOM VR implicit?
* @returns {object} The item data as a list of data elements.
*/
dwv.dicom.DicomParser.prototype.readImplicitItemDataElement = function (
reader, offset, implicit) {
var itemData = {};
// read the first item
var item = this.readDataElement(reader, offset, implicit);
offset = item.endOffset;
// exit if it is a sequence delimitation item
if (item.tag.name === 'xFFFEE0DD') {
return {
data: itemData,
endOffset: item.endOffset,
isSeqDelim: true
};
}
// store item
itemData[item.tag.name] = item;
// read until the item delimitation item
var isItemDelim = false;
while (!isItemDelim) {
item = this.readDataElement(reader, offset, implicit);
offset = item.endOffset;
isItemDelim = item.tag.name === 'xFFFEE00D';
if (!isItemDelim) {
itemData[item.tag.name] = item;
}
}
return {
data: itemData,
endOffset: offset,
isSeqDelim: false
};
};
/**
* Read the pixel item data element.
* Ref: [Single frame fragments]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_A.4.html#table_A.4-1}.
*
* @param {dwv.dicom.DataReader} reader The raw data reader.
* @param {number} offset The offset where to start to read.
* @param {boolean} implicit Is the DICOM VR implicit?
* @returns {Array} The item data as an array of data elements.
*/
dwv.dicom.DicomParser.prototype.readPixelItemDataElement = function (
reader, offset, implicit) {
var itemData = [];
// first item: basic offset table
var item = this.readDataElement(reader, offset, implicit);
var offsetTableVl = item.vl;
offset = item.endOffset;
// read until the sequence delimitation item
var isSeqDelim = false;
while (!isSeqDelim) {
item = this.readDataElement(reader, offset, implicit);
offset = item.endOffset;
isSeqDelim = item.tag.name === 'xFFFEE0DD';
if (!isSeqDelim) {
itemData.push(item);
}
}
return {
data: itemData,
endOffset: offset,
offsetTableVl: offsetTableVl
};
};
/**
* Read a DICOM data element.
* Reference: [DICOM VRs]{@link http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_6.2.html#table_6.2-1}.
*
* @param {dwv.dicom.DataReader} reader The raw data reader.
* @param {number} offset The offset where to start to read.
* @param {boolean} implicit Is the DICOM VR implicit?
* @returns {object} An object containing the element
* 'tag', 'vl', 'vr', 'data' and 'endOffset'.
*/
dwv.dicom.DicomParser.prototype.readDataElement = function (
reader, offset, implicit) {
// Tag: group, element
var tagData = this.readTag(reader, offset);
var tag = new dwv.dicom.Tag(tagData.group, tagData.element);
offset = tagData.endOffset;
// Value Representation (VR)
var vr = null;
var is32bitVLVR = false;
if (tag.isWithVR()) {
// implicit VR
if (implicit) {
vr = tag.getVrFromDictionary();
if (vr === null) {
vr = 'UN';
}
is32bitVLVR = true;
} else {
vr = reader.readString(offset, 2);
offset += 2 * Uint8Array.BYTES_PER_ELEMENT;
is32bitVLVR = dwv.dicom.is32bitVLVR(vr);
// reserved 2 bytes
if (is32bitVLVR) {
offset += 2 * Uint8Array.BYTES_PER_ELEMENT;
}
}
} else {
vr = 'UN';
is32bitVLVR = true;
}
// Value Length (VL)
var vl = 0;
if (is32bitVLVR) {
vl = reader.readUint32(offset);
offset += Uint32Array.BYTES_PER_ELEMENT;
} else {
vl = reader.readUint16(offset);
offset += Uint16Array.BYTES_PER_ELEMENT;
}
// check the value of VL
var vlString = vl;
if (vl === 0xffffffff) {
vlString = 'u/l';
vl = 0;
}
// treat private tag with unknown VR and zero VL as a sequence (see #799)
//if (dwv.dicom.isPrivateGroup(tag.group) && vr === 'UN' && vl === 0) {
if (tag.isPrivate() && vr === 'UN' && vl === 0) {
vr = 'SQ';
}
var startOffset = offset;
var endOffset = startOffset + vl;
// read sequence elements
var data = null;
if (dwv.dicom.isPixelDataTag(tag) && vlString === 'u/l') {
// pixel data sequence (implicit)
var pixItemData = this.readPixelItemDataElement(reader, offset, implicit);
offset = pixItemData.endOffset;
startOffset += pixItemData.offsetTableVl;
data = pixItemData.data;
endOffset = offset;
} else if (vr === 'SQ') {
// sequence
data = [];
var itemData;
if (vlString !== 'u/l') {
// explicit VR sequence
if (vl !== 0) {
// read until the end offset
var sqEndOffset = offset + vl;
while (offset < sqEndOffset) {
itemData = this.readExplicitItemDataElement(reader, offset, implicit);
data.push(itemData.data);
offset = itemData.endOffset;
}
endOffset = offset;
}
} else {
// implicit VR sequence
// read until the sequence delimitation item
var isSeqDelim = false;
while (!isSeqDelim) {
itemData = this.readImplicitItemDataElement(reader, offset, implicit);
isSeqDelim = itemData.isSeqDelim;
offset = itemData.endOffset;
// do not store the delimitation item
if (!isSeqDelim) {
data.push(itemData.data);
}
}
endOffset = offset;
}
}
// return
var element = {
tag: tagData,
vr: vr,
vl: vlString,
startOffset: startOffset,
endOffset: endOffset
};
if (data) {
element.elements = data;
}
return element;
};
/**
* Interpret the data of an element.
*
* @param {object} element The data element.
* @param {dwv.dicom.DataReader} reader The raw data reader.
* @param {number} pixelRepresentation PixelRepresentation 0->unsigned,
* 1->signed (needed for pixel data or VR=xs).
* @param {number} bitsAllocated Bits allocated (needed for pixel data).
* @returns {object} The interpreted data.
*/
dwv.dicom.DicomParser.prototype.interpretElement = function (
element, reader, pixelRepresentation, bitsAllocated) {
var tag = element.tag;
var vl = element.vl;
var vr = element.vr;
var offset = element.startOffset;
// data
var data = null;
var isPixelDataTag = dwv.dicom.isPixelDataTag(
new dwv.dicom.Tag(tag.group, tag.element));
if (isPixelDataTag && vl === 'u/l') {
// implicit pixel data sequence
data = [];
for (var j = 0; j < element.elements.length; ++j) {
data.push(this.interpretElement(
element.elements[j], reader,
pixelRepresentation, bitsAllocated));
}
} else if (isPixelDataTag &&
(vr === 'OB' || vr === 'OW' || vr === 'OF' || vr === 'ox')) {
// check bits allocated and VR
if (bitsAllocated === 8 && vr === 'OW') {
dwv.logger.warn(
'Reading DICOM pixel data with vr=OW' +
' and bitsAllocated=8 (should be 16).'
);
}
if (bitsAllocated === 16 && vr === 'OB') {
dwv.logger.warn(
'Reading DICOM pixel data with vr=OB' +
' and bitsAllocated=16 (should be 8).'
);
}
// read
data = [];
if (bitsAllocated === 1) {
data.push(reader.readBinaryArray(offset, vl));
} else if (bitsAllocated === 8) {
if (pixelRepresentation === 0) {
data.push(reader.readUint8Array(offset, vl));
} else {
data.push(reader.readInt8Array(offset, vl));
}
} else if (bitsAllocated === 16) {
if (pixelRepresentation === 0) {
data.push(reader.readUint16Array(offset, vl));
} else {
data.push(reader.readInt16Array(offset, vl));
}
} else if (bitsAllocated === 32) {
if (pixelRepresentation === 0) {
data.push(reader.readUint32Array(offset, vl));
} else {
data.push(reader.readInt32Array(offset, vl));
}
} else if (bitsAllocated === 64) {
if (pixelRepresentation === 0) {
data.push(reader.readUint64Array(offset, vl));
} else {
data.push(reader.readInt64Array(offset, vl));
}
} else {
throw new Error('Unsupported bits allocated: ' + bitsAllocated);
}
} else if (vr === 'OB') {
data = reader.readUint8Array(offset, vl);
} else if (vr === 'OW') {
data = reader.readUint16Array(offset, vl);
} else if (vr === 'OF') {
data = reader.readUint32Array(offset, vl);
} else if (vr === 'OD') {
data = reader.readUint64Array(offset, vl);
} else if (vr === 'US') {
data = reader.readUint16Array(offset, vl);
} else if (vr === 'UL') {
data = reader.readUint32Array(offset, vl);
} else if (vr === 'SS') {
data = reader.readInt16Array(offset, vl);
} else if (vr === 'SL') {
data = reader.readInt32Array(offset, vl);
} else if (vr === 'FL') {
data = reader.readFloat32Array(offset, vl);
} else if (vr === 'FD') {
data = reader.readFloat64Array(offset, vl);
} else if (vr === 'xs') {
if (pixelRepresentation === 0) {
data = reader.readUint16Array(offset, vl);
} else {
data = reader.readInt16Array(offset, vl);
}
} else if (vr === 'AT') {
// attribute
var raw = reader.readUint16Array(offset, vl);
data = [];
for (var i = 0, leni = raw.length; i < leni; i += 2) {
var stri = raw[i].toString(16);
var stri1 = raw[i + 1].toString(16);
var str = '(';
str += '0000'.substr(0, 4 - stri.length) + stri.toUpperCase();
str += ',';
str += '0000'.substr(0, 4 - stri1.length) + stri1.toUpperCase();
str += ')';
data.push(str);
}
} else if (vr === 'UN') {
// not available
data = reader.readUint8Array(offset, vl);
} else if (vr === 'SQ') {
// sequence
data = [];
for (var k = 0; k < element.elements.length; ++k) {
var item = element.elements[k];
var itemData = {};
var keys = Object.keys(item);
for (var l = 0; l < keys.length; ++l) {
var subElement = item[keys[l]];
subElement.value = this.interpretElement(
subElement, reader,
pixelRepresentation, bitsAllocated);
itemData[keys[l]] = subElement;
}
data.push(itemData);
}
} else {
// raw
if (vr === 'SH' || vr === 'LO' || vr === 'ST' ||
vr === 'PN' || vr === 'LT' || vr === 'UT') {
data = reader.readSpecialString(offset, vl);
} else {
data = reader.readString(offset, vl);
}
data = data.split('\\');
}
return data;
};
/**
* Interpret the data of a list of elements.
*
* @param {Array} elements A list of data elements.
* @param {dwv.dicom.DataReader} reader The raw data reader.
* @param {number} pixelRepresentation PixelRepresentation 0->unsigned,
* 1->signed.
* @param {number} bitsAllocated Bits allocated.
*/
dwv.dicom.DicomParser.prototype.interpret = function (
elements, reader,
pixelRepresentation, bitsAllocated) {
var keys = Object.keys(elements);
for (var i = 0; i < keys.length; ++i) {
var element = elements[keys[i]];
if (typeof element.value === 'undefined') {
element.value = this.interpretElement(
element, reader, pixelRepresentation, bitsAllocated);
}
}
};
/**
* Parse the complete DICOM file (given as input to the class).
* Fills in the member object 'dicomElements'.
*
* @param {object} buffer The input array buffer.
*/
dwv.dicom.DicomParser.prototype.parse = function (buffer) {
var offset = 0;
var syntax = '';
var dataElement = null;
// default readers
var metaReader = new dwv.dicom.DataReader(buffer);
var dataReader = new dwv.dicom.DataReader(buffer);
// 128 -> 132: magic word
offset = 128;
var magicword = metaReader.readString(offset, 4);
offset += 4 * Uint8Array.BYTES_PER_ELEMENT;
if (magicword === 'DICM') {
// 0x0002, 0x0000: FileMetaInformationGroupLength
dataElement = this.readDataElement(metaReader, offset, false);
dataElement.value = this.interpretElement(dataElement, metaReader);
// increment offset
offset = dataElement.endOffset;
// store the data element
this.dicomElements[dataElement.tag.name] = dataElement;
// get meta length
var metaLength = parseInt(dataElement.value[0], 10);
// meta elements
var metaEnd = offset + metaLength;
while (offset < metaEnd) {
// get the data element
dataElement = this.readDataElement(metaReader, offset, false);
offset = dataElement.endOffset;
// store the data element
this.dicomElements[dataElement.tag.name] = dataElement;
}
// check the TransferSyntaxUID (has to be there!)
dataElement = this.dicomElements.x00020010;
if (typeof dataElement === 'undefined') {
throw new Error('Not a valid DICOM file (no TransferSyntaxUID found)');
}
dataElement.value = this.interpretElement(dataElement, metaReader);
syntax = dwv.dicom.cleanString(dataElement.value[0]);
} else {
// read first element
dataElement = this.readDataElement(dataReader, 0, false);
// guess transfer syntax
var tsElement = dwv.dicom.guessTransferSyntax(dataElement);
// store
this.dicomElements[tsElement.tag.name] = tsElement;
syntax = dwv.dicom.cleanString(tsElement.value[0]);
// reset offset
offset = 0;
}
// check transfer syntax support
if (!dwv.dicom.isReadSupportedTransferSyntax(syntax)) {
throw new Error('Unsupported DICOM transfer syntax: \'' + syntax +
'\' (' + dwv.dicom.getTransferSyntaxName(syntax) + ')');
}
// set implicit flag
var implicit = false;
if (dwv.dicom.isImplicitTransferSyntax(syntax)) {
implicit = true;
}
// Big Endian
if (dwv.dicom.isBigEndianTransferSyntax(syntax)) {
dataReader = new dwv.dicom.DataReader(buffer, false);
}
// default character set
if (typeof this.getDefaultCharacterSet() !== 'undefined') {
dataReader.setUtfLabel(this.getDefaultCharacterSet());
}
// DICOM data elements
while (offset < buffer.byteLength) {
// get the data element
dataElement = this.readDataElement(dataReader, offset, implicit);
// increment offset
offset = dataElement.endOffset;
// store the data element
if (typeof this.dicomElements[dataElement.tag.name] === 'undefined') {
this.dicomElements[dataElement.tag.name] = dataElement;
} else {
dwv.logger.warn('Not saving duplicate tag: ' + dataElement.tag.name);
}
}
// safety checks...
if (isNaN(offset)) {
throw new Error('Problem while parsing, bad offset');
}
if (buffer.byteLength !== offset) {
dwv.logger.warn('Did not reach the end of the buffer: ' +
offset + ' != ' + buffer.byteLength);
}
//-------------------------------------------------
// values needed for data interpretation
// PixelRepresentation 0->unsigned, 1->signed
var pixelRepresentation = 0;
dataElement = this.dicomElements.x00280103;
if (typeof dataElement !== 'undefined') {
dataElement.value = this.interpretElement(dataElement, dataReader);
pixelRepresentation = dataElement.value[0];
} else {
dwv.logger.warn(
'Reading DICOM pixel data with default pixelRepresentation.');
}
// BitsAllocated
var bitsAllocated = 16;
dataElement = this.dicomElements.x00280100;
if (typeof dataElement !== 'undefined') {
dataElement.value = this.interpretElement(dataElement, dataReader);
bitsAllocated = dataElement.value[0];
} else {
dwv.logger.warn('Reading DICOM pixel data with default bitsAllocated.');
}
// character set
dataElement = this.dicomElements.x00080005;
if (typeof dataElement !== 'undefined') {
dataElement.value = this.interpretElement(dataElement, dataReader);
var charSetTerm;
if (dataElement.value.length === 1) {
charSetTerm = dwv.dicom.cleanString(dataElement.value[0]);
} else {
charSetTerm = dwv.dicom.cleanString(dataElement.value[1]);
dwv.logger.warn('Unsupported character set with code extensions: \'' +
charSetTerm + '\'.');
}
dataReader.setUtfLabel(dwv.dicom.getUtfLabel(charSetTerm));
}
// interpret the dicom elements
this.interpret(
this.dicomElements, dataReader,
pixelRepresentation, bitsAllocated
);
// handle fragmented pixel buffer
// Reference: http://dicom.nema.org/dicom/2013/output/chtml/part05/sect_8.2.html
// (third note, "Depending on the transfer syntax...")
dataElement = this.dicomElements.x7FE00010;
if (typeof dataElement !== 'undefined') {
if (dataElement.vl === 'u/l') {
var numberOfFrames = 1;
if (typeof this.dicomElements.x00280008 !== 'undefined') {
numberOfFrames = dwv.dicom.cleanString(
this.dicomElements.x00280008.value[0]);
}
var pixItems = dataElement.value;
if (pixItems.length > 1 && pixItems.length > numberOfFrames) {
// concatenate pixel data items
// concat does not work on typed arrays
//this.pixelBuffer = this.pixelBuffer.concat( dataElement.data );
// manual concat...
var nItemPerFrame = pixItems.length / numberOfFrames;
var newPixItems = [];
var index = 0;
for (var f = 0; f < numberOfFrames; ++f) {
index = f * nItemPerFrame;
// calculate the size of a frame
var size = 0;
for (var i = 0; i < nItemPerFrame; ++i) {
size += pixItems[index + i].length;
}
// create new buffer
var newBuffer = new pixItems[0].constructor(size);
// fill new buffer
var fragOffset = 0;
for (var j = 0; j < nItemPerFrame; ++j) {
newBuffer.set(pixItems[index + j], fragOffset);
fragOffset += pixItems[index + j].length;
}
newPixItems[f] = newBuffer;
}
// store as pixel data
dataElement.value = newPixItems;
}
}
}
};