import {describe, test, assert} from 'vitest';
import {Point3D} from '../../src/math/point.js';
import {Index} from '../../src/math/index.js';
import {getStats} from '../../src/math/stats.js';
import {arrayEquals} from '../../src/utils/array.js';
import {Size} from '../../src/image/size.js';
import {Spacing} from '../../src/image/spacing.js';
import {Geometry} from '../../src/image/geometry.js';
import {RescaleSlopeAndIntercept} from '../../src/image/rsi.js';
import {Image} from '../../src/image/image.js';
import {ImageFactory} from '../../src/image/imageFactory.js';
import {Matrix33} from '../../src/math/matrix.js';

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

describe('image', () => {

  /**
   * Compare an image and a buffer.
   *
   * @param {Image} image The input image.
   * @param {Size} 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
   */
  test('Image getValue', () => {
    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.deepEqual(
      res0.valuesStats,
      zeroStats,
      'Values should be equal');
    assert.deepEqual(
      res0.rescaledStats,
      zeroStats,
      'Rescaled values should be equal');
    // outside value
    assert.equal(isNaN(image0.getValue(4, 3, 0)), true,
      'Value outside is NaN');
    assert.equal(isNaN(image0.getValue(5, 0, 0)), true,
      'Value at 5,0,0 is NaN');
    // 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.deepEqual(
      res1.valuesStats,
      zeroStats,
      'Values should be equal');
    assert.deepEqual(
      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
   */
  test('Image histogram', () => {
    // 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
   */
  test('Image append slice', () => {
    /**
     * 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);
    },
    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
   */
  test('Image convolute2D', () => {
    // 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
   */
  test('Image transform', () => {
    // 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
   */
  test('Image compose', () => {
    // 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
   */
  test('ImageFactory', () => {
    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.deepEqual(
      res0.valuesStats,
      zeroStats,
      'Values should be equal');
    assert.deepEqual(
      res0.rescaledStats,
      zeroStats,
      'Rescaled values should be equal');
  });

  /**
   * Tests for {@link Image} hasValues and getOffsets.
   *
   * @function module:tests/image~hasvalues-getoffsets
   */
  test('hasValues getOffsets', () => {
    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');
  });

  /**
   * Tests for {@link Image} getContourDistance.
   *
   * @function module:tests/image~image-getcontourdistance
   */
  test('Image getContourDistance', () => {

    const imgOrigins = new Point3D(0, 0, 0);
    const imgSpacing = new Spacing([1, 1, 1]);
    const imgSize0 = new Size([6, 6, 3]);
    const imgGeometry0 =
      new Geometry(
        imgOrigins,
        imgSize0,
        imgSpacing
      );
    /* eslint-disable @stylistic/js/array-element-newline */
    const imgBuffer0 = new Uint8Array([
      0, 0, 0, 0, 0, 0,
      0, 0, 1, 0, 1, 0,
      0, 0, 1, 0, 1, 0,
      0, 0, 0, 0, 1, 0,
      0, 0, 0, 0, 1, 0,
      0, 0, 0, 0, 0, 0,

      0, 0, 0, 0, 0, 0,
      1, 1, 1, 1, 1, 0,
      1, 1, 1, 1, 1, 0,
      1, 1, 1, 1, 1, 0,
      1, 1, 1, 1, 1, 0,
      1, 1, 1, 1, 1, 0,

      0, 0, 0, 0, 0, 0,
      0, 1, 1, 1, 1, 0,
      0, 1, 1, 1, 1, 0,
      0, 0, 0, 1, 1, 0,
      0, 0, 0, 1, 1, 0,
      0, 0, 0, 0, 0, 0,
    ]);
    /* eslint-enable @stylistic/js/array-element-newline */

    const image0 = new Image(imgGeometry0, imgBuffer0);

    image0.initializeContour();

    /* eslint-disable @stylistic/js/array-element-newline */
    const o00 = [
      1, 0, 0,
      0, 1, 0,
      0, 0, 1,
    ];
    /* eslint-enable @stylistic/js/array-element-newline */
    const orientation00 = new Matrix33(o00);

    const distance00 = image0.getContourDistance(57, orientation00);
    assert.equal(
      distance00,
      2,
      'Expected distance from contour 0'
    );

    const distance01 = image0.getContourDistance(52, orientation00);
    assert.equal(
      distance01,
      1,
      'Expected distance from contour 1'
    );

    /* eslint-disable @stylistic/js/array-element-newline */
    const o01 = [
      1, 0, 0,
      0, 0, 1,
      0, 1, 0,
    ];
    /* eslint-enable @stylistic/js/array-element-newline */
    const orientation01 = new Matrix33(o01);

    const distance02 = image0.getContourDistance(52, orientation01);
    assert.equal(
      distance02,
      1,
      'Expected distance from contour 2'
    );

    const distance03 = image0.getContourDistance(50, orientation01);
    assert.equal(
      distance03,
      1,
      'Expected distance from contour 3'
    );
  });

  /**
   * Tests for {@link Image} clone.
   *
   * @function module:tests/image~clone
   */
  test('clone', () => {
    const size00 = 3;
    const imgSize00 = new Size([size00, size00, 1]);
    const imgSpacing00 = new Spacing([1, 1, 1]);
    const imgOrigin00 = new Point3D(0, 0, 0);
    const imgGeometry00 = new Geometry([imgOrigin00], imgSize00, imgSpacing00);
    const buffer00 = [];
    buffer00[0] = 1;
    for (let i0 = 1; i0 < 2 * size00; ++i0) {
      buffer00[i0] = 0;
    }
    for (let i1 = 2 * size00; i1 < size00 * size00; ++i1) {
      buffer00[i1] = 1;
    }

    // original image
    const image0 = new Image(imgGeometry00, buffer00);
    const modality0 = 'MR';
    image0.getMeta().Modality = modality0;
    // clone image
    const image1 = image0.clone();

    // retrieve geometries
    const geometry0 = image0.getGeometry();
    const geometry1 = image1.getGeometry();

    // test size #0
    assert.ok(geometry0.getSize().equals(imgSize00), 'Image size #0');
    assert.ok(geometry1.getSize().equals(imgSize00), 'Clone image size #0');

    // append origin, will increase size
    geometry1.appendOrigin(new Point3D(0, 0, 1));
    // new theoretical size
    const theoSize1 = new Size([size00, size00, imgSize00.get(2) + 1]);

    // test size #1
    assert.ok(geometry0.getSize().equals(imgSize00), 'Image size #1');
    assert.ok(geometry1.getSize().equals(theoSize1), 'Clone image size #1');

    // test modality #0
    assert.equal(image0.getMeta().Modality, modality0, 'Image modality #0');
    assert.equal(image1.getMeta().Modality, modality0,
      'Clone image modality #0');

    // change cloned image modality
    const modality1 = 'CT';
    image1.getMeta().Modality = modality1;

    // test modality #1
    assert.equal(image0.getMeta().Modality, modality0, 'Image modality #1');
    assert.equal(image1.getMeta().Modality, modality1,
      'Clone image modality #1');
  });

});
