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