diff --git a/packages/compare-images/python/itkwasm-compare-images-emscripten/itkwasm_compare_images_emscripten/__init__.py b/packages/compare-images/python/itkwasm-compare-images-emscripten/itkwasm_compare_images_emscripten/__init__.py index fcd001d9a..3aae2a8eb 100644 --- a/packages/compare-images/python/itkwasm-compare-images-emscripten/itkwasm_compare_images_emscripten/__init__.py +++ b/packages/compare-images/python/itkwasm-compare-images-emscripten/itkwasm_compare_images_emscripten/__init__.py @@ -1,6 +1,7 @@ """itkwasm-compare-images-emscripten: Compare images with a tolerance for regression testing. Emscripten implementation.""" from .compare_double_images_async import compare_double_images_async +from .compare_images_async import compare_images_async from .vector_magnitude_async import vector_magnitude_async from ._version import __version__ diff --git a/packages/compare-images/python/itkwasm-compare-images-emscripten/itkwasm_compare_images_emscripten/compare_images_async.py b/packages/compare-images/python/itkwasm-compare-images-emscripten/itkwasm_compare_images_emscripten/compare_images_async.py new file mode 100644 index 000000000..afcb75cd4 --- /dev/null +++ b/packages/compare-images/python/itkwasm-compare-images-emscripten/itkwasm_compare_images_emscripten/compare_images_async.py @@ -0,0 +1,84 @@ +from pathlib import Path +import os +from typing import Dict, Tuple, Optional, List + +from .js_package import js_package + +from itkwasm.pyodide import ( + to_js, + to_py, + js_resources +) +from itkwasm import ( + InterfaceTypes, + Image, +) + +async def compare_images_async( + test_image: Image, + baseline_images: List[Image] = [], + difference_threshold: float = 0, + radius_tolerance: int = 0, + number_of_pixels_tolerance: int = 0, + ignore_boundary_pixels: bool = False, +) -> Tuple[Dict, Image, Image]: + """Compare double pixel type images with a tolerance for regression testing. + + :param test_image: The input test image + :type test_image: Image + + :param baseline_images: Baseline images compare against + :type baseline_images: Image + + :param difference_threshold: Intensity difference for pixels to be considered different. + :type difference_threshold: float + + :param radius_tolerance: Radius of the neighborhood around a pixel to search for similar intensity values. + :type radius_tolerance: int + + :param number_of_pixels_tolerance: Number of pixels that can be different before the test fails. + :type number_of_pixels_tolerance: int + + :param ignore_boundary_pixels: Ignore boundary pixels. Useful when resampling may have introduced difference pixel values along the image edge. + :type ignore_boundary_pixels: bool + + :return: Metrics for the baseline with the fewest number of pixels outside the tolerances. + :rtype: Dict + + :return: Absolute difference image + :rtype: Image + + :return: Unsigned char, 2D difference image for rendering + :rtype: Image + """ + js_module = await js_package.js_module + web_worker = js_resources.web_worker + + kwargs = {} + if baseline_images is not None: + kwargs["baselineImages"] = to_js(baseline_images) + if difference_threshold: + kwargs["differenceThreshold"] = to_js(difference_threshold) + if radius_tolerance: + kwargs["radiusTolerance"] = to_js(radius_tolerance) + if number_of_pixels_tolerance: + kwargs["numberOfPixelsTolerance"] = to_js(number_of_pixels_tolerance) + if ignore_boundary_pixels: + kwargs["ignoreBoundaryPixels"] = to_js(ignore_boundary_pixels) + + outputs = await js_module.compareImages(web_worker, to_js(test_image), **kwargs) + + output_web_worker = None + output_list = [] + outputs_object_map = outputs.as_object_map() + for output_name in outputs.object_keys(): + if output_name == 'webWorker': + output_web_worker = outputs_object_map[output_name] + else: + output_list.append(to_py(outputs_object_map[output_name])) + + js_resources.web_worker = output_web_worker + + if len(output_list) == 1: + return output_list[0] + return tuple(output_list) diff --git a/packages/compare-images/python/itkwasm-compare-images-emscripten/test/test_compare_images_async.py b/packages/compare-images/python/itkwasm-compare-images-emscripten/test/test_compare_images_async.py index f38a85348..9defcb277 100644 --- a/packages/compare-images/python/itkwasm-compare-images-emscripten/test/test_compare_images_async.py +++ b/packages/compare-images/python/itkwasm-compare-images-emscripten/test/test_compare_images_async.py @@ -8,8 +8,6 @@ @run_in_pyodide(packages=['micropip', 'numpy']) async def test_compare_double_images_async(selenium, package_wheel, input_data): - import json - import micropip await micropip.install(package_wheel, 'numpy', 'itkwasm') @@ -34,4 +32,68 @@ async def test_compare_double_images_async(selenium, package_wheel, input_data): assert metrics['meanDifference'] == 34.02259203227433 assert difference_image.imageType.componentType == 'float64' - assert difference_image_rendering.imageType.componentType == 'uint8' \ No newline at end of file + assert difference_image_rendering.imageType.componentType == 'uint8' + +@run_in_pyodide(packages=['micropip', 'numpy']) +async def test_compare_images_async(selenium, package_wheel, input_data): + import micropip + await micropip.install(package_wheel, 'numpy', 'itkwasm') + + from itkwasm_compare_images_emscripten import compare_images_async + import numpy as np + from itkwasm import Image + from itkwasm.pyodide import to_js as itkwasm_to_js + + test_image_file = 'cake_easy.iwi.cbor' + test_image = Image(**input_data[test_image_file]) + + baseline_image_file = 'cake_hard.iwi.cbor' + baseline_image = Image(**input_data[baseline_image_file]) + + metrics, difference_image, difference_image_rendering = await compare_images_async(test_image, baseline_images=[baseline_image]) + + assert metrics['almostEqual'] == False + assert metrics['numberOfPixelsWithDifferences'] == 9915 + assert metrics['minimumDifference'] == 1.0 + assert metrics['maximumDifference'] == 107.0 + assert metrics['totalDifference'] == 337334.0 + assert metrics['meanDifference'] == 34.02259203227433 + + assert difference_image.imageType.componentType == 'float64' + assert difference_image_rendering.imageType.componentType == 'uint8' + + test_image_file = 'cake_easy.png' + test_image = Image(**input_data[test_image_file]) + + baseline_image_file = 'cake_hard.png' + baseline_image = Image(**input_data[baseline_image_file]) + + metrics, difference_image, difference_image_rendering = await compare_images_async(test_image, baseline_images=[baseline_image]) + + assert metrics['almostEqual'] == False + assert metrics['numberOfPixelsWithDifferences'] == 9915 + assert metrics['minimumDifference'] == 1.0 + assert metrics['maximumDifference'] == 107.0 + assert metrics['totalDifference'] == 337334.0 + assert metrics['meanDifference'] == 34.02259203227433 + + assert difference_image.imageType.componentType == 'float64' + assert difference_image_rendering.imageType.componentType == 'uint8' + + test_image_file = 'apple.jpg' + test_image = Image(**input_data[test_image_file]) + + baseline_image_file = 'orange.jpg' + baseline_image = Image(**input_data[baseline_image_file]) + + metrics, difference_image, difference_image_rendering = await compare_images_async(test_image, baseline_images=[baseline_image]) + + assert metrics['almostEqual'] == False + assert metrics['numberOfPixelsWithDifferences'] == 26477 + assert metrics['minimumDifference'] == 0.002273026683894841 + assert metrics['maximumDifference'] == 312.2511648746159 + assert metrics['totalDifference'] == 3121656.100202402 + assert metrics['meanDifference'] == 117.90067228924735 + + assert difference_image.imageType.componentType == 'float64' + assert difference_image_rendering.imageType.componentType == 'uint8'