cardio 2025.8.1__tar.gz → 2025.9.0__tar.gz
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-2025.8.1 → cardio-2025.9.0}/PKG-INFO +5 -3
- {cardio-2025.8.1 → cardio-2025.9.0}/README.md +1 -1
- {cardio-2025.8.1 → cardio-2025.9.0}/pyproject.toml +5 -3
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/__init__.py +8 -7
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/app.py +1 -1
- cardio-2025.9.0/src/cardio/blend_transfer_functions.py +84 -0
- cardio-2025.9.0/src/cardio/color_transfer_function.py +29 -0
- cardio-2025.9.0/src/cardio/logic.py +775 -0
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/mesh.py +2 -2
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/object.py +1 -6
- cardio-2025.9.0/src/cardio/piecewise_function.py +29 -0
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/scene.py +132 -1
- cardio-2025.9.0/src/cardio/transfer_function_pair.py +25 -0
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/ui.py +296 -60
- cardio-2025.9.0/src/cardio/volume.py +274 -0
- cardio-2025.9.0/src/cardio/volume_property.py +46 -0
- cardio-2025.9.0/src/cardio/volume_property_presets.py +53 -0
- cardio-2025.9.0/src/cardio/window_level.py +35 -0
- cardio-2025.8.1/src/cardio/logic.py +0 -365
- cardio-2025.8.1/src/cardio/transfer_functions.py +0 -272
- cardio-2025.8.1/src/cardio/volume.py +0 -61
- {cardio-2025.8.1 → cardio-2025.9.0}/LICENSE +0 -0
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/assets/bone.toml +0 -0
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/assets/vascular_closed.toml +0 -0
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/assets/vascular_open.toml +0 -0
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/assets/xray.toml +0 -0
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/property_config.py +0 -0
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/screenshot.py +0 -0
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/segmentation.py +0 -0
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/types.py +0 -0
- {cardio-2025.8.1 → cardio-2025.9.0}/src/cardio/utils.py +0 -0
@@ -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
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "cardio"
|
7
|
-
version = "2025.
|
7
|
+
version = "2025.9.0"
|
8
8
|
authors = [
|
9
9
|
{name = "Davis Marc Vigneault", email = "davis.vigneault@gmail.com"}
|
10
10
|
]
|
@@ -25,13 +25,14 @@ classifiers = [
|
|
25
25
|
dependencies = [
|
26
26
|
"trame-vuetify>=3.0.2",
|
27
27
|
"trame-vtk>=2.9.1",
|
28
|
-
"trame>=3.11.0",
|
29
28
|
"itk>=5.4.4.post1",
|
30
29
|
"vtk>=9.5.0",
|
31
30
|
"tomlkit>=0.13.3",
|
32
31
|
"numpy>=2.2.6",
|
33
32
|
"pydantic>=2.11.7",
|
34
33
|
"pydantic-settings>=2.0.0",
|
34
|
+
"trame>=3.12.0",
|
35
|
+
"trame-client>=3.10.1",
|
35
36
|
]
|
36
37
|
description = "A simple web-based viewer for 3D and 4D ('cine') medical imaging data."
|
37
38
|
keywords = ["Medical", "Imaging", "3D", "4D", "Visualization"]
|
@@ -48,6 +49,7 @@ dev = [
|
|
48
49
|
"isort>=6.0.1",
|
49
50
|
"bumpver>=2025.1131",
|
50
51
|
"pytest>=8.4.1",
|
52
|
+
"coverage>=7.10.4",
|
51
53
|
]
|
52
54
|
|
53
55
|
[project.gui-scripts]
|
@@ -60,7 +62,7 @@ repository = "https://github.com/sudomakeinstall/cardio"
|
|
60
62
|
profile = "black"
|
61
63
|
|
62
64
|
[tool.bumpver]
|
63
|
-
current_version = "2025.
|
65
|
+
current_version = "2025.9.0"
|
64
66
|
version_pattern = "YYYY.MM.INC0"
|
65
67
|
commit_message = "ENH: Bump version from {old_version} => {new_version}"
|
66
68
|
commit = true
|
@@ -1,15 +1,16 @@
|
|
1
|
+
from . import window_level
|
1
2
|
from .logic import Logic
|
2
3
|
from .mesh import Mesh
|
3
4
|
from .object import Object
|
4
5
|
from .scene import Scene
|
5
6
|
from .screenshot import Screenshot
|
6
7
|
from .segmentation import Segmentation
|
7
|
-
from .transfer_functions import (
|
8
|
-
list_available_presets,
|
9
|
-
load_preset,
|
10
|
-
)
|
11
8
|
from .ui import UI
|
12
9
|
from .volume import Volume
|
10
|
+
from .volume_property_presets import (
|
11
|
+
list_volume_property_presets,
|
12
|
+
load_volume_property_preset,
|
13
|
+
)
|
13
14
|
|
14
15
|
__all__ = [
|
15
16
|
"Object",
|
@@ -20,8 +21,8 @@ __all__ = [
|
|
20
21
|
"Screenshot",
|
21
22
|
"UI",
|
22
23
|
"Logic",
|
23
|
-
"
|
24
|
-
"
|
24
|
+
"load_volume_property_preset",
|
25
|
+
"list_volume_property_presets",
|
25
26
|
]
|
26
27
|
|
27
|
-
__version__ = "2025.
|
28
|
+
__version__ = "2025.9.0"
|
@@ -15,7 +15,7 @@ from .ui import UI
|
|
15
15
|
|
16
16
|
class CardioApp(tm.app.TrameApp):
|
17
17
|
def __init__(self, name=None):
|
18
|
-
super().__init__(server=name, client_type="
|
18
|
+
super().__init__(server=name, client_type="vue3")
|
19
19
|
|
20
20
|
# Add config file argument to Trame's parser
|
21
21
|
self.server.cli.add_argument(
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# System
|
2
|
+
import numpy as np
|
3
|
+
|
4
|
+
# Third Party
|
5
|
+
import vtk
|
6
|
+
|
7
|
+
|
8
|
+
def blend_transfer_functions(tfs, scalar_range=(-2000, 2000), num_samples=512):
|
9
|
+
"""
|
10
|
+
Blend multiple transfer functions using volume rendering emission-absorption model.
|
11
|
+
|
12
|
+
Based on the volume rendering equation from:
|
13
|
+
- Levoy, M. "Display of Surfaces from Volume Data" IEEE Computer Graphics and Applications, 1988
|
14
|
+
- Kajiya, J.T. & Von Herzen, B.P. "Ray tracing volume densities" ACM SIGGRAPH Computer Graphics, 1984
|
15
|
+
- Engel, K. et al. "Real-time Volume Graphics" A K Peters, 2006, Chapter 2
|
16
|
+
|
17
|
+
The volume rendering integral: I = ∫ C(s) * μ(s) * T(s) ds
|
18
|
+
where C(s) = emission color, μ(s) = opacity, T(s) = transmission
|
19
|
+
|
20
|
+
For discrete transfer functions, this becomes:
|
21
|
+
- Total emission = Σ(color_i * opacity_i)
|
22
|
+
- Total absorption = Σ(opacity_i)
|
23
|
+
- Final color = total_emission / total_absorption
|
24
|
+
"""
|
25
|
+
if len(tfs) == 1:
|
26
|
+
return tfs[0]
|
27
|
+
|
28
|
+
sample_points = np.linspace(
|
29
|
+
start=scalar_range[0],
|
30
|
+
stop=scalar_range[1],
|
31
|
+
num=num_samples,
|
32
|
+
)
|
33
|
+
|
34
|
+
# Initialize arrays to store blended values
|
35
|
+
blended_opacity = []
|
36
|
+
blended_color = []
|
37
|
+
|
38
|
+
for scalar_val in sample_points:
|
39
|
+
# Accumulate emission and absorption for volume rendering
|
40
|
+
total_emission = [0.0, 0.0, 0.0]
|
41
|
+
total_absorption = 0.0
|
42
|
+
|
43
|
+
for otf, ctf in tfs:
|
44
|
+
# Get opacity and color for this scalar value
|
45
|
+
layer_opacity = otf.GetValue(scalar_val)
|
46
|
+
layer_color = [0.0, 0.0, 0.0]
|
47
|
+
ctf.GetColor(scalar_val, layer_color)
|
48
|
+
|
49
|
+
# Volume rendering accumulation:
|
50
|
+
# Emission = color * opacity (additive)
|
51
|
+
# Absorption = opacity (multiplicative through transmission)
|
52
|
+
for i in range(3):
|
53
|
+
total_emission[i] += layer_color[i] * layer_opacity
|
54
|
+
|
55
|
+
total_absorption += layer_opacity
|
56
|
+
|
57
|
+
# Clamp values to reasonable ranges
|
58
|
+
total_absorption = min(total_absorption, 1.0)
|
59
|
+
for i in range(3):
|
60
|
+
total_emission[i] = min(total_emission[i], 1.0)
|
61
|
+
|
62
|
+
# For the final color, normalize emission by absorption if absorption > 0
|
63
|
+
if total_absorption > 0.001: # Avoid division by zero
|
64
|
+
final_color = [total_emission[i] / total_absorption for i in range(3)]
|
65
|
+
else:
|
66
|
+
final_color = [0.0, 0.0, 0.0]
|
67
|
+
|
68
|
+
# Clamp final colors
|
69
|
+
final_color = [min(c, 1.0) for c in final_color]
|
70
|
+
|
71
|
+
blended_opacity.append(total_absorption)
|
72
|
+
blended_color.append(final_color)
|
73
|
+
|
74
|
+
# Create new VTK transfer functions with blended values
|
75
|
+
blended_otf = vtk.vtkPiecewiseFunction()
|
76
|
+
blended_ctf = vtk.vtkColorTransferFunction()
|
77
|
+
|
78
|
+
for i, scalar_val in enumerate(sample_points):
|
79
|
+
blended_otf.AddPoint(scalar_val, blended_opacity[i])
|
80
|
+
blended_ctf.AddRGBPoint(
|
81
|
+
scalar_val, blended_color[i][0], blended_color[i][1], blended_color[i][2]
|
82
|
+
)
|
83
|
+
|
84
|
+
return blended_otf, blended_ctf
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Third Party
|
2
|
+
import pydantic as pc
|
3
|
+
import vtk
|
4
|
+
|
5
|
+
# Internal
|
6
|
+
from .types import RGBColor
|
7
|
+
|
8
|
+
|
9
|
+
class ColorTransferFunctionPoint(pc.BaseModel):
|
10
|
+
"""A single point in a color transfer function."""
|
11
|
+
|
12
|
+
x: float = pc.Field(description="Scalar value")
|
13
|
+
color: RGBColor
|
14
|
+
|
15
|
+
|
16
|
+
class ColorTransferFunctionConfig(pc.BaseModel):
|
17
|
+
"""Configuration for a VTK color transfer function."""
|
18
|
+
|
19
|
+
points: list[ColorTransferFunctionPoint] = pc.Field(
|
20
|
+
min_length=1, description="Points defining the color transfer function"
|
21
|
+
)
|
22
|
+
|
23
|
+
@property
|
24
|
+
def vtk_function(self) -> vtk.vtkColorTransferFunction:
|
25
|
+
"""Create VTK color transfer function from this configuration."""
|
26
|
+
ctf = vtk.vtkColorTransferFunction()
|
27
|
+
for point in self.points:
|
28
|
+
ctf.AddRGBPoint(point.x, *point.color)
|
29
|
+
return ctf
|