Skip to main content

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, and meshio.
  • 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, .sol size 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:

  1. Convert a mesh object or file into MeshGeometry.
  2. Build MeshStatistics from 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, or coordinates
  • element connectivity: elements, cells, connectivity, or t
  • facet connectivity: facets, faces, edges, or boundary_facets
  • labels: facet_labels, boundary_labels, or boundaries
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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mesh_metrics-0.1.1.tar.gz (44.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mesh_metrics-0.1.1-py3-none-any.whl (49.0 kB view details)

Uploaded Python 3

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

Hashes for mesh_metrics-0.1.1.tar.gz
Algorithm Hash digest
SHA256 17ad7c734074193760a140ef0304eaebefcc722d245387c1979309c363250226
MD5 59cd56345848b26cb44ef68ee7e193e4
BLAKE2b-256 4b0e1cd322c1b4b1f070e6a5ba3820b5e38a27ec222b82516a64d774b5f484b8

See more details on using hashes here.

Provenance

The following attestation bundles were made for mesh_metrics-0.1.1.tar.gz:

Publisher: workflow.yml on kevin-tofu/mesh-metrics

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for mesh_metrics-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e86fbebf0185e77ed504cbf5ac498f2c73a869835e016b6682b4660e5189f2f4
MD5 44f3d347200e8831c210dba42266e69b
BLAKE2b-256 f44ff8564f75f9a3b28f2f91a11d054106811734299062f7e370fda9e34cea89

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page