cardio 2026.1.2__py3-none-any.whl → 2026.1.3__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 +1 -1
- cardio/logic.py +40 -1
- cardio/rotation.py +10 -4
- {cardio-2026.1.2.dist-info → cardio-2026.1.3.dist-info}/METADATA +2 -2
- {cardio-2026.1.2.dist-info → cardio-2026.1.3.dist-info}/RECORD +7 -7
- {cardio-2026.1.2.dist-info → cardio-2026.1.3.dist-info}/WHEEL +0 -0
- {cardio-2026.1.2.dist-info → cardio-2026.1.3.dist-info}/entry_points.txt +0 -0
cardio/__init__.py
CHANGED
cardio/logic.py
CHANGED
|
@@ -338,10 +338,18 @@ class Logic:
|
|
|
338
338
|
|
|
339
339
|
def _apply_current_mpr_settings(self, active_volume, frame):
|
|
340
340
|
"""Apply current slice positions and window/level to MPR actors."""
|
|
341
|
+
from .orientation import IndexOrder
|
|
342
|
+
|
|
341
343
|
# Apply slice positions
|
|
342
344
|
origin = getattr(self.server.state, "mpr_origin", [0.0, 0.0, 0.0])
|
|
343
345
|
rotation_sequence, rotation_angles = self._get_visible_rotation_data()
|
|
344
346
|
|
|
347
|
+
# VTK needs origin in ITK convention - convert if necessary
|
|
348
|
+
current_convention = self.scene.mpr_rotation_sequence.metadata.index_order
|
|
349
|
+
if current_convention == IndexOrder.ROMA:
|
|
350
|
+
# Convert Roma to ITK: swap X and Z
|
|
351
|
+
origin = [origin[2], origin[1], origin[0]]
|
|
352
|
+
|
|
345
353
|
active_volume.update_slice_positions(
|
|
346
354
|
frame,
|
|
347
355
|
origin,
|
|
@@ -659,6 +667,11 @@ class Logic:
|
|
|
659
667
|
|
|
660
668
|
self.server.state.mpr_rotation_data = updated_data
|
|
661
669
|
|
|
670
|
+
# Transform mpr_origin: swap X and Z (indices 0 and 2)
|
|
671
|
+
mpr_origin = getattr(self.server.state, "mpr_origin", None)
|
|
672
|
+
if mpr_origin is not None and len(mpr_origin) == 3:
|
|
673
|
+
self.server.state.mpr_origin = [mpr_origin[2], mpr_origin[1], mpr_origin[0]]
|
|
674
|
+
|
|
662
675
|
self.scene.mpr_rotation_sequence.metadata.index_order = new_convention
|
|
663
676
|
|
|
664
677
|
def _initialize_clipping_state(self):
|
|
@@ -744,6 +757,8 @@ class Logic:
|
|
|
744
757
|
|
|
745
758
|
# Initialize origin to volume center (in LPS coordinates)
|
|
746
759
|
try:
|
|
760
|
+
from .orientation import IndexOrder
|
|
761
|
+
|
|
747
762
|
current_frame = getattr(self.server.state, "frame", 0)
|
|
748
763
|
volume_actor = active_volume.actors[current_frame]
|
|
749
764
|
image_data = volume_actor.GetMapper().GetInput()
|
|
@@ -752,7 +767,16 @@ class Logic:
|
|
|
752
767
|
# Set origin to volume center if it's at default [0,0,0]
|
|
753
768
|
current_origin = getattr(self.server.state, "mpr_origin", [0.0, 0.0, 0.0])
|
|
754
769
|
if current_origin == [0.0, 0.0, 0.0]:
|
|
755
|
-
|
|
770
|
+
# VTK returns center in ITK convention (X=L, Y=P, Z=S)
|
|
771
|
+
# Transform to current convention if needed
|
|
772
|
+
center_list = list(center)
|
|
773
|
+
if (
|
|
774
|
+
self.scene.mpr_rotation_sequence.metadata.index_order
|
|
775
|
+
== IndexOrder.ROMA
|
|
776
|
+
):
|
|
777
|
+
# Convert ITK -> Roma: swap X and Z
|
|
778
|
+
center_list = [center_list[2], center_list[1], center_list[0]]
|
|
779
|
+
self.server.state.mpr_origin = center_list
|
|
756
780
|
except (RuntimeError, IndexError) as e:
|
|
757
781
|
print(f"Error: Cannot get center for volume '{active_volume_label}': {e}")
|
|
758
782
|
return
|
|
@@ -821,6 +845,7 @@ class Logic:
|
|
|
821
845
|
|
|
822
846
|
def update_slice_positions(self, **kwargs):
|
|
823
847
|
"""Update MPR slice positions when sliders change."""
|
|
848
|
+
from .orientation import IndexOrder
|
|
824
849
|
|
|
825
850
|
if not getattr(self.server.state, "mpr_enabled", False):
|
|
826
851
|
return
|
|
@@ -845,6 +870,12 @@ class Logic:
|
|
|
845
870
|
|
|
846
871
|
rotation_sequence, rotation_angles = self._get_visible_rotation_data()
|
|
847
872
|
|
|
873
|
+
# VTK needs origin in ITK convention - convert if necessary
|
|
874
|
+
current_convention = self.scene.mpr_rotation_sequence.metadata.index_order
|
|
875
|
+
if current_convention == IndexOrder.ROMA:
|
|
876
|
+
# Convert Roma to ITK: swap X and Z
|
|
877
|
+
origin = [origin[2], origin[1], origin[0]]
|
|
878
|
+
|
|
848
879
|
# Update slice positions with rotation
|
|
849
880
|
active_volume.update_slice_positions(
|
|
850
881
|
current_frame,
|
|
@@ -921,6 +952,8 @@ class Logic:
|
|
|
921
952
|
|
|
922
953
|
def update_mpr_rotation(self, **kwargs):
|
|
923
954
|
"""Update MPR views when rotation changes."""
|
|
955
|
+
from .orientation import IndexOrder
|
|
956
|
+
|
|
924
957
|
if not getattr(self.server.state, "mpr_enabled", False):
|
|
925
958
|
return
|
|
926
959
|
|
|
@@ -944,6 +977,12 @@ class Logic:
|
|
|
944
977
|
|
|
945
978
|
rotation_sequence, rotation_angles = self._get_visible_rotation_data()
|
|
946
979
|
|
|
980
|
+
# VTK needs origin in ITK convention - convert if necessary
|
|
981
|
+
current_convention = self.scene.mpr_rotation_sequence.metadata.index_order
|
|
982
|
+
if current_convention == IndexOrder.ROMA:
|
|
983
|
+
# Convert Roma to ITK: swap X and Z
|
|
984
|
+
origin = [origin[2], origin[1], origin[0]]
|
|
985
|
+
|
|
947
986
|
# Update slice positions with rotation
|
|
948
987
|
active_volume.update_slice_positions(
|
|
949
988
|
current_frame,
|
cardio/rotation.py
CHANGED
|
@@ -56,11 +56,12 @@ class RotationMetadata(pc.BaseModel):
|
|
|
56
56
|
class RotationSequence(pc.BaseModel):
|
|
57
57
|
"""Complete rotation sequence.
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
All data (angles, axes, and origin) are stored in the current convention/units:
|
|
60
60
|
- Angles: stored in units specified by metadata.angle_units
|
|
61
61
|
- Axes: stored in convention specified by metadata.index_order
|
|
62
|
+
- Origin: stored in axis order specified by metadata.index_order
|
|
62
63
|
|
|
63
|
-
When convention/units change in the UI, all existing
|
|
64
|
+
When convention/units change in the UI, all existing data is converted.
|
|
64
65
|
"""
|
|
65
66
|
|
|
66
67
|
model_config = pc.ConfigDict(frozen=False)
|
|
@@ -69,7 +70,7 @@ class RotationSequence(pc.BaseModel):
|
|
|
69
70
|
angles_list: list[RotationStep] = pc.Field(default_factory=list)
|
|
70
71
|
mpr_origin: list[float] = pc.Field(
|
|
71
72
|
default_factory=lambda: [0.0, 0.0, 0.0],
|
|
72
|
-
description="MPR origin position [x, y, z] in
|
|
73
|
+
description="MPR origin position [x, y, z] in current index_order convention",
|
|
73
74
|
)
|
|
74
75
|
|
|
75
76
|
@pc.field_validator("mpr_origin")
|
|
@@ -123,8 +124,13 @@ class RotationSequence(pc.BaseModel):
|
|
|
123
124
|
|
|
124
125
|
def to_toml(self) -> str:
|
|
125
126
|
"""Serialize to TOML using stored serialization preferences."""
|
|
127
|
+
from . import __version__
|
|
128
|
+
|
|
126
129
|
data = self.model_dump(mode="json")
|
|
127
|
-
|
|
130
|
+
toml_str = tk.dumps(data)
|
|
131
|
+
|
|
132
|
+
version_comment = f"# Generated by cardio version {__version__}\n\n"
|
|
133
|
+
return version_comment + toml_str
|
|
128
134
|
|
|
129
135
|
@classmethod
|
|
130
136
|
def from_toml(cls, toml_content: str) -> "RotationSequence":
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: cardio
|
|
3
|
-
Version: 2026.1.
|
|
3
|
+
Version: 2026.1.3
|
|
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
|
|
@@ -75,7 +75,7 @@ $ uv init
|
|
|
75
75
|
$ uv add cardio
|
|
76
76
|
$ . ./.venv/bin/activate
|
|
77
77
|
(project) cardio --version
|
|
78
|
-
cardio 2026.1.
|
|
78
|
+
cardio 2026.1.3
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
### Developing
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
cardio/__init__.py,sha256=
|
|
1
|
+
cardio/__init__.py,sha256=07x-I5z0KKgiJwm-tqtY1qZTh5YKg32IScpzzFVEeno,601
|
|
2
2
|
cardio/app.py,sha256=h-Xh57Txw3Hom-fDv08YxjXzovuIxY8_5qPy6MJThZs,1354
|
|
3
3
|
cardio/assets/bone.toml,sha256=vv8uVYSHIoKuHkNCoBOkGe2_qoEbXMvQO6ypm3mMOtA,675
|
|
4
4
|
cardio/assets/vascular_closed.toml,sha256=XtaZS_Zd6NSAtY3ZlUfiog3T86u9Ii0oSutU2wBQy78,1267
|
|
@@ -6,13 +6,13 @@ cardio/assets/vascular_open.toml,sha256=1M3sV1IGt3zh_3vviysKEk9quKfjF9xUBcIq3kxV
|
|
|
6
6
|
cardio/assets/xray.toml,sha256=siPem0OZ2OkWH0e5pizftpItJKGJgxKJ_S2K0316ubQ,693
|
|
7
7
|
cardio/blend_transfer_functions.py,sha256=fkLDYGMj_QXYs0vmXYT_B1tgTfl3YICLYimtWlxrmbQ,2958
|
|
8
8
|
cardio/color_transfer_function.py,sha256=uTyPdwxi0HjR4wm418cQN9-q9SspkkeqvLNqXaF3zzg,792
|
|
9
|
-
cardio/logic.py,sha256=
|
|
9
|
+
cardio/logic.py,sha256=AuKQZ_ACTRrtB4oxPuaKYjajsoA3yoszw7GnZK11UdQ,44721
|
|
10
10
|
cardio/mesh.py,sha256=wYdU0BU84eXrrHp0U0VLwYW7ZpRJ6GbT5Kl_-K6CBzY,9356
|
|
11
11
|
cardio/object.py,sha256=98A32VpFR4UtVqW8dZsRJR13VVyUoJJ20uoOZBgN4js,6168
|
|
12
12
|
cardio/orientation.py,sha256=xsWAFPwf3-toT4XFYyGiZJkcGFQ2uz3eLxpC1_i6yZ8,6266
|
|
13
13
|
cardio/piecewise_function.py,sha256=X-_C-BVStufmFjfF8IbkqKk9Xw_jh00JUmjC22ZqeqQ,764
|
|
14
14
|
cardio/property_config.py,sha256=YrWIyCoSAfJPigkhriIQybQpJantGnXjT4nJfrtIJco,1689
|
|
15
|
-
cardio/rotation.py,sha256=
|
|
15
|
+
cardio/rotation.py,sha256=BCh3x347AdnsRMK-6-N9o12jnGiwg7vG-DuJirzn468,5341
|
|
16
16
|
cardio/scene.py,sha256=FrUQIJrBR_jPrW7utOI3uDbaUxjmZEapzVTEVIgEWaA,15365
|
|
17
17
|
cardio/screenshot.py,sha256=l8bLgxnU5O0FlnmsyVAzKwM9Y0401IzcdnDP0WqFSTY,640
|
|
18
18
|
cardio/segmentation.py,sha256=KT1ClgultXyGpZDdpYxor38GflY7uCgRzfA8JEGQVaU,6642
|
|
@@ -24,7 +24,7 @@ cardio/volume.py,sha256=sdQ7gtX4jQDD9U8U95xD9LIV_1hpykj8NTCo1_aIKVM,15035
|
|
|
24
24
|
cardio/volume_property.py,sha256=-EUMV9sWCaetgGjnqIWxPp39qrxEZKTNDJ5GHUgLMlk,1619
|
|
25
25
|
cardio/volume_property_presets.py,sha256=4-hjo-dukm5sMMmWidbWnVXq0IN4sWpBnDITY9MqUFg,1625
|
|
26
26
|
cardio/window_level.py,sha256=XMkwLAHcmsEYcI0SoHySQZvptq4VwX2gj--ps3hV8AQ,784
|
|
27
|
-
cardio-2026.1.
|
|
28
|
-
cardio-2026.1.
|
|
29
|
-
cardio-2026.1.
|
|
30
|
-
cardio-2026.1.
|
|
27
|
+
cardio-2026.1.3.dist-info/WHEEL,sha256=4n27za1eEkOnA7dNjN6C5-O2rUiw6iapszm14Uj-Qmk,79
|
|
28
|
+
cardio-2026.1.3.dist-info/entry_points.txt,sha256=xRd1otqKtW9xmidJ4CFRX1V9KWmsBbtxrgMtDColq3w,40
|
|
29
|
+
cardio-2026.1.3.dist-info/METADATA,sha256=BE_DMgEZ959_kbfIfIMTbDs-hIhjep_SGFSmoeMm3Ag,3518
|
|
30
|
+
cardio-2026.1.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|