tests_image_image.test.js

import {Point3D} from '../../src/math/point';
import {Index} from '../../src/math/index';
import {getStats} from '../../src/math/stats';
import {arrayEquals} from '../../src/utils/array';
import {Size} from '../../src/image/size';
import {Spacing} from '../../src/image/spacing';
import {Geometry} from '../../src/image/geometry';
import {RescaleSlopeAndIntercept} from '../../src/image/rsi';
import {Image} from '../../src/image/image';
import {ImageFactory} from '../../src/image/imageFactory';

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

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

/**
 * Compare an image and a buffer.
 *
 * @param {object} image The input image.
 * @param {object} size The size of the input buffer.
 * @param {Array} buffer The input buffer.
 * @param {object} rsi The rescale slope of the input buffer.
 * @returns {object} Statistics of the value and rescaled value differences.
 */
function compareImageAndBuffer(image, size, buffer, rsi) {
  const diffs = [];
  const diffsRescaled = [];

  // calculate differences
  let index = 0;
  for (let k = 0; k < size.get(2); ++k) {
    for (let j = 0; j < size.get(1); ++j) {
      for (let i = 0; i < size.get(0); ++i) {
        const diff = Math.abs(image.getValue(i, j, k) - buffer[index]);
        if (diff !== 0) {
          diffs.push(diff);
        }
        const diffRescaled = Math.abs(
          image.getRescaledValue(i, j, k) - rsi.apply(buffer[index]));
        if (diffRescaled !== 0) {
          diffsRescaled.push(diffRescaled);
        }
        ++index;
      }
    }
  }

  // calculate stats if necessary
  let statsDiff = {min: 0, max: 0, mean: 0, stdDev: 0};
  if (diffs.length !== 0) {
    statsDiff = getStats(diffs);
  }
  let statsDiffRescaled = {min: 0, max: 0, mean: 0, stdDev: 0};
  if (diffsRescaled.length !== 0) {
    statsDiffRescaled = getStats(diffsRescaled);
  }

  // return stats
  return {
    valuesStats: statsDiff,
    rescaledStats: statsDiffRescaled
  };
}

/**
 * Tests for {@link Image} getValue.
 *
 * @function module:tests/image~image-getvalue
 */
QUnit.test('Image getValue', function (assert) {
  const zeroStats = {min: 0, max: 0, mean: 0, stdDev: 0};

  // create a simple image
  const size0 = 4;
  const imgSize0 = new Size([size0, size0, 1]);
  const imgSpacing0 = new Spacing([1, 1, 1]);
  const imgOrigin0 = new Point3D(0, 0, 0);
  const imgGeometry0 = new Geometry(imgOrigin0, imgSize0, imgSpacing0);
  const buffer0 = [];
  for (let i = 0; i < size0 * size0; ++i) {
    buffer0[i] = i;
  }
  const image0 = new Image(imgGeometry0, buffer0);
  // test its geometry
  assert.equal(image0.getGeometry(), imgGeometry0, 'Image geometry');
  // test its values
  const rsi0 = new RescaleSlopeAndIntercept(1, 0);
  const res0 = compareImageAndBuffer(image0, imgSize0, buffer0, rsi0);
  assert.propEqual(
    res0.valuesStats,
    zeroStats,
    'Values should be equal');
  assert.propEqual(
    res0.rescaledStats,
    zeroStats,
    'Rescaled values should be equal');
  // outside value
  assert.equal(isNaN(image0.getValue(4, 3, 0)), true, 'Value outside is NaN');
  // TODO: wrong, should not be accessed
  assert.equal(image0.getValue(5, 0, 0), 1 * size0 + 1, 'Value at 5,0,0');
  // check range
  const theoRange0 = {min: 0, max: (size0 * size0) - 1};
  const imgRange00 = image0.getDataRange();
  assert.equal(imgRange00.max, theoRange0.max, 'Range max');
  assert.equal(imgRange00.min, theoRange0.min, 'Range min');
  const imgRange01 = image0.getRescaledDataRange();
  assert.equal(imgRange01.max, theoRange0.max, 'Rescaled range max');
  assert.equal(imgRange01.min, theoRange0.min, 'Rescaled range min');

  // image with rescale
  const image1 = new Image(imgGeometry0, buffer0);
  const slope1 = 2;
  const intercept1 = 10;
  const rsi1 = new RescaleSlopeAndIntercept(slope1, intercept1);
  image1.setRescaleSlopeAndIntercept(rsi1, new Index([0, 0, 0]));
  // test its geometry
  assert.equal(image1.getGeometry(), imgGeometry0, 'Image geometry');
  // test its values
  const res1 = compareImageAndBuffer(image1, imgSize0, buffer0, rsi1);
  assert.propEqual(
    res1.valuesStats,
    zeroStats,
    'Values should be equal');
  assert.propEqual(
    res1.rescaledStats,
    zeroStats,
    'Rescaled values should be equal');
  // check range
  const imgRange10 = image0.getDataRange();
  assert.equal(imgRange10.max, theoRange0.max, 'Range max');
  assert.equal(imgRange10.min, theoRange0.min, 'Range min');
  const theoRange1 = {
    min: theoRange0.min * slope1 + intercept1,
    max: theoRange0.max * slope1 + intercept1
  };
  const imgRange11 = image1.getRescaledDataRange();
  assert.equal(imgRange11.max, theoRange1.max, 'Rescaled range max');
  assert.equal(imgRange11.min, theoRange1.min, 'Rescaled range min');
});

/**
 * Tests for {@link Image} histogram.
 *
 * @function module:tests/image~image-histogram
 */
QUnit.test('Image histogram', function (assert) {
  // create a simple image
  const size0 = 4;
  const imgSize0 = new Size([size0, size0, 1]);
  const imgSpacing0 = new Spacing([1, 1, 1]);
  const imgOrigin0 = new Point3D(0, 0, 0);
  const imgGeometry0 = new Geometry(imgOrigin0, imgSize0, imgSpacing0);
  const buffer0 = [];
  for (let i = 0; i < size0 * size0; ++i) {
    buffer0[i] = i;
  }
  const image0 = new Image(imgGeometry0, buffer0);

  // histogram
  const histogram = image0.getHistogram();
  assert.equal(histogram.length, size0 * size0, 'histogram size');
  let histoContentTest = true;
  for (let j = 0; j < size0 * size0; ++j) {
    if (histogram[j][0] !== j) {
      histoContentTest = false;
      break;
    }
    if (histogram[j][1] !== 1) {
      histoContentTest = false;
      break;
    }
  }
  assert.equal(histoContentTest, true, 'histogram content');
});

/**
 * Tests for {@link Image} append.
 *
 * @function module:tests/image~image-append-slice
 */
QUnit.test('Image append slice', function (assert) {
  /**
   * Compare two arrays of vectors.
   *
   * @param {Point3D[]} arr0 The first array.
   * @param {Point3D[]} arr1 The second array.
   * @returns {boolean} True if both arrays are equal.
   */
  function compareArrayOfVectors(arr0, arr1) {
    return arr0.every(function (element, index) {
      return element.equals(arr1[index]);
    });
  }

  const size = 4;
  const imgSize = new Size([size, size, 2]);
  const imgSizeMinusOne = new Size([size, size, 1]);
  const imgSpacing = new Spacing([1, 1, 1]);
  const imgOrigin = new Point3D(0, 0, 0);

  // slice to append
  const sliceSize = new Size([size, size, 1]);
  const sliceBuffer = new Int16Array(sliceSize.getTotalSize());
  for (let i = 0; i < size * size; ++i) {
    sliceBuffer[i] = 2;
  }

  // image buffer
  const buffer = new Int16Array(imgSize.getTotalSize());
  for (let j = 0; j < size * size; ++j) {
    buffer[j] = 0;
  }
  for (let k = size * size; k < 2 * size * size; ++k) {
    buffer[k] = 1;
  }

  // image 0
  const imgGeometry0 = new Geometry(
    imgOrigin, imgSizeMinusOne, imgSpacing);
  imgGeometry0.appendOrigin(new Point3D(0, 0, 1), 1);
  const image0 = new Image(imgGeometry0, buffer, ['0']);
  image0.setMeta({numberOfFiles: 3});
  // append null
  assert.throws(function () {
    image0.appendSlice(null);
  }, new Error('Cannot append null slice'), 'append null slice');
  // real slice
  const sliceOrigin = new Point3D(0, 0, -1);
  const sliceGeometry = new Geometry(
    sliceOrigin, sliceSize, imgSpacing);
  const slice0 = new Image(sliceGeometry, sliceBuffer, ['1']);
  slice0.setMeta({numberOfFiles: 3});
  // append slice before
  image0.appendSlice(slice0);
  // test its values
  assert.equal(image0.getValue(0, 0, 0), 2, 'Value at 0,0,0 (append before)');
  assert.equal(image0.getValue(3, 3, 0), 2, 'Value at 3,3,0 (append before)');
  assert.equal(image0.getValue(0, 0, 1), 0, 'Value at 0,0,1 (append before)');
  assert.equal(image0.getValue(3, 3, 1), 0, 'Value at 3,3,1 (append before)');
  assert.equal(image0.getValue(0, 0, 2), 1, 'Value at 0,0,2 (append before)');
  assert.equal(image0.getValue(3, 3, 2), 1, 'Value at 3,3,2 (append before)');
  // test its positions
  const sliceOrigins0 = [];
  sliceOrigins0[0] = new Point3D(0, 0, -1);
  sliceOrigins0[1] = new Point3D(0, 0, 0);
  sliceOrigins0[2] = new Point3D(0, 0, 1);
  assert.ok(
    compareArrayOfVectors(imgGeometry0.getOrigins(), sliceOrigins0),
    'Slice positions (append before)');

  // image 1
  const imgGeometry1 = new Geometry(
    imgOrigin, imgSizeMinusOne, imgSpacing);
  imgGeometry1.appendOrigin(new Point3D(0, 0, 1), 1);
  const image1 = new Image(imgGeometry1, buffer, ['0']);
  image1.setMeta({numberOfFiles: 3});
  const sliceOrigin1 = new Point3D(0, 0, 2);
  const sliceGeometry1 = new Geometry(
    sliceOrigin1, sliceSize, imgSpacing);
  const slice1 = new Image(sliceGeometry1, sliceBuffer, ['1']);
  slice1.setMeta({numberOfFiles: 3});
  // append slice before
  image1.appendSlice(slice1);
  // test its values
  assert.equal(image1.getValue(0, 0, 0), 0, 'Value at 0,0,0 (append after)');
  assert.equal(image1.getValue(3, 3, 0), 0, 'Value at 3,3,0 (append after)');
  assert.equal(image1.getValue(0, 0, 1), 1, 'Value at 0,0,1 (append after)');
  assert.equal(image1.getValue(3, 3, 1), 1, 'Value at 3,3,1 (append after)');
  assert.equal(image1.getValue(0, 0, 2), 2, 'Value at 0,0,2 (append after)');
  assert.equal(image1.getValue(3, 3, 2), 2, 'Value at 3,3,2 (append after)');
  // test its positions
  const sliceOrigins1 = [];
  sliceOrigins1[0] = new Point3D(0, 0, 0);
  sliceOrigins1[1] = new Point3D(0, 0, 1);
  sliceOrigins1[2] = new Point3D(0, 0, 2);
  assert.ok(
    compareArrayOfVectors(imgGeometry1.getOrigins(), sliceOrigins1),
    'Slice positions (append after)');

  // image 2
  const imgGeometry2 = new Geometry(
    imgOrigin, imgSizeMinusOne, imgSpacing);
  imgGeometry2.appendOrigin(new Point3D(0, 0, 1), 1);
  const image2 = new Image(imgGeometry2, buffer, ['0']);
  image2.setMeta({numberOfFiles: 3});
  const sliceOrigin2 = new Point3D(0, 0, 0.4);
  const sliceGeometry2 = new Geometry(
    sliceOrigin2, sliceSize, imgSpacing);
  const slice2 = new Image(sliceGeometry2, sliceBuffer, ['1']);
  slice2.setMeta({numberOfFiles: 3});
  // append slice before
  image2.appendSlice(slice2);
  // test its values
  assert.equal(image2.getValue(0, 0, 0), 0, 'Value at 0,0,0 (append between)');
  assert.equal(image2.getValue(3, 3, 0), 0, 'Value at 3,3,0 (append between)');
  assert.equal(image2.getValue(0, 0, 1), 2, 'Value at 0,0,1 (append between)');
  assert.equal(image2.getValue(3, 3, 1), 2, 'Value at 3,3,1 (append between)');
  assert.equal(image2.getValue(0, 0, 2), 1, 'Value at 0,0,2 (append between)');
  assert.equal(image2.getValue(3, 3, 2), 1, 'Value at 3,3,2 (append between)');
  // test its positions
  const sliceOrigins2 = [];
  sliceOrigins2[0] = new Point3D(0, 0, 0);
  sliceOrigins2[1] = new Point3D(0, 0, 0.4);
  sliceOrigins2[2] = new Point3D(0, 0, 1);
  assert.ok(
    compareArrayOfVectors(imgGeometry2.getOrigins(), sliceOrigins2),
    'Slice positions (append between)');
});

/**
 * Tests for {@link Image} convolute2D.
 *
 * @function module:tests/image~image-convolute2D
 */
QUnit.test('Image convolute2D', function (assert) {
  // create a simple image
  const size0 = 3;
  const imgSize0 = new Size([size0, size0, 1]);
  const imgSpacing0 = new Spacing([1, 1, 1]);
  const imgOrigin0 = new Point3D(0, 0, 0);
  const imgGeometry0 = new Geometry(imgOrigin0, imgSize0, imgSpacing0);
  const buffer0 = [];
  for (let i = 0; i < size0 * size0; ++i) {
    buffer0[i] = i;
  }
  const image0 = new Image(imgGeometry0, buffer0);
  // id convolution
  const weights0 = [0, 0, 0, 0, 1, 0, 0, 0, 0];
  const resImage0 = image0.convolute2D(weights0);
  let testContent0 = true;
  for (let i = 0; i < size0 * size0; ++i) {
    if (image0.getValueAtOffset(i, 0) !== resImage0.getValueAtOffset(i, 0)) {
      testContent0 = false;
      break;
    }
  }
  assert.equal(testContent0, true, 'convolute2D id');
  // blur convolution
  const weights1 = [1, 1, 1, 1, 1, 1, 1, 1, 1];
  const resImage1 = image0.convolute2D(weights1);
  const theoResImage1 = [12, 18, 24, 30, 36, 42, 48, 54, 60];
  let testContent1 = true;
  for (let i = 0; i < size0 * size0; ++i) {
    if (theoResImage1[i] !== resImage1.getValueAtOffset(i, 0)) {
      testContent1 = false;
      break;
    }
  }
  assert.equal(testContent1, true, 'convolute2D blur');
});

/**
 * Tests for {@link Image} transform.
 *
 * @function module:tests/image~image-transform
 */
QUnit.test('Image transform', function (assert) {
  // create a simple image
  const size0 = 3;
  const imgSize0 = new Size([size0, size0, 1]);
  const imgSpacing0 = new Spacing([1, 1, 1]);
  const imgOrigin0 = new Point3D(0, 0, 0);
  const imgGeometry0 = new Geometry(imgOrigin0, imgSize0, imgSpacing0);
  const buffer0 = [];
  for (let i = 0; i < size0 * size0; ++i) {
    buffer0[i] = i;
  }
  let image0 = new Image(imgGeometry0, buffer0);

  // treshold function
  const func0 = function (value) {
    if (value < 3 || value > 5) {
      return 0;
    } else {
      return value;
    }
  };
  const resImage0 = image0.transform(func0);
  const theoResImage0 = [0, 0, 0, 3, 4, 5, 0, 0, 0];
  let testContent0 = true;
  for (let i = 0; i < size0 * size0; ++i) {
    if (theoResImage0[i] !== resImage0.getValueAtOffset(i, 0)) {
      testContent0 = false;
      break;
    }
  }
  assert.equal(testContent0, true, 'transform threshold');

  // new image
  image0 = new Image(imgGeometry0, buffer0);

  // multiply function
  const func1 = function (value) {
    return value * 2;
  };
  const resImage1 = image0.transform(func1);
  const theoResImage1 = [0, 2, 4, 6, 8, 10, 12, 14, 16];
  let testContent1 = true;
  for (let i = 0; i < size0 * size0; ++i) {
    if (theoResImage1[i] !== resImage1.getValueAtOffset(i, 0)) {
      testContent1 = false;
      break;
    }
  }
  assert.equal(testContent1, true, 'transform multiply');
});

/**
 * Tests for {@link Image} compose.
 *
 * @function module:tests/image~image-compose
 */
QUnit.test('Image compose', function (assert) {
  // create two simple images
  const size0 = 3;
  const imgSize0 = new Size([size0, size0, 1]);
  const imgSpacing0 = new Spacing([1, 1, 1]);
  const imgOrigin0 = new Point3D(0, 0, 0);
  const imgGeometry0 = new Geometry(imgOrigin0, imgSize0, imgSpacing0);
  const buffer0 = [];
  for (let i = 0; i < size0 * size0; ++i) {
    buffer0[i] = i;
  }
  const image0 = new Image(imgGeometry0, buffer0);
  const buffer1 = [];
  for (let i = 0; i < size0 * size0; ++i) {
    buffer1[i] = i;
  }
  const image1 = new Image(imgGeometry0, buffer1);

  // addition function
  const func0 = function (a, b) {
    return a + b;
  };
  const resImage0 = image0.compose(image1, func0);
  const theoResImage0 = [0, 2, 4, 6, 8, 10, 12, 14, 16];
  let testContent0 = true;
  for (let i = 0; i < size0 * size0; ++i) {
    if (theoResImage0[i] !== resImage0.getValueAtOffset(i, 0)) {
      testContent0 = false;
      break;
    }
  }
  assert.equal(testContent0, true, 'compose addition');
});

/**
 * Tests for {@link ImageFactory}.
 *
 * @function module:tests/image~imageFactory
 */
QUnit.test('ImageFactory', function (assert) {
  const zeroStats = {min: 0, max: 0, mean: 0, stdDev: 0};

  const size0 = 3;
  const imgSize0 = new Size([size0, size0, 1]);
  const imgSpacing0 = new Spacing([1, 1, 1]);
  const imgOrigin0 = new Point3D(0, 0, 0);
  const imgGeometry0 = new Geometry(imgOrigin0, imgSize0, imgSpacing0);
  const buffer0 = [];
  for (let i = 0; i < size0 * size0; ++i) {
    buffer0[i] = i;
  }
  const rsi0 = new RescaleSlopeAndIntercept(1, 0);

  const dicomElements0 = [];
  // columns
  dicomElements0['00280011'] = {value: [imgSize0.get(0)]};
  // rows
  dicomElements0['00280010'] = {value: [imgSize0.get(1)]};
  // spacing
  dicomElements0['00280030'] = {
    value: [imgSpacing0.get(1), imgSpacing0.get(2)]
  };
  // transfer syntax (explicit VR)
  dicomElements0['00020010'] = {value: ['1.2.840.10008.1.2.1']};
  // SOP class UID
  dicomElements0['00080016'] = {value: ['1.2.840.10008.5.1.4.1.1.4']};
  // modality
  dicomElements0['00080060'] = {value: ['MR']};
  // SOP instance UID
  dicomElements0['00080018'] = {value: ['1.2.840.34.56.78999654.234]']};

  // create the image factory
  const factory0 = new ImageFactory();
  // create the image
  const image0 = factory0.create(dicomElements0, buffer0);

  // test its geometry
  assert.ok(image0.getGeometry().equals(imgGeometry0), 'Image geometry');
  // test its values
  const res0 = compareImageAndBuffer(image0, imgSize0, buffer0, rsi0);
  assert.propEqual(
    res0.valuesStats,
    zeroStats,
    'Values should be equal');
  assert.propEqual(
    res0.rescaledStats,
    zeroStats,
    'Rescaled values should be equal');
});

/**
 * Tests for {@link Image} hasValues and getOffsets.
 *
 * @function module:tests/image~hasValues-getOffsets
 */
QUnit.test('hasValues getOffsets', function (assert) {
  const size0 = 3;
  const imgSize0 = new Size([size0, size0, 1]);
  const imgSpacing0 = new Spacing([1, 1, 1]);
  const imgOrigin0 = new Point3D(0, 0, 0);
  const imgGeometry0 = new Geometry(imgOrigin0, imgSize0, imgSpacing0);
  const buffer0 = [];
  buffer0[0] = 1;
  for (let i0 = 1; i0 < 2 * size0; ++i0) {
    buffer0[i0] = 0;
  }
  for (let i1 = 2 * size0; i1 < size0 * size0; ++i1) {
    buffer0[i1] = 1;
  }
  const theoOffset0 = [1, 2, 3, 4, 5];
  const theoOffset1 = [0, 6, 7, 8];

  // create the image
  const image0 = new Image(imgGeometry0, buffer0);

  // test hasValues
  assert.ok(
    arrayEquals(image0.hasValues([0]), [true]),
    'Image has values 0'
  );
  assert.ok(
    arrayEquals(image0.hasValues([1]), [true]),
    'Image has values 1'
  );
  assert.ok(
    arrayEquals(image0.hasValues([2]), [false]),
    'Image has values 2'
  );
  assert.ok(
    arrayEquals(image0.hasValues([0, 1]), [true, true]),
    'Image has values 0,1'
  );
  assert.ok(
    arrayEquals(image0.hasValues([0, 2]), [true, false]),
    'Image has values 0,2'
  );
  assert.ok(
    arrayEquals(image0.hasValues([2, 0]), [false, true]),
    'Image has values 2,0'
  );
  assert.ok(
    arrayEquals(image0.hasValues([0, 2, 1]), [true, false, true]),
    'Image has values 0,2,1'
  );
  assert.ok(
    arrayEquals(image0.hasValues([2, 1, 0]), [false, true, true]),
    'Image has values 2,1,0'
  );

  // test offsets list
  const off00 = image0.getOffsets(0);
  const off01 = image0.getOffsets(1);
  assert.ok(arrayEquals(off00, theoOffset0), 'Image offsets 0');
  assert.ok(arrayEquals(off01, theoOffset1), 'Image offsets 1');
});