diff --git a/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/__init__.py b/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/__init__.py index d287980ea..7ca9d9ce1 100644 --- a/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/__init__.py +++ b/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/__init__.py @@ -1,7 +1,8 @@ -# Generated file. To retain edits, remove this comment. - """itkwasm-mesh-io-wasi: Input and output for scientific and medical image file formats. WASI implementation.""" +from .read_mesh import read_mesh, meshread +from .write_mesh import write_mesh, meshwrite + from .byu_read_mesh import byu_read_mesh from .byu_write_mesh import byu_write_mesh from .free_surfer_ascii_read_mesh import free_surfer_ascii_read_mesh diff --git a/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/extension_to_mesh_io.py b/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/extension_to_mesh_io.py new file mode 100644 index 000000000..43d407a95 --- /dev/null +++ b/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/extension_to_mesh_io.py @@ -0,0 +1,16 @@ +from collections import OrderedDict + +extension_to_mesh_io = OrderedDict([ + ('.vtk', 'vtk_poly_data'), + ('.byu', 'byu'), + ('.fsa', 'free_surfer_ascii'), + ('.fsb', 'free_surfer_binary'), + ('.obj', 'obj'), + ('.off', 'off'), + ('.stl', 'stl'), + ('.swc', 'swc'), + ('.iwm', 'wasm'), + ('.iwm.cbor', 'wasm'), + ('.iwm.cbor.zst', 'wasm_zstd'), + ('.bmp', 'bmp'), +]) diff --git a/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/mesh_io_index.py b/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/mesh_io_index.py new file mode 100644 index 000000000..6137bdbc2 --- /dev/null +++ b/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/mesh_io_index.py @@ -0,0 +1,13 @@ +mesh_io_index = [ + 'vtk_poly_data', + 'byu', + 'free_surfer_ascii', + 'free_surfer_binary', + 'obj', + 'off', + 'stl', + 'swc', + 'wasm', + 'wasm_zstd', +] + diff --git a/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/read_mesh.py b/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/read_mesh.py new file mode 100644 index 000000000..e72018d90 --- /dev/null +++ b/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/read_mesh.py @@ -0,0 +1,61 @@ +import os +import importlib +from pathlib import Path + +from itkwasm import Mesh + +from .extension_to_mesh_io import extension_to_mesh_io +from .mesh_io_index import mesh_io_index + +def read_mesh( + 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 + """ + extension = ''.join(Path(serialized_mesh).suffixes) + + io = None + if extension in extension_to_mesh_io: + func = f"{extension_to_mesh_io[extension]}_read_mesh" + mod_name = f"itkwasm_mesh_io_wasi.{func}" + mod = importlib.import_module(mod_name) + io = getattr(mod, func) + else: + for ioname in mesh_io_index: + func = f"{ioname}_read_mesh" + mod_name = f"itkwasm_mesh_io_wasi.{func}" + mod = importlib.import_module(mod_name) + io = getattr(mod, func) + could_read, mesh = io(serialized_mesh, information_only=information_only) + if could_read: + return mesh + + if io is None: + raise RuntimeError(f"Could not find an mesh reader for {extension}") + + could_read, mesh = io(serialized_mesh, information_only=information_only) + if not could_read: + raise RuntimeError(f"Could not read {serialized_mesh}") + + return mesh + + +def meshread( + serialized_mesh: os.PathLike, + information_only: bool = False, +) -> Mesh: + return read_mesh(serialized_mesh, information_only=information_only) + +meshread.__doc__ = f"""{read_mesh.__doc__} + Alias for read_mesh. + """ \ No newline at end of file diff --git a/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/write_mesh.py b/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/write_mesh.py new file mode 100644 index 000000000..d1495fa93 --- /dev/null +++ b/packages/mesh-io/python/itkwasm-mesh-io-wasi/itkwasm_mesh_io_wasi/write_mesh.py @@ -0,0 +1,68 @@ +import os +import importlib +from pathlib import Path + +from itkwasm import Mesh, PixelTypes, IntTypes, FloatTypes + +from .extension_to_mesh_io import extension_to_mesh_io +from .mesh_io_index import mesh_io_index + +def write_mesh( + 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 + """ + extension = ''.join(Path(serialized_mesh).suffixes) + + io = None + if extension in extension_to_mesh_io: + func = f"{extension_to_mesh_io[extension]}_write_mesh" + mod_name = f"itkwasm_mesh_io_wasi.{func}" + mod = importlib.import_module(mod_name) + io = getattr(mod, func) + else: + for ioname in mesh_io_index: + func = f"{ioname}_write_mesh" + mod_name = f"itkwasm_mesh_io_wasi.{func}" + mod = importlib.import_module(mod_name) + io = getattr(mod, func) + could_write = io(mesh, serialized_mesh, information_only=information_only, use_compression=use_compression) + if could_write: + return + + if io is None: + raise RuntimeError(f"Could not find an mesh writer for {extension}") + + could_write = io(mesh, serialized_mesh, information_only=information_only, use_compression=use_compression) + if not could_write: + raise RuntimeError(f"Could not write {serialized_mesh}") + +def meshwrite( + mesh: Mesh, + serialized_mesh: os.PathLike, + information_only: bool = False, + use_compression: bool = False, +) -> None: + return write_mesh(mesh, serialized_mesh, information_only=information_only, use_compression=use_compression) + +meshwrite.__doc__ = f"""{write_mesh.__doc__} + Alias for write_mesh. + """ \ No newline at end of file diff --git a/packages/mesh-io/python/itkwasm-mesh-io-wasi/tests/test_read_write_mesh.py b/packages/mesh-io/python/itkwasm-mesh-io-wasi/tests/test_read_write_mesh.py new file mode 100644 index 000000000..12ee1580a --- /dev/null +++ b/packages/mesh-io/python/itkwasm-mesh-io-wasi/tests/test_read_write_mesh.py @@ -0,0 +1,48 @@ +from itkwasm import IntTypes, FloatTypes +import numpy as np + +from itkwasm_mesh_io_wasi import read_mesh, meshread, write_mesh, meshwrite + +from .common import test_input_path, test_output_path + +test_input_file_path = test_input_path / "cow.vtk" +test_output_file_path = test_output_path / "read-write-cow.vtk" + +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[0],3.71636) + assert np.allclose(mesh.points[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 + +def test_read_mesh(): + mesh = read_mesh(test_input_file_path) + verify_mesh(mesh) + +def test_meshread(): + mesh = meshread(test_input_file_path) + verify_mesh(mesh) + +def test_write_mesh(): + mesh = read_mesh(test_input_file_path) + + use_compression = False + write_mesh(mesh, test_output_file_path, use_compression=use_compression) + + mesh = read_mesh(test_output_file_path) + verify_mesh(mesh) + +def test_meshwrite(): + mesh = meshread(test_input_file_path) + + use_compression = False + meshwrite(mesh, test_output_file_path, use_compression=use_compression) + + mesh = meshread(test_output_file_path) + verify_mesh(mesh)