tests_dicom_dicomPet.test.js

import {describe, test, assert} from 'vitest';
import {getSuvFactor} from '../../src/dicom/dicomPet.js';
import {DataElement} from '../../src/dicom/dataElement.js';

/**
 * Related DICOM tag keys.
 */
const TagKeys = {
  SeriesDate: '00080021',
  AcquisitionDate: '00080022',
  SeriesTime: '00080031',
  AcquisitionTime: '00080032',
  RadiopharmaceuticalInformationSequence: '00540016',
  RadionuclideTotalDose: '00181074',
  RadionuclideHalfLife: '00181075',
  RadiopharmaceuticalStartDateTime: '00181078',
  RadiopharmaceuticalStartTime: '00181072',
  FrameReferenceTime: '00541300',
  ActualFrameDuration: '00181242',
  CorrectedImage: '00280051',
  DecayCorrection: '00541102',
  Units: '00541001',
  PatientWeight: '00101030',
  CodeMeaning: '00080104',
  CodeValue: '00080100',
  CodingSchemeDesignator: '00080102'
};

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

describe('dicom', () => {

  describe('getSuvFactor', () => {

    /**
     * Tests for {@link getSuvFactor} without corrected image.
     *
     * @function module:tests/dicom~getsuvfactor-no-corrected
     */
    test('no corrected', () => {
      const deSeriesDate = new DataElement('DA');
      deSeriesDate.value = ['20231215'];

      const deSeriesTime = new DataElement('TM');
      deSeriesTime.value = ['120000'];

      const elements = {
        [TagKeys.SeriesDate]: deSeriesDate,
        [TagKeys.SeriesTime]: deSeriesTime
      };

      const result = getSuvFactor(elements);

      assert.ok(result.warning);
      assert.isUndefined(result.value);
      assert.include(result.warning, 'Corrected Image');
    });

    /**
     * Tests for {@link getSuvFactor} without decay correction.
     *
     * @function module:tests/dicom~getsuvfactor-no-decay
     */
    test('no decay', () => {
      const deCorrectImage = new DataElement('CS');
      deCorrectImage.value = ['ATTN', 'DECY'];

      const deSeriesDate = new DataElement('DA');
      deSeriesDate.value = ['20231215'];

      const deSeriesTime = new DataElement('TM');
      deSeriesTime.value = ['120000'];

      const elements = {
        [TagKeys.CorrectedImage]: deCorrectImage,
        [TagKeys.SeriesDate]: deSeriesDate,
        [TagKeys.SeriesTime]: deSeriesTime
      };

      const result = getSuvFactor(elements);

      assert.ok(result.warning);
      assert.isUndefined(result.value);
      assert.include(result.warning, 'Decay Correction');
    });

    /**
     * Tests for {@link getSuvFactor} without Units.
     *
     * @function module:tests/dicom~getsuvfactor-no-units
     */
    test('no units', () => {
      const deCorrectImage = new DataElement('CS');
      deCorrectImage.value = ['ATTN', 'DECY'];

      const deDecayCorr = new DataElement('CS');
      deDecayCorr.value = ['START'];

      const deSeriesDate = new DataElement('DA');
      deSeriesDate.value = ['20231215'];

      const deSeriesTime = new DataElement('TM');
      deSeriesTime.value = ['120000'];

      const elements = {
        [TagKeys.CorrectedImage]: deCorrectImage,
        [TagKeys.DecayCorrection]: deDecayCorr,
        [TagKeys.SeriesDate]: deSeriesDate,
        [TagKeys.SeriesTime]: deSeriesTime
      };

      const result = getSuvFactor(elements);

      assert.ok(result.warning);
      assert.isUndefined(result.value);
      assert.include(result.warning, 'Units');
    });

    /**
     * Tests for {@link getSuvFactor} without PatientWeight.
     *
     * @function module:tests/dicom~getsuvfactor-no-weight
     */
    test('no weight', () => {
      const deCorrectImage = new DataElement('CS');
      deCorrectImage.value = ['ATTN', 'DECY'];

      const deDecayCorr = new DataElement('CS');
      deDecayCorr.value = ['START'];

      const deUnits = new DataElement('CS');
      deUnits.value = ['BQML'];

      const deSeriesDate = new DataElement('DA');
      deSeriesDate.value = ['20231215'];

      const deSeriesTime = new DataElement('TM');
      deSeriesTime.value = ['120000'];

      const elements = {
        [TagKeys.CorrectedImage]: deCorrectImage,
        [TagKeys.DecayCorrection]: deDecayCorr,
        [TagKeys.Units]: deUnits,
        [TagKeys.SeriesDate]: deSeriesDate,
        [TagKeys.SeriesTime]: deSeriesTime
      };

      const result = getSuvFactor(elements);

      assert.ok(result.warning);
      assert.isUndefined(result.value);
      assert.include(result.warning, 'PatientWeight');
    });

    /**
     * Tests for {@link getSuvFactor} without
     * RadiopharmaceuticalInformationSequence.
     *
     * @function module:tests/dicom~getsuvfactor-no-radio
     */
    test('no radio', () => {
      const deCorrectImage = new DataElement('CS');
      deCorrectImage.value = ['ATTN', 'DECY'];

      const deDecayCorr = new DataElement('CS');
      deDecayCorr.value = ['START'];

      const deUnits = new DataElement('CS');
      deUnits.value = ['BQML'];

      const dePatWeight = new DataElement('DS');
      dePatWeight.value = ['70.5'];

      const deSeriesDate = new DataElement('DA');
      deSeriesDate.value = ['20231215'];

      const deSeriesTime = new DataElement('TM');
      deSeriesTime.value = ['120000'];

      const elements = {
        [TagKeys.CorrectedImage]: deCorrectImage,
        [TagKeys.DecayCorrection]: deDecayCorr,
        [TagKeys.Units]: deUnits,
        [TagKeys.PatientWeight]: dePatWeight,
        [TagKeys.SeriesDate]: deSeriesDate,
        [TagKeys.SeriesTime]: deSeriesTime
      };

      const result = getSuvFactor(elements);

      assert.ok(result.warning);
      assert.isUndefined(result.value);
      assert.include(result.warning,
        'RadiopharmaceuticalInformationSequence');
    });

    /**
     * Tests for {@link getSuvFactor} without RadionuclideTotalDose.
     *
     * @function module:tests/dicom~getsuvfactor-no-total-dose
     */
    test('no total dose', () => {
      const deCorrectImage = new DataElement('CS');
      deCorrectImage.value = ['ATTN', 'DECY'];

      const deDecayCorr = new DataElement('CS');
      deDecayCorr.value = ['START'];

      const deUnits = new DataElement('CS');
      deUnits.value = ['BQML'];

      const dePatWeight = new DataElement('DS');
      dePatWeight.value = ['70.5'];

      const deSeriesDate = new DataElement('DA');
      deSeriesDate.value = ['20231215'];

      const deSeriesTime = new DataElement('TM');
      deSeriesTime.value = ['120000'];

      const radioInfoSq = new DataElement('SQ');
      radioInfoSq.value = [{}];

      const elements = {
        [TagKeys.CorrectedImage]: deCorrectImage,
        [TagKeys.DecayCorrection]: deDecayCorr,
        [TagKeys.Units]: deUnits,
        [TagKeys.PatientWeight]: dePatWeight,
        [TagKeys.SeriesDate]: deSeriesDate,
        [TagKeys.SeriesTime]: deSeriesTime,
        [TagKeys.RadiopharmaceuticalInformationSequence]: radioInfoSq
      };

      const result = getSuvFactor(elements);

      assert.ok(result.warning);
      assert.isUndefined(result.value);
      assert.include(result.warning, 'RadionuclideTotalDose');
    });

    /**
     * Tests for {@link getSuvFactor} without RadionuclideHalfLife.
     *
     * @function module:tests/dicom~getsuvfactor-no-half-life
     */
    test('no half life', () => {
      const deCorrectImage = new DataElement('CS');
      deCorrectImage.value = ['ATTN', 'DECY'];

      const deDecayCorr = new DataElement('CS');
      deDecayCorr.value = ['START'];

      const deUnits = new DataElement('CS');
      deUnits.value = ['BQML'];

      const dePatWeight = new DataElement('DS');
      dePatWeight.value = ['70.5'];

      const deSeriesDate = new DataElement('DA');
      deSeriesDate.value = ['20231215'];

      const deSeriesTime = new DataElement('TM');
      deSeriesTime.value = ['120000'];

      const deTotalDose = new DataElement('DS');
      deTotalDose.value = ['400000000'];

      const radioInfoSq = new DataElement('SQ');
      radioInfoSq.value = [{
        [TagKeys.RadionuclideTotalDose]: deTotalDose
      }];

      const elements = {
        [TagKeys.CorrectedImage]: deCorrectImage,
        [TagKeys.DecayCorrection]: deDecayCorr,
        [TagKeys.Units]: deUnits,
        [TagKeys.PatientWeight]: dePatWeight,
        [TagKeys.SeriesDate]: deSeriesDate,
        [TagKeys.SeriesTime]: deSeriesTime,
        [TagKeys.RadiopharmaceuticalInformationSequence]: radioInfoSq
      };

      const result = getSuvFactor(elements);

      assert.ok(result.warning);
      assert.isUndefined(result.value);
      assert.include(result.warning, 'RadionuclideHalfLife');
    });

    /**
     * Tests for {@link getSuvFactor}.
     *
     * @function module:tests/dicom~getsuvfactor-good-input
     */
    test('good input', () => {
      const deCorrectImage = new DataElement('CS');
      deCorrectImage.value = ['ATTN', 'DECY'];

      const deDecayCorr = new DataElement('CS');
      deDecayCorr.value = ['START'];

      const deUnits = new DataElement('CS');
      deUnits.value = ['BQML'];

      const dePatWeight = new DataElement('DS');
      dePatWeight.value = ['70'];

      const deSeriesDate = new DataElement('DA');
      deSeriesDate.value = ['20231215'];

      const deSeriesTime = new DataElement('TM');
      deSeriesTime.value = ['120000'];

      const deTotalDose = new DataElement('DS');
      deTotalDose.value = ['400000000'];

      const deHalfLife = new DataElement('DS');
      deHalfLife.value = ['6588'];

      const radioInfoSq = new DataElement('SQ');
      radioInfoSq.value = [{
        [TagKeys.RadionuclideTotalDose]: deTotalDose,
        [TagKeys.RadionuclideHalfLife]: deHalfLife
      }];

      const elements = {
        [TagKeys.CorrectedImage]: deCorrectImage,
        [TagKeys.DecayCorrection]: deDecayCorr,
        [TagKeys.Units]: deUnits,
        [TagKeys.PatientWeight]: dePatWeight,
        [TagKeys.SeriesDate]: deSeriesDate,
        [TagKeys.SeriesTime]: deSeriesTime,
        [TagKeys.RadiopharmaceuticalInformationSequence]: radioInfoSq
      };

      const result = getSuvFactor(elements);

      assert.isUndefined(result.warning);
      assert.ok(typeof result.value === 'number');
      assert.ok(result.value > 0);
    });

    /**
     * Tests for {@link getSuvFactor} with
     * RadiopharmaceuticalStartDateTime.
     *
     * @function module:tests/dicom~getsuvfactor-radio-start-date
     */
    test('radio start date', () => {
      const deCorrectImage = new DataElement('CS');
      deCorrectImage.value = ['ATTN', 'DECY'];

      const deDecayCorr = new DataElement('CS');
      deDecayCorr.value = ['START'];

      const deUnits = new DataElement('CS');
      deUnits.value = ['BQML'];

      const dePatWeight = new DataElement('DS');
      dePatWeight.value = ['70'];

      const deSeriesDate = new DataElement('DA');
      deSeriesDate.value = ['20231215'];

      const deSeriesTime = new DataElement('TM');
      deSeriesTime.value = ['120000'];

      const deTotalDose = new DataElement('DS');
      deTotalDose.value = ['400000000'];

      const deHalfLife = new DataElement('DS');
      deHalfLife.value = ['6588'];

      const deRadioStartDateTime = new DataElement('DT');
      deRadioStartDateTime.value = ['20231215110000'];

      const radioInfoSq = new DataElement('SQ');
      radioInfoSq.value = [{
        [TagKeys.RadionuclideTotalDose]: deTotalDose,
        [TagKeys.RadionuclideHalfLife]: deHalfLife,
        [TagKeys.RadiopharmaceuticalStartDateTime]: deRadioStartDateTime
      }];

      const elements = {
        [TagKeys.CorrectedImage]: deCorrectImage,
        [TagKeys.DecayCorrection]: deDecayCorr,
        [TagKeys.Units]: deUnits,
        [TagKeys.PatientWeight]: dePatWeight,
        [TagKeys.SeriesDate]: deSeriesDate,
        [TagKeys.SeriesTime]: deSeriesTime,
        [TagKeys.RadiopharmaceuticalInformationSequence]: radioInfoSq
      };

      const result = getSuvFactor(elements);

      assert.isUndefined(result.warning);
      assert.ok(typeof result.value === 'number');
      assert.ok(result.value > 0);
    });

    /**
     * Tests for {@link getSuvFactor} with invalid
     * PatientWeight.
     *
     * @function module:tests/dicom~getsuvfactor-invalid-weight
     */
    test('invalid weight', () => {
      const deCorrectImage = new DataElement('CS');
      deCorrectImage.value = ['ATTN', 'DECY'];

      const deDecayCorr = new DataElement('CS');
      deDecayCorr.value = ['START'];

      const deUnits = new DataElement('CS');
      deUnits.value = ['BQML'];

      const dePatWeight = new DataElement('DS');
      dePatWeight.value = ['invalid'];

      const deSeriesDate = new DataElement('DA');
      deSeriesDate.value = ['20231215'];

      const deSeriesTime = new DataElement('TM');
      deSeriesTime.value = ['120000'];

      const elements = {
        [TagKeys.CorrectedImage]: deCorrectImage,
        [TagKeys.DecayCorrection]: deDecayCorr,
        [TagKeys.Units]: deUnits,
        [TagKeys.PatientWeight]: dePatWeight,
        [TagKeys.SeriesDate]: deSeriesDate,
        [TagKeys.SeriesTime]: deSeriesTime
      };

      const result = getSuvFactor(elements);

      assert.ok(result.warning);
      assert.isUndefined(result.value);
      assert.include(result.warning, 'PatientWeight is not a number');
    });

    /**
     * Tests for {@link getSuvFactor} correct.
     *
     * @function module:tests/dicom~getsuvfactor-correct
     */
    test('correct', () => {
      const deCorrectImage = new DataElement('CS');
      deCorrectImage.value = ['ATTN', 'DECY'];

      const deDecayCorr = new DataElement('CS');
      deDecayCorr.value = ['START'];

      const deUnits = new DataElement('CS');
      deUnits.value = ['BQML'];

      const dePatWeight = new DataElement('DS');
      dePatWeight.value = ['80'];

      const deSeriesDate = new DataElement('DA');
      deSeriesDate.value = ['20231215'];

      const deSeriesTime = new DataElement('TM');
      deSeriesTime.value = ['120000'];

      const deTotalDose = new DataElement('DS');
      deTotalDose.value = ['370000000'];

      const deHalfLife = new DataElement('DS');
      deHalfLife.value = ['6588'];

      const radioInfoSq = new DataElement('SQ');
      radioInfoSq.value = [{
        [TagKeys.RadionuclideTotalDose]: deTotalDose,
        [TagKeys.RadionuclideHalfLife]: deHalfLife
      }];

      const elements = {
        [TagKeys.CorrectedImage]: deCorrectImage,
        [TagKeys.DecayCorrection]: deDecayCorr,
        [TagKeys.Units]: deUnits,
        [TagKeys.PatientWeight]: dePatWeight,
        [TagKeys.SeriesDate]: deSeriesDate,
        [TagKeys.SeriesTime]: deSeriesTime,
        [TagKeys.RadiopharmaceuticalInformationSequence]: radioInfoSq
      };

      const result = getSuvFactor(elements);

      assert.isUndefined(result.warning);
      // SUV factor = (patWeight * 1000) / decayedDose
      // With 80kg weight, should get reasonable SUV factor
      assert.ok(result.value > 0);
      assert.ok(result.value < 100);
    });

  });

});