Skip to content

Mesh Export

The voids.mesh sub-package converts regular porosity and permeability maps into structured mesh files for downstream continuum workflows, including quadrilateral/triangular 2-D exports and hexahedral/tetrahedral 3-D exports.

These helpers preserve the map grid and cell ordering; they do not generate a boundary-conforming mesh of the original segmented pore/bone interface. For the map definitions, schemes, Kozeny-Carman closure, export assumptions, and solver-facing caveats, see Porosity Maps.

API

voids.mesh

StructuredMapMesh dataclass

Structured mesh representation for regular porosity/permeability maps.

Attributes:

Name Type Description
points ndarray

Mesh point coordinates. Points are always stored as three-dimensional coordinates so 2-D maps can be written by formats that expect 3-D coordinates with z=0.

cells tuple[tuple[str, ndarray], ...]

A single meshio-compatible cell block. 2-D maps use "quad" cells by default or "triangle" cells when requested. 3-D maps use "hexahedron" cells by default or "tetra" cells when requested.

cell_data dict[str, list[ndarray]]

Cell-wise data arrays whose flattened order follows numpy.ravel(order="C") of the original map arrays.

Source code in src/voids/mesh/structured.py
@dataclass(slots=True)
class StructuredMapMesh:
    """Structured mesh representation for regular porosity/permeability maps.

    Attributes
    ----------
    points :
        Mesh point coordinates. Points are always stored as three-dimensional
        coordinates so 2-D maps can be written by formats that expect 3-D
        coordinates with ``z=0``.
    cells :
        A single meshio-compatible cell block. 2-D maps use ``"quad"`` cells by
        default or ``"triangle"`` cells when requested. 3-D maps use
        ``"hexahedron"`` cells by default or ``"tetra"`` cells when requested.
    cell_data :
        Cell-wise data arrays whose flattened order follows
        ``numpy.ravel(order="C")`` of the original map arrays.
    """

    points: np.ndarray
    cells: tuple[tuple[str, np.ndarray], ...]
    cell_data: dict[str, list[np.ndarray]]

    @property
    def cell_type(self) -> str:
        """Return the meshio cell type used by the structured map mesh."""

        return self.cells[0][0]

    @property
    def cell_count(self) -> int:
        """Return the number of cells in the structured map mesh."""

        return int(self.cells[0][1].shape[0])

    def to_meshio(self, *, include_cell_data: bool = True) -> Any:
        """Return a ``meshio.Mesh`` object.

        Parameters
        ----------
        include_cell_data :
            If ``False``, only geometry is included. This is useful for formats
            that do not preserve arbitrary floating-point cell data.
        """

        meshio = _import_meshio()
        return meshio.Mesh(
            points=self.points,
            cells=list(self.cells),
            cell_data=self.cell_data if include_cell_data else {},
        )

    def write(self, path: str | Path, *, file_format: str | None = None) -> Path:
        """Write the mesh with meshio and return the destination path."""

        return _write_mesh(self, path, file_format=file_format)

cell_type property

cell_type

Return the meshio cell type used by the structured map mesh.

cell_count property

cell_count

Return the number of cells in the structured map mesh.

to_meshio

to_meshio(*, include_cell_data=True)

Return a meshio.Mesh object.

Parameters:

Name Type Description Default
include_cell_data bool

If False, only geometry is included. This is useful for formats that do not preserve arbitrary floating-point cell data.

True
Source code in src/voids/mesh/structured.py
def to_meshio(self, *, include_cell_data: bool = True) -> Any:
    """Return a ``meshio.Mesh`` object.

    Parameters
    ----------
    include_cell_data :
        If ``False``, only geometry is included. This is useful for formats
        that do not preserve arbitrary floating-point cell data.
    """

    meshio = _import_meshio()
    return meshio.Mesh(
        points=self.points,
        cells=list(self.cells),
        cell_data=self.cell_data if include_cell_data else {},
    )

write

write(path, *, file_format=None)

Write the mesh with meshio and return the destination path.

Source code in src/voids/mesh/structured.py
def write(self, path: str | Path, *, file_format: str | None = None) -> Path:
    """Write the mesh with meshio and return the destination path."""

    return _write_mesh(self, path, file_format=file_format)

mesh_format_extension

mesh_format_extension(format_name)

Return the default filename extension for a supported mesh export format.

Source code in src/voids/mesh/structured.py
def mesh_format_extension(format_name: str) -> str:
    """Return the default filename extension for a supported mesh export format."""

    key = str(format_name).strip().lower().lstrip(".")
    try:
        return _FORMAT_EXTENSIONS[key]
    except KeyError as exc:
        supported = ", ".join(sorted(_FORMAT_EXTENSIONS))
        raise ValueError(
            f"Unsupported mesh format {format_name!r}; expected one of {supported}"
        ) from exc

structured_map_mesh

structured_map_mesh(
    porosity_map,
    *,
    permeability_map=None,
    extra_cell_data=None,
    element_type="auto",
    include_cell_index=True,
    require_finite_cell_data=True,
)

Generate a structured mesh from a regular porosity map.

Parameters:

Name Type Description Default
porosity_map PorosityMap

Cell-average porosity map. Its grid defines the mesh topology and physical coordinates.

required
permeability_map PermeabilityMap | None

Optional permeability map on the same grid. When supplied, it is written as the "permeability" cell-data field.

None
extra_cell_data Mapping[str, ndarray] | None

Optional additional cell-data arrays. Each array must have the same shape as porosity_map.values.

None
element_type MapMeshElement

Mesh cell type. "auto" uses quadrilaterals for 2-D maps and hexahedra for 3-D maps. "triangle" is valid only for 2-D maps and splits each quadrilateral into two triangles. "tetra" and "tetrahedron" are valid only for 3-D maps and split each hexahedron into six tetrahedra.

'auto'
include_cell_index bool

If True, include a zero-based "cell_index" integer field that maps each mesh cell back to its parent map cell. For simplex exports, the child triangles or tetrahedra from one map cell share the same value.

True
require_finite_cell_data bool

If True, reject NaN or infinite cell-data values before export.

True

Returns:

Type Description
StructuredMapMesh

Meshio-compatible points, cell connectivity, and cell-data arrays.

Notes

The mesh is a representation of a regular map grid, not an image-boundary conforming segmentation mesh. Cell n corresponds to porosity_map.values.ravel(order="C")[n].

Source code in src/voids/mesh/structured.py
def structured_map_mesh(
    porosity_map: PorosityMap,
    *,
    permeability_map: PermeabilityMap | None = None,
    extra_cell_data: Mapping[str, np.ndarray] | None = None,
    element_type: MapMeshElement = "auto",
    include_cell_index: bool = True,
    require_finite_cell_data: bool = True,
) -> StructuredMapMesh:
    """Generate a structured mesh from a regular porosity map.

    Parameters
    ----------
    porosity_map :
        Cell-average porosity map. Its grid defines the mesh topology and
        physical coordinates.
    permeability_map :
        Optional permeability map on the same grid. When supplied, it is written
        as the ``"permeability"`` cell-data field.
    extra_cell_data :
        Optional additional cell-data arrays. Each array must have the same shape
        as ``porosity_map.values``.
    element_type :
        Mesh cell type. ``"auto"`` uses quadrilaterals for 2-D maps and
        hexahedra for 3-D maps. ``"triangle"`` is valid only for 2-D maps and
        splits each quadrilateral into two triangles. ``"tetra"`` and
        ``"tetrahedron"`` are valid only for 3-D maps and split each
        hexahedron into six tetrahedra.
    include_cell_index :
        If ``True``, include a zero-based ``"cell_index"`` integer field that
        maps each mesh cell back to its parent map cell. For simplex exports,
        the child triangles or tetrahedra from one map cell share the same
        value.
    require_finite_cell_data :
        If ``True``, reject NaN or infinite cell-data values before export.

    Returns
    -------
    StructuredMapMesh
        Meshio-compatible points, cell connectivity, and cell-data arrays.

    Notes
    -----
    The mesh is a representation of a regular map grid, not an image-boundary
    conforming segmentation mesh. Cell ``n`` corresponds to
    ``porosity_map.values.ravel(order="C")[n]``.
    """

    _validate_map_grid(porosity_map)
    points, cells = _structured_points_and_cells(
        shape=porosity_map.shape,
        cell_size=_map_cell_size(porosity_map),
        origin=_map_origin(porosity_map),
        element_type=element_type,
    )
    base_cell_count = int(np.prod(porosity_map.shape))
    mesh_cell_count = int(cells[1].shape[0])

    cell_data: dict[str, list[np.ndarray]] = {
        "porosity": [
            _expand_cell_data_to_mesh_cells(
                _flatten_cell_data(
                    porosity_map.values,
                    shape=porosity_map.shape,
                    name="porosity",
                    require_finite=require_finite_cell_data,
                ),
                base_cell_count=base_cell_count,
                mesh_cell_count=mesh_cell_count,
            )
        ]
    }

    if permeability_map is not None:
        _validate_matching_map_grid(porosity_map, permeability_map)
        cell_data["permeability"] = [
            _expand_cell_data_to_mesh_cells(
                _flatten_cell_data(
                    permeability_map.values,
                    shape=porosity_map.shape,
                    name="permeability",
                    require_finite=require_finite_cell_data,
                ),
                base_cell_count=base_cell_count,
                mesh_cell_count=mesh_cell_count,
            )
        ]

    if include_cell_index:
        cell_data["cell_index"] = [
            _expand_cell_data_to_mesh_cells(
                np.arange(base_cell_count, dtype=np.int64),
                base_cell_count=base_cell_count,
                mesh_cell_count=mesh_cell_count,
            )
        ]

    if extra_cell_data:
        for raw_name, raw_values in extra_cell_data.items():
            name = str(raw_name)
            if not name:
                raise ValueError("extra_cell_data names must be non-empty strings")
            if name in cell_data:
                raise ValueError(f"Duplicate cell-data name {name!r}")
            cell_data[name] = [
                _expand_cell_data_to_mesh_cells(
                    _flatten_cell_data(
                        raw_values,
                        shape=porosity_map.shape,
                        name=name,
                        require_finite=require_finite_cell_data,
                    ),
                    base_cell_count=base_cell_count,
                    mesh_cell_count=mesh_cell_count,
                )
            ]

    return StructuredMapMesh(points=points, cells=(cells,), cell_data=cell_data)

write_structured_map_mesh

write_structured_map_mesh(
    porosity_map,
    path,
    *,
    permeability_map=None,
    extra_cell_data=None,
    element_type="auto",
    file_format=None,
    require_finite_cell_data=True,
)

Write a structured map mesh with porosity/permeability cell data.

Parameters:

Name Type Description Default
porosity_map PorosityMap
required
permeability_map PorosityMap
required
extra_cell_data PorosityMap
required
element_type PorosityMap
required
require_finite_cell_data bool

Passed to :func:structured_map_mesh.

True
path str | Path

Destination mesh path. The file extension normally determines the meshio writer.

required
file_format str | None

Optional meshio file-format override.

None
Notes

Netgen .vol export is geometry-oriented in meshio. The writer does not preserve arbitrary floating-point cell-data arrays, so keep the HDF5 map export as the authoritative porosity/permeability field when using Netgen.

Source code in src/voids/mesh/structured.py
def write_structured_map_mesh(
    porosity_map: PorosityMap,
    path: str | Path,
    *,
    permeability_map: PermeabilityMap | None = None,
    extra_cell_data: Mapping[str, np.ndarray] | None = None,
    element_type: MapMeshElement = "auto",
    file_format: str | None = None,
    require_finite_cell_data: bool = True,
) -> Path:
    """Write a structured map mesh with porosity/permeability cell data.

    Parameters
    ----------
    porosity_map, permeability_map, extra_cell_data, element_type,
    require_finite_cell_data :
        Passed to :func:`structured_map_mesh`.
    path :
        Destination mesh path. The file extension normally determines the meshio
        writer.
    file_format :
        Optional meshio file-format override.

    Notes
    -----
    Netgen ``.vol`` export is geometry-oriented in meshio. The writer does not
    preserve arbitrary floating-point cell-data arrays, so keep the HDF5 map
    export as the authoritative porosity/permeability field when using Netgen.
    """

    mesh = structured_map_mesh(
        porosity_map,
        permeability_map=permeability_map,
        extra_cell_data=extra_cell_data,
        element_type=element_type,
        require_finite_cell_data=require_finite_cell_data,
    )
    return mesh.write(path, file_format=file_format)

write_structured_map_meshes

write_structured_map_meshes(
    porosity_map,
    output_dir,
    *,
    stem,
    permeability_map=None,
    formats=("gmsh", "vtk", "vtu", "netgen"),
    extra_cell_data=None,
    element_type="auto",
    require_finite_cell_data=True,
)

Write the same structured map mesh to several mesh formats.

Parameters:

Name Type Description Default
porosity_map PorosityMap
required
permeability_map PorosityMap
required
extra_cell_data PorosityMap
required
element_type PorosityMap
required
require_finite_cell_data bool

Passed to :func:structured_map_mesh.

True
output_dir str | Path

Directory where mesh files will be written.

required
stem str

Filename stem used for every exported file.

required
formats Sequence[str]

Format labels. Supported labels are "gmsh", "vtk", "vtu", and "netgen". Aliases "msh" and "vol" are also accepted.

('gmsh', 'vtk', 'vtu', 'netgen')

Returns:

Type Description
dict[str, Path]

Mapping from the requested format label to the written mesh path.

Source code in src/voids/mesh/structured.py
def write_structured_map_meshes(
    porosity_map: PorosityMap,
    output_dir: str | Path,
    *,
    stem: str,
    permeability_map: PermeabilityMap | None = None,
    formats: Sequence[str] = ("gmsh", "vtk", "vtu", "netgen"),
    extra_cell_data: Mapping[str, np.ndarray] | None = None,
    element_type: MapMeshElement = "auto",
    require_finite_cell_data: bool = True,
) -> dict[str, Path]:
    """Write the same structured map mesh to several mesh formats.

    Parameters
    ----------
    porosity_map, permeability_map, extra_cell_data, element_type,
    require_finite_cell_data :
        Passed to :func:`structured_map_mesh`.
    output_dir :
        Directory where mesh files will be written.
    stem :
        Filename stem used for every exported file.
    formats :
        Format labels. Supported labels are ``"gmsh"``, ``"vtk"``, ``"vtu"``,
        and ``"netgen"``. Aliases ``"msh"`` and ``"vol"`` are also accepted.

    Returns
    -------
    dict[str, pathlib.Path]
        Mapping from the requested format label to the written mesh path.
    """

    out = Path(output_dir)
    out.mkdir(parents=True, exist_ok=True)
    normalized_stem = str(stem).strip()
    if not normalized_stem:
        raise ValueError("stem must be a non-empty string")

    mesh = structured_map_mesh(
        porosity_map,
        permeability_map=permeability_map,
        extra_cell_data=extra_cell_data,
        element_type=element_type,
        require_finite_cell_data=require_finite_cell_data,
    )

    paths: dict[str, Path] = {}
    for fmt in formats:
        key = str(fmt).strip().lower().lstrip(".")
        extension = mesh_format_extension(key)
        path = out / f"{normalized_stem}{extension}"
        paths[key] = mesh.write(path)

    return paths