/*! dicom-parser - v1.2.1 - 2016-02-07 | (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')
            {
              // https://github.com/nodeca/pako
              if(typeof(pako) === "undefined") {
                throw 'dicomParser.parseDicom: deflated transfer syntax encountered but pako not loaded';
              }
              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 = new Uint8Array(inflated.length + position);
              fullByteArray.set(byteArray.slice(0, position), 0);
              fullByteArray.set(inflated, position);
              return new dicomParser.ByteStream(dicomParser.littleEndianByteArrayParser, fullByteArray, 0);
            }
            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 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];
        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";
        }

        // 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 &&
          (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 "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));
/**
 * 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";
      }
    }

    // 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
        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.2.1";

  return dicomParser;
}(dicomParser));
    return dicomParser;
}));
