src_image_voiLut.js

// doc imports
/* eslint-disable no-unused-vars */
import {WindowLevel} from './windowLevel';
/* eslint-enable no-unused-vars */

/**
 * VOI (Values of Interest) LUT class: apply window centre and width.
 *
 * ```
 * 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
 * ```
 *
 * Ref: {@link https://dicom.nema.org/medical/dicom/2022a/output/chtml/part03/sect_C.11.2.html}.
 */
export class VoiLut {

  /**
   * The window and level.
   *
   * @type {WindowLevel}
   */
  #windowLevel;

  /**
   * 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;

  /**
   * @param {WindowLevel} wl The window center and width.
   */
  constructor(wl) {
    this.#windowLevel = wl;
    this.#init();
  }

  /**
   * Get the window and level.
   *
   * @returns {WindowLevel} The window center and width.
   */
  getWindowLevel() {
    return this.#windowLevel;
  }

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

  /**
   * 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;
    }
  }

} // class VoiLut