cardio 2025.8.1__py3-none-any.whl → 2025.10.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 CHANGED
@@ -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,9 @@ __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",
26
+ "window_level",
25
27
  ]
26
28
 
27
- __version__ = "2025.8.1"
29
+ __version__ = "2025.10.0"
cardio/app.py CHANGED
@@ -2,49 +2,42 @@
2
2
 
3
3
  # Third Party
4
4
  import pydantic_settings as ps
5
- import tomlkit as tk
6
5
  import trame as tm
6
+ import trame.decorators
7
7
 
8
+ # Internal
8
9
  from . import __version__
9
10
  from .logic import Logic
10
-
11
- # Internal
12
11
  from .scene import Scene
13
12
  from .ui import UI
14
13
 
15
14
 
16
- class CardioApp(tm.app.TrameApp):
17
- def __init__(self, name=None):
18
- super().__init__(server=name, client_type="vue2")
15
+ @tm.decorators.TrameApp()
16
+ class CardioApp:
17
+ def __init__(self, server=None):
18
+ self.server = tm.app.get_server(server, client_type="vue3")
19
19
 
20
- # Add config file argument to Trame's parser
21
20
  self.server.cli.add_argument(
22
21
  "--config", help="TOML configuration file.", dest="cfg_file", required=False
23
22
  )
24
23
 
25
- # Add version argument
26
24
  self.server.cli.add_argument(
27
25
  "--version", action="version", version=f"cardio {__version__}"
28
26
  )
29
27
 
30
- # Create CLI settings source with Trame's parser - enable argument parsing
31
28
  cli_settings = ps.CliSettingsSource(
32
29
  Scene, root_parser=self.server.cli, cli_parse_args=True
33
30
  )
34
31
 
35
- # Parse arguments to get config file path (use parse_known_args to avoid conflicts)
36
32
  args, unknown = self.server.cli.parse_known_args()
37
33
  config_file = getattr(args, "cfg_file", None)
38
34
 
39
- # Set the CLI source and config file on the Scene class temporarily
40
35
  Scene._cli_source = cli_settings
41
36
  Scene._config_file = config_file
42
37
 
43
38
  try:
44
- # Create Scene with CLI and config file support
45
39
  scene = Scene()
46
40
  finally:
47
- # Clean up class attributes
48
41
  if hasattr(Scene, "_cli_source"):
49
42
  delattr(Scene, "_cli_source")
50
43
  if hasattr(Scene, "_config_file"):
@@ -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