Skip to content

Commit

Permalink
feat(mesh-io-emscripten): add read_image_async, write_image_async
Browse files Browse the repository at this point in the history
  • Loading branch information
thewtex committed Nov 28, 2023
1 parent ab054ce commit 5d181f7
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

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

from .read_mesh_async import read_mesh_async
from .write_mesh_async import write_mesh_async

from .byu_read_mesh_async import byu_read_mesh_async
from .byu_write_mesh_async import byu_write_mesh_async
from .free_surfer_ascii_read_mesh_async import free_surfer_ascii_read_mesh_async
Expand Down
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'),
])
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',
]

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.
"""
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.
"""
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)

0 comments on commit 5d181f7

Please sign in to comment.