cardio 2026.1.4__tar.gz → 2026.2.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-2026.1.4 → cardio-2026.2.0}/PKG-INFO +2 -2
- {cardio-2026.1.4 → cardio-2026.2.0}/README.md +1 -1
- {cardio-2026.1.4 → cardio-2026.2.0}/pyproject.toml +2 -2
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/__init__.py +1 -1
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/logic.py +58 -47
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/rotation.py +8 -63
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/ui.py +19 -18
- {cardio-2026.1.4 → cardio-2026.2.0}/LICENSE +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/app.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/assets/bone.toml +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/assets/vascular_closed.toml +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/assets/vascular_open.toml +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/assets/xray.toml +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/blend_transfer_functions.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/color_transfer_function.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/mesh.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/object.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/orientation.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/piecewise_function.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/property_config.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/scene.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/screenshot.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/segmentation.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/transfer_function_pair.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/types.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/utils.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/volume.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/volume_property.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/volume_property_presets.py +0 -0
- {cardio-2026.1.4 → cardio-2026.2.0}/src/cardio/window_level.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: cardio
|
|
3
|
-
Version: 2026.
|
|
3
|
+
Version: 2026.2.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
|
|
@@ -75,7 +75,7 @@ $ uv init
|
|
|
75
75
|
$ uv add cardio
|
|
76
76
|
$ . ./.venv/bin/activate
|
|
77
77
|
(project) cardio --version
|
|
78
|
-
cardio 2026.
|
|
78
|
+
cardio 2026.2.0
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
### Developing
|
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cardio"
|
|
7
|
-
version = "2026.
|
|
7
|
+
version = "2026.2.0"
|
|
8
8
|
authors = [
|
|
9
9
|
{name = "Davis Marc Vigneault", email = "davis.vigneault@gmail.com"}
|
|
10
10
|
]
|
|
@@ -62,7 +62,7 @@ repository = "https://github.com/sudomakeinstall/cardio"
|
|
|
62
62
|
profile = "black"
|
|
63
63
|
|
|
64
64
|
[tool.bumpver]
|
|
65
|
-
current_version = "2026.
|
|
65
|
+
current_version = "2026.2.0"
|
|
66
66
|
version_pattern = "YYYY.MM.INC0"
|
|
67
67
|
commit_message = "ENH: Bump version from {old_version} => {new_version}"
|
|
68
68
|
commit = true
|
|
@@ -28,8 +28,8 @@ class Logic:
|
|
|
28
28
|
visible_index = 0
|
|
29
29
|
for rotation in angles_list:
|
|
30
30
|
if rotation.get("visible", True):
|
|
31
|
-
rotation_sequence.append({"axis": rotation["
|
|
32
|
-
rotation_angles[visible_index] = rotation["
|
|
31
|
+
rotation_sequence.append({"axis": rotation["axis"]})
|
|
32
|
+
rotation_angles[visible_index] = rotation["angle"]
|
|
33
33
|
visible_index += 1
|
|
34
34
|
|
|
35
35
|
# CRITICAL: VTK always needs rotations in ITK convention
|
|
@@ -201,17 +201,18 @@ class Logic:
|
|
|
201
201
|
self.server.state.mpr_level = self.scene.mpr_level
|
|
202
202
|
self.server.state.mpr_window_level_preset = self.scene.mpr_window_level_preset
|
|
203
203
|
|
|
204
|
-
# Initialize rotation data from RotationSequence
|
|
204
|
+
# Initialize rotation data from RotationSequence (includes metadata)
|
|
205
205
|
self.server.state.mpr_rotation_data = (
|
|
206
|
-
self.scene.mpr_rotation_sequence.
|
|
206
|
+
self.scene.mpr_rotation_sequence.model_dump(mode="json")
|
|
207
207
|
)
|
|
208
208
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
209
|
+
# Keep mirror variables for UI binding convenience
|
|
210
|
+
self.server.state.angle_units = self.server.state.mpr_rotation_data["metadata"][
|
|
211
|
+
"angle_units"
|
|
212
|
+
]
|
|
213
|
+
self.server.state.index_order = self.server.state.mpr_rotation_data["metadata"][
|
|
214
|
+
"index_order"
|
|
215
|
+
]
|
|
215
216
|
|
|
216
217
|
# Initialize MPR presets data
|
|
217
218
|
try:
|
|
@@ -541,23 +542,14 @@ class Logic:
|
|
|
541
542
|
save_dir = self.scene.rotations_directory / active_volume_label
|
|
542
543
|
save_dir.mkdir(parents=True, exist_ok=True)
|
|
543
544
|
|
|
544
|
-
rotation_data = getattr(
|
|
545
|
-
self.server.state, "mpr_rotation_data", {"angles_list": []}
|
|
546
|
-
)
|
|
547
|
-
rotation_seq = RotationSequence.from_ui_dict(rotation_data, active_volume_label)
|
|
545
|
+
rotation_data = getattr(self.server.state, "mpr_rotation_data", {})
|
|
548
546
|
|
|
549
|
-
|
|
550
|
-
rotation_seq
|
|
547
|
+
# Create RotationSequence directly from full data structure
|
|
548
|
+
rotation_seq = RotationSequence(**rotation_data)
|
|
551
549
|
|
|
550
|
+
# Update only timestamp and volume_label (rest already in metadata)
|
|
552
551
|
rotation_seq.metadata.timestamp = timestamp.isoformat()
|
|
553
552
|
rotation_seq.metadata.volume_label = active_volume_label
|
|
554
|
-
rotation_seq.metadata.coordinate_system = self.scene.coordinate_system
|
|
555
|
-
rotation_seq.metadata.index_order = (
|
|
556
|
-
self.scene.mpr_rotation_sequence.metadata.index_order
|
|
557
|
-
)
|
|
558
|
-
rotation_seq.metadata.angle_units = (
|
|
559
|
-
self.scene.mpr_rotation_sequence.metadata.angle_units
|
|
560
|
-
)
|
|
561
553
|
|
|
562
554
|
output_path = save_dir / f"{timestamp_str}.toml"
|
|
563
555
|
rotation_seq.to_file(output_path)
|
|
@@ -606,26 +598,31 @@ class Logic:
|
|
|
606
598
|
if new_units is None or old_units == new_units:
|
|
607
599
|
return
|
|
608
600
|
|
|
609
|
-
#
|
|
610
|
-
rotation_data =
|
|
611
|
-
self.server.state, "mpr_rotation_data", {
|
|
601
|
+
# Get current rotation data (now includes metadata)
|
|
602
|
+
rotation_data = copy.deepcopy(
|
|
603
|
+
getattr(self.server.state, "mpr_rotation_data", {})
|
|
612
604
|
)
|
|
613
|
-
if rotation_data.get("angles_list"):
|
|
614
|
-
updated_data = copy.deepcopy(rotation_data)
|
|
615
605
|
|
|
616
|
-
|
|
617
|
-
|
|
606
|
+
# Convert all existing rotation angles
|
|
607
|
+
if rotation_data.get("angles_list"):
|
|
608
|
+
for rotation in rotation_data["angles_list"]:
|
|
609
|
+
current_angle = rotation.get("angle", 0)
|
|
618
610
|
|
|
619
611
|
# Convert based on old -> new units
|
|
620
612
|
if old_units == AngleUnits.DEGREES and new_units == AngleUnits.RADIANS:
|
|
621
|
-
rotation["
|
|
613
|
+
rotation["angle"] = np.radians(current_angle)
|
|
622
614
|
elif (
|
|
623
615
|
old_units == AngleUnits.RADIANS and new_units == AngleUnits.DEGREES
|
|
624
616
|
):
|
|
625
|
-
rotation["
|
|
617
|
+
rotation["angle"] = np.degrees(current_angle)
|
|
626
618
|
|
|
627
|
-
|
|
619
|
+
# Update nested metadata
|
|
620
|
+
rotation_data["metadata"]["angle_units"] = angle_units
|
|
628
621
|
|
|
622
|
+
# Update state (triggers re-render)
|
|
623
|
+
self.server.state.mpr_rotation_data = rotation_data
|
|
624
|
+
|
|
625
|
+
# Update scene
|
|
629
626
|
self.scene.mpr_rotation_sequence.metadata.angle_units = new_units
|
|
630
627
|
|
|
631
628
|
def sync_index_order(self, index_order, **kwargs):
|
|
@@ -651,27 +648,33 @@ class Logic:
|
|
|
651
648
|
if old_convention == new_convention:
|
|
652
649
|
return
|
|
653
650
|
|
|
654
|
-
|
|
655
|
-
|
|
651
|
+
# Get current rotation data (now includes metadata)
|
|
652
|
+
rotation_data = copy.deepcopy(
|
|
653
|
+
getattr(self.server.state, "mpr_rotation_data", {})
|
|
656
654
|
)
|
|
657
|
-
if rotation_data.get("angles_list"):
|
|
658
|
-
updated_data = copy.deepcopy(rotation_data)
|
|
659
655
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
656
|
+
# Convert rotation axes and angles
|
|
657
|
+
if rotation_data.get("angles_list"):
|
|
658
|
+
for rotation in rotation_data["angles_list"]:
|
|
659
|
+
current_axis = rotation.get("axis")
|
|
660
|
+
current_angle = rotation.get("angle", 0)
|
|
663
661
|
|
|
664
662
|
# Conversion is the same for both ITK<->ROMA directions
|
|
665
|
-
rotation["
|
|
666
|
-
rotation["
|
|
663
|
+
rotation["axis"] = {"X": "Z", "Y": "Y", "Z": "X"}[current_axis]
|
|
664
|
+
rotation["angle"] = -current_angle
|
|
665
|
+
|
|
666
|
+
# Update nested metadata
|
|
667
|
+
rotation_data["metadata"]["index_order"] = index_order
|
|
667
668
|
|
|
668
|
-
|
|
669
|
+
# Update state (triggers re-render)
|
|
670
|
+
self.server.state.mpr_rotation_data = rotation_data
|
|
669
671
|
|
|
670
672
|
# Transform mpr_origin: swap X and Z (indices 0 and 2)
|
|
671
673
|
mpr_origin = getattr(self.server.state, "mpr_origin", None)
|
|
672
674
|
if mpr_origin is not None and len(mpr_origin) == 3:
|
|
673
675
|
self.server.state.mpr_origin = [mpr_origin[2], mpr_origin[1], mpr_origin[0]]
|
|
674
676
|
|
|
677
|
+
# Update scene
|
|
675
678
|
self.scene.mpr_rotation_sequence.metadata.index_order = new_convention
|
|
676
679
|
|
|
677
680
|
def _initialize_clipping_state(self):
|
|
@@ -1030,8 +1033,8 @@ class Logic:
|
|
|
1030
1033
|
|
|
1031
1034
|
angles_list.append(
|
|
1032
1035
|
{
|
|
1033
|
-
"
|
|
1034
|
-
"
|
|
1036
|
+
"axis": axis,
|
|
1037
|
+
"angle": 0,
|
|
1035
1038
|
"visible": True,
|
|
1036
1039
|
"name": "",
|
|
1037
1040
|
"name_editable": True,
|
|
@@ -1065,13 +1068,21 @@ class Logic:
|
|
|
1065
1068
|
angles_list = current_data["angles_list"]
|
|
1066
1069
|
|
|
1067
1070
|
if 0 <= index < len(angles_list):
|
|
1068
|
-
angles_list[index]["
|
|
1071
|
+
angles_list[index]["angle"] = 0
|
|
1069
1072
|
current_data["angles_list"] = angles_list
|
|
1070
1073
|
self.server.state.mpr_rotation_data = current_data
|
|
1071
1074
|
|
|
1072
1075
|
def reset_mpr_rotations(self):
|
|
1073
1076
|
"""Reset all MPR rotations."""
|
|
1074
|
-
|
|
1077
|
+
from .rotation import RotationSequence
|
|
1078
|
+
|
|
1079
|
+
# Create fresh RotationSequence and serialize
|
|
1080
|
+
new_rotation_seq = RotationSequence()
|
|
1081
|
+
self.server.state.mpr_rotation_data = new_rotation_seq.model_dump(mode="json")
|
|
1082
|
+
|
|
1083
|
+
# Update mirror variables
|
|
1084
|
+
self.server.state.angle_units = "radians"
|
|
1085
|
+
self.server.state.index_order = "itk"
|
|
1075
1086
|
|
|
1076
1087
|
def finalize_mpr_initialization(self, **kwargs):
|
|
1077
1088
|
"""Set the active volume label after UI is ready to avoid race condition."""
|
|
@@ -15,33 +15,18 @@ from .orientation import AngleUnits, IndexOrder
|
|
|
15
15
|
class RotationStep(pc.BaseModel):
|
|
16
16
|
"""Single rotation step.
|
|
17
17
|
|
|
18
|
-
Both
|
|
19
|
-
-
|
|
20
|
-
-
|
|
18
|
+
Both angle and axis are stored in the current convention/units.
|
|
19
|
+
- Angle: stored in units specified by parent metadata.angle_units
|
|
20
|
+
- Axis: stored in convention specified by parent metadata.index_order
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
axis: ty.Literal["X", "Y", "Z"]
|
|
24
24
|
angle: float = 0.0
|
|
25
25
|
visible: bool = True
|
|
26
26
|
name: str = ""
|
|
27
27
|
name_editable: bool = True
|
|
28
28
|
deletable: bool = True
|
|
29
29
|
|
|
30
|
-
@pc.model_validator(mode="before")
|
|
31
|
-
@classmethod
|
|
32
|
-
def handle_legacy_format(cls, data):
|
|
33
|
-
"""Handle legacy 'angles' list format and 'axis' field."""
|
|
34
|
-
if isinstance(data, dict):
|
|
35
|
-
if "angles" in data and "angle" not in data:
|
|
36
|
-
angles = data["angles"]
|
|
37
|
-
if isinstance(angles, list) and len(angles) > 0:
|
|
38
|
-
data["angle"] = angles[0]
|
|
39
|
-
else:
|
|
40
|
-
data["angle"] = angles
|
|
41
|
-
if "axis" in data and "axes" not in data:
|
|
42
|
-
data["axes"] = data["axis"]
|
|
43
|
-
return data
|
|
44
|
-
|
|
45
30
|
|
|
46
31
|
class RotationMetadata(pc.BaseModel):
|
|
47
32
|
"""Metadata for TOML files."""
|
|
@@ -51,14 +36,15 @@ class RotationMetadata(pc.BaseModel):
|
|
|
51
36
|
angle_units: AngleUnits = AngleUnits.RADIANS
|
|
52
37
|
timestamp: str = pc.Field(default_factory=lambda: dt.datetime.now().isoformat())
|
|
53
38
|
volume_label: str = ""
|
|
39
|
+
deletable: bool = True
|
|
54
40
|
|
|
55
41
|
|
|
56
42
|
class RotationSequence(pc.BaseModel):
|
|
57
43
|
"""Complete rotation sequence.
|
|
58
44
|
|
|
59
|
-
All data (
|
|
60
|
-
-
|
|
61
|
-
-
|
|
45
|
+
All data (angle, axis, and origin) are stored in the current convention/units:
|
|
46
|
+
- Angle: stored in units specified by metadata.angle_units
|
|
47
|
+
- Axis: stored in convention specified by metadata.index_order
|
|
62
48
|
- Origin: stored in axis order specified by metadata.index_order
|
|
63
49
|
|
|
64
50
|
When convention/units change in the UI, all existing data is converted.
|
|
@@ -81,47 +67,6 @@ class RotationSequence(pc.BaseModel):
|
|
|
81
67
|
raise ValueError("mpr_origin must be a 3-element list [x, y, z]")
|
|
82
68
|
return [float(x) for x in v]
|
|
83
69
|
|
|
84
|
-
@pc.field_validator("angles_list", mode="before")
|
|
85
|
-
@classmethod
|
|
86
|
-
def convert_legacy_list(cls, v):
|
|
87
|
-
"""Convert legacy list of dicts to list of RotationStep."""
|
|
88
|
-
if isinstance(v, list) and len(v) > 0 and isinstance(v[0], dict):
|
|
89
|
-
return [RotationStep(**item) for item in v]
|
|
90
|
-
return v
|
|
91
|
-
|
|
92
|
-
def to_dict_for_ui(self) -> dict:
|
|
93
|
-
"""Convert to UI format.
|
|
94
|
-
|
|
95
|
-
Angles are passed through in their current units (metadata.angle_units).
|
|
96
|
-
UI expects 'angles' as a list for backward compatibility.
|
|
97
|
-
"""
|
|
98
|
-
return {
|
|
99
|
-
"angles_list": [
|
|
100
|
-
{
|
|
101
|
-
"axes": step.axes,
|
|
102
|
-
"angles": [step.angle],
|
|
103
|
-
"visible": step.visible,
|
|
104
|
-
"name": step.name,
|
|
105
|
-
"name_editable": step.name_editable,
|
|
106
|
-
"deletable": step.deletable,
|
|
107
|
-
}
|
|
108
|
-
for step in self.angles_list
|
|
109
|
-
],
|
|
110
|
-
"mpr_origin": self.mpr_origin,
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
@classmethod
|
|
114
|
-
def from_ui_dict(cls, data: dict, volume_label: str = "") -> "RotationSequence":
|
|
115
|
-
"""Create from UI format.
|
|
116
|
-
|
|
117
|
-
Angles from UI are stored as-is (in current UI units).
|
|
118
|
-
Caller should set metadata.angle_units to match the UI's current units.
|
|
119
|
-
"""
|
|
120
|
-
angles_list = [RotationStep(**step) for step in data.get("angles_list", [])]
|
|
121
|
-
metadata = RotationMetadata(volume_label=volume_label)
|
|
122
|
-
mpr_origin = data.get("mpr_origin", [0.0, 0.0, 0.0])
|
|
123
|
-
return cls(metadata=metadata, angles_list=angles_list, mpr_origin=mpr_origin)
|
|
124
|
-
|
|
125
70
|
def to_toml(self) -> str:
|
|
126
71
|
"""Serialize to TOML using stored serialization preferences."""
|
|
127
72
|
from . import __version__
|
|
@@ -173,9 +173,9 @@ class UI:
|
|
|
173
173
|
|
|
174
174
|
for rotation in angles_list:
|
|
175
175
|
if rotation.get("visible", True):
|
|
176
|
-
angle = rotation.get("
|
|
176
|
+
angle = rotation.get("angle", 0)
|
|
177
177
|
rotation_matrix = euler_angle_to_rotation_matrix(
|
|
178
|
-
EulerAxis(rotation["
|
|
178
|
+
EulerAxis(rotation["axis"]), angle, angle_units
|
|
179
179
|
)
|
|
180
180
|
cumulative_rotation = cumulative_rotation @ rotation_matrix
|
|
181
181
|
|
|
@@ -526,20 +526,6 @@ class UI:
|
|
|
526
526
|
color="primary",
|
|
527
527
|
)
|
|
528
528
|
|
|
529
|
-
# Reset rotations button
|
|
530
|
-
vuetify.VBtn(
|
|
531
|
-
"Remove All Rotations",
|
|
532
|
-
v_if="!maximized_view && active_volume_label && mpr_rotation_data.angles_list && mpr_rotation_data.angles_list.length > 0",
|
|
533
|
-
click=self.server.controller.reset_rotations,
|
|
534
|
-
small=True,
|
|
535
|
-
dense=True,
|
|
536
|
-
outlined=True,
|
|
537
|
-
color="warning",
|
|
538
|
-
block=True,
|
|
539
|
-
classes="mb-2",
|
|
540
|
-
prepend_icon="mdi-refresh",
|
|
541
|
-
)
|
|
542
|
-
|
|
543
529
|
# Individual rotation sliders with DeepReactive
|
|
544
530
|
with client.DeepReactive("mpr_rotation_data"):
|
|
545
531
|
for i in range(self.scene.max_mpr_rotations):
|
|
@@ -569,7 +555,7 @@ class UI:
|
|
|
569
555
|
with vuetify.VCol(cols="12"):
|
|
570
556
|
vuetify.VSlider(
|
|
571
557
|
v_model=(
|
|
572
|
-
f"mpr_rotation_data.angles_list[{i}].
|
|
558
|
+
f"mpr_rotation_data.angles_list[{i}].angle",
|
|
573
559
|
),
|
|
574
560
|
min=(
|
|
575
561
|
"angle_units === 'radians' ? -Math.PI : -180",
|
|
@@ -591,7 +577,7 @@ class UI:
|
|
|
591
577
|
with vuetify.VCol(cols="4"):
|
|
592
578
|
vuetify.VSelect(
|
|
593
579
|
v_model=(
|
|
594
|
-
f"mpr_rotation_data.angles_list[{i}].
|
|
580
|
+
f"mpr_rotation_data.angles_list[{i}].axis",
|
|
595
581
|
),
|
|
596
582
|
items=(["X", "Y", "Z"],),
|
|
597
583
|
dense=True,
|
|
@@ -692,6 +678,21 @@ class UI:
|
|
|
692
678
|
prepend_icon="mdi-content-save",
|
|
693
679
|
)
|
|
694
680
|
|
|
681
|
+
# Delete rotations button
|
|
682
|
+
vuetify.VBtn(
|
|
683
|
+
"Delete All Rotations",
|
|
684
|
+
v_if="!maximized_view && active_volume_label && mpr_rotation_data.angles_list && mpr_rotation_data.angles_list.length > 0",
|
|
685
|
+
click=self.server.controller.reset_rotations,
|
|
686
|
+
small=True,
|
|
687
|
+
dense=True,
|
|
688
|
+
outlined=True,
|
|
689
|
+
color="error",
|
|
690
|
+
block=True,
|
|
691
|
+
classes="mb-2",
|
|
692
|
+
prepend_icon="mdi-refresh",
|
|
693
|
+
disabled=("!mpr_rotation_data.metadata.deletable",),
|
|
694
|
+
)
|
|
695
|
+
|
|
695
696
|
vuetify.VDivider(classes="my-2")
|
|
696
697
|
|
|
697
698
|
vuetify.VListSubheader("Playback Controls")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|