tests_image_iterator.test.js

import {Point3D} from '../../src/math/point';
import {Index} from '../../src/math/index';
import {
  Matrix33,
  getIdentityMat33
} from '../../src/math/matrix';
import {getMatrixFromName} from '../../src/math/orientation';
import {Size} from '../../src/image/size';
import {Spacing} from '../../src/image/spacing';
import {Geometry} from '../../src/image/geometry';
import {
  simpleRange,
  simpleRange3d,
  range,
  rangeRegion,
  getSliceIterator
} from '../../src/image/iterator';
import {Image} from '../../src/image/image';

/**
 * Tests for the 'image/iterator.js' file.
 */

/* global QUnit */
QUnit.module('image');

/* eslint-disable @stylistic/js/array-element-newline */
const dataIterator0 = {
  ncols: 3,
  nrows: 2,
  nslices: 4,
  buffer: [
    0, 1, 2,
    3, 4, 5,
    10, 11, 12,
    13, 14, 15,
    20, 21, 22,
    23, 24, 25,
    30, 31, 32,
    33, 34, 35
  ],
  valuesAx: [
    [0, 1, 2, 3, 4, 5],
    [10, 11, 12, 13, 14, 15],
    [20, 21, 22, 23, 24, 25],
    [30, 31, 32, 33, 34, 35]
  ],
  valuesAxR1: [
    [5, 4, 3, 2, 1, 0],
    [15, 14, 13, 12, 11, 10],
    [25, 24, 23, 22, 21, 20],
    [35, 34, 33, 32, 31, 30]
  ],
  valuesAxR2: [
    [2, 1, 0, 5, 4, 3],
    [12, 11, 10, 15, 14, 13],
    [22, 21, 20, 25, 24, 23],
    [32, 31, 30, 35, 34, 33]
  ],
  valuesAxR1R2: [
    [3, 4, 5, 0, 1, 2],
    [13, 14, 15, 10, 11, 12],
    [23, 24, 25, 20, 21, 22],
    [33, 34, 35, 30, 31, 32]
  ],
  valuesAx2: [
    [0, 3, 1, 4, 2, 5],
    [10, 13, 11, 14, 12, 15],
    [20, 23, 21, 24, 22, 25],
    [30, 33, 31, 34, 32, 35]
  ],
  valuesAx2R1: [
    [5, 2, 4, 1, 3, 0],
    [15, 12, 14, 11, 13, 10],
    [25, 22, 24, 21, 23, 20],
    [35, 32, 34, 31, 33, 30]
  ],
  valuesAx2R2: [
    [3, 0, 4, 1, 5, 2],
    [13, 10, 14, 11, 15, 12],
    [23, 20, 24, 21, 25, 22],
    [33, 30, 34, 31, 35, 32]
  ],
  valuesAx2R1R2: [
    [2, 5, 1, 4, 0, 3],
    [12, 15, 11, 14, 10, 13],
    [22, 25, 21, 24, 20, 23],
    [32, 35, 31, 34, 30, 33]
  ],
  valuesCo: [
    [0, 1, 2, 10, 11, 12, 20, 21, 22, 30, 31, 32],
    [3, 4, 5, 13, 14, 15, 23, 24, 25, 33, 34, 35]
  ],
  valuesCoR1: [
    [32, 31, 30, 22, 21, 20, 12, 11, 10, 2, 1, 0],
    [35, 34, 33, 25, 24, 23, 15, 14, 13, 5, 4, 3]
  ],
  valuesCoR2: [
    [2, 1, 0, 12, 11, 10, 22, 21, 20, 32, 31, 30],
    [5, 4, 3, 15, 14, 13, 25, 24, 23, 35, 34, 33]
  ],
  valuesCoR1R2: [
    [30, 31, 32, 20, 21, 22, 10, 11, 12, 0, 1, 2],
    [33, 34, 35, 23, 24, 25, 13, 14, 15, 3, 4, 5]
  ],
  valuesCo2: [
    [0, 10, 20, 30, 1, 11, 21, 31, 2, 12, 22, 32],
    [3, 13, 23, 33, 4, 14, 24, 34, 5, 15, 25, 35]
  ],
  valuesCo2R1: [
    [32, 22, 12, 2, 31, 21, 11, 1, 30, 20, 10, 0],
    [35, 25, 15, 5, 34, 24, 14, 4, 33, 23, 13, 3]
  ],
  valuesCo2R2: [
    [30, 20, 10, 0, 31, 21, 11, 1, 32, 22, 12, 2],
    [33, 23, 13, 3, 34, 24, 14, 4, 35, 25, 15, 5]
  ],
  valuesCo2R1R2: [
    [2, 12, 22, 32, 1, 11, 21, 31, 0, 10, 20, 30],
    [5, 15, 25, 35, 4, 14, 24, 34, 3, 13, 23, 33]
  ],
  valuesSa: [
    [0, 3, 10, 13, 20, 23, 30, 33],
    [1, 4, 11, 14, 21, 24, 31, 34],
    [2, 5, 12, 15, 22, 25, 32, 35]
  ],
  valuesSaR1: [
    [33, 30, 23, 20, 13, 10, 3, 0],
    [34, 31, 24, 21, 14, 11, 4, 1],
    [35, 32, 25, 22, 15, 12, 5, 2]
  ],
  valuesSaR2: [
    [3, 0, 13, 10, 23, 20, 33, 30],
    [4, 1, 14, 11, 24, 21, 34, 31],
    [5, 2, 15, 12, 25, 22, 35, 32]
  ],
  valuesSaR1R2: [
    [30, 33, 20, 23, 10, 13, 0, 3],
    [31, 34, 21, 24, 11, 14, 1, 4],
    [32, 35, 22, 25, 12, 15, 2, 5]
  ],
  valuesSa2: [
    [0, 10, 20, 30, 3, 13, 23, 33],
    [1, 11, 21, 31, 4, 14, 24, 34],
    [2, 12, 22, 32, 5, 15, 25, 35]
  ],
  valuesSa2R1: [
    [33, 23, 13, 3, 30, 20, 10, 0],
    [34, 24, 14, 4, 31, 21, 11, 1],
    [35, 25, 15, 5, 32, 22, 12, 2]
  ],
  valuesSa2R2: [
    [30, 20, 10, 0, 33, 23, 13, 3],
    [31, 21, 11, 1, 34, 24, 14, 4],
    [32, 22, 12, 2, 35, 25, 15, 5]
  ],
  valuesSa2R1R2: [
    [3, 13, 23, 33, 0, 10, 20, 30],
    [4, 14, 24, 34, 1, 11, 21, 31],
    [5, 15, 25, 35, 2, 12, 22, 32]
  ]
};
/* eslint-enable @stylistic/js/array-element-newline */

/**
 * Run an input iterator and store values.
 *
 * @param {object} iter The iterator.
 * @returns {Array} The result array.
 */
function runIterator(iter) {
  const res = [];
  let ival = iter.next();
  while (!ival.done) {
    res.push(ival.value);
    ival = iter.next();
  }
  return res;
}

/**
 * Check iter.
 *
 * @param {object} assert The qunit assert.
 * @param {Function} getIter Function to get the iter at a given position.
 * @param {Array} theoValues Theoretical values.
 * @param {string} name String to identify test.
 */
function checkIterator(assert, getIter, theoValues, name) {
  for (let i = 0; i < theoValues.length; ++i) {
    const res = runIterator(getIter(i));
    const theo = theoValues[i];
    assert.deepEqual(res, theo, 'range ' + name + ' #' + i);
  }
}

/**
 * Tests for {@link simpleRange}.
 *
 * @function module:tests/image~simpleRange-iterator
 */
QUnit.test('simpleRange iterator', function (assert) {
  const dataAccessor = function (offset) {
    return offset;
  };
  // test #0: default increment
  const test0Min = 0;
  const test0Max = 10;
  let i0Theo = test0Min;
  const iter0 = simpleRange(dataAccessor, test0Min, test0Max);
  let ival0 = iter0.next();
  while (!ival0.done) {
    assert.equal(ival0.value, i0Theo, '#0 iterator next');
    ival0 = iter0.next();
    ++i0Theo;
  }
  assert.equal(test0Max, i0Theo, '#0 iterator max');

  // test #1: specific increment
  const test1Min = 1;
  const test1Max = 21;
  const test1Incr = 2;
  let i1Theo = test1Min;
  const iter1 = simpleRange(
    dataAccessor, test1Min, test1Max, test1Incr);
  let ival1 = iter1.next();
  while (!ival1.done) {
    assert.equal(ival1.value, i1Theo, '#1 iterator next');
    ival1 = iter1.next();
    i1Theo += test1Incr;
  }
  assert.equal(test1Max, i1Theo, '#1 iterator max');
});

/**
 * Tests for {@link range}.
 *
 * @function module:tests/image~range-iterator-axial
 */
QUnit.test('Range iterator axial', function (assert) {
  // test data
  const testData0 = dataIterator0;
  const ncols = testData0.ncols;
  const nrows = testData0.nrows;
  const sliceSize = ncols * nrows;
  const dataAccessor = function (offset) {
    return testData0.buffer[offset];
  };

  // axial: xyz
  const getAxIter = function (reverse1, reverse2) {
    return function (index) {
      const min = index * sliceSize;
      const max = min + sliceSize;
      const start = reverse1 ? max - 1 : min;
      const maxIter = sliceSize;
      return range(dataAccessor,
        start, maxIter, 1, ncols, ncols, reverse1, reverse2);
    };
  };

  checkIterator(assert,
    getAxIter(false, false), testData0.valuesAx, 'axial');
  checkIterator(assert,
    getAxIter(true, false), testData0.valuesAxR1, 'axialR1');
  checkIterator(assert,
    getAxIter(false, true), testData0.valuesAxR2, 'axialR2');
  checkIterator(assert,
    getAxIter(true, true), testData0.valuesAxR1R2, 'axialR1R2');

  // axial: yxz
  const getAx2Iter = function (reverse1, reverse2) {
    return function (index) {
      const min = index * sliceSize;
      const max = min + sliceSize;
      const start = reverse1 ? max - 1 : min;
      const maxIter = sliceSize;
      return range(dataAccessor,
        start, maxIter, ncols, nrows, 1, reverse1, reverse2);
    };
  };

  checkIterator(assert,
    getAx2Iter(false, false), testData0.valuesAx2, 'axial2');
  checkIterator(assert,
    getAx2Iter(true, false), testData0.valuesAx2R1, 'axial2R1');
  checkIterator(assert,
    getAx2Iter(false, true), testData0.valuesAx2R2, 'axial2R2');
  checkIterator(assert,
    getAx2Iter(true, true), testData0.valuesAx2R1R2, 'axial2R1R2');
});

/**
 * Tests for {@link range}.
 *
 * @function module:tests/image~range-iterator-coronal
 */
QUnit.test('Range iterator coronal', function (assert) {
  // test data
  const testData0 = dataIterator0;
  const ncols = testData0.ncols;
  const nrows = testData0.nrows;
  const nslices = testData0.nslices;
  const sliceSize = ncols * nrows;
  const dataAccessor = function (offset) {
    return testData0.buffer[offset];
  };

  // coronal: xzy
  const getCoroIter = function (reverse1, reverse2) {
    return function (index) {
      const min = index * ncols;
      const max = min + (nslices - 1) * sliceSize + ncols;
      const start = reverse1 ? max - 1 : min;
      const maxIter = nslices * ncols;
      return range(dataAccessor,
        start, maxIter, 1, ncols, sliceSize, reverse1, reverse2);
    };
  };

  checkIterator(assert,
    getCoroIter(false, false), testData0.valuesCo, 'coronal');
  checkIterator(assert,
    getCoroIter(true, false), testData0.valuesCoR1, 'coronalR1');
  checkIterator(assert,
    getCoroIter(false, true), testData0.valuesCoR2, 'coronalR2');
  checkIterator(assert,
    getCoroIter(true, true), testData0.valuesCoR1R2, 'coronalR1R2');

  // coronal: zxy
  const getCoro2Iter = function (reverse1, reverse2) {
    return function (index) {
      const min = index * ncols;
      const max = min + (nslices - 1) * sliceSize + ncols;
      const start = reverse1 ? max - 1 : min;
      const maxIter = nslices * ncols;
      return range(dataAccessor,
        start, maxIter, sliceSize, nslices, 1, reverse1, reverse2);
    };
  };

  checkIterator(assert,
    getCoro2Iter(false, false), testData0.valuesCo2, 'coronal2');
  checkIterator(assert,
    getCoro2Iter(true, false), testData0.valuesCo2R1, 'coronal2R1');
  checkIterator(assert,
    getCoro2Iter(false, true), testData0.valuesCo2R2, 'coronal2R2');
  checkIterator(assert,
    getCoro2Iter(true, true), testData0.valuesCo2R1R2, 'coronal2R1R2');
});

/**
 * Tests for {@link range}.
 *
 * @function module:tests/image~range-iterator-sagittal
 */
QUnit.test('Range iterator sagittal', function (assert) {
  // test data
  const testData0 = dataIterator0;
  const ncols = testData0.ncols;
  const nrows = testData0.nrows;
  const nslices = testData0.nslices;
  const sliceSize = ncols * nrows;
  const dataAccessor = function (offset) {
    return testData0.buffer[offset];
  };

  // sagittal: yzx
  const getSagIter = function (reverse1, reverse2) {
    return function (index) {
      const min = index;
      const max = min + (nslices - 1) * sliceSize + ncols * (nrows - 1);
      const start = reverse1 ? max : min;
      const maxIter = nslices * nrows;
      return range(dataAccessor,
        start, maxIter, ncols, nrows, sliceSize, reverse1, reverse2);
    };
  };

  checkIterator(assert,
    getSagIter(false, false), testData0.valuesSa, 'sagittal');
  checkIterator(assert,
    getSagIter(true, false), testData0.valuesSaR1, 'sagittalR1');
  checkIterator(assert,
    getSagIter(false, true), testData0.valuesSaR2, 'sagittalR2');
  checkIterator(assert,
    getSagIter(true, true), testData0.valuesSaR1R2, 'sagittalR1R2');

  // sagittal: zyx
  const getSag2Iter = function (reverse1, reverse2) {
    return function (index) {
      const min = index;
      const max = min + (nslices - 1) * sliceSize + ncols * (nrows - 1);
      const start = reverse1 ? max : min;
      const maxIter = nslices * nrows;
      return range(dataAccessor,
        start, maxIter, sliceSize, nslices, ncols, reverse1, reverse2);
    };
  };

  checkIterator(assert,
    getSag2Iter(false, false), testData0.valuesSa2, 'sagittal2');
  checkIterator(assert,
    getSag2Iter(true, false), testData0.valuesSa2R1, 'sagittal2R1');
  checkIterator(assert,
    getSag2Iter(false, true), testData0.valuesSa2R2, 'sagittal2R2');
  checkIterator(assert,
    getSag2Iter(true, true), testData0.valuesSa2R1R2, 'sagittal2R1R2');
});

/**
 * Tests for {@link simpleRange3d}.
 *
 * @function module:tests/image~rgb-iterator
 */
QUnit.test('RGB iterator', function (assert) {
  const dataAccessor = function (offset) {
    return offset;
  };
  // test #0: default increment, default planar
  const test0Min = 0;
  const test0Size = 3;
  const test0Max = test0Min + 3 * test0Size;
  let i0Theo = test0Min;
  const iter0 = simpleRange3d(dataAccessor, test0Min, test0Max);
  let ival0 = iter0.next();
  while (!ival0.done) {
    assert.equal(ival0.value[0], i0Theo, '#0 3d iterator value');
    assert.equal(ival0.value[1], i0Theo + 1, '#0 3d iterator value1');
    assert.equal(ival0.value[2], i0Theo + 2, '#0 3d iterator value2');
    // increment
    i0Theo += 3;
    ival0 = iter0.next();
  }
  assert.equal(test0Max, i0Theo, '#0 3d iterator max');

  // test #1: non default increment, default planar (false)
  const test1Min = 1;
  const test1Size = 6;
  const test1Max = test1Min + 3 * test1Size;
  const test1Incr = 2;
  let i1Theo = test1Min;
  const iter1 = simpleRange3d(
    dataAccessor, test1Min, test1Max, test1Incr);
  let ival1 = iter1.next();
  while (!ival1.done) {
    assert.equal(ival1.value[0], i1Theo, '#1 3d iterator value');
    assert.equal(ival1.value[1], i1Theo + 1, '#1 3d iterator value1');
    assert.equal(ival1.value[2], i1Theo + 2, '#1 3d iterator value2');
    i1Theo += test1Incr * 3;
    ival1 = iter1.next();
  }
  assert.equal(test1Max, i1Theo, '#1 3d iterator max');

  // test #2: default increment, planar
  const test2Min = 2;
  const test2Size = 6;
  const test2Max = test2Min + 3 * test2Size;
  let i2Theo = test2Min;
  const iter2 = simpleRange3d(
    dataAccessor, test2Min, test2Max, 1, true);
  let ival2 = iter2.next();
  while (!ival2.done) {
    assert.equal(ival2.value[0], i2Theo, '#2 3d iterator value');
    assert.equal(ival2.value[1], i2Theo + test2Size, '#2 3d iterator value1');
    assert.equal(ival2.value[2], i2Theo + 2 * test2Size,
      '#2 3d iterator value2');
    ++i2Theo;
    ival2 = iter2.next();
  }
  assert.equal(test2Max, i2Theo, '#2 3d iterator max');

  // test #2: non default increment, planar
  const test3Min = 3;
  const test3Size = 6;
  const test3Max = test3Min + 3 * test3Size;
  const test3Incr = 2;
  let i3Theo = test3Min;
  const iter3 = simpleRange3d(
    dataAccessor, test3Min, test3Max, test3Incr, true);
  let ival3 = iter3.next();
  while (!ival3.done) {
    assert.equal(ival3.value[0], i3Theo, '#3 3d iterator value');
    assert.equal(ival3.value[1], i3Theo + test3Size, '#3 3d iterator value1');
    assert.equal(ival3.value[2], i3Theo + 2 * test3Size,
      '#3 3d iterator value2');
    i3Theo += test3Incr;
    ival3 = iter3.next();
  }
  assert.equal(test3Max, i3Theo, '#3 3d iterator max');
});

/**
 * Tests for {@link getSliceIterator}.
 *
 * @function module:tests/image~getSliceIterator
 */
QUnit.test('getSliceIterator', function (assert) {

  // test data
  const testData0 = dataIterator0;

  const imgSize00 = new Size([
    testData0.ncols, testData0.nrows, 1
  ]);
  const imgSpacing0 = new Spacing([1, 1, 1]);
  const imgOrigin0 = new Point3D(0, 0, 0);
  const imgGeometry0 = new Geometry(imgOrigin0, imgSize00, imgSpacing0);
  imgGeometry0.appendOrigin(new Point3D(0, 0, 1), 1);
  imgGeometry0.appendOrigin(new Point3D(0, 0, 2), 2);
  imgGeometry0.appendOrigin(new Point3D(0, 0, 3), 3);
  const image0 = new Image(imgGeometry0, testData0.buffer);

  const isRescaled = false;
  let viewOrientation;

  // axial
  const getAxIter = function (orientation) {
    return function (index) {
      const position = new Index([0, 0, index]);
      return getSliceIterator(
        image0, position, isRescaled, orientation);
    };
  };

  // axial: xyz
  viewOrientation = getIdentityMat33();
  checkIterator(assert,
    getAxIter(viewOrientation), testData0.valuesAx, 'axial');
  // axial: yxz
  /* eslint-disable @stylistic/js/array-element-newline */
  viewOrientation = new Matrix33([
    0, 1, 0,
    1, 0, 0,
    0, 0, 1
  ]);
  /* eslint-enable @stylistic/js/array-element-newline */
  checkIterator(assert,
    getAxIter(viewOrientation), testData0.valuesAx2, 'axial2');

  // coronal
  const getCoroIter = function (orientation) {
    return function (index) {
      const position = new Index([0, index, 0]);
      return getSliceIterator(
        image0, position, isRescaled, orientation);
    };
  };

  // coronal: xzy
  viewOrientation = getMatrixFromName('coronal');
  checkIterator(assert,
    getCoroIter(viewOrientation), testData0.valuesCo, 'coronal');
  // coronal: zxy
  /* eslint-disable @stylistic/js/array-element-newline */
  viewOrientation = new Matrix33([
    0, 1, 0,
    0, 0, 1,
    1, 0, 0
  ]);
  /* eslint-enable @stylistic/js/array-element-newline */
  checkIterator(assert,
    getCoroIter(viewOrientation), testData0.valuesCo2, 'coronal2');

  // sagittal
  const getSagIter = function (orientation) {
    return function (index) {
      const position = new Index([index, 0, 0]);
      return getSliceIterator(
        image0, position, isRescaled, orientation);
    };
  };

  // sagittal: yzx
  viewOrientation = getMatrixFromName('sagittal');
  checkIterator(assert,
    getSagIter(viewOrientation), testData0.valuesSa, 'sagittal');
  // sagittal: zyx
  /* eslint-disable @stylistic/js/array-element-newline */
  viewOrientation = new Matrix33([
    0, 0, 1,
    0, 1, 0,
    1, 0, 0
  ]);
  /* eslint-enable @stylistic/js/array-element-newline */
  checkIterator(assert,
    getSagIter(viewOrientation), testData0.valuesSa2, 'sagittal2');
});

/**
 * Tests for {@link rangeRegion}.
 *
 * @function module:tests/image~region-iterator
 */
QUnit.test('Region iterator', function (assert) {
  const dataAccessor = function (offset) {
    return offset;
  };
  // test #0: simulate regular iterator
  const test0Min = 0;
  const test0Max = 10;
  const test0Incr = 1;
  const test0NCols = 3;
  const test0RowIncr = 0;
  let i0Theo = test0Min;
  const iter0 = rangeRegion(
    dataAccessor, test0Min, test0Max, test0Incr, test0NCols, test0RowIncr);
  let ival0 = iter0.next();
  while (!ival0.done) {
    assert.equal(ival0.value, i0Theo, '#0 iterator next');
    ival0 = iter0.next();
    ++i0Theo;
  }
  assert.equal(test0Max, i0Theo, '#0 iterator max');

  // test #1: with col/row
  const test1Min = 0;
  const test1Max = 10;
  const test1Incr = 1;
  const test1NCols = 2;
  const test1RowIncr = 1;
  let i1Theo = test1Min;
  const iter1 = rangeRegion(
    dataAccessor, test1Min, test1Max, test1Incr, test1NCols, test1RowIncr);
  let ival1 = iter1.next();
  let countCol = 0;
  while (!ival1.done) {
    assert.equal(ival1.value, i1Theo, '#1 iterator next');
    ival1 = iter1.next();
    ++i1Theo;
    ++countCol;
    if (countCol === test1NCols) {
      countCol = 0;
      i1Theo += test1RowIncr;
    }
  }
  assert.equal(test1Max, i1Theo, '#1 iterator max');
});