7 Commits

Author SHA1 Message Date
Unknown MX Person d1844eaeba WIP 2025-03-25 17:42:55 +01:00
gac-x06da d53ec4c14c WIP 2025-03-21 09:57:25 +01:00
gac-x06da 4091f11b0a WIP 2025-03-21 09:27:29 +01:00
gac-x06da 29ae5c196b WIP 2025-03-14 16:10:16 +01:00
gac-x06da 74521da7b3 Blacking 2025-03-14 12:40:23 +01:00
gac-x06da a5f844b816 Keyword scans for 2D 2025-03-14 12:32:57 +01:00
gac-x06da 34d4d6ef8c Added AttributeError to updates 2025-03-14 12:32:57 +01:00
65 changed files with 1135 additions and 4724 deletions
-9
View File
@@ -1,9 +0,0 @@
# 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: v1.4.0
_src_path: https://github.com/bec-project/plugin_copier_template.git
make_commit: false
project_name: pxiii_bec
widget_plugins_input: null
-3
View File
@@ -1,3 +0,0 @@
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
semantic-release changelog -D version_variable=$SCRIPT_DIR/../../semantic_release/__init__.py:__version__
semantic-release version -D version_variable=$SCRIPT_DIR/../../semantic_release/__init__.py:__version__
-3
View File
@@ -1,3 +0,0 @@
black --line-length=100 $(git diff --cached --name-only --diff-filter=ACM -- '*.py')
isort --line-length=100 --profile=black --multi-line=3 --trailing-comma $(git diff --cached --name-only --diff-filter=ACM -- '*.py')
git add $(git diff --cached --name-only --diff-filter=ACM -- '*.py')
-102
View File
@@ -1,102 +0,0 @@
name: CI for pxiii_bec
on:
push:
pull_request:
workflow_dispatch:
inputs:
BEC_WIDGETS_BRANCH:
description: "Branch of BEC Widgets to install"
required: false
type: string
default: "main"
BEC_CORE_BRANCH:
description: "Branch of BEC Core to install"
required: false
type: string
default: "main"
OPHYD_DEVICES_BRANCH:
description: "Branch of Ophyd Devices to install"
required: false
type: string
default: "main"
BEC_PLUGIN_REPO_BRANCH:
description: "Branch of the BEC Plugin Repository to install"
required: false
type: string
default: "main"
PYTHON_VERSION:
description: "Python version to use"
required: false
type: string
default: "3.12"
permissions:
pull-requests: write
jobs:
test:
runs-on: ubuntu-latest
env:
QTWEBENGINE_DISABLE_SANDBOX: 1
QT_QPA_PLATFORM: "offscreen"
steps:
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "${{ inputs.PYTHON_VERSION || '3.12' }}"
- name: Checkout BEC Plugin Repository
uses: actions/checkout@v4
with:
repository: bec/pxiii_bec
ref: "${{ inputs.BEC_PLUGIN_REPO_BRANCH || github.head_ref || github.sha }}"
path: ./pxiii_bec
- name: Lint for merge conflicts from template updates
shell: bash
# Find all Copier conflicts except this line
run: '! grep -r "<<<<<<< before updating" | grep -v "grep -r \"<<<<<<< before updating"'
- name: Checkout BEC Core
uses: actions/checkout@v4
with:
repository: bec/bec
ref: "${{ inputs.BEC_CORE_BRANCH || 'main' }}"
path: ./bec
- name: Checkout Ophyd Devices
uses: actions/checkout@v4
with:
repository: bec/ophyd_devices
ref: "${{ inputs.OPHYD_DEVICES_BRANCH || 'main' }}"
path: ./ophyd_devices
- name: Checkout BEC Widgets
uses: actions/checkout@v4
with:
repository: bec/bec_widgets
ref: "${{ inputs.BEC_WIDGETS_BRANCH || 'main' }}"
path: ./bec_widgets
- name: Install dependencies
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y libgl1 libegl1 x11-utils libxkbcommon-x11-0 libdbus-1-3 xvfb
sudo apt-get -y install libnss3 libxdamage1 libasound2t64 libatomic1 libxcursor1
- name: Install Python dependencies
shell: bash
run: |
pip install uv
uv pip install --system -e ./ophyd_devices
uv pip install --system -e ./bec/bec_lib[dev]
uv pip install --system -e ./bec/bec_ipython_client
uv pip install --system -e ./bec/bec_server[dev]
uv pip install --system -e ./bec_widgets[dev,pyside6]
uv pip install --system -e ./pxiii_bec
- name: Run Pytest with Coverage
id: coverage
run: pytest --random-order --cov=./pxiii_bec --cov-config=./pxiii_bec/pyproject.toml --cov-branch --cov-report=xml --no-cov-on-fail ./pxiii_bec/tests/ || test $? -eq 5
-70
View File
@@ -1,70 +0,0 @@
name: Create template upgrade PR for pxiii_bec
on:
workflow_dispatch:
permissions:
pull-requests: write
jobs:
create_update_branch_and_pr:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Checkout
uses: actions/checkout@v4
- name: Create virtualenv
run: |
python -m virtualenv .venv
- name: Install tools
run: |
source .venv/bin/activate
pip install copier PySide6 bec_lib
- name: Perform update
run: |
source .venv/bin/activate
git config --global user.email "bec_ci_staging@psi.ch"
git config --global user.name "BEC automated CI"
branch="chore/update-template-$(python -m uuid)"
echo "switching to branch $branch"
git checkout -b $branch
echo "Running copier update..."
copier update --trust --defaults --conflict inline 2>&1 | tee copier.log
status=${PIPESTATUS[0]}
output="$(cat copier.log)"
echo $output
msg="$(printf '%s\n' "$output" | head -n 1)"
if ! grep -q "make_commit: true" .copier-answers.yml ; then
echo "Autocommit not made, committing..."
git add -A
git commit -a -m "$msg"
fi
if diff-index --quiet HEAD ; then
echo "No changes detected"
exit 0
fi
git push -u origin $branch
curl -X POST "https://gitea.psi.ch/api/v1/repos/${{ gitea.repository }}/pulls" \
-H "Authorization: token ${{ secrets.CI_REPO_WRITE }}" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"Template: $(echo $msg)\",
\"body\": \"This PR was created by Gitea Actions\",
\"head\": \"$(echo $branch)\",
\"base\": \"main\"
}"
-3
View File
@@ -8,9 +8,6 @@
**/.pytest_cache
**/*.egg*
# recovery_config files
recovery_config_*
# file writer data
**.h5
+7
View File
@@ -0,0 +1,7 @@
include:
- project: bec/awi_utils
file: /templates/plugin-repo-template.yml
inputs:
name: "pxiii"
target: "pxiii_bec"
branch: $CHILD_PIPELINE_BRANCH
+2 -3
View File
@@ -1,7 +1,6 @@
BSD 3-Clause License
Copyright (c) 2025, Paul Scherrer Institute
Copyright (c) 2024, Paul Scherrer Institute
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
@@ -26,4 +25,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.
-1
View File
@@ -1 +0,0 @@
# Add anything you don't want to check in to git, e.g. very large files
+11
View File
@@ -0,0 +1,11 @@
# This file is used to select the BEC and Ophyd Devices version for the auto deployment process.
# Do not edit this file unless you know what you are doing!
# The version can be a git tag, branch or commit hash.
# BEC version to use
BEC_AUTODEPLOY_VERSION="master"
# ophyd_devices version to use
OPHYD_DEVICES_AUTODEPLOY_VERSION="master"
+18
View File
@@ -0,0 +1,18 @@
redis:
host: localhost
port: 6379
mongodb:
host: localhost
port: 27017
scibec:
host: http://[::1]
port: 3030
beamline: "PXIII"
service_config:
general:
reset_queue_on_cancel: True
enforce_ACLs: False
file_writer:
plugin: default_NeXus_format
base_path: ./
+29
View File
@@ -0,0 +1,29 @@
# deployment script to be translated to Ansible
# can be removed once we have the autodeployment in place
BEAMLINE_REPO=gitlab.psi.ch:bec/pxiii-bec.git
git clone git@$BEAMLINE_REPO
module add psi-python311/2024.02
# start redis
docker run --network=host --name redis-bec -d redis
# alternative:
# conda install -y redis; redis-server &
# get the target versions for ophyd_devices and BEC
source ./pxiii-bec/deployment/autodeploy_versions
git clone -b $OPHYD_DEVICES_AUTODEPLOY_VERSION https://gitlab.psi.ch/bec/ophyd_devices.git
git clone -b $BEC_AUTODEPLOY_VERSION https://gitlab.psi.ch/bec/bec.git
# install BEC
cd bec
source ./bin/install_bec_dev.sh
cd ../
pip install -e ./pxiii-bec
# start the BEC server
bec-server start --config ./pxiii-bec/deployment/bec-server-config.yaml
@@ -1,14 +1,10 @@
"""
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.
"""
import os
from bec_lib.service_config import ServiceConfig
import pxiii_bec
def extend_command_line_args(parser):
"""
@@ -22,11 +18,6 @@ def extend_command_line_args(parser):
def get_config() -> ServiceConfig:
"""
Create and return the ServiceConfig for the plugin repository
Create and return the service configuration.
"""
deployment_path = os.path.dirname(os.path.dirname(os.path.dirname(pxiii_bec.__file__)))
files = os.listdir(deployment_path)
if "bec_config.yaml" in files:
return ServiceConfig(config_path=os.path.join(deployment_path, "bec_config.yaml"))
else:
return ServiceConfig(redis={"host": "localhost", "port": 6379})
return ServiceConfig(redis={"host": "x06da-bec-001", "port": 6379})
@@ -1,135 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1801</width>
<height>1459</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>1801</width>
<height>1459</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Control Panel</string>
</attribute>
<layout class="QGridLayout" name="gridLayout" rowstretch="3,4" columnstretch="2,5">
<item row="0" column="0">
<widget class="Waveform" name="waveform"/>
</item>
<item row="0" column="1">
<widget class="ScanControl" name="scan_control"/>
</item>
<item row="1" column="0">
<widget class="ScanHistory" name="scan_history"/>
</item>
<item row="1" column="1">
<widget class="BECQueue" name="bec_queue"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Logbook</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="font">
<font>
<pointsize>24</pointsize>
</font>
</property>
<property name="text">
<string>Coming soon...</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_3">
<attribute name="title">
<string>Take a break</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="Minesweeper" name="minesweeper"/>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>1073</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>ScanControl</class>
<extends>QWidget</extends>
<header>scan_control</header>
</customwidget>
<customwidget>
<class>Waveform</class>
<extends>QWidget</extends>
<header>waveform</header>
</customwidget>
<customwidget>
<class>BECQueue</class>
<extends>QWidget</extends>
<header>bec_queue</header>
</customwidget>
<customwidget>
<class>Minesweeper</class>
<extends>QWidget</extends>
<header>minesweeper</header>
</customwidget>
<customwidget>
<class>ScanHistory</class>
<extends>QWidget</extends>
<header>scan_history</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
+1 -1
View File
@@ -1 +1 @@
from .auto_updates import PlotUpdate
+173
View File
@@ -0,0 +1,173 @@
from bec_widgets.cli.auto_updates import AutoUpdates, ScanInfo
from bec_widgets.cli.rpc.rpc_base import RPCResponseTimeoutError
class PlotUpdate(AutoUpdates):
create_default_dock = True
dock_name = "default_dock"
enabled = True
_scan_msg = None
# def __init__(self, gui: BECDockArea):
# super().__init__(gui)
def start_default_dock(self):
"""
Create a default dock for the auto updates.
"""
self._default_dock = self.gui.add_dock(self.dock_name)
self._default_dock.add_widget("BECWaveformWidget")
self._default_fig = self._default_dock.widget_list[0]
def do_update(self, msg):
"""Save the original scan message for future use"""
self._scan_msg = msg
return super().do_update(msg)
# def simple_line_scan(self, info: ScanInfo) -> None:
# """
# Simple line scan.
# """
# dev_x = info.scan_report_devices[0]
# dev_y = self.get_selected_device(info.monitored_devices, self.figure.selected_device)
# if not dev_y:
# return
# self.figure.clear_all()
# plt = self.figure.plot(dev_x, dev_y)
# plt.set(title=f"PXIII: Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
def plot_handler(self, info: ScanInfo) -> None:
"""Simple keyword handler for 'plot'
This simple keyword handler looks for the keyword 'plot' in the scan arguments.
This allows the user to explictly specify the desired data source. Useful for alignment
scans.
"""
print(info.scan_report_devices)
dev_x = info.scan_report_devices[0]
# Keyword lookup for 'plot' and 'fit'
if "kwargs" in self._scan_msg.info:
signals = self._scan_msg.info["kwargs"].get("plot", None)
fit = self._scan_msg.info["kwargs"].get("fit", None)
else:
signals = None
fit = None
if not signals:
return
if isinstance(signals, str):
signals = [signals]
if isinstance(fit, str):
fit = [fit]
# try:
# self.gui.clear_all()
# except RPCResponseTimeoutError:
# pass
# try:
# self._default_dock = self.gui.add_dock(self.dock_name)
# except RPCResponseTimeoutError:
# pass
# try:
# self._default_fig = self._default_dock.add_widget("BECWaveformWidget")
# except RPCResponseTimeoutError:
# pass
# try:
# self._default_fig = self._default_dock.widget_list[0]
# except RPCResponseTimeoutError:
# pass
# # dck1 = self._default_dock
# # dck1.clear_all()
plt1 = self.get_default_figure()
# # Clear figure
# try:
# old_data = yield plt1.get_all_data()
# except RPCResponseTimeoutError:
# old_data = [1,2,3,4,5,6,7,8,9]
# print(old_data)
# for _ in range(len(old_data)):
# try:
# plt1.remove_curve(-1)
# except RPCResponseTimeoutError:
# pass
# except Exception as ex:
# print(f"{ex}\t{type(ex)}")
# clear_all() will throw RPCResponseTimeoutError
# try:
# plt1.clear_all()
# except RPCResponseTimeoutError:
# pass
print(type(plt1), plt1)
print(f"Plotted signals: {signals}")
# plot() will throw RPCResponseTimeoutError
try:
if len(info.scan_report_devices) == 2:
# 2D plot
dev_x = info.scan_report_devices[0]
dev_y = info.scan_report_devices[1]
plt1.plot(
x_name=dev_x,
y_name=dev_y,
z_name=signals[0],
title=f"Scan {info.scan_number}",
x_label=dev_x,
y_label=dev_y,
z_label=signals[0],
)
elif len(info.scan_report_devices) == 1:
# 1D plot
dev_x = info.scan_report_devices[0]
for sig in signals:
try:
plt1.plot(
x_name=dev_x,
y_name=sig,
title=f"Scan {info.scan_number}",
x_label=dev_x,
y_label=sig,
)
except RPCResponseTimeoutError:
pass
else:
# Default is 1D
dev_x = info.scan_report_devices[0]
for sig in signals:
try:
plt1.plot(
x_name=dev_x,
y_name=sig,
title=f"Scan {info.scan_number}",
x_label=dev_x,
y_label=sig,
)
except RPCResponseTimeoutError:
pass
except RPCResponseTimeoutError:
pass
if fit is not None and len(fit):
dev_x = info.scan_report_devices[0]
sig = signals[0]
plt1.add_dap(dev_x, sig, dap=fit[0])
def handler(self, info: ScanInfo) -> None:
"""Dock configuration handler"""
# EXAMPLES:
# if info.scan_name == "line_scan" and info.scan_report_devices:
# self.simple_line_scan(info)
# return
# if info.scan_name == "grid_scan" and info.scan_report_devices:
# self.run_grid_scan_update(info)
# return
super().handler(info)
self.plot_handler(info)
@@ -1 +0,0 @@
from .auto_updates import AutoUpdates
@@ -1,71 +0,0 @@
from bec_widgets.widgets.containers.auto_update.auto_updates import AutoUpdates
from bec_lib.messages import ScanStatusMessage
from bec_widgets.cli.rpc.rpc_base import RPCResponseTimeoutError
class PlotUpdate(AutoUpdates):
#######################################################################
################# GUI Callbacks #######################################
#######################################################################
def on_start(self) -> None:
"""
Procedure to run when the auto updates are enabled.
"""
self.start_default_dock()
def on_stop(self) -> None:
"""
Procedure to run when the auto updates are disabled.
"""
def on_scan_open(self, msg: ScanStatusMessage) -> None:
"""
Procedure to run when a scan starts.
Args:
msg (ScanStatusMessage): The scan status message.
"""
if msg.scan_name == "line_scan" and msg.scan_report_devices:
return self.simple_line_scan(msg)
if msg.scan_name == "grid_scan" and msg.scan_report_devices:
return self.simple_grid_scan(msg)
dev_x = msg.scan_report_devices[0]
if "kwargs" in msg.request_inputs:
dev_y = msg.request_inputs["kwargs"].get("plot", None)
if dev_y is not None:
# Set the dock to the waveform widget
wf = self.set_dock_to_widget("Waveform")
# Clear the waveform widget and plot the data
wf.clear_all()
wf.plot(
x_name=dev_x,
y_name=dev_y,
label=f"Scan {msg.info.scan_number} - {dev_y}",
title=f"Scan {msg.info.scan_number}",
x_label=dev_x,
y_label=dev_y,
)
elif msg.scan_report_devices:
return self.best_effort(msg)
return None
def on_scan_closed(self, msg: ScanStatusMessage) -> None:
"""
Procedure to run when a scan ends.
Args:
msg (ScanStatusMessage): The scan status message.
"""
def on_scan_abort(self, msg: ScanStatusMessage) -> None:
"""
Procedure to run when a scan is aborted.
Args:
msg (ScanStatusMessage): The scan status message.
"""
-42
View File
@@ -1,42 +0,0 @@
# 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, rpc_timeout
logger = bec_logger.logger
# pylint: skip-file
_Widgets = {
"ScanHistory": "ScanHistory",
}
class ScanHistory(RPCBase):
_IMPORT_MODULE = "pxiii_bec.bec_widgets.widgets.scan_history.scan_history"
@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.
"""
@@ -1,13 +0,0 @@
# This file was automatically generated by generate_cli.py
# type: ignore
from __future__ import annotations
# pylint: skip-file
designer_plugins = {
"ScanHistory": ("pxiii_bec.bec_widgets.widgets.scan_history.scan_history", "ScanHistory"),
}
widget_icons = {
"ScanHistory": "widgets",
}
@@ -1,15 +0,0 @@
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 pxiii_bec.bec_widgets.widgets.scan_history.scan_history_plugin import ScanHistoryPlugin
QPyDesignerCustomWidgetCollection.addCustomWidget(ScanHistoryPlugin())
if __name__ == "__main__": # pragma: no cover
main()
@@ -1,191 +0,0 @@
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 = ['dccm_diode_bottom', 'dccm_diode_top', 'dccm_xbpm', 'ssxbpm', 'xbox_diode']
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_())
@@ -1 +0,0 @@
{'files': ['scan_history.py']}
@@ -1,115 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>955</width>
<height>796</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="9,3">
<item>
<widget class="Waveform" name="waveform">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="monitor_label">
<property name="font">
<font/>
</property>
<property name="text">
<string>BPM Monitor</string>
</property>
</widget>
</item>
<item>
<widget class="DeviceComboBox" name="monitor_combobox"/>
</item>
<item>
<widget class="QLabel" name="history_label">
<property name="text">
<string>Scan History</string>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="history_spin_box"/>
</item>
<item>
<widget class="QPushButton" name="history_add">
<property name="text">
<string>Add scan</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="history_clear">
<property name="text">
<string>clear all</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="TextBox" name="metadata_text_box">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>795</width>
<height>191</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>TextBox</class>
<extends>QWidget</extends>
<header>text_box</header>
</customwidget>
<customwidget>
<class>DeviceComboBox</class>
<extends>QComboBox</extends>
<header>device_combobox</header>
</customwidget>
<customwidget>
<class>Waveform</class>
<extends>QWidget</extends>
<header>waveform</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
@@ -1,54 +0,0 @@
# 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 pxiii_bec.bec_widgets.widgets.scan_history.scan_history import ScanHistory
DOM_XML = """
<ui language='c++'>
<widget class='ScanHistory' name='scan_history'>
</widget>
</ui>
"""
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()
View File
@@ -1,11 +0,0 @@
import os
def setup_epics_ca():
# os.environ["EPICS_CA_AUTO_ADDR_LIST"] = "NO"
# os.environ["EPICS_CA_ADDR_LIST"] = "129.129.122.255 sls-x12sa-cagw.psi.ch:5836"
os.environ["PYTHONIOENCODING"] = "latin1"
def run():
setup_epics_ca()
@@ -1,151 +0,0 @@
states:
robot_sample_exchange:
allow_modifiers: true
bl_pos: in
bl_bright: 'off'
bs_pos: in
bs_z: safe
coll_y: out
cryo_pos: in
det_cov: 'close'
diag_y: out
fl_bright: 'off'
gon_x: in
# smargon: not implemented
xrf_pos: out
sample_alignment:
allow_modifiers: true
bl_pos: in
bl_bright: 'on'
bs_pos: in
bs_z: safe
coll_y: out
cryo_pos: in
det_cov: 'close'
diag_y: out
fl_bright: 'on'
gon_x: in
# smargon: not implemented
xrf_pos: out
data_collection:
allow_modifiers: true
bl_pos: out
bl_bright: 'off'
bs_pos: in
bs_z: safe
coll_y: in
cryo_pos: in
det_cov: 'open'
diag_y: out
fl_bright: 'on'
gon_x: in
# smargon: not implemented
xrf_pos: out
DC_XRF:
allow_modifiers: true
# bl_pos: out
bl_bright: 'off'
bs_pos: in
bs_z: safe
coll_y: in
cryo_pos: in
det_cov: 'close'
diag_y: out
fl_bright: 'on'
gon_x: in
# smargon: not implemented
xrf_pos: in
manual_sample_exchange:
allow_modifiers: true
bl_pos: out
bl_bright: 'off'
bs_pos: out
bs_z: safe
coll_y: park
cryo_pos: in
det_cov: 'close'
diag_y: park
fl_bright: 'off'
gon_x: in
# smargon: not implemented
xrf_pos: out
beam_visualisation:
bl_pos: out
bl_bright: 'off'
bs_pos: in
bs_z: safe
coll_y: out
cryo_pos: out
det_cov: 'close'
diag_y: scint
fl_bright: 'off'
gon_x: out
# smargon: not implemented
xrf_pos: out
flux_measurement:
bl_pos: in
bl_bright: 'off'
bs_pos: in
bs_z: safe
coll_y: out
cryo_pos: out
det_cov: 'close'
diag_y: i1
fl_bright: 'off'
gon_x: out
# smargon: not implemented
xrf_pos: out
beamstop_alignment:
bl_pos: out
bl_bright: 'off'
bs_pos: in
bs_z: samp
coll_y: out
cryo_pos: out
det_cov: 'close'
diag_y: out
fl_bright: 'on'
gon_x: out
# smargon: not implemented
xrf_pos: out
maintenance:
allow_modifiers: true
bl_pos: out
bl_bright: 'off'
bs_pos: out
bs_z: safe
coll_y: park
cryo_pos: in
det_cov: 'close'
diag_y: park
fl_bright: 'off'
gon_x: out
# smargon: not implemented
xrf_pos: out
xtal_snapshot:
allow_modifiers: true
bl_pos: in
bl_bright: 'on'
bs_pos: in
bs_z: safe
coll_y: intermediate
cryo_pos: in
det_cov: 'close'
diag_y: out
fl_bright: 'on'
gon_x: in
# smargon: not implemented
xrf_pos: out
-81
View File
@@ -1,81 +0,0 @@
import csv
import json
def str_to_bool(val):
return str(val).strip().lower() in ["yes", "true", "1"]
def create(INPUT_CSV, OUTPUT_YAML):
with open(INPUT_CSV, newline="") as csvfile:
reader = csv.DictReader(csvfile)
with open(OUTPUT_YAML, "w") as yamlfile:
for row in reader:
include = row["include"]
name = row["name"]
desc = row["description"]
device_class = row["deviceClass"]
pv = row["PV"]
readout_priority = row["readoutPriority"]
tag = row["tag"]
read_only = str_to_bool(row["readOnly"])
user_param = row.get("userParameter", "").strip()
if str(include).strip().lower() != "yes":
continue
yamlfile.write(f"{name}:\n")
yamlfile.write(f" description: {desc}\n")
if device_class == "Motor" or device_class == "MotorEC":
yamlfile.write(f" deviceClass: ophyd_devices.Epics{device_class}\n")
yamlfile.write(f" deviceConfig: {{prefix: '{pv}'}}\n")
else:
yamlfile.write(f" deviceClass: ophyd.Epics{device_class}\n")
yamlfile.write(
f" deviceConfig: {{read_pv: '{pv}', auto_monitor: true}}\n"
)
yamlfile.write(" onFailure: buffer\n")
yamlfile.write(" enabled: True\n")
yamlfile.write(f" readoutPriority: {readout_priority}\n")
yamlfile.write(" deviceTags:\n")
yamlfile.write(f" - {tag}\n")
yamlfile.write(f" readOnly: {read_only}\n")
yamlfile.write(" softwareTrigger: false\n")
# Only add userParameter for Motors if present
# if device_class == "Motor" and user_param:
if user_param:
try:
parsed = json.loads(user_param)
yamlfile.write(" userParameter:\n")
for k, v in parsed.items():
yamlfile.write(f" {k}: {v}\n")
except json.JSONDecodeError:
yamlfile.write(f" userParameter: {user_param}\n")
yamlfile.write("\n")
print(f"YAML written to {OUTPUT_YAML}")
def main():
devices = "pxiii-standard-devices"
states = "pxiii-state-devices"
device_files = [
f"{devices}.csv",
f"{devices}.yaml"
]
state_files = [
f"{states}.csv",
f"{states}.yaml"
]
create(device_files[0],device_files[1])
create(state_files[0],state_files[1])
main()
@@ -1,127 +0,0 @@
name,description,deviceClass,PV,readoutPriority,tag,readOnly,include,userParameter,
sls_current,SLS Current,SignalRO,ARS07-DPCT-0100:CURR,monitored,SLS,yes,yes,,
fe_sl_xr,FE Slit X Ring,MotorEC,X06DA-FE-SLDI:TRXR,baseline,fe,no,yes,,
fe_sl_yt,FE Slit Y Top,MotorEC,X06DA-FE-SLDI:TRYT,baseline,fe,no,yes,,
fe_sl_xw,FE Slit X Wall,MotorEC,X06DA-FE-SLDI:TRXW,baseline,fe,no,yes,,
fe_sl_yb,FE Slit Y Bottom,MotorEC,X06DA-FE-SLDI:TRYB,baseline,fe,no,yes,,
fe_sl_xcen,FE Slit X Centre,MotorEC,X06DA-FE-SLDI:CENTERX,baseline,fe,no,yes,,
fe_sl_xsize,FE Slit X Size,MotorEC,X06DA-FE-SLDI:SIZEX,baseline,fe,no,yes,,
fe_sl_ycen,FE Slit Y Centre,MotorEC,X06DA-FE-SLDI:CENTERY,baseline,fe,no,yes,,
fe_sl_ysize,FE Slit Y Size,MotorEC,X06DA-FE-SLDI:SIZEY,baseline,fe,no,yes,,
tm_xu,TorM Upstream X,MotorEC,X06DA-FE-MI1:TRXU,baseline,tm,no,yes,,
tm_xd,TorM Downstream X,MotorEC,X06DA-FE-MI1:TRXD,baseline,tm,no,yes,,
tm_yur,TorM Upstream Ring Y,MotorEC,X06DA-FE-MI1:TRYUR,baseline,tm,no,yes,,
tm_yw,TorM Wall Y,MotorEC,X06DA-FE-MI1:TRYUW,baseline,tm,no,yes,,
tm_yd,TorM Downstream Y,MotorEC,X06DA-FE-MI1:TRYD,baseline,tm,no,yes,,
tm_b1,TorM Bender,MotorEC,X06DA-FE-MI1:BEND1,baseline,tm,no,yes,,
tm_yaw,TorM Virtual Yaw,MotorEC,X06DA-FE-MI1:YAW,baseline,tm,no,yes,,
tm_roll,TorM Virtual Roll,MotorEC,X06DA-FE-MI1:ROLL,baseline,tm,no,yes,,
tm_pitch,TorM Virtual Pitch,MotorEC,X06DA-FE-MI1:PITCH,baseline,tm,no,yes,,
tm_x,TorM Virtual X,MotorEC,X06DA-FE-MI1:TRX,baseline,tm,no,yes,,
tm_y,TorM Virtual Y ,MotorEC,X06DA-FE-MI1:TRY,baseline,tm,no,yes,,
bsf_bpm1,BSF BPM Channel 1,SignalRO,X06DA-OP-BSFBPM:SIGNAL1,monitored,bpm,yes,no,,
bsf_bpm2,BSF BPM Channel 2,SignalRO,X06DA-OP-BSFBPM:SIGNAL2,monitored,bpm,yes,no,,
bsf_bpm3,BSF BPM Channel 3,SignalRO,X06DA-OP-BSFBPM:SIGNAL3,monitored,bpm,yes,no,,
bsf_bpm4,BSF BPM Channel 4,SignalRO,X06DA-OP-BSFBPM:SIGNAL4,monitored,bpm,yes,no,,
bsf_bpmsum,BSF BPM Summed,SignalRO,X06DA-OP-BSFBPM:SUM,monitored,bpm,yes,no,,
bsf_sl_xw,BSF Slit outboard,MotorEC,X06DA-OP-BSFSLH:TRXW,baseline,bsf,no,yes,,
bsf_sl_xr,BSF Slit inboard,MotorEC,X06DA-OP-BSFSLH:TRXR,baseline,bsf,no,yes,,
bsf_sl_xcen,BSF X Centre,MotorEC,X06DA-OP-BSFSLH:CENTER,baseline,bsf,no,yes,,
bsf_sl_xsize,BSF X Size,MotorEC,X06DA-OP-BSFSLH:SIZE,baseline,bsf,no,yes,,
bsf_f1_y,BSF Filter 1 Y,MotorEC,X06DA-OP-BSFFI1:TRY,baseline,bsf,no,yes,,
dccm_theta1,DCCM Theta Xtal1,MotorEC,X06DA-OP-DCCM:ROTX-CR1,baseline,dccm,no,yes,,
dccm_theta2,DCCM Theta Xtal2,MotorEC,X06DA-OP-DCCM:ROTX-CR2,baseline,dccm,no,yes,,
dccm_rotz,DCCM RotZ Xtal 2,MotorEC,X06DA-OP-DCCM:ROTZ-CR2,baseline,dccm,no,yes,,
dccm_xbpm1_y,DCCM BPM1 Y,MotorEC,X06DA-OP-DCCMXBPM1:TRY,baseline,dccm,no,yes,,
dccm_xbpm2_y,DCCM BPM2 Y,MotorEC,X06DA-OP-DCCMXBPM2:TRY,baseline,dccm,no,yes,,
dccm_energy,DCCM Energy,Motor,X06DA-OP-DCCM:ENERGY,baseline,dccm,no,yes,,
dccm_di_top,DCCM Diode Top,SignalRO,X06DA-OP-DCCMXBPM1T:READOUT,monitored,dccm,no,yes,,
dccm_di_bot,DCCM Diode Bottom,SignalRO,X06DA-OP-DCCMXBPM1B:READOUT,monitored,dccm,no,yes,,
dccm_bpm1,DCCM BPM Channel 1,SignalRO,X06DA-OP-DCCMXBPM2:Current1:MeanValue_RBV,monitored,dccm,no,yes,,
dccm_bpm2,DCCM BPM Channel 2,SignalRO,X06DA-OP-DCCMXBPM2:Current2:MeanValue_RBV,monitored,dccm,no,yes,,
dccm_bpm3,DCCM BPM Channel 3,SignalRO,X06DA-OP-DCCMXBPM2:Current3:MeanValue_RBV,monitored,dccm,no,yes,,
dccm_bpm4,DCCM BPM Channel 4,SignalRO,X06DA-OP-DCCMXBPM2:Current4:MeanValue_RBV,monitored,dccm,no,yes,,
dccm_bpmsum,DCCM BPM Summed,SignalRO,X06DA-OP-DCCMXBPM2:SumAll:MeanValue_RBV,monitored,dccm,no,yes,,
ss_bpm1,SS BPM Channel 1,SignalRO,X06DA-ES-SSBPM:Current1:MeanValue_RBV,monitored,bpm,yes,yes,,
ss_bpm2,SS BPM Channel 2,SignalRO,X06DA-ES-SSBPM:Current2:MeanValue_RBV,monitored,bpm,yes,yes,,
ss_bpm3,SS BPM Channel 3,SignalRO,X06DA-ES-SSBPM:Current3:MeanValue_RBV,monitored,bpm,yes,yes,,
ss_bpm4,SS BPM Channel 4,SignalRO,X06DA-ES-SSBPM:Current4:MeanValue_RBV,monitored,bpm,yes,yes,,
ss_bpmsum,SS BPM Summed,SignalRO,X06DA-ES-SSBPM:SumAll:MeanValue_RBV,monitored,bpm,yes,yes,,
ss_bpm_x,SS BPM X,Motor,X06DA-ES-SSBPM:TRX,baseline,ss,no,yes,,
ss_bpm_y,SS BPM Y,Motor,X06DA-ES-SSBPM:TRY,baseline,ss,no,yes,,
ss_sl_xw,SS Slit Wall,Motor,X06DA-ES-SSSLH:TRXW,baseline,ss,no,yes,,
ss_sl_xr,SS Slit Ring,Motor,X06DA-ES-SSSLH:TRXR,baseline,ss,no,yes,,
ss_sl_xcen,SS Slit X Centre,Motor,X06DA-ES-SSSLH:CENTER,baseline,ss,no,yes,,
ss_sl_xsize,SS Slit X Size,Motor,X06DA-ES-SSSLH:SIZE,baseline,ss,no,yes,,
ss_sl_yt,SS Slit Top,Motor,X06DA-ES-SSSLV:TRYT,baseline,ss,no,yes,,
ss_sl_yb,SS Slit Bottom,Motor,X06DA-ES-SSSLV:TRYB,baseline,ss,no,yes,,
ss_sl_ycen,SS Slit Y Centre,Motor,X06DA-ES-SSSLV:CENTER,baseline,ss,no,yes,,
ss_sl_ysize,SS Slit Y Size,Motor,X06DA-ES-SSSLV:SIZE,baseline,ss,no,yes,,
ss_xi_x,SS X-ray Eye X,Motor,X06DA-ES-SSXI:TRX,baseline,ss,no,yes,"{""type"": multi-position,""in"": 7.5, ""out"": -2.1}",
ss_xi_y,SS X-ray Eye Y,Motor,X06DA-ES-SSXI:TRY,baseline,ss,no,yes,,
ss_xicam_x,SS Camera X,SignalRO,X06DA-ES-SSCAM:Stats5:CentroidX_RBV,baseline,ss,yes,yes,,
ss_xicam_y,SS Camera Y,SignalRO,X06DA-ES-SSCAM:Stats5:CentroidY_RBV,baseline,ss,yes,yes,,
ss_xicam_max,SS Cam Max,SignalRO,X06DA-ES-SSCAM:Stats5:MaxValue_RBV,monitored,ss,yes,yes,,
ss_xicam_exp,SS Camera Exposure,Signal,X06DA-ES-SSCAM:cam1:AcquireTime,baseline,ss,no,yes,,
ss_xicam_gain,SS Camera Gain,Signal,X06DA-ES-SSCAM:cam1:Gain,baseline,ss,no,yes,,
ss_xicam_xsig,SS Camera X Sigma,Signal,X06DA-ES-SSCAM:Stats5:SigmaX_RBV,baseline,ss,yes,yes,,
ss_xicam_ysig,SS Camera Y Sigma,Signal,X06DA-ES-SSCAM:Stats5:SigmaY_RBV,baseline,ss,yes,yes,,
vfm_xu,VFM Upstream X,MotorEC,X06DA-ES-VFM:TRXU,baseline,vfm,no,yes,,
vfm_xd,VFM Downstream X,MotorEC,X06DA-ES-VFM:TRXD,baseline,vfm,no,yes,,
vfm_yur,VFM Upstream Ring Y,MotorEC,X06DA-ES-VFM:TRYUR,baseline,vfm,no,yes,,
vfm_yw,VFM Wall Y,MotorEC,X06DA-ES-VFM:TRYW,baseline,vfm,no,yes,,
vfm_ydr,VFM Downstream Ring Y,MotorEC,X06DA-ES-VFM:TRYDR,baseline,vfm,no,yes,,
vfm_bu,VFM Upstream Bender,MotorEC,X06DA-ES-VFM:BNDU,baseline,vfm,no,yes,,
vfm_bd,VFM Downstream Bender,MotorEC,X06DA-ES-VFM:BNDD,baseline,vfm,no,yes,,
vfm_yaw,VFM Virtual Yaw,MotorEC,X06DA-ES-VFM:YAW,baseline,vfm,no,yes,,
vfm_roll,VFM Virtual Roll,MotorEC,X06DA-ES-VFM:ROLL,baseline,vfm,no,yes,,
vfm_pitch,VFM Virtual Pitch,MotorEC,X06DA-ES-VFM:PITCH,baseline,vfm,no,yes,,
vfm_x,VFM Virtual X,MotorEC,X06DA-ES-VFM:TRX,baseline,vfm,no,yes,,
vfm_y,VFM Virtual Y ,MotorEC,X06DA-ES-VFM:TRY,baseline,vfm,no,yes,,
hfm_xu,HFM Upstream X,MotorEC,X06DA-ES-HFM:TRXU,baseline,hfm,no,yes,,
hfm_xd,HFM Downstream X,MotorEC,X06DA-ES-HFM:TRXD,baseline,hfm,no,yes,,
hfm_yuw,HFM Upstream Wall Y,MotorEC,X06DA-ES-HFM:TRYUW,baseline,hfm,no,yes,,
hfm_yr,HFM Ring Y,MotorEC,X06DA-ES-HFM:TRYR,baseline,hfm,no,yes,,
hfm_ydw,HFM Downstream Wall Y,MotorEC,X06DA-ES-HFM:TRYDW,baseline,hfm,no,yes,,
hfm_bu,HFM Upstream Bender,MotorEC,X06DA-ES-HFM:BNDU,baseline,hfm,no,yes,,
hfm_bd,HFM Downstream Bender,MotorEC,X06DA-ES-HFM:BNDD,baseline,hfm,no,yes,,
hfm_yaw,HFM Virtual Yaw,MotorEC,X06DA-ES-HFM:YAW,baseline,hfm,no,yes,,
hfm_roll,HFM Virtual Roll,MotorEC,X06DA-ES-HFM:ROLL,baseline,hfm,no,yes,,
hfm_pitch,HFM Virtual Pitch,MotorEC,X06DA-ES-HFM:PITCH,baseline,hfm,no,yes,,
hfm_x,HFM Virtual X,MotorEC,X06DA-ES-HFM:TRX,baseline,hfm,no,yes,,
hfm_y,HFM Virtual Y ,MotorEC,X06DA-ES-HFM:TRY,baseline,hfm,no,yes,,
bcu_bpm1,BCU BPM Channel 1 ,SignalRO,X06DA-ES-BCBPM:Current1:MeanValue_RBV,monitored,bpm,yes,yes,,
bcu_bpm2,BCU BPM Channel 2,SignalRO,X06DA-ES-BCBPM:Current2:MeanValue_RBV,monitored,bpm,yes,yes,,
bcu_bpm3,BCU BPM Channel 3,SignalRO,X06DA-ES-BCBPM:Current3:MeanValue_RBV,monitored,bpm,yes,yes,,
bcu_bpm4,BCU BPM Channel 4,SignalRO,X06DA-ES-BCBPM:Current4:MeanValue_RBV,monitored,bpm,yes,yes,,
bcu_bpmsum,BCU BPM Summed,SignalRO,X06DA-ES-BCBPM:SumAll:MeanValue_RBV,monitored,bpm,yes,yes,,
bcu_bpm_x,BCU BPM X,Motor,X06DA-ES-BCBPM:TRX,baseline,bcu,no,yes,,
bcu_bpm_y,BCU BPM Y ,Motor,X06DA-ES-BCBPM:TRY,baseline,bcu,no,yes,,
bcu_sl_xw,BCU Slit Wall,Motor,X06DA-ES-BCSLH:TRXW,baseline,bcu,no,yes,,
bcu_sl_xr,BCU Slit Ring,Motor,X06DA-ES-BCSLH:TRXR,baseline,bcu,no,yes,,
bcu_sl_xcen,BCU Slit X Centre,Motor,X06DA-ES-BCSLH:CENTER,baseline,bcu,no,yes,,
bcu_sl_xsize,BCU Slit X Size,Motor,X06DA-ES-BCSLH:SIZE,baseline,bcu,no,yes,,
bcu_sl_yt,BCU Slit top,Motor,X06DA-ES-BCSLV:TRYT,baseline,bcu,no,yes,,
bcu_sl_yb,BCU Slit Bottom,Motor,X06DA-ES-BCSLV:TRYB,baseline,bcu,no,yes,,
bcu_sl_ycen,BCU Slit Y Centre,Motor,X06DA-ES-BCSLV:CENTER,baseline,bcu,no,yes,,
bcu_sl_ysize,BCU Slit Y Size,Motor,X06DA-ES-BCSLV:SIZE,baseline,bcu,no,yes,,
samcam_x,Sample Camera X ,SignalRO,X06DA-ES-MS:Stats5:CentroidX_RBV,baseline,scam,yes,no,,
samcam_xsig,Sample Camera X Sigma,SignalRO,X06DA-ES-MS:Stats5:SigmaX_RBV,monitored,scam,yes,no,,
samcam_y,Sample Camera Y ,SignalRO,X06DA-ES-MS:Stats5:CentroidY_RBV,baseline,scam,yes,no,,
samcam_ysig,Sample Camera Y Sigma,SignalRO,X06DA-ES-MS:Stats5:SigmaY_RBV,monitored,scam,yes,no,,
samcam_max,Sample Camera Max,SignalRO,X06DA-ES-MS:Stats5:MaxValue_RBV,monitored,scam,yes,no,,
samcam_exp,Sample Camera Exposure,Signal,X06DA-ES-MS:cam1:AcquireTime,baseline,scam,no,no,,
samcam_gain,Sample Camera Gain,Signal,X06DA-ES-MS:cam1:Gain,baseline,scam,no,no,,
scam_zoom,Sample Camera Zoom,Motor,X06DA-ES-MS:ZOOM,baseline,scam,no,yes,,
coll_x,Collimator X,Motor,X06DA-ES-COL:TRX,baseline,se,no,no,,
diag_z,Scintillator/diode Z,Motor,X06DA-ES-SCL:TRZ,baseline,se,no,no,,
i1,I1 diode,SignalRO,X06DA-ES-SCLDI:READOUT,monitored,bpm,yes,no,,
bs_x,Beamstop X,Motor,X06DA-ES-BS:TRX,baseline,se,no,no,,
bs_y,Beamstop Y,Motor,X06DA-ES-BS:TRY,baseline,se,no,no,,
gon_y,Goniometer Y,Motor,X06DA-ES-DF1:TRY1,baseline,det,no,no,,
gon_z,Goniometer X,Motor,X06DA-ES-DF1:TRZ1,baseline,det,no,no,,
omega,Omega,Motor,X06DA-ES-DF1:ROTU,baseline,det,no,no,,
cryo_x,Cryo X ,Motor,X06DA-ES-CS:TRX,baseline,se,no,no,,
cryo_temp,Cryo Temperature,SignalRO,X06DA-ES-CS:TEMP_RBV,baseline,se,no,no,,
det_y,Detector Y,MotorEC,X06DA-ES-DET:TRY,baseline,det,no,no,,
det_z,Detector Z,MotorEC,X06DA-ES-DET:TRZ,baseline,det,no,no,,
1 name description deviceClass PV readoutPriority tag readOnly include userParameter
2 sls_current SLS Current SignalRO ARS07-DPCT-0100:CURR monitored SLS yes yes
3 fe_sl_xr FE Slit X Ring MotorEC X06DA-FE-SLDI:TRXR baseline fe no yes
4 fe_sl_yt FE Slit Y Top MotorEC X06DA-FE-SLDI:TRYT baseline fe no yes
5 fe_sl_xw FE Slit X Wall MotorEC X06DA-FE-SLDI:TRXW baseline fe no yes
6 fe_sl_yb FE Slit Y Bottom MotorEC X06DA-FE-SLDI:TRYB baseline fe no yes
7 fe_sl_xcen FE Slit X Centre MotorEC X06DA-FE-SLDI:CENTERX baseline fe no yes
8 fe_sl_xsize FE Slit X Size MotorEC X06DA-FE-SLDI:SIZEX baseline fe no yes
9 fe_sl_ycen FE Slit Y Centre MotorEC X06DA-FE-SLDI:CENTERY baseline fe no yes
10 fe_sl_ysize FE Slit Y Size MotorEC X06DA-FE-SLDI:SIZEY baseline fe no yes
11 tm_xu TorM Upstream X MotorEC X06DA-FE-MI1:TRXU baseline tm no yes
12 tm_xd TorM Downstream X MotorEC X06DA-FE-MI1:TRXD baseline tm no yes
13 tm_yur TorM Upstream Ring Y MotorEC X06DA-FE-MI1:TRYUR baseline tm no yes
14 tm_yw TorM Wall Y MotorEC X06DA-FE-MI1:TRYUW baseline tm no yes
15 tm_yd TorM Downstream Y MotorEC X06DA-FE-MI1:TRYD baseline tm no yes
16 tm_b1 TorM Bender MotorEC X06DA-FE-MI1:BEND1 baseline tm no yes
17 tm_yaw TorM Virtual Yaw MotorEC X06DA-FE-MI1:YAW baseline tm no yes
18 tm_roll TorM Virtual Roll MotorEC X06DA-FE-MI1:ROLL baseline tm no yes
19 tm_pitch TorM Virtual Pitch MotorEC X06DA-FE-MI1:PITCH baseline tm no yes
20 tm_x TorM Virtual X MotorEC X06DA-FE-MI1:TRX baseline tm no yes
21 tm_y TorM Virtual Y MotorEC X06DA-FE-MI1:TRY baseline tm no yes
22 bsf_bpm1 BSF BPM Channel 1 SignalRO X06DA-OP-BSFBPM:SIGNAL1 monitored bpm yes no
23 bsf_bpm2 BSF BPM Channel 2 SignalRO X06DA-OP-BSFBPM:SIGNAL2 monitored bpm yes no
24 bsf_bpm3 BSF BPM Channel 3 SignalRO X06DA-OP-BSFBPM:SIGNAL3 monitored bpm yes no
25 bsf_bpm4 BSF BPM Channel 4 SignalRO X06DA-OP-BSFBPM:SIGNAL4 monitored bpm yes no
26 bsf_bpmsum BSF BPM Summed SignalRO X06DA-OP-BSFBPM:SUM monitored bpm yes no
27 bsf_sl_xw BSF Slit outboard MotorEC X06DA-OP-BSFSLH:TRXW baseline bsf no yes
28 bsf_sl_xr BSF Slit inboard MotorEC X06DA-OP-BSFSLH:TRXR baseline bsf no yes
29 bsf_sl_xcen BSF X Centre MotorEC X06DA-OP-BSFSLH:CENTER baseline bsf no yes
30 bsf_sl_xsize BSF X Size MotorEC X06DA-OP-BSFSLH:SIZE baseline bsf no yes
31 bsf_f1_y BSF Filter 1 Y MotorEC X06DA-OP-BSFFI1:TRY baseline bsf no yes
32 dccm_theta1 DCCM Theta Xtal1 MotorEC X06DA-OP-DCCM:ROTX-CR1 baseline dccm no yes
33 dccm_theta2 DCCM Theta Xtal2 MotorEC X06DA-OP-DCCM:ROTX-CR2 baseline dccm no yes
34 dccm_rotz DCCM RotZ Xtal 2 MotorEC X06DA-OP-DCCM:ROTZ-CR2 baseline dccm no yes
35 dccm_xbpm1_y DCCM BPM1 Y MotorEC X06DA-OP-DCCMXBPM1:TRY baseline dccm no yes
36 dccm_xbpm2_y DCCM BPM2 Y MotorEC X06DA-OP-DCCMXBPM2:TRY baseline dccm no yes
37 dccm_energy DCCM Energy Motor X06DA-OP-DCCM:ENERGY baseline dccm no yes
38 dccm_di_top DCCM Diode Top SignalRO X06DA-OP-DCCMXBPM1T:READOUT monitored dccm no yes
39 dccm_di_bot DCCM Diode Bottom SignalRO X06DA-OP-DCCMXBPM1B:READOUT monitored dccm no yes
40 dccm_bpm1 DCCM BPM Channel 1 SignalRO X06DA-OP-DCCMXBPM2:Current1:MeanValue_RBV monitored dccm no yes
41 dccm_bpm2 DCCM BPM Channel 2 SignalRO X06DA-OP-DCCMXBPM2:Current2:MeanValue_RBV monitored dccm no yes
42 dccm_bpm3 DCCM BPM Channel 3 SignalRO X06DA-OP-DCCMXBPM2:Current3:MeanValue_RBV monitored dccm no yes
43 dccm_bpm4 DCCM BPM Channel 4 SignalRO X06DA-OP-DCCMXBPM2:Current4:MeanValue_RBV monitored dccm no yes
44 dccm_bpmsum DCCM BPM Summed SignalRO X06DA-OP-DCCMXBPM2:SumAll:MeanValue_RBV monitored dccm no yes
45 ss_bpm1 SS BPM Channel 1 SignalRO X06DA-ES-SSBPM:Current1:MeanValue_RBV monitored bpm yes yes
46 ss_bpm2 SS BPM Channel 2 SignalRO X06DA-ES-SSBPM:Current2:MeanValue_RBV monitored bpm yes yes
47 ss_bpm3 SS BPM Channel 3 SignalRO X06DA-ES-SSBPM:Current3:MeanValue_RBV monitored bpm yes yes
48 ss_bpm4 SS BPM Channel 4 SignalRO X06DA-ES-SSBPM:Current4:MeanValue_RBV monitored bpm yes yes
49 ss_bpmsum SS BPM Summed SignalRO X06DA-ES-SSBPM:SumAll:MeanValue_RBV monitored bpm yes yes
50 ss_bpm_x SS BPM X Motor X06DA-ES-SSBPM:TRX baseline ss no yes
51 ss_bpm_y SS BPM Y Motor X06DA-ES-SSBPM:TRY baseline ss no yes
52 ss_sl_xw SS Slit Wall Motor X06DA-ES-SSSLH:TRXW baseline ss no yes
53 ss_sl_xr SS Slit Ring Motor X06DA-ES-SSSLH:TRXR baseline ss no yes
54 ss_sl_xcen SS Slit X Centre Motor X06DA-ES-SSSLH:CENTER baseline ss no yes
55 ss_sl_xsize SS Slit X Size Motor X06DA-ES-SSSLH:SIZE baseline ss no yes
56 ss_sl_yt SS Slit Top Motor X06DA-ES-SSSLV:TRYT baseline ss no yes
57 ss_sl_yb SS Slit Bottom Motor X06DA-ES-SSSLV:TRYB baseline ss no yes
58 ss_sl_ycen SS Slit Y Centre Motor X06DA-ES-SSSLV:CENTER baseline ss no yes
59 ss_sl_ysize SS Slit Y Size Motor X06DA-ES-SSSLV:SIZE baseline ss no yes
60 ss_xi_x SS X-ray Eye X Motor X06DA-ES-SSXI:TRX baseline ss no yes {"type": multi-position,"in": 7.5, "out": -2.1}
61 ss_xi_y SS X-ray Eye Y Motor X06DA-ES-SSXI:TRY baseline ss no yes
62 ss_xicam_x SS Camera X SignalRO X06DA-ES-SSCAM:Stats5:CentroidX_RBV baseline ss yes yes
63 ss_xicam_y SS Camera Y SignalRO X06DA-ES-SSCAM:Stats5:CentroidY_RBV baseline ss yes yes
64 ss_xicam_max SS Cam Max SignalRO X06DA-ES-SSCAM:Stats5:MaxValue_RBV monitored ss yes yes
65 ss_xicam_exp SS Camera Exposure Signal X06DA-ES-SSCAM:cam1:AcquireTime baseline ss no yes
66 ss_xicam_gain SS Camera Gain Signal X06DA-ES-SSCAM:cam1:Gain baseline ss no yes
67 ss_xicam_xsig SS Camera X Sigma Signal X06DA-ES-SSCAM:Stats5:SigmaX_RBV baseline ss yes yes
68 ss_xicam_ysig SS Camera Y Sigma Signal X06DA-ES-SSCAM:Stats5:SigmaY_RBV baseline ss yes yes
69 vfm_xu VFM Upstream X MotorEC X06DA-ES-VFM:TRXU baseline vfm no yes
70 vfm_xd VFM Downstream X MotorEC X06DA-ES-VFM:TRXD baseline vfm no yes
71 vfm_yur VFM Upstream Ring Y MotorEC X06DA-ES-VFM:TRYUR baseline vfm no yes
72 vfm_yw VFM Wall Y MotorEC X06DA-ES-VFM:TRYW baseline vfm no yes
73 vfm_ydr VFM Downstream Ring Y MotorEC X06DA-ES-VFM:TRYDR baseline vfm no yes
74 vfm_bu VFM Upstream Bender MotorEC X06DA-ES-VFM:BNDU baseline vfm no yes
75 vfm_bd VFM Downstream Bender MotorEC X06DA-ES-VFM:BNDD baseline vfm no yes
76 vfm_yaw VFM Virtual Yaw MotorEC X06DA-ES-VFM:YAW baseline vfm no yes
77 vfm_roll VFM Virtual Roll MotorEC X06DA-ES-VFM:ROLL baseline vfm no yes
78 vfm_pitch VFM Virtual Pitch MotorEC X06DA-ES-VFM:PITCH baseline vfm no yes
79 vfm_x VFM Virtual X MotorEC X06DA-ES-VFM:TRX baseline vfm no yes
80 vfm_y VFM Virtual Y MotorEC X06DA-ES-VFM:TRY baseline vfm no yes
81 hfm_xu HFM Upstream X MotorEC X06DA-ES-HFM:TRXU baseline hfm no yes
82 hfm_xd HFM Downstream X MotorEC X06DA-ES-HFM:TRXD baseline hfm no yes
83 hfm_yuw HFM Upstream Wall Y MotorEC X06DA-ES-HFM:TRYUW baseline hfm no yes
84 hfm_yr HFM Ring Y MotorEC X06DA-ES-HFM:TRYR baseline hfm no yes
85 hfm_ydw HFM Downstream Wall Y MotorEC X06DA-ES-HFM:TRYDW baseline hfm no yes
86 hfm_bu HFM Upstream Bender MotorEC X06DA-ES-HFM:BNDU baseline hfm no yes
87 hfm_bd HFM Downstream Bender MotorEC X06DA-ES-HFM:BNDD baseline hfm no yes
88 hfm_yaw HFM Virtual Yaw MotorEC X06DA-ES-HFM:YAW baseline hfm no yes
89 hfm_roll HFM Virtual Roll MotorEC X06DA-ES-HFM:ROLL baseline hfm no yes
90 hfm_pitch HFM Virtual Pitch MotorEC X06DA-ES-HFM:PITCH baseline hfm no yes
91 hfm_x HFM Virtual X MotorEC X06DA-ES-HFM:TRX baseline hfm no yes
92 hfm_y HFM Virtual Y MotorEC X06DA-ES-HFM:TRY baseline hfm no yes
93 bcu_bpm1 BCU BPM Channel 1 SignalRO X06DA-ES-BCBPM:Current1:MeanValue_RBV monitored bpm yes yes
94 bcu_bpm2 BCU BPM Channel 2 SignalRO X06DA-ES-BCBPM:Current2:MeanValue_RBV monitored bpm yes yes
95 bcu_bpm3 BCU BPM Channel 3 SignalRO X06DA-ES-BCBPM:Current3:MeanValue_RBV monitored bpm yes yes
96 bcu_bpm4 BCU BPM Channel 4 SignalRO X06DA-ES-BCBPM:Current4:MeanValue_RBV monitored bpm yes yes
97 bcu_bpmsum BCU BPM Summed SignalRO X06DA-ES-BCBPM:SumAll:MeanValue_RBV monitored bpm yes yes
98 bcu_bpm_x BCU BPM X Motor X06DA-ES-BCBPM:TRX baseline bcu no yes
99 bcu_bpm_y BCU BPM Y Motor X06DA-ES-BCBPM:TRY baseline bcu no yes
100 bcu_sl_xw BCU Slit Wall Motor X06DA-ES-BCSLH:TRXW baseline bcu no yes
101 bcu_sl_xr BCU Slit Ring Motor X06DA-ES-BCSLH:TRXR baseline bcu no yes
102 bcu_sl_xcen BCU Slit X Centre Motor X06DA-ES-BCSLH:CENTER baseline bcu no yes
103 bcu_sl_xsize BCU Slit X Size Motor X06DA-ES-BCSLH:SIZE baseline bcu no yes
104 bcu_sl_yt BCU Slit top Motor X06DA-ES-BCSLV:TRYT baseline bcu no yes
105 bcu_sl_yb BCU Slit Bottom Motor X06DA-ES-BCSLV:TRYB baseline bcu no yes
106 bcu_sl_ycen BCU Slit Y Centre Motor X06DA-ES-BCSLV:CENTER baseline bcu no yes
107 bcu_sl_ysize BCU Slit Y Size Motor X06DA-ES-BCSLV:SIZE baseline bcu no yes
108 samcam_x Sample Camera X SignalRO X06DA-ES-MS:Stats5:CentroidX_RBV baseline scam yes no
109 samcam_xsig Sample Camera X Sigma SignalRO X06DA-ES-MS:Stats5:SigmaX_RBV monitored scam yes no
110 samcam_y Sample Camera Y SignalRO X06DA-ES-MS:Stats5:CentroidY_RBV baseline scam yes no
111 samcam_ysig Sample Camera Y Sigma SignalRO X06DA-ES-MS:Stats5:SigmaY_RBV monitored scam yes no
112 samcam_max Sample Camera Max SignalRO X06DA-ES-MS:Stats5:MaxValue_RBV monitored scam yes no
113 samcam_exp Sample Camera Exposure Signal X06DA-ES-MS:cam1:AcquireTime baseline scam no no
114 samcam_gain Sample Camera Gain Signal X06DA-ES-MS:cam1:Gain baseline scam no no
115 scam_zoom Sample Camera Zoom Motor X06DA-ES-MS:ZOOM baseline scam no yes
116 coll_x Collimator X Motor X06DA-ES-COL:TRX baseline se no no
117 diag_z Scintillator/diode Z Motor X06DA-ES-SCL:TRZ baseline se no no
118 i1 I1 diode SignalRO X06DA-ES-SCLDI:READOUT monitored bpm yes no
119 bs_x Beamstop X Motor X06DA-ES-BS:TRX baseline se no no
120 bs_y Beamstop Y Motor X06DA-ES-BS:TRY baseline se no no
121 gon_y Goniometer Y Motor X06DA-ES-DF1:TRY1 baseline det no no
122 gon_z Goniometer X Motor X06DA-ES-DF1:TRZ1 baseline det no no
123 omega Omega Motor X06DA-ES-DF1:ROTU baseline det no no
124 cryo_x Cryo X Motor X06DA-ES-CS:TRX baseline se no no
125 cryo_temp Cryo Temperature SignalRO X06DA-ES-CS:TEMP_RBV baseline se no no
126 det_y Detector Y MotorEC X06DA-ES-DET:TRY baseline det no no
127 det_z Detector Z MotorEC X06DA-ES-DET:TRZ baseline det no no
File diff suppressed because it is too large Load Diff
@@ -1,11 +0,0 @@
name,description,deviceClass,PV,readoutPriority,tag,readOnly,include,userParameter
bl_bright,Backlight Brightness,Signal,X06DA-ES-BL:SET,baseline,state,no,yes,
bl_pos,Backlight Positioner,Signal,X06DA-ES-BL:POS-SET,baseline,state,no,no,"{""type"":positioner}"
bs_pos,Beamstop Positioner,Signal,X06DA-ES-BS:POS-SET,baseline,state,no,no,"{""type"":positioner}"
bs_z,Beamstop Z,Motor,X06DA-ES-BS:TRZ,baseline,state,no,yes,"{""type"": guarded, ""min"": 13, ""samp"": 15, ""work_min"": 20, ""safe"": 41, ""max_blin"": 42, ""max_blout"": 70}"
coll_y,Collimator Y,Motor,X06DA-ES-COL:TRY,baseline,state,no,yes,"{""type"": multi-position, ""in"": 40, ""out"": 20.0, ""park"": 0,""tol"":0.05}"
cryo_pos,Cryo positioner,Signal,X06DA-ES-CS:POS-SET,baseline,state,no,no,"{""type"":positioner}"
det_cov,Detector cover,Signal,X06DA-ES-DETCOV:SET,baseline,state,no,no,"{""type"":positioner}"
diag_y,Scintillator/diode Y,Motor,X06DA-ES-SCL:TRY,baseline,state,no,yes,"{""type"": multi-position, ""scint"": 39, ""i1"": 44.0, ""out"": 20.0,""park"": 0,""tol"":0.3}"
fl_bright,Frontlight Brightness,Signal,X06DA-ES-FL:SET,baseline,state,no,yes,
xrf_pos,XRF Positioner,Signal,X06DA-ES-XRF:POS-SET,baseline,state,no,no,"{""type"":positioner}"
1 name description deviceClass PV readoutPriority tag readOnly include userParameter
2 bl_bright Backlight Brightness Signal X06DA-ES-BL:SET baseline state no yes
3 bl_pos Backlight Positioner Signal X06DA-ES-BL:POS-SET baseline state no no {"type":positioner}
4 bs_pos Beamstop Positioner Signal X06DA-ES-BS:POS-SET baseline state no no {"type":positioner}
5 bs_z Beamstop Z Motor X06DA-ES-BS:TRZ baseline state no yes {"type": guarded, "min": 13, "samp": 15, "work_min": 20, "safe": 41, "max_blin": 42, "max_blout": 70}
6 coll_y Collimator Y Motor X06DA-ES-COL:TRY baseline state no yes {"type": multi-position, "in": 40, "out": 20.0, "park": 0,"tol":0.05}
7 cryo_pos Cryo positioner Signal X06DA-ES-CS:POS-SET baseline state no no {"type":positioner}
8 det_cov Detector cover Signal X06DA-ES-DETCOV:SET baseline state no no {"type":positioner}
9 diag_y Scintillator/diode Y Motor X06DA-ES-SCL:TRY baseline state no yes {"type": multi-position, "scint": 39, "i1": 44.0, "out": 20.0,"park": 0,"tol":0.3}
10 fl_bright Frontlight Brightness Signal X06DA-ES-FL:SET baseline state no yes
11 xrf_pos XRF Positioner Signal X06DA-ES-XRF:POS-SET baseline state no no {"type":positioner}
@@ -1,63 +0,0 @@
bl_bright:
description: Backlight Brightness
deviceClass: ophyd.EpicsSignal
deviceConfig: {read_pv: 'X06DA-ES-BL:SET', auto_monitor: true}
onFailure: buffer
enabled: True
readoutPriority: baseline
deviceTags:
- state
readOnly: False
softwareTrigger: false
bs_z:
description: Beamstop Z
deviceClass: ophyd_devices.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-BS:TRZ'}
onFailure: buffer
enabled: True
readoutPriority: baseline
deviceTags:
- state
readOnly: False
softwareTrigger: false
userParameter: {"type": guarded, "min": 13, "samp": 15, "work_min": 20, "safe": 41, "max_blin": 42, "max_blout": 70}
coll_y:
description: Collimator Y
deviceClass: ophyd_devices.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-COL:TRY'}
onFailure: buffer
enabled: True
readoutPriority: baseline
deviceTags:
- state
readOnly: False
softwareTrigger: false
userParameter: {"type": multi-position, "in": 40, "out": 20.0, "park": 0,"tol":0.05}
diag_y:
description: Scintillator/diode Y
deviceClass: ophyd_devices.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-SCL:TRY'}
onFailure: buffer
enabled: True
readoutPriority: baseline
deviceTags:
- state
readOnly: False
softwareTrigger: false
userParameter: {"type": multi-position, "scint": 39, "i1": 44.0, "out": 20.0,"park": 0,"tol":0.3}
fl_bright:
description: Frontlight Brightness
deviceClass: ophyd.EpicsSignal
deviceConfig: {read_pv: 'X06DA-ES-FL:SET', auto_monitor: true}
onFailure: buffer
enabled: True
readoutPriority: baseline
deviceTags:
- state
readOnly: False
softwareTrigger: false
@@ -1,4 +1,703 @@
base_config:
- !include ./pxiii-standard-devices.yaml
states_config:
- !include ./pxiii-state-devices.yaml
sls_current:
description: SLS current
deviceClass: ophyd.EpicsSignalRO
deviceConfig: {read_pv: 'ARS07-DPCT-0100:CURR', auto_monitor: true}
onFailure: buffer
enabled: true
readoutPriority: monitored
deviceTags:
- ring
- fe
readOnly: true
softwareTrigger: false
vg0_press:
description: VG0 pressure
deviceClass: ophyd.EpicsSignalRO
deviceConfig: {read_pv: 'X06DA-FE-VMCC-0000:PRESSURE', auto_monitor: true}
onFailure: buffer
enabled: true
readoutPriority: monitored
deviceTags:
- fe
readOnly: true
softwareTrigger: false
abs_press:
description: Absorber pressure
deviceClass: ophyd.EpicsSignalRO
deviceConfig: {read_pv: 'X06DA-FE-ABS1-VMCC-1010:PRESSURE', auto_monitor: true}
onFailure: buffer
enabled: true
readoutPriority: monitored
deviceTags:
- fe
readOnly: true
softwareTrigger: false
sldi_cenx:
description: FE slit-diaphragm horizontal center
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-FE-SLDI:CENX'}
onFailure: buffer
enabled: true
readoutPriority: monitored
deviceTags:
- fe
readOnly: false
softwareTrigger: false
sldi_sizex:
description: FE slit-diaphragm horizontal size
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-FE-SLDI:SIZEX'}
onFailure: buffer
enabled: true
readoutPriority: monitored
deviceTags:
- fe
readOnly: false
softwareTrigger: false
sldi_ceny:
description: FE slit-diaphragm vertical center
deviceClass: ophyd_devices.EpicsMotorEC
deviceConfig: {prefix: 'X06DA-FE-SLDI:CENY'}
onFailure: buffer
enabled: true
readoutPriority: monitored
deviceTags:
- fe
readOnly: false
softwareTrigger: false
sldi_sizey:
description: FE slit-diaphragm vertical size
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-FE-SLDI:SIZEY'}
onFailure: buffer
enabled: true
readoutPriority: monitored
deviceTags:
- fe
readOnly: false
softwareTrigger: false
slh_trxr:
description: OP slit inner blade motion
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-OP-SLH:TRXR'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
slh_trxw:
description: OP slit outer blade motion
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-OP-SLH:TRXW'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
fi1_try:
description: Beam attenuator motion before mono
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-OP-FI1:TRY1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
dccm_theta1:
description: Monochromator pitch 1
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-OP-DCCM:PITCH1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
dccm_diode:
description: Diode between mono crystals
deviceClass: ophyd.EpicsSignalRO
deviceConfig: {read_pv: 'X06DA-OP-XPM1:BOT:READOUT', auto_monitor: true}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: true
softwareTrigger: false
dccm_theta2:
description: Monochromator pitch 2
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-OP-DCCM:PITCH2'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
dccm_xbpm:
description: XBPM total intensity after monochromator
deviceClass: ophyd.EpicsSignalRO
deviceConfig: {read_pv: 'X06DA-OP-XBPM1:SumAll:MeanValue_RBV', auto_monitor: true}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: true
softwareTrigger: false
dccm_energy:
description: Monochromator energy
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-OP-DCCM:ENERGY'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
dccm_eoffset:
description: Monochromator energy offset
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-OP-DCCM:EOFFSET'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
ssxbpm_trx:
description: XBPM motion before secondary source
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-SSBPM1:TRX1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
ssxbpm_try:
description: XBPM motion before secondary source
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-SSBPM1:TRY1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
# ssxbpm:
# description: XBPM before secondary source
# deviceClass: ophyd.EpicsSignalRO
# deviceConfig: {read_pv: 'X06DA-ES-SSBPM1:SumAll:MeanValue_RBV'}
# onFailure: buffer
# enabled: true
# readoutPriority: monitored
# readOnly: true
# softwareTrigger: false
ssslit_trxr:
description: Secondary source blade motion
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-SSSH1:TRXR'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
ssslit_trxw:
description: Secondary source blade motion
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-SSSH1:TRXW'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
ssslit_tryt:
description: Secondary source blade motion
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-SSSV1:TRYT'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
ssslit_tryb:
description: Secondary source blade motion
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-SSSV1:TRYB'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
ssxi1_trx:
description: Secondary source diagnostic screen motion
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-SSXI1:TRX1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
ssxi1_try:
description: Secondary source diagnostic screen motion
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-SSXI1:TRY1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
vfm_trxu:
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-VFM:TRXU'}
enabled: false
onFailure: buffer
readoutPriority: monitored
readOnly: false
softwareTrigger: false
vfm_trxd:
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-VFM:TRXD'}
enabled: false
onFailure: buffer
readoutPriority: monitored
readOnly: false
softwareTrigger: false
# vfm_tryuw:
# deviceClass: ophyd.EpicsMotor
# deviceConfig: {prefix: 'X06DA-ES-VFM:TRYUW'}
# onFailure: buffer
# enabled: true
# readoutPriority: monitored
# readOnly: false
# softwareTrigger: false
# vfm_tryr:
# deviceClass: ophyd.EpicsMotor
# deviceConfig: {prefix: 'X06DA-ES-VFM:TRYR'}
# onFailure: buffer
# enabled: true
# readoutPriority: monitored
# readOnly: false
# softwareTrigger: false
# vfm_trydw:
# deviceClass: ophyd.EpicsMotor
# deviceConfig: {prefix: 'X06DA-ES-VFM:TRYDW'}
# onFailure: buffer
# enabled: true
# readoutPriority: monitored
# readOnly: false
# softwareTrigger: false
vfm_pitch:
description: KB mirror vertical steering
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-VFM:PITCH'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
vfm_yaw:
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-VFM:YAW'}
enabled: false
onFailure: buffer
readoutPriority: monitored
readOnly: false
softwareTrigger: false
vfm_roll:
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-VFM:ROLL'}
enabled: false
onFailure: buffer
readoutPriority: monitored
readOnly: false
softwareTrigger: false
vfm_trx:
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-VFM:TRX'}
enabled: false
onFailure: buffer
readoutPriority: monitored
readOnly: false
softwareTrigger: false
vfm_try:
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-VFM:TRY'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
hfm_trxu:
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-HFM:TRXU'}
enabled: false
onFailure: buffer
readoutPriority: monitored
readOnly: false
softwareTrigger: false
hfm_trxd:
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-HFM:TRXD'}
enabled: false
onFailure: buffer
readoutPriority: monitored
readOnly: false
softwareTrigger: false
# hfm_tryur:
# deviceClass: ophyd.EpicsMotor
# deviceConfig: {prefix: 'X06DA-ES-HFM:TRYUR'}
# onFailure: buffer
# enabled: true
# readoutPriority: monitored
# readOnly: false
# softwareTrigger: false
# hfm_tryw:
# deviceClass: ophyd.EpicsMotor
# deviceConfig: {prefix: 'X06DA-ES-HFM:TRYW'}
# onFailure: buffer
# enabled: true
# readoutPriority: monitored
# readOnly: false
# softwareTrigger: false
# hfm_trydr:
# deviceClass: ophyd.EpicsMotor
# deviceConfig: {prefix: 'X06DA-ES-HFM:TRYDR'}
# onFailure: buffer
# enabled: true
# readoutPriority: monitored
# readOnly: false
# softwareTrigger: false
hfm_pitch:
description: KB mirror horizontal steering
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-HFM:PITCH'}
enabled: false
onFailure: buffer
readoutPriority: monitored
readOnly: false
softwareTrigger: false
hfm_yaw:
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-HFM:YAW'}
enabled: false
onFailure: buffer
readoutPriority: monitored
readOnly: false
softwareTrigger: false
hfm_roll:
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-HFM:ROLL'}
enabled: false
onFailure: buffer
readoutPriority: monitored
readOnly: false
softwareTrigger: false
hfm_trx:
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-HFM:TRX'}
enabled: false
onFailure: buffer
readoutPriority: monitored
readOnly: false
softwareTrigger: false
hfm_try:
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-HFM:TRY'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
# xbox_xbpm:
# description: Exposure box XBPM
# deviceClass: ophyd.EpicsSignalRO
# deviceConfig: {read_pv: 'X06DA-ES-XBBPM1:SumAll:MeanValue_RBV'}
# onFailure: buffer
# enabled: true
# readoutPriority: monitored
# readOnly: true
# softwareTrigger: false
xbox_fil1:
description: Exposure box filter wheel 1
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-FI1:ROZ1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
xbox_fil2:
description: Exposure box filter wheel 2
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-FI2:ROZ1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
xbox_fil3:
description: Exposure box filter wheel 3
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-FI3:ROZ1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
xbox_fil4:
description: Exposure box filter wheel 4
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-FI4:ROZ1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
xbox_diode:
description: Exposure box diode
deviceClass: ophyd.EpicsSignalRO
deviceConfig: {read_pv: 'X06DA-ES-DI1:READOUT'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: true
softwareTrigger: false
gonpos:
description: Sample sensor distance
deviceClass: ophyd.EpicsSignalRO
deviceConfig: {read_pv: 'X06DA-ES-DF1:CBOX-USER1', auto_monitor: true}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: true
softwareTrigger: false
gonvalid:
description: Sample in valid distance
deviceClass: ophyd.EpicsSignalRO
deviceConfig: {read_pv: 'X06DA-ES-DF1:CBOX-CMP1', auto_monitor: true}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: true
softwareTrigger: false
samzoom:
description: Sample microscope zoom
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-SAMCAM:ZOOM'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
samcam:
description: Sample camera aggregate device
deviceClass: pxiii_bec.devices.SamCamDetector
deviceConfig: {prefix: 'X06DA-SAMCAM:'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
samstream:
description: Sample camera ZMQ stream
deviceClass: pxiii_bec.devices.StdDaqPreviewDetector
deviceConfig:
url: 'tcp://129.129.110.12:9089'
deviceTags:
- detector
enabled: true
readoutPriority: async
readOnly: false
softwareTrigger: false
# samimg:
# description: Sample camera image from EPICS
# deviceClass: pxiii_bec.devices.NDArrayPreview
# deviceConfig:
# prefix: 'X06DA-SAMCAM:image1:'
# deviceTags:
# - detector
# enabled: true
# readoutPriority: async
# readOnly: false
# softwareTrigger: false
bstop_pneum:
description: Beamstop pneumatic in-out
deviceClass: ophyd.EpicsSignal
deviceConfig: {read_pv: 'X06DA-ES-BS:GET-POS', write_pv: 'X06DA-ES-BS:SET-POS'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
bstop_x:
description: Beamstop translation
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-BS:TRX1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
bstop_y:
description: Beamstop translation
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-BS:TRY1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
bstop_z:
description: Beamstop translation
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-BS:TRZ1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
bstop_pneum:
description: Beamstop pneumatic
deviceClass: pxiii_bec.devices.PneumaticValve
deviceConfig: {read_pv: 'X06DA-ES-BS:GET-POS', write_pv: 'X06DA-ES-BS:SET-POS', kind: 'config', auto_monitor: true, put_complete: true}
onFailure: buffer
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
bstop_diode:
description: Beamstop diode
deviceClass: ophyd.EpicsSignalRO
deviceConfig: {read_pv: 'X06DA-ES-BS:READOUT', auto_monitor: true}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: true
softwareTrigger: false
frontlight:
description: Microscope frontlight
deviceClass: ophyd.EpicsSignal
deviceConfig: {read_pv: 'X06DA-ES-FL:SET-BRGHT', kind: 'config', put_complete: true}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
backlight:
description: Backlight reflector
deviceClass: pxiii_bec.devices.PneumaticValve
deviceConfig: {read_pv: 'X06DA-ES-BL:GET-POS', write_pv: 'X06DA-ES-BL:SET-POS', kind: 'config', auto_monitor: true, put_complete: true}
onFailure: buffer
enabled: true
readoutPriority: baseline
readOnly: false
softwareTrigger: false
det_y:
description: Pilatus height
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-DET:TRY1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
det_z:
description: Pilatus translation
deviceClass: ophyd.EpicsMotor
deviceConfig: {prefix: 'X06DA-ES-DET:TRZ1'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
gmx:
description: ABR horizontal stage
deviceClass: pxiii_bec.devices.A3200Axis
deviceConfig: {prefix: 'X06DA-ES-DF1:GMX', base_pv: 'X06DA-ES'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
gmy:
description: ABR vertical stage
deviceClass: pxiii_bec.devices.A3200Axis
deviceConfig: {prefix: 'X06DA-ES-DF1:GMY', base_pv: 'X06DA-ES'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
gmz:
description: ABR axial stage
deviceClass: pxiii_bec.devices.A3200Axis
deviceConfig: {prefix: 'X06DA-ES-DF1:GMZ', base_pv: 'X06DA-ES'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
omega:
description: ABR rotation stage
deviceClass: pxiii_bec.devices.A3200Axis
deviceConfig: {prefix: 'X06DA-ES-DF1:OMEGA', base_pv: 'X06DA-ES'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
abr:
description: Aerotech ABR motion system
deviceClass: pxiii_bec.devices.AerotechAbrStage
deviceConfig: {prefix: 'X06DA-ES'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
shx:
description: SmarGon X axis
deviceClass: pxiii_bec.devices.SmarGonAxisB
deviceConfig: {prefix: 'SCS', low_limit: -2, high_limit: 2, sg_url: 'http://x06da-smargopolo.psi.ch:3000'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
shy:
description: SmarGon Y axis
deviceClass: pxiii_bec.devices.SmarGonAxisB
deviceConfig: {prefix: 'SCS', low_limit: -2, high_limit: 2, sg_url: 'http://x06da-smargopolo.psi.ch:3000'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
shz:
description: SmarGon Z axis
deviceClass: pxiii_bec.devices.SmarGonAxisB
deviceConfig: {prefix: 'SCS', low_limit: 10, high_limit: 22, sg_url: 'http://x06da-smargopolo.psi.ch:3000'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
chi:
description: SmarGon CHI axis
deviceClass: pxiii_bec.devices.SmarGonAxisB
deviceConfig: {prefix: 'SCS', low_limit: 0, high_limit: 40, sg_url: 'http://x06da-smargopolo.psi.ch:3000'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
phi:
description: SmarGon PHI axis
deviceClass: pxiii_bec.devices.SmarGonAxisB
deviceConfig: {prefix: 'SCS', sg_url: 'http://x06da-smargopolo.psi.ch:3000'}
onFailure: buffer
enabled: true
readoutPriority: monitored
readOnly: false
softwareTrigger: false
+91 -107
View File
@@ -51,9 +51,9 @@ Examples
"""
import time
from ophyd import Device, Component, EpicsSignal, EpicsSignalRO, Kind
from ophyd import Component, EpicsSignal, EpicsSignalRO, Kind
from ophyd.status import SubscriptionStatus
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
from ophyd_devices.interfaces.base_classes.bec_device_base import BECDeviceBase, CustomPrepare
try:
from .A3200enums import AbrCmd, AbrMode
@@ -67,7 +67,90 @@ logger = bec_logger.logger
# pylint: disable=logging-fstring-interpolation
class AerotechAbrStage(PSIDeviceBase, Device):
class AerotechAbrMixin(CustomPrepare):
"""Configuration class for the Aerotech A3200 controller for the ABR stage"""
def on_stage(self):
"""
NOTE: Zac's request is that stage is essentially ARM, i.e. get ready and don't do anything.
"""
logger.warning(f"Configuring {self.parent.scaninfo.scan_msg.info['scan_name']} on ABR")
d = {}
if self.parent.scaninfo.scan_type in ("measure", "measurement", "fly"):
scanargs = self.parent.scaninfo.scan_msg.info["kwargs"]
scanname = self.parent.scaninfo.scan_msg.info["scan_name"]
if scanname in (
"standardscan",
"helicalscan",
"helicalscan1",
"helicalscan2",
"helicalscan3",
):
d["scan_command"] = AbrCmd.MEASURE_STANDARD
d["var_1"] = scanargs["start"]
d["var_2"] = scanargs["range"]
d["var_3"] = scanargs["move_time"]
d["var_4"] = scanargs.get("ready_rate", 500)
d["var_5"] = 0
d["var_6"] = 0
d["var_7"] = 0
# d["var_8"] = 0
# d["var_9"] = 0
if scanname in ("verticallinescan", "vlinescan"):
d["scan_command"] = AbrCmd.VERTICAL_LINE_SCAN
d["var_1"] = scanargs["range"] / scanargs["steps"]
d["var_2"] = scanargs["steps"]
d["var_3"] = scanargs["exp_time"]
d["var_4"] = 0
d["var_5"] = 0
d["var_6"] = 0
d["var_7"] = 0
# d["var_8"] = 0
# d["var_9"] = 0
if scanname in ("screeningscan"):
d["scan_command"] = AbrCmd.SCREENING
d["var_1"] = scanargs["start"]
d["var_2"] = scanargs["oscrange"]
d["var_3"] = scanargs["exp_time"]
d["var_4"] = scanargs["range"] / scanargs["steps"]
d["var_5"] = scanargs["steps"]
d["var_6"] = scanargs.get("delta", 0.5)
d["var_7"] = 0
# d["var_8"] = 0
# d["var_9"] = 0
if scanname in ("rasterscan", "rastersimplescan"):
d["scan_command"] = AbrCmd.RASTER_SCAN_SIMPLE
d["var_1"] = scanargs["exp_time"]
d["var_2"] = scanargs["range_x"] / scanargs["steps_x"]
d["var_3"] = scanargs["range_y"] / scanargs["steps_y"]
d["var_4"] = scanargs["steps_x"]
d["var_5"] = scanargs["steps_y"]
d["var_6"] = 0
d["var_7"] = 0
# d["var_8"] = 0
# d["var_9"] = 0
# Reconfigure if got a valid scan config
if len(d) > 0:
self.parent.configure(d)
# Stage the parent
self.parent.bluestage()
def on_kickoff(self):
"""Kick off parent"""
self.parent.bluekickoff()
def on_unstage(self):
"""Unstage the ABR controller"""
self.parent.blueunstage()
class AerotechAbrStage(BECDeviceBase):
"""Standard PX stage on A3200 controller
This is the wrapper class for the standard rotation stage layout for the PX
@@ -78,7 +161,8 @@ class AerotechAbrStage(PSIDeviceBase, Device):
it via 10+1 global variables.
"""
USER_ACCESS = ["reset", "kickoff", "complete", "set_axis_mode", "arm", "disarm"]
custom_prepare_cls = AerotechAbrMixin
USER_ACCESS = ["reset", "kickoff", "bluekickoff", "complete", "set_axis_mode", "arm", "disarm"]
taskStop = Component(EpicsSignal, "-AERO:TSK-STOP", put_complete=True, kind=Kind.omitted)
status = Component(EpicsSignal, "-AERO:STAT", put_complete=True, kind=Kind.omitted)
@@ -130,30 +214,6 @@ class AerotechAbrStage(PSIDeviceBase, Device):
task4 = Component(EpicsSignalRO, "-AERO:TSK4-DONE", auto_monitor=True)
scan_done = Component(EpicsSignal, "-GRD:SCAN-DONE", kind=Kind.config)
def __init__(
self,
prefix="",
*,
name,
kind=None,
read_attrs=None,
configuration_attrs=None,
parent=None,
scan_info=None,
**kwargs,
):
# super() will call the mixin class
super().__init__(
prefix=prefix,
name=name,
kind=kind,
read_attrs=read_attrs,
configuration_attrs=configuration_attrs,
parent=parent,
scan_info=scan_info,
**kwargs,
)
def set_axis_mode(self, mode: str, settle_time=0.1) -> None:
"""Set axis mode to direct/measurement mode.
@@ -170,82 +230,6 @@ class AerotechAbrStage(PSIDeviceBase, Device):
if mode == "measuring":
self.axisAxesMode.set(AbrMode.MEASURING, settle_time=settle_time).wait()
def on_stage(self):
"""
NOTE: Zac's request is that stage is essentially ARM, i.e. get ready and don't do anything.
"""
d = {}
# FIXME: I don't care about how we fish out config parameters from scan info
scan_args = {
**self.scan_info.msg.request_inputs["inputs"],
**self.scan_info.msg.request_inputs["kwargs"],
**self.scan_info.msg.scan_parameters,
}
scanname = self.scan_info.msg.scan_name
if scanname in (
"standardscan",
"helicalscan",
"helicalscan1",
"helicalscan2",
"helicalscan3",
):
d["scan_command"] = AbrCmd.MEASURE_STANDARD
d["var_1"] = scan_args["start"]
d["var_2"] = scan_args["range"]
d["var_3"] = scan_args["move_time"]
d["var_4"] = scan_args.get("ready_rate", 500)
d["var_5"] = 0
d["var_6"] = 0
d["var_7"] = 0
# d["var_8"] = 0
# d["var_9"] = 0
if scanname in ("verticallinescan", "vlinescan"):
d["scan_command"] = AbrCmd.VERTICAL_LINE_SCAN
d["var_1"] = scan_args["range"] / scan_args["steps"]
d["var_2"] = scan_args["steps"]
d["var_3"] = scan_args["exp_time"]
d["var_4"] = 0
d["var_5"] = 0
d["var_6"] = 0
d["var_7"] = 0
# d["var_8"] = 0
# d["var_9"] = 0
if scanname in ("screeningscan"):
d["scan_command"] = AbrCmd.SCREENING
d["var_1"] = scan_args["start"]
d["var_2"] = scan_args["oscrange"]
d["var_3"] = scan_args["exp_time"]
d["var_4"] = scan_args["range"] / scan_args["steps"]
d["var_5"] = scan_args["steps"]
d["var_6"] = scan_args.get("delta", 0.5)
d["var_7"] = 0
# d["var_8"] = 0
# d["var_9"] = 0
if scanname in ("rasterscan", "rastersimplescan"):
d["scan_command"] = AbrCmd.RASTER_SCAN_SIMPLE
d["var_1"] = scan_args["exp_time"]
d["var_2"] = scan_args["range_x"] / scan_args["steps_x"]
d["var_3"] = scan_args["range_y"] / scan_args["steps_y"]
d["var_4"] = scan_args["steps_x"]
d["var_5"] = scan_args["steps_y"]
d["var_6"] = 0
d["var_7"] = 0
# d["var_8"] = 0
# d["var_9"] = 0
# Reconfigure if got a valid scan config
if len(d) > 0:
self.configure(d)
# Stage the ABR stage
self.arm()
def on_unstage(self):
"""Unstage the ABR controller"""
self.disarm()
def configure(self, d: dict) -> tuple:
""" " Configure the exposure scripts
@@ -300,14 +284,14 @@ class AerotechAbrStage(PSIDeviceBase, Device):
new = self.read_configuration()
return old, new
def arm(self):
def bluestage(self):
"""Bluesky-style stage
Since configuration synchronization is not guaranteed, this does
nothing. The script launched by kickoff().
"""
def on_kickoff(self, timeout=1) -> SubscriptionStatus:
def bluekickoff(self, timeout=1) -> SubscriptionStatus:
"""Kick off the set program"""
self.start_command.set(1).wait()
@@ -320,7 +304,7 @@ class AerotechAbrStage(PSIDeviceBase, Device):
status.wait()
# return status
def disarm(self, settle_time=0.1):
def blueunstage(self, settle_time=0.1):
"""Stops current script and releases the axes"""
# Disarm commands
self.scan_command.set(AbrCmd.NONE, settle_time=settle_time).wait()
+3
View File
@@ -102,6 +102,8 @@ class StdDaqPreviewMixin(CustomDetectorMixin):
self.parent.array_counter.put(header["frame"], force=True)
self.parent.ndimensions.put(len(header["shape"]), force=True)
self.parent.array_size.put(header["shape"], force=True)
self.parent.array_average.put(np.mean(image), force=True)
# self.parent.array_data.put(data, force=True)
self.parent.shaped_image.put(image, force=True)
@@ -152,6 +154,7 @@ class StdDaqPreviewDetector(PSIDetectorBase):
url = Component(Signal, kind=Kind.config, metadata={"write_access": False})
throttle = Component(Signal, value=0.25, kind=Kind.config)
# Streamed data status
array_average = Component(Signal, kind=Kind.hinted, metadata={"write_access": False})
array_counter = Component(Signal, kind=Kind.hinted, metadata={"write_access": False})
ndimensions = Component(Signal, kind=Kind.normal, metadata={"write_access": False})
array_size = Component(Signal, kind=Kind.normal, metadata={"write_access": False})
-6
View File
@@ -1,6 +0,0 @@
# Macros
This directory is intended to store macros which will be loaded automatically when starting BEC.
Macros are small functions to make repetitive tasks easier. Functions defined in python files in this directory will be accessible from the BEC console.
Please do not put any code outside of function definitions here. If you wish for code to be automatically run when starting BEC, see the startup script at pxiii_bec/bec_ipython_client/startup/post_startup.py
For a guide on writing macros, please see: https://bec.readthedocs.io/en/latest/user/command_line_interface.html#how-to-write-a-macro
View File
-357
View File
@@ -1,357 +0,0 @@
"""Utility functions for calculating energy, wavelength, and Bragg angle."""
from dataclasses import dataclass
import numpy as np
# from pxii_parameters import (EnergyDefaults, CamConversion)
@dataclass(frozen=True)
class Constants:
"""Constants used in energy calculations"""
# # Physical Constants from https://physics.nist.gov/cuu/Constants/index.html
ANGSTROM_CONVERSION = 1e10 # Convert meters to angstrom
PLANCK_CONST_EV = 4.135667696e-15 # eV/Hz
SPEED_OF_LIGHT = 299792458 # m/s
# d-spacings
d_spacing = {120: 3.13481, 298: 3.13562}
def speed_of_light_ang():
"""
Calculate the speed of light in angstroms per second.
Returns:
float: The speed of light converted to angstroms per second.
"""
return Constants.SPEED_OF_LIGHT * Constants.ANGSTROM_CONVERSION
def en_wav_factor():
"""
Calculate the energy wavelength factor.
This function computes a constant factor used to calculate energy
values in relation to wavelength by combining Planck's constant,
in eV/Hz, and the speed of light in angstrom.
Returns:
float: The computed energy wavelength factor.
"""
return Constants.PLANCK_CONST_EV * speed_of_light_ang()
# Helper Functions
def convert_to_degrees(angle_mrad: float) -> float:
"""
Convert an angle from milliradians to degrees.
Args:
angle_mrad: The angle value in milliradians.
Returns:
The angle converted into degrees as a float.
"""
return np.rad2deg(angle_mrad / 1000)
def create_conversion_result(
energy_ev: float, wavelength: float, bragg_angle_mrad: float
) -> dict:
"""
Creates a dictionary containing converted values of energy and angles.
This function takes the energy in electron-volts, the wavelength,
and the Bragg angle in milliradians as input. It computes and
returns a dictionary containing the energy in both electron-volts
and kiloelectron-volts, the wavelength, the Bragg angle in milliradians,
and the Bragg angle converted to degrees.
Args:
energy_ev: Energy value in electron-volts.
wavelength: Wavelength value.
bragg_angle_mrad: Bragg angle in milliradians.
Returns:
dict: A dictionary containing the following keys:
- "energy_kev": Energy value in kiloelectron-volts.
- "energy_ev": Energy value in electron-volts.
- "wavelength": Wavelength value.
- "bragg_angle_mrad": Bragg angle in milliradians.
- "bragg_angle_deg": Bragg angle in degrees.
"""
return {
"energy_kev": energy_ev / 1000,
"energy_ev": energy_ev,
"wavelength": wavelength,
"bragg_angle_mrad": float(bragg_angle_mrad),
"bragg_angle_deg": float(convert_to_degrees(bragg_angle_mrad)),
}
def print_conversion_result(result: dict) -> None:
"""
Prints the energy-related conversion results to the console.
"""
line = (
f"energy: {result['energy_ev']:.6g} eV, energy: {result['energy_kev']:.6g} keV, "
f"wavelength: {result['wavelength']:.4g} Å, "
f"bragg angle: {result['bragg_angle_mrad']:.5g} mrad, {result['bragg_angle_deg']:.4g} deg"
)
print(line)
# Conversion Functions
def calculate_wavelength_from_angle(bragg_angle_mrad: float, temp=120) -> float:
"""
calculate_wavelength_from_angle(bragg_angle_mrad: float) -> float
Arguments:
bragg_angle_mrad: The Bragg angle in milliradians, used to compute the
sine value required for the wavelength calculation.
Returns:
The calculated wavelength as a float value.
"""
d = Constants.d_spacing[temp]
return 2 * d * np.sin(bragg_angle_mrad / 1000)
def calculate_energy_from_wavelength(wavelength: float) -> float:
"""
Calculates the energy of a photon based on its wavelength.
Args:
wavelength: The wavelength of the photon in angstrom.
Returns:
The energy of the photon in eV.
"""
return en_wav_factor() / wavelength
def calculate_wavelength_from_energy(energy_ev: float) -> float:
"""
Calculates the wavelength of a photon from its energy.
Arguments:
energy_ev: float
The energy of the photon in electronvolts (eV).
Returns:
float
The calculated wavelength of the photon in angstrom.
"""
return en_wav_factor() / energy_ev
def calculate_bragg_angle_from_wavelength(wavelength: float, temp=120) -> float:
"""
Calculate the Bragg angle in milliradians for a given wavelength.
Args:
wavelength: The wavelength in angstrom.
Returns:
The Bragg angle in milliradians as a float.
"""
d = Constants.d_spacing[temp]
angle_rad = np.arcsin(wavelength / (2 * d))
return angle_rad * 1000
def convert_input_angle_to_mrad(bragg_angle: float) -> float:
"""
Convert input angle into milliradians (mrad).
This function takes an angle as input and determines its likely unit,
converting it to milliradians (mrad) if necessary. If the input value
is less than 1, it is assumed to be in radians and is converted to
mrad. If the input value falls between predefined minimum and
maximum values for mrad, it is assumed to be in degrees and thus
converted to mrad using the degrees-to-radians conversion factor.
For input values that don't match these scenarios, it assumes
that the input is already in mrad and returns it unchanged.
Arguments:
bragg_angle (float): The input Bragg angle, which can be in
radians, degrees, or milliradians.
Returns:
float: The Bragg angle converted into milliradians (mrad).
"""
if bragg_angle < 1: # Likely the input angle is in radians
return bragg_angle * 1000
if 3 < bragg_angle < 25: # Likely input angle is in degrees
return np.deg2rad(bragg_angle) * 1000
return bragg_angle # Already in mrad
# Core Functions
def validate_energy(energy_ev):
"""
Validates the energy value to ensure it falls within the acceptable range. The function
converts the provided energy from keV to eV if the input value is less than 1/1000 of the
maximum energy value. It then checks whether the energy is within the defined bounds.
If the energy value is outside the acceptable range, the function raises a ValueError.
Args:
energy_ev (float): The energy value in eV or keV to be validated. If this value is
smaller than 1/1000 of the maximum allowed energy (in eV), it will be multiplied
by 1000 to convert it from keV to eV.
Returns:
float: The validated energy value in eV that falls within the acceptable range.
Raises:
ValueError: If the energy value is outside the defined range of
[MIN_ENERGY_EV, MAX_ENERGY_EV].
"""
if energy_ev < EnergyDefaults.max_energy_ev / 1000: # Assuming the input is in keV.
energy_ev *= 1000
if not EnergyDefaults.min_energy_ev <= energy_ev <= EnergyDefaults.max_energy_ev:
raise ValueError(
f"Energy of {energy_ev} eV is outside the valid range "
f"({EnergyDefaults.min_energy_ev} eV to {EnergyDefaults.max_energy_ev} eV)"
)
return energy_ev
def convert_from_bragg(
bragg_angle_mrad: float, temp=120, print_result: bool = False
) -> dict:
"""
Convert the Bragg angle to wavelength and energy, returning the result as a dictionary.
This function converts a given Bragg angle (in milliradians) into the corresponding
wavelength and energy values, and returns them in a dictionary format. The function
also supports optional printing of the calculated results.
Args:
bragg_angle_mrad (float): The Bragg angle in milliradians to be converted.
print_result (bool): Whether to print the conversion result. Defaults to False.
Returns:
dict: A dictionary containing the following keys:
- 'energy_ev': Energy in electronvolts.
- 'wavelength': Wavelength corresponding to the input angle.
- 'bragg_angle_mrad': Input Bragg angle in milliradians.
"""
bragg_angle_mrad = convert_input_angle_to_mrad(bragg_angle_mrad)
wavelength = float(calculate_wavelength_from_angle(bragg_angle_mrad, temp=temp))
energy_ev = float(calculate_energy_from_wavelength(wavelength))
result = create_conversion_result(energy_ev, wavelength, bragg_angle_mrad)
if print_result:
print_conversion_result(result)
return result
def convert_from_energy(energy_ev: float, temp=120, print_result: bool = False) -> dict:
"""
Convert energy in electron volts (eV) to wavelength and Bragg angle in milliradians
(mrad). This method validates the given energy, calculates corresponding properties,
and optionally prints the result.
Args:
energy_ev: Energy value in electron volts (float) to be converted.
print_result: Flag indicating whether to print the resulting
conversion details (bool). Defaults to False.
Returns:
A dictionary containing the following key-value pairs:
- "energy_ev" (float): Validated energy in eV.
- "wavelength" (float): Calculated wavelength in meters.
- "bragg_angle_mrad" (float): Calculated Bragg angle in mrad.
"""
energy_ev = validate_energy(energy_ev)
wavelength = calculate_wavelength_from_energy(energy_ev)
bragg_angle_mrad = float(
calculate_bragg_angle_from_wavelength(wavelength, temp=temp)
)
result = create_conversion_result(energy_ev, wavelength, bragg_angle_mrad)
if print_result:
print_conversion_result(result)
return result
def convert_from_wavelength(
wavelength: float,
temp: float = 120,
print_result: bool = False,
) -> dict:
"""
Convert a given wavelength value into corresponding energy, Bragg angle, and
generate a result dictionary.
The function processes a wavelength value, checks its validity against a
permitted range, calculates corresponding energy and Bragg angle, and
formats the results into a dictionary. Optionally, the function can print
the result.
Parameters:
wavelength: float
The input wavelength value in Angstroms to be converted. Should
fall within the permitted wavelength range.
print_result: bool
Optional flag indicating whether to print the conversion result.
Default is False.
Returns:
dict
A dictionary containing the energy (electron-volts), wavelength
(Angstroms), and Bragg angle (milliradians). If the wavelength is
outside of the permitted range, returns None.
"""
energy_ev = calculate_energy_from_wavelength(wavelength)
bragg_angle_mrad = float(
calculate_bragg_angle_from_wavelength(wavelength, temp=temp)
)
result = create_conversion_result(energy_ev, wavelength, bragg_angle_mrad)
if print_result:
print_conversion_result(result)
return result
def calc_perp_position(
energy_ev: float,
print_result: bool = False,
) -> float:
"""
Calculate the perpendicular motor position based on provided energy in electron-volts (eV).
This function computes the perpendicular motor position using the given energy value in
electron-volts. The calculation is based on the Bragg angle derived from the energy. An optional
parameter allows printing the result during execution.
Parameters:
energy_ev (float): The energy value in electron-volts used for the calculation.
print_result (bool): Flag to determine whether to print the computed perpendicular offset.
Default is False.
Returns:
float: The computed perpendicular position.
Raises:
None
"""
result = convert_from_energy(energy_ev, print_result=False)
bragg_angle_rad = result["bragg_angle_mrad"] / 1000
perp_offset = float(EnergyDefaults.beam_offset / (2 * np.cos(bragg_angle_rad))) - 3
if print_result:
print(f"Perp = {perp_offset: .4f}")
return perp_offset
def calc_scam_microns(pixels, zoom = 1000):
return pixels/(0.5208 * np.exp(0.002586 * zoom))
def calc_scam_microns(pixels, zoom=1000):
"""Convert pixels to microns for the sample camera"""
return float(pixels / (CamConversion.a * np.exp(CamConversion.b * zoom)))
def calc_bsccam_microns(pixels):
"""Convert pixels to microns for the BSC camera"""
return pixels*20
-495
View File
@@ -1,495 +0,0 @@
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Callable
from datetime import datetime
from bec_lib.device import Signal, Positioner
# -------------------------------------------------------------------
# Status Enum
# -------------------------------------------------------------------
class Status(Enum):
OK = 0
WARNING = 1
ERROR = 2
UNKNOWN = 3
@property
def color(self):
return {
Status.OK: "green",
Status.WARNING: "yellow",
Status.ERROR: "red",
Status.UNKNOWN: "blue",
}[self]
@property
def color_scilog(self):
return {
Status.OK: "green",
Status.WARNING: "",
Status.ERROR: "red",
Status.UNKNOWN: "",
}[self]
# -------------------------------------------------------------------
# Health Result Object
# -------------------------------------------------------------------
@dataclass
class HealthCheckResult:
name: str
description: str
status: Status
value: Any = None
message: str = ""
category: str = "general"
def __str__(self):
if self.status == Status.OK:
return f"[{self.status.name}] {self.description}"
return (
f"[{self.status.name}] "
f"{self.description}: {self.message}"
)
def formatted_message(self):
if self.status == Status.OK:
return f"[{self.status.name}] {self.name}"
return (
f"[{self.status.name}] "
f"{self.description}: {self.message}"
)
# -------------------------------------------------------------------
# Send to SciLog
# -------------------------------------------------------------------
def send_to_scilog(results):
counts = {
Status.OK: 0,
Status.WARNING: 0,
Status.ERROR: 0,
Status.UNKNOWN: 0,
}
for result in results:
counts[result.status] += 1
timestamp = datetime.now().strftime("%Y/%m/%d %H:%M")
msg = bec.messaging.scilog.new()
msg.add_text(
f"Beamline Health Summary {timestamp}",
bold = True
)
msg.add_text("\n")
for status, count in counts.items():
msg.add_text(
f"{status.name:<10}: {count}",
bold=True,
)
msg.add_text("\n")
for result in results:
msg.add_text(
result.formatted_message(),
# bold=result.status != Status.OK,
color = result.status.color_scilog
)
msg.add_text("\n")
msg.add_tags(["beamline health check"])
msg.send()
# -------------------------------------------------------------------
# Configuration
# -------------------------------------------------------------------
@dataclass
class BeamlineHealthConfig:
signal_rules: dict[str, Callable] = field(
default_factory=lambda: {
"cam": lambda x: x != 0,
"bpm": lambda x: x != 0,
}
)
motor_tolerances: dict[str, float] = field(
default_factory=lambda: {
# examples
# "mono_theta": 0.001,
# "detector_z": 0.1,
}
)
default_motor_tolerance: float = 0.02
# -------------------------------------------------------------------
# Device Collection
# -------------------------------------------------------------------
def get_devices():
return list(dev.items())
# -------------------------------------------------------------------
# Signal Checks
# -------------------------------------------------------------------
def check_signals(devices, config: BeamlineHealthConfig):
results = []
signal_devices = [
(name, obj)
for name, obj in devices
if isinstance(obj, Signal)
]
for name, obj in signal_devices:
try:
data = obj.read()
actual = data[name]["value"]
description = obj.description
except Exception as e:
results.append(
HealthCheckResult(
name=name,
description=name,
status=Status.UNKNOWN,
message=f"Failed to read signal: {e}",
category="signals",
)
)
continue
matched = False
for keyword, rule in config.signal_rules.items():
if keyword in name:
matched = True
try:
passed = rule(actual)
except Exception as e:
results.append(
HealthCheckResult(
name=name,
description=name,
status=Status.UNKNOWN,
value=actual,
message=f"Rule evaluation failed: {e}",
category="signals",
)
)
break
if passed:
results.append(
HealthCheckResult(
name=name,
description=description,
status=Status.OK,
value=actual,
category="signals",
)
)
else:
results.append(
HealthCheckResult(
name=name,
description=description,
status=Status.ERROR,
value=actual,
message=f"Signal value {actual} failed validation",
category="signals",
)
)
break
if not matched:
continue
return results
# -------------------------------------------------------------------
# Motor Checks
# -------------------------------------------------------------------
def check_motors(devices, config: BeamlineHealthConfig):
results = []
motor_devices = [
(name, obj)
for name, obj in devices
if isinstance(obj, Positioner)
]
for name, obj in motor_devices:
try:
data = obj.read()
description = obj.description
actual = data[name]["value"]
error_code = obj.motor_status.get()
move_state = obj.motor_is_moving.get()
except Exception as e:
results.append(
HealthCheckResult(
name=name,
description=name,
status=Status.UNKNOWN,
message=f"Failed to read motor: {e}",
category="motors",
)
)
continue
# -----------------------------------------------------------
# Error state
# -----------------------------------------------------------
if error_code != 0:
results.append(
HealthCheckResult(
name=name,
description=description,
status=Status.ERROR,
value=error_code,
message=f"motor error code: {error_code}",
category="motors",
)
)
continue
# -----------------------------------------------------------
# Moving state
# -----------------------------------------------------------
if move_state != 0:
results.append(
HealthCheckResult(
name=name,
description=description,
status=Status.WARNING,
value=move_state,
message="motor is currently moving",
category="motors",
)
)
continue
# -----------------------------------------------------------
# Setpoint comparison
# -----------------------------------------------------------
sp_key = f"{name}_user_setpoint"
if sp_key in data:
setpoint = data[sp_key]["value"]
diff = abs(actual - setpoint)
tolerance = config.motor_tolerances.get(
name,
config.default_motor_tolerance,
)
if diff > tolerance:
results.append(
HealthCheckResult(
name=name,
description=description,
status=Status.WARNING,
value=diff,
message=(
f"Setpoint {setpoint:.5g} differs "
f"from readback {actual:.5g} "
f"by {diff:.4g}"
),
category="motors",
)
)
else:
results.append(
HealthCheckResult(
name=name,
description=description,
status=Status.OK,
value=actual,
category="motors",
)
)
else:
results.append(
HealthCheckResult(
name=name,
description=description,
status=Status.UNKNOWN,
message="No setpoint available",
category="motors",
)
)
return results
# -------------------------------------------------------------------
# Main Check Entry Point
# -------------------------------------------------------------------
def check2(config: BeamlineHealthConfig | None = None):
if config is None:
config = BeamlineHealthConfig()
devices = get_devices()
results = []
results.extend(check_signals(devices, config))
results.extend(check_motors(devices, config))
# ---------------------------------------------------------------
# Sort by severity
# ---------------------------------------------------------------
results.sort(
key=lambda r: r.status.value,
)
return results
# -------------------------------------------------------------------
# Summary Printer
# -------------------------------------------------------------------
def summary_text(results):
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M")
n_ok = sum(r.status == Status.OK for r in results)
n_warn = sum(r.status == Status.WARNING for r in results)
n_err = sum(r.status == Status.ERROR for r in results)
n_unknown = sum(r.status == Status.UNKNOWN for r in results)
return (
f"==========================================\n"
f"Beamline Health Check at {timestamp}\n"
f"==========================================\n"
f"OK : {n_ok}\n"
f"WARNING : {n_warn}\n"
f"ERROR : {n_err}\n"
f"UNKNOWN : {n_unknown}\n"
"==========================================\n"
)
# return "\n".join(lines)
# -------------------------------------------------------------------
# Filter results
# -------------------------------------------------------------------
def filter_results(results, statuses = None):
if statuses is None:
return results
return [
r for r in results
if r.status in statuses
]
# -------------------------------------------------------------------
# CLI Entry Point
# -------------------------------------------------------------------
def run_check(show_all= False):
results = check2()
print(summary_text(results))
problem_results = filter_results(
results,
statuses={
Status.WARNING,
Status.ERROR,
Status.UNKNOWN,
}
)
send_to_scilog(results)
if not show_all:
results = filter_results(
results,
statuses={
Status.WARNING,
Status.ERROR,
Status.UNKNOWN
}
)
for result in results:
print(result)
-252
View File
@@ -1,252 +0,0 @@
"""Get data from an h5 file or BEC history and perform fitting."""
import numpy as np
from lmfit.models import (
GaussianModel,
LorentzianModel,
VoigtModel,
ConstantModel,
LinearModel,
)
from scipy.ndimage import gaussian_filter1d
import h5py
import matplotlib.pyplot as plt
def create_fit_parameters(
deriv: bool = False,
model: str = "Voigt",
baseline: str = "Linear",
smoothing: None = None,
):
"""Store the fit parameters in a dictionary."""
# map input model to lmfit model name
model_mappings = {
"Gaussian": GaussianModel,
"Lorentzian": LorentzianModel,
"Voigt": VoigtModel,
"Constant": ConstantModel,
"Linear": LinearModel,
}
return {
"deriv": deriv,
"model": model_mappings[model],
"baseline": model_mappings[baseline],
"smoothing": smoothing,
}
def get_data_from_h5(signal_name: str = "lu_bpmsum"):
"""Get data from an h5 file."""
with h5py.File("scan_676.h5", "r") as f:
entry = f["entry"]["collection"]
y_data = entry["devices"][signal_name][signal_name]["value"][:]
motor_data = entry["metadata"]["bec"]
motor_name = motor_data["scan_motors"][0].decode()
scan_number = motor_data["scan_number"][()]
x_data = entry["devices"][motor_name][motor_name]["value"][:]
return {
"x_data": x_data,
"y_data": y_data,
"signal_name": signal_name,
"motor_name": motor_name,
"scan_number": str(scan_number),
}
def get_data_from_history(
history_index: int,
signal_name: str = "lu_bpmsum",
):
"""Read data from the BEC history and return the X and Y data as arrays."""
scan = bec.history[history_index]
md = scan.metadata["bec"]
motor_name = md["scan_motors"][0].decode()
scan_number = md["scan_number"]
x_data = scan.devices[motor_name][motor_name].read()["value"]
y_data = scan.devices[signal_name][signal_name].read()["value"]
return {
"signal_name": signal_name,
"x_data": x_data,
"y_data": y_data,
"motor_name": motor_name,
"scan_number": scan_number,
}
def process_data(data, fit_params):
"""
Process the signal data for fitting based on derivative or smoothing.
"""
smoothing, deriv = fit_params["smoothing"], fit_params["deriv"]
signal_name = data["signal_name"]
y_data = data["y_data"]
if deriv:
if smoothing:
y_smooth = gaussian_filter1d(y_data, smoothing)
fitting_data = np.gradient(y_smooth)
signal_name = f"Derivative of smoothed {signal_name}"
else:
fitting_data = np.gradient(y_data)
signal_name = f"Derivative of {signal_name}"
elif smoothing and smoothing > 0.01:
fitting_data = gaussian_filter1d(y_data, smoothing)
signal_name = f"Smoothed {signal_name}"
else:
fitting_data = y_data
updated_data = {
"y_to_fit": fitting_data,
"signal_name": signal_name,
}
data.update(updated_data)
return data
def fit(data, fit_params):
"""Fit a signal to a model and return the fitting results."""
# Create the model
peak_model = fit_params["model"](prefix="peak_")
baseline_model = fit_params["baseline"](prefix="base_")
full_model = peak_model + baseline_model
# Prepare data
processed_data = process_data(data, fit_params)
params = full_model.make_params()
y_min = np.min(processed_data["y_to_fit"])
# Configure baseline parameters
if fit_params["baseline"] == ConstantModel:
params["base_c"].set(value=y_min)
elif fit_params["baseline"] == LinearModel:
params["base_intercept"].set(value=y_min)
params["base_slope"].set(value=0)
# Add peak-specific parameters
params.update(
peak_model.guess(processed_data["y_to_fit"], x=processed_data["x_data"])
)
# Perform the fitting
lmfit_result = full_model.fit(
processed_data["y_to_fit"], params, x=processed_data["x_data"]
)
# Find the X that gives the max Y
max_index = np.argmax(processed_data["y_to_fit"])
x_max = processed_data["x_data"][max_index]
# Generate data for a smoothed fit curve
fit_xdata = np.linspace(np.min(data["x_data"]), np.max(data["x_data"]), 500)
fit_ydata = lmfit_result.eval(x=fit_xdata, params=lmfit_result.params)
# Collect results
return {
"model": fit_params["model"].__name__,
"fwhm": lmfit_result.params["peak_fwhm"].value,
"centre": lmfit_result.best_values["peak_center"],
"height": lmfit_result.params["peak_height"].value,
"chi_sq": lmfit_result.chisqr,
"lmfit_result": lmfit_result,
"x_max": x_max,
"fit_xdata": fit_xdata,
"fit_ydata": fit_ydata,
}
def plot_fitted_data(data, fit_result):
"""Plot the original data and the fitted model."""
plt.plot(data["x_data"], data["y_to_fit"], label="Data")
plt.plot(fit_result['fit_xdata'], fit_result['fit_ydata'], label="Fit")
plt.xlabel(data["motor_name"])
plt.ylabel(data["signal_name"])
plt.title(f"Scan {data['scan_number']}, fitted with {fit_result['model']}")
plt.grid(True)
plt.legend()
plt.show()
def select_bec_window(dock_area_name="Fitting"):
"""Check to see if the fitting results dock is already open and re-create it if not"""
open_docks = bec.gui.windows
if open_docks.get(dock_area_name) is None:
dock_area = bec.gui.new(dock_area_name)
# wf = dock_area.new("Plot").new(bec.gui.available_widgets.Waveform)
wf = dock_area.new(widget='Waveform', object_name='Plot')
text_box = dock_area.new(widget='TextBox', object_name="Results", where="bottom")
else:
wf = bec.gui.Fitting.Plot
text_box = bec.gui.Fitting.Results
return wf, text_box
def plot_live_data_bec(
motor_name,
signal_name,
window_name="Fitting"
):
"""
Plotting live data for motor and signal using BEC.
This function plots live data from a specified motor and signal.
It clears the current plot window, sets its title, labels the axes
with the provided motor and signal names, and initializes live plotting
on the given signal against the motor.
Args:
motor_name (str): The name of the motor to be used as the x-axis.
signal_name (str): The name of the signal to be used as the y-axis.
Returns:
None
"""
wf, text_box = select_bec_window(window_name)
text_box.set_plain_text("Plotting live data")
wf.clear_all()
wf.title = "Scan: Live scan"
wf.x_label = motor_name
wf.y_label = signal_name
wf.plot(device_x=motor_name, device_y=signal_name)
def plot_fitted_data_bec(
data,
fit_result,
):
"""
Plot fitted data and display fitting parameters in the specified window.
This function selects a BEC window and plots the original data along with the
fitted function. Additionally, it displays the fitting results in a text
box within the same window for better visualization of the fit results.
Parameters:
data : dict
Dictionary containing the original dataset, where 'x_data' and 'y_to_fit'
hold the independent variable and the dependent variable, respectively,
'scan_number' represents the scan number, 'motor_name' and 'signal_name'
provide axis labels.
fit_result : dict
Dictionary containing the results of the fit, including parameters such
as 'centre', 'fwhm', 'height', and the fitted model stored under
'lmfit_result', with its 'best_fit' attribute representing the fitted data.
"""
wf, text_box = select_bec_window()
fit_text = (
f"Fit parameters: Centre = {fit_result['centre']:.4f}, "
f"FWHM = {fit_result['fwhm']:.3f}, "
f"Height = {fit_result['height']:.4f}\n"
f"Model = {fit_result['model']}\n"
f"Chi sq = {fit_result['chi_sq']:.3g}"
)
text_box.set_plain_text(fit_text)
wf.clear_all()
wf.title = f"Scan: {data['scan_number']}"
wf.x_label = data["motor_name"]
wf.y_label = data["signal_name"]
wf.plot(x=data["x_data"], y=data["y_to_fit"], label="Data")
wf.plot(x=fit_result["fit_xdata"], y=fit_result["fit_ydata"], label="Fit")
# wf.Fit.set(symbol_size = 0)
wf.get_curve('Fit').set(symbol_size=0)
-321
View File
@@ -1,321 +0,0 @@
"""Use the methods in mx_basics to perform:
1) a go_to_peak scan, that scans a motor, finds the peak position and moves to peak
2) fits data from a bec history file
"""
from dataclasses import dataclass
import numpy as np
# from pxiii_parameters import FitDefaults, BPMScans, MirrorConfig
# from mx_basics import (
# create_fit_parameters,
# get_data_from_history,
# fit,
# plot_fitted_data_bec,
# plot_live_data_bec,
# )
# Method functions
def calculate_step_size(start: float, stop: float, steps: int) -> float:
"""
Provides the function to calculate the step size for dividing a specified range
into a given number of steps.
Args:
start: The starting value of the range.
stop: The stopping value of the range.
steps: The number of steps to divide the range into. Must be at least 1.
Raises:
ValueError: If the steps value is less than 1.
Returns:
The calculated step size as a float, rounded to three decimal places.
"""
if steps < 1:
raise ValueError("Number of steps must be at least 1.")
return round((stop - start) / steps, 3)
def move_to_position(motor_device, motor_name: str, position: float, data: dict):
"""
Function to move a specified motor device to a given position.
The function verifies if the requested position is within the scan range of the
motor device provided. If the position is outside the range, the motor is
moved to the center of its scan range, an error message is raised, and the
operation is halted. If the position is valid, the motor is moved to the
specified position.
Parameters:
motor_device: The motor device to be moved.
motor_name: str
The name of the motor as a string
position: float
The desired position to move the motor to. Position should be within
the scan range of the motor determined by the provided data.
data: dict
A dictionary containing "x_data", which is used to determine the
scan range of the motor.
Raises:
ValueError: Raised if the specified position is outside the valid scan
range determined by "x_data" in the data dictionary. The motor will
return to the center of its scan range in this case.
"""
motor_min = np.min(data["x_data"])
motor_max = np.max(data["x_data"])
motor_centre = (motor_max + motor_min) / 2
if not motor_min <= position <= motor_max:
scans.umv(motor_device, motor_centre, relative=False)
msg = (
f"Position {position: .2f} is outside the scan range of "
f"{motor_min: .2f} to {motor_max: .2f}. "
f"Returning to centre of scan range {motor_centre: .3f}."
)
raise ValueError(msg)
motor_position = round(position, 4)
scans.umv(motor_device, motor_position, relative=False)
print(f"\n Moving {motor_name} to position {motor_position: .3f}")
# @dataclass(frozen=True)
# class FitDefaults:
# """Default values for fitting routines"""
# # Constants for default models, baselines, and parameters
# MODEL = "Voigt"
# BASELINE = "Linear"
# SETTLE_TIME = 0.1
# RELATIVE_MODE = True
def go_to_peak(
motor_device,
signal_device,
start: float,
stop: float,
steps: int,
relative: bool = FitDefaults.RELATIVE_MODE,
plot: bool = True,
settle: float = FitDefaults.SETTLE_TIME,
confirm: bool = True,
gomax: bool = False,
):
"""
Go to the peak of a signal by scanning a motor within a specified range and
identifying the optimal position based on signal peak data.
Parameters:
motor_device: The motor device to be scanned.
signal_device: The signal device to monitor during the scan.
start (float): The starting position of the scan. Ignored if `relative` is True.
stop (float): The ending position of the scan. Ignored if `relative` is True.
steps (int): The number of steps to divide the scan range into.
relative (bool, optional): If True, interpret `start` and `stop` as relative to
the current motor position. Defaults to RELATIVE_MODE constant.
plot (bool, optional): If True, plot the scan data and the fitted results.
Defaults to True.
settle (float, optional): The time in seconds to wait after each step for the
signal to stabilize. Defaults to DEFAULT_SETTLE_TIME constant.
confirm (bool, optional): If True, ask for user confirmation before starting
the scan. Defaults to True.
Raises:
Exception: Raises exceptions potentially raised by dependent functions or
operations such as plotting, fitting, or motor movement.
Returns:
None
"""
motor_name = motor_device.name
signal_name = signal_device.name
# wf.plot(x_name=motor_name, y_name=signal_name)
if plot:
plot_live_data_bec(motor_name, signal_name)
# Validate and calculate step size
step_size = calculate_step_size(start, stop, steps)
# Confirm the scan range
# current_motor_position = motor_device.user_readback.get()
current_motor_position = motor_device.read()[motor_name]["value"]
if confirm:
if relative:
scan_start = current_motor_position + start
scan_end = current_motor_position + stop
print(
f"\nScanning from {scan_start: .6g} to {scan_end: .6g} in "
f"{steps} steps of size {step_size}"
)
print(f"Relative mode = {relative}")
else:
print(
f"\nScanning from {start: .5g} to {stop: .5g} in {steps} steps of size {step_size}"
)
print(f"Relative mode = {relative}")
input("Press Enter to continue...")
# Perform the scan
scan_result = scans.line_scan(
motor_device, start, stop, steps=steps, relative=relative, settling_time=settle
)
motor_data = scan_result.scan.live_data[motor_name][motor_name].val
signal_data = scan_result.scan.live_data[signal_name][signal_name].val
scan_number = "Current"
data = {
"x_data": np.array(motor_data),
"y_data": np.array(signal_data),
"motor_name": motor_name,
"signal_name": signal_name,
"motor_device": motor_device,
"scan_number": scan_number,
}
# Define and fit model to scan data
fit_params = create_fit_parameters(False, FitDefaults.MODEL, FitDefaults.BASELINE)
fit_result = fit(data, fit_params)
# Plot the fitted data if plot = True
if plot:
plot_fitted_data_bec(data, fit_result)
# If gomax is set then move to the maximum value, rather than the fit centre
if gomax:
value = fit_result["x_max"]
print(f"Max position is at {value}")
move_to_position(data["motor_device"], data["motor_name"], fit_result["x_max"], data)
else:
# Safely move the motor to the peak position
move_to_position(data["motor_device"], data["motor_name"], fit_result["centre"], data)
def fit_history(
history_index: int,
signal_name: str,
deriv: bool = False,
model: str = FitDefaults.MODEL,
move_to_peak: bool = False,
):
"""
Retrieve and analyze historical data by fitting a model, optionally moving to
a peak position.
Parameters:
history_index (int): Index of the historical data set to retrieve.
signal_name (str): Name of the signal to fit.
deriv (bool, optional): Whether to include the derivative in the fitting
procedure. Defaults to False.
model (str, optional): Name of the model to use for fitting. Defaults to
DEFAULT_MODEL.
move_to_peak (bool, optional): Whether to move the motor to the peak position
after fitting. Defaults to False.
Raises:
KeyError: If required keys are not found in the retrieved data dictionary.
ValueError: If the fitting process fails or produces invalid results.
Returns:
None
"""
# Retrieve historical data
data = get_data_from_history(history_index, signal_name)
# Define fitting parameters
fit_params = create_fit_parameters(deriv, model, FitDefaults.BASELINE)
# Perform fit and plot the data
fit_result = fit(data, fit_params)
plot_fitted_data_bec(data, fit_result)
# Optionally move the motor to the peak position
if move_to_peak:
move_to_position(data["motor_device"], data["motor_name"], fit_result["centre"], data)
def scan_bpm(bpmname):
"""
Runs a grid scan of a BPM in x and y, and plots each channel
as a heatmap.
Parameters:
bpmname: the name of the bpm to be scanned e.g. "fe"
"""
# Open a dock area and set up the heatmaps
dock_area = bec.gui.new("XBPM_Scan")
wf5 = dock_area.new("Sum").new(bec.gui.available_widgets.Heatmap)
wf1 = dock_area.new("Ch1", relative_to="Sum", position="bottom").new(
bec.gui.available_widgets.Heatmap
)
wf3 = dock_area.new("Ch3", relative_to="Ch1", position="right").new(
bec.gui.available_widgets.Heatmap
)
wf4 = dock_area.new("Ch4", relative_to="Ch3", position="bottom").new(
bec.gui.available_widgets.Heatmap
)
wf2 = dock_area.new("Ch2", relative_to="Ch1", position="bottom").new(
bec.gui.available_widgets.Heatmap
)
wfscan = dock_area.new("ScanControl").new(bec.gui.available_widgets.ScanControl)
cfg = getattr(BPMScans, bpmname)
wf1.x_label = cfg["x_name"]
wf1.y_label = cfg["y_name"]
wf1.plot(x_name=cfg["x_name"], y_name=cfg["y_name"], z_name=cfg["z1_name"], color_map="plasma")
wf2.x_label = cfg["x_name"]
wf2.y_label = cfg["y_name"]
wf2.plot(x_name=cfg["x_name"], y_name=cfg["y_name"], z_name=cfg["z2_name"], color_map="plasma")
wf3.x_label = cfg["x_name"]
wf3.y_label = cfg["y_name"]
wf3.plot(x_name=cfg["x_name"], y_name=cfg["y_name"], z_name=cfg["z3_name"], color_map="plasma")
wf4.x_label = cfg["x_name"]
wf4.y_label = cfg["y_name"]
wf4.plot(x_name=cfg["x_name"], y_name=cfg["y_name"], z_name=cfg["z4_name"], color_map="plasma")
wf5.x_label = cfg["x_name"]
wf5.y_label = cfg["y_name"]
wf5.plot(x_name=cfg["x_name"], y_name=cfg["y_name"], z_name=cfg["z5_name"], color_map="plasma")
# Run the scan
x_mot = cfg["x_device"]
y_mot = cfg["y_device"]
# scans.grid_scan(x_mot, -0.5, 0.5, 20, y_mot, -0.5, 0.5, 20,
# exp_time=0.5, relative=False, snaked=True)
def optimise_kb(mirror):
"""
Runs a grid scan of a the upstream and downstream benders,
and plots a heatmap of the sample camera x or y sigma.
Parameters:
mirror: either "hfm" or :vfm"
"""
# Open a dock area and set up the heatmaps
dock_area = bec.gui.new(mirror)
wf1 = dock_area.new("Heatmap").new(bec.gui.available_widgets.Heatmap)
wfscan = dock_area.new("ScanControl").new(bec.gui.available_widgets.ScanControl)
cfg = getattr(MirrorConfig, mirror)
wf1.x_label = cfg["bu_name"]
wf1.y_label = cfg["bd_name"]
wf1.plot(x_name=cfg["bu_name"], y_name=cfg["bd_name"], z_name=cfg["z_name"], color_map="plasma")
# Run the scan
x_mot = cfg["x_device"]
y_mot = cfg["y_device"]
# scans.grid_scan(x_mot, -0.02, 0.02, 11, y_mot, -0.02, 0.02, 11,
# exp_time=0.5, relative=True, snaked=True)
-251
View File
@@ -1,251 +0,0 @@
"""Script to change energy at PXIII by setting DCCM motors and mirror stripe
Moving DCCM motors - implemented for dccm_theta1 and dccm_theta2
Mirrors - change of mirror stripe is not yet implemented
Plotting optional
"""
import pandas as pd
import numpy as np
# from mx_methods import go_to_peak
# from pxiii_parameters import EnergyDefaults
# from calculator import (
# validate_energy,
# convert_from_bragg,
# convert_from_energy,
# )
def get_current_energy():
"""
Returns the energy in eV from the current bragg angle.
"""
# current_bragg_angle = dev.dccm_theta1.user_readback.get()
current_bragg_angle = -EnergyDefaults.energy.user_readback.get()
current_energy = convert_from_bragg(current_bragg_angle, print_result=False)[
"energy_ev"
]
return current_energy
# Functions below are common to all beamlines
def calculate_energy_difference(current_energy, target_energy):
"""
Calculate the absolute difference in energy between the current energy level
and the target energy level.
"""
return abs(target_energy - current_energy)
def interpolate_column(energy_ev, x_values, y_values):
"""
Perform interpolation for a specific column of data.
This function uses numpy's interpolation method to perform linear
interpolation. It calculates the interpolated y-values corresponding
to the given energy in electron-volts (energy_ev), based on specified
x-values and y-values.
Parameters:
energy_ev (array-like): Array of energy values in electron-volts
at which interpolation is needed.
x_values (array-like): Array of x-values corresponding to known
data points.
y_values (array-like): Array of y-values corresponding to known
data points.
Returns:
numpy.ndarray: Interpolated y-values computed for the energy_ev
input.
"""
return np.interp(energy_ev, x_values, y_values)
def get_value_from_lut(energy_ev):
"""
Retrieve interpolated values from a Lookup Table (LUT) based on the provided energy
in electron volts (eV).
This function reads a CSV file containing energy lookup data and processes the LUT
to return interpolated values for specified relevant columns. The interpolation is
performed for the provided energy value using the LUT's energy and data values.
Args:
energy_ev (float): The energy value in electron volts for which the interpolated data is
required.
Returns:
dict: A dictionary where the keys are the relevant column names from the LUT, and the values
are the interpolated values as floats.
"""
energy_lookup_data = pd.read_csv(EnergyDefaults.LUT_table)
column_names = energy_lookup_data.columns.tolist()
lut_values = energy_lookup_data.values.astype(float).T
# Filter relevant columns for interpolation
relevant_columns = {"dccm_pitch"}
int_values = {}
for i, col_name in enumerate(column_names):
if col_name in relevant_columns:
int_values[col_name] = float(
interpolate_column(energy_ev, lut_values[0], lut_values[i])
)
return int_values
def get_mirror_stripe(energy_ev):
"""
Determines the mirror stripe material based on the energy level provided.
This function evaluates the given energy level in electron volts (eV) and
identifies the material type that corresponds to the specified thresholds
for silicon, rhodium, and, if applicable, platinum.
Args:
energy_ev (float): Energy level in electron volts, used to determine
the corresponding material type.
Returns:
str: A string indicating the material type corresponding to the provided
energy level. Possible values are "silicon", "rhodium", or "platinum".
"""
if energy_ev <= EnergyDefaults.stripe_thresholds["silicon"]:
return "silicon"
if (
EnergyDefaults.stripe_thresholds["silicon"]
< energy_ev
<= EnergyDefaults.stripe_thresholds["rhodium"]
):
return "rhodium"
return "platinum"
def set_mirror_stripe(energy_ev):
"""
Selects and sets the appropriate mirror stripe based on the given energy value
in electron volts (eV). Prints the selected mirror stripe.
Args:
energy_ev (float): The energy value in electron volts used to determine
the appropriate mirror stripe.
Returns:
None
"""
selected_stripe = get_mirror_stripe(energy_ev)
print(f"Selected mirror stripe: {selected_stripe}")
def mono_pitch_scan(plot=True):
"""Scan the monochromator pitch and move to the peak."""
if plot:
print("Scanning monochromator pitch and moving to peak, with plotting.")
go_to_peak(
EnergyDefaults.mono_pitch,
EnergyDefaults.signals["sig1"],
-EnergyDefaults.pitch_scan["halfwidth"],
EnergyDefaults.pitch_scan["halfwidth"],
steps=EnergyDefaults.pitch_scan["steps"],
relative=True,
settle=0.01,
plot=True,
confirm=False,
)
else:
print("Scanning monochromator pitch and moving to peak, without plotting.")
go_to_peak(
EnergyDefaults.mono_pitch,
EnergyDefaults.signals["sig1"],
-EnergyDefaults.pitch_scan["halfwidth"],
EnergyDefaults.pitch_scan["halfwidth"],
steps=EnergyDefaults.pitch_scan["steps"],
relative=True,
settle=0.01,
plot=False,
confirm=False,
)
def get_dccm_motors_positions(energy_ev):
"""
Retrieve the positions of DCCM motors based on given energy value.
The function returns a dictionary containing all
calculated motor positions.
Arguments:
energy_ev (float): The energy value in electron volts for which the
DCCM motor positions are to be calculated.
Returns:
dict: A dictionary containing the calculated DCCM motor positions
including values retrieved from the lookup table, if applicable.
"""
# dccm_motor_values = get_value_from_lut(energy_ev)
th1_angle = -convert_from_energy(energy_ev, print_result=False)["bragg_angle_deg"]
th2_angle = convert_from_energy(energy_ev, temp=298, print_result=False)["bragg_angle_deg"]
# dccm_motor_values.update({"theta1_angle": th1_angle, "theta2_angle": th2_angle})
dccm_motor_values = {"theta1_angle": th1_angle, "theta2_angle": th2_angle}
return dccm_motor_values
def move_dccm_motors(energy_ev):
"""
Move the DCCM theta1 and theta2 motors to the required positions
for the given energy in eV.
"""
dccm_pos = get_dccm_motors_positions(energy_ev)
print(
f"Moving DCCM theta1: {dccm_pos['theta1_angle']: .5g} deg, theta2: {dccm_pos['theta2_angle']: .5g} deg, "
# f"DCM pitch: {dcm_pos['dcm_pitch']: .5g} mrad, "
)
umv(EnergyDefaults.energy, dccm_pos["theta1_angle"],
EnergyDefaults.mono_pitch, dccm_pos["theta2_angle"])
def bl_energy(energy_ev, plot=True):
"""
Adjusts the beamline's energy to the specified energy in electron volts (eV).
The function validates the target energy, checks the current energy, and makes
adjustments only if the energy difference is significant enough. It performs
necessary operations including changing DCCM motors, updating
the mirror stripe, and scanning to find the optimal DCCM pitch of 2nd crystal..
Args:
energy_ev: Target energy in electron volts to which the beamline should be adjusted.
plot: Boolean flag indicating whether to plot the DCCM pitch scan for finding the peak.
Returns:
None
"""
energy_ev = validate_energy(energy_ev) # Ensure energy is valid.
# Check current energy to avoid unnecessary adjustments.
current_energy = get_current_energy()
energy_diff = calculate_energy_difference(current_energy, energy_ev)
if energy_diff <= EnergyDefaults.min_energy_change:
print(
f"Energy change of {energy_diff:.2f} eV is too small, not changing energy."
)
return
# Step 1: Move and set the DCCM motors.
move_dccm_motors(energy_ev)
# Step 2: Update the mirror stripe.
set_mirror_stripe(energy_ev)
# Step 3: Perform DCCM pitch scan and move to peak.
if plot:
mono_pitch_scan(plot=True)
else:
mono_pitch_scan(plot=False)
-80
View File
@@ -1,80 +0,0 @@
"""File to store beamline parameters and defaults"""
from dataclasses import dataclass
import numpy as np
@dataclass(frozen=True)
class FitDefaults:
"""Default values for fitting routines"""
# Constants for default models, baselines, and parameters
MODEL = "Voigt"
BASELINE = "Linear"
SETTLE_TIME = 0.1
RELATIVE_MODE = True
@dataclass(frozen=True)
class EnergyDefaults:
"""Parameters for PXIII energy changes"""
min_energy_change = 1
min_energy_ev = 4500
max_energy_ev = 15000
signals = {
"sig1": dev.dccm_di_top,
"sig2": dev.dccm_bpmsum,
"sig3": dev.ss_bpmsum,
# "sig4": dev.xbox_xbpm,
}
energy = dev.dccm_theta1
mono_pitch = dev.dccm_theta2
# LUT_table = "luts/energy_lut.csv"
stripe_thresholds = {"silicon": 9000, "rhodium": 40000}
pitch_scan = {"halfwidth": 0.15, "steps": 30}
@dataclass(frozen=True)
class CamConversion:
"""Convert pixels to microns for sam cam"""
a = 0.5208
b = 0.002586
@dataclass(frozen=True)
class BPMScans:
"""Define the names of the motors and bpm channels"""
ss = {
"x_name": dev.ss_bpm_x.name,
"y_name": dev.ss_bpm_y.name,
"z1_name": dev.ss_bpm1.name,
"z2_name": dev.ss_bpm2.name,
"z3_name": dev.ss_bpm3.name,
"z4_name": dev.ss_bpm3.name,
"z5_name": dev.ss_bpmsum.name,
"x_device": dev.ss_bpm_x,
"y_device": dev.ss_bpm_y,
}
# @dataclass(frozen=True)
# class MirrorConfig:
# """Define the names of the mirror channels"""
# hfm = {
# "bu_name": dev.hfm_bu.name,
# "bd_name": dev.hfm_bd.name,
# "z_name": dev.samcam_xsig.name,
# "x_device": dev.hfm_bu,
# "y_device": dev.hfm_bd,
# }
# vfm = {
# "bu_name": dev.vfm_bu.name,
# "bd_name": dev.vfm_bd.name,
# "z_name": dev.samcam_ysig.name,
# "x_device": dev.vfm_bu,
# "y_device": dev.vfm_bd,
# }
@@ -1,12 +0,0 @@
# 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
@@ -1,34 +0,0 @@
# # 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.
@@ -1,12 +0,0 @@
"""
Scan components for pxiii_bec.
The scan components module allows you to define custom components that can be used in your scans.
These components can be used to encapsulate reusable logic, interact with devices, or perform specific actions during the scan lifecycle.
"""
from bec_server.scan_server.scans.scan_components import ScanComponents
class PxiiiBecScanComponents(ScanComponents):
"""Scan components for pxiii_bec."""
@@ -1,33 +0,0 @@
"""
Scan modifier plugin for pxiii_bec.
The scan modifier allows you to modify the scan lifecycle and run custom actions before or after the scan hook or replace the scan hook entirely.
Note that the scan_modifier module must be registered as a plugin in the pyproject.toml file for it to be recognized by the BEC framework and that
there can only be one scan_modifier plugin registered at a time. If you need to run multiple scan modifiers, you can create a single scan
modifier plugin that runs multiple actions in sequence with conditional logic to determine which actions to run based on the scan context.
"""
from bec_server.scan_server.scans.scan_modifier import ScanModifier, scan_hook_impl
class PxiiiBecScanModifier(ScanModifier):
"""
Scan modifier for pxiii_bec.
By inheriting from the ScanModifier base class, you get access to currently running scan (self.scan), the devices (self.dev), the scan info (self.scan_info),
the scan components (self.components) and the scan actions (self.actions).
"""
def __init__(self, **kwargs):
"""Initialize the scan modifier."""
super().__init__(**kwargs)
# Example of running code before the scan stage for a specific scan
# @scan_hook_impl("stage", "before")
# def before_stage(self):
# """Run before the stage hook."""
# self.actions.send_client_info("Custom stage logic executed by ScanModifier.")
# if self.scan_info.scan_name == "example_scan":
# self.dev.samx.set(20)
-50
View File
@@ -1,50 +0,0 @@
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
signal_name (str | None): the signal to plot, if different from the device name.
smoothing_sigma (float): the sigma for the Gaussian smoothing filter
"""
# 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(bec.gui.available_widgets.Waveform)
wf.title = f"Scan {md['scan_number']}: {md['scan_name']} of {motor_name}"
text = dock_area.new(bec.gui.available_widgets.TextBox, where="right")
# 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
+15 -31
View File
@@ -2,20 +2,6 @@
# import bec
# import bec_lib.devicemanager.DeviceContainer as dev
import time
def _device_name(device):
return device.name if hasattr(device, "name") else str(device)
def _get_or_create_scan_window(name="CurrentScan"):
window = bec.gui.windows.get(name)
if window is None:
return bec.gui.new(name)
window.delete_all()
return window
def rock(steps, exp_time, scan_start=None, scan_end=None, datasource=None, visual=True, **kwargs):
"""Demo step scan with plotting
@@ -38,18 +24,22 @@ def rock(steps, exp_time, scan_start=None, scan_end=None, datasource=None, visua
scan_end = 0.05 / dev.dccm_energy.user_readback.get()
if visual:
# Get or create scan specific window.
window = _get_or_create_scan_window("CurrentScan")
motor_name = _device_name(motor)
datasource_name = _device_name(datasource)
# Get or create scan specific window
window = None
for _, val in bec.gui.windows.items():
if val.title == "CurrentScan":
window = val.widget
window.clear_all()
if window is None:
window = bec.gui.new("CurrentScan")
# Draw a waveform plot in the window.
plt1 = window.new(
bec.gui.available_widgets.Waveform, object_name=f"ScanDisplay_{motor_name}"
)
plt1.plot(device_x=motor_name, device_y=datasource_name, dap="LinearModel")
plt1.x_label = motor_name
plt1.y_label = datasource_name
# Draw a simploe plot in the window
dock = window.add_dock(f"ScanDisplay {motor}")
plt1 = dock.add_widget("BECWaveformWidget")
plt1.plot(x_name=motor, y_name=datasource)
plt1.set_x_label(motor)
plt1.set_y_label(datasource)
plt1.add_dap(motor, datasource, dap="LinearModel")
window.show()
print("Handing over to 'scans.line_scan'")
@@ -77,9 +67,3 @@ def rock(steps, exp_time, scan_start=None, scan_end=None, datasource=None, visua
# TODO: Move to fitted maximum
return s, firt_par
def monitor(device, steps, t=1):
for _ in range(steps):
print(device.read())
time.sleep(t)
-29
View File
@@ -1,29 +0,0 @@
def scan_theta2(scan_start, scan_end, stepno, exp):
# Save the motor starting position
start_value = dev.dccm_theta2.read()['dccm_theta2']['value']
print(f"Motor position is {start_value}")
# Run the scan
s = scans.line_scan(dev.dccm_theta2, scan_start, scan_end, steps=stepno, exp_time=exp, relative=True)
# data = s.devices[dccm_xbpm][dccm_xbpm].read()["value"]
# Move motor back to starting position and print XBPM reading
umv(dev.dccm_theta2, start_value)
xbpm_reading = dev.dccm_xbpm.read()['dccm_xbpm']['value']
print(f"Moving dccm_theta2 back to start position of where XBPM Reading is {xbpm_reading}")
end_value = dev.dccm_theta2.read()['dccm_theta2']['value']
print(f"Motor was at {start_value} before the scan, now at {end_value}")
# # Create a plot to display the results
dock_area = bec.gui.new()
wf = dock_area.new(bec.gui.available_widgets.Waveform)
wf.title = f"Scan of DCCM_theta2"
wf.plot(device_x="dccm_theta2", device_y="dccm_xbpm", label="dccm_xbpm-dccm_xbpm")
dap_xbpm = wf.add_dap_curve(device_label="dccm_xbpm-dccm_xbpm", dap_name="GaussianModel")
print(dap_xbpm.dap_params)
+29 -26
View File
@@ -1,4 +1,5 @@
# pylint: disable=undefined-variable
# import bec
# import bec_lib.devicemanager.DeviceContainer as dev
def bl_check_beam():
@@ -6,20 +7,16 @@ def bl_check_beam():
return True
def _device_name(device):
return device.name if hasattr(device, "name") else str(device)
def _get_or_create_scan_window(name="CurrentScan"):
window = bec.gui.windows.get(name)
if window is None:
return bec.gui.new(name)
window.delete_all()
return window
def ascan(
motor, scan_start, scan_end, steps, exp_time, plot=None, visual=True, relative=False, **kwargs
motor,
scan_start,
scan_end,
steps,
exp_time,
plot=None,
visual=True,
relative=False,
**kwargs,
):
"""Demo step scan with plotting
@@ -35,18 +32,22 @@ def ascan(
raise RuntimeError("Beamline is not in ready state")
if visual:
# Get or create scan specific window.
window = _get_or_create_scan_window("CurrentScan")
motor_name = _device_name(motor)
plot_name = _device_name(plot)
# Get or create scan specific window
window = None
for _, val in bec.gui.windows.items():
if val.title == "CurrentScan":
window = val.widget
window.clear_all()
if window is None:
window = bec.gui.new("CurrentScan")
# Draw a waveform plot in the window.
plt1 = window.new(
bec.gui.available_widgets.Waveform, object_name=f"ScanDisplay_{motor_name}"
)
plt1.plot(device_x=motor_name, device_y=plot_name, dap="LinearModel")
plt1.x_label = motor_name
plt1.y_label = plot_name
# Draw a simploe plot in the window
dock = window.add_dock(f"ScanDisplay {motor}")
plt1 = dock.add_widget("BECWaveformWidget")
plt1.plot(x_name=motor, y_name=plot)
plt1.set_x_label(motor)
plt1.set_y_label(plot)
plt1.add_dap(motor, plot, dap="LinearModel")
window.show()
print("Handing over to 'scans.line_scan'")
@@ -66,7 +67,9 @@ def ascan(
firt_par = plt1.get_dap_params()
else:
# Fitting without GUI
firt_par = bec.dap.LinearModel.fit(s, motor.name, motor.name, plot.name, plot.name)
firt_par = bec.dap.LinearModel.fit(
s, motor.name, motor.name, plot.name, plot.name
)
# # Some basic fit
# dkey = datasource.full_name
View File
+5 -16
View File
@@ -5,8 +5,8 @@ build-backend = "hatchling.build"
[project]
name = "pxiii_bec"
version = "0.0.0"
description = "A plugin repository for BEC"
requires-python = ">=3.11"
description = "Custom device implementations based on the ophyd hardware abstraction layer"
requires-python = ">=3.10"
classifiers = [
"Development Status :: 3 - Alpha",
"Programming Language :: Python :: 3",
@@ -29,21 +29,19 @@ dependencies = [
[project.optional-dependencies]
dev = [
"black",
"copier",
"isort",
"coverage",
"pylint",
"pytest",
"pytest-random-order",
"ophyd_devices",
"bec_server",
"pytest-redis",
]
[project.entry-points."bec"]
plugin_bec = "pxiii_bec"
[project.entry-points."bec.deployment.device_server"]
plugin_ds_startup = "pxiii_bec.deployments.device_server.startup:run"
plugin_ds_startup = "pxiii_bec.deployment.device_server.startup:run"
[project.entry-points."bec.file_writer"]
plugin_file_writer = "pxiii_bec.file_writer"
@@ -51,21 +49,12 @@ plugin_file_writer = "pxiii_bec.file_writer"
[project.entry-points."bec.scans"]
plugin_scans = "pxiii_bec.scans"
[project.entry-points."bec.scans.scan_modifier"]
plugin_scan_modifier = "pxiii_bec.scans.scan_customization.scan_modifier"
[project.entry-points."bec.scans.metadata_schema"]
plugin_metadata_schema = "pxiii_bec.scans.metadata_schema"
[project.entry-points."bec.ipython_client_startup"]
plugin_ipython_client_pre = "pxiii_bec.bec_ipython_client.startup.pre_startup"
plugin_ipython_client_post = "pxiii_bec.bec_ipython_client.startup"
[project.entry-points."bec.widgets.auto_updates"]
plugin_widgets_update = "pxiii_bec.bec_widgets.auto_updates"
[project.entry-points."bec.widgets.user_widgets"]
plugin_widgets = "pxiii_bec.bec_widgets.widgets"
plugin_widgets_update = "pxiii_bec.bec_widgets.auto_updates:PlotUpdate"
[tool.hatch.build.targets.wheel]
include = ["*"]
+9 -12
View File
@@ -1,34 +1,31 @@
# Getting Started with Testing using pytest
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
It can be installed via
```bash
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
It can be install 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 `<test_module_name.py>`.
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).
+9 -12
View File
@@ -1,34 +1,31 @@
# Getting Started with Testing using pytest
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
It can be installed via
```bash
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
It can be install 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 `<test_module_name.py>`.
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).
+9 -12
View File
@@ -1,34 +1,31 @@
# Getting Started with Testing using pytest
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
It can be installed via
```bash
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
It can be install 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 `<test_module_name.py>`.
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).
+9 -12
View File
@@ -1,34 +1,31 @@
# Getting Started with Testing using pytest
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
It can be installed via
```bash
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
It can be install 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 `<test_module_name.py>`.
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).
-34
View File
@@ -1,34 +0,0 @@
# Getting Started with Testing using pytest
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_.
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 `<test_module_name.py>`.
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
```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.
## Test examples
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).
+9 -12
View File
@@ -1,34 +1,31 @@
# Getting Started with Testing using pytest
BEC is using the [pytest](https://docs.pytest.org/en/latest/) framework.
It can be installed via
```bash
BEC is using the [pytest](https://docs.pytest.org/en/8.0.x/) framework.
It can be install 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 `<test_module_name.py>`.
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).