stentfit

Submodules

Classes

Stent

A stent design, from its STL surface mesh to a fitted spline wireframe.

Artery

A test artery: a wall surface, its centreline, and the 3D solid meshed from them.

Simulation

A mixed-dimensional beam-to-solid simulation setup for one stent and artery.

Package Contents

class stentfit.Stent(stl_file: str, stent_name: str, output_dir: str, n_points: int | None = None, max_display: int = 500000, remove_supports: bool = False, random_seed: int = 0, n_rings: int | None = None, auto_tune: bool = True, pixels_per_strut: int = 10, dilate_px: int = 3, pad_fraction: float = 0.2, tune_time_limit: int = 120, quality_gamma: float = 2.0, ring_halo_frac: float = 0.4)

A stent design, from its STL surface mesh to a fitted spline wireframe.

Holds one stent’s data as it moves through the skeletonisation pipeline. Every stage sets more attributes and writes its own inspectable intermediate (CSV + Plotly HTML) into output_dir, so the run can be checked before committing to an expensive contact simulation.

Run the whole thing with skeletonize(), or drive the three phases separately for the interactive per-ring workflow:

stent = Stent(stl_file, "stent01", "outputs/stent01").skeletonize()

# or, phase by phase, inspecting the plots in between:
stent = Stent(stl_file, "stent01", "outputs/stent01")
stent.skeletonize_2d()        # sample -> rings -> 2D skeleton + checkpoint
stent.edit_and_assemble()     # manual 2D fixes -> assembled flat skeleton
stent.finalize()              # wrap to 3D -> clean graph -> fit splines

After a kernel restart, load() rebuilds the object from the checkpoint in an existing output folder, so the run can pick up at edit_and_assemble() without recomputing anything.

The tuning parameters below are set once on the instance; per-operation parameters stay as arguments on the method that uses them.

Parameters:
  • stl_file – Path to the stent surface mesh (STL).

  • stent_name – Name used to label outputs and plots.

  • output_dir – Folder for all outputs. If it already exists and is non-empty, skeletonize_2d() asks whether to reuse, overwrite, or branch into a versioned folder, and may replace this with the folder actually used.

  • n_points – Number of points to sample from the mesh. None picks the count automatically from the stent size.

  • max_display – Maximum number of points drawn in the HTML views.

  • remove_supports – Drop print-support points during sampling.

  • random_seed – Seed for the point sampling, for repeatable runs.

  • n_rings – Expected ring count. None lets ring detection decide.

  • auto_tune – Search for the best 2D skeletonisation parameters per ring.

  • pixels_per_strut – Raster resolution, in pixels across one strut width.

  • dilate_px – Dilation radius, in pixels, before thinning.

  • pad_fraction – Seam padding added when unrolling a ring to 2D.

  • tune_time_limit – Time budget, in seconds, for the auto-tune search.

  • quality_gamma – Weight of the skeleton quality score during tuning.

  • ring_halo_frac – Z-halo, as a fraction of ring height, so struts reconnect across neighbouring rings.

stl_file
stent_name
output_dir
n_points = None
max_display = 500000
remove_supports = False
random_seed = 0
n_rings = None
auto_tune = True
pixels_per_strut = 10
dilate_px = 3
pad_fraction = 0.2
tune_time_limit = 120
quality_gamma = 2.0
ring_halo_frac = 0.4
mesh = None
stent_df = None
stent_features = None
stent_centerline_direction = None
ring_edges = None
ring_order = None
ring_2d = None
skel_arc = None
skel_z = None
skel_px = None
surf_df = None
skeleton_df = None
skeleton_curves = None
skeleton_splines = None
_versioned_candidates() list[str]

List the existing output folders belonging to this stent.

Matches output_dir itself plus any versioned siblings (<name>_v02, _v03, …) in the same parent folder.

Returns:

Sorted folder paths, empty if none exist yet.

_pick_existing_output_dir(action: str = 'use') str

Resolve which of this stent’s versioned output folders to act on.

With no candidate found, returns output_dir unchanged; with exactly one, returns it; with several, prints them and prompts the user to pick one. Used for both reusing and overwriting, so a stent with several versions never has one silently picked for it.

Parameters:

action – Verb used in the prompt, so it reads as the operation actually about to happen — "use" or "overwrite".

Returns:

The resolved folder path to actually act on.

_resolve_output_dir() bool

Settle on the output folder, asking the user if one already exists.

If output_dir exists and is non-empty, offers to reuse it as-is, overwrite (wipe) it, or branch into a new versioned folder (<name>_v02, _v03, …). Reusing loads that folder’s checkpoint straight onto this object instead of recomputing anything. Updates output_dir in place, and creates the folder.

Reusing and overwriting both go through _pick_existing_output_dir(), so when several versions of this stent exist the user is asked which one — an overwrite never silently wipes the unversioned base folder while other versions sit alongside it.

Returns:

True if an existing checkpoint was loaded and the caller should skip recomputation, False to carry on with a fresh run.

save_checkpoint(verbose: bool = True) str

Write the per-ring 2D skeletons and stent geometry to ring_2d.pkl.

Together with the already-saved ring_points.csv, this lets load() rebuild the object after a kernel restart, without rerunning sampling, ring detection, or 2D skeletonisation.

Parameters:

verbose – Print the saved path and ring count.

Returns:

Path to the written ring_2d.pkl.

_load_checkpoint_into(output_dir: str) None

Restore this object’s state from an output folder’s checkpoint.

Parameters:

output_dir – Folder holding ring_2d.pkl and ring_points.csv.

classmethod load(output_dir: str, stl_file: str = '', stent_name: str = '', **kwargs) Stent

Rebuild a Stent from an existing output folder.

Reads back the ring_2d.pkl checkpoint and ring_points.csv, so the pipeline can resume at edit_and_assemble() after a kernel restart. This replaces the procedural pipeline’s state=None resume branch — a reloaded object is the resumed state.

The stent’s own geometry (features, centreline direction, ring edges, surface cloud) all comes from the checkpoint. stl_file only matters if you intend to re-run skeletonize_2d() on the reloaded object.

Parameters:
  • output_dir – Folder holding ring_2d.pkl and ring_points.csv.

  • stl_file – Path to the original STL, if it is needed again.

  • stent_name – Name used to label outputs and plots. Empty uses the output folder’s basename.

  • kwargs – Any Stent tuning parameter, to override the constructor defaults on the reloaded object.

Raises:

FileNotFoundError – If the folder has no checkpoint to load.

Returns:

The reconstructed stent.

skeletonize_2d() Stent

Sample the stent mesh, detect its rings, and 2D-skeletonise each ring.

Loads the STL, samples a point cloud, splits the stent into rings, then runs the 2D skeletonisation per ring. The assembled ring data is checkpointed to disk (save_checkpoint()) so the run can resume after a kernel restart.

If output_dir already exists and is non-empty, the user is asked whether to reuse it as-is, overwrite it, or branch into a new versioned folder. Reusing loads that folder’s checkpoint instead of recomputing anything.

Sets stent_df, stent_features, stent_centerline_direction, ring_edges, ring_2d, and ring_order.

Raises:

FileNotFoundError – If stl_file does not exist.

Returns:

self, so phases can be chained.

edit_and_assemble() Stent

Apply the interactive manual 2D edits, then assemble the flat skeleton.

Runs after skeletonize_2d(). Always prompts once to manually edit any ring, so defects the automatic detector missed can be fixed by hand on the flat ring skeleton, then concatenates every ring into one 2D skeleton.

Sets skel_arc, skel_z, skel_px, and surf_df.

Returns:

self, so phases can be chained.

finalize(prune_tip_frac: float = 0, max_display: int = 500000, random_seed: int = 0) Stent

Wrap the 2D skeleton to 3D, fit splines, and write the final exports.

Runs after edit_and_assemble(). Lifts the assembled 2D skeleton onto the 3D stent surface (which also cleans up the graph: junction blobs contracted to a centroid, short dead-ends pruned), fits a B-spline per curve, then writes the final feature/view exports plus the unrolled-2D and trimesh-3D spline views.

Sets skeleton_df, skeleton_curves, and skeleton_splines.

Parameters:
  • prune_tip_frac – Fraction of each curve tip to prune after wrapping.

  • max_display – Maximum number of points drawn in the HTML views.

  • random_seed – Seed for any subsampling during the 3D wrap.

Returns:

self, so phases can be chained.

skeletonize(prune_tip_frac: float = 0) Stent

Run the full skeletonisation, from the STL mesh to fitted splines.

Chains all three phases: skeletonize_2d(), edit_and_assemble(), then finalize(). Each writes its own intermediates into output_dir.

(skeletonize_2d() is only the first phase, despite the similar name — the verb alone means the whole run, the _2d suffix marks the sub-step.)

Parameters:

prune_tip_frac – Fraction of each curve tip to prune after wrapping.

Returns:

self, holding the 2D skeleton, the 3D skeleton graph, the grouped curves, and the fitted splines.

plot_splines_2d() None

Draw the fitted splines on the unrolled (arc, z) plane.

Writes skeleton_splines_2d.html / .png into output_dir.

plot_splines_trimesh() None

Draw the fitted splines as 3D tubes with trimesh.

Writes skeleton_splines_trimesh.html and skeleton_splines.glb into output_dir.

__repr__() str
Returns:

A short summary of how far this stent has been processed.

class stentfit.Artery(stent: stentfit.stent.Stent, artery_type: str = 'straight', inner_margin: float = 0.5, wall_thickness: float = 0.5, noise_amplitude: float = 0.05, noise_seed: float = 0, bend_angle_deg: float = 180.0, mesh_type: str = 'HEX8', artery_youngs: float = 2.0, n_circumference: int = 64, n_axial: int = 150)

A test artery: a wall surface, its centreline, and the 3D solid meshed from them.

The geometry is parametric and generated to fit a given stent, rather than imported from imaging — enough to exercise the whole mixed-dimensional chain end to end. The shape is a parameter, not a separate constructor:

artery = Artery(stent, artery_type="curved", inner_margin=0.5)
sim = Simulation(stent, artery, sim_input_dir)
sim.setup()

Everything is resolved and built at construction, so radius, geometry and centreline are real the moment the object exists. Note this builds only the wall surface (a trimesh tube); the 3D finite-element solid 4C actually solves on is a separate step, mesh_solid(), which setup() runs for you.

Every dimension is derived from the stent: the lumen radius is the stent’s outer radius plus inner_margin clearance, the length a multiple of the stent length (so the stent always sits well inside), and any bend radius is picked so the arc roughly spans that length at the given bend angle. There is deliberately no way to set those dimensions by hand — a hand-sized tube is still a synthetic artery, and the real use for a specific geometry is importing patient anatomy, which is a different construction path.

Parameters:
  • stent – The stent this artery is sized to hold. Its skeletonisation must have run, so its features are populated.

  • artery_type – Shape: 'straight', 'curved', or 's_bend'.

  • inner_margin – Clearance, in mm, between the stent’s outer radius and the lumen wall.

  • wall_thickness – Wall thickness, in mm. 0 builds the lumen surface only, with no separate wall.

  • noise_amplitude – Fractional wall-roughness noise, as a fraction of the radius. 0 gives a smooth pipe.

  • noise_seed – Seed for the wall noise, for repeatable runs.

  • bend_angle_deg – Total bend angle, in degrees. Used only by 'curved' and 's_bend'.

  • mesh_type – GMSH element type for the solid: 'TET4', 'TET10', or 'HEX8'. Used by mesh_solid().

  • artery_youngs – Wall Young’s modulus, in MPa (placeholder StVenantKirchhoff material). Used by mesh_solid().

  • n_circumference – Number of vertices around each cross-section.

  • n_axial – Number of cross-sections along the length.

Raises:

ValueError – If artery_type is unknown, or the stent has not been skeletonised yet.

stent
artery_type = 'straight'
inner_margin = 0.5
wall_thickness
noise_amplitude = 0.05
noise_seed = 0
bend_angle_deg = 180.0
mesh_type = 'HEX8'
artery_youngs = 2.0
radius
length
bend_radius = None
solid_yaml: pathlib.Path | None = None
_stent_features() dict

Read the stent’s geometry features, checking it has been skeletonised.

Raises:

ValueError – If the stent’s pipeline has not run yet.

Returns:

The stent’s features dict.

_resolve_bend_radius(cap: float | None) float | None

Work out the arc radius for a bent artery.

Picked so the arc spans about 90% of the artery’s own length at the given bend angle, then limited by cap so a shallow angle cannot produce an arc so wide the artery looks straight. An S-bend splits its length across two arcs.

Parameters:

cap – Widest arc radius allowed, in mm. None for a straight artery, which has no arc.

Returns:

The arc radius in mm, or None for a straight artery.

_build(n_circumference: int, n_axial: int) tuple

Build the wall surface mesh and the centreline it is swept along.

Parameters:
  • n_circumference – Number of vertices around each cross-section.

  • n_axial – Number of cross-sections along the length.

Returns:

(geometry, centreline) — the trimesh wall surface and the (n, 3) centreline points.

_print_summary() None

Print the built artery’s dimensions, matching the old pipeline’s output.

mesh_solid(out_path: str | pathlib.Path, element_size: float, mesh_type: str | None = None, youngs_modulus: float | None = None, poisson_ratio: float = 0.3, density: float = 1.0, material_id: int = 1) pathlib.Path

Mesh the artery wall as a hollow 3D solid with GMSH and write a 4C .yaml.

This is the finite-element mesh 4C solves on, as opposed to the wall surface built at construction. Meshes the annulus between radius and radius + wall_thickness as a straight tube, classifies its boundary nodes into DSURFACE sets (1 = lumen, 2 = inlet, 3 = outlet), then warps the whole tube onto centreline using the same frame convention as the stent warp, so beam and solid stay aligned. Writes the mesh with a placeholder MAT_Struct_StVenantKirchhoff material.

Stores the written path on solid_yaml, which assemble() reads back.

Parameters:
  • out_path – File path the 4C .yaml solid is written to.

  • element_size – Target element size, in mm.

  • mesh_type – Element type: 'TET4', 'TET10', or 'HEX8'. None uses mesh_type from the constructor.

  • youngs_modulus – Material Young’s modulus, in MPa. None uses artery_youngs from the constructor.

  • poisson_ratio – Placeholder material Poisson’s ratio.

  • density – Placeholder material density.

  • material_id – Material ID written into the 4C input.

Raises:

ValueError – If the wall has no thickness, or the element type is not supported.

Returns:

The path written, also stored on solid_yaml.

__repr__() str
Returns:

A short summary of the artery’s shape and size.

class stentfit.Simulation(stent: stentfit.stent.Stent, artery: stentfit.artery.Artery, sim_input_dir: str | pathlib.Path, stent_youngs: float = 200000.0, stent_poisson: float = 0.3, stent_density: float = 0.0, beam_class_label: str = 'Beam3rHerm2Line3', factor_solid: float = 1.5, factor_beam: float = 1.2, n_steps: int = 10, expansion_force: float = 0.0001)

A mixed-dimensional beam-to-solid simulation setup for one stent and artery.

Composes a Stent and an Artery into a runnable 4C input: the stent is meshed as 1D beams and warped onto the artery centreline, the artery wall is meshed as a 3D solid, the two are tied together with BeamMe’s mortar beam-to-solid coupling, and — provided the coupling assumptions hold — a static solver header, boundary conditions and a quasi-static radial expansion load are written out:

artery = Artery(stent, artery_type="curved", inner_margin=0.5)
sim = Simulation(stent, artery, "outputs/simulation/input")
sim.setup()

The artery is built first and passed in, so every artery-shape and wall-material knob lives on Artery and everything here concerns the stent, the coupling, and the load. Build both against the same stent — the constructor rejects a mismatch.

This is a smoke test, not the physics of the reference papers: the artery uses a placeholder StVenantKirchhoff material, coupling is tied meshtying rather than true contact, and the balloon is a simplified radial point force.

Parameters:
  • stent – The stent to deploy. Its skeletonisation must have run, since the beam mesh is built from the splines in its output folder.

  • artery – The artery to deploy into, already built by Artery. Every artery-shape and wall-material parameter lives on that object, not here.

  • sim_input_dir – Folder every generated .4C.yaml and .vtu is written into.

  • stent_youngs – Stent beam Young’s modulus, in MPa.

  • stent_poisson – Stent beam Poisson’s ratio.

  • stent_density – Stent beam material density.

  • beam_class_label – BeamMe beam element type, either 'Beam3rHerm2Line3' or 'Beam3rLine2Line2'.

  • factor_solid – Safety factor sizing the artery solid element size relative to the beam diameter.

  • factor_beam – Additional safety factor sizing the beam element length beyond factor_solid.

  • n_steps – Number of load steps for the balloon expansion ramp.

  • expansion_force – Radial point-force magnitude for the balloon expansion.

Raises:

ValueError – If artery was built for a different stent.

stent
artery
sim_input_dir
factor_solid = 1.5
stent_youngs = 200000.0
stent_poisson = 0.3
stent_density = 0.0
beam_class_label = 'Beam3rHerm2Line3'
factor_beam = 1.2
n_steps = 10
expansion_force = 0.0001
beam_mesh = None
full_mesh = None
coupling_report = None
property beam_diameter: float

The beam cross-section diameter — the stent’s strut thickness, read straight off the composed stent, in mm.

Type:

returns

property solid_element_size: float

Target artery solid element size: the beam diameter with the factor_solid safety factor applied, in mm.

Type:

returns

property beam_element_size: float

Target beam element length: the solid element size with the further factor_beam safety factor applied, in mm.

Type:

returns

print_stent_summary() None

Print the stent’s key dimensions, as a sanity check before meshing.

The values come from the live Stent object, so unlike the procedural pipeline nothing is re-read from stent_features.json / skeleton_points.csv. A stent restored with load() has no 3D skeleton in memory, so the node count is only printed when it is available.

mesh_artery() pathlib.Path

Mesh the artery wall as a 3D solid, sized to this simulation’s stent.

Thin wrapper over mesh_solid() that fills in the element size (which depends on the stent’s strut thickness, so the artery cannot work it out alone) and the output path.

Returns:

Path to the written artery_solid.4C.yaml.

align() Simulation

Mesh the straight stent as beams and warp it onto the artery centreline.

Builds the beam mesh from the stent’s fitted splines, represents the artery centreline as a BeamMe CosseratCurve, then warps the straight stent onto it — rotating the stent’s own straight axis onto the curve’s tangent, and centring the stent’s z_min/z_max mid-point on the curve’s arc mid-point. Writes stent_warped.4C.yaml.

Sets beam_mesh.

Returns:

self, so steps can be chained.

assemble(lumen_surface_index: int = 0, bc_type=None, output_filename: str = 'artery_stent.4C.yaml') Simulation

Import the artery solid and tie the stent beam mesh to it, as one 4C input.

Imports the artery’s solid .yaml (written by mesh_solid()), then couples it to beam_mesh with BeamMe’s mortar beam-to-solid method, writing the combined 4C input file. Coupling defaults to tied meshtying; pass bme.bc.beam_to_solid_surface_contact for a real deployment simulation instead of this smoke test.

Sets full_mesh.

Parameters:
  • lumen_surface_index – Index into the artery solid’s surface sets for the lumen surface the beams couple to. 0 is the lumen (DSURFACE 1, written first by the mesher).

  • bc_type – BeamMe beam-to-solid coupling type. None defaults to tied meshtying.

  • output_filename – Filename for the assembled 4C input, written into sim_input_dir.

Returns:

self, so steps can be chained.

export_paraview(output_name: str = 'artery_stent_mesh') tuple | None

Export the assembled beam+solid mesh as separate .vtu files for ParaView.

BeamMe’s write_vtk splits beams and solid elements into two files by itself; this just names them and reports their paths.

Parameters:

output_name – Base filename; _beam.vtu / _solid.vtu are appended.

Returns:

None if nothing has been assembled yet. Otherwise (beam_vtu, solid_vtu) — the paths to the two written files.

check_coupling(stiffness_ratio_min: float = 10.0, length_ratio_min: float = 1, length_ratio_max: float = 6, length_ratio_accuracy_max: float = 8.0) dict

Check the mixed-dimensional beam-to-solid coupling assumptions.

Three checks, following Steinbrecher et al., each independent:

  1. Stiffness — the beam must be much stiffer than the solid (E_beam / E_solid >= stiffness_ratio_min), since the coupling assumes the solid deforms around an effectively rigid-ish beam.

  2. Solid size vs. beam diameter — the solid element size must be at least the beam’s cross-section diameter, the spatial-resolution limit the mortar coupling is only valid above.

  3. Element length ratio — beam elements should be longer than solid elements, but not by too much: a valid band up to length_ratio_accuracy_max, and a narrower optimal band up to length_ratio_max.

The beam element length is measured from the meshed beams themselves (mean end-to-end chord), not from the requested target.

Sets coupling_report.

Parameters:
  • stiffness_ratio_min – Minimum acceptable E_beam / E_solid.

  • length_ratio_min – Lower bound of both the valid and optimal L_beam / L_solid bands.

  • length_ratio_max – Upper bound of the optimal band.

  • length_ratio_accuracy_max – Upper bound of the valid band, above which coupling accuracy degrades.

Raises:

ValueError – If the beam mesh has not been built yet.

Returns:

The report dict, one entry per check plus all_passed.

plot_overview(show: bool = True) pathlib.Path

Draw the artery surface, its centreline, and the warped stent together.

Writes stent_artery_view.html into sim_input_dir, and shows the figure inline when running in a notebook.

Parameters:

show – Try to display the figure inline as well as saving it.

Returns:

Path to the written HTML view.

write_input(out_path: str | pathlib.Path | None = None, total_time: float = 1.0, inlet_surface_index: int = 1, outlet_surface_index: int = 2, fix_stent_node: bool = True) pathlib.Path

Build a runnable, schema-validated 4C static simulation input.

Adds a static solver header and runtime VTK output, fixes the artery’s inlet and outlet surfaces (3 translational DOF, Dirichlet), and applies a quasi-static radial “balloon” expansion: a point force at each beam centreline node, directed radially outward from the artery centreline and ramped from 0 to expansion_force over n_steps by a time function. If fix_stent_node, one stent node is also pinned in translation to remove the stent’s rigid-body motion, since the radial forces alone do not constrain it.

Parameters:
  • out_path – File path the simulation input is written to. None writes simulation.4C.yaml into sim_input_dir.

  • total_time – Total simulation time for the static solver.

  • inlet_surface_index – Index into the solid’s surface geometry sets for the inlet (fixed) surface.

  • outlet_surface_index – Index into the solid’s surface geometry sets for the outlet (fixed) surface.

  • fix_stent_node – Pin one stent centreline node’s translation, to remove rigid-body motion.

Raises:

ValueError – If nothing has been assembled yet, or the imported solid is missing the inlet/outlet surface sets.

Returns:

The path written.

setup(show_plot: bool = True) Simulation

Prepare a runnable 4C input, from the stent and artery through to the load.

Chains the whole synthetic pipeline: prints the stent summary, meshes the stent as beams and warps it onto the artery centreline (align()), meshes the artery wall as a 3D solid (mesh_artery()), assembles the beam-to-solid mesh (assemble()) and exports it for ParaView (export_paraview()). It then checks the coupling assumptions (check_coupling()), shows the overview plot, and — only if those checks pass — writes the runnable input (write_input()).

Named setup rather than run on purpose: it prepares a runnable 4C input, it does not execute the analysis. Running the solve is 4C’s job, external to this package.

Parameters:

show_plot – Display the artery/stent overview figure inline.

Returns:

self, holding the meshes and the coupling report.

__repr__() str
Returns:

A short summary of how far this simulation has been set up.