From 0713539425cf0f09689b52faa1cc79b4a9e6d794 Mon Sep 17 00:00:00 2001 From: "Ezequiel (x10sa) Panepucci" Date: Fri, 25 Apr 2025 15:59:47 +0200 Subject: [PATCH 1/5] create a script which plots smoothed data and gradient of data --- .../plugins/alignment_fit.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 pxii_bec/bec_ipython_client/plugins/alignment_fit.py diff --git a/pxii_bec/bec_ipython_client/plugins/alignment_fit.py b/pxii_bec/bec_ipython_client/plugins/alignment_fit.py new file mode 100644 index 0000000..5cd0418 --- /dev/null +++ b/pxii_bec/bec_ipython_client/plugins/alignment_fit.py @@ -0,0 +1,49 @@ +import numpy as np +from scipy.ndimage import gaussian_filter1d +from lmfit.models import GaussianModel + + +def alignment_fit_and_plot( + history_index: int, + device_name: str, + signal_name: str | None = None, + smoothing_sigma: float = 2.0, +): + """ + Get data for a completed scan from the BEC history, apply smoothing, gaussian fit, + gradient, and plot all the results. + + Args: + history_index (int): scan to fetch, e.g. -1 for the most recent scan + device_name (str): the device for which to get the monitoring data + + """ + # Fetch scan data from the history + # by default, signal = device name, unless otherwise specified + signal = signal_name or device_name + scan = bec.history[history_index] + md = scan.metadata["bec"] + data = scan.devices[device_name][signal].read()["value"] + # motor name is a bytes object in the metadata, so make a string + motor_name = md["scan_motors"][0].decode() + + # Create a plot and a text box to display results + dock_area = bec.gui.new() + wf = dock_area.new().new(bec.gui.available_widgets.Waveform) + wf.title = f"Scan {md['scan_number']}: {md['scan_name']} of {motor_name}" + text = dock_area.new(position="right").new(widget=bec.gui.available_widgets.TextBox) + + # Calculate some processed data and add everything to the plot + wf.plot(data, label="Raw data") + smoothed_data = gaussian_filter1d(data, smoothing_sigma) + wf.plot(smoothed_data, label="Smoothed") + gradient = np.gradient(smoothed_data) + wf.plot(gradient, label="gradient") + + # Fit a Gaussian model to the smoothed data and show the fitting parameters in the textbox + x_data = scan.devices[motor_name][motor_name].read()["value"] + model = GaussianModel() + result = model.fit(smoothed_data, x=x_data) + text.set_plain_text(f"Fit parameters: \n{result.params.pretty_repr()}") + + return result -- 2.49.1 From 2e8338aca96df18de7a25f999f3cb83869d6519c Mon Sep 17 00:00:00 2001 From: "Ezequiel (x10sa) Panepucci" Date: Fri, 25 Apr 2025 16:24:00 +0200 Subject: [PATCH 2/5] create scan history widget --- pxii_bec/bec_widgets/auto_updates/__init__.py | 0 pxii_bec/bec_widgets/widgets/client.py | 40 ++++ .../widgets/scan_history/__init__.py | 0 .../scan_history/register_scan_history.py | 15 ++ .../widgets/scan_history/scan_history.py | 191 ++++++++++++++++++ .../scan_history/scan_history.pyproject | 1 + .../widgets/scan_history/scan_history.ui | 115 +++++++++++ .../scan_history/scan_history_plugin.py | 54 +++++ pyproject.toml | 2 +- 9 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 pxii_bec/bec_widgets/auto_updates/__init__.py create mode 100644 pxii_bec/bec_widgets/widgets/client.py create mode 100644 pxii_bec/bec_widgets/widgets/scan_history/__init__.py create mode 100644 pxii_bec/bec_widgets/widgets/scan_history/register_scan_history.py create mode 100644 pxii_bec/bec_widgets/widgets/scan_history/scan_history.py create mode 100644 pxii_bec/bec_widgets/widgets/scan_history/scan_history.pyproject create mode 100644 pxii_bec/bec_widgets/widgets/scan_history/scan_history.ui create mode 100644 pxii_bec/bec_widgets/widgets/scan_history/scan_history_plugin.py diff --git a/pxii_bec/bec_widgets/auto_updates/__init__.py b/pxii_bec/bec_widgets/auto_updates/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pxii_bec/bec_widgets/widgets/client.py b/pxii_bec/bec_widgets/widgets/client.py new file mode 100644 index 0000000..abc25f8 --- /dev/null +++ b/pxii_bec/bec_widgets/widgets/client.py @@ -0,0 +1,40 @@ +# This file was automatically generated by generate_cli.py +# type: ignore + +from __future__ import annotations + +from bec_lib.logger import bec_logger + +from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call + +logger = bec_logger.logger + +# pylint: skip-file + + +_Widgets = { + "ScanHistory": "ScanHistory", +} + + +class ScanHistory(RPCBase): + @rpc_call + def select_scan_from_history(self, value: "int") -> "None": + """ + Set scan from CLI. + + Args: + value (int) : value from history -1 ...-10000 + """ + + @rpc_call + def add_scan_from_history(self) -> "None": + """ + Load selected scan from history. + """ + + @rpc_call + def clear_plot(self) -> "None": + """ + Delete all curves on the plot. + """ diff --git a/pxii_bec/bec_widgets/widgets/scan_history/__init__.py b/pxii_bec/bec_widgets/widgets/scan_history/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pxii_bec/bec_widgets/widgets/scan_history/register_scan_history.py b/pxii_bec/bec_widgets/widgets/scan_history/register_scan_history.py new file mode 100644 index 0000000..41c16ad --- /dev/null +++ b/pxii_bec/bec_widgets/widgets/scan_history/register_scan_history.py @@ -0,0 +1,15 @@ +def main(): # pragma: no cover + from qtpy import PYSIDE6 + + if not PYSIDE6: + print("PYSIDE6 is not available in the environment. Cannot patch designer.") + return + from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection + + from pxii_bec.bec_widgets.widgets.scan_history.scan_history_plugin import ScanHistoryPlugin + + QPyDesignerCustomWidgetCollection.addCustomWidget(ScanHistoryPlugin()) + + +if __name__ == "__main__": # pragma: no cover + main() diff --git a/pxii_bec/bec_widgets/widgets/scan_history/scan_history.py b/pxii_bec/bec_widgets/widgets/scan_history/scan_history.py new file mode 100644 index 0000000..bbaabb2 --- /dev/null +++ b/pxii_bec/bec_widgets/widgets/scan_history/scan_history.py @@ -0,0 +1,191 @@ +from __future__ import annotations + +import os +from typing import TYPE_CHECKING, TypedDict + +from bec_lib.logger import bec_logger +from bec_qthemes import material_icon +from bec_widgets.utils.bec_widget import BECWidget + + +from bec_widgets.utils.error_popups import SafeSlot +from bec_widgets.utils.ui_loader import UILoader +from qtpy.QtWidgets import QVBoxLayout, QWidget + +logger = bec_logger.logger + +if TYPE_CHECKING: + from qtpy.QtWidgets import QPushButton, QLabel, QSpinBox + from bec_widgets.widgets.plots.waveform.waveform import Waveform + from bec_widgets.widgets.control.device_input.device_combobox.device_combobox import DeviceComboBox + from bec_widgets.widgets.editors.text_box.text_box import TextBox + + +class ScanHistoryUIComponents(TypedDict): + waveform: Waveform + metadata_text_box: TextBox + monitor_label: QLabel + monitor_combobox: DeviceComboBox + history_label: QLabel + history_spin_box: QSpinBox + history_add: QPushButton + history_clear: QPushButton + + +class ScanHistory(BECWidget, QWidget): + USER_ACCESS = ["select_scan_from_history", "add_scan_from_history", "clear_plot"] + PLUGIN = True + ui_file = "./scan_history.ui" + components: ScanHistoryUIComponents + + def __init__(self, parent=None, **kwargs): + super().__init__(parent=parent, **kwargs) + self._load_ui() + + def _load_ui(self): + current_path = os.path.dirname(__file__) + self.ui = UILoader(self).loader(os.path.join(current_path, self.ui_file)) + layout = QVBoxLayout() + layout.addWidget(self.ui) + self.setLayout(layout) + + self.components: ScanHistoryUIComponents = { + "waveform" : self.ui.waveform, + "metadata_text_box" : self.ui.metadata_text_box, + "monitor_label" : self.ui.monitor_label, + "monitor_combobox" : self.ui.monitor_combobox, + "history_label" : self.ui.history_label, + "history_spin_box" : self.ui.history_spin_box, + "history_add" : self.ui.history_add, + "history_clear" : self.ui.history_clear, + } + + icon_options = {"size": (16, 16), "convert_to_pixmap": False} + + self.components['monitor_combobox'].apply_filter = False + self.components['monitor_combobox'].devices = ['bpm4i', 'bpm3i'] + + self.components['history_spin_box'].setMinimum(-10000) + self.components['history_spin_box'].setMaximum(-1) + self.components['history_spin_box'].valueChanged.connect(self._scan_history_selected) + self._scan_history_selected(-1) + self.components['history_spin_box'].setValue(-1) + self.components['history_add'].setText("Load") + self.components['history_add'].setStyleSheet( + "background-color: #129490; color: white; font-weight: bold; font-size: 12px;" + ) + + self.components['history_clear'].setText("Clear") + self.components['history_clear'].setStyleSheet( + "background-color: #065143; color: white; font-weight: bold; font-size: 12px;" + ) + + self.components['history_add'].clicked.connect(self._refresh_plot) + self.components['history_clear'].clicked.connect(self.clear_plot) + self.setWindowTitle("Scan History") + self._scan_history_selected(-1) + + @SafeSlot() + def add_scan_from_history(self) -> None: + """Load selected scan from history.""" + self.components['history_add'].click() + + @SafeSlot() + def clear_plot(self) -> None: + """Delete all curves on the plot.""" + self.components['waveform'].clear_all() + + @SafeSlot() + def _refresh_plot(self) -> None: + """Refresh plot.""" + spin_box_value = self.components['history_spin_box'].value() + self._check_scan_in_history(spin_box_value) + + # Get the data from the client + data = self.client.history[spin_box_value] + + # Check that the plot does not already have a curve with the same data + scan_number = int(data.metadata.bec['scan_number']) + monitor_name = self.components['monitor_combobox'].currentText() + # Get signal hints + signal_name = getattr(self.client.device_manager.devices, monitor_name)._hints + signal_name = signal_name[0] if len(signal_name)>0 else signal_name + + curve_label = f"Scan-{scan_number}-{monitor_name}-{signal_name}" + if len([curve for curve in self.components['waveform'].curves if curve.config.label == curve_label]): + return + if not hasattr(data.devices, monitor_name): + raise ValueError(f"Device {monitor_name} not found in data.") + + # Get scan motors and check that the plot x_axis motor is the same as the scan motor, if not, clear the plot + scan_motors = [motor.decode() for motor in data.metadata.bec['scan_motors']] + x_motor_name = self.components['waveform'].x_mode + if x_motor_name not in scan_motors: + self.clear_plot() + self.components['waveform'].x_mode = x_motor_name = scan_motors[0] + + # fetching the data + monitor_data = getattr(data.devices, monitor_name).read()[signal_name]['value'] + motor_data = getattr(data.devices, x_motor_name).read()[x_motor_name]['value'] + + # Plot custom curve, with custom label + self.components['waveform'].plot(x=motor_data, y=monitor_data, label=curve_label) + x_label = f"{x_motor_name} / [{getattr(self.client.device_manager.devices, x_motor_name).egu()}]" + self.components['waveform'].x_label = x_label + + def _check_scan_in_history(self, history_value:int) -> None: + """ + Check if scan is in history. + + Args: + history_value (int): Value from history -1...-10000 + """ + if len(self.client.history) < abs(history_value): + self.components['metadata_text_box'].set_plain_text(f"Scan history does not have the request scan {history_value} of history with length: {len(self.client.history)}") + return + + + def select_scan_from_history(self, value:int) -> None: + """ + Set scan from CLI. + + Args: + value (int) : value from history -1 ...-10000 + """ + if value >=0: + raise ValueError(f"Value must be smaller or equal -1, provided {value}") + self.components['history_spin_box'].setValue(value) + + @SafeSlot(int) + def _scan_history_selected(self, spin_box_value:int) -> None: + self._check_scan_in_history(spin_box_value) + data = self.client.history[spin_box_value] + data.metadata.bec['scan_motors'][0].decode() + + text = str(data) + scan_motor_text = "\n" + "Scan Motors: " + for motor in data.metadata.bec['scan_motors']: + scan_motor_text += f" {motor.decode()}" + + self.components['metadata_text_box'].set_plain_text(text + scan_motor_text) + + @SafeSlot(str) + def _set_x_axis(self, device_x:str) -> None: + self.components['waveform'].x_mode = device_x + + @SafeSlot(str) + def _plot_new_device(self, device:str) -> None: + # if len(curve for curve in self.components["waveform"].curves if curve.config.label == f"{device}-{device}": + self.components["waveform"].plot(device) + + +if __name__ == "__main__": # pragma: no cover + import sys + + from qtpy.QtWidgets import QApplication + + app = QApplication(sys.argv) + widget = ScanHistory() + + widget.show() + sys.exit(app.exec_()) diff --git a/pxii_bec/bec_widgets/widgets/scan_history/scan_history.pyproject b/pxii_bec/bec_widgets/widgets/scan_history/scan_history.pyproject new file mode 100644 index 0000000..591325d --- /dev/null +++ b/pxii_bec/bec_widgets/widgets/scan_history/scan_history.pyproject @@ -0,0 +1 @@ +{'files': ['scan_history.py']} \ No newline at end of file diff --git a/pxii_bec/bec_widgets/widgets/scan_history/scan_history.ui b/pxii_bec/bec_widgets/widgets/scan_history/scan_history.ui new file mode 100644 index 0000000..a1ba889 --- /dev/null +++ b/pxii_bec/bec_widgets/widgets/scan_history/scan_history.ui @@ -0,0 +1,115 @@ + + + Form + + + + 0 + 0 + 955 + 796 + + + + Form + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + + + + + + + + + + + BPM Monitor + + + + + + + + + + Scan History + + + + + + + + + + Add scan + + + + + + + clear all + + + + + + + + + + 0 + 0 + + + + + 795 + 191 + + + + + + + + + + + TextBox + QWidget +
text_box
+
+ + DeviceComboBox + QComboBox +
device_combobox
+
+ + Waveform + QWidget +
waveform
+
+
+ + +
diff --git a/pxii_bec/bec_widgets/widgets/scan_history/scan_history_plugin.py b/pxii_bec/bec_widgets/widgets/scan_history/scan_history_plugin.py new file mode 100644 index 0000000..762a3fe --- /dev/null +++ b/pxii_bec/bec_widgets/widgets/scan_history/scan_history_plugin.py @@ -0,0 +1,54 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +from qtpy.QtDesigner import QDesignerCustomWidgetInterface + +from bec_widgets.utils.bec_designer import designer_material_icon +from pxii_bec.bec_widgets.widgets.scan_history.scan_history import ScanHistory + +DOM_XML = """ + + + + +""" + + +class ScanHistoryPlugin(QDesignerCustomWidgetInterface): # pragma: no cover + def __init__(self): + super().__init__() + self._form_editor = None + + def createWidget(self, parent): + t = ScanHistory(parent) + return t + + def domXml(self): + return DOM_XML + + def group(self): + return "" + + def icon(self): + return designer_material_icon(ScanHistory.ICON_NAME) + + def includeFile(self): + return "scan_history" + + def initialize(self, form_editor): + self._form_editor = form_editor + + def isContainer(self): + return False + + def isInitialized(self): + return self._form_editor is not None + + def name(self): + return "ScanHistory" + + def toolTip(self): + return "ScanHistory" + + def whatsThis(self): + return self.toolTip() diff --git a/pyproject.toml b/pyproject.toml index 57c7ecb..7c8e264 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ plugin_ipython_client_pre = "pxii_bec.bec_ipython_client.startup.pre_startup" plugin_ipython_client_post = "pxii_bec.bec_ipython_client.startup" [project.entry-points."bec.widgets.auto_updates"] -plugin_widgets_update = "pxii_bec.bec_widgets.auto_updates:PlotUpdate" +plugin_widgets_update = "pxii_bec.bec_widgets.auto_updates" [project.entry-points."bec.widgets.user_widgets"] plugin_widgets = "pxii_bec.bec_widgets.widgets" -- 2.49.1 From 15a9439b295675dab89ff2ec11e16c721defd814 Mon Sep 17 00:00:00 2001 From: "Ezequiel (x10sa) Panepucci" Date: Fri, 25 Apr 2025 16:24:19 +0200 Subject: [PATCH 3/5] draft gui with commissioning widget --- pxii_bec/bec_widgets/ComissioningGUIDraft.ui | 135 +++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 pxii_bec/bec_widgets/ComissioningGUIDraft.ui diff --git a/pxii_bec/bec_widgets/ComissioningGUIDraft.ui b/pxii_bec/bec_widgets/ComissioningGUIDraft.ui new file mode 100644 index 0000000..be17da6 --- /dev/null +++ b/pxii_bec/bec_widgets/ComissioningGUIDraft.ui @@ -0,0 +1,135 @@ + + + Form + + + + 0 + 0 + 1801 + 1459 + + + + + 1801 + 1459 + + + + Form + + + + + + 0 + + + + Control Panel + + + + + + + + + + + + + + + + + + + Logbook + + + + + + + 24 + + + + Coming soon... + + + + + + + + Take a break + + + + + + + + + Qt::Orientation::Horizontal + + + + 1073 + 20 + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + + + + + + ScanControl + QWidget +
scan_control
+
+ + Waveform + QWidget +
waveform
+
+ + BECQueue + QWidget +
bec_queue
+
+ + Minesweeper + QWidget +
minesweeper
+
+ + ScanHistory + QWidget +
scan_history
+
+
+ + +
-- 2.49.1 From 08544f3f064baf5323a0a90f3dab46230592c17e Mon Sep 17 00:00:00 2001 From: David Perl Date: Wed, 7 May 2025 14:34:14 +0200 Subject: [PATCH 4/5] Init repo pxii_bec at template version v0.3.3 --- .copier-answers.yml | 10 ++ .gitlab-ci.yml | 1 + LICENSE | 5 +- bin/.gitignore | 1 + copier.yml | 84 +++++++++++++ .../bec_ipython_client/startup/pre_startup.py | 9 +- .../widgets/scan_history/scan_history.ui | 117 +----------------- .../widgets/scan_history/scan_history_ui.py | 33 +++++ .../metadata_schema_registry.py | 12 ++ .../metadata_schema_template.py | 34 +++++ pyproject.toml | 6 +- tests/tests_bec_ipython_client/README.md | 21 ++-- tests/tests_bec_widgets/README.md | 21 ++-- tests/tests_dap_services/README.md | 21 ++-- tests/tests_devices/README.md | 21 ++-- tests/tests_file_writer/README.md | 21 ++-- tests/tests_scans/README.md | 21 ++-- 17 files changed, 261 insertions(+), 177 deletions(-) create mode 100644 .copier-answers.yml create mode 100644 bin/.gitignore create mode 100644 copier.yml create mode 100644 pxii_bec/bec_widgets/widgets/scan_history/scan_history_ui.py create mode 100644 pxii_bec/scans/metadata_schema/metadata_schema_registry.py create mode 100644 pxii_bec/scans/metadata_schema/metadata_schema_template.py diff --git a/.copier-answers.yml b/.copier-answers.yml new file mode 100644 index 0000000..3a5df24 --- /dev/null +++ b/.copier-answers.yml @@ -0,0 +1,10 @@ +# Do not edit this file! +# It is needed to track the repo template version, and editing may break things. +# This file will be overwritten by copier on template updates. + +_commit: v0.3.3 +_src_path: https://gitea.psi.ch/bec/bec_plugin_copier_template.git +project_name: pxii_bec +widget_plugins_input: +- name: scan_history + use_ui: true diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 5639714..8b20a2b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,4 +3,5 @@ include: inputs: name: pxii_bec target: pxii_bec + branch: $CHILD_PIPELINE_BRANCH project: bec/awi_utils diff --git a/LICENSE b/LICENSE index 06070a5..f9b721a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ + BSD 3-Clause License -Copyright (c) 2024, Paul Scherrer Institute +Copyright (c) 2025, Paul Scherrer Institute Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -25,4 +26,4 @@ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/bin/.gitignore b/bin/.gitignore new file mode 100644 index 0000000..20a11ba --- /dev/null +++ b/bin/.gitignore @@ -0,0 +1 @@ +# Add anything you don't want to check in to git, e.g. very large files \ No newline at end of file diff --git a/copier.yml b/copier.yml new file mode 100644 index 0000000..0fd4255 --- /dev/null +++ b/copier.yml @@ -0,0 +1,84 @@ +_exclude: + - ".gitea/" + - ".git/" + - "CHANGELOG.md" +# imports + +_jinja_extensions: +- bec_lib.utils.copier_jinja_filters.CopierFilters + +# predefined values +# make sure these have 'when: false' so that the questions are not asked and the items are not saved in answers.yml + +test_directories: + type: str + multiselect: true + default: ["tests_bec_ipython_client", "tests_bec_widgets", "tests_dap_services", "tests_devices", "tests_file_writer", "tests_scans"] + when: false + +copyright_year: + type: int + default: 2025 + when: false + +# questions + +project_name: + type: str + help: What is your project name? + +widget_plugins_input: + # Defines the list of plugin widgets, following: + # - name: widget name in snake case + # use_ui: whether to generate a .ui file + type: yaml + multiline: true + default: [] + +# derived from questions: single point of configuration for plugin class names etc. + +widget_plugins: + type: yaml + multiline: true + default: > + {% if not widget_plugins_input %} [] + {% else %} + {% for wp in widget_plugins_input %} + - module: {{ wp.name }} # Module name for the whole plugin + class: {{ wp.name | snake_to_pascal }} # Class name for the plugin widget + use_ui: {{ wp.use_ui }} # Whether to create a .ui file and import it + {% if wp.use_ui %} + ui_module: {{ wp.name }}_ui # Module name for the compiled python UI + ui_class: Ui_{{ wp.name | snake_to_pascal }} # Class name for the compiled python UI + {% endif %} + {% endfor %} + {% endif %} + when: false + + +# other configuration + +ui_fileinfo: # would like to save this programatically when generated but don't see how + type: yaml + multiline: true + default: > + {% if not widget_plugins %} [] + {% else %} + {% for wp in widget_plugins %} + {% if wp.use_ui %} + - ui_file: {{ project_name }}/bec_widgets/widgets/{{ wp.module }}/{{ wp.module }}.ui + out_file: {{ project_name }}/bec_widgets/widgets/{{ wp.module }}/{{ wp.ui_module }}.py + {% endif %} + {% endfor %} + {% endif %} + when: false + +_tasks: + - "git init --initial-branch=main" + - > + {% if not ui_fileinfo %} echo "No .ui files to process" + {% else %} + {% for info in ui_fileinfo %}pyside6-uic {{ info.ui_file }} -o {{ info.out_file }}; + {% endfor %} + {% endif %} + - "git add -A; git commit -a -m 'Init repo {{ project_name }} at template version {{ _commit }}'" diff --git a/pxii_bec/bec_ipython_client/startup/pre_startup.py b/pxii_bec/bec_ipython_client/startup/pre_startup.py index e22e554..1132c92 100644 --- a/pxii_bec/bec_ipython_client/startup/pre_startup.py +++ b/pxii_bec/bec_ipython_client/startup/pre_startup.py @@ -1,6 +1,6 @@ """ Pre-startup script for BEC client. This script is executed before the BEC client -is started. It can be used to add additional command line arguments. +is started. It can be used to add additional command line arguments. """ from bec_lib.service_config import ServiceConfig @@ -14,10 +14,3 @@ def extend_command_line_args(parser): # parser.add_argument("--session", help="Session name", type=str, default="cSAXS") return parser - - -# def get_config() -> ServiceConfig: -# """ -# Create and return the service configuration. -# """ -# return ServiceConfig(redis={"host": "localhost", "port": 6379}) diff --git a/pxii_bec/bec_widgets/widgets/scan_history/scan_history.ui b/pxii_bec/bec_widgets/widgets/scan_history/scan_history.ui index a1ba889..00b01c3 100644 --- a/pxii_bec/bec_widgets/widgets/scan_history/scan_history.ui +++ b/pxii_bec/bec_widgets/widgets/scan_history/scan_history.ui @@ -1,115 +1,8 @@ - Form - - - - 0 - 0 - 955 - 796 - - - - Form - - - - - - - 0 - 0 - - - - - 0 - 0 - - + ScanHistory + - - - - - - - - - - - - BPM Monitor - - - - - - - - - - Scan History - - - - - - - - - - Add scan - - - - - - - clear all - - - - - - - - - - 0 - 0 - - - - - 795 - 191 - - - - - - - - - - - TextBox - QWidget -
text_box
-
- - DeviceComboBox - QComboBox -
device_combobox
-
- - Waveform - QWidget -
waveform
-
-
- - -
+ + + \ No newline at end of file diff --git a/pxii_bec/bec_widgets/widgets/scan_history/scan_history_ui.py b/pxii_bec/bec_widgets/widgets/scan_history/scan_history_ui.py new file mode 100644 index 0000000..5a4f6b5 --- /dev/null +++ b/pxii_bec/bec_widgets/widgets/scan_history/scan_history_ui.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'scan_history.ui' +## +## Created by: Qt User Interface Compiler version 6.8.2 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QApplication, QSizePolicy, QWidget) + +class Ui_ScanHistory(object): + def setupUi(self, ScanHistory): + if not ScanHistory.objectName(): + ScanHistory.setObjectName(u"ScanHistory") + + self.retranslateUi(ScanHistory) + + QMetaObject.connectSlotsByName(ScanHistory) + # setupUi + + def retranslateUi(self, ScanHistory): + pass + # retranslateUi + diff --git a/pxii_bec/scans/metadata_schema/metadata_schema_registry.py b/pxii_bec/scans/metadata_schema/metadata_schema_registry.py new file mode 100644 index 0000000..deb6ef4 --- /dev/null +++ b/pxii_bec/scans/metadata_schema/metadata_schema_registry.py @@ -0,0 +1,12 @@ +# from .metadata_schema_template import ExampleSchema + +METADATA_SCHEMA_REGISTRY = { + # Add models which should be used to validate scan metadata here. + # Make a model according to the template, and import it as above + # Then associate it with a scan like so: + # "example_scan": ExampleSchema +} + +# Define a default schema type which should be used as the fallback for everything: + +DEFAULT_SCHEMA = None diff --git a/pxii_bec/scans/metadata_schema/metadata_schema_template.py b/pxii_bec/scans/metadata_schema/metadata_schema_template.py new file mode 100644 index 0000000..c12e364 --- /dev/null +++ b/pxii_bec/scans/metadata_schema/metadata_schema_template.py @@ -0,0 +1,34 @@ +# # By inheriting from BasicScanMetadata you can define a schema by which metadata +# # supplied to a scan must be validated. +# # This schema is a Pydantic model: https://docs.pydantic.dev/latest/concepts/models/ +# # but by default it will still allow you to add any arbitrary information to it. +# # That is to say, when you run a scan with which such a model has been associated in the +# # metadata_schema_registry, you can supply any python dictionary with strings as keys +# # and built-in python types (strings, integers, floats) as values, and these will be +# # added to the experiment metadata, but it *must* contain the keys and values of the +# # types defined in the schema class. +# # +# # +# # For example, say that you would like to enforce recording information about sample +# # pretreatment, you could define the following: +# # +# +# from bec_lib.metadata_schema import BasicScanMetadata +# +# +# class ExampleSchema(BasicScanMetadata): +# treatment_description: str +# treatment_temperature_k: int +# +# +# # If this was used according to the example in metadata_schema_registry.py, +# # then when calling the scan, the user would need to write something like: +# >>> scans.example_scan( +# >>> motor, +# >>> 1, +# >>> 2, +# >>> 3, +# >>> metadata={"treatment_description": "oven overnight", "treatment_temperature_k": 575}, +# >>> ) +# +# # And the additional metadata would be saved in the HDF5 file created for the scan. diff --git a/pyproject.toml b/pyproject.toml index 7c8e264..5aaba84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "pxii_bec" version = "0.0.0" -description = "Custom device implementations based on the ophyd hardware abstraction layer" +description = "The PX-II plugin repository for BEC" requires-python = ">=3.10" classifiers = [ "Development Status :: 3 - Alpha", @@ -17,6 +17,7 @@ dependencies = ["pandas~=2.0", "matplotlib"] [project.optional-dependencies] dev = [ "black", + "copier", "isort", "coverage", "pylint", @@ -38,6 +39,9 @@ plugin_file_writer = "pxii_bec.file_writer" [project.entry-points."bec.scans"] plugin_scans = "pxii_bec.scans" +[project.entry-points."bec.scans.metadata_schema"] +plugin_metadata_schema = "pxii_bec.scans.metadata_schema" + [project.entry-points."bec.ipython_client_startup"] plugin_ipython_client_pre = "pxii_bec.bec_ipython_client.startup.pre_startup" plugin_ipython_client_post = "pxii_bec.bec_ipython_client.startup" diff --git a/tests/tests_bec_ipython_client/README.md b/tests/tests_bec_ipython_client/README.md index 5762245..63d535f 100644 --- a/tests/tests_bec_ipython_client/README.md +++ b/tests/tests_bec_ipython_client/README.md @@ -1,31 +1,34 @@ # Getting Started with Testing using pytest -BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework. -It can be install via -``` bash +BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework. +It can be installed via + +```bash pip install pytest ``` -in your *python environment*. + +in your _python environment_. We note that pytest is part of the optional-dependencies `[dev]` of the plugin package. ## Introduction Tests in this package should be stored in the `tests` directory. We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of ``. +It is mandatory for test files to begin with `test_` for pytest to discover them. -To run all tests, navigate to the directory of the plugin from the command line, and run the command +To run all tests, navigate to the directory of the plugin from the command line, and run the command -``` bash +```bash pytest -v --random-order ./tests ``` + Note, the python environment needs to be active. The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run. -The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines. +The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines. ## Test examples -Writing tests can be quite specific for the given function. +Writing tests can be quite specific for the given function. We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes. A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html). In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html). - diff --git a/tests/tests_bec_widgets/README.md b/tests/tests_bec_widgets/README.md index 5762245..63d535f 100644 --- a/tests/tests_bec_widgets/README.md +++ b/tests/tests_bec_widgets/README.md @@ -1,31 +1,34 @@ # Getting Started with Testing using pytest -BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework. -It can be install via -``` bash +BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework. +It can be installed via + +```bash pip install pytest ``` -in your *python environment*. + +in your _python environment_. We note that pytest is part of the optional-dependencies `[dev]` of the plugin package. ## Introduction Tests in this package should be stored in the `tests` directory. We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of ``. +It is mandatory for test files to begin with `test_` for pytest to discover them. -To run all tests, navigate to the directory of the plugin from the command line, and run the command +To run all tests, navigate to the directory of the plugin from the command line, and run the command -``` bash +```bash pytest -v --random-order ./tests ``` + Note, the python environment needs to be active. The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run. -The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines. +The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines. ## Test examples -Writing tests can be quite specific for the given function. +Writing tests can be quite specific for the given function. We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes. A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html). In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html). - diff --git a/tests/tests_dap_services/README.md b/tests/tests_dap_services/README.md index 5762245..63d535f 100644 --- a/tests/tests_dap_services/README.md +++ b/tests/tests_dap_services/README.md @@ -1,31 +1,34 @@ # Getting Started with Testing using pytest -BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework. -It can be install via -``` bash +BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework. +It can be installed via + +```bash pip install pytest ``` -in your *python environment*. + +in your _python environment_. We note that pytest is part of the optional-dependencies `[dev]` of the plugin package. ## Introduction Tests in this package should be stored in the `tests` directory. We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of ``. +It is mandatory for test files to begin with `test_` for pytest to discover them. -To run all tests, navigate to the directory of the plugin from the command line, and run the command +To run all tests, navigate to the directory of the plugin from the command line, and run the command -``` bash +```bash pytest -v --random-order ./tests ``` + Note, the python environment needs to be active. The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run. -The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines. +The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines. ## Test examples -Writing tests can be quite specific for the given function. +Writing tests can be quite specific for the given function. We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes. A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html). In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html). - diff --git a/tests/tests_devices/README.md b/tests/tests_devices/README.md index 5762245..63d535f 100644 --- a/tests/tests_devices/README.md +++ b/tests/tests_devices/README.md @@ -1,31 +1,34 @@ # Getting Started with Testing using pytest -BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework. -It can be install via -``` bash +BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework. +It can be installed via + +```bash pip install pytest ``` -in your *python environment*. + +in your _python environment_. We note that pytest is part of the optional-dependencies `[dev]` of the plugin package. ## Introduction Tests in this package should be stored in the `tests` directory. We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of ``. +It is mandatory for test files to begin with `test_` for pytest to discover them. -To run all tests, navigate to the directory of the plugin from the command line, and run the command +To run all tests, navigate to the directory of the plugin from the command line, and run the command -``` bash +```bash pytest -v --random-order ./tests ``` + Note, the python environment needs to be active. The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run. -The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines. +The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines. ## Test examples -Writing tests can be quite specific for the given function. +Writing tests can be quite specific for the given function. We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes. A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html). In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html). - diff --git a/tests/tests_file_writer/README.md b/tests/tests_file_writer/README.md index 5762245..63d535f 100644 --- a/tests/tests_file_writer/README.md +++ b/tests/tests_file_writer/README.md @@ -1,31 +1,34 @@ # Getting Started with Testing using pytest -BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework. -It can be install via -``` bash +BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework. +It can be installed via + +```bash pip install pytest ``` -in your *python environment*. + +in your _python environment_. We note that pytest is part of the optional-dependencies `[dev]` of the plugin package. ## Introduction Tests in this package should be stored in the `tests` directory. We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of ``. +It is mandatory for test files to begin with `test_` for pytest to discover them. -To run all tests, navigate to the directory of the plugin from the command line, and run the command +To run all tests, navigate to the directory of the plugin from the command line, and run the command -``` bash +```bash pytest -v --random-order ./tests ``` + Note, the python environment needs to be active. The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run. -The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines. +The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines. ## Test examples -Writing tests can be quite specific for the given function. +Writing tests can be quite specific for the given function. We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes. A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html). In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html). - diff --git a/tests/tests_scans/README.md b/tests/tests_scans/README.md index 5762245..63d535f 100644 --- a/tests/tests_scans/README.md +++ b/tests/tests_scans/README.md @@ -1,31 +1,34 @@ # Getting Started with Testing using pytest -BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework. -It can be install via -``` bash +BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework. +It can be installed via + +```bash pip install pytest ``` -in your *python environment*. + +in your _python environment_. We note that pytest is part of the optional-dependencies `[dev]` of the plugin package. ## Introduction Tests in this package should be stored in the `tests` directory. We suggest to sort tests of different submodules, i.e. `scans` or `devices` in the respective folder structure, and to folow a naming convention of ``. +It is mandatory for test files to begin with `test_` for pytest to discover them. -To run all tests, navigate to the directory of the plugin from the command line, and run the command +To run all tests, navigate to the directory of the plugin from the command line, and run the command -``` bash +```bash pytest -v --random-order ./tests ``` + Note, the python environment needs to be active. The additional arg `-v` allows pytest to run in verbose mode which provides more detailed information about the tests being run. -The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines. +The argument `--random-order` instructs pytest to run the tests in random order, which is the default in the CI pipelines. ## Test examples -Writing tests can be quite specific for the given function. +Writing tests can be quite specific for the given function. We recommend writing tests as isolated as possible, i.e. try to test single functions instead of full classes. A very useful class to enable isolated testing is [MagicMock](https://docs.python.org/3/library/unittest.mock.html). In addition, we also recommend to take a look at the [How-to guides from pytest](https://docs.pytest.org/en/8.0.x/how-to/index.html). - -- 2.49.1 From 287250dd44a38b2ce181e52ffd6a005c990a12f0 Mon Sep 17 00:00:00 2001 From: David Perl Date: Wed, 7 May 2025 14:34:48 +0200 Subject: [PATCH 5/5] fix: remove unused compiled UI file --- .../widgets/scan_history/scan_history_ui.py | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 pxii_bec/bec_widgets/widgets/scan_history/scan_history_ui.py diff --git a/pxii_bec/bec_widgets/widgets/scan_history/scan_history_ui.py b/pxii_bec/bec_widgets/widgets/scan_history/scan_history_ui.py deleted file mode 100644 index 5a4f6b5..0000000 --- a/pxii_bec/bec_widgets/widgets/scan_history/scan_history_ui.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'scan_history.ui' -## -## Created by: Qt User Interface Compiler version 6.8.2 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, - QMetaObject, QObject, QPoint, QRect, - QSize, QTime, QUrl, Qt) -from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, - QFont, QFontDatabase, QGradient, QIcon, - QImage, QKeySequence, QLinearGradient, QPainter, - QPalette, QPixmap, QRadialGradient, QTransform) -from PySide6.QtWidgets import (QApplication, QSizePolicy, QWidget) - -class Ui_ScanHistory(object): - def setupUi(self, ScanHistory): - if not ScanHistory.objectName(): - ScanHistory.setObjectName(u"ScanHistory") - - self.retranslateUi(ScanHistory) - - QMetaObject.connectSlotsByName(ScanHistory) - # setupUi - - def retranslateUi(self, ScanHistory): - pass - # retranslateUi - -- 2.49.1