itasc 0.2.0.dev0__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.
- itasc/__init__.py +16 -0
- itasc/cellpose/__init__.py +31 -0
- itasc/cellpose/cellpose_runner.py +334 -0
- itasc/cellpose/divergence_maps.py +380 -0
- itasc/cellpose/flow_following.py +283 -0
- itasc/cellpose/joint.py +98 -0
- itasc/cellpose/napari.yaml +10 -0
- itasc/cellpose/native_masks.py +313 -0
- itasc/cellpose/retrack.py +232 -0
- itasc/cellpose/shape.py +51 -0
- itasc/cellpose/track_laptrack.py +279 -0
- itasc/contact_analysis/__init__.py +61 -0
- itasc/contact_analysis/_provenance.py +29 -0
- itasc/contact_analysis/catalog.py +393 -0
- itasc/contact_analysis/config.py +155 -0
- itasc/contact_analysis/contacts/__init__.py +24 -0
- itasc/contact_analysis/contacts/batch.py +158 -0
- itasc/contact_analysis/contacts/build.py +837 -0
- itasc/contact_analysis/contacts/contact_labels.py +136 -0
- itasc/contact_analysis/contacts/neighborhood.py +146 -0
- itasc/contact_analysis/contacts/reader.py +58 -0
- itasc/contact_analysis/contacts/signed_contact_length.py +143 -0
- itasc/contact_analysis/curation.py +185 -0
- itasc/contact_analysis/dynamics/__init__.py +43 -0
- itasc/contact_analysis/dynamics/collective.py +206 -0
- itasc/contact_analysis/dynamics/kinematics.py +278 -0
- itasc/contact_analysis/dynamics/msd.py +165 -0
- itasc/contact_analysis/dynamics/store.py +251 -0
- itasc/contact_analysis/dynamics/trajectories.py +100 -0
- itasc/contact_analysis/frame_interval.py +85 -0
- itasc/contact_analysis/napari.yaml +10 -0
- itasc/contact_analysis/pipeline.py +313 -0
- itasc/contact_analysis/pixel_size.py +166 -0
- itasc/contact_analysis/quantifier.py +204 -0
- itasc/contact_analysis/quantifiers/__init__.py +7 -0
- itasc/contact_analysis/quantifiers/_contacts_derived.py +42 -0
- itasc/contact_analysis/quantifiers/cell_density.py +65 -0
- itasc/contact_analysis/quantifiers/cell_dynamics.py +81 -0
- itasc/contact_analysis/quantifiers/cell_shape.py +32 -0
- itasc/contact_analysis/quantifiers/contacts.py +72 -0
- itasc/contact_analysis/quantifiers/neighbor_count.py +26 -0
- itasc/contact_analysis/quantifiers/nucleus_dynamics.py +77 -0
- itasc/contact_analysis/quantifiers/nucleus_shape.py +35 -0
- itasc/contact_analysis/quantifiers/shape_relational.py +34 -0
- itasc/contact_analysis/quantifiers/signed_contact_length.py +44 -0
- itasc/contact_analysis/records.py +129 -0
- itasc/contact_analysis/shape/__init__.py +19 -0
- itasc/contact_analysis/shape/core.py +141 -0
- itasc/contact_analysis/shape/relational.py +134 -0
- itasc/contact_analysis/shape_tables.py +387 -0
- itasc/core/__init__.py +0 -0
- itasc/core/cancellation.py +15 -0
- itasc/core/commit.py +61 -0
- itasc/core/imageops.py +34 -0
- itasc/core/label_store.py +53 -0
- itasc/core/lineage.py +113 -0
- itasc/core/logging.py +51 -0
- itasc/core/paths.py +34 -0
- itasc/core/tiff.py +13 -0
- itasc/correction/__init__.py +25 -0
- itasc/correction/labels.py +983 -0
- itasc/napari/__init__.py +7 -0
- itasc/napari/_experiments_panel.py +1091 -0
- itasc/napari/_flow_layout.py +98 -0
- itasc/napari/_icons.py +126 -0
- itasc/napari/_napari_compat.py +118 -0
- itasc/napari/_paths.py +231 -0
- itasc/napari/_preview_cache.py +60 -0
- itasc/napari/_spotlight.py +46 -0
- itasc/napari/_stage_loader.py +146 -0
- itasc/napari/_stage_status.py +110 -0
- itasc/napari/_standalone_paths.py +89 -0
- itasc/napari/_state.py +146 -0
- itasc/napari/_status_rail.py +128 -0
- itasc/napari/_track_render.py +114 -0
- itasc/napari/_widget_helpers.py +236 -0
- itasc/napari/aggregate_widget.py +339 -0
- itasc/napari/candidate_gallery_controller.py +285 -0
- itasc/napari/cell_workflow_widget.py +1405 -0
- itasc/napari/cellpose_segment_track_widget.py +1253 -0
- itasc/napari/cellpose_widget.py +1034 -0
- itasc/napari/contact_analysis/__init__.py +6 -0
- itasc/napari/contact_analysis/curation_controller.py +94 -0
- itasc/napari/contact_analysis/plugins/__init__.py +102 -0
- itasc/napari/contact_analysis/plugins/catalog_summary.py +56 -0
- itasc/napari/contact_analysis/plugins/curation.py +247 -0
- itasc/napari/contact_analysis/plugins/visualize_contacts.py +64 -0
- itasc/napari/contact_analysis_widget.py +951 -0
- itasc/napari/contact_visualization.py +895 -0
- itasc/napari/correction/__init__.py +8 -0
- itasc/napari/correction/_correction_anchor.py +54 -0
- itasc/napari/correction/_correction_candidate_panel.py +259 -0
- itasc/napari/correction/_correction_candidates.py +169 -0
- itasc/napari/correction/_correction_centroids.py +132 -0
- itasc/napari/correction/_correction_commit.py +71 -0
- itasc/napari/correction/_correction_events.py +39 -0
- itasc/napari/correction/_correction_keymap.py +80 -0
- itasc/napari/correction/_correction_layer_lifecycle.py +137 -0
- itasc/napari/correction/_correction_layer_loader.py +86 -0
- itasc/napari/correction/_correction_navigation.py +97 -0
- itasc/napari/correction/_correction_paint.py +46 -0
- itasc/napari/correction/_correction_playback.py +68 -0
- itasc/napari/correction/_correction_protection.py +44 -0
- itasc/napari/correction/_correction_takeover.py +64 -0
- itasc/napari/correction/_correction_track_accordion.py +566 -0
- itasc/napari/correction/_correction_track_path.py +388 -0
- itasc/napari/correction/_correction_ui.py +367 -0
- itasc/napari/correction/_correction_ui_nucleus.py +393 -0
- itasc/napari/correction/_correction_utils.py +204 -0
- itasc/napari/correction/_correction_validation.py +83 -0
- itasc/napari/correction/cell_correction_widget.py +1190 -0
- itasc/napari/correction/correction_widget.py +1363 -0
- itasc/napari/correction/nucleus_correction_widget.py +2185 -0
- itasc/napari/divergence_maps_widget.py +795 -0
- itasc/napari/lineage_canvas_controller.py +351 -0
- itasc/napari/main_widget.py +1184 -0
- itasc/napari/nucleus_atom_extraction_widget.py +648 -0
- itasc/napari/nucleus_db_browser_widget.py +1442 -0
- itasc/napari/nucleus_pipeline_widget.py +579 -0
- itasc/napari/nucleus_segmentation_inputs_widget.py +21 -0
- itasc/napari/nucleus_tracking_inputs_widget.py +248 -0
- itasc/napari/nucleus_workflow_widget.py +963 -0
- itasc/napari/track_path_controller.py +387 -0
- itasc/napari/ui_gate.py +266 -0
- itasc/napari/ui_style.py +503 -0
- itasc/napari/validated_overlay_controller.py +254 -0
- itasc/napari/widgets.py +616 -0
- itasc/napari.yaml +45 -0
- itasc/segmentation/__init__.py +60 -0
- itasc/segmentation/cell_divergence_segmentation.py +433 -0
- itasc/segmentation/cell_label_icm.py +637 -0
- itasc/segmentation/contour_filtering.py +128 -0
- itasc/segmentation/lineage.py +18 -0
- itasc/segmentation/nucleus_segmentation.py +35 -0
- itasc/tracking_ultrack/__init__.py +12 -0
- itasc/tracking_ultrack/_node_geometry.py +191 -0
- itasc/tracking_ultrack/atoms.py +470 -0
- itasc/tracking_ultrack/config.py +76 -0
- itasc/tracking_ultrack/corrections.py +1213 -0
- itasc/tracking_ultrack/db_build.py +492 -0
- itasc/tracking_ultrack/db_query.py +952 -0
- itasc/tracking_ultrack/export.py +133 -0
- itasc/tracking_ultrack/extend.py +243 -0
- itasc/tracking_ultrack/ingest.py +83 -0
- itasc/tracking_ultrack/linking.py +286 -0
- itasc/tracking_ultrack/multi_threshold.py +46 -0
- itasc/tracking_ultrack/napari.yaml +10 -0
- itasc/tracking_ultrack/reseed.py +456 -0
- itasc/tracking_ultrack/retracker.py +153 -0
- itasc/tracking_ultrack/scoring.py +74 -0
- itasc/tracking_ultrack/seed_prior.py +175 -0
- itasc/tracking_ultrack/solve.py +52 -0
- itasc/tracking_ultrack/swap_candidate.py +171 -0
- itasc/tracking_ultrack/track_quality.py +142 -0
- itasc/tracking_ultrack/validation_nodes.py +315 -0
- itasc/tracking_ultrack/validation_state.py +430 -0
- itasc-0.2.0.dev0.data/data/itasc/napari.yaml +45 -0
- itasc-0.2.0.dev0.dist-info/METADATA +186 -0
- itasc-0.2.0.dev0.dist-info/RECORD +162 -0
- itasc-0.2.0.dev0.dist-info/WHEEL +4 -0
- itasc-0.2.0.dev0.dist-info/entry_points.txt +2 -0
- itasc-0.2.0.dev0.dist-info/licenses/LICENSE +661 -0
itasc/napari/ui_style.py
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from qtpy.QtCore import Qt
|
|
4
|
+
from qtpy.QtGui import QColor
|
|
5
|
+
from qtpy.QtWidgets import (
|
|
6
|
+
QGridLayout,
|
|
7
|
+
QLabel,
|
|
8
|
+
QPushButton,
|
|
9
|
+
QSizePolicy,
|
|
10
|
+
QToolButton,
|
|
11
|
+
QVBoxLayout,
|
|
12
|
+
QWidget,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
TINY_MARGIN = 2
|
|
16
|
+
SECTION_MARGIN = 4
|
|
17
|
+
TIGHT_SPACING = 4
|
|
18
|
+
DEFAULT_FIELD_SPACING = 8
|
|
19
|
+
DEFAULT_ROW_SPACING = 4
|
|
20
|
+
BLOCK_GRID_COLUMNS = 4
|
|
21
|
+
|
|
22
|
+
# ── Theme palette ────────────────────────────────────────────────────────
|
|
23
|
+
# Accent palettes keyed by the same color names. THEME_PALETTES selects the
|
|
24
|
+
# active set; ACTIVE_THEME_NAME picks the default. Call sites reference accents
|
|
25
|
+
# via stage_accent() so theme changes propagate automatically.
|
|
26
|
+
|
|
27
|
+
# Nord — https://www.nordtheme.com/docs/colors-and-palettes
|
|
28
|
+
NORD = {
|
|
29
|
+
"rosewater": "#bf616a", "flamingo": "#bf616a", "pink": "#b48ead",
|
|
30
|
+
"mauve": "#b48ead", "red": "#bf616a", "maroon": "#bf616a",
|
|
31
|
+
"peach": "#d08770", "yellow": "#ebcb8b", "green": "#a3be8c",
|
|
32
|
+
"teal": "#8fbcbb", "sky": "#88c0d0", "sapphire": "#81a1c1",
|
|
33
|
+
"blue": "#5e81ac", "lavender": "#b48ead",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Solarized Dark — https://ethanschoonover.com/solarized/
|
|
37
|
+
SOLARIZED_DARK = {
|
|
38
|
+
"rosewater": "#cb4b16", "flamingo": "#dc322f", "pink": "#d33682",
|
|
39
|
+
"mauve": "#6c71c4", "red": "#dc322f", "maroon": "#cb4b16",
|
|
40
|
+
"peach": "#cb4b16", "yellow": "#b58900", "green": "#859900",
|
|
41
|
+
"teal": "#2aa198", "sky": "#2aa198", "sapphire": "#268bd2",
|
|
42
|
+
"blue": "#268bd2", "lavender": "#6c71c4",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
# ITASC Field Notes. Earlier second choice after Nord: a quiet custom
|
|
46
|
+
# palette with softer workbench-style accents for the visible stages.
|
|
47
|
+
FIELD_NOTES = {
|
|
48
|
+
"rosewater": "#c9b7a2", "flamingo": "#b98175", "pink": "#b4838f",
|
|
49
|
+
"mauve": "#6f9f95", "red": "#a05f55", "maroon": "#8a5d4f",
|
|
50
|
+
"peach": "#b88a63", "yellow": "#b7a56a", "green": "#8fa77a",
|
|
51
|
+
"teal": "#6f9f95", "sky": "#789ca8", "sapphire": "#7f8c8d",
|
|
52
|
+
"blue": "#6d8aa0", "lavender": "#9a879d",
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# ITASC Museum Label. Muted, warmer, and more material than Nord while
|
|
56
|
+
# keeping enough separation between workflow stage accents.
|
|
57
|
+
MUSEUM_LABEL = {
|
|
58
|
+
"rosewater": "#d7c6b2", "flamingo": "#b98276", "pink": "#a87582",
|
|
59
|
+
"mauve": "#8f8a6f", "red": "#a65f4f", "maroon": "#7f5648",
|
|
60
|
+
"peach": "#b8794f", "yellow": "#b69b5e", "green": "#78906f",
|
|
61
|
+
"teal": "#6f9388", "sky": "#7d95a0", "sapphire": "#737a7c",
|
|
62
|
+
"blue": "#687f91", "lavender": "#8b7b8f",
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# ITASC Dusk Gradient. Previous first choice: based on a compact
|
|
66
|
+
# teal-to-plum reference palette with five ordered workflow accents.
|
|
67
|
+
DUSK_GRADIENT = {
|
|
68
|
+
"rosewater": "#b48aa4", "flamingo": "#a3739c", "pink": "#8c5ca6",
|
|
69
|
+
"mauve": "#483a5e", "red": "#9b5d86", "maroon": "#6f4790",
|
|
70
|
+
"peach": "#98d2d4", "yellow": "#b9c5c8", "green": "#8c5ca6",
|
|
71
|
+
"teal": "#608a9a", "sky": "#7eabba", "sapphire": "#608a9a",
|
|
72
|
+
"blue": "#6f8eaa", "lavender": "#6f4790",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# ITASC Sunset. Current second choice: warm reference palette mapped
|
|
76
|
+
# left-to-right across the visible workflow accents.
|
|
77
|
+
SUNSET = {
|
|
78
|
+
"rosewater": "#fccc73", "flamingo": "#fd9d5d", "pink": "#d04e6c",
|
|
79
|
+
"mauve": "#8a5687", "red": "#fd6b5d", "maroon": "#d04e6c",
|
|
80
|
+
"peach": "#fd9d5d", "yellow": "#fccc73", "green": "#fd6b5d",
|
|
81
|
+
"teal": "#d98f71", "sky": "#c66d78", "sapphire": "#fccc73",
|
|
82
|
+
"blue": "#b66b83", "lavender": "#d04e6c",
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# ITASC Signal Archive. Previous first choice: muted red, parchment, sage,
|
|
86
|
+
# teal, and charcoal reference palette mapped across the workflow accents.
|
|
87
|
+
SIGNAL_ARCHIVE = {
|
|
88
|
+
"rosewater": "#d7b2a7", "flamingo": "#c94b4b", "pink": "#a55b62",
|
|
89
|
+
"mauve": "#414643", "red": "#c94b4b", "maroon": "#8b4447",
|
|
90
|
+
"peach": "#eae3c3", "yellow": "#eae3c3", "green": "#9bb6a1",
|
|
91
|
+
"teal": "#3b7b7a", "sky": "#6f9a95", "sapphire": "#c94b4b",
|
|
92
|
+
"blue": "#3b7b7a", "lavender": "#3b7b7a",
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# ITASC Viridis. Five-stop scientific colormap palette sampled from the
|
|
96
|
+
# inverted interior 15%-85% range to avoid the darkest and brightest endpoints.
|
|
97
|
+
VIRIDIS = {
|
|
98
|
+
"rosewater": "#9bd93c", "flamingo": "#38b977", "pink": "#31668e",
|
|
99
|
+
"mauve": "#463480", "red": "#31668e", "maroon": "#463480",
|
|
100
|
+
"peach": "#38b977", "yellow": "#9bd93c", "green": "#21918c",
|
|
101
|
+
"teal": "#21918c", "sky": "#31668e", "sapphire": "#9bd93c",
|
|
102
|
+
"blue": "#31668e", "lavender": "#31668e",
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# ITASC Cividis. Current default: five-stop color-vision-friendly scientific
|
|
106
|
+
# colormap sampled from the inverted interior 15%-85% range.
|
|
107
|
+
CIVIDIS = {
|
|
108
|
+
"rosewater": "#d6c35d", "flamingo": "#a79d73", "pink": "#555c6d",
|
|
109
|
+
"mauve": "#243c6e", "red": "#555c6d", "maroon": "#243c6e",
|
|
110
|
+
"peach": "#a79d73", "yellow": "#d6c35d", "green": "#7d7c78",
|
|
111
|
+
"teal": "#7d7c78", "sky": "#555c6d", "sapphire": "#d6c35d",
|
|
112
|
+
"blue": "#555c6d", "lavender": "#555c6d",
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
THEME_PALETTES = {
|
|
116
|
+
"Cividis": CIVIDIS,
|
|
117
|
+
"Viridis": VIRIDIS,
|
|
118
|
+
"Signal Archive": SIGNAL_ARCHIVE,
|
|
119
|
+
"Dusk Gradient": DUSK_GRADIENT,
|
|
120
|
+
"Sunset": SUNSET,
|
|
121
|
+
"Nord": NORD,
|
|
122
|
+
"Field Notes": FIELD_NOTES,
|
|
123
|
+
"Museum Label": MUSEUM_LABEL,
|
|
124
|
+
}
|
|
125
|
+
ACTIVE_THEME_NAME = "Cividis"
|
|
126
|
+
ACTIVE_PALETTE = THEME_PALETTES[ACTIVE_THEME_NAME]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def theme_names() -> tuple[str, ...]:
|
|
130
|
+
return tuple(THEME_PALETTES)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def active_theme_name() -> str:
|
|
134
|
+
return ACTIVE_THEME_NAME
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def set_active_theme(name: str) -> None:
|
|
138
|
+
global ACTIVE_PALETTE, ACTIVE_THEME_NAME
|
|
139
|
+
|
|
140
|
+
ACTIVE_THEME_NAME = name
|
|
141
|
+
ACTIVE_PALETTE = THEME_PALETTES[name]
|
|
142
|
+
|
|
143
|
+
STAGE_ACCENTS = {
|
|
144
|
+
"project_status": "sapphire",
|
|
145
|
+
"cellpose": "sapphire",
|
|
146
|
+
"nucleus": "peach",
|
|
147
|
+
"cell": "green",
|
|
148
|
+
"contact_analysis": "lavender",
|
|
149
|
+
"aggregate": "teal",
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def stage_accent(stage_key: str) -> str:
|
|
154
|
+
"""Resolve a stage key to its accent hex via the active palette."""
|
|
155
|
+
return ACTIVE_PALETTE[STAGE_ACCENTS[stage_key]]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def muted_accent(hex_str: str) -> str:
|
|
159
|
+
"""Return a quieter variant of an accent color for nested or compact labels."""
|
|
160
|
+
c = QColor(hex_str)
|
|
161
|
+
h, s, l, a = c.getHslF()
|
|
162
|
+
new_s = max(0.0, s * 0.35)
|
|
163
|
+
new_l = 0.55 + (l - 0.55) * 0.3
|
|
164
|
+
new_l = max(0.0, min(1.0, new_l))
|
|
165
|
+
c.setHslF(h, new_s, new_l, a)
|
|
166
|
+
return c.name()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def muted_stage_accent(stage_key: str) -> str:
|
|
170
|
+
return muted_accent(stage_accent(stage_key))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def stage_header_pill_background(stage_key: str, alpha: int = 38) -> str:
|
|
174
|
+
color = QColor(muted_stage_accent(stage_key))
|
|
175
|
+
red, green, blue, _ = color.getRgb()
|
|
176
|
+
return f"rgba({red}, {green}, {blue}, {alpha})"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def stage_header_disabled_action_color(stage_key: str) -> str:
|
|
180
|
+
color = QColor(muted_stage_accent(stage_key))
|
|
181
|
+
h, s, l, a = color.getHslF()
|
|
182
|
+
color.setHslF(h, s * 0.55, max(0.0, l * 0.62), a)
|
|
183
|
+
return color.name()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ── Designed-surface tokens (napariTFM ExperimentsList parity) ────────────
|
|
187
|
+
# Theme-agnostic so the experiments panel sits on any host background: selected
|
|
188
|
+
# rows are a translucent white "lift", text uses a grey ramp, inputs are pills.
|
|
189
|
+
COMPACT_SPACING = 4
|
|
190
|
+
TEXT_BRIGHT = "#e6edf3"
|
|
191
|
+
TEXT_MID = "#aeb6c0"
|
|
192
|
+
TEXT_DIM = "#6b7484"
|
|
193
|
+
ROW_LIFT_BG = "rgba(255, 255, 255, 13)" # a selected/raised row surface
|
|
194
|
+
HAIRLINE = "rgba(255, 255, 255, 18)"
|
|
195
|
+
|
|
196
|
+
# Experiment-row overall-status word -> color (amber running, green done, dim queued).
|
|
197
|
+
EXPERIMENT_STATUS_COLORS = {
|
|
198
|
+
"run": "#e3b341",
|
|
199
|
+
"done": "#3fb950",
|
|
200
|
+
"queued": TEXT_DIM,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def experiment_status_color(label: str) -> str:
|
|
205
|
+
"""Color for an experiment row's overall-status word (run/done/queued)."""
|
|
206
|
+
return EXPERIMENT_STATUS_COLORS.get(label, TEXT_DIM)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def experiment_name_color(selected: bool) -> str:
|
|
210
|
+
"""Brighten the active row's name; dim the rest."""
|
|
211
|
+
return TEXT_BRIGHT if selected else TEXT_MID
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def experiment_row_style(selected: bool, accent: str) -> str:
|
|
215
|
+
"""Row container style: a raised, accent-bordered surface when selected."""
|
|
216
|
+
if not selected:
|
|
217
|
+
return (
|
|
218
|
+
"QWidget#experiment_row { background: transparent; "
|
|
219
|
+
"border: 1px solid transparent; border-radius: 8px; }"
|
|
220
|
+
)
|
|
221
|
+
r, g, b, _ = QColor(accent).getRgb()
|
|
222
|
+
return (
|
|
223
|
+
"QWidget#experiment_row { "
|
|
224
|
+
f"background: {ROW_LIFT_BG}; "
|
|
225
|
+
f"border: 1px solid rgba({r}, {g}, {b}, 130); "
|
|
226
|
+
"border-radius: 8px; }"
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def action_button_style() -> str:
|
|
231
|
+
"""Give a text QToolButton real button chrome (fill, border, rounded,
|
|
232
|
+
hover/pressed feedback) so list actions don't read as flat status labels."""
|
|
233
|
+
return (
|
|
234
|
+
"QToolButton { "
|
|
235
|
+
"background: rgba(255, 255, 255, 10); "
|
|
236
|
+
f"border: 1px solid {HAIRLINE}; border-radius: 6px; "
|
|
237
|
+
f"padding: 4px 12px; color: {TEXT_BRIGHT}; }} "
|
|
238
|
+
"QToolButton:hover { background: rgba(255, 255, 255, 22); "
|
|
239
|
+
"border-color: rgba(255, 255, 255, 46); } "
|
|
240
|
+
"QToolButton:pressed { background: rgba(255, 255, 255, 32); } "
|
|
241
|
+
"QToolButton:disabled { "
|
|
242
|
+
f"color: {TEXT_DIM}; background: rgba(255, 255, 255, 4); "
|
|
243
|
+
"border-color: rgba(255, 255, 255, 10); }"
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def mono_input_style() -> str:
|
|
248
|
+
"""Themed pill style for a QLineEdit, so config fields aren't raw Qt."""
|
|
249
|
+
return (
|
|
250
|
+
"QLineEdit { "
|
|
251
|
+
"background: rgba(255, 255, 255, 8); "
|
|
252
|
+
f"border: 1px solid {HAIRLINE}; border-radius: 6px; "
|
|
253
|
+
f"padding: 3px 7px; color: {TEXT_BRIGHT}; }} "
|
|
254
|
+
"QLineEdit:focus { border-color: rgba(255, 255, 255, 38); }"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _fixed_widget(widget, width=None):
|
|
259
|
+
if width is not None:
|
|
260
|
+
widget.setMaximumWidth(width)
|
|
261
|
+
widget.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
|
262
|
+
return widget
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
_DISABLED_PUSH_BUTTON_STYLE = (
|
|
266
|
+
"QPushButton:disabled { "
|
|
267
|
+
"color: palette(mid); "
|
|
268
|
+
"background-color: rgba(127, 124, 120, 42); "
|
|
269
|
+
"border: 1px solid rgba(127, 124, 120, 72); "
|
|
270
|
+
"border-radius: 4px; "
|
|
271
|
+
"}"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _append_button_style(button, style: str):
|
|
276
|
+
current = button.styleSheet().strip()
|
|
277
|
+
if style in current:
|
|
278
|
+
return button
|
|
279
|
+
button.setStyleSheet(f"{current} {style}".strip())
|
|
280
|
+
return button
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _disabled_push_button(button):
|
|
284
|
+
if isinstance(button, QPushButton):
|
|
285
|
+
_append_button_style(button, _DISABLED_PUSH_BUTTON_STYLE)
|
|
286
|
+
return button
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def action_button(button, expand=False):
|
|
290
|
+
horizontal_policy = (
|
|
291
|
+
QSizePolicy.Policy.Expanding if expand else QSizePolicy.Policy.Fixed
|
|
292
|
+
)
|
|
293
|
+
button.setSizePolicy(horizontal_policy, QSizePolicy.Policy.Fixed)
|
|
294
|
+
_disabled_push_button(button)
|
|
295
|
+
return button
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def icon_button(button, width=24, height=None):
|
|
299
|
+
button.setFixedWidth(width)
|
|
300
|
+
if height is not None:
|
|
301
|
+
button.setFixedHeight(height)
|
|
302
|
+
_disabled_push_button(button)
|
|
303
|
+
return button
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def muted_label(label, size_pt=8):
|
|
307
|
+
label.setStyleSheet(f"color: palette(mid); font-size: {size_pt}pt;")
|
|
308
|
+
return label
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def status_label(label, size_pt=8, italic=False, muted=False):
|
|
312
|
+
style = f"font-size: {size_pt}pt;"
|
|
313
|
+
if muted:
|
|
314
|
+
style += " color: palette(mid);"
|
|
315
|
+
if italic:
|
|
316
|
+
style += " font-style: italic;"
|
|
317
|
+
label.setStyleSheet(style)
|
|
318
|
+
return label
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def parameter_heading(label):
|
|
322
|
+
label.setStyleSheet("font-weight: 600;")
|
|
323
|
+
return label
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def stage_header_label(label, stage_key: str, size_pt: int = 9):
|
|
327
|
+
label.setProperty("itasc_stage_key", stage_key)
|
|
328
|
+
label.setProperty("itasc_stage_header_size_pt", size_pt)
|
|
329
|
+
apply_stage_header_label_style(label)
|
|
330
|
+
return label
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def stage_header_action_button(button: QToolButton, stage_key: str, size_px: int = 22):
|
|
334
|
+
button.setProperty("itasc_stage_key", stage_key)
|
|
335
|
+
button.setProperty("itasc_stage_header_action", True)
|
|
336
|
+
button.setFixedSize(size_px, size_px)
|
|
337
|
+
button.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
|
338
|
+
color = muted_stage_accent(stage_key)
|
|
339
|
+
disabled_color = stage_header_disabled_action_color(stage_key)
|
|
340
|
+
background = stage_header_pill_background(stage_key)
|
|
341
|
+
button.setStyleSheet(
|
|
342
|
+
"QToolButton { "
|
|
343
|
+
"font-weight: bold; "
|
|
344
|
+
"font-size: 9pt; "
|
|
345
|
+
f"color: {color}; "
|
|
346
|
+
f"background-color: {background}; "
|
|
347
|
+
"border: none; "
|
|
348
|
+
"border-radius: 4px; "
|
|
349
|
+
"padding: 0; "
|
|
350
|
+
"margin: 0; "
|
|
351
|
+
"text-align: center; "
|
|
352
|
+
"} "
|
|
353
|
+
"QToolButton:hover { "
|
|
354
|
+
f"background-color: {stage_header_pill_background(stage_key, alpha=58)}; "
|
|
355
|
+
"} "
|
|
356
|
+
"QToolButton:checked { "
|
|
357
|
+
f"background-color: {stage_header_pill_background(stage_key, alpha=82)}; "
|
|
358
|
+
"} "
|
|
359
|
+
"QToolButton:disabled { "
|
|
360
|
+
f"color: {disabled_color}; "
|
|
361
|
+
f"background-color: {stage_header_pill_background(stage_key, alpha=28)}; "
|
|
362
|
+
"} "
|
|
363
|
+
"QToolButton:disabled:checked { "
|
|
364
|
+
f"color: {disabled_color}; "
|
|
365
|
+
f"background-color: {stage_header_pill_background(stage_key, alpha=44)}; "
|
|
366
|
+
"}"
|
|
367
|
+
)
|
|
368
|
+
return button
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def apply_stage_header_label_style(label):
|
|
372
|
+
stage_key = label.property("itasc_stage_key")
|
|
373
|
+
if not stage_key:
|
|
374
|
+
return label
|
|
375
|
+
size_pt = label.property("itasc_stage_header_size_pt") or 9
|
|
376
|
+
label.setStyleSheet(
|
|
377
|
+
"font-weight: bold; "
|
|
378
|
+
f"font-size: {size_pt}pt; "
|
|
379
|
+
f"color: {muted_stage_accent(stage_key)}; "
|
|
380
|
+
f"background-color: {stage_header_pill_background(stage_key)}; "
|
|
381
|
+
"border-radius: 4px; "
|
|
382
|
+
"padding: 1px 6px;"
|
|
383
|
+
)
|
|
384
|
+
return label
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def refresh_stage_header_labels(root) -> None:
|
|
388
|
+
for label in root.findChildren(QLabel):
|
|
389
|
+
if label.property("itasc_stage_key"):
|
|
390
|
+
apply_stage_header_label_style(label)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def danger_button(button):
|
|
394
|
+
_disabled_push_button(button)
|
|
395
|
+
return button
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def checked_success_button(button):
|
|
399
|
+
_disabled_push_button(button)
|
|
400
|
+
return button
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def block_grid(horizontal_spacing=8, vertical_spacing=4):
|
|
404
|
+
layout = QGridLayout()
|
|
405
|
+
layout.setHorizontalSpacing(horizontal_spacing)
|
|
406
|
+
layout.setVerticalSpacing(vertical_spacing)
|
|
407
|
+
for col in range(BLOCK_GRID_COLUMNS):
|
|
408
|
+
layout.setColumnStretch(col, 0)
|
|
409
|
+
return layout
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def section_grid():
|
|
413
|
+
"""A 4-column grid (label, field, label, field) where field columns
|
|
414
|
+
stretch — so sliders, combos, and labels fill the available width and
|
|
415
|
+
label columns stay aligned across all sections that share the grid."""
|
|
416
|
+
layout = QGridLayout()
|
|
417
|
+
layout.setHorizontalSpacing(DEFAULT_FIELD_SPACING)
|
|
418
|
+
layout.setVerticalSpacing(DEFAULT_ROW_SPACING)
|
|
419
|
+
layout.setColumnStretch(0, 0)
|
|
420
|
+
layout.setColumnStretch(1, 1)
|
|
421
|
+
layout.setColumnStretch(2, 0)
|
|
422
|
+
layout.setColumnStretch(3, 1)
|
|
423
|
+
return layout
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def add_section_header(grid, row, widget):
|
|
427
|
+
"""Add a heading widget spanning all 4 columns of a section_grid."""
|
|
428
|
+
grid.addWidget(widget, row, 0, 1, 4)
|
|
429
|
+
return widget
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def add_section_full_row(grid, row, widget):
|
|
433
|
+
"""Add a widget (separator, checkbox, …) spanning all 4 columns."""
|
|
434
|
+
grid.addWidget(widget, row, 0, 1, 4)
|
|
435
|
+
return widget
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def add_section_pair_row(
|
|
439
|
+
grid, row,
|
|
440
|
+
left_label, left_widget,
|
|
441
|
+
right_label=None, right_widget=None,
|
|
442
|
+
):
|
|
443
|
+
"""Add a row with up to two [label][widget] pairs. Widgets keep their
|
|
444
|
+
natural size policy (no fixed-width wrap) so sliders/combos can stretch."""
|
|
445
|
+
left_label_widget = _block_label(left_label)
|
|
446
|
+
_add_section_pair_cell(grid, row, 0, left_label_widget, left_widget)
|
|
447
|
+
|
|
448
|
+
right_label_widget = None
|
|
449
|
+
if right_widget is not None:
|
|
450
|
+
right_label_widget = _block_label(right_label or "")
|
|
451
|
+
_add_section_pair_cell(grid, row, 2, right_label_widget, right_widget)
|
|
452
|
+
return left_label_widget, left_widget, right_label_widget, right_widget
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def _add_section_pair_cell(grid, row, column, label_widget, widget):
|
|
456
|
+
container = QWidget()
|
|
457
|
+
layout = QVBoxLayout(container)
|
|
458
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
459
|
+
layout.setSpacing(1)
|
|
460
|
+
label_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBottom)
|
|
461
|
+
layout.addWidget(label_widget)
|
|
462
|
+
layout.addWidget(widget)
|
|
463
|
+
grid.addWidget(container, row, column, 1, 2)
|
|
464
|
+
return container
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _block_label(text):
|
|
468
|
+
label = QLabel(text)
|
|
469
|
+
label.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
|
470
|
+
return label
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def add_block_pair_row(
|
|
474
|
+
grid,
|
|
475
|
+
row,
|
|
476
|
+
left_label,
|
|
477
|
+
left_widget,
|
|
478
|
+
right_label=None,
|
|
479
|
+
right_widget=None,
|
|
480
|
+
field_width=70,
|
|
481
|
+
):
|
|
482
|
+
left_label_widget = _block_label(left_label)
|
|
483
|
+
_add_block_pair_cell(grid, row, 0, left_label_widget, left_widget, field_width)
|
|
484
|
+
|
|
485
|
+
right_label_widget = None
|
|
486
|
+
if right_widget is not None:
|
|
487
|
+
right_label_widget = _block_label(right_label or "")
|
|
488
|
+
_add_block_pair_cell(grid, row, 2, right_label_widget, right_widget, field_width)
|
|
489
|
+
|
|
490
|
+
return left_label_widget, left_widget, right_label_widget, right_widget
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
def _add_block_pair_cell(grid, row, column, label_widget, widget, field_width):
|
|
494
|
+
container = QWidget()
|
|
495
|
+
layout = QVBoxLayout(container)
|
|
496
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
497
|
+
layout.setSpacing(1)
|
|
498
|
+
label_widget.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBottom)
|
|
499
|
+
layout.addWidget(label_widget)
|
|
500
|
+
field = widget if widget.property("itasc_stack_section_label") else _fixed_widget(widget, field_width)
|
|
501
|
+
layout.addWidget(field)
|
|
502
|
+
grid.addWidget(container, row, column, 1, 2)
|
|
503
|
+
return container
|