diff --git a/packages/core/python/itkwasm/itkwasm/__init__.py b/packages/core/python/itkwasm/itkwasm/__init__.py index 20f734fca..c216112b0 100644 --- a/packages/core/python/itkwasm/itkwasm/__init__.py +++ b/packages/core/python/itkwasm/itkwasm/__init__.py @@ -1,9 +1,9 @@ """itkwasm: Python interface to itk-wasm WebAssembly modules.""" -__version__ = "1.0b171" +__version__ = "1.0b175" from .interface_types import InterfaceTypes -from .image import Image, ImageType +from .image import Image, ImageType, ImageRegion from .pointset import PointSet, PointSetType from .mesh import Mesh, MeshType from .polydata import PolyData, PolyDataType @@ -21,36 +21,41 @@ from .environment_dispatch import environment_dispatch, function_factory from .cast_image import cast_image from .image_from_array import image_from_array -from .to_numpy_array import array_like_to_numpy_array, array_like_to_bytes, buffer_to_numpy_array +from .to_numpy_array import ( + array_like_to_numpy_array, + array_like_to_bytes, + buffer_to_numpy_array, +) from .to_cupy_array import array_like_to_cupy_array __all__ = [ - "InterfaceTypes", - "Pipeline", - "PipelineInput", - "PipelineOutput", - "Image", - "ImageType", - "PointSet", - "PointSetType", - "Mesh", - "MeshType", - "PolyData", - "PolyDataType", - "BinaryFile", - "BinaryStream", - "TextFile", - "TextStream", - "JsonCompatible", - "FloatTypes", - "IntTypes", - "PixelTypes", - "environment_dispatch", - "function_factory", - "cast_image", - "image_from_array", - "array_like_to_numpy_array", - "array_like_to_bytes", - "array_like_to_cupy_array", - "buffer_to_numpy_array", - ] + "InterfaceTypes", + "Pipeline", + "PipelineInput", + "PipelineOutput", + "Image", + "ImageType", + "ImageRegion", + "PointSet", + "PointSetType", + "Mesh", + "MeshType", + "PolyData", + "PolyDataType", + "BinaryFile", + "BinaryStream", + "TextFile", + "TextStream", + "JsonCompatible", + "FloatTypes", + "IntTypes", + "PixelTypes", + "environment_dispatch", + "function_factory", + "cast_image", + "image_from_array", + "array_like_to_numpy_array", + "array_like_to_bytes", + "array_like_to_cupy_array", + "buffer_to_numpy_array", +] diff --git a/packages/core/python/itkwasm/itkwasm/binary_file.py b/packages/core/python/itkwasm/itkwasm/binary_file.py index 724bfd1d6..50084e2cf 100644 --- a/packages/core/python/itkwasm/itkwasm/binary_file.py +++ b/packages/core/python/itkwasm/itkwasm/binary_file.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from pathlib import PurePosixPath + @dataclass class BinaryFile: - path: PurePosixPath \ No newline at end of file + path: PurePosixPath diff --git a/packages/core/python/itkwasm/itkwasm/binary_stream.py b/packages/core/python/itkwasm/itkwasm/binary_stream.py index 40691957a..190a31729 100644 --- a/packages/core/python/itkwasm/itkwasm/binary_stream.py +++ b/packages/core/python/itkwasm/itkwasm/binary_stream.py @@ -1,5 +1,6 @@ from dataclasses import dataclass + @dataclass class BinaryStream: - data: bytes \ No newline at end of file + data: bytes diff --git a/packages/core/python/itkwasm/itkwasm/cast_image.py b/packages/core/python/itkwasm/itkwasm/cast_image.py index 351561e95..63bb08304 100644 --- a/packages/core/python/itkwasm/itkwasm/cast_image.py +++ b/packages/core/python/itkwasm/itkwasm/cast_image.py @@ -9,9 +9,12 @@ from .int_types import IntTypes from .float_types import FloatTypes -def cast_image(input_image: Image, - pixel_type: Optional[PixelTypes]=None, - component_type: Optional[Union[IntTypes, FloatTypes]]=None) -> Image: + +def cast_image( + input_image: Image, + pixel_type: Optional[PixelTypes] = None, + component_type: Optional[Union[IntTypes, FloatTypes]] = None, +) -> Image: """Cast an image to another pixel type and / or component type. :param input_image: Image to be cast. @@ -72,6 +75,6 @@ def cast_image(input_image: Image, elif component_type == FloatTypes.Float64: output_image.data = input_image.data.astype(np.float64) else: - raise ValueError('Unsupported component type') + raise ValueError("Unsupported component type") return output_image diff --git a/packages/core/python/itkwasm/itkwasm/environment_dispatch.py b/packages/core/python/itkwasm/itkwasm/environment_dispatch.py index 6476133c4..d7d6e4ec6 100644 --- a/packages/core/python/itkwasm/itkwasm/environment_dispatch.py +++ b/packages/core/python/itkwasm/itkwasm/environment_dispatch.py @@ -2,18 +2,20 @@ import sys import importlib import sys + if sys.version_info < (3, 10): from importlib_metadata import entry_points else: from importlib.metadata import entry_points + class FunctionFactory: def __init__(self): self._registered: Dict[Tuple[str, str], Set[Callable]] = {} self._priorities: Dict[Callable, int] = {} self._has_entry_point_lookup: Set[Tuple[str, str]] = set() - def register(self, interface_package: str, func_name: str, func: Callable, priority: int=1)-> None: + def register(self, interface_package: str, func_name: str, func: Callable, priority: int = 1) -> None: key = (interface_package, func_name) registered = self._registered.get(key, set()) registered.add(func) @@ -23,9 +25,9 @@ def register(self, interface_package: str, func_name: str, func: Callable, prior def lookup(self, interface_package: str, func_name: str) -> Optional[Set[Callable]]: key = (interface_package, func_name) if not key in self._has_entry_point_lookup: - discovered_funcs = entry_points(group=f'{interface_package}.{func_name}') + discovered_funcs = entry_points(group=f"{interface_package}.{func_name}") for ep in discovered_funcs: - priority = ep.name.partition('.priority.')[2] + priority = ep.name.partition(".priority.")[2] if priority: priority = int(priority) else: @@ -40,17 +42,20 @@ def highest_priority(self, interface_package: str, func_name: str) -> Optional[C registered = self.lookup(interface_package, func_name) if registered is None: return None - highest = max(self._registered[(interface_package, func_name)], key=lambda x: self._priorities[x]) + highest = max( + self._registered[(interface_package, func_name)], + key=lambda x: self._priorities[x], + ) if self._priorities[highest] < 1: return None return highest - def set_priority(self, func: Callable, priority: int)-> None: + def set_priority(self, func: Callable, priority: int) -> None: if func not in self._priorities: raise ValueError(f"Function {func} has not been registered") self._priorities[func] = priority - def get_priority(self, func: Callable)-> int: + def get_priority(self, func: Callable) -> int: if func not in self._priorities: raise ValueError(f"Function {func} has not been registered") return self._priorities[func] @@ -61,22 +66,24 @@ def disable(self, interface_package: str, func_name: str): for func in registered: self._priorities[func] = -1 + function_factory = FunctionFactory() + def environment_dispatch(interface_package: str, func_name: str) -> Callable: factory_func = function_factory.highest_priority(interface_package, func_name) if factory_func is not None: return factory_func if sys.platform != "emscripten": - if func_name.endswith('_async'): - raise ValueError('async function are only implemented for emscripten') + if func_name.endswith("_async"): + raise ValueError("async function are only implemented for emscripten") package = f"{interface_package}_wasi" else: - if not func_name.endswith('_async'): - raise ValueError('emscripten only implements the _async version of this function') + if not func_name.endswith("_async"): + raise ValueError("emscripten only implements the _async version of this function") package = f"{interface_package}_emscripten" mod = importlib.import_module(package) func = getattr(mod, func_name) - return func \ No newline at end of file + return func diff --git a/packages/core/python/itkwasm/itkwasm/float_types.py b/packages/core/python/itkwasm/itkwasm/float_types.py index 59fa42b38..7b7732229 100644 --- a/packages/core/python/itkwasm/itkwasm/float_types.py +++ b/packages/core/python/itkwasm/itkwasm/float_types.py @@ -1,12 +1,13 @@ from enum import Enum + class FloatTypes(str, Enum): - Float32 = 'float32' - Float64 = 'float64' - SpacePrecisionType = 'float64' + Float32 = "float32" + Float64 = "float64" + SpacePrecisionType = "float64" def __str__(self): return str(self.value) def __repr__(self): - return f'"{str(self.value)}"' \ No newline at end of file + return f'"{str(self.value)}"' diff --git a/packages/core/python/itkwasm/itkwasm/image.py b/packages/core/python/itkwasm/itkwasm/image.py index 7c07a30fb..fd9387dd5 100644 --- a/packages/core/python/itkwasm/itkwasm/image.py +++ b/packages/core/python/itkwasm/itkwasm/image.py @@ -12,6 +12,7 @@ from .float_types import FloatTypes from .pixel_types import PixelTypes + @dataclass class ImageType: dimension: int = 2 @@ -19,19 +20,28 @@ class ImageType: pixelType: PixelTypes = PixelTypes.Scalar components: int = 1 + def _default_direction() -> ArrayLike: return np.empty((0,), np.float64) + +@dataclass +class ImageRegion: + index: Sequence[int] = field(default_factory=list) + size: Sequence[int] = field(default_factory=list) + + @dataclass class Image: imageType: Union[ImageType, Dict] = field(default_factory=ImageType) - name: str = 'Image' + name: str = "Image" origin: Sequence[float] = field(default_factory=list) spacing: Sequence[float] = field(default_factory=list) direction: ArrayLike = field(default_factory=_default_direction) size: Sequence[int] = field(default_factory=list) metadata: Dict = field(default_factory=dict) data: Optional[ArrayLike] = None + bufferedRegion: Optional[ImageRegion] = None def __post_init__(self): if isinstance(self.imageType, dict): @@ -39,13 +49,28 @@ def __post_init__(self): dimension = self.imageType.dimension if len(self.origin) == 0: - self.origin += [0.0,] * dimension + self.origin += [ + 0.0, + ] * dimension if len(self.spacing) == 0: - self.spacing += [1.0,] * dimension + self.spacing += [ + 1.0, + ] * dimension if len(self.direction) == 0: self.direction = np.eye(dimension).astype(np.float64) if len(self.size) == 0: - self.size += [1,] * dimension + self.size += [ + 1, + ] * dimension + + if self.bufferedRegion is None: + self.bufferedRegion = ImageRegion( + index=[ + 0, + ] + * dimension, + size=self.size, + ) diff --git a/packages/core/python/itkwasm/itkwasm/image_from_array.py b/packages/core/python/itkwasm/itkwasm/image_from_array.py index 2d212352e..34e7add34 100644 --- a/packages/core/python/itkwasm/itkwasm/image_from_array.py +++ b/packages/core/python/itkwasm/itkwasm/image_from_array.py @@ -1,4 +1,5 @@ from typing import Optional + try: from numpy.typing import ArrayLike except ImportError: @@ -9,6 +10,7 @@ from .pixel_types import PixelTypes + def image_from_array(arr, is_vector: bool = False, image_type: Optional[ImageType] = None) -> Image: """Convert a numpy array-like to an itkwasm Image. @@ -33,7 +35,7 @@ def image_from_array(arr, is_vector: bool = False, image_type: Optional[ImageTyp image_type = ImageType( dimension=dimension, componentType=_dtype_to_component_type(arr.dtype), - pixelType=PixelTypes.Scalar if not is_vector else PixelTypes.VariableLengthVector, + pixelType=(PixelTypes.Scalar if not is_vector else PixelTypes.VariableLengthVector), components=arr.shape[-1] if is_vector else 1, ) diff --git a/packages/core/python/itkwasm/itkwasm/int_types.py b/packages/core/python/itkwasm/itkwasm/int_types.py index b5ec217fb..4f3c047f6 100644 --- a/packages/core/python/itkwasm/itkwasm/int_types.py +++ b/packages/core/python/itkwasm/itkwasm/int_types.py @@ -1,19 +1,20 @@ from enum import Enum + class IntTypes(str, Enum): - Int8 = 'int8' - UInt8 = 'uint8' - Int16 = 'int16' - UInt16 = 'uint16' - Int32 = 'int32' - UInt32 = 'uint32' - Int64 = 'int64' - UInt64 = 'uint64' + Int8 = "int8" + UInt8 = "uint8" + Int16 = "int16" + UInt16 = "uint16" + Int32 = "int32" + UInt32 = "uint32" + Int64 = "int64" + UInt64 = "uint64" - SizeValueType = 'uint64' - IdentifierType = 'uint64' - IndexValueType = 'int64' - OffsetValueType = 'int64' + SizeValueType = "uint64" + IdentifierType = "uint64" + IndexValueType = "int64" + OffsetValueType = "int64" def __str__(self): return str(self.value) diff --git a/packages/core/python/itkwasm/itkwasm/interface_types.py b/packages/core/python/itkwasm/itkwasm/interface_types.py index eff8c07da..357169d7a 100644 --- a/packages/core/python/itkwasm/itkwasm/interface_types.py +++ b/packages/core/python/itkwasm/itkwasm/interface_types.py @@ -1,11 +1,12 @@ from enum import Enum + class InterfaceTypes(Enum): - TextFile = 'InterfaceTextFile' - BinaryFile = 'InterfaceBinaryFile' - TextStream = 'InterfaceTextStream' - BinaryStream = 'InterfaceBinaryStream' - Image = 'InterfaceImage' - Mesh = 'InterfaceMesh' - PolyData = 'InterfacePolyData' - JsonCompatible = 'InterfaceJsonCompatible' + TextFile = "InterfaceTextFile" + BinaryFile = "InterfaceBinaryFile" + TextStream = "InterfaceTextStream" + BinaryStream = "InterfaceBinaryStream" + Image = "InterfaceImage" + Mesh = "InterfaceMesh" + PolyData = "InterfacePolyData" + JsonCompatible = "InterfaceJsonCompatible" diff --git a/packages/core/python/itkwasm/itkwasm/json_compatible.py b/packages/core/python/itkwasm/itkwasm/json_compatible.py index 9397cb202..b607a7d54 100644 --- a/packages/core/python/itkwasm/itkwasm/json_compatible.py +++ b/packages/core/python/itkwasm/itkwasm/json_compatible.py @@ -1,4 +1,3 @@ from typing import Dict, Union, List -JsonCompatible = Union[Dict[str, "JsonCompatible"], None, bool, str, int, - float, List["JsonCompatible"]] +JsonCompatible = Union[Dict[str, "JsonCompatible"], None, bool, str, int, float, List["JsonCompatible"]] diff --git a/packages/core/python/itkwasm/itkwasm/mesh.py b/packages/core/python/itkwasm/itkwasm/mesh.py index b691f7de4..c3b054b39 100644 --- a/packages/core/python/itkwasm/itkwasm/mesh.py +++ b/packages/core/python/itkwasm/itkwasm/mesh.py @@ -11,6 +11,7 @@ from .int_types import IntTypes from .pixel_types import PixelTypes + @dataclass class MeshType: dimension: int = 3 @@ -30,7 +31,7 @@ class MeshType: class Mesh: meshType: Union[MeshType, Dict] = field(default_factory=MeshType) - name: str = 'Mesh' + name: str = "Mesh" numberOfPoints: int = 0 points: Optional[ArrayLike] = None diff --git a/packages/core/python/itkwasm/itkwasm/pipeline.py b/packages/core/python/itkwasm/itkwasm/pipeline.py index 2ca4b61eb..fd189ed30 100644 --- a/packages/core/python/itkwasm/itkwasm/pipeline.py +++ b/packages/core/python/itkwasm/itkwasm/pipeline.py @@ -4,8 +4,10 @@ from typing import List, Union, Dict, Tuple, Set import ctypes import sys +import os import numpy as np + try: from numpy.typing import ArrayLike except ImportError: @@ -24,27 +26,51 @@ from .json_compatible import JsonCompatible from .int_types import IntTypes from .float_types import FloatTypes -from .to_numpy_array import buffer_to_numpy_array, array_like_to_numpy_array, array_like_to_bytes +from .to_numpy_array import ( + buffer_to_numpy_array, + array_like_to_numpy_array, + array_like_to_bytes, +) if sys.platform != "emscripten": import zlib from platformdirs import user_cache_dir - from wasmtime import Config, Store, Engine, Module, WasiConfig, Linker, WasmtimeError - - _module_store_dir = Path(user_cache_dir("itkwasm")) + from wasmtime import ( + Config, + Store, + Engine, + Module, + WasiConfig, + Linker, + WasmtimeError, + ) + + # Get the value of the ITKWASM_CACHE_DIR environment variable + # If it is not set, use the default cache directory + _module_store_dir = os.environ.get("ITKWASM_CACHE_DIR", user_cache_dir("itkwasm")) + _module_store_dir = Path(_module_store_dir) _module_store_dir.mkdir(parents=True, exist_ok=True) + def array_like_to_bytes(arr: ArrayLike) -> bytes: """Convert a numpy array-like to bytes.""" - if hasattr(arr, 'tobytes'): + if hasattr(arr, "tobytes"): return arr.tobytes() return array_like_to_numpy_array(arr).tobytes() + class RunInstance: """Helper for working with the wasm module instance created when a Pipeline is run.""" - def __init__(self, engine: "Engine", linker: "Linker", module: "Module", args: List[str], preopen_directories: Set[str]): + def __init__( + self, + engine: "Engine", + linker: "Linker", + module: "Module", + args: List[str], + preopen_directories: Set[str], + ): self._store = Store(engine) store = self._store @@ -75,25 +101,21 @@ def __init__(self, engine: "Engine", linker: "Linker", module: "Module", args: L _initialize(store) def wasmtime_lift(self, ptr: int, size: int): - ptr = ptr & 0xffffffff - size = size & 0xffffffff + ptr = ptr & 0xFFFFFFFF + size = size & 0xFFFFFFFF if ptr + size > self._memory.data_len(self._store): - raise IndexError('attempting to lift of bounds') + raise IndexError("attempting to lift of bounds") raw_base = self._memory.data_ptr(self._store) - base = ctypes.POINTER(ctypes.c_ubyte)( - ctypes.c_ubyte.from_address(ctypes.addressof(raw_base.contents) + ptr) - ) + base = ctypes.POINTER(ctypes.c_ubyte)(ctypes.c_ubyte.from_address(ctypes.addressof(raw_base.contents) + ptr)) return ctypes.string_at(base, size) def wasmtime_lower(self, ptr: int, data: Union[bytes, bytearray]): - ptr = ptr & 0xffffffff + ptr = ptr & 0xFFFFFFFF size = len(data) if ptr + size > self._memory.data_len(self._store): - raise IndexError('attempting to lower out of bounds') + raise IndexError("attempting to lower out of bounds") raw_base = self._memory.data_ptr(self._store) - base = ctypes.POINTER(ctypes.c_ubyte)( - ctypes.c_ubyte.from_address(ctypes.addressof(raw_base.contents) + ptr) - ) + base = ctypes.POINTER(ctypes.c_ubyte)(ctypes.c_ubyte.from_address(ctypes.addressof(raw_base.contents) + ptr)) ctypes.memmove(base, data, size) def set_input_array(self, data_array: Union[bytes, bytearray], input_index: int, sub_index: int) -> int: @@ -130,6 +152,7 @@ def delayed_exit(self, return_code): func = self._instance.exports(self._store)["itk_wasm_delayed_exit"] func(self._store, return_code) + class Pipeline: """Run an itk-wasm WASI pipeline.""" @@ -143,29 +166,34 @@ def __init__(self, pipeline: Union[str, Path, bytes]): if isinstance(pipeline, bytes): wasm_bytes = pipeline else: - with open(pipeline, 'rb') as fp: + with open(pipeline, "rb") as fp: wasm_bytes = fp.read() self.linker = Linker(self.engine) self.linker.define_wasi() self.linker.allow_shadowing = True checksum = zlib.adler32(wasm_bytes) - module_cache = _module_store_dir / Path(f'module-{checksum}') + module_cache = _module_store_dir / Path(f"module-{checksum}") if module_cache.exists(): try: self.module = Module.deserialize_file(self.engine, str(module_cache)) except WasmtimeError: self.module = Module(self.engine, wasm_bytes) - with module_cache.open('wb') as fp: + with module_cache.open("wb") as fp: fp.write(self.module.serialize()) else: self.module = Module(self.engine, wasm_bytes) - with module_cache.open('wb') as fp: + with module_cache.open("wb") as fp: fp.write(self.module.serialize()) - def run(self, args: List[str], outputs: List[PipelineOutput]=[], inputs: List[PipelineInput]=[]) -> Tuple[PipelineOutput]: + def run( + self, + args: List[str], + outputs: List[PipelineOutput] = [], + inputs: List[PipelineInput] = [], + ) -> Tuple[PipelineOutput]: """Run the itk-wasm pipeline.""" - preopen_directories=set() + preopen_directories = set() for index, input_ in enumerate(inputs): if input_.type == InterfaceTypes.TextFile or input_.type == InterfaceTypes.BinaryFile: preopen_directories.add(str(PurePosixPath(input_.data.path).parent)) @@ -180,12 +208,18 @@ def run(self, args: List[str], outputs: List[PipelineOutput]=[], inputs: List[Pi if input_.type == InterfaceTypes.TextStream: data_array = input_.data.data.encode() array_ptr = ri.set_input_array(data_array, index, 0) - data_json = { "size": len(data_array), "data": f"data:application/vnd.itk.address,0:{array_ptr}" } + data_json = { + "size": len(data_array), + "data": f"data:application/vnd.itk.address,0:{array_ptr}", + } ri.set_input_json(data_json, index) elif input_.type == InterfaceTypes.BinaryStream: data_array = input_.data.data array_ptr = ri.set_input_array(data_array, index, 0) - data_json = { "size": len(data_array), "data": f"data:application/vnd.itk.address,0:{array_ptr}" } + data_json = { + "size": len(data_array), + "data": f"data:application/vnd.itk.address,0:{array_ptr}", + } ri.set_input_json(data_json, index) elif input_.type == InterfaceTypes.TextFile: pass @@ -204,7 +238,7 @@ def run(self, args: List[str], outputs: List[PipelineOutput]=[], inputs: List[Pi "spacing": image.spacing, "direction": f"data:application/vnd.itk.address,0:{direction_ptr}", "size": image.size, - "data": f"data:application/vnd.itk.address,0:{data_ptr}" + "data": f"data:application/vnd.itk.address,0:{data_ptr}", } ri.set_input_json(image_json, index) elif input_.type == InterfaceTypes.Mesh: @@ -232,17 +266,13 @@ def run(self, args: List[str], outputs: List[PipelineOutput]=[], inputs: List[Pi mesh_json = { "meshType": asdict(mesh.meshType), "name": mesh.name, - "numberOfPoints": mesh.numberOfPoints, "points": f"data:application/vnd.itk.address,0:{points_ptr}", - "numberOfCells": mesh.numberOfCells, "cells": f"data:application/vnd.itk.address,0:{cells_ptr}", "cellBufferSize": mesh.cellBufferSize, - "numberOfPointPixels": mesh.numberOfPointPixels, "pointData": f"data:application/vnd.itk.address,0:{point_data_ptr}", - "numberOfCellPixels": mesh.numberOfCellPixels, "cellData": f"data:application/vnd.itk.address,0:{cell_data_ptr}", } @@ -294,36 +324,32 @@ def run(self, args: List[str], outputs: List[PipelineOutput]=[], inputs: List[Pi polydata_json = { "polyDataType": asdict(polydata.polyDataType), "name": polydata.name, - "numberOfPoints": polydata.numberOfPoints, "points": f"data:application/vnd.itk.address,0:{points_ptr}", - "verticesBufferSize": polydata.verticesBufferSize, "vertices": f"data:application/vnd.itk.address,0:{vertices_ptr}", - "linesBufferSize": polydata.linesBufferSize, "lines": f"data:application/vnd.itk.address,0:{lines_ptr}", - "polygonsBufferSize": polydata.polygonsBufferSize, "polygons": f"data:application/vnd.itk.address,0:{polygons_ptr}", - "triangleStripsBufferSize": polydata.triangleStripsBufferSize, "triangleStrips": f"data:application/vnd.itk.address,0:{triangleStrips_ptr}", - "numberOfPointPixels": polydata.numberOfPointPixels, "pointData": f"data:application/vnd.itk.address,0:{pointData_ptr}", - "numberOfCellPixels": polydata.numberOfCellPixels, - "cellData": f"data:application/vnd.itk.address,0:{cellData_ptr}" + "cellData": f"data:application/vnd.itk.address,0:{cellData_ptr}", } ri.set_input_json(polydata_json, index) elif input_.type == InterfaceTypes.JsonCompatible: data_array = json.dumps(input_.data).encode() array_ptr = ri.set_input_array(data_array, index, 0) - data_json = { "size": len(data_array), "data": f"data:application/vnd.itk.address,0:{array_ptr}" } + data_json = { + "size": len(data_array), + "data": f"data:application/vnd.itk.address,0:{array_ptr}", + } ri.set_input_json(data_json, index) else: - raise ValueError(f'Unexpected/not yet supported input.type {input_.type}') + raise ValueError(f"Unexpected/not yet supported input.type {input_.type}") return_code = ri.delayed_start() @@ -352,7 +378,10 @@ def run(self, args: List[str], outputs: List[PipelineOutput]=[], inputs: List[Pi data_ptr = ri.get_output_array_address(0, index, 0) data_size = ri.get_output_array_size(0, index, 0) - data_array = buffer_to_numpy_array(image.imageType.componentType, ri.wasmtime_lift(data_ptr, data_size)) + data_array = buffer_to_numpy_array( + image.imageType.componentType, + ri.wasmtime_lift(data_ptr, data_size), + ) shape = list(image.size)[::-1] if image.imageType.components > 1: shape.append(image.imageType.components) @@ -360,7 +389,10 @@ def run(self, args: List[str], outputs: List[PipelineOutput]=[], inputs: List[Pi direction_ptr = ri.get_output_array_address(0, index, 1) direction_size = ri.get_output_array_size(0, index, 1) - direction_array = buffer_to_numpy_array(FloatTypes.Float64, ri.wasmtime_lift(direction_ptr, direction_size)) + direction_array = buffer_to_numpy_array( + FloatTypes.Float64, + ri.wasmtime_lift(direction_ptr, direction_size), + ) dimension = image.imageType.dimension direction_array.shape = (dimension, dimension) image.direction = direction_array @@ -373,29 +405,41 @@ def run(self, args: List[str], outputs: List[PipelineOutput]=[], inputs: List[Pi if mesh.numberOfPoints > 0: data_ptr = ri.get_output_array_address(0, index, 0) data_size = ri.get_output_array_size(0, index, 0) - mesh.points = buffer_to_numpy_array(mesh.meshType.pointComponentType, ri.wasmtime_lift(data_ptr, data_size)) + mesh.points = buffer_to_numpy_array( + mesh.meshType.pointComponentType, + ri.wasmtime_lift(data_ptr, data_size), + ) else: - mesh.points = buffer_to_numpy_array(mesh.meshType.pointComponentType, bytes([])) + mesh.points = buffer_to_numpy_array(mesh.meshType.pointComponentType, bytes([])) if mesh.numberOfCells > 0: data_ptr = ri.get_output_array_address(0, index, 1) data_size = ri.get_output_array_size(0, index, 1) - mesh.cells = buffer_to_numpy_array(mesh.meshType.cellComponentType, ri.wasmtime_lift(data_ptr, data_size)) + mesh.cells = buffer_to_numpy_array( + mesh.meshType.cellComponentType, + ri.wasmtime_lift(data_ptr, data_size), + ) else: - mesh.cells = buffer_to_numpy_array(mesh.meshType.cellComponentType, bytes([])) + mesh.cells = buffer_to_numpy_array(mesh.meshType.cellComponentType, bytes([])) if mesh.numberOfPointPixels > 0: data_ptr = ri.get_output_array_address(0, index, 2) data_size = ri.get_output_array_size(0, index, 2) - mesh.pointData = buffer_to_numpy_array(mesh.meshType.pointPixelComponentType, ri.wasmtime_lift(data_ptr, data_size)) + mesh.pointData = buffer_to_numpy_array( + mesh.meshType.pointPixelComponentType, + ri.wasmtime_lift(data_ptr, data_size), + ) else: - mesh.pointData = buffer_to_numpy_array(mesh.meshType.pointPixelComponentType, bytes([])) + mesh.pointData = buffer_to_numpy_array(mesh.meshType.pointPixelComponentType, bytes([])) if mesh.numberOfCellPixels > 0: data_ptr = ri.get_output_array_address(0, index, 3) data_size = ri.get_output_array_size(0, index, 3) - mesh.cellData = buffer_to_numpy_array(mesh.meshType.cellPixelComponentType, ri.wasmtime_lift(data_ptr, data_size)) + mesh.cellData = buffer_to_numpy_array( + mesh.meshType.cellPixelComponentType, + ri.wasmtime_lift(data_ptr, data_size), + ) else: - mesh.cellData = buffer_to_numpy_array(mesh.meshType.cellPixelComponentType, bytes([])) + mesh.cellData = buffer_to_numpy_array(mesh.meshType.cellPixelComponentType, bytes([])) output_data = PipelineOutput(InterfaceTypes.Mesh, mesh) elif output.type == InterfaceTypes.PolyData: @@ -405,51 +449,69 @@ def run(self, args: List[str], outputs: List[PipelineOutput]=[], inputs: List[Pi if polydata.numberOfPoints > 0: data_ptr = ri.get_output_array_address(0, index, 0) data_size = ri.get_output_array_size(0, index, 0) - polydata.points = buffer_to_numpy_array(FloatTypes.Float32, ri.wasmtime_lift(data_ptr, data_size)) + polydata.points = buffer_to_numpy_array( + FloatTypes.Float32, ri.wasmtime_lift(data_ptr, data_size) + ) else: - polydata.points = buffer_to_numpy_array(FloatTypes.Float32, bytes([])) + polydata.points = buffer_to_numpy_array(FloatTypes.Float32, bytes([])) if polydata.verticesBufferSize > 0: data_ptr = ri.get_output_array_address(0, index, 1) data_size = ri.get_output_array_size(0, index, 1) - polydata.vertices = buffer_to_numpy_array(IntTypes.UInt32, ri.wasmtime_lift(data_ptr, data_size)) + polydata.vertices = buffer_to_numpy_array( + IntTypes.UInt32, ri.wasmtime_lift(data_ptr, data_size) + ) else: - polydata.vertices = buffer_to_numpy_array(IntTypes.UInt32, bytes([])) + polydata.vertices = buffer_to_numpy_array(IntTypes.UInt32, bytes([])) if polydata.linesBufferSize > 0: data_ptr = ri.get_output_array_address(0, index, 2) data_size = ri.get_output_array_size(0, index, 2) polydata.lines = buffer_to_numpy_array(IntTypes.UInt32, ri.wasmtime_lift(data_ptr, data_size)) else: - polydata.lines = buffer_to_numpy_array(IntTypes.UInt32, bytes([])) + polydata.lines = buffer_to_numpy_array(IntTypes.UInt32, bytes([])) if polydata.polygonsBufferSize > 0: data_ptr = ri.get_output_array_address(0, index, 3) data_size = ri.get_output_array_size(0, index, 3) - polydata.polygons = buffer_to_numpy_array(IntTypes.UInt32, ri.wasmtime_lift(data_ptr, data_size)) + polydata.polygons = buffer_to_numpy_array( + IntTypes.UInt32, ri.wasmtime_lift(data_ptr, data_size) + ) else: - polydata.polygons = buffer_to_numpy_array(IntTypes.UInt32, bytes([])) + polydata.polygons = buffer_to_numpy_array(IntTypes.UInt32, bytes([])) if polydata.triangleStripsBufferSize > 0: data_ptr = ri.get_output_array_address(0, index, 4) data_size = ri.get_output_array_size(0, index, 4) - polydata.triangleStrips = buffer_to_numpy_array(IntTypes.UInt32, ri.wasmtime_lift(data_ptr, data_size)) + polydata.triangleStrips = buffer_to_numpy_array( + IntTypes.UInt32, ri.wasmtime_lift(data_ptr, data_size) + ) else: - polydata.triangleStrips = buffer_to_numpy_array(IntTypes.UInt32, bytes([])) + polydata.triangleStrips = buffer_to_numpy_array(IntTypes.UInt32, bytes([])) if polydata.numberOfPointPixels > 0: data_ptr = ri.get_output_array_address(0, index, 5) data_size = ri.get_output_array_size(0, index, 5) - polydata.pointData = buffer_to_numpy_array(polydata.polyDataType.pointPixelComponentType, ri.wasmtime_lift(data_ptr, data_size)) + polydata.pointData = buffer_to_numpy_array( + polydata.polyDataType.pointPixelComponentType, + ri.wasmtime_lift(data_ptr, data_size), + ) else: - polydata.triangleStrips = buffer_to_numpy_array(polydata.polyDataType.pointPixelComponentType, bytes([])) + polydata.triangleStrips = buffer_to_numpy_array( + polydata.polyDataType.pointPixelComponentType, bytes([]) + ) if polydata.numberOfCellPixels > 0: data_ptr = ri.get_output_array_address(0, index, 6) data_size = ri.get_output_array_size(0, index, 6) - polydata.cellData = buffer_to_numpy_array(polydata.polyDataType.cellPixelComponentType, ri.wasmtime_lift(data_ptr, data_size)) + polydata.cellData = buffer_to_numpy_array( + polydata.polyDataType.cellPixelComponentType, + ri.wasmtime_lift(data_ptr, data_size), + ) else: - polydata.triangleStrips = buffer_to_numpy_array(polydata.polyDataType.cellPixelComponentType, bytes([])) + polydata.triangleStrips = buffer_to_numpy_array( + polydata.polyDataType.cellPixelComponentType, bytes([]) + ) output_data = PipelineOutput(InterfaceTypes.PolyData, polydata) elif output.type == InterfaceTypes.JsonCompatible: @@ -458,7 +520,7 @@ def run(self, args: List[str], outputs: List[PipelineOutput]=[], inputs: List[Pi data_array = ri.wasmtime_lift(data_ptr, data_size) output_data = PipelineOutput(InterfaceTypes.JsonCompatible, json.loads(data_array.decode())) else: - raise ValueError(f'Unexpected/not yet supported output.type {output.type}') + raise ValueError(f"Unexpected/not yet supported output.type {output.type}") populated_outputs.append(output_data) diff --git a/packages/core/python/itkwasm/itkwasm/pipeline_input.py b/packages/core/python/itkwasm/itkwasm/pipeline_input.py index d47cf35fe..d5cf77e5e 100644 --- a/packages/core/python/itkwasm/itkwasm/pipeline_input.py +++ b/packages/core/python/itkwasm/itkwasm/pipeline_input.py @@ -10,8 +10,19 @@ from .mesh import Mesh from .polydata import PolyData + @dataclass class PipelineInput: type: InterfaceTypes - data: Union[str, bytes, TextStream, BinaryStream, TextFile, BinaryFile, Image, Mesh, PolyData] - path: Optional[str] = None \ No newline at end of file + data: Union[ + str, + bytes, + TextStream, + BinaryStream, + TextFile, + BinaryFile, + Image, + Mesh, + PolyData, + ] + path: Optional[str] = None diff --git a/packages/core/python/itkwasm/itkwasm/pipeline_output.py b/packages/core/python/itkwasm/itkwasm/pipeline_output.py index 0983a7eaa..418499d98 100644 --- a/packages/core/python/itkwasm/itkwasm/pipeline_output.py +++ b/packages/core/python/itkwasm/itkwasm/pipeline_output.py @@ -10,8 +10,21 @@ from .mesh import Mesh from .polydata import PolyData + @dataclass class PipelineOutput: type: InterfaceTypes - data: Optional[Union[str, bytes, TextStream, BinaryStream, TextFile, BinaryFile, Image, Mesh, PolyData]] = None + data: Optional[ + Union[ + str, + bytes, + TextStream, + BinaryStream, + TextFile, + BinaryFile, + Image, + Mesh, + PolyData, + ] + ] = None path: Optional[str] = None diff --git a/packages/core/python/itkwasm/itkwasm/pixel_types.py b/packages/core/python/itkwasm/itkwasm/pixel_types.py index 0a497e65b..704ec0d73 100644 --- a/packages/core/python/itkwasm/itkwasm/pixel_types.py +++ b/packages/core/python/itkwasm/itkwasm/pixel_types.py @@ -1,22 +1,23 @@ from enum import Enum + class PixelTypes(str, Enum): - Unknown = 'Unknown' - Scalar = 'Scalar' - RGB = 'RGB' - RGBA = 'RGBA' - Offset = 'Offset' - Vector = 'Vector' - Point = 'Point' - CovariantVector = 'CovariantVector' - SymmetricSecondRankTensor = 'SymmetricSecondRankTensor' - DiffusionTensor3D = 'DiffusionTensor3D' - Complex = 'Complex' - FixedArray = 'FixedArray' - Array = 'Array' - Matrix = 'Matrix' - VariableLengthVector = 'VariableLengthVector' - VariableSizeMatrix = 'VariableSizeMatrix' + Unknown = "Unknown" + Scalar = "Scalar" + RGB = "RGB" + RGBA = "RGBA" + Offset = "Offset" + Vector = "Vector" + Point = "Point" + CovariantVector = "CovariantVector" + SymmetricSecondRankTensor = "SymmetricSecondRankTensor" + DiffusionTensor3D = "DiffusionTensor3D" + Complex = "Complex" + FixedArray = "FixedArray" + Array = "Array" + Matrix = "Matrix" + VariableLengthVector = "VariableLengthVector" + VariableSizeMatrix = "VariableSizeMatrix" def __str__(self): return str(self.value) diff --git a/packages/core/python/itkwasm/itkwasm/pointset.py b/packages/core/python/itkwasm/itkwasm/pointset.py index 4af7ceb03..3a8e3b059 100644 --- a/packages/core/python/itkwasm/itkwasm/pointset.py +++ b/packages/core/python/itkwasm/itkwasm/pointset.py @@ -11,6 +11,7 @@ from .int_types import IntTypes from .pixel_types import PixelTypes + @dataclass class PointSetType: dimension: int = 3 @@ -25,7 +26,7 @@ class PointSetType: class PointSet: pointSetType: Union[PointSetType, Dict] = field(default_factory=PointSetType) - name: str = 'PointSet' + name: str = "PointSet" numberOfPoints: int = 0 points: Optional[ArrayLike] = None @@ -35,4 +36,4 @@ class PointSet: def __post_init__(self): if isinstance(self.pointSetType, dict): - self.pointSetType = PointSetType(**self.pointSetType) \ No newline at end of file + self.pointSetType = PointSetType(**self.pointSetType) diff --git a/packages/core/python/itkwasm/itkwasm/polydata.py b/packages/core/python/itkwasm/itkwasm/polydata.py index 51949030d..9e9d6be11 100644 --- a/packages/core/python/itkwasm/itkwasm/polydata.py +++ b/packages/core/python/itkwasm/itkwasm/polydata.py @@ -11,6 +11,7 @@ from .int_types import IntTypes from .pixel_types import PixelTypes + @dataclass class PolyDataType: pointPixelComponentType: Union[IntTypes, FloatTypes] = FloatTypes.Float32 @@ -21,13 +22,15 @@ class PolyDataType: cellPixelType: PixelTypes = PixelTypes.Scalar cellPixelComponents: int = 1 + def _default_points() -> ArrayLike: return np.empty((0,), np.float32) + @dataclass class PolyData: polyDataType: Union[PolyDataType, Dict] = field(default_factory=PolyDataType) - name: str = 'PolyData' + name: str = "PolyData" numberOfPoints: int = 0 points: ArrayLike = field(default_factory=_default_points) diff --git a/packages/core/python/itkwasm/itkwasm/pyodide.py b/packages/core/python/itkwasm/itkwasm/pyodide.py index 6a818fd97..55b912759 100644 --- a/packages/core/python/itkwasm/itkwasm/pyodide.py +++ b/packages/core/python/itkwasm/itkwasm/pyodide.py @@ -14,12 +14,14 @@ from .json_compatible import JsonCompatible from .to_numpy_array import buffer_to_numpy_array + @dataclass class JsPackageConfig: module_url: str pipelines_base_url: Optional[str] = None pipeline_worker_url: Optional[str] = None + class JsPackage: def __init__(self, config: JsPackageConfig): self._config = config @@ -38,6 +40,7 @@ async def js_module(self): if self._js_module is not None: return self._js_module from pyodide.code import run_js + js_module = await run_js(f"import('{self._config.module_url}')") if self._config.pipelines_base_url is not None: js_module.setPipelinesBaseUrl(self._config.pipelines_base_url) @@ -46,6 +49,7 @@ async def js_module(self): self._js_module = js_module return js_module + class JsResources: def __init__(self): self._web_worker = None @@ -58,84 +62,97 @@ def web_worker(self): def web_worker(self, value): self._web_worker = value + js_resources = JsResources() + def to_py(js_proxy): import pyodide - if hasattr(js_proxy, 'constructor') and js_proxy.constructor.name == "Uint8Array": + + if hasattr(js_proxy, "constructor") and js_proxy.constructor.name == "Uint8Array": return js_proxy.to_bytes() elif isinstance(js_proxy, pyodide.ffi.JsArray): return [to_py(value) for value in js_proxy] elif hasattr(js_proxy, "imageType"): image_dict = js_proxy.to_py() - image_type = ImageType(**image_dict['imageType']) - image_dict['imageType'] = image_type + image_type = ImageType(**image_dict["imageType"]) + image_dict["imageType"] = image_type dimension = image_type.dimension component_type = image_type.componentType - image_dict['direction'] = buffer_to_numpy_array(str(FloatTypes.Float64), image_dict['direction']).reshape((dimension, dimension)) - shape = list(image_dict['size'])[::-1] + image_dict["direction"] = buffer_to_numpy_array(str(FloatTypes.Float64), image_dict["direction"]).reshape( + (dimension, dimension) + ) + shape = list(image_dict["size"])[::-1] if image_type.components > 1: shape.append(image_type.components) - if image_dict['data'] is not None: - image_dict['data'] = buffer_to_numpy_array(component_type, image_dict['data']).reshape(tuple(shape)) + if image_dict["data"] is not None: + image_dict["data"] = buffer_to_numpy_array(component_type, image_dict["data"]).reshape(tuple(shape)) return Image(**image_dict) elif hasattr(js_proxy, "pointSetType"): point_set_dict = js_proxy.to_py() - point_set_type = PointSetType(**point_set_dict['pointSetType']) - point_set_dict['pointSetType'] = point_set_type + point_set_type = PointSetType(**point_set_dict["pointSetType"]) + point_set_dict["pointSetType"] = point_set_type dimension = point_set_type.dimension point_component_type = point_set_type.pointComponentType point_pixel_component_type = point_set_type.pointPixelComponentType - if point_set_dict['points'] is not None: - point_set_dict['points'] = buffer_to_numpy_array(point_component_type, point_set_dict['points']).reshape((-1, dimension)) - if point_set_dict['pointData'] is not None: - point_set_dict['pointData'] = buffer_to_numpy_array(point_pixel_component_type, point_set_dict['pointData']) + if point_set_dict["points"] is not None: + point_set_dict["points"] = buffer_to_numpy_array(point_component_type, point_set_dict["points"]).reshape( + (-1, dimension) + ) + if point_set_dict["pointData"] is not None: + point_set_dict["pointData"] = buffer_to_numpy_array(point_pixel_component_type, point_set_dict["pointData"]) return PointSet(**point_set_dict) elif hasattr(js_proxy, "meshType"): mesh_dict = js_proxy.to_py() - mesh_type = MeshType(**mesh_dict['meshType']) - mesh_dict['meshType'] = mesh_type + mesh_type = MeshType(**mesh_dict["meshType"]) + mesh_dict["meshType"] = mesh_type dimension = mesh_type.dimension point_component_type = mesh_type.pointComponentType point_pixel_component_type = mesh_type.pointPixelComponentType cell_component_type = mesh_type.cellComponentType cell_pixel_component_type = mesh_type.cellPixelComponentType - if mesh_dict['points'] is not None: - mesh_dict['points'] = buffer_to_numpy_array(point_component_type, mesh_dict['points']).reshape((-1, dimension)) - if mesh_dict['pointData'] is not None: - mesh_dict['pointData'] = buffer_to_numpy_array(point_pixel_component_type, mesh_dict['pointData']) - if mesh_dict['cells'] is not None: - mesh_dict['cells'] = buffer_to_numpy_array(cell_component_type, mesh_dict['cells']) - if mesh_dict['cellData'] is not None: - mesh_dict['cellData'] = buffer_to_numpy_array(cell_pixel_component_type, mesh_dict['cellData']) + if mesh_dict["points"] is not None: + mesh_dict["points"] = buffer_to_numpy_array(point_component_type, mesh_dict["points"]).reshape( + (-1, dimension) + ) + if mesh_dict["pointData"] is not None: + mesh_dict["pointData"] = buffer_to_numpy_array(point_pixel_component_type, mesh_dict["pointData"]) + if mesh_dict["cells"] is not None: + mesh_dict["cells"] = buffer_to_numpy_array(cell_component_type, mesh_dict["cells"]) + if mesh_dict["cellData"] is not None: + mesh_dict["cellData"] = buffer_to_numpy_array(cell_pixel_component_type, mesh_dict["cellData"]) return Mesh(**mesh_dict) elif hasattr(js_proxy, "polyDataType"): polydata_dict = js_proxy.to_py() - polydata_type = PolyDataType(**polydata_dict['polyDataType']) - polydata_dict['polyDataType'] = polydata_type + polydata_type = PolyDataType(**polydata_dict["polyDataType"]) + polydata_dict["polyDataType"] = polydata_type point_pixel_component_type = polydata_type.pointPixelComponentType cell_pixel_component_type = polydata_type.cellPixelComponentType - if polydata_dict['points'] is not None: - polydata_dict['points'] = buffer_to_numpy_array(str(FloatTypes.Float32), polydata_dict['points']).reshape((-1, 3)) - if polydata_dict['vertices'] is not None: - polydata_dict['vertices'] = buffer_to_numpy_array(str(IntTypes.UInt32), polydata_dict['vertices']) - if polydata_dict['lines'] is not None: - polydata_dict['lines'] = buffer_to_numpy_array(str(IntTypes.UInt32), polydata_dict['lines']) - if polydata_dict['polygons'] is not None: - polydata_dict['polygons'] = buffer_to_numpy_array(str(IntTypes.UInt32), polydata_dict['polygons']) - if polydata_dict['triangleStrips'] is not None: - polydata_dict['triangleStrips'] = buffer_to_numpy_array(str(IntTypes.UInt32), polydata_dict['triangleStrips']) - if polydata_dict['pointData'] is not None: - polydata_dict['pointData'] = buffer_to_numpy_array(point_pixel_component_type, polydata_dict['pointData']) - if polydata_dict['cellData'] is not None: - polydata_dict['cellData'] = buffer_to_numpy_array(cell_pixel_component_type, polydata_dict['cellData']) + if polydata_dict["points"] is not None: + polydata_dict["points"] = buffer_to_numpy_array(str(FloatTypes.Float32), polydata_dict["points"]).reshape( + (-1, 3) + ) + if polydata_dict["vertices"] is not None: + polydata_dict["vertices"] = buffer_to_numpy_array(str(IntTypes.UInt32), polydata_dict["vertices"]) + if polydata_dict["lines"] is not None: + polydata_dict["lines"] = buffer_to_numpy_array(str(IntTypes.UInt32), polydata_dict["lines"]) + if polydata_dict["polygons"] is not None: + polydata_dict["polygons"] = buffer_to_numpy_array(str(IntTypes.UInt32), polydata_dict["polygons"]) + if polydata_dict["triangleStrips"] is not None: + polydata_dict["triangleStrips"] = buffer_to_numpy_array( + str(IntTypes.UInt32), polydata_dict["triangleStrips"] + ) + if polydata_dict["pointData"] is not None: + polydata_dict["pointData"] = buffer_to_numpy_array(point_pixel_component_type, polydata_dict["pointData"]) + if polydata_dict["cellData"] is not None: + polydata_dict["cellData"] = buffer_to_numpy_array(cell_pixel_component_type, polydata_dict["cellData"]) return PolyData(**polydata_dict) elif hasattr(js_proxy, "path") and hasattr(js_proxy, "data") and isinstance(js_proxy.data, str): - with open(js_proxy.path, 'w') as fp: + with open(js_proxy.path, "w") as fp: fp.write(js_proxy.data) return TextFile(path=js_proxy.path) elif hasattr(js_proxy, "path") and hasattr(js_proxy, "data"): - with open(js_proxy.path, 'wb') as fp: + with open(js_proxy.path, "wb") as fp: js_proxy.data.to_file(fp) return BinaryFile(path=js_proxy.path) elif hasattr(js_proxy, "data") and isinstance(js_proxy.data, str): @@ -143,16 +160,18 @@ def to_py(js_proxy): return TextStream(**text_stream_dict) elif hasattr(js_proxy, "data"): binary_stream_dict = js_proxy.to_py() - binary_stream_dict['data'] = bytes(binary_stream_dict['data']) + binary_stream_dict["data"] = bytes(binary_stream_dict["data"]) return BinaryStream(**binary_stream_dict) elif isinstance(js_proxy, pyodide.ffi.JsProxy): return js_proxy.to_py() # int, etc return js_proxy + def to_js(py, **kwargs): import pyodide import js + if isinstance(py, list): js_array = pyodide.ffi.to_js([]) for value in py: @@ -160,48 +179,48 @@ def to_js(py, **kwargs): return js_array elif isinstance(py, Image): image_dict = asdict(py) - image_dict['direction'] = image_dict['direction'].ravel() - if image_dict['data'] is not None: - image_dict['data'] = image_dict['data'].ravel() - if 'metadata' in image_dict: - image_dict['metadata'] = pyodide.ffi.to_js(image_dict['metadata'], dict_converter=js.Map.new) + image_dict["direction"] = image_dict["direction"].ravel() + if image_dict["data"] is not None: + image_dict["data"] = image_dict["data"].ravel() + if "metadata" in image_dict: + image_dict["metadata"] = pyodide.ffi.to_js(image_dict["metadata"], dict_converter=js.Map.new) else: - image_dict['metadata'] = pyodide.ffi.to_js(dict(), dict_converter=js.Map.new) + image_dict["metadata"] = pyodide.ffi.to_js(dict(), dict_converter=js.Map.new) return pyodide.ffi.to_js(image_dict, dict_converter=js.Object.fromEntries) elif isinstance(py, PointSet): point_set_dict = asdict(py) - if point_set_dict['points'] is not None: - point_set_dict['points'] = point_set_dict['points'].ravel() - if point_set_dict['pointData'] is not None: - point_set_dict['pointData'] = point_set_dict['pointData'].ravel() + if point_set_dict["points"] is not None: + point_set_dict["points"] = point_set_dict["points"].ravel() + if point_set_dict["pointData"] is not None: + point_set_dict["pointData"] = point_set_dict["pointData"].ravel() return pyodide.ffi.to_js(point_set_dict, dict_converter=js.Object.fromEntries) elif isinstance(py, Mesh): mesh_dict = asdict(py) - if mesh_dict['points'] is not None: - mesh_dict['points'] = mesh_dict['points'].ravel() - if mesh_dict['pointData'] is not None: - mesh_dict['pointData'] = mesh_dict['pointData'].ravel() - if mesh_dict['cells'] is not None: - mesh_dict['cells'] = mesh_dict['cells'].ravel() - if mesh_dict['cellData'] is not None: - mesh_dict['cellData'] = mesh_dict['cellData'].ravel() + if mesh_dict["points"] is not None: + mesh_dict["points"] = mesh_dict["points"].ravel() + if mesh_dict["pointData"] is not None: + mesh_dict["pointData"] = mesh_dict["pointData"].ravel() + if mesh_dict["cells"] is not None: + mesh_dict["cells"] = mesh_dict["cells"].ravel() + if mesh_dict["cellData"] is not None: + mesh_dict["cellData"] = mesh_dict["cellData"].ravel() return pyodide.ffi.to_js(mesh_dict, dict_converter=js.Object.fromEntries) elif isinstance(py, PolyData): polydata_dict = asdict(py) - if polydata_dict['points'] is not None: - polydata_dict['points'] = polydata_dict['points'].ravel() - if polydata_dict['vertices'] is not None: - polydata_dict['vertices'] = polydata_dict['vertices'].ravel() - if polydata_dict['lines'] is not None: - polydata_dict['lines'] = polydata_dict['lines'].ravel() - if polydata_dict['polygons'] is not None: - polydata_dict['polygons'] = polydata_dict['polygons'].ravel() - if polydata_dict['triangleStrips'] is not None: - polydata_dict['triangleStrips'] = polydata_dict['triangleStrips'].ravel() - if polydata_dict['pointData'] is not None: - polydata_dict['pointData'] = polydata_dict['pointData'].ravel() - if polydata_dict['cellData'] is not None: - polydata_dict['cellData'] = polydata_dict['cellData'].ravel() + if polydata_dict["points"] is not None: + polydata_dict["points"] = polydata_dict["points"].ravel() + if polydata_dict["vertices"] is not None: + polydata_dict["vertices"] = polydata_dict["vertices"].ravel() + if polydata_dict["lines"] is not None: + polydata_dict["lines"] = polydata_dict["lines"].ravel() + if polydata_dict["polygons"] is not None: + polydata_dict["polygons"] = polydata_dict["polygons"].ravel() + if polydata_dict["triangleStrips"] is not None: + polydata_dict["triangleStrips"] = polydata_dict["triangleStrips"].ravel() + if polydata_dict["pointData"] is not None: + polydata_dict["pointData"] = polydata_dict["pointData"].ravel() + if polydata_dict["cellData"] is not None: + polydata_dict["cellData"] = polydata_dict["cellData"].ravel() return pyodide.ffi.to_js(polydata_dict, dict_converter=js.Object.fromEntries) elif isinstance(py, TextStream): text_stream_dict = asdict(py) @@ -211,15 +230,15 @@ def to_js(py, **kwargs): return pyodide.ffi.to_js(binary_stream_dict, dict_converter=js.Object.fromEntries) elif isinstance(py, BinaryFile): binary_file_dict = asdict(py) - with open(py.path, 'rb') as fp: + with open(py.path, "rb") as fp: data = fp.read() - binary_file_dict['data'] = data + binary_file_dict["data"] = data return pyodide.ffi.to_js(binary_file_dict, dict_converter=js.Object.fromEntries) elif isinstance(py, TextFile): text_file_dict = asdict(py) - with open(py.path, 'r') as fp: + with open(py.path, "r") as fp: data = fp.read() - text_file_dict['data'] = data + text_file_dict["data"] = data return pyodide.ffi.to_js(text_file_dict, dict_converter=js.Object.fromEntries) return pyodide.ffi.to_js(py, **kwargs) diff --git a/packages/core/python/itkwasm/itkwasm/text_file.py b/packages/core/python/itkwasm/itkwasm/text_file.py index 762f4c0ec..556ba6591 100644 --- a/packages/core/python/itkwasm/itkwasm/text_file.py +++ b/packages/core/python/itkwasm/itkwasm/text_file.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from pathlib import PurePosixPath + @dataclass class TextFile: - path: PurePosixPath \ No newline at end of file + path: PurePosixPath diff --git a/packages/core/python/itkwasm/itkwasm/text_stream.py b/packages/core/python/itkwasm/itkwasm/text_stream.py index 4194c2d23..b71bfa4e2 100644 --- a/packages/core/python/itkwasm/itkwasm/text_stream.py +++ b/packages/core/python/itkwasm/itkwasm/text_stream.py @@ -1,5 +1,6 @@ from dataclasses import dataclass + @dataclass class TextStream: - data: str \ No newline at end of file + data: str diff --git a/packages/core/python/itkwasm/itkwasm/to_cupy_array.py b/packages/core/python/itkwasm/itkwasm/to_cupy_array.py index f6dd20b2e..d0d63dc19 100644 --- a/packages/core/python/itkwasm/itkwasm/to_cupy_array.py +++ b/packages/core/python/itkwasm/itkwasm/to_cupy_array.py @@ -1,4 +1,5 @@ import sys + if sys.version_info < (3, 10): from importlib_metadata import distribution else: @@ -8,16 +9,18 @@ try: import cupy + _CUPY_AVAILABLE = True except: _CUPY_AVAILABLE = False try: - distribution('dask') + distribution("dask") _DASK_AVAILABLE = True except: _DASK_AVAILABLE = False + def is_cupy_array(arr: ArrayLike) -> bool: """Check if the input is a CuPy array. @@ -30,8 +33,10 @@ def is_cupy_array(arr: ArrayLike) -> bool: if not _CUPY_AVAILABLE: return False import cupy as cp + return isinstance(arr, cp.ndarray) + def array_like_to_cupy_array(arr: ArrayLike) -> "cp.ndarray": """Convert a numpy array-like to a cupy ndarray. @@ -42,10 +47,12 @@ def array_like_to_cupy_array(arr: ArrayLike) -> "cp.ndarray": :rtype: cp.ndarray """ import cupy as cp + if isinstance(arr, cp.ndarray): return arr if _DASK_AVAILABLE: import dask.array as da + if isinstance(arr, da.Array): arr = arr.compute() if isinstance(arr, cp.ndarray): diff --git a/packages/core/python/itkwasm/itkwasm/to_numpy_array.py b/packages/core/python/itkwasm/itkwasm/to_numpy_array.py index 8d82166b2..0a635898e 100644 --- a/packages/core/python/itkwasm/itkwasm/to_numpy_array.py +++ b/packages/core/python/itkwasm/itkwasm/to_numpy_array.py @@ -1,5 +1,6 @@ from typing import Union import sys + if sys.version_info < (3, 10): from importlib_metadata import distribution else: @@ -13,11 +14,12 @@ from .to_cupy_array import is_cupy_array try: - distribution('dask') + distribution("dask") _DASK_AVAILABLE = True except: _DASK_AVAILABLE = False + def array_like_to_numpy_array(arr: ArrayLike) -> np.ndarray: """Convert a numpy array-like to a numpy ndarray. @@ -31,18 +33,21 @@ def array_like_to_numpy_array(arr: ArrayLike) -> np.ndarray: return arr if _DASK_AVAILABLE: import dask.array as da + if isinstance(arr, da.Array): arr = arr.compute() if is_cupy_array(arr): return arr.get() return np.array(arr) + def array_like_to_bytes(arr: ArrayLike) -> bytes: """Convert a numpy array-like to bytes.""" - if hasattr(arr, 'tobytes'): + if hasattr(arr, "tobytes"): return arr.tobytes() return array_like_to_numpy_array(arr).tobytes() + def buffer_to_numpy_array(component_type: Union[IntTypes, FloatTypes], buf): if component_type == IntTypes.UInt8: return np.frombuffer(buf, dtype=np.uint8) @@ -65,7 +70,8 @@ def buffer_to_numpy_array(component_type: Union[IntTypes, FloatTypes], buf): elif component_type == FloatTypes.Float64: return np.frombuffer(buf, dtype=np.float64) else: - raise ValueError('Unsupported component type') + raise ValueError("Unsupported component type") + def _dtype_to_component_type(dtype): if dtype == np.uint8: @@ -89,4 +95,4 @@ def _dtype_to_component_type(dtype): elif dtype == np.float64: return FloatTypes.Float64 else: - raise ValueError('Unsupported dtype') \ No newline at end of file + raise ValueError("Unsupported dtype") diff --git a/packages/core/python/itkwasm/pyproject.toml b/packages/core/python/itkwasm/pyproject.toml index 8bfe84d1d..e49b281c6 100644 --- a/packages/core/python/itkwasm/pyproject.toml +++ b/packages/core/python/itkwasm/pyproject.toml @@ -19,6 +19,7 @@ classifiers = [ 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', ] keywords = [ "itk", @@ -43,7 +44,7 @@ Issues = "https://github.com/InsightSoftwareConsortium/itk-wasm/issues" [tool.hatch.envs.default] dependencies = [ - "itk>=5.3.0", + "itk>=5.4.0", "pytest >=2.7.3", "pytest-pyodide", "dask[array]", @@ -64,3 +65,6 @@ download-pyodide = [ [tool.hatch.version] path = "itkwasm/__init__.py" + +[tool.black] +line-length = 120 diff --git a/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/__init__.py b/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/__init__.py index 942e528c4..b28547386 100644 --- a/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/__init__.py +++ b/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/__init__.py @@ -1,3 +1,3 @@ from .faster_mod import faster from .fastest_mod import fastest -from .slower_mod import slower \ No newline at end of file +from .slower_mod import slower diff --git a/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/faster_mod.py b/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/faster_mod.py index 33be5644f..7ea6952d9 100644 --- a/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/faster_mod.py +++ b/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/faster_mod.py @@ -1,2 +1,2 @@ def faster(): - pass \ No newline at end of file + pass diff --git a/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/fastest_mod.py b/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/fastest_mod.py index 86d23bdaf..d39b810a4 100644 --- a/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/fastest_mod.py +++ b/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/fastest_mod.py @@ -1,2 +1,2 @@ def fastest(): - pass \ No newline at end of file + pass diff --git a/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/slower_mod.py b/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/slower_mod.py index 00a7ee262..9fb1b3fe5 100644 --- a/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/slower_mod.py +++ b/packages/core/python/itkwasm/test/test-accelerator/test_accelerator/slower_mod.py @@ -1,2 +1,2 @@ def slower(): - pass \ No newline at end of file + pass diff --git a/packages/core/python/itkwasm/test/test_cast_image.py b/packages/core/python/itkwasm/test/test_cast_image.py index 0cbea80e7..8900acb64 100644 --- a/packages/core/python/itkwasm/test/test_cast_image.py +++ b/packages/core/python/itkwasm/test/test_cast_image.py @@ -8,6 +8,7 @@ from itkwasm import cast_image + def test_cast_image_component(): data = Path(__file__).absolute().parent / "input" / "cthead1.png" itk_image = itk.imread(data, itk.UC) @@ -18,6 +19,7 @@ def test_cast_image_component(): assert itkwasm_image_double.imageType.componentType == FloatTypes.Float64 assert np.array_equal(itkwasm_image.data, itkwasm_image_double.data) + def test_cast_image_pixel_type(): data = Path(__file__).absolute().parent / "input" / "cthead1.png" itk_image = itk.imread(data, itk.UC) diff --git a/packages/core/python/itkwasm/test/test_function_factory.py b/packages/core/python/itkwasm/test/test_function_factory.py index c655ce8e8..0570999ee 100644 --- a/packages/core/python/itkwasm/test/test_function_factory.py +++ b/packages/core/python/itkwasm/test/test_function_factory.py @@ -1,9 +1,11 @@ import pytest + def test_function_factory(): test_accelerator = pytest.importorskip("test_accelerator") from itkwasm import function_factory + interface_package = "itkwasm_example_package" func_name = "example_function" registered = function_factory.lookup(interface_package, func_name) @@ -23,16 +25,20 @@ def test_function_factory(): highest = function_factory.highest_priority(interface_package, func_name) assert highest == None + def test_environment_dispatch(): test_accelerator = pytest.importorskip("test_accelerator") from itkwasm import function_factory, environment_dispatch + interface_package = "itkwasm_example_package" func_name = "example_function" from test_accelerator import fastest + function_factory.register(interface_package, func_name, fastest, 1) func = environment_dispatch(interface_package, func_name) from test_accelerator import fastest + assert func == fastest diff --git a/packages/core/python/itkwasm/test/test_image.py b/packages/core/python/itkwasm/test/test_image.py index e9a649421..6cbc4eb3b 100644 --- a/packages/core/python/itkwasm/test/test_image.py +++ b/packages/core/python/itkwasm/test/test_image.py @@ -6,6 +6,7 @@ from dataclasses import asdict import numpy as np + def test_image(): data = Path(__file__).absolute().parent / "input" / "cthead1.png" itk_image = itk.imread(data, itk.UC) @@ -16,12 +17,13 @@ def test_image(): difference = np.sum(itk.comparison_image_filter(itk_image, itk_image_roundtrip)) assert difference == 0.0 + def test_image_defaults(): image = Image() assert image.imageType.dimension == 2 - assert image.imageType.componentType == 'uint8' - assert image.imageType.pixelType == 'Scalar' + assert image.imageType.componentType == "uint8" + assert image.imageType.pixelType == "Scalar" assert image.imageType.components == 1 assert image.name == "Image" @@ -35,4 +37,4 @@ def test_image_defaults(): assert image.size[1] == 1 assert isinstance(image.metadata, dict) - assert image.data == None \ No newline at end of file + assert image.data == None diff --git a/packages/core/python/itkwasm/test/test_image_from_array.py b/packages/core/python/itkwasm/test/test_image_from_array.py index 6cbbadc1b..9d08fcd0a 100644 --- a/packages/core/python/itkwasm/test/test_image_from_array.py +++ b/packages/core/python/itkwasm/test/test_image_from_array.py @@ -2,6 +2,7 @@ from itkwasm import image_from_array, ImageType, PixelTypes, IntTypes, FloatTypes + def test_image_from_array(): arr = np.random.rand(10, 10) image = image_from_array(arr) @@ -11,6 +12,7 @@ def test_image_from_array(): assert image.imageType.pixelType == PixelTypes.Scalar assert image.imageType.components == 1 + def test_image_from_array_anisotropic(): arr = np.random.rand(10, 5) image = image_from_array(arr) @@ -22,6 +24,7 @@ def test_image_from_array_anisotropic(): assert image.size[0] == 5 assert image.size[1] == 10 + def test_image_from_array_vector(): arr = np.random.rand(10, 10, 3) image = image_from_array(arr, is_vector=True) @@ -31,13 +34,14 @@ def test_image_from_array_vector(): assert image.imageType.pixelType == PixelTypes.VariableLengthVector assert image.imageType.components == 3 + def test_image_from_array_explicit(): arr = np.random.rand(10, 10, 3) image_type = { - 'dimension': 2, - 'componentType': FloatTypes.Float32, - 'pixelType': PixelTypes.VariableLengthVector, - 'components': 3, + "dimension": 2, + "componentType": FloatTypes.Float32, + "pixelType": PixelTypes.VariableLengthVector, + "components": 3, } image_type = ImageType(**image_type) image = image_from_array(arr, image_type=image_type) @@ -45,4 +49,4 @@ def test_image_from_array_explicit(): assert image.imageType.dimension == 2 assert image.imageType.componentType == FloatTypes.Float32 assert image.imageType.pixelType == PixelTypes.VariableLengthVector - assert image.imageType.components == 3 \ No newline at end of file + assert image.imageType.components == 3 diff --git a/packages/core/python/itkwasm/test/test_js_package_config.py b/packages/core/python/itkwasm/test/test_js_package_config.py index ea80dcff4..4b92f883c 100644 --- a/packages/core/python/itkwasm/test/test_js_package_config.py +++ b/packages/core/python/itkwasm/test/test_js_package_config.py @@ -1,16 +1,18 @@ - def test_itkwasm_js_package_config(): - from itkwasm.pyodide import JsPackageConfig - module_url = 'https://cdn.jsdelivr.net/npm/@itk-wasm/compress-stringify@0.4.2/dist/bundles/compress-stringify.js' - pipelines_base_url = 'https://cdn.jsdelivr.net/npm/@itk-wasm/compress-stringify@0.4.2/dist/pipelines' - pipeline_worker_url = 'https://cdn.jsdelivr.net/npm/@itk-wasm/compress-stringify@0.4.2/dist/web-workers/pipeline.worker.js' + from itkwasm.pyodide import JsPackageConfig + + module_url = "https://cdn.jsdelivr.net/npm/@itk-wasm/compress-stringify@0.4.2/dist/bundles/compress-stringify.js" + pipelines_base_url = "https://cdn.jsdelivr.net/npm/@itk-wasm/compress-stringify@0.4.2/dist/pipelines" + pipeline_worker_url = ( + "https://cdn.jsdelivr.net/npm/@itk-wasm/compress-stringify@0.4.2/dist/web-workers/pipeline.worker.js" + ) - config = JsPackageConfig(module_url, pipelines_base_url, pipeline_worker_url) - assert config.module_url == module_url - assert config.pipelines_base_url == pipelines_base_url - assert config.pipeline_worker_url == pipeline_worker_url + config = JsPackageConfig(module_url, pipelines_base_url, pipeline_worker_url) + assert config.module_url == module_url + assert config.pipelines_base_url == pipelines_base_url + assert config.pipeline_worker_url == pipeline_worker_url - config = JsPackageConfig(module_url) - assert config.module_url == module_url - assert config.pipelines_base_url is None - assert config.pipeline_worker_url is None + config = JsPackageConfig(module_url) + assert config.module_url == module_url + assert config.pipelines_base_url is None + assert config.pipeline_worker_url is None diff --git a/packages/core/python/itkwasm/test/test_mesh.py b/packages/core/python/itkwasm/test/test_mesh.py index 01c96ae5f..c6bdd7d62 100644 --- a/packages/core/python/itkwasm/test/test_mesh.py +++ b/packages/core/python/itkwasm/test/test_mesh.py @@ -6,6 +6,7 @@ from dataclasses import asdict import numpy as np + def test_mesh(): data = Path(__file__).absolute().parent / "input" / "cow.vtk" itk_mesh = itk.meshread(data) @@ -43,4 +44,4 @@ def test_mesh(): assert itk_mesh_dict["cellBufferSize"] == itk_mesh_roundtrip_dict["cellBufferSize"] assert itk_mesh_dict["numberOfCellPixels"] == itk_mesh_roundtrip_dict["numberOfCellPixels"] - assert np.array_equal(itk_mesh_dict["cellData"], itk_mesh_roundtrip_dict["cellData"]) \ No newline at end of file + assert np.array_equal(itk_mesh_dict["cellData"], itk_mesh_roundtrip_dict["cellData"]) diff --git a/packages/core/python/itkwasm/test/test_pipeline.py b/packages/core/python/itkwasm/test/test_pipeline.py index dbdc02717..ace299c01 100644 --- a/packages/core/python/itkwasm/test/test_pipeline.py +++ b/packages/core/python/itkwasm/test/test_pipeline.py @@ -8,31 +8,44 @@ import itk import numpy as np -from itkwasm import InterfaceTypes, TextStream, BinaryStream, PipelineInput, PipelineOutput, Pipeline, TextFile, BinaryFile, Image, Mesh - -test_input_dir = Path(__file__).resolve().parent / 'input' -test_baseline_dir = Path(__file__).resolve().parent / 'baseline' +from itkwasm import ( + InterfaceTypes, + TextStream, + BinaryStream, + PipelineInput, + PipelineOutput, + Pipeline, + TextFile, + BinaryFile, + Image, + Mesh, +) + +test_input_dir = Path(__file__).resolve().parent / "input" +test_baseline_dir = Path(__file__).resolve().parent / "baseline" def test_stdout_stderr(): - pipeline = Pipeline(test_input_dir / 'stdout-stderr-test.wasi.wasm') + pipeline = Pipeline(test_input_dir / "stdout-stderr-test.wasi.wasm") pipeline.run([]) # Test re-run pipeline.run([]) + def test_pipeline_bytes(): - pipeline_path = test_input_dir / 'stdout-stderr-test.wasi.wasm' - with open(pipeline_path, 'rb') as fp: + pipeline_path = test_input_dir / "stdout-stderr-test.wasi.wasm" + with open(pipeline_path, "rb") as fp: wasm_bytes = fp.read() pipeline = Pipeline(wasm_bytes) pipeline.run([]) + def test_pipeline_input_output_streams(): - pipeline = Pipeline(test_input_dir / 'input-output-files-test.wasi.wasm') + pipeline = Pipeline(test_input_dir / "input-output-files-test.wasi.wasm") pipeline_inputs = [ - PipelineInput(InterfaceTypes.TextStream, TextStream('The answer is 42.')), + PipelineInput(InterfaceTypes.TextStream, TextStream("The answer is 42.")), PipelineInput(InterfaceTypes.BinaryStream, BinaryStream(bytes([222, 173, 190, 239]))), ] @@ -42,11 +55,15 @@ def test_pipeline_input_output_streams(): ] args = [ - '--memory-io', - '--input-text-stream', '0', - '--input-binary-stream', '1', - '--output-text-stream', '0', - '--output-binary-stream', '1' + "--memory-io", + "--input-text-stream", + "0", + "--input-binary-stream", + "1", + "--output-text-stream", + "0", + "--output-binary-stream", + "1", ] outputs = pipeline.run(args, pipeline_outputs, pipeline_inputs) @@ -55,7 +72,7 @@ def test_pipeline_input_output_streams(): outputs = pipeline.run(args, pipeline_outputs, pipeline_inputs) assert outputs[0].type == InterfaceTypes.TextStream - assert outputs[0].data.data == 'The answer is 42.' + assert outputs[0].data.data == "The answer is 42." assert outputs[1].type, InterfaceTypes.BinaryStream assert outputs[1].data.data[0], 222 assert outputs[1].data.data[1], 173 @@ -63,15 +80,18 @@ def test_pipeline_input_output_streams(): assert outputs[1].data.data[3], 239 -@pytest.mark.skipif(sys.platform == "win32", reason="Windows tempfile resource, https://github.com/bytecodealliance/wasmtime-py/issues/132") +@pytest.mark.skipif( + sys.platform == "win32", + reason="Windows tempfile resource, https://github.com/bytecodealliance/wasmtime-py/issues/132", +) def test_pipeline_input_output_files(): - pipeline = Pipeline(test_input_dir / 'input-output-files-test.wasi.wasm') - input_text_file = PurePosixPath(test_input_dir / 'input.txt') - input_binary_file = PurePosixPath(test_input_dir / 'input.bin') + pipeline = Pipeline(test_input_dir / "input-output-files-test.wasi.wasm") + input_text_file = PurePosixPath(test_input_dir / "input.txt") + input_binary_file = PurePosixPath(test_input_dir / "input.bin") with tempfile.TemporaryDirectory() as tmpdirname: - output_text_file = PurePosixPath(Path(tmpdirname) / 'output.txt') - output_binary_file = PurePosixPath(Path(tmpdirname) / 'output.bin') + output_text_file = PurePosixPath(Path(tmpdirname) / "output.txt") + output_binary_file = PurePosixPath(Path(tmpdirname) / "output.bin") pipeline_inputs = [ PipelineInput(InterfaceTypes.TextFile, TextFile(input_text_file)), @@ -84,12 +104,16 @@ def test_pipeline_input_output_files(): ] args = [ - '--memory-io', - '--use-files', - '--input-text-file', str(input_text_file), - '--input-binary-file', str(input_binary_file), - '--output-text-file', str(output_text_file), - '--output-binary-file', str(output_binary_file), + "--memory-io", + "--use-files", + "--input-text-file", + str(input_text_file), + "--input-binary-file", + str(input_binary_file), + "--output-text-file", + str(output_text_file), + "--output-binary-file", + str(output_binary_file), ] outputs = pipeline.run(args, pipeline_outputs, pipeline_inputs) @@ -98,19 +122,20 @@ def test_pipeline_input_output_files(): outputs = pipeline.run(args, pipeline_outputs, pipeline_inputs) assert outputs[0].type == InterfaceTypes.TextFile - with open(outputs[0].data.path, 'r') as fp: + with open(outputs[0].data.path, "r") as fp: content = fp.read() - assert content == 'The answer is 42.' + assert content == "The answer is 42." assert outputs[1].type, InterfaceTypes.BinaryFile - with open(outputs[1].data.path, 'rb') as fp: + with open(outputs[1].data.path, "rb") as fp: content = fp.read() assert content[0] == 222 assert content[1] == 173 assert content[2] == 190 assert content[3] == 239 + def test_pipeline_write_read_image(): - pipeline = Pipeline(test_input_dir / 'median-filter-test.wasi.wasm') + pipeline = Pipeline(test_input_dir / "median-filter-test.wasi.wasm") data = test_input_dir / "cthead1.png" itk_image = itk.imread(data, itk.UC) @@ -126,25 +151,27 @@ def test_pipeline_write_read_image(): ] args = [ - '--memory-io', - '0', - '0', - '--radius', '2', - ] + "--memory-io", + "0", + "0", + "--radius", + "2", + ] outputs = pipeline.run(args, pipeline_outputs, pipeline_inputs) out_image = itk.image_from_dict(asdict(outputs[0].data)) # To be addressed in itk-5.3.1 - out_image.SetRegions([256,256]) + out_image.SetRegions([256, 256]) baseline = itk.imread(test_baseline_dir / "test_pipeline_write_read_image.png") difference = np.sum(itk.comparison_image_filter(out_image, baseline)) assert difference == 0.0 + def test_pipeline_dask_array_input(): - pipeline = Pipeline(test_input_dir / 'median-filter-test.wasi.wasm') + pipeline = Pipeline(test_input_dir / "median-filter-test.wasi.wasm") data = test_input_dir / "cthead1.png" itk_image = itk.imread(data, itk.UC) @@ -152,6 +179,7 @@ def test_pipeline_dask_array_input(): itkwasm_image = Image(**itk_image_dict) from dask.array import from_array + itkwasm_image.data = from_array(itkwasm_image.data) pipeline_inputs = [ @@ -163,25 +191,27 @@ def test_pipeline_dask_array_input(): ] args = [ - '--memory-io', - '0', - '0', - '--radius', '2', - ] + "--memory-io", + "0", + "0", + "--radius", + "2", + ] outputs = pipeline.run(args, pipeline_outputs, pipeline_inputs) out_image = itk.image_from_dict(asdict(outputs[0].data)) # To be addressed in itk-5.3.1 - out_image.SetRegions([256,256]) + out_image.SetRegions([256, 256]) baseline = itk.imread(test_baseline_dir / "test_pipeline_write_read_image.png") difference = np.sum(itk.comparison_image_filter(out_image, baseline)) assert difference == 0.0 + def test_pipeline_write_read_mesh(): - pipeline = Pipeline(test_input_dir / 'mesh-read-write-test.wasi.wasm') + pipeline = Pipeline(test_input_dir / "mesh-read-write-test.wasi.wasm") data = test_input_dir / "cow.vtk" itk_mesh = itk.meshread(data) @@ -197,24 +227,25 @@ def test_pipeline_write_read_mesh(): ] args = [ - '--memory-io', - '0', - '0', - ] + "--memory-io", + "0", + "0", + ] outputs = pipeline.run(args, pipeline_outputs, pipeline_inputs) out_mesh_dict = asdict(outputs[0].data) # Native ITK Python binaries require uint64 - out_mesh_dict['cells'] = out_mesh_dict['cells'].astype(np.uint64) - out_mesh_dict['meshType']['cellComponentType'] = 'uint64' + out_mesh_dict["cells"] = out_mesh_dict["cells"].astype(np.uint64) + out_mesh_dict["meshType"]["cellComponentType"] = "uint64" out_mesh = itk.mesh_from_dict(out_mesh_dict) assert out_mesh.GetNumberOfPoints() == 2903 assert out_mesh.GetNumberOfCells() == 3263 + def test_pipeline_write_read_polydata(): - pipeline = Pipeline(test_input_dir / 'mesh-to-poly-data.wasi.wasm') + pipeline = Pipeline(test_input_dir / "mesh-to-poly-data.wasi.wasm") data = test_input_dir / "cow.vtk" itk_mesh = itk.meshread(data) @@ -230,15 +261,15 @@ def test_pipeline_write_read_polydata(): ] args = [ - '--memory-io', - '0', - '0', - ] + "--memory-io", + "0", + "0", + ] outputs = pipeline.run(args, pipeline_outputs, pipeline_inputs) polydata = outputs[0].data - pipeline = Pipeline(test_input_dir / 'poly-data-to-mesh.wasi.wasm') + pipeline = Pipeline(test_input_dir / "poly-data-to-mesh.wasi.wasm") pipeline_inputs = [ PipelineInput(InterfaceTypes.PolyData, polydata), @@ -249,19 +280,19 @@ def test_pipeline_write_read_polydata(): ] args = [ - '--memory-io', - '0', - '0', - ] + "--memory-io", + "0", + "0", + ] outputs = pipeline.run(args, pipeline_outputs, pipeline_inputs) out_mesh_dict = asdict(outputs[0].data) # native itk python binaries require uint64 - out_mesh_dict['cells'] = out_mesh_dict['cells'].astype(np.uint64) - out_mesh_dict['meshType']['cellComponentType'] = 'uint64' - assert np.isclose(out_mesh_dict['points'][0], 3.71636) + out_mesh_dict["cells"] = out_mesh_dict["cells"].astype(np.uint64) + out_mesh_dict["meshType"]["cellComponentType"] = "uint64" + assert np.isclose(out_mesh_dict["points"][0], 3.71636) out_mesh = itk.mesh_from_dict(out_mesh_dict) assert out_mesh.GetNumberOfPoints() == 2903 diff --git a/packages/core/python/itkwasm/test/test_pointset.py b/packages/core/python/itkwasm/test/test_pointset.py index 28fb691a2..194cbaae8 100644 --- a/packages/core/python/itkwasm/test/test_pointset.py +++ b/packages/core/python/itkwasm/test/test_pointset.py @@ -4,6 +4,7 @@ from dataclasses import asdict import numpy as np + def test_pointset(): n_points = 5 dimension = 3 @@ -19,7 +20,7 @@ def test_pointset(): itk_pointset_dict = itk.dict_from_pointset(pointset) # Bug, to be fixed by 5.3.0 - itk_pointset_dict.pop('dimension', None) + itk_pointset_dict.pop("dimension", None) itkwasm_pointset = PointSet(**itk_pointset_dict) itkwasm_pointset_dict = asdict(itkwasm_pointset) itk_pointset_roundtrip = itk.pointset_from_dict(itkwasm_pointset_dict) @@ -40,4 +41,4 @@ def test_pointset(): assert np.array_equal(itk_pointset_dict["points"], itk_pointset_roundtrip_dict["points"]) assert itk_pointset_dict["numberOfPointPixels"] == itk_pointset_roundtrip_dict["numberOfPointPixels"] - assert np.array_equal(itk_pointset_dict["pointData"], itk_pointset_roundtrip_dict["pointData"]) \ No newline at end of file + assert np.array_equal(itk_pointset_dict["pointData"], itk_pointset_roundtrip_dict["pointData"]) diff --git a/packages/core/python/itkwasm/test/test_polydata.py b/packages/core/python/itkwasm/test/test_polydata.py index 137b1ddf8..d79d470d1 100644 --- a/packages/core/python/itkwasm/test/test_polydata.py +++ b/packages/core/python/itkwasm/test/test_polydata.py @@ -2,6 +2,7 @@ from itkwasm import PolyData + def test_polydata(): itkwasm_polydata = PolyData() - itkwasm_polydata_dict = asdict(itkwasm_polydata) \ No newline at end of file + itkwasm_polydata_dict = asdict(itkwasm_polydata) diff --git a/packages/core/python/itkwasm/test/test_pyodide.py b/packages/core/python/itkwasm/test/test_pyodide.py index aa35550b2..017748321 100644 --- a/packages/core/python/itkwasm/test/test_pyodide.py +++ b/packages/core/python/itkwasm/test/test_pyodide.py @@ -1,20 +1,23 @@ import pytest import sys -if sys.version_info < (3,10): +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 import __version__ as test_package_version + @pytest.fixture def package_wheel(): return f"itkwasm-{test_package_version}-py3-none-any.whl" -@run_in_pyodide(packages=['micropip', 'numpy']) + +@run_in_pyodide(packages=["micropip", "numpy"]) async def test_image_conversion(selenium, package_wheel): import micropip + await micropip.install(package_wheel) from itkwasm import Image @@ -24,8 +27,8 @@ async def test_image_conversion(selenium, package_wheel): image = Image() assert image.imageType.dimension == 2 - assert image.imageType.componentType == 'uint8' - assert image.imageType.pixelType == 'Scalar' + assert image.imageType.componentType == "uint8" + assert image.imageType.pixelType == "Scalar" assert image.imageType.components == 1 assert image.name == "Image" @@ -40,16 +43,16 @@ async def test_image_conversion(selenium, package_wheel): image.size = [4, 4] assert isinstance(image.metadata, dict) - image.metadata['a_string'] = 'some text' - image.metadata['an int'] = 3 + image.metadata["a_string"] = "some text" + image.metadata["an int"] = 3 assert image.data == None - image.data = np.arange(16, dtype=np.uint8).reshape((4,4)) + image.data = np.arange(16, dtype=np.uint8).reshape((4, 4)) image_js = to_js(image) image_py = to_py(image_js) assert image_py.imageType.dimension == 2 - assert image_py.imageType.componentType == 'uint8' - assert image_py.imageType.pixelType == 'Scalar' + assert image_py.imageType.componentType == "uint8" + assert image_py.imageType.pixelType == "Scalar" assert image_py.imageType.components == 1 assert image_py.name == "Image" @@ -62,15 +65,17 @@ async def test_image_conversion(selenium, package_wheel): assert image_py.size[0] == 4 assert image_py.size[1] == 4 - assert image_py.metadata['a_string'] == 'some text' - assert image_py.metadata['an int'] == 3 + assert image_py.metadata["a_string"] == "some text" + assert image_py.metadata["an int"] == 3 assert isinstance(image_py.metadata, dict) - assert np.array_equal(image_py.data, np.arange(16, dtype=np.uint8).reshape((4,4))) + assert np.array_equal(image_py.data, np.arange(16, dtype=np.uint8).reshape((4, 4))) -@run_in_pyodide(packages=['micropip', 'numpy']) + +@run_in_pyodide(packages=["micropip", "numpy"]) async def test_point_set_conversion(selenium, package_wheel): import micropip + await micropip.install(package_wheel) from itkwasm import PointSet, PointSetType, PixelTypes, FloatTypes @@ -83,9 +88,15 @@ async def test_point_set_conversion(selenium, package_wheel): points = np.random.random((n_points, dimension)).astype(np.float32) point_data = np.random.random((n_points,)).astype(np.float32) - point_set_type = PointSetType(dimension, FloatTypes.Float32, FloatTypes.Float32, PixelTypes.Scalar, FloatTypes.Float32) + point_set_type = PointSetType( + dimension, + FloatTypes.Float32, + FloatTypes.Float32, + PixelTypes.Scalar, + FloatTypes.Float32, + ) - point_set = PointSet(point_set_type, 'point_set', n_points, points, n_points, point_data) + point_set = PointSet(point_set_type, "point_set", n_points, points, n_points, point_data) point_set_js = to_js(point_set) point_set_py = to_py(point_set_js) @@ -103,9 +114,11 @@ async def test_point_set_conversion(selenium, package_wheel): assert point_set.numberOfPointPixels == point_set_py.numberOfPointPixels assert np.array_equal(point_set.pointData, point_set_py.pointData) -@run_in_pyodide(packages=['micropip', 'numpy']) + +@run_in_pyodide(packages=["micropip", "numpy"]) async def test_mesh_conversion(selenium, package_wheel): import micropip + await micropip.install(package_wheel) from itkwasm import Mesh, MeshType @@ -120,7 +133,13 @@ async def test_mesh_conversion(selenium, package_wheel): points = np.random.random((n_points, dimension)).astype(np.float32) point_data = np.random.random((n_points,)).astype(np.float32) - mesh = Mesh(mesh_type, points=points, numberOfPoints=n_points, pointData=point_data, numberOfPointPixels=n_points) + mesh = Mesh( + mesh_type, + points=points, + numberOfPoints=n_points, + pointData=point_data, + numberOfPointPixels=n_points, + ) mesh_js = to_js(mesh) mesh_py = to_py(mesh_js) @@ -138,9 +157,11 @@ async def test_mesh_conversion(selenium, package_wheel): assert mesh.numberOfPointPixels == mesh_py.numberOfPointPixels assert np.array_equal(mesh.pointData, mesh_py.pointData) -@run_in_pyodide(packages=['micropip', 'numpy']) + +@run_in_pyodide(packages=["micropip", "numpy"]) async def test_polydata_conversion(selenium, package_wheel): import micropip + await micropip.install(package_wheel) from itkwasm import PolyData, PolyDataType @@ -155,7 +176,13 @@ async def test_polydata_conversion(selenium, package_wheel): points = np.random.random((n_points, dimension)).astype(np.float32) point_data = np.random.random((n_points,)).astype(np.float32) - polydata = PolyData(polydata_type, points=points, numberOfPoints=n_points, pointData=point_data, numberOfPointPixels=n_points) + polydata = PolyData( + polydata_type, + points=points, + numberOfPoints=n_points, + pointData=point_data, + numberOfPointPixels=n_points, + ) polydata_js = to_js(polydata) polydata_py = to_py(polydata_js) @@ -171,15 +198,17 @@ async def test_polydata_conversion(selenium, package_wheel): assert polydata.numberOfPointPixels == polydata_py.numberOfPointPixels assert np.array_equal(polydata.pointData, polydata_py.pointData) -@run_in_pyodide(packages=['micropip', 'numpy']) + +@run_in_pyodide(packages=["micropip", "numpy"]) async def test_binary_stream_conversion(selenium, package_wheel): import micropip + await micropip.install(package_wheel) from itkwasm import BinaryStream from itkwasm.pyodide import to_js, to_py - data = bytes([222,173,190,239]) + data = bytes([222, 173, 190, 239]) binary_stream = BinaryStream(data) binary_stream_js = to_js(binary_stream) @@ -190,9 +219,11 @@ async def test_binary_stream_conversion(selenium, package_wheel): assert binary_stream_py.data[2], 190 assert binary_stream_py.data[3], 239 -@run_in_pyodide(packages=['micropip', 'numpy']) + +@run_in_pyodide(packages=["micropip", "numpy"]) async def test_text_stream_conversion(selenium, package_wheel): import micropip + await micropip.install(package_wheel) from itkwasm import TextStream @@ -206,9 +237,11 @@ async def test_text_stream_conversion(selenium, package_wheel): assert text_stream_py.data == data -@run_in_pyodide(packages=['micropip', 'numpy']) + +@run_in_pyodide(packages=["micropip", "numpy"]) async def test_binary_file_conversion(selenium, package_wheel): import micropip + await micropip.install(package_wheel) from itkwasm import BinaryFile @@ -216,16 +249,16 @@ async def test_binary_file_conversion(selenium, package_wheel): import numpy as np from pathlib import PurePosixPath - data = bytes([222,173,190,239]) - path = PurePosixPath('file.bin') - with open(path, 'wb') as fp: + data = bytes([222, 173, 190, 239]) + path = PurePosixPath("file.bin") + with open(path, "wb") as fp: fp.write(data) binary_file = BinaryFile(path) binary_file_js = to_js(binary_file) binary_file_py = to_py(binary_file_js) - with open(binary_file_py.path, 'rb') as fp: + with open(binary_file_py.path, "rb") as fp: data_py = fp.read() assert data_py[0], 222 @@ -233,9 +266,11 @@ async def test_binary_file_conversion(selenium, package_wheel): assert data_py[2], 190 assert data_py[3], 239 -@run_in_pyodide(packages=['micropip', 'numpy']) + +@run_in_pyodide(packages=["micropip", "numpy"]) async def test_text_file_conversion(selenium, package_wheel): import micropip + await micropip.install(package_wheel) from itkwasm import TextFile @@ -244,22 +279,24 @@ async def test_text_file_conversion(selenium, package_wheel): from pathlib import PurePosixPath data = "The answer is 42." - path = PurePosixPath('file.txt') - with open(path, 'w') as fp: + path = PurePosixPath("file.txt") + with open(path, "w") as fp: fp.write(data) text_file = TextFile(path) text_file_js = to_js(text_file) text_file_py = to_py(text_file_js) - with open(text_file_py.path, 'r') as fp: + with open(text_file_py.path, "r") as fp: data_py = fp.read() assert data_py == data -@run_in_pyodide(packages=['micropip', 'numpy']) + +@run_in_pyodide(packages=["micropip", "numpy"]) async def test_list_conversion(selenium, package_wheel): import micropip + await micropip.install(package_wheel) from itkwasm import TextFile @@ -270,8 +307,8 @@ async def test_list_conversion(selenium, package_wheel): data = "The answer is 42." def create_text_file(index): - path = PurePosixPath(f'file{index}.txt') - with open(path, 'w') as fp: + path = PurePosixPath(f"file{index}.txt") + with open(path, "w") as fp: fp.write(data) text_file = TextFile(path) return text_file @@ -282,7 +319,7 @@ def create_text_file(index): text_files_py = to_py(text_files_js) def verify_text_file(text_file): - with open(text_file.path, 'r') as fp: + with open(text_file.path, "r") as fp: data_py = fp.read() assert data_py == data @@ -290,14 +327,16 @@ def verify_text_file(text_file): for text_file in text_files_py: verify_text_file(text_file) -@run_in_pyodide(packages=['micropip', 'numpy']) + +@run_in_pyodide(packages=["micropip", "numpy"]) async def test_uint8array_conversion(selenium, package_wheel): import micropip + await micropip.install(package_wheel) from itkwasm.pyodide import to_js, to_py - data = bytes([222,173,190,239]) + data = bytes([222, 173, 190, 239]) data_js = to_js(data) data_py = to_py(data_js) @@ -315,20 +354,22 @@ async def test_uint8array_conversion(selenium, package_wheel): assert ivalue == ivalue_py -@run_in_pyodide(packages=['micropip']) + +@run_in_pyodide(packages=["micropip"]) async def test_json_object_conversion(selenium, package_wheel): import micropip + await micropip.install(package_wheel) from itkwasm.pyodide import to_js, to_py - data = { 'a': 1, 'b': True, 'c': { 'nested': True }} + data = {"a": 1, "b": True, "c": {"nested": True}} data_js = to_js(data) data_py = to_py(data_js) assert isinstance(data_py, dict) - assert data_py['a'] == 1 - assert data_py['b'] == True - assert data_py['c']['nested'] == True + assert data_py["a"] == 1 + assert data_py["b"] == True + assert data_py["c"]["nested"] == True diff --git a/packages/core/python/itkwasm/test/test_to_cupy_array.py b/packages/core/python/itkwasm/test/test_to_cupy_array.py index 177580254..136b86e75 100644 --- a/packages/core/python/itkwasm/test/test_to_cupy_array.py +++ b/packages/core/python/itkwasm/test/test_to_cupy_array.py @@ -5,6 +5,8 @@ from itkwasm import array_like_to_cupy_array pytest.importorskip("cupy") + + def test_array_like_to_numpy_array(): import cupy as cp @@ -16,4 +18,4 @@ def test_array_like_to_numpy_array(): arr = cp.array([1, 2, 3]) result = array_like_to_cupy_array(arr) assert isinstance(result, cp.ndarray) - assert np.array_equal(result.get(), arr.get()) \ No newline at end of file + assert np.array_equal(result.get(), arr.get()) diff --git a/packages/core/python/itkwasm/test/test_to_numpy_array.py b/packages/core/python/itkwasm/test/test_to_numpy_array.py index dda6a7109..e9ca6d2bd 100644 --- a/packages/core/python/itkwasm/test/test_to_numpy_array.py +++ b/packages/core/python/itkwasm/test/test_to_numpy_array.py @@ -5,6 +5,8 @@ from itkwasm import array_like_to_numpy_array pytest.importorskip("cupy") + + def test_array_like_to_numpy_array(): arr = np.array([1, 2, 3]) result = array_like_to_numpy_array(arr) @@ -12,7 +14,8 @@ def test_array_like_to_numpy_array(): assert np.array_equal(result, arr) import cupy as cp + arr = cp.array([1, 2, 3]) result = array_like_to_numpy_array(arr) assert isinstance(result, np.ndarray) - assert np.array_equal(result, arr.get()) \ No newline at end of file + assert np.array_equal(result, arr.get())