tests_image_view.test.js

import {describe, test, assert} from 'vitest';
import {Point3D} from '../../src/math/point.js';
import {Size} from '../../src/image/size.js';
import {Spacing} from '../../src/image/spacing.js';
import {Geometry} from '../../src/image/geometry.js';
import {Image} from '../../src/image/image.js';
import {View} from '../../src/image/view.js';
import {RescaleSlopeAndIntercept} from '../../src/image/rsi.js';
import {WindowLevel} from '../../src/image/windowLevel.js';

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

describe('image', () => {

  /**
   * Tests for {@link View} listeners.
   *
   * @function module:tests/image~view-wlchange-event
   */
  test('View wlchange event - #DWV-REQ-UI-03-001 Change image window/level',
    () => {
      // create an 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);
      image0.setMeta({BitsStored: 8});
      // create a view
      const view0 = new View(image0);

      // listeners
      const listener1 = function (event) {
        assert.equal(event.wc, 0, 'Expected call to listener1.');
      };
      const listener2 = function (event) {
        assert.equal(event.ww, 1, 'Expected call to listener2.');
      };
        // with two listeners
      view0.addEventListener('wlchange', listener1);
      view0.addEventListener('wlchange', listener2);
      view0.setWindowLevel(new WindowLevel(0, 1));
      // without listener2
      view0.removeEventListener('wlchange', listener2);
      view0.setWindowLevel(new WindowLevel(0, 2));
      // without listener1
      view0.removeEventListener('wlchange', listener1);
      view0.setWindowLevel(new WindowLevel(1, 1));
    }
  );

  /**
   * Tests for {@link View} getImage meta.
   *
   * @function module:tests/image~playback-milliseconds
   */
  test('Playback milliseconds', () => {
    // create an 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);
    image0.setMeta({RecommendedDisplayFrameRate: 20});

    // create a view
    const view0 = new View(image0);

    // get frame rate from meta
    const recommendedDisplayFrameRate =
      view0.getImage().getMeta().RecommendedDisplayFrameRate;

    assert.equal(recommendedDisplayFrameRate, 20, 'check image meta');

    // get milliseconds per frame from frame rate
    const milliseconds =
      view0.getPlaybackMilliseconds(recommendedDisplayFrameRate);

    assert.equal(milliseconds, 50, 'check view getPlaybackMilliseconds');

    // get default milliseconds if no frame rate provided
    const defaultMilliseconds = view0.getPlaybackMilliseconds(null);

    // default to 10 fps
    assert.equal(defaultMilliseconds, 100,
      'check view getPlaybackMilliseconds');
  });

  /**
   * Tests for {@link View} generateImageData MONO.
   *
   * @function module:tests/image~generate-data-mono
   */
  test('Generate data MONO - #DWV-REQ-UI-02-001 Display image',
    () => {
      // create an image
      const size0 = 2;
      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);
      image0.setMeta({BitsStored: 8});
      // create a view
      const view0 = new View(image0);
      // create the image data
      const imageData = {
        width: size0,
        height: size0,
        data: new Uint8ClampedArray(size0 * size0 * 4)
      };

      // default window level
      view0.setWindowLevelMinMax();
      // call generate data
      view0.generateImageData(imageData);
      // TODO proper data?
      const theoData0 = [0,
        0,
        0,
        255,
        128,
        128,
        128,
        255,
        255,
        255,
        255,
        255,
        255,
        255,
        255,
        255];
      let testContent0 = true;
      for (let i = 0; i < size0 * size0 * 4; ++i) {
        if (theoData0[i] !== imageData.data[i]) {
          testContent0 = false;
          break;
        }
      }
      assert.equal(testContent0, true, 'check image data');
    }
  );

  /**
   * Tests for {@link View} generateImageData MONO with RSI.
   *
   * @function module:tests/image~generate-data-mono-with-rsi
   */
  test('Generate data MONO with RSI - #DWV-REQ-UI-02-001 Display image',
    () => {
      // create an image
      const size0 = 2;
      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);
      image0.setMeta({BitsStored: 8});
      image0.setRescaleSlopeAndIntercept(new RescaleSlopeAndIntercept(2, 0));
      // create a view
      const view0 = new View(image0);
      // create the image data
      const imageData = {
        width: size0,
        height: size0,
        data: new Uint8ClampedArray(size0 * size0 * 4)
      };

      // default window level
      view0.setWindowLevelMinMax();
      // call generate data
      view0.generateImageData(imageData);
      // TODO proper data?
      const theoData0 = [0,
        0,
        0,
        255,
        102,
        102,
        102,
        255,
        204,
        204,
        204,
        255,
        255,
        255,
        255,
        255];
      let testContent0 = true;
      for (let i = 0; i < size0 * size0 * 4; ++i) {
        if (theoData0[i] !== imageData.data[i]) {
          console.log(i, theoData0[i], imageData.data[i]);
          testContent0 = false;
          break;
        }
      }
      assert.equal(testContent0, true, 'check image data');
    }
  );

  /**
   * Tests for {@link View} generateImageData RGB.
   *
   * @function module:tests/image~generate-data-rgb
   */
  test('Generate data RGB - #DWV-REQ-UI-02-001 Display image',
    () => {
      // create an image
      const size0 = 2;
      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 = [];
      let index = 0;
      let value = 0;
      // 0, 85, 170, 255
      for (let i = 0; i < size0 * size0; ++i) {
        value = i * 255 / ((size0 * size0) - 1);
        buffer0[index] = value;
        buffer0[index + 1] = value;
        buffer0[index + 2] = value;
        index += 3;
      }
      const image0 = new Image(imgGeometry0, buffer0);
      image0.setPhotometricInterpretation('RGB');
      image0.setMeta({BitsStored: 8});
      // create a view
      const view0 = new View(image0);
      // create the image data
      const imageData = {
        width: size0,
        height: size0,
        data: new Uint8ClampedArray(size0 * size0 * 4)
      };

      // default window level
      view0.setWindowLevel(new WindowLevel(127, 255));
      // call generate data
      view0.generateImageData(imageData);
      // check data content
      const theoData0 = [0,
        0,
        0,
        255,
        85,
        85,
        85,
        255,
        170,
        170,
        170,
        255,
        255,
        255,
        255,
        255];
      let testContent0 = true;
      for (let i = 0; i < size0 * size0 * 4; ++i) {
        if (theoData0[i] !== imageData.data[i]) {
          console.log(theoData0[i], imageData.data[i]);
          testContent0 = false;
          break;
        }
      }
      assert.equal(testContent0, true, 'check image data non planar');

      const buffer1 = [];
      index = 0;
      // 0, 85, 170, 255
      for (let i = 0; i < 3; ++i) {
        buffer1[index] = 0;
        buffer1[index + 1] = 85;
        buffer1[index + 2] = 170;
        buffer1[index + 3] = 255;
        index += 4;
      }
      const image1 = new Image(imgGeometry0, buffer1);
      image1.setPhotometricInterpretation('RGB');
      image1.setPlanarConfiguration(1);
      image1.setMeta({BitsStored: 8});
      // create a view
      const view1 = new View(image1);

      // default window level
      view1.setWindowLevel(new WindowLevel(127, 255));
      // call generate data
      view1.generateImageData(imageData);
      // check data content
      let testContent1 = true;
      for (let i = 0; i < size0 * size0 * 4; ++i) {
        if (theoData0[i] !== imageData.data[i]) {
          console.log(theoData0[i], imageData.data[i]);
          testContent1 = false;
          break;
        }
      }
      assert.equal(testContent1, true, 'check image data planar');
    }
  );

  /**
   * Tests for {@link View} generateImageData timing.
   *
   * @function module:tests/image~generate-data-timing
   */
  test('Generate data timing - #DWV-REQ-UI-02-001 Display image',
    () => {
      // create an image
      const size0 = 128;
      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);
      image0.setMeta({BitsStored: 8});
      // create a view
      const view0 = new View(image0);
      // create the image data
      const imageData = {
        width: size0,
        height: size0,
        data: new Uint8Array(size0 * size0 * 4)
      };

      // default window level
      view0.setWindowLevelMinMax();

      // start time
      const start0 = new Date();
      // call generate data
      view0.generateImageData(imageData);
      // time taken
      const time0 = (new Date()) - start0;
      // check time taken
      assert.ok(time0 < 90, 'First generateImageData: ' + time0 + 'ms.');

      // Change the window level
      view0.setWindowLevel(new WindowLevel(4000, 200));

      // start time
      const start1 = (new Date()).getMilliseconds();
      // call generate data
      view0.generateImageData(imageData);
      // time taken
      const time1 = (new Date()).getMilliseconds() - start1;
      // check time taken
      assert.ok(time1 < 90, 'Second generateImageData: ' + time1 + 'ms.');
    }
  );

});