cardio 2025.8.0__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.
Files changed (31) hide show
  1. {cardio-2025.8.0 → cardio-2025.9.0}/PKG-INFO +16 -6
  2. {cardio-2025.8.0 → cardio-2025.9.0}/README.md +9 -1
  3. {cardio-2025.8.0 → cardio-2025.9.0}/pyproject.toml +8 -6
  4. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/__init__.py +8 -7
  5. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/app.py +1 -1
  6. cardio-2025.9.0/src/cardio/blend_transfer_functions.py +84 -0
  7. cardio-2025.9.0/src/cardio/color_transfer_function.py +29 -0
  8. cardio-2025.9.0/src/cardio/logic.py +775 -0
  9. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/mesh.py +2 -2
  10. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/object.py +1 -6
  11. cardio-2025.9.0/src/cardio/piecewise_function.py +29 -0
  12. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/scene.py +132 -1
  13. cardio-2025.9.0/src/cardio/transfer_function_pair.py +25 -0
  14. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/ui.py +296 -60
  15. cardio-2025.9.0/src/cardio/volume.py +274 -0
  16. cardio-2025.9.0/src/cardio/volume_property.py +46 -0
  17. cardio-2025.9.0/src/cardio/volume_property_presets.py +53 -0
  18. cardio-2025.9.0/src/cardio/window_level.py +35 -0
  19. cardio-2025.8.0/src/cardio/logic.py +0 -365
  20. cardio-2025.8.0/src/cardio/transfer_functions.py +0 -272
  21. cardio-2025.8.0/src/cardio/volume.py +0 -61
  22. {cardio-2025.8.0 → cardio-2025.9.0}/LICENSE +0 -0
  23. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/assets/bone.toml +0 -0
  24. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/assets/vascular_closed.toml +0 -0
  25. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/assets/vascular_open.toml +0 -0
  26. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/assets/xray.toml +0 -0
  27. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/property_config.py +0 -0
  28. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/screenshot.py +0 -0
  29. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/segmentation.py +0 -0
  30. {cardio-2025.8.0 → cardio-2025.9.0}/src/cardio/types.py +0 -0
  31. {cardio-2025.8.0 → 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.8.0
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
44
- Requires-Dist: isort ; extra == 'dev'
45
- Requires-Dist: pytest ; extra == 'dev'
46
- Requires-Dist: bumpver ; extra == 'dev'
43
+ Requires-Dist: trame>=3.12.0
44
+ Requires-Dist: trame-client>=3.10.1
47
45
  Requires-Dist: ruff>=0.12.10 ; extra == 'dev'
46
+ Requires-Dist: isort>=6.0.1 ; extra == 'dev'
47
+ Requires-Dist: bumpver>=2025.1131 ; extra == 'dev'
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.8.0
79
+ cardio 2025.9.0
78
80
  ```
79
81
 
80
82
  ### Developing
@@ -92,3 +94,11 @@ $ isort .
92
94
  $ ruff format
93
95
  $ pytest -v
94
96
  ```
97
+
98
+ Uploading:
99
+
100
+ ```bash
101
+ $ bumpver update
102
+ $ uv build --no-sources
103
+ $ uv publish --token <pypi_api_key>
104
+ ```
@@ -20,7 +20,7 @@ $ uv init
20
20
  $ uv add cardio
21
21
  $ . ./.venv/bin/activate
22
22
  (project) cardio --version
23
- cardio 2025.8.0
23
+ cardio 2025.9.0
24
24
  ```
25
25
 
26
26
  ### Developing
@@ -38,3 +38,11 @@ $ isort .
38
38
  $ ruff format
39
39
  $ pytest -v
40
40
  ```
41
+
42
+ Uploading:
43
+
44
+ ```bash
45
+ $ bumpver update
46
+ $ uv build --no-sources
47
+ $ uv publish --token <pypi_api_key>
48
+ ```
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "cardio"
7
- version = "2025.8.0"
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"]
@@ -44,10 +45,11 @@ requires-python = ">=3.10"
44
45
 
45
46
  [project.optional-dependencies]
46
47
  dev = [
47
- "isort",
48
- "pytest",
49
- "bumpver",
50
48
  "ruff>=0.12.10",
49
+ "isort>=6.0.1",
50
+ "bumpver>=2025.1131",
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.8.0"
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
- "load_preset",
24
- "list_available_presets",
24
+ "load_volume_property_preset",
25
+ "list_volume_property_presets",
25
26
  ]
26
27
 
27
- __version__ = "2025.8.0"
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="vue2")
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