Skip to content

Commit 5d181f7

Browse files
committed
feat(mesh-io-emscripten): add read_image_async, write_image_async
1 parent ab054ce commit 5d181f7

File tree

7 files changed

+267
-0
lines changed

7 files changed

+267
-0
lines changed

packages/mesh-io/python/itkwasm-mesh-io-emscripten/itkwasm_mesh_io_emscripten/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
"""itkwasm-mesh-io-emscripten: Input and output for scientific and medical image file formats. Emscripten implementation."""
44

5+
from .read_mesh_async import read_mesh_async
6+
from .write_mesh_async import write_mesh_async
7+
58
from .byu_read_mesh_async import byu_read_mesh_async
69
from .byu_write_mesh_async import byu_write_mesh_async
710
from .free_surfer_ascii_read_mesh_async import free_surfer_ascii_read_mesh_async
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from collections import OrderedDict
2+
3+
extension_to_mesh_io = OrderedDict([
4+
('.vtk', 'vtkPolyData'),
5+
('.byu', 'byu'),
6+
('.fsa', 'freeSurferAscii'),
7+
('.fsb', 'freeSurferBinary'),
8+
('.obj', 'obj'),
9+
('.off', 'off'),
10+
('.stl', 'stl'),
11+
('.swc', 'swc'),
12+
('.iwm', 'wasm'),
13+
('.iwm.cbor', 'wasm'),
14+
('.iwm.cbor.zst', 'wasmZstd'),
15+
('.bmp', 'bmp'),
16+
])
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
mesh_io_index = [
2+
'vtk_poly_data',
3+
'byu',
4+
'free_surfer_ascii',
5+
'free_surfer_binary',
6+
'obj',
7+
'off',
8+
'stl',
9+
'swc',
10+
'wasm',
11+
'wasm_zstd',
12+
]
13+
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import os
2+
from typing import Optional, Union
3+
from pathlib import Path
4+
5+
from itkwasm import (
6+
Mesh,
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_mesh_io import extension_to_mesh_io
19+
from .mesh_io_index import mesh_io_index
20+
21+
async def read_mesh_async(
22+
serialized_mesh: os.PathLike,
23+
information_only: bool = False,
24+
) -> Mesh:
25+
"""Read an mesh file format and convert it to the itk-wasm file format
26+
27+
:param serialized_mesh: Input mesh serialized in the file format
28+
:type serialized_mesh: os.PathLike
29+
30+
:param information_only: Only read mesh metadata -- do not read pixel data.
31+
:type information_only: bool
32+
33+
:return: Output mesh
34+
:rtype: Mesh
35+
"""
36+
js_module = await js_package.js_module
37+
web_worker = js_resources.web_worker
38+
39+
kwargs = {}
40+
if information_only:
41+
kwargs["informationOnly"] = to_js(information_only)
42+
43+
extension = ''.join(Path(serialized_mesh).suffixes)
44+
45+
io = None
46+
if extension in extension_to_mesh_io:
47+
func = f"{extension_to_mesh_io[extension]}ReadMesh"
48+
io = getattr(js_module, func)
49+
else:
50+
for ioname in mesh_io_index:
51+
func = f"{ioname}ReadMesh"
52+
io = getattr(js_module, func)
53+
outputs = await io(web_worker, to_js(BinaryFile(serialized_mesh)), **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+
mesh = to_py(outputs_object_map['mesh'])
60+
return mesh
61+
62+
if io is None:
63+
raise RuntimeError(f"Could not find an mesh reader for {extension}")
64+
65+
outputs = await js_module.readMesh(web_worker, to_js(BinaryFile(serialized_mesh)), **kwargs)
66+
outputs = await io(web_worker, to_js(BinaryFile(serialized_mesh)), **kwargs)
67+
outputs_object_map = outputs.as_object_map()
68+
web_worker = outputs_object_map['webWorker']
69+
could_read = to_py(outputs_object_map['couldRead'])
70+
71+
if not could_read:
72+
raise RuntimeError(f"Could not read {serialized_mesh}")
73+
74+
js_resources.web_worker = web_worker
75+
76+
mesh = to_py(outputs_object_map['mesh'])
77+
78+
return mesh
79+
80+
async def meshread_async(
81+
serialized_mesh: os.PathLike,
82+
information_only: bool = False,
83+
) -> Mesh:
84+
return await read_mesh_async(serialized_mesh, information_only=information_only)
85+
86+
meshread_async.__doc__ = f"""{read_mesh_async.__doc__}
87+
Alias for read_mesh_async.
88+
"""
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import os
2+
import importlib
3+
from pathlib import Path
4+
from typing import Optional, Union
5+
6+
from itkwasm import Mesh, 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_mesh_io import extension_to_mesh_io
17+
from .mesh_io_index import mesh_io_index
18+
19+
async def write_mesh_async(
20+
mesh: Mesh,
21+
serialized_mesh: os.PathLike,
22+
information_only: bool = False,
23+
use_compression: bool = False,
24+
) -> None:
25+
"""Write an itk-wasm Mesh to an mesh file format.
26+
27+
:param mesh: Input mesh
28+
:type mesh: Mesh
29+
30+
:param serialized_mesh: Output mesh serialized in the file format.
31+
:type serialized_mesh: str
32+
33+
:param information_only: Only write mesh metadata -- do not write pixel data.
34+
:type information_only: bool
35+
36+
:param use_compression: Use compression in the written file
37+
:type use_compression: bool
38+
39+
:param serialized_mesh: Input mesh serialized in the file format
40+
:type serialized_mesh: os.PathLike
41+
"""
42+
js_module = await js_package.js_module
43+
web_worker = js_resources.web_worker
44+
45+
kwargs = {}
46+
if information_only:
47+
kwargs["informationOnly"] = to_js(information_only)
48+
if use_compression:
49+
kwargs["useCompression"] = to_js(use_compression)
50+
51+
extension = ''.join(Path(serialized_mesh).suffixes)
52+
53+
io = None
54+
if extension in extension_to_mesh_io:
55+
func = f"{extension_to_mesh_io[extension]}WriteMesh"
56+
io = getattr(js_module, func)
57+
else:
58+
for ioname in mesh_io_index:
59+
func = f"{ioname}WriteMesh"
60+
io = getattr(js_module, func)
61+
outputs = await io(web_worker, to_js(mesh), to_js(serialized_mesh), **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['serializedMesh'])
68+
return
69+
70+
if io is None:
71+
raise RuntimeError(f"Could not find an mesh writer for {extension}")
72+
73+
io = getattr(js_module, func)
74+
outputs = await io(web_worker, to_js(mesh), to_js(serialized_mesh), **kwargs)
75+
outputs_object_map = outputs.as_object_map()
76+
web_worker = outputs_object_map['webWorker']
77+
js_resources.web_worker = web_worker
78+
could_write = to_py(outputs_object_map['couldWrite'])
79+
80+
if not could_write:
81+
raise RuntimeError(f"Could not write {serialized_mesh}")
82+
83+
to_py(outputs_object_map['serializedMesh'])
84+
85+
async def meshwrite_async(
86+
mesh: Mesh,
87+
serialized_mesh: os.PathLike,
88+
information_only: bool = False,
89+
use_compression: bool = False,
90+
) -> None:
91+
return write_mesh_async(mesh, serialized_mesh, information_only=information_only, use_compression=use_compression)
92+
93+
meshwrite_async.__doc__ = f"""{write_mesh_async.__doc__}
94+
Alias for write_mesh.
95+
"""
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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+
from pytest_pyodide import run_in_pyodide
7+
from .fixtures import package_wheel, input_data
8+
9+
@run_in_pyodide(packages=['micropip', 'numpy'])
10+
async def test_read_write_mesh_async(selenium, package_wheel, input_data):
11+
import micropip
12+
await micropip.install(package_wheel)
13+
def write_input_data_to_fs(input_data, filename):
14+
with open(filename, 'wb') as fp:
15+
fp.write(input_data[filename])
16+
17+
from pathlib import Path
18+
19+
from itkwasm import FloatTypes, IntTypes
20+
import numpy as np
21+
22+
from itkwasm_mesh_io_emscripten import read_mesh_async, write_mesh_async
23+
24+
25+
def verify_mesh(mesh):
26+
assert mesh.meshType.dimension == 3
27+
assert mesh.meshType.pointComponentType == FloatTypes.Float32
28+
assert mesh.meshType.pointPixelComponentType == IntTypes.Int8
29+
assert mesh.numberOfPoints == 2903
30+
assert np.allclose(mesh.points.ravel()[0], 3.71636)
31+
assert np.allclose(mesh.points.ravel()[1], 2.34339)
32+
assert mesh.numberOfCells == 3263
33+
assert mesh.cellBufferSize == 18856
34+
assert mesh.cells[0] == 4
35+
assert mesh.cells[1] == 4
36+
assert mesh.cells[2] == 250
37+
38+
test_input_file_path = 'cow.vtk'
39+
test_output_file_path = "read-write-cow.vtk"
40+
write_input_data_to_fs(input_data, test_input_file_path)
41+
42+
assert Path(test_input_file_path).exists()
43+
44+
45+
mesh = await read_mesh_async(test_input_file_path)
46+
verify_mesh(mesh)
47+
48+
use_compression = False
49+
await write_mesh_async(mesh, test_output_file_path, use_compression=use_compression)
50+
51+
mesh = await read_mesh_async(test_output_file_path)
52+
verify_mesh(mesh)

0 commit comments

Comments
 (0)