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