Core Functions

class multimodars.PyCenterline(points)

Bases: object

Python representation of a vessel centerline.

points

Ordered list of centerline points.

Type:

list of PyCenterlinePoint

branch_start_indices

Index into points where each branch begins. Entry 0 is always 0 (the main vessel); subsequent entries mark the start of side branches. Read-only — recomputed by calculate_branches.

Type:

list of int

Examples

>>> centerline = PyCenterline(points=[p1, p2, p3])
branch_start_indices
calculate_branches(spacing_tolerance=1.0)

Detect branches by spatial proximity and return a new centerline with branch_id assigned on every point.

Points whose mutual distance is ≤ spacing_tolerance × median_nn_spacing are considered spatially consecutive regardless of their original array order. The largest connected group becomes branch 0 (main vessel); further groups are numbered by descending size.

Parameters:

spacing_tolerance (float) – Multiplier on the median nearest-neighbour spacing used as the adjacency threshold. 1.5 is a reasonable starting value; increase it if branches are incorrectly split, decrease it if distinct branches are incorrectly merged.

Returns:

New centerline with branch_id set on every point and branch_start_indices populated.

Return type:

PyCenterline

Examples

>>> cl = centerline.calculate_branches(1.5)
>>> main = [p for p in cl.points if p.branch_id == 0]
check_centerline()

Normalise branch ordering so that downstream processing is consistent.

  • Branch 0 – the point with the highest z-coordinate is moved to index 0 (the whole branch is reversed if necessary).

  • Side branches – the endpoint closest to branch 0 becomes index 0 (the branch is reversed if necessary).

Returns:

New centerline with all branches in canonical order.

Return type:

PyCenterline

find_sharp_angles(branch_id, cos_threshold)

Return local positions (0-indexed within the branch) of interior points where the opening angle is sharper than cos_threshold.

Parameters:
  • branch_id (int) – Branch to inspect (0 = main vessel).

  • cos_threshold (float) – Cosine of the opening angle above which a point is considered sharp. Use 0.0 for < 90°, 0.5 for < 60°, 0.866 for < 30°, etc.

Returns:

Local positions within the branch where sharp angles were found.

Return type:

list[int]

static from_contour_points(contour_points)

Build a centerline from a flat list of PyContourPoint objects.

Parameters:

contour_points (list of PyContourPoint) – Ordered sequence of contour points.

Returns:

Centerline constructed from the provided points.

Return type:

PyCenterline

Examples

>>> pts = [PyContourPoint(...), PyContourPoint(...), ...]
>>> cl = PyCenterline.from_contour_points(pts)
get_branch(branch_id)

Return a new centerline containing only the points of one branch.

All retained points are reassigned to branch_id = 0 and branch_start_indices is reset to [0].

Parameters:

branch_id (int) – Branch to extract.

Returns:

Single-branch centerline with the requested points.

Return type:

PyCenterline

Raises:

ValueError – If branch_id does not exist in this centerline.

merge_branches(branch_id_a, branch_id_b)

Merge two branches and return the updated centerline.

Segments are joined at the closest endpoint pair. If either branch is the main branch (id 0) the merged result becomes branch 0.

Parameters:
  • branch_id_a (int)

  • branch_id_b (int)

Returns:

New centerline with the two branches merged and all IDs reassigned.

Return type:

PyCenterline

points
points_as_tuples()
split_branch(branch_id, local_pos)

Split a branch at a local position and return the updated centerline.

Both resulting segments include the split point. When splitting the main branch (branch_id=0) the longer segment stays as branch 0; for side branches the first segment keeps its slot and the second is appended.

Parameters:
  • branch_id (int) – Branch to split.

  • local_pos (int) – 0-indexed position within the branch (as returned by find_sharp_angles).

Returns:

New centerline with the branch split and all IDs reassigned.

Return type:

PyCenterline

class multimodars.PyCenterlinePoint(contour_point, normal, branch_id=0)

Bases: object

Python representation of a centerline point.

Combines a contour point with its local normal vector.

contour_point

Position of the centerline point in 3D space.

Type:

PyContourPoint

normal

Normal vector (nx, ny, nz) at this centerline position.

Type:

tuple of float

branch_id

Branch identifier. 0 = main vessel; 1+ = side branches ordered by descending length.

Type:

int

Examples

>>> cl_point = PyCenterlinePoint(
...     contour_point=point,
...     normal=(0.0, 1.0, 0.0)
... )
branch_id
contour_point
normal
class multimodars.PyContour(id, original_frame, points, centroid, aortic_thickness, pulmonary_thickness, kind)

Bases: object

Python representation of a 3D contour.

id

Contour identifier (sequence number).

Type:

int

original_frame

Frame index from which this contour originates.

Type:

int

points

Ordered list of contour points.

Type:

list of PyContourPoint

centroid

(x, y, z) centroid coordinates of the contour.

Type:

tuple of float

aortic_thickness

Aortic wall thickness at this contour, if available.

Type:

float or None

pulmonary_thickness

Pulmonary wall thickness at this contour, if available.

Type:

float or None

kind

String representation of the contour type (e.g. "Lumen").

Type:

str

Examples

>>> contour = PyContour(
...     id=0,
...     points=[point1, point2, ...],
...     centroid=(1.0, 1.0, 1.0)
... )
aortic_thickness
centroid
compute_centroid()

Calculate the contour centroid by averaging all point coordinates.

Examples

>>> contour.compute_centroid()
find_closest_opposite()

Find the closest points on opposite sides of the contour.

Returns:

  • points (tuple of PyContourPoint) – Pair (p1, p2) of opposing contour points with minimum distance.

  • distance (float) – Euclidean distance between p1 and p2.

Examples

>>> (p1, p2), distance = contour.find_closest_opposite()
find_farthest_points()

Find the two farthest points in the contour.

Returns:

  • points (tuple of PyContourPoint) – Pair (p1, p2) of the two most distant points.

  • distance (float) – Euclidean distance between p1 and p2.

Examples

>>> (p1, p2), distance = contour.find_farthest_points()
get_area()

Get the area of the current contour using the shoelace formula.

Returns:

Area of the contour in the units of the original data (e.g. mm²).

Return type:

float

Examples

>>> area = contour.get_area()
get_elliptic_ratio()

Get the elliptic ratio of the current contour.

Returns:

Ratio of the farthest-points distance divided by the closest-opposite-points distance.

Return type:

float

Examples

>>> elliptic_ratio = contour.get_elliptic_ratio()
id
kind
original_frame
points
points_as_tuples()

Return contour points as a list of (x, y, z) tuples.

Returns:

Each element is (x, y, z) coordinates of one contour point.

Return type:

list of tuple of float

Examples

>>> contour.points_as_tuples()
[(1.0, 2.0, 3.0), (4.0, 5.0, 6.0)]
pulmonary_thickness
rotate(angle_deg)

Rotate the contour around its own centroid by an angle in degrees.

Returns:

New contour rotated around its centroid.

Return type:

PyContour

Examples

>>> contour = contour.rotate(20)
sort_contour_points()

Sort points within the contour in counterclockwise order.

The point with the highest y-coordinate receives index 0; all remaining points are ordered counterclockwise.

Returns:

New contour with rearranged point indices.

Return type:

PyContour

Examples

>>> contour = contour.sort_contour_points()
translate(dx, dy, dz)

Translate the contour by (dx, dy, dz) coordinates.

Parameters:
  • dx (float) – Translation in the x-direction.

  • dy (float) – Translation in the y-direction.

  • dz (float) – Translation in the z-direction.

Returns:

New contour translated by (dx, dy, dz).

Return type:

PyContour

Examples

>>> contour = contour.translate((0.0, 1.0, 2.0))
class multimodars.PyContourPoint(frame_index, point_index, x, y, z, aortic)

Bases: object

Python representation of a 3D contour point.

frame_index

Frame number in the acquisition sequence.

Type:

int

point_index

Index of this point within its contour.

Type:

int

x

X-coordinate in mm.

Type:

float

y

Y-coordinate in mm.

Type:

float

z

Z-coordinate (depth) in mm.

Type:

float

aortic

True when the point is at an aortic position (relevant for intramural vessel courses).

Type:

bool

Examples

>>> point = PyContourPoint(
...     frame_index=0,
...     point_index=1,
...     x=1.23,
...     y=4.56,
...     z=7.89,
...     aortic=True
... )
aortic
distance(other)

Euclidean distance to another PyContourPoint.

Parameters:

other (PyContourPoint) – The target point to measure the distance to.

Returns:

Euclidean distance between the two points in mm.

Return type:

float

Examples

>>> p1.distance(p2)
frame_index
point_index
x
y
z
class multimodars.PyContourType

Bases: object

Python representation of the available intravascular contour types.

Examples

>>> from multimodars import PyContourType
>>> contour_type = PyContourType.Lumen
>>> contour_type.name
'Lumen'
Calcification = PyContourType.Calcification
Catheter = PyContourType.Catheter
Eem = PyContourType.Eem
Lumen = PyContourType.Lumen
Sidebranch = PyContourType.Sidebranch
Wall = PyContourType.Wall
static all_types()

Get all available contour types

static from_string(name)

Create from string name

name

Get the string name of the contour type

class multimodars.PyDiscretizedVesselTree

Bases: object

Fully discretized coronary vessel tree (aorta + RCA + LCA + side branches).

discretized_aorta

Cross-sectional contours along the aortic centerline.

Type:

list of PyContour

discretized_rca_main

Cross-sectional contours along the RCA main vessel.

Type:

list of PyContour

discretized_lca_main

Cross-sectional contours along the LCA main vessel.

Type:

list of PyContour

rca_branches

Per-side-branch contour lists for the RCA. rca_branches[i] corresponds to RCA branch_id i + 1.

Type:

list of list of PyContour

lca_branches

Per-side-branch contour lists for the LCA.

Type:

list of list of PyContour

rca_references

Orientation triplets (main_ref, clock_ref, counter_clock_ref) along the RCA, sorted proximal → distal. Each element is a 3-tuple of (x, y, z) coordinate tuples.

Type:

list of tuple

lca_references

Same structure for the LCA.

Type:

list of tuple

ao_rca

Centroid (x, y, z) of the aorta slice closest to the RCA ostium.

Type:

tuple of float

ao_lca

Centroid (x, y, z) of the aorta slice closest to the LCA ostium.

Type:

tuple of float

ao_lca
ao_rca
calculate_ref_pts()

Recompute orientation reference triplets and aortic ostium centroids from the current contour data.

Call this after replacing contours (e.g. with B-spline fits) so that rca_references, lca_references, ao_rca, and ao_lca reflect the updated geometry.

discretized_aorta
discretized_lca_main
discretized_rca_main
lca_branches
lca_references
rca_branches
rca_references
class multimodars.PyFrame(id, centroid, lumen, extras, reference_point)

Bases: object

Python representation of a single intravascular imaging frame.

id

Frame identifier.

Type:

int

centroid

(x, y, z) centroid of the frame.

Type:

tuple of float

lumen

Lumen contour for this frame.

Type:

PyContour

extras

Additional contour types keyed by name: "Eem", "Calcification", "Sidebranch", "Catheter", "Wall".

Type:

dict of str to PyContour

reference_point

Reference position used for alignment, if available.

Type:

PyContourPoint or None

Examples

>>> geom = PyFrame(
...     id=0,
...     centroid=(0.0, 0.0, 0.0),
...     lumen=lumen_contour,
...     extras={"Eem": eem_contour},
...     reference_point=ref_point
... )
centroid
extras
id
lumen
reference_point
rotate(angle_deg)

Rotate all contour points in the frame by the given angle.

Parameters:

angle_deg (float) – Rotation angle in degrees.

Returns:

frame – New frame with all contours rotated.

Return type:

PyFrame

sort_frame_points()

Sort contour points in the frame by their angular position.

Returns:

frame – New frame with contour points sorted angularly.

Return type:

PyFrame

translate(dx, dy, dz)

Translate all contour points in the frame by the given offsets.

Parameters:
  • dx (float) – Translation along the x-axis in mm.

  • dy (float) – Translation along the y-axis in mm.

  • dz (float) – Translation along the z-axis in mm.

Returns:

frame – New frame with all contours translated.

Return type:

PyFrame

class multimodars.PyGeometry(frames, label)

Bases: object

Python representation of a full intravascular imaging geometry (sequence of frames).

frames

Ordered list of imaging frames constituting the geometry.

Type:

list of PyFrame

label

Human-readable label for this geometry.

Type:

str

Examples

>>> geom = PyGeometry(
...     frames=[frame1, frame2, ...],
...     label="Pat00_diastole"
... )
center_to_contour(contour_type)

Center the entire geometry on a specific contour type.

Parameters:

contour_type (PyContourType) – Contour type to center on (e.g. PyContourType.Lumen).

Returns:

New geometry centered on the specified contour type.

Return type:

PyGeometry

downsample(n_points)

Return a new geometry with n_points per ContourType.

Parameters:

n_points (int) – Number of points remaining per Contour.

Returns:

New downsampled geometry.

Return type:

PyGeometry

Examples

>>> new_geom = geometry.downsample(100)
frames
get_contours(contour_type)

Get contours by type string

get_contours_by_type(contour_type)

Get all contours of a specific type

get_frame_at_index(index)

Return the frame at position index.

Parameters:

index (int) – Zero-based index of the frame to retrieve.

Returns:

Frame at the given index.

Return type:

PyFrame

Raises:

IndexError – If index is out of range.

Examples

>>> frame = geometry.get_frame_at_index(0)
get_frame_at_z(z)

Return the frame whose centroid z-coordinate is closest to z.

Parameters:

z (float) – Target z position in the same units as the geometry.

Returns:

Frame with centroid z nearest to z.

Return type:

PyFrame

Raises:

ValueError – If the geometry contains no frames.

Examples

>>> frame = geometry.get_frame_at_z(34.8)
get_lumen_contours()

Get lumen contours (convenience method)

get_summary()

Get a compact summary of lumen properties for this geometry.

When all contours have an elliptic ratio below 1.3 the vessel is treated as elliptic and a lenient threshold of 70 % of the maximum area is used to identify stenotic segments; otherwise a stricter 50 % threshold is applied.

Returns:

  • mla (float) – Minimal lumen area in the units of the input data (e.g. mm²).

  • max_stenosis (float) – 1 - (mla / max_area).

  • stenosis_length_mm (float) – Length in mm of the longest contiguous region where the contour area falls below the threshold.

label
replace_frame(index, frame)

Return a new geometry with the frame at index replaced by frame.

Parameters:
  • index (int) – Zero-based index of the frame to replace.

  • frame (PyFrame) – Replacement frame.

Returns:

New geometry with the specified frame replaced.

Return type:

PyGeometry

Raises:

IndexError – If index is out of range.

Examples

>>> new_geom = geometry.replace_frame(5, other_frame)
rotate(angle_deg)

Rotate the entire geometry around its centroid by an angle in degrees.

All frames (lumen, extras) are rotated around the same centroid.

Returns:

New geometry rotated around its centroid.

Return type:

PyGeometry

Examples

>>> geometry = geometry.rotate(20)
smooth_frames()

Apply smoothing to all frames using a three-point moving average.

Returns:

New geometry with smoothed frames.

Return type:

PyGeometry

Examples

>>> geom.smooth_frames()
sort_frame_points()

Re-index all frame contour points so the point with the highest Z-value in frame 0’s lumen gets point_index = 0. The same index offset is applied to every contour in every frame. Physical point positions are unchanged — only the point_index fields are reassigned.

Returns:

New geometry with re-indexed frames.

Return type:

PyGeometry

translate(dx, dy, dz)

Translate all frames in the geometry by (dx, dy, dz).

Parameters:
  • dx (float) – Translation in the x-direction.

  • dy (float) – Translation in the y-direction.

  • dz (float) – Translation in the z-direction.

Returns:

New geometry with all frames translated.

Return type:

PyGeometry

class multimodars.PyGeometryPair(geom_a, geom_b, label)

Bases: object

Python representation of a diastolic/systolic geometry pair.

geom_a

First geometry (typically diastolic).

Type:

PyGeometry

geom_b

Second geometry (typically systolic).

Type:

PyGeometry

label

Human-readable label for this geometry pair.

Type:

str

Examples

>>> pair = PyGeometryPair(
...     geom_a=diastole,
...     geom_b=systole,
...     label="Pat00_rest"
... )
create_deformation_table()
geom_a
geom_b
get_summary()

Get summaries for both geometries and a per-frame deformation table.

Calls PyGeometry.get_summary() on each contained geometry and additionally computes per-frame area and elliptic ratio for both phases.

Returns:

  • summaries (tuple) – ((dia_mla, dia_max_stenosis, dia_len_mm), (sys_mla, sys_max_stenosis, sys_len_mm)).

  • table (list of list of float) – Matrix of shape (N, 6) with columns [id, area_dia, ellip_dia, area_sys, ellip_sys, z].

label
class multimodars.PyInputData(lumen, eem, calcification, sidebranch, record, ref_point, diastole, label)

Bases: object

Python representation of the intravascular imaging input data for one cardiac phase.

lumen

Vessel lumen contours.

Type:

list of PyContour

eem

Vessel EEM (external elastic membrane) contours.

Type:

list of PyContour or None

calcification

Vessel calcification contours.

Type:

list of PyContour or None

sidebranch

Vessel sidebranch contours.

Type:

list of PyContour or None

record

Metadata records about the input data.

Type:

list of PyRecord or None

ref_point

Reference point used for alignment.

Type:

PyContourPoint

diastole

True when the data corresponds to the diastolic phase.

Type:

bool

label

Human-readable label for this input dataset.

Type:

str

Examples

>>> input_data = PyInputData(
...     lumen=[lumen_contour1, lumen_contour2, ...],
...     eem=[eem_contour1, eem_contour2, ...],
...     calcification=[],
...     sidebranch=[],
...     record=record,
...     diastole=True,
...     lablel="Pat00_diastole_rest"
... )
calcification
diastole
eem
label
lumen
record
ref_point
sidebranch
class multimodars.PyRecord(frame, phase, measurement_1, measurement_2)

Bases: object

Python representation of a per-frame measurement record.

frame

Frame number within the acquisition sequence.

Type:

int

phase

Cardiac phase identifier: "D" for diastole or "S" for systole.

Type:

str

measurement_1

Primary measurement value. In coronary artery anomalies this is the wall thickness between the aorta and the coronary artery.

Type:

float or None

measurement_2

Secondary measurement value. In coronary artery anomalies this is the wall thickness between the pulmonary artery and the coronary artery.

Type:

float or None

Examples

>>> record = PyRecord(
...     frame=5,
...     phase="D",
...     measurement_1=1.4,
...     measurement_2=2.1
... )
frame
measurement_1
measurement_2
phase
multimodars.align_combined(centerline, geometry, main_ref_pt, counterclockwise_ref_pt, clockwise_ref_pt, points, angle_step_deg=1.0, angle_range_deg=15.0, index_range=2, write=False, watertight=True, interpolation_steps=0, output_dir='output/aligned', contour_types=None, case_name='None', align_wall_anomalous=False)[source]

Align a geometry (or geometry pair) using three reference points and Hausdorff refinement.

Creates centerline-aligned meshes using three anatomical reference points for an initial orientation and a set of additional points for Hausdorff distance-based fine-tuning of the rotation.

Parameters:
  • centerline (PyCenterline) – Centerline of the vessel.

  • geometry (PyGeometryPair | PyGeometry) – Single geometry or diastolic/systolic geometry pair to align.

  • main_ref_pt (tuple[float, float, float]) – (x, y, z) reference point at the aortic ostium.

  • counterclockwise_ref_pt (tuple[float, float, float]) – (x, y, z) counterclockwise reference point (viewed proximal → distal).

  • clockwise_ref_pt (tuple[float, float, float]) – (x, y, z) clockwise reference point (viewed proximal → distal).

  • points (list[tuple[float, float, float]]) – Point cloud used for Hausdorff distance calculation during rotation refinement.

  • angle_step_deg (float) – Step size in degrees for the rotation search. Default is 1.0.

  • angle_range_deg (float) – Total rotation search range in degrees. Default is 15.0.

  • index_range (int) – Number of centerline indices considered around the reference. Default is 2.

  • write (bool) – Whether to write the aligned meshes to OBJ files. Default is False.

  • watertight (bool) – Whether to write a watertight or shell mesh. Default is True.

  • interpolation_steps (int) – Number of interpolation steps between phases. Only used when geometry is a PyGeometryPair. Default is 0.

  • output_dir (str) – Output directory for aligned meshes. Default is "output/aligned".

  • contour_types (list[PyContourType] | None) – Contour types to export. Default is [PyContourType.Lumen, PyContourType.Catheter, PyContourType.Wall].

  • case_name (str) – Case name used as a filename prefix. Default is "None".

  • align_wall_anomalous (bool) – When True, rotate the Wall contour in every frame (from frame 2 onward) so its aortic straight portion aligns to the plane defined by frames 0 and 1. Only meaningful for anomalous vessels. Default is False.

Return type:

tuple[PyGeometryPair | PyGeometry, PyCenterline]

Returns:

  • geometry (PyGeometryPair or PyGeometry) – Aligned geometry, matching the type of the input.

  • centerline (PyCenterline) – Resampled centerline.

Examples

>>> import multimodars as mm
>>> result, cl = mm.align_combined(
...     centerline,
...     geometry_pair,
...     (12.2605, -201.3643, 1751.0554),
...     (11.7567, -202.1920, 1754.7975),
...     (15.6605, -202.1920, 1749.9655),
...     point_cloud,
... )
multimodars.align_manual(centerline, geometry, rotation_angle, ref_point, write=False, watertight=True, interpolation_steps=0, output_dir='output/aligned', contour_types=None, case_name='None', align_wall_anomalous=False)[source]

Align a geometry (or geometry pair) to the centerline using a manual rotation angle.

Creates centerline-aligned meshes using an explicit rotation angle and a single reference point on the centerline. Only works for elliptic vessels such as coronary artery anomalies.

Parameters:
  • centerline (PyCenterline) – Centerline of the vessel.

  • geometry (PyGeometryPair | PyGeometry) – Single geometry or diastolic/systolic geometry pair to align.

  • rotation_angle (float) – Rotation angle in radians to apply.

  • ref_point (tuple[float, float, float]) – (x, y, z) reference point on the centerline.

  • write (bool) – Whether to write the aligned meshes to OBJ files. Default is False.

  • watertight (bool) – Whether to write a watertight or shell mesh. Default is True.

  • interpolation_steps (int) – Number of interpolation steps between phases. Only used when geometry is a PyGeometryPair. Default is 0.

  • output_dir (str) – Output directory for aligned meshes. Default is "output/aligned".

  • contour_types (list[PyContourType] | None) – Contour types to export. Default is [PyContourType.Lumen, PyContourType.Catheter, PyContourType.Wall].

  • case_name (str) – Case name used as a filename prefix. Default is "None".

  • align_wall_anomalous (bool) – When True, rotate the Wall contour in every frame (from frame 2 onward) so its aortic straight portion aligns to the plane defined by frames 0 and 1. Only meaningful for anomalous vessels. Default is False.

Return type:

tuple[PyGeometryPair | PyGeometry, PyCenterline]

Returns:

  • geometry (PyGeometryPair or PyGeometry) – Aligned geometry, matching the type of the input.

  • centerline (PyCenterline) – Resampled centerline.

Examples

>>> import multimodars as mm
>>> result, cl = mm.align_manual(
...     centerline, geometry_pair, rotation_angle=1.57, ref_point=(1.0, 2.0, 3.0)
... )
multimodars.align_three_point(centerline, geometry, main_ref_pt, counterclockwise_ref_pt, clockwise_ref_pt, angle_step_deg=1.0, write=False, watertight=True, interpolation_steps=0, output_dir='output/aligned', contour_types=None, case_name='None', align_wall_anomalous=False)[source]

Align a geometry (or geometry pair) to the centerline using three reference points.

Creates centerline-aligned meshes based on three anatomical reference points (main ostium, counterclockwise side, clockwise side). Only works for elliptic vessels such as coronary artery anomalies.

Parameters:
  • centerline (PyCenterline) – Centerline of the vessel.

  • geometry (PyGeometryPair | PyGeometry) – Single geometry or diastolic/systolic geometry pair to align.

  • main_ref_pt (tuple[float, float, float]) – (x, y, z) reference point at the aortic ostium.

  • counterclockwise_ref_pt (tuple[float, float, float]) – (x, y, z) counterclockwise reference point (viewed proximal → distal).

  • clockwise_ref_pt (tuple[float, float, float]) – (x, y, z) clockwise reference point (viewed proximal → distal).

  • angle_step_deg (float) – Step size in degrees for the rotation search. Default is 1.0.

  • write (bool) – Whether to write the aligned meshes to OBJ files. Default is False.

  • watertight (bool) – Whether to write a watertight or shell mesh. Default is True.

  • interpolation_steps (int) – Number of interpolation steps between phases. Only used when geometry is a PyGeometryPair. Default is 0.

  • output_dir (str) – Output directory for aligned meshes. Default is "output/aligned".

  • contour_types (list[PyContourType] | None) – Contour types to export. Default is [PyContourType.Lumen, PyContourType.Catheter, PyContourType.Wall].

  • case_name (str) – Case name used as a filename prefix. Default is "None".

  • align_wall_anomalous (bool) – When True, rotate the Wall contour in every frame (from frame 2 onward) so its aortic straight portion aligns to the plane defined by frames 0 and 1. Only meaningful for anomalous vessels. Default is False.

Return type:

tuple[PyGeometryPair | PyGeometry, PyCenterline]

Returns:

  • geometry (PyGeometryPair or PyGeometry) – Aligned geometry, matching the type of the input.

  • centerline (PyCenterline) – Resampled centerline.

Examples

>>> import multimodars as mm
>>> result, cl = mm.align_three_point(
...     centerline,
...     geometry_pair,
...     (12.2605, -201.3643, 1751.0554),
...     (11.7567, -202.1920, 1754.7975),
...     (15.6605, -202.1920, 1749.9655),
... )
multimodars.build_adjacency_map(faces)[source]

Build a vertex adjacency map from a triangle mesh face list. For each triangle face, all three undirected edges are recorded so that every vertex maps to the set of vertices it shares an edge with.

Parameters:

faces (list[list[int]]) – Triangle faces, each represented as a three-element array of vertex indices [v0, v1, v2].

Returns:

adjacency_map – Mapping from each vertex index to the set of its directly connected neighbour vertex indices.

Return type:

dict[int, set[int]]

Examples

>>> import multimodars as mm
>>>
>>> faces = [[0, 1, 2], [1, 2, 3]]
>>> adj = mm.build_adjacency_map(faces)
>>> print(adj[1])  # {0, 2, 3}
multimodars.create_wall_mesh(frames, cl_aorta, cl_rca, cl_lca, results, aortic_scaling=None, coronary_scaling=1.0)[source]

Create a wall, by assigning a wall thickness for coronaries, and then finding either directly the best scaling for aorta (for coronary artery anomalies), by finding the distance between aortic wall and the reference point on the coronary or by directly providing a scaling factor for coronarie sand aorta. …

Return type:

dict

Parameters:
multimodars.discretize_vessel(centerline, points, branch_id=0, step_size=0.5, n_points=200)[source]

Discretize a vessel surface mesh along a centerline branch into uniform cross-sections.

Walks the specified centerline branch at uniform arc-length intervals of step_size, projects the supplied mesh points onto each perpendicular cross-sectional plane, discards incomplete slices (empty or not covering all four angular quadrants), and resamples the remaining contours to exactly n_points evenly-spaced points via a closed Catmull-Rom spline.

Parameters:
  • centerline (PyCenterline) – Centerline object containing one or more branches.

  • points (list[tuple[float, float, float]]) – 3-D surface mesh points (x, y, z) to project onto each cross-section.

  • branch_id (int) – Index of the centerline branch to walk. Default is 0.

  • step_size (float) – Arc-length distance between consecutive cross-sections in the same units as centerline and points. Default is 0.5.

  • n_points (int) – Number of evenly-spaced points per output contour. Default is 200.

Returns:

contours – One contour per surviving cross-section, each containing exactly n_points uniformly distributed points lying on a Catmull-Rom spline fit to the projected surface points.

Return type:

list[PyContour]

Examples

>>> import multimodars as mm
>>>
>>> contours = mm.discretize_vessel(centerline, mesh_points, branch_id=0, step_size=0.5)
>>> print(len(contours))
multimodars.discretize_vessel_tree(ao_cl, rca_cl, lca_cl, results_dict, branch_id_rca=0, branch_id_lca=0, step_size=1.0, n_points=100, b_spline=False, bspline_smoothing=100.0, bspline_degree=3, control_plot=False)[source]

Discretize a coronary vessel tree, optionally smoothing contours with B-splines.

Expects results_dict to already contain labelled branch point keys (aorta_points, rca_points_main, rca_points_side_1, …, lca_points_main, lca_points_side_1, …). Use prepare_and_discretize() if you also need branch labelling to run automatically.

Parameters:
  • ao_cl (PyCenterline) – Aortic, RCA, and LCA centerlines (branches already computed and labelled).

  • rca_cl (PyCenterline) – Aortic, RCA, and LCA centerlines (branches already computed and labelled).

  • lca_cl (PyCenterline) – Aortic, RCA, and LCA centerlines (branches already computed and labelled).

  • results_dict (dict) – Dictionary produced by label_branches() containing keys aorta_points, rca_points_main, lca_points_main, and any rca_points_side_N / lca_points_side_N entries.

  • branch_id_rca (int) – Main-vessel branch IDs (almost always 0).

  • branch_id_lca (int) – Main-vessel branch IDs (almost always 0).

  • step_size (float) – Arc-length distance between consecutive cross-sections in mm.

  • n_points (int) – Number of evenly-spaced points per output contour.

  • b_spline (bool) – When True, all contours are replaced with closed B-spline fits before reference points are computed.

  • control_plot (bool) – When True, open an interactive Plotly 3-D visualisation of the finished tree (calls plot_vessel_tree()).

  • bspline_smoothing (float) –

    Smoothing condition s for scipy.interpolate.splprep. Range: [0, ∞).

    • s = 0 — exact interpolation.

    • s n_points — gentle smoothing (~1 mm² average residual per point).

    • s 5 * n_points — strong smoothing.

      Tune empirically; the right value depends on how irregular the raw contours are.

  • bspline_degree (int) – B-spline polynomial degree (1-5). Default is cubic (3).

Returns:

Fully populated vessel tree including orientation reference triplets.

Return type:

PyDiscretizedVesselTree

multimodars.export_section_stl(results, type='all', output_dir=None)[source]

Export the mesh (or a labeled sub-region) as an STL file.

Parameters:
  • results (dict) – Labeled results dictionary containing "mesh" and the point-label lists produced by label() / scale().

  • type (str) –

    Which region to export. One of:

    • "all" - the full mesh as-is.

    • "aorta" - only the aorta region.

    • "rca" - only the RCA region (includes adjacent aorta ring).

    • "lca" - only the LCA region (includes adjacent aorta ring).

    Default is "all".

  • output_dir (Path | str | None) – Directory in which to write the STL file. Defaults to the current working directory when None.

Return type:

None

multimodars.find_aorta_scaling(frames, cl_aorta, results)[source]

Compute the optimal radial scaling factor for the aortic region.

Extracts reconstructed wall points from the intravascular frames (using aortic_thickness and the "Wall" extras) as a reference, then calls the Rust find_aortic_scaling routine to determine the factor that best aligns the removed RCA points to those references.

Parameters:
  • frames (list[PyFrame]) – Intravascular imaging frames containing aortic_thickness and extras["Wall"] data.

  • cl_aorta (PyCenterline) – Centerline of the aortic region.

  • results (dict) – Labelled results dictionary containing "rca_removed_points".

  • debug_plot (bool, optional) – Reserved for future use; currently unused. Default is True.

Returns:

Optimal radial scaling factor for the aortic segment.

Return type:

float

multimodars.find_aortic_wall_scaling(frames, cl_aorta, results)[source]

Compute the optimal radial scaling factor for the aortic wall region. This is created for anomalous coronaries, and tries to optimize the aortic wall to the point on the first quarter towards the aortic wall of the first round lumen (marking the end of the intramural course).

End of the intramural course is defined as the first lumen with an elliptic ratio <1.3

Parameters:
  • frames (list[PyFrame]) – Intravascular imaging frames.

  • cl_aorta (PyCenterline) – Centerline of the aortic region.

  • results (dict) – Labelled results dictionary containing "rca_removed_points".

Returns:

Optimal radial scaling factor for the aortic wall.

Return type:

float

multimodars.find_centerline_bounded_points_simple(centerline, points, radius)[source]

Find points bounded by spheres along a coronary vessel centerline.

This version accepts and returns simple Python lists of tuples.

Parameters:
  • centerline (PyCenterline) – Centerline of the vessel.

  • points (list[tuple[float, float, float]]) – List of (x, y, z) point coordinates.

  • radius (float) – Radius of the bounding spheres around each centerline point.

Returns:

bounded_points – Filtered points that are inside the bounding spheres.

Return type:

list[tuple[float, float, float]]

Examples

>>> import multimodars as mm
>>>
>>> # Load centerline and point cloud
>>> centerline = mm.load_centerline("path/to/centerline.json")
>>> points = [(0.0, 0.0, 0.0), (1.0, 0.0, 0.0), ...]  # or mesh.vertices.tolist()
>>>
>>> # Find points bounded by centerline spheres
>>> bounded_points = mm.find_centerline_bounded_points(centerline, points, 2.0)
>>> print(f"Found {len(bounded_points)} points inside vessel bounds")
multimodars.find_distal_and_proximal_scaling(frames, centerline, results, dist_range=3, prox_range=2)[source]

Compute the optimal radial scaling factors for the proximal and distal segments.

Collects lumen wall points from the first prox_range and last dist_range imaging frames as reference geometry, then calls the Rust find_proximal_distal_scaling routine to find the scaling factors that best match the anomalous segment endpoints to those references.

Parameters:
  • frames (list of PyFrame) – Ordered intravascular imaging frames for the vessel.

  • centerline (PyCenterline) – Centerline of the vessel region.

  • results (dict) – Labelled results dictionary containing "anomalous_points".

  • dist_range (int) – Number of frames from the distal end used as the distal reference. Default is 3.

  • prox_range (int) – Number of frames from the proximal end used as the proximal reference. Default is 2.

Return type:

tuple[float, float]

Returns:

  • prox_scaling (float) – Optimal radial scaling factor for the proximal segment.

  • dist_scaling (float) – Optimal radial scaling factor for the distal segment.

multimodars.find_proximal_distal_scaling(anomalous_points, n_proximal, n_distal, centerline, proximal_reference, distal_reference)[source]

Find the optimal diameter scaling for the proximal and distal regions.

Parameters:
  • anomalous_points (list[tuple[float, float, float]]) – (x, y, z) coordinates of the anomalous vessel region.

  • n_proximal (int) – Number of proximal points used for comparison.

  • n_distal (int) – Number of distal points used for comparison.

  • centerline (PyCenterline) – Centerline of the vessel region.

  • proximal_reference (list[tuple[float, float, float]]) – Reference (x, y, z) points from the CCTA mesh for the proximal region.

  • distal_reference (list[tuple[float, float, float]]) – Reference (x, y, z) points for the distal region.

Returns:

  • proximal_scaling (float) – Optimal scaling distance for the proximal region.

  • distal_scaling (float) – Optimal scaling distance for the distal region.

Examples

>>> import multimodars as mm
multimodars.find_sharp_angles(cl, branch_id, cos_threshold=0.0, control_plot=False)[source]

Find sharp angles in a centerline branch and optionally plot them.

A thin wrapper around cl.find_sharp_angles that adds an optional debug visualisation where each flagged position is shown in a distinct colour so they can be counted and identified before deciding whether to call split_branch / merge_branches.

Parameters:
  • cl (PyCenterline) – Centerline after calculate_branches (and optionally check_centerline).

  • branch_id (int) – Branch to inspect (0 = main vessel).

  • cos_threshold (float) – Cosine above which an angle is considered sharp. Use 0.0 for < 90°, 0.5 for < 60°, 0.866 for < 30°.

  • control_plot (bool) – When True opens an interactive 3-D scene with each sharp-angle position highlighted in a distinct colour.

Returns:

0-indexed positions within the branch (suitable for split_branch).

Return type:

list[int]

multimodars.fix_and_remesh_stitched_mesh(mesh, *, target_edge_length_mm=None, remesh_iterations=10, verbose=False)[source]

Fill holes and remesh a stitched mesh, replicating Meshmixer workflow.

Return type:

Trimesh

Parameters:
  • mesh (Trimesh)

  • target_edge_length_mm (float | None)

  • remesh_iterations (int)

  • verbose (bool)

Steps

  1. Repair non-manifold edges/vertices.

  2. Close holes - flat fill (all holes).

  3. Isotropic remesh to target_edge_length_mm.

type mesh:

Trimesh

param mesh:

Input trimesh (the stitched surface).

type target_edge_length_mm:

float | None

param target_edge_length_mm:

Target edge length in mm for the isotropic remesh. If None, uses the 25th-percentile edge length of the input mesh (preserves the fine IV-mesh resolution as reference).

type remesh_iterations:

int

param remesh_iterations:

Number of isotropic remeshing iterations (default 10).

type verbose:

bool

param verbose:

Print progress info.

multimodars.from_array_doublepair(input_data_a, input_data_b, input_data_c, input_data_d, step_rotation_deg=0.5, range_rotation_deg=90.0, sample_size=500, image_center=(4.5, 4.5), radius=0.5, n_points=20, write_obj=True, watertight=True, contour_types=None, output_path_ab='output/rest', output_path_cd='output/stress', interpolation_steps=0, bruteforce=False, smooth=True, postprocessing=True)[source]

Process two PyInputData pairs in parallel, aligning frames within each pair independently.

Accepts pre-loaded data for REST (diastole + systole) and STRESS (diastole + systole), aligns each pair independently, and writes interpolated OBJ meshes.

a REST diastole                      c STRESS diastole
        │  output_path_ab (Rest: a+b)        │  output_path_cd (Stress: c+d)
        ▼                                        ▼
b REST systole                       d STRESS systole
Parameters:
  • input_data_a (PyInputData) – Diastolic REST input data.

  • input_data_b (PyInputData) – Systolic REST input data.

  • input_data_c (PyInputData) – Diastolic STRESS input data.

  • input_data_d (PyInputData) – Systolic STRESS input data.

  • step_rotation_deg (float) – Rotation step in degrees. Default is 0.5.

  • range_rotation_deg (float) – Rotation search range (±) in degrees. Default is 90.0.

  • sample_size (int) – Number of points to downsample to. Default is 500.

  • image_center (tuple[float, float]) – Image center (x, y) in mm. Default is (4.5, 4.5).

  • radius (float) – Catheter radius in mm. Default is 0.5.

  • n_points (int) – Number of catheter points. Default is 20.

  • write_obj (bool) – Whether to write OBJ files. Default is True.

  • watertight (bool) – Whether to write a watertight or shell mesh. Default is True.

  • contour_types (list[PyContourType] | None) – Contour types to export. Default is [PyContourType.Lumen, PyContourType.Catheter, PyContourType.Wall].

  • output_path_ab (str) – Output directory for REST results (pair a+b). Default is "output/rest".

  • output_path_cd (str) – Output directory for STRESS results (pair c+d). Default is "output/stress".

  • interpolation_steps (int) – Number of interpolation steps between phases. Default is 28.

  • bruteforce (bool) – Whether to use brute-force alignment. Default is False.

  • smooth (bool) – Whether to smooth frames after alignment. Default is True.

  • postprocessing (bool) – Whether to equalise spacing within/between geometries. Default is True.

Return type:

tuple[PyGeometryPair, PyGeometryPair, tuple[list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]]]]

Returns:

  • rest (PyGeometryPair) – Aligned geometry pair for the REST condition.

  • stress (PyGeometryPair) – Aligned geometry pair for the STRESS condition.

  • logs (tuple of list) – 4-tuple of alignment logs (logs_a, logs_b, logs_c, logs_d); each entry is a list of (id, matched_to, rel_rot_deg, total_rot_deg, tx, ty, centroid_x, centroid_y).

Examples

>>> import multimodars as mm
>>> rest, stress, _ = mm.from_array_doublepair(
...     rest_dia, rest_sys, stress_dia, stress_sys
... )
multimodars.from_array_full(input_data_a, input_data_b, input_data_c, input_data_d, step_rotation_deg=0.5, range_rotation_deg=90.0, sample_size=500, image_center=(4.5, 4.5), radius=0.5, n_points=20, write_obj=True, watertight=True, contour_types=None, output_path_ab='output/rest', output_path_cd='output/stress', output_path_ac='output/diastole', output_path_bd='output/systole', interpolation_steps=0, bruteforce=False, smooth=True, postprocessing=True)[source]

Process four PyInputData objects in parallel, aligning and interpolating between phases.

Accepts pre-loaded input data for REST diastole, REST systole, STRESS diastole, and STRESS systole, then aligns frames within and between each cardiac phase.

                    output_path_ac (Diastole: a vs. c)
            ┌──────────────────────────────────────────┐
            ▼                                          ▼
a REST diastole                      c STRESS diastole
        │  output_path_ab (Rest: a+b)        │  output_path_cd (Stress: c+d)
        ▼                                        ▼
b REST systole                       d STRESS systole
            └──────────────────────────────────────────┘
                    output_path_bd (Systole: b vs. d)
Parameters:
  • input_data_a (PyInputData) – Diastolic REST input data.

  • input_data_b (PyInputData) – Systolic REST input data.

  • input_data_c (PyInputData) – Diastolic STRESS input data.

  • input_data_d (PyInputData) – Systolic STRESS input data.

  • step_rotation_deg (float) – Rotation step in degrees. Default is 0.5.

  • range_rotation_deg (float) – Rotation search range (±) in degrees. Default is 90.0.

  • sample_size (int) – Number of points to downsample to. Default is 500.

  • image_center (tuple[float, float]) – Image center (x, y) in mm. Default is (4.5, 4.5).

  • radius (float) – Catheter radius in mm. Default is 0.5.

  • n_points (int) – Number of catheter points. Default is 20.

  • write_obj (bool) – Whether to write OBJ files. Default is True.

  • watertight (bool) – Whether to write a watertight or shell mesh. Default is True.

  • contour_types (list[PyContourType] | None) – Contour types to export. Default is [PyContourType.Lumen, PyContourType.Catheter, PyContourType.Wall].

  • output_path_ab (str) – Output directory for REST results (pair a+b). Default is "output/rest".

  • output_path_cd (str) – Output directory for STRESS results (pair c+d). Default is "output/stress".

  • output_path_ac (str) – Output directory for DIASTOLE results (pair a+c). Default is "output/diastole".

  • output_path_bd (str) – Output directory for SYSTOLE results (pair b+d). Default is "output/systole".

  • interpolation_steps (int) – Number of interpolation steps between phases. Default is 28.

  • bruteforce (bool) – Whether to use brute-force alignment. Default is False.

  • smooth (bool) – Whether to smooth frames after alignment. Default is True.

  • postprocessing (bool) – Whether to equalise spacing within/between geometries. Default is True.

Return type:

tuple[PyGeometryPair, PyGeometryPair, PyGeometryPair, PyGeometryPair, tuple[list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]]]]

Returns:

  • rest (PyGeometryPair) – Aligned geometry pair for the REST condition.

  • stress (PyGeometryPair) – Aligned geometry pair for the STRESS condition.

  • diastole (PyGeometryPair) – Aligned geometry pair for the diastolic phase.

  • systole (PyGeometryPair) – Aligned geometry pair for the systolic phase.

  • logs (tuple of list) – 4-tuple of alignment logs (logs_a, logs_b, logs_c, logs_d); each entry is a list of (id, matched_to, rel_rot_deg, total_rot_deg, tx, ty, centroid_x, centroid_y).

Examples

>>> import multimodars as mm
>>> rest, stress, dia, sys, _ = mm.from_array_full(
...     rest_dia, rest_sys, stress_dia, stress_sys
... )
multimodars.from_array_single(input_data, step_rotation_deg=0.5, range_rotation_deg=90.0, sample_size=500, image_center=(4.5, 4.5), radius=0.5, n_points=20, write_obj=False, watertight=True, contour_types=None, output_path='output/single', bruteforce=False, smooth=True)[source]

Process a single geometry phase from a PyInputData object.

Accepts pre-loaded input data for one cardiac phase, aligns frames within the geometry, and optionally writes OBJ output.

Parameters:
  • input_data (PyInputData) – Input data for a single cardiac phase (e.g. diastolic REST).

  • step_rotation_deg (float) – Rotation step in degrees. Default is 0.5.

  • range_rotation_deg (float) – Rotation search range (±) in degrees. Default is 90.0.

  • sample_size (int) – Number of points to downsample to. Default is 500.

  • image_center (tuple[float, float]) – Image center (x, y) in mm. Default is (4.5, 4.5).

  • radius (float) – Catheter radius in mm. Default is 0.5.

  • n_points (int) – Number of catheter points. Default is 20.

  • write_obj (bool) – Whether to write OBJ files. Default is False.

  • watertight (bool) – Whether to write a watertight or shell mesh. Default is True.

  • contour_types (list[PyContourType] | None) – Contour types to export. Default is [PyContourType.Lumen, PyContourType.Catheter, PyContourType.Wall].

  • output_path (str) – Directory path to write the processed geometry. Default is "output/single".

  • bruteforce (bool) – Whether to use brute-force alignment. Default is False.

  • smooth (bool) – Whether to smooth frames after alignment. Default is True.

Return type:

tuple[PyGeometry, list[tuple[int, int, float, float, float, float, float]]]

Returns:

  • geom (PyGeometry) – Processed geometry for the chosen phase.

  • logs (list) – Alignment log entries; each entry is (id, matched_to, rel_rot_deg, total_rot_deg, tx, ty, centroid_x, centroid_y).

Examples

>>> import multimodars as mm
>>> geom, _ = mm.from_array_single(input_data)
multimodars.from_array_singlepair(input_data_a, input_data_b, step_rotation_deg=0.5, range_rotation_deg=90.0, sample_size=500, image_center=(4.5, 4.5), radius=0.5, n_points=20, write_obj=True, watertight=True, contour_types=None, output_path='output/singlepair', interpolation_steps=0, bruteforce=False, smooth=True, postprocessing=True)[source]

Align and interpolate between two PyInputData objects (diastole and systole).

Accepts pre-loaded diastolic and systolic input data, aligns frames between the two phases, and returns a single PyGeometryPair.

diastole ──▶ systole
Parameters:
  • input_data_a (PyInputData) – Diastolic input data.

  • input_data_b (PyInputData) – Systolic input data.

  • step_rotation_deg (float) – Rotation step in degrees. Default is 0.5.

  • range_rotation_deg (float) – Rotation search range (±) in degrees. Default is 90.0.

  • sample_size (int) – Number of points to downsample to. Default is 500.

  • image_center (tuple[float, float]) – Image center (x, y) in mm. Default is (4.5, 4.5).

  • radius (float) – Catheter radius in mm. Default is 0.5.

  • n_points (int) – Number of catheter points. Default is 20.

  • write_obj (bool) – Whether to write OBJ files. Default is True.

  • watertight (bool) – Whether to write a watertight or shell mesh. Default is True.

  • contour_types (list[PyContourType] | None) – Contour types to export. Default is [PyContourType.Lumen, PyContourType.Catheter, PyContourType.Wall].

  • output_path (str) – Directory path to write interpolated output files. Default is "output/singlepair".

  • interpolation_steps (int) – Number of interpolation steps between phases. Default is 28.

  • bruteforce (bool) – Whether to use brute-force alignment. Default is False.

  • smooth (bool) – Whether to smooth frames after alignment. Default is True.

  • postprocessing (bool) – Whether to equalise spacing within/between geometries. Default is True.

Return type:

tuple[PyGeometryPair, tuple[list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]]]]

Returns:

  • pair (PyGeometryPair) – Aligned diastole/systole geometry pair.

  • logs (tuple of list) – 2-tuple of alignment logs (logs_a, logs_b); each entry is a list of (id, matched_to, rel_rot_deg, total_rot_deg, tx, ty, centroid_x, centroid_y).

Examples

>>> import multimodars as mm
>>> pair, _ = mm.from_array_singlepair(rest_dia, rest_sys)
multimodars.from_file_doublepair(input_path_ab, input_path_cd, labels=None, step_rotation_deg=0.5, range_rotation_deg=90.0, sample_size=500, image_center=(4.5, 4.5), radius=0.5, n_points=20, write_obj=True, watertight=True, contour_types=None, output_path_ab='output/rest', output_path_cd='output/stress', interpolation_steps=0, bruteforce=False, smooth=True, postprocessing=True)[source]

Process two diastole/systole pairs in parallel from CSV folders.

Reads REST and STRESS acquisitions independently, aligns frames within each pair, and writes interpolated OBJ meshes.

a REST diastole                      c STRESS diastole
        │  output_path_ab (Rest: a+b)        │  output_path_cd (Stress: c+d)
        ▼                                        ▼
b REST systole                       d STRESS systole

Warning

The CSV must have no header. Each row is (frame index, x-coord (mm), y-coord (mm), z-coord (mm)):

185, 5.32, 2.37, 0.0
...
Parameters:
  • input_path_ab (str) – Path to the REST input folder (contains diastolic a and systolic b CSVs).

  • input_path_cd (str) – Path to the STRESS input folder (contains diastolic c and systolic d CSVs).

  • labels (list[str] | None) – Labels for the four geometries [rest_dia, rest_sys, stress_dia, stress_sys]. Must be exactly 4 strings; if a different number is supplied the last component of each input path is used instead. Default is [].

  • step_rotation_deg (float) – Rotation step in degrees. Default is 0.5.

  • range_rotation_deg (float) – Rotation search range (±) in degrees. Default is 90.0.

  • sample_size (int) – Number of points to downsample to. Default is 500.

  • image_center (tuple[float, float]) – Image center (x, y) in mm. Default is (4.5, 4.5).

  • radius (float) – Catheter radius in mm. Default is 0.5.

  • n_points (int) – Number of catheter points. Default is 20.

  • write_obj (bool) – Whether to write OBJ files. Default is True.

  • watertight (bool) – Whether to write a watertight or shell mesh. Default is True.

  • contour_types (list[PyContourType] | None) – Contour types to export. Default is [PyContourType.Lumen, PyContourType.Catheter, PyContourType.Wall].

  • output_path_ab (str) – Output directory for REST results (pair a+b). Default is "output/rest".

  • output_path_cd (str) – Output directory for STRESS results (pair c+d). Default is "output/stress".

  • interpolation_steps (int) – Number of interpolated meshes between phases. Default is 28.

  • bruteforce (bool) – Whether to use brute-force alignment. Default is False.

  • smooth (bool) – Whether to smooth frames after alignment. Default is True.

  • postprocessing (bool) – Whether to equalise spacing within/between geometries. Default is True.

Return type:

tuple[PyGeometryPair, PyGeometryPair, tuple[list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]]]]

Returns:

  • rest (PyGeometryPair) – Aligned geometry pair for the REST condition.

  • stress (PyGeometryPair) – Aligned geometry pair for the STRESS condition.

  • logs (tuple of list) – 4-tuple of alignment logs (logs_a, logs_b, logs_c, logs_d); each entry is a list of (id, matched_to, rel_rot_deg, total_rot_deg, tx, ty, centroid_x, centroid_y).

Examples

>>> import multimodars as mm
>>> rest, stress, _ = mm.from_file_doublepair(
...     "data/ivus_rest", "data/ivus_stress"
... )
multimodars.from_file_full(input_path_ab, input_path_cd, labels=None, step_rotation_deg=0.5, range_rotation_deg=90.0, sample_size=500, image_center=(4.5, 4.5), radius=0.5, n_points=20, write_obj=True, watertight=True, contour_types=None, output_path_ab='output/rest', output_path_cd='output/stress', output_path_ac='output/diastole', output_path_bd='output/systole', interpolation_steps=0, bruteforce=False, smooth=True, postprocessing=True)[source]

Process four intravascular imaging geometries in parallel from CSV folders.

Reads REST and STRESS acquisitions from two input folders, aligns frames within and between each cardiac phase in parallel, and writes interpolated OBJ meshes.

                    output_path_ac (Diastole: a vs. c)
            ┌──────────────────────────────────────────┐
            ▼                                          ▼
a REST diastole                      c STRESS diastole
        │  output_path_ab (Rest: a+b)        │  output_path_cd (Stress: c+d)
        ▼                                        ▼
b REST systole                       d STRESS systole
            └──────────────────────────────────────────┘
                    output_path_bd (Systole: b vs. d)

Warning

The CSV must have no header. Each row is (frame index, x-coord (mm), y-coord (mm), z-coord (mm)):

185, 5.32, 2.37, 0.0
...
Parameters:
  • input_path_ab (str) – Path to the REST input folder (contains diastolic a and systolic b CSVs).

  • input_path_cd (str) – Path to the STRESS input folder (contains diastolic c and systolic d CSVs).

  • labels (list[str] | None) – Labels for the four geometries [rest_dia, rest_sys, stress_dia, stress_sys]. Must be exactly 4 strings; if a different number is supplied the last component of each input path is used instead. Default is [].

  • step_rotation_deg (float) – Rotation step in degrees. Default is 0.5.

  • range_rotation_deg (float) – Rotation search range (±) in degrees; a range of 90° gives 180° total. Default is 90.0.

  • sample_size (int) – Number of points to downsample to during alignment. Default is 500.

  • image_center (tuple[float, float]) – Image center (x, y) in mm. Default is (4.5, 4.5).

  • radius (float) – Catheter radius in mm. Default is 0.5.

  • n_points (int) – Number of catheter points; more points increases the influence of the image center. Default is 20.

  • write_obj (bool) – Whether to write OBJ files to disk. Default is True.

  • watertight (bool) – Whether to write a watertight or shell mesh. Default is True.

  • contour_types (list[PyContourType] | None) – Contour types to export. Default is [PyContourType.Lumen, PyContourType.Catheter, PyContourType.Wall].

  • output_path_ab (str) – Output directory for REST results (pair a+b). Default is "output/rest".

  • output_path_cd (str) – Output directory for STRESS results (pair c+d). Default is "output/stress".

  • output_path_ac (str) – Output directory for DIASTOLE results (pair a+c). Default is "output/diastole".

  • output_path_bd (str) – Output directory for SYSTOLE results (pair b+d). Default is "output/systole".

  • interpolation_steps (int) – Number of interpolated meshes between phases. Default is 28.

  • bruteforce (bool) – Whether to use brute-force alignment (one comparison per step). Default is False.

  • smooth (bool) – Whether to smooth frames after alignment using a 3-point moving average. Default is True.

  • postprocessing (bool) – Whether to adjust spacing within/between geometries to equal offsets. Default is True.

Return type:

tuple[PyGeometryPair, PyGeometryPair, PyGeometryPair, PyGeometryPair, tuple[list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]]]]

Returns:

  • rest (PyGeometryPair) – Aligned geometry pair for the REST condition.

  • stress (PyGeometryPair) – Aligned geometry pair for the STRESS condition.

  • diastole (PyGeometryPair) – Aligned geometry pair for the diastolic phase.

  • systole (PyGeometryPair) – Aligned geometry pair for the systolic phase.

  • logs (tuple of list) – 4-tuple of alignment logs (logs_a, logs_b, logs_c, logs_d); each entry is a list of (id, matched_to, rel_rot_deg, total_rot_deg, tx, ty, centroid_x, centroid_y).

Examples

>>> import multimodars as mm
>>> rest, stress, dia, sys, _ = mm.from_file_full(
...     "data/ivus_rest", "data/ivus_stress"
... )
multimodars.from_file_single(input_path, labels=None, diastole=True, step_rotation_deg=0.5, range_rotation_deg=90.0, sample_size=500, image_center=(4.5, 4.5), radius=0.5, n_points=20, write_obj=True, watertight=True, contour_types=None, output_path='output/single', bruteforce=False, smooth=True)[source]

Process a single intravascular imaging geometry from a CSV file.

Reads one phase (diastole or systole) from an IVUS CSV file, aligns frames within the geometry, and optionally writes OBJ output.

Warning

The CSV must have no header. Each row is (frame index, x-coord (mm), y-coord (mm), z-coord (mm)).

Parameters:
  • input_path (str) – Path to the input CSV folder.

  • labels (list[str] | None) – Label for the geometry (1 string). If a different number is supplied the last component of the input path is used instead. Default is [].

  • diastole (bool) – When True process the diastolic phase; otherwise systole. Default is True.

  • step_rotation_deg (float) – Rotation step in degrees. Default is 0.5.

  • range_rotation_deg (float) – Rotation search range (±) in degrees. Default is 90.0.

  • sample_size (int) – Number of points to downsample to. Default is 500.

  • image_center (tuple[float, float]) – Image center (x, y) in mm. Default is (4.5, 4.5).

  • radius (float) – Catheter radius in mm. Default is 0.5.

  • n_points (int) – Number of catheter points. Default is 20.

  • write_obj (bool) – Whether to write OBJ files. Default is True.

  • watertight (bool) – Whether to write a watertight or shell mesh. Default is True.

  • contour_types (list[PyContourType] | None) – Contour types to export. Default is [PyContourType.Lumen, PyContourType.Catheter, PyContourType.Wall].

  • output_path (str) – Directory path to write the processed geometry. Default is "output/single".

  • bruteforce (bool) – Whether to use brute-force alignment. Default is False.

  • smooth (bool) – Whether to smooth frames after alignment. Default is True.

Return type:

tuple[PyGeometry, list[tuple[int, int, float, float, float, float, float]]]

Returns:

  • geom (PyGeometry) – Processed geometry for the chosen phase.

  • logs (list) – Alignment log entries; each entry is (id, matched_to, rel_rot_deg, total_rot_deg, tx, ty, centroid_x, centroid_y).

Examples

>>> import multimodars as mm
>>> geom, _ = mm.from_file_single("data/ivus.csv", diastole=False)
multimodars.from_file_singlepair(input_path, labels=None, step_rotation_deg=0.5, range_rotation_deg=90.0, sample_size=500, image_center=(4.5, 4.5), radius=0.5, n_points=20, write_obj=True, watertight=True, contour_types=None, output_path='output/singlepair', interpolation_steps=0, bruteforce=False, smooth=True, postprocessing=True)[source]

Process a single diastole/systole pair from an input CSV folder.

Reads one acquisition folder, aligns the diastolic and systolic frames, and returns a single PyGeometryPair.

Pipeline:
   diastole
     │
     ▼
   systole

Warning

The CSV must have no header. Each row is (frame index, x-coord (mm), y-coord (mm), z-coord (mm)):

185, 5.32, 2.37, 0.0
...
Parameters:
  • input_path (str) – Path to the input CSV folder.

  • labels (list[str] | None) – Labels for the two geometries [diastole, systole]. Must be exactly 2 strings; if a different number is supplied the last component of the input path is used instead. Default is [].

  • step_rotation_deg (float) – Rotation step in degrees. Default is 0.5.

  • range_rotation_deg (float) – Rotation search range (±) in degrees. Default is 90.0.

  • sample_size (int) – Number of points to downsample to. Default is 500.

  • image_center (tuple[float, float]) – Image center (x, y) in mm. Default is (4.5, 4.5).

  • radius (float) – Catheter radius in mm. Default is 0.5.

  • n_points (int) – Number of catheter points. Default is 20.

  • write_obj (bool) – Whether to write OBJ files. Default is True.

  • watertight (bool) – Whether to write a watertight or shell mesh. Default is True.

  • contour_types (list[PyContourType] | None) – Contour types to export. Default is [PyContourType.Lumen, PyContourType.Catheter, PyContourType.Wall].

  • output_path (str) – Directory path to write the processed geometry. Default is "output/singlepair".

  • interpolation_steps (int) – Number of interpolated meshes. Default is 28.

  • bruteforce (bool) – Whether to use brute-force alignment. Default is False.

  • smooth (bool) – Whether to smooth frames after alignment. Default is True.

  • postprocessing (bool) – Whether to equalise spacing within/between geometries. Default is True.

Return type:

tuple[PyGeometryPair, tuple[list[tuple[int, int, float, float, float, float, float]], list[tuple[int, int, float, float, float, float, float]]]]

Returns:

  • pair (PyGeometryPair) – Aligned diastole/systole geometry pair.

  • logs (tuple of list) – 2-tuple of alignment logs (logs_a, logs_b); each entry is a list of (id, matched_to, rel_rot_deg, total_rot_deg, tx, ty, centroid_x, centroid_y).

Examples

>>> import multimodars as mm
>>> pair, _ = mm.from_file_singlepair("data/ivus_rest")
multimodars.keep_labeled_points_from_mesh(results, region_key)[source]

Keep only the labeled region of vertices and remove everything else.

Retains only the vertices stored under region_key (and the faces that reference exclusively those vertices), remaps faces, and rebuilds every coordinate list in results to reflect the new vertex indices.

Parameters:
  • results (dict) – Dictionary containing at minimum the key "mesh". Any of "aorta_points", "rca_points", "lca_points", "rca_removed_points", "lca_removed_points", "proximal_points", and "distal_points" are also updated if present.

  • region_key (str | list[str]) – Key (or list of keys) in results whose point lists define the vertices to keep. When multiple keys are given the union of all their point sets is kept.

Returns:

Updated results dict with "mesh" replaced by the trimmed mesh and all other coordinate lists filtered to the surviving vertex set. A new "boundary_points" entry is added containing the open-boundary ring vertices adjacent to the removed region.

Return type:

dict

multimodars.label(path_ccta_geometry, path_centerline_aorta, path_centerline_rca, path_centerline_lca, aligned_frames, anomalous_rca=False, anomalous_lca=False, n_points_intramural=120, bounding_sphere_radius_mm=3.0, tolerance_float=1e-06, control_plot=True)[source]

Label CCTA mesh vertices as aorta, RCA, or LCA using centerline-based region detection.

Loads a 3-D surface mesh and three centerlines (aorta, RCA, LCA), then assigns each mesh vertex to one of the anatomical regions. For anomalous vessels an additional occlusion-removal step uses ray-triangle intersection to strip intramural segments, followed by adjacency-map reclassification to clean up isolated mis-labelled vertices. Herfore, a ray is cast from every aorta point to the centerline points of the anomalous section and if 3 faces are intersected by the ray the points from the first face must correspond to the intramural section.

Additionally partition a coronary region into proximal, anomalous, and distal sub-regions.

Uses the intravascular imaging frames to determine where along the centerline the anomalous (intramural) segment begins and ends, then tags each mesh vertex accordingly.

Parameters:
  • path_ccta_geometry (Path | str | Trimesh) – Path to the CCTA surface mesh file (any format supported by multimodars.io.read_geometrical.read_mesh()).

  • path_centerline_aorta (Path | str | PyCenterline) – Path to a CSV file containing the aortic centerline (comma-delimited, columns: x, y, z, …).

  • path_centerline_rca (Path | str | PyCenterline) – Path to a CSV file containing the RCA centerline.

  • path_centerline_lca (Path | str | PyCenterline) – Path to a CSV file containing the LCA centerline.

  • aligned_frames (list[PyFrame]) – Ordered list of intravascular imaging frames for the vessel.

  • anomalous_rca (bool) – When True applies ray-triangle occlusion removal to the RCA region to handle anomalous (intramural) courses. Default is False.

  • anomalous_lca (bool) – When True applies ray-triangle occlusion removal to the LCA region. Default is False.

  • n_points_intramural (int) – Number of coronary centerline points examined during occlusion removal (the intramural segment length). Default is 120.

  • bounding_sphere_radius_mm (float) – Radius in millimetres of the rolling sphere used to collect candidate mesh vertices around each centerline point. Default is 3.0.

  • tolerance_float (float) – Distance tolerance used when matching mesh vertices to points during face lookup. Default is 1e-6.

  • control_plot (bool) – When True opens an interactive 3-D scene showing the labelled mesh after processing. Default is True.

Return type:

tuple[dict, tuple[PyCenterline, PyCenterline, PyCenterline]]

Returns:

  • results (dict) – Dictionary with keys:

    • "mesh" - the original trimesh.Trimesh object.

    • "aorta_points" - list of (x, y, z) tuples for aortic vertices.

    • "rca_points" - list of (x, y, z) tuples for RCA vertices.

    • "lca_points" - list of (x, y, z) tuples for LCA vertices.

    • "rca_removed_points" - RCA vertices removed by occlusion detection.

    • "lca_removed_points" - LCA vertices removed by occlusion detection.

  • centerlines (tuple) – A 3-tuple (cl_rca, cl_lca, cl_aorta) of PyCenterline objects.

Raises:

Exception – Re-raises any error that occurs while reading the mesh or centerline files, after printing a descriptive message.

multimodars.label_anomalous_region(centerline, frames, results, results_key='rca_points', debug_plot=False)[source]

Partition a coronary region into proximal, anomalous, and distal sub-regions.

Uses the intravascular imaging frames to determine where along the centerline the anomalous (intramural) segment begins and ends, then tags each mesh vertex accordingly.

Parameters:
  • centerline (PyCenterline) – Centerline of the coronary vessel of interest.

  • frames (list of PyFrame) – Ordered list of intravascular imaging frames for the vessel.

  • results (dict) – Labelled results dictionary (e.g. from label_geometry()). Must contain the key specified by results_key.

  • results_key (str) – Key in results whose point list is partitioned. Default is "rca_points".

  • debug_plot (bool) – When True opens an interactive visualisation of the three sub-regions. Default is False.

Returns:

The input results dictionary extended with three new keys:

  • "proximal_points" - vertices proximal to the anomalous segment.

  • "distal_points" - vertices distal to the anomalous segment.

  • "anomalous_points" - vertices within the anomalous segment.

Return type:

dict

multimodars.label_branches(centerline, results, results_key='rca_points', branch_id=0, bounding_sphere_radius_mm=3.0)[source]

Partition a coronary region into main branch and per-side-branch point sets.

Parameters:
  • centerline (PyCenterline) – Centerline of the coronary vessel of interest.

  • results (dict) – Labelled results dictionary (e.g. from label_geometry()). Must contain the key specified by results_key.

  • results_key (str) – Key in results whose point list is partitioned. Default is "rca_points".

  • branch_id (int | list[int]) – Branch index or list of branch indices whose combined points form the main branch (e.g. [0, 1] for LAD + LCx). Default is 0.

  • bounding_sphere_radius_mm (float) – Radius of the rolling sphere used to collect candidate vertices. Default is 3.0.

Returns:

The input results dictionary extended with:

  • "{results_key}_main" - vertices along the main branch(es).

  • "{results_key}_side" - all remaining vertices (aggregate).

  • "{results_key}_side_{k}" - vertices near side branch k, one key per side branch discovered in centerline. A point may appear in more than one side-branch set when it sits near a bifurcation; the Voronoi inside discretize_vessel() resolves the assignment.

Return type:

dict

multimodars.label_geometry(path_ccta_geometry, path_centerline_aorta, path_centerline_rca, path_centerline_lca, anomalous_rca=False, anomalous_lca=False, n_points_intramural=120, bounding_sphere_radius_mm=3.0, tolerance_float=1e-06, control_plot=True)[source]

Label CCTA mesh vertices as aorta, RCA, or LCA using centerline-based region detection.

Loads a 3-D surface mesh and three centerlines (aorta, RCA, LCA), then assigns each mesh vertex to one of the anatomical regions. For anomalous vessels an additional occlusion-removal step uses ray-triangle intersection to strip intramural segments, followed by adjacency-map reclassification to clean up isolated mis-labelled vertices. Herfore, a ray is cast from every aorta point to the centerline points of the anomalous section and if 3 faces are intersected by the ray the points from the first face must correspond to the intramural section.

Parameters:
  • path_ccta_geometry (Path | str | Trimesh) – Path to the CCTA surface mesh file (any format supported by multimodars.io.read_geometrical.read_mesh()).

  • path_centerline_aorta (Path | str | PyCenterline) – Path to a CSV file containing the aortic centerline (comma-delimited, columns: x, y, z, …).

  • path_centerline_rca (Path | str | PyCenterline) – Path to a CSV file containing the RCA centerline.

  • path_centerline_lca (Path | str | PyCenterline) – Path to a CSV file containing the LCA centerline.

  • anomalous_rca (bool) – When True applies ray-triangle occlusion removal to the RCA region to handle anomalous (intramural) courses. Default is False.

  • anomalous_lca (bool) – When True applies ray-triangle occlusion removal to the LCA region. Default is False.

  • n_points_intramural (int) – Number of coronary centerline points examined during occlusion removal (the intramural segment length). Default is 120.

  • bounding_sphere_radius_mm (float) – Radius in millimetres of the rolling sphere used to collect candidate mesh vertices around each centerline point. Default is 3.0.

  • tolerance_float (float) – Distance tolerance used when matching mesh vertices to points during face lookup. Default is 1e-6.

  • control_plot (bool) – When True opens an interactive 3-D scene showing the labelled mesh after processing. Default is True.

Return type:

tuple[dict, tuple[PyCenterline, PyCenterline, PyCenterline]]

Returns:

  • results (dict) – Dictionary with keys:

    • "mesh" - the original trimesh.Trimesh object.

    • "aorta_points" - list of (x, y, z) tuples for aortic vertices.

    • "rca_points" - list of (x, y, z) tuples for RCA vertices.

    • "lca_points" - list of (x, y, z) tuples for LCA vertices.

    • "rca_removed_points" - RCA vertices removed by occlusion detection.

    • "lca_removed_points" - LCA vertices removed by occlusion detection.

  • centerlines (tuple) – A 3-tuple (cl_rca, cl_lca, cl_aorta) of PyCenterline objects.

Raises:

Exception – Re-raises any error that occurs while reading the mesh or centerline files, after printing a descriptive message.

multimodars.manual_hole_fill(mesh)[source]

Fill holes by fan-triangulating each boundary loop to its centroid.

Return type:

Trimesh

Parameters:

mesh (Trimesh)

multimodars.numpy_to_centerline(arr, aortic=False)[source]

Build a PyCenterline from a numpy array.

Linearly interpolates NaN values along each coordinate axis. Raises ValueError if an entire coordinate column is NaN or fewer than two points remain after processing.

Parameters:
  • arr (ndarray) – (N, 3) array where each row is (x, y, z).

  • aortic (bool) – Whether to mark each point as aortic. Default is False.

Returns:

centerline – Centerline object built from the provided points.

Return type:

PyCenterline

Raises:

ValueError – If arr is not (N, 3), is empty, all values in a coordinate column are NaN, or fewer than two points remain after interpolation.

multimodars.numpy_to_geometry(lumen_arr, eem_arr=None, catheter_arr=None, wall_arr=None, reference_arr=None, label='')[source]

Build a PyGeometry from numpy arrays, grouping by frame index into frames.

Each row in the array arguments must be [frame_index, x, y, z].

Parameters:
  • lumen_arr (ndarray) – (N, 4) array of lumen points [frame_index, x, y, z].

  • eem_arr (ndarray | None) – (M, 4) array of EEM points [frame_index, x, y, z]. Default is None.

  • catheter_arr (ndarray | None) – (K, 4) array of catheter points [frame_index, x, y, z]. Default is None.

  • wall_arr (ndarray | None) – (L, 4) array of wall points [frame_index, x, y, z]. Default is None.

  • reference_arr (ndarray | None) – (1, 4) or (4,) array [frame_index, x, y, z] for the reference point. Default is None.

  • label (str) – Label string for the geometry. Default is "".

Returns:

geometry – Geometry object with frames constructed from the provided arrays.

Return type:

PyGeometry

Raises:

ValueError – If lumen_arr is empty.

multimodars.numpy_to_inputdata(lumen_arr, ref_point, diastole, record=None, eem_arr=None, calcification=None, sidebranch=None, label='')[source]

Build a PyInputData from numpy arrays, grouping by frame index into frames.

Each row in the array arguments must be [frame_index, x, y, z].

Parameters:
  • lumen_arr (ndarray) – (N, 4) array of lumen points [frame_index, x, y, z].

  • record (ndarray | None) – (M, 4) array of records [frame, phase, measurement_1, measurement_2].

  • ref_point (ndarray) – (1, 4) or (4,) array [frame_index, x, y, z] for the reference point.

  • diastole (bool) – True if the data corresponds to the diastolic phase.

  • eem_arr (ndarray | None) – (N, 4) array of EEM points [frame_index, x, y, z]. Default is None.

  • calcification (ndarray | None) – (N, 4) array of calcification points. Default is None.

  • sidebranch (ndarray | None) – (N, 4) array of side-branch points. Default is None.

  • label (str) – Label string for the input data. Default is "".

Returns:

input_data – Input data object with contours grouped by frame index.

Return type:

PyInputData

Raises:

ValueError – If lumen_arr is empty.

multimodars.plot_centerline_edges(cl, cos_threshold=0.0)[source]

Open an interactive trimesh scene of a centerline with sharp angles highlighted.

Each branch is shown as a coloured point cloud. Positions flagged by find_sharp_angles are overlaid as red points so they are easy to spot and decide whether a split_branch call is needed.

Parameters:
  • cl (PyCenterline) – Centerline after calculate_branches (and optionally check_centerline).

  • cos_threshold (float) – Cosine threshold forwarded to cl.find_sharp_angles. 0.0 flags all angles ≥ 90 °; negative values flag increasingly obtuse bends.

Return type:

None

multimodars.plot_results_key(results, aorta_points=True, rca_points=False, lca_points=False, rca_removed_points=False, proximal_points=False, distal_points=False, anomalous_points=False, cl_rca=None, cl_lca=None, cl_aorta=None)[source]

Open an interactive 3-D scene visualising selected regions from a results dict.

Toggle which regions are shown by passing True/False for each flag.

Colour coding:

  • Yellow - aortic points.

  • Blue - RCA coronary points.

  • Green - LCA coronary points.

  • Red - removed / reassigned points (RCA + LCA combined).

  • Cyan - proximal points.

  • Magenta - distal points.

  • Orange - anomalous (intramural) points.

Parameters:
  • results (dict) – Dictionary with keys "aorta_points", "rca_points", "lca_points", "rca_removed_points", "proximal_points", "distal_points", "anomalous_points" - each a list of (x, y, z) tuples.

  • aorta_points (bool) – Show aortic points (yellow).

  • rca_points (bool) – Show RCA coronary points (blue).

  • lca_points (bool) – Show LCA coronary points (green).

  • rca_removed_points (bool) – Show removed RCA points (red).

  • proximal_points (bool) – Show proximal points (cyan).

  • distal_points (bool) – Show distal points (magenta).

  • anomalous_points (bool) – Show anomalous points (orange).

  • cl_rca (PyCenterline | None)

  • cl_lca (PyCenterline | None)

  • cl_aorta (PyCenterline | None)

multimodars.plot_sharp_angles(cl, branch_id, sharp_positions, context_pts=3)[source]

Open an interactive trimesh scene highlighting each sharp-angle position.

The full centerline is shown dimmed in gray. Each flagged position is overlaid in a distinct colour together with context_pts neighbours on each side so individual angles are easy to count and locate.

Return type:

None

Parameters:
  • cl (PyCenterline)

  • branch_id (int)

  • sharp_positions (list[int])

  • context_pts (int)

Colour coding

  • Gray (dim) — all centerline points (background).

  • Distinct colours per index — each sharp-angle position and its local context.

type cl:

PyCenterline

param cl:

Centerline after calculate_branches.

type branch_id:

int

param branch_id:

Branch that was inspected (0 = main vessel).

type sharp_positions:

list[int]

param sharp_positions:

Local positions within the branch as returned by find_sharp_angles() or cl.find_sharp_angles.

type context_pts:

int

param context_pts:

Number of neighbouring points shown on each side of each position.

multimodars.prepare_centerlines(rca_cl, lca_cl, results_dict, branch_sigma=2.0, control_plot=False)[source]

Compute branches, validate, and label both coronary centerlines.

This is the standard preparation step before discretize_vessel_tree(). It runs the non-interactive part of centerline setup automatically:

  1. rca_cl.calculate_branches(branch_sigma) + check_centerline()

  2. lca_cl.calculate_branches(branch_sigma) + check_centerline()

  3. label_branches() for RCA, then LCA

Note

Manual edits — find_sharp_angles, split_branch, merge_branches — cannot be automated. If your data needs them, call those methods on the returned centerlines before passing them to discretize_vessel_tree().

Parameters:
  • rca_cl (PyCenterline) – Raw centerlines as returned by numpy_to_centerline() or label_geometry().

  • lca_cl (PyCenterline) – Raw centerlines as returned by numpy_to_centerline() or label_geometry().

  • results_dict (dict) – Dictionary produced by label_geometry().

  • branch_sigma (float) – Smoothing sigma (mm) passed to calculate_branches for both vessels.

  • control_plot (bool) – When True, open an interactive Plotly 3-D visualisation showing centerline points coloured by branch ID and the labelled surface-mesh points, so you can verify assignments before discretizing (calls plot_centerline_branches()).

Return type:

tuple[PyCenterline, PyCenterline, dict]

Returns:

  • rca_cl (PyCenterline) – RCA centerline with branches computed, validated, and labelled.

  • lca_cl (PyCenterline) – LCA centerline with branches computed, validated, and labelled.

  • results_dict (dict) – Updated dictionary with rca_points_main, rca_points_side_N, lca_points_main, and lca_points_side_N keys added.

multimodars.remove_labeled_points_from_mesh(results, region_keys='anomalous_points')[source]

Remove one or more labeled regions of vertices from the mesh.

Collects all points stored under region_keys, deletes the corresponding vertices (and any faces referencing them) from the mesh, remaps the remaining faces, and rebuilds every coordinate list in results to reflect the new vertex indices.

Parameters:
  • results (dict) – Dictionary containing at minimum the key "mesh". Any of "aorta_points", "rca_points", "lca_points", "rca_removed_points", "lca_removed_points", "proximal_points", and "distal_points" are also updated if present.

  • region_keys (list[str] | str) – Key(s) in results whose point lists should be removed from the mesh. Defaults to "anomalous_points" for backwards compatibility.

Returns:

Updated results dict with "mesh" replaced by the trimmed mesh, all region_keys cleared, and all other coordinate lists remapped to the new vertex set. A new "boundary_points" entry is added containing the open-boundary ring vertices adjacent to the removed region(s).

Return type:

dict

multimodars.scale(results, cl_vessel, cl_aorta, aligned_frames)[source]

Scale the distal, proximal, and aortic regions of the vessel mesh.

  1. Computes proximal and distal radial scaling factors via manipulating.find_distal_and_proximal_scaling(), which matches the anomalous segment endpoints to lumen wall points from the first and last intravascular frames.

  2. Computes an aortic radial scaling factor via manipulating.find_aorta_scaling(), which aligns removed RCA points to reconstructed aortic wall points from the frames.

  3. Applies the scaling in sequence — distal, then aortic (aorta_points + rca_removed_points), then proximal — using manipulating.scale_region_centerline_morphing(), which displaces each vertex radially around the nearest centerline point.

  4. After each aortic/proximal scaling step, manipulating.sync_results_to_mesh() remaps all coordinate lists in results to the updated vertex positions.

Parameters:
  • results (dict) –

    Labelled results dictionary containing at minimum:

    • "mesh" - the trimesh.Trimesh to scale.

    • "anomalous_points" - points in the anomalous segment.

    • "distal_points" - vertices of the distal region.

    • "proximal_points" - vertices of the proximal region.

    • "aorta_points" - vertices of the aortic wall region.

    • "rca_removed_points" - RCA vertices removed by occlusion detection.

  • cl_vessel (PyCenterline) – Centerline of the vessel (used for proximal/distal scaling).

  • cl_aorta (PyCenterline) – Centerline of the aorta (used for aortic scaling).

  • aligned_frames (list[PyFrame]) – Ordered intravascular imaging frames, used as the reference geometry for computing all three scaling factors.

Returns:

Updated results with "mesh" replaced by the fully scaled mesh and all coordinate lists remapped to the new vertex positions.

Return type:

dict

multimodars.scale_region_centerline_morphing(mesh, region_points, centerline, diameter_adjustment_mm)[source]

Scale a mesh region radially around its centerline.

Each vertex in region_points is displaced along the direction from the nearest centerline point outward (positive diameter_adjustment_mm) or inward (negative).

Parameters:
  • mesh (Trimesh) – The original mesh. A copy is returned; the input is not modified.

  • region_points (list) – (x, y, z) coordinates of the vertices to be scaled. Only vertices present in this list are moved.

  • centerline (PyCenterline) – Centerline of the vessel region used as the morphing axis.

  • diameter_adjustment_mm (float) – Diameter change in millimetres. Positive values expand the lumen; negative values contract it.

Returns:

A new mesh with the selected region scaled.

Return type:

Trimesh

Warns:
  • If no vertices matching *region_points* are found in the mesh, a warning

  • is printed and the unmodified copy is returned.

multimodars.stitch(results, geometry, postprocessing=False, region_remove=['anomalous_points', 'proximal_points'], prox_start_mode='highest_z', dist_start_mode='nearest_iv', **postprocessing_kwargs)[source]

Stitch a CCTA mesh to the intravascular geometry and optionally remesh.

Removes labeled anatomical regions from the CCTA mesh, then stitches the remaining surface to the intravascular geometry reconstructed from geometry.

Anomalous vessel: The recommended approach for anomalies is to remove anomalous points and proximal points and stitch the intravascular vessel directly to aorta with highest_z approach (default).

When postprocessing is True and pymeshlab is installed, the stitched mesh is repaired, isotropically remeshed, and smoothed with a Taubin filter before being returned.

Parameters:
  • results (dict) – Labelled results dictionary (output of scale()), containing at minimum "mesh" and the point-label lists produced by label().

  • geometry (PyGeometry) – Intravascular imaging geometry whose contours define the vessel lumen used as the stitching target.

  • postprocessing (bool) – When True, run fixing_functions.fix_and_remesh_stitched_mesh() followed by Taubin smoothing on the stitched mesh. Silently skipped if pymeshlab is not installed. Default is False.

  • prox_start_mode (str) – How to choose index 0 of the proximal boundary ring before stitching. "nearest_iv" (default) rotates to the point closest to IV point 0; "highest_z" rotates to the point with the largest z-coordinate.

  • dist_start_mode (str) – Same as prox_start_mode but for the distal boundary ring.

  • **postprocessing_kwargs – Keyword arguments forwarded to fixing_functions.fix_and_remesh_stitched_mesh(), e.g. target_edge_length_mm, remesh_iterations, verbose.

  • region_remove (list[str] | str)

Returns:

Stitched results dictionary with the same structure as results, where "mesh" is the stitched (and optionally postprocessed) surface.

Return type:

dict

multimodars.stitch_ccta_to_intravascular(iv_mesh, mesh, results, n_points_iv_cont=100, prox_start_mode='nearest_iv', dist_start_mode='nearest_iv', proximal_is_ostium=True, clamp_overshoot=0.5)[source]

Stitch an aligned intravascular mesh to a CCTA mesh.

prox_start_mode / dist_start_mode control how index 0 of each boundary ring is chosen before stitching:

  • "nearest_iv" (default) - rotate to the point closest to IV point 0.

  • "highest_z" - rotate to the point with the largest z-coordinate.

clamp_overshoot sets the minimum distance (mm) that every proximal boundary point must sit away from the IV plane after clamping. Points that land too close are pushed further until they are exactly clamp_overshoot mm from the plane, creating a slight inward step that softens the stitching angle. The two mesh rings adjacent to the boundary are also pushed radially outward (ring 1: 0.1 mm, ring 2: 0.2 mm) within the IV plane to avoid ridges at the clamping zone. Only active when the boundary-ring plane and the IV plane form an angle ≥ ostium_angle_threshold_deg (default 45°).

Return type:

dict

Parameters:
  • iv_mesh (PyGeometry)

  • mesh (Trimesh)

  • results (dict)

  • n_points_iv_cont (int)

  • prox_start_mode (str)

  • dist_start_mode (str)

  • proximal_is_ostium (bool)

  • clamp_overshoot (float)

multimodars.sync_results_to_mesh(results, old_mesh, new_mesh)[source]

Update all coordinate lists in results after vertices have been moved.

Use this after scale_region_centerline_morphing() to keep the stored point lists consistent with the new vertex positions. The two meshes must have the same vertex count and ordering (only positions change, no vertices are added or removed).

Parameters:
  • results (dict) – Results dict whose coordinate lists should be refreshed.

  • old_mesh (Trimesh) – The mesh whose vertex positions match the current coordinate lists.

  • new_mesh (Trimesh) – The mesh with updated vertex positions (same indices, new coordinates).

Returns:

Updated results with "mesh" replaced by new_mesh and all coordinate lists remapped to the new vertex positions.

Return type:

dict

multimodars.to_array(generic)[source]

Convert various multimodars Py* objects into numpy array(s) or dictionaries of arrays.

Parameters:

generic (PyContour, PyCenterline, PyGeometry, PyGeometryPair, PyFrame, or PyInputData) – The object to be converted to numpy representation.

Return type:

ndarray | dict | tuple[dict, dict]

Returns:

  • np.ndarray – For PyContour or PyCenterline: A 2D array of shape (N, 4), where each row is (frame_index, x, y, z).

  • dict[str, np.ndarray] – For PyGeometry: A dictionary with keys for each contour type and “reference”, each containing a 2D array of shape (M, 4), where M is the number of points in that layer.

  • tuple[dict[str, np.ndarray], dict[str, np.ndarray]] – For PyGeometryPair: A tuple of two dictionaries (one for geom_a, one for geom_b), each in the same format as returned for a single PyGeometry.

  • dict[str, np.ndarray | list[str] | bool] – For PyInputData: A dictionary containing arrays for each contour type and metadata.

Raises:

TypeError – If the input type is not one of the supported multimodars types.

multimodars.to_obj(geometry, output_path, watertight=True, contour_types=None, filename_prefix='')[source]

Convert a PyGeometry object into OBJ files and write them to disk.

Writes the specified contour types as OBJ meshes without UV coordinates. Each contour type is written to its own file together with a corresponding MTL material file.

Parameters:
  • geometry (PyGeometry) – Input geometry instance containing the mesh data.

  • output_path (str) – Directory path where the OBJ and MTL files will be written.

  • watertight (bool) – Whether to write a watertight or shell mesh. Default is True.

  • contour_types (list[PyContourType] | None) – Contour types to export. Default is [PyContourType.Lumen, PyContourType.Catheter, PyContourType.Wall].

  • filename_prefix (str) – Optional prefix prepended to all output filenames. Default is "".

Raises:

RuntimeError – If any of the underlying file writes fail.

Return type:

None

Examples

>>> import multimodars as mm
>>> mm.to_obj(geometry, "output/meshes", watertight=True)