Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d1844eaeba | |||
| d53ec4c14c | |||
| 4091f11b0a | |||
| 29ae5c196b | |||
| 74521da7b3 | |||
| a5f844b816 | |||
| 34d4d6ef8c |
@@ -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
|
||||
@@ -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__
|
||||
@@ -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')
|
||||
@@ -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
|
||||
@@ -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\"
|
||||
}"
|
||||
@@ -8,9 +8,6 @@
|
||||
**/.pytest_cache
|
||||
**/*.egg*
|
||||
|
||||
# recovery_config files
|
||||
recovery_config_*
|
||||
|
||||
# file writer data
|
||||
**.h5
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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 +0,0 @@
|
||||
# Add anything you don't want to check in to git, e.g. very large files
|
||||
@@ -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"
|
||||
|
||||
@@ -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: ./
|
||||
|
||||
Executable
+29
@@ -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 @@
|
||||
|
||||
from .auto_updates import PlotUpdate
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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,,
|
||||
|
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,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
@@ -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()
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+5
-16
@@ -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 = ["*"]
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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).
|
||||
@@ -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).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user