-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Python): Add Pyodide Image support
- Loading branch information
Showing
11 changed files
with
209 additions
and
44 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,19 @@ | ||
# itkwasm | ||
|
||
Python interface to [itk-wasm](https://wasm.itk.org) WebAssembly modules. | ||
|
||
## Development | ||
|
||
Thank you for contributing a pull request! | ||
|
||
**Welcome to the ITK community!** | ||
|
||
We are glad you are here and appreciate your contribution. Please keep in mind our [community participation guidelines](https://github.com/InsightSoftwareConsortium/ITK/blob/main/CODE_OF_CONDUCT.md). | ||
|
||
``` | ||
git clone https://github.com/InsightSoftwareConsortium/itk-wasm | ||
cd itk-wasm/packages/core/python/itkwasm | ||
pip install hatch | ||
hatch run download-pyodide | ||
hatch run test | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import numpy as np | ||
|
||
from .int_types import IntTypes | ||
from .float_types import FloatTypes | ||
|
||
def _to_numpy_array(component_type, buf): | ||
if component_type == IntTypes.UInt8: | ||
return np.frombuffer(buf, dtype=np.uint8) | ||
elif component_type == IntTypes.Int8: | ||
return np.frombuffer(buf, dtype=np.int8) | ||
elif component_type == IntTypes.UInt16: | ||
return np.frombuffer(buf, dtype=np.uint16) | ||
elif component_type == IntTypes.Int16: | ||
return np.frombuffer(buf, dtype=np.int16) | ||
elif component_type == IntTypes.UInt32: | ||
return np.frombuffer(buf, dtype=np.uint32) | ||
elif component_type == IntTypes.Int32: | ||
return np.frombuffer(buf, dtype=np.int32) | ||
elif component_type == IntTypes.UInt64: | ||
return np.frombuffer(buf, dtype=np.uint64) | ||
elif component_type == IntTypes.Int64: | ||
return np.frombuffer(buf, dtype=np.int64) | ||
elif component_type == FloatTypes.Float32: | ||
return np.frombuffer(buf, dtype=np.float32) | ||
elif component_type == FloatTypes.Float64: | ||
return np.frombuffer(buf, dtype=np.float64) | ||
else: | ||
raise ValueError('Unsupported component type') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
from dataclasses import dataclass, asdict | ||
from typing import Optional | ||
|
||
from .image import Image, ImageType | ||
from .pointset import PointSet, PointSetType | ||
from .mesh import Mesh, MeshType | ||
from .polydata import PolyData, PolyDataType | ||
from .binary_file import BinaryFile | ||
from .binary_stream import BinaryStream | ||
from .text_file import TextFile | ||
from .text_stream import TextStream | ||
from .float_types import FloatTypes | ||
from .int_types import IntTypes | ||
from .pixel_types import PixelTypes | ||
from ._to_numpy_array import _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 | ||
self._js_module = None | ||
|
||
@property | ||
def config(self): | ||
return self._config | ||
|
||
@config.setter | ||
def config(self, value): | ||
self._config = value | ||
|
||
@property | ||
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) | ||
if self._config.pipeline_worker_url is not None: | ||
js_module.setPipelineWorkerUrl(self._config.pipeline_worker_url) | ||
self._js_module = js_module | ||
return js_module | ||
|
||
class JsResources: | ||
def __init__(self): | ||
self._web_worker = None | ||
|
||
@property | ||
def web_worker(self): | ||
return self._web_worker | ||
|
||
@web_worker.setter | ||
def web_worker(self, value): | ||
self._web_worker = value | ||
|
||
js_resources = JsResources() | ||
|
||
def to_py(js_proxy): | ||
import pyodide | ||
if hasattr(js_proxy, "imageType"): | ||
image_dict = js_proxy.to_py() | ||
image_type = image_dict['imageType'] | ||
dimension = image_type['dimension'] | ||
component_type = image_type['componentType'] | ||
image_dict['direction'] = _to_numpy_array(str(FloatTypes.Float64), image_dict['direction']).reshape((dimension, dimension)) | ||
image_dict['data'] = _to_numpy_array(component_type, image_dict['data']).reshape((dimension, dimension)) | ||
return Image(**image_dict) | ||
return js_proxy.to_py() | ||
|
||
def to_js(py): | ||
import pyodide | ||
import js | ||
if isinstance(py, Image): | ||
image_dict = asdict(py) | ||
image_dict['direction'] = image_dict['direction'].ravel() | ||
image_dict['data'] = image_dict['data'].ravel() | ||
return pyodide.ffi.to_js(image_dict, dict_converter=js.Object.fromEntries) | ||
return py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
|
||
def test_itkwasm_js_package_config(): | ||
from itkwasm import JsPackageConfig | ||
from itkwasm.pyodide import JsPackageConfig | ||
module_url = 'https://cdn.jsdelivr.net/npm/@itk-wasm/[email protected]/dist/bundles/compress-stringify.js' | ||
pipelines_base_url = 'https://cdn.jsdelivr.net/npm/@itk-wasm/[email protected]/dist/pipelines' | ||
pipeline_worker_url = 'https://cdn.jsdelivr.net/npm/@itk-wasm/[email protected]/dist/web-workers/pipeline.worker.js' | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
from pytest_pyodide import run_in_pyodide | ||
import pytest | ||
|
||
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']) | ||
async def test_image_conversion(selenium, package_wheel): | ||
import micropip | ||
await micropip.install(package_wheel) | ||
|
||
from itkwasm import Image | ||
from itkwasm.pyodide import to_js, to_py | ||
import numpy as np | ||
|
||
image = Image() | ||
|
||
assert image.imageType.dimension == 2 | ||
assert image.imageType.componentType == 'uint8' | ||
assert image.imageType.pixelType == 'Scalar' | ||
assert image.imageType.components == 1 | ||
|
||
assert image.name == "image" | ||
assert image.origin[0] == 0.0 | ||
assert image.origin[1] == 0.0 | ||
assert image.spacing[0] == 1.0 | ||
assert image.spacing[1] == 1.0 | ||
assert np.array_equal(image.direction, np.eye(2).astype(np.float64)) | ||
|
||
assert image.size[0] == 1 | ||
assert image.size[1] == 1 | ||
image.size = [2, 2] | ||
|
||
assert isinstance(image.metadata, dict) | ||
assert image.data == None | ||
image.data = np.arange(4, dtype=np.uint8).reshape((2,2)) | ||
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.components == 1 | ||
|
||
assert image_py.name == "image" | ||
assert image_py.origin[0] == 0.0 | ||
assert image_py.origin[1] == 0.0 | ||
assert image_py.spacing[0] == 1.0 | ||
assert image_py.spacing[1] == 1.0 | ||
assert np.array_equal(image_py.direction, np.eye(2).astype(np.float64)) | ||
|
||
assert image_py.size[0] == 2 | ||
assert image_py.size[1] == 2 | ||
|
||
assert isinstance(image_py.metadata, dict) | ||
assert np.array_equal(image_py.data, np.arange(4, dtype=np.uint8).reshape((2,2))) |