src/dicom/dataReader.js

// namespaces
var dwv = dwv || {};
dwv.dicom = dwv.dicom || {};

/**
 * Is the Native endianness Little Endian.
 *
 * @type {boolean}
 */
dwv.dicom.isNativeLittleEndian = function () {
  return new Int8Array(new Int16Array([1]).buffer)[0] > 0;
};

/**
 * Flip an array's endianness.
 * Inspired from [DataStream.js]{@link https://github.com/kig/DataStream.js}.
 *
 * @param {object} array The array to flip (modified).
 */
dwv.dicom.flipArrayEndianness = function (array) {
  var blen = array.byteLength;
  var u8 = new Uint8Array(array.buffer, array.byteOffset, blen);
  var bpe = array.BYTES_PER_ELEMENT;
  var tmp;
  for (var i = 0; i < blen; i += bpe) {
    for (var j = i + bpe - 1, k = i; j > k; j--, k++) {
      tmp = u8[k];
      u8[k] = u8[j];
      u8[j] = tmp;
    }
  }
};

/**
 * Data reader.
 *
 * @class
 * @param {Array} buffer The input array buffer.
 * @param {boolean} isLittleEndian Flag to tell if the data is little
 *   or big endian.
 */
dwv.dicom.DataReader = function (buffer, isLittleEndian) {
  // Set endian flag if not defined.
  if (typeof isLittleEndian === 'undefined') {
    isLittleEndian = true;
  }

  // Default text decoder
  var defaultTextDecoder = {};
  defaultTextDecoder.decode = function (buffer) {
    var result = '';
    for (var i = 0, leni = buffer.length; i < leni; ++i) {
      result += String.fromCharCode(buffer[i]);
    }
    return result;
  };

  // Text decoder
  var textDecoder = defaultTextDecoder;
  if (typeof window.TextDecoder !== 'undefined') {
    textDecoder = new TextDecoder('iso-8859-1');
  }

  /**
   * Set the utfLabel used to construct the TextDecoder.
   *
   * @param {string} label The encoding label.
   */
  this.setUtfLabel = function (label) {
    if (typeof window.TextDecoder !== 'undefined') {
      textDecoder = new TextDecoder(label);
    }
  };

  /**
   * Is the Native endianness Little Endian.
   *
   * @private
   * @type {boolean}
   */
  var isNativeLittleEndian = dwv.dicom.isNativeLittleEndian();

  /**
   * Flag to know if the TypedArray data needs flipping.
   *
   * @private
   * @type {boolean}
   */
  var needFlip = (isLittleEndian !== isNativeLittleEndian);

  /**
   * The main data view.
   *
   * @private
   * @type {DataView}
   */
  var view = new DataView(buffer);

  /**
   * Read Uint16 (2 bytes) data.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @returns {number} The read data.
   */
  this.readUint16 = function (byteOffset) {
    return view.getUint16(byteOffset, isLittleEndian);
  };

  /**
   * Read Uint32 (4 bytes) data.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @returns {number} The read data.
   */
  this.readUint32 = function (byteOffset) {
    return view.getUint32(byteOffset, isLittleEndian);
  };

  /**
   * Read Int32 (4 bytes) data.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @returns {number} The read data.
   */
  this.readInt32 = function (byteOffset) {
    return view.getInt32(byteOffset, isLittleEndian);
  };

  /**
   * Read Float32 (4 bytes) data.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @returns {number} The read data.
   */
  this.readFloat32 = function (byteOffset) {
    return view.getFloat32(byteOffset, isLittleEndian);
  };

  /**
   * Read Float64 (8 bytes) data.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @returns {number} The read data.
   */
  this.readFloat64 = function (byteOffset) {
    return view.getFloat64(byteOffset, isLittleEndian);
  };

  /**
   * Read binary (0/1) array.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @param {number} size The size of the array.
   * @returns {Array} The read data.
   */
  this.readBinaryArray = function (byteOffset, size) {
    // input
    var bitArray = new Uint8Array(buffer, byteOffset, size);
    // result
    var byteArrayLength = 8 * bitArray.length;
    var data = new Uint8Array(byteArrayLength);
    var bitNumber = 0;
    var bitIndex = 0;
    for (var i = 0; i < byteArrayLength; ++i) {
      bitNumber = i % 8;
      bitIndex = Math.floor(i / 8);
      // see https://stackoverflow.com/questions/4854207/get-a-specific-bit-from-byte/4854257
      data[i] = 255 * ((bitArray[bitIndex] & (1 << bitNumber)) !== 0);
    }
    return data;
  };

  /**
   * Read Uint8 array.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @param {number} size The size of the array.
   * @returns {Array} The read data.
   */
  this.readUint8Array = function (byteOffset, size) {
    return new Uint8Array(buffer, byteOffset, size);
  };

  /**
   * Read Int8 array.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @param {number} size The size of the array.
   * @returns {Array} The read data.
   */
  this.readInt8Array = function (byteOffset, size) {
    return new Int8Array(buffer, byteOffset, size);
  };

  /**
   * Read Uint16 array.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @param {number} size The size of the array.
   * @returns {Array} The read data.
   */
  this.readUint16Array = function (byteOffset, size) {
    var bpe = Uint16Array.BYTES_PER_ELEMENT;
    var arraySize = size / bpe;
    var data = null;
    // byteOffset should be a multiple of Uint16Array.BYTES_PER_ELEMENT (=2)
    if (byteOffset % bpe === 0) {
      data = new Uint16Array(buffer, byteOffset, arraySize);
      if (needFlip) {
        dwv.dicom.flipArrayEndianness(data);
      }
    } else {
      data = new Uint16Array(arraySize);
      for (var i = 0; i < arraySize; ++i) {
        data[i] = this.readUint16(byteOffset + bpe * i);
      }
    }
    return data;
  };

  /**
   * Read Int16 array.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @param {number} size The size of the array.
   * @returns {Array} The read data.
   */
  this.readInt16Array = function (byteOffset, size) {
    var bpe = Int16Array.BYTES_PER_ELEMENT;
    var arraySize = size / bpe;
    var data = null;
    // byteOffset should be a multiple of Int16Array.BYTES_PER_ELEMENT (=2)
    if (byteOffset % bpe === 0) {
      data = new Int16Array(buffer, byteOffset, arraySize);
      if (needFlip) {
        dwv.dicom.flipArrayEndianness(data);
      }
    } else {
      data = new Int16Array(arraySize);
      for (var i = 0; i < arraySize; ++i) {
        data[i] = this.readInt16(byteOffset + bpe * i);
      }
    }
    return data;
  };

  /**
   * Read Uint32 array.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @param {number} size The size of the array.
   * @returns {Array} The read data.
   */
  this.readUint32Array = function (byteOffset, size) {
    var bpe = Uint32Array.BYTES_PER_ELEMENT;
    var arraySize = size / bpe;
    var data = null;
    // byteOffset should be a multiple of Uint32Array.BYTES_PER_ELEMENT (=4)
    if (byteOffset % bpe === 0) {
      data = new Uint32Array(buffer, byteOffset, arraySize);
      if (needFlip) {
        dwv.dicom.flipArrayEndianness(data);
      }
    } else {
      data = new Uint32Array(arraySize);
      for (var i = 0; i < arraySize; ++i) {
        data[i] = this.readUint32(byteOffset + bpe * i);
      }
    }
    return data;
  };

  /**
   * Read Int32 array.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @param {number} size The size of the array.
   * @returns {Array} The read data.
   */
  this.readInt32Array = function (byteOffset, size) {
    var bpe = Int32Array.BYTES_PER_ELEMENT;
    var arraySize = size / bpe;
    var data = null;
    // byteOffset should be a multiple of Int32Array.BYTES_PER_ELEMENT (=4)
    if (byteOffset % bpe === 0) {
      data = new Int32Array(buffer, byteOffset, arraySize);
      if (needFlip) {
        dwv.dicom.flipArrayEndianness(data);
      }
    } else {
      data = new Int32Array(arraySize);
      for (var i = 0; i < arraySize; ++i) {
        data[i] = this.readInt32(byteOffset + bpe * i);
      }
    }
    return data;
  };

  /**
   * Read Float32 array.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @param {number} size The size of the array.
   * @returns {Array} The read data.
   */
  this.readFloat32Array = function (byteOffset, size) {
    var bpe = Float32Array.BYTES_PER_ELEMENT;
    var arraySize = size / bpe;
    var data = null;
    // byteOffset should be a multiple of Float32Array.BYTES_PER_ELEMENT (=4)
    if (byteOffset % bpe === 0) {
      data = new Float32Array(buffer, byteOffset, arraySize);
      if (needFlip) {
        dwv.dicom.flipArrayEndianness(data);
      }
    } else {
      data = new Float32Array(arraySize);
      for (var i = 0; i < arraySize; ++i) {
        data[i] = this.readFloat32(byteOffset + bpe * i);
      }
    }
    return data;
  };

  /**
   * Read Float64 array.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @param {number} size The size of the array.
   * @returns {Array} The read data.
   */
  this.readFloat64Array = function (byteOffset, size) {
    var bpe = Float64Array.BYTES_PER_ELEMENT;
    var arraySize = size / bpe;
    var data = null;
    // byteOffset should be a multiple of Float64Array.BYTES_PER_ELEMENT (=8)
    if (byteOffset % bpe === 0) {
      data = new Float64Array(buffer, byteOffset, arraySize);
      if (needFlip) {
        dwv.dicom.flipArrayEndianness(data);
      }
    } else {
      data = new Float64Array(arraySize);
      for (var i = 0; i < arraySize; ++i) {
        data[i] = this.readFloat64(byteOffset + bpe * i);
      }
    }
    return data;
  };

  /**
   * Read data as a string.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @param {number} nChars The number of characters to read.
   * @returns {string} The read data.
   */
  this.readString = function (byteOffset, nChars) {
    var data = this.readUint8Array(byteOffset, nChars);
    return defaultTextDecoder.decode(data);
  };

  /**
   * Read data as a 'special' string, decoding it if the
   *   TextDecoder is available.
   *
   * @param {number} byteOffset The offset to start reading from.
   * @param {number} nChars The number of characters to read.
   * @returns {string} The read data.
   */
  this.readSpecialString = function (byteOffset, nChars) {
    var data = this.readUint8Array(byteOffset, nChars);
    return textDecoder.decode(data);
  };

}; // class DataReader

/**
 * Read data as an hexadecimal string.
 *
 * @param {number} byteOffset The offset to start reading from.
 * @returns {Array} The read data.
 */
dwv.dicom.DataReader.prototype.readHex = function (byteOffset) {
  // read and convert to hex string
  var str = this.readUint16(byteOffset).toString(16);
  // return padded
  return '0x0000'.substr(0, 6 - str.length) + str.toUpperCase();
};