Last active
May 10, 2016 23:11
-
-
Save cancan101/b888ed5d1163ec954966432c5aa505ae to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! 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