Skip to content

Commit c0f9bc1

Browse files
committed
autoload and such
1 parent abea391 commit c0f9bc1

File tree

9 files changed

+193
-72
lines changed

9 files changed

+193
-72
lines changed

omc3_gui/plotting/tfs_plotter.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import logging
1414

1515
from qtpy.QtCore import Qt
16+
from qtpy.QtGui import QColor
1617

1718
from omc3_gui.plotting.classes import ObservablePlotDataItem
1819

@@ -136,8 +137,12 @@ def plot_errorbar(
136137
xerr = safe_convert_to_numpy(xerr)
137138
yerr = safe_convert_to_numpy(yerr)
138139
names = safe_convert_to_numpy(names)
140+
141+
hex_color = None
142+
if color is not None:
143+
hex_color = curvePen.color().name(QColor.NameFormat.HexRgb)
139144

140-
tooltips = create_tooltips(x, y, xerr, yerr, names, label)
145+
tooltips = create_tooltips(x, y, xerr, yerr, names, label, hex_color)
141146
curve = ObservablePlotDataItem(
142147
x=x, y=y, data=tooltips,
143148
name=label,
@@ -167,7 +172,7 @@ def plot_errorbar(
167172
return curve, errorbar
168173

169174

170-
def create_tooltips(x, y, xerr, yerr, names, label) -> list[str]:
175+
def create_tooltips(x, y, xerr, yerr, names, label, color) -> list[str]:
171176
"""
172177
Create a list of tooltips for a given errorbar.
173178
@@ -177,27 +182,36 @@ def create_tooltips(x, y, xerr, yerr, names, label) -> list[str]:
177182
xerr (Sequence): The xerr values of the errorbar.
178183
yerr (Sequence): The yerr values of the errorbar.
179184
names (Sequence): The names of the entries in the data sequence.
180-
label (str | None, optional): The label of the errorbar. Defaults to None.
185+
label (str | None, optional): The label of the errorbar
186+
color (str | None, optional): The color of the tooltip background
181187
"""
182188
tooltips = [""] * len(x)
183189

184190

185191
for index in range(len(x)):
186-
tooltip_text = ""
192+
tooltip_text = "<html>"
193+
194+
if color is not None:
195+
tooltip_text += "" # TODO
196+
187197
if label is not None:
188-
tooltip_text += f"{label}\n"
198+
tooltip_text += f"{label}<br>"
189199

190200
tooltip_text += f"x: {x[index]:.2e}"
191201
if xerr is not None:
192202
tooltip_text += f" ± {xerr[index]:.2e}"
193203

194-
tooltip_text += f"\ny: {y[index]:.2e}"
204+
tooltip_text += f"<br>y: {y[index]:.2e}"
195205
if yerr is not None:
196206
tooltip_text += f" ± {yerr[index]:.2e}"
197207

198208
if names is not None:
199-
tooltip_text += f"\n{names[index]}"
200-
209+
tooltip_text += f"<br>{names[index]}"
210+
211+
if color is not None:
212+
tooltip_text += ""
213+
214+
tooltip_text += "</html>"
201215

202216
tooltips[index] = tooltip_text
203217
return tooltips

omc3_gui/segment_by_segment.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@
3434

3535
if __name__ == "__main__":
3636
init_logging()
37-
sys.exit(SbSController.run_application())
37+
sys.exit(SbSController.run_application(measurements=["/mnt/volume/jdilly/projects/omc3_gui/tst_SBStest_wACD/measured_optics"]))

omc3_gui/segment_by_segment/defaults.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
55
Defaults for segment by segment.
66
"""
7+
from omc3_gui.segment_by_segment.segment_model import SegmentTuple
78

89
DEFAULT_SEGMENTS =(
9-
("IP1", "BPM.12L1", "BPM.12R1"),
10-
("IP2", "BPM.12L2", "BPM.12R2"),
11-
("IP5", "BPM.12L5", "BPM.12R5"),
12-
("IP8", "BPM.12L8", "BPM.12R8"),
10+
SegmentTuple("IP1", "BPM.12L1", "BPM.12R1"),
11+
SegmentTuple("IP2", "BPM.12L2", "BPM.12R2"),
12+
SegmentTuple("IP5", "BPM.12L5", "BPM.12R5"),
13+
SegmentTuple("IP8", "BPM.12L8", "BPM.12R8"),
1314
)

omc3_gui/segment_by_segment/main_controller.py

Lines changed: 97 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
SegmentDataModel,
2727
SegmentItemModel,
2828
compare_segments,
29+
get_segments_from_directory,
2930
)
3031
from omc3_gui.segment_by_segment.segment_view import SegmentDialog
3132
from omc3_gui.ui_components.dataclass_ui import SettingsDialog
@@ -46,15 +47,18 @@ class SbSController(Controller):
4647
settings: Settings
4748
_view: SbSWindow
4849

49-
def __init__(self, settings: Settings | None = None):
50+
def __init__(self, measurements: list[Path | str] | None = None, settings: Settings | None = None):
5051
super().__init__(SbSWindow())
5152
self.settings: Settings = settings or Settings()
52-
self._last_selected_optics_path: Path = self.settings.main.cwd
53+
self._last_selected_measurement_path: Path = self.settings.main.cwd
5354
self._running_tasks: list[BackgroundThread] = []
5455

5556
self.connect_signals()
5657
self.set_measurement_interaction_buttons_enabled(False)
5758
self.set_all_segment_buttons_enabled(False)
59+
60+
if measurements is not None:
61+
self.open_measurements_from_paths(measurements)
5862

5963
def connect_signals(self):
6064
""" Connect the signals from the GUI components (view) to the slots (controller). """
@@ -153,7 +157,6 @@ def set_measurement_interaction_buttons_enabled(self, enabled: bool = True):
153157
for button in measurement_interaction_buttons:
154158
button.setEnabled(enabled)
155159

156-
157160
def add_measurement(self, measurement: OpticsMeasurement):
158161
""" Add a measurement to the GUI.
159162
@@ -172,27 +175,63 @@ def open_measurements(self):
172175
filenames = OpenDirectoriesDialog(
173176
parent=view,
174177
caption="Select Optics Folders",
175-
directory=self._last_selected_optics_path,
178+
directory=self._last_selected_measurement_path,
176179
).run_selection_dialog()
177180

178181
loaded_measurements = view.get_measurement_list()
179182
measurement_indices = []
180183

181184
LOGGER.debug(f"User selected {len(filenames)} files.")
182185
for filename in filenames:
183-
LOGGER.debug(f"adding: {filename!s}")
186+
LOGGER.debug(f"Adding: {filename!s}")
184187
optics_measurement = OpticsMeasurement.from_path(filename)
188+
189+
if self.settings.main.autoload_segments:
190+
self.load_segments_for_measurement(optics_measurement)
191+
192+
if self.settings.main.autodefault_segments:
193+
self.add_default_segments(optics_measurement)
194+
185195
try:
186196
loaded_measurements.add_item(optics_measurement)
187197
except ValueError as e:
188198
LOGGER.error(str(e))
189199
else:
190200
measurement_indices.append(loaded_measurements.get_index(optics_measurement))
191201

192-
self._last_selected_optics_path = filename.parent
202+
self._last_selected_measurement_path = filename.parent
193203

194204
view.set_selected_measurements(measurement_indices)
195205

206+
def open_measurements_from_paths(self, paths: Sequence[Path | str]):
207+
""" Open the given paths as measurements on start. """
208+
if not len(paths):
209+
LOGGER.debug("No measurement paths to load.")
210+
return
211+
212+
view: SbSWindow = self._view
213+
loaded_measurements = view.get_measurement_list()
214+
215+
LOGGER.debug(f"Loading {len(paths)} measurements.")
216+
for directory in paths:
217+
directory = Path(directory)
218+
219+
LOGGER.debug(f"Adding: {directory!s}")
220+
optics_measurement = OpticsMeasurement.from_path(directory)
221+
222+
if self.settings.main.autoload_segments:
223+
self.load_segments_for_measurement(optics_measurement)
224+
225+
if self.settings.main.autodefault_segments:
226+
self.add_default_segments(optics_measurement)
227+
228+
try:
229+
loaded_measurements.add_item(optics_measurement)
230+
except ValueError as e:
231+
LOGGER.error(str(e))
232+
233+
view.set_selected_measurements()
234+
196235
@Slot()
197236
def edit_measurement(self, measurement: OpticsMeasurement | None = None):
198237
""" Open the edit dialog for a measurement.
@@ -256,7 +295,7 @@ def measurement_selection_changed(self, measurements: Sequence[OpticsMeasurement
256295
self.set_measurement_interaction_buttons_enabled(True)
257296
self.set_all_segment_buttons_enabled(True)
258297

259-
# Group the segments fo the measurements into table-items when they have the same defintion ---
298+
# Group the segments for the measurements into table-items when they have the same defintion ---
260299
segment_table_items: list[SegmentItemModel] = []
261300

262301
for measurement in measurements:
@@ -463,18 +502,27 @@ def add_default_segments(self):
463502
return
464503

465504
for measurement in selected_measurements:
466-
beam = measurement.beam
467-
if beam is None:
468-
LOGGER.error(f"No beam found in measurement {measurement.display()}. Cannot add default segments.")
469-
continue
505+
self.add_default_segements_to_measurement(measurement)
506+
507+
self.measurement_selection_changed(selected_measurements)
508+
509+
def add_default_segements_to_measurement(self, measurement: OpticsMeasurement):
510+
""" Add default segments to the given measurement.
511+
These segments are defined in :data:`omc3_gui.segment_by_segment.defaults.DEFAULT_SEGMENTS`.
470512
513+
Args:
514+
measurement (OpticsMeasurement): The measurement to add segments to.
515+
"""
516+
if measurement.beam is not None: # LHC
471517
for segment_tuple in DEFAULT_SEGMENTS:
472518
segment = SegmentDataModel(measurement, *segment_tuple)
473-
segment.start = f"{segment.start}.B{beam}"
474-
segment.end = f"{segment.end}.B{beam}"
519+
segment.start = f"{segment.start}.B{measurement.beam}"
520+
segment.end = f"{segment.end}.B{measurement.beam}"
475521
measurement.try_add_segment(segment)
522+
523+
# TODO: Implement for other accelerators
524+
LOGGER.error(f"No beam found in measurement {measurement.display()}. Cannot add default segments.")
476525

477-
self.measurement_selection_changed(selected_measurements)
478526

479527
@Slot()
480528
def new_segment(self):
@@ -578,13 +626,44 @@ def remove_segment(self, segments: Sequence[SegmentItemModel] | None = None):
578626

579627
@Slot()
580628
def load_segments(self):
581-
LOGGER.debug("Loading segments from file/folder.")
582-
# TODO
583-
# Either parse a folder to find the segements therein, or load from a json file.
629+
self.load_segments_for_selected_measurements()
630+
# LOGGER.debug("Loading segments from file/folder.")
631+
# TODO: implement file saving and loading and ask user which one to do.
632+
633+
def load_segments_for_selected_measurements(self):
634+
""" Load segments for the currently selected measurements. """
635+
LOGGER.debug("Loading segments for selected measurements.")
636+
view: SbSWindow = self._view
637+
638+
selected_measurements = view.get_selected_measurements()
639+
if not selected_measurements:
640+
LOGGER.error("Please select at least one measurement.")
641+
return
642+
643+
for measurement in selected_measurements:
644+
self.load_segments_for_measurement(measurement)
645+
646+
# Update the view
647+
self.measurement_selection_changed(selected_measurements)
648+
649+
def load_segments_for_measurement(self, measurement: OpticsMeasurement):
650+
""" Load segments for the given measurement. """
651+
LOGGER.debug(f"Loading segments for {measurement.display()}.")
652+
653+
segments = get_segments_from_directory(measurement.output_dir)
654+
if not segments:
655+
LOGGER.debug(f"No segments found in {measurement.output_dir}.")
656+
return
657+
658+
for segment_tuple in segments:
659+
segment = SegmentDataModel(measurement, *segment_tuple)
660+
measurement.try_add_segment(segment)
584661

585662
@Slot()
586663
def save_segments(self):
587664
LOGGER.debug("Saving segments to a file.")
665+
view: SbSWindow = self._view
666+
view.showErrorDialog("Error: Not Implemented", "The save segments function is not implemented yet.")
588667
# TODO
589668
# Save current segements to a json file
590669

@@ -654,6 +733,7 @@ def clear_all():
654733
# For Debugging: Start sbs directly ---
655734
sbs_function()
656735
clear_all()
736+
LOGGER.info(f"Finished {measurement_task.message}")
657737
# -------------------------------------
658738

659739
# Plotting ---------------------------------------------------------------------

omc3_gui/segment_by_segment/measurement_model.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ def _parse_info_from_model_dir(model_dir: Path) -> dict[str, Any]:
246246
if "lhc" in sequence:
247247
result['accel'] = "lhc"
248248
result['beam'] = int(sequence[-1])
249-
result['year'] = _get_year_from_header(headers)
249+
result['year'] = map_lhc_year(_get_year_from_header(headers))
250250
elif "psb" in sequence:
251251
result['accel'] = "psb"
252252
result['ring'] = int(sequence[-1])
@@ -257,13 +257,39 @@ def _parse_info_from_model_dir(model_dir: Path) -> dict[str, Any]:
257257

258258

259259
def _get_year_from_header(headers: dict) -> str | None:
260-
""" Parses the year from the date in the LHC twiss.dat file."""
260+
""" Parses the year from the date in the twiss.dat file.
261+
262+
TODO: Will not work for hl-lhc models. These should return the hl-version.
263+
"""
261264
date = headers.get(DATE)
262265

263266
if date is None:
264267
return None
265268

266269
year = f"20{date.split('/')[-1]}"
267-
LOGGER.debug(f"Assume model year {year!s} from '{date}'!")
270+
LOGGER.debug(f"Assuming model year {year!s} from '{date}'!")
271+
return year
272+
273+
274+
def map_lhc_year(year: str | None) -> str:
275+
""" Maps the input year to the corresponding available model year. """
276+
if year is None:
277+
return None
278+
279+
try:
280+
int_year = int(year)
281+
except ValueError:
282+
return year
283+
284+
# no new models (see omc/model/accelerators/lhc)
285+
if 2012 < int_year < 2015:
286+
LOGGER.info(f"Mapping year {year} to LHC model 2012!")
287+
return "2012"
288+
289+
# no new models (there was a 2021 in acc-models, but we were not using it then)
290+
if 2018 < int_year < 2022:
291+
LOGGER.info(f"Mapping year {year} to LHC model 2018!")
292+
return "2018"
293+
268294
return year
269295

0 commit comments

Comments
 (0)