-
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(mesh-io-emscripten): add read_image_async, write_image_async
- Loading branch information
Showing
7 changed files
with
267 additions
and
0 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
16 changes: 16 additions & 0 deletions
16
...h-io/python/itkwasm-mesh-io-emscripten/itkwasm_mesh_io_emscripten/extension_to_mesh_io.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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
from collections import OrderedDict | ||
|
||
extension_to_mesh_io = OrderedDict([ | ||
('.vtk', 'vtkPolyData'), | ||
('.byu', 'byu'), | ||
('.fsa', 'freeSurferAscii'), | ||
('.fsb', 'freeSurferBinary'), | ||
('.obj', 'obj'), | ||
('.off', 'off'), | ||
('.stl', 'stl'), | ||
('.swc', 'swc'), | ||
('.iwm', 'wasm'), | ||
('.iwm.cbor', 'wasm'), | ||
('.iwm.cbor.zst', 'wasmZstd'), | ||
('.bmp', 'bmp'), | ||
]) |
13 changes: 13 additions & 0 deletions
13
...ges/mesh-io/python/itkwasm-mesh-io-emscripten/itkwasm_mesh_io_emscripten/mesh_io_index.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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
mesh_io_index = [ | ||
'vtk_poly_data', | ||
'byu', | ||
'free_surfer_ascii', | ||
'free_surfer_binary', | ||
'obj', | ||
'off', | ||
'stl', | ||
'swc', | ||
'wasm', | ||
'wasm_zstd', | ||
] | ||
|
88 changes: 88 additions & 0 deletions
88
...s/mesh-io/python/itkwasm-mesh-io-emscripten/itkwasm_mesh_io_emscripten/read_mesh_async.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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import os | ||
from typing import Optional, Union | ||
from pathlib import Path | ||
|
||
from itkwasm import ( | ||
Mesh, | ||
BinaryFile, | ||
) | ||
|
||
from .js_package import js_package | ||
|
||
from itkwasm.pyodide import ( | ||
to_js, | ||
to_py, | ||
js_resources | ||
) | ||
|
||
from .extension_to_mesh_io import extension_to_mesh_io | ||
from .mesh_io_index import mesh_io_index | ||
|
||
async def read_mesh_async( | ||
serialized_mesh: os.PathLike, | ||
information_only: bool = False, | ||
) -> Mesh: | ||
"""Read an mesh file format and convert it to the itk-wasm file format | ||
:param serialized_mesh: Input mesh serialized in the file format | ||
:type serialized_mesh: os.PathLike | ||
:param information_only: Only read mesh metadata -- do not read pixel data. | ||
:type information_only: bool | ||
:return: Output mesh | ||
:rtype: Mesh | ||
""" | ||
js_module = await js_package.js_module | ||
web_worker = js_resources.web_worker | ||
|
||
kwargs = {} | ||
if information_only: | ||
kwargs["informationOnly"] = to_js(information_only) | ||
|
||
extension = ''.join(Path(serialized_mesh).suffixes) | ||
|
||
io = None | ||
if extension in extension_to_mesh_io: | ||
func = f"{extension_to_mesh_io[extension]}ReadMesh" | ||
io = getattr(js_module, func) | ||
else: | ||
for ioname in mesh_io_index: | ||
func = f"{ioname}ReadMesh" | ||
io = getattr(js_module, func) | ||
outputs = await io(web_worker, to_js(BinaryFile(serialized_mesh)), **kwargs) | ||
outputs_object_map = outputs.as_object_map() | ||
web_worker = outputs_object_map['webWorker'] | ||
js_resources.web_worker = web_worker | ||
could_read = to_py(outputs_object_map['couldRead']) | ||
if could_read: | ||
mesh = to_py(outputs_object_map['mesh']) | ||
return mesh | ||
|
||
if io is None: | ||
raise RuntimeError(f"Could not find an mesh reader for {extension}") | ||
|
||
outputs = await js_module.readMesh(web_worker, to_js(BinaryFile(serialized_mesh)), **kwargs) | ||
outputs = await io(web_worker, to_js(BinaryFile(serialized_mesh)), **kwargs) | ||
outputs_object_map = outputs.as_object_map() | ||
web_worker = outputs_object_map['webWorker'] | ||
could_read = to_py(outputs_object_map['couldRead']) | ||
|
||
if not could_read: | ||
raise RuntimeError(f"Could not read {serialized_mesh}") | ||
|
||
js_resources.web_worker = web_worker | ||
|
||
mesh = to_py(outputs_object_map['mesh']) | ||
|
||
return mesh | ||
|
||
async def meshread_async( | ||
serialized_mesh: os.PathLike, | ||
information_only: bool = False, | ||
) -> Mesh: | ||
return await read_mesh_async(serialized_mesh, information_only=information_only) | ||
|
||
meshread_async.__doc__ = f"""{read_mesh_async.__doc__} | ||
Alias for read_mesh_async. | ||
""" |
95 changes: 95 additions & 0 deletions
95
.../mesh-io/python/itkwasm-mesh-io-emscripten/itkwasm_mesh_io_emscripten/write_mesh_async.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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import os | ||
import importlib | ||
from pathlib import Path | ||
from typing import Optional, Union | ||
|
||
from itkwasm import Mesh, PixelTypes, IntTypes, FloatTypes, BinaryFile | ||
|
||
from itkwasm.pyodide import ( | ||
to_js, | ||
to_py, | ||
js_resources | ||
) | ||
|
||
from .js_package import js_package | ||
|
||
from .extension_to_mesh_io import extension_to_mesh_io | ||
from .mesh_io_index import mesh_io_index | ||
|
||
async def write_mesh_async( | ||
mesh: Mesh, | ||
serialized_mesh: os.PathLike, | ||
information_only: bool = False, | ||
use_compression: bool = False, | ||
) -> None: | ||
"""Write an itk-wasm Mesh to an mesh file format. | ||
:param mesh: Input mesh | ||
:type mesh: Mesh | ||
:param serialized_mesh: Output mesh serialized in the file format. | ||
:type serialized_mesh: str | ||
:param information_only: Only write mesh metadata -- do not write pixel data. | ||
:type information_only: bool | ||
:param use_compression: Use compression in the written file | ||
:type use_compression: bool | ||
:param serialized_mesh: Input mesh serialized in the file format | ||
:type serialized_mesh: os.PathLike | ||
""" | ||
js_module = await js_package.js_module | ||
web_worker = js_resources.web_worker | ||
|
||
kwargs = {} | ||
if information_only: | ||
kwargs["informationOnly"] = to_js(information_only) | ||
if use_compression: | ||
kwargs["useCompression"] = to_js(use_compression) | ||
|
||
extension = ''.join(Path(serialized_mesh).suffixes) | ||
|
||
io = None | ||
if extension in extension_to_mesh_io: | ||
func = f"{extension_to_mesh_io[extension]}WriteMesh" | ||
io = getattr(js_module, func) | ||
else: | ||
for ioname in mesh_io_index: | ||
func = f"{ioname}WriteMesh" | ||
io = getattr(js_module, func) | ||
outputs = await io(web_worker, to_js(mesh), to_js(serialized_mesh), **kwargs) | ||
outputs_object_map = outputs.as_object_map() | ||
web_worker = outputs_object_map['webWorker'] | ||
js_resources.web_worker = web_worker | ||
could_write = to_py(outputs_object_map['couldWrite']) | ||
if could_write: | ||
to_py(outputs_object_map['serializedMesh']) | ||
return | ||
|
||
if io is None: | ||
raise RuntimeError(f"Could not find an mesh writer for {extension}") | ||
|
||
io = getattr(js_module, func) | ||
outputs = await io(web_worker, to_js(mesh), to_js(serialized_mesh), **kwargs) | ||
outputs_object_map = outputs.as_object_map() | ||
web_worker = outputs_object_map['webWorker'] | ||
js_resources.web_worker = web_worker | ||
could_write = to_py(outputs_object_map['couldWrite']) | ||
|
||
if not could_write: | ||
raise RuntimeError(f"Could not write {serialized_mesh}") | ||
|
||
to_py(outputs_object_map['serializedMesh']) | ||
|
||
async def meshwrite_async( | ||
mesh: Mesh, | ||
serialized_mesh: os.PathLike, | ||
information_only: bool = False, | ||
use_compression: bool = False, | ||
) -> None: | ||
return write_mesh_async(mesh, serialized_mesh, information_only=information_only, use_compression=use_compression) | ||
|
||
meshwrite_async.__doc__ = f"""{write_mesh_async.__doc__} | ||
Alias for write_mesh. | ||
""" |
52 changes: 52 additions & 0 deletions
52
packages/mesh-io/python/itkwasm-mesh-io-emscripten/test/test_read_write_mesh_async.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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import sys | ||
|
||
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 .fixtures import package_wheel, input_data | ||
|
||
@run_in_pyodide(packages=['micropip', 'numpy']) | ||
async def test_read_write_mesh_async(selenium, package_wheel, input_data): | ||
import micropip | ||
await micropip.install(package_wheel) | ||
def write_input_data_to_fs(input_data, filename): | ||
with open(filename, 'wb') as fp: | ||
fp.write(input_data[filename]) | ||
|
||
from pathlib import Path | ||
|
||
from itkwasm import FloatTypes, IntTypes | ||
import numpy as np | ||
|
||
from itkwasm_mesh_io_emscripten import read_mesh_async, write_mesh_async | ||
|
||
|
||
def verify_mesh(mesh): | ||
assert mesh.meshType.dimension == 3 | ||
assert mesh.meshType.pointComponentType == FloatTypes.Float32 | ||
assert mesh.meshType.pointPixelComponentType == IntTypes.Int8 | ||
assert mesh.numberOfPoints == 2903 | ||
assert np.allclose(mesh.points.ravel()[0], 3.71636) | ||
assert np.allclose(mesh.points.ravel()[1], 2.34339) | ||
assert mesh.numberOfCells == 3263 | ||
assert mesh.cellBufferSize == 18856 | ||
assert mesh.cells[0] == 4 | ||
assert mesh.cells[1] == 4 | ||
assert mesh.cells[2] == 250 | ||
|
||
test_input_file_path = 'cow.vtk' | ||
test_output_file_path = "read-write-cow.vtk" | ||
write_input_data_to_fs(input_data, test_input_file_path) | ||
|
||
assert Path(test_input_file_path).exists() | ||
|
||
|
||
mesh = await read_mesh_async(test_input_file_path) | ||
verify_mesh(mesh) | ||
|
||
use_compression = False | ||
await write_mesh_async(mesh, test_output_file_path, use_compression=use_compression) | ||
|
||
mesh = await read_mesh_async(test_output_file_path) | ||
verify_mesh(mesh) |
File renamed without changes.