diff --git a/packages/dicom/package.json b/packages/dicom/package.json index 25b2f08e8..5cb37a227 100644 --- a/packages/dicom/package.json +++ b/packages/dicom/package.json @@ -33,7 +33,7 @@ "build:python:versionSync": "itk-wasm pnpm-script build:python:versionSync", "publish:python": "itk-wasm pnpm-script publish:python", "test": "pnpm test:data:download && pnpm build:gen:python && pnpm test:python", - "test:data:download": "dam download test/data test/data.tar.gz bafybeicfhacth6jwhtqd2caxwtepoydf54kv3zzr36zpoj7og5iuzptv7u https://data.kitware.com/api/v1/file/66c62d8faf422925a42121ab/download", + "test:data:download": "dam download test/data test/data.tar.gz bafybeiaqmwshl3vrcp5soxvmsez4nfhugcv7p432k6ndmb3jecspnvvv6u https://data.kitware.com/api/v1/file/6706def5fb903c47575aa6cc/download", "test:data:pack": "dam pack test/data test/data.tar.gz", "test:python:wasi": "itk-wasm pnpm-script test:python:wasi", "test:python:emscripten": "itk-wasm pnpm-script test:python:emscripten", diff --git a/packages/dicom/python/itkwasm-dicom-wasi/test/test_read_segmentation.py b/packages/dicom/python/itkwasm-dicom-wasi/test/test_read_segmentation.py index e494bb423..ca48c647a 100644 --- a/packages/dicom/python/itkwasm-dicom-wasi/test/test_read_segmentation.py +++ b/packages/dicom/python/itkwasm-dicom-wasi/test/test_read_segmentation.py @@ -1,4 +1,5 @@ from pathlib import Path +import numpy as np from test_helper import compare_json testPathPrefix = '../../test/data/input/' @@ -20,4 +21,27 @@ def test_read_segmentation(): assert outputJSON is not None baselineJsonFile = 'dicom-images/SEG/MR_ref_3DSAGT2SPACE_tumor_seg.json' baselineJsonFilePath = Path(baselinePathPrefix, baselineJsonFile) - assert compare_json(baselineJsonFilePath, outputJSON) \ No newline at end of file + assert compare_json(baselineJsonFilePath, outputJSON) + +def test_read_segmentation_idc1(): + from itkwasm_dicom_wasi import read_segmentation + fileName = 'dicom-images/IDC/nlst/100010/1.2.840.113654.2.55.236467930500313421847662756581858562399/SEG_1.2.276.0.7230010.3.1.3.313263360.35955.1706319184.882151/0ec3f890-c11a-4b33-8a37-17a68a5c7545.dcm' + testFilePath = Path(testPathPrefix, fileName) + outputImage, outputJSON = read_segmentation(testFilePath) + assert outputImage is not None + assert outputImage.data is not None + assert outputImage.imageType.dimension == 3 + assert outputImage.imageType.componentType == 'int16' + assert outputImage.imageType.pixelType == 'Scalar' + assert outputImage.imageType.components == 1 + + assert np.array_equal(outputImage.origin, [ -174.5, 174.316528, 9.04000854 ]) + assert np.array_equal(outputImage.spacing, [ 0.683594, 0.683594, 2.5 ]) + assert np.array_equal(np.array(outputImage.direction).flatten(), [ 1, 0, 0, 0, -1, 0, 0, 0, -1 ]) + assert np.array_equal(outputImage.size, [ 512, 512, 137 ]) + assert (outputImage.data.size == 35913728) + + assert outputJSON is not None + baselineJsonFile = 'dicom-images/IDC/nlst/100010/1.2.840.113654.2.55.236467930500313421847662756581858562399/SEG_1.2.276.0.7230010.3.1.3.313263360.35955.1706319184.882151/metaInfo.json' + baselineJsonFilePath = Path(testPathPrefix, baselineJsonFile) + assert compare_json(baselineJsonFilePath, outputJSON) diff --git a/packages/dicom/python/itkwasm-dicom-wasi/tests/common.py b/packages/dicom/python/itkwasm-dicom-wasi/tests/common.py index 3a182e694..edbcd81b6 100644 --- a/packages/dicom/python/itkwasm-dicom-wasi/tests/common.py +++ b/packages/dicom/python/itkwasm-dicom-wasi/tests/common.py @@ -3,4 +3,13 @@ test_input_path = Path(__file__).parent / ".." / ".." / ".." / "test" / "data" / "input" test_baseline_path = Path(__file__).parent / ".." / ".." / ".." / "test" / "data" / "baseline" test_output_path = Path(__file__).parent / ".." / ".." / ".." / "test" / "data" / "output" / "python" -test_output_path.mkdir(parents=True, exist_ok=True) \ No newline at end of file +test_output_path.mkdir(parents=True, exist_ok=True) + +def compare_rounded_float(a: float, b: float, n: int) -> bool: + return round(a, n) == round(b, n) + +def compare_array_float(a, b) -> bool: + for x in range(len(a)): + if not compare_rounded_float(a[x], b[x], 3): + return False + return True diff --git a/packages/dicom/python/itkwasm-dicom-wasi/tests/test_read_image_dicom_file_series.py b/packages/dicom/python/itkwasm-dicom-wasi/tests/test_read_image_dicom_file_series.py index 9a3f9ec3e..959a74231 100644 --- a/packages/dicom/python/itkwasm-dicom-wasi/tests/test_read_image_dicom_file_series.py +++ b/packages/dicom/python/itkwasm-dicom-wasi/tests/test_read_image_dicom_file_series.py @@ -1,8 +1,48 @@ -# Generated file. To retain edits, remove this comment. - from itkwasm_dicom_wasi import read_image_dicom_file_series -from .common import test_input_path, test_output_path +from .common import test_input_path, test_output_path, compare_array_float + +from pathlib import Path +import numpy as np +from glob import glob + +testDataInputDirectory = Path("../../test/data/input").resolve() def test_read_image_dicom_file_series(): pass + +def test_read_image_dicom_file_series_idc1(): + nmFileName = "dicom-images/IDC/acrin_nsclc_fdg_pet/ACRIN-NSCLC-FDG-PET-134/1.3.6.1.4.1.14519.5.2.1.7009.2403.154904565437993902842316547208/NM_1.3.6.1.4.1.14519.5.2.1.7009.2403.484725606860278331095617627781/0d6c2de3-168b-4ea5-a1c8-c7d7438faa15.dcm" + testFilePath = Path(testDataInputDirectory, nmFileName) + output = read_image_dicom_file_series([testFilePath]) + outputImage = output[0] + assert outputImage is not None + assert outputImage.data is not None + assert outputImage.imageType.dimension == 3 + assert outputImage.imageType.componentType == 'uint16' + assert outputImage.imageType.pixelType == 'Scalar' + assert outputImage.imageType.components == 1 + + assert compare_array_float(outputImage.origin, [-204.602, -399.602, 1759.7]) + assert compare_array_float(outputImage.spacing, [2.8125, 2.8125, 2]) + assert compare_array_float(np.array(outputImage.direction).flatten(), [1, 0, 0, 0, 1, 0, 0, 0, -1]) + assert np.array_equal(outputImage.size, [128, 128, 196]) + assert (outputImage.data.size == 3211264) + +def test_read_image_dicom_file_series_idc2(): + dirPath = 'dicom-images/IDC/nsclc_radiomics_genomics/LUNG3-49/1.3.6.1.4.1.32722.99.99.278496019127563640059579120768864824043/CT_1.3.6.1.4.1.32722.99.99.239963936032720978832553442140518002510' + testDicomSeriesFiles = glob(f"{Path(testDataInputDirectory, dirPath)}/*.dcm") + output = read_image_dicom_file_series(testDicomSeriesFiles) + outputImage = output[0] + assert outputImage is not None + assert outputImage.data is not None + assert outputImage.imageType.dimension == 3 + assert outputImage.imageType.componentType == 'int16' + assert outputImage.imageType.pixelType == 'Scalar' + assert outputImage.imageType.components == 1 + + assert compare_array_float(outputImage.origin, [ -171, -102, 673.3 ]) + assert compare_array_float(outputImage.spacing, [ 0.672, 0.672, 4.0]) + assert compare_array_float(np.array(outputImage.direction).flatten(), [ 1, 0, 0, 0, 1, 0, 0, 0, -1 ]) + assert np.array_equal(outputImage.size, [ 512, 512, 89 ]) + assert (outputImage.data.size == 23330816) diff --git a/packages/dicom/typescript/test/node/dcmqi.js b/packages/dicom/typescript/test/node/dcmqi.js index c76e28100..055a4e3b3 100644 --- a/packages/dicom/typescript/test/node/dcmqi.js +++ b/packages/dicom/typescript/test/node/dcmqi.js @@ -352,3 +352,35 @@ test('itk2dcm_makeSEG_seg_size', async t => { t.assert(r1.metrics.maximumDifference < 1e-8) } }) + +// ========= IDC data tests ============= +test('IDC: DICOM segmentation', async t => { + const segFileName = 'dicom-images/IDC/nlst/100010/1.2.840.113654.2.55.236467930500313421847662756581858562399/SEG_1.2.276.0.7230010.3.1.3.313263360.35955.1706319184.882151/0ec3f890-c11a-4b33-8a37-17a68a5c7545.dcm' + const testFilePath = path.join(testPathPrefix, segFileName) + const output = await readSegmentationNode(testFilePath) + console.log(output) + console.log(output.segImage.imageType) + console.log(output.segImage.data) + console.log(JSON.stringify(output.metaInfo)) + + t.assert(output.segImage != null) + t.assert(output.segImage.data != null) + t.deepEqual(output.segImage.imageType, { + dimension: 3, + componentType: 'int16', + pixelType: 'Scalar', + components: 1 + }) + t.deepEqual(output.segImage.origin, [ -174.5, 174.316528, 9.04000854 ]) + t.deepEqual(output.segImage.spacing, [ 0.683594, 0.683594, 2.5 ]) + t.assert(arrayEquals(output.segImage.direction, [ 1, 0, 0, 0, -1, 0, 0, 0, -1 ])) + t.deepEqual(output.segImage.size, [ 512, 512, 137 ]) + t.deepEqual(output.segImage.data.length, 35913728) + + // The json file is simply generated from a previous call to readSegmentationNode. It is therefore, simply testing for regressions. + const baselineJsonFile = '/dicom-images/IDC/nlst/100010/1.2.840.113654.2.55.236467930500313421847662756581858562399/SEG_1.2.276.0.7230010.3.1.3.313263360.35955.1706319184.882151/metaInfo.json' + const baselineJsonFilePath = path.join(testPathPrefix, baselineJsonFile) + const baselineJsonFileBuffer = fs.readFileSync(baselineJsonFilePath) + const baselineJsonObject = JSON.parse(baselineJsonFileBuffer) + t.assert(JSON.stringify(baselineJsonObject) === JSON.stringify(output.metaInfo)) +}) diff --git a/packages/dicom/typescript/test/node/gdcm.js b/packages/dicom/typescript/test/node/gdcm.js index bb1771f7b..a1d0d633c 100644 --- a/packages/dicom/typescript/test/node/gdcm.js +++ b/packages/dicom/typescript/test/node/gdcm.js @@ -14,6 +14,18 @@ function arrayEquals(a, b) { return (a.length === b.length && a.every((val, idx) => val === b[idx])) } +function roundToThreePlaces(a) { + return Math.round((a + Number.EPSILON) * 1000) / 1000 +} + +function floatCompare(a, b) { + return (roundToThreePlaces(a) === roundToThreePlaces(b)) +} + +function floatCompareArray(a, b) { + return (a.length === b.length && a.every((val, idx) => floatCompare(val, b[idx]))) +} + function verifyImage (t, image, expectedComponentType, expectedPixelType) { let componentType = IntTypes.Int16 if (expectedComponentType) { @@ -335,3 +347,48 @@ test('DICOM SOP: Nuclear Medicine Image.', async t => { ])) t.deepEqual(outputImage.size, [128, 128, 69]) }) + +// ========= IDC data tests ============= +test('IDC: Nuclear Medicine with negative SpacingBetweenSlices Test', async t => { + const nmFileName = 'dicom-images/IDC/acrin_nsclc_fdg_pet/ACRIN-NSCLC-FDG-PET-134/1.3.6.1.4.1.14519.5.2.1.7009.2403.154904565437993902842316547208/NM_1.3.6.1.4.1.14519.5.2.1.7009.2403.484725606860278331095617627781/0d6c2de3-168b-4ea5-a1c8-c7d7438faa15.dcm' + const testFilePath = path.join(testDataInputDirectory, nmFileName) + const output = await readImageDicomFileSeriesNode({inputImages: [testFilePath]}) + t.assert(output) + const outputImage = output.outputImage + t.assert(outputImage != null) + t.assert(outputImage.imageType != null) + t.deepEqual(outputImage.imageType, { + dimension: 3, + componentType: 'uint16', + pixelType: 'Scalar', + components: 1 + }) + t.deepEqual(outputImage.origin, [ -204.60240003467, -399.60240003467, 1759.7 ]) + t.deepEqual(outputImage.spacing, [ 2.8125, 2.8125, 2 ]) + // Z-axis in direction matrix is flipped/negative due to the negative SpacingBetweenSlices + t.assert(arrayEquals(outputImage.direction, [ 1, 0, 0, 0, 1, 0, 0, 0, -1 ])) + t.deepEqual(outputImage.size, [ 128, 128, 196 ]) + t.deepEqual(outputImage.data.length, 3211264) +}) + +test('IDC: CT with negative SpacingBetweenSlices Test', async t => { + const dirPath = 'dicom-images/IDC/nsclc_radiomics_genomics/LUNG3-49/1.3.6.1.4.1.32722.99.99.278496019127563640059579120768864824043/CT_1.3.6.1.4.1.32722.99.99.239963936032720978832553442140518002510' + const testDirPath = path.join(testDataInputDirectory, dirPath) + const testDicomSeriesFiles = glob.sync(`${testDirPath}/*.dcm`) + const output = await readImageDicomFileSeriesNode({inputImages: testDicomSeriesFiles}) + t.assert(output) + const outputImage = output.outputImage + t.assert(outputImage != null) + t.assert(outputImage.imageType != null) + t.deepEqual(outputImage.imageType, { + dimension: 3, + componentType: 'int16', + pixelType: 'Scalar', + components: 1 + }) + t.assert(floatCompareArray(outputImage.origin, [ -171, -102, 673.300 ])) + t.assert(floatCompareArray(outputImage.spacing, [ 0.672, 0.672, 4.0])) + t.assert(arrayEquals(outputImage.direction, [ 1, 0, 0, 0, 1, 0, 0, 0, -1 ])) + t.deepEqual(outputImage.size, [ 512, 512, 89 ]) + t.deepEqual(outputImage.data.length, 23330816) +})