Skip to content

Commit abea391

Browse files
committed
bugfixes and plot settings quick access
1 parent 8947c59 commit abea391

File tree

6 files changed

+122
-27
lines changed

6 files changed

+122
-27
lines changed

omc3_gui/plotting/tfs_plotter.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def plot_dataframes(
6666
try:
6767
df[ycolumn]
6868
except KeyError:
69-
LOGGER.debug(f"Could not find column '{ycolumn}' in DataFrame '{name}. Skipping!'")
69+
LOGGER.debug(f"Could not find column '{ycolumn}' in DataFrame for '{name}'. Skipping!")
7070
continue
7171

7272
plot_errorbar(
@@ -101,7 +101,7 @@ def plot_errorbar(
101101
label: str | None = None,
102102
color: str | pg.Color | None = None,
103103
marker: str = 'o',
104-
markersize: int = 10,
104+
markersize: int = 6,
105105
linestyle: PenStyle = PenStyle.SolidLine,
106106
linewidth: float = 2,
107107
) -> tuple[pg.PlotDataItem, pg.ErrorBarItem]:

omc3_gui/segment_by_segment.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
"""
2+
Segment-by-Segment GUI
3+
----------------------
4+
5+
Graphical user interface to run the Segment-by-Segment propagation.
6+
7+
8+
TODO:
9+
10+
GUI:
11+
- Load segments from file or folder (check sbs/sbs_ files)
12+
- Save segments to file
13+
- Autoload segments when opening measurement folder
14+
- Pass measurement folders via cli args.
15+
16+
Settings:
17+
- Save settings to json file
18+
- Load settings from file
19+
- Pass path to settings file as cli arg and load on startup
20+
21+
Plotting:
22+
- Going back through plot history on double-click
23+
24+
"""
25+
126
import sys
227
from omc3_gui.segment_by_segment.main_controller import SbSController
328
from omc3_gui.utils.log_handler import init_logging

omc3_gui/segment_by_segment/main_controller.py

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,11 @@ class SbSController(Controller):
4848

4949
def __init__(self, settings: Settings | None = None):
5050
super().__init__(SbSWindow())
51-
self.connect_signals()
5251
self.settings: Settings = settings or Settings()
53-
5452
self._last_selected_optics_path: Path = self.settings.main.cwd
5553
self._running_tasks: list[BackgroundThread] = []
5654

55+
self.connect_signals()
5756
self.set_measurement_interaction_buttons_enabled(False)
5857
self.set_all_segment_buttons_enabled(False)
5958

@@ -64,6 +63,12 @@ def connect_signals(self):
6463
# Menu Bar -------------------------------------------------------------
6564
view.sig_menu_settings.connect(self.show_settings)
6665

66+
view.add_settings_to_menu(
67+
menu="View",
68+
settings=self.settings.plotting,
69+
hook=partial(self.plot, weak=True),
70+
)
71+
6772
# Measurements -------------------------------------------------------------
6873
view.button_load_measurement.clicked.connect(self.open_measurements)
6974
view.button_edit_measurement.clicked.connect(self.edit_measurement)
@@ -607,7 +612,7 @@ def run_segments(self, segments: Sequence[SegmentItemModel] | None = None):
607612
return
608613

609614
all_selected_segment_data: list[SegmentDataModel] = [sdata for s in segments for sdata in s.segments]
610-
for measurement in selected_measurements:
615+
for idx, measurement in enumerate(selected_measurements):
611616
# Filter segments that are in the measurement and sort into segments/elements
612617
selected_segments_in_meas = [s for s in measurement.segments if s in all_selected_segment_data]
613618
if not selected_segments_in_meas:
@@ -626,41 +631,47 @@ def run_segments(self, segments: Sequence[SegmentItemModel] | None = None):
626631
)
627632

628633
def clear_all():
629-
""" Clear all segment data, so that the GUI loads the new SbS data. """
634+
""" Clear all chached segment data, so that the GUI loads the new SbS data. """
630635
for segment in selected_segments_in_meas:
631636
segment.data.clear()
632637

638+
# At the very end, update plots.
639+
if idx == len(selected_measurements) - 1:
640+
self.plot()
641+
633642
# Create thread
634643
measurement_task = BackgroundThread(
635644
function=sbs_function,
636645
message=f"SbS for {measurement.display()}",
637646
on_end_function=clear_all,
638647
)
639648

640-
# Run task
641-
LOGGER.info(f"Starting {measurement_task.message}")
642-
self._add_running_task(task=measurement_task)
643-
measurement_task.start()
644-
649+
# For Real Use: Run Task ---
650+
# LOGGER.info(f"Starting {measurement_task.message}")
651+
# self._add_running_task(task=measurement_task)
652+
# measurement_task.start()
653+
645654
# For Debugging: Start sbs directly ---
646-
# sbs_function()
655+
sbs_function()
656+
clear_all()
647657
# -------------------------------------
648658

649659
# Plotting ---------------------------------------------------------------------
650-
def plot(self):
660+
def plot(self, weak: bool = False):
651661
""" Trigger a plot update with the currently selected segments. """
652662
view: SbSWindow = self._view
653663
settings: PlotSettings = self.settings.plotting
654-
655-
segments = view.get_selected_segments()
656-
if len(segments) != 1:
657-
LOGGER.error("Please select exactly one segment to plot.")
658-
return
659664

660665
if not settings.forward and not settings.backward:
661666
LOGGER.error("Please enable at least one propagation method to show.")
662667
return
663668

669+
segments = view.get_selected_segments()
670+
if len(segments) != 1:
671+
if not weak:
672+
LOGGER.error("Please select exactly one segment to plot.")
673+
return
674+
664675
self.clear_plots()
665676

666677
segments_data: list[SegmentDataModel] = segments[0].segments
@@ -684,5 +695,10 @@ def show_settings(self):
684695
LOGGER.debug("Showing settings.")
685696
settings_dialog = SettingsDialog(settings=self.settings)
686697
if settings_dialog.exec_():
687-
self.plot()
698+
view: SbSWindow = self._view
699+
view.update_menu_settings(
700+
menu="View",
701+
settings=self.settings.plotting,
702+
)
703+
self.plot(weak=True)
688704

omc3_gui/segment_by_segment/main_view.py

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,12 @@
33
-----------------------
44
55
This is the main view for the Segment-by-Segment application.
6-
7-
TODO:
8-
- Going back through plot history on double-click
9-
106
"""
117
# from omc3_gui.segment_by_segment.segment_by_segment_ui import Ui_main_window
128
from __future__ import annotations
139

10+
from dataclasses import fields
11+
from functools import partial
1412
import logging
1513
from collections.abc import Sequence
1614

@@ -129,6 +127,59 @@ def _add_menus(self):
129127
# insert before the last entry (which is "Exit")
130128
file_menu.insertAction(file_menu.actions()[-1], menu_settings)
131129

130+
def add_settings_to_menu(self, menu: str, settings: object, names: Sequence[str] | None = None, hook: callable = None):
131+
""" Add quick-access checkboxes to the menu which are connected to the respective attributes in settings.
132+
133+
Args:
134+
menu (str): Main menu name to add the settings to.
135+
settings (object): Settings to connect with the menu. Assumes dataclasses.
136+
names (Sequence[str] | None): Which fields to connect. All fields need to be boolean.
137+
hook (callable | None): Function to call after the settings have been updated.
138+
139+
"""
140+
qmenu: QtWidgets.QMenu = self.get_action_by_title(menu)
141+
142+
def update_settings(value: bool, name: str):
143+
setattr(settings, name, value)
144+
if hook is not None:
145+
hook()
146+
147+
for field in fields(settings):
148+
if names is not None and field.name not in names:
149+
continue
150+
151+
if field.name.startswith("_"):
152+
continue
153+
label = field.metadata.get("label", field.name)
154+
entry = QtWidgets.QAction(label, self)
155+
entry.setCheckable(True)
156+
entry.setChecked(getattr(settings, field.name))
157+
158+
entry.toggled.connect(partial(update_settings, name=field.name))
159+
qmenu.addAction(entry)
160+
161+
def update_menu_settings(self, menu: str, settings: object, names: Sequence[str] | None = None):
162+
""" Update the menu settings.
163+
See :func:`add_settings_to_menu`.
164+
165+
Args:
166+
menu (str): Main menu name where the settings are located.
167+
settings (object): Settings to connected with the menu. Assumes dataclasses.
168+
names (Sequence[str] | None): Which fields to connect. All fields need to be boolean.
169+
"""
170+
qmenu: QtWidgets.QMenu = self.get_action_by_title(menu)
171+
172+
for field in fields(settings):
173+
if names is not None and field.name not in names:
174+
continue
175+
176+
if field.name.startswith("_"):
177+
continue
178+
179+
label = field.metadata.get("label", field.name)
180+
entry: QtWidgets.QAction = self.get_action_by_title(label, parent=qmenu)
181+
entry.setChecked(getattr(settings, field.name))
182+
132183
def _build_gui(self):
133184
self._central = QtWidgets.QSplitter(Qt.Horizontal)
134185

omc3_gui/segment_by_segment/settings.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from __future__ import annotations
88

99
from pathlib import Path
10-
from dataclasses import dataclass, field
10+
from dataclasses import dataclass, field, fields
1111

1212
from omc3_gui.ui_components.dataclass_ui import metafield
1313

@@ -26,7 +26,6 @@ class PlotSettings:
2626
backward: bool = metafield("Backward Propagation", "Show backward propagation.", default=True)
2727
expected: bool = metafield("Expectation", "Show expected value after correction instead of correction itself.", default=False)
2828

29-
3029
@dataclass(slots=True)
3130
class Settings:
3231
main: MainSettings = field(default_factory=MainSettings)

omc3_gui/ui_components/base_classes_cvm.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
QApplication,
1919
QDesktopWidget,
2020
QDockWidget,
21+
QMenu,
2122
QMenuBar,
2223
QStatusBar,
2324
QStyle,
@@ -136,7 +137,7 @@ def build_menu_bar(self):
136137
# Set menu bar ---
137138
self.setMenuBar(self._menu_bar)
138139

139-
def get_action_by_title(self, title: str, parent: QMenuBar | None = None) -> QAction:
140+
def get_action_by_title(self, title: str, parent: QMenuBar | QMenu | None = None) -> QAction | QMenu:
140141
""" Retrieve a menu action by its title.
141142
142143
Args:
@@ -152,7 +153,10 @@ def get_action_by_title(self, title: str, parent: QMenuBar | None = None) -> QAc
152153

153154
for action in parent.actions():
154155
if action.text() == title:
155-
return action.menu()
156+
menu = action.menu()
157+
if menu is not None: # action is a menu (submenu)
158+
return menu
159+
return action # action is an entry (leaf)
156160

157161
LOGGER.debug(f"Unable to find action with title: {title} in {parent!r}")
158162
return None

0 commit comments

Comments
 (0)