Metadata-Version: 2.5
Name: stentingmesh
Version: 0.1.0
Summary: Process geometry and mesh results from virtual stent implantation.
Project-URL: Homepage, https://gitlab.git.nrw/maria.martinazzo/stenting-mesh
Author: Maria Martinazzo
License-Expression: GPL-3.0-only
License-File: LICENSE
Keywords: biomechanics,cardiovascular,finite-element,gmsh,mesh-generation,stent
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Python: >=3.12
Requires-Dist: gmsh>=4.15.2
Requires-Dist: numpy>=2.3.5
Requires-Dist: pyvista>=0.48.4
Requires-Dist: scipy>=1.18.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.16.2; extra == 'dev'
Description-Content-Type: text/markdown

# stentingmesh

Process geometry and mesh results from virtual stent implantation.

`stentingmesh` turns a stented-artery geometry — an artery wall surface plus a
deployed stent surface — into an analysis-ready lumen mesh in which the
stent-contact footprints are tagged as their own physical groups, and the mesh
is refined around them. The output is a Gmsh `.msh` file.

## Why

After a virtual stenting implantation you have two surfaces that overlap: the
artery wall and the expanded stent. The solver needs the *lumen wall* — the
inner wall only — meshed finely where the stent struts press into the tissue,
with those contact footprints identifiable as boundary regions so you can
assign them their own boundary conditions.

This package automates the process, avoiding a time-consuming manual geometry
preparation. In **stentingmesh** the lumen is isolated from the wall
automatically, the stent footprint is imprinted into the surface as real mesh
edges (not just a painted-on field), each footprint becomes a named physical
group, and the refined mesh is generated and recombined based on the given
parameters.

## Installation

```bash
pip install stentingmesh
```

Requires Python 3.12 or newer. `gmsh`, `pyvista`, `numpy` and `scipy` are
installed as dependencies.

On Linux, the gmsh wheel additionally needs the OpenMP and OpenGL system
libraries. On Debian/Ubuntu:

```bash
sudo apt-get install libgomp1 libgl1 libglu1-mesa libxcursor1 \
    libxinerama1 libxft2 libxrender1 libxfixes3 libfontconfig1
```

## Quick start

```python
from pathlib import Path
from stentingmesh import preprocessing, meshing

results_dir = Path("results")
results_dir.mkdir(exist_ok=True)

# 1. read the two input surfaces
artery = preprocessing.read_surface("artery.ply")
stent = preprocessing.read_surface("stent.ply")

# 2. isolate the lumen (inner wall) from the artery
lumen = preprocessing.extract_lumen_surface(artery, results_dir=results_dir)

# 3. intersect lumen with stent, embed that curve into a gmsh mesh
h = 0.008
curve = preprocessing.get_intersection_curve(lumen, stent, max_segment_length=h)
embedded, curve_edges = preprocessing.embed_curve(
    results_dir=results_dir, curve=curve, min_mesh_size=h
)

# 4. label each region bounded by the curve as contact or free lumen
regions = preprocessing.classify_contact_regions(embedded, curve_edges, stent)
preprocessing.write_tagged_msh(regions, results_dir=results_dir)

# 5. refine near the contact patches, then recombine into quads
meshing.refine_mesh(
    results_dir=results_dir,
    distance_min=h * 3,
    distance_max=h * 100,
    min_mesh_size=h,
    max_mesh_size=h * 40,
    show_result=False,
)
meshing.makequad(results_dir=results_dir, show_result=False)
```

By default the gmsh GUI opens after the refine and recombine steps so the
result can be inspected; close the window to continue, or pass
`show_result=False` to run headless.

## The pipeline

**1. Read the input surfaces.** `read_surface` accepts anything PyVista can
read (`.ply`, `.stl`, `.vtp`, …) and returns a triangulated, cleaned
`PolyData` with duplicate points merged.

**2. Extract the lumen.** The lumen is not tagged in the input, so it has to be
found geometrically. `extract_centerline` estimates the tube axis by taking the
dominant singular vector of the point cloud and averaging points in bands along
it. `classify_walls` then labels every face by comparing its normal against the
local centerline direction — a face whose normal is roughly parallel to the
axis is a *cap*, one pointing toward the axis is *inner*, one pointing away is
*outer*. `extract_lumen_surface` keeps the inner faces and writes
`lumen_surface.stl`. If the mean radius of the "inner" set comes out larger
than the "outer" set the two labels are swapped, which makes the classifier
robust to inconsistently oriented input normals.

The centerline estimate assumes a roughly tubular vessel with one dominant
axis. For strongly curved or branching anatomy, pass your own `centerline`
array (e.g. from VMTK) to `classify_walls` or `extract_lumen_surface`.

**3. Intersect with the stent.** `get_intersection_curve` computes the
lumen/stent surface intersection and subdivides the resulting loops so no
segment is longer than `max_segment_length`. That resampling matters: the curve
is about to become mesh edges, so its segment length sets the local element
size along the footprint boundary.

**4. Embed the curve.** `embed_curve` is the step that makes the footprints
real geometry rather than a painted-on field. The lumen STL is loaded into
gmsh, `createTopology` and `createGeometry` reparametrise it into a meshable
surface, and the intersection curve is added as a discrete 1-D entity and
*embedded* into that surface. Gmsh then meshes the surface with the curve
forced into the element edges, so the footprint boundary is exactly
representable.

**5. Classify contact regions.** `classify_contact_regions` builds a
face-adjacency graph of the embedded mesh and deletes the adjacencies that
cross an embedded curve edge. The connected components of what remains are
exactly the regions bounded by the intersection curve. Each component is then
tested against the stent: a region is a contact patch if at least
`min_inside_fraction` of its cell centres fall inside the stent surface. The
inside test defaults to `cell_locator` (ray casting) rather than signed
distance, because real stent meshes are rarely watertight — small gaps at strut
junctions make signed distance unreliable, while ray casting degrades
gracefully.

**6. Export with physical groups.** `write_tagged_msh` writes a Gmsh 2.2 ASCII
`.msh` in which the free lumen (`region_id == 0`) becomes physical group `1`
(`lumen`) on one shared elementary surface, and each contact patch becomes
physical group `2` (`stent_contact`) on its own elementary surface. Giving
every patch its own elementary surface is what keeps them distinct when gmsh
remeshes. Pass `per_patch_groups=True` to additionally name each patch
`stent_contact_k`.

**7. Refine and recombine.** `refine_mesh` reloads the tagged mesh and builds a
distance field to the contact *surfaces* rather than to the intersection curve.
This is deliberate: measuring distance to the curve would leave cells deep
inside a wide footprint treated as "far from the stent" and therefore coarse.
Measuring to the surfaces keeps the whole footprint fine. `makequad` then
recombines the triangles into quadrangles and applies one subdivision pass.

Note that `refine_mesh` builds its size field with **twice** the
`min_mesh_size` and `max_mesh_size` you pass, because the `makequad`
subdivision pass halves element size again. Ask for the size you want in the
final mesh.

## Output files

Everything is written into `results_dir`:

| file | written by | contents |
| --- | --- | --- |
| `lumen_surface.stl` | `extract_lumen_surface` | extracted inner wall |
| `intersection_embed.msh` | `embed_curve` | initial mesh with curve embedded |
| `tagged_mesh.msh` | `write_tagged_msh` | regions tagged as physical groups |
| `refined_mesh.msh` | `refine_mesh` | refined near contact patches |
| `quad_mesh.msh` | `makequad` | final recombined quad mesh |

## API reference

### `stentingmesh.preprocessing`

| function | purpose |
| --- | --- |
| `read_surface(path, clean=True)` | read and clean a surface mesh |
| `extract_centerline(artery, npoints=25)` | estimate the vessel centerline |
| `classify_walls(artery, centerline=None, cap_angle_deg=45.0)` | per-face inner/outer/cap labels |
| `extract_lumen_surface(artery, results_dir, ...)` | isolate and save the lumen |
| `get_intersection_curve(lumen_surface, stent, max_segment_length=0.01)` | resampled intersection polyline |
| `embed_curve(results_dir, curve, ...)` | embed the curve into a gmsh surface mesh |
| `classify_contact_regions(mesh, curve_edges, stent, ...)` | split into contact vs. free regions |
| `write_tagged_msh(mesh_regions_result, results_dir, per_patch_groups=False)` | export with physical groups |
| `save_stls(res, outdir)` | per-region STL export |
| `export_all(res, outdir, save_stl=False)` | convenience STL + `.msh` export |
| `plot_contacts(contact_result, stent=None, ...)` | interactive contact-patch view |
| `plot_patches_individually(contact_result)` | each patch in its own colour |
| `plot_curves_over_lumen(inner_surface, curves, stent=None)` | draw intersection loops on the lumen |
| `patch_report(contact_result)` | per-patch face count and area |

### `stentingmesh.meshing`

| function | purpose |
| --- | --- |
| `refine_mesh(results_dir, ...)` | refine by distance to the contact surfaces |
| `makequad(results_dir, ...)` | recombine into quadrangles and subdivide |

Every function carries a full numpydoc docstring, including the gmsh algorithm
and output-format enumerations:

```python
help(stentingmesh.meshing.refine_mesh)
```

## License

`stentingmesh` was created by Maria Martinazzo. It is licensed under the terms
of the GNU General Public License v3.0.
