Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(python): support ITKWASM_CACHE_DIR for cache directory #1134

Merged
merged 4 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 37 additions & 32 deletions packages/core/python/itkwasm/itkwasm/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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",
]
3 changes: 2 additions & 1 deletion packages/core/python/itkwasm/itkwasm/binary_file.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from dataclasses import dataclass
from pathlib import PurePosixPath


@dataclass
class BinaryFile:
path: PurePosixPath
path: PurePosixPath
3 changes: 2 additions & 1 deletion packages/core/python/itkwasm/itkwasm/binary_stream.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from dataclasses import dataclass


@dataclass
class BinaryStream:
data: bytes
data: bytes
11 changes: 7 additions & 4 deletions packages/core/python/itkwasm/itkwasm/cast_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
29 changes: 18 additions & 11 deletions packages/core/python/itkwasm/itkwasm/environment_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand All @@ -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]
Expand All @@ -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
return func
9 changes: 5 additions & 4 deletions packages/core/python/itkwasm/itkwasm/float_types.py
Original file line number Diff line number Diff line change
@@ -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)}"'
return f'"{str(self.value)}"'
33 changes: 29 additions & 4 deletions packages/core/python/itkwasm/itkwasm/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,65 @@
from .float_types import FloatTypes
from .pixel_types import PixelTypes


@dataclass
class ImageType:
dimension: int = 2
componentType: Union[IntTypes, FloatTypes] = IntTypes.UInt8
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):
self.imageType = ImageType(**self.imageType)

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,
)
4 changes: 3 additions & 1 deletion packages/core/python/itkwasm/itkwasm/image_from_array.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Optional

try:
from numpy.typing import ArrayLike
except ImportError:
Expand All @@ -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.

Expand All @@ -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,
)

Expand Down
25 changes: 13 additions & 12 deletions packages/core/python/itkwasm/itkwasm/int_types.py
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
17 changes: 9 additions & 8 deletions packages/core/python/itkwasm/itkwasm/interface_types.py
Original file line number Diff line number Diff line change
@@ -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"
3 changes: 1 addition & 2 deletions packages/core/python/itkwasm/itkwasm/json_compatible.py
Original file line number Diff line number Diff line change
@@ -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"]]
Loading
Loading