src_image_windowCenterAndWidth.js

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

/**
 * List of default window level presets.
 *
 * @type {Object.<string, Object.<string, {center: number, width: number}>>}
 */
export const defaultPresets = {
  CT: {
    mediastinum: {center: 40, width: 400},
    lung: {center: -500, width: 1500},
    bone: {center: 500, width: 2000},
    brain: {center: 40, width: 80},
    head: {center: 90, width: 350}
  }
};

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

/**
 * WindowCenterAndWidth class.
 * <br>Pseudo-code:
 * <pre>
 *  if (x &lt;= 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}
 */
export class WindowCenterAndWidth {

  /**
   * The center.
   *
   * @type {number}
   */
  #center;

  /**
   * The width.
   *
   * @type {number}
   */
  #width;

  /**
   * @param {number} center The window center.
   * @param {number} width The window width.
   */
  constructor(center, width) {
    // check width
    if (width < minWindowWidth) {
      throw new Error('Window width shall always be greater than or equal to ' +
        minWindowWidth);
    }
    this.#center = center;
    this.#width = width;

    this.#init();
  }

  /**
   * Signed data offset. Defaults to 0.
   *
   * @type {number}
   */
  #signedOffset = 0;

  /**
   * Output value minimum. Defaults to 0.
   *
   * @type {number}
   */
  #ymin = 0;

  /**
   * Output value maximum. Defaults to 255.
   *
   * @type {number}
   */
  #ymax = 255;

  /**
   * Input value minimum (calculated).
   *
   * @type {number}
   */
  #xmin = null;

  /**
   * Input value maximum (calculated).
   *
   * @type {number}
   */
  #xmax = null;

  /**
   * Window level equation slope (calculated).
   *
   * @type {number}
   */
  #slope = null;

  /**
   * Window level equation intercept (calculated).
   *
   * @type {number}
   */
  #inter = null;

  /**
   * Initialise members. Called at construction.
   *
   */
  #init() {
    const c = this.#center + this.#signedOffset;
    // from the standard
    this.#xmin = c - 0.5 - ((this.#width - 1) / 2);
    this.#xmax = c - 0.5 + ((this.#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
    this.#slope = (this.#ymax - this.#ymin) / (this.#width - 1);
    this.#inter = (-(c - 0.5) / (this.#width - 1) + 0.5) *
      (this.#ymax - this.#ymin) + this.#ymin;
  }

  /**
   * Get the window center.
   *
   * @returns {number} The window center.
   */
  getCenter() {
    return this.#center;
  }

  /**
   * Get the window width.
   *
   * @returns {number} The window width.
   */
  getWidth() {
    return this.#width;
  }

  /**
   * Set the output value range.
   *
   * @param {string} min The output value minimum.
   * @param {string} max The output value maximum.
   */
  setRange(min, max) {
    this.#ymin = parseInt(min, 10);
    this.#ymax = parseInt(max, 10);
    // re-initialise
    this.#init();
  }

  /**
   * Set the signed offset.
   *
   * @param {number} offset The signed data offset,
   *   typically: slope * ( size / 2).
   */
  setSignedOffset(offset) {
    this.#signedOffset = offset;
    // re-initialise
    this.#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]).
   */
  apply(value) {
    if (value <= this.#xmin) {
      return this.#ymin;
    } else if (value > this.#xmax) {
      return this.#ymax;
    } else {
      return (value * this.#slope) + this.#inter;
    }
  }

  /**
   * Check for window level equality.
   *
   * @param {WindowCenterAndWidth} rhs The other window level to compare to.
   * @returns {boolean} True if both window level are equal.
   */
  equals(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.
   */
  toString() {
    return (this.getCenter() + ', ' + this.getWidth());
  }

} // class WindowLevel