Mesh quality metrics, facet statistics, visualization, and MMG3D remeshing workflow helpers.
Project description
mesh-metrics
Mesh quality metrics, facet statistics, visualization, and MMG3D remeshing workflow helpers for Python.
mesh-metrics is built for workflows where you inspect a mesh, decide how to control facet sizes and element counts, run MMG3D, and evaluate whether the remeshed result improved. It separates element-level mesh quality metrics from facet-level size metrics, and it keeps facet labels and geometric regions available across remeshing iterations.
Features
- Element statistics: measure, diameter, edge aspect ratio, histograms.
- Facet statistics: facet measure, diameter, edge aspect ratio, label-wise stats, histograms.
- Dataclass API for meshes, facets, labels, histograms, regions, iteration metrics, and remeshing evaluation.
- Backends for
skfem,fluxfem, andmeshio. - Matplotlib histogram and metrics-vs-iteration plots.
- PyVista VTU export and PNG rendering for mesh quality and labeled surface regions.
- MMG3D helpers for sizing recommendations,
.solsize fields, remeshing, comparison, evaluation, and iterative workflows.
Installation
Install the base package:
pip install mesh-metrics
Install optional mesh backends as needed:
pip install "mesh-metrics[skfem]"
pip install "mesh-metrics[fluxfem]"
pip install "mesh-metrics[all]"
For local development with Poetry:
git clone git@github.com:kevin-tofu/mesh-metrics.git
cd mesh-metrics
python -m pip install --user pipx
pipx install poetry
poetry install --with dev --extras all
poetry run python -c "import mesh_metrics; print(mesh_metrics.__name__)"
poetry run pytest -q
poetry install --with dev --extras all installs the package in editable mode together with the development dependency group and optional mesh backends used by the backend tests.
MMG3D is not bundled. Install mmg3d separately and make sure it is available on PATH, or pass --mmg3d-bin.
Python API
The core API has two steps:
- Convert a mesh object or file into
MeshGeometry. - Build
MeshStatisticsfrom that geometry.
MeshStatistics intentionally separates element quality from facet sizing:
stats.elements: element measure, diameter, and edge aspect ratio.stats.facets: facet measure, diameter, edge aspect ratio, and label-wise facet statistics.stats.histograms: histogram data for element and facet quantities.stats.to_dict(): JSON-serializable output for reports, optimization loops, or remeshing drivers.
From arrays
import numpy as np
from mesh_metrics import FacetLabels, MeshGeometry, MeshStatistics
mesh = MeshGeometry(
points=np.asarray(
[
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]
),
elements=np.asarray([[0], [1], [2], [3]]),
facets=np.asarray([[0, 0, 0, 1], [1, 1, 2, 2], [2, 3, 3, 3]]),
facet_labels=FacetLabels({"wall": np.asarray([0, 1, 2]), "outlet": np.asarray([3])}),
)
stats = MeshStatistics.from_mesh(mesh, bins=30)
print(stats.elements.edge_aspect_ratio.p95)
print(stats.facets.measure.mean)
print(stats.facets.labels["wall"].diameter.median)
stats.save_histograms("result/histograms")
MeshGeometry expects dimension-first arrays:
points: shape(dimension, npoints)elements: shape(nodes_per_element, nelements)facets: shape(nodes_per_facet, nfacets)
If facets is omitted, boundary facets are inferred for common triangle, quad, tetrahedron, and hexahedron meshes.
From a scikit-fem mesh
Install the optional backend first:
pip install "mesh-metrics[skfem]"
Then pass a skfem mesh directly:
from skfem import MeshTet
from mesh_metrics import MeshGeometry, MeshStatistics
sk_mesh = MeshTet().refined(2)
mesh = MeshGeometry.from_skfem(sk_mesh)
stats = MeshStatistics.from_mesh(mesh, bins=40)
print("elements:", mesh.nelements)
print("facets:", mesh.nfacets)
print("element volume mean:", stats.elements.measure.mean)
print("element aspect p95:", stats.elements.edge_aspect_ratio.p95)
print("facet area median:", stats.facets.measure.median)
MeshGeometry.from_skfem reads mesh.p, mesh.t, mesh.facets, and mesh.boundaries when present. mesh.boundaries becomes FacetLabels, so named boundaries are available in stats.facets.labels.
For labeled skfem boundaries:
from skfem import MeshTri
from mesh_metrics import MeshGeometry, MeshStatistics
sk_mesh = MeshTri().refined(3).with_boundaries(
{
"left": lambda x: x[0] == 0.0,
"right": lambda x: x[0] == 1.0,
}
)
mesh = MeshGeometry.from_skfem(sk_mesh)
stats = MeshStatistics.from_mesh(mesh)
left = stats.facets.labels["left"]
print(left.measure.count)
print(left.diameter.mean)
From a fluxfem-style mesh object
Install the optional backend first:
pip install "mesh-metrics[fluxfem]"
For in-memory mesh objects, use MeshGeometry.from_object. In the examples below, flux_mesh is the mesh object you already have in a fluxfem application, for example one returned by your mesh generator, file-loading layer, preprocessing step, or solver setup. mesh-metrics does not create a fluxfem mesh; it adapts the mesh-like object you pass in.
MeshGeometry.from_object accepts common fluxfem-style attribute names:
- coordinates:
points,vertices,nodes,coords, orcoordinates - element connectivity:
elements,cells,connectivity, ort - facet connectivity:
facets,faces,edges, orboundary_facets - labels:
facet_labels,boundary_labels, orboundaries
from mesh_metrics import MeshGeometry, MeshStatistics
# flux_mesh comes from your fluxfem-side setup code.
mesh = MeshGeometry.from_object(flux_mesh, backend="fluxfem")
stats = MeshStatistics.from_mesh(mesh)
print(stats.elements.measure.total)
print(stats.elements.edge_aspect_ratio.max)
print(stats.facets.diameter.p95)
If your fluxfem workflow keeps mesh arrays separately, wrap them in a small adapter object:
from dataclasses import dataclass
import numpy as np
from mesh_metrics import MeshGeometry, MeshStatistics
@dataclass
class FluxMeshAdapter:
points: np.ndarray
elements: np.ndarray
faces: np.ndarray | None = None
facet_labels: dict[str, list[int]] | None = None
flux_mesh = FluxMeshAdapter(
points=np.asarray(
[
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
]
),
elements=np.asarray([[0, 1, 2, 3]]),
faces=np.asarray([[0, 1, 2], [0, 1, 3], [0, 2, 3], [1, 2, 3]]),
facet_labels={"wall": [0, 1, 2], "outlet": [3]},
)
mesh = MeshGeometry.from_object(flux_mesh, backend="fluxfem")
stats = MeshStatistics.from_mesh(mesh)
print(stats.facets.labels["wall"].measure.mean)
If your object does not expose labels in a supported attribute, attach them explicitly:
from mesh_metrics import FacetLabels, MeshGeometry, MeshStatistics
mesh = MeshGeometry.from_object(flux_mesh, backend="fluxfem")
mesh = mesh.with_facet_labels(
FacetLabels(
{
"wall": [0, 1, 2, 3],
"inlet": [4, 5],
"outlet": [6, 7],
}
)
)
stats = MeshStatistics.from_mesh(mesh)
print(stats.facets.labels["wall"].measure.mean)
From mesh files
Use MeshGeometry.load when you want the backend to read a file:
from mesh_metrics import MeshGeometry, MeshStatistics
mesh = MeshGeometry.load("mesh.mesh", backend="meshio")
stats = MeshStatistics.from_mesh(mesh)
payload = stats.to_dict()
Available file backends are meshio, skfem, and fluxfem.
mesh = MeshGeometry.load("mesh.vtu", backend="meshio")
mesh = MeshGeometry.load("mesh.msh", backend="skfem")
mesh = MeshGeometry.load("mesh.mesh", backend="fluxfem")
The fluxfem file backend currently uses meshio as the file adapter while keeping backend="fluxfem" in the resulting MeshGeometry.
Reading statistics
stats = MeshStatistics.from_mesh(mesh, bins=50)
element_quality = stats.elements
facet_size = stats.facets
print(element_quality.measure.min)
print(element_quality.measure.max)
print(element_quality.edge_aspect_ratio.mean)
print(element_quality.edge_aspect_ratio.p95)
print(facet_size.measure.mean)
print(facet_size.diameter.p05)
print(facet_size.diameter.p95)
Each quantity is a QuantityStats dataclass:
q = stats.elements.edge_aspect_ratio
print(q.count)
print(q.min, q.max)
print(q.mean, q.median, q.std)
print(q.p05, q.p25, q.p75, q.p95)
Label-wise facet statistics are stored under stats.facets.labels:
for label, label_stats in stats.facets.labels.items():
print(label)
print("facet count:", label_stats.measure.count)
print("area mean:", label_stats.measure.mean)
print("diameter p95:", label_stats.diameter.p95)
Histograms and report data
Save all histograms as PNG files:
stats = MeshStatistics.from_mesh(mesh, bins=40)
paths = stats.save_histograms("result/histograms")
Use to_dict for optimization tools, reports, or JSON output:
import json
from pathlib import Path
payload = stats.to_dict()
Path("result/stats.json").write_text(json.dumps(payload, indent=2), encoding="utf-8")
Omit histogram arrays when the mesh is large:
payload = stats.to_dict(include_histograms=False)
Visualization and remeshing helpers
The Python API also exposes VTU export, PNG rendering, MMG3D execution metadata, size fields, region transfer, and iteration plots:
from mesh_metrics import SizeField, export_vtu
field = SizeField.from_mesh(
mesh,
default_size=0.5,
facet_size_map={"wall": 0.1, "inlet": 0.2},
)
field.write_mmg_sol("metric.sol", dimension=mesh.dimension)
export_vtu(mesh, mesh_path="result/mesh.vtu", facets_path="result/facets.vtu")
The command line interface is available as mesh-metrics, but the library is designed so optimization and remeshing loops can use these dataclasses directly.
Development
Set up the repository:
git clone git@github.com:kevin-tofu/mesh-metrics.git
cd mesh-metrics
poetry install --with dev --extras all
Run the local checks:
poetry run python -c "from mesh_metrics import MeshGeometry, MeshStatistics; print(MeshGeometry, MeshStatistics)"
poetry run pytest -q
poetry check
poetry build
When dependencies change, update the lock file and verify the package again:
poetry lock
poetry install --with dev --extras all
poetry run pytest -q
poetry build
The GitHub Actions workflow runs poetry install --with dev --extras all, poetry check, and pytest on Python 3.12.
The package is licensed under the Apache License 2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file mesh_metrics-0.1.1.tar.gz.
File metadata
- Download URL: mesh_metrics-0.1.1.tar.gz
- Upload date:
- Size: 44.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17ad7c734074193760a140ef0304eaebefcc722d245387c1979309c363250226
|
|
| MD5 |
59cd56345848b26cb44ef68ee7e193e4
|
|
| BLAKE2b-256 |
4b0e1cd322c1b4b1f070e6a5ba3820b5e38a27ec222b82516a64d774b5f484b8
|
Provenance
The following attestation bundles were made for mesh_metrics-0.1.1.tar.gz:
Publisher:
workflow.yml on kevin-tofu/mesh-metrics
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mesh_metrics-0.1.1.tar.gz -
Subject digest:
17ad7c734074193760a140ef0304eaebefcc722d245387c1979309c363250226 - Sigstore transparency entry: 2190468752
- Sigstore integration time:
-
Permalink:
kevin-tofu/mesh-metrics@cd97fb1a33776b131f346c9b8b5692b36b6e8ddd -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/kevin-tofu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@cd97fb1a33776b131f346c9b8b5692b36b6e8ddd -
Trigger Event:
push
-
Statement type:
File details
Details for the file mesh_metrics-0.1.1-py3-none-any.whl.
File metadata
- Download URL: mesh_metrics-0.1.1-py3-none-any.whl
- Upload date:
- Size: 49.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e86fbebf0185e77ed504cbf5ac498f2c73a869835e016b6682b4660e5189f2f4
|
|
| MD5 |
44f3d347200e8831c210dba42266e69b
|
|
| BLAKE2b-256 |
f44ff8564f75f9a3b28f2f91a11d054106811734299062f7e370fda9e34cea89
|
Provenance
The following attestation bundles were made for mesh_metrics-0.1.1-py3-none-any.whl:
Publisher:
workflow.yml on kevin-tofu/mesh-metrics
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
mesh_metrics-0.1.1-py3-none-any.whl -
Subject digest:
e86fbebf0185e77ed504cbf5ac498f2c73a869835e016b6682b4660e5189f2f4 - Sigstore transparency entry: 2190468762
- Sigstore integration time:
-
Permalink:
kevin-tofu/mesh-metrics@cd97fb1a33776b131f346c9b8b5692b36b6e8ddd -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/kevin-tofu
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
workflow.yml@cd97fb1a33776b131f346c9b8b5692b36b6e8ddd -
Trigger Event:
push
-
Statement type: