stentfit.core.skeleton_3d

Functions

adjust_skeleton_to_local_midsurface(→ dict)

Lift each flat 2D skeleton point to 3D at its own local mid-wall radius.

analyze_skeleton_connectivity(→ pandas.DataFrame)

Build the 3D skeleton graph and classify every point by its degree.

prune_skeleton_spurs(→ pandas.DataFrame)

Remove short dead-end branches (spurs) from the 3D skeleton graph.

collapse_junction_clusters(→ pandas.DataFrame)

Contract each blob of edge-connected junction nodes to a single centroid.

wrap_skeleton_to_3d(→ pandas.DataFrame)

Lift the flat 2D skeleton onto the 3D stent surface and clean up its graph.

save_stent_features_and_views(→ dict)

Write the final stent_features.json and the skeleton-with-cloud view.

Module Contents

stentfit.core.skeleton_3d.adjust_skeleton_to_local_midsurface(skel_arc: numpy.ndarray, skel_z: numpy.ndarray, stent_df: pandas.DataFrame, r_mid: float, circumference: float, search_radius: float) dict[source]

Lift each flat 2D skeleton point to 3D at its own local mid-wall radius.

A skeleton point is placed at the angle and z given by its (arc, z) coordinates, but its radius is the mean radius of the nearby surface points within search_radius (falling back to r_mid if none are found) — not a fixed radius. This lets the 3D skeleton follow the stent’s real surface undulation instead of sitting on a perfect cylinder. Surface points are triple-tiled along arc (one copy shifted left, one right) first, so the neighbour search sees across the seam without a periodic-distance correction.

Parameters:
  • skel_arc – Flat arc-coordinates of the assembled 2D skeleton.

  • skel_z – Flat z-coordinates of the assembled 2D skeleton.

  • stent_df – Stent surface point cloud with theta, z_cylindrical, and r columns.

  • r_mid – Mid-wall radius, used to convert arc back to angle, and as the fallback radius where no surface points are nearby.

  • circumference – Full circumference at r_mid, used to tile the surface points across the seam.

  • search_radius – Radius, in the (arc, z) plane, used to gather nearby surface points for the local radius average.

Returns:

Dict with the lifted skeleton as a DataFrame (df_skeleton_3d, with theta, x, y, z, r columns) and the same points as a plain (N, 3) array (skeleton_points).

stentfit.core.skeleton_3d.analyze_skeleton_connectivity(df_skeleton_3d: pandas.DataFrame, pixel_size: float | numpy.ndarray, neighbor_radius_factor: float = 1.8) pandas.DataFrame[source]

Build the 3D skeleton graph and classify every point by its degree.

Two points are neighbours if they’re within neighbor_radius_factor times the local pixel size — this is also what rejoins the two coincident seam ends left over from the periodic 2D raster, since they sit at the same 3D position once wrapped. pixel_size may be a single scalar (one skeletonisation resolution for the whole stent) or a per-point array (one entry per ring, from assemble_2d_skeleton()), in which case a pair only counts if it’s within the coarser of the two points’ radii. Each point’s degree then sets its node_type: 0 is isolated, 1 is endpoint, 2 is line, and 3+ is junction.

Parameters:
  • df_skeleton_3d – 3D skeleton points with x, y, z columns, from adjust_skeleton_to_local_midsurface().

  • pixel_size – Pixel size the skeleton was rasterised at — a scalar, or a per-point array matching df_skeleton_3d.

  • neighbor_radius_factor – How many pixel-sizes apart two points may be and still count as neighbours.

Returns:

df_skeleton_3d, with added skeleton_point_id, degree, node_type, and neighbor_ids (list of connected point indices) columns.

stentfit.core.skeleton_3d.prune_skeleton_spurs(df_connectivity: pandas.DataFrame, tip_frac: float = 0, max_spur_len: float | None = None, max_iter: int = 10, verbose: bool = True) pandas.DataFrame[source]

Remove short dead-end branches (spurs) from the 3D skeleton graph.

Each round, every degree-1 endpoint not protected as a tip is walked along its dead-end chain until it hits a junction, another endpoint, or a loop. The whole chain is removed, unless it terminates on a real tip (kept so the stent’s actual ends survive) or is longer than max_spur_len (kept as a real branch, not a stray spur). This repeats for up to max_iter rounds, since removing one spur can expose a new endpoint one step further in. Degrees and node types are recomputed afterward.

Parameters:
  • df_connectivity – 3D skeleton graph with x, y, z, and neighbor_ids columns, from analyze_skeleton_connectivity() or collapse_junction_clusters().

  • tip_frac – Fraction of the stent’s z-range, from each axial end, protected as a real tip — endpoints in that band are never pruned. 0 protects nothing, so every dead-end is prunable.

  • max_spur_len – Chains longer than this are kept as real branches instead of being pruned. None prunes regardless of length.

  • max_iter – Maximum number of pruning rounds.

  • verbose – Print how many nodes/branches were removed, and how many non-tip endpoints remain.

Returns:

The skeleton graph with spurs removed, and skeleton_point_id, degree, node_type, neighbor_ids recomputed to match.

stentfit.core.skeleton_3d.collapse_junction_clusters(df_connectivity: pandas.DataFrame, verbose: bool = True) pandas.DataFrame[source]

Contract each blob of edge-connected junction nodes to a single centroid.

A real strut crossing should be one junction point, but the raster -> thin -> lift pipeline can leave a small cluster of several degree-3+ nodes sitting right next to each other instead. Every such cluster (found by walking edges between junction nodes only) is collapsed: one member survives at the cluster’s centroid, every external neighbour of any member is rewired to point at the survivor, and the rest of the cluster is dropped. Degrees and node types are recomputed afterward, since collapsing can turn a junction into a plain line point.

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

  • verbose – Print how many clusters were collapsed and the resulting node counts.

Returns:

The skeleton graph with junction clusters collapsed, and skeleton_point_id, degree, node_type, neighbor_ids recomputed to match.

stentfit.core.skeleton_3d.wrap_skeleton_to_3d(skel_arc: numpy.ndarray, skel_z: numpy.ndarray, skel_px: numpy.ndarray, surf_df: pandas.DataFrame, r_mid: float, circumference: float, strut_thickness: float, output_dir: str, stent_name: str, wrap_max_surf: int | None = None, prune_tip_frac: float = 0, max_display: int = 500000, random_seed: int = 0) pandas.DataFrame[source]

Lift the flat 2D skeleton onto the 3D stent surface and clean up its graph.

Each point is lifted to 3D using a per-point local mid-surface radius (adjust_skeleton_to_local_midsurface()), then the graph is classified by node degree and its 3D KD-tree rejoins the coincident seam ends (analyze_skeleton_connectivity()). Junction blobs are contracted to a centroid (collapse_junction_clusters()) and short dead-ends are pruned (prune_skeleton_spurs()). Saves skeleton_points.csv and skeleton_only.html.

Parameters:
  • skel_arc – Flat arc-coordinates of the assembled 2D skeleton.

  • skel_z – Flat z-coordinates of the assembled 2D skeleton.

  • skel_px – Per-point pixel size, from assemble_2d_skeleton().

  • surf_df – Stent surface point cloud, used to find each skeleton point’s local mid-surface radius.

  • r_mid – Mid-wall radius.

  • circumference – Full circumference at r_mid.

  • strut_thickness – Strut thickness, used as the local-midsurface search radius.

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

  • stent_name – Name used to label outputs and plots.

  • wrap_max_surf – Maximum surface points used for the wrap. None uses every surface point.

  • prune_tip_frac – Fraction of each curve tip to prune as a spur.

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

  • random_seed – Seed for the surface downsampling, when wrap_max_surf applies.

Returns:

The final 3D skeleton graph, with skeleton_point_id, x, y, z, r, theta, node_type, degree, and neighbor_ids columns.

stentfit.core.skeleton_3d.save_stent_features_and_views(skeleton_df: pandas.DataFrame, stent_df: pandas.DataFrame, stent_features: dict, stent_centerline_direction: numpy.ndarray, ring_edges: numpy.ndarray | None, output_dir: str, max_display: int = 500000) dict[source]

Write the final stent_features.json and the skeleton-with-cloud view.

Draws the 3D skeleton overlaid on a sparse cloud of the original surface points (skeleton_with_cloud.html), then folds stent_centerline_direction and the ring z-boundaries into stent_features.json. Ring boundaries come from ring_edges if given, else are derived from stent_df’s ring_id groups (the midpoint between each pair of neighbouring rings). Also prints a summary of every file this pipeline run has produced — the skeleton CSV and spline exports were already written by earlier steps (wrap_skeleton_to_3d(), fit_skeleton_splines()); only the JSON and cloud view are written here.

Parameters:
  • skeleton_df – Final 3D skeleton graph.

  • stent_df – Stent surface point cloud, used as the cloud underlay and, if needed, to derive ring boundaries from its ring_id column.

  • stent_features – Stent features dict to write out (length, diameter, strut_thickness, …).

  • stent_centerline_direction – Stent centreline unit vector, folded into the saved features.

  • ring_edges – Z-boundaries between rings. None (or fewer than 2 values) falls back to deriving them from stent_df.

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

  • max_display – Maximum skeleton points drawn in the HTML view; the surface cloud underlay is drawn at a fifth of this.

Returns:

Dict with the path to the written JSON (features_path) and the ring z-boundaries actually used (ring_boundaries, None if neither source was available).