Skip to content

Commit 395beb7

Browse files
committed
feat(transform-io): emscripten read_transform_async, write_transform_async
1 parent 2ffc2ca commit 395beb7

File tree

6 files changed

+263
-2
lines changed

6 files changed

+263
-2
lines changed

packages/transform-io/python/itkwasm-transform-io-emscripten/itkwasm_transform_io_emscripten/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
# Generated file. To retain edits, remove this comment.
2-
31
"""itkwasm-transform-io-emscripten: Input and output for scientific and medical coordinate transform file formats. Emscripten implementation."""
42

3+
from .read_transform_async import read_transform_async
4+
from .write_transform_async import write_transform_async
5+
56
from .hdf5_read_transform_async import hdf5_read_transform_async
67
from .hdf5_write_transform_async import hdf5_write_transform_async
78
from .mat_read_transform_async import mat_read_transform_async
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from collections import OrderedDict
2+
3+
extension_to_transform_io = OrderedDict([
4+
("h5", "hdf5"),
5+
("hdf5", "hdf5"),
6+
("txt", "txt"),
7+
("mat", "mat"),
8+
("xfm", "mnc"),
9+
("iwt", "wasm"),
10+
("iwt.cbor", "wasm"),
11+
("iwt.cbor.zst", "wasmZstd"),
12+
])
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import os
2+
from typing import Optional, Union
3+
from pathlib import Path
4+
5+
from itkwasm import (
6+
TransformList,
7+
BinaryFile,
8+
)
9+
10+
from .js_package import js_package
11+
12+
from itkwasm.pyodide import (
13+
to_js,
14+
to_py,
15+
js_resources
16+
)
17+
18+
from .extension_to_transform_io import extension_to_transform_io
19+
from .transform_io_index import transform_io_index
20+
21+
async def read_transform_async(
22+
serialized_transform: os.PathLike,
23+
float_parameters: bool = False,
24+
) -> TransformList:
25+
"""Read an transform file format and convert it to the itk-wasm file format
26+
27+
:param serialized_transform: Input transform serialized in the file format
28+
:type serialized_transform: os.PathLike
29+
30+
:param float_parameters: Use float for the parameter value type. The default is double.
31+
:type float_parameters: bool
32+
33+
:return: Output transform
34+
:rtype: TransformList
35+
"""
36+
js_module = await js_package.js_module
37+
web_worker = js_resources.web_worker
38+
39+
kwargs = {}
40+
if float_parameters:
41+
kwargs["floatParameters"] = to_js(float_parameters)
42+
43+
extension = ''.join(Path(serialized_transform).suffixes)
44+
45+
io = None
46+
if extension in extension_to_transform_io:
47+
func = f"{extension_to_transform_io[extension]}ReadTransform"
48+
io = getattr(js_module, func)
49+
else:
50+
for ioname in transform_io_index:
51+
func = f"{ioname}ReadTransform"
52+
io = getattr(js_module, func)
53+
outputs = await io(to_js(BinaryFile(serialized_transform)), webWorker=web_worker, noCopy=True, **kwargs)
54+
outputs_object_map = outputs.as_object_map()
55+
web_worker = outputs_object_map['webWorker']
56+
js_resources.web_worker = web_worker
57+
could_read = to_py(outputs_object_map['couldRead'])
58+
if could_read:
59+
transform = to_py(outputs_object_map['transform'])
60+
return transform
61+
62+
if io is None:
63+
raise RuntimeError(f"Could not find an transform reader for {extension}")
64+
65+
outputs = await io(to_js(BinaryFile(serialized_transform)), webWorker=web_worker, noCopy=True, **kwargs)
66+
outputs_object_map = outputs.as_object_map()
67+
web_worker = outputs_object_map['webWorker']
68+
could_read = to_py(outputs_object_map['couldRead'])
69+
70+
if not could_read:
71+
raise RuntimeError(f"Could not read {serialized_transform}")
72+
73+
js_resources.web_worker = web_worker
74+
75+
transform = to_py(outputs_object_map['transform'])
76+
77+
return transform
78+
79+
async def transformread_async(
80+
serialized_transform: os.PathLike,
81+
float_parameters: bool = False,
82+
) -> TransformList:
83+
return await read_transform_async(serialized_transform, float_parameters=float_parameters)
84+
85+
transformread_async.__doc__ = f"""{read_transform_async.__doc__}
86+
Alias for read_transform_async.
87+
"""
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
transform_io_index = [
2+
'hdf5',
3+
'mat',
4+
'mnc',
5+
'txt',
6+
'wasm',
7+
'wasm_ztd',
8+
]
9+
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import os
2+
import importlib
3+
from pathlib import Path
4+
from typing import Optional, Union
5+
6+
from itkwasm import TransformList, PixelTypes, IntTypes, FloatTypes, BinaryFile
7+
8+
from itkwasm.pyodide import (
9+
to_js,
10+
to_py,
11+
js_resources
12+
)
13+
14+
from .js_package import js_package
15+
16+
from .extension_to_transform_io import extension_to_transform_io
17+
from .transform_io_index import transform_io_index
18+
19+
async def write_transform_async(
20+
transform: TransformList,
21+
serialized_transform: os.PathLike,
22+
float_parameters: bool = False,
23+
use_compression: bool = False,
24+
) -> None:
25+
"""Write an itk-wasm TransformList to an transform file format.
26+
27+
:param transform: Input transform
28+
:type transform: TransformList
29+
30+
:param serialized_transform: Output transform serialized in the file format.
31+
:type serialized_transform: str
32+
33+
:param float_parameters: Use float for the parameter value type. The default is double.
34+
:type float_parameters: bool
35+
36+
:param use_compression: Use compression in the written file
37+
:type use_compression: bool
38+
39+
:param serialized_transform: Input transform serialized in the file format
40+
:type serialized_transform: os.PathLike
41+
"""
42+
js_module = await js_package.js_module
43+
web_worker = js_resources.web_worker
44+
45+
kwargs = {}
46+
if float_parameters:
47+
kwargs["floatParameters"] = to_js(float_parameters)
48+
if use_compression:
49+
kwargs["useCompression"] = to_js(use_compression)
50+
51+
extension = ''.join(Path(serialized_transform).suffixes)
52+
53+
io = None
54+
if extension in extension_to_transform_io:
55+
func = f"{extension_to_transform_io[extension]}WriteTransform"
56+
io = getattr(js_module, func)
57+
else:
58+
for ioname in transform_io_index:
59+
func = f"{ioname}WriteTransform"
60+
io = getattr(js_module, func)
61+
outputs = await io(to_js(transform), to_js(serialized_transform), webWorker=web_worker, noCopy=True, **kwargs)
62+
outputs_object_map = outputs.as_object_map()
63+
web_worker = outputs_object_map['webWorker']
64+
js_resources.web_worker = web_worker
65+
could_write = to_py(outputs_object_map['couldWrite'])
66+
if could_write:
67+
to_py(outputs_object_map['serializedTransform'])
68+
return
69+
70+
if io is None:
71+
raise RuntimeError(f"Could not find an transform writer for {extension}")
72+
73+
outputs = await io(to_js(transform), to_js(serialized_transform), webWorker=web_worker, noCopy=True, **kwargs)
74+
outputs_object_map = outputs.as_object_map()
75+
web_worker = outputs_object_map['webWorker']
76+
js_resources.web_worker = web_worker
77+
could_write = to_py(outputs_object_map['couldWrite'])
78+
79+
if not could_write:
80+
raise RuntimeError(f"Could not write {serialized_transform}")
81+
82+
to_py(outputs_object_map['serializedTransform'])
83+
84+
async def transformwrite_async(
85+
transform: TransformList,
86+
serialized_transform: os.PathLike,
87+
float_parameters: bool = False,
88+
use_compression: bool = False,
89+
) -> None:
90+
return write_transform_async(transform, serialized_transform, float_parameters=float_parameters, use_compression=use_compression)
91+
92+
transformwrite_async.__doc__ = f"""{write_transform_async.__doc__}
93+
Alias for write_transform.
94+
"""
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import sys
2+
3+
if sys.version_info < (3,10):
4+
pytest.skip("Skipping pyodide tests on older Python", allow_module_level=True)
5+
6+
import pytest
7+
from pytest_pyodide import run_in_pyodide
8+
from .fixtures import package_wheel, input_data
9+
10+
@pytest.mark.driver_timeout(30)
11+
@run_in_pyodide(packages=['micropip', 'numpy'])
12+
async def test_read_write_mesh_async(selenium, package_wheel, input_data):
13+
import micropip
14+
await micropip.install(package_wheel)
15+
def write_input_data_to_fs(input_data, filename):
16+
with open(filename, 'wb') as fp:
17+
fp.write(input_data[filename])
18+
19+
from pathlib import Path
20+
21+
from itkwasm import TransformParameterizations, FloatTypes
22+
import numpy as np
23+
24+
from itkwasm_transform_io_emscripten import read_transform_async, write_transform_async
25+
26+
def write_input_data_to_fs(input_data, filename):
27+
with open(filename, 'wb') as fp:
28+
fp.write(input_data[filename])
29+
30+
def verify_test_linear_transform(transform_list):
31+
assert len(transform_list) == 1
32+
transform = transform_list[0]
33+
assert transform.transformType.transformParameterization == TransformParameterizations.Affine
34+
assert transform.transformType.parametersValueType == FloatTypes.Float64
35+
assert transform.numberOfParameters == 12
36+
assert transform.numberOfFixedParameters == 3
37+
np.testing.assert_allclose(transform.fixedParameters, np.array([0.0, 0.0, 0.0]))
38+
np.testing.assert_allclose(transform.parameters, np.array([
39+
0.65631490118447, 0.5806583745824385, -0.4817536741017158,
40+
-0.7407986817430222, 0.37486398378429736, -0.5573995934598175,
41+
-0.14306664045479867, 0.7227121458012518, 0.676179776908723,
42+
-65.99999999999997, 69.00000000000004, 32.000000000000036]))
43+
44+
test_file_path = 'LinearTransform.h5'
45+
write_input_data_to_fs(input_data, test_file_path)
46+
47+
assert Path(test_file_path).exists()
48+
49+
transform = await read_transform_async(test_file_path)
50+
verify_test_linear_transform(transform)
51+
52+
test_output_file_path = 'out-LinearTransform.h5'
53+
54+
use_compression = False
55+
await write_transform_async(transform, test_output_file_path, use_compression)
56+
57+
transform = await read_transform_async(test_output_file_path)
58+
verify_test_linear_transform(transform)

0 commit comments

Comments
 (0)