Skip to content

Commit d5a7bcc

Browse files
committed
test(csps): fix and add tests, test data and baseline
Fixed typescript tests to properly compare json strings. Added python tests for itkwasm-dicom-wasi and itkwasm-dicom-emscripten packages. Added test and baseline datasets for dicom package. Updated CI workflow to include installation of pillow python dependency for itkwasm-dicom-wasi.
1 parent daa4a2a commit d5a7bcc

File tree

8 files changed

+135
-15
lines changed

8 files changed

+135
-15
lines changed

.github/workflows/python-wasm.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ jobs:
143143
if: ${{ matrix.python-minor-version < 10 }}
144144
working-directory: ./packages/dicom/python/itkwasm-dicom-wasi
145145
run: |
146+
python -m pip install pillow
146147
python -m pip install -e .
147148
pytest
148149
- name: Test dicom-emscripten chrome

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
"commit": "git cz",
2727
"bindgen": "node ./src/itk-wasm-cli.js bindgen ./dist/dicom/src ./dist/dicom/public/pipelines/*.wasm",
2828
"build": "npm run build:testData && npm run build:emscripten && npm run build:tsc && npm run build:tscWorkersModuleLoader && npm run build:tscWebWorkers && npm run build:workerBundles && npm run build:workerMinBundles && npm run build:webpack && node ./src/io/internal/packages/package-json-gen.cjs && npm run build:emscripten:packages",
29-
"build:testData": "dam download packages/dicom/test/data packages/dicom/test/data.tar.gz bafybeig4g7wosycckndpiouphtwowakccbkmunsvvgrg5bvjoz63p4s6hi https://w3s.link/ipfs/bafkreicrqj3nps6xep75zafmevtseitj6md2dgbce22jgizefuouc5vfca",
29+
"build:testData": "dam download packages/dicom/test/data packages/dicom/test/data.tar.gz bafybeic2ckitzhl5b476fgfewt7ilel3mkzi5x66o6gga5s7n34g2t36xm https://w3s.link/ipfs/bafkreibxuanogkwccski66azxafdpjjj3sbua6g3pqdvwwwe72h3d6agoi",
3030
"build:debug": "npm run build:emscripten -- --debug && npm run build:tsc && npm run build:tscWorkersModuleLoader && npm run build:tscWebWorkers && npm run build:workerBundles && npm run build:workerMinBundles && npm run build:webpack -- --mode development",
3131
"build:tsc": "tsc --pretty",
3232
"build:tscWorkersModuleLoader": "tsc --types --lib es2017,webworker --rootDir ./src/ --outDir ./dist/ --moduleResolution node --target es2017 --module es2020 --strict --forceConsistentCasingInFileNames --declaration ./src/core/internal/loadEmscriptenModuleWebWorker.ts",

packages/dicom/python/itkwasm-dicom-emscripten/test/fixtures.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,12 @@ def input_data():
1717
from pathlib import Path
1818
input_base_path = Path('..', '..', 'test', 'data')
1919
test_files = [
20+
Path('input') / 'csps-input-image.dcm',
21+
Path('input') / 'csps-input-pstate.dcm',
2022
Path('input') / 'gsps-pstate-test-input-image.dcm',
2123
Path('input') / 'gsps-pstate-test-input-pstate.dcm',
24+
Path('baseline') / 'csps-pstate-baseline.json',
25+
Path('baseline') / 'csps-output-image-baseline.bmp',
2226
Path('baseline') / 'gsps-pstate-baseline.json',
2327
Path('baseline') / 'gsps-pstate-image-baseline.pgm',
2428
Path('input') / '104.1-SR-printed-to-pdf.dcm',

packages/dicom/python/itkwasm-dicom-emscripten/test/test_apply_presentation_state_to_image_async.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,74 @@ def write_input_data_to_fs(input_data, filename):
4747
# the slice operation removes the last EOF char from the baseline file.
4848
buffer = fp.read()[:-1]
4949
baseline_json_object = json.loads(buffer)
50-
assert baseline_json_object['PresentationLabel'] == p_state_json_out['PresentationLabel']
51-
assert baseline_json_object['PresentationSizeMode'] == p_state_json_out['PresentationSizeMode']
50+
51+
for key in baseline_json_object:
52+
assert baseline_json_object[key] == p_state_json_out[key]
53+
54+
for key in p_state_json_out:
55+
assert p_state_json_out[key] == baseline_json_object[key]
5256

5357
baseline_image = 'gsps-pstate-image-baseline.pgm'
5458
baseline_buffer = input_data[baseline_image]
5559
# slice to get only the pixel buffer from the baseline image (pgm file)
5660
baseline_pixels = baseline_buffer[15:]
57-
assert np.array_equal(np.frombuffer(baseline_pixels, dtype=np.uint8), output_image.data.ravel())
61+
assert np.array_equal(np.frombuffer(baseline_pixels, dtype=np.uint8), output_image.data.ravel())
62+
63+
@run_in_pyodide(packages=['micropip','pillow'])
64+
async def test_apply_color_presentation_state_to_dicom_image(selenium, package_wheel, input_data):
65+
import json
66+
67+
import micropip
68+
await micropip.install(package_wheel)
69+
def write_input_data_to_fs(input_data, filename):
70+
with open(filename, 'wb') as fp:
71+
fp.write(input_data[filename])
72+
73+
from itkwasm_dicom_emscripten import apply_presentation_state_to_image_async
74+
import numpy as np
75+
76+
input_file = 'csps-input-image.dcm'
77+
write_input_data_to_fs(input_data, input_file)
78+
79+
p_state_file = 'csps-input-pstate.dcm'
80+
write_input_data_to_fs(input_data, p_state_file)
81+
82+
from itkwasm.pyodide import to_js
83+
p_state_json_out, output_image = await apply_presentation_state_to_image_async(input_file, p_state_file, color_output=True)
84+
85+
assert p_state_json_out != None
86+
assert output_image != None
87+
88+
assert output_image.imageType.dimension == 2
89+
assert output_image.imageType.componentType == 'uint8'
90+
assert output_image.imageType.pixelType == 'RGB'
91+
assert output_image.imageType.components == 3
92+
93+
assert np.array_equal(output_image.origin, [0, 0])
94+
assert np.array_equal(output_image.spacing, [0.683, 0.683])
95+
assert np.array_equal(output_image.direction, [[1, 0], [0, 1]])
96+
assert np.array_equal(output_image.size, [768, 1024])
97+
98+
baseline_json_file = 'csps-pstate-baseline.json'
99+
write_input_data_to_fs(input_data, baseline_json_file)
100+
with open(baseline_json_file, 'r') as fp:
101+
# the slice operation removes the last EOF char from the baseline file.
102+
buffer = fp.read()[:-1]
103+
baseline_json_object = json.loads(buffer)
104+
105+
for key in baseline_json_object:
106+
assert baseline_json_object[key] == p_state_json_out[key]
107+
108+
for key in p_state_json_out:
109+
assert p_state_json_out[key] == baseline_json_object[key]
110+
111+
baseline_image = 'csps-output-image-baseline.bmp'
112+
write_input_data_to_fs(input_data, baseline_image)
113+
114+
from PIL import Image
115+
im = Image.open(baseline_image)
116+
117+
baseline_pixels = np.array(im).flatten()
118+
output_pixels = output_image.data.ravel().flatten()
119+
120+
assert np.array_equal(output_pixels, baseline_pixels)

packages/dicom/python/itkwasm-dicom-wasi/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ npm run build:testData
2323
cd -
2424

2525
pip install pytest
26+
pip install pillow
2627
pip install -e .
2728
pytest
2829
# or

packages/dicom/python/itkwasm-dicom-wasi/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ requires-python = ">=3.7"
3434
dependencies = [
3535
"itkwasm >= 1.0.b105",
3636
"importlib_resources",
37-
3837
]
3938

4039
[project.urls]
@@ -48,6 +47,7 @@ path = "itkwasm_dicom_wasi/_version.py"
4847
[tool.hatch.envs.default]
4948
dependencies = [
5049
"pytest",
50+
"pillow",
5151
]
5252

5353

packages/dicom/python/itkwasm-dicom-wasi/test/test_apply_presentation_state_to_image.py

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from pathlib import Path
22
import numpy as np
33
import json
4+
from PIL import Image
45

56
def test_apply_presentation_state_to_dicom_image():
67
from itkwasm_dicom_wasi import apply_presentation_state_to_image
@@ -32,9 +33,12 @@ def test_apply_presentation_state_to_dicom_image():
3233
# the slice operation removes the last EOF char from the baseline file.
3334
buffer = fp.read()[:-1]
3435
baseline_json_object = json.loads(buffer)
35-
assert baseline_json_object['PresentationLabel'] == p_state_json_out['PresentationLabel']
36-
assert baseline_json_object['PresentationSizeMode'] == p_state_json_out['PresentationSizeMode']
37-
assert str(baseline_json_object) == str(p_state_json_out)
36+
37+
for key in baseline_json_object:
38+
assert baseline_json_object[key] == p_state_json_out[key]
39+
40+
for key in p_state_json_out:
41+
assert p_state_json_out[key] == baseline_json_object[key]
3842

3943
baseline_image = 'gsps-pstate-image-baseline.pgm'
4044
baseline_image_file_path = Path('..', '..', 'test', 'data', 'baseline', baseline_image)
@@ -58,3 +62,51 @@ def test_apply_presentation_state_to_dicom_image_input_does_not_exist():
5862
assert False
5963
except FileNotFoundError:
6064
pass
65+
66+
def test_apply_color_presentation_state_to_dicom_image():
67+
from itkwasm_dicom_wasi import apply_presentation_state_to_image
68+
69+
input_file = 'csps-input-image.dcm'
70+
input_file_path = Path('..', '..', 'test', 'data', 'input', input_file)
71+
72+
p_state_file = 'csps-input-pstate.dcm'
73+
p_state_file_path = Path('..', '..', 'test', 'data', 'input', p_state_file)
74+
75+
p_state_json_out, output_image = apply_presentation_state_to_image(input_file_path, p_state_file_path, color_output=True)
76+
77+
assert p_state_json_out != None
78+
assert output_image != None
79+
80+
assert output_image.imageType.dimension == 2
81+
assert output_image.imageType.componentType == 'uint8'
82+
assert output_image.imageType.pixelType == 'RGB'
83+
assert output_image.imageType.components == 3
84+
85+
assert np.array_equal(output_image.origin, [0, 0])
86+
assert np.array_equal(output_image.spacing, [0.683, 0.683])
87+
assert np.array_equal(output_image.direction, [[1, 0], [0, 1]])
88+
assert np.array_equal(output_image.size, [768, 1024])
89+
90+
baseline_json_file = 'csps-pstate-baseline.json'
91+
baseline_json_file_path = Path('..', '..', 'test', 'data', 'baseline', baseline_json_file)
92+
with open(baseline_json_file_path, 'r') as fp:
93+
# the slice operation removes the last EOF char from the baseline file.
94+
buffer = fp.read()[:-1]
95+
baseline_json_object = json.loads(buffer)
96+
97+
for key in baseline_json_object:
98+
assert baseline_json_object[key] == p_state_json_out[key]
99+
100+
for key in p_state_json_out:
101+
assert p_state_json_out[key] == baseline_json_object[key]
102+
103+
baseline_image = 'csps-output-image-baseline.bmp'
104+
baseline_image_file_path = Path('..', '..', 'test', 'data', 'baseline', baseline_image)
105+
106+
from PIL import Image
107+
im = Image.open(baseline_image_file_path)
108+
109+
baseline_pixels = np.array(im).flatten()
110+
output_pixels = output_image.data.ravel().flatten()
111+
112+
assert np.array_equal(output_pixels, baseline_pixels)

packages/dicom/typescript/test/node.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
readDicomEncapsulatedPdfNode,
77
applyPresentationStateToImageNode,
88
} from '../dist/bundles/dicom-node.js'
9+
import { readImageLocalFile } from '../../../../dist/index.js'
910

1011
function arrayEquals(a, b) {
1112
return (a.length === b.length && a.every((val, idx) => val === b[idx]))
@@ -157,7 +158,7 @@ test('Apply presentation state to dicom image.', async t => {
157158

158159
t.assert(baselineJsonObject.PresentationLabel === pstateJsonOut.PresentationLabel)
159160
t.assert(baselineJsonObject.PresentationSizeMode === pstateJsonOut.PresentationSizeMode)
160-
t.assert(baselineJsonObject.toString() === pstateJsonOut.toString())
161+
t.assert(JSON.stringify(baselineJsonObject) === JSON.stringify(pstateJsonOut))
161162

162163
const baselineImage = 'gsps-pstate-image-baseline.pgm'
163164
const baselineImageFilePath = baselinePathPrefix + baselineImage
@@ -209,13 +210,11 @@ test('Apply color presentation state (CSPS) to a color dicom image.', async t =>
209210

210211
t.assert(baselineJsonObject.PresentationLabel === pstateJsonOut.PresentationLabel)
211212
t.assert(baselineJsonObject.PresentationSizeMode === pstateJsonOut.PresentationSizeMode)
212-
t.assert(baselineJsonObject.toString() === pstateJsonOut.toString())
213+
t.assert(JSON.stringify(baselineJsonObject) === JSON.stringify(pstateJsonOut))
213214

214215
const baselineImage = 'csps-output-image-baseline.bmp'
215216
const baselineImageFilePath = baselinePathPrefix + baselineImage
216-
const baselineImageFileBuffer = fs.readFileSync(baselineImageFilePath)
217-
// Slice to get only the pixel buffer from the baseline image (BMP file has 54 bytes of header data before pixel buffer begins).
218-
const baselinePixels = baselineImageFileBuffer.slice(54)
219-
t.assert(baselinePixels.length === outputImage.data.length)
220-
t.assert(Buffer.compare(baselinePixels, outputImage.data) === 0)
217+
const baselinePixels = await readImageLocalFile(baselineImageFilePath)
218+
t.assert(baselinePixels.data.length === outputImage.data.length)
219+
t.assert(Buffer.compare(baselinePixels.data, outputImage.data) === 0)
221220
})

0 commit comments

Comments
 (0)