/*! dicom-parser - v1.1.5 - 2015-08-17 | (c) 2014 Chris Hafey | https://github.com/chafey/dicomParser */ (function (root, factory) { // node.js if (typeof module !== 'undefined' && module.exports) { module.exports = factory(); } else if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define([], factory); } else { // Browser globals if(typeof cornerstone === 'undefined'){ dicomParser = {}; // meteor if (typeof Package !== 'undefined') { root.dicomParser = dicomParser; } } dicomParser = factory(); } }(this, function () { /** * Parses a DICOM P10 byte array and returns a DataSet object with the parsed elements. If the options * argument is supplied and it contains the untilTag property, parsing will stop once that * tag is encoutered. This can be used to parse partial byte streams. * * @param byteArray the byte array * @param options object to control parsing behavior (optional) * @returns {DataSet} * @throws error if an error occurs while parsing. The exception object will contain a property dataSet with the * elements successfully parsed before the error. */ var dicomParser = (function(dicomParser) { if(dicomParser === undefined) { dicomParser = {}; } dicomParser.parseDicom = function(byteArray, options) { if(byteArray === undefined) { throw "dicomParser.parseDicom: missing required parameter 'byteArray'"; } var littleEndianByteStream = new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, byteArray); function readPrefix() { littleEndianByteStream.seek(128); var prefix = littleEndianByteStream.readFixedString(4); if(prefix !== "DICM") { throw "dicomParser.parseDicom: DICM prefix not found at location 132"; } } function readPart10Header() { // Per the DICOM standard, the header is always encoded in Explicit VR Little Endian (see PS3.10, section 7.1) // so use littleEndianByteStream throughout this method regardless of the transfer syntax readPrefix(); var warnings = []; var elements = {}; while(littleEndianByteStream.position < littleEndianByteStream.byteArray.length) { var position = littleEndianByteStream.position; var element = dicomParser.readDicomElementExplicit(littleEndianByteStream, warnings); if(element.tag > 'x0002ffff') { littleEndianByteStream.position = position; break; } // Cache the littleEndianByteArrayParser for meta header elements, since the rest of the data set may be big endian // and this parser will be needed later if the meta header values are to be read. element.parser = dicomParser.littleEndianByteArrayParser; elements[element.tag] = element; } var metaHeaderDataSet = new dicomParser.DataSet(littleEndianByteStream.byteArrayParser, littleEndianByteStream.byteArray, elements); metaHeaderDataSet.warnings = littleEndianByteStream.warnings; return metaHeaderDataSet; } function readTransferSyntax(metaHeaderDataSet) { if(metaHeaderDataSet.elements.x00020010 === undefined) { throw 'dicomParser.parseDicom: missing required meta header attribute 0002,0010'; } var transferSyntaxElement = metaHeaderDataSet.elements.x00020010; return dicomParser.readFixedString(littleEndianByteStream.byteArray, transferSyntaxElement.dataOffset, transferSyntaxElement.length); } function isExplicit(transferSyntax) { if(transferSyntax === '1.2.840.10008.1.2') // implicit little endian { return false; } // all other transfer syntaxes should be explicit return true; } function getDataSetByteStream(transferSyntax) { if(transferSyntax === '1.2.840.10008.1.2.2') // explicit big endian { return new dicomParser.ByteStream(dicomParser.bigEndianByteArrayParser, byteArray, littleEndianByteStream.position); } else { // all other transfer syntaxes are little endian; only the pixel encoding differs // make a new stream so the metaheader warnings don't come along for the ride return new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, byteArray, littleEndianByteStream.position); } } function mergeDataSets(metaHeaderDataSet, instanceDataSet) { for (var propertyName in metaHeaderDataSet.elements) { if(metaHeaderDataSet.elements.hasOwnProperty(propertyName)) { instanceDataSet.elements[propertyName] = metaHeaderDataSet.elements[propertyName]; } } if (metaHeaderDataSet.warnings !== undefined) { instanceDataSet.warnings = metaHeaderDataSet.warnings.concat(instanceDataSet.warnings); } return instanceDataSet; } function readDataSet(metaHeaderDataSet) { var transferSyntax = readTransferSyntax(metaHeaderDataSet); var explicit = isExplicit(transferSyntax); var dataSetByteStream = getDataSetByteStream(transferSyntax); var elements = {}; var dataSet = new dicomParser.DataSet(dataSetByteStream.byteArrayParser, dataSetByteStream.byteArray, elements); dataSet.warnings = dataSetByteStream.warnings; try{ if(explicit) { dicomParser.parseDicomDataSetExplicit(dataSet, dataSetByteStream, dataSetByteStream.byteArray.length, options); } else { dicomParser.parseDicomDataSetImplicit(dataSet, dataSetByteStream, dataSetByteStream.byteArray.length, options); } } catch(e) { var ex = { exception: e, dataSet: dataSet }; throw ex; } return dataSet; } // main function here function parseTheByteStream() { var metaHeaderDataSet = readPart10Header(); var dataSet = readDataSet(metaHeaderDataSet); return mergeDataSets(metaHeaderDataSet, dataSet); } // This is where we actually start parsing return parseTheByteStream(); }; return dicomParser; })(dicomParser); var dicomParser = (function (dicomParser) { "use strict"; if (dicomParser === undefined) { dicomParser = {}; } /** * converts an explicit dataSet to a javascript object * @param dataSet * @param options */ dicomParser.explicitDataSetToJS = function (dataSet, options) { if(dataSet === undefined) { throw 'dicomParser.explicitDataSetToJS: missing required parameter dataSet'; } options = options || { omitPrivateAttibutes: true, // true if private elements should be omitted maxElementLength : 128 // maximum element length to try and convert to string format }; var result = { }; for(var tag in dataSet.elements) { var element = dataSet.elements[tag]; // skip this element if it a private element and our options specify that we should if(options.omitPrivateAttibutes === true && dicomParser.isPrivateTag(tag)) { continue; } if(element.items) { // handle sequences var sequenceItems = []; for(var i=0; i < element.items.length; i++) { sequenceItems.push(dicomParser.explicitDataSetToJS(element.items[i].dataSet, options)); } result[tag] = sequenceItems; } else { var asString; asString = undefined; if(element.length < options.maxElementLength) { asString = dicomParser.explicitElementToString(dataSet, element); } if(asString !== undefined) { result[tag] = asString; } else { result[tag] = { dataOffset: element.dataOffset, length : element.length }; } } } return result; }; return dicomParser; }(dicomParser)); var dicomParser = (function (dicomParser) { "use strict"; if (dicomParser === undefined) { dicomParser = {}; } /** * Converts an explicit VR element to a string or undefined if it is not possible to convert. * Throws an error if an implicit element is supplied * @param dataSet * @param element * @returns {*} */ dicomParser.explicitElementToString = function(dataSet, element) { if(dataSet === undefined || element === undefined) { throw 'dicomParser.explicitElementToString: missing required parameters'; } if(element.vr === undefined) { throw 'dicomParser.explicitElementToString: cannot convert implicit element to string'; } var vr = element.vr; var tag = element.tag; var textResult; function multiElementToString(numItems, func) { var result = ""; for(var i=0; i < numItems; i++) { if(i !== 0) { result += '/'; } result += func.call(dataSet, tag).toString(); } return result; } if(dicomParser.isStringVr(vr) === true) { textResult = dataSet.string(tag); } else if (vr == 'AT') { var num = dataSet.uint32(tag); if(num === undefined) { return undefined; } if (num < 0) { num = 0xFFFFFFFF + num + 1; } return 'x' + num.toString(16).toUpperCase(); } else if (vr == 'US') { textResult = multiElementToString(element.length / 2, dataSet.uint16); } else if(vr === 'SS') { textResult = multiElementToString(element.length / 2, dataSet.int16); } else if (vr == 'UL') { textResult = multiElementToString(element.length / 4, dataSet.uint32); } else if(vr === 'SL') { textResult = multiElementToString(element.length / 4, dataSet.int32); } else if(vr == 'FD') { textResult = multiElementToString(element.length / 8, dataSet.double); } else if(vr == 'FL') { textResult = multiElementToString(element.length / 4, dataSet.float); } return textResult; }; return dicomParser; }(dicomParser)); /** * Utility functions for dealing with DICOM */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } // algorithm based on http://stackoverflow.com/questions/1433030/validate-number-of-days-in-a-given-month function daysInMonth(m, y) { // m is 0 indexed: 0-11 switch (m) { case 2 : return (y % 4 == 0 && y % 100) || y % 400 == 0 ? 29 : 28; case 9 : case 4 : case 6 : case 11 : return 30; default : return 31 } } function isValidDate(d, m, y) { // make year is a number if(isNaN(y)) { return false; } return m > 0 && m <= 12 && d > 0 && d <= daysInMonth(m, y); } /** * Parses a DA formatted string into a Javascript object * @param {string} date a string in the DA VR format * @param {boolean} [validate] - true if an exception should be thrown if the date is invalid * @returns {*} Javascript object with properties year, month and day or undefined if not present or not 8 bytes long */ dicomParser.parseDA = function(date, validate) { if(date && date.length === 8) { var yyyy = parseInt(date.substring(0, 4), 10); var mm = parseInt(date.substring(4, 6), 10); var dd = parseInt(date.substring(6, 8), 10); if(validate) { if (isValidDate(dd, mm, yyyy) !== true) { throw "invalid DA '" + date + "'"; } } return { year: yyyy, month: mm, day: dd }; } if(validate) { throw "invalid DA '" + date + "'"; } return undefined; }; return dicomParser; }(dicomParser)); /** * Utility functions for dealing with DICOM */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } /** * Parses a TM formatted string into a javascript object with properties for hours, minutes, seconds and fractionalSeconds * @param {string} time - a string in the TM VR format * @param {boolean} [validate] - true if an exception should be thrown if the date is invalid * @returns {*} javascript object with properties for hours, minutes, seconds and fractionalSeconds or undefined if no element or data. Missing fields are set to undefined */ dicomParser.parseTM = function(time, validate) { if (time.length >= 2) // must at least have HH { // 0123456789 // HHMMSS.FFFFFF var hh = parseInt(time.substring(0, 2), 10); var mm = time.length >= 4 ? parseInt(time.substring(2, 4), 10) : undefined; var ss = time.length >= 6 ? parseInt(time.substring(4, 6), 10) : undefined; var ffffff = time.length >= 8 ? parseInt(time.substring(7, 13), 10) : undefined; if(validate) { if((isNaN(hh)) || (mm !== undefined && isNaN(mm)) || (ss !== undefined && isNaN(ss)) || (ffffff !== undefined && isNaN(ffffff)) || (hh < 0 || hh > 23) || (mm && (mm <0 || mm > 59)) || (ss && (ss <0 || ss > 59)) || (ffffff && (ffffff <0 || ffffff > 999999))) { throw "invalid TM '" + time + "'"; } } return { hours: hh, minutes: mm, seconds: ss, fractionalSeconds: ffffff }; } if(validate) { throw "invalid TM '" + time + "'"; } return undefined; }; return dicomParser; }(dicomParser)); /** * Utility functions for dealing with DICOM */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } var stringVrs = { AE: true, AS: true, AT: false, CS: true, DA: true, DS: true, DT: true, FL: false, FD: false, IS: true, LO: true, LT: true, OB: false, OD: false, OF: false, OW: false, PN: true, SH: true, SL: false, SQ: false, SS: false, ST: true, TM: true, UI: true, UL: false, UN: undefined, // dunno UR: true, US: false, UT: true }; /** * Tests to see if vr is a string or not. * @param vr * @returns true if string, false it not string, undefined if unknown vr or UN type */ dicomParser.isStringVr = function(vr) { return stringVrs[vr]; }; /** * Tests to see if a given tag in the format xggggeeee is a private tag or not * @param tag * @returns {boolean} */ dicomParser.isPrivateTag = function(tag) { var lastGroupDigit = parseInt(tag[4]); var groupIsOdd = (lastGroupDigit % 2) === 1; return groupIsOdd; }; /** * Parses a PN formatted string into a javascript object with properties for givenName, familyName, middleName, prefix and suffix * @param personName a string in the PN VR format * @param index * @returns {*} javascript object with properties for givenName, familyName, middleName, prefix and suffix or undefined if no element or data */ dicomParser.parsePN = function(personName) { if(personName === undefined) { return undefined; } var stringValues = personName.split('^'); return { familyName: stringValues[0], givenName: stringValues[1], middleName: stringValues[2], prefix: stringValues[3], suffix: stringValues[4] }; }; return dicomParser; }(dicomParser)); /** * Functionality for extracting encapsulated pixel data */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } function getPixelDataFromFragments(byteStream, fragments, bufferSize) { // if there is only one fragment, return a view on this array to avoid copying if(fragments.length === 1) { return new Uint8Array(byteStream.byteArray.buffer, fragments[0].dataOffset, fragments[0].length); } // more than one fragment, combine all of the fragments into one buffer var pixelData = new Uint8Array(bufferSize); var pixelDataIndex = 0; for(var i=0; i < fragments.length; i++) { var fragmentOffset = fragments[i].dataOffset; for(var j=0; j < fragments[i].length; j++) { pixelData[pixelDataIndex++] = byteStream.byteArray[fragmentOffset++]; } } return pixelData; } function readFragmentsUntil(byteStream, endOfFrame) { // Read fragments until we reach endOfFrame var fragments = []; var bufferSize = 0; while(byteStream.position < endOfFrame && byteStream.position < byteStream.byteArray.length) { var fragment = dicomParser.readSequenceItem(byteStream); // NOTE: we only encounter this for the sequence delimiter tag when extracting the last frame if(fragment.tag === 'xfffee0dd') { break; } fragments.push(fragment); byteStream.seek(fragment.length); bufferSize += fragment.length; } // Convert the fragments into a single pixelData buffer var pixelData = getPixelDataFromFragments(byteStream, fragments, bufferSize); return pixelData; } function readEncapsulatedPixelDataWithBasicOffsetTable(pixelDataElement, byteStream, frame) { // validate that we have an offset for this frame var numFrames = pixelDataElement.basicOffsetTable.length; if(frame > numFrames) { throw "dicomParser.readEncapsulatedPixelData: parameter frame exceeds number of frames in basic offset table"; } // move to the start of this frame var frameOffset = pixelDataElement.basicOffsetTable[frame]; byteStream.seek(frameOffset); // Find the end of this frame var endOfFrameOffset = pixelDataElement.basicOffsetTable[frame + 1]; if(endOfFrameOffset === undefined) { // special case for last frame endOfFrameOffset = byteStream.position + pixelDataElement.length; } // read this frame var pixelData = readFragmentsUntil(byteStream, endOfFrameOffset); return pixelData; } function readEncapsulatedDataNoBasicOffsetTable(pixelDataElement, byteStream, frame) { // if the basic offset table is empty, this is a single frame so make sure the requested // frame is 0 if(frame !== 0) { throw 'dicomParser.readEncapsulatedPixelData: non zero frame specified for single frame encapsulated pixel data'; } // read this frame var endOfFrame = byteStream.position + pixelDataElement.length; var pixelData = readFragmentsUntil(byteStream, endOfFrame); return pixelData; } /** * Returns the pixel data for the specified frame in an encapsulated pixel data element * * @param dataSet - the dataSet containing the encapsulated pixel data * @param pixelDataElement - the pixel data element (x7fe00010) to extract the frame from * @param frame - the zero based frame index * @returns Uint8Array with the encapsulated pixel data */ dicomParser.readEncapsulatedPixelData = function(dataSet, pixelDataElement, frame) { if(dataSet === undefined) { throw "dicomParser.readEncapsulatedPixelData: missing required parameter 'dataSet'"; } if(pixelDataElement === undefined) { throw "dicomParser.readEncapsulatedPixelData: missing required parameter 'element'"; } if(frame === undefined) { throw "dicomParser.readEncapsulatedPixelData: missing required parameter 'frame'"; } if(pixelDataElement.tag !== 'x7fe00010') { throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to non pixel data tag (expected tag = x7fe00010'"; } if(pixelDataElement.encapsulatedPixelData !== true) { throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to pixel data element that does not have encapsulated pixel data"; } if(pixelDataElement.hadUndefinedLength !== true) { throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to pixel data element that does not have encapsulated pixel data"; } if(pixelDataElement.basicOffsetTable === undefined) { throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to pixel data element that does not have encapsulated pixel data"; } if(pixelDataElement.fragments === undefined) { throw "dicomParser.readEncapsulatedPixelData: parameter 'element' refers to pixel data element that does not have encapsulated pixel data"; } if(frame < 0) { throw "dicomParser.readEncapsulatedPixelData: parameter 'frame' must be >= 0"; } // seek past the basic offset table (no need to parse it again since we already have) var byteStream = new dicomParser.ByteStream(dataSet.byteArrayParser, dataSet.byteArray, pixelDataElement.dataOffset); var basicOffsetTable = dicomParser.readSequenceItem(byteStream); if(basicOffsetTable.tag !== 'xfffee000') { throw "dicomParser.readEncapsulatedPixelData: missing basic offset table xfffee000"; } byteStream.seek(basicOffsetTable.length); // If the basic offset table is empty (no entries), it is a single frame. If it is not empty, // it has at least one frame so use the basic offset table to find the bytes if(pixelDataElement.basicOffsetTable.length !== 0) { return readEncapsulatedPixelDataWithBasicOffsetTable(pixelDataElement, byteStream, frame); } else { return readEncapsulatedDataNoBasicOffsetTable(pixelDataElement, byteStream, frame); } }; return dicomParser; }(dicomParser)); /** * Internal helper functions for parsing different types from a big-endian byte array */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } dicomParser.bigEndianByteArrayParser = { /** * * Parses an unsigned int 16 from a big-endian byte array * * @param byteArray the byte array to read from * @param position the position in the byte array to read from * @returns {*} the parsed unsigned int 16 * @throws error if buffer overread would occur * @access private */ readUint16: function (byteArray, position) { if (position < 0) { throw 'bigEndianByteArrayParser.readUint16: position cannot be less than 0'; } if (position + 2 > byteArray.length) { throw 'bigEndianByteArrayParser.readUint16: attempt to read past end of buffer'; } return (byteArray[position] << 8) + byteArray[position + 1]; }, /** * * Parses a signed int 16 from a big-endian byte array * * @param byteArray the byte array to read from * @param position the position in the byte array to read from * @returns {*} the parsed signed int 16 * @throws error if buffer overread would occur * @access private */ readInt16: function (byteArray, position) { if (position < 0) { throw 'bigEndianByteArrayParser.readInt16: position cannot be less than 0'; } if (position + 2 > byteArray.length) { throw 'bigEndianByteArrayParser.readInt16: attempt to read past end of buffer'; } var int16 = (byteArray[position] << 8) + byteArray[position + 1]; // fix sign if (int16 & 0x8000) { int16 = int16 - 0xFFFF - 1; } return int16; }, /** * Parses an unsigned int 32 from a big-endian byte array * * @param byteArray the byte array to read from * @param position the position in the byte array to read from * @returns {*} the parsed unsigned int 32 * @throws error if buffer overread would occur * @access private */ readUint32: function (byteArray, position) { if (position < 0) { throw 'bigEndianByteArrayParser.readUint32: position cannot be less than 0'; } if (position + 4 > byteArray.length) { throw 'bigEndianByteArrayParser.readUint32: attempt to read past end of buffer'; } var uint32 = (256 * (256 * (256 * byteArray[position] + byteArray[position + 1]) + byteArray[position + 2]) + byteArray[position + 3]); return uint32; }, /** * Parses a signed int 32 from a big-endian byte array * * @param byteArray the byte array to read from * @param position the position in the byte array to read from * @returns {*} the parsed signed int 32 * @throws error if buffer overread would occur * @access private */ readInt32: function (byteArray, position) { if (position < 0) { throw 'bigEndianByteArrayParser.readInt32: position cannot be less than 0'; } if (position + 4 > byteArray.length) { throw 'bigEndianByteArrayParser.readInt32: attempt to read past end of buffer'; } var int32 = ((byteArray[position] << 24) + (byteArray[position + 1] << 16) + (byteArray[position + 2] << 8) + byteArray[position + 3]); return int32; }, /** * Parses 32-bit float from a big-endian byte array * * @param byteArray the byte array to read from * @param position the position in the byte array to read from * @returns {*} the parsed 32-bit float * @throws error if buffer overread would occur * @access private */ readFloat: function (byteArray, position) { if (position < 0) { throw 'bigEndianByteArrayParser.readFloat: position cannot be less than 0'; } if (position + 4 > byteArray.length) { throw 'bigEndianByteArrayParser.readFloat: attempt to read past end of buffer'; } // I am sure there is a better way than this but this should be safe var byteArrayForParsingFloat = new Uint8Array(4); byteArrayForParsingFloat[3] = byteArray[position]; byteArrayForParsingFloat[2] = byteArray[position + 1]; byteArrayForParsingFloat[1] = byteArray[position + 2]; byteArrayForParsingFloat[0] = byteArray[position + 3]; var floatArray = new Float32Array(byteArrayForParsingFloat.buffer); return floatArray[0]; }, /** * Parses 64-bit float from a big-endian byte array * * @param byteArray the byte array to read from * @param position the position in the byte array to read from * @returns {*} the parsed 64-bit float * @throws error if buffer overread would occur * @access private */ readDouble: function (byteArray, position) { if (position < 0) { throw 'bigEndianByteArrayParser.readDouble: position cannot be less than 0'; } if (position + 8 > byteArray.length) { throw 'bigEndianByteArrayParser.readDouble: attempt to read past end of buffer'; } // I am sure there is a better way than this but this should be safe var byteArrayForParsingFloat = new Uint8Array(8); byteArrayForParsingFloat[7] = byteArray[position]; byteArrayForParsingFloat[6] = byteArray[position + 1]; byteArrayForParsingFloat[5] = byteArray[position + 2]; byteArrayForParsingFloat[4] = byteArray[position + 3]; byteArrayForParsingFloat[3] = byteArray[position + 4]; byteArrayForParsingFloat[2] = byteArray[position + 5]; byteArrayForParsingFloat[1] = byteArray[position + 6]; byteArrayForParsingFloat[0] = byteArray[position + 7]; var floatArray = new Float64Array(byteArrayForParsingFloat.buffer); return floatArray[0]; } }; return dicomParser; }(dicomParser)); /** * Internal helper functions common to parsing byte arrays of any type */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } /** * Reads a string of 8-bit characters from an array of bytes and advances * the position by length bytes. A null terminator will end the string * but will not effect advancement of the position. Trailing and leading * spaces are preserved (not trimmed) * @param byteArray the byteArray to read from * @param position the position in the byte array to read from * @param length the maximum number of bytes to parse * @returns {string} the parsed string * @throws error if buffer overread would occur * @access private */ dicomParser.readFixedString = function(byteArray, position, length) { if(length < 0) { throw 'readFixedString - length cannot be less than 0'; } if(position + length > byteArray.length) { throw 'dicomParser.readFixedString: attempt to read past end of buffer'; } var result = ""; for(var i=0; i < length; i++) { var byte = byteArray[position + i]; if(byte === 0) { position += length; return result; } result += String.fromCharCode(byte); } return result; }; return dicomParser; }(dicomParser)); /** * * Internal helper class to assist with parsing. Supports reading from a byte * stream contained in a Uint8Array. Example usage: * * var byteArray = new Uint8Array(32); * var byteStream = new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, byteArray); * * */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } /** * Constructor for ByteStream objects. * @param byteArrayParser a parser for parsing the byte array * @param byteArray a Uint8Array containing the byte stream * @param position (optional) the position to start reading from. 0 if not specified * @constructor * @throws will throw an error if the byteArrayParser parameter is not present * @throws will throw an error if the byteArray parameter is not present or invalid * @throws will throw an error if the position parameter is not inside the byte array */ dicomParser.ByteStream = function(byteArrayParser, byteArray, position) { if(byteArrayParser === undefined) { throw "dicomParser.ByteStream: missing required parameter 'byteArrayParser'"; } if(byteArray === undefined) { throw "dicomParser.ByteStream: missing required parameter 'byteArray'"; } if((byteArray instanceof Uint8Array) === false) { throw 'dicomParser.ByteStream: parameter byteArray is not of type Uint8Array'; } if(position < 0) { throw "dicomParser.ByteStream: parameter 'position' cannot be less than 0"; } if(position >= byteArray.length) { throw "dicomParser.ByteStream: parameter 'position' cannot be greater than or equal to 'byteArray' length"; } this.byteArrayParser = byteArrayParser; this.byteArray = byteArray; this.position = position ? position : 0; this.warnings = []; // array of string warnings encountered while parsing }; /** * Safely seeks through the byte stream. Will throw an exception if an attempt * is made to seek outside of the byte array. * @param offset the number of bytes to add to the position * @throws error if seek would cause position to be outside of the byteArray */ dicomParser.ByteStream.prototype.seek = function(offset) { if(this.position + offset < 0) { throw "cannot seek to position < 0"; } this.position += offset; }; /** * Returns a new ByteStream object from the current position and of the requested number of bytes * @param numBytes the length of the byte array for the ByteStream to contain * @returns {dicomParser.ByteStream} * @throws error if buffer overread would occur */ dicomParser.ByteStream.prototype.readByteStream = function(numBytes) { if(this.position + numBytes > this.byteArray.length) { throw 'readByteStream - buffer overread'; } var byteArrayView = new Uint8Array(this.byteArray.buffer, this.position, numBytes); this.position += numBytes; return new dicomParser.ByteStream(this.byteArrayParser, byteArrayView); }; /** * * Parses an unsigned int 16 from a byte array and advances * the position by 2 bytes * * @returns {*} the parsed unsigned int 16 * @throws error if buffer overread would occur */ dicomParser.ByteStream.prototype.readUint16 = function() { var result = this.byteArrayParser.readUint16(this.byteArray, this.position); this.position += 2; return result; }; /** * Parses an unsigned int 32 from a byte array and advances * the position by 2 bytes * * @returns {*} the parse unsigned int 32 * @throws error if buffer overread would occur */ dicomParser.ByteStream.prototype.readUint32 = function() { var result = this.byteArrayParser.readUint32(this.byteArray, this.position); this.position += 4; return result; }; /** * Reads a string of 8-bit characters from an array of bytes and advances * the position by length bytes. A null terminator will end the string * but will not effect advancement of the position. * @param length the maximum number of bytes to parse * @returns {string} the parsed string * @throws error if buffer overread would occur */ dicomParser.ByteStream.prototype.readFixedString = function(length) { var result = dicomParser.readFixedString(this.byteArray, this.position, length); this.position += length; return result; }; return dicomParser; }(dicomParser)); /** * * The DataSet class encapsulates a collection of DICOM Elements and provides various functions * to access the data in those elements * * Rules for handling padded spaces: * DS = Strip leading and trailing spaces * DT = Strip trailing spaces * IS = Strip leading and trailing spaces * PN = Strip trailing spaces * TM = Strip trailing spaces * AE = Strip leading and trailing spaces * CS = Strip leading and trailing spaces * SH = Strip leading and trailing spaces * LO = Strip leading and trailing spaces * LT = Strip trailing spaces * ST = Strip trailing spaces * UT = Strip trailing spaces * */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } function getByteArrayParser(element, defaultParser) { return (element.parser !== undefined ? element.parser : defaultParser); } /** * Constructs a new DataSet given byteArray and collection of elements * @param byteArrayParser * @param byteArray * @param elements * @constructor */ dicomParser.DataSet = function(byteArrayParser, byteArray, elements) { this.byteArrayParser = byteArrayParser; this.byteArray = byteArray; this.elements = elements; }; /** * Finds the element for tag and returns an unsigned int 16 if it exists and has data * @param tag The DICOM tag in the format xGGGGEEEE * @param index the index of the value in a multivalued element. Default is index 0 if not supplied * @returns {*} unsigned int 16 or undefined if the attribute is not present or has data of length 0 */ dicomParser.DataSet.prototype.uint16 = function(tag, index) { var element = this.elements[tag]; index = (index !== undefined) ? index : 0; if(element && element.length !== 0) { return getByteArrayParser(element, this.byteArrayParser).readUint16(this.byteArray, element.dataOffset + (index *2)); } return undefined; }; /** * Finds the element for tag and returns an signed int 16 if it exists and has data * @param tag The DICOM tag in the format xGGGGEEEE * @param index the index of the value in a multivalued element. Default is index 0 if not supplied * @returns {*} signed int 16 or undefined if the attribute is not present or has data of length 0 */ dicomParser.DataSet.prototype.int16 = function(tag, index) { var element = this.elements[tag]; index = (index !== undefined) ? index : 0; if(element && element.length !== 0) { return getByteArrayParser(element, this.byteArrayParser).readInt16(this.byteArray, element.dataOffset + (index * 2)); } return undefined; }; /** * Finds the element for tag and returns an unsigned int 32 if it exists and has data * @param tag The DICOM tag in the format xGGGGEEEE * @param index the index of the value in a multivalued element. Default is index 0 if not supplied * @returns {*} unsigned int 32 or undefined if the attribute is not present or has data of length 0 */ dicomParser.DataSet.prototype.uint32 = function(tag, index) { var element = this.elements[tag]; index = (index !== undefined) ? index : 0; if(element && element.length !== 0) { return getByteArrayParser(element, this.byteArrayParser).readUint32(this.byteArray, element.dataOffset + (index * 4)); } return undefined; }; /** * Finds the element for tag and returns an signed int 32 if it exists and has data * @param tag The DICOM tag in the format xGGGGEEEE * @param index the index of the value in a multivalued element. Default is index 0 if not supplied * @returns {*} signed int 32 or undefined if the attribute is not present or has data of length 0 */ dicomParser.DataSet.prototype.int32 = function(tag, index) { var element = this.elements[tag]; index = (index !== undefined) ? index : 0; if(element && element.length !== 0) { return getByteArrayParser(element, this.byteArrayParser).readInt32(this.byteArray, element.dataOffset + (index * 4)); } return undefined; }; /** * Finds the element for tag and returns a 32 bit floating point number (VR=FL) if it exists and has data * @param tag The DICOM tag in the format xGGGGEEEE * @param index the index of the value in a multivalued element. Default is index 0 if not supplied * @returns {*} float or undefined if the attribute is not present or has data of length 0 */ dicomParser.DataSet.prototype.float = function(tag, index) { var element = this.elements[tag]; index = (index !== undefined) ? index : 0; if(element && element.length !== 0) { return getByteArrayParser(element, this.byteArrayParser).readFloat(this.byteArray, element.dataOffset + (index * 4)); } return undefined; }; /** * Finds the element for tag and returns a 64 bit floating point number (VR=FD) if it exists and has data * @param tag The DICOM tag in the format xGGGGEEEE * @param index the index of the value in a multivalued element. Default is index 0 if not supplied * @returns {*} float or undefined if the attribute is not present or doesn't has data of length 0 */ dicomParser.DataSet.prototype.double = function(tag, index) { var element = this.elements[tag]; index = (index !== undefined) ? index : 0; if(element && element.length !== 0) { return getByteArrayParser(element, this.byteArrayParser).readDouble(this.byteArray, element.dataOffset + (index * 8)); } return undefined; }; /** * Returns the number of string values for the element * @param tag The DICOM tag in the format xGGGGEEEE * @returns {*} the number of string values or undefined if the attribute is not present or has zero length data */ dicomParser.DataSet.prototype.numStringValues = function(tag) { var element = this.elements[tag]; if(element && element.length > 0) { var fixedString = dicomParser.readFixedString(this.byteArray, element.dataOffset, element.length); var numMatching = fixedString.match(/\\/g); if(numMatching === null) { return 1; } return numMatching.length + 1; } return undefined; }; /** * Returns a string for the element. If index is provided, the element is assumed to be * multi-valued and will return the component specified by index. Undefined is returned * if there is no component with the specified index, the element does not exist or is zero length. * * Use this function for VR types of AE, CS, SH and LO * * @param tag The DICOM tag in the format xGGGGEEEE * @param index the index of the desired value in a multi valued string or undefined for the entire string * @returns {*} */ dicomParser.DataSet.prototype.string = function(tag, index) { var element = this.elements[tag]; if(element && element.length > 0) { var fixedString = dicomParser.readFixedString(this.byteArray, element.dataOffset, element.length); if(index >= 0) { var values = fixedString.split('\\'); // trim trailing spaces return values[index].trim(); } else { // trim trailing spaces return fixedString.trim(); } } return undefined; }; /** * Returns a string with the leading spaces preserved and trailing spaces removed. * * Use this function to access data for VRs of type UT, ST and LT * * @param tag * @param index * @returns {*} */ dicomParser.DataSet.prototype.text = function(tag, index) { var element = this.elements[tag]; if(element && element.length > 0) { var fixedString = dicomParser.readFixedString(this.byteArray, element.dataOffset, element.length); if(index >= 0) { var values = fixedString.split('\\'); return values[index].replace(/ +$/, ''); } else { return fixedString.replace(/ +$/, ''); } } return undefined; }; /** * Parses a string to a float for the specified index in a multi-valued element. If index is not specified, * the first value in a multi-valued VR will be parsed if present. * @param tag The DICOM tag in the format xGGGGEEEE * @param index the index of the desired value in a multi valued string or undefined for the first value * @returns {*} a floating point number or undefined if not present or data not long enough */ dicomParser.DataSet.prototype.floatString = function(tag, index) { var element = this.elements[tag]; if(element && element.length > 0) { index = (index !== undefined) ? index : 0; var value = this.string(tag, index); if(value !== undefined) { return parseFloat(value); } } return undefined; }; /** * Parses a string to an integer for the specified index in a multi-valued element. If index is not specified, * the first value in a multi-valued VR will be parsed if present. * @param tag The DICOM tag in the format xGGGGEEEE * @param index the index of the desired value in a multi valued string or undefined for the first value * @returns {*} an integer or undefined if not present or data not long enough */ dicomParser.DataSet.prototype.intString = function(tag, index) { var element = this.elements[tag]; if(element && element.length > 0) { index = (index !== undefined) ? index : 0; var value = this.string(tag, index); if(value !== undefined) { return parseInt(value); } } return undefined; }; //dicomParser.DataSet = DataSet; return dicomParser; }(dicomParser)); /** * Internal helper functions for parsing DICOM elements */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } /** * Reads an encapsulated pixel data element and adds an array of fragments to the element * containing the offset and length of each fragment and any offsets from the basic offset * table * @param byteStream * @param element */ dicomParser.findEndOfEncapsulatedElement = function(byteStream, element, warnings) { if(byteStream === undefined) { throw "dicomParser.findEndOfEncapsulatedElement: missing required parameter 'byteStream'"; } if(element === undefined) { throw "dicomParser.findEndOfEncapsulatedElement: missing required parameter 'element'"; } element.encapsulatedPixelData = true; element.basicOffsetTable = []; element.fragments = []; var basicOffsetTableItemTag = dicomParser.readTag(byteStream); if(basicOffsetTableItemTag !== 'xfffee000') { throw "dicomParser.findEndOfEncapsulatedElement: basic offset table not found"; } var basicOffsetTableItemlength = byteStream.readUint32(); var numFragments = basicOffsetTableItemlength / 4; for(var i =0; i < numFragments; i++) { var offset = byteStream.readUint32(); element.basicOffsetTable.push(offset); } var baseOffset = byteStream.position; while(byteStream.position < byteStream.byteArray.length) { var tag = dicomParser.readTag(byteStream); var length = byteStream.readUint32(); if(tag === 'xfffee0dd') { byteStream.seek(length); element.length = byteStream.position - element.dataOffset; return; } else if(tag === 'xfffee000') { element.fragments.push({ offset: byteStream.position - baseOffset - 8, position : byteStream.position, length : length }); } else { if(warnings) { warnings.push('unexpected tag ' + tag + ' while searching for end of pixel data element with undefined length'); } if(length > byteStream.byteArray.length - byteStream.position) { // fix length length = byteStream.byteArray.length - byteStream.position; } element.fragments.push({ offset: byteStream.position - baseOffset - 8, position : byteStream.position, length : length }); byteStream.seek(length); element.length = byteStream.position - element.dataOffset; return; } byteStream.seek(length); } if(warnings) { warnings.push("pixel data element " + element.tag + " missing sequence delimiter tag xfffee0dd"); } }; return dicomParser; }(dicomParser)); /** * Internal helper functions for parsing DICOM elements */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } /** * reads from the byte stream until it finds the magic numbers for the item delimitation item * and then sets the length of the element * @param byteStream * @param element */ dicomParser.findItemDelimitationItemAndSetElementLength = function(byteStream, element) { if(byteStream === undefined) { throw "dicomParser.readDicomElementImplicit: missing required parameter 'byteStream'"; } var itemDelimitationItemLength = 8; // group, element, length var maxPosition = byteStream.byteArray.length - itemDelimitationItemLength; while(byteStream.position <= maxPosition) { var groupNumber = byteStream.readUint16(); if(groupNumber === 0xfffe) { var elementNumber = byteStream.readUint16(); if(elementNumber === 0xe00d) { // NOTE: It would be better to also check for the length to be 0 as part of the check above // but we will just log a warning for now var itemDelimiterLength = byteStream.readUint32(); // the length if(itemDelimiterLength !== 0) { byteStream.warnings('encountered non zero length following item delimiter at position' + byteStream.position - 4 + " while reading element of undefined length with tag ' + element.tag"); } element.length = byteStream.position - element.dataOffset; return; } } } // No item delimitation item - silently set the length to the end of the buffer and set the position past the end of the buffer element.length = byteStream.byteArray.length - element.dataOffset; byteStream.seek(byteStream.byteArray.length - byteStream.position); }; return dicomParser; }(dicomParser)); /** * Internal helper functions for parsing different types from a little-endian byte array */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } dicomParser.littleEndianByteArrayParser = { /** * * Parses an unsigned int 16 from a little-endian byte array * * @param byteArray the byte array to read from * @param position the position in the byte array to read from * @returns {*} the parsed unsigned int 16 * @throws error if buffer overread would occur * @access private */ readUint16: function (byteArray, position) { if (position < 0) { throw 'littleEndianByteArrayParser.readUint16: position cannot be less than 0'; } if (position + 2 > byteArray.length) { throw 'littleEndianByteArrayParser.readUint16: attempt to read past end of buffer'; } return byteArray[position] + (byteArray[position + 1] * 256); }, /** * * Parses a signed int 16 from a little-endian byte array * * @param byteArray the byte array to read from * @param position the position in the byte array to read from * @returns {*} the parsed signed int 16 * @throws error if buffer overread would occur * @access private */ readInt16: function (byteArray, position) { if (position < 0) { throw 'littleEndianByteArrayParser.readInt16: position cannot be less than 0'; } if (position + 2 > byteArray.length) { throw 'littleEndianByteArrayParser.readInt16: attempt to read past end of buffer'; } var int16 = byteArray[position] + (byteArray[position + 1] << 8); // fix sign if (int16 & 0x8000) { int16 = int16 - 0xFFFF - 1; } return int16; }, /** * Parses an unsigned int 32 from a little-endian byte array * * @param byteArray the byte array to read from * @param position the position in the byte array to read from * @returns {*} the parsed unsigned int 32 * @throws error if buffer overread would occur * @access private */ readUint32: function (byteArray, position) { if (position < 0) { throw 'littleEndianByteArrayParser.readUint32: position cannot be less than 0'; } if (position + 4 > byteArray.length) { throw 'littleEndianByteArrayParser.readUint32: attempt to read past end of buffer'; } var uint32 = (byteArray[position] + (byteArray[position + 1] * 256) + (byteArray[position + 2] * 256 * 256) + (byteArray[position + 3] * 256 * 256 * 256 )); return uint32; }, /** * Parses a signed int 32 from a little-endian byte array * * @param byteArray the byte array to read from * @param position the position in the byte array to read from * @returns {*} the parsed unsigned int 32 * @throws error if buffer overread would occur * @access private */ readInt32: function (byteArray, position) { if (position < 0) { throw 'littleEndianByteArrayParser.readInt32: position cannot be less than 0'; } if (position + 4 > byteArray.length) { throw 'littleEndianByteArrayParser.readInt32: attempt to read past end of buffer'; } var int32 = (byteArray[position] + (byteArray[position + 1] << 8) + (byteArray[position + 2] << 16) + (byteArray[position + 3] << 24)); return int32; }, /** * Parses 32-bit float from a little-endian byte array * * @param byteArray the byte array to read from * @param position the position in the byte array to read from * @returns {*} the parsed 32-bit float * @throws error if buffer overread would occur * @access private */ readFloat: function (byteArray, position) { if (position < 0) { throw 'littleEndianByteArrayParser.readFloat: position cannot be less than 0'; } if (position + 4 > byteArray.length) { throw 'littleEndianByteArrayParser.readFloat: attempt to read past end of buffer'; } // I am sure there is a better way than this but this should be safe var byteArrayForParsingFloat = new Uint8Array(4); byteArrayForParsingFloat[0] = byteArray[position]; byteArrayForParsingFloat[1] = byteArray[position + 1]; byteArrayForParsingFloat[2] = byteArray[position + 2]; byteArrayForParsingFloat[3] = byteArray[position + 3]; var floatArray = new Float32Array(byteArrayForParsingFloat.buffer); return floatArray[0]; }, /** * Parses 64-bit float from a little-endian byte array * * @param byteArray the byte array to read from * @param position the position in the byte array to read from * @returns {*} the parsed 64-bit float * @throws error if buffer overread would occur * @access private */ readDouble: function (byteArray, position) { if (position < 0) { throw 'littleEndianByteArrayParser.readDouble: position cannot be less than 0'; } if (position + 8 > byteArray.length) { throw 'littleEndianByteArrayParser.readDouble: attempt to read past end of buffer'; } // I am sure there is a better way than this but this should be safe var byteArrayForParsingFloat = new Uint8Array(8); byteArrayForParsingFloat[0] = byteArray[position]; byteArrayForParsingFloat[1] = byteArray[position + 1]; byteArrayForParsingFloat[2] = byteArray[position + 2]; byteArrayForParsingFloat[3] = byteArray[position + 3]; byteArrayForParsingFloat[4] = byteArray[position + 4]; byteArrayForParsingFloat[5] = byteArray[position + 5]; byteArrayForParsingFloat[6] = byteArray[position + 6]; byteArrayForParsingFloat[7] = byteArray[position + 7]; var floatArray = new Float64Array(byteArrayForParsingFloat.buffer); return floatArray[0]; } }; return dicomParser; }(dicomParser)); /** * Internal helper functions for parsing implicit and explicit DICOM data sets */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } /** * reads an explicit data set * @param byteStream the byte stream to read from * @param maxPosition the maximum position to read up to (optional - only needed when reading sequence items) */ dicomParser.parseDicomDataSetExplicit = function (dataSet, byteStream, maxPosition, options) { maxPosition = (maxPosition === undefined) ? byteStream.byteArray.length : maxPosition ; options = options || {}; if(byteStream === undefined) { throw "dicomParser.parseDicomDataSetExplicit: missing required parameter 'byteStream'"; } if(maxPosition < byteStream.position || maxPosition > byteStream.byteArray.length) { throw "dicomParser.parseDicomDataSetExplicit: invalid value for parameter 'maxPosition'"; } var elements = dataSet.elements; while(byteStream.position < maxPosition) { var element = dicomParser.readDicomElementExplicit(byteStream, dataSet.warnings, options.untilTag); elements[element.tag] = element; if(element.tag === options.untilTag) { return; } } if(byteStream.position > maxPosition) { throw "dicomParser:parseDicomDataSetExplicit: buffer overrun"; } }; /** * reads an implicit data set * @param byteStream the byte stream to read from * @param maxPosition the maximum position to read up to (optional - only needed when reading sequence items) */ dicomParser.parseDicomDataSetImplicit = function(dataSet, byteStream, maxPosition, options) { maxPosition = (maxPosition === undefined) ? dataSet.byteArray.length : maxPosition ; options = options || {}; if(byteStream === undefined) { throw "dicomParser.parseDicomDataSetImplicit: missing required parameter 'byteStream'"; } if(maxPosition < byteStream.position || maxPosition > byteStream.byteArray.length) { throw "dicomParser.parseDicomDataSetImplicit: invalid value for parameter 'maxPosition'"; } var elements = dataSet.elements; while(byteStream.position < maxPosition) { var element = dicomParser.readDicomElementImplicit(byteStream, options.untilTag); elements[element.tag] = element; if(element.tag === options.untilTag) { return; } } }; return dicomParser; }(dicomParser)); /** * Internal helper functions for for parsing DICOM elements */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } function getDataLengthSizeInBytesForVR(vr) { if( vr === 'OB' || vr === 'OW' || vr === 'SQ' || vr === 'OF' || vr === 'UT' || vr === 'UN') { return 4; } else { return 2; } } dicomParser.readDicomElementExplicit = function(byteStream, warnings, untilTag) { if(byteStream === undefined) { throw "dicomParser.readDicomElementExplicit: missing required parameter 'byteStream'"; } var element = { tag : dicomParser.readTag(byteStream), vr : byteStream.readFixedString(2) // length set below based on VR // dataOffset set below based on VR and size of length }; var dataLengthSizeBytes = getDataLengthSizeInBytesForVR(element.vr); if(dataLengthSizeBytes === 2) { element.length = byteStream.readUint16(); element.dataOffset = byteStream.position; } else { byteStream.seek(2); element.length = byteStream.readUint32(); element.dataOffset = byteStream.position; } if(element.length === 4294967295) { element.hadUndefinedLength = true; } if(element.tag === untilTag) { return element; } // if VR is SQ, parse the sequence items if(element.vr === 'SQ') { dicomParser.readSequenceItemsExplicit(byteStream, element, warnings); return element; } if(element.length === 4294967295) { if(element.tag === 'x7fe00010') { dicomParser.findEndOfEncapsulatedElement(byteStream, element, warnings); return element; } else { dicomParser.findItemDelimitationItemAndSetElementLength(byteStream, element); return element; } } byteStream.seek(element.length); return element; }; return dicomParser; }(dicomParser)); /** * Internal helper functions for for parsing DICOM elements */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } dicomParser.readDicomElementImplicit = function(byteStream, untilTag) { if(byteStream === undefined) { throw "dicomParser.readDicomElementImplicit: missing required parameter 'byteStream'"; } var element = { tag : dicomParser.readTag(byteStream), length: byteStream.readUint32(), dataOffset : byteStream.position }; if(element.length === 4294967295) { element.hadUndefinedLength = true; } if(element.tag === untilTag) { return element; } // peek ahead at the next tag to see if it looks like a sequence. This is not 100% // safe because a non sequence item could have data that has these bytes, but this // is how to do it without a data dictionary. if ((byteStream.position + 4) <= byteStream.byteArray.length) { var nextTag = dicomParser.readTag(byteStream); byteStream.seek(-4); // zero length sequence if (element.hadUndefinedLength && nextTag === 'xfffee0dd') { element.length = 0; byteStream.seek(8); return element; } if (nextTag === 'xfffee000') { // parse the sequence dicomParser.readSequenceItemsImplicit(byteStream, element); //element.length = byteStream.byteArray.length - element.dataOffset; return element; } } // if element is not a sequence and has undefined length, we have to // scan the data for a magic number to figure out when it ends. if(element.length === 4294967295) { dicomParser.findItemDelimitationItemAndSetElementLength(byteStream, element); return element; } // non sequence element with known length, skip over the data part byteStream.seek(element.length); return element; }; return dicomParser; }(dicomParser)); /** * Internal helper functions for parsing DICOM elements */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } function readDicomDataSetExplicitUndefinedLength(byteStream, warnings) { var elements = {}; while(byteStream.position < byteStream.byteArray.length) { var element = dicomParser.readDicomElementExplicit(byteStream, warnings); elements[element.tag] = element; // we hit an item delimiter tag, return the current offset to mark // the end of this sequence item if(element.tag === 'xfffee00d') { return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements); } } // eof encountered - log a warning and return what we have for the element byteStream.warnings.push('eof encountered before finding sequence delimitation item while reading sequence item of undefined length'); return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements); } function readSequenceItemExplicit(byteStream, warnings) { var item = dicomParser.readSequenceItem(byteStream); if(item.length === 4294967295) { item.hadUndefinedLength = true; item.dataSet = readDicomDataSetExplicitUndefinedLength(byteStream, warnings); item.length = byteStream.position - item.dataOffset; } else { item.dataSet = new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, {}); dicomParser.parseDicomDataSetExplicit(item.dataSet, byteStream, byteStream.position + item.length); } return item; } function readSQElementUndefinedLengthExplicit(byteStream, element, warnings) { while(byteStream.position < byteStream.byteArray.length) { // end reading this sequence if the next tag is the sequence delimitation item var nextTag = dicomParser.readTag(byteStream); byteStream.seek(-4); if (nextTag === 'xfffee0dd') { // set the correct length element.length = byteStream.position - element.dataOffset; byteStream.seek(8); return element; } var item = readSequenceItemExplicit(byteStream, warnings); element.items.push(item); } element.length = byteStream.position - element.dataOffset; } function readSQElementKnownLengthExplicit(byteStream, element, warnings) { var maxPosition = element.dataOffset + element.length; while(byteStream.position < maxPosition) { var item = readSequenceItemExplicit(byteStream, warnings); element.items.push(item); } } dicomParser.readSequenceItemsExplicit = function(byteStream, element, warnings) { if(byteStream === undefined) { throw "dicomParser.readSequenceItemsExplicit: missing required parameter 'byteStream'"; } if(element === undefined) { throw "dicomParser.readSequenceItemsExplicit: missing required parameter 'element'"; } element.items = []; if(element.length === 4294967295) { readSQElementUndefinedLengthExplicit(byteStream, element); } else { readSQElementKnownLengthExplicit(byteStream, element, warnings); } }; return dicomParser; }(dicomParser)); /** * Internal helper functions for parsing DICOM elements */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } function readDicomDataSetImplicitUndefinedLength(byteStream) { var elements = {}; while(byteStream.position < byteStream.byteArray.length) { var element = dicomParser.readDicomElementImplicit(byteStream); elements[element.tag] = element; // we hit an item delimiter tag, return the current offset to mark // the end of this sequence item if(element.tag === 'xfffee00d') { return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements); } } // eof encountered - log a warning and return what we have for the element byteStream.warnings.push('eof encountered before finding sequence item delimiter in sequence item of undefined length'); return new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, elements); } function readSequenceItemImplicit(byteStream) { var item = dicomParser.readSequenceItem(byteStream); if(item.length === 4294967295) { item.hadUndefinedLength = true; item.dataSet = readDicomDataSetImplicitUndefinedLength(byteStream); item.length = byteStream.position - item.dataOffset; } else { item.dataSet = new dicomParser.DataSet(byteStream.byteArrayParser, byteStream.byteArray, {}); dicomParser.parseDicomDataSetImplicit(item.dataSet, byteStream, byteStream.position + item.length); } return item; } function readSQElementUndefinedLengthImplicit(byteStream, element) { while(byteStream.position < byteStream.byteArray.length) { // end reading this sequence if the next tag is the sequence delimitation item var nextTag = dicomParser.readTag(byteStream); byteStream.seek(-4); if (nextTag === 'xfffee0dd') { // set the correct length element.length = byteStream.position - element.dataOffset; byteStream.seek(8); return element; } var item = readSequenceItemImplicit(byteStream); element.items.push(item); } element.length = byteStream.byteArray.length - element.dataOffset; } function readSQElementKnownLengthImplicit(byteStream, element) { var maxPosition = element.dataOffset + element.length; while(byteStream.position < maxPosition) { var item = readSequenceItemImplicit(byteStream); element.items.push(item); } } /** * Reads sequence items for an element in an implicit little endian byte stream * @param byteStream the implicit little endian byte stream * @param element the element to read the sequence items for */ dicomParser.readSequenceItemsImplicit = function(byteStream, element) { if(byteStream === undefined) { throw "dicomParser.readSequenceItemsImplicit: missing required parameter 'byteStream'"; } if(element === undefined) { throw "dicomParser.readSequenceItemsImplicit: missing required parameter 'element'"; } element.items = []; if(element.length === 4294967295) { readSQElementUndefinedLengthImplicit(byteStream, element); } else { readSQElementKnownLengthImplicit(byteStream, element); } }; return dicomParser; }(dicomParser)); /** * Internal helper functions for parsing DICOM elements */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } /** * Reads the tag and length of a sequence item and returns them as an object with the following properties * tag : string for the tag of this element in the format xggggeeee * length: the number of bytes in this item or 4294967295 if undefined * dataOffset: the offset into the byteStream of the data for this item * @param byteStream the byte * @returns {{tag: string, length: integer, dataOffset: integer}} */ dicomParser.readSequenceItem = function(byteStream) { if(byteStream === undefined) { throw "dicomParser.readSequenceItem: missing required parameter 'byteStream'"; } var element = { tag : dicomParser.readTag(byteStream), length : byteStream.readUint32(), dataOffset : byteStream.position }; return element; }; return dicomParser; }(dicomParser)); /** * Internal helper functions for parsing DICOM elements */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } /** * Reads a tag (group number and element number) from a byteStream * @param byteStream the byte stream to read from * @returns {string} the tag in format xggggeeee where gggg is the lowercase hex value of the group number * and eeee is the lower case hex value of the element number */ dicomParser.readTag = function(byteStream) { if(byteStream === undefined) { throw "dicomParser.readTag: missing required parameter 'byteStream'"; } var groupNumber = byteStream.readUint16() * 256 * 256; var elementNumber = byteStream.readUint16(); var tag = "x" + ('00000000' + (groupNumber + elementNumber).toString(16)).substr(-8); return tag; }; return dicomParser; }(dicomParser)); /** * Version */ var dicomParser = (function (dicomParser) { "use strict"; if(dicomParser === undefined) { dicomParser = {}; } dicomParser.version = "1.1.5"; return dicomParser; }(dicomParser)); return dicomParser; }));