Core Functions
- class multimodars.PyCenterline(points)
Bases:
objectPython representation of a vessel centerline.
- points
Ordered list of centerline points.
- Type:
list of PyCenterlinePoint
- branch_start_indices
Index into
pointswhere each branch begins. Entry 0 is always 0 (the main vessel); subsequent entries mark the start of side branches. Read-only — recomputed bycalculate_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_idassigned on every point.Points whose mutual distance is ≤
spacing_tolerance × median_nn_spacingare 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.5is a reasonable starting value; increase it if branches are incorrectly split, decrease it if distinct branches are incorrectly merged.- Returns:
New centerline with
branch_idset on every point andbranch_start_indicespopulated.- Return type:
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:
- 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
PyContourPointobjects.- Parameters:
contour_points (list of PyContourPoint) – Ordered sequence of contour points.
- Returns:
Centerline constructed from the provided points.
- Return type:
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 = 0andbranch_start_indicesis reset to[0].- Parameters:
branch_id (int) – Branch to extract.
- Returns:
Single-branch centerline with the requested points.
- Return type:
- Raises:
ValueError – If
branch_iddoes 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:
- 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:
- class multimodars.PyCenterlinePoint(contour_point, normal, branch_id=0)
Bases:
objectPython 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:
- 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:
objectPython 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:
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:
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:
Examples
>>> contour = contour.translate((0.0, 1.0, 2.0))
- class multimodars.PyContourPoint(frame_index, point_index, x, y, z, aortic)
Bases:
objectPython 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
Truewhen 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:
objectPython 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:
objectFully 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_idi + 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, andao_lcareflect 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:
objectPython 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:
- 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:
- sort_frame_points()
Sort contour points in the frame by their angular position.
- Returns:
frame – New frame with contour points sorted angularly.
- Return type:
- 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:
- class multimodars.PyGeometry(frames, label)
Bases:
objectPython 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:
- downsample(n_points)
Return a new geometry with
n_pointsper ContourType.- Parameters:
n_points (int) – Number of points remaining per Contour.
- Returns:
New downsampled geometry.
- Return type:
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:
- Raises:
IndexError – If
indexis 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:
- 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
indexreplaced byframe.- 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:
- Raises:
IndexError – If
indexis 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:
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:
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 thepoint_indexfields are reassigned.- Returns:
New geometry with re-indexed frames.
- Return type:
- 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:
- class multimodars.PyGeometryPair(geom_a, geom_b, label)
Bases:
objectPython representation of a diastolic/systolic geometry pair.
- geom_a
First geometry (typically diastolic).
- Type:
- geom_b
Second geometry (typically systolic).
- Type:
- 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:
objectPython 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:
- diastole
Truewhen 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:
objectPython 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 is1.0.angle_range_deg (
float) – Total rotation search range in degrees. Default is15.0.index_range (
int) – Number of centerline indices considered around the reference. Default is2.write (
bool) – Whether to write the aligned meshes to OBJ files. Default isFalse.watertight (
bool) – Whether to write a watertight or shell mesh. Default isTrue.interpolation_steps (
int) – Number of interpolation steps between phases. Only used when geometry is aPyGeometryPair. Default is0.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) – WhenTrue, 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 isFalse.
- 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 isFalse.watertight (
bool) – Whether to write a watertight or shell mesh. Default isTrue.interpolation_steps (
int) – Number of interpolation steps between phases. Only used when geometry is aPyGeometryPair. Default is0.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) – WhenTrue, 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 isFalse.
- 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 is1.0.write (
bool) – Whether to write the aligned meshes to OBJ files. Default isFalse.watertight (
bool) – Whether to write a watertight or shell mesh. Default isTrue.interpolation_steps (
int) – Number of interpolation steps between phases. Only used when geometry is aPyGeometryPair. Default is0.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) – WhenTrue, 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 isFalse.
- 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:
frames (list[PyFrame] | None)
cl_aorta (PyCenterline)
cl_rca (PyCenterline)
cl_lca (PyCenterline)
results (dict)
aortic_scaling (float | None)
coronary_scaling (float)
- 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 exactlyn_pointsevenly-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 is0.step_size (
float) – Arc-length distance between consecutive cross-sections in the same units ascenterlineandpoints. Default is0.5.n_points (
int) – Number of evenly-spaced points per output contour. Default is200.
- Returns:
contours – One contour per surviving cross-section, each containing exactly
n_pointsuniformly 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, …). Useprepare_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 bylabel_branches()containing keysaorta_points,rca_points_main,lca_points_main, and anyrca_points_side_N/lca_points_side_Nentries.branch_id_rca (
int) – Main-vessel branch IDs (almost always0).branch_id_lca (
int) – Main-vessel branch IDs (almost always0).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) – WhenTrue, all contours are replaced with closed B-spline fits before reference points are computed.control_plot (
bool) – WhenTrue, open an interactive Plotly 3-D visualisation of the finished tree (callsplot_vessel_tree()).bspline_smoothing (
float) –Smoothing condition
sforscipy.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:
- 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 bylabel()/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 whenNone.
- 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_thicknessand the"Wall"extras) as a reference, then calls the Rustfind_aortic_scalingroutine to determine the factor that best aligns the removed RCA points to those references.- Parameters:
frames (
list[PyFrame]) – Intravascular imaging frames containingaortic_thicknessandextras["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_scalingroutine 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 is3.prox_range (
int) – Number of frames from the proximal end used as the proximal reference. Default is2.
- 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_anglesthat 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 callsplit_branch/merge_branches.- Parameters:
cl (
PyCenterline) – Centerline aftercalculate_branches(and optionallycheck_centerline).branch_id (
int) – Branch to inspect (0 = main vessel).cos_threshold (
float) – Cosine above which an angle is considered sharp. Use0.0for < 90°,0.5for < 60°,0.866for < 30°.control_plot (
bool) – WhenTrueopens 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
Repair non-manifold edges/vertices.
Close holes - flat fill (all holes).
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
PyInputDatapairs 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 is0.5.range_rotation_deg (
float) – Rotation search range (±) in degrees. Default is90.0.sample_size (
int) – Number of points to downsample to. Default is500.image_center (
tuple[float,float]) – Image center(x, y)in mm. Default is(4.5, 4.5).radius (
float) – Catheter radius in mm. Default is0.5.n_points (
int) – Number of catheter points. Default is20.write_obj (
bool) – Whether to write OBJ files. Default isTrue.watertight (
bool) – Whether to write a watertight or shell mesh. Default isTrue.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 is28.bruteforce (
bool) – Whether to use brute-force alignment. Default isFalse.smooth (
bool) – Whether to smooth frames after alignment. Default isTrue.postprocessing (
bool) – Whether to equalise spacing within/between geometries. Default isTrue.
- 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
PyInputDataobjects 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 is0.5.range_rotation_deg (
float) – Rotation search range (±) in degrees. Default is90.0.sample_size (
int) – Number of points to downsample to. Default is500.image_center (
tuple[float,float]) – Image center(x, y)in mm. Default is(4.5, 4.5).radius (
float) – Catheter radius in mm. Default is0.5.n_points (
int) – Number of catheter points. Default is20.write_obj (
bool) – Whether to write OBJ files. Default isTrue.watertight (
bool) – Whether to write a watertight or shell mesh. Default isTrue.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 is28.bruteforce (
bool) – Whether to use brute-force alignment. Default isFalse.smooth (
bool) – Whether to smooth frames after alignment. Default isTrue.postprocessing (
bool) – Whether to equalise spacing within/between geometries. Default isTrue.
- 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
PyInputDataobject.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 is0.5.range_rotation_deg (
float) – Rotation search range (±) in degrees. Default is90.0.sample_size (
int) – Number of points to downsample to. Default is500.image_center (
tuple[float,float]) – Image center(x, y)in mm. Default is(4.5, 4.5).radius (
float) – Catheter radius in mm. Default is0.5.n_points (
int) – Number of catheter points. Default is20.write_obj (
bool) – Whether to write OBJ files. Default isFalse.watertight (
bool) – Whether to write a watertight or shell mesh. Default isTrue.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 isFalse.smooth (
bool) – Whether to smooth frames after alignment. Default isTrue.
- 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
PyInputDataobjects (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 is0.5.range_rotation_deg (
float) – Rotation search range (±) in degrees. Default is90.0.sample_size (
int) – Number of points to downsample to. Default is500.image_center (
tuple[float,float]) – Image center(x, y)in mm. Default is(4.5, 4.5).radius (
float) – Catheter radius in mm. Default is0.5.n_points (
int) – Number of catheter points. Default is20.write_obj (
bool) – Whether to write OBJ files. Default isTrue.watertight (
bool) – Whether to write a watertight or shell mesh. Default isTrue.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 is28.bruteforce (
bool) – Whether to use brute-force alignment. Default isFalse.smooth (
bool) – Whether to smooth frames after alignment. Default isTrue.postprocessing (
bool) – Whether to equalise spacing within/between geometries. Default isTrue.
- 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 systoleWarning
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 diastolicaand systolicbCSVs).input_path_cd (
str) – Path to the STRESS input folder (contains diastoliccand systolicdCSVs).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 is0.5.range_rotation_deg (
float) – Rotation search range (±) in degrees. Default is90.0.sample_size (
int) – Number of points to downsample to. Default is500.image_center (
tuple[float,float]) – Image center(x, y)in mm. Default is(4.5, 4.5).radius (
float) – Catheter radius in mm. Default is0.5.n_points (
int) – Number of catheter points. Default is20.write_obj (
bool) – Whether to write OBJ files. Default isTrue.watertight (
bool) – Whether to write a watertight or shell mesh. Default isTrue.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 is28.bruteforce (
bool) – Whether to use brute-force alignment. Default isFalse.smooth (
bool) – Whether to smooth frames after alignment. Default isTrue.postprocessing (
bool) – Whether to equalise spacing within/between geometries. Default isTrue.
- 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 diastolicaand systolicbCSVs).input_path_cd (
str) – Path to the STRESS input folder (contains diastoliccand systolicdCSVs).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 is0.5.range_rotation_deg (
float) – Rotation search range (±) in degrees; a range of 90° gives 180° total. Default is90.0.sample_size (
int) – Number of points to downsample to during alignment. Default is500.image_center (
tuple[float,float]) – Image center(x, y)in mm. Default is(4.5, 4.5).radius (
float) – Catheter radius in mm. Default is0.5.n_points (
int) – Number of catheter points; more points increases the influence of the image center. Default is20.write_obj (
bool) – Whether to write OBJ files to disk. Default isTrue.watertight (
bool) – Whether to write a watertight or shell mesh. Default isTrue.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 is28.bruteforce (
bool) – Whether to use brute-force alignment (one comparison per step). Default isFalse.smooth (
bool) – Whether to smooth frames after alignment using a 3-point moving average. Default isTrue.postprocessing (
bool) – Whether to adjust spacing within/between geometries to equal offsets. Default isTrue.
- 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) – WhenTrueprocess the diastolic phase; otherwise systole. Default isTrue.step_rotation_deg (
float) – Rotation step in degrees. Default is0.5.range_rotation_deg (
float) – Rotation search range (±) in degrees. Default is90.0.sample_size (
int) – Number of points to downsample to. Default is500.image_center (
tuple[float,float]) – Image center(x, y)in mm. Default is(4.5, 4.5).radius (
float) – Catheter radius in mm. Default is0.5.n_points (
int) – Number of catheter points. Default is20.write_obj (
bool) – Whether to write OBJ files. Default isTrue.watertight (
bool) – Whether to write a watertight or shell mesh. Default isTrue.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 isFalse.smooth (
bool) – Whether to smooth frames after alignment. Default isTrue.
- 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 │ ▼ systoleWarning
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 is0.5.range_rotation_deg (
float) – Rotation search range (±) in degrees. Default is90.0.sample_size (
int) – Number of points to downsample to. Default is500.image_center (
tuple[float,float]) – Image center(x, y)in mm. Default is(4.5, 4.5).radius (
float) – Catheter radius in mm. Default is0.5.n_points (
int) – Number of catheter points. Default is20.write_obj (
bool) – Whether to write OBJ files. Default isTrue.watertight (
bool) – Whether to write a watertight or shell mesh. Default isTrue.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 is28.bruteforce (
bool) – Whether to use brute-force alignment. Default isFalse.smooth (
bool) – Whether to smooth frames after alignment. Default isTrue.postprocessing (
bool) – Whether to equalise spacing within/between geometries. Default isTrue.
- 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 bymultimodars.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) – WhenTrueapplies ray-triangle occlusion removal to the RCA region to handle anomalous (intramural) courses. Default isFalse.anomalous_lca (
bool) – WhenTrueapplies ray-triangle occlusion removal to the LCA region. Default isFalse.n_points_intramural (
int) – Number of coronary centerline points examined during occlusion removal (the intramural segment length). Default is120.bounding_sphere_radius_mm (
float) – Radius in millimetres of the rolling sphere used to collect candidate mesh vertices around each centerline point. Default is3.0.tolerance_float (
float) – Distance tolerance used when matching mesh vertices to points during face lookup. Default is1e-6.control_plot (
bool) – WhenTrueopens an interactive 3-D scene showing the labelled mesh after processing. Default isTrue.
- Return type:
tuple[dict,tuple[PyCenterline,PyCenterline,PyCenterline]]- Returns:
results (dict) – Dictionary with keys:
"mesh"- the originaltrimesh.Trimeshobject."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)ofPyCenterlineobjects.
- 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. fromlabel_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) – WhenTrueopens an interactive visualisation of the three sub-regions. Default isFalse.
- 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. fromlabel_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 is0.bounding_sphere_radius_mm (
float) – Radius of the rolling sphere used to collect candidate vertices. Default is3.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 insidediscretize_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 bymultimodars.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) – WhenTrueapplies ray-triangle occlusion removal to the RCA region to handle anomalous (intramural) courses. Default isFalse.anomalous_lca (
bool) – WhenTrueapplies ray-triangle occlusion removal to the LCA region. Default isFalse.n_points_intramural (
int) – Number of coronary centerline points examined during occlusion removal (the intramural segment length). Default is120.bounding_sphere_radius_mm (
float) – Radius in millimetres of the rolling sphere used to collect candidate mesh vertices around each centerline point. Default is3.0.tolerance_float (
float) – Distance tolerance used when matching mesh vertices to points during face lookup. Default is1e-6.control_plot (
bool) – WhenTrueopens an interactive 3-D scene showing the labelled mesh after processing. Default isTrue.
- Return type:
tuple[dict,tuple[PyCenterline,PyCenterline,PyCenterline]]- Returns:
results (dict) – Dictionary with keys:
"mesh"- the originaltrimesh.Trimeshobject."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)ofPyCenterlineobjects.
- 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
PyCenterlinefrom a numpy array.Linearly interpolates NaN values along each coordinate axis. Raises
ValueErrorif 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 isFalse.
- Returns:
centerline – Centerline object built from the provided points.
- Return type:
- Raises:
ValueError – If
arris 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
PyGeometryfrom 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 isNone.catheter_arr (
ndarray|None) –(K, 4)array of catheter points[frame_index, x, y, z]. Default isNone.wall_arr (
ndarray|None) –(L, 4)array of wall points[frame_index, x, y, z]. Default isNone.reference_arr (
ndarray|None) –(1, 4)or(4,)array[frame_index, x, y, z]for the reference point. Default isNone.label (
str) – Label string for the geometry. Default is"".
- Returns:
geometry – Geometry object with frames constructed from the provided arrays.
- Return type:
- Raises:
ValueError – If
lumen_arris empty.
- multimodars.numpy_to_inputdata(lumen_arr, ref_point, diastole, record=None, eem_arr=None, calcification=None, sidebranch=None, label='')[source]
Build a
PyInputDatafrom 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) –Trueif the data corresponds to the diastolic phase.eem_arr (
ndarray|None) –(N, 4)array of EEM points[frame_index, x, y, z]. Default isNone.calcification (
ndarray|None) –(N, 4)array of calcification points. Default isNone.sidebranch (
ndarray|None) –(N, 4)array of side-branch points. Default isNone.label (
str) – Label string for the input data. Default is"".
- Returns:
input_data – Input data object with contours grouped by frame index.
- Return type:
- Raises:
ValueError – If
lumen_arris 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_anglesare overlaid as red points so they are easy to spot and decide whether asplit_branchcall is needed.- Parameters:
cl (
PyCenterline) – Centerline aftercalculate_branches(and optionallycheck_centerline).cos_threshold (
float) – Cosine threshold forwarded tocl.find_sharp_angles.0.0flags 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/Falsefor 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:
- 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()orcl.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:rca_cl.calculate_branches(branch_sigma)+check_centerline()lca_cl.calculate_branches(branch_sigma)+check_centerline()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 todiscretize_vessel_tree().- Parameters:
rca_cl (
PyCenterline) – Raw centerlines as returned bynumpy_to_centerline()orlabel_geometry().lca_cl (
PyCenterline) – Raw centerlines as returned bynumpy_to_centerline()orlabel_geometry().results_dict (
dict) – Dictionary produced bylabel_geometry().branch_sigma (
float) – Smoothing sigma (mm) passed tocalculate_branchesfor both vessels.control_plot (
bool) – WhenTrue, 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 (callsplot_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, andlca_points_side_Nkeys 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.
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.Computes an aortic radial scaling factor via
manipulating.find_aorta_scaling(), which aligns removed RCA points to reconstructed aortic wall points from the frames.Applies the scaling in sequence — distal, then aortic (
aorta_points+rca_removed_points), then proximal — usingmanipulating.scale_region_centerline_morphing(), which displaces each vertex radially around the nearest centerline point.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"- thetrimesh.Trimeshto 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
Trueand 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 ofscale()), containing at minimum"mesh"and the point-label lists produced bylabel().geometry (
PyGeometry) – Intravascular imaging geometry whose contours define the vessel lumen used as the stitching target.postprocessing (
bool) – WhenTrue, runfixing_functions.fix_and_remesh_stitched_mesh()followed by Taubin smoothing on the stitched mesh. Silently skipped if pymeshlab is not installed. Default isFalse.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_modecontrol 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_overshootsets 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 exactlyclamp_overshootmm 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
PyGeometryobject 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 isTrue.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)