cardio 2025.8.0__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.
@@ -1,272 +0,0 @@
1
- # System
2
- import pathlib as pl
3
-
4
- import numpy as np
5
- import pydantic as pc
6
-
7
- # Third Party
8
- import tomlkit as tk
9
- import vtk
10
-
11
- # Internal
12
- from .types import RGBColor, ScalarComponent
13
-
14
-
15
- def blend_transfer_functions(tfs, scalar_range=(-2000, 2000), num_samples=512):
16
- """
17
- Blend multiple transfer functions using volume rendering emission-absorption model.
18
-
19
- Based on the volume rendering equation from:
20
- - Levoy, M. "Display of Surfaces from Volume Data" IEEE Computer Graphics and Applications, 1988
21
- - Kajiya, J.T. & Von Herzen, B.P. "Ray tracing volume densities" ACM SIGGRAPH Computer Graphics, 1984
22
- - Engel, K. et al. "Real-time Volume Graphics" A K Peters, 2006, Chapter 2
23
-
24
- The volume rendering integral: I = ∫ C(s) * μ(s) * T(s) ds
25
- where C(s) = emission color, μ(s) = opacity, T(s) = transmission
26
-
27
- For discrete transfer functions, this becomes:
28
- - Total emission = Σ(color_i * opacity_i)
29
- - Total absorption = Σ(opacity_i)
30
- - Final color = total_emission / total_absorption
31
- """
32
- if len(tfs) == 1:
33
- return tfs[0]
34
-
35
- sample_points = np.linspace(
36
- start=scalar_range[0],
37
- stop=scalar_range[1],
38
- num=num_samples,
39
- )
40
-
41
- # Initialize arrays to store blended values
42
- blended_opacity = []
43
- blended_color = []
44
-
45
- for scalar_val in sample_points:
46
- # Accumulate emission and absorption for volume rendering
47
- total_emission = [0.0, 0.0, 0.0]
48
- total_absorption = 0.0
49
-
50
- for otf, ctf in tfs:
51
- # Get opacity and color for this scalar value
52
- layer_opacity = otf.GetValue(scalar_val)
53
- layer_color = [0.0, 0.0, 0.0]
54
- ctf.GetColor(scalar_val, layer_color)
55
-
56
- # Volume rendering accumulation:
57
- # Emission = color * opacity (additive)
58
- # Absorption = opacity (multiplicative through transmission)
59
- for i in range(3):
60
- total_emission[i] += layer_color[i] * layer_opacity
61
-
62
- total_absorption += layer_opacity
63
-
64
- # Clamp values to reasonable ranges
65
- total_absorption = min(total_absorption, 1.0)
66
- for i in range(3):
67
- total_emission[i] = min(total_emission[i], 1.0)
68
-
69
- # For the final color, normalize emission by absorption if absorption > 0
70
- if total_absorption > 0.001: # Avoid division by zero
71
- final_color = [total_emission[i] / total_absorption for i in range(3)]
72
- else:
73
- final_color = [0.0, 0.0, 0.0]
74
-
75
- # Clamp final colors
76
- final_color = [min(c, 1.0) for c in final_color]
77
-
78
- blended_opacity.append(total_absorption)
79
- blended_color.append(final_color)
80
-
81
- # Create new VTK transfer functions with blended values
82
- blended_otf = vtk.vtkPiecewiseFunction()
83
- blended_ctf = vtk.vtkColorTransferFunction()
84
-
85
- for i, scalar_val in enumerate(sample_points):
86
- blended_otf.AddPoint(scalar_val, blended_opacity[i])
87
- blended_ctf.AddRGBPoint(
88
- scalar_val, blended_color[i][0], blended_color[i][1], blended_color[i][2]
89
- )
90
-
91
- return blended_otf, blended_ctf
92
-
93
-
94
- class PiecewiseFunctionPoint(pc.BaseModel):
95
- """A single point in a piecewise function."""
96
-
97
- x: float = pc.Field(description="Scalar value")
98
- y: ScalarComponent
99
-
100
-
101
- class ColorTransferFunctionPoint(pc.BaseModel):
102
- """A single point in a color transfer function."""
103
-
104
- x: float = pc.Field(description="Scalar value")
105
- color: RGBColor
106
-
107
-
108
- class PiecewiseFunctionConfig(pc.BaseModel):
109
- """Configuration for a VTK piecewise function (opacity)."""
110
-
111
- points: list[PiecewiseFunctionPoint] = pc.Field(
112
- min_length=1, description="Points defining the piecewise function"
113
- )
114
-
115
- @property
116
- def vtk_function(self) -> vtk.vtkPiecewiseFunction:
117
- """Create VTK piecewise function from this configuration."""
118
- otf = vtk.vtkPiecewiseFunction()
119
- for point in self.points:
120
- otf.AddPoint(point.x, point.y)
121
- return otf
122
-
123
-
124
- class ColorTransferFunctionConfig(pc.BaseModel):
125
- """Configuration for a VTK color transfer function."""
126
-
127
- points: list[ColorTransferFunctionPoint] = pc.Field(
128
- min_length=1, description="Points defining the color transfer function"
129
- )
130
-
131
- @property
132
- def vtk_function(self) -> vtk.vtkColorTransferFunction:
133
- """Create VTK color transfer function from this configuration."""
134
- ctf = vtk.vtkColorTransferFunction()
135
- for point in self.points:
136
- ctf.AddRGBPoint(point.x, *point.color)
137
- return ctf
138
-
139
-
140
- class TransferFunctionConfig(pc.BaseModel):
141
- """Configuration for a single transfer function (legacy format for compatibility)."""
142
-
143
- window: float = pc.Field(gt=0, description="Window width for transfer function")
144
- level: float = pc.Field(description="Window level for transfer function")
145
- locolor: RGBColor
146
- hicolor: RGBColor
147
- opacity: ScalarComponent
148
-
149
- @property
150
- def vtk_functions(
151
- self,
152
- ) -> tuple[vtk.vtkPiecewiseFunction, vtk.vtkColorTransferFunction]:
153
- """Create VTK transfer functions from this configuration."""
154
- # Create opacity transfer function
155
- otf = vtk.vtkPiecewiseFunction()
156
- otf.AddPoint(self.level - self.window * 0.50, 0.0)
157
- otf.AddPoint(self.level + self.window * 0.14, self.opacity)
158
- otf.AddPoint(self.level + self.window * 0.50, 0.0)
159
-
160
- # Create color transfer function
161
- ctf = vtk.vtkColorTransferFunction()
162
- ctf.AddRGBPoint(
163
- self.level - self.window / 2,
164
- *self.locolor,
165
- )
166
- ctf.AddRGBPoint(
167
- self.level + self.window / 2,
168
- *self.hicolor,
169
- )
170
-
171
- return otf, ctf
172
-
173
-
174
- class TransferFunctionPairConfig(pc.BaseModel):
175
- """Configuration for a pair of opacity and color transfer functions."""
176
-
177
- opacity: PiecewiseFunctionConfig = pc.Field(
178
- description="Opacity transfer function configuration"
179
- )
180
- color: ColorTransferFunctionConfig = pc.Field(
181
- description="Color transfer function configuration"
182
- )
183
-
184
- @property
185
- def vtk_functions(
186
- self,
187
- ) -> tuple[vtk.vtkPiecewiseFunction, vtk.vtkColorTransferFunction]:
188
- """Create VTK transfer functions from this pair configuration."""
189
- return self.opacity.vtk_function, self.color.vtk_function
190
-
191
-
192
- class VolumePropertyConfig(pc.BaseModel):
193
- """Configuration for volume rendering properties and transfer functions."""
194
-
195
- name: str = pc.Field(description="Display name of the preset")
196
- description: str = pc.Field(description="Description of the preset")
197
-
198
- # Lighting parameters
199
- ambient: ScalarComponent
200
- diffuse: ScalarComponent
201
- specular: ScalarComponent
202
-
203
- # Transfer functions
204
- transfer_functions: list[TransferFunctionPairConfig] = pc.Field(
205
- min_length=1, description="List of transfer function pairs to blend"
206
- )
207
-
208
- @property
209
- def vtk_property(self) -> vtk.vtkVolumeProperty:
210
- """Create a fully configured VTK volume property from this configuration."""
211
- # Get VTK transfer functions from each pair config
212
- tfs = [pair.vtk_functions for pair in self.transfer_functions]
213
-
214
- # Blend all transfer functions into a single composite
215
- blended_otf, blended_ctf = blend_transfer_functions(tfs)
216
-
217
- # Create and configure the volume property
218
- _vtk_property = vtk.vtkVolumeProperty()
219
- _vtk_property.SetScalarOpacity(blended_otf)
220
- _vtk_property.SetColor(blended_ctf)
221
- _vtk_property.ShadeOn()
222
- _vtk_property.SetInterpolationTypeToLinear()
223
- _vtk_property.SetAmbient(self.ambient)
224
- _vtk_property.SetDiffuse(self.diffuse)
225
- _vtk_property.SetSpecular(self.specular)
226
-
227
- return _vtk_property
228
-
229
-
230
- def load_preset(preset_name: str) -> VolumePropertyConfig:
231
- """Load a specific preset from its individual file."""
232
- assets_dir = pl.Path(__file__).parent / "assets"
233
- preset_file = assets_dir / f"{preset_name}.toml"
234
-
235
- if not preset_file.exists():
236
- available = list(list_available_presets().keys())
237
- raise KeyError(
238
- f"Transfer function preset '{preset_name}' not found. "
239
- f"Available presets: {available}"
240
- )
241
-
242
- with preset_file.open("rt", encoding="utf-8") as fp:
243
- raw_data = tk.load(fp)
244
-
245
- try:
246
- return VolumePropertyConfig.model_validate(raw_data)
247
- except pc.ValidationError as e:
248
- raise ValueError(f"Invalid preset file '{preset_name}.toml': {e}") from e
249
-
250
-
251
- def list_available_presets() -> dict[str, str]:
252
- """
253
- List all available transfer function presets.
254
-
255
- Returns:
256
- Dictionary mapping preset names to descriptions
257
- """
258
- assets_dir = pl.Path(__file__).parent / "assets"
259
- preset_files = assets_dir.glob("*.toml")
260
-
261
- presets = {}
262
- for preset_file in preset_files:
263
- preset_name = preset_file.stem
264
- try:
265
- with preset_file.open("rt", encoding="utf-8") as fp:
266
- preset_data = tk.load(fp)
267
- presets[preset_name] = preset_data["description"]
268
- except (KeyError, OSError):
269
- # Skip files that don't have the expected structure
270
- continue
271
-
272
- return presets
@@ -1,22 +0,0 @@
1
- cardio/__init__.py,sha256=EgRG0mJMRZ4hUzkTXtNr6REwgAliqXoRa3FbkMJSTNI,505
2
- cardio/app.py,sha256=sJ2IbOrV2X-5XTkORUbxuvqvg3-Iaotkq_ofIjVzJCA,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/logic.py,sha256=Ir4W1pz_hNlQA3QgzIrh701LJY7qBF4O05qkNKfszJw,15126
8
- cardio/mesh.py,sha256=DUsjzYsTuUDsICqRK3pj49FtXjQ79CJ0HaEYupEE9R4,9392
9
- cardio/object.py,sha256=6YnEwXL5CUWoG-RAI8ETZG_DWmWi1URjVMiimCxD1zk,6448
10
- cardio/property_config.py,sha256=XJYcKeRcq8s9W9jqxzVer75r5jBLuvebv780FYdPV8U,1723
11
- cardio/scene.py,sha256=u9j2ZDobTvO8snTnNk-n3NmO7ZQ2cSC6maRCfkcMQaM,7401
12
- cardio/screenshot.py,sha256=l8bLgxnU5O0FlnmsyVAzKwM9Y0401IzcdnDP0WqFSTY,640
13
- cardio/segmentation.py,sha256=enj1V5Rw42dxjqSRUiylEQcdgvj8A-Do9Z638tf7nc8,6706
14
- cardio/transfer_functions.py,sha256=oOKShXBOBpDt8VTEZyc4IT9mCMxuYkWCKHE9kzGrs3o,9183
15
- cardio/types.py,sha256=DYDgA5QmYdU3QQrEgZMouEbMEIf40DJCeXo4V7cDXtg,356
16
- cardio/ui.py,sha256=ifbXHRSDY2Sm-zcriIk4wVySuF0NwHrZu8NCN098gIY,23180
17
- cardio/utils.py,sha256=zgyJ2PWTAWIaU8SVA9KF_XFB4251QxYAT6w3cqaIerA,3051
18
- cardio/volume.py,sha256=vCOUWKeN99Whdv0b9IEalFGUpKuNe4DILKWBL02nUUM,1892
19
- cardio-2025.8.0.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
20
- cardio-2025.8.0.dist-info/entry_points.txt,sha256=xRd1otqKtW9xmidJ4CFRX1V9KWmsBbtxrgMtDColq3w,40
21
- cardio-2025.8.0.dist-info/METADATA,sha256=CCzucRfA2xbbEqCI89K-52Nl16jEnzgXC8auFNkEWLo,3326
22
- cardio-2025.8.0.dist-info/RECORD,,