Skip to content

liblaf.melon.io

I/O dispatchers and mesh-format conversion helpers.

Modules:

  • abc

    Reusable dispatchers for readers, writers, and converters.

  • pyvista

    PyVista readers, writers, and conversion dispatchers.

  • trimesh

    Trimesh conversion and writer registration.

  • utils
  • vtk
  • warp

    Warp mesh conversion helpers.

  • wrap

    JSON sidecar helpers for Faceform Wrap projects.

Classes:

Functions:

Attributes:

as_multiblock module-attribute

as_multiblock: ConverterDispatcher[MultiBlock] = (
    ConverterDispatcher(MultiBlock)
)

Convert supported scene objects to [pyvista.MultiBlock][].

as_polydata module-attribute

as_polydata: ConverterDispatcher[PolyData] = (
    ConverterDispatcher(PolyData)
)

Convert supported mesh objects to [pyvista.PolyData][].

as_trimesh module-attribute

as_trimesh: ConverterDispatcher[Trimesh] = (
    ConverterDispatcher(Trimesh)
)

Convert supported triangular mesh objects to [trimesh.Trimesh][].

as_unstructured_grid module-attribute

as_unstructured_grid: ConverterDispatcher[
    UnstructuredGrid
] = ConverterDispatcher(UnstructuredGrid)

Convert supported volume meshes to [pyvista.UnstructuredGrid][].

as_warp_mesh module-attribute

as_warp_mesh: ConverterDispatcher[Mesh] = (
    ConverterDispatcher(Mesh)
)

Convert supported triangular meshes to [warp.Mesh][].

load_multiblock module-attribute

load_multiblock: ReaderDispatcher[MultiBlock] = (
    ReaderDispatcher(MultiBlock)
)

Load a multi-block PyVista dataset.

load_polydata module-attribute

load_polydata: ReaderDispatcher[PolyData] = (
    ReaderDispatcher(PolyData)
)

Load a surface mesh as [pyvista.PolyData][].

load_unstructured_grid module-attribute

load_unstructured_grid: ReaderDispatcher[
    UnstructuredGrid
] = ReaderDispatcher(UnstructuredGrid)

Load an unstructured volume mesh with PyVista.

save module-attribute

Write registered mesh and scene objects to disk based on path suffix.

AbstractConverter

Bases: Protocol


              flowchart TD
              liblaf.melon.io.AbstractConverter[AbstractConverter]

              

              click liblaf.melon.io.AbstractConverter href "" "liblaf.melon.io.AbstractConverter"
            

Callable that converts one object into another mesh representation.

Methods:

__call__

__call__(obj: F, /, **kwargs) -> T
Source code in src/liblaf/melon/io/abc/_converter.py
def __call__(self, obj: F, /, **kwargs) -> T: ...

AbstractReader

Bases: Protocol


              flowchart TD
              liblaf.melon.io.AbstractReader[AbstractReader]

              

              click liblaf.melon.io.AbstractReader href "" "liblaf.melon.io.AbstractReader"
            

Callable that loads an object from a path.

Methods:

__call__

__call__(path: Path, /, **kwargs) -> T
Source code in src/liblaf/melon/io/abc/_reader.py
def __call__(self, path: Path, /, **kwargs) -> T: ...

AbstractWriter

Bases: Protocol


              flowchart TD
              liblaf.melon.io.AbstractWriter[AbstractWriter]

              

              click liblaf.melon.io.AbstractWriter href "" "liblaf.melon.io.AbstractWriter"
            

Callable that writes an object to a path.

Methods:

__call__

__call__(obj: T, path: Path, /, **kwargs) -> None
Source code in src/liblaf/melon/io/abc/_writer.py
def __call__(self, obj: T, path: Path, /, **kwargs) -> None: ...

ConverterDispatcher

Single-dispatch conversion registry with identity conversion built in.

The target type is registered as an identity conversion, so callers can pass through objects that are already in the requested representation.

Examples:

>>> converter = ConverterDispatcher(str)
>>> converter("mesh")
'mesh'
>>> @converter.register(int)
... def _from_int(obj, /, **kwargs):
...     return str(obj)
>>> converter(42)
'42'

Parameters:

  • to_type (type[T]) –

    Target type returned unchanged when the input already has that runtime type.

  • registry (_SingleDispatchCallable[T], default: <dynamic> ) –

    Underlying functools.singledispatch registry.

Methods:

Attributes:

  • registry (_SingleDispatchCallable[T]) –

    Underlying functools.singledispatch registry.

  • to_type (type[T]) –

    Target type returned unchanged when the input already has that runtime type.

registry class-attribute instance-attribute

registry: _SingleDispatchCallable[T] = field(
    default=Factory(_default_registry, takes_self=True)
)

Underlying functools.singledispatch registry.

to_type instance-attribute

to_type: type[T]

Target type returned unchanged when the input already has that runtime type.

__call__

__call__(obj: Any, /, **kwargs) -> T
Source code in src/liblaf/melon/io/abc/_converter.py
def __call__(self, obj: Any, /, **kwargs) -> T:
    return self.registry(obj, **kwargs)

register

register[F](
    cls: _RegType, converter: AbstractConverter[F, T]
) -> AbstractConverter[F, T]
register[F](
    cls: _RegType, converter: None = None
) -> Callable[
    [AbstractConverter[F, T]], AbstractConverter[F, T]
]
Source code in src/liblaf/melon/io/abc/_converter.py
def register[F](
    self, cls: _RegType, converter: AbstractConverter[F, T] | None = None
) -> Callable[..., Any]:
    if converter is None:
        return functools.partial(self.register, cls)
    self.registry.register(cls, converter)
    return converter

ReaderDispatcher

Dispatch readers by file suffix with an optional fallback reader.

Raises:

  • KeyError

    If the path suffix is unknown and no fallback reader exists.

Attributes:

Parameters:

  • to_type (type[T]) –
  • fallback (AbstractReader[T] | None, default: None ) –
  • registry (dict[str, AbstractReader[T]], default: <class 'dict'> ) –

    dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)

Methods:

fallback class-attribute instance-attribute

fallback: AbstractReader[T] | None = None

registry class-attribute instance-attribute

registry: dict[str, AbstractReader[T]] = field(
    factory=dict
)

to_type instance-attribute

to_type: type[T]

__call__

__call__(path: StrPath, /, **kwargs) -> T
Source code in src/liblaf/melon/io/abc/_reader.py
def __call__(self, path: StrPath, /, **kwargs) -> T:
    path: Path = Path(path)
    reader: AbstractReader[T] | None = self.registry.get(path.suffix, self.fallback)
    if reader is None:
        raise KeyError(path.suffix)
    return reader(path, **kwargs)

register

register(
    suffixes: Iterable[str], reader: AbstractReader[T]
) -> AbstractReader[T]
register(
    suffixes: Iterable[str], reader: None = None
) -> Callable[[AbstractReader[T]], AbstractReader[T]]
Source code in src/liblaf/melon/io/abc/_reader.py
def register(
    self, suffixes: Iterable[str], reader: AbstractReader[T] | None = None
) -> Callable[..., Any]:
    if reader is None:
        return functools.partial(self.register, suffixes)
    for s in suffixes:
        self.registry[s] = reader
    return reader

register_fallback

register_fallback(
    reader: AbstractReader[T],
) -> AbstractReader[T]
Source code in src/liblaf/melon/io/abc/_reader.py
def register_fallback(self, reader: AbstractReader[T]) -> AbstractReader[T]:
    self.fallback = reader
    return reader

VTKHDFTemporalUnstructuredGridWriter

Methods:

Attributes:

file property

file: File

n_steps property writable

n_steps: int

__enter__

__enter__() -> Self
Source code in src/liblaf/melon/io/vtk/_temporal_unstructured_grid.py
def __enter__(self) -> Self:
    group: h5.Group = self.group("/VTKHDF")
    group.attrs["Version"] = (2, 5)
    group.attrs["Type"] = "UnstructuredGrid"
    steps: h5.Group = self.group("/VTKHDF/Steps")
    if "NSteps" not in steps.attrs:
        steps.attrs["NSteps"] = 0
    return self

__exit__

__exit__(
    exc_type: type[BaseException] | None,
    exc_value: BaseException | None,
    traceback: TracebackType | None,
) -> None
Source code in src/liblaf/melon/io/vtk/_temporal_unstructured_grid.py
def __exit__(
    self,
    exc_type: type[BaseException] | None,
    exc_value: BaseException | None,
    traceback: types.TracebackType | None,
) -> None:
    if self._file is not None:
        self._file.close()
    self._tmp_path.unlink(missing_ok=True)

append

append(
    mesh: UnstructuredGrid,
    time: SupportsFloat | None = None,
) -> None
Source code in src/liblaf/melon/io/vtk/_temporal_unstructured_grid.py
def append(
    self, mesh: pv.UnstructuredGrid, time: SupportsFloat | None = None
) -> None:
    if time is None:
        if "/VTKHDF/Steps/Values" in self.file:
            time: float = self.dataset("/VTKHDF/Steps/Values")[-1] + 1.0
        else:
            time: float = 0.0
    else:
        time: float = float(time)
    mesh._store_metadata()  # noqa: SLF001
    self._append_topology(mesh)
    self._append_points(mesh)
    self._append_point_data(mesh)
    self._append_cell_data(mesh)
    self._append_field_data(mesh)
    self.dataset("/VTKHDF/Steps/Values", time)
    self.n_steps += 1
    self.file.close()
    shutil.copy2(self._tmp_path, self._path)

dataset

dataset(
    name: str, data: ArrayLike | None = None
) -> Dataset
Source code in src/liblaf/melon/io/vtk/_temporal_unstructured_grid.py
def dataset(self, name: str, data: ArrayLike | None = None) -> h5.Dataset:
    if data is None:
        return self.file[name]
    data: np.ndarray = _sanitize_array(data)
    kwargs: dict[str, object] = DEFAULT_DATASET_OPTIONS.copy()
    kwargs["chunks"] = _chunk_shape(data)
    dataset: h5.Dataset = self.file.require_dataset(
        name=name,
        shape=(0, *data.shape[1:]),
        dtype=data.dtype,
        maxshape=(None, *data.shape[1:]),
        **kwargs,
    )
    if data.size > 0:
        dataset.resize(dataset.len() + data.shape[0], axis=0)
        dataset[-data.shape[0] :] = data
    return dataset

dataset_offsets

dataset_offsets(
    dataset_name: str, offsets_name: str, data: ArrayLike
) -> tuple[Dataset, Dataset]
Source code in src/liblaf/melon/io/vtk/_temporal_unstructured_grid.py
def dataset_offsets(
    self, dataset_name: str, offsets_name: str, data: ArrayLike
) -> tuple[h5.Dataset, h5.Dataset]:
    data: np.ndarray = _sanitize_array(data)
    if not self.dataset_tail_equal(dataset_name, data):
        dataset: h5.Dataset = self.dataset(dataset_name, data)
    else:
        dataset: h5.Dataset = self.dataset(dataset_name)
    offsets: h5.Dataset = self.dataset(offsets_name, dataset.len() - data.shape[0])
    return dataset, offsets

dataset_tail_equal

dataset_tail_equal(name: str, data: ArrayLike) -> bool
Source code in src/liblaf/melon/io/vtk/_temporal_unstructured_grid.py
def dataset_tail_equal(self, name: str, data: ArrayLike) -> bool:
    data: np.ndarray = _sanitize_array(data)
    if name not in self.file:
        return False
    dataset: h5.Dataset = self.file[name]
    if dataset.len() == 0 or data.size == 0:
        return False
    tail: np.ndarray = dataset[-data.shape[0] :]
    tail: np.ndarray = _sanitize_array(tail)
    if tail.dtype != data.dtype:
        return False
    numeric: bool = dataset.dtype.kind in {"b", "i", "u", "f", "c"}
    return np.array_equal(tail, data, equal_nan=numeric)

group

group(name: str) -> Group
Source code in src/liblaf/melon/io/vtk/_temporal_unstructured_grid.py
def group(self, name: str) -> h5.Group:
    return self.file.require_group(name)

WriterDispatcher

Dispatch writers first by file suffix and then by object type.

Registered writers receive a pathlib.Path. Parent directories are created before the writer is called, which keeps concrete writer implementations focused on serialization.

Raises:

  • KeyError

    If no writer registry exists for the path suffix.

  • NotImplementedError

    If the suffix exists but the object type is not registered for that suffix.

Parameters:

  • registry (dict[str, _SingleDispatchCallable[None]], default: <class 'dict'> ) –

    dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)

Methods:

Attributes:

registry class-attribute instance-attribute

registry: dict[str, _SingleDispatchCallable[None]] = field(
    factory=dict
)

__call__

__call__(obj: T, path: StrPath, /, **kwargs) -> None
Source code in src/liblaf/melon/io/abc/_writer.py
def __call__(self, obj: T, path: StrPath, /, **kwargs) -> None:
    path: Path = Path(path)
    writer: _SingleDispatchCallable[None] = self.registry[path.suffix]
    path.parent.mkdir(parents=True, exist_ok=True)
    return writer(obj, path, **kwargs)

register

register(
    cls: _RegType,
    suffixes: Iterable[str],
    writer: AbstractWriter[T],
) -> AbstractWriter[T]
register(
    cls: _RegType,
    suffixes: Iterable[str],
    writer: None = None,
) -> Callable[[AbstractWriter[T]], AbstractWriter[T]]
Source code in src/liblaf/melon/io/abc/_writer.py
def register(
    self,
    cls: _RegType,
    suffixes: Iterable[str],
    writer: AbstractWriter[T] | None = None,
) -> Callable[..., Any]:
    if writer is None:
        return functools.partial(self.register, cls, suffixes)
    for suffix in suffixes:
        if suffix not in self.registry:
            self.registry[suffix] = functools.singledispatch(_default_writer)
        self.registry[suffix].register(cls, writer)
    return writer

load_landmarks

load_landmarks(path: StrPath) -> Float[ndarray, 'L 3']

Load Wrap landmark points from JSON.

Non-JSON mesh paths are mapped to a sibling .landmarks.json file. Missing files return an empty (0, 3) array so annotation workflows can start from an unmarked mesh.

Parameters:

  • path (StrPath) –

    Landmark JSON file or mesh path whose landmark sidecar should be inferred.

Returns:

  • Float[ndarray, 'L 3']

    Landmark coordinates in x, y, z order.

Source code in src/liblaf/melon/io/wrap/_landmarks.py
def load_landmarks(path: StrPath) -> Float[np.ndarray, "L 3"]:
    """Load Wrap landmark points from JSON.

    Non-JSON mesh paths are mapped to a sibling `.landmarks.json` file. Missing
    files return an empty `(0, 3)` array so annotation workflows can start from
    an unmarked mesh.

    Args:
        path: Landmark JSON file or mesh path whose landmark sidecar should be
            inferred.

    Returns:
        Landmark coordinates in `x`, `y`, `z` order.
    """
    path: Path = _infer_path(path)
    if not path.exists():
        return np.zeros((0, 3))
    with path.open() as fp:
        data: list[dict[str, float]] = json.load(fp)
    return np.asarray([[point["x"], point["y"], point["z"]] for point in data])

load_polygons

load_polygons(path: StrPath) -> Integer[ndarray, ' N']

Load selected polygon indices from JSON.

Parameters:

  • path (StrPath) –

    JSON file containing polygon indices.

Returns:

  • Integer[ndarray, ' N']

    One-dimensional int32 array of selected polygon indices. Missing files

  • Integer[ndarray, ' N']

    return an empty array.

Source code in src/liblaf/melon/io/wrap/_polygons.py
def load_polygons(path: StrPath) -> Integer[np.ndarray, " N"]:
    """Load selected polygon indices from JSON.

    Args:
        path: JSON file containing polygon indices.

    Returns:
        One-dimensional `int32` array of selected polygon indices. Missing files
        return an empty array.
    """
    path: Path = Path(path)
    if not path.exists():
        return np.empty((0,), np.int32)
    with path.open() as fp:
        data: list[int] = json.load(fp)
    polygons: Integer[np.ndarray, " N"] = np.asarray(data, np.int32)
    return polygons

save_landmarks

save_landmarks(
    landmarks: Float[ArrayLike, "L 3"], path: StrPath
) -> None

Save landmark points in Wrap-compatible JSON format.

Parameters:

  • landmarks (Float[ArrayLike, 'L 3']) –

    Array-like landmark coordinates with shape (n, 3).

  • path (StrPath) –

    Landmark JSON file or mesh path whose landmark sidecar should be inferred.

Source code in src/liblaf/melon/io/wrap/_landmarks.py
def save_landmarks(landmarks: Float[ArrayLike, "L 3"], path: StrPath) -> None:
    """Save landmark points in Wrap-compatible JSON format.

    Args:
        landmarks: Array-like landmark coordinates with shape `(n, 3)`.
        path: Landmark JSON file or mesh path whose landmark sidecar should be
            inferred.
    """
    landmarks: Float[np.ndarray, "L 3"] = np.asarray(landmarks)
    path: Path = _infer_path(path)
    data: list[dict[str, float]] = [
        {"x": x, "y": y, "z": z} for x, y, z in landmarks.tolist()
    ]
    with path.open("w") as fp:
        json.dump(data, fp)

save_polygons

save_polygons(
    polygons: Bool[ArrayLike, " full"]
    | Integer[ArrayLike, " selection"],
    path: StrPath,
) -> None

Save selected polygon indices for Wrap projects.

Boolean masks are converted to their selected indices before serialization.

Parameters:

  • polygons (Bool[ArrayLike, ' full'] | Integer[ArrayLike, ' selection']) –

    Boolean mask over all polygons or explicit polygon indices.

  • path (StrPath) –

    JSON file to write.

Source code in src/liblaf/melon/io/wrap/_polygons.py
def save_polygons(
    polygons: Bool[ArrayLike, " full"] | Integer[ArrayLike, " selection"], path: StrPath
) -> None:
    """Save selected polygon indices for Wrap projects.

    Boolean masks are converted to their selected indices before serialization.

    Args:
        polygons: Boolean mask over all polygons or explicit polygon indices.
        path: JSON file to write.
    """
    path: Path = Path(path)
    polygons: Bool[np.ndarray, " full"] | Integer[np.ndarray, " selection"] = (
        np.asarray(polygons)
    )
    if np.isdtype(polygons.dtype, "bool"):
        polygons: Integer[np.ndarray, " selection"] = np.flatnonzero(polygons)
    with path.open("w") as fp:
        json.dump(polygons.tolist(), fp)