src/image/windowLevel.js

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

/**
 * Minimum window width value.
 *
 * @see http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.11.html#sect_C.11.2.1.2
 */
dwv.image.MinWindowWidth = 1;

/**
 * Validate an input window width.
 *
 * @param {number} value The value to test.
 * @returns {number} A valid window width.
 */
dwv.image.validateWindowWidth = function (value) {
  return value < dwv.image.MinWindowWidth ? dwv.image.MinWindowWidth : value;
};

/**
 * WindowLevel class.
 * <br>Pseudo-code:
 * <pre>
 *  if (x <= c - 0.5 - (w-1)/2), then y = ymin
 *  else if (x > c - 0.5 + (w-1)/2), then y = ymax,
 *  else y = ((x - (c - 0.5)) / (w-1) + 0.5) * (ymax - ymin) + ymin
 * </pre>
 *
 * @see DICOM doc for [Window Center and Window Width]{@link http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_C.11.html#sect_C.11.2.1.2}
 * @param {number} center The window center.
 * @param {number} width The window width.
 * @class
 */
dwv.image.WindowLevel = function (center, width) {
  // check width
  if (width < dwv.image.MinWindowWidth) {
    throw new Error('Window width shall always be greater than or equal to ' +
      dwv.image.MinWindowWidth);
  }

  /**
   * Signed data offset. Defaults to 0.
   *
   * @private
   * @type {number}
   */
  var signedOffset = 0;
  /**
   * Output value minimum. Defaults to 0.
   *
   * @private
   * @type {number}
   */
  var ymin = 0;
  /**
   * Output value maximum. Defaults to 255.
   *
   * @private
   * @type {number}
   */
  var ymax = 255;

  /**
   * Input value minimum (calculated).
   *
   * @private
   * @type {number}
   */
  var xmin = null;
  /**
   * Input value maximum (calculated).
   *
   * @private
   * @type {number}
   */
  var xmax = null;
  /**
   * Window level equation slope (calculated).
   *
   * @private
   * @type {number}
   */
  var slope = null;
  /**
   * Window level equation intercept (calculated).
   *
   * @private
   * @type {number}
   */
  var inter = null;

  /**
   * Initialise members. Called at construction.
   *
   * @private
   */
  function init() {
    var c = center + signedOffset;
    // from the standard
    xmin = c - 0.5 - ((width - 1) / 2);
    xmax = c - 0.5 + ((width - 1) / 2);
    // develop the equation:
    // y = ( ( x - (c - 0.5) ) / (w-1) + 0.5 ) * (ymax - ymin) + ymin
    // y = ( x / (w-1) ) * (ymax - ymin) +
    //     ( -(c - 0.5) / (w-1) + 0.5 ) * (ymax - ymin) + ymin
    slope = (ymax - ymin) / (width - 1);
    inter = (-(c - 0.5) / (width - 1) + 0.5) * (ymax - ymin) + ymin;
  }

  // call init
  init();

  /**
   * Get the window center.
   *
   * @returns {number} The window center.
   */
  this.getCenter = function () {
    return center;
  };
  /**
   * Get the window width.
   *
   * @returns {number} The window width.
   */
  this.getWidth = function () {
    return width;
  };

  /**
   * Set the output value range.
   *
   * @param {number} min The output value minimum.
   * @param {number} max The output value maximum.
   */
  this.setRange = function (min, max) {
    ymin = parseInt(min, 10);
    ymax = parseInt(max, 10);
    // re-initialise
    init();
  };
  /**
   * Set the signed offset.
   *
   * @param {number} offset The signed data offset,
   *   typically: slope * ( size / 2).
   */
  this.setSignedOffset = function (offset) {
    signedOffset = offset;
    // re-initialise
    init();
  };

  /**
   * Apply the window level on an input value.
   *
   * @param {number} value The value to rescale as an integer.
   * @returns {number} The leveled value, in the
   *  [ymin, ymax] range (default [0,255]).
   */
  this.apply = function (value) {
    if (value <= xmin) {
      return ymin;
    } else if (value > xmax) {
      return ymax;
    } else {
      return parseInt(((value * slope) + inter), 10);
    }
  };

};

/**
 * Check for window level equality.
 *
 * @param {object} rhs The other window level to compare to.
 * @returns {boolean} True if both window level are equal.
 */
dwv.image.WindowLevel.prototype.equals = function (rhs) {
  return rhs !== null &&
        this.getCenter() === rhs.getCenter() &&
        this.getWidth() === rhs.getWidth();
};

/**
 * Get a string representation of the window level.
 *
 * @returns {string} The window level as a string.
 */
dwv.image.WindowLevel.prototype.toString = function () {
  return (this.getCenter() + ', ' + this.getWidth());
};