tests_dicom_mprPixGenerator.js

import {
  getOrientationName,
  Orientation
} from '../../src/math/orientation.js';

import {getImageDataData} from './dicomGenerator.js';

/**
 * MPRPixGenerator: generates pixel data from file
 *   with an input per orientation.
 */
export class MPRPixGenerator {

  #numberOfColumns;
  #numberOfRows;
  #numberOfSlices = 0;

  #halfNCols;
  #halfNRows;
  #halfNSlices = 0;

  #isRGB;
  #orientationName;

  #images = [];
  #buffers = [];

  /**
   * @param {object} options The generator options.
   */
  constructor(options) {
    this.#numberOfColumns = options.numberOfColumns;
    this.#numberOfRows = options.numberOfRows;
    this.#isRGB = options.photometricInterpretation === 'RGB';

    if (this.#isRGB) {
      throw new Error('The MPRPixGenerator does not support RGB data.');
    }

    this.#halfNCols = this.#numberOfColumns * 0.5;
    this.#halfNRows = this.#numberOfRows * 0.5;

    this.#orientationName =
      getOrientationName(options.imageOrientationPatient);
  }

  setNumberOfSlices(num) {
    this.#numberOfSlices = num;
    this.#halfNSlices = num * 0.5;
  };

  setImages(imgs) {
    // check sizes
    let img;
    for (let i = 0; i < imgs.length; ++i) {
      img = imgs[i];
      if (img.width !== this.#halfNCols) {
        throw new Error('Image width mismatch: ' +
          img.width + '!=' + this.#halfNCols);
      }
      if (img.height !== this.#halfNRows) {
        throw new Error('Image height mismatch: ' +
          img.height + '!=' + this.#halfNRows);
      }
    }
    // store
    this.#images = imgs;
    // store buffers
    this.#buffers = [];
    for (let i0 = 0; i0 < imgs.length; ++i0) {
      this.#buffers.push(getImageDataData(this.#images[i0]));
    }
  };

  generate(pixelBuffer, sliceNumber) {
    if (sliceNumber > this.#numberOfSlices) {
      throw new Error('Cannot generate slice, number is above size: ' +
        sliceNumber + ', ' + this.#numberOfSlices);
    }
    if (this.#orientationName === Orientation.Axial) {
      this.#generateAsAxial(pixelBuffer, sliceNumber);
    } else if (this.#orientationName === Orientation.Coronal) {
      this.#generateAsCoronal(pixelBuffer, sliceNumber);
    } else if (this.#orientationName === Orientation.Sagittal) {
      this.#generateAsSagittal(pixelBuffer, sliceNumber);
    }
  };

  #generateAsAxial(pixelBuffer, sliceNumber) {
    // axial
    let offset = 0;
    for (let j0 = 0; j0 < this.#halfNRows; ++j0) {
      for (let i0 = 0; i0 < this.#halfNCols; ++i0) {
        pixelBuffer[offset] = this.#getFunc(Orientation.Axial, i0, j0);
        ++offset;
      }
      offset += this.#halfNCols;
    }
    if (sliceNumber < this.#halfNSlices) {
      // coronal
      offset = this.#halfNCols;
      for (let j1 = 0; j1 < this.#numberOfRows; ++j1) {
        for (let i1 = 0; i1 < this.#halfNCols; ++i1) {
          pixelBuffer[offset] = this.#getFunc(
            Orientation.Coronal, i1, (this.#halfNSlices - 1 - sliceNumber));
          ++offset;
        }
        offset += this.#halfNCols;
      }
    } else {
      // sagittal
      offset = this.#numberOfColumns * this.#halfNRows;
      for (let j2 = 0; j2 < this.#halfNRows; ++j2) {
        for (let i2 = 0; i2 < this.#numberOfColumns; ++i2) {
          pixelBuffer[offset] = this.#getFunc(
            Orientation.Sagittal, j2, (this.#numberOfSlices - 1 - sliceNumber));
          ++offset;
        }
      }
    }
  };

  #generateAsCoronal(pixelBuffer, sliceNumber) {
    // coronal
    let offset = this.#numberOfColumns * this.#halfNRows + this.#halfNCols;
    for (let j0 = 0; j0 < this.#halfNRows; ++j0) {
      for (let i0 = 0; i0 < this.#halfNCols; ++i0) {
        pixelBuffer[offset] = this.#getFunc(Orientation.Coronal, i0, j0);
        ++offset;
      }
      offset += this.#halfNCols;
    }
    if (sliceNumber < this.#halfNSlices) {
      // axial
      offset = 0;
      for (let j1 = 0; j1 < this.#numberOfRows; ++j1) {
        for (let i1 = 0; i1 < this.#halfNCols; ++i1) {
          pixelBuffer[offset] = this.#getFunc(
            Orientation.Axial, i1, sliceNumber);
          ++offset;
        }
        offset += this.#halfNCols;
      }

    } else {
      // sagittal
      offset = 0;
      for (let j2 = 0; j2 < this.#halfNRows; ++j2) {
        for (let i2 = 0; i2 < this.#numberOfColumns; ++i2) {
          pixelBuffer[offset] = this.#getFunc(
            Orientation.Sagittal, sliceNumber, j2 - 1);
          ++offset;
        }
      }
    }
  };

  #generateAsSagittal(pixelBuffer, sliceNumber) {
    // sagittal
    let offset = this.#halfNCols;
    for (let j0 = 0; j0 < this.#halfNRows; ++j0) {
      for (let i0 = 0; i0 < this.#halfNCols; ++i0) {
        pixelBuffer[offset] = this.#getFunc(Orientation.Sagittal, i0, j0);
        ++offset;
      }
      offset += this.#halfNCols;
    }
    if (sliceNumber < this.#halfNSlices) {
      // axial
      offset = 0;
      for (let j1 = 0; j1 < this.#numberOfRows; ++j1) {
        for (let i1 = 0; i1 < this.#halfNCols; ++i1) {
          pixelBuffer[offset] = this.#getFunc(
            Orientation.Axial, sliceNumber, i1);
          ++offset;
        }
        offset += this.#halfNCols;
      }

    } else {
      // coronal
      offset = this.#numberOfColumns * this.#halfNRows;
      for (let j2 = 0; j2 < this.#halfNRows; ++j2) {
        for (let i2 = 0; i2 < this.#numberOfColumns; ++i2) {
          pixelBuffer[offset] = this.#getFunc(
            Orientation.Coronal, sliceNumber, j2 - 1);
          ++offset;
        }
      }
    }
  };

  /**
   * @param {number} i The column index.
   * @param {number} j The row index.
   * @returns {number} The offset for the given position.
   */
  #getOffset(i, j) {
    return i + j * this.#halfNCols;
  }

  /**
   * @param {string} name The image orientation.
   * @param {number} i The column index.
   * @param {number} j The row index.
   * @returns {number} The value at the given position.
   */
  #getFunc(name, i, j) {
    let imgIdx = 0;
    if (name === Orientation.Axial) {
      imgIdx = 0;
    } else if (name === Orientation.Coronal) {
      imgIdx = 1;
    } else if (name === Orientation.Sagittal) {
      imgIdx = 2;
    }
    return this.#buffers[imgIdx][this.#getOffset(i, j) * 4];
  }
};

/**
 * Check tags are coherent with image size.
 *
 * @param {object} tags The tags to check.
 * @param {object} image The associated image.
 * @returns {boolean} True if the tags are ok.
 */
export function mprCheckTags(tags, image) {
  /**
   * @param {number} value The value to check.
   * @returns {number} The expected value.
   */
  function getExpectedSize(value) {
    return 2 * value;
  }

  let needUpdate = false;
  if (tags.Columns !== getExpectedSize(image.width)) {
    tags.Columns = getExpectedSize(image.width);
    needUpdate = true;
  }
  if (tags.Rows !== getExpectedSize(image.height)) {
    tags.Rows = getExpectedSize(image.height);
    needUpdate = true;
  }
  return needUpdate;
}