Skip to content

Commit

Permalink
feat(compare-images): add python bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
thewtex committed Aug 21, 2023
1 parent 953e040 commit 0d8c87d
Show file tree
Hide file tree
Showing 26 changed files with 749 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# itkwasm-compare-images-emscripten

[![PyPI version](https://badge.fury.io/py/itkwasm-compare-images-emscripten.svg)](https://badge.fury.io/py/itkwasm-compare-images-emscripten)

Read files and images related to compare-images file format. Emscripten implementation.

This package provides the Emscripten WebAssembly implementation. It is usually not called directly. Please use the [`itkwasm-compare-images`](https://pypi.org/project/itkwasm-compare-images/) instead.


## Installation

```sh
import micropip
await micropip.install('itkwasm-compare-images-emscripten')
```

## Development

```sh
pip install hatch
hatch run download-pyodide
hatch run test
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""itkwasm-compare-images-emscripten: Read files and images related to compare-images file format. Emscripten implementation."""

from .compare_double_images_async import compare_double_images_async

from ._version import __version__
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "1.0.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Generated file. Do not edit.

from pathlib import Path
import os
from typing import Dict, Tuple, Optional, List, Any

from .js_package import js_package

from itkwasm.pyodide import (
to_js,
to_py,
js_resources
)
from itkwasm import (
InterfaceTypes,
Image,
)

async def compare_double_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.compareDoubleImages(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)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from itkwasm.pyodide import JsPackageConfig, JsPackage

from ._version import __version__

default_config = JsPackageConfig(f"https://cdn.jsdelivr.net/npm/@itk-wasm/compare-images@{__version__}/dist/bundles/compare-images.js")
js_package = JsPackage(default_config)
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[project]
name = "itkwasm-compare-images-emscripten"
readme = "README.md"
license = "Apache-2.0"
dynamic = ["version", "description"]
classifiers = [
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
"Programming Language :: C++",
"Environment :: WebAssembly",
"Environment :: WebAssembly :: Emscripten",
"Environment :: WebAssembly :: WASI",
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
]
keywords = [
"itkwasm",
"webassembly",
"emscripten",
]

requires-python = ">=3.7"
dependencies = [
"itkwasm >= 1.0.b105",
]

[tool.hatch.version]
path = "itkwasm_compare_images_emscripten/_version.py"

[tool.hatch.envs.default]
dependencies = [
"pytest",
"pytest-pyodide",
]

[project.urls]
Home = "https://github.com/InsightSoftwareConsortium/itk-wasm"
Source = "https://github.com/InsightSoftwareConsortium/itk-wasm"

[tool.hatch.envs.default.scripts]
test = [
"hatch build -t wheel",
"pytest --dist-dir=./dist --rt=chrome",
]
download-pyodide = [
"curl -L https://github.com/pyodide/pyodide/releases/download/0.23.1/pyodide-0.23.1.tar.bz2 -o pyodide.tar.bz2",
"tar xjf pyodide.tar.bz2",
"rm -rf dist pyodide.tar.bz2",
"mv pyodide dist",
]
serve = [
"hatch build -t wheel",
'echo "\nVisit http://localhost:8877/console.html\n"',
"python -m http.server --directory=./dist 8877",
]


[tool.hatch.build]
exclude = [
"/examples",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import pytest
import sys

if sys.version_info < (3,10):
pytest.skip("Skipping pyodide tests on older Python", allow_module_level=True)

from pytest_pyodide import run_in_pyodide

from itkwasm_compare_images_emscripten import __version__ as test_package_version

@pytest.fixture
def package_wheel():
return f"itkwasm_compare_images_emscripten-{test_package_version}-py3-none-any.whl"

@run_in_pyodide(packages=['micropip'])
async def test_example(selenium, package_wheel):
import micropip
await micropip.install(package_wheel)

# Write your test code here
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# itkwasm-compare-images-wasi

[![PyPI version](https://badge.fury.io/py/itkwasm-compare-images-wasi.svg)](https://badge.fury.io/py/itkwasm-compare-images-wasi)

Read files and images related to compare-images file format. WASI implementation.

This package provides the WASI WebAssembly implementation. It is usually not called directly. Please use [`itkwasm-compare-images`](https://pypi.org/project/itkwasm-compare-images/) instead.


## Installation

```sh
pip install itkwasm-compare-images-wasi
```

## Development

```sh
pip install pytest
pip install -e .
pytest

# or
pip install hatch
hatch run test
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""itkwasm-compare-images-wasi: Read files and images related to compare-images file format. WASI implementation."""

from .compare_double_images import compare_double_images

from ._version import __version__
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "1.0.0"
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Generated file. Do not edit.

from pathlib import Path, PurePosixPath
import os
from typing import Dict, Tuple, Optional, List, Any

from importlib_resources import files as file_resources

_pipeline = None

from itkwasm import (
InterfaceTypes,
PipelineOutput,
PipelineInput,
Pipeline,
Image,
)

def compare_double_images(
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
"""
global _pipeline
if _pipeline is None:
_pipeline = Pipeline(file_resources('itkwasm_compare_images_wasi').joinpath(Path('wasm_modules') / Path('compare-double-images.wasi.wasm')))

pipeline_outputs: List[PipelineOutput] = [
PipelineOutput(InterfaceTypes.JsonObject),
PipelineOutput(InterfaceTypes.Image),
PipelineOutput(InterfaceTypes.Image),
]

pipeline_inputs: List[PipelineInput] = [
PipelineInput(InterfaceTypes.Image, test_image),
]

args: List[str] = ['--memory-io',]
# Inputs
args.append('0')
# Outputs
args.append('0')
args.append('1')
args.append('2')
# Options
if len(baseline_images) > 1:
if len(baseline_images) < 1:
raise ValueError('"baseline-images" option must have a length > 1')

args.append('--baseline-images')
for value in baseline_images:
input_count_string = str(len(pipeline_inputs))
pipeline_inputs.push(PipelineInput(InterfaceTypes.Image, value))
args.append(input_count_string)

if difference_threshold:
args.append('--difference-threshold')
args.append(str(difference_threshold))

if radius_tolerance:
args.append('--radius-tolerance')
args.append(str(radius_tolerance))

if number_of_pixels_tolerance:
args.append('--number-of-pixels-tolerance')
args.append(str(number_of_pixels_tolerance))

if ignore_boundary_pixels:
args.append('--ignore-boundary-pixels')


outputs = _pipeline.run(args, pipeline_outputs, pipeline_inputs)

result = (
outputs[0].data,
outputs[1].data,
outputs[2].data,
)
return result

Binary file not shown.
Loading

0 comments on commit 0d8c87d

Please sign in to comment.