cardio 2025.9.0__py3-none-any.whl → 2025.10.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cardio/__init__.py +2 -1
- cardio/app.py +6 -13
- cardio/logic.py +151 -18
- cardio/mesh.py +1 -2
- cardio/object.py +7 -7
- cardio/orientation.py +215 -0
- cardio/scene.py +74 -43
- cardio/segmentation.py +5 -3
- cardio/ui.py +231 -32
- cardio/utils.py +4 -47
- cardio/volume.py +124 -115
- {cardio-2025.9.0.dist-info → cardio-2025.10.1.dist-info}/METADATA +2 -2
- {cardio-2025.9.0.dist-info → cardio-2025.10.1.dist-info}/RECORD +15 -14
- {cardio-2025.9.0.dist-info → cardio-2025.10.1.dist-info}/WHEEL +0 -0
- {cardio-2025.9.0.dist-info → cardio-2025.10.1.dist-info}/entry_points.txt +0 -0
cardio/utils.py
CHANGED
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import enum
|
|
4
4
|
|
|
5
|
-
import itk
|
|
6
|
-
import numpy as np
|
|
7
|
-
|
|
8
5
|
|
|
9
6
|
class InterpolatorType(enum.Enum):
|
|
10
7
|
"""Interpolation methods for image resampling."""
|
|
@@ -13,51 +10,11 @@ class InterpolatorType(enum.Enum):
|
|
|
13
10
|
NEAREST = "nearest"
|
|
14
11
|
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
):
|
|
19
|
-
"""Reset image direction to identity matrix, preserving origin.
|
|
20
|
-
|
|
21
|
-
This function handles the VTK reader issue where origin is not retained
|
|
22
|
-
by using ITK to properly transform the image coordinates.
|
|
13
|
+
class AngleUnit(enum.Enum):
|
|
14
|
+
"""Units for angle measurements."""
|
|
23
15
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
interpolator_type: InterpolatorType enum for interpolation method
|
|
27
|
-
"""
|
|
28
|
-
origin = np.asarray(itk.origin(image))
|
|
29
|
-
spacing = np.asarray(itk.spacing(image))
|
|
30
|
-
size = np.asarray(itk.size(image))
|
|
31
|
-
direction = np.asarray(image.GetDirection())
|
|
32
|
-
|
|
33
|
-
direction[direction == 1] = 0
|
|
34
|
-
origin += np.dot(size, np.dot(np.diag(spacing), direction))
|
|
35
|
-
direction = np.identity(3)
|
|
36
|
-
|
|
37
|
-
origin = itk.Point[itk.F, 3](origin)
|
|
38
|
-
spacing = itk.spacing(image)
|
|
39
|
-
size = itk.size(image)
|
|
40
|
-
direction = itk.matrix_from_array(direction)
|
|
41
|
-
|
|
42
|
-
# Select interpolator based on type
|
|
43
|
-
match interpolator_type:
|
|
44
|
-
case InterpolatorType.NEAREST:
|
|
45
|
-
interpolator = itk.NearestNeighborInterpolateImageFunction.New(image)
|
|
46
|
-
case InterpolatorType.LINEAR:
|
|
47
|
-
interpolator = itk.LinearInterpolateImageFunction.New(image)
|
|
48
|
-
case _:
|
|
49
|
-
raise ValueError(f"Unsupported interpolator type: {interpolator_type}")
|
|
50
|
-
|
|
51
|
-
output = itk.resample_image_filter(
|
|
52
|
-
image,
|
|
53
|
-
size=size,
|
|
54
|
-
interpolator=interpolator,
|
|
55
|
-
output_spacing=spacing,
|
|
56
|
-
output_origin=origin,
|
|
57
|
-
output_direction=direction,
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
return output
|
|
16
|
+
DEGREES = "degrees"
|
|
17
|
+
RADIANS = "radians"
|
|
61
18
|
|
|
62
19
|
|
|
63
20
|
def calculate_combined_bounds(actors):
|
cardio/volume.py
CHANGED
|
@@ -6,45 +6,21 @@ import pydantic as pc
|
|
|
6
6
|
import vtk
|
|
7
7
|
|
|
8
8
|
from .object import Object
|
|
9
|
-
from .
|
|
9
|
+
from .orientation import (
|
|
10
|
+
EulerAxis,
|
|
11
|
+
axcode_transform_matrix,
|
|
12
|
+
create_vtk_reslice_matrix,
|
|
13
|
+
euler_angle_to_rotation_matrix,
|
|
14
|
+
reset_direction,
|
|
15
|
+
)
|
|
10
16
|
from .volume_property_presets import load_volume_property_preset
|
|
11
17
|
|
|
12
18
|
|
|
13
|
-
def create_rotation_matrix(axis, angle_degrees):
|
|
14
|
-
"""Create rotation matrix for given axis and angle."""
|
|
15
|
-
angle = np.radians(angle_degrees)
|
|
16
|
-
cos_a, sin_a = np.cos(angle), np.sin(angle)
|
|
17
|
-
if axis == "X":
|
|
18
|
-
return np.array([[1, 0, 0], [0, cos_a, -sin_a], [0, sin_a, cos_a]])
|
|
19
|
-
elif axis == "Y":
|
|
20
|
-
return np.array([[cos_a, 0, sin_a], [0, 1, 0], [-sin_a, 0, cos_a]])
|
|
21
|
-
elif axis == "Z":
|
|
22
|
-
return np.array([[cos_a, -sin_a, 0], [sin_a, cos_a, 0], [0, 0, 1]])
|
|
23
|
-
return np.eye(3)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def create_reslice_matrix(normal, up, origin):
|
|
27
|
-
"""Create a 4x4 reslice matrix from normal vector, up vector, and origin"""
|
|
28
|
-
normal = normal / np.linalg.norm(normal)
|
|
29
|
-
up = up / np.linalg.norm(up)
|
|
30
|
-
right = np.cross(normal, up)
|
|
31
|
-
right = right / np.linalg.norm(right)
|
|
32
|
-
up = np.cross(right, normal)
|
|
33
|
-
matrix = vtk.vtkMatrix4x4()
|
|
34
|
-
for i in range(3):
|
|
35
|
-
matrix.SetElement(i, 0, right[i])
|
|
36
|
-
matrix.SetElement(i, 1, up[i])
|
|
37
|
-
matrix.SetElement(i, 2, normal[i])
|
|
38
|
-
matrix.SetElement(i, 3, origin[i])
|
|
39
|
-
matrix.SetElement(3, 3, 1.0)
|
|
40
|
-
return matrix
|
|
41
|
-
|
|
42
|
-
|
|
43
19
|
class Volume(Object):
|
|
44
20
|
"""Volume object with transfer functions and clipping support."""
|
|
45
21
|
|
|
46
22
|
pattern: str = pc.Field(
|
|
47
|
-
default="
|
|
23
|
+
default="{frame}.nii.gz",
|
|
48
24
|
description="Filename pattern with $frame placeholder",
|
|
49
25
|
)
|
|
50
26
|
transfer_function_preset: str = pc.Field(
|
|
@@ -62,7 +38,7 @@ class Volume(Object):
|
|
|
62
38
|
logging.info(f"{self.label}: Loading frame {frame}.")
|
|
63
39
|
|
|
64
40
|
image = itk.imread(path)
|
|
65
|
-
image = reset_direction(image
|
|
41
|
+
image = reset_direction(image)
|
|
66
42
|
image = itk.vtk_image_from_image(image)
|
|
67
43
|
|
|
68
44
|
mapper = vtk.vtkGPUVolumeRayCastMapper()
|
|
@@ -113,12 +89,12 @@ class Volume(Object):
|
|
|
113
89
|
reslice.SetInputData(image_data)
|
|
114
90
|
reslice.SetOutputDimensionality(2)
|
|
115
91
|
reslice.SetInterpolationModeToLinear()
|
|
116
|
-
reslice.SetBackgroundLevel(-1000.0)
|
|
92
|
+
reslice.SetBackgroundLevel(-1000.0)
|
|
117
93
|
|
|
118
94
|
# Create image actor
|
|
119
95
|
actor = vtk.vtkImageActor()
|
|
120
96
|
actor.GetMapper().SetInputConnection(reslice.GetOutputPort())
|
|
121
|
-
actor.SetVisibility(False)
|
|
97
|
+
actor.SetVisibility(False)
|
|
122
98
|
|
|
123
99
|
mpr_actors[orientation] = {"reslice": reslice, "actor": actor}
|
|
124
100
|
|
|
@@ -133,40 +109,19 @@ class Volume(Object):
|
|
|
133
109
|
return mpr_actors
|
|
134
110
|
|
|
135
111
|
def _setup_center_slices(self, image_data, frame: int):
|
|
136
|
-
"""Set up reslice matrices to show center slices using
|
|
112
|
+
"""Set up reslice matrices to show center slices using axcode-based coordinate systems."""
|
|
137
113
|
center = image_data.GetCenter()
|
|
138
|
-
|
|
139
114
|
actors = self._mpr_actors[frame]
|
|
140
115
|
|
|
141
|
-
#
|
|
142
|
-
|
|
143
|
-
base_axial_up = np.array([0.0, -1.0, 0.0]) # -Y axis (Anterior)
|
|
144
|
-
|
|
145
|
-
base_sagittal_normal = np.array([1.0, 0.0, 0.0]) # X axis (Left)
|
|
146
|
-
base_sagittal_up = np.array([0.0, 0.0, 1.0]) # Z axis (Superior)
|
|
147
|
-
|
|
148
|
-
base_coronal_normal = np.array([0.0, 1.0, 0.0]) # Y axis (Posterior in data)
|
|
149
|
-
base_coronal_up = np.array([0.0, 0.0, 1.0]) # Z axis (Superior)
|
|
116
|
+
# Get coordinate system transformations for each MPR view
|
|
117
|
+
transforms = self._get_mpr_coordinate_systems()
|
|
150
118
|
|
|
151
|
-
# Create reslice matrices
|
|
152
|
-
|
|
153
|
-
axial_matrix = create_reslice_matrix(
|
|
154
|
-
base_axial_normal, base_axial_up, axial_origin
|
|
155
|
-
)
|
|
156
|
-
actors["axial"]["reslice"].SetResliceAxes(axial_matrix)
|
|
157
|
-
|
|
158
|
-
sagittal_origin = [center[0], center[1], center[2]]
|
|
159
|
-
sagittal_matrix = create_reslice_matrix(
|
|
160
|
-
base_sagittal_normal, base_sagittal_up, sagittal_origin
|
|
161
|
-
)
|
|
162
|
-
actors["sagittal"]["reslice"].SetResliceAxes(sagittal_matrix)
|
|
119
|
+
# Create reslice matrices directly from transforms
|
|
120
|
+
origin = [center[0], center[1], center[2]]
|
|
163
121
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
base_coronal_normal, base_coronal_up, coronal_origin
|
|
168
|
-
)
|
|
169
|
-
actors["coronal"]["reslice"].SetResliceAxes(coronal_matrix)
|
|
122
|
+
for orientation in ["axial", "sagittal", "coronal"]:
|
|
123
|
+
mat = create_vtk_reslice_matrix(transforms[orientation], origin)
|
|
124
|
+
actors[orientation]["reslice"].SetResliceAxes(mat)
|
|
170
125
|
|
|
171
126
|
@property
|
|
172
127
|
def mpr_actors(self) -> dict[str, list[vtk.vtkImageActor]]:
|
|
@@ -179,16 +134,86 @@ class Volume(Object):
|
|
|
179
134
|
return self.create_mpr_actors(frame)
|
|
180
135
|
return self._mpr_actors[frame]
|
|
181
136
|
|
|
137
|
+
def _get_mpr_coordinate_systems(self):
|
|
138
|
+
"""Get coordinate system transformation matrices for MPR views."""
|
|
139
|
+
view_axcodes = {
|
|
140
|
+
"axial": "LAS", # Left-Anterior-Superior
|
|
141
|
+
"sagittal": "ASL", # Anterior-Superior-Left
|
|
142
|
+
"coronal": "LSA", # Left-Superior-Anterior
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
transforms = {}
|
|
146
|
+
for view, target_axcode in view_axcodes.items():
|
|
147
|
+
transforms[view] = axcode_transform_matrix("LPS", target_axcode)
|
|
148
|
+
|
|
149
|
+
return transforms
|
|
150
|
+
|
|
151
|
+
def get_physical_bounds(
|
|
152
|
+
self, frame: int = 0
|
|
153
|
+
) -> tuple[float, float, float, float, float, float]:
|
|
154
|
+
"""Get physical coordinate bounds for the volume.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
(x_min, x_max, y_min, y_max, z_min, z_max) in LAS coordinate system
|
|
158
|
+
"""
|
|
159
|
+
if not self._actors:
|
|
160
|
+
raise RuntimeError(f"No actors configured for volume '{self.label}'")
|
|
161
|
+
if frame >= len(self._actors):
|
|
162
|
+
raise IndexError(
|
|
163
|
+
f"Frame {frame} out of range for volume '{self.label}' (max: {len(self._actors) - 1})"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
volume_actor = self._actors[frame]
|
|
167
|
+
image_data = volume_actor.GetMapper().GetInput()
|
|
168
|
+
|
|
169
|
+
# Get VTK image metadata
|
|
170
|
+
origin = np.array(image_data.GetOrigin())
|
|
171
|
+
spacing = np.array(image_data.GetSpacing())
|
|
172
|
+
dimensions = np.array(image_data.GetDimensions())
|
|
173
|
+
direction_matrix = np.array(
|
|
174
|
+
[
|
|
175
|
+
[image_data.GetDirectionMatrix().GetElement(i, j) for j in range(3)]
|
|
176
|
+
for i in range(3)
|
|
177
|
+
]
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Calculate antiorigin using direction matrix
|
|
181
|
+
antiorigin = origin + direction_matrix @ (spacing * (dimensions - 1))
|
|
182
|
+
|
|
183
|
+
# Transform both corners from LPS to LAS
|
|
184
|
+
transform = axcode_transform_matrix("LPS", "LAS")
|
|
185
|
+
origin_las = origin @ transform.T
|
|
186
|
+
antiorigin_las = antiorigin @ transform.T
|
|
187
|
+
|
|
188
|
+
# Interleave coordinates directly without min/max
|
|
189
|
+
bounds = (
|
|
190
|
+
origin_las[0],
|
|
191
|
+
antiorigin_las[0], # x bounds
|
|
192
|
+
origin_las[1],
|
|
193
|
+
antiorigin_las[1], # y bounds
|
|
194
|
+
origin_las[2],
|
|
195
|
+
antiorigin_las[2], # z bounds
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
return bounds
|
|
199
|
+
|
|
182
200
|
def update_slice_positions(
|
|
183
201
|
self,
|
|
184
202
|
frame: int,
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
203
|
+
axial_pos: float,
|
|
204
|
+
sagittal_pos: float,
|
|
205
|
+
coronal_pos: float,
|
|
188
206
|
rotation_sequence: list = None,
|
|
189
207
|
rotation_angles: dict = None,
|
|
190
208
|
):
|
|
191
|
-
"""Update slice positions for MPR views with optional rotation.
|
|
209
|
+
"""Update slice positions for MPR views with optional rotation.
|
|
210
|
+
|
|
211
|
+
Args:
|
|
212
|
+
frame: Frame index
|
|
213
|
+
axial_pos: Physical position along Z axis (LAS Superior)
|
|
214
|
+
sagittal_pos: Physical position along X axis (LAS Left)
|
|
215
|
+
coronal_pos: Physical position along Y axis (LAS Anterior)
|
|
216
|
+
"""
|
|
192
217
|
if frame not in self._mpr_actors:
|
|
193
218
|
return
|
|
194
219
|
|
|
@@ -198,65 +223,49 @@ class Volume(Object):
|
|
|
198
223
|
|
|
199
224
|
actors = self._mpr_actors[frame]
|
|
200
225
|
|
|
201
|
-
#
|
|
202
|
-
axial_pos = bounds[4]
|
|
203
|
-
sagittal_pos = bounds[0]
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
# Apply cumulative rotation if provided
|
|
226
|
+
# Clamp positions to volume bounds
|
|
227
|
+
axial_pos = max(bounds[4], min(bounds[5], axial_pos)) # Z bounds
|
|
228
|
+
sagittal_pos = max(bounds[0], min(bounds[1], sagittal_pos)) # X bounds
|
|
229
|
+
coronal_pos = max(bounds[2], min(bounds[3], coronal_pos)) # Y bounds
|
|
230
|
+
|
|
231
|
+
# Get coordinate system transformations for each MPR view
|
|
232
|
+
transforms = self._get_mpr_coordinate_systems()
|
|
233
|
+
|
|
234
|
+
center = image_data.GetCenter()
|
|
235
|
+
|
|
236
|
+
# Step 1: Apply translation to determine slice origins in physical space
|
|
237
|
+
axial_origin = [center[0], center[1], axial_pos]
|
|
238
|
+
sagittal_origin = [sagittal_pos, center[1], center[2]]
|
|
239
|
+
coronal_origin = [center[0], coronal_pos, center[2]]
|
|
240
|
+
|
|
241
|
+
# Step 2: Apply cumulative rotation around the translated origins
|
|
218
242
|
if rotation_sequence and rotation_angles:
|
|
219
243
|
cumulative_rotation = np.eye(3)
|
|
220
244
|
for i, rotation in enumerate(rotation_sequence):
|
|
221
245
|
angle = rotation_angles.get(i, 0)
|
|
222
|
-
rotation_matrix =
|
|
246
|
+
rotation_matrix = euler_angle_to_rotation_matrix(
|
|
247
|
+
EulerAxis(rotation["axis"]), angle
|
|
248
|
+
)
|
|
223
249
|
cumulative_rotation = cumulative_rotation @ rotation_matrix
|
|
224
250
|
|
|
225
|
-
# Apply rotation to base
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
sagittal_up = cumulative_rotation @ base_sagittal_up
|
|
230
|
-
coronal_normal = cumulative_rotation @ base_coronal_normal
|
|
231
|
-
coronal_up = cumulative_rotation @ base_coronal_up
|
|
251
|
+
# Apply rotation to base transforms
|
|
252
|
+
axial_transform = cumulative_rotation @ transforms["axial"]
|
|
253
|
+
sagittal_transform = cumulative_rotation @ transforms["sagittal"]
|
|
254
|
+
coronal_transform = cumulative_rotation @ transforms["coronal"]
|
|
232
255
|
else:
|
|
233
|
-
# Use base
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
sagittal_up = base_sagittal_up
|
|
238
|
-
coronal_normal = base_coronal_normal
|
|
239
|
-
coronal_up = base_coronal_up
|
|
256
|
+
# Use base transforms without rotation
|
|
257
|
+
axial_transform = transforms["axial"]
|
|
258
|
+
sagittal_transform = transforms["sagittal"]
|
|
259
|
+
coronal_transform = transforms["coronal"]
|
|
240
260
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
# Update axial slice
|
|
244
|
-
axial_origin = [center[0], center[1], axial_pos]
|
|
245
|
-
axial_matrix = create_reslice_matrix(axial_normal, axial_up, axial_origin)
|
|
261
|
+
# Update slices with translated origins and rotated transforms
|
|
262
|
+
axial_matrix = create_vtk_reslice_matrix(axial_transform, axial_origin)
|
|
246
263
|
actors["axial"]["reslice"].SetResliceAxes(axial_matrix)
|
|
247
264
|
|
|
248
|
-
|
|
249
|
-
sagittal_origin = [sagittal_pos, center[1], center[2]]
|
|
250
|
-
sagittal_matrix = create_reslice_matrix(
|
|
251
|
-
sagittal_normal, sagittal_up, sagittal_origin
|
|
252
|
-
)
|
|
265
|
+
sagittal_matrix = create_vtk_reslice_matrix(sagittal_transform, sagittal_origin)
|
|
253
266
|
actors["sagittal"]["reslice"].SetResliceAxes(sagittal_matrix)
|
|
254
267
|
|
|
255
|
-
|
|
256
|
-
coronal_origin = [center[0], coronal_pos, center[2]]
|
|
257
|
-
coronal_matrix = create_reslice_matrix(
|
|
258
|
-
coronal_normal, coronal_up, coronal_origin
|
|
259
|
-
)
|
|
268
|
+
coronal_matrix = create_vtk_reslice_matrix(coronal_transform, coronal_origin)
|
|
260
269
|
actors["coronal"]["reslice"].SetResliceAxes(coronal_matrix)
|
|
261
270
|
|
|
262
271
|
def update_mpr_window_level(self, frame: int, window: float, level: float):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: cardio
|
|
3
|
-
Version: 2025.
|
|
3
|
+
Version: 2025.10.1
|
|
4
4
|
Summary: A simple web-based viewer for 3D and 4D ('cine') medical imaging data.
|
|
5
5
|
Keywords: Medical,Imaging,3D,4D,Visualization
|
|
6
6
|
Author: Davis Marc Vigneault
|
|
@@ -76,7 +76,7 @@ $ uv init
|
|
|
76
76
|
$ uv add cardio
|
|
77
77
|
$ . ./.venv/bin/activate
|
|
78
78
|
(project) cardio --version
|
|
79
|
-
cardio 2025.
|
|
79
|
+
cardio 2025.10.1
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
### Developing
|
|
@@ -1,28 +1,29 @@
|
|
|
1
|
-
cardio/__init__.py,sha256=
|
|
2
|
-
cardio/app.py,sha256=
|
|
1
|
+
cardio/__init__.py,sha256=cVW5yofdKIbjqc0sf_VVnIMah9BvAgELy7IzZR50IZQ,602
|
|
2
|
+
cardio/app.py,sha256=TEzgA03EAgI7HSCHhYwYb8tsnAptsrpcysV5CytfAq4,1379
|
|
3
3
|
cardio/assets/bone.toml,sha256=vv8uVYSHIoKuHkNCoBOkGe2_qoEbXMvQO6ypm3mMOtA,675
|
|
4
4
|
cardio/assets/vascular_closed.toml,sha256=XtaZS_Zd6NSAtY3ZlUfiog3T86u9Ii0oSutU2wBQy78,1267
|
|
5
5
|
cardio/assets/vascular_open.toml,sha256=1M3sV1IGt3zh_3vviysKEk9quKfjF9xUBcIq3kxVHFM,879
|
|
6
6
|
cardio/assets/xray.toml,sha256=siPem0OZ2OkWH0e5pizftpItJKGJgxKJ_S2K0316ubQ,693
|
|
7
7
|
cardio/blend_transfer_functions.py,sha256=s5U4hO810oE434wIkPmAP2mrAfqFb4xxxi3hHf_k8og,2982
|
|
8
8
|
cardio/color_transfer_function.py,sha256=KV4j11AXYeaYGeJWBc9I-WZf7Shrm5xjQVq-0bq9Qc8,817
|
|
9
|
-
cardio/logic.py,sha256=
|
|
10
|
-
cardio/mesh.py,sha256=
|
|
11
|
-
cardio/object.py,sha256=
|
|
9
|
+
cardio/logic.py,sha256=a73JJ2b8t-TYggYMGI1IGpn0Q6AFD8I1JNz9fn6QeDk,37557
|
|
10
|
+
cardio/mesh.py,sha256=XIJX2Ldv1Bw82yykE0N4IlQ0xE2r6xitjxsbEvEKrcI,9390
|
|
11
|
+
cardio/object.py,sha256=FvMzhO9gyHhXgME8B6fcnWH74HmsYNoUjpVKUWEpWhM,6191
|
|
12
|
+
cardio/orientation.py,sha256=J3bqZbv8vfl4loGl7ksmuyqWb3zFAz-TVSIahKcg0pc,6145
|
|
12
13
|
cardio/piecewise_function.py,sha256=bwtwgrAMGkgu1krnvsOF9gRMaZb6smsS9jLrgBecSbo,789
|
|
13
14
|
cardio/property_config.py,sha256=XJYcKeRcq8s9W9jqxzVer75r5jBLuvebv780FYdPV8U,1723
|
|
14
|
-
cardio/scene.py,sha256=
|
|
15
|
+
cardio/scene.py,sha256=9jskdEARyJjk7QBIcMt5X2HsnikTAJdoRoLSI0LxcJE,14301
|
|
15
16
|
cardio/screenshot.py,sha256=l8bLgxnU5O0FlnmsyVAzKwM9Y0401IzcdnDP0WqFSTY,640
|
|
16
|
-
cardio/segmentation.py,sha256=
|
|
17
|
+
cardio/segmentation.py,sha256=SK9f834lld08qYH9l2Ligml212u7MzfcOEgTz8wnpzs,6676
|
|
17
18
|
cardio/transfer_function_pair.py,sha256=90PQXByCL6mMaODo7Yfd-lmdFtCKhcBZbNaiH-PTds0,805
|
|
18
19
|
cardio/types.py,sha256=DYDgA5QmYdU3QQrEgZMouEbMEIf40DJCeXo4V7cDXtg,356
|
|
19
|
-
cardio/ui.py,sha256=
|
|
20
|
-
cardio/utils.py,sha256=
|
|
21
|
-
cardio/volume.py,sha256=
|
|
20
|
+
cardio/ui.py,sha256=wsRujwiNtMeq65R4bx-myxSPDhJT0RskwvBWlGenmHk,42901
|
|
21
|
+
cardio/utils.py,sha256=tFUQ4FxfidTH6GjEIKQwguqhO9T_wJ2Vk0IhbEfxRGA,1616
|
|
22
|
+
cardio/volume.py,sha256=k7NYZCwsmoAbs8mNWDC51gS6nlzPqD9JhDr1iyUQ0cs,10377
|
|
22
23
|
cardio/volume_property.py,sha256=6T2r67SSIDl8F6ZlQvgMCZESLxuXVVAUjOC50lgQEmk,1644
|
|
23
24
|
cardio/volume_property_presets.py,sha256=U2a2MnyCjryzOLEADs3OLSMMmAUnXq82mYK7OVXbQV0,1659
|
|
24
25
|
cardio/window_level.py,sha256=gjk39Iv6jMJ52y6jydOjxBZBsI1nZQMs2CdWWTshQoE,807
|
|
25
|
-
cardio-2025.
|
|
26
|
-
cardio-2025.
|
|
27
|
-
cardio-2025.
|
|
28
|
-
cardio-2025.
|
|
26
|
+
cardio-2025.10.1.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
|
|
27
|
+
cardio-2025.10.1.dist-info/entry_points.txt,sha256=xRd1otqKtW9xmidJ4CFRX1V9KWmsBbtxrgMtDColq3w,40
|
|
28
|
+
cardio-2025.10.1.dist-info/METADATA,sha256=0GkZB7yixZKd4nFaAHgWtgCA6VxlV_pbUYdHokFROD4,3540
|
|
29
|
+
cardio-2025.10.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|