cardio 2025.8.1__py3-none-any.whl → 2025.9.0__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 +8 -7
- cardio/app.py +1 -1
- cardio/blend_transfer_functions.py +84 -0
- cardio/color_transfer_function.py +29 -0
- cardio/logic.py +415 -5
- cardio/mesh.py +2 -2
- cardio/object.py +1 -6
- cardio/piecewise_function.py +29 -0
- cardio/scene.py +132 -1
- cardio/transfer_function_pair.py +25 -0
- cardio/ui.py +296 -60
- cardio/volume.py +216 -3
- cardio/volume_property.py +46 -0
- cardio/volume_property_presets.py +53 -0
- cardio/window_level.py +35 -0
- {cardio-2025.8.1.dist-info → cardio-2025.9.0.dist-info}/METADATA +5 -3
- cardio-2025.9.0.dist-info/RECORD +28 -0
- cardio/transfer_functions.py +0 -272
- cardio-2025.8.1.dist-info/RECORD +0 -22
- {cardio-2025.8.1.dist-info → cardio-2025.9.0.dist-info}/WHEEL +0 -0
- {cardio-2025.8.1.dist-info → cardio-2025.9.0.dist-info}/entry_points.txt +0 -0
cardio/volume.py
CHANGED
@@ -6,8 +6,38 @@ import pydantic as pc
|
|
6
6
|
import vtk
|
7
7
|
|
8
8
|
from .object import Object
|
9
|
-
from .transfer_functions import load_preset
|
10
9
|
from .utils import InterpolatorType, reset_direction
|
10
|
+
from .volume_property_presets import load_volume_property_preset
|
11
|
+
|
12
|
+
|
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
|
11
41
|
|
12
42
|
|
13
43
|
class Volume(Object):
|
@@ -17,8 +47,13 @@ class Volume(Object):
|
|
17
47
|
default="${frame}.nii.gz",
|
18
48
|
description="Filename pattern with $frame placeholder",
|
19
49
|
)
|
20
|
-
transfer_function_preset: str = pc.Field(
|
50
|
+
transfer_function_preset: str = pc.Field(
|
51
|
+
default="bone", description="Transfer function preset key"
|
52
|
+
)
|
21
53
|
_actors: list[vtk.vtkVolume] = pc.PrivateAttr(default_factory=list)
|
54
|
+
_mpr_actors: dict[str, list[vtk.vtkImageActor]] = pc.PrivateAttr(
|
55
|
+
default_factory=dict
|
56
|
+
)
|
22
57
|
|
23
58
|
@pc.model_validator(mode="after")
|
24
59
|
def initialize_volume(self):
|
@@ -47,7 +82,7 @@ class Volume(Object):
|
|
47
82
|
@property
|
48
83
|
def preset(self):
|
49
84
|
"""Load preset based on transfer_function_preset."""
|
50
|
-
return
|
85
|
+
return load_volume_property_preset(self.transfer_function_preset)
|
51
86
|
|
52
87
|
def configure_actors(self):
|
53
88
|
"""Configure volume properties without adding to renderer."""
|
@@ -59,3 +94,181 @@ class Volume(Object):
|
|
59
94
|
"""Apply the current preset to all actors."""
|
60
95
|
for actor in self._actors:
|
61
96
|
actor.SetProperty(self.preset.vtk_property)
|
97
|
+
|
98
|
+
def create_mpr_actors(self, frame: int = 0):
|
99
|
+
"""Create MPR (reslice) actors for axial, sagittal, and coronal views."""
|
100
|
+
if frame >= len(self._actors):
|
101
|
+
frame = 0
|
102
|
+
|
103
|
+
# Get the image data from the volume actor
|
104
|
+
volume_actor = self._actors[frame]
|
105
|
+
image_data = volume_actor.GetMapper().GetInput()
|
106
|
+
|
107
|
+
# Create reslice actors for each orientation
|
108
|
+
mpr_actors = {}
|
109
|
+
|
110
|
+
for orientation in ["axial", "sagittal", "coronal"]:
|
111
|
+
# Create reslice filter
|
112
|
+
reslice = vtk.vtkImageReslice()
|
113
|
+
reslice.SetInputData(image_data)
|
114
|
+
reslice.SetOutputDimensionality(2)
|
115
|
+
reslice.SetInterpolationModeToLinear()
|
116
|
+
reslice.SetBackgroundLevel(-1000.0) # Set background to air value
|
117
|
+
|
118
|
+
# Create image actor
|
119
|
+
actor = vtk.vtkImageActor()
|
120
|
+
actor.GetMapper().SetInputConnection(reslice.GetOutputPort())
|
121
|
+
actor.SetVisibility(False) # Start hidden
|
122
|
+
|
123
|
+
mpr_actors[orientation] = {"reslice": reslice, "actor": actor}
|
124
|
+
|
125
|
+
# Store actors for this frame
|
126
|
+
if frame not in self._mpr_actors:
|
127
|
+
self._mpr_actors[frame] = {}
|
128
|
+
self._mpr_actors[frame] = mpr_actors
|
129
|
+
|
130
|
+
# Set up initial reslice matrices for center slices
|
131
|
+
self._setup_center_slices(image_data, frame)
|
132
|
+
|
133
|
+
return mpr_actors
|
134
|
+
|
135
|
+
def _setup_center_slices(self, image_data, frame: int):
|
136
|
+
"""Set up reslice matrices to show center slices using LAS coordinate system."""
|
137
|
+
center = image_data.GetCenter()
|
138
|
+
|
139
|
+
actors = self._mpr_actors[frame]
|
140
|
+
|
141
|
+
# Base LAS vectors (Left-Anterior-Superior coordinate system)
|
142
|
+
base_axial_normal = np.array([0.0, 0.0, 1.0]) # Z axis (Superior)
|
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)
|
150
|
+
|
151
|
+
# Create reslice matrices with proper LAS vectors
|
152
|
+
axial_origin = [center[0], center[1], center[2]]
|
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)
|
163
|
+
|
164
|
+
# Coronal view: LPS->LAS Y coordinate conversion
|
165
|
+
coronal_origin = [center[0], center[1], center[2]]
|
166
|
+
coronal_matrix = create_reslice_matrix(
|
167
|
+
base_coronal_normal, base_coronal_up, coronal_origin
|
168
|
+
)
|
169
|
+
actors["coronal"]["reslice"].SetResliceAxes(coronal_matrix)
|
170
|
+
|
171
|
+
@property
|
172
|
+
def mpr_actors(self) -> dict[str, list[vtk.vtkImageActor]]:
|
173
|
+
"""Get MPR actors for all frames."""
|
174
|
+
return self._mpr_actors
|
175
|
+
|
176
|
+
def get_mpr_actors_for_frame(self, frame: int) -> dict:
|
177
|
+
"""Get MPR actors for a specific frame."""
|
178
|
+
if frame not in self._mpr_actors:
|
179
|
+
return self.create_mpr_actors(frame)
|
180
|
+
return self._mpr_actors[frame]
|
181
|
+
|
182
|
+
def update_slice_positions(
|
183
|
+
self,
|
184
|
+
frame: int,
|
185
|
+
axial_frac: float,
|
186
|
+
sagittal_frac: float,
|
187
|
+
coronal_frac: float,
|
188
|
+
rotation_sequence: list = None,
|
189
|
+
rotation_angles: dict = None,
|
190
|
+
):
|
191
|
+
"""Update slice positions for MPR views with optional rotation."""
|
192
|
+
if frame not in self._mpr_actors:
|
193
|
+
return
|
194
|
+
|
195
|
+
volume_actor = self._actors[frame]
|
196
|
+
image_data = volume_actor.GetMapper().GetInput()
|
197
|
+
bounds = image_data.GetBounds()
|
198
|
+
|
199
|
+
actors = self._mpr_actors[frame]
|
200
|
+
|
201
|
+
# Calculate slice positions from fractions
|
202
|
+
axial_pos = bounds[4] + axial_frac * (bounds[5] - bounds[4]) # Z bounds
|
203
|
+
sagittal_pos = bounds[0] + sagittal_frac * (bounds[1] - bounds[0]) # X bounds
|
204
|
+
# Coronal: LPS->LAS Y coordinate conversion (flip direction)
|
205
|
+
coronal_pos = bounds[3] - coronal_frac * (
|
206
|
+
bounds[3] - bounds[2]
|
207
|
+
) # Flipped Y bounds
|
208
|
+
|
209
|
+
# Base LAS vectors (Left-Anterior-Superior coordinate system)
|
210
|
+
base_axial_normal = np.array([0.0, 0.0, 1.0]) # Z axis (Superior)
|
211
|
+
base_axial_up = np.array([0.0, -1.0, 0.0]) # -Y axis (Anterior)
|
212
|
+
base_sagittal_normal = np.array([1.0, 0.0, 0.0]) # X axis (Left)
|
213
|
+
base_sagittal_up = np.array([0.0, 0.0, 1.0]) # Z axis (Superior)
|
214
|
+
base_coronal_normal = np.array([0.0, 1.0, 0.0]) # Y axis (Posterior in data)
|
215
|
+
base_coronal_up = np.array([0.0, 0.0, 1.0]) # Z axis (Superior)
|
216
|
+
|
217
|
+
# Apply cumulative rotation if provided
|
218
|
+
if rotation_sequence and rotation_angles:
|
219
|
+
cumulative_rotation = np.eye(3)
|
220
|
+
for i, rotation in enumerate(rotation_sequence):
|
221
|
+
angle = rotation_angles.get(i, 0)
|
222
|
+
rotation_matrix = create_rotation_matrix(rotation["axis"], angle)
|
223
|
+
cumulative_rotation = cumulative_rotation @ rotation_matrix
|
224
|
+
|
225
|
+
# Apply rotation to base view vectors
|
226
|
+
axial_normal = cumulative_rotation @ base_axial_normal
|
227
|
+
axial_up = cumulative_rotation @ base_axial_up
|
228
|
+
sagittal_normal = cumulative_rotation @ base_sagittal_normal
|
229
|
+
sagittal_up = cumulative_rotation @ base_sagittal_up
|
230
|
+
coronal_normal = cumulative_rotation @ base_coronal_normal
|
231
|
+
coronal_up = cumulative_rotation @ base_coronal_up
|
232
|
+
else:
|
233
|
+
# Use base vectors without rotation
|
234
|
+
axial_normal = base_axial_normal
|
235
|
+
axial_up = base_axial_up
|
236
|
+
sagittal_normal = base_sagittal_normal
|
237
|
+
sagittal_up = base_sagittal_up
|
238
|
+
coronal_normal = base_coronal_normal
|
239
|
+
coronal_up = base_coronal_up
|
240
|
+
|
241
|
+
center = image_data.GetCenter()
|
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)
|
246
|
+
actors["axial"]["reslice"].SetResliceAxes(axial_matrix)
|
247
|
+
|
248
|
+
# Update sagittal slice
|
249
|
+
sagittal_origin = [sagittal_pos, center[1], center[2]]
|
250
|
+
sagittal_matrix = create_reslice_matrix(
|
251
|
+
sagittal_normal, sagittal_up, sagittal_origin
|
252
|
+
)
|
253
|
+
actors["sagittal"]["reslice"].SetResliceAxes(sagittal_matrix)
|
254
|
+
|
255
|
+
# Update coronal slice
|
256
|
+
coronal_origin = [center[0], coronal_pos, center[2]]
|
257
|
+
coronal_matrix = create_reslice_matrix(
|
258
|
+
coronal_normal, coronal_up, coronal_origin
|
259
|
+
)
|
260
|
+
actors["coronal"]["reslice"].SetResliceAxes(coronal_matrix)
|
261
|
+
|
262
|
+
def update_mpr_window_level(self, frame: int, window: float, level: float):
|
263
|
+
"""Update window/level properties for MPR actors."""
|
264
|
+
if frame not in self._mpr_actors:
|
265
|
+
return
|
266
|
+
|
267
|
+
actors = self._mpr_actors[frame]
|
268
|
+
|
269
|
+
for orientation in ["axial", "sagittal", "coronal"]:
|
270
|
+
if orientation in actors:
|
271
|
+
actor = actors[orientation]["actor"]
|
272
|
+
property_obj = actor.GetProperty()
|
273
|
+
property_obj.SetColorWindow(window)
|
274
|
+
property_obj.SetColorLevel(level)
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# Third Party
|
2
|
+
import pydantic as pc
|
3
|
+
import vtk
|
4
|
+
|
5
|
+
# Internal
|
6
|
+
from .blend_transfer_functions import blend_transfer_functions
|
7
|
+
from .transfer_function_pair import TransferFunctionPairConfig
|
8
|
+
from .types import ScalarComponent
|
9
|
+
|
10
|
+
|
11
|
+
class VolumePropertyConfig(pc.BaseModel):
|
12
|
+
"""Configuration for volume rendering properties and transfer functions."""
|
13
|
+
|
14
|
+
name: str = pc.Field(description="Display name of the preset")
|
15
|
+
description: str = pc.Field(description="Description of the preset")
|
16
|
+
|
17
|
+
# Lighting parameters
|
18
|
+
ambient: ScalarComponent
|
19
|
+
diffuse: ScalarComponent
|
20
|
+
specular: ScalarComponent
|
21
|
+
|
22
|
+
# Transfer functions
|
23
|
+
transfer_functions: list[TransferFunctionPairConfig] = pc.Field(
|
24
|
+
min_length=1, description="List of transfer function pairs to blend"
|
25
|
+
)
|
26
|
+
|
27
|
+
@property
|
28
|
+
def vtk_property(self) -> vtk.vtkVolumeProperty:
|
29
|
+
"""Create a fully configured VTK volume property from this configuration."""
|
30
|
+
# Get VTK transfer functions from each pair config
|
31
|
+
tfs = [pair.vtk_functions for pair in self.transfer_functions]
|
32
|
+
|
33
|
+
# Blend all transfer functions into a single composite
|
34
|
+
blended_otf, blended_ctf = blend_transfer_functions(tfs)
|
35
|
+
|
36
|
+
# Create and configure the volume property
|
37
|
+
_vtk_property = vtk.vtkVolumeProperty()
|
38
|
+
_vtk_property.SetScalarOpacity(blended_otf)
|
39
|
+
_vtk_property.SetColor(blended_ctf)
|
40
|
+
_vtk_property.ShadeOn()
|
41
|
+
_vtk_property.SetInterpolationTypeToLinear()
|
42
|
+
_vtk_property.SetAmbient(self.ambient)
|
43
|
+
_vtk_property.SetDiffuse(self.diffuse)
|
44
|
+
_vtk_property.SetSpecular(self.specular)
|
45
|
+
|
46
|
+
return _vtk_property
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# System
|
2
|
+
import pathlib as pl
|
3
|
+
|
4
|
+
# Third Party
|
5
|
+
import pydantic as pc
|
6
|
+
import tomlkit as tk
|
7
|
+
|
8
|
+
# Internal
|
9
|
+
from .volume_property import VolumePropertyConfig
|
10
|
+
|
11
|
+
|
12
|
+
def load_volume_property_preset(preset_name: str) -> VolumePropertyConfig:
|
13
|
+
"""Load a specific volume property preset from its individual file."""
|
14
|
+
assets_dir = pl.Path(__file__).parent / "assets"
|
15
|
+
preset_file = assets_dir / f"{preset_name}.toml"
|
16
|
+
|
17
|
+
if not preset_file.exists():
|
18
|
+
available = list(list_volume_property_presets().keys())
|
19
|
+
raise KeyError(
|
20
|
+
f"Volume property preset '{preset_name}' not found. "
|
21
|
+
f"Available presets: {available}"
|
22
|
+
)
|
23
|
+
|
24
|
+
try:
|
25
|
+
with preset_file.open("rt", encoding="utf-8") as fp:
|
26
|
+
raw_data = tk.load(fp)
|
27
|
+
return VolumePropertyConfig.model_validate(raw_data)
|
28
|
+
except (pc.ValidationError, Exception) as e:
|
29
|
+
raise ValueError(f"Invalid preset file '{preset_name}.toml': {e}") from e
|
30
|
+
|
31
|
+
|
32
|
+
def list_volume_property_presets() -> dict[str, str]:
|
33
|
+
"""
|
34
|
+
List all available volume property presets.
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
Dictionary mapping preset names to descriptions
|
38
|
+
"""
|
39
|
+
assets_dir = pl.Path(__file__).parent / "assets"
|
40
|
+
preset_files = assets_dir.glob("*.toml")
|
41
|
+
|
42
|
+
presets = {}
|
43
|
+
for preset_file in preset_files:
|
44
|
+
preset_name = preset_file.stem
|
45
|
+
try:
|
46
|
+
with preset_file.open("rt", encoding="utf-8") as fp:
|
47
|
+
preset_data = tk.load(fp)
|
48
|
+
presets[preset_name] = preset_data["description"]
|
49
|
+
except (KeyError, OSError):
|
50
|
+
# Skip files that don't have the expected structure
|
51
|
+
continue
|
52
|
+
|
53
|
+
return presets
|
cardio/window_level.py
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# System
|
2
|
+
import functools as ft
|
3
|
+
|
4
|
+
# Third Party
|
5
|
+
import pydantic as pc
|
6
|
+
|
7
|
+
|
8
|
+
@pc.dataclasses.dataclass(config=dict(frozen=True))
|
9
|
+
class WindowLevel:
|
10
|
+
name: str
|
11
|
+
window: float
|
12
|
+
level: float
|
13
|
+
|
14
|
+
@pc.computed_field
|
15
|
+
@ft.cached_property
|
16
|
+
def lower(self) -> float:
|
17
|
+
return self.level - self.window / 2
|
18
|
+
|
19
|
+
@pc.computed_field
|
20
|
+
@ft.cached_property
|
21
|
+
def upper(self) -> float:
|
22
|
+
return self.level + self.window / 2
|
23
|
+
|
24
|
+
|
25
|
+
presets = {
|
26
|
+
1: WindowLevel("Abdomen", 400, 40),
|
27
|
+
2: WindowLevel("Lung", 1500, -700),
|
28
|
+
3: WindowLevel("Liver", 100, 110),
|
29
|
+
4: WindowLevel("Bone", 1500, 500),
|
30
|
+
5: WindowLevel("Brain", 85, 42),
|
31
|
+
6: WindowLevel("Stroke", 36, 28),
|
32
|
+
7: WindowLevel("Vascular", 800, 200),
|
33
|
+
8: WindowLevel("Subdural", 160, 60),
|
34
|
+
9: WindowLevel("Normalized", 0, 1),
|
35
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: cardio
|
3
|
-
Version: 2025.
|
3
|
+
Version: 2025.9.0
|
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
|
@@ -34,17 +34,19 @@ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
34
34
|
Classifier: Topic :: Scientific/Engineering :: Visualization
|
35
35
|
Requires-Dist: trame-vuetify>=3.0.2
|
36
36
|
Requires-Dist: trame-vtk>=2.9.1
|
37
|
-
Requires-Dist: trame>=3.11.0
|
38
37
|
Requires-Dist: itk>=5.4.4.post1
|
39
38
|
Requires-Dist: vtk>=9.5.0
|
40
39
|
Requires-Dist: tomlkit>=0.13.3
|
41
40
|
Requires-Dist: numpy>=2.2.6
|
42
41
|
Requires-Dist: pydantic>=2.11.7
|
43
42
|
Requires-Dist: pydantic-settings>=2.0.0
|
43
|
+
Requires-Dist: trame>=3.12.0
|
44
|
+
Requires-Dist: trame-client>=3.10.1
|
44
45
|
Requires-Dist: ruff>=0.12.10 ; extra == 'dev'
|
45
46
|
Requires-Dist: isort>=6.0.1 ; extra == 'dev'
|
46
47
|
Requires-Dist: bumpver>=2025.1131 ; extra == 'dev'
|
47
48
|
Requires-Dist: pytest>=8.4.1 ; extra == 'dev'
|
49
|
+
Requires-Dist: coverage>=7.10.4 ; extra == 'dev'
|
48
50
|
Maintainer: Davis Marc Vigneault
|
49
51
|
Maintainer-email: Davis Marc Vigneault <davis.vigneault@gmail.com>
|
50
52
|
Requires-Python: >=3.10
|
@@ -74,7 +76,7 @@ $ uv init
|
|
74
76
|
$ uv add cardio
|
75
77
|
$ . ./.venv/bin/activate
|
76
78
|
(project) cardio --version
|
77
|
-
cardio 2025.
|
79
|
+
cardio 2025.9.0
|
78
80
|
```
|
79
81
|
|
80
82
|
### Developing
|
@@ -0,0 +1,28 @@
|
|
1
|
+
cardio/__init__.py,sha256=tEZTwdKZnx4WPaNTsZZSwBqLfCQgB0e3Rzov5-ci1GI,581
|
2
|
+
cardio/app.py,sha256=jcT0AUccoB6CPAKQ_gAJE3Q-jsrc9OT4XlK1XskdrI4,1791
|
3
|
+
cardio/assets/bone.toml,sha256=vv8uVYSHIoKuHkNCoBOkGe2_qoEbXMvQO6ypm3mMOtA,675
|
4
|
+
cardio/assets/vascular_closed.toml,sha256=XtaZS_Zd6NSAtY3ZlUfiog3T86u9Ii0oSutU2wBQy78,1267
|
5
|
+
cardio/assets/vascular_open.toml,sha256=1M3sV1IGt3zh_3vviysKEk9quKfjF9xUBcIq3kxVHFM,879
|
6
|
+
cardio/assets/xray.toml,sha256=siPem0OZ2OkWH0e5pizftpItJKGJgxKJ_S2K0316ubQ,693
|
7
|
+
cardio/blend_transfer_functions.py,sha256=s5U4hO810oE434wIkPmAP2mrAfqFb4xxxi3hHf_k8og,2982
|
8
|
+
cardio/color_transfer_function.py,sha256=KV4j11AXYeaYGeJWBc9I-WZf7Shrm5xjQVq-0bq9Qc8,817
|
9
|
+
cardio/logic.py,sha256=usIAZkF68op3apQJXR8m2y8ozuulVHlGSgX7qqyctV4,31462
|
10
|
+
cardio/mesh.py,sha256=Q-5MgEoX3nInwUZe4o2MU4nKy9oR34Qj41JwevxW8bc,9410
|
11
|
+
cardio/object.py,sha256=zly-2bGnB7K45gRLnMgxE-q7cUtdlOLLr4z4QoVkZJ0,6172
|
12
|
+
cardio/piecewise_function.py,sha256=bwtwgrAMGkgu1krnvsOF9gRMaZb6smsS9jLrgBecSbo,789
|
13
|
+
cardio/property_config.py,sha256=XJYcKeRcq8s9W9jqxzVer75r5jBLuvebv780FYdPV8U,1723
|
14
|
+
cardio/scene.py,sha256=2GAbSvFMPQAHoNz2EO4ukXkIXmfy3-Km3ik5OHdDzJE,13015
|
15
|
+
cardio/screenshot.py,sha256=l8bLgxnU5O0FlnmsyVAzKwM9Y0401IzcdnDP0WqFSTY,640
|
16
|
+
cardio/segmentation.py,sha256=enj1V5Rw42dxjqSRUiylEQcdgvj8A-Do9Z638tf7nc8,6706
|
17
|
+
cardio/transfer_function_pair.py,sha256=90PQXByCL6mMaODo7Yfd-lmdFtCKhcBZbNaiH-PTds0,805
|
18
|
+
cardio/types.py,sha256=DYDgA5QmYdU3QQrEgZMouEbMEIf40DJCeXo4V7cDXtg,356
|
19
|
+
cardio/ui.py,sha256=ON8SUuxyo1W3m1IX18K-8wxd1dCFlNcRufxH88iXEZk,33738
|
20
|
+
cardio/utils.py,sha256=zgyJ2PWTAWIaU8SVA9KF_XFB4251QxYAT6w3cqaIerA,3051
|
21
|
+
cardio/volume.py,sha256=Wt1EoOJ_U2RZLjK67_iIYMjFiXIKV2DNLkXd-8qFslU,10613
|
22
|
+
cardio/volume_property.py,sha256=6T2r67SSIDl8F6ZlQvgMCZESLxuXVVAUjOC50lgQEmk,1644
|
23
|
+
cardio/volume_property_presets.py,sha256=U2a2MnyCjryzOLEADs3OLSMMmAUnXq82mYK7OVXbQV0,1659
|
24
|
+
cardio/window_level.py,sha256=gjk39Iv6jMJ52y6jydOjxBZBsI1nZQMs2CdWWTshQoE,807
|
25
|
+
cardio-2025.9.0.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
|
26
|
+
cardio-2025.9.0.dist-info/entry_points.txt,sha256=xRd1otqKtW9xmidJ4CFRX1V9KWmsBbtxrgMtDColq3w,40
|
27
|
+
cardio-2025.9.0.dist-info/METADATA,sha256=5Sf5ChcVgdhHQNZM45oqsIVPCKGkhPqqCkunHvpR0vQ,3538
|
28
|
+
cardio-2025.9.0.dist-info/RECORD,,
|