stentfit.core.splines

Functions

group_skeleton_curves(→ list[list[int]])

Split the skeleton graph into curves: chains between junctions/endpoints,

prune_spur_curves(→ list[list[int]])

Drop curves that end in a free (unshared) endpoint, iteratively.

fit_curve_spline(→ dict | None)

Fit one B-spline to a single curve's skeleton points.

_spline_record(→ dict | None)

Convert one fit_curve_spline() result into a JSON-serializable record.

fit_skeleton_splines(→ dict)

Group the skeleton graph into curves and fit a B-spline to each.

_feat(stent_features, key)

Read one stent feature, whichever of its two saved shapes it's stored in.

_bspline_from_record(→ splinepy.BSpline)

Rebuild a splinepy.BSpline from one _spline_record() entry.

mesh_skeleton_beams(→ beamme.core.mesh.Mesh)

Build a BeamMe 1D beam mesh from a stent's saved splines.

Module Contents

stentfit.core.splines.group_skeleton_curves(skeleton_points_df: pandas.DataFrame) list[list[int]]

Split the skeleton graph into curves: chains between junctions/endpoints, plus closed loops.

A “special” node is anything with degree != 2 (an endpoint, junction, or isolated point). From every special node, each unvisited edge is walked until it reaches another special node, tracing out one curve. Any edges left unvisited afterward belong to a closed loop with no special node at all (every node on it has degree 2), so those are walked separately, starting anywhere on the loop and ending back where they started.

Parameters:

skeleton_points_df – 3D skeleton graph with skeleton_point_id and neighbor_ids columns.

Returns:

List of curves, each a list of point IDs in path order. A closed loop’s first and last ID are the same point.

stentfit.core.splines.prune_spur_curves(curves: list[list[int]]) list[list[int]][source]

Drop curves that end in a free (unshared) endpoint, iteratively.

A closed loop (same start and end point) is always kept. Otherwise, a curve is dropped if either end is “free” — touched by only that one curve, meaning it’s a dead-end rather than a real junction shared with another curve. This repeats until a full pass drops nothing, since removing one spur can free up the endpoint of its neighbour, exposing a new spur to drop next round.

Parameters:

curves – Curves from group_skeleton_curves(), each a list of point IDs in path order.

Returns:

The curves that survived pruning.

stentfit.core.splines.fit_curve_spline(point_ids: list[int], coords: pandas.DataFrame, every: int, k: int, s: float) dict | None[source]

Fit one B-spline to a single curve’s skeleton points.

Consecutive duplicate points are dropped first. Every every-th point is kept as a spline control point (always including the last), with every reduced automatically if the curve is too short to leave enough control points for degree k. A closed loop is fit as a periodic spline, dropping its duplicated start/end control point first; if that leaves too few points to close the loop, it falls back to an open curve. If scipy.interpolate.splprep fails (or there aren’t enough control points for any spline), the control points themselves are returned as a polyline fallback instead.

Parameters:
  • point_ids – One curve’s point IDs in path order, from group_skeleton_curves().

  • coords – Skeleton points’ x, y, z coordinates, indexed by point ID.

  • every – Take every Nth point as a control point.

  • k – Target B-spline degree.

  • s – Smoothing factor passed to splprep. 0 interpolates the control points exactly.

Returns:

None if fewer than 2 distinct points remain. Otherwise a dict with the fitted tck/u (None if fitting failed or wasn’t attempted), the ctrl points used, n_ctrl, the actual degree k used, whether it closed as a loop (is_loop), and the curve’s physical length.

stentfit.core.splines._spline_record(spl: dict | None) dict | None[source]

Convert one fit_curve_spline() result into a JSON-serializable record.

Unpacks scipy’s tck tuple into plain degree/knot_vector/ control_points fields. Where tck is None (the polyline fallback), records degree=1 and knot_vector=None with the raw control points instead.

Parameters:

spl – One curve’s fit result from fit_curve_spline(), or None.

Returns:

None if spl is None. Otherwise a dict with degree, knot_vector (None for the polyline fallback), control_points (as a plain nested list), is_loop, and length.

stentfit.core.splines.fit_skeleton_splines(skeleton_df: pandas.DataFrame, output_dir: str, spline_every: int = 10, spline_degree: int = 3, smooth: float = 0.0, prune_spurs: bool = True) dict[source]

Group the skeleton graph into curves and fit a B-spline to each.

Groups the 3D skeleton into curves with group_skeleton_curves() (degree-2 chains between junctions/endpoints, plus closed loops), drops any spur curve with a free end if prune_spurs (via prune_spur_curves()), then fits each remaining curve with fit_curve_spline(). Saves splines.html and skeleton_splines.json (per-curve degree, knot vector, control points — with a plain polyline as the fallback where a curve is too short to fit a spline to).

Parameters:
  • skeleton_df – 3D skeleton graph with skeleton_point_id, x, y, z, and neighbor_ids columns, from wrap_skeleton_to_3d().

  • output_dir – Folder the HTML view and JSON export are written into.

  • spline_every – Take every Nth skeleton point as a spline control point, to keep the control polygon from being one point per sample.

  • spline_degree – Target B-spline degree, reduced automatically for short curves.

  • smooth – Smoothing factor passed to scipy.interpolate.splprep. 0 interpolates the control points exactly.

  • prune_spurs – Drop curves with a free (non-junction, non-loop) end, instead of fitting a spline to them.

Returns:

Dict with the grouped point-id curves (curves) and their fitted splines (splines, one entry per curve, None where fitting failed).

stentfit.core.splines._feat(stent_features: dict, key: str)[source]

Read one stent feature, whichever of its two saved shapes it’s stored in.

A feature is either a plain scalar or a {'value': ..., 'unit': ...} dict (the shape used for material parameters elsewhere in the pipeline); this reads either the same way.

Parameters:
  • stent_features – Stent features dict (from stent_features.json or in-memory).

  • key – Feature name to read.

Returns:

The feature’s plain value.

stentfit.core.splines._bspline_from_record(rec: dict) splinepy.BSpline[source]

Rebuild a splinepy.BSpline from one _spline_record() entry.

Where knot_vector is None (the polyline fallback), builds a degree-1 B-spline with a uniform clamped knot vector instead, so the polyline can still be meshed the same way as a real spline.

Parameters:

rec – One curve’s record, from skeleton_splines.json (or _spline_record() directly) — with degree, knot_vector, and control_points.

Returns:

The reconstructed B-spline.

stentfit.core.splines.mesh_skeleton_beams(input_dir: str, l_el: float = 0.1, youngs_modulus: float = 200000.0, poisson_ratio: float = 0.3, density: float = 0.0, beam_class_label: str = 'Beam3rHerm2Line3') beamme.core.mesh.Mesh[source]

Build a BeamMe 1D beam mesh from a stent’s saved splines.

Reads back stent_features.json (for the strut thickness, used as the circular cross-section radius) and skeleton_splines.json (for the per-curve splines) from input_dir, then meshes each curve with BeamMe’s create_beam_mesh_from_splinepy, using _bspline_from_record() to rebuild each spline. A curve is skipped if it has fewer than 2 control points or meshing raises. Each curve’s new elements are tagged with a curve_color VTK cell scalar (folded into 16 bins) for ParaView inspection — this is visualisation-only and has no effect on the 4C simulation input.

Parameters:
  • input_dir – Stent output folder to read stent_features.json and skeleton_splines.json from — normally the same folder skeletonize() wrote them to.

  • l_el – Target element length, in mm.

  • youngs_modulus – Beam material Young’s modulus, in MPa.

  • poisson_ratio – Beam material Poisson’s ratio.

  • density – Beam material density.

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

Returns:

The assembled BeamMe Mesh.