src/image/dicomBufferToView.js

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

/**
 * Create a dwv.image.View from a DICOM buffer.
 *
 * @class
 */
dwv.image.DicomBufferToView = function () {
  // closure to self
  var self = this;

  /**
   * Converter options.
   *
   * @private
   * @type {object}
   */
  var options;

  /**
   * Set the converter options.
   *
   * @param {object} opt The input options.
   */
  this.setOptions = function (opt) {
    options = opt;
  };

  /**
   * Pixel buffer decoder.
   * Define only once to allow optional asynchronous mode.
   *
   * @private
   * @type {object}
   */
  var pixelDecoder = null;

  // local tmp storage
  var dicomParserStore = [];
  var finalBufferStore = [];
  var decompressedSizes = [];

  /**
   * Generate the image object.
   *
   * @param {number} index The data index.
   * @param {string} origin The data origin.
   */
  function generateImage(index, origin) {
    // create the image
    try {
      var imageFactory = new dwv.ImageFactory();
      var image = imageFactory.create(
        dicomParserStore[index].getDicomElements(),
        finalBufferStore[index],
        options.numberOfFiles);
      // call onloaditem
      self.onloaditem({
        data: {
          image: image,
          info: dicomParserStore[index].getRawDicomElements()
        },
        source: origin
      });
    } catch (error) {
      self.onerror({
        error: error,
        source: origin
      });
      self.onloadend({
        source: origin
      });
    }
  }

  /**
   * Handle a decoded item event.
   *
   * @param {object} event The decoded item event.
   */
  function onDecodedItem(event) {
    // send progress
    self.onprogress({
      lengthComputable: true,
      loaded: event.itemNumber + 1,
      total: event.numberOfItems,
      index: event.dataIndex,
      source: origin
    });

    var dataIndex = event.dataIndex;

    // store decoded data
    var decodedData = event.data[0];
    if (event.numberOfItems !== 1) {
      // allocate buffer if not done yet
      if (typeof decompressedSizes[dataIndex] === 'undefined') {
        decompressedSizes[dataIndex] = decodedData.length;
        var fullSize = event.numberOfItems * decompressedSizes[dataIndex];
        try {
          finalBufferStore[dataIndex] = new decodedData.constructor(fullSize);
        } catch (error) {
          if (error instanceof RangeError) {
            var powerOf2 = Math.floor(Math.log(fullSize) / Math.log(2));
            dwv.logger.error('Cannot allocate ' +
              decodedData.constructor.name +
              ' of size: ' +
              fullSize + ' (>2^' + powerOf2 + ') for decompressed data.');
          }
          // abort
          pixelDecoder.abort();
          // send events
          self.onerror({
            error: error,
            source: origin
          });
          self.onloadend({
            source: origin
          });
          // exit
          return;
        }
      }
      // hoping for all items to have the same size...
      if (decodedData.length !== decompressedSizes[dataIndex]) {
        dwv.logger.warn('Unsupported varying decompressed data size: ' +
          decodedData.length + ' != ' + decompressedSizes[dataIndex]);
      }
      // set buffer item data
      finalBufferStore[dataIndex].set(
        decodedData, decompressedSizes[dataIndex] * event.itemNumber);
    } else {
      finalBufferStore[dataIndex] = decodedData;
    }

    // create image for the first item
    if (event.itemNumber === 0) {
      generateImage(dataIndex, origin);
    }
  }

  /**
   * Get data from an input buffer using a DICOM parser.
   *
   * @param {Array} buffer The input data buffer.
   * @param {string} origin The data origin.
   * @param {number} dataIndex The data index.
   */
  this.convert = function (buffer, origin, dataIndex) {
    self.onloadstart({
      source: origin,
      dataIndex: dataIndex
    });

    // DICOM parser
    var dicomParser = new dwv.dicom.DicomParser();
    var imageFactory = new dwv.ImageFactory();

    if (typeof options.defaultCharacterSet !== 'undefined') {
      dicomParser.setDefaultCharacterSet(options.defaultCharacterSet);
    }
    // parse the buffer
    try {
      dicomParser.parse(buffer);
      // check elements are good for image
      imageFactory.checkElements(dicomParser.getDicomElements());
    } catch (error) {
      self.onerror({
        error: error,
        source: origin
      });
      self.onloadend({
        source: origin
      });
      return;
    }

    var pixelBuffer = dicomParser.getRawDicomElements().x7FE00010.value;
    // help GC: discard pixel buffer from elements
    dicomParser.getRawDicomElements().x7FE00010.value = [];
    var syntax = dwv.dicom.cleanString(
      dicomParser.getRawDicomElements().x00020010.value[0]);
    var algoName = dwv.dicom.getSyntaxDecompressionName(syntax);
    var needDecompression = (algoName !== null);

    // store
    dicomParserStore[dataIndex] = dicomParser;
    finalBufferStore[dataIndex] = pixelBuffer[0];

    if (needDecompression) {
      // gather pixel buffer meta data
      var bitsAllocated = dicomParser.getRawDicomElements().x00280100.value[0];
      var pixelRepresentation =
        dicomParser.getRawDicomElements().x00280103.value[0];
      var pixelMeta = {
        bitsAllocated: bitsAllocated,
        isSigned: (pixelRepresentation === 1)
      };
      var columnsElement = dicomParser.getRawDicomElements().x00280011;
      var rowsElement = dicomParser.getRawDicomElements().x00280010;
      if (typeof columnsElement !== 'undefined' &&
        typeof rowsElement !== 'undefined') {
        pixelMeta.sliceSize = columnsElement.value[0] * rowsElement.value[0];
      }
      var samplesPerPixelElement = dicomParser.getRawDicomElements().x00280002;
      if (typeof samplesPerPixelElement !== 'undefined') {
        pixelMeta.samplesPerPixel = samplesPerPixelElement.value[0];
      }
      var planarConfigurationElement =
        dicomParser.getRawDicomElements().x00280006;
      if (typeof planarConfigurationElement !== 'undefined') {
        pixelMeta.planarConfiguration = planarConfigurationElement.value[0];
      }

      // number of items
      var numberOfItems = pixelBuffer.length;

      // setup the decoder (one decoder per all converts)
      if (pixelDecoder === null) {
        pixelDecoder = new dwv.image.PixelBufferDecoder(
          algoName, numberOfItems);
        // callbacks
        // pixelDecoder.ondecodestart: nothing to do
        pixelDecoder.ondecodeditem = function (event) {
          onDecodedItem(event);
          // send onload and onloadend when all items have been decoded
          if (event.itemNumber + 1 === event.numberOfItems) {
            self.onload(event);
            self.onloadend(event);
          }
        };
        // pixelDecoder.ondecoded: nothing to do
        // pixelDecoder.ondecodeend: nothing to do
        pixelDecoder.onerror = self.onerror;
        pixelDecoder.onabort = self.onabort;
      }

      // launch decode
      for (var i = 0; i < numberOfItems; ++i) {
        pixelDecoder.decode(pixelBuffer[i], pixelMeta,
          {
            itemNumber: i,
            numberOfItems: numberOfItems,
            dataIndex: dataIndex
          }
        );
      }
    } else {
      // no decompression
      // send progress
      self.onprogress({
        lengthComputable: true,
        loaded: 100,
        total: 100,
        index: dataIndex,
        source: origin
      });
      // generate image
      generateImage(dataIndex, origin);
      // send load events
      self.onload({
        source: origin
      });
      self.onloadend({
        source: origin
      });
    }
  };

  /**
   * Abort a conversion.
   */
  this.abort = function () {
    // abort decoding, will trigger pixelDecoder.onabort
    if (pixelDecoder) {
      pixelDecoder.abort();
    }
  };
};

/**
 * Handle a load start event.
 * Default does nothing.
 *
 * @param {object} _event The load start event.
 */
dwv.image.DicomBufferToView.prototype.onloadstart = function (_event) {};
/**
 * Handle a load item event.
 * Default does nothing.
 *
 * @param {object} _event The load item event.
 */
dwv.image.DicomBufferToView.prototype.onloaditem = function (_event) {};
/**
 * Handle a load progress event.
 * Default does nothing.
 *
 * @param {object} _event The progress event.
 */
dwv.image.DicomBufferToView.prototype.onprogress = function (_event) {};
/**
 * Handle a load event.
 * Default does nothing.
 *
 * @param {object} _event The load event fired
 *   when a file has been loaded successfully.
 */
dwv.image.DicomBufferToView.prototype.onload = function (_event) {};
/**
 * Handle a load end event.
 * Default does nothing.
 *
 * @param {object} _event The load end event fired
 *  when a file load has completed, successfully or not.
 */
dwv.image.DicomBufferToView.prototype.onloadend = function (_event) {};
/**
 * Handle an error event.
 * Default does nothing.
 *
 * @param {object} _event The error event.
 */
dwv.image.DicomBufferToView.prototype.onerror = function (_event) {};
/**
 * Handle an abort event.
 * Default does nothing.
 *
 * @param {object} _event The abort event.
 */
dwv.image.DicomBufferToView.prototype.onabort = function (_event) {};