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/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
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/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
+
+
+
+ Waveform
+ QWidget
+
+
+
+ BECQueue
+ QWidget
+
+
+
+ Minesweeper
+ QWidget
+
+
+
+ ScanHistory
+ QWidget
+
+
+
+
+
+
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..00b01c3
--- /dev/null
+++ b/pxii_bec/bec_widgets/widgets/scan_history/scan_history.ui
@@ -0,0 +1,8 @@
+
+
+ ScanHistory
+
+
+
+
+
\ No newline at end of file
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/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 57c7ecb..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,12 +39,15 @@ 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"
[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"
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).
-