/*! dicom-parser - v1.5.0 - 2016-05-08 | (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'"; | |
} | |
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(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, position) { | |
if(transferSyntax === '1.2.840.10008.1.2.1.99') | |
{ | |
// if an infalter callback is registered, use it | |
if (options && options.inflater) { | |
var fullByteArrayCallback = options.inflater(byteArray, position); | |
return new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, fullByteArrayCallback, 0); | |
} | |
// if running on node, use the zlib library to inflate | |
// http://stackoverflow.com/questions/4224606/how-to-check-whether-a-script-is-running-under-node-js | |
else if (typeof module !== 'undefined' && this.module !== module) { | |
// inflate it | |
var zlib = require('zlib'); | |
var deflatedBuffer = dicomParser.sharedCopy(byteArray, position, byteArray.length - position); | |
var inflatedBuffer = zlib.inflateRawSync(deflatedBuffer); | |
// create a single byte array with the full header bytes and the inflated bytes | |
var fullByteArrayBuffer = dicomParser.alloc(byteArray, inflatedBuffer.length + position); | |
byteArray.copy(fullByteArrayBuffer, 0, 0, position); | |
inflatedBuffer.copy(fullByteArrayBuffer, position); | |
return new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, fullByteArrayBuffer, 0); | |
} | |
// if pako is defined - use it. This is the web browser path | |
// https://github.com/nodeca/pako | |
else if(typeof pako !== "undefined") { | |
// inflate it | |
var deflated = byteArray.slice(position); | |
var inflated = pako.inflateRaw(deflated); | |
// create a single byte array with the full header bytes and the inflated bytes | |
var fullByteArray = dicomParser.alloc(byteArray, inflated.length + position); | |
fullByteArray.set(byteArray.slice(0, position), 0); | |
fullByteArray.set(inflated, position); | |
return new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, fullByteArray, 0); | |
} | |
// throw exception since no inflater is available | |
else { | |
throw 'dicomParser.parseDicom: no inflater available to handle deflate transfer syntax'; | |
} | |
} | |
if(transferSyntax === '1.2.840.10008.1.2.2') // explicit big endian | |
{ | |
return new dicomParser.ByteStream(dicomParser.bigEndianByteArrayParser, byteArray, 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, 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, metaHeaderDataSet.position); | |
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 = dicomParser.readPart10Header(byteArray, options); | |
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, i).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 dicomParser.sharedCopy(byteStream.byteArray, fragments[0].dataOffset, fragments[0].length); | |
} | |
// more than one fragment, combine all of the fragments into one buffer | |
var pixelData = dicomParser.alloc(byteStream.byteArray, 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) { | |
// read the fragment | |
var item = { | |
tag : dicomParser.readTag(byteStream), | |
length : byteStream.readUint32(), | |
dataOffset : byteStream.position | |
}; | |
// NOTE: we only encounter this for the sequence delimiter item when extracting the last frame | |
if(item.tag === 'xfffee0dd') { | |
break; | |
} | |
fragments.push(item); | |
byteStream.seek(item.length); | |
bufferSize += item.length; | |
} | |
// Convert the fragments into a single pixelData buffer | |
var pixelData = getPixelDataFromFragments(byteStream, fragments, bufferSize); | |
return pixelData; | |
} | |
function getFragmentOffset(byteStream) { | |
// read the fragment | |
var item = { | |
tag : dicomParser.readTag(byteStream), | |
length : byteStream.readUint32(), | |
dataOffset : byteStream.position | |
}; | |
// NOTE: we only encounter this for the sequence delimiter item when extracting the last frame | |
if(item.tag === 'xfffee0dd') { | |
return; | |
} | |
byteStream.seek(item.length); | |
} | |
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]; | |
var firstFragment = byteStream.position; | |
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; | |
} else { | |
endOfFrameOffset += firstFragment; | |
} | |
// 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"; | |
} | |
if(pixelDataElement.basicOffsetTable.length === 0 && dataSet.intString('x00280008') && dataSet.intString('x00280008') > 1){ | |
var byteStream2 = new dicomParser.ByteStream(dataSet.byteArrayParser, dataSet.byteArray, pixelDataElement.dataOffset); | |
var basicOffsetTable2 = dicomParser.readSequenceItem(byteStream2); | |
byteStream2.seek(basicOffsetTable2.length); | |
var endOfFrame = byteStream2.position + pixelDataElement.length; | |
var firstFragment = byteStream2.position; | |
pixelDataElement.basicOffsetTable[0] = 0; | |
for(var k=0; k< dataSet.intString('x00280008') - 1; k++){ | |
getFragmentOffset(byteStream2); | |
pixelDataElement.basicOffsetTable[k + 1] = byteStream2.position - firstFragment; | |
} | |
} | |
// 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 function to allocate new byteArray buffers | |
*/ | |
var dicomParser = (function (dicomParser) | |
{ | |
"use strict"; | |
if(dicomParser === undefined) | |
{ | |
dicomParser = {}; | |
} | |
/** | |
* Creates a new byteArray of the same type (Uint8Array or Buffer) of the specified length. | |
* @param byteArray the underlying byteArray (either Uint8Array or Buffer) | |
* @param length number of bytes of the Byte Array | |
* @returns {object} Uint8Array or Buffer depending on the type of byteArray | |
*/ | |
dicomParser.alloc = function(byteArray, length) { | |
if (typeof Buffer !== 'undefined' && byteArray instanceof Buffer) { | |
return Buffer.alloc(length); | |
} | |
else if(byteArray instanceof Uint8Array) { | |
return new Uint8Array(length); | |
} else { | |
throw 'dicomParser.alloc: unknown type for byteArray'; | |
} | |
}; | |
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 'dicomParser.readFixedString - length cannot be less than 0'; | |
} | |
if(position + length > byteArray.length) { | |
throw 'dicomParser.readFixedString: attempt to read past end of buffer'; | |
} | |
var result = ""; | |
var byte; | |
for(var i=0; i < length; i++) | |
{ | |
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 && | |
(byteArray instanceof Buffer) === false ) { | |
throw 'dicomParser.ByteStream: parameter byteArray is not of type Uint8Array or Buffer'; | |
} | |
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 "dicomParser.ByteStream.prototype.seek: 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 'dicomParser.ByteStream.prototype.readByteStream: readByteStream - buffer overread'; | |
} | |
var byteArrayView = dicomParser.sharedCopy(this.byteArray, 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, options.vrCallback); | |
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 = {}; | |
} | |
function isSequence(element, byteStream, vrCallback) { | |
// if a data dictionary callback was provided, use that to verify that the element is a sequence. | |
if (typeof vrCallback !== 'undefined') { | |
return (vrCallback(element.tag) === 'SQ'); | |
} | |
if ((byteStream.position + 4) <= byteStream.byteArray.length) { | |
var nextTag = dicomParser.readTag(byteStream); | |
byteStream.seek(-4); | |
// Item start tag (fffe,e000) or sequence delimiter (i.e. end of sequence) tag (0fffe,e0dd) | |
// These are the tags that could potentially be found directly after a sequence start tag (the delimiter | |
// is found in the case of an empty 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. | |
return (nextTag === 'xfffee000') || (nextTag === 'xfffee0dd'); | |
} | |
byteStream.warnings.push('eof encountered before finding sequence item tag or sequence delimiter tag in peeking to determine VR'); | |
return false; | |
} | |
dicomParser.readDicomElementImplicit = function(byteStream, untilTag, vrCallback) | |
{ | |
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; | |
} | |
if (isSequence(element, byteStream, vrCallback)) { | |
// parse the sequence | |
dicomParser.readSequenceItemsImplicit(byteStream, element); | |
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.hadUndefinedLength) | |
{ | |
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)); | |
/** | |
* 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.readPart10Header = function(byteArray, options) { | |
if(byteArray === undefined) | |
{ | |
throw "dicomParser.readPart10Header: 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.readPart10Header: DICM prefix not found at location 132 - this is not a valid DICOM P10 file."; | |
} | |
} | |
// main function here | |
function readTheHeader() { | |
// 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; | |
metaHeaderDataSet.position = littleEndianByteStream.position; | |
return metaHeaderDataSet; | |
} | |
// This is where we actually start parsing | |
return readTheHeader(); | |
}; | |
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 | |
warnings.push('eof encountered before finding item delimiter tag 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 + 4) <= 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); | |
} | |
warnings.push('eof encountered before finding sequence delimitation tag while reading sequence of undefined length'); | |
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, warnings); | |
} | |
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, vrCallback) | |
{ | |
var elements = {}; | |
while(byteStream.position < byteStream.byteArray.length) | |
{ | |
var element = dicomParser.readDicomElementImplicit(byteStream, undefined, vrCallback); | |
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, vrCallback) | |
{ | |
var item = dicomParser.readSequenceItem(byteStream); | |
if(item.length === 4294967295) | |
{ | |
item.hadUndefinedLength = true; | |
item.dataSet = readDicomDataSetImplicitUndefinedLength(byteStream, vrCallback); | |
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, {vrCallback: vrCallback}); | |
} | |
return item; | |
} | |
function readSQElementUndefinedLengthImplicit(byteStream, element, vrCallback) | |
{ | |
while((byteStream.position + 4) <= 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, vrCallback); | |
element.items.push(item); | |
} | |
byteStream.warnings.push('eof encountered before finding sequence delimiter in sequence of undefined length'); | |
element.length = byteStream.byteArray.length - element.dataOffset; | |
} | |
function readSQElementKnownLengthImplicit(byteStream, element, vrCallback) | |
{ | |
var maxPosition = element.dataOffset + element.length; | |
while(byteStream.position < maxPosition) | |
{ | |
var item = readSequenceItemImplicit(byteStream, vrCallback); | |
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 | |
* @param vrCallback an optional method that returns a VR string given a tag | |
*/ | |
dicomParser.readSequenceItemsImplicit = function(byteStream, element, vrCallback) | |
{ | |
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, vrCallback); | |
} | |
else | |
{ | |
readSQElementKnownLengthImplicit(byteStream, element, vrCallback); | |
} | |
}; | |
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 | |
}; | |
if (element.tag !== 'xfffee000') { | |
var startPosition = byteStream.position; | |
throw "dicomParser.readSequenceItem: item tag (FFFE,E000) not found at offset " + startPosition; | |
} | |
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)); | |
/** | |
* | |
* Internal helper function to create a shared copy of a byteArray | |
* | |
*/ | |
var dicomParser = (function (dicomParser) | |
{ | |
"use strict"; | |
if(dicomParser === undefined) | |
{ | |
dicomParser = {}; | |
} | |
/** | |
* Creates a view of the underlying byteArray. The view is of the same type as the byteArray (e.g. | |
* Uint8Array or Buffer) and shares the same underlying memory (changing one changes the other) | |
* @param byteArray the underlying byteArray (either Uint8Array or Buffer) | |
* @param byteOffset offset into the underlying byteArray to create the view of | |
* @param length number of bytes in the view | |
* @returns {object} Uint8Array or Buffer depending on the type of byteArray | |
*/ | |
dicomParser.sharedCopy = function(byteArray, byteOffset, length) { | |
if (typeof Buffer !== 'undefined' && byteArray instanceof Buffer) { | |
return byteArray.slice(byteOffset, byteOffset + length); | |
} | |
else if(byteArray instanceof Uint8Array) { | |
return new Uint8Array(byteArray.buffer, byteArray.byteOffset + byteOffset, length); | |
} else { | |
throw 'dicomParser.from: unknown type for byteArray'; | |
} | |
}; | |
return dicomParser; | |
}(dicomParser)); | |
/** | |
* Version | |
*/ | |
var dicomParser = (function (dicomParser) | |
{ | |
"use strict"; | |
if(dicomParser === undefined) | |
{ | |
dicomParser = {}; | |
} | |
dicomParser.version = "1.5.0"; | |
return dicomParser; | |
}(dicomParser)); | |
return dicomParser; | |
})); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment