tests_dicom_dicomParser.test.js

import {
  cleanString,
  hasDicomPrefix,
  DicomParser,
  getOrientationName
} from '../../src/dicom/dicomParser';
import {getFileListFromDicomDir} from '../../src/dicom/dicomElementsWrapper';
import {b64urlToArrayBuffer} from './utils';

import dwvTestSimple from '../data/dwv-test-simple.dcm';
import dwvTestSequence from '../data/dwv-test-sequence.dcm';
import dwvDicomDir from '../data/DICOMDIR';

/**
 * Tests for the 'dicom/dicomParser.js' file.
 */

// Do not warn if these variables were not defined before.
/* global QUnit */
QUnit.module('dicom');

/**
 * Tests for {@link DicomParser} using simple DICOM data.
 * Using remote file for CI integration.
 *
 * @function module:tests/dicom~simple-dicom-parsing
 */
QUnit.test('Simple DICOM parsing - #DWV-REQ-IO-01-001 Load DICOM file(s)',
  function (assert) {
    const buffer = b64urlToArrayBuffer(dwvTestSimple);

    assert.ok(hasDicomPrefix(buffer), 'Response has DICOM prefix.');

    // parse DICOM
    const dicomParser = new DicomParser();
    dicomParser.parse(buffer);

    const numRows = 32;
    const numCols = 32;

    // raw tags
    const rawTags = dicomParser.getDicomElements();
    // check values
    assert.equal(rawTags['00280010'].value[0], numRows, 'Number of rows (raw)');
    assert.equal(
      rawTags['00280011'].value[0], numCols, 'Number of columns (raw)');
    // ReferencedImageSequence - ReferencedSOPInstanceUID
    assert.equal(rawTags['00081140'].value[0]['00081155'].value[0],
      '1.3.12.2.1107.5.2.32.35162.2012021515511672669154094',
      'ReferencedImageSequence SQ (raw)');

    // wrapped tags
    const tags = dicomParser.getDicomElements();
    // wrong key
    assert.ok(typeof tags['12345678'] === 'undefined',
      'Wrong key fails if test');
    // empty key
    assert.ok(typeof tags[''] === 'undefined',
      'Empty key fails if test');
    // good key
    assert.ok(typeof tags['00280010'] !== 'undefined',
      'Good key passes if test');

    // zero value (passes test since it is a string)
    assert.equal(tags['00181318'].value[0], 0, 'Good key, zero value');
    // check values
    assert.equal(tags['00280010'].value[0], numRows, 'Number of rows');
    assert.equal(tags['00280011'].value[0], numCols, 'Number of columns');
    // ReferencedImageSequence - ReferencedSOPInstanceUID
    // only one item value -> returns the object directly
    // (no need for tags["ReferencedImageSequence")[0])
    assert.equal(tags['00081140'].value[0]['00081155'].value[0],
      '1.3.12.2.1107.5.2.32.35162.2012021515511672669154094',
      'ReferencedImageSequence SQ');
  }
);

/**
 * Tests for {@link DicomParser} using sequence test DICOM data.
 * Using remote file for CI integration.
 *
 * @function module:tests/dicom~dicom-sequence-parsing
 */
QUnit.test('DICOM sequence parsing - #DWV-REQ-IO-01-001 Load DICOM file(s)',
  function (assert) {
    const buffer = b64urlToArrayBuffer(dwvTestSequence);

    assert.ok(hasDicomPrefix(buffer), 'Response has DICOM prefix.');

    // parse DICOM
    const dicomParser = new DicomParser();
    dicomParser.parse(buffer);
    // raw tags
    const tags = dicomParser.getDicomElements();
    assert.ok((Object.keys(tags).length !== 0), 'Got raw tags.');

    // ReferencedImageSequence: explicit sequence
    const seq00 = tags['00081140'].value;
    assert.equal(seq00.length, 3, 'ReferencedImageSequence length');
    assert.equal(seq00[0]['00081155'].value[0],
      '1.3.12.2.1107.5.2.32.35162.2012021515511672669154094',
      'ReferencedImageSequence - item0 - ReferencedSOPInstanceUID');
    assert.equal(seq00[1]['00081155'].value[0],
      '1.3.12.2.1107.5.2.32.35162.2012021515511286933854090',
      'ReferencedImageSequence - item1 - ReferencedSOPInstanceUID');

    // SourceImageSequence: implicit sequence
    const seq01 = tags['00082112'].value;
    assert.equal(seq01.length, 3, 'SourceImageSequence length');
    assert.equal(seq01[0]['00081155'].value[0],
      '1.3.12.2.1107.5.2.32.35162.2012021515511672669154094',
      'SourceImageSequence - item0 - ReferencedSOPInstanceUID');
    assert.equal(seq01[1]['00081155'].value[0],
      '1.3.12.2.1107.5.2.32.35162.2012021515511286933854090',
      'SourceImageSequence - item1 - ReferencedSOPInstanceUID');

    // ReferencedPatientSequence: explicit empty sequence
    const seq10 = tags['00081120'].value;
    assert.equal(seq10.length, 0, 'ReferencedPatientSequence length');

    // ReferencedOverlaySequence: implicit empty sequence
    const seq11 = tags['00081130'].value;
    assert.equal(seq11.length, 0, 'ReferencedOverlaySequence length');

    // ReferringPhysicianIdentificationSequence: explicit empty item
    const seq12 = tags['00080096'].value;
    assert.equal(seq12[0]['FFFEE000'].value.length, 0,
      'ReferringPhysicianIdentificationSequence item length');

    // ConsultingPhysicianIdentificationSequence: implicit empty item
    const seq13 = tags['0008009D'].value;
    assert.equal(seq13.length, 0,
      'ConsultingPhysicianIdentificationSequence item length');

    // ReferencedStudySequence: explicit sequence of sequence
    const seq20 = tags['00081110'].value;
    // just one element
    //assert.equal(seq20.length, 2, "ReferencedStudySequence length");
    assert.equal(seq20[0]['0040A170'].value[0]['00080100'].value[0],
      '123456',
      'ReferencedStudySequence - seq - item0 - CodeValue');

    // ReferencedSeriesSequence: implicit sequence of sequence
    const seq21 = tags['00081115'].value;
    // just one element
    //assert.equal(seq21.length, 2, "ReferencedSeriesSequence length");
    assert.equal(seq21[0]['0040A170'].value[0]['00080100'].value[0],
      '789101',
      'ReferencedSeriesSequence - seq - item0 - CodeValue');

    // ReferencedInstanceSequence: explicit empty sequence of sequence
    const seq30 = tags['0008114A'].value;
    assert.equal(seq30[0]['0040A170'].value.length, 0,
      'ReferencedInstanceSequence - seq - length');

    // ReferencedVisitSequence: implicit empty sequence of sequence
    const seq31 = tags['00081125'].value;
    assert.equal(seq31[0]['0040A170'].value.length, 0,
      'ReferencedVisitSequence - seq - length');
  }
);

/**
 * Tests for {@link cleanString}.
 *
 * @function module:tests/dicom~cleanstring
 */
QUnit.test('cleanString', function (assert) {
  // undefined
  assert.throws(function () {
    cleanString();
  },
  new TypeError('Cannot read properties of undefined (reading \'length\')'),
  'cleanstring undefined throws.');
  // null
  assert.throws(function () {
    cleanString(null);
  },
  new TypeError('Cannot read properties of null (reading \'length\')'),
  'cleanstring null throws.');
  // number
  assert.throws(function () {
    cleanString(3);
  },
  new TypeError('res.trim is not a function'),
  'cleanstring number throws.');
  // empty
  assert.equal(cleanString(''), '', 'Clean empty');
  // short
  assert.equal(cleanString('a'), 'a', 'Clean short');
  // special
  const special = String.fromCharCode('u200B');
  assert.equal(cleanString(special), '', 'Clean just special');
  // regular
  let str = ' El cielo azul ';
  let refStr = 'El cielo azul';
  assert.equal(cleanString(str), refStr, 'Clean regular');
  // regular with special
  str = ' El cielo azul' + special;
  refStr = 'El cielo azul';
  assert.equal(
    cleanString(str), refStr, 'Clean regular with special');
  // regular with special and ending space (not trimmed)
  str = ' El cielo azul ' + special;
  refStr = 'El cielo azul';
  assert.equal(
    cleanString(str), refStr, 'Clean regular with special 2');
});

/**
 * Tests for {@link DicomParser} using DICOMDIR data.
 * Using remote file for CI integration.
 *
 * @function module:tests/dicom~dicomdir-parsing
 */
QUnit.test('DICOMDIR parsing - #DWV-REQ-IO-02-004 Load DICOMDIR URL',
  function (assert) {
    // get the file list
    const list = getFileListFromDicomDir(
      b64urlToArrayBuffer(dwvDicomDir)
    );

    // check file list
    const nFilesSeries0Study0 = 23;
    const nFilesSeries1Study0 = 20;
    const nFilesSeries0Study1 = 1;
    assert.equal(list.length, 2, 'Number of study');
    assert.equal(list[0].length, 2, 'Number of series in first study');
    assert.equal(list[1].length, 1, 'Number of series in second study');
    assert.equal(
      list[0][0].length,
      nFilesSeries0Study0,
      'Study#0:Series#0 number of files');
    assert.equal(
      list[0][1].length,
      nFilesSeries1Study0,
      'Study#0:Series#1 number of files');
    assert.equal(
      list[1][0].length,
      nFilesSeries0Study1,
      'Study#1:Series#0 number of files'
    );

    // files
    const files00 = [];
    let iStart = 0;
    let iEnd = nFilesSeries0Study0;
    for (let i = iStart; i < iEnd; ++i) {
      files00.push('IMAGES/IM' + i);
    }
    assert.deepEqual(list[0][0], files00, 'Study#0:Series#0 file names');
    const files01 = [];
    iStart = iEnd;
    iEnd += nFilesSeries1Study0;
    for (let i = iStart; i < iEnd; ++i) {
      files01.push('IMAGES/IM' + i);
    }
    assert.deepEqual(list[0][0], files00, 'Study#0:Series#1 file names');
    const files10 = [];
    iStart = iEnd;
    iEnd += nFilesSeries0Study1;
    for (let i = iStart; i < iEnd; ++i) {
      files10.push('IMAGES/IM' + i);
    }
    assert.deepEqual(list[0][0], files00, 'Study#1:Series#0 file names');
  }
);

/**
 * Tests for {@link getOrientationName}.
 *
 * @function module:tests/dicom~getorientationname
 */
QUnit.test('getOrientationName', function (assert) {
  const test00 = [1, 0, 0, 0, 1, 0];
  assert.equal(getOrientationName(test00), 'axial', 'test axial #0');
  const test01 = [0.99, 0.02, 0.05, -0.02, 0.99, 1.4e-08];
  assert.equal(getOrientationName(test01), 'axial', 'test axial #1');

  const test10 = [1, 0, 0, 0, 0, -1];
  assert.equal(getOrientationName(test10), 'coronal', 'test coronal #0');
  const test11 = [0.7, 0.3, 0, 0, 0.4, -0.6];
  assert.equal(getOrientationName(test11), 'coronal', 'test coronal #1');

  const test20 = [0, 1, 0, 0, 0, -1];
  assert.equal(getOrientationName(test20), 'sagittal', 'test sagittal #0');
  const test21 = [-0.01, 0.98, -0.20, 0.05, -0.19, -0.98];
  assert.equal(getOrientationName(test21), 'sagittal', 'test axial #1');
});