src_math_rectangle.js

import {Point2D} from './point';
import {getStats} from './stats';
import {Index} from './index';

// doc imports
/* eslint-disable no-unused-vars */
import {ViewController} from '../app/viewController';
import {Scalar2D} from './scalar';
/* eslint-enable no-unused-vars */

/**
 * Mulitply the three inputs if the last two are not null.
 *
 * @param {number} a The first input.
 * @param {number} b The second input.
 * @param {number} c The third input.
 * @returns {number} The multiplication of the three inputs or
 *  null if one of the last two is null.
 */
function mulABC(a, b, c) {
  let res = null;
  if (b !== null && c !== null) {
    res = a * b * c;
  }
  return res;
}

/**
 * Rectangle shape.
 */
export class Rectangle {

  /**
   * Rectangle begin point.
   *
   * @type {Point2D}
   */
  #begin;

  /**
   * Rectangle end point.
   *
   * @type {Point2D}
   */
  #end;

  /**
   * @param {Point2D} begin A Point2D representing the beginning
   *   of the rectangle.
   * @param {Point2D} end A Point2D representing the end
   *   of the rectangle.
   */
  constructor(begin, end) {
    this.#begin = new Point2D(
      Math.min(begin.getX(), end.getX()),
      Math.min(begin.getY(), end.getY())
    );
    this.#end = new Point2D(
      Math.max(begin.getX(), end.getX()),
      Math.max(begin.getY(), end.getY())
    );
  }

  /**
   * Get the begin point of the rectangle.
   *
   * @returns {Point2D} The begin point of the rectangle.
   */
  getBegin() {
    return this.#begin;
  }

  /**
   * Get the end point of the rectangle.
   *
   * @returns {Point2D} The end point of the rectangle.
   */
  getEnd() {
    return this.#end;
  }

  /**
   * Check for equality.
   *
   * @param {Rectangle} rhs The object to compare to.
   * @returns {boolean} True if both objects are equal.
   */
  equals(rhs) {
    return rhs !== null &&
      this.getBegin().equals(rhs.getBegin()) &&
      this.getEnd().equals(rhs.getEnd());
  }

  /**
   * Get the surface of the rectangle.
   *
   * @returns {number} The surface of the rectangle.
   */
  getSurface() {
    const begin = this.getBegin();
    const end = this.getEnd();
    return Math.abs(end.getX() - begin.getX()) *
      Math.abs(end.getY() - begin.getY());
  }

  /**
   * Get the surface of the rectangle according to a spacing.
   *
   * @param {Scalar2D} spacing2D The 2D spacing.
   * @returns {number} The surface of the rectangle multiplied by the given
   *  spacing or null for null spacings.
   */
  getWorldSurface(spacing2D) {
    return mulABC(this.getSurface(), spacing2D.x, spacing2D.y);
  }

  /**
   * Get the real width of the rectangle.
   *
   * @returns {number} The real width of the rectangle.
   */
  getRealWidth() {
    return this.getEnd().getX() - this.getBegin().getX();
  }

  /**
   * Get the real height of the rectangle.
   *
   * @returns {number} The real height of the rectangle.
   */
  getRealHeight() {
    return this.getEnd().getY() - this.getBegin().getY();
  }

  /**
   * Get the width of the rectangle.
   *
   * @returns {number} The width of the rectangle.
   */
  getWidth() {
    return Math.abs(this.getRealWidth());
  }

  /**
   * Get the height of the rectangle.
   *
   * @returns {number} The height of the rectangle.
   */
  getHeight() {
    return Math.abs(this.getRealHeight());
  }

  /**
   * Get the rounded limits of the rectangle.
   *
   * @returns {object} The rounded limits as {min, max} (Point2D).
   */
  getRound() {
    const roundBegin = new Point2D(
      Math.round(this.getBegin().getX()),
      Math.round(this.getBegin().getY())
    );
    const roundEnd = new Point2D(
      Math.round(this.getEnd().getX()),
      Math.round(this.getEnd().getY())
    );
    return {
      min: roundBegin,
      max: roundEnd
    };
  }

  /**
   * Get the centroid of the rectangle.
   *
   * @returns {Point2D} The centroid point.
   */
  getCentroid() {
    return new Point2D(
      this.getBegin().getX() + this.getWidth() / 2,
      this.getBegin().getY() + this.getHeight() / 2
    );
  }

  /**
   * Quantify a rectangle according to view information.
   *
   * @param {ViewController} viewController The associated view controller.
   * @param {string[]} flags A list of stat values to calculate.
   * @returns {object} A quantification object.
   */
  quantify(viewController, flags) {
    const quant = {};
    // shape quantification
    const spacing2D = viewController.get2DSpacing();
    quant.width = {
      value: this.getWidth() * spacing2D.x,
      unit: 'unit.mm'
    };
    quant.height = {
      value: this.getHeight() * spacing2D.y,
      unit: 'unit.mm'
    };
    const surface = this.getWorldSurface(spacing2D);
    if (surface !== null) {
      quant.surface = {
        value: surface / 100,
        unit: 'unit.cm2'
      };
    }

    // pixel values quantification
    if (viewController.canQuantifyImage()) {
      const round = this.getRound();
      const values = viewController.getImageRegionValues(round.min, round.max);
      const unit = viewController.getPixelUnit();
      const quantif = getStats(values, flags);
      quant.min = {value: quantif.min, unit: unit};
      quant.max = {value: quantif.max, unit: unit};
      quant.mean = {value: quantif.mean, unit: unit};
      quant.stdDev = {value: quantif.stdDev, unit: unit};
      if (typeof quantif.median !== 'undefined') {
        quant.median = {value: quantif.median, unit: unit};
      }
      if (typeof quantif.p25 !== 'undefined') {
        quant.p25 = {value: quantif.p25, unit: unit};
      }
      if (typeof quantif.p75 !== 'undefined') {
        quant.p75 = {value: quantif.p75, unit: unit};
      }
    }

    // return
    return quant;
  }

} // Rectangle class

/**
 * Get the indices that form a rectangle.
 *
 * @param {Index} center The rectangle center.
 * @param {number[]} size The 2 rectangle sizes.
 * @param {number[]} dir The 2 rectangle directions.
 * @returns {Index[]} The indices of the rectangle.
 */
export function getRectangleIndices(center, size, dir) {
  const centerValues = center.getValues();
  // keep all values for possible extra dimensions
  const values = centerValues.slice();
  const indices = [];
  const sizeI = size[0];
  const halfSizeI = Math.floor(sizeI / 2);
  const sizeJ = size[1];
  const halfSizeJ = Math.floor(sizeJ / 2);
  const di = dir[0];
  const dj = dir[1];
  for (let j = 0; j < sizeJ; ++j) {
    values[dj] = centerValues[dj] - halfSizeJ + j;
    for (let i = 0; i < sizeI; ++i) {
      values[di] = centerValues[di] - halfSizeI + i;
      indices.push(new Index(values.slice()));
    }
  }
  return indices;
}