Compare commits
1 Commits
refactor/m
...
ci/securit
| Author | SHA1 | Date | |
|---|---|---|---|
| 0849d093f8 |
@@ -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.2.2
|
||||
_src_path: https://github.com/bec-project/plugin_copier_template.git
|
||||
make_commit: false
|
||||
project_name: csaxs_bec
|
||||
widget_plugins_input: []
|
||||
@@ -1,85 +0,0 @@
|
||||
name: CI for csaxs_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.11"
|
||||
|
||||
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.11' }}"
|
||||
|
||||
- name: Checkout BEC Core
|
||||
run: git clone --depth 1 --branch "${{ inputs.BEC_CORE_BRANCH || 'main' }}" https://github.com/bec-project/bec.git ./bec
|
||||
|
||||
- name: Checkout Ophyd Devices
|
||||
run: git clone --depth 1 --branch "${{ inputs.OPHYD_DEVICES_BRANCH || 'main' }}" https://github.com/bec-project/ophyd_devices.git ./ophyd_devices
|
||||
|
||||
- name: Checkout BEC Widgets
|
||||
run: git clone --depth 1 --branch "${{ inputs.BEC_WIDGETS_BRANCH || 'main' }}" https://github.com/bec-project/bec_widgets.git ./bec_widgets
|
||||
|
||||
- name: Checkout BEC Plugin Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: bec/csaxs_bec
|
||||
ref: "${{ inputs.BEC_PLUGIN_REPO_BRANCH || github.head_ref || github.sha }}"
|
||||
path: ./csaxs_bec
|
||||
|
||||
- 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 ./csaxs_bec
|
||||
|
||||
- name: Run Pytest with Coverage
|
||||
id: coverage
|
||||
run: pytest --random-order --cov=./csaxs_bec --cov-config=./csaxs_bec/pyproject.toml --cov-branch --cov-report=xml --no-cov-on-fail ./csaxs_bec/tests/ || test $? -eq 5
|
||||
119
.gitlab-ci.yml
119
.gitlab-ci.yml
@@ -1,20 +1,105 @@
|
||||
include:
|
||||
- project: bec/awi_utils
|
||||
file: /templates/plugin-repo-template.yml
|
||||
inputs:
|
||||
name: "csaxs"
|
||||
target: "csaxs_bec"
|
||||
branch: $CHILD_PIPELINE_BRANCH
|
||||
# This file is a template, and might need editing before it works on your project.
|
||||
# Official language image. Look for the different tagged releases at:
|
||||
# https://hub.docker.com/r/library/python/tags/
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.10
|
||||
|
||||
pages:
|
||||
stage: Deploy
|
||||
needs: []
|
||||
variables:
|
||||
TARGET_BRANCH: $CI_COMMIT_REF_NAME
|
||||
workflow:
|
||||
rules:
|
||||
- if: "$CI_COMMIT_TAG != null"
|
||||
variables:
|
||||
TARGET_BRANCH: $CI_COMMIT_TAG
|
||||
- if: '$CI_COMMIT_REF_NAME == "main" && $CI_PROJECT_PATH == "bec/csaxs_bec"'
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
- if: $CI_PIPELINE_SOURCE == "web"
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
|
||||
include:
|
||||
- template: Jobs/Secret-Detection.gitlab-ci.yml
|
||||
|
||||
|
||||
#commands to run in the Docker container before starting each job.
|
||||
before_script:
|
||||
- pip install -e .[dev]
|
||||
|
||||
# different stages in the pipeline
|
||||
stages:
|
||||
- Formatter
|
||||
- test # must be called test for security/secret-detection to work
|
||||
- AdditionalTests
|
||||
- Deploy
|
||||
|
||||
pylint:
|
||||
stage: Formatter
|
||||
script:
|
||||
- curl -X POST -d "branches=$CI_COMMIT_REF_NAME" -d "token=$RTD_TOKEN" https://readthedocs.org/api/v2/webhook/sls-csaxs/270162/
|
||||
- pip install pylint pylint-exit anybadge
|
||||
- pip install -e .[dev]
|
||||
- mkdir ./pylint
|
||||
- pylint ./csaxs_bec --output-format=text --output=./pylint/pylint.log | tee ./pylint/pylint.log || pylint-exit $?
|
||||
- PYLINT_SCORE=$(sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' ./pylint/pylint.log)
|
||||
- anybadge --label=Pylint --file=pylint/pylint.svg --value=$PYLINT_SCORE 2=red 4=orange 8=yellow 10=green
|
||||
- echo "Pylint score is $PYLINT_SCORE"
|
||||
artifacts:
|
||||
paths:
|
||||
- ./pylint/
|
||||
expire_in: 1 week
|
||||
|
||||
pylint-check:
|
||||
stage: Formatter
|
||||
needs: []
|
||||
allow_failure: true
|
||||
before_script:
|
||||
- pip install pylint pylint-exit anybadge
|
||||
- apt-get update
|
||||
- apt-get install -y bc
|
||||
script:
|
||||
# Identify changed Python files
|
||||
- if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]; then
|
||||
TARGET_BRANCH_COMMIT_SHA=$(git rev-parse $CI_MERGE_REQUEST_TARGET_BRANCH_NAME);
|
||||
CHANGED_FILES=$(git diff --name-only $SOURCE_BRANCH_COMMIT_SHA $TARGET_BRANCH_COMMIT_SHA | grep '\.py$' || true);
|
||||
else
|
||||
CHANGED_FILES=$(git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA | grep '\.py$' || true);
|
||||
fi
|
||||
- if [ -z "$CHANGED_FILES" ]; then echo "No Python files changed."; exit 0; fi
|
||||
|
||||
# Run pylint only on changed files
|
||||
- mkdir ./pylint
|
||||
- pylint $CHANGED_FILES --output-format=text . | tee ./pylint/pylint_changed_files.log || pylint-exit $?
|
||||
- PYLINT_SCORE=$(sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' ./pylint/pylint_changed_files.log)
|
||||
- echo "Pylint score is $PYLINT_SCORE"
|
||||
|
||||
# Fail the job if the pylint score is below 9
|
||||
- if [ "$(echo "$PYLINT_SCORE < 9" | bc)" -eq 1 ]; then echo "Your pylint score is below the acceptable threshold (9)."; exit 1; fi
|
||||
artifacts:
|
||||
paths:
|
||||
- ./pylint/
|
||||
expire_in: 1 week
|
||||
|
||||
secret_detection:
|
||||
before_script:
|
||||
- ''
|
||||
|
||||
config_test:
|
||||
stage: test
|
||||
script:
|
||||
- ophyd_test --config ./csaxs_bec/device_configs/ --output ./config_tests
|
||||
artifacts:
|
||||
paths:
|
||||
- ./config_tests
|
||||
when: on_failure
|
||||
expire_in: "30 days"
|
||||
allow_failure: true
|
||||
|
||||
|
||||
pytest:
|
||||
stage: test
|
||||
script:
|
||||
- pip install coverage
|
||||
- coverage run --source=./csaxs_bec -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests
|
||||
- coverage report
|
||||
- coverage xml
|
||||
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
|
||||
artifacts:
|
||||
reports:
|
||||
junit: report.xml
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# .readthedocs.yaml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3.10"
|
||||
jobs:
|
||||
pre_install:
|
||||
- pip install .
|
||||
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
# If using Sphinx, optionally build your docs in additional formats such as PDF
|
||||
# formats:
|
||||
# - pdf
|
||||
|
||||
# Optionally declare the Python requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
|
||||
29
LICENSE
29
LICENSE
@@ -1,29 +0,0 @@
|
||||
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2025, Paul Scherrer Institute
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
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.
|
||||
1
bin/.gitignore
vendored
1
bin/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
# Add anything you don't want to check in to git, e.g. very large files
|
||||
@@ -3,9 +3,8 @@ from __future__ import annotations
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from bec_lib import bec_logger, messages
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_lib.redis_connector import MessageObject, RedisConnector
|
||||
from bec_lib import MessageEndpoints, RedisConnector, bec_logger, messages
|
||||
from bec_lib.redis_connector import MessageObject
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
@@ -13,6 +12,7 @@ logger = bec_logger.logger
|
||||
class PilatusConverter:
|
||||
def __init__(self, host: str, port: int) -> None:
|
||||
self._connector = RedisConnector(f"{host}:{port}")
|
||||
self._producer = self._connector.producer()
|
||||
|
||||
def start(self) -> None:
|
||||
"""start the consumer"""
|
||||
@@ -27,7 +27,7 @@ class PilatusConverter:
|
||||
message (MessageObject): Message object
|
||||
parent (PilatusConverter): Parent object
|
||||
"""
|
||||
msg = message.value
|
||||
msg = messages.MessageReader.loads(message.value)
|
||||
print(msg)
|
||||
if not msg:
|
||||
return
|
||||
@@ -56,9 +56,10 @@ class PilatusConverter:
|
||||
"""
|
||||
Start the consumer.
|
||||
"""
|
||||
self._connector.register(
|
||||
file_consumer = self._connector.consumer(
|
||||
MessageEndpoints.file_event("pilatus_2"), cb=self.on_new_message, parent=self
|
||||
)
|
||||
file_consumer.start()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -13,166 +13,6 @@ umv = builtins.__dict__.get("umv")
|
||||
bec = builtins.__dict__.get("bec")
|
||||
|
||||
|
||||
class LamNIInitError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LaMNIInitStagesMixin:
|
||||
def lamni_init_stages(self):
|
||||
user_input = input("Starting initialization of LamNI stages. OK? [y/n]")
|
||||
if user_input == "y":
|
||||
print("staring...")
|
||||
dev.lsamrot.enabled = True
|
||||
else:
|
||||
return
|
||||
|
||||
if self.check_all_axes_of_lamni_referenced():
|
||||
user_input = input("Continue anyways? [y/n]")
|
||||
if user_input == "y":
|
||||
print("ok then...")
|
||||
else:
|
||||
return
|
||||
|
||||
axis_id_lsamrot = dev.lsamrot._config["deviceConfig"].get("axis_Id")
|
||||
if dev.lsamrot.controller.get_motor_limit_switch(axis_id_lsamrot)[1] == False:
|
||||
user_input = input("The rotation stage will be moved to one limit [y/n]")
|
||||
if user_input == "y":
|
||||
print("starting...")
|
||||
else:
|
||||
return
|
||||
|
||||
self.drive_axis_to_limit(dev.lsamrot, "forward")
|
||||
dev.lsamrot.enabled = False
|
||||
print("Now hard reboot the controller and run the initialization routine again.")
|
||||
print("The controller will be disabled in bec. To enable dev.lsamrot.enabled=True")
|
||||
return
|
||||
|
||||
user_input = input(
|
||||
"Init of loptz. Can the stage move to the upstream limit without collision?? [y/n]"
|
||||
)
|
||||
if user_input == "y":
|
||||
print("ok then...")
|
||||
else:
|
||||
return
|
||||
|
||||
print("Referencing loptz")
|
||||
self.drive_axis_to_limit(dev.loptz, "forward")
|
||||
self.find_reference_mark(dev.loptz)
|
||||
|
||||
print("Referencing loptx")
|
||||
self.drive_axis_to_limit(dev.loptx, "reverse")
|
||||
self.find_reference_mark(dev.loptx)
|
||||
|
||||
print("Referencing lopty")
|
||||
self.drive_axis_to_limit(dev.lopty, "forward")
|
||||
self.find_reference_mark(dev.lopty)
|
||||
|
||||
print("Referencing lsamx")
|
||||
self.drive_axis_to_limit(dev.lsamx, "forward")
|
||||
self.find_reference_mark(dev.lsamx)
|
||||
|
||||
print("Referencing lsamy")
|
||||
self.drive_axis_to_limit(dev.lsamy, "reverse")
|
||||
self.find_reference_mark(dev.lsamy)
|
||||
|
||||
# the dual encoder requires the reference mark to pass on both encoders
|
||||
print("Referencing lsamrot")
|
||||
self.drive_axis_to_limit(dev.lsamrot, "reverse")
|
||||
time.sleep(0.1)
|
||||
self.find_reference_mark(dev.lsamrot)
|
||||
|
||||
user_input = input("Init of leye. Can the stage move to -x limit without collision? [y/n]")
|
||||
if user_input == "y":
|
||||
print("starting...")
|
||||
else:
|
||||
return
|
||||
|
||||
print("Referencing leyex")
|
||||
self.drive_axis_to_limit(dev.leyex, "forward")
|
||||
print("Referencing leyey")
|
||||
self.drive_axis_to_limit(dev.leyey, "forward")
|
||||
|
||||
# set_lm lsamx 6 14
|
||||
# set_lm lsamy 6 14
|
||||
# set_lm lsamrot -3 362
|
||||
# set_lm loptx -1 -0.2
|
||||
# set_lm lopty 3.0 3.6
|
||||
# set_lm loptz 82 87
|
||||
# set_lm leyex 0 25
|
||||
# set_lm leyey 0.5 50
|
||||
|
||||
print("Init of Smaract stages")
|
||||
dev.losax.controller.find_reference_mark(2, 0, 1000, 1)
|
||||
time.sleep(1)
|
||||
dev.losax.controller.find_reference_mark(0, 0, 1000, 1)
|
||||
time.sleep(1)
|
||||
dev.losax.controller.find_reference_mark(1, 0, 1000, 1)
|
||||
time.sleep(1)
|
||||
# dev.losax.controller.find_reference_mark(3, 1, 1000, 1)
|
||||
# time.sleep(1)
|
||||
# dev.losax.controller.find_reference_mark(4, 1, 1000, 1)
|
||||
# time.sleep(1)
|
||||
|
||||
# set_lm losax -1.5 0.25
|
||||
# set_lm losay -2.5 4.1
|
||||
# set_lm losaz -4.1 -0.5
|
||||
# set_lm lcsy -1.5 5
|
||||
|
||||
self._align_setup()
|
||||
|
||||
def find_reference_mark(self, device):
|
||||
axis_id = device._config["deviceConfig"].get("axis_Id")
|
||||
axis_id_numeric = self.axis_id_to_numeric(axis_id)
|
||||
device.controller.find_reference(axis_id_numeric)
|
||||
|
||||
def drive_axis_to_limit(self, device, direction):
|
||||
axis_id = device._config["deviceConfig"].get("axis_Id")
|
||||
axis_id_numeric = self.axis_id_to_numeric(axis_id)
|
||||
device.controller.drive_axis_to_limit(axis_id_numeric, direction)
|
||||
|
||||
def axis_id_to_numeric(self, axis_id) -> int:
|
||||
return ord(axis_id.lower()) - 97
|
||||
|
||||
def _align_setup(self):
|
||||
user_input = input("Start moving stages to default initial positions? [y/n]")
|
||||
if user_input == "y":
|
||||
print("Start moving stages...")
|
||||
else:
|
||||
print("Stopping.")
|
||||
return
|
||||
|
||||
lsamx_center = dev.lsamx.user_parameter.get("center")
|
||||
if lsamx_center is None:
|
||||
raise LamNIInitError(
|
||||
"Could not find a lsamx center position. Please check your device config."
|
||||
)
|
||||
lsamy_center = dev.lsamy.user_parameter.get("center")
|
||||
if lsamy_center is None:
|
||||
raise LamNIInitError(
|
||||
"Could not find a lsamy center position. Please check your device config."
|
||||
)
|
||||
umv(dev.lsamx, lsamx_center, dev.lsamy, lsamy_center, dev.loptx, -0.3, dev.lopty, 0)
|
||||
umv(dev.losax, -1)
|
||||
umv(dev.loptz, 82.25)
|
||||
umv(dev.lsamrot, -1)
|
||||
umv(dev.lsamrot, 0)
|
||||
|
||||
time.sleep(2)
|
||||
dev.rtx.controller.feedback_disable_and_even_reset_lamni_angle_interferometer()
|
||||
|
||||
def check_all_axes_of_lamni_referenced(self):
|
||||
if (
|
||||
dev.losax.controller.axis_is_referenced(0)
|
||||
& dev.losax.controller.axis_is_referenced(1)
|
||||
& dev.losax.controller.axis_is_referenced(2)
|
||||
& dev.lsamx.controller.all_axes_referenced()
|
||||
):
|
||||
print("All axes of LamNI are referenced.")
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class LamNIOpticsMixin:
|
||||
@staticmethod
|
||||
def _get_user_param_safe(device, var):
|
||||
@@ -280,22 +120,14 @@ class LamNIOpticsMixin:
|
||||
umv(dev.losaz, losaz_out)
|
||||
umv(dev.losay, losay_out)
|
||||
|
||||
def lfzp_info(self, mokev_val=-1):
|
||||
|
||||
if mokev_val == -1:
|
||||
try:
|
||||
mokev_val = dev.mokev.readback.get()
|
||||
except:
|
||||
print(
|
||||
"Device mokev does not exist. You can specify the energy in keV as an argument instead."
|
||||
)
|
||||
return
|
||||
def lfzp_info(self):
|
||||
loptz_val = dev.loptz.read()["loptz"]["value"]
|
||||
distance = -loptz_val + 85.6 + 52
|
||||
print(f"The sample is in a distance of {distance:.1f} mm from the FZP.")
|
||||
|
||||
diameters = [80e-6, 100e-6, 120e-6, 150e-6, 170e-6, 200e-6, 220e-6, 250e-6]
|
||||
|
||||
mokev_val = dev.mokev.read()["mokev"]["value"]
|
||||
console = Console()
|
||||
table = Table(
|
||||
title=f"At the current energy of {mokev_val:.4f} keV we have following options:",
|
||||
|
||||
0
csaxs_bec/bec_ipython_client/plugins/LamNI/load_additional_correction.py
Normal file → Executable file
0
csaxs_bec/bec_ipython_client/plugins/LamNI/load_additional_correction.py
Normal file → Executable file
@@ -16,7 +16,7 @@ from typeguard import typechecked
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen
|
||||
|
||||
from .lamni_optics_mixin import LaMNIInitStagesMixin, LamNIOpticsMixin
|
||||
from .lamni_optics_mixin import LamNIOpticsMixin
|
||||
|
||||
logger = bec_logger.logger
|
||||
bec = builtins.__dict__.get("bec")
|
||||
@@ -32,7 +32,7 @@ class XrayEyeAlign:
|
||||
self.lamni = lamni
|
||||
self.device_manager = client.device_manager
|
||||
self.scans = client.scans
|
||||
# self.xeye = self.device_manager.devices.xeye
|
||||
self.xeye = self.device_manager.devices.xeye
|
||||
self.alignment_values = defaultdict(list)
|
||||
self._reset_init_values()
|
||||
self.corr_pos_x = []
|
||||
@@ -511,7 +511,7 @@ class LamNI(LamNIOpticsMixin):
|
||||
super().__init__()
|
||||
self.client = client
|
||||
self.align = XrayEyeAlign(client, self)
|
||||
self.init = LaMNIInitStagesMixin()
|
||||
|
||||
self.check_shutter = True
|
||||
self.check_light_available = True
|
||||
self.check_fofb = True
|
||||
@@ -586,27 +586,6 @@ class LamNI(LamNIOpticsMixin):
|
||||
def tomo_circfov(self, val: float):
|
||||
self.client.set_global_var("tomo_circfov", val)
|
||||
|
||||
@property
|
||||
def tomo_type(self):
|
||||
val = self.client.get_global_var("tomo_type")
|
||||
if val is None:
|
||||
return 1
|
||||
return val
|
||||
|
||||
@tomo_type.setter
|
||||
def tomo_type(self, val: float):
|
||||
if val == 1:
|
||||
# equally spaced tomography with 8 sub tomograms
|
||||
self.client.set_global_var("tomo_type", val)
|
||||
# elif val == 2:
|
||||
# # golden ratio tomography (sorted bunches)
|
||||
# self.client.set_global_var("tomo_type", val)
|
||||
# elif val == 3:
|
||||
# # equally spaced tomography with starting angles shifted by golden ratio
|
||||
# self.client.set_global_var("tomo_type", val)
|
||||
else:
|
||||
raise ValueError("Unknown tomo_type.")
|
||||
|
||||
@property
|
||||
def tomo_countingtime(self):
|
||||
val = self.client.get_global_var("tomo_countingtime")
|
||||
@@ -969,63 +948,25 @@ class LamNI(LamNIOpticsMixin):
|
||||
# _tomo_shift_angles (potential global variable)
|
||||
_tomo_shift_angles = 0
|
||||
angle_end = start_angle + 360
|
||||
angles = np.linspace(
|
||||
for angle in np.linspace(
|
||||
start_angle + _tomo_shift_angles,
|
||||
angle_end,
|
||||
num=int(360 / self.tomo_angle_stepsize) + 1,
|
||||
endpoint=True,
|
||||
)
|
||||
|
||||
# reverse even sub-tomograms
|
||||
if not (subtomo_number % 2):
|
||||
angles = np.flip(angles)
|
||||
for angle in angles:
|
||||
self.progress["subtomo"] = subtomo_number
|
||||
self.progress["subtomo_projection"] = angles.index(angle)
|
||||
self.progress["subtomo_total_projections"] = 180 / self.tomo_angle_stepsize
|
||||
self.progress["projection"] = (subtomo_number - 1) * self.progress[
|
||||
"subtomo_total_projections"
|
||||
] + self.progress["subtomo_projection"]
|
||||
self.progress["total_projections"] = 180 / self.tomo_angle_stepsize * 8
|
||||
self.progress["angle"] = angle
|
||||
self._tomo_scan_at_angle(angle, subtomo_number)
|
||||
|
||||
def _print_progress(self):
|
||||
print("\x1b[95mProgress report:")
|
||||
print(f"Tomo type: ....................... {self.progress['tomo_type']}")
|
||||
print(f"Projection: ...................... {self.progress['projection']}")
|
||||
print(f"Total projections expected ....... {self.progress['total_projections']}")
|
||||
print(f"Angle: ........................... {self.progress['angle']}")
|
||||
print(f"Current subtomo: ................. {self.progress['subtomo']}")
|
||||
print(f"Current projection within subtomo: {self.progress['subtomo_projection']}\x1b[0m")
|
||||
|
||||
def _tomo_scan_at_angle(self, angle, subtomo_number):
|
||||
successful = False
|
||||
error_caught = False
|
||||
if 0 <= angle < 360.05:
|
||||
print(f"Starting LamNI scan for angle {angle} in subtomo {subtomo_number}")
|
||||
self._print_progress()
|
||||
while not successful:
|
||||
self._start_beam_check()
|
||||
if not self.special_angles:
|
||||
self._current_special_angles = []
|
||||
if self._current_special_angles:
|
||||
next_special_angle = self._current_special_angles[0]
|
||||
if np.isclose(angle, next_special_angle, atol=0.5):
|
||||
self._current_special_angles.pop(0)
|
||||
num_repeats = self.special_angle_repeats
|
||||
else:
|
||||
num_repeats = 1
|
||||
try:
|
||||
start_scan_number = bec.queue.next_scan_number
|
||||
for i in range(num_repeats):
|
||||
self._at_each_angle(angle)
|
||||
error_caught = False
|
||||
except AlarmBase as exc:
|
||||
if exc.alarm_type == "TimeoutError":
|
||||
bec.queue.request_queue_reset()
|
||||
time.sleep(2)
|
||||
error_caught = True
|
||||
):
|
||||
successful = False
|
||||
error_caught = False
|
||||
if 0 <= angle < 360.05:
|
||||
print(f"Starting LamNI scan for angle {angle}")
|
||||
while not successful:
|
||||
self._start_beam_check()
|
||||
if not self.special_angles:
|
||||
self._current_special_angles = []
|
||||
if self._current_special_angles:
|
||||
next_special_angle = self._current_special_angles[0]
|
||||
if np.isclose(angle, next_special_angle, atol=0.5):
|
||||
self._current_special_angles.pop(0)
|
||||
num_repeats = self.special_angle_repeats
|
||||
else:
|
||||
num_repeats = 1
|
||||
try:
|
||||
@@ -1065,7 +1006,7 @@ class LamNI(LamNIOpticsMixin):
|
||||
scans = builtins.__dict__.get("scans")
|
||||
self._current_special_angles = self.special_angles.copy()
|
||||
|
||||
if self.tomo_type == 1 and subtomo_start == 1 and start_angle is None:
|
||||
if subtomo_start == 1 and start_angle is None:
|
||||
# pylint: disable=undefined-variable
|
||||
self.tomo_id = self.add_sample_database(
|
||||
self.sample_name,
|
||||
|
||||
@@ -1,351 +0,0 @@
|
||||
corr_elements = 175
|
||||
corr_angle[0] = 0.100000
|
||||
corr_angle[1] = 1.001000
|
||||
corr_angle[2] = 1.902000
|
||||
corr_angle[3] = 2.798000
|
||||
corr_angle[4] = 7.305000
|
||||
corr_angle[5] = 8.204000
|
||||
corr_angle[6] = 9.104000
|
||||
corr_angle[7] = 10.005000
|
||||
corr_angle[8] = 14.504000
|
||||
corr_angle[9] = 15.404000
|
||||
corr_angle[10] = 16.304000
|
||||
corr_angle[11] = 17.204000
|
||||
corr_angle[12] = 21.704000
|
||||
corr_angle[13] = 22.604000
|
||||
corr_angle[14] = 23.504000
|
||||
corr_angle[15] = 24.404000
|
||||
corr_angle[16] = 28.904000
|
||||
corr_angle[17] = 29.804000
|
||||
corr_angle[18] = 30.704000
|
||||
corr_angle[19] = 31.604000
|
||||
corr_angle[20] = 36.104000
|
||||
corr_angle[21] = 37.004000
|
||||
corr_angle[22] = 37.904000
|
||||
corr_angle[23] = 38.804000
|
||||
corr_angle[24] = 43.305000
|
||||
corr_angle[25] = 44.205000
|
||||
corr_angle[26] = 45.104000
|
||||
corr_angle[27] = 46.005000
|
||||
corr_angle[28] = 50.504000
|
||||
corr_angle[29] = 51.404000
|
||||
corr_angle[30] = 52.305000
|
||||
corr_angle[31] = 53.205000
|
||||
corr_angle[32] = 57.705000
|
||||
corr_angle[33] = 58.605000
|
||||
corr_angle[34] = 59.505000
|
||||
corr_angle[35] = 60.405000
|
||||
corr_angle[36] = 64.904000
|
||||
corr_angle[37] = 65.804000
|
||||
corr_angle[38] = 66.704000
|
||||
corr_angle[39] = 67.604000
|
||||
corr_angle[40] = 72.104000
|
||||
corr_angle[41] = 73.004000
|
||||
corr_angle[42] = 73.904000
|
||||
corr_angle[43] = 74.804000
|
||||
corr_angle[44] = 79.304000
|
||||
corr_angle[45] = 80.204000
|
||||
corr_angle[46] = 81.104000
|
||||
corr_angle[47] = 82.005000
|
||||
corr_angle[48] = 86.505000
|
||||
corr_angle[49] = 87.404000
|
||||
corr_angle[50] = 88.304000
|
||||
corr_angle[51] = 89.205000
|
||||
corr_angle[52] = 93.704000
|
||||
corr_angle[53] = 94.604000
|
||||
corr_angle[54] = 95.505000
|
||||
corr_angle[55] = 96.404000
|
||||
corr_angle[56] = 100.904000
|
||||
corr_angle[57] = 101.804000
|
||||
corr_angle[58] = 102.704000
|
||||
corr_angle[59] = 103.604000
|
||||
corr_angle[60] = 108.104000
|
||||
corr_angle[61] = 109.004000
|
||||
corr_angle[62] = 109.904000
|
||||
corr_angle[63] = 110.804000
|
||||
corr_angle[64] = 115.304000
|
||||
corr_angle[65] = 116.204000
|
||||
corr_angle[66] = 117.104000
|
||||
corr_angle[67] = 118.004000
|
||||
corr_angle[68] = 122.504000
|
||||
corr_angle[69] = 123.404000
|
||||
corr_angle[70] = 124.304000
|
||||
corr_angle[71] = 125.204000
|
||||
corr_angle[72] = 129.704000
|
||||
corr_angle[73] = 130.604000
|
||||
corr_angle[74] = 131.504000
|
||||
corr_angle[75] = 132.404000
|
||||
corr_angle[76] = 136.904000
|
||||
corr_angle[77] = 137.804000
|
||||
corr_angle[78] = 138.701000
|
||||
corr_angle[79] = 139.604000
|
||||
corr_angle[80] = 144.104000
|
||||
corr_angle[81] = 145.004000
|
||||
corr_angle[82] = 145.904000
|
||||
corr_angle[83] = 146.804000
|
||||
corr_angle[84] = 151.304000
|
||||
corr_angle[85] = 152.204000
|
||||
corr_angle[86] = 153.104000
|
||||
corr_angle[87] = 154.004000
|
||||
corr_angle[88] = 158.504000
|
||||
corr_angle[89] = 159.404000
|
||||
corr_angle[90] = 160.304000
|
||||
corr_angle[91] = 161.204000
|
||||
corr_angle[92] = 165.704000
|
||||
corr_angle[93] = 166.604000
|
||||
corr_angle[94] = 167.504000
|
||||
corr_angle[95] = 168.404000
|
||||
corr_angle[96] = 172.904000
|
||||
corr_angle[97] = 173.805000
|
||||
corr_angle[98] = 174.704000
|
||||
corr_angle[99] = 180.104000
|
||||
corr_angle[100] = 183.704000
|
||||
corr_angle[101] = 184.603000
|
||||
corr_angle[102] = 185.504000
|
||||
corr_angle[103] = 190.904000
|
||||
corr_angle[104] = 191.805000
|
||||
corr_angle[105] = 192.704000
|
||||
corr_angle[106] = 198.104000
|
||||
corr_angle[107] = 199.004000
|
||||
corr_angle[108] = 199.904000
|
||||
corr_angle[109] = 205.304000
|
||||
corr_angle[110] = 206.204000
|
||||
corr_angle[111] = 207.104000
|
||||
corr_angle[112] = 212.504000
|
||||
corr_angle[113] = 213.404000
|
||||
corr_angle[114] = 214.304000
|
||||
corr_angle[115] = 219.704000
|
||||
corr_angle[116] = 220.604000
|
||||
corr_angle[117] = 221.504000
|
||||
corr_angle[118] = 226.904000
|
||||
corr_angle[119] = 227.804000
|
||||
corr_angle[120] = 228.704000
|
||||
corr_angle[121] = 234.104000
|
||||
corr_angle[122] = 235.004000
|
||||
corr_angle[123] = 235.904000
|
||||
corr_angle[124] = 241.304000
|
||||
corr_angle[125] = 242.204000
|
||||
corr_angle[126] = 243.104000
|
||||
corr_angle[127] = 248.504000
|
||||
corr_angle[128] = 249.404000
|
||||
corr_angle[129] = 250.304000
|
||||
corr_angle[130] = 255.704000
|
||||
corr_angle[131] = 256.604000
|
||||
corr_angle[132] = 257.504000
|
||||
corr_angle[133] = 262.904000
|
||||
corr_angle[134] = 263.804000
|
||||
corr_angle[135] = 264.704000
|
||||
corr_angle[136] = 270.104000
|
||||
corr_angle[137] = 271.004000
|
||||
corr_angle[138] = 271.904000
|
||||
corr_angle[139] = 277.304000
|
||||
corr_angle[140] = 278.205000
|
||||
corr_angle[141] = 279.104000
|
||||
corr_angle[142] = 284.504000
|
||||
corr_angle[143] = 285.405000
|
||||
corr_angle[144] = 286.304000
|
||||
corr_angle[145] = 291.703000
|
||||
corr_angle[146] = 292.604000
|
||||
corr_angle[147] = 293.504000
|
||||
corr_angle[148] = 298.904000
|
||||
corr_angle[149] = 299.804000
|
||||
corr_angle[150] = 300.704000
|
||||
corr_angle[151] = 306.104000
|
||||
corr_angle[152] = 307.004000
|
||||
corr_angle[153] = 307.904000
|
||||
corr_angle[154] = 313.304000
|
||||
corr_angle[155] = 314.204000
|
||||
corr_angle[156] = 315.104000
|
||||
corr_angle[157] = 320.504000
|
||||
corr_angle[158] = 321.404000
|
||||
corr_angle[159] = 322.304000
|
||||
corr_angle[160] = 327.704000
|
||||
corr_angle[161] = 328.605000
|
||||
corr_angle[162] = 329.504000
|
||||
corr_angle[163] = 334.904000
|
||||
corr_angle[164] = 335.804000
|
||||
corr_angle[165] = 336.705000
|
||||
corr_angle[166] = 342.104000
|
||||
corr_angle[167] = 343.004000
|
||||
corr_angle[168] = 343.904000
|
||||
corr_angle[169] = 349.304000
|
||||
corr_angle[170] = 350.204000
|
||||
corr_angle[171] = 351.104000
|
||||
corr_angle[172] = 356.504000
|
||||
corr_angle[173] = 357.404000
|
||||
corr_angle[174] = 358.304000
|
||||
corr_pos[0] = 0.012330
|
||||
corr_pos[1] = 0.024870
|
||||
corr_pos[2] = 0.037262
|
||||
corr_pos[3] = 0.049438
|
||||
corr_pos[4] = 0.108462
|
||||
corr_pos[5] = 0.119791
|
||||
corr_pos[6] = 0.130986
|
||||
corr_pos[7] = 0.142044
|
||||
corr_pos[8] = 0.195045
|
||||
corr_pos[9] = 0.205834
|
||||
corr_pos[10] = 0.216589
|
||||
corr_pos[11] = 0.226402
|
||||
corr_pos[12] = 0.271931
|
||||
corr_pos[13] = 0.281684
|
||||
corr_pos[14] = 0.290769
|
||||
corr_pos[15] = 0.299392
|
||||
corr_pos[16] = 0.337986
|
||||
corr_pos[17] = 0.345178
|
||||
corr_pos[18] = 0.352253
|
||||
corr_pos[19] = 0.358528
|
||||
corr_pos[20] = 0.379520
|
||||
corr_pos[21] = 0.382947
|
||||
corr_pos[22] = 0.386110
|
||||
corr_pos[23] = 0.388887
|
||||
corr_pos[24] = 0.395645
|
||||
corr_pos[25] = 0.396154
|
||||
corr_pos[26] = 0.396679
|
||||
corr_pos[27] = 0.396367
|
||||
corr_pos[28] = 0.392400
|
||||
corr_pos[29] = 0.392530
|
||||
corr_pos[30] = 0.391826
|
||||
corr_pos[31] = 0.391325
|
||||
corr_pos[32] = 0.379188
|
||||
corr_pos[33] = 0.376449
|
||||
corr_pos[34] = 0.373195
|
||||
corr_pos[35] = 0.369588
|
||||
corr_pos[36] = 0.343021
|
||||
corr_pos[37] = 0.336754
|
||||
corr_pos[38] = 0.330310
|
||||
corr_pos[39] = 0.323447
|
||||
corr_pos[40] = 0.281171
|
||||
corr_pos[41] = 0.272735
|
||||
corr_pos[42] = 0.263888
|
||||
corr_pos[43] = 0.254469
|
||||
corr_pos[44] = 0.203024
|
||||
corr_pos[45] = 0.192980
|
||||
corr_pos[46] = 0.182359
|
||||
corr_pos[47] = 0.170780
|
||||
corr_pos[48] = 0.111580
|
||||
corr_pos[49] = 0.101222
|
||||
corr_pos[50] = 0.090051
|
||||
corr_pos[51] = 0.077918
|
||||
corr_pos[52] = 0.015225
|
||||
corr_pos[53] = 0.003876
|
||||
corr_pos[54] = -0.007756
|
||||
corr_pos[55] = -0.020383
|
||||
corr_pos[56] = -0.084910
|
||||
corr_pos[57] = -0.096848
|
||||
corr_pos[58] = -0.108732
|
||||
corr_pos[59] = -0.121432
|
||||
corr_pos[60] = -0.184349
|
||||
corr_pos[61] = -0.195904
|
||||
corr_pos[62] = -0.207113
|
||||
corr_pos[63] = -0.219319
|
||||
corr_pos[64] = -0.274799
|
||||
corr_pos[65] = -0.284942
|
||||
corr_pos[66] = -0.294809
|
||||
corr_pos[67] = -0.305437
|
||||
corr_pos[68] = -0.352114
|
||||
corr_pos[69] = -0.359689
|
||||
corr_pos[70] = -0.367451
|
||||
corr_pos[71] = -0.375629
|
||||
corr_pos[72] = -0.404347
|
||||
corr_pos[73] = -0.407683
|
||||
corr_pos[74] = -0.410857
|
||||
corr_pos[75] = -0.414904
|
||||
corr_pos[76] = -0.424625
|
||||
corr_pos[77] = -0.424357
|
||||
corr_pos[78] = -0.424411
|
||||
corr_pos[79] = -0.424615
|
||||
corr_pos[80] = -0.421392
|
||||
corr_pos[81] = -0.419977
|
||||
corr_pos[82] = -0.417860
|
||||
corr_pos[83] = -0.416400
|
||||
corr_pos[84] = -0.398108
|
||||
corr_pos[85] = -0.392613
|
||||
corr_pos[86] = -0.386455
|
||||
corr_pos[87] = -0.380338
|
||||
corr_pos[88] = -0.341288
|
||||
corr_pos[89] = -0.332298
|
||||
corr_pos[90] = -0.322767
|
||||
corr_pos[91] = -0.311630
|
||||
corr_pos[92] = -0.256798
|
||||
corr_pos[93] = -0.244808
|
||||
corr_pos[94] = -0.232932
|
||||
corr_pos[95] = -0.219785
|
||||
corr_pos[96] = -0.158685
|
||||
corr_pos[97] = -0.146198
|
||||
corr_pos[98] = -0.130743
|
||||
corr_pos[99] = -0.054066
|
||||
corr_pos[100] = -0.001498
|
||||
corr_pos[101] = 0.012010
|
||||
corr_pos[102] = 0.025195
|
||||
corr_pos[103] = 0.094982
|
||||
corr_pos[104] = 0.109235
|
||||
corr_pos[105] = 0.120813
|
||||
corr_pos[106] = 0.179893
|
||||
corr_pos[107] = 0.192147
|
||||
corr_pos[108] = 0.201902
|
||||
corr_pos[109] = 0.245092
|
||||
corr_pos[110] = 0.250501
|
||||
corr_pos[111] = 0.255536
|
||||
corr_pos[112] = 0.280598
|
||||
corr_pos[113] = 0.280673
|
||||
corr_pos[114] = 0.282529
|
||||
corr_pos[115] = 0.293286
|
||||
corr_pos[116] = 0.292713
|
||||
corr_pos[117] = 0.291123
|
||||
corr_pos[118] = 0.288721
|
||||
corr_pos[119] = 0.288260
|
||||
corr_pos[120] = 0.286480
|
||||
corr_pos[121] = 0.271630
|
||||
corr_pos[122] = 0.268004
|
||||
corr_pos[123] = 0.265418
|
||||
corr_pos[124] = 0.239187
|
||||
corr_pos[125] = 0.233224
|
||||
corr_pos[126] = 0.226652
|
||||
corr_pos[127] = 0.189034
|
||||
corr_pos[128] = 0.180302
|
||||
corr_pos[129] = 0.170931
|
||||
corr_pos[130] = 0.125760
|
||||
corr_pos[131] = 0.116433
|
||||
corr_pos[132] = 0.106845
|
||||
corr_pos[133] = 0.057551
|
||||
corr_pos[134] = 0.048531
|
||||
corr_pos[135] = 0.038276
|
||||
corr_pos[136] = -0.012292
|
||||
corr_pos[137] = -0.021223
|
||||
corr_pos[138] = -0.030046
|
||||
corr_pos[139] = -0.077647
|
||||
corr_pos[140] = -0.085863
|
||||
corr_pos[141] = -0.093816
|
||||
corr_pos[142] = -0.138532
|
||||
corr_pos[143] = -0.145584
|
||||
corr_pos[144] = -0.152699
|
||||
corr_pos[145] = -0.192594
|
||||
corr_pos[146] = -0.200980
|
||||
corr_pos[147] = -0.208816
|
||||
corr_pos[148] = -0.241983
|
||||
corr_pos[149] = -0.248319
|
||||
corr_pos[150] = -0.253789
|
||||
corr_pos[151] = -0.276869
|
||||
corr_pos[152] = -0.279240
|
||||
corr_pos[153] = -0.281538
|
||||
corr_pos[154] = -0.294317
|
||||
corr_pos[155] = -0.295586
|
||||
corr_pos[156] = -0.296029
|
||||
corr_pos[157] = -0.297443
|
||||
corr_pos[158] = -0.297185
|
||||
corr_pos[159] = -0.295946
|
||||
corr_pos[160] = -0.284463
|
||||
corr_pos[161] = -0.282598
|
||||
corr_pos[162] = -0.281260
|
||||
corr_pos[163] = -0.256509
|
||||
corr_pos[164] = -0.250326
|
||||
corr_pos[165] = -0.244372
|
||||
corr_pos[166] = -0.204043
|
||||
corr_pos[167] = -0.196147
|
||||
corr_pos[168] = -0.187928
|
||||
corr_pos[169] = -0.131831
|
||||
corr_pos[170] = -0.121351
|
||||
corr_pos[171] = -0.110548
|
||||
corr_pos[172] = -0.038951
|
||||
corr_pos[173] = -0.025887
|
||||
corr_pos[174] = -0.012501
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -14,8 +14,6 @@ from typeguard import typechecked
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import cSAXSBeamlineChecks
|
||||
from csaxs_bec.bec_ipython_client.plugins.flomni.flomni_optics_mixin import FlomniOpticsMixin
|
||||
from csaxs_bec.bec_ipython_client.plugins.flomni.x_ray_eye_align import XrayEyeAlign
|
||||
from csaxs_bec.bec_ipython_client.plugins.flomni.gui_tools import flomniGuiTools
|
||||
from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import OMNYTools
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
@@ -26,57 +24,27 @@ if builtins.__dict__.get("bec") is not None:
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
|
||||
class FlomniToolsError(Exception):
|
||||
pass
|
||||
|
||||
class FlomniInitError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FlomniError(Exception):
|
||||
pass
|
||||
|
||||
class FlomniTools:
|
||||
def yesno(self, message: str, default="none", autoconfirm=0) -> bool:
|
||||
if autoconfirm and default == "y":
|
||||
self.printgreen(message + " Automatically confirming default: yes")
|
||||
return True
|
||||
elif autoconfirm and default == "n":
|
||||
self.printgreen(message + " Automatically confirming default: no")
|
||||
return False
|
||||
if default == "y":
|
||||
message_ending = " [Y]/n? "
|
||||
elif default == "n":
|
||||
message_ending = " y/[N]? "
|
||||
else:
|
||||
message_ending = " y/n? "
|
||||
while True:
|
||||
user_input = input(self.OKBLUE + message + message_ending + self.ENDC)
|
||||
if (
|
||||
user_input == "Y" or user_input == "y" or user_input == "yes" or user_input == "Yes"
|
||||
) or (default == "y" and user_input == ""):
|
||||
return True
|
||||
if (
|
||||
user_input == "N" or user_input == "n" or user_input == "no" or user_input == "No"
|
||||
) or (default == "n" and user_input == ""):
|
||||
return False
|
||||
else:
|
||||
print("Please expicitely confirm y or n.")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class FlomniInitStagesMixin:
|
||||
|
||||
def flomni_init_stages(self):
|
||||
|
||||
if self.OMNYTools.yesno("Starting initialization of flOMNI stages. OK?"):
|
||||
user_input = input("Starting initialization of flOMNI stages. OK? [y/n]")
|
||||
if user_input == "y":
|
||||
print("staring...")
|
||||
else:
|
||||
return
|
||||
|
||||
if self.check_all_axes_of_fomni_referenced():
|
||||
if self.OMNYTools.yesno("All axes are referenced. Continue anyways?"):
|
||||
user_input = input("Continue anyways? [y/n]")
|
||||
if user_input == "y":
|
||||
print("ok then...")
|
||||
else:
|
||||
return
|
||||
@@ -106,8 +74,10 @@ class FlomniInitStagesMixin:
|
||||
dev.feyex.limits = [-30, -1]
|
||||
print("done")
|
||||
|
||||
|
||||
if self.OMNYTools.yesno("Init of foptz. Can the stage move to the upstream limit without collision?"):
|
||||
user_input = input(
|
||||
"Init of foptz. Can the stage move to the upstream limit without collision? [y/n]"
|
||||
)
|
||||
if user_input == "y":
|
||||
print("good then")
|
||||
else:
|
||||
return
|
||||
@@ -161,7 +131,10 @@ class FlomniInitStagesMixin:
|
||||
dev.fsamy.limits = [2, 3.1]
|
||||
print("done")
|
||||
|
||||
if self.OMNYTools.yesno("Init of tracking stages. Did you remove the outer laser flight tubes?"):
|
||||
user_input = input(
|
||||
"Init of tracking stages. Did you remove the outer laser flight tubes? [y/n]"
|
||||
)
|
||||
if user_input == "y":
|
||||
print("good then")
|
||||
else:
|
||||
print("Stopping.")
|
||||
@@ -177,7 +150,8 @@ class FlomniInitStagesMixin:
|
||||
dev.ftrackz.limits = [4.5, 5.5]
|
||||
print("done")
|
||||
|
||||
if self.OMNYTools.yesno("Init of sample stage. Is the piezo at about 0 deg?"):
|
||||
user_input = input("Init of sample stage. Is the piezo at about 0 deg? [y/n]")
|
||||
if user_input == "y":
|
||||
print("good then")
|
||||
else:
|
||||
print("Stopping.")
|
||||
@@ -194,7 +168,11 @@ class FlomniInitStagesMixin:
|
||||
print("done")
|
||||
|
||||
print("Initializing UPR stage.")
|
||||
if self.OMNYTools.yesno("To ensure that the end switches work, please check that they are currently not pushed. Is everything okay?"):
|
||||
user_input = input(
|
||||
"To ensure that the end switches work, please check that they are currently not pushed."
|
||||
" Is everything okay? [y/n]"
|
||||
)
|
||||
if user_input == "y":
|
||||
print("good then")
|
||||
else:
|
||||
print("Stopping.")
|
||||
@@ -215,7 +193,8 @@ class FlomniInitStagesMixin:
|
||||
time.sleep(1)
|
||||
continue
|
||||
break
|
||||
if self.OMNYTools.yesno("Shall I start the index search?"):
|
||||
user_input = input("Shall I start the index search? [y/n]")
|
||||
if user_input == "y":
|
||||
print("good then. Starting index search.")
|
||||
else:
|
||||
print("Stopping.")
|
||||
@@ -234,7 +213,11 @@ class FlomniInitStagesMixin:
|
||||
dev.fsamroy.limits = [-5, 365]
|
||||
print("done")
|
||||
|
||||
if self.OMNYTools.yesno("Init of foptx. Can the stage move to the positive limit without collision? Attention: tracker flight tube!"):
|
||||
user_input = input(
|
||||
"Init of foptx. Can the stage move to the positive limit without collision? Attention:"
|
||||
" tracker flight tube! [y/n]"
|
||||
)
|
||||
if user_input == "y":
|
||||
print("good then")
|
||||
else:
|
||||
print("Stopping.")
|
||||
@@ -258,7 +241,8 @@ class FlomniInitStagesMixin:
|
||||
continue
|
||||
break
|
||||
|
||||
if self.OMNYTools.yesno("Start limit switch search of fopty?"):
|
||||
user_input = input("Start limit switch search of fopty? [y/n]")
|
||||
if user_input == "y":
|
||||
print("good then")
|
||||
else:
|
||||
print("Stopping.")
|
||||
@@ -291,7 +275,8 @@ class FlomniInitStagesMixin:
|
||||
return False
|
||||
|
||||
def set_limits(self):
|
||||
if self.OMNYTools.yesno("Set default limits for flOMNI?"):
|
||||
user_input = input("Set default limits for flOMNI? [y/n]")
|
||||
if user_input == "y":
|
||||
print("setting limits...")
|
||||
else:
|
||||
print("Stopping.")
|
||||
@@ -318,12 +303,12 @@ class FlomniInitStagesMixin:
|
||||
dev.ftrackz.limits = [4.5, 5.5]
|
||||
|
||||
def _align_setup(self):
|
||||
if self.OMNYTools.yesno("Start moving stages to default initial positions?", "y"):
|
||||
user_input = input("Start moving stages to default initial positions? [y/n]")
|
||||
if user_input == "y":
|
||||
print("Start moving stages...")
|
||||
else:
|
||||
print("Stopping.")
|
||||
return
|
||||
dev.rtx.controller.feedback_disable()
|
||||
# positions for optics out and 50 mm distance to sample
|
||||
umv(dev.ftrackz, 4.73, dev.ftracky, 2.5170, dev.foptx, -14.3, dev.fopty, 3.87)
|
||||
|
||||
@@ -411,8 +396,7 @@ class FlomniSampleTransferMixin:
|
||||
raise FlomniError("Ftray is not at the 'IN' position. Aborting.")
|
||||
|
||||
def ftransfer_flomni_stage_in(self):
|
||||
sample_in_position = dev.flomni_samples.is_sample_slot_used(0)
|
||||
#bool(float(dev.flomni_samples.sample_placed.sample0.get()))
|
||||
sample_in_position = bool(float(dev.flomni_samples.sample_placed.sample0.get()))
|
||||
if not sample_in_position:
|
||||
raise FlomniError("There is no sample in the sample stage. Aborting.")
|
||||
self.reset_correction()
|
||||
@@ -425,15 +409,13 @@ class FlomniSampleTransferMixin:
|
||||
umv(dev.fsamx, fsamx_in)
|
||||
dev.fsamx.limits = [fsamx_in - 0.4, fsamx_in + 0.4]
|
||||
|
||||
self.flomnigui_idle()
|
||||
|
||||
def laser_tracker_show_all(self):
|
||||
dev.rtx.controller.laser_tracker_show_all()
|
||||
|
||||
def laser_tracker_on(self):
|
||||
dev.rtx.controller.laser_tracker_on()
|
||||
time.sleep(0.2)
|
||||
dev.rtx.controller.laser_tracker_check_signalstrength()
|
||||
self._laser_tracker_check_signalstrength()
|
||||
|
||||
def laser_tracker_off(self):
|
||||
dev.rtx.controller.laser_tracker_off()
|
||||
@@ -441,18 +423,18 @@ class FlomniSampleTransferMixin:
|
||||
def show_signal_strength_interferometer(self):
|
||||
dev.rtx.controller.show_signal_strength_interferometer()
|
||||
|
||||
def feedback_disable(self):
|
||||
def rt_feedback_disable(self):
|
||||
self.device_manager.devices.rtx.controller.feedback_disable()
|
||||
|
||||
def feedback_enable_with_reset(self):
|
||||
def rt_feedback_enable_with_reset(self):
|
||||
self.device_manager.devices.rtx.controller.feedback_enable_with_reset()
|
||||
self.feedback_status()
|
||||
self.rt_feedback_status()
|
||||
|
||||
def feedback_enable_without_reset(self):
|
||||
def rt_feedback_enable_without_reset(self):
|
||||
self.device_manager.devices.rtx.controller.feedback_enable_without_reset()
|
||||
self.feedback_status()
|
||||
self.rt_feedback_status()
|
||||
|
||||
def feedback_status(self):
|
||||
def rt_feedback_status(self):
|
||||
feedback_status = self.device_manager.devices.rtx.controller.feedback_is_running()
|
||||
if feedback_status == True:
|
||||
print("The rt feedback is \x1b[92mrunning\x1b[0m.")
|
||||
@@ -466,17 +448,13 @@ class FlomniSampleTransferMixin:
|
||||
self.device_manager.devices.fsamx.controller.lights_on()
|
||||
|
||||
def ftransfer_flomni_stage_out(self):
|
||||
|
||||
self.flomnigui_show_cameras()
|
||||
self.flomnigui_raise()
|
||||
|
||||
target_pos = -162
|
||||
if np.isclose(dev.fsamx.readback.get(), target_pos, 0.01):
|
||||
return
|
||||
|
||||
umv(dev.fsamroy, 0)
|
||||
|
||||
self.feedback_disable()
|
||||
self.rt_feedback_disable()
|
||||
|
||||
self.ensure_fheater_up()
|
||||
|
||||
@@ -517,20 +495,22 @@ class FlomniSampleTransferMixin:
|
||||
self.check_tray_in()
|
||||
self.check_sensor_connected()
|
||||
|
||||
sample_in_gripper = dev.flomni_samples.is_sample_in_gripper()
|
||||
sample_in_gripper = bool(float(dev.flomni_samples.sample_in_gripper.get()))
|
||||
if sample_in_gripper:
|
||||
raise FlomniError(
|
||||
"The gripper does carry a sample. Cannot proceed getting another sample."
|
||||
)
|
||||
|
||||
sample_in_position = dev.flomni_samples.is_sample_slot_used(position)
|
||||
sample_signal = getattr(dev.flomni_samples.sample_placed, f"sample{position}")
|
||||
sample_in_position = bool(float(sample_signal.get()))
|
||||
if not sample_in_position:
|
||||
raise FlomniError(f"The planned pick position [{position}] does not have a sample.")
|
||||
|
||||
self.flomnigui_show_cameras()
|
||||
|
||||
|
||||
if self.OMNYTools.yesno("Please confirm that there is currently no sample in the gripper. It would be dropped!", "y"):
|
||||
user_input = input(
|
||||
"Please confirm that there is currently no sample in the gripper. It would be dropped!"
|
||||
" [y/n]"
|
||||
)
|
||||
if user_input == "y":
|
||||
print("good then")
|
||||
else:
|
||||
print("Stopping.")
|
||||
@@ -574,12 +554,12 @@ class FlomniSampleTransferMixin:
|
||||
self.check_tray_in()
|
||||
self.check_sensor_connected()
|
||||
|
||||
sample_in_gripper = dev.flomni_samples.is_sample_in_gripper()
|
||||
#bool(float(dev.flomni_samples.sample_in_gripper.get()))
|
||||
sample_in_gripper = bool(float(dev.flomni_samples.sample_in_gripper.get()))
|
||||
if not sample_in_gripper:
|
||||
raise FlomniError("The gripper does not carry a sample.")
|
||||
|
||||
sample_in_position = dev.flomni_samples.is_sample_slot_used(position)
|
||||
sample_signal = getattr(dev.flomni_samples.sample_placed, f"sample{position}")
|
||||
sample_in_position = bool(float(sample_signal.get()))
|
||||
if sample_in_position:
|
||||
raise FlomniError(f"The planned put position [{position}] already has a sample.")
|
||||
|
||||
@@ -612,9 +592,8 @@ class FlomniSampleTransferMixin:
|
||||
self.flomni_modify_storage_non_interactive(100, 0, "-")
|
||||
self.flomni_modify_storage_non_interactive(position, 1, sample_name)
|
||||
|
||||
if position == 0:
|
||||
self.ftransfer_flomni_stage_in()
|
||||
bec.queue.next_dataset_number += 1
|
||||
# TODO: flomni_stage_in if position == 0
|
||||
# bec.queue.next_dataset_number += 1
|
||||
|
||||
def sample_get_name(self, position: int = 0) -> str:
|
||||
"""
|
||||
@@ -625,51 +604,36 @@ class FlomniSampleTransferMixin:
|
||||
|
||||
def ftransfer_sample_change(self, new_sample_position: int):
|
||||
self.check_tray_in()
|
||||
# sample_in_gripper = dev.flomni_samples.sample_in_gripper.get()
|
||||
sample_in_gripper = dev.flomni_samples.is_sample_in_gripper()
|
||||
sample_in_gripper = dev.flomni_samples.sample_in_gripper.get()
|
||||
if sample_in_gripper:
|
||||
raise FlomniError("There is already a sample in the gripper. Aborting.")
|
||||
|
||||
self.check_position_is_valid(new_sample_position)
|
||||
|
||||
if new_sample_position == 0:
|
||||
raise FlomniError("The new sample to place cannot be the sample in the sample stage. Aborting.")
|
||||
|
||||
# sample_placed = getattr(
|
||||
# dev.flomni_samples.sample_placed, f"sample{new_sample_position}"
|
||||
# ).get()
|
||||
sample_placed = dev.flomni_samples.is_sample_slot_used(new_sample_position)
|
||||
sample_placed = getattr(
|
||||
dev.flomni_samples.sample_placed, f"sample{new_sample_position}"
|
||||
).get()
|
||||
if not sample_placed:
|
||||
raise FlomniError(
|
||||
f"There is currently no sample in position [{new_sample_position}]. Aborting."
|
||||
)
|
||||
|
||||
# sample_in_sample_stage = dev.flomni_samples.sample_placed.sample0.get()
|
||||
sample_in_sample_stage = dev.flomni_samples.is_sample_slot_used(0)
|
||||
sample_in_sample_stage = dev.flomni_samples.sample_placed.sample0.get()
|
||||
if sample_in_sample_stage:
|
||||
# find a new home for the sample...
|
||||
empty_slots = []
|
||||
# for name, val in dev.flomni_samples.read().items():
|
||||
# if "flomni_samples_sample_placed_sample" not in name:
|
||||
# continue
|
||||
# if val.get("value") == 0:
|
||||
# empty_slots.append(int(name.split("flomni_samples_sample_placed_sample")[1]))
|
||||
for j in range(1,20):
|
||||
if not dev.flomni_samples.is_sample_slot_used(j):
|
||||
empty_slots.append(j)
|
||||
for name, val in dev.flomni_samples.read().items():
|
||||
if "flomni_samples_sample_placed_sample" not in name:
|
||||
continue
|
||||
if val.get("value") == 0:
|
||||
empty_slots.append(int(name.split("flomni_samples_sample_placed_sample")[1]))
|
||||
if not empty_slots:
|
||||
raise FlomniError("There are no empty slots available. Aborting.")
|
||||
|
||||
print(f"The following slots are empty: {empty_slots}.")
|
||||
|
||||
while True:
|
||||
user_input = input(f"Where shall I put the sample? Default: [{empty_slots[0]}] ")
|
||||
|
||||
if user_input.strip() == "":
|
||||
# No entry: use default
|
||||
user_input = empty_slots[0]
|
||||
break
|
||||
|
||||
user_input = input(f"Where shall I put the sample? Default: [{empty_slots[0]}]")
|
||||
try:
|
||||
user_input = int(user_input)
|
||||
if user_input not in empty_slots:
|
||||
@@ -735,20 +699,20 @@ class FlomniSampleTransferMixin:
|
||||
if confirm != -1:
|
||||
return
|
||||
|
||||
if self.OMNYTools.yesno("All OK? Continue?", "y"):
|
||||
user_input = input("All OK? Continue? [y/n]")
|
||||
if user_input == "y":
|
||||
print("good then")
|
||||
dev.ftransy.controller.socket_put_confirmed("confirm=1")
|
||||
else:
|
||||
print("Stopping.")
|
||||
exit
|
||||
return
|
||||
|
||||
def ftransfer_gripper_is_open(self) -> bool:
|
||||
status = bool(float(dev.ftransy.controller.socket_put_and_receive("MG @OUT[9]").strip()))
|
||||
return status
|
||||
|
||||
def ftransfer_gripper_open(self):
|
||||
sample_in_gripper = dev.flomni_samples.is_sample_in_gripper()
|
||||
#dev.flomni_samples.sample_in_gripper.get()
|
||||
sample_in_gripper = dev.flomni_samples.sample_in_gripper.get()
|
||||
if sample_in_gripper:
|
||||
raise FlomniError(
|
||||
"Cannot open gripper. There is still a sample in the gripper! Aborting."
|
||||
@@ -768,8 +732,11 @@ class FlomniSampleTransferMixin:
|
||||
|
||||
fsamx_pos = dev.fsamx.readback.get()
|
||||
if position == 0 and fsamx_pos > -160:
|
||||
|
||||
if self.OMNYTools.yesno("May the flomni stage be moved out for the sample change? Feedback will be disabled and alignment will be lost!", "y"):
|
||||
user_input = input(
|
||||
"May the flomni stage be moved out for the sample change? Feedback will be disabled"
|
||||
" and alignment will be lost! [y/n]"
|
||||
)
|
||||
if user_input == "y":
|
||||
print("good then")
|
||||
self.ftransfer_flomni_stage_out()
|
||||
else:
|
||||
@@ -924,20 +891,7 @@ class FlomniSampleTransferMixin:
|
||||
|
||||
|
||||
class FlomniAlignmentMixin:
|
||||
import csaxs_bec
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# Ensure this is a Path object, not a string
|
||||
csaxs_bec_basepath = Path(csaxs_bec.__file__)
|
||||
|
||||
default_correction_file_rel = "correction_flomni_20210300_360deg.txt"
|
||||
|
||||
# Build the absolute path correctly
|
||||
default_correction_file = (
|
||||
csaxs_bec_basepath.parent / 'bec_ipython_client' / 'plugins' / 'flomni' / default_correction_file_rel
|
||||
).resolve()
|
||||
|
||||
default_correction_file = "correction_flomni_20210300_360deg.txt"
|
||||
|
||||
def reset_correction(self, use_default_correction=True):
|
||||
"""
|
||||
@@ -1096,7 +1050,7 @@ class FlomniAlignmentMixin:
|
||||
value = line.split(" ")[2]
|
||||
name = line.split(" ")[0].split("[")[0]
|
||||
if name == "corr_pos":
|
||||
corr_pos.append(float(value))
|
||||
corr_pos.append(float(value) / 1000)
|
||||
elif name == "corr_angle":
|
||||
corr_angle.append(float(value))
|
||||
print(
|
||||
@@ -1151,7 +1105,6 @@ class Flomni(
|
||||
FlomniAlignmentMixin,
|
||||
FlomniOpticsMixin,
|
||||
cSAXSBeamlineChecks,
|
||||
flomniGuiTools
|
||||
):
|
||||
def __init__(self, client):
|
||||
super().__init__()
|
||||
@@ -1174,36 +1127,29 @@ class Flomni(
|
||||
self.corr_pos_y_2 = []
|
||||
self.corr_angle_y_2 = []
|
||||
self.progress = {}
|
||||
self.progress["subtomo"] = 0
|
||||
self.progress["subtomo_projection"] = 0
|
||||
self.progress["subtomo_total_projections"] = 1
|
||||
self.progress["projection"] = 0
|
||||
self.progress["total_projections"] = 1
|
||||
self.progress["angle"] = 0
|
||||
self.progress["tomo_type"] = 0
|
||||
self.OMNYTools = OMNYTools(self.client)
|
||||
self.align = XrayEyeAlign(self.client, self)
|
||||
self.set_client(client)
|
||||
|
||||
def start_x_ray_eye_alignment(self, keep_shutter_open=False):
|
||||
|
||||
if self.OMNYTools.yesno("Starting Xrayeye alignment. Deleting any potential existing alignment for this sample.", "y"):
|
||||
def start_x_ray_eye_alignment(self):
|
||||
user_input = input(
|
||||
"Starting Xrayeye alignment. Deleting any potential existing alignment for this sample. [Y/n]"
|
||||
)
|
||||
if user_input == "y" or user_input == "":
|
||||
self.align = XrayEyeAlign(self.client, self)
|
||||
try:
|
||||
self.align.align(keep_shutter_open)
|
||||
self.align.align()
|
||||
except KeyboardInterrupt as exc:
|
||||
fsamx_in = self._get_user_param_safe(dev.fsamx, "in")
|
||||
if np.isclose(fsamx_in, dev.fsamx.readback.get(), 0.5):
|
||||
print("Stopping alignment. Returning to fsamx in position.")
|
||||
self.feedback_disable()
|
||||
self.rt_feedback_disable()
|
||||
umv(dev.fsamx, fsamx_in)
|
||||
raise exc
|
||||
|
||||
def xrayeye_update_frame(self,keep_shutter_open=False):
|
||||
self.align.update_frame(keep_shutter_open)
|
||||
def xrayeye_update_frame(self):
|
||||
self.align.update_frame()
|
||||
|
||||
def xrayeye_alignment_start(self, keep_shutter_open=False):
|
||||
self.start_x_ray_eye_alignment(keep_shutter_open)
|
||||
def xrayeye_alignment_start(self):
|
||||
self.start_x_ray_eye_alignment()
|
||||
|
||||
def drive_axis_to_limit(self, device, direction):
|
||||
axis_id = device._config["deviceConfig"].get("axis_Id")
|
||||
@@ -1574,7 +1520,7 @@ class Flomni(
|
||||
angles = np.flip(angles)
|
||||
for angle in angles:
|
||||
self.progress["subtomo"] = subtomo_number
|
||||
self.progress["subtomo_projection"] = np.where(angles == angle)[0][0]
|
||||
self.progress["subtomo_projection"] = angles.index(angle)
|
||||
self.progress["subtomo_total_projections"] = 180 / self.tomo_angle_stepsize
|
||||
self.progress["projection"] = (subtomo_number - 1) * self.progress[
|
||||
"subtomo_total_projections"
|
||||
@@ -1623,9 +1569,6 @@ class Flomni(
|
||||
|
||||
def tomo_scan(self, subtomo_start=1, start_angle=None, projection_number=None):
|
||||
"""start a tomo scan"""
|
||||
|
||||
self.flomnigui_show_progress()
|
||||
|
||||
bec = builtins.__dict__.get("bec")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
self._current_special_angles = self.special_angles.copy()
|
||||
@@ -1757,12 +1700,11 @@ class Flomni(
|
||||
def _print_progress(self):
|
||||
print("\x1b[95mProgress report:")
|
||||
print(f"Tomo type: ....................... {self.progress['tomo_type']}")
|
||||
print(f"Projection: ...................... {self.progress['projection']:.0f}")
|
||||
print(f"Projection: ...................... {self.progress['projection']}")
|
||||
print(f"Total projections expected ....... {self.progress['total_projections']}")
|
||||
print(f"Angle: ........................... {self.progress['angle']}")
|
||||
print(f"Current subtomo: ................. {self.progress['subtomo']}")
|
||||
print(f"Current projection within subtomo: {self.progress['subtomo_projection']}\x1b[0m")
|
||||
self._flomnigui_update_progress()
|
||||
|
||||
def add_sample_database(
|
||||
self, samplename, date, eaccount, scan_number, setup, sample_additional_info, user
|
||||
@@ -1852,7 +1794,7 @@ class Flomni(
|
||||
|
||||
def _write_tomo_scan_number(self, scan_number: int, angle: float, subtomo_number: int) -> None:
|
||||
tomo_scan_numbers_file = os.path.expanduser(
|
||||
"~/tomography_scannumbers.txt"
|
||||
"~/Data10/specES1/dat-files/tomography_scannumbers.txt"
|
||||
)
|
||||
with open(tomo_scan_numbers_file, "a+") as out_file:
|
||||
# pylint: disable=undefined-variable
|
||||
@@ -1951,8 +1893,8 @@ class Flomni(
|
||||
)
|
||||
print(f"\nSample name: {self.sample_name}\n")
|
||||
|
||||
|
||||
if self.OMNYTools.yesno("Are these parameters correctly set for your scan?", "y"):
|
||||
user_input = input("Are these parameters correctly set for your scan? [Y/n]")
|
||||
if user_input == "y" or user_input == "":
|
||||
print("... excellent!")
|
||||
else:
|
||||
self.tomo_countingtime = self._get_val("<ctime> s", self.tomo_countingtime, float)
|
||||
|
||||
@@ -33,7 +33,7 @@ class FlomniOpticsMixin:
|
||||
feyex_in = self._get_user_param_safe("feyex", "in")
|
||||
feyey_in = self._get_user_param_safe("feyey", "in")
|
||||
umv(dev.feyex, feyex_in, dev.feyey, feyey_in)
|
||||
#self.align.update_frame()
|
||||
self.align.update_frame()
|
||||
|
||||
def _ffzp_in(self):
|
||||
foptx_in = self._get_user_param_safe("foptx", "in")
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
import builtins
|
||||
|
||||
from bec_widgets.cli.client import BECDockArea
|
||||
|
||||
# from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose
|
||||
|
||||
if builtins.__dict__.get("bec") is not None:
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
umv = builtins.__dict__.get("umv")
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
|
||||
class flomniGuiToolsError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class flomniGuiTools:
|
||||
|
||||
def __init__(self):
|
||||
self.text_box = None
|
||||
self.progressbar = None
|
||||
|
||||
def set_client(self, client):
|
||||
self.client = client
|
||||
self.gui = self.client.gui
|
||||
|
||||
def flomnigui_show_gui(self):
|
||||
if "flomni" in self.gui.windows:
|
||||
self.gui.flomni.show()
|
||||
else:
|
||||
self.gui.new("flomni")
|
||||
|
||||
def flomnigui_stop_gui(self):
|
||||
self.gui.flomni.hide()
|
||||
|
||||
def flomnigui_raise(self):
|
||||
self.gui.flomni.raise_window()
|
||||
|
||||
# def flomnigui_show_xeyealign(self):
|
||||
# self.flomnigui_show_gui()
|
||||
# if self.xeyegui is None:
|
||||
# self.flomnigui_remove_all_docks()
|
||||
# self.xeyegui = self.gui.flomni.new("xeyegui").new("XRayEye")
|
||||
# # start live
|
||||
# if not dev.cam_xeye.live_mode:
|
||||
# dev.cam_xeye.live_mode = True
|
||||
|
||||
def flomnigui_show_xeyealign(self):
|
||||
self.flomnigui_show_gui()
|
||||
if self._flomnigui_check_attribute_not_exists("xeyegui"):
|
||||
self.flomnigui_remove_all_docks()
|
||||
self.xeyegui = self.gui.flomni.new("xeyegui").new("XRayEye")
|
||||
# start live
|
||||
if not dev.cam_xeye.live_mode:
|
||||
dev.cam_xeye.live_mode = True
|
||||
|
||||
|
||||
def _flomnigui_check_attribute_not_exists(self, attribute_name):
|
||||
if hasattr(self.gui,"flomni"):
|
||||
if hasattr(self.gui.flomni,attribute_name):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def flomnigui_show_cameras(self):
|
||||
self.flomnigui_show_gui()
|
||||
if self._flomnigui_check_attribute_not_exists("camera_gripper") or self._flomnigui_check_attribute_not_exists("camera_overview"):
|
||||
self.flomnigui_remove_all_docks()
|
||||
camera_gripper_image = self.gui.flomni.new("camera_gripper").new("Image")
|
||||
if self._flomnicam_check_device_exists(dev.cam_flomni_gripper):
|
||||
camera_gripper_image.image(("cam_flomni_gripper", "preview"))
|
||||
camera_gripper_image.lock_aspect_ratio = True
|
||||
camera_gripper_image.enable_fps_monitor = True
|
||||
camera_gripper_image.enable_toolbar = False
|
||||
camera_gripper_image.outer_axes = False
|
||||
camera_gripper_image.inner_axes = False
|
||||
dev.cam_flomni_gripper.start_live_mode()
|
||||
else:
|
||||
print("Cannot open camera_gripper. Device does not exist.")
|
||||
camera_overview_image = self.gui.flomni.new("camera_overview").new("Image")
|
||||
if self._flomnicam_check_device_exists(dev.cam_flomni_overview):
|
||||
camera_overview_image.image(("cam_flomni_overview", "preview"))
|
||||
camera_overview_image.lock_aspect_ratio = True
|
||||
camera_overview_image.enable_fps_monitor = True
|
||||
camera_overview_image.enable_toolbar = False
|
||||
camera_overview_image.outer_axes = False
|
||||
camera_overview_image.inner_axes = False
|
||||
dev.cam_flomni_overview.start_live_mode()
|
||||
else:
|
||||
print("Cannot open camera_overview. Device does not exist.")
|
||||
|
||||
def flomnigui_remove_all_docks(self):
|
||||
#dev.cam_flomni_overview.stop_live_mode()
|
||||
#dev.cam_flomni_gripper.stop_live_mode()
|
||||
#dev.cam_xeye.live_mode = False
|
||||
self.gui.flomni.delete_all()
|
||||
self.progressbar = None
|
||||
self.text_box = None
|
||||
|
||||
def flomnigui_idle(self):
|
||||
self.flomnigui_show_gui()
|
||||
if self._flomnigui_check_attribute_not_exists("idle_text_box"):
|
||||
self.flomnigui_remove_all_docks()
|
||||
idle_text_box = self.gui.flomni.new("idle_textbox").new("TextBox")
|
||||
text = (
|
||||
"<pre>"
|
||||
+ " ,---.,--. ,-----. ,--. ,--.,--. ,--.,--. \n"
|
||||
+ "/ .-'| |' .-. '| `.' || ,'.| || | \n"
|
||||
+ "| `-,| || | | || |'.'| || |' ' || | \n"
|
||||
+ "| .-'| |' '-' '| | | || | ` || | \n"
|
||||
+ "`--' `--' `-----' `--' `--'`--' `--'`--' \n"
|
||||
+ "</pre>"
|
||||
)
|
||||
idle_text_box.set_html_text(text)
|
||||
|
||||
def flomnigui_docs(self, filename: str | None = None):
|
||||
import csaxs_bec
|
||||
from pathlib import Path
|
||||
|
||||
print("The general flOMNI documentation is at \nhttps://sls-csaxs.readthedocs.io/en/latest/user/ptychography/flomni.html#user-ptychography-flomni")
|
||||
|
||||
csaxs_bec_basepath = Path(csaxs_bec.__file__).parent
|
||||
docs_folder = (
|
||||
csaxs_bec_basepath /
|
||||
"bec_ipython_client" / "plugins" / "flomni" / "docs"
|
||||
)
|
||||
|
||||
if not docs_folder.is_dir():
|
||||
raise NotADirectoryError(f"Docs folder not found: {docs_folder}")
|
||||
|
||||
pdfs = sorted(docs_folder.glob("*.pdf"))
|
||||
if not pdfs:
|
||||
raise FileNotFoundError(f"No PDF files found in {docs_folder}")
|
||||
|
||||
# --- Resolve PDF ------------------------------------------------------
|
||||
if filename is not None:
|
||||
pdf_file = docs_folder / filename
|
||||
if not pdf_file.exists():
|
||||
raise FileNotFoundError(f"Requested file not found: {filename}")
|
||||
else:
|
||||
print("\nAvailable flOMNI documentation PDFs:\n")
|
||||
for i, pdf in enumerate(pdfs, start=1):
|
||||
print(f" {i:2d}) {pdf.name}")
|
||||
print()
|
||||
|
||||
while True:
|
||||
try:
|
||||
choice = int(input(f"Select a file (1–{len(pdfs)}): "))
|
||||
if 1 <= choice <= len(pdfs):
|
||||
pdf_file = pdfs[choice - 1]
|
||||
break
|
||||
print(f"Enter a number between 1 and {len(pdfs)}.")
|
||||
except ValueError:
|
||||
print("Invalid input. Please enter a number.")
|
||||
|
||||
# --- GUI handling (active existence check) ----------------------------
|
||||
self.flomnigui_show_gui()
|
||||
|
||||
if self._flomnigui_check_attribute_not_exists("PdfViewerWidget"):
|
||||
self.flomnigui_remove_all_docks()
|
||||
self.pdf_viewer = self.gui.flomni.new(widget="PdfViewerWidget")
|
||||
|
||||
# --- Load PDF ---------------------------------------------------------
|
||||
self.pdf_viewer.PdfViewerWidget.load_pdf(str(pdf_file.resolve()))
|
||||
print(f"\nLoaded: {pdf_file.name}\n")
|
||||
|
||||
|
||||
def _flomnicam_check_device_exists(self, device):
|
||||
try:
|
||||
device
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def flomnigui_show_progress(self):
|
||||
self.flomnigui_show_gui()
|
||||
if self._flomnigui_check_attribute_not_exists("progressbar"):
|
||||
self.flomnigui_remove_all_docks()
|
||||
# Add a new dock with a RingProgressBar widget
|
||||
self.progressbar = self.gui.flomni.new("progressbar").new("RingProgressBar")
|
||||
# Customize the size of the progress ring
|
||||
self.progressbar.set_line_widths(20)
|
||||
# Disable automatic updates and manually set the self.progressbar value
|
||||
self.progressbar.enable_auto_updates(False)
|
||||
# Set precision for the self.progressbar display
|
||||
self.progressbar.set_precision(1) # Display self.progressbar with one decimal places
|
||||
# Setting multiple rigns with different values
|
||||
self.progressbar.set_number_of_bars(3)
|
||||
self.progressbar.rings[0].set_update("manual")
|
||||
self.progressbar.rings[1].set_update("manual")
|
||||
self.progressbar.rings[2].set_update("scan")
|
||||
# Set the values of the rings to 50, 75, and 25 from outer to inner ring
|
||||
# self.progressbar.set_value([50, 75])
|
||||
# Add a new dock with a TextBox widget
|
||||
self.text_box = self.gui.flomni.new(name="progress_text").new("TextBox")
|
||||
|
||||
self._flomnigui_update_progress()
|
||||
|
||||
def _flomnigui_update_progress(self):
|
||||
if self.progressbar is not None:
|
||||
progress = self.progress["projection"] / self.progress["total_projections"] * 100
|
||||
subtomo_progress = (
|
||||
self.progress["subtomo_projection"]
|
||||
/ self.progress["subtomo_total_projections"]
|
||||
* 100
|
||||
)
|
||||
self.progressbar.set_value([progress, subtomo_progress, 0])
|
||||
if self.text_box is not None:
|
||||
text = f"Progress report:\n Tomo type: ....................... {self.progress['tomo_type']}\n Projection: ...................... {self.progress['projection']:.0f}\n Total projections expected ....... {self.progress['total_projections']}\n Angle: ........................... {self.progress['angle']}\n Current subtomo: ................. {self.progress['subtomo']}\n Current projection within subtomo: {self.progress['subtomo_projection']}\n Total projections per subtomo: ... {self.progress['subtomo_total_projections']}"
|
||||
self.text_box.set_plain_text(text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from bec_lib.client import BECClient
|
||||
from bec_widgets.cli.client_utils import BECGuiClient
|
||||
|
||||
client = BECClient()
|
||||
client.start()
|
||||
client.gui = BECGuiClient()
|
||||
|
||||
flomni_gui = flomniGuiTools(client)
|
||||
flomni_gui.flomnigui_show_gui()
|
||||
flomni_gui.flomnigui_show_progress()
|
||||
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from bec_lib import bec_logger
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen
|
||||
|
||||
logger = bec_logger.logger
|
||||
# import builtins to avoid linter errors
|
||||
@@ -22,7 +22,6 @@ if TYPE_CHECKING:
|
||||
|
||||
class XrayEyeAlign:
|
||||
# pixel calibration, multiply to get mm
|
||||
labview=False
|
||||
PIXEL_CALIBRATION = 0.1 / 113 # .2 with binning
|
||||
|
||||
def __init__(self, client, flomni: Flomni) -> None:
|
||||
@@ -41,40 +40,28 @@ class XrayEyeAlign:
|
||||
def save_frame(self):
|
||||
epics_put("XOMNYI-XEYE-SAVFRAME:0", 1)
|
||||
|
||||
def update_frame(self,keep_shutter_open=False):
|
||||
if self.labview:
|
||||
epics_put("XOMNYI-XEYE-ACQDONE:0", 0)
|
||||
|
||||
if not self.labview:
|
||||
self.flomni.flomnigui_show_xeyealign()
|
||||
if not dev.cam_xeye.live_mode:
|
||||
dev.cam_xeye.live_mode = True
|
||||
|
||||
def update_frame(self):
|
||||
epics_put("XOMNYI-XEYE-ACQDONE:0", 0)
|
||||
# start live
|
||||
epics_put("XOMNYI-XEYE-ACQ:0", 1)
|
||||
if self.labview:
|
||||
# wait for start live
|
||||
while epics_get("XOMNYI-XEYE-ACQDONE:0") == 0:
|
||||
time.sleep(0.5)
|
||||
print("waiting for live view to start...")
|
||||
|
||||
# wait for start live
|
||||
while epics_get("XOMNYI-XEYE-ACQDONE:0") == 0:
|
||||
time.sleep(0.5)
|
||||
print("waiting for live view to start...")
|
||||
fshopen()
|
||||
|
||||
if self.labview:
|
||||
epics_put("XOMNYI-XEYE-ACQDONE:0", 0)
|
||||
epics_put("XOMNYI-XEYE-ACQDONE:0", 0)
|
||||
|
||||
while epics_get("XOMNYI-XEYE-ACQDONE:0") == 0:
|
||||
print("waiting for new frame...")
|
||||
time.sleep(0.5)
|
||||
while epics_get("XOMNYI-XEYE-ACQDONE:0") == 0:
|
||||
print("waiting for new frame...")
|
||||
time.sleep(0.5)
|
||||
|
||||
time.sleep(0.5)
|
||||
# stop live view
|
||||
if not keep_shutter_open:
|
||||
epics_put("XOMNYI-XEYE-ACQ:0", 0)
|
||||
time.sleep(0.1)
|
||||
fshclose()
|
||||
print("got new frame")
|
||||
else:
|
||||
print("Staying in live view, shutter is and remains open!")
|
||||
epics_put("XOMNYI-XEYE-ACQ:0", 0)
|
||||
time.sleep(1)
|
||||
# fshclose
|
||||
print("got new frame")
|
||||
|
||||
def tomo_rotate(self, val: float):
|
||||
# pylint: disable=undefined-variable
|
||||
@@ -100,23 +87,12 @@ class XrayEyeAlign:
|
||||
def send_message(self, msg: str):
|
||||
epics_put("XOMNYI-XEYE-MESSAGE:0.DESC", msg)
|
||||
|
||||
def align(self,keep_shutter_open=False):
|
||||
if not keep_shutter_open:
|
||||
print("This routine can be called with paramter keep_shutter_open=True to keep the shutter always open")
|
||||
self.send_message("Getting things ready. Please wait...")
|
||||
|
||||
#potential unresolved movement requests to zero
|
||||
epics_put("XOMNYI-XEYE-MVX:0", 0)
|
||||
epics_put("XOMNYI-XEYE-MVY:0", 0)
|
||||
|
||||
def align(self):
|
||||
# reset shift xy and fov params
|
||||
self._reset_init_values()
|
||||
|
||||
self.flomni.lights_off()
|
||||
|
||||
self.flomni.flomnigui_show_xeyealign()
|
||||
self.flomni.flomnigui_raise()
|
||||
|
||||
self.tomo_rotate(0)
|
||||
epics_put("XOMNYI-XEYE-ANGLE:0", 0)
|
||||
|
||||
@@ -124,7 +100,7 @@ class XrayEyeAlign:
|
||||
|
||||
self.flomni.laser_tracker_on()
|
||||
|
||||
self.flomni.feedback_enable_with_reset()
|
||||
self.flomni.rt_feedback_enable_with_reset()
|
||||
|
||||
# disable movement buttons
|
||||
self.movement_buttons_enabled = False
|
||||
@@ -133,7 +109,7 @@ class XrayEyeAlign:
|
||||
epics_put("XOMNYI-XEYE-SAMPLENAME:0.DESC", sample_name)
|
||||
|
||||
# this makes sure we are in a defined state
|
||||
self.flomni.feedback_disable()
|
||||
self.flomni.rt_feedback_disable()
|
||||
|
||||
epics_put("XOMNYI-XEYE-PIXELSIZE:0", self.PIXEL_CALIBRATION)
|
||||
|
||||
@@ -143,7 +119,7 @@ class XrayEyeAlign:
|
||||
umv(dev.fsamx, fsamx_in - 0.25)
|
||||
|
||||
self.flomni.ffzp_in()
|
||||
self.update_frame(keep_shutter_open)
|
||||
self.update_frame()
|
||||
|
||||
# enable submit buttons
|
||||
self.movement_buttons_enabled = False
|
||||
@@ -167,27 +143,26 @@ class XrayEyeAlign:
|
||||
self.movement_buttons_enabled = False
|
||||
epics_put("XOMNYI-XEYE-SUBMIT:0", -1) # disable submit button
|
||||
|
||||
self.flomni.feedback_disable()
|
||||
self.flomni.rt_feedback_disable()
|
||||
fsamx_in = self.flomni._get_user_param_safe("fsamx", "in")
|
||||
umv(dev.fsamx, fsamx_in)
|
||||
|
||||
self.flomni.foptics_out()
|
||||
|
||||
self.flomni.feedback_disable()
|
||||
self.flomni.rt_feedback_disable()
|
||||
umv(dev.fsamx, fsamx_in - 0.25)
|
||||
|
||||
if self.labview:
|
||||
self.update_frame(keep_shutter_open)
|
||||
epics_put("XOMNYI-XEYE-RECBG:0", 1)
|
||||
while epics_get("XOMNYI-XEYE-RECBG:0") == 1:
|
||||
time.sleep(0.5)
|
||||
print("waiting for background frame...")
|
||||
self.update_frame()
|
||||
epics_put("XOMNYI-XEYE-RECBG:0", 1)
|
||||
while epics_get("XOMNYI-XEYE-RECBG:0") == 1:
|
||||
time.sleep(0.5)
|
||||
print("waiting for background frame...")
|
||||
|
||||
umv(dev.fsamx, fsamx_in)
|
||||
time.sleep(0.5)
|
||||
self.flomni.feedback_enable_with_reset()
|
||||
self.flomni.rt_feedback_enable_with_reset()
|
||||
|
||||
self.update_frame(keep_shutter_open)
|
||||
self.update_frame()
|
||||
self.send_message("Adjust sample height and submit center")
|
||||
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
|
||||
self.movement_buttons_enabled = True
|
||||
@@ -200,7 +175,7 @@ class XrayEyeAlign:
|
||||
umv(dev.rtx, 0)
|
||||
self.tomo_rotate(k * 45)
|
||||
epics_put("XOMNYI-XEYE-ANGLE:0", self.get_tomo_angle())
|
||||
self.update_frame(keep_shutter_open)
|
||||
self.update_frame()
|
||||
self.send_message("Submit sample center")
|
||||
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
|
||||
epics_put("XOMNYI-XEYE-ENAMVX:0", 1)
|
||||
@@ -224,18 +199,18 @@ class XrayEyeAlign:
|
||||
if k > 0:
|
||||
epics_put(f"XOMNYI-XEYE-STAGEPOSX:{k}", dev.rtx.readback.get() / 1000)
|
||||
time.sleep(3)
|
||||
self.update_frame(keep_shutter_open)
|
||||
self.update_frame()
|
||||
|
||||
if k < 2:
|
||||
# allow movements, store movements to calculate center
|
||||
_xrayeyalignmvy = epics_get("XOMNYI-XEYE-MVY:0")
|
||||
if _xrayeyalignmvy != 0:
|
||||
self.flomni.feedback_disable()
|
||||
self.flomni.rt_feedback_disable()
|
||||
umvr(dev.fsamy, _xrayeyalignmvy / 1000)
|
||||
time.sleep(2)
|
||||
epics_put("XOMNYI-XEYE-MVY:0", 0)
|
||||
self.flomni.feedback_enable_with_reset()
|
||||
self.update_frame(keep_shutter_open)
|
||||
self.flomni.rt_feedback_enable_with_reset()
|
||||
self.update_frame()
|
||||
time.sleep(0.2)
|
||||
|
||||
self.write_output()
|
||||
@@ -246,16 +221,8 @@ class XrayEyeAlign:
|
||||
|
||||
umv(dev.rtx, 0)
|
||||
|
||||
# free camera
|
||||
if self.labview:
|
||||
epics_put("XOMNYI-XEYE-ACQ:0", 2)
|
||||
if keep_shutter_open and not self.labview:
|
||||
if self.flomni.OMNYTools.yesno("Close the shutter now?","y"):
|
||||
fshclose()
|
||||
epics_put("XOMNYI-XEYE-ACQ:0", 0)
|
||||
if not self.labview:
|
||||
self.flomni.flomnigui_idle()
|
||||
|
||||
# free camera
|
||||
epics_put("XOMNYI-XEYE-ACQ:0", 2)
|
||||
|
||||
print(
|
||||
f"The largest field of view from the xrayeyealign was \nfovx = {fovx:.0f} microns, fovy"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
from .omny import OMNY
|
||||
@@ -1,715 +0,0 @@
|
||||
corr_elements = 357
|
||||
corr_angle[0] = 0.097400
|
||||
corr_angle[1] = 0.603500
|
||||
corr_angle[2] = 1.134200
|
||||
corr_angle[3] = 1.625000
|
||||
corr_angle[4] = 2.162200
|
||||
corr_angle[5] = 2.700100
|
||||
corr_angle[6] = 3.191600
|
||||
corr_angle[7] = 3.714300
|
||||
corr_angle[8] = 4.223200
|
||||
corr_angle[9] = 4.730900
|
||||
corr_angle[10] = 5.253300
|
||||
corr_angle[11] = 5.743300
|
||||
corr_angle[12] = 6.279200
|
||||
corr_angle[13] = 6.782900
|
||||
corr_angle[14] = 7.301200
|
||||
corr_angle[15] = 7.808100
|
||||
corr_angle[16] = 8.325300
|
||||
corr_angle[17] = 8.859400
|
||||
corr_angle[18] = 9.359400
|
||||
corr_angle[19] = 9.887900
|
||||
corr_angle[20] = 10.395400
|
||||
corr_angle[21] = 10.930700
|
||||
corr_angle[22] = 11.415400
|
||||
corr_angle[23] = 11.928900
|
||||
corr_angle[24] = 12.456900
|
||||
corr_angle[25] = 12.955900
|
||||
corr_angle[26] = 13.479000
|
||||
corr_angle[27] = 13.982700
|
||||
corr_angle[28] = 14.500900
|
||||
corr_angle[29] = 15.016200
|
||||
corr_angle[30] = 15.528000
|
||||
corr_angle[31] = 16.053800
|
||||
corr_angle[32] = 16.562800
|
||||
corr_angle[33] = 17.076600
|
||||
corr_angle[34] = 17.592400
|
||||
corr_angle[35] = 18.094100
|
||||
corr_angle[36] = 18.623800
|
||||
corr_angle[37] = 19.118400
|
||||
corr_angle[38] = 19.655000
|
||||
corr_angle[39] = 20.143900
|
||||
corr_angle[40] = 20.672200
|
||||
corr_angle[41] = 21.171500
|
||||
corr_angle[42] = 21.696300
|
||||
corr_angle[43] = 22.215900
|
||||
corr_angle[44] = 22.728900
|
||||
corr_angle[45] = 23.233100
|
||||
corr_angle[46] = 23.760000
|
||||
corr_angle[47] = 24.267700
|
||||
corr_angle[48] = 24.792100
|
||||
corr_angle[49] = 25.287900
|
||||
corr_angle[50] = 25.824600
|
||||
corr_angle[51] = 26.321900
|
||||
corr_angle[52] = 26.842600
|
||||
corr_angle[53] = 27.337200
|
||||
corr_angle[54] = 27.873500
|
||||
corr_angle[55] = 28.373900
|
||||
corr_angle[56] = 28.895900
|
||||
corr_angle[57] = 29.404900
|
||||
corr_angle[58] = 29.926700
|
||||
corr_angle[59] = 30.439300
|
||||
corr_angle[60] = 30.963700
|
||||
corr_angle[61] = 31.460000
|
||||
corr_angle[62] = 31.989500
|
||||
corr_angle[63] = 32.489700
|
||||
corr_angle[64] = 33.026400
|
||||
corr_angle[65] = 33.516500
|
||||
corr_angle[66] = 34.044000
|
||||
corr_angle[67] = 34.547700
|
||||
corr_angle[68] = 35.070800
|
||||
corr_angle[69] = 35.578400
|
||||
corr_angle[70] = 36.103200
|
||||
corr_angle[71] = 36.603200
|
||||
corr_angle[72] = 37.128200
|
||||
corr_angle[73] = 37.645200
|
||||
corr_angle[74] = 38.164300
|
||||
corr_angle[75] = 38.662100
|
||||
corr_angle[76] = 39.191000
|
||||
corr_angle[77] = 39.693400
|
||||
corr_angle[78] = 40.222500
|
||||
corr_angle[79] = 40.719500
|
||||
corr_angle[80] = 41.247000
|
||||
corr_angle[81] = 41.755700
|
||||
corr_angle[82] = 42.271300
|
||||
corr_angle[83] = 42.767600
|
||||
corr_angle[84] = 43.302200
|
||||
corr_angle[85] = 43.818700
|
||||
corr_angle[86] = 44.330000
|
||||
corr_angle[87] = 44.835300
|
||||
corr_angle[88] = 45.359800
|
||||
corr_angle[89] = 45.867200
|
||||
corr_angle[90] = 46.396200
|
||||
corr_angle[91] = 46.895000
|
||||
corr_angle[92] = 47.411500
|
||||
corr_angle[93] = 47.915200
|
||||
corr_angle[94] = 48.436600
|
||||
corr_angle[95] = 48.946100
|
||||
corr_angle[96] = 49.472300
|
||||
corr_angle[97] = 49.979800
|
||||
corr_angle[98] = 50.503300
|
||||
corr_angle[99] = 51.008700
|
||||
corr_angle[100] = 51.535000
|
||||
corr_angle[101] = 52.036700
|
||||
corr_angle[102] = 52.563700
|
||||
corr_angle[103] = 53.063600
|
||||
corr_angle[104] = 53.591700
|
||||
corr_angle[105] = 54.091200
|
||||
corr_angle[106] = 54.618900
|
||||
corr_angle[107] = 55.116900
|
||||
corr_angle[108] = 55.636100
|
||||
corr_angle[109] = 56.143100
|
||||
corr_angle[110] = 56.672200
|
||||
corr_angle[111] = 57.172900
|
||||
corr_angle[112] = 57.704600
|
||||
corr_angle[113] = 58.204800
|
||||
corr_angle[114] = 58.728600
|
||||
corr_angle[115] = 59.239500
|
||||
corr_angle[116] = 59.768400
|
||||
corr_angle[117] = 60.268300
|
||||
corr_angle[118] = 60.788800
|
||||
corr_angle[119] = 61.289300
|
||||
corr_angle[120] = 61.813300
|
||||
corr_angle[121] = 62.310800
|
||||
corr_angle[122] = 62.836700
|
||||
corr_angle[123] = 63.353700
|
||||
corr_angle[124] = 63.866600
|
||||
corr_angle[125] = 64.377200
|
||||
corr_angle[126] = 64.906600
|
||||
corr_angle[127] = 65.414000
|
||||
corr_angle[128] = 65.937100
|
||||
corr_angle[129] = 66.429400
|
||||
corr_angle[130] = 66.970000
|
||||
corr_angle[131] = 67.459200
|
||||
corr_angle[132] = 67.996400
|
||||
corr_angle[133] = 68.499800
|
||||
corr_angle[134] = 69.014500
|
||||
corr_angle[135] = 69.509500
|
||||
corr_angle[136] = 70.044000
|
||||
corr_angle[137] = 70.543200
|
||||
corr_angle[138] = 71.079400
|
||||
corr_angle[139] = 71.579300
|
||||
corr_angle[140] = 72.103500
|
||||
corr_angle[141] = 72.607000
|
||||
corr_angle[142] = 73.137100
|
||||
corr_angle[143] = 73.633000
|
||||
corr_angle[144] = 74.164500
|
||||
corr_angle[145] = 74.660200
|
||||
corr_angle[146] = 75.180600
|
||||
corr_angle[147] = 75.674200
|
||||
corr_angle[148] = 76.215400
|
||||
corr_angle[149] = 76.718900
|
||||
corr_angle[150] = 77.242300
|
||||
corr_angle[151] = 77.752000
|
||||
corr_angle[152] = 78.279300
|
||||
corr_angle[153] = 78.780500
|
||||
corr_angle[154] = 79.314900
|
||||
corr_angle[155] = 79.424500
|
||||
corr_angle[156] = 79.807000
|
||||
corr_angle[157] = 80.336500
|
||||
corr_angle[158] = 80.338700
|
||||
corr_angle[159] = 80.835300
|
||||
corr_angle[160] = 81.367100
|
||||
corr_angle[161] = 81.376100
|
||||
corr_angle[162] = 81.859000
|
||||
corr_angle[163] = 82.382300
|
||||
corr_angle[164] = 82.384000
|
||||
corr_angle[165] = 82.881100
|
||||
corr_angle[166] = 83.415000
|
||||
corr_angle[167] = 83.421600
|
||||
corr_angle[168] = 83.917100
|
||||
corr_angle[169] = 84.439200
|
||||
corr_angle[170] = 84.439300
|
||||
corr_angle[171] = 84.947400
|
||||
corr_angle[172] = 85.472700
|
||||
corr_angle[173] = 85.987700
|
||||
corr_angle[174] = 86.512400
|
||||
corr_angle[175] = 87.009500
|
||||
corr_angle[176] = 87.536500
|
||||
corr_angle[177] = 88.035600
|
||||
corr_angle[178] = 88.560100
|
||||
corr_angle[179] = 89.057600
|
||||
corr_angle[180] = 89.583200
|
||||
corr_angle[181] = 90.090500
|
||||
corr_angle[182] = 90.614600
|
||||
corr_angle[183] = 91.119900
|
||||
corr_angle[184] = 91.638300
|
||||
corr_angle[185] = 92.153300
|
||||
corr_angle[186] = 92.681600
|
||||
corr_angle[187] = 93.181800
|
||||
corr_angle[188] = 93.710900
|
||||
corr_angle[189] = 94.206100
|
||||
corr_angle[190] = 94.732700
|
||||
corr_angle[191] = 95.226600
|
||||
corr_angle[192] = 95.766400
|
||||
corr_angle[193] = 96.259000
|
||||
corr_angle[194] = 96.783400
|
||||
corr_angle[195] = 97.283100
|
||||
corr_angle[196] = 97.811500
|
||||
corr_angle[197] = 98.323600
|
||||
corr_angle[198] = 98.839400
|
||||
corr_angle[199] = 99.350700
|
||||
corr_angle[200] = 99.880200
|
||||
corr_angle[201] = 100.378100
|
||||
corr_angle[202] = 100.913300
|
||||
corr_angle[203] = 101.405000
|
||||
corr_angle[204] = 101.935700
|
||||
corr_angle[205] = 102.426300
|
||||
corr_angle[206] = 102.957300
|
||||
corr_angle[207] = 103.456500
|
||||
corr_angle[208] = 103.985100
|
||||
corr_angle[209] = 104.490300
|
||||
corr_angle[210] = 105.015100
|
||||
corr_angle[211] = 105.518400
|
||||
corr_angle[212] = 106.047400
|
||||
corr_angle[213] = 106.551100
|
||||
corr_angle[214] = 107.077100
|
||||
corr_angle[215] = 107.575800
|
||||
corr_angle[216] = 108.115200
|
||||
corr_angle[217] = 108.598800
|
||||
corr_angle[218] = 109.129100
|
||||
corr_angle[219] = 109.626200
|
||||
corr_angle[220] = 110.158400
|
||||
corr_angle[221] = 110.661700
|
||||
corr_angle[222] = 111.188600
|
||||
corr_angle[223] = 111.695000
|
||||
corr_angle[224] = 112.216400
|
||||
corr_angle[225] = 112.720000
|
||||
corr_angle[226] = 113.246200
|
||||
corr_angle[227] = 113.757100
|
||||
corr_angle[228] = 114.281900
|
||||
corr_angle[229] = 114.780000
|
||||
corr_angle[230] = 115.306800
|
||||
corr_angle[231] = 115.799800
|
||||
corr_angle[232] = 116.328400
|
||||
corr_angle[233] = 116.829900
|
||||
corr_angle[234] = 117.355400
|
||||
corr_angle[235] = 117.858200
|
||||
corr_angle[236] = 118.380600
|
||||
corr_angle[237] = 118.893200
|
||||
corr_angle[238] = 119.418200
|
||||
corr_angle[239] = 119.921000
|
||||
corr_angle[240] = 120.446100
|
||||
corr_angle[241] = 120.953800
|
||||
corr_angle[242] = 121.480300
|
||||
corr_angle[243] = 121.977600
|
||||
corr_angle[244] = 122.507100
|
||||
corr_angle[245] = 122.996200
|
||||
corr_angle[246] = 123.529600
|
||||
corr_angle[247] = 124.032200
|
||||
corr_angle[248] = 124.554800
|
||||
corr_angle[249] = 125.057800
|
||||
corr_angle[250] = 125.580800
|
||||
corr_angle[251] = 126.090000
|
||||
corr_angle[252] = 126.611600
|
||||
corr_angle[253] = 127.127000
|
||||
corr_angle[254] = 127.650900
|
||||
corr_angle[255] = 128.153700
|
||||
corr_angle[256] = 128.677200
|
||||
corr_angle[257] = 129.170800
|
||||
corr_angle[258] = 129.703000
|
||||
corr_angle[259] = 130.194900
|
||||
corr_angle[260] = 130.733600
|
||||
corr_angle[261] = 131.228300
|
||||
corr_angle[262] = 131.756900
|
||||
corr_angle[263] = 132.263200
|
||||
corr_angle[264] = 132.784700
|
||||
corr_angle[265] = 133.296300
|
||||
corr_angle[266] = 133.818800
|
||||
corr_angle[267] = 134.318900
|
||||
corr_angle[268] = 134.846800
|
||||
corr_angle[269] = 135.344800
|
||||
corr_angle[270] = 135.876000
|
||||
corr_angle[271] = 136.372600
|
||||
corr_angle[272] = 136.902700
|
||||
corr_angle[273] = 137.397800
|
||||
corr_angle[274] = 137.926800
|
||||
corr_angle[275] = 138.428000
|
||||
corr_angle[276] = 138.953700
|
||||
corr_angle[277] = 139.461300
|
||||
corr_angle[278] = 139.987800
|
||||
corr_angle[279] = 140.492600
|
||||
corr_angle[280] = 141.017900
|
||||
corr_angle[281] = 141.524000
|
||||
corr_angle[282] = 142.061200
|
||||
corr_angle[283] = 142.545400
|
||||
corr_angle[284] = 143.069900
|
||||
corr_angle[285] = 143.568700
|
||||
corr_angle[286] = 144.102200
|
||||
corr_angle[287] = 144.596200
|
||||
corr_angle[288] = 145.126400
|
||||
corr_angle[289] = 145.626900
|
||||
corr_angle[290] = 146.158700
|
||||
corr_angle[291] = 146.662200
|
||||
corr_angle[292] = 147.184700
|
||||
corr_angle[293] = 147.692300
|
||||
corr_angle[294] = 148.225900
|
||||
corr_angle[295] = 148.716000
|
||||
corr_angle[296] = 149.243400
|
||||
corr_angle[297] = 149.739400
|
||||
corr_angle[298] = 150.272400
|
||||
corr_angle[299] = 150.762100
|
||||
corr_angle[300] = 151.301800
|
||||
corr_angle[301] = 151.796300
|
||||
corr_angle[302] = 152.333500
|
||||
corr_angle[303] = 152.833100
|
||||
corr_angle[304] = 153.363600
|
||||
corr_angle[305] = 153.862900
|
||||
corr_angle[306] = 154.396100
|
||||
corr_angle[307] = 154.895400
|
||||
corr_angle[308] = 155.417700
|
||||
corr_angle[309] = 155.916900
|
||||
corr_angle[310] = 156.451600
|
||||
corr_angle[311] = 156.942700
|
||||
corr_angle[312] = 157.467600
|
||||
corr_angle[313] = 157.970900
|
||||
corr_angle[314] = 158.501100
|
||||
corr_angle[315] = 158.999600
|
||||
corr_angle[316] = 159.526100
|
||||
corr_angle[317] = 160.028100
|
||||
corr_angle[318] = 160.555800
|
||||
corr_angle[319] = 161.067700
|
||||
corr_angle[320] = 161.597300
|
||||
corr_angle[321] = 162.091300
|
||||
corr_angle[322] = 162.616600
|
||||
corr_angle[323] = 163.107400
|
||||
corr_angle[324] = 163.639100
|
||||
corr_angle[325] = 164.139700
|
||||
corr_angle[326] = 164.672500
|
||||
corr_angle[327] = 165.176800
|
||||
corr_angle[328] = 165.698600
|
||||
corr_angle[329] = 166.205400
|
||||
corr_angle[330] = 166.727300
|
||||
corr_angle[331] = 167.238600
|
||||
corr_angle[332] = 167.771500
|
||||
corr_angle[333] = 168.265000
|
||||
corr_angle[334] = 168.794800
|
||||
corr_angle[335] = 169.293800
|
||||
corr_angle[336] = 169.813300
|
||||
corr_angle[337] = 170.320200
|
||||
corr_angle[338] = 170.845100
|
||||
corr_angle[339] = 171.344000
|
||||
corr_angle[340] = 171.866200
|
||||
corr_angle[341] = 172.375300
|
||||
corr_angle[342] = 172.899400
|
||||
corr_angle[343] = 173.404400
|
||||
corr_angle[344] = 173.928500
|
||||
corr_angle[345] = 174.430800
|
||||
corr_angle[346] = 174.827800
|
||||
corr_angle[347] = 175.460400
|
||||
corr_angle[348] = 175.993100
|
||||
corr_angle[349] = 176.492100
|
||||
corr_angle[350] = 177.014200
|
||||
corr_angle[351] = 177.512100
|
||||
corr_angle[352] = 178.044200
|
||||
corr_angle[353] = 178.643800
|
||||
corr_angle[354] = 179.067200
|
||||
corr_angle[355] = 179.571600
|
||||
corr_angle[356] = 180.093700
|
||||
corr_pos[0] = -0.814519
|
||||
corr_pos[1] = -0.810675
|
||||
corr_pos[2] = -0.806338
|
||||
corr_pos[3] = -0.802047
|
||||
corr_pos[4] = -0.797044
|
||||
corr_pos[5] = -0.791712
|
||||
corr_pos[6] = -0.786558
|
||||
corr_pos[7] = -0.780781
|
||||
corr_pos[8] = -0.774864
|
||||
corr_pos[9] = -0.768674
|
||||
corr_pos[10] = -0.762005
|
||||
corr_pos[11] = -0.755474
|
||||
corr_pos[12] = -0.748024
|
||||
corr_pos[13] = -0.740730
|
||||
corr_pos[14] = -0.732929
|
||||
corr_pos[15] = -0.725011
|
||||
corr_pos[16] = -0.716637
|
||||
corr_pos[17] = -0.707676
|
||||
corr_pos[18] = -0.698861
|
||||
corr_pos[19] = -0.686245
|
||||
corr_pos[20] = -0.676596
|
||||
corr_pos[21] = -0.663643
|
||||
corr_pos[22] = -0.655664
|
||||
corr_pos[23] = -0.642249
|
||||
corr_pos[24] = -0.633516
|
||||
corr_pos[25] = -0.621402
|
||||
corr_pos[26] = -0.612846
|
||||
corr_pos[27] = -0.599978
|
||||
corr_pos[28] = -0.592135
|
||||
corr_pos[29] = -0.577949
|
||||
corr_pos[30] = -0.570695
|
||||
corr_pos[31] = -0.555560
|
||||
corr_pos[32] = -0.549015
|
||||
corr_pos[33] = -0.532162
|
||||
corr_pos[34] = -0.524479
|
||||
corr_pos[35] = -0.507848
|
||||
corr_pos[36] = -0.500301
|
||||
corr_pos[37] = -0.484361
|
||||
corr_pos[38] = -0.477365
|
||||
corr_pos[39] = -0.461751
|
||||
corr_pos[40] = -0.455963
|
||||
corr_pos[41] = -0.439438
|
||||
corr_pos[42] = -0.436103
|
||||
corr_pos[43] = -0.420587
|
||||
corr_pos[44] = -0.417361
|
||||
corr_pos[45] = -0.402270
|
||||
corr_pos[46] = -0.400459
|
||||
corr_pos[47] = -0.384745
|
||||
corr_pos[48] = -0.385280
|
||||
corr_pos[49] = -0.371217
|
||||
corr_pos[50] = -0.373612
|
||||
corr_pos[51] = -0.361838
|
||||
corr_pos[52] = -0.365771
|
||||
corr_pos[53] = -0.354641
|
||||
corr_pos[54] = -0.360647
|
||||
corr_pos[55] = -0.351446
|
||||
corr_pos[56] = -0.359147
|
||||
corr_pos[57] = -0.348696
|
||||
corr_pos[58] = -0.359348
|
||||
corr_pos[59] = -0.349962
|
||||
corr_pos[60] = -0.360782
|
||||
corr_pos[61] = -0.355040
|
||||
corr_pos[62] = -0.367441
|
||||
corr_pos[63] = -0.360511
|
||||
corr_pos[64] = -0.372617
|
||||
corr_pos[65] = -0.366120
|
||||
corr_pos[66] = -0.377568
|
||||
corr_pos[67] = -0.370362
|
||||
corr_pos[68] = -0.380918
|
||||
corr_pos[69] = -0.372553
|
||||
corr_pos[70] = -0.381460
|
||||
corr_pos[71] = -0.371828
|
||||
corr_pos[72] = -0.379428
|
||||
corr_pos[73] = -0.368465
|
||||
corr_pos[74] = -0.374297
|
||||
corr_pos[75] = -0.360340
|
||||
corr_pos[76] = -0.365429
|
||||
corr_pos[77] = -0.349168
|
||||
corr_pos[78] = -0.351544
|
||||
corr_pos[79] = -0.332514
|
||||
corr_pos[80] = -0.333505
|
||||
corr_pos[81] = -0.313316
|
||||
corr_pos[82] = -0.313014
|
||||
corr_pos[83] = -0.291107
|
||||
corr_pos[84] = -0.287807
|
||||
corr_pos[85] = -0.264483
|
||||
corr_pos[86] = -0.260891
|
||||
corr_pos[87] = -0.236646
|
||||
corr_pos[88] = -0.231362
|
||||
corr_pos[89] = -0.205833
|
||||
corr_pos[90] = -0.200663
|
||||
corr_pos[91] = -0.174834
|
||||
corr_pos[92] = -0.171662
|
||||
corr_pos[93] = -0.146200
|
||||
corr_pos[94] = -0.142890
|
||||
corr_pos[95] = -0.118389
|
||||
corr_pos[96] = -0.112895
|
||||
corr_pos[97] = -0.088879
|
||||
corr_pos[98] = -0.084146
|
||||
corr_pos[99] = -0.061625
|
||||
corr_pos[100] = -0.054742
|
||||
corr_pos[101] = -0.032741
|
||||
corr_pos[102] = -0.026725
|
||||
corr_pos[103] = -0.004827
|
||||
corr_pos[104] = 0.002678
|
||||
corr_pos[105] = 0.024418
|
||||
corr_pos[106] = 0.031105
|
||||
corr_pos[107] = 0.052334
|
||||
corr_pos[108] = 0.060072
|
||||
corr_pos[109] = 0.081470
|
||||
corr_pos[110] = 0.088550
|
||||
corr_pos[111] = 0.109685
|
||||
corr_pos[112] = 0.116675
|
||||
corr_pos[113] = 0.136298
|
||||
corr_pos[114] = 0.142152
|
||||
corr_pos[115] = 0.161986
|
||||
corr_pos[116] = 0.166996
|
||||
corr_pos[117] = 0.186466
|
||||
corr_pos[118] = 0.190874
|
||||
corr_pos[119] = 0.208729
|
||||
corr_pos[120] = 0.212714
|
||||
corr_pos[121] = 0.229992
|
||||
corr_pos[122] = 0.232685
|
||||
corr_pos[123] = 0.248748
|
||||
corr_pos[124] = 0.249688
|
||||
corr_pos[125] = 0.264768
|
||||
corr_pos[126] = 0.264432
|
||||
corr_pos[127] = 0.278801
|
||||
corr_pos[128] = 0.278424
|
||||
corr_pos[129] = 0.289656
|
||||
corr_pos[130] = 0.289612
|
||||
corr_pos[131] = 0.299899
|
||||
corr_pos[132] = 0.299782
|
||||
corr_pos[133] = 0.307473
|
||||
corr_pos[134] = 0.309489
|
||||
corr_pos[135] = 0.315999
|
||||
corr_pos[136] = 0.317413
|
||||
corr_pos[137] = 0.325060
|
||||
corr_pos[138] = 0.325931
|
||||
corr_pos[139] = 0.332544
|
||||
corr_pos[140] = 0.332095
|
||||
corr_pos[141] = 0.338738
|
||||
corr_pos[142] = 0.337864
|
||||
corr_pos[143] = 0.343909
|
||||
corr_pos[144] = 0.342973
|
||||
corr_pos[145] = 0.348449
|
||||
corr_pos[146] = 0.346060
|
||||
corr_pos[147] = 0.351949
|
||||
corr_pos[148] = 0.349873
|
||||
corr_pos[149] = 0.354969
|
||||
corr_pos[150] = 0.352375
|
||||
corr_pos[151] = 0.358388
|
||||
corr_pos[152] = 0.354736
|
||||
corr_pos[153] = 0.359539
|
||||
corr_pos[154] = 0.354369
|
||||
corr_pos[155] = 0.358549
|
||||
corr_pos[156] = 0.353217
|
||||
corr_pos[157] = 0.355795
|
||||
corr_pos[158] = 0.351299
|
||||
corr_pos[159] = 0.352922
|
||||
corr_pos[160] = 0.346933
|
||||
corr_pos[161] = 0.350201
|
||||
corr_pos[162] = 0.343937
|
||||
corr_pos[163] = 0.349304
|
||||
corr_pos[164] = 0.344857
|
||||
corr_pos[165] = 0.347471
|
||||
corr_pos[166] = 0.335793
|
||||
corr_pos[167] = 0.341109
|
||||
corr_pos[168] = 0.330453
|
||||
corr_pos[169] = 0.326576
|
||||
corr_pos[170] = 0.320299
|
||||
corr_pos[171] = 0.315348
|
||||
corr_pos[172] = 0.300744
|
||||
corr_pos[173] = 0.294366
|
||||
corr_pos[174] = 0.280438
|
||||
corr_pos[175] = 0.273618
|
||||
corr_pos[176] = 0.259335
|
||||
corr_pos[177] = 0.251464
|
||||
corr_pos[178] = 0.237247
|
||||
corr_pos[179] = 0.229092
|
||||
corr_pos[180] = 0.214882
|
||||
corr_pos[181] = 0.207363
|
||||
corr_pos[182] = 0.193246
|
||||
corr_pos[183] = 0.185868
|
||||
corr_pos[184] = 0.173256
|
||||
corr_pos[185] = 0.165378
|
||||
corr_pos[186] = 0.153853
|
||||
corr_pos[187] = 0.147031
|
||||
corr_pos[188] = 0.135535
|
||||
corr_pos[189] = 0.129435
|
||||
corr_pos[190] = 0.119843
|
||||
corr_pos[191] = 0.115062
|
||||
corr_pos[192] = 0.107115
|
||||
corr_pos[193] = 0.105182
|
||||
corr_pos[194] = 0.099647
|
||||
corr_pos[195] = 0.098162
|
||||
corr_pos[196] = 0.094628
|
||||
corr_pos[197] = 0.095257
|
||||
corr_pos[198] = 0.084490
|
||||
corr_pos[199] = 0.085389
|
||||
corr_pos[200] = 0.074302
|
||||
corr_pos[201] = 0.075265
|
||||
corr_pos[202] = 0.063623
|
||||
corr_pos[203] = 0.064487
|
||||
corr_pos[204] = 0.052841
|
||||
corr_pos[205] = 0.053559
|
||||
corr_pos[206] = 0.042022
|
||||
corr_pos[207] = 0.043321
|
||||
corr_pos[208] = 0.032826
|
||||
corr_pos[209] = 0.032277
|
||||
corr_pos[210] = 0.022169
|
||||
corr_pos[211] = 0.020740
|
||||
corr_pos[212] = 0.010378
|
||||
corr_pos[213] = 0.008668
|
||||
corr_pos[214] = -0.001930
|
||||
corr_pos[215] = -0.003882
|
||||
corr_pos[216] = -0.014694
|
||||
corr_pos[217] = -0.016629
|
||||
corr_pos[218] = -0.026380
|
||||
corr_pos[219] = -0.028069
|
||||
corr_pos[220] = -0.037125
|
||||
corr_pos[221] = -0.039429
|
||||
corr_pos[222] = -0.047773
|
||||
corr_pos[223] = -0.049261
|
||||
corr_pos[224] = -0.056872
|
||||
corr_pos[225] = -0.058756
|
||||
corr_pos[226] = -0.065279
|
||||
corr_pos[227] = -0.066859
|
||||
corr_pos[228] = -0.073027
|
||||
corr_pos[229] = -0.074253
|
||||
corr_pos[230] = -0.079062
|
||||
corr_pos[231] = -0.079800
|
||||
corr_pos[232] = -0.084350
|
||||
corr_pos[233] = -0.084805
|
||||
corr_pos[234] = -0.088621
|
||||
corr_pos[235] = -0.088437
|
||||
corr_pos[236] = -0.092166
|
||||
corr_pos[237] = -0.090769
|
||||
corr_pos[238] = -0.093820
|
||||
corr_pos[239] = -0.091724
|
||||
corr_pos[240] = -0.094152
|
||||
corr_pos[241] = -0.090866
|
||||
corr_pos[242] = -0.092575
|
||||
corr_pos[243] = -0.089239
|
||||
corr_pos[244] = -0.089666
|
||||
corr_pos[245] = -0.086864
|
||||
corr_pos[246] = -0.087183
|
||||
corr_pos[247] = -0.083974
|
||||
corr_pos[248] = -0.083709
|
||||
corr_pos[249] = -0.079848
|
||||
corr_pos[250] = -0.078494
|
||||
corr_pos[251] = -0.072782
|
||||
corr_pos[252] = -0.069638
|
||||
corr_pos[253] = -0.063004
|
||||
corr_pos[254] = -0.058599
|
||||
corr_pos[255] = -0.051737
|
||||
corr_pos[256] = -0.046451
|
||||
corr_pos[257] = -0.040238
|
||||
corr_pos[258] = -0.034071
|
||||
corr_pos[259] = -0.028578
|
||||
corr_pos[260] = -0.021481
|
||||
corr_pos[261] = -0.016346
|
||||
corr_pos[262] = -0.009475
|
||||
corr_pos[263] = -0.004141
|
||||
corr_pos[264] = 0.002780
|
||||
corr_pos[265] = 0.008455
|
||||
corr_pos[266] = 0.015507
|
||||
corr_pos[267] = 0.021672
|
||||
corr_pos[268] = 0.029704
|
||||
corr_pos[269] = 0.036662
|
||||
corr_pos[270] = 0.044794
|
||||
corr_pos[271] = 0.052031
|
||||
corr_pos[272] = 0.061478
|
||||
corr_pos[273] = 0.069150
|
||||
corr_pos[274] = 0.078715
|
||||
corr_pos[275] = 0.087785
|
||||
corr_pos[276] = 0.098593
|
||||
corr_pos[277] = 0.107863
|
||||
corr_pos[278] = 0.118256
|
||||
corr_pos[279] = 0.127631
|
||||
corr_pos[280] = 0.139011
|
||||
corr_pos[281] = 0.150077
|
||||
corr_pos[282] = 0.162154
|
||||
corr_pos[283] = 0.173758
|
||||
corr_pos[284] = 0.186998
|
||||
corr_pos[285] = 0.200111
|
||||
corr_pos[286] = 0.214116
|
||||
corr_pos[287] = 0.227291
|
||||
corr_pos[288] = 0.240662
|
||||
corr_pos[289] = 0.252955
|
||||
corr_pos[290] = 0.265359
|
||||
corr_pos[291] = 0.275995
|
||||
corr_pos[292] = 0.287613
|
||||
corr_pos[293] = 0.295789
|
||||
corr_pos[294] = 0.306424
|
||||
corr_pos[295] = 0.313027
|
||||
corr_pos[296] = 0.322181
|
||||
corr_pos[297] = 0.329313
|
||||
corr_pos[298] = 0.338191
|
||||
corr_pos[299] = 0.345243
|
||||
corr_pos[300] = 0.353316
|
||||
corr_pos[301] = 0.360491
|
||||
corr_pos[302] = 0.367891
|
||||
corr_pos[303] = 0.375027
|
||||
corr_pos[304] = 0.380865
|
||||
corr_pos[305] = 0.388354
|
||||
corr_pos[306] = 0.395257
|
||||
corr_pos[307] = 0.401478
|
||||
corr_pos[308] = 0.408087
|
||||
corr_pos[309] = 0.414083
|
||||
corr_pos[310] = 0.420361
|
||||
corr_pos[311] = 0.424284
|
||||
corr_pos[312] = 0.429614
|
||||
corr_pos[313] = 0.433888
|
||||
corr_pos[314] = 0.438529
|
||||
corr_pos[315] = 0.442302
|
||||
corr_pos[316] = 0.445899
|
||||
corr_pos[317] = 0.449014
|
||||
corr_pos[318] = 0.451693
|
||||
corr_pos[319] = 0.453795
|
||||
corr_pos[320] = 0.455132
|
||||
corr_pos[321] = 0.455438
|
||||
corr_pos[322] = 0.455334
|
||||
corr_pos[323] = 0.454055
|
||||
corr_pos[324] = 0.451146
|
||||
corr_pos[325] = 0.447259
|
||||
corr_pos[326] = 0.442478
|
||||
corr_pos[327] = 0.437520
|
||||
corr_pos[328] = 0.432297
|
||||
corr_pos[329] = 0.426442
|
||||
corr_pos[330] = 0.418918
|
||||
corr_pos[331] = 0.411040
|
||||
corr_pos[332] = 0.402610
|
||||
corr_pos[333] = 0.394491
|
||||
corr_pos[334] = 0.383925
|
||||
corr_pos[335] = 0.374590
|
||||
corr_pos[336] = 0.363650
|
||||
corr_pos[337] = 0.353143
|
||||
corr_pos[338] = 0.339756
|
||||
corr_pos[339] = 0.328074
|
||||
corr_pos[340] = 0.315463
|
||||
corr_pos[341] = 0.302641
|
||||
corr_pos[342] = 0.288898
|
||||
corr_pos[343] = 0.275134
|
||||
corr_pos[344] = 0.260308
|
||||
corr_pos[345] = 0.245582
|
||||
corr_pos[346] = 0.233584
|
||||
corr_pos[347] = 0.213812
|
||||
corr_pos[348] = 0.196540
|
||||
corr_pos[349] = 0.179844
|
||||
corr_pos[350] = 0.161839
|
||||
corr_pos[351] = 0.144160
|
||||
corr_pos[352] = 0.124715
|
||||
corr_pos[353] = 0.102123
|
||||
corr_pos[354] = 0.085736
|
||||
corr_pos[355] = 0.065743
|
||||
corr_pos[356] = 0.044511
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,171 +0,0 @@
|
||||
import builtins
|
||||
|
||||
from bec_widgets.cli.client import BECDockArea
|
||||
|
||||
# from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose
|
||||
|
||||
if builtins.__dict__.get("bec") is not None:
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
umv = builtins.__dict__.get("umv")
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
|
||||
class OMNYGuiToolsError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class OMNYGuiTools:
|
||||
|
||||
def __init__(self, client):
|
||||
self.gui = getattr(client, "gui", None)
|
||||
self.gui_window = self.gui.windows['main'].widget
|
||||
self.fig200 = None
|
||||
self.fig201 = None
|
||||
self.fig202 = None
|
||||
self.fig203 = None
|
||||
self.progressbar = None
|
||||
self.text_box = None
|
||||
self.idle_text_box = None
|
||||
|
||||
def omnygui_show_gui(self):
|
||||
self.gui_window.show()
|
||||
|
||||
def omnygui_stop_gui(self):
|
||||
self.gui_window.hide()
|
||||
|
||||
def _omnycam_parking(self):
|
||||
self.omnygui_show_omnycam_parking()
|
||||
|
||||
def omnygui_show_omnycam_parking(self):
|
||||
self.omnygui_show_gui()
|
||||
if self.fig200 is None:
|
||||
self._omnycam_clear()
|
||||
self.fig200 = self.gui_window.add_dock(name="omnycam200").add_widget("BECImageWidget")
|
||||
if self._omnycam_check_device_exists(dev.cam200):
|
||||
fig = self.fig200.image("cam200")
|
||||
fig.set_rotation(deg_90=3)
|
||||
self.fig200.lock_aspect_ratio(True)
|
||||
else:
|
||||
print("Cannot open cam200. Device does not exist.")
|
||||
self.fig203 = self.gui_window.add_dock(name="omnycam203").add_widget("BECImageWidget")
|
||||
if self._omnycam_check_device_exists(dev.cam203):
|
||||
fig = self.fig203.image("cam203")
|
||||
fig.set_rotation(deg_90=3)
|
||||
self.fig203.lock_aspect_ratio(True)
|
||||
else:
|
||||
print("Cannot open cam203. Device does not exist.")
|
||||
try:
|
||||
self.gui_window.remove_dock(name="default_figure")
|
||||
except:
|
||||
pass
|
||||
|
||||
def omnygui_remove_all_docks(self):
|
||||
self.gui_window.clear_all()
|
||||
self.fig200 = None
|
||||
self.fig201 = None
|
||||
self.fig202 = None
|
||||
self.fig203 = None
|
||||
self.progressbar = None
|
||||
self.text_box = None
|
||||
self.idle_text_box = None
|
||||
|
||||
def omnygui_idle(self):
|
||||
self.omnygui_show_gui()
|
||||
if self.idle_text_box is None:
|
||||
self.omnygui_remove_all_docks()
|
||||
self.idle_text_box = self.gui_window.add_dock(name="idle_text").add_widget("TextBox")
|
||||
try:
|
||||
self.gui_window.remove_dock(name="default_figure")
|
||||
except:
|
||||
pass
|
||||
text = (
|
||||
"<pre>"
|
||||
+ " ,o888888o. ,8. ,8. b. 8 `8.`8888. ,8' \n"
|
||||
+ " . 8888 `88. ,888. ,888. 888o. 8 `8.`8888. ,8' \n"
|
||||
+ ",8 8888 `8b .`8888. .`8888. Y88888o. 8 `8.`8888. ,8' \n"
|
||||
+ "88 8888 `8b ,8.`8888. ,8.`8888. .`Y888888o. 8 `8.`8888.,8' \n"
|
||||
+ "88 8888 88 ,8'8.`8888,8^8.`8888. 8o. `Y888888o. 8 `8.`88888' \n"
|
||||
+ "88 8888 88 ,8' `8.`8888' `8.`8888. 8`Y8o. `Y88888o8 `8. 8888 \n"
|
||||
+ "88 8888 ,8P ,8' `8.`88' `8.`8888. 8 `Y8o. `Y8888 `8 8888 \n"
|
||||
+ "`8 8888 ,8P ,8' `8.`' `8.`8888. 8 `Y8o. `Y8 8 8888 \n"
|
||||
+ " ` 8888 ,88' ,8' `8 `8.`8888. 8 `Y8o.` 8 8888 \n"
|
||||
+ " `8888888P' ,8' ` `8.`8888. 8 `Yo 8 8888 \n"
|
||||
+ "</pre>"
|
||||
)
|
||||
self.idle_text_box.set_html_text(text)
|
||||
|
||||
def _omnycam_clear(self):
|
||||
self.omnygui_remove_all_docks()
|
||||
|
||||
def _omnycam_check_device_exists(self, device):
|
||||
try:
|
||||
device
|
||||
except:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def _omnycam_samplestage(self):
|
||||
self.omnygui_show_omnycam_samplestage()
|
||||
|
||||
def omnygui_show_omnycam_samplestage(self):
|
||||
self.omnygui_show_gui()
|
||||
if self.fig201 is None:
|
||||
self.omnygui_remove_all_docks()
|
||||
self.fig201 = self.gui_window.add_dock(name="omnycam201").add_widget("BECImageWidget")
|
||||
if self._omnycam_check_device_exists(dev.cam201):
|
||||
fig = self.fig201.image("cam201")
|
||||
fig.set_rotation(deg_90=3)
|
||||
self.fig201.lock_aspect_ratio(True)
|
||||
else:
|
||||
print("Cannot open cam201. Device does not exist.")
|
||||
self.fig202 = self.gui_window.add_dock(name="omnycam202").add_widget("BECImageWidget")
|
||||
if self._omnycam_check_device_exists(dev.cam202):
|
||||
fig = self.fig202.image("cam202")
|
||||
fig.set_rotation(deg_90=3)
|
||||
self.fig202.lock_aspect_ratio(True)
|
||||
else:
|
||||
print("Cannot open cam202. Device does not exist.")
|
||||
try:
|
||||
self.gui_window.remove_dock(name="default_figure")
|
||||
except:
|
||||
pass
|
||||
|
||||
def omnygui_show_progress(self):
|
||||
self.omnygui_show_gui()
|
||||
if self.progressbar is None:
|
||||
self.omnygui_remove_all_docks()
|
||||
# Add a new dock with a RingProgressBar widget
|
||||
self.progressbar = self.gui_window.add_dock(name="progress").add_widget("RingProgressBar")
|
||||
# Customize the size of the progress ring
|
||||
self.progressbar.set_line_widths(20)
|
||||
# Disable automatic updates and manually set the self.progressbar value
|
||||
self.progressbar.enable_auto_updates(False)
|
||||
# Set precision for the self.progressbar display
|
||||
self.progressbar.set_precision(1) # Display self.progressbar with one decimal places
|
||||
# Setting multiple rigns with different values
|
||||
self.progressbar.set_number_of_bars(3)
|
||||
self.progressbar.rings[2].set_update("scan")
|
||||
# Set the values of the rings to 50, 75, and 25 from outer to inner ring
|
||||
# self.progressbar.set_value([50, 75])
|
||||
# Add a new dock with a TextBox widget
|
||||
self.text_box = self.gui_window.add_dock(name="progress_text").add_widget("TextBox")
|
||||
try:
|
||||
self.gui_window.remove_dock(name="default_figure")
|
||||
except:
|
||||
pass
|
||||
self._omnygui_update_progress()
|
||||
|
||||
def _omnygui_update_progress(self):
|
||||
if self.progressbar is not None:
|
||||
progress = self.progress["projection"] / self.progress["total_projections"] * 100
|
||||
subotmo_progress = (
|
||||
self.progress["subtomo_projection"]
|
||||
/ self.progress["subtomo_total_projections"]
|
||||
* 100
|
||||
)
|
||||
self.progressbar.set_value([progress, subotmo_progress])
|
||||
|
||||
text = f"Progress report:\n Tomo type: ....................... {self.progress['tomo_type']}\n Projection: ...................... {self.progress['projection']:.0f}\n Total projections expected ....... {self.progress['total_projections']}\n Angle: ........................... {self.progress['angle']}\n Current subtomo: ................. {self.progress['subtomo']}\n Current projection within subtomo: {self.progress['subtomo_projection']}\n Total projections per subtomo: ... {self.progress['subtomo_total_projections']}"
|
||||
self.text_box.set_plain_text(text)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,246 +0,0 @@
|
||||
import time
|
||||
import numpy as np
|
||||
import os
|
||||
|
||||
from rich import box
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
from typeguard import typechecked
|
||||
from bec_lib import bec_logger
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class OMNYAlignmentError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class OMNYAlignmentMixin:
|
||||
default_correction_file = "correction_omny_202204.txt"
|
||||
default_correction_file_x = "correction_omny_202204_x.txt"
|
||||
|
||||
def reset_correction(self, use_default_correction=True):
|
||||
"""
|
||||
Reset the correction to the default values.
|
||||
If use_default_correction is False, the correction will be set to empty values.
|
||||
Otherwise the default values will be loaded.
|
||||
|
||||
Args:
|
||||
use_default_correction (bool, optional): If set to true, a call reset the correction to the default values. Defaults to True.
|
||||
"""
|
||||
self.corr_pos_x = []
|
||||
self.corr_angle_x = []
|
||||
self.corr_pos_y = []
|
||||
self.corr_angle_y = []
|
||||
self.corr_pos_y_2 = []
|
||||
self.corr_angle_y_2 = []
|
||||
|
||||
if use_default_correction:
|
||||
try:
|
||||
self.read_additional_correction_x(self.default_correction_file_x)
|
||||
logger.info(f"Applying default x correction from {self.default_correction_file_x}")
|
||||
except FileNotFoundError:
|
||||
logger.warning(
|
||||
f"Could not find default correction file {self.default_correction_file_x}."
|
||||
)
|
||||
logger.warning("Not applying any correction.")
|
||||
try:
|
||||
self.read_additional_correction_y(self.default_correction_file)
|
||||
logger.info(f"Applying default y correction from {self.default_correction_file}")
|
||||
except FileNotFoundError:
|
||||
logger.warning(
|
||||
f"Could not find default correction file {self.default_correction_file}."
|
||||
)
|
||||
logger.warning("Not applying any correction.")
|
||||
|
||||
def reset_tomo_alignment_fit(self):
|
||||
self.client.delete_global_var("tomo_alignment_fit")
|
||||
|
||||
def read_alignment_offset(
|
||||
self,
|
||||
dir_path=os.path.expanduser("~/Data10/specES1/internal/"),
|
||||
setup="omny",
|
||||
use_vertical_default_values=True,
|
||||
):
|
||||
"""
|
||||
Read the alignment offset from the given directory and set the global parameter
|
||||
tomo_alignment_fit.
|
||||
|
||||
Args:
|
||||
dir_path (str, optional): The directory to read the alignment offset from. Defaults to os.path.expanduser("~/Data10/specES1/internal/").
|
||||
"""
|
||||
tomo_alignment_fit = np.zeros((2, 5))
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Ax.txt"), "r") as file:
|
||||
tomo_alignment_fit[0][0] = file.readline()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Bx.txt"), "r") as file:
|
||||
tomo_alignment_fit[0][1] = file.readline()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Cx.txt"), "r") as file:
|
||||
tomo_alignment_fit[0][2] = file.readline()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Ay.txt"), "r") as file:
|
||||
tomo_alignment_fit[1][0] = file.readline()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_By.txt"), "r") as file:
|
||||
tomo_alignment_fit[1][1] = file.readline()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Cy.txt"), "r") as file:
|
||||
tomo_alignment_fit[1][2] = file.readline()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Ay3.txt"), "r") as file:
|
||||
tomo_alignment_fit[1][3] = file.readline()
|
||||
|
||||
with open(os.path.join(dir_path, "ptychotomoalign_Cy3.txt"), "r") as file:
|
||||
tomo_alignment_fit[1][4] = file.readline()
|
||||
|
||||
print("New alignment parameters loaded:")
|
||||
print(
|
||||
f"X Amplitude {tomo_alignment_fit[0][0]}, "
|
||||
f"X Phase {tomo_alignment_fit[0][1]}, "
|
||||
f"X Offset {tomo_alignment_fit[0][2]}, "
|
||||
f"Y Amplitude {tomo_alignment_fit[1][0]}, "
|
||||
f"Y Phase {tomo_alignment_fit[1][1]}, "
|
||||
f"Y Offset {tomo_alignment_fit[1][2]}, "
|
||||
f"Y 3rd Order Amplitude {tomo_alignment_fit[1][3]}, "
|
||||
f"Y 3rd Order Phase {tomo_alignment_fit[1][4]} ."
|
||||
)
|
||||
|
||||
if use_vertical_default_values:
|
||||
print(
|
||||
f"Using default values for vertical alignment for setup {setup}. Optional: use_vertical_default_values=False"
|
||||
)
|
||||
if setup == "flomni":
|
||||
tomo_alignment_fit[1][0] = 0
|
||||
tomo_alignment_fit[1][1] = 0
|
||||
tomo_alignment_fit[1][2] = 0
|
||||
tomo_alignment_fit[1][3] = 0
|
||||
tomo_alignment_fit[1][4] = 0
|
||||
elif setup == "omny":
|
||||
tomo_alignment_fit[1][0] = 2.588628
|
||||
tomo_alignment_fit[1][1] = -2.385422
|
||||
tomo_alignment_fit[1][2] = 0
|
||||
tomo_alignment_fit[1][3] = 1.010583
|
||||
tomo_alignment_fit[1][4] = -1.359157
|
||||
|
||||
print("Follwing parameters will be used:")
|
||||
print(
|
||||
f"X Amplitude {tomo_alignment_fit[0][0]}, "
|
||||
f"X Phase {tomo_alignment_fit[0][1]}, "
|
||||
f"X Offset {tomo_alignment_fit[0][2]}, "
|
||||
f"Y Amplitude {tomo_alignment_fit[1][0]}, "
|
||||
f"Y Phase {tomo_alignment_fit[1][1]}, "
|
||||
f"Y Offset {tomo_alignment_fit[1][2]}, "
|
||||
f"Y 3rd Order Amplitude {tomo_alignment_fit[1][3]}, "
|
||||
f"Y 3rd Order Phase {tomo_alignment_fit[1][4]} ."
|
||||
)
|
||||
|
||||
self.client.set_global_var("tomo_alignment_fit", tomo_alignment_fit.tolist())
|
||||
# x amp, phase, offset, y amp, phase, offset, 3rd order amp, 3rd order phase
|
||||
# 0 0 0 1 0 2 1 0 1 1 1 2 1 3 1 4
|
||||
|
||||
def get_alignment_offset(self, angle: float):
|
||||
"""
|
||||
Compute the alignment offset for the given angle.
|
||||
|
||||
Args:
|
||||
angle (float): The angle to compute the alignment offset for.
|
||||
|
||||
Returns:
|
||||
tuple: The alignment offset in x, y and z direction.
|
||||
"""
|
||||
tomo_alignment_fit = self.client.get_global_var("tomo_alignment_fit")
|
||||
if tomo_alignment_fit is None:
|
||||
print("Not applying any alignment offsets. No tomo alignment fit data available.\n")
|
||||
return (0, 0, 0)
|
||||
|
||||
# x amp, phase, offset, y amp, phase, offset
|
||||
# 0 0 0 1 0 2 1 0 1 1 1 2
|
||||
correction_x = (
|
||||
tomo_alignment_fit[0][0] * np.sin(np.radians(angle) + tomo_alignment_fit[0][1])
|
||||
+ tomo_alignment_fit[0][2]
|
||||
)
|
||||
correction_y = (
|
||||
tomo_alignment_fit[1][0] * np.sin(np.radians(angle) + tomo_alignment_fit[1][1])
|
||||
+ tomo_alignment_fit[1][2]
|
||||
+ tomo_alignment_fit[1][3] * np.sin(3 * np.radians(angle) + tomo_alignment_fit[1][4])
|
||||
)
|
||||
correction_z = tomo_alignment_fit[0][0] * np.sin(
|
||||
np.radians(angle + 90) + tomo_alignment_fit[0][1]
|
||||
)
|
||||
|
||||
print(
|
||||
f"Alignment offset x {correction_x}, y {correction_y}, z {correction_z} for angle"
|
||||
f" {angle}\n"
|
||||
)
|
||||
return (correction_x, correction_y, correction_z)
|
||||
|
||||
def _read_correction_file(self, correction_file: str):
|
||||
with open(correction_file, "r") as f:
|
||||
num_elements = f.readline()
|
||||
int_num_elements = int(num_elements.split(" ")[2])
|
||||
corr_pos = []
|
||||
corr_angle = []
|
||||
for j in range(int_num_elements * 2):
|
||||
line = f.readline()
|
||||
value = line.split(" ")[2]
|
||||
name = line.split(" ")[0].split("[")[0]
|
||||
if name == "corr_pos":
|
||||
corr_pos.append(float(value))
|
||||
elif name == "corr_angle":
|
||||
corr_angle.append(float(value))
|
||||
print(
|
||||
f"Loading default mirror correction from file {correction_file} containing {int_num_elements} elements."
|
||||
)
|
||||
# print(corr_pos)
|
||||
return corr_pos, corr_angle
|
||||
|
||||
def read_additional_correction_x(self, correction_file: str):
|
||||
self.corr_pos_x, self.corr_angle_x = self._read_correction_file(correction_file)
|
||||
|
||||
def read_additional_correction_y(self, correction_file: str):
|
||||
self.corr_pos_y, self.corr_angle_y = self._read_correction_file(correction_file)
|
||||
|
||||
def read_additional_correction_y_2(self, correction_file: str):
|
||||
self.corr_pos_y_2, self.corr_angle_y_2 = self._read_correction_file(correction_file)
|
||||
|
||||
def compute_additional_correction_x(self, angle):
|
||||
return self._compute_additional_correction(angle, iteration="x1")
|
||||
|
||||
def compute_additional_correction_y(self, angle):
|
||||
return self._compute_additional_correction(angle, iteration="y1")
|
||||
|
||||
def compute_additional_correction_y_2(self, angle):
|
||||
return self._compute_additional_correction(angle, iteration="y2")
|
||||
|
||||
def _compute_additional_correction(self, angle, iteration="y1"):
|
||||
if iteration == "x1":
|
||||
corr_pos = self.corr_pos_x
|
||||
corr_angle = self.corr_angle_x
|
||||
elif iteration == "y1":
|
||||
corr_pos = self.corr_pos_y
|
||||
corr_angle = self.corr_angle_y
|
||||
elif iteration == "y2":
|
||||
corr_pos = self.corr_pos_y_2
|
||||
corr_angle = self.corr_angle_y_2
|
||||
if not corr_pos:
|
||||
print(f"Not applying any additional correction {iteration}. No data available.\n")
|
||||
return 0
|
||||
|
||||
# find index of closest angle
|
||||
for j, _ in enumerate(corr_pos):
|
||||
newangledelta = np.fabs(corr_angle[j] - angle)
|
||||
if j == 0:
|
||||
angledelta = newangledelta
|
||||
additional_correction_shift = corr_pos[j]
|
||||
continue
|
||||
|
||||
if newangledelta < angledelta:
|
||||
additional_correction_shift = corr_pos[j]
|
||||
angledelta = newangledelta
|
||||
|
||||
if additional_correction_shift == 0 and angle > corr_angle[-1]:
|
||||
additional_correction_shift = corr_pos[-1]
|
||||
print(f"Additional correction shift {iteration}: {additional_correction_shift}")
|
||||
return additional_correction_shift
|
||||
@@ -1,153 +0,0 @@
|
||||
import time
|
||||
import numpy as np
|
||||
import sys
|
||||
import termios
|
||||
import tty
|
||||
import fcntl
|
||||
import os
|
||||
import builtins
|
||||
|
||||
from rich import box
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
# from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose
|
||||
|
||||
if builtins.__dict__.get("bec") is not None:
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
umv = builtins.__dict__.get("umv")
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
|
||||
class OMNYToolsError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class OMNYTools:
|
||||
|
||||
HEADER = "\033[95m"
|
||||
OKBLUE = "\033[94m"
|
||||
OKCYAN = "\033[96m"
|
||||
OKGREEN = "\033[92m"
|
||||
WARNING = "\033[93m"
|
||||
FAIL = "\033[91m"
|
||||
ENDC = "\033[0m"
|
||||
BOLD = "\033[1m"
|
||||
UNDERLINE = "\033[4m"
|
||||
|
||||
def __init__(self, client) -> None:
|
||||
self.client = client
|
||||
|
||||
@staticmethod
|
||||
def _get_user_param_safe(device, var):
|
||||
param = dev[device].user_parameter
|
||||
if not param or param.get(var) is None:
|
||||
raise ValueError(f"Device {device} has no user parameter definition for {var}.")
|
||||
return param.get(var)
|
||||
|
||||
def printgreen(self, string: str):
|
||||
print(self.OKGREEN + string + self.ENDC)
|
||||
|
||||
def printgreenbold(self, string: str):
|
||||
print(self.BOLD + self.OKGREEN + string + self.ENDC)
|
||||
|
||||
def yesno(self, message: str, default="none", autoconfirm=0) -> bool:
|
||||
if autoconfirm and default == "y":
|
||||
self.printgreen(message + " Automatically confirming default: yes")
|
||||
return True
|
||||
elif autoconfirm and default == "n":
|
||||
self.printgreen(message + " Automatically confirming default: no")
|
||||
return False
|
||||
if default == "y":
|
||||
message_ending = " [Y]/n? "
|
||||
elif default == "n":
|
||||
message_ending = " y/[N]? "
|
||||
else:
|
||||
message_ending = " y/n? "
|
||||
while True:
|
||||
user_input = input(self.OKBLUE + message + message_ending + self.ENDC)
|
||||
if (
|
||||
user_input == "Y" or user_input == "y" or user_input == "yes" or user_input == "Yes"
|
||||
) or (default == "y" and user_input == ""):
|
||||
return True
|
||||
if (
|
||||
user_input == "N" or user_input == "n" or user_input == "no" or user_input == "No"
|
||||
) or (default == "n" and user_input == ""):
|
||||
return False
|
||||
else:
|
||||
print("Please expicitely confirm y or n.")
|
||||
|
||||
def tweak_cursor(
|
||||
self, dev1, step1: float, dev2="none", step2: float = "0", special_command="none"
|
||||
):
|
||||
if dev1 not in dev.enabled_devices:
|
||||
print(f"Device 1 {dev} is not in enabled devices.")
|
||||
return
|
||||
if dev2 not in dev.enabled_devices and dev2 != "none":
|
||||
print(f"Device 2 {dev} is not in enabled devices.")
|
||||
return
|
||||
# Save the current terminal settings
|
||||
fd = sys.stdin.fileno()
|
||||
old_term = termios.tcgetattr(fd)
|
||||
try:
|
||||
# Set the terminal to raw mode to capture single key presses
|
||||
tty.setraw(fd)
|
||||
# Set stdin to non-blocking mode
|
||||
old_flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags | os.O_NONBLOCK)
|
||||
print("Tweak Cursor." + self.BOLD + self.OKBLUE + "Press (q) to quit!\r" + self.ENDC)
|
||||
while True:
|
||||
try:
|
||||
# Read single character input
|
||||
key = sys.stdin.read(1)
|
||||
if key == "q":
|
||||
print("\n\rExiting tweak mode\r")
|
||||
break
|
||||
elif key == "\x1b": # Escape sequences for arrow keys
|
||||
next1, next2 = sys.stdin.read(2)
|
||||
if next1 == "[":
|
||||
if next2 == "A":
|
||||
# print("up")
|
||||
if dev2 != "none":
|
||||
umvr(dev2, step2)
|
||||
if special_command != "none":
|
||||
special_command()
|
||||
elif next2 == "B":
|
||||
# print(" down")
|
||||
if dev2 != "none":
|
||||
umvr(dev2, -step2)
|
||||
if special_command != "none":
|
||||
special_command()
|
||||
elif next2 == "C":
|
||||
# print("right")
|
||||
umvr(dev1, step1)
|
||||
if special_command != "none":
|
||||
special_command()
|
||||
elif next2 == "D":
|
||||
# print("left")
|
||||
umvr(dev1, -step1)
|
||||
if special_command != "none":
|
||||
special_command()
|
||||
|
||||
elif key == "+":
|
||||
step1 = step1 * 2
|
||||
if dev2 != "none":
|
||||
step2 = step2 * 2
|
||||
print(f"\rDouble step size. New step size: {step1}, {step2}\r")
|
||||
elif key == "-":
|
||||
step1 = step1 / 2
|
||||
if dev2 != "none":
|
||||
step2 = step2 / 2
|
||||
print(f"\rHalf step size. New step size: {step1}, {step2}\r")
|
||||
except IOError:
|
||||
# No input available, keep looping
|
||||
pass
|
||||
|
||||
# Sleep for a short period to avoid high CPU usage
|
||||
time.sleep(0.02)
|
||||
|
||||
finally:
|
||||
# Restore the terminal to its original state
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_term)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags)
|
||||
@@ -1,384 +0,0 @@
|
||||
import time
|
||||
import numpy as np
|
||||
|
||||
from rich import box
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_put, fshclose
|
||||
|
||||
|
||||
class OMNYError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class OMNYOpticsMixin:
|
||||
@staticmethod
|
||||
def _get_user_param_safe(device, var):
|
||||
param = dev[device].user_parameter
|
||||
if not param or param.get(var) is None:
|
||||
raise ValueError(f"Device {device} has no user parameter definition for {var}.")
|
||||
return param.get(var)
|
||||
|
||||
def ooptics_in(self):
|
||||
self.ofzp_in()
|
||||
# ocs_in
|
||||
self.oosa_in()
|
||||
|
||||
if "rtx" in dev and dev.rtx.enabled:
|
||||
dev.rtx.controller.feedback_enable()
|
||||
|
||||
self.align.update_frame()
|
||||
|
||||
user_input = input(
|
||||
"Is the direct beam gone on the xray eye? Do you see the cone of the FZP?"
|
||||
)
|
||||
if user_input == "y":
|
||||
printf("Next oeye_out...\n")
|
||||
else:
|
||||
raise OMNYError("Failed to properly move in the Xray optics")
|
||||
|
||||
def _oeyey_mv(self, position):
|
||||
# direction dependent speeds
|
||||
if dev.oeyez.get().readback < position:
|
||||
dev.oeyez.controller.socket_put_confirmed("axspeed[7]=15000")
|
||||
else:
|
||||
dev.oeyez.controller.socket_put_confirmed("axspeed[7]=10000")
|
||||
umv(dev.oeyey, position)
|
||||
dev.oeyez.controller.socket_put_confirmed("axspeed[7]=10000")
|
||||
|
||||
def oeye_out(self):
|
||||
fshclose()
|
||||
if self.OMNYTools.yesno("Did you move in the optics?"):
|
||||
umv(dev.oeyez, -2)
|
||||
self._oeyey_mv(-60.3)
|
||||
# free camera
|
||||
epics_put("XOMNYI-XEYE-ACQ:0", 2)
|
||||
else:
|
||||
raise OMNYError("The optics were not moved in. Please do so prior to eyey_out")
|
||||
self.OMNYTools.printgreen("Oeye is out.")
|
||||
|
||||
def oeye_cam_in(self):
|
||||
if dev.oeyez.get().readback < -80:
|
||||
umv(dev.oeyez, -50)
|
||||
|
||||
if np.fabs(dev.oeyey.get().readback + 4.8) > 0.1:
|
||||
self._oeyey_mv(-4.8)
|
||||
|
||||
if np.fabs(dev.oeyez.get().readback + 2) > 0.1 or np.fabs(dev.oeyex.get().readback) > 0.1:
|
||||
umv(dev.oeyez, -2, dev.oeyex, 0)
|
||||
# if still too close in z -- safety check
|
||||
if np.fabs(dev.oeyez.get().readback + 2) > 0.1:
|
||||
raise OMNYError("The oeye is too close in z for transfer. ERROR! Aborting.")
|
||||
self.OMNYTools.printgreen("Oeye is at cam position.")
|
||||
|
||||
def _oeye_xray_is_in(self) -> bool:
|
||||
omny_oeye_xray_inx = self._get_user_param_safe("oeyex", "xray_in")
|
||||
omny_oeye_xray_iny = self._get_user_param_safe("oeyey", "xray_in")
|
||||
omny_oeye_currentx = dev.oeyex.get().readback
|
||||
omny_oeye_currenty = dev.oeyey.get().readback
|
||||
|
||||
if (
|
||||
np.fabs(omny_oeye_currentx - omny_oeye_xray_inx) < 0.1
|
||||
and np.fabs(omny_oeye_currenty - omny_oeye_xray_iny) < 0.1
|
||||
):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def oeye_xray_in(self):
|
||||
if self._oeye_xray_is_in():
|
||||
pass
|
||||
else:
|
||||
# todo
|
||||
# self._otransfer_gripper_safe_xray_in_operation()
|
||||
# if(!_oshield_is_ST_closed())
|
||||
# {
|
||||
# printf("The shield of the sample stage is not closed. Aborting.\n")
|
||||
# exit
|
||||
# }
|
||||
omny_oeye_xray_inx = self._get_user_param_safe("oeyex", "xray_in")
|
||||
omny_oeye_xray_iny = self._get_user_param_safe("oeyey", "xray_in")
|
||||
omny_oeye_xray_inz = self._get_user_param_safe("oeyez", "xray_in")
|
||||
|
||||
self._oeyey_mv(omny_oeye_xray_iny)
|
||||
omny_oeye_currenty = dev.oeyey.get().readback
|
||||
if np.fabs(omny_oeye_currenty - omny_oeye_xray_iny) > 0.1:
|
||||
raise OMNYError("The oeye did not move up.\n")
|
||||
umv(dev.oeyex, omny_oeye_xray_inx, dev.oeyez, omny_oeye_xray_inz)
|
||||
self.OMNYTools.printgreen("Oeye is at X-ray position.")
|
||||
|
||||
# some notes for the vis microscope:
|
||||
# initial position for the vis light microscope
|
||||
# do not open the shield when the microscope is at the vis mic position
|
||||
# found eoeyx -45.13, z -84.9, y 0.64
|
||||
# for a samy position of 2.8 with delta off
|
||||
# the osa position should be in z around 7.4. in x it seems better
|
||||
# around -0.6, where potentially xrays dont pass anymore
|
||||
#
|
||||
|
||||
def _oosa_check_y(self):
|
||||
omny_oosa_currenty = dev.oosay.get().readback
|
||||
if np.fabs(omny_oosa_currenty - 0.9) > 0.05:
|
||||
umv(dev.oosay, 0.9)
|
||||
omny_oosa_currenty = dev.oosay.get().readback
|
||||
if np.fabs(omny_oosa_currenty - 0.9) > 0.05:
|
||||
raise OMNYError("oosay is not around 0.9. Aborting.")
|
||||
|
||||
def _oosa_to_move_corridor(self):
|
||||
self._oosa_check_y()
|
||||
dev.oosax.limits = [-3, 3.7] # risk collision with shield
|
||||
umv(dev.oosax, -2)
|
||||
dev.oosax.read(cached=False)
|
||||
omny_oosa_currentx = dev.oosax.get().readback
|
||||
if np.fabs(omny_oosa_currentx + 2) > 0.1:
|
||||
raise OMNYError("oosax did not reach target position. Not moving in z.\n")
|
||||
|
||||
def oosa_in(self):
|
||||
self._oosa_check_y()
|
||||
dev.oshield.read(cached=False)
|
||||
omny_oshield_current = dev.oshield.get().readback
|
||||
if omny_oshield_current < 15:
|
||||
self._oshield_ST_close()
|
||||
if self.near_field == False:
|
||||
x_in_pos = self._get_user_param_safe("oosax", "far_field_in")
|
||||
y_in_pos = self._get_user_param_safe("oosay", "far_field_in")
|
||||
z_in_pos = self._get_user_param_safe("oosaz", "far_field_in")
|
||||
print("OSA movement in far-field mode.")
|
||||
dev.oosaz.read(cached=False)
|
||||
omny_oosa_currentz = dev.oosaz.get().readback
|
||||
if omny_oosa_currentz < 6.4:
|
||||
self._oosa_to_move_corridor()
|
||||
dev.oosaz.limits = [6.4, 6.6]
|
||||
umv(dev.oosaz, z_in_pos)
|
||||
umv(dev.oosax, x_in_pos)
|
||||
umv(dev.oosay, y_in_pos)
|
||||
#### For the 30 nm FZP 220 um we use this part
|
||||
# umv oosaz 6.5
|
||||
# umv oosax 3.2453
|
||||
# umv oosay 0.386015
|
||||
|
||||
if self.near_field == True:
|
||||
x_in_pos = self._get_user_param_safe("oosax", "near_field_in")
|
||||
y_in_pos = self._get_user_param_safe("oosay", "near_field_in")
|
||||
z_in_pos = self._get_user_param_safe("oosaz", "near_field_in")
|
||||
print("OSA movement in near-field mode.")
|
||||
dev.oosaz.read(cached=False)
|
||||
omny_oosa_currentz = dev.oosaz.get().readback
|
||||
if omny_oosa_currentz > 0:
|
||||
self._oosa_to_move_corridor()
|
||||
dev.oosaz.limits = [-0.4, -0.6]
|
||||
umv(dev.oosaz, z_in_pos)
|
||||
umv(dev.oosax, x_in_pos)
|
||||
omny_osamy_current = dev.osamy.get().readback
|
||||
if omny_osamy_current < 3.25:
|
||||
umv(dev.oosay, y_in_pos)
|
||||
else:
|
||||
raise OMNYError("Failed to move oosa in. osamy position is too large.")
|
||||
|
||||
self.OMNYTools.printgreen("OSA is in.")
|
||||
|
||||
# todo
|
||||
# _omny_interferometer_align_tracking
|
||||
# rt_feedback_enable
|
||||
|
||||
def oosa_out(self):
|
||||
self._oosa_check_y()
|
||||
dev.oshield.read(cached=False)
|
||||
omny_oshield_current = dev.oshield.get().readback
|
||||
if omny_oshield_current < 15:
|
||||
self._oshield_ST_close()
|
||||
omny_oosaz_current = dev.oosaz.get().readback
|
||||
if self.near_field == False:
|
||||
print("OSA movement in far-field mode.")
|
||||
if omny_oosaz_current < 6.4:
|
||||
self._oosa_to_move_corridor()
|
||||
dev.oosaz.limits = [6.4, 6.6]
|
||||
umv(dev.oosaz, 6.5)
|
||||
umv(dev.oosax, -2)
|
||||
if self.near_field == True:
|
||||
print("OSA movement in near-field mode.")
|
||||
if omny_oosaz_current > 0:
|
||||
self._oosa_to_move_corridor()
|
||||
dev.oosaz.limits = [-0.4, -0.6]
|
||||
umv(dev.oosaz, -0.45)
|
||||
umv(dev.oosax, -2)
|
||||
# todo _omny_interferometer_align_tracking
|
||||
|
||||
self.OMNYTools.printgreen("OSA is out.")
|
||||
|
||||
def oosa_move_out_of_shield(self):
|
||||
# todo: _omnycam_samplestage
|
||||
self._oosa_check_y()
|
||||
self._oosa_to_move_corridor()
|
||||
omny_osamx_current = dev.osamx.get().readback
|
||||
if np.fabs(omny_osamx_current) > 0.2:
|
||||
umv(dev.osamx, 0)
|
||||
omny_oosaz_current = dev.oosaz.get().readback
|
||||
if omny_oosaz_current > 0.1:
|
||||
dev.oosaz.limits = [-0.1, 0.1]
|
||||
umv(dev.oosaz, 0)
|
||||
|
||||
self.OMNYTools.printgreen("OSA is out of shield.")
|
||||
|
||||
def ofzp_out(self):
|
||||
if "rtx" in dev and dev.rtx.enabled:
|
||||
dev.rtx.controller.feedback_disable()
|
||||
y_out_pos = self._get_user_param_safe("ofzpy", "out")
|
||||
if np.fabs(dev.ofzpy.get().readback - y_out_pos) > 0.02:
|
||||
umv(dev.ofzpy, y_out_pos)
|
||||
self.OMNYTools.printgreen("FZP at out position")
|
||||
|
||||
def ofzp_in(self):
|
||||
if "rtx" in dev and dev.rtx.enabled:
|
||||
dev.rtx.controller.feedback_disable()
|
||||
x_in_pos = self._get_user_param_safe("ofzpx", "in")
|
||||
y_in_pos = self._get_user_param_safe("ofzpy", "in")
|
||||
if np.fabs(dev.ofzpy.get().readback - y_in_pos) > 0.02:
|
||||
umv(dev.ofzpy, y_in_pos)
|
||||
|
||||
if np.fabs(dev.ofzpx.get().readback - x_in_pos) > 0.02:
|
||||
umv(dev.ofzpx, x_in_pos)
|
||||
self.OMNYTools.printgreen("FZP at in position")
|
||||
# 220 mu FZP at ofzpz 31.8025 for eiger probe (about 2.4 mm propagation after focus)
|
||||
# umv(dev.ofzpy, 0.7944)
|
||||
# if np.fabs(dev.ofzpx.get().readback+0.4317)>0.05:
|
||||
# umv(dev.ofzpx, -0.4317)
|
||||
# note the 220 fzp also works for near field 6.2 kev by just moving back osa and fzp
|
||||
# ofzpz 24.8 leads to a 9.5 mm propagation distance.
|
||||
# With the 220 mu FZP this gives 100 nm pixel recons
|
||||
# for the oosa macro set near_field=1
|
||||
# 170 mu FZP at 6.2 kev for large beam at ofzpz 31.8025 of about 58 mu diameter
|
||||
# 120 mu FZP at ofzpz 28.1991
|
||||
|
||||
# 250 mu FZP 60 nm at 5.65 keV
|
||||
# ofzpz 29.7 for propagation distance 2.2
|
||||
# umv ofzpx -0.4457
|
||||
# umv ofzpy 0.193630
|
||||
|
||||
# 150 um fzp, 60 nm, ofzpz 33.8 at 8.9 kev for propagation of 1.7 mm after focus
|
||||
# umv ofzpx -0.756678
|
||||
# umv ofzpy 0.193515
|
||||
|
||||
# 250 um 30 nm FZP upper right
|
||||
# small abberrations, seems to give good results in weak objects
|
||||
# ofzpx -0.609240
|
||||
# umv ofzpy 0.118265
|
||||
# 250 um 30 nm FZP lower right very aberated
|
||||
# ofzpx -0.881935
|
||||
# umv ofzpy 0.537050
|
||||
|
||||
# ofzpz 28.4027
|
||||
# 5.30 mm prop at 8.9 keV, 45 nm pixel in near field
|
||||
|
||||
# ofzpz 33.103
|
||||
# 0.6 mm prop at 8.9 kev far field 7 m flight tube at foptz
|
||||
|
||||
# ofzpz 49.4 is reachable just without interferometer swap
|
||||
# which at 6.2 keV and 250 um diam, 30 nm should gives a propagation of 0.8 after focus
|
||||
# and a beam size of 6 microns diamter
|
||||
###coordinates 30 nm FZP for comparing them
|
||||
# not sure if that is really correct
|
||||
# FZP 1 - FZP 2
|
||||
# FZP 5
|
||||
# FZP 4 - FZP 1
|
||||
|
||||
# FZP
|
||||
##upper right
|
||||
# umv ofzpx -0.6154 ofzpy 0.1183
|
||||
# umv ocsx -0.6070 ocsy 0.0540
|
||||
|
||||
# lower right
|
||||
# umv ofzpx -0.8341 ofzpy 0.5683
|
||||
# umv ocsx -0.3880 ocsy -0.3960
|
||||
|
||||
# lower left
|
||||
# umv ofzpx -0.3876 ofzpy 0.7902
|
||||
# umv ocsx -0.8380 ocsy -0.6180
|
||||
|
||||
# upper left
|
||||
# umv ofzpx -0.1678 ofzpy 0.3403
|
||||
# umv ocsx -1.0550 ocsy -0.1680
|
||||
|
||||
def ofzp_info(self, mokev_val=-1, ofzpz_val=-1):
|
||||
print(f"{ofzpz_val}")
|
||||
|
||||
if mokev_val == -1:
|
||||
try:
|
||||
mokev_val = dev.mokev.readback.get()
|
||||
except:
|
||||
print(
|
||||
"Device mokev does not exist. You can specify the energy in keV as an argument instead."
|
||||
)
|
||||
return
|
||||
if ofzpz_val == -1:
|
||||
ofzpz_val = dev.ofzpz.readback.get()
|
||||
distance = 66 + 2.4 + 31.8025 - ofzpz_val
|
||||
print(
|
||||
f"\nThe sample is in a distance of \033[1m{distance:.1f} mm\033[0m from the 60 nm FZP.\n"
|
||||
)
|
||||
print(f"At the current energy of {mokev_val:.4f} keV we have following options:\n")
|
||||
|
||||
diameters = [80e-6, 100e-6, 120e-6, 150e-6, 170e-6, 200e-6, 220e-6, 250e-6]
|
||||
|
||||
console = Console()
|
||||
table = Table(title="Outermost zone width \033[1m60 nm\033[0m", box=box.SQUARE)
|
||||
table.add_column("Diameter", justify="center")
|
||||
table.add_column("Focal distance", justify="center")
|
||||
table.add_column("Current beam size", justify="center")
|
||||
|
||||
wavelength = 1.2398e-9 / mokev_val
|
||||
|
||||
for diameter in diameters:
|
||||
outermost_zonewidth = 60e-9
|
||||
focal_distance = diameter * outermost_zonewidth / wavelength * 1000
|
||||
beam_size = -diameter / (focal_distance * 1000) * (focal_distance - distance) * 1e9
|
||||
table.add_row(
|
||||
f"{diameter*1e6:.2f} microns",
|
||||
f"{focal_distance:.2f} mm",
|
||||
f"{beam_size:.2f} microns",
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
|
||||
# 30 nm with additional spacer
|
||||
distance = 53.84 + 0.6 + 33.1 - ofzpz_val
|
||||
print(
|
||||
f"\nThe sample is in a distance of \033[1m{distance:.1f} mm\033[0m from the 30 nm FZP.\n"
|
||||
)
|
||||
|
||||
diameters = [150e-6, 250e-6]
|
||||
|
||||
console = Console()
|
||||
table = Table(title="Outermost zone width \033[1m30 nm\033[0m", box=box.SQUARE)
|
||||
table.add_column("Diameter", justify="center")
|
||||
table.add_column("Focal distance", justify="center")
|
||||
table.add_column("Current beam size", justify="center")
|
||||
|
||||
wavelength = 1.2398e-9 / mokev_val
|
||||
|
||||
for diameter in diameters:
|
||||
outermost_zonewidth = 30e-9
|
||||
focal_distance = diameter * outermost_zonewidth / wavelength * 1000
|
||||
beam_size = -diameter / (focal_distance * 1000) * (focal_distance - distance) * 1e9
|
||||
table.add_row(
|
||||
f"{diameter*1e6:.2f} microns",
|
||||
f"{focal_distance:.2f} mm",
|
||||
f"{beam_size:.2f} microns",
|
||||
)
|
||||
|
||||
console.print(table)
|
||||
|
||||
print(
|
||||
"This function can be called with explicit energy and ofzpz position.\n Example: omny.ffzp_info(mokev_val=6.2, ofzpz_val=33.2)"
|
||||
)
|
||||
|
||||
# from flomni
|
||||
# oosaz_val = dev.oosaz.readback.get()
|
||||
|
||||
# print("\nOSA Information:")
|
||||
# print(f" Current fosaz {fosaz_val:.1f}")
|
||||
# print(
|
||||
# f" The OSA will collide with a normal OMNY pin at fosaz \033[1m{(33-fosaz_val):.1f}\033[0m"
|
||||
# )
|
||||
# print(f" Remaining space: \033[1m{-fosaz_val+(33-foptz_val):.1f}\033[0m")
|
||||
@@ -1,241 +0,0 @@
|
||||
import time
|
||||
import numpy as np
|
||||
import sys
|
||||
import termios
|
||||
import tty
|
||||
import fcntl
|
||||
import os
|
||||
|
||||
|
||||
from rich import box
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen, fshclose
|
||||
|
||||
|
||||
class OMNY_rt_clientError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class OMNY_rt_client:
|
||||
def __init__(self):
|
||||
self.mirror_channel = -1
|
||||
self.mirror_amplitutde_increase = 0
|
||||
self.mirror_parameters = {}
|
||||
for j in range(1, 9):
|
||||
self.mirror_parameters[j] = dev.rtx.controller.get_mirror_parameters(j)
|
||||
|
||||
@staticmethod
|
||||
def _get_user_param_safe(device, var):
|
||||
param = dev[device].user_parameter
|
||||
if not param or param.get(var) is None:
|
||||
raise OMNY_rt_clientError(
|
||||
f"Device {device} has no user parameter definition for {var}."
|
||||
)
|
||||
return param.get(var)
|
||||
|
||||
def _omny_interferometer_openloop_steps(self, channel, steps, amplitude):
|
||||
dev.rtx.controller._omny_interferometer_openloop_steps(channel, steps, amplitude)
|
||||
|
||||
def interferometer_tweaking(self):
|
||||
self._tweak_interferometer()
|
||||
|
||||
def _tweak_interferometer(self):
|
||||
self.mirror_channel = -1
|
||||
|
||||
# Save the current terminal settings
|
||||
fd = sys.stdin.fileno()
|
||||
old_term = termios.tcgetattr(fd)
|
||||
|
||||
print("Ready to tweak the interferometer. Press q to quit.")
|
||||
print("The arrows adjust directions.")
|
||||
print("Numbers select the mirror aligner.")
|
||||
|
||||
try:
|
||||
# Set the terminal to raw mode to capture single key presses
|
||||
tty.setraw(fd)
|
||||
# Set stdin to non-blocking mode
|
||||
old_flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags | os.O_NONBLOCK)
|
||||
opt_mirrorname = "none"
|
||||
max = 0
|
||||
while True:
|
||||
try:
|
||||
# Read single character input
|
||||
key = sys.stdin.read(1)
|
||||
|
||||
if key == "q":
|
||||
self.mirror_amplitutde_increase = 0
|
||||
self.mirror_channel = -1
|
||||
print("\n\rExiting tweak mode\r")
|
||||
break
|
||||
elif key == "\x1b": # Escape sequences for arrow keys
|
||||
next1, next2 = sys.stdin.read(2)
|
||||
|
||||
if next1 == "[":
|
||||
printit = True
|
||||
if next2 == "A":
|
||||
# print("up")
|
||||
if self.mirror_channel != -1:
|
||||
self._omny_interferometer_openloop_steps(
|
||||
4,
|
||||
-self.mirror_parameters[self.mirror_channel][
|
||||
"opt_steps2_neg"
|
||||
],
|
||||
self.mirror_parameters[self.mirror_channel][
|
||||
"opt_amplitude2_neg"
|
||||
]
|
||||
+ self.mirror_amplitutde_increase,
|
||||
)
|
||||
elif next2 == "B":
|
||||
# print(" down")
|
||||
if self.mirror_channel != -1:
|
||||
self._omny_interferometer_openloop_steps(
|
||||
4,
|
||||
self.mirror_parameters[self.mirror_channel][
|
||||
"opt_steps2_pos"
|
||||
],
|
||||
self.mirror_parameters[self.mirror_channel][
|
||||
"opt_amplitude2_pos"
|
||||
]
|
||||
+ self.mirror_amplitutde_increase,
|
||||
)
|
||||
elif next2 == "C":
|
||||
# print("right")
|
||||
if self.mirror_channel != -1:
|
||||
self._omny_interferometer_openloop_steps(
|
||||
3,
|
||||
-self.mirror_parameters[self.mirror_channel][
|
||||
"opt_steps1_neg"
|
||||
],
|
||||
self.mirror_parameters[self.mirror_channel][
|
||||
"opt_amplitude1_neg"
|
||||
]
|
||||
+ self.mirror_amplitutde_increase,
|
||||
)
|
||||
elif next2 == "D":
|
||||
# print("left")
|
||||
if self.mirror_channel != -1:
|
||||
self._omny_interferometer_openloop_steps(
|
||||
3,
|
||||
self.mirror_parameters[self.mirror_channel][
|
||||
"opt_steps1_pos"
|
||||
],
|
||||
self.mirror_parameters[self.mirror_channel][
|
||||
"opt_amplitude1_pos"
|
||||
]
|
||||
+ self.mirror_amplitutde_increase,
|
||||
)
|
||||
elif key.isdigit() and 1 <= int(key) <= 8:
|
||||
self.mirror_channel = int(key)
|
||||
opt_mirrorname = self.mirror_parameters[self.mirror_channel][
|
||||
"opt_mirrorname"
|
||||
]
|
||||
autostop = self.mirror_parameters[self.mirror_channel]["opt_signal_stop"]
|
||||
averaging_time = self.mirror_parameters[self.mirror_channel][
|
||||
"opt_averaging_time"
|
||||
]
|
||||
print(
|
||||
f"\nSelected mirror channel {self.mirror_channel}: {opt_mirrorname}. Autostop {autostop}. Signal averaging time: {averaging_time}\r"
|
||||
)
|
||||
if int(key) == 6:
|
||||
dev.rtx.controller.laser_tracker_on()
|
||||
dev.rtx.controller._omny_interferometer_switch_channel(self.mirror_channel)
|
||||
max = 0
|
||||
printit = True
|
||||
elif key == "+":
|
||||
print("\nIncreasing voltage amplitudes by 100.\r")
|
||||
self.mirror_amplitutde_increase += 100
|
||||
elif key == "-":
|
||||
print("\nDecreasing voltage amplitudes by 100.\r")
|
||||
self.mirror_amplitutde_increase -= 100
|
||||
elif key == "a":
|
||||
if self.mirror_channel != -1:
|
||||
dev.rtx.controller._omny_interferometer_optimize(
|
||||
mirror_channel=self.mirror_channel, channel=3
|
||||
)
|
||||
dev.rtx.controller._omny_interferometer_optimize(
|
||||
mirror_channel=self.mirror_channel, channel=4
|
||||
)
|
||||
dev.rtx.controller._omny_interferometer_optimize(
|
||||
mirror_channel=self.mirror_channel, channel=3
|
||||
)
|
||||
dev.rtx.controller._omny_interferometer_optimize(
|
||||
mirror_channel=self.mirror_channel, channel=4
|
||||
)
|
||||
if self.mirror_channel != -1 and printit:
|
||||
printit = False
|
||||
signal = dev.rtx.controller._omny_interferometer_get_signalsample(
|
||||
self.mirror_parameters[self.mirror_channel]["opt_signalchannel"],
|
||||
self.mirror_parameters[self.mirror_channel]["opt_averaging_time"],
|
||||
)
|
||||
if signal > max:
|
||||
max = signal
|
||||
info_str = f"Channel {self.mirror_channel}, {opt_mirrorname}, Current signal: {signal:.0f}"
|
||||
filling = " " * (50 - len(info_str))
|
||||
# Calculate the number of filled and unfilled segments
|
||||
length = 30
|
||||
percentage = signal / max
|
||||
filled_length = int(length * percentage)
|
||||
unfilled_length = length - filled_length
|
||||
bar = "#" * filled_length + "-" * unfilled_length
|
||||
print(info_str + filling + "0 " + bar + f" {max:.0f} (q)uit\r", end="")
|
||||
|
||||
except IOError:
|
||||
# No input available, keep looping
|
||||
pass
|
||||
|
||||
# Sleep for a short period to avoid high CPU usage
|
||||
time.sleep(0.02)
|
||||
|
||||
finally:
|
||||
# Restore the terminal to its original state
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old_term)
|
||||
fcntl.fcntl(fd, fcntl.F_SETFL, old_flags)
|
||||
dev.rtx.controller._omny_interferometer_switch_alloff()
|
||||
self.mirror_channel = -1
|
||||
self.mirror_amplitutde_increase = 0
|
||||
dev.rtx.controller.show_signal_strength_interferometer()
|
||||
|
||||
def show_signal_strength_interferometer(self):
|
||||
dev.rtx.controller.show_signal_strength_interferometer()
|
||||
|
||||
def omny_interferometer_align_incoupling_angle(self):
|
||||
dev.rtx.controller.omny_interferometer_align_incoupling_angle()
|
||||
|
||||
def interferometer_tweak_otrack(self):
|
||||
self.OMNYTools.tweak_cursor(
|
||||
dev.otrackz,
|
||||
0.1,
|
||||
dev.otracky,
|
||||
0.1,
|
||||
special_command=dev.rtx.controller.laser_tracker_print_intensity_for_otrack_tweaking,
|
||||
)
|
||||
|
||||
def feedback_enable_with_reset(self):
|
||||
dev.rtx.controller.feedback_enable_with_reset()
|
||||
|
||||
def feedback_disable(self):
|
||||
dev.rtx.controller.feedback_disable()
|
||||
|
||||
def feedback_status(self):
|
||||
if dev.rtx.controller.feedback_is_running():
|
||||
print("Feedback is running.")
|
||||
else:
|
||||
print("Feedback is NOT running.")
|
||||
|
||||
def laser_tracker_on(self):
|
||||
dev.rtx.controller.laser_tracker_on()
|
||||
|
||||
def laser_tracker_off(self):
|
||||
dev.rtx.controller.laser_tracker_off()
|
||||
|
||||
def laser_tracker_show_all(self):
|
||||
dev.rtx.controller.laser_tracker_show_all()
|
||||
|
||||
def omny_interferometer_align_tracking(self):
|
||||
dev.rtx.controller.omny_interferometer_align_tracking()
|
||||
|
||||
def laser_tracker_check_and_wait_for_signalstrength(self):
|
||||
dev.rtx.controller.laser_tracker_check_and_wait_for_signalstrength()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,235 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import builtins
|
||||
import os
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bec_lib import bec_logger
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fshopen
|
||||
|
||||
logger = bec_logger.logger
|
||||
# import builtins to avoid linter errors
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
umv = builtins.__dict__.get("umv")
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_ipython_client.plugins.omny import OMNY
|
||||
|
||||
|
||||
class XrayEyeAlign:
|
||||
# pixel calibration, multiply to get mm
|
||||
PIXEL_CALIBRATION = 0.2 / 218 # .2 with binning
|
||||
|
||||
def __init__(self, client, omny: OMNY) -> None:
|
||||
self.client = client
|
||||
self.omny = omny
|
||||
self.device_manager = client.device_manager
|
||||
self.scans = client.scans
|
||||
self.alignment_values = {}
|
||||
self.omny.reset_correction()
|
||||
self.omny.reset_tomo_alignment_fit()
|
||||
|
||||
def _reset_init_values(self):
|
||||
self.shift_xy = [0, 0]
|
||||
self._xray_fov_xy = [0, 0]
|
||||
|
||||
def save_frame(self):
|
||||
epics_put("XOMNYI-XEYE-SAVFRAME:0", 1)
|
||||
|
||||
def update_frame(self):
|
||||
epics_put("XOMNYI-XEYE-ACQDONE:0", 0)
|
||||
# start live
|
||||
epics_put("XOMNYI-XEYE-ACQ:0", 1)
|
||||
# wait for start live
|
||||
while epics_get("XOMNYI-XEYE-ACQDONE:0") == 0:
|
||||
time.sleep(0.5)
|
||||
print("waiting for live view to start...")
|
||||
fshopen()
|
||||
|
||||
epics_put("XOMNYI-XEYE-ACQDONE:0", 0)
|
||||
|
||||
while epics_get("XOMNYI-XEYE-ACQDONE:0") == 0:
|
||||
print("waiting for new frame...")
|
||||
time.sleep(0.5)
|
||||
|
||||
time.sleep(0.5)
|
||||
# stop live view
|
||||
epics_put("XOMNYI-XEYE-ACQ:0", 0)
|
||||
time.sleep(1)
|
||||
# fshclose
|
||||
print("got new frame")
|
||||
|
||||
def tomo_rotate(self, val: float):
|
||||
# pylint: disable=undefined-variable
|
||||
umv(self.device_manager.devices.osamroy, val)
|
||||
|
||||
def get_tomo_angle(self):
|
||||
return self.device_manager.devices.osamroy.readback.get()
|
||||
|
||||
def update_fov(self, k: int):
|
||||
self._xray_fov_xy[0] = max(epics_get(f"XOMNYI-XEYE-XWIDTH_X:{k}"), self._xray_fov_xy[0])
|
||||
self._xray_fov_xy[1] = max(0, self._xray_fov_xy[0])
|
||||
|
||||
@property
|
||||
def movement_buttons_enabled(self):
|
||||
return [epics_get("XOMNYI-XEYE-ENAMVX:0"), epics_get("XOMNYI-XEYE-ENAMVY:0")]
|
||||
|
||||
@movement_buttons_enabled.setter
|
||||
def movement_buttons_enabled(self, enabled: bool):
|
||||
enabled = int(enabled)
|
||||
epics_put("XOMNYI-XEYE-ENAMVX:0", enabled)
|
||||
epics_put("XOMNYI-XEYE-ENAMVY:0", enabled)
|
||||
|
||||
def send_message(self, msg: str):
|
||||
epics_put("XOMNYI-XEYE-MESSAGE:0.DESC", msg)
|
||||
|
||||
def align(self):
|
||||
# reset shift xy and fov params
|
||||
self._reset_init_values()
|
||||
|
||||
self.tomo_rotate(0)
|
||||
epics_put("XOMNYI-XEYE-ANGLE:0", 0)
|
||||
|
||||
self.omny.oeye_xray_in()
|
||||
|
||||
self.omny.feedback_enable_with_reset()
|
||||
|
||||
# disable movement buttons
|
||||
self.movement_buttons_enabled = False
|
||||
|
||||
sample_name = dev.omny_samples.get_sample_name_in_samplestage()
|
||||
epics_put("XOMNYI-XEYE-SAMPLENAME:0.DESC", sample_name)
|
||||
|
||||
# this makes sure we are in a defined state
|
||||
self.omny.feedback_disable()
|
||||
|
||||
epics_put("XOMNYI-XEYE-PIXELSIZE:0", self.PIXEL_CALIBRATION)
|
||||
|
||||
osamx_in = self.omny.OMNYTools._get_user_param_safe("osamx", "in")
|
||||
umv(dev.osamx, osamx_in - 0.35)
|
||||
|
||||
self.omny.ofzp_in()
|
||||
self.update_frame()
|
||||
|
||||
# enable submit buttons
|
||||
self.movement_buttons_enabled = False
|
||||
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
|
||||
epics_put("XOMNYI-XEYE-STEP:0", 0)
|
||||
self.send_message("Submit center value of FZP.")
|
||||
|
||||
k = 0
|
||||
while True:
|
||||
if epics_get("XOMNYI-XEYE-SUBMIT:0") == 1:
|
||||
val_x = epics_get(f"XOMNYI-XEYE-XVAL_X:{k}") / 2 * self.PIXEL_CALIBRATION # in mm
|
||||
self.alignment_values[k] = val_x
|
||||
print(f"Clicked position {k}: x {self.alignment_values[k]}")
|
||||
rtx_position = dev.rtx.readback.get() / 1000
|
||||
print(f"Current rtx position {rtx_position}")
|
||||
self.alignment_values[k] -= rtx_position
|
||||
print(f"Corrected position {k}: x {self.alignment_values[k]}")
|
||||
|
||||
if k == 0: # received center value of FZP
|
||||
self.send_message("please wait ...")
|
||||
self.movement_buttons_enabled = False
|
||||
epics_put("XOMNYI-XEYE-SUBMIT:0", -1) # disable submit button
|
||||
|
||||
self.omny.feedback_disable()
|
||||
osamx_in = self.omny.OMNYTools._get_user_param_safe("osamx", "in")
|
||||
umv(dev.osamx, osamx_in)
|
||||
|
||||
self.omny.ofzp_out()
|
||||
|
||||
self.update_frame()
|
||||
epics_put("XOMNYI-XEYE-RECBG:0", 1)
|
||||
while epics_get("XOMNYI-XEYE-RECBG:0") == 1:
|
||||
time.sleep(0.5)
|
||||
print("waiting for background frame...")
|
||||
|
||||
umv(dev.osamx, osamx_in)
|
||||
time.sleep(0.5)
|
||||
self.omny.feedback_enable_with_reset()
|
||||
|
||||
self.update_frame()
|
||||
self.send_message("Adjust sample height and submit center")
|
||||
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
|
||||
self.movement_buttons_enabled = True
|
||||
|
||||
elif 1 <= k < 5: # received sample center value at samroy 0 ... 315
|
||||
self.send_message("please wait ...")
|
||||
epics_put("XOMNYI-XEYE-SUBMIT:0", -1)
|
||||
self.movement_buttons_enabled = False
|
||||
|
||||
umv(dev.rtx, 0)
|
||||
self.tomo_rotate(k * 45)
|
||||
epics_put("XOMNYI-XEYE-ANGLE:0", self.get_tomo_angle())
|
||||
self.update_frame()
|
||||
self.send_message("Submit sample center")
|
||||
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
|
||||
epics_put("XOMNYI-XEYE-ENAMVX:0", 1)
|
||||
self.update_fov(k)
|
||||
|
||||
elif k == 5: # received sample center value at samroy 270 and done
|
||||
self.send_message("done...")
|
||||
epics_put("XOMNYI-XEYE-SUBMIT:0", -1) # disable submit button
|
||||
self.movement_buttons_enabled = False
|
||||
self.update_fov(k)
|
||||
break
|
||||
|
||||
k += 1
|
||||
epics_put("XOMNYI-XEYE-STEP:0", k)
|
||||
|
||||
_xrayeyalignmvx = epics_get("XOMNYI-XEYE-MVX:0")
|
||||
if _xrayeyalignmvx != 0:
|
||||
umvr(dev.rtx, _xrayeyalignmvx)
|
||||
print(f"Current rtx position {dev.rtx.readback.get() / 1000}")
|
||||
epics_put("XOMNYI-XEYE-MVX:0", 0)
|
||||
if k > 0:
|
||||
epics_put(f"XOMNYI-XEYE-STAGEPOSX:{k}", dev.rtx.readback.get() / 1000)
|
||||
time.sleep(3)
|
||||
self.update_frame()
|
||||
|
||||
if k < 2:
|
||||
# allow movements, store movements to calculate center
|
||||
_xrayeyalignmvy = epics_get("XOMNYI-XEYE-MVY:0")
|
||||
if _xrayeyalignmvy != 0:
|
||||
self.omny.feedback_disable()
|
||||
umvr(dev.osamy, _xrayeyalignmvy / 1000)
|
||||
time.sleep(2)
|
||||
epics_put("XOMNYI-XEYE-MVY:0", 0)
|
||||
self.omny.feedback_enable_with_reset()
|
||||
self.update_frame()
|
||||
time.sleep(0.2)
|
||||
|
||||
self.write_output()
|
||||
fovx = self._xray_fov_xy[0] * self.PIXEL_CALIBRATION * 1000 / 2
|
||||
fovy = self._xray_fov_xy[1] * self.PIXEL_CALIBRATION * 1000 / 2
|
||||
|
||||
self.tomo_rotate(0)
|
||||
|
||||
umv(dev.rtx, 0)
|
||||
|
||||
# free camera
|
||||
epics_put("XOMNYI-XEYE-ACQ:0", 2)
|
||||
|
||||
print(
|
||||
f"The largest field of view from the xrayeyealign was \nfovx = {fovx:.0f} microns, fovy"
|
||||
f" = {fovy:.0f} microns"
|
||||
)
|
||||
print("Use the matlab routine to FIT the current alignment...")
|
||||
|
||||
print("Then LOAD ALIGNMENT PARAMETERS by running omny.read_alignment_offset()\n")
|
||||
|
||||
def write_output(self):
|
||||
file = os.path.expanduser("~/Data10/specES1/internal/xrayeye_alignmentvalues")
|
||||
if not os.path.exists(file):
|
||||
os.makedirs(os.path.dirname(file), exist_ok=True)
|
||||
with open(file, "w") as alignment_values_file:
|
||||
alignment_values_file.write("angle\thorizontal\n")
|
||||
for k in range(1, 6):
|
||||
fovx_offset = self.alignment_values[0] - self.alignment_values[k]
|
||||
print(f"Writing to file new alignment: number {k}, value x {fovx_offset}")
|
||||
alignment_values_file.write(f"{(k-1)*45}\t{fovx_offset*1000}\n")
|
||||
@@ -1,250 +0,0 @@
|
||||
"""Module providing debugging tools for the BEC IPython client at cSAXS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
import numpy as np
|
||||
from pydantic import BaseModel
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
from slugify import slugify
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_ipython_client.main import BECIPythonClient
|
||||
from bec_lib.devicemanager import DeviceManagerBase
|
||||
from bec_lib.scans import Scans
|
||||
from bec_widgets.cli.client_utils import BECGuiClient
|
||||
|
||||
scans: Scans # type: ignore[no-redef]
|
||||
|
||||
bec: BECIPythonClient # type: ignore[no-redef]
|
||||
|
||||
dev: DeviceManagerBase # type: ignore[no-redef]
|
||||
|
||||
|
||||
class Detector(BaseModel):
|
||||
"""Model representing a detector configuration."""
|
||||
|
||||
name: str
|
||||
hostnames: list[str]
|
||||
cfg: dict
|
||||
|
||||
|
||||
def to_identifier(text: str) -> str:
|
||||
"""
|
||||
Convert an unsafe string into a valid Python identifier.
|
||||
"""
|
||||
name = slugify(text.strip(), separator="_")
|
||||
name = re.sub(r"[^a-zA-Z0-9_]", "", name)
|
||||
|
||||
if not name:
|
||||
raise ValueError(f"Cannot convert '{text}' to a valid identifier.")
|
||||
|
||||
if name[0].isdigit():
|
||||
name = f"_{name}"
|
||||
|
||||
return name
|
||||
|
||||
|
||||
class DebugTools:
|
||||
"""A collection of debugging tools for the BEC IPython client at cSAXS."""
|
||||
|
||||
_PURPOSE = (
|
||||
"Debugging helpers for the cSAXS BEC IPython client. These tools are intended for advanced users "
|
||||
"and developers to diagnose and troubleshoot issues within the BEC environment. "
|
||||
"Below are the available methods together with a brief description of their functionality."
|
||||
)
|
||||
|
||||
######################
|
||||
## Internal Methods ##
|
||||
######################
|
||||
|
||||
def _describe(self) -> None:
|
||||
"""Pretty-print a description of this debugging tool."""
|
||||
console = Console()
|
||||
|
||||
# Offset for IPython prompt misplacement
|
||||
console.print("\n\n", end="")
|
||||
|
||||
header = Text("DebugTools", style="bold cyan")
|
||||
purpose = Text(self._PURPOSE, style="dim")
|
||||
|
||||
console.print(Panel(purpose, title=header, expand=False))
|
||||
|
||||
table = Table(show_header=True, header_style="bold magenta")
|
||||
table.add_column("Method", style="bold", no_wrap=True)
|
||||
table.add_column("Description")
|
||||
|
||||
for name, member in inspect.getmembers(self, predicate=inspect.ismethod):
|
||||
if name.startswith("_"):
|
||||
continue
|
||||
|
||||
doc = inspect.getdoc(member)
|
||||
short_doc = doc.splitlines()[0] if doc else ""
|
||||
table.add_row(name, short_doc)
|
||||
|
||||
console.print(table)
|
||||
|
||||
def _repr_pretty_(self, p, cycle: bool) -> None:
|
||||
if cycle:
|
||||
p.text("DebugTools(...)")
|
||||
else:
|
||||
self._describe()
|
||||
|
||||
#####################
|
||||
### MCS Card Check ###
|
||||
#####################
|
||||
|
||||
def _check_if_device_is_loaded(self, device_name: str):
|
||||
"""Check if a device is loaded in the current BEC session."""
|
||||
if device_name not in dev:
|
||||
raise RuntimeError(
|
||||
f"Device {device_name} was not loaded in the current active BEC session."
|
||||
)
|
||||
|
||||
def mcs_test_acquire(
|
||||
self, mode: Literal["high_frame", "medium_frame", "low_frame"] = "high_frame"
|
||||
):
|
||||
"""
|
||||
Method to perform a test acquisition with randomized exposure time, burst frames, and cycles
|
||||
on the MCS card using the DDG trigger setup.
|
||||
|
||||
Args:
|
||||
mode (Literal["high_frame", "medium_frame", "low_frame"]): The mode of the test.
|
||||
- 'high_frame': Tests high frame rates with short exposure times.
|
||||
- 'medium_frame': Tests medium frame rates with moderate exposure times.
|
||||
- 'low_frame': Tests low frame rates with longer exposure times.
|
||||
"""
|
||||
self._check_if_device_is_loaded("mcs")
|
||||
self._check_if_device_is_loaded("ddg1")
|
||||
self._check_if_device_is_loaded("ddg2")
|
||||
|
||||
if mode == "high_frame":
|
||||
burst_frames = np.random.randint(10_000, 100_000) # between 10000 and 100000
|
||||
cycles = np.random.randint(5, 20) # between 5 and 20
|
||||
exp_time = (
|
||||
np.random.rand() * (0.001 - 0.201e-3) + 0.201e-3
|
||||
) # between 0.000201 ms and 0.001 s
|
||||
elif mode == "medium_frame":
|
||||
burst_frames = np.random.randint(50, 500) # between 50 and 500
|
||||
cycles = np.random.randint(1, 10) # between 1 and 10
|
||||
exp_time = np.random.rand() * (0.01 - 0.001) + 0.001 # between 0.001 ms and 0.01 s
|
||||
elif mode == "low_frame":
|
||||
burst_frames = np.random.randint(5, 20) # between 5 and 20
|
||||
cycles = np.random.randint(1, 5) # between 1 and 5
|
||||
exp_time = np.random.rand() * (2 - 0.1) + 0.1 # between 0.1 ms and 2 s
|
||||
else:
|
||||
raise ValueError(f"Invalid mode '{mode}' specified for acquire scan test.")
|
||||
print(
|
||||
f"Starting acquire measurement with exp_time={exp_time:.6f}, burst_frames={burst_frames}, cycles={cycles}"
|
||||
)
|
||||
s = scans.acquire(
|
||||
exp_time=exp_time, frames_per_trigger=burst_frames, burst_at_each_point=cycles
|
||||
)
|
||||
s.wait(file_written=True)
|
||||
print("Acquire measurement finished.")
|
||||
print("Checking MCS data...")
|
||||
scan_data = bec.history.get_by_scan_id(s.scan.scan_id)
|
||||
mcs_data = scan_data.devices.mcs
|
||||
print(mcs_data)
|
||||
|
||||
shape = mcs_data._info["mcs_mca_mca1"]["value"]["shape"]
|
||||
expected_shape = (cycles * burst_frames,)
|
||||
# Assert will raise an error if the shapes do not match
|
||||
assert (
|
||||
shape == expected_shape
|
||||
), f"MCS data shape {shape} does not match expected shape {expected_shape}."
|
||||
|
||||
########################
|
||||
### JFJ/Eiger Checks ###
|
||||
########################
|
||||
|
||||
def _get_jfj_eiger_config(self) -> dict[str, Detector]:
|
||||
"""Retrieve the current JFJ/Eiger detector configuration from the BEC client."""
|
||||
# FIXME: Implement REST API call once ready for use from Leo Sala's team.
|
||||
ret = {}
|
||||
base_path = os.path.dirname(__file__)
|
||||
config_path = os.path.join(base_path, "jfj_config.json")
|
||||
with open(config_path, "r", encoding="utf-8") as fh:
|
||||
cfg = json.load(fh)
|
||||
|
||||
for entry in cfg["detector"]:
|
||||
det = Detector(
|
||||
name=to_identifier(entry["description"]), hostnames=entry["hostname"], cfg=cfg
|
||||
)
|
||||
ret[det.name] = det
|
||||
return ret
|
||||
|
||||
def list_detectors(self) -> list[str]:
|
||||
"""
|
||||
List the names of all JFJ/Eiger detectors configured in the BEC client.
|
||||
|
||||
Returns:
|
||||
list[str]: A list of detector names.
|
||||
"""
|
||||
detectors = self._get_jfj_eiger_config()
|
||||
return list(detectors.keys())
|
||||
|
||||
def ping_detector(self, detector_name: str) -> bool:
|
||||
"""
|
||||
Ping a JFJ/Eiger detector to check if it is reachable.
|
||||
|
||||
Args:
|
||||
detector_name (str): The name of the detector to ping.
|
||||
|
||||
Returns:
|
||||
bool: True if the detector is reachable, False otherwise.
|
||||
"""
|
||||
detectors = self._get_jfj_eiger_config()
|
||||
if detector_name not in detectors:
|
||||
raise ValueError(f"Detector '{detector_name}' not found in configuration.")
|
||||
|
||||
det = detectors[detector_name]
|
||||
results = self._ping_many(det.hostnames)
|
||||
|
||||
table = Table(title=f"Ping results for detector '{detector_name}'")
|
||||
table.add_column("Hostname", style="cyan", no_wrap=True)
|
||||
table.add_column("Status", style="magenta")
|
||||
|
||||
for host, alive in results.items():
|
||||
status = "[green]OK[/green]" if alive else "[red]DOWN[/red]"
|
||||
table.add_row(host, status)
|
||||
|
||||
console = Console()
|
||||
console.print(table)
|
||||
|
||||
def _ping_many(self, hosts: list[str], port=22, timeout=2, max_workers=None):
|
||||
max_workers = max_workers or len(hosts)
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
primed_ping = partial(self._ping, port=port, timeout=timeout)
|
||||
pings = executor.map(primed_ping, hosts)
|
||||
return dict(zip(hosts, pings))
|
||||
|
||||
def _ping(self, host: str, port=23, timeout=2): # telnet is port 23
|
||||
address = (host, port)
|
||||
try:
|
||||
with socket.create_connection(address, timeout):
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
def open_it_service_page(self):
|
||||
"""Open the overview of IT services hosted by Science IT Infrastructure and Services for cSAXS."""
|
||||
gui: BECGuiClient = bec.gui
|
||||
dock_area = gui.new()
|
||||
print("Opening IT service page in new dock...")
|
||||
url = "https://metrics.psi.ch/d/saf8mxv/x12sa?orgId=1&from=now-24h&to=now&timezone=browser&var-receiver_hosts=sls-jfjoch-001.psi.ch&var-writer_hosts=xbl-daq-34.psi.ch&var-beamline=X12SA&var-slurm_partitions=csaxs&var-receiver_services=broker&var-writer_services=writer&refresh=15m"
|
||||
# FIXME BEC WIDGETS v3
|
||||
dock = dock_area.new()
|
||||
wb = dock.new(widget=gui.available_widgets.WebsiteWidget)
|
||||
wb.set_url(url)
|
||||
@@ -1,162 +0,0 @@
|
||||
{
|
||||
"zeromq" : {
|
||||
"image_socket": ["tcp://0.0.0.0:5500"]
|
||||
},
|
||||
"zeromq_preview": {
|
||||
"socket_address": "tcp://0.0.0.0:5400",
|
||||
"enabled": true,
|
||||
"period_ms": 1000
|
||||
},
|
||||
"zeromq_metadata" : {
|
||||
"socket_address": "tcp://0.0.0.0:5600",
|
||||
"enabled": true,
|
||||
"period_ms": 100
|
||||
},
|
||||
"instrument" : {
|
||||
"source_name": "Swiss Light Source",
|
||||
"instrument_name": "cSAXS",
|
||||
"source_type": "Synchrotron X-ray Source"
|
||||
},
|
||||
"detector": [
|
||||
{
|
||||
"description": "EIGER 9M",
|
||||
"serial_number": "E1",
|
||||
"type": "EIGER",
|
||||
"mirror_y": true,
|
||||
"base_data_ipv4_address": "10.10.10.10",
|
||||
"calibration_file":["/opt/jfjoch/calibration/"],
|
||||
"standard_geometry" : {
|
||||
"nmodules": 18,
|
||||
"modules_in_row": 3,
|
||||
"gap_x": 8,
|
||||
"gap_y": 36
|
||||
},
|
||||
"hostname": [
|
||||
"beb101",
|
||||
"beb103",
|
||||
"beb014",
|
||||
"beb078",
|
||||
"beb060",
|
||||
"beb030",
|
||||
"beb092",
|
||||
"beb178",
|
||||
"beb009",
|
||||
"beb038",
|
||||
"beb056",
|
||||
"beb058",
|
||||
"beb033",
|
||||
"beb113",
|
||||
"beb005",
|
||||
"beb017",
|
||||
"beb119",
|
||||
"beb095",
|
||||
"beb186",
|
||||
"beb042",
|
||||
"beb106",
|
||||
"beb059",
|
||||
"beb111",
|
||||
"beb203",
|
||||
"beb100",
|
||||
"beb093",
|
||||
"beb123",
|
||||
"beb061",
|
||||
"beb121",
|
||||
"beb055",
|
||||
"beb004",
|
||||
"beb190",
|
||||
"beb054",
|
||||
"beb189",
|
||||
"beb107",
|
||||
"beb115"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "EIGER 8.5M (tmp)",
|
||||
"serial_number": "E1-tmp",
|
||||
"type": "EIGER",
|
||||
"mirror_y": true,
|
||||
"base_data_ipv4_address": "10.10.10.10",
|
||||
"calibration_file":["/opt/jfjoch/calibration/"],
|
||||
"standard_geometry" : {
|
||||
"nmodules": 17,
|
||||
"modules_in_row": 3,
|
||||
"gap_x": 8,
|
||||
"gap_y": 36
|
||||
},
|
||||
"hostname": [
|
||||
"beb101",
|
||||
"beb103",
|
||||
"beb014",
|
||||
"beb078",
|
||||
"beb060",
|
||||
"beb030",
|
||||
"beb092",
|
||||
"beb178",
|
||||
"beb009",
|
||||
"beb038",
|
||||
"beb056",
|
||||
"beb058",
|
||||
"beb033",
|
||||
"beb113",
|
||||
"beb005",
|
||||
"beb017",
|
||||
"beb119",
|
||||
"beb095",
|
||||
"beb186",
|
||||
"beb042",
|
||||
"beb106",
|
||||
"beb059",
|
||||
"beb100",
|
||||
"beb093",
|
||||
"beb123",
|
||||
"beb061",
|
||||
"beb121",
|
||||
"beb055",
|
||||
"beb004",
|
||||
"beb190",
|
||||
"beb054",
|
||||
"beb189",
|
||||
"beb107",
|
||||
"beb115"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "EIGER 1.5M",
|
||||
"serial_number": "E2",
|
||||
"type": "EIGER",
|
||||
"mirror_y": true,
|
||||
"base_data_ipv4_address": "10.10.11.10",
|
||||
"calibration_file":["/opt/jfjoch/calibration_e1p5m/"],
|
||||
"standard_geometry" : {
|
||||
"nmodules": 3,
|
||||
"modules_in_row": 1,
|
||||
"gap_x": 8,
|
||||
"gap_y": 36
|
||||
},
|
||||
"hostname": ["beb062", "beb026", "beb099", "beb084", "beb120", "beb108"]
|
||||
}
|
||||
],
|
||||
"frontend_directory": "/usr/share/jfjoch/frontend/",
|
||||
"image_pusher": "ZeroMQ",
|
||||
"numa_policy": "n2g2",
|
||||
"receiver_threads": 64,
|
||||
"image_buffer_MiB": 96000,
|
||||
"pcie": [
|
||||
{
|
||||
"blk": "/dev/jfjoch0",
|
||||
"ipv4": "10.10.10.1"
|
||||
},
|
||||
{
|
||||
"blk": "/dev/jfjoch1",
|
||||
"ipv4": "10.10.10.2"
|
||||
},
|
||||
{
|
||||
"blk": "/dev/jfjoch2",
|
||||
"ipv4": "10.10.10.3"
|
||||
},
|
||||
{
|
||||
"blk": "/dev/jfjoch3",
|
||||
"ipv4": "10.10.10.4"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -48,11 +48,6 @@ elif _args.session.lower() == "csaxs":
|
||||
|
||||
logger.success("cSAXS session loaded.")
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.tool_box.debug_tools import DebugTools
|
||||
|
||||
debug = DebugTools()
|
||||
logger.success("Debug tools loaded. Use 'debug' to access them.")
|
||||
|
||||
|
||||
# SETUP BEAMLINE INFO
|
||||
from bec_ipython_client.plugins.SLS.sls_info import OperatorInfo, SLSInfo
|
||||
@@ -64,5 +59,28 @@ bec._beamline_mixin._bl_info_register(SLSInfo)
|
||||
bec._beamline_mixin._bl_info_register(OperatorInfo)
|
||||
|
||||
# SETUP PROMPTS
|
||||
bec._ip.prompts.session_name = _session_name
|
||||
bec._ip.prompts.username = _session_name
|
||||
bec._ip.prompts.status = 1
|
||||
|
||||
|
||||
# REGISTER BEAMLINE CHECKS
|
||||
from bec_lib.bl_conditions import (
|
||||
FastOrbitFeedbackCondition,
|
||||
LightAvailableCondition,
|
||||
ShutterCondition,
|
||||
)
|
||||
|
||||
if "sls_machine_status" in dev:
|
||||
print("Registering light available condition for SLS machine status")
|
||||
_light_available_condition = LightAvailableCondition(dev.sls_machine_status)
|
||||
bec.bl_checks.register(_light_available_condition)
|
||||
|
||||
if "x12sa_es1_shutter_status" in dev:
|
||||
print("Registering shutter condition for X12SA ES1 shutter status")
|
||||
_shutter_condition = ShutterCondition(dev.x12sa_es1_shutter_status)
|
||||
bec.bl_checks.register(_shutter_condition)
|
||||
|
||||
# if hasattr(dev, "sls_fast_orbit_feedback"):
|
||||
# print("Registering fast orbit feedback condition for SLS fast orbit feedback")
|
||||
# _fast_orbit_feedback_condition = FastOrbitFeedbackCondition(dev.sls_fast_orbit_feedback)
|
||||
# bec.bl_checks.register(_fast_orbit_feedback_condition)
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
"""
|
||||
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 csaxs_bec
|
||||
|
||||
|
||||
def extend_command_line_args(parser):
|
||||
"""
|
||||
@@ -18,15 +12,3 @@ def extend_command_line_args(parser):
|
||||
parser.add_argument("--session", help="Session name", type=str, default="cSAXS")
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
# def get_config() -> ServiceConfig:
|
||||
# """
|
||||
# Create and return the ServiceConfig for the plugin repository
|
||||
# """
|
||||
# deployment_path = os.path.dirname(os.path.dirname(os.path.dirname(csaxs_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})
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bec_widgets.widgets.containers.auto_update.auto_updates import AutoUpdates
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.messages import ScanStatusMessage
|
||||
|
||||
|
||||
class cSAXSUpdate(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)
|
||||
if 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.
|
||||
"""
|
||||
|
||||
|
||||
class cSAXSUpdateAlignment(AutoUpdates): ...
|
||||
|
||||
|
||||
class cSAXSUpdateScan(AutoUpdates): ...
|
||||
@@ -1,148 +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 = {
|
||||
"OmnyAlignment": "OmnyAlignment",
|
||||
"XRayEye": "XRayEye",
|
||||
}
|
||||
|
||||
|
||||
class OmnyAlignment(RPCBase):
|
||||
@property
|
||||
@rpc_call
|
||||
def enable_live_view(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@enable_live_view.setter
|
||||
@rpc_call
|
||||
def enable_live_view(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def user_message(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@user_message.setter
|
||||
@rpc_call
|
||||
def user_message(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def sample_name(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@sample_name.setter
|
||||
@rpc_call
|
||||
def sample_name(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def enable_move_buttons(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@enable_move_buttons.setter
|
||||
@rpc_call
|
||||
def enable_move_buttons(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
|
||||
class XRayEye(RPCBase):
|
||||
@rpc_call
|
||||
def active_roi(self) -> "BaseROI | None":
|
||||
"""
|
||||
Return the currently active ROI, or None if no ROI is active.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def enable_live_view(self):
|
||||
"""
|
||||
Get or set the live view enabled state.
|
||||
"""
|
||||
|
||||
@enable_live_view.setter
|
||||
@rpc_call
|
||||
def enable_live_view(self):
|
||||
"""
|
||||
Get or set the live view enabled state.
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def user_message(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@user_message.setter
|
||||
@rpc_call
|
||||
def user_message(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def sample_name(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@sample_name.setter
|
||||
@rpc_call
|
||||
def sample_name(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@property
|
||||
@rpc_call
|
||||
def enable_move_buttons(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
@enable_move_buttons.setter
|
||||
@rpc_call
|
||||
def enable_move_buttons(self):
|
||||
"""
|
||||
None
|
||||
"""
|
||||
|
||||
|
||||
class XRayEye2DControl(RPCBase):
|
||||
@rpc_call
|
||||
def remove(self):
|
||||
"""
|
||||
Cleanup the BECConnector
|
||||
"""
|
||||
@@ -1,140 +0,0 @@
|
||||
|
||||
|
||||
from typing import TypedDict
|
||||
from bec_widgets.utils.error_popups import SafeSlot
|
||||
import os
|
||||
from bec_widgets.utils.bec_widget import BECWidget
|
||||
from bec_widgets.utils.ui_loader import UILoader
|
||||
from qtpy.QtWidgets import QWidget, QPushButton, QLineEdit, QLabel, QVBoxLayout
|
||||
from bec_qthemes import material_icon
|
||||
from bec_lib.logger import bec_logger
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
# class OmnyAlignmentUIComponents(TypedDict):
|
||||
# moveRightButton: QPushButton
|
||||
# moveLeftButton: QPushButton
|
||||
# moveUpButton: QPushButton
|
||||
# moveDownButton: QPushButton
|
||||
# image: Image
|
||||
|
||||
|
||||
class OmnyAlignment(BECWidget, QWidget):
|
||||
USER_ACCESS = ["enable_live_view", "enable_live_view.setter", "user_message", "user_message.setter","sample_name", "sample_name.setter", "enable_move_buttons", "enable_move_buttons.setter"]
|
||||
PLUGIN = True
|
||||
ui_file = "./omny_alignment.ui"
|
||||
|
||||
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)
|
||||
|
||||
icon_options = {"size": (16, 16), "convert_to_pixmap": False}
|
||||
self.ui.moveRightButton.setText("")
|
||||
self.ui.moveRightButton.setIcon(
|
||||
material_icon(icon_name="keyboard_arrow_right", **icon_options)
|
||||
)
|
||||
|
||||
self.ui.moveLeftButton.setText("")
|
||||
self.ui.moveLeftButton.setIcon(
|
||||
material_icon(icon_name="keyboard_arrow_left", **icon_options)
|
||||
)
|
||||
|
||||
self.ui.moveUpButton.setText("")
|
||||
self.ui.moveUpButton.setIcon(
|
||||
material_icon(icon_name="keyboard_arrow_up", **icon_options)
|
||||
)
|
||||
|
||||
self.ui.moveDownButton.setText("")
|
||||
self.ui.moveDownButton.setIcon(
|
||||
material_icon(icon_name="keyboard_arrow_down", **icon_options)
|
||||
)
|
||||
|
||||
self.ui.confirmButton.setText("OK")
|
||||
|
||||
|
||||
self.ui.liveViewSwitch.enabled.connect(self.on_live_view_enabled)
|
||||
|
||||
# self.ui.moveUpButton.clicked.connect(self.on_move_up)
|
||||
|
||||
|
||||
@property
|
||||
def enable_live_view(self):
|
||||
return self.ui.liveViewSwitch.checked
|
||||
|
||||
@enable_live_view.setter
|
||||
def enable_live_view(self, enable:bool):
|
||||
self.ui.liveViewSwitch.checked = enable
|
||||
|
||||
|
||||
@property
|
||||
def user_message(self):
|
||||
return self.ui.messageLineEdit.text()
|
||||
|
||||
@user_message.setter
|
||||
def user_message(self, message:str):
|
||||
self.ui.messageLineEdit.setText(message)
|
||||
|
||||
@property
|
||||
def sample_name(self):
|
||||
return self.ui.sampleLineEdit.text()
|
||||
|
||||
@sample_name.setter
|
||||
def sample_name(self, message:str):
|
||||
self.ui.sampleLineEdit.setText(message)
|
||||
|
||||
|
||||
@SafeSlot(bool)
|
||||
def on_live_view_enabled(self, enabled:bool):
|
||||
from bec_widgets.widgets.plots.image.image import Image
|
||||
logger.info(f"Live view is enabled: {enabled}")
|
||||
image: Image = self.ui.image
|
||||
if enabled:
|
||||
image.image("cam_xeye")
|
||||
return
|
||||
|
||||
image.disconnect_monitor("cam_xeye")
|
||||
|
||||
|
||||
@property
|
||||
def enable_move_buttons(self):
|
||||
move_up:QPushButton = self.ui.moveUpButton
|
||||
move_down:QPushButton = self.ui.moveDownButton
|
||||
move_left:QPushButton = self.ui.moveLeftButton
|
||||
move_right:QPushButton = self.ui.moveRightButton
|
||||
return move_up.isEnabled() and move_down.isEnabled() and move_left.isEnabled() and move_right.isEnabled()
|
||||
|
||||
@enable_move_buttons.setter
|
||||
def enable_move_buttons(self, enabled:bool):
|
||||
move_up:QPushButton = self.ui.moveUpButton
|
||||
move_down:QPushButton = self.ui.moveDownButton
|
||||
move_left:QPushButton = self.ui.moveLeftButton
|
||||
move_right:QPushButton = self.ui.moveRightButton
|
||||
|
||||
move_up.setEnabled(enabled)
|
||||
move_down.setEnabled(enabled)
|
||||
move_left.setEnabled(enabled)
|
||||
move_right.setEnabled(enabled)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from qtpy.QtWidgets import QApplication
|
||||
import sys
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
widget = OmnyAlignment()
|
||||
|
||||
widget.show()
|
||||
sys.exit(app.exec_())
|
||||
@@ -1 +0,0 @@
|
||||
{'files': ['omny_alignment.py']}
|
||||
@@ -1,125 +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>988</width>
|
||||
<height>821</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="2" column="2">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="2">
|
||||
<widget class="QPushButton" name="moveRightButton">
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="moveLeftButton">
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="moveUpButton">
|
||||
<property name="text">
|
||||
<string>Up</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="moveDownButton">
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="confirmButton">
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="sampleLineEdit"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="messageLineEdit"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Sample</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Message</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="3">
|
||||
<widget class="Image" name="image">
|
||||
<property name="enable_toolbar" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="inner_axes" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="monitor" stdset="0">
|
||||
<string>cam_xeye</string>
|
||||
</property>
|
||||
<property name="rotation" stdset="0">
|
||||
<number>3</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="ToggleSwitch" name="liveViewSwitch"/>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Live View</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Image</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>image</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ToggleSwitch</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>toggle_switch</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 csaxs_bec.bec_widgets.widgets.omny_alignment.omny_alignment import OmnyAlignment
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
<widget class='OmnyAlignment' name='omny_alignment'>
|
||||
</widget>
|
||||
</ui>
|
||||
"""
|
||||
|
||||
|
||||
class OmnyAlignmentPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._form_editor = None
|
||||
|
||||
def createWidget(self, parent):
|
||||
t = OmnyAlignment(parent)
|
||||
return t
|
||||
|
||||
def domXml(self):
|
||||
return DOM_XML
|
||||
|
||||
def group(self):
|
||||
return ""
|
||||
|
||||
def icon(self):
|
||||
return designer_material_icon(OmnyAlignment.ICON_NAME)
|
||||
|
||||
def includeFile(self):
|
||||
return "omny_alignment"
|
||||
|
||||
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 "OmnyAlignment"
|
||||
|
||||
def toolTip(self):
|
||||
return "OmnyAlignment"
|
||||
|
||||
def whatsThis(self):
|
||||
return self.toolTip()
|
||||
@@ -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 csaxs_bec.bec_widgets.widgets.omny_alignment.omny_alignment_plugin import OmnyAlignmentPlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(OmnyAlignmentPlugin())
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
main()
|
||||
@@ -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 csaxs_bec.bec_widgets.widgets.xray_eye.x_ray_eye_plugin import XRayEyePlugin
|
||||
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(XRayEyePlugin())
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
main()
|
||||
@@ -1,426 +0,0 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from bec_lib import bec_logger
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_qthemes import material_icon
|
||||
from bec_widgets import BECWidget, SafeProperty, SafeSlot
|
||||
from bec_widgets.widgets.plots.image.image import Image
|
||||
from bec_widgets.widgets.plots.image.setting_widgets.image_roi_tree import ROIPropertyTree
|
||||
from bec_widgets.widgets.plots.roi.image_roi import BaseROI, CircularROI, RectangularROI
|
||||
from bec_widgets.widgets.utility.toggle.toggle import ToggleSwitch
|
||||
from qtpy.QtCore import Qt, QTimer
|
||||
from qtpy.QtWidgets import (
|
||||
QFrame,
|
||||
QGridLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QSizePolicy,
|
||||
QSpinBox,
|
||||
QToolButton,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
logger = bec_logger.logger
|
||||
CAMERA = ("cam_xeye", "image")
|
||||
|
||||
|
||||
class XRayEye2DControl(BECWidget, QWidget):
|
||||
def __init__(self, parent=None, step_size: int = 100, *arg, **kwargs):
|
||||
super().__init__(parent=parent, *arg, **kwargs)
|
||||
self.get_bec_shortcuts()
|
||||
self._step_size = step_size
|
||||
self.root_layout = QGridLayout(self)
|
||||
self.setStyleSheet("""
|
||||
QToolButton {
|
||||
border: 1px solid;
|
||||
border-radius: 4px;
|
||||
}
|
||||
""")
|
||||
# Up
|
||||
self.move_up_button = QToolButton(parent=self)
|
||||
self.move_up_button.setIcon(material_icon('keyboard_double_arrow_up'))
|
||||
self.root_layout.addWidget(self.move_up_button, 0, 2)
|
||||
# Up tweak button
|
||||
self.move_up_tweak_button = QToolButton(parent=self)
|
||||
self.move_up_tweak_button.setIcon(material_icon('keyboard_arrow_up'))
|
||||
self.root_layout.addWidget(self.move_up_tweak_button, 1, 2)
|
||||
|
||||
# Left
|
||||
self.move_left_button = QToolButton(parent=self)
|
||||
self.move_left_button.setIcon(material_icon('keyboard_double_arrow_left'))
|
||||
self.root_layout.addWidget(self.move_left_button, 2, 0)
|
||||
# Left tweak button
|
||||
self.move_left_tweak_button = QToolButton(parent=self)
|
||||
self.move_left_tweak_button.setIcon(material_icon('keyboard_arrow_left'))
|
||||
self.root_layout.addWidget(self.move_left_tweak_button, 2, 1)
|
||||
|
||||
# Right
|
||||
self.move_right_button = QToolButton(parent=self)
|
||||
self.move_right_button.setIcon(material_icon('keyboard_double_arrow_right'))
|
||||
self.root_layout.addWidget(self.move_right_button, 2, 4)
|
||||
# Right tweak button
|
||||
self.move_right_tweak_button = QToolButton(parent=self)
|
||||
self.move_right_tweak_button.setIcon(material_icon('keyboard_arrow_right'))
|
||||
self.root_layout.addWidget(self.move_right_tweak_button, 2, 3)
|
||||
|
||||
# Down
|
||||
self.move_down_button = QToolButton(parent=self)
|
||||
self.move_down_button.setIcon(material_icon('keyboard_double_arrow_down'))
|
||||
self.root_layout.addWidget(self.move_down_button, 4, 2)
|
||||
# Down tweak button
|
||||
self.move_down_tweak_button = QToolButton(parent=self)
|
||||
self.move_down_tweak_button.setIcon(material_icon('keyboard_arrow_down'))
|
||||
self.root_layout.addWidget(self.move_down_tweak_button, 3, 2)
|
||||
|
||||
# Connections
|
||||
self.move_up_button.clicked.connect(lambda: self.move("up", tweak=False))
|
||||
self.move_up_tweak_button.clicked.connect(lambda: self.move("up", tweak=True))
|
||||
self.move_down_button.clicked.connect(lambda: self.move("down", tweak=False))
|
||||
self.move_down_tweak_button.clicked.connect(lambda: self.move("down", tweak=True))
|
||||
self.move_left_button.clicked.connect(lambda: self.move("left", tweak=False))
|
||||
self.move_left_tweak_button.clicked.connect(lambda: self.move("left", tweak=True))
|
||||
self.move_right_button.clicked.connect(lambda: self.move("right", tweak=False))
|
||||
self.move_right_tweak_button.clicked.connect(lambda: self.move("right", tweak=True))
|
||||
|
||||
@SafeProperty(int)
|
||||
def step_size(self) -> int:
|
||||
return self._step_size
|
||||
|
||||
@step_size.setter
|
||||
def step_size(self, step_size: int):
|
||||
self._step_size = step_size
|
||||
|
||||
@SafeSlot(bool)
|
||||
def enable_controls_hor(self, enable: bool):
|
||||
self.move_left_button.setEnabled(enable)
|
||||
self.move_left_tweak_button.setEnabled(enable)
|
||||
self.move_right_button.setEnabled(enable)
|
||||
self.move_right_tweak_button.setEnabled(enable)
|
||||
|
||||
@SafeSlot(bool)
|
||||
def enable_controls_ver(self, enable: bool):
|
||||
self.move_up_button.setEnabled(enable)
|
||||
self.move_up_tweak_button.setEnabled(enable)
|
||||
self.move_down_button.setEnabled(enable)
|
||||
self.move_down_tweak_button.setEnabled(enable)
|
||||
|
||||
def move(self, direction: str, tweak: bool = False):
|
||||
step = self._step_size
|
||||
if tweak:
|
||||
step = int(self._step_size / 5)
|
||||
if direction == "up":
|
||||
self.dev.omny_xray_gui.mvy.set(step)
|
||||
elif direction == "down":
|
||||
self.dev.omny_xray_gui.mvy.set(-step)
|
||||
elif direction == "left":
|
||||
self.dev.omny_xray_gui.mvx.set(-step)
|
||||
elif direction == "right":
|
||||
self.dev.omny_xray_gui.mvx.set(step)
|
||||
else:
|
||||
logger.warning(f"Unknown direction {direction} for move command.")
|
||||
|
||||
|
||||
class XRayEye(BECWidget, QWidget):
|
||||
USER_ACCESS = ["active_roi", "enable_live_view", "enable_live_view.setter", "user_message", "user_message.setter",
|
||||
"sample_name", "sample_name.setter", "enable_move_buttons", "enable_move_buttons.setter"]
|
||||
PLUGIN = True
|
||||
|
||||
def __init__(self, parent=None, **kwargs):
|
||||
super().__init__(parent=parent, **kwargs)
|
||||
self.get_bec_shortcuts()
|
||||
|
||||
self._init_ui()
|
||||
self._make_connections()
|
||||
|
||||
# Connection to redis endpoints
|
||||
self.bec_dispatcher.connect_slot(self.device_updates, MessageEndpoints.device_readback("omny_xray_gui"))
|
||||
self.connect_motors()
|
||||
self.resize(800, 600)
|
||||
QTimer.singleShot(0, self._init_gui_trigger)
|
||||
|
||||
def _init_ui(self):
|
||||
self.core_layout = QHBoxLayout(self)
|
||||
|
||||
self.image = Image(parent=self)
|
||||
self.image.enable_toolbar = False # Disable default toolbar to not allow to user set anything
|
||||
self.image.inner_axes = False # Disable inner axes to maximize image area
|
||||
self.image.plot_item.vb.invertY(True) # #TODO Invert y axis to match logic of LabView GUI
|
||||
|
||||
# Control panel on the right: vertical layout inside a fixed-width widget
|
||||
self.control_panel = QWidget(parent=self)
|
||||
self.control_panel_layout = QVBoxLayout(self.control_panel)
|
||||
self.control_panel_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.control_panel_layout.setSpacing(10)
|
||||
|
||||
# ROI toolbar + Live toggle (header row)
|
||||
self.roi_manager = ROIPropertyTree(parent=self, image_widget=self.image, compact=True,
|
||||
compact_orientation="horizontal")
|
||||
header_row = QHBoxLayout()
|
||||
header_row.setContentsMargins(0, 0, 0, 0)
|
||||
header_row.setSpacing(8)
|
||||
header_row.addWidget(self.roi_manager, 0)
|
||||
header_row.addStretch()
|
||||
self.live_preview_label = QLabel("Live Preview", parent=self)
|
||||
self.live_preview_toggle = ToggleSwitch(parent=self)
|
||||
self.live_preview_toggle.checked = False
|
||||
header_row.addWidget(self.live_preview_label, 0, Qt.AlignVCenter)
|
||||
header_row.addWidget(self.live_preview_toggle, 0, Qt.AlignVCenter)
|
||||
self.control_panel_layout.addLayout(header_row)
|
||||
|
||||
# separator
|
||||
self.control_panel_layout.addWidget(self._create_separator())
|
||||
|
||||
# 2D Positioner (fixed size)
|
||||
self.motor_control_2d = XRayEye2DControl(parent=self)
|
||||
self.control_panel_layout.addWidget(self.motor_control_2d, 0, Qt.AlignTop | Qt.AlignCenter)
|
||||
|
||||
# separator
|
||||
self.control_panel_layout.addWidget(self._create_separator())
|
||||
|
||||
# Step size label
|
||||
step_size_form = QGridLayout()
|
||||
# General Step size
|
||||
self.step_size = QSpinBox(parent=self)
|
||||
self.step_size.setRange(10, 100)
|
||||
self.step_size.setSingleStep(10)
|
||||
self.step_size.setValue(100)
|
||||
# Submit button
|
||||
self.submit_button = QPushButton("Submit", parent=self)
|
||||
# Add to layout form
|
||||
step_size_form.addWidget(QLabel("Horizontal", parent=self), 0, 0)
|
||||
step_size_form.addWidget(self.step_size, 0, 1)
|
||||
step_size_form.addWidget(QLabel("Vertical", parent=self), 1, 0)
|
||||
step_size_form.addWidget(self.submit_button, 2, 0, 1, 2)
|
||||
|
||||
# Add form to control panel
|
||||
self.control_panel_layout.addLayout(step_size_form)
|
||||
|
||||
# Push form to bottom
|
||||
self.control_panel_layout.addStretch()
|
||||
|
||||
# Sample/Message form (bottom)
|
||||
form = QGridLayout()
|
||||
self.sample_name_line_edit = QLineEdit(parent=self)
|
||||
self.sample_name_line_edit.setReadOnly(True)
|
||||
form.addWidget(QLabel("Sample", parent=self), 0, 0)
|
||||
form.addWidget(self.sample_name_line_edit, 0, 1)
|
||||
self.message_line_edit = QLineEdit(parent=self)
|
||||
self.message_line_edit.setReadOnly(True)
|
||||
form.addWidget(QLabel("Message", parent=self), 1, 0)
|
||||
form.addWidget(self.message_line_edit, 1, 1)
|
||||
self.control_panel_layout.addLayout(form)
|
||||
|
||||
# Fix panel width and allow vertical expansion
|
||||
self.control_panel.adjustSize()
|
||||
p_hint = self.control_panel.sizeHint()
|
||||
self.control_panel.setFixedWidth(p_hint.width())
|
||||
self.control_panel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
|
||||
|
||||
# Core Layout: image (expanding) | control panel (fixed)
|
||||
self.core_layout.addWidget(self.image)
|
||||
self.core_layout.addWidget(self.control_panel)
|
||||
|
||||
def _make_connections(self):
|
||||
# Fetch initial state
|
||||
self.on_live_view_enabled(True)
|
||||
self.step_size.setValue(self.motor_control_2d.step_size)
|
||||
|
||||
# Make connections
|
||||
self.live_preview_toggle.enabled.connect(self.on_live_view_enabled)
|
||||
self.step_size.valueChanged.connect(lambda x: self.motor_control_2d.setProperty("step_size", x))
|
||||
self.submit_button.clicked.connect(self.submit)
|
||||
|
||||
def _create_separator(self):
|
||||
sep = QFrame(parent=self)
|
||||
sep.setFrameShape(QFrame.HLine)
|
||||
sep.setFrameShadow(QFrame.Sunken)
|
||||
sep.setLineWidth(1)
|
||||
return sep
|
||||
|
||||
def _init_gui_trigger(self):
|
||||
self.dev.omny_xray_gui.read()
|
||||
|
||||
################################################################################
|
||||
# Device Connection logic
|
||||
################################################################################
|
||||
|
||||
def connect_motors(self):
|
||||
""" Checks one of the possible motors for flomni, omny and lamni setup."""
|
||||
possible_motors = ['osamroy', 'lsamrot', 'fsamroy']
|
||||
|
||||
for motor in possible_motors:
|
||||
if motor in self.dev:
|
||||
self.bec_dispatcher.connect_slot(self.on_tomo_angle_readback, MessageEndpoints.device_readback(motor))
|
||||
logger.info(f"Succesfully connected to {motor}")
|
||||
|
||||
################################################################################
|
||||
# Properties ported from the original OmnyAlignment, can be adjusted as needed
|
||||
################################################################################
|
||||
@SafeProperty(str)
|
||||
def user_message(self):
|
||||
return self.message_line_edit.text()
|
||||
|
||||
@user_message.setter
|
||||
def user_message(self, message: str):
|
||||
self.message_line_edit.setText(message)
|
||||
|
||||
@SafeProperty(str)
|
||||
def sample_name(self):
|
||||
return self.sample_name_line_edit.text()
|
||||
|
||||
@sample_name.setter
|
||||
def sample_name(self, message: str):
|
||||
self.sample_name_line_edit.setText(message)
|
||||
|
||||
@SafeProperty(bool)
|
||||
def enable_move_buttons(self):
|
||||
return self.motor_control_2d.isEnabled()
|
||||
|
||||
@enable_move_buttons.setter
|
||||
def enable_move_buttons(self, enabled: bool):
|
||||
self.motor_control_2d.setEnabled(enabled)
|
||||
|
||||
def active_roi(self) -> BaseROI | None:
|
||||
"""Return the currently active ROI, or None if no ROI is active."""
|
||||
return self.roi_manager.single_active_roi
|
||||
|
||||
################################################################################
|
||||
# Slots ported from the original OmnyAlignment, can be adjusted as needed
|
||||
################################################################################
|
||||
|
||||
@SafeSlot()
|
||||
def get_roi_coordinates(self) -> dict | None:
|
||||
"""Get the coordinates of the currently active ROI."""
|
||||
roi = self.roi_manager.single_active_roi
|
||||
if roi is None:
|
||||
logger.warning("No active ROI")
|
||||
return None
|
||||
logger.info(f"Active ROI coordinates: {roi.get_coordinates()}")
|
||||
return roi.get_coordinates()
|
||||
|
||||
@SafeSlot(bool)
|
||||
def on_live_view_enabled(self, enabled: bool):
|
||||
logger.info(f"Live view is enabled: {enabled}")
|
||||
self.live_preview_toggle.blockSignals(True)
|
||||
if enabled:
|
||||
self.live_preview_toggle.checked = enabled
|
||||
self.image.image(CAMERA)
|
||||
self.live_preview_toggle.blockSignals(False)
|
||||
return
|
||||
|
||||
self.image.disconnect_monitor(CAMERA)
|
||||
self.live_preview_toggle.checked = enabled
|
||||
self.live_preview_toggle.blockSignals(False)
|
||||
|
||||
@SafeSlot(bool, bool)
|
||||
def on_motors_enable(self, x_enable: bool, y_enable: bool):
|
||||
"""
|
||||
Enable/Disable motor controls
|
||||
|
||||
Args:
|
||||
x_enable(bool): enable x motor controls
|
||||
y_enable(bool): enable y motor controls
|
||||
"""
|
||||
self.motor_control_2d.enable_controls_hor(x_enable)
|
||||
self.motor_control_2d.enable_controls_ver(y_enable)
|
||||
|
||||
@SafeSlot(int)
|
||||
def enable_submit_button(self, enable: int):
|
||||
"""
|
||||
Enable/disable submit button.
|
||||
Args:
|
||||
enable(int): -1 disable else enable
|
||||
"""
|
||||
if enable == -1:
|
||||
self.submit_button.setEnabled(False)
|
||||
else:
|
||||
self.submit_button.setEnabled(True)
|
||||
|
||||
@SafeSlot(bool, bool)
|
||||
def on_tomo_angle_readback(self, data: dict, meta: dict):
|
||||
#TODO implement if needed
|
||||
print(f"data: {data}")
|
||||
print(f"meta: {meta}")
|
||||
|
||||
@SafeSlot(dict, dict)
|
||||
def device_updates(self, data: dict, meta: dict):
|
||||
"""
|
||||
Slot to handle device updates from omny_xray_gui device.
|
||||
|
||||
Args:
|
||||
data(dict): data from device
|
||||
meta(dict): metadata from device
|
||||
"""
|
||||
|
||||
signals = data.get('signals')
|
||||
enable_live_preview = signals.get("omny_xray_gui_update_frame_acq").get('value')
|
||||
enable_x_motor = signals.get("omny_xray_gui_enable_mv_x").get('value')
|
||||
enable_y_motor = signals.get("omny_xray_gui_enable_mv_y").get('value')
|
||||
self.on_live_view_enabled(bool(enable_live_preview))
|
||||
self.on_motors_enable(bool(enable_x_motor), bool(enable_y_motor))
|
||||
|
||||
# Signals from epics gui device
|
||||
# send message
|
||||
user_message = signals.get("omny_xray_gui_send_message").get('value')
|
||||
self.user_message = user_message
|
||||
# sample name
|
||||
sample_message = signals.get("omny_xray_gui_sample_name").get('value')
|
||||
self.sample_name = sample_message
|
||||
# enable frame acquisition
|
||||
update_frame_acq = signals.get("omny_xray_gui_update_frame_acq").get('value')
|
||||
self.on_live_view_enabled(bool(update_frame_acq))
|
||||
# enable submit button
|
||||
enable_submit_button = signals.get("omny_xray_gui_submit").get('value')
|
||||
self.enable_submit_button(enable_submit_button)
|
||||
|
||||
@SafeSlot()
|
||||
def submit(self):
|
||||
"""Execute submit action by submit button."""
|
||||
if self.roi_manager.single_active_roi is None:
|
||||
logger.warning("No active ROI")
|
||||
return
|
||||
roi_coordinates = self.roi_manager.single_active_roi.get_coordinates()
|
||||
roi_center_x = roi_coordinates['center_x']
|
||||
roi_center_y = roi_coordinates['center_y']
|
||||
# Case of rectangular ROI
|
||||
if isinstance(self.roi_manager.single_active_roi, RectangularROI):
|
||||
roi_width = roi_coordinates['width']
|
||||
roi_height = roi_coordinates['height']
|
||||
elif isinstance(self.roi_manager.single_active_roi, CircularROI):
|
||||
roi_width = roi_coordinates['diameter']
|
||||
roi_height = roi_coordinates['radius']
|
||||
else:
|
||||
logger.warning("Unsupported ROI type for submit action.")
|
||||
return
|
||||
|
||||
print(f"current roi: x:{roi_center_x}, y:{roi_center_y}, w:{roi_width},h:{roi_height}") #TODO remove when will be not needed for debugging
|
||||
# submit roi coordinates
|
||||
step = int(self.dev.omny_xray_gui.step.read().get("omny_xray_gui_step").get('value'))
|
||||
|
||||
xval_x = getattr(self.dev.omny_xray_gui.xval_x, f"xval_x_{step}").set(roi_center_x)
|
||||
xval_y = getattr(self.dev.omny_xray_gui.yval_y, f"yval_y_{step}").set(roi_center_y)
|
||||
width_x = getattr(self.dev.omny_xray_gui.width_x, f"width_x_{step}").set(roi_width)
|
||||
width_y = getattr(self.dev.omny_xray_gui.width_y, f"width_y_{step}").set(roi_height)
|
||||
self.dev.omny_xray_gui.submit.set(1)
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup connections on widget close -> disconnect slots and stop live mode of camera."""
|
||||
self.bec_dispatcher.disconnect_slot(self.device_updates, MessageEndpoints.device_readback("omny_xray_gui"))
|
||||
getattr(self.dev,CAMERA[0]).live_mode = False
|
||||
super().cleanup()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
win = XRayEye()
|
||||
|
||||
win.resize(1000, 800)
|
||||
win.show()
|
||||
sys.exit(app.exec_())
|
||||
@@ -1 +0,0 @@
|
||||
{'files': ['x_ray_eye.py']}
|
||||
@@ -1,57 +0,0 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
from bec_widgets.utils.bec_designer import designer_material_icon
|
||||
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
||||
from qtpy.QtWidgets import QWidget
|
||||
|
||||
from csaxs_bec.bec_widgets.widgets.xray_eye.x_ray_eye import XRayEye
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
<widget class='XRayEye' name='x_ray_eye'>
|
||||
</widget>
|
||||
</ui>
|
||||
"""
|
||||
|
||||
|
||||
class XRayEyePlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._form_editor = None
|
||||
|
||||
def createWidget(self, parent):
|
||||
if parent is None:
|
||||
return QWidget()
|
||||
t = XRayEye(parent)
|
||||
return t
|
||||
|
||||
def domXml(self):
|
||||
return DOM_XML
|
||||
|
||||
def group(self):
|
||||
return ""
|
||||
|
||||
def icon(self):
|
||||
return designer_material_icon(XRayEye.ICON_NAME)
|
||||
|
||||
def includeFile(self):
|
||||
return "x_ray_eye"
|
||||
|
||||
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 "XRayEye"
|
||||
|
||||
def toolTip(self):
|
||||
return "XRayEye"
|
||||
|
||||
def whatsThis(self):
|
||||
return self.toolTip()
|
||||
11
csaxs_bec/deployment/device_server/startup.py
Normal file
11
csaxs_bec/deployment/device_server/startup.py
Normal file
@@ -0,0 +1,11 @@
|
||||
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,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()
|
||||
133
csaxs_bec/device_configs/bec_device_config_sastt.yaml
Normal file → Executable file
133
csaxs_bec/device_configs/bec_device_config_sastt.yaml
Normal file → Executable file
@@ -17,7 +17,7 @@ bpm4i:
|
||||
softwareTrigger: false
|
||||
mokev:
|
||||
description: Monochromator energy in keV
|
||||
deviceClass: csaxs_bec.devices.epics.specMotors.EnergyKev
|
||||
deviceClass: csaxs_bec.devices.epics.devices.specMotors.EnergyKev
|
||||
deviceConfig:
|
||||
read_pv: X12SA-OP-MO:ROX2
|
||||
deviceTags:
|
||||
@@ -27,23 +27,23 @@ mokev:
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
# mcs:
|
||||
# description: Mcs scalar card for transmission readout
|
||||
# deviceClass: csaxs_bec.devices.epics.mcs_csaxs.MCScSAXS
|
||||
# deviceConfig:
|
||||
# prefix: 'X12SA-MCS:'
|
||||
# mcs_config:
|
||||
# num_lines: 1
|
||||
# deviceTags:
|
||||
# - cSAXS
|
||||
# - mcs
|
||||
# onFailure: buffer
|
||||
# enabled: true
|
||||
# readoutPriority: monitored
|
||||
# softwareTrigger: false
|
||||
mcs:
|
||||
description: Mcs scalar card for transmission readout
|
||||
deviceClass: csaxs_bec.devices.epics.devices.MCScSAXS
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-MCS:'
|
||||
mcs_config:
|
||||
num_lines: 1
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- mcs
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: monitored
|
||||
softwareTrigger: false
|
||||
eiger9m:
|
||||
description: Eiger9m HPC area detector 9M
|
||||
deviceClass: csaxs_bec.devices.epics.eiger9m_csaxs.Eiger9McSAXS
|
||||
deviceClass: csaxs_bec.devices.epics.devices.Eiger9McSAXS
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-ES-EIGER9M:'
|
||||
deviceTags:
|
||||
@@ -53,9 +53,92 @@ eiger9m:
|
||||
enabled: true
|
||||
readoutPriority: async
|
||||
softwareTrigger: false
|
||||
ddg_detectors:
|
||||
description: DelayGenerator for detector triggering
|
||||
deviceClass: csaxs_bec.devices.epics.devices.DelayGeneratorcSAXS
|
||||
deviceConfig:
|
||||
prefix: 'delaygen:DG1:'
|
||||
ddg_config:
|
||||
delay_burst: 40.e-3
|
||||
delta_width: 0
|
||||
additional_triggers: 0
|
||||
polarity:
|
||||
- 1 # T0 -> DDG MCS
|
||||
- 0 # eiger
|
||||
- 1 # falcon
|
||||
- 1
|
||||
- 1
|
||||
amplitude: 4.5
|
||||
offset: 0
|
||||
thres_trig_level: 2.5
|
||||
set_high_on_exposure: False
|
||||
set_high_on_stage: False
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- ddg_detectors
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: async
|
||||
softwareTrigger: false
|
||||
ddg_mcs:
|
||||
description: DelayGenerator for mcs triggering
|
||||
deviceClass: csaxs_bec.devices.epics.devices.DelayGeneratorcSAXS
|
||||
deviceConfig:
|
||||
prefix: 'delaygen:DG2:'
|
||||
ddg_config:
|
||||
delay_burst: 0
|
||||
delta_width: 0
|
||||
additional_triggers: 1
|
||||
polarity:
|
||||
- 1
|
||||
- 0
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
amplitude: 4.5
|
||||
offset: 0
|
||||
thres_trig_level: 2.5
|
||||
set_high_on_exposure: False
|
||||
set_high_on_stage: False
|
||||
set_trigger_source: EXT_RISING_EDGE
|
||||
trigger_width: 3.e-3
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- ddg_mcs
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: async
|
||||
softwareTrigger: false
|
||||
ddg_fsh:
|
||||
description: DelayGenerator for fast shutter control
|
||||
deviceClass: csaxs_bec.devices.epics.devices.DelayGeneratorcSAXS
|
||||
deviceConfig:
|
||||
prefix: 'delaygen:DG3:'
|
||||
ddg_config:
|
||||
delay_burst: 0
|
||||
delta_width: 80.e-3
|
||||
additional_triggers: 0
|
||||
polarity:
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
- 1
|
||||
amplitude: 4.5
|
||||
offset: 0
|
||||
thres_trig_level: 2.5
|
||||
set_high_on_exposure: True
|
||||
set_high_on_stage: False
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- ddg_fsh
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: async
|
||||
softwareTrigger: false
|
||||
falcon:
|
||||
description: Falcon detector x-ray fluoresence
|
||||
deviceClass: csaxs_bec.devices.epics.falcon_csaxs.FalconcSAXS
|
||||
deviceClass: csaxs_bec.devices.epics.devices.FalconcSAXS
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-SITORO:'
|
||||
deviceTags:
|
||||
@@ -67,7 +150,7 @@ falcon:
|
||||
softwareTrigger: false
|
||||
pilatus_2:
|
||||
description: Pilatus2 HPC area detector 300k
|
||||
deviceClass: csaxs_bec.devices.epics.pilatus_csaxs.PilatuscSAXS
|
||||
deviceClass: csaxs_bec.devices.epics.devices.PilatuscSAXS
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-ES-PILATUS300K:'
|
||||
deviceTags:
|
||||
@@ -79,7 +162,7 @@ pilatus_2:
|
||||
softwareTrigger: false
|
||||
samx:
|
||||
description: SGalil motor stage
|
||||
deviceClass: csaxs_bec.devices.omny.galil.SGalilMotor
|
||||
deviceClass: csaxs_bec.devices.galil.SGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: "E"
|
||||
host: '129.129.122.26'
|
||||
@@ -97,7 +180,7 @@ samx:
|
||||
softwareTrigger: false
|
||||
samy:
|
||||
description: SGalil motor stage
|
||||
deviceClass: csaxs_bec.devices.omny.galil.SGalilMotor
|
||||
deviceClass: csaxs_bec.devices.galil.SGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: "C"
|
||||
host: '129.129.122.26'
|
||||
@@ -115,7 +198,7 @@ samy:
|
||||
softwareTrigger: false
|
||||
micfoc:
|
||||
description: Focusing motor of Microscope stage
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceClass: ophyd_devices.epics.devices.EpicsMotorEx
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES06
|
||||
motor_resolution: 0.00125
|
||||
@@ -133,7 +216,7 @@ micfoc:
|
||||
softwareTrigger: false
|
||||
owis_samx:
|
||||
description: Owis motor stage samx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceClass: ophyd_devices.epics.devices.EpicsMotorEx
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES01
|
||||
motor_resolution: 0.00125
|
||||
@@ -151,7 +234,7 @@ owis_samx:
|
||||
softwareTrigger: false
|
||||
owis_samy:
|
||||
description: Owis motor stage samx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceClass: ophyd_devices.epics.devices.EpicsMotorEx
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES02
|
||||
motor_resolution: 0.00125
|
||||
@@ -169,7 +252,7 @@ owis_samy:
|
||||
softwareTrigger: false
|
||||
rotx:
|
||||
description: Rotation stage rotx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceClass: ophyd_devices.epics.devices.EpicsMotorEx
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES05
|
||||
motor_resolution: 0.0025
|
||||
@@ -190,7 +273,7 @@ rotx:
|
||||
softwareTrigger: false
|
||||
roty:
|
||||
description: Rotation stage rotx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceClass: ophyd_devices.epics.devices.EpicsMotorEx
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES04
|
||||
motor_resolution: 0.0025
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
############################################################
|
||||
leyex:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceClass: csaxs_bec.devices.galil.galil_ophyd.GalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: G
|
||||
host: mpc2680.psi.ch
|
||||
@@ -22,7 +22,7 @@ leyex:
|
||||
in: 14.117
|
||||
leyey:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceClass: csaxs_bec.devices.galil.galil_ophyd.GalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: H
|
||||
host: mpc2680.psi.ch
|
||||
@@ -42,7 +42,7 @@ leyey:
|
||||
out: 0.5
|
||||
loptx:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceClass: csaxs_bec.devices.galil.galil_ophyd.GalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: E
|
||||
host: mpc2680.psi.ch
|
||||
@@ -62,7 +62,7 @@ loptx:
|
||||
out: -0.699
|
||||
lopty:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceClass: csaxs_bec.devices.galil.galil_ophyd.GalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: F
|
||||
host: mpc2680.psi.ch
|
||||
@@ -82,7 +82,7 @@ lopty:
|
||||
out: 3.53
|
||||
loptz:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceClass: csaxs_bec.devices.galil.galil_ophyd.GalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: D
|
||||
host: mpc2680.psi.ch
|
||||
@@ -99,7 +99,7 @@ loptz:
|
||||
readoutPriority: baseline
|
||||
lsamrot:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceClass: csaxs_bec.devices.galil.galil_ophyd.GalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc2680.psi.ch
|
||||
@@ -116,7 +116,7 @@ lsamrot:
|
||||
readoutPriority: baseline
|
||||
lsamx:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceClass: csaxs_bec.devices.galil.galil_ophyd.GalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2680.psi.ch
|
||||
@@ -135,7 +135,7 @@ lsamx:
|
||||
center: 8.768
|
||||
lsamy:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceClass: csaxs_bec.devices.galil.galil_ophyd.GalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc2680.psi.ch
|
||||
@@ -162,7 +162,7 @@ lsamy:
|
||||
############################################################
|
||||
|
||||
rtx:
|
||||
deviceClass: csaxs_bec.devices.omny.rt.rt_lamni_ophyd.RtLamniMotor
|
||||
deviceClass: csaxs_bec.devices.rt_lamni.rt_lamni_ophyd.RtLamniMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
device_access: true
|
||||
@@ -179,7 +179,7 @@ rtx:
|
||||
enabled: true
|
||||
readOnly: False
|
||||
rty:
|
||||
deviceClass: csaxs_bec.devices.omny.rt.rt_lamni_ophyd.RtLamniMotor
|
||||
deviceClass: csaxs_bec.devices.rt_lamni.rt_lamni_ophyd.RtLamniMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
device_access: true
|
||||
@@ -293,7 +293,7 @@ losaz:
|
||||
|
||||
eiger1p5m:
|
||||
description: Eiger 1.5M in vacuum detector, in-house developed, PSI
|
||||
deviceClass: csaxs_bec.devices.omny.eiger1p5m.Eiger1p5MDetector
|
||||
deviceClass: csaxs_bec.devices.eiger1p5m_csaxs.eiger1p5m.Eiger1p5MDetector
|
||||
deviceConfig:
|
||||
device_access: true
|
||||
deviceTags:
|
||||
@@ -504,7 +504,7 @@ sls_filling_pattern:
|
||||
readOnly: True
|
||||
sls_info:
|
||||
readoutPriority: on_request
|
||||
deviceClass: ophyd_devices.devices.sls_devices.SLSInfo
|
||||
deviceClass: ophyd_devices.sls_devices.sls_devices.SLSInfo
|
||||
deviceConfig:
|
||||
deviceTags:
|
||||
- SLS status
|
||||
@@ -537,7 +537,7 @@ sls_machine_status:
|
||||
readOnly: True
|
||||
sls_operator:
|
||||
readoutPriority: on_request
|
||||
deviceClass: ophyd_devices.devices.sls_devices.SLSOperatorMessages
|
||||
deviceClass: ophyd_devices.sls_devices.sls_devices.SLSOperatorMessages
|
||||
deviceConfig:
|
||||
auto_monitor: true
|
||||
read_pv: ARIDI-BPM:OFB-MODE
|
||||
@@ -802,7 +802,7 @@ bm5try:
|
||||
softwareTrigger: false
|
||||
bpm1:
|
||||
description: 'XBPM 1: Somewhere around mono (VME)'
|
||||
deviceClass: csaxs_bec.devices.epics.XbpmBase.XbpmCsaxsOp
|
||||
deviceClass: csaxs_bec.devices.epics.devices.XbpmBase.XbpmCsaxsOp
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-BPM2:'
|
||||
deviceTags:
|
||||
@@ -824,7 +824,7 @@ bpm1i:
|
||||
softwareTrigger: false
|
||||
bpm2:
|
||||
description: 'XBPM 2: Somewhere around mono (VME)'
|
||||
deviceClass: csaxs_bec.devices.epics.XbpmBase.XbpmCsaxsOp
|
||||
deviceClass: csaxs_bec.devices.epics.devices.XbpmBase.XbpmCsaxsOp
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-BPM2:'
|
||||
deviceTags:
|
||||
@@ -1187,7 +1187,7 @@ dtpush:
|
||||
softwareTrigger: false
|
||||
dtth:
|
||||
description: Detector tower tilt rotation
|
||||
deviceClass: csaxs_bec.devices.epics.specMotors.PmDetectorRotation
|
||||
deviceClass: csaxs_bec.devices.epics.devices.specMotors.PmDetectorRotation
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES1-DETT:ROX1
|
||||
deviceTags:
|
||||
@@ -1462,7 +1462,7 @@ fttrz:
|
||||
softwareTrigger: false
|
||||
idgap:
|
||||
description: Undulator gap size [mm]
|
||||
deviceClass: csaxs_bec.devices.epics.InsertionDevice.InsertionDevice
|
||||
deviceClass: csaxs_bec.devices.epics.devices.InsertionDevice
|
||||
deviceConfig:
|
||||
prefix: X12SA-ID
|
||||
deviceTags:
|
||||
@@ -1561,7 +1561,7 @@ mitry3:
|
||||
softwareTrigger: false
|
||||
mobd:
|
||||
description: Monochromator bender virtual motor
|
||||
deviceClass: csaxs_bec.devices.epics.specMotors.PmMonoBender
|
||||
deviceClass: csaxs_bec.devices.epics.devices.specMotors.PmMonoBender
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-MO:'
|
||||
deviceTags:
|
||||
@@ -1616,7 +1616,7 @@ mobddi:
|
||||
softwareTrigger: false
|
||||
mokev:
|
||||
description: Monochromator energy in keV
|
||||
deviceClass: csaxs_bec.devices.epics.specMotors.EnergyKev
|
||||
deviceClass: csaxs_bec.devices.epics.devices.specMotors.EnergyKev
|
||||
deviceConfig:
|
||||
read_pv: X12SA-OP-MO:ROX2
|
||||
deviceTags:
|
||||
@@ -1671,7 +1671,7 @@ moroll2:
|
||||
softwareTrigger: false
|
||||
moth1:
|
||||
description: Monochromator Theta 1
|
||||
deviceClass: csaxs_bec.devices.epics.specMotors.MonoTheta1
|
||||
deviceClass: csaxs_bec.devices.epics.devices.specMotors.MonoTheta1
|
||||
deviceConfig:
|
||||
read_pv: X12SA-OP-MO:ROX1
|
||||
deviceTags:
|
||||
@@ -1693,7 +1693,7 @@ moth1e:
|
||||
softwareTrigger: false
|
||||
moth2:
|
||||
description: Monochromator Theta 2
|
||||
deviceClass: csaxs_bec.devices.epics.specMotors.MonoTheta2
|
||||
deviceClass: csaxs_bec.devices.epics.devices.specMotors.MonoTheta2
|
||||
deviceConfig:
|
||||
read_pv: X12SA-OP-MO:ROX2
|
||||
deviceTags:
|
||||
@@ -1812,17 +1812,17 @@ sec:
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
# sl0h:
|
||||
# description: FrontEnd slit virtual movement
|
||||
# deviceClass: ophyd_devices.devices.SlitH
|
||||
# deviceConfig:
|
||||
# prefix: 'X12SA-FE-SH1:'
|
||||
# deviceTags:
|
||||
# - epicsDevice
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
sl0h:
|
||||
description: FrontEnd slit virtual movement
|
||||
deviceClass: ophyd_devices.epics.devices.SlitH
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-FE-SH1:'
|
||||
deviceTags:
|
||||
- epicsDevice
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
sl0trxi:
|
||||
description: FrontEnd slit inner blade movement
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
@@ -1845,17 +1845,17 @@ sl0trxo:
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
# sl1h:
|
||||
# description: OpticsHutch slit virtual movement
|
||||
# deviceClass: ophyd_devices.devices.SlitH
|
||||
# deviceConfig:
|
||||
# prefix: 'X12SA-OP-SH1:'
|
||||
# deviceTags:
|
||||
# - epicsDevice
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
sl1h:
|
||||
description: OpticsHutch slit virtual movement
|
||||
deviceClass: ophyd_devices.epics.devices.SlitH
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-SH1:'
|
||||
deviceTags:
|
||||
- epicsDevice
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
sl1trxi:
|
||||
description: OpticsHutch slit inner blade movement
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
@@ -1900,28 +1900,28 @@ sl1tryt:
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
# sl1v:
|
||||
# description: OpticsHutch slit virtual movement
|
||||
# deviceClass: ophyd_devices.devices.SlitV
|
||||
# deviceConfig:
|
||||
# prefix: 'X12SA-OP-SV1:'
|
||||
# deviceTags:
|
||||
# - epicsDevice
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
# sl2h:
|
||||
# description: OpticsHutch slit 2 virtual movement
|
||||
# deviceClass: ophyd_devices.devices.SlitH
|
||||
# deviceConfig:
|
||||
# prefix: 'X12SA-OP-SH2:'
|
||||
# deviceTags:
|
||||
# - epicsDevice
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
sl1v:
|
||||
description: OpticsHutch slit virtual movement
|
||||
deviceClass: ophyd_devices.epics.devices.SlitV
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-SV1:'
|
||||
deviceTags:
|
||||
- epicsDevice
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
sl2h:
|
||||
description: OpticsHutch slit 2 virtual movement
|
||||
deviceClass: ophyd_devices.epics.devices.SlitH
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-SH2:'
|
||||
deviceTags:
|
||||
- epicsDevice
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
sl2trxi:
|
||||
description: OpticsHutch slit 2 inner blade movement
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
@@ -1966,72 +1966,72 @@ sl2tryt:
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
# sl2v:
|
||||
# description: OpticsHutch slit 2 virtual movement
|
||||
# deviceClass: ophyd_devices.devices.SlitV
|
||||
# deviceConfig:
|
||||
# prefix: 'X12SA-OP-SV2:'
|
||||
# deviceTags:
|
||||
# - epicsDevice
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
# strox:
|
||||
# description: Girder virtual pitch
|
||||
# deviceClass: ophyd_devices.devices.GirderMotorPITCH
|
||||
# deviceConfig:
|
||||
# prefix: X12SA-HG
|
||||
# deviceTags:
|
||||
# - beamlineMotor
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
# stroy:
|
||||
# description: Girder virtual yaw
|
||||
# deviceClass: ophyd_devices.devices.GirderMotorYAW
|
||||
# deviceConfig:
|
||||
# prefix: X12SA-HG
|
||||
# deviceTags:
|
||||
# - beamlineMotor
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
# stroz:
|
||||
# description: Girder virtual roll
|
||||
# deviceClass: ophyd_devices.devices.GirderMotorROLL
|
||||
# deviceConfig:
|
||||
# prefix: X12SA-HG
|
||||
# deviceTags:
|
||||
# - beamlineMotor
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
# sttrx:
|
||||
# description: Girder X translation
|
||||
# deviceClass: ophyd_devices.devices.GirderMotorX1
|
||||
# deviceConfig:
|
||||
# prefix: X12SA-HG
|
||||
# deviceTags:
|
||||
# - beamlineMotor
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
# sttry:
|
||||
# description: Girder Y translation
|
||||
# deviceClass: ophyd_devices.devices.GirderMotorY1
|
||||
# deviceConfig:
|
||||
# prefix: X12SA-HG
|
||||
# deviceTags:
|
||||
# - beamlineMotor
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
sl2v:
|
||||
description: OpticsHutch slit 2 virtual movement
|
||||
deviceClass: ophyd_devices.epics.devices.SlitV
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-SV2:'
|
||||
deviceTags:
|
||||
- epicsDevice
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
strox:
|
||||
description: Girder virtual pitch
|
||||
deviceClass: ophyd_devices.epics.devices.GirderMotorPITCH
|
||||
deviceConfig:
|
||||
prefix: X12SA-HG
|
||||
deviceTags:
|
||||
- beamlineMotor
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
stroy:
|
||||
description: Girder virtual yaw
|
||||
deviceClass: ophyd_devices.epics.devices.GirderMotorYAW
|
||||
deviceConfig:
|
||||
prefix: X12SA-HG
|
||||
deviceTags:
|
||||
- beamlineMotor
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
stroz:
|
||||
description: Girder virtual roll
|
||||
deviceClass: ophyd_devices.epics.devices.GirderMotorROLL
|
||||
deviceConfig:
|
||||
prefix: X12SA-HG
|
||||
deviceTags:
|
||||
- beamlineMotor
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
sttrx:
|
||||
description: Girder X translation
|
||||
deviceClass: ophyd_devices.epics.devices.GirderMotorX1
|
||||
deviceConfig:
|
||||
prefix: X12SA-HG
|
||||
deviceTags:
|
||||
- beamlineMotor
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
sttry:
|
||||
description: Girder Y translation
|
||||
deviceClass: ophyd_devices.epics.devices.GirderMotorY1
|
||||
deviceConfig:
|
||||
prefix: X12SA-HG
|
||||
deviceTags:
|
||||
- beamlineMotor
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
transd:
|
||||
description: Transmission diode
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
ddg1:
|
||||
description: Main delay Generator for triggering
|
||||
deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DDG1
|
||||
enabled: true
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-CPCL-DDG1:'
|
||||
onFailure: raise
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: true
|
||||
|
||||
ddg2:
|
||||
description: Detector delay Generator for trigger burst
|
||||
deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DDG2
|
||||
enabled: true
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-CPCL-DDG2:'
|
||||
onFailure: raise
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
|
||||
mcs:
|
||||
description: Mcs scalar card for transmission readout
|
||||
deviceClass: csaxs_bec.devices.epics.mcs_card.mcs_card_csaxs.MCSCardCSAXS
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-MCS:'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: monitored
|
||||
softwareTrigger: false
|
||||
|
||||
ids_cam:
|
||||
description: IDS camera for live image acquisition
|
||||
deviceClass: csaxs_bec.devices.ids_cameras.IDSCamera
|
||||
deviceConfig:
|
||||
camera_id: 201
|
||||
bits_per_pixel: 24
|
||||
m_n_colormode: 1
|
||||
live_mode: True
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: async
|
||||
softwareTrigger: True
|
||||
|
||||
eiger_1_5:
|
||||
description: Eiger 1.5M in-vacuum detector
|
||||
deviceClass: csaxs_bec.devices.jungfraujoch.eiger_1_5m.Eiger1_5M
|
||||
deviceConfig:
|
||||
detector_distance: 100
|
||||
beam_center: [0, 0]
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: async
|
||||
softwareTrigger: False
|
||||
@@ -1,8 +0,0 @@
|
||||
optics:
|
||||
- !include ./optics_hutch.yaml
|
||||
|
||||
frontend:
|
||||
- !include ./frontend.yaml
|
||||
|
||||
endstation:
|
||||
- !include ./endstation.yaml
|
||||
@@ -1,10 +1,6 @@
|
||||
############################################################
|
||||
#################### flOMNI Galil motors ###################
|
||||
############################################################
|
||||
|
||||
feyex:
|
||||
description: Xray eye X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: D
|
||||
host: mpc2844.psi.ch
|
||||
@@ -21,8 +17,8 @@ feyex:
|
||||
in: -16.267
|
||||
out: -1
|
||||
feyey:
|
||||
description: Xray eye Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: E
|
||||
host: mpc2844.psi.ch
|
||||
@@ -38,8 +34,8 @@ feyey:
|
||||
userParameter:
|
||||
in: -10.467
|
||||
fheater:
|
||||
description: Heater Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc2844.psi.ch
|
||||
@@ -52,9 +48,17 @@ fheater:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
flomni_samples:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.epics.devices.flomni_sample_storage.FlomniSampleStorage
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
foptx:
|
||||
description: Optics X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc2844.psi.ch
|
||||
@@ -70,8 +74,8 @@ foptx:
|
||||
userParameter:
|
||||
in: -13.761
|
||||
fopty:
|
||||
description: Optics Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: F
|
||||
host: mpc2844.psi.ch
|
||||
@@ -88,8 +92,8 @@ fopty:
|
||||
in: 0.552
|
||||
out: 0.752
|
||||
foptz:
|
||||
description: Optics Z
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2844.psi.ch
|
||||
@@ -104,168 +108,8 @@ foptz:
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 23
|
||||
fsamroy:
|
||||
description: Sample rotation
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fupr_ophyd.FuprGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -5
|
||||
- 365
|
||||
port: 8084
|
||||
sign: -1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
fsamx:
|
||||
description: Sample coarse X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: E
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -162
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: -1.1
|
||||
fsamy:
|
||||
description: Sample coarse Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: F
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 2
|
||||
- 3.1
|
||||
port: 8081
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 2.75
|
||||
ftracky:
|
||||
description: Laser Tracker coarse Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: H
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 2.2
|
||||
- 2.8
|
||||
port: 8082
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
ftrackz:
|
||||
description: Laser Tracker coarse Z
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: G
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 4.5
|
||||
- 5.5
|
||||
port: 8082
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
ftransx:
|
||||
description: Sample transer X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 50
|
||||
port: 8081
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
ftransy:
|
||||
description: Sample transer Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -100
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
sensor_voltage: -2.4
|
||||
ftransz:
|
||||
description: Sample transer Z
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 145
|
||||
port: 8081
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
ftray:
|
||||
description: Sample transfer tray
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: D
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 0
|
||||
port: 8081
|
||||
sign: -1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
|
||||
|
||||
############################################################
|
||||
#################### flOMNI Sample Names ###################
|
||||
############################################################
|
||||
|
||||
flomni_samples:
|
||||
description: Sample names and storage
|
||||
deviceClass: csaxs_bec.devices.omny.flomni_sample_storage.FlomniSampleStorage
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
|
||||
############################################################
|
||||
#################### flOMNI Smaract motors #################
|
||||
############################################################
|
||||
|
||||
fosax:
|
||||
description: OSA X
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
@@ -283,7 +127,7 @@ fosax:
|
||||
in: 9.124
|
||||
out: 5.3
|
||||
fosay:
|
||||
description: OSA Y
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
@@ -300,7 +144,7 @@ fosay:
|
||||
userParameter:
|
||||
in: 0.367
|
||||
fosaz:
|
||||
description: OSA Z
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
@@ -317,14 +161,148 @@ fosaz:
|
||||
userParameter:
|
||||
in: 8.5
|
||||
out: 6
|
||||
|
||||
############################################################
|
||||
#################### flOMNI RT motors ######################
|
||||
############################################################
|
||||
|
||||
fsamroy:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fupr_ophyd.FuprGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -5
|
||||
- 365
|
||||
port: 8084
|
||||
sign: -1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
fsamx:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: E
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -162
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: -1.1
|
||||
fsamy:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: F
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 2
|
||||
- 3.1
|
||||
port: 8081
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 2.75
|
||||
ftracky:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: H
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 2.2
|
||||
- 2.8
|
||||
port: 8082
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
ftrackz:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: G
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 4.5
|
||||
- 5.5
|
||||
port: 8082
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
ftransx:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 50
|
||||
port: 8081
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
ftransy:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -100
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
ftransz:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 145
|
||||
port: 8081
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
ftray:
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: D
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 0
|
||||
port: 8081
|
||||
sign: -1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
rtx:
|
||||
description: flomni rt
|
||||
deviceClass: csaxs_bec.devices.omny.rt.rt_flomni_ophyd.RtFlomniMotor
|
||||
deviceClass: csaxs_bec.devices.rt_lamni.rt_flomni_ophyd.RtFlomniMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2844.psi.ch
|
||||
@@ -335,12 +313,12 @@ rtx:
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
userParameter:
|
||||
low_signal: 10000
|
||||
min_signal: 9000
|
||||
low_signal: 11000
|
||||
min_signal: 10000
|
||||
rt_pid_voltage: -0.06219
|
||||
rty:
|
||||
description: flomni rt
|
||||
deviceClass: csaxs_bec.devices.omny.rt.rt_flomni_ophyd.RtFlomniMotor
|
||||
deviceClass: csaxs_bec.devices.rt_lamni.rt_flomni_ophyd.RtFlomniMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc2844.psi.ch
|
||||
@@ -354,7 +332,7 @@ rty:
|
||||
tomo_additional_offsety: 0
|
||||
rtz:
|
||||
description: flomni rt
|
||||
deviceClass: csaxs_bec.devices.omny.rt.rt_flomni_ophyd.RtFlomniMotor
|
||||
deviceClass: csaxs_bec.devices.rt_lamni.rt_flomni_ophyd.RtFlomniMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc2844.psi.ch
|
||||
@@ -364,105 +342,3 @@ rtz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
|
||||
############################################################
|
||||
####################### Cameras ############################
|
||||
############################################################
|
||||
|
||||
cam_flomni_gripper:
|
||||
description: Camera sample changer
|
||||
deviceClass: csaxs_bec.devices.omny.webcam_viewer.WebcamViewer
|
||||
deviceConfig:
|
||||
url: http://flomnicamserver:5000/video_high
|
||||
num_rotation_90: 3
|
||||
transpose: false
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
|
||||
cam_flomni_overview:
|
||||
description: Camera flomni overview
|
||||
deviceClass: csaxs_bec.devices.omny.webcam_viewer.WebcamViewer
|
||||
deviceConfig:
|
||||
url: http://flomnicamserver:5001/video_high
|
||||
num_rotation_90: 3
|
||||
transpose: false
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
|
||||
cam_xeye:
|
||||
description: Camera flOMNI Xray eye ID1
|
||||
deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera
|
||||
deviceConfig:
|
||||
camera_id: 1
|
||||
bits_per_pixel: 24
|
||||
num_rotation_90: 3
|
||||
transpose: false
|
||||
force_monochrome: true
|
||||
m_n_colormode: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: async
|
||||
|
||||
cam_ids_rgb:
|
||||
description: Camera flOMNI Xray eye ID203
|
||||
deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera
|
||||
deviceConfig:
|
||||
camera_id: 203
|
||||
bits_per_pixel: 24
|
||||
num_rotation_90: 3
|
||||
transpose: false
|
||||
force_monochrome: true
|
||||
m_n_colormode: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: async
|
||||
|
||||
|
||||
# ############################################################
|
||||
# ################### flOMNI temperatures ####################
|
||||
# ############################################################
|
||||
flomni_temphum:
|
||||
description: flOMNI Temperatures and humidity
|
||||
deviceClass: csaxs_bec.devices.omny.flomni_temp_and_humidity.FlomniTempHum
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
# ############################################################
|
||||
# ########## OMNY / flOMNI / LamNI fast shutter ##############
|
||||
# ############################################################
|
||||
omnyfsh:
|
||||
description: omnyfsh connects to read fast shutter at X12 if in that network
|
||||
deviceClass: csaxs_bec.devices.omny.shutter.OMNYFastEpicsShutter
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
############################################################
|
||||
#################### GUI Signals ###########################
|
||||
############################################################
|
||||
omny_xray_gui:
|
||||
description: Gui Epics signals
|
||||
deviceClass: csaxs_bec.devices.omny.xray_epics_gui.OMNYXRayEpicsGUI
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
|
||||
calculated_signal:
|
||||
description: Calculated signal from alignment for fit
|
||||
deviceClass: ophyd_devices.ComputedSignal
|
||||
deviceConfig:
|
||||
compute_method: "def just_rand():\n return 42"
|
||||
enabled: true
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
340
csaxs_bec/device_configs/flomni_test_config.yaml
Normal file
340
csaxs_bec/device_configs/flomni_test_config.yaml
Normal file
@@ -0,0 +1,340 @@
|
||||
fheater:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -15
|
||||
- 0
|
||||
port: 8082
|
||||
sign: -1
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
feyex:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: D
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -30
|
||||
- -1
|
||||
port: 8082
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
userParameter:
|
||||
in: -16.267
|
||||
out: -1
|
||||
enabled: true
|
||||
readOnly: false
|
||||
feyey:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: E
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -1
|
||||
- -10
|
||||
port: 8082
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
userParameter:
|
||||
in: -10.467
|
||||
enabled: true
|
||||
readOnly: false
|
||||
foptx:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -17
|
||||
- -12
|
||||
port: 8082
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
userParameter:
|
||||
in: -13.761
|
||||
enabled: true
|
||||
readOnly: false
|
||||
fopty:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: F
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 4
|
||||
port: 8082
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
userParameter:
|
||||
in: 0.552
|
||||
out: 0.752
|
||||
enabled: true
|
||||
readOnly: false
|
||||
foptz:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 27
|
||||
port: 8082
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
userParameter:
|
||||
in: 23
|
||||
enabled: true
|
||||
readOnly: false
|
||||
fosax:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 10.2
|
||||
- 10.6
|
||||
port: 3334
|
||||
sign: -1
|
||||
onFailure: buffer
|
||||
userParameter:
|
||||
in: 9.124
|
||||
out: 5.3
|
||||
enabled: true
|
||||
readOnly: false
|
||||
fosay:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -3.1
|
||||
- -2.9
|
||||
port: 3334
|
||||
sign: -1
|
||||
onFailure: buffer
|
||||
userParameter:
|
||||
in: 0.367
|
||||
enabled: true
|
||||
readOnly: false
|
||||
fosaz:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -6
|
||||
- -4
|
||||
port: 3334
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
userParameter:
|
||||
in: 8.5
|
||||
out: 6
|
||||
enabled: true
|
||||
readOnly: false
|
||||
fsamroy:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fupr_ophyd.FuprGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -5
|
||||
- 365
|
||||
port: 8084
|
||||
sign: -1
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
fsamx:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: E
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -162
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
userParameter:
|
||||
in: -1.1
|
||||
fsamy:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: F
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 2
|
||||
- 3.1
|
||||
port: 8081
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
userParameter:
|
||||
in: 2.75
|
||||
ftracky:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: H
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 2.2
|
||||
- 2.8
|
||||
port: 8082
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
ftrackz:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: G
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 4.5
|
||||
- 5.5
|
||||
port: 8082
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
ftransx:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 50
|
||||
port: 8081
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
ftransy:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -100
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
ftransz:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 145
|
||||
port: 8081
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
ftray:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: D
|
||||
host: mpc2844.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 0
|
||||
port: 8081
|
||||
sign: -1
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
|
||||
flomni_samples:
|
||||
readoutPriority: baseline
|
||||
description: phase plate angle
|
||||
deviceClass: csaxs_bec.devices.epics.devices.FlomniSampleStorage
|
||||
deviceConfig:
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
|
||||
rtx:
|
||||
readoutPriority: on_request
|
||||
description: flomni rt
|
||||
deviceClass: csaxs_bec.devices.rt_lamni.rt_flomni_ophyd.RtFlomniMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2844.psi.ch
|
||||
port: 2222
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
rty:
|
||||
readoutPriority: on_request
|
||||
description: flomni rt
|
||||
deviceClass: csaxs_bec.devices.rt_lamni.rt_flomni_ophyd.RtFlomniMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc2844.psi.ch
|
||||
port: 2222
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
rtz:
|
||||
readoutPriority: on_request
|
||||
description: flomni rt
|
||||
deviceClass: csaxs_bec.devices.rt_lamni.rt_flomni_ophyd.RtFlomniMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc2844.psi.ch
|
||||
port: 2222
|
||||
sign: 1
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readOnly: false
|
||||
@@ -1,215 +0,0 @@
|
||||
idgap:
|
||||
description: 'Motor to control the IDGap of X12SA'
|
||||
deviceClass: ophyd_devices.devices.undulator.UndulatorGap
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-UIND:'
|
||||
onFailure: raise # Consider changing to buffer
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false # put to false if you like to move it
|
||||
softwareTrigger: false
|
||||
|
||||
xbpm1x:
|
||||
description: 'X-ray BPM1 in frontend translation x'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-FE-XBPM1:TRX'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- frontend
|
||||
|
||||
xbpm1y:
|
||||
description: 'X-ray BPM1 in frontend translation y'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-FE-XBPM1:TRY'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- frontend
|
||||
|
||||
sl1xr:
|
||||
description: 'slit 1 (frontend) x ring'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-FE-SL1:TRXR'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- frontend
|
||||
|
||||
sl1xw:
|
||||
description: 'slit 1 (frontend) x wall'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-FE-SL1:TRXW'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- frontend
|
||||
|
||||
sl1yb:
|
||||
description: 'slit 1 (frontend) y bottom'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-FE-SL1:TRYB'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- frontend
|
||||
|
||||
sl1yt:
|
||||
description: 'slit 1 (frontend) y top'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-FE-SL1:TRYT'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- frontend
|
||||
|
||||
sl1xc:
|
||||
description: 'slit 1 (frontend) x center'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-FE-SL1:CENTERX'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- frontend
|
||||
|
||||
sl1xs:
|
||||
description: 'slit 1 (frontend) x size'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-FE-SL1:SIZEX'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- frontend
|
||||
|
||||
sl1yc:
|
||||
description: 'slit 1 (frontend) y center'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-FE-SL1:CENTERY'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- frontend
|
||||
|
||||
sl1ys:
|
||||
description: 'slit 1 (frontend) y size'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-FE-SL1:SIZEY'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- frontend
|
||||
#####################################
|
||||
#### XBPM ###########################
|
||||
#####################################
|
||||
|
||||
# Note: The following device may not be relevant anymore
|
||||
# and can be fully replaced by the combined device "xbpm1", see below
|
||||
|
||||
xbpm1c1:
|
||||
description: 'XBPM1 (frontend) current 1'
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: 'X12SA-FE-XBPM1:Current1:MeanValue_RBV'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: monitored
|
||||
readOnly: true
|
||||
softwareTrigger: false
|
||||
|
||||
xbpm1c2:
|
||||
description: 'XBPM1 (frontend) current 2'
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: 'X12SA-FE-XBPM1:Current2:MeanValue_RBV'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: monitored
|
||||
readOnly: true
|
||||
softwareTrigger: false
|
||||
|
||||
xbpm1c3:
|
||||
description: 'XBPM1 (frontend) current 3'
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: 'X12SA-FE-XBPM1:Current3:MeanValue_RBV'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: monitored
|
||||
readOnly: true
|
||||
softwareTrigger: false
|
||||
|
||||
xbpm1c4:
|
||||
description: 'XBPM1 (frontend) current 4'
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
deviceConfig:
|
||||
read_pv: 'X12SA-FE-XBPM1:Current4:MeanValue_RBV'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: monitored
|
||||
readOnly: true
|
||||
softwareTrigger: false
|
||||
|
||||
############################################
|
||||
######### End of xbpm sub devices ##########
|
||||
############################################
|
||||
|
||||
xbpm1:
|
||||
description: 'XBPM1 (frontend)'
|
||||
deviceClass: csaxs_bec.devices.epics.xbpms.BPMDevice
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-FE-XBPM1'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: monitored
|
||||
readOnly: true
|
||||
softwareTrigger: false
|
||||
@@ -1,261 +0,0 @@
|
||||
############################################################
|
||||
#################### LamNI Galil motors ####################
|
||||
############################################################
|
||||
|
||||
leyex:
|
||||
description: Xray eye X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: G
|
||||
host: mpc2680.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: -1
|
||||
deviceTags:
|
||||
- lamni
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 14.117
|
||||
leyey:
|
||||
description: Xray eye Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: H
|
||||
host: mpc2680.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: -1
|
||||
deviceTags:
|
||||
- lamni
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 48.069
|
||||
out: 0.5
|
||||
loptx:
|
||||
description: Optics X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: E
|
||||
host: mpc2680.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- lamni
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: -0.244
|
||||
out: -0.699
|
||||
lopty:
|
||||
description: Optics Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: F
|
||||
host: mpc2680.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- lamni
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 3.724
|
||||
out: 3.53
|
||||
loptz:
|
||||
description: Optics Z
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: D
|
||||
host: mpc2680.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: -1
|
||||
deviceTags:
|
||||
- lamni
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
lsamrot:
|
||||
description: Sample rotation
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc2680.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- lamni
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
lsamx:
|
||||
description: Sample coarse X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2680.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: -1
|
||||
deviceTags:
|
||||
- lamni
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
center: 8.768
|
||||
lsamy:
|
||||
description: Sample coarse Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc2680.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- lamni
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
center: 10.041
|
||||
|
||||
|
||||
############################################################
|
||||
################ LamNI Smaract motors ######################
|
||||
############################################################
|
||||
|
||||
losax:
|
||||
description: OSA X
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc2680.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8085
|
||||
sign: -1
|
||||
deviceTags:
|
||||
- lamni
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: -1.442
|
||||
losay:
|
||||
description: OSA Y
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc2680.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8085
|
||||
sign: -1
|
||||
deviceTags:
|
||||
- lamni
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: -0.171
|
||||
out: 3.8
|
||||
losaz:
|
||||
description: OSA Z
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc2680.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8085
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- lamni
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: -1
|
||||
out: -3
|
||||
|
||||
############################################################
|
||||
#################### flOMNI RT motors ######################
|
||||
############################################################
|
||||
|
||||
rtx:
|
||||
deviceClass: csaxs_bec.devices.omny.rt.rt_lamni_ophyd.RtLamniMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
device_access: true
|
||||
host: mpc2680.psi.ch
|
||||
labels: rtx
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 3333
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- lamni
|
||||
readoutPriority: baseline
|
||||
enabled: true
|
||||
readOnly: False
|
||||
rty:
|
||||
deviceClass: csaxs_bec.devices.omny.rt.rt_lamni_ophyd.RtLamniMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
device_access: true
|
||||
host: mpc2680.psi.ch
|
||||
labels: rty
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 3333
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- lamni
|
||||
readoutPriority: baseline
|
||||
enabled: true
|
||||
readOnly: False
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
############################################################
|
||||
#################### npoint motors #########################
|
||||
############################################################
|
||||
|
||||
npx:
|
||||
description: nPoint x axis on the big npoint controller
|
||||
deviceClass: csaxs_bec.devices.npoint.npoint.NPointAxis
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: "nPoint000003.psi.ch"
|
||||
limits:
|
||||
- -50
|
||||
- 50
|
||||
port: 23
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
deviceTags:
|
||||
- npoint
|
||||
npy:
|
||||
description: nPoint y axis on the big npoint controller
|
||||
deviceClass: csaxs_bec.devices.npoint.npoint.NPointAxis
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: "nPoint000003.psi.ch"
|
||||
limits:
|
||||
- -50
|
||||
- 50
|
||||
port: 23
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
deviceTags:
|
||||
- npoint
|
||||
@@ -1,610 +0,0 @@
|
||||
# ############################################################
|
||||
# #################### IDS Camera ######################
|
||||
# ############################################################
|
||||
cam200:
|
||||
description: Camera200
|
||||
deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera
|
||||
deviceConfig:
|
||||
camera_ID: 200
|
||||
bits_per_pixel: 24
|
||||
channels: 3
|
||||
m_n_colormode: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: async
|
||||
|
||||
cam201:
|
||||
description: Camera201
|
||||
deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera
|
||||
deviceConfig:
|
||||
camera_ID: 201
|
||||
bits_per_pixel: 24
|
||||
channels: 3
|
||||
m_n_colormode: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: async
|
||||
|
||||
cam202:
|
||||
description: Camera202
|
||||
deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera
|
||||
deviceConfig:
|
||||
camera_ID: 202
|
||||
bits_per_pixel: 24
|
||||
channels: 3
|
||||
m_n_colormode: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: async
|
||||
|
||||
cam203:
|
||||
description: Camera203
|
||||
deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera
|
||||
deviceConfig:
|
||||
camera_ID: 203
|
||||
bits_per_pixel: 24
|
||||
channels: 3
|
||||
m_n_colormode: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: async
|
||||
|
||||
############################################################
|
||||
#################### OMNY RT motors ########################
|
||||
############################################################
|
||||
|
||||
rtx:
|
||||
description: OMNY rt
|
||||
deviceClass: csaxs_bec.devices.omny.rt.rt_omny_ophyd.RtOMNYMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc3217.psi.ch
|
||||
port: 3333
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
userParameter:
|
||||
low_signal: 8500
|
||||
min_signal: 8000
|
||||
rty:
|
||||
description: OMNY rt
|
||||
deviceClass: csaxs_bec.devices.omny.rt.rt_omny_ophyd.RtOMNYMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc3217.psi.ch
|
||||
port: 3333
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
userParameter:
|
||||
tomo_additional_offsety: 0
|
||||
rtz:
|
||||
description: OMNY rt
|
||||
deviceClass: csaxs_bec.devices.omny.rt.rt_omny_ophyd.RtOMNYMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc3217.psi.ch
|
||||
port: 3333
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
|
||||
# ############################################################
|
||||
# ##################### OMNY samples #########################
|
||||
# ############################################################
|
||||
omny_samples:
|
||||
description: OMNYSampleStorage
|
||||
deviceClass: csaxs_bec.devices.omny.omny_sample_storage.OMNYSampleStorage
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
# ############################################################
|
||||
# ##################### OMNY vacuum ##########################
|
||||
# ############################################################
|
||||
# omny_vcs:
|
||||
# description: OMNYVCS
|
||||
# deviceClass: csaxs_bec.devices.omny.omny_vcs.OMNYVCS
|
||||
# deviceConfig: {}
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readOnly: false
|
||||
# readoutPriority: baseline
|
||||
# ############################################################
|
||||
# ##################### OMNY dewar ###########################
|
||||
# ############################################################
|
||||
omny_dewar:
|
||||
description: OMNY Dewar Information
|
||||
deviceClass: csaxs_bec.devices.omny.omny_dewar.OMNYDewar
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
|
||||
# ############################################################
|
||||
# ##################### OMNY temperatures ####################
|
||||
# ############################################################
|
||||
omny_temperatures:
|
||||
description: OMNY Temperatures and pressures
|
||||
deviceClass: csaxs_bec.devices.omny.omny_temperatures.OMNYTemperatures
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
|
||||
############################################################
|
||||
##################### OMNY Galil motors ####################
|
||||
############################################################
|
||||
ofzpx:
|
||||
description: FZP X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: -0.4317
|
||||
ofzpy:
|
||||
description: FZP Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 0.7944
|
||||
out: 0.6377
|
||||
ofzpz:
|
||||
description: FZP Z
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: -1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 0
|
||||
otransx:
|
||||
description: Transfer X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: D
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 0
|
||||
otransy:
|
||||
description: Transfer Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: E
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
up_position: -1.2
|
||||
gripper_sensorvoltagetarget: -2.30
|
||||
otransz:
|
||||
description: Transfer Z
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: F
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8081
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 0
|
||||
osamx:
|
||||
description: Sample X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8082
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: -0.1
|
||||
osamz:
|
||||
description: Sample Z
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8082
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: false
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 0
|
||||
oosay:
|
||||
description: OSA Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8082
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
near_field_in: 0.531
|
||||
far_field_in: 0.4122
|
||||
oosax:
|
||||
description: OSA X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: D
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8082
|
||||
sign: -1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
near_field_in: 3.2044
|
||||
far_field_in: 3.022
|
||||
oosaz:
|
||||
description: OSA Z
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: E
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8082
|
||||
sign: -1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
near_field_in: -0.4452
|
||||
far_field_in: 6.5
|
||||
oparkz:
|
||||
description: OSA Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: F
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8082
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 0
|
||||
oshuttleopen:
|
||||
description: Shuttle opener
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: G
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8082
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 0
|
||||
oshuttlealign:
|
||||
description: Shuttle aligner
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: H
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8082
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 0
|
||||
osamy:
|
||||
description: Sample Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8083
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 0
|
||||
otracky:
|
||||
description: Laser Tracker Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8083
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
start_pos: -4.3431
|
||||
osamroy:
|
||||
description: Sample rotation
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8083
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
in: 0
|
||||
otrackz:
|
||||
description: Laser Tracker Z
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: E
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8083
|
||||
sign: -1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
start_pos: -0.6948
|
||||
oeyex:
|
||||
description: Xray eye X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: F
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8083
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
xray_in: -45.7394
|
||||
oeyez:
|
||||
description: Xray eye Z
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: G
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8083
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
xray_in: -2
|
||||
oeyey:
|
||||
description: Xray eye Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.ogalil_ophyd.OMNYGalilMotor
|
||||
deviceConfig:
|
||||
axis_Id: H
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- 0
|
||||
- 0
|
||||
port: 8083
|
||||
sign: 1
|
||||
deviceTags:
|
||||
- omny
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
xray_in: 0.0229
|
||||
|
||||
############################################################
|
||||
#################### flOMNI Smaract motors #################
|
||||
############################################################
|
||||
|
||||
ocsx:
|
||||
description: Central Stop X
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- -2
|
||||
- 2
|
||||
port: 3334
|
||||
sign: -1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
nothing: 0
|
||||
ocsy:
|
||||
description: Central Stop Y
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- -2
|
||||
- 2
|
||||
port: 3334
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
nothing: 0
|
||||
oshield:
|
||||
description: Thermal Shield Sample Stage
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: mpc3217.psi.ch
|
||||
limits:
|
||||
- -14.5
|
||||
- 15.8
|
||||
port: 3334
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
userParameter:
|
||||
nothing: 0
|
||||
@@ -1,162 +0,0 @@
|
||||
dmmroty:
|
||||
description: 'Double Multilayer Monochromator rotation Y'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-DMM1:ROTY'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- optics
|
||||
|
||||
dmmx:
|
||||
description: 'Double Multilayer Monochromator, translation X'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-DMM1:TRX'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- optics
|
||||
|
||||
dmmy:
|
||||
description: 'Double Multilayer Monochromator, translation Y'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-DMM1:TRY'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- optics
|
||||
|
||||
ccmroty:
|
||||
description: 'Channel-cut Monochromator rotation Y'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-CCM1:ROTY'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- optics
|
||||
|
||||
ccmx:
|
||||
description: 'Channel-cut Monochromator, translation X'
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-CCM1:TRX'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- optics
|
||||
|
||||
xbpm2x:
|
||||
description: X-ray beam position monitor 1 in OPbox
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: x12sa-eb-smaract-mcs-03.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: 1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
|
||||
xbpm2y:
|
||||
description: X-ray beam position monitor 1 in OPbox
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: x12sa-eb-smaract-mcs-03.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: 1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
|
||||
cu_foilx:
|
||||
description: Cu foil in OPbox
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: x12sa-eb-smaract-mcs-03.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: 1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
|
||||
scinx:
|
||||
description: scintillator in OPbox
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: D
|
||||
host: x12sa-eb-smaract-mcs-03.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: 1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
|
||||
# dmm1_trx_readback_example: # This is the same template as for i.e. bpm4i
|
||||
# description: 'This is an example of a read-only Epics signal'
|
||||
# deviceClass: ophyd.EpicsSignalRO
|
||||
# deviceConfig:
|
||||
# read_pv: 'X12SA-OP-DMM1:TRX.RBV'
|
||||
# onFailure: raise
|
||||
# enabled: true
|
||||
# readoutPriority: monitored
|
||||
# readOnly: true
|
||||
# softwareTrigger: false
|
||||
# my_settable_signal:
|
||||
# description: 'This is an example of a settable Epics signal'
|
||||
# deviceClass: ophyd.EpicsSignal
|
||||
# deviceConfig:
|
||||
# read_pv: 'X07MA-FE-DSAPER'
|
||||
# onFailure: retry
|
||||
# enabled: true
|
||||
# readoutPriority: baseline
|
||||
# readOnly: false
|
||||
# softwareTrigger: false
|
||||
@@ -1,96 +0,0 @@
|
||||
samx:
|
||||
description: Owis motor stage samx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES01
|
||||
motor_resolution: 0.00125
|
||||
base_velocity: 0.0625
|
||||
velocity: 10
|
||||
backlash_distance: 0.125
|
||||
acceleration: 0.2
|
||||
user_offset_dir: 1
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- owis_samx
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
samy:
|
||||
description: Owis motor stage samy
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES02
|
||||
motor_resolution: 0.00125
|
||||
base_velocity: 0.0625
|
||||
velocity: 10
|
||||
backlash_distance: 0.125
|
||||
acceleration: 0.2
|
||||
user_offset_dir: 0
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- owis_samx
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
rotx:
|
||||
description: Rotation stage rotx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES03
|
||||
motor_resolution: 0.0025
|
||||
base_velocity: 0.5
|
||||
velocity: 7.5
|
||||
backlash_distance: 0.25
|
||||
acceleration: 0.2
|
||||
user_offset_dir: 1
|
||||
limits:
|
||||
- -0.1
|
||||
- 0.1
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- rotx
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
roty:
|
||||
description: Rotation stage roty
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES04
|
||||
motor_resolution: 0.0025
|
||||
base_velocity: 0.5
|
||||
velocity: 7.5
|
||||
backlash_distance: 0.25
|
||||
acceleration: 0.2
|
||||
user_offset_dir: 0
|
||||
limits:
|
||||
- -0.1
|
||||
- 0.1
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- roty
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
micfoc:
|
||||
description: Focusing motor of Microscope stage
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES05
|
||||
motor_resolution: 0.00125
|
||||
base_velocity: 0.25
|
||||
velocity: 2.5
|
||||
backlash_distance: 0.125
|
||||
acceleration: 0.4
|
||||
user_offset_dir: 0
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- micfoc
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
@@ -220,7 +220,7 @@ bm5try:
|
||||
softwareTrigger: false
|
||||
bpm1:
|
||||
description: 'XBPM 1: Somewhere around mono (VME)'
|
||||
deviceClass: csaxs_bec.devices.epics.XbpmBase.XbpmCsaxsOp
|
||||
deviceClass: csaxs_bec.devices.epics.devices.XbpmBase.XbpmCsaxsOp
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-BPM2:'
|
||||
deviceTags:
|
||||
@@ -242,7 +242,7 @@ bpm1i:
|
||||
softwareTrigger: false
|
||||
bpm2:
|
||||
description: 'XBPM 2: Somewhere around mono (VME)'
|
||||
deviceClass: csaxs_bec.devices.epics.XbpmBase.XbpmCsaxsOp
|
||||
deviceClass: csaxs_bec.devices.epics.devices.XbpmBase.XbpmCsaxsOp
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-BPM2:'
|
||||
deviceTags:
|
||||
@@ -605,7 +605,7 @@ dtpush:
|
||||
softwareTrigger: false
|
||||
dtth:
|
||||
description: Detector tower tilt rotation
|
||||
deviceClass: csaxs_bec.devices.epics.specMotors.PmDetectorRotation
|
||||
deviceClass: csaxs_bec.devices.epics.devices.specMotors.PmDetectorRotation
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES1-DETT:ROX1
|
||||
deviceTags:
|
||||
@@ -880,7 +880,7 @@ fttrz:
|
||||
softwareTrigger: false
|
||||
idgap:
|
||||
description: Undulator gap size [mm]
|
||||
deviceClass: csaxs_bec.devices.epics.InsertionDevice.InsertionDevice
|
||||
deviceClass: csaxs_bec.devices.epics.devices.InsertionDevice.InsertionDevice
|
||||
deviceConfig:
|
||||
prefix: X12SA-ID
|
||||
deviceTags:
|
||||
@@ -979,7 +979,7 @@ mitry3:
|
||||
softwareTrigger: false
|
||||
mobd:
|
||||
description: Monochromator bender virtual motor
|
||||
deviceClass: csaxs_bec.devices.epics.specMotors.PmMonoBender
|
||||
deviceClass: csaxs_bec.devices.epics.devices.specMotors.PmMonoBender
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-MO:'
|
||||
deviceTags:
|
||||
@@ -1034,7 +1034,7 @@ mobddi:
|
||||
softwareTrigger: false
|
||||
mokev:
|
||||
description: Monochromator energy in keV
|
||||
deviceClass: csaxs_bec.devices.epics.specMotors.EnergyKev
|
||||
deviceClass: csaxs_bec.devices.epics.devices.specMotors.EnergyKev
|
||||
deviceConfig:
|
||||
read_pv: X12SA-OP-MO:ROX2
|
||||
deviceTags:
|
||||
@@ -1089,7 +1089,7 @@ moroll2:
|
||||
softwareTrigger: false
|
||||
moth1:
|
||||
description: Monochromator Theta 1
|
||||
deviceClass: csaxs_bec.devices.epics.specMotors.MonoTheta1
|
||||
deviceClass: csaxs_bec.devices.epics.devices.specMotors.MonoTheta1
|
||||
deviceConfig:
|
||||
read_pv: X12SA-OP-MO:ROX1
|
||||
deviceTags:
|
||||
@@ -1111,7 +1111,7 @@ moth1e:
|
||||
softwareTrigger: false
|
||||
moth2:
|
||||
description: Monochromator Theta 2
|
||||
deviceClass: csaxs_bec.devices.epics.specMotors.MonoTheta2
|
||||
deviceClass: csaxs_bec.devices.epics.devices.specMotors.MonoTheta2
|
||||
deviceConfig:
|
||||
read_pv: X12SA-OP-MO:ROX2
|
||||
deviceTags:
|
||||
@@ -1230,17 +1230,17 @@ sec:
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
# sl0h:
|
||||
# description: FrontEnd slit virtual movement
|
||||
# deviceClass: ophyd_devices.devices.SlitH
|
||||
# deviceConfig:
|
||||
# prefix: 'X12SA-FE-SH1:'
|
||||
# deviceTags:
|
||||
# - epicsDevice
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
sl0h:
|
||||
description: FrontEnd slit virtual movement
|
||||
deviceClass: ophyd_devices.epics.devices.SlitH
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-FE-SH1:'
|
||||
deviceTags:
|
||||
- epicsDevice
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
sl0trxi:
|
||||
description: FrontEnd slit inner blade movement
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
@@ -1263,17 +1263,17 @@ sl0trxo:
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
# sl1h:
|
||||
# description: OpticsHutch slit virtual movement
|
||||
# deviceClass: ophyd_devices.devices.SlitH
|
||||
# deviceConfig:
|
||||
# prefix: 'X12SA-OP-SH1:'
|
||||
# deviceTags:
|
||||
# - epicsDevice
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
sl1h:
|
||||
description: OpticsHutch slit virtual movement
|
||||
deviceClass: ophyd_devices.epics.devices.SlitH
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-SH1:'
|
||||
deviceTags:
|
||||
- epicsDevice
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
sl1trxi:
|
||||
description: OpticsHutch slit inner blade movement
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
@@ -1318,28 +1318,28 @@ sl1tryt:
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
# sl1v:
|
||||
# description: OpticsHutch slit virtual movement
|
||||
# deviceClass: ophyd_devices.devices.SlitV
|
||||
# deviceConfig:
|
||||
# prefix: 'X12SA-OP-SV1:'
|
||||
# deviceTags:
|
||||
# - epicsDevice
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
# sl2h:
|
||||
# description: OpticsHutch slit 2 virtual movement
|
||||
# deviceClass: ophyd_devices.devices.SlitH
|
||||
# deviceConfig:
|
||||
# prefix: 'X12SA-OP-SH2:'
|
||||
# deviceTags:
|
||||
# - epicsDevice
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
sl1v:
|
||||
description: OpticsHutch slit virtual movement
|
||||
deviceClass: ophyd_devices.epics.devices.SlitV
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-SV1:'
|
||||
deviceTags:
|
||||
- epicsDevice
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
sl2h:
|
||||
description: OpticsHutch slit 2 virtual movement
|
||||
deviceClass: ophyd_devices.epics.devices.SlitH
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-SH2:'
|
||||
deviceTags:
|
||||
- epicsDevice
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
sl2trxi:
|
||||
description: OpticsHutch slit 2 inner blade movement
|
||||
deviceClass: ophyd.EpicsMotor
|
||||
@@ -1384,72 +1384,72 @@ sl2tryt:
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
# sl2v:
|
||||
# description: OpticsHutch slit 2 virtual movement
|
||||
# deviceClass: ophyd_devices.devices.SlitV
|
||||
# deviceConfig:
|
||||
# prefix: 'X12SA-OP-SV2:'
|
||||
# deviceTags:
|
||||
# - epicsDevice
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
# strox:
|
||||
# description: Girder virtual pitch
|
||||
# deviceClass: ophyd_devices.devices.GirderMotorPITCH
|
||||
# deviceConfig:
|
||||
# prefix: X12SA-HG
|
||||
# deviceTags:
|
||||
# - beamlineMotor
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
# stroy:
|
||||
# description: Girder virtual yaw
|
||||
# deviceClass: ophyd_devices.devices.GirderMotorYAW
|
||||
# deviceConfig:
|
||||
# prefix: X12SA-HG
|
||||
# deviceTags:
|
||||
# - beamlineMotor
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
# stroz:
|
||||
# description: Girder virtual roll
|
||||
# deviceClass: ophyd_devices.devices.GirderMotorROLL
|
||||
# deviceConfig:
|
||||
# prefix: X12SA-HG
|
||||
# deviceTags:
|
||||
# - beamlineMotor
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
# sttrx:
|
||||
# description: Girder X translation
|
||||
# deviceClass: ophyd_devices.devices.GirderMotorX1
|
||||
# deviceConfig:
|
||||
# prefix: X12SA-HG
|
||||
# deviceTags:
|
||||
# - beamlineMotor
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
# sttry:
|
||||
# description: Girder Y translation
|
||||
# deviceClass: ophyd_devices.devices.GirderMotorY1
|
||||
# deviceConfig:
|
||||
# prefix: X12SA-HG
|
||||
# deviceTags:
|
||||
# - beamlineMotor
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readoutPriority: baseline
|
||||
# softwareTrigger: false
|
||||
sl2v:
|
||||
description: OpticsHutch slit 2 virtual movement
|
||||
deviceClass: ophyd_devices.epics.devices.SlitV
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-OP-SV2:'
|
||||
deviceTags:
|
||||
- epicsDevice
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
strox:
|
||||
description: Girder virtual pitch
|
||||
deviceClass: ophyd_devices.epics.devices.GirderMotorPITCH
|
||||
deviceConfig:
|
||||
prefix: X12SA-HG
|
||||
deviceTags:
|
||||
- beamlineMotor
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
stroy:
|
||||
description: Girder virtual yaw
|
||||
deviceClass: ophyd_devices.epics.devices.GirderMotorYAW
|
||||
deviceConfig:
|
||||
prefix: X12SA-HG
|
||||
deviceTags:
|
||||
- beamlineMotor
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
stroz:
|
||||
description: Girder virtual roll
|
||||
deviceClass: ophyd_devices.epics.devices.GirderMotorROLL
|
||||
deviceConfig:
|
||||
prefix: X12SA-HG
|
||||
deviceTags:
|
||||
- beamlineMotor
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
sttrx:
|
||||
description: Girder X translation
|
||||
deviceClass: ophyd_devices.epics.devices.GirderMotorX1
|
||||
deviceConfig:
|
||||
prefix: X12SA-HG
|
||||
deviceTags:
|
||||
- beamlineMotor
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
sttry:
|
||||
description: Girder Y translation
|
||||
deviceClass: ophyd_devices.epics.devices.GirderMotorY1
|
||||
deviceConfig:
|
||||
prefix: X12SA-HG
|
||||
deviceTags:
|
||||
- beamlineMotor
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
transd:
|
||||
description: Transmission diode
|
||||
deviceClass: ophyd.EpicsSignalRO
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
// This file was autogenerated. Do not edit it manually.
|
||||
## Device List
|
||||
### csaxs_bec
|
||||
| Device | Documentation | Module |
|
||||
| :----- | :------------- | :------ |
|
||||
| Bpm4i | | [csaxs_bec.devices.epics.specMotors](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/specMotors.py) |
|
||||
| DelayGeneratorcSAXS | <br> DG645 delay generator at cSAXS (multiple can be in use depending on the setup)<br><br> Default values for setting up DDG.<br> Note: checks of set calues are not (only partially) included, check manual for details on possible settings.<br> https://www.thinksrs.com/downloads/pdfs/manuals/DG645m.pdf<br><br> - delay_burst : (float >=0) Delay between trigger and first pulse in burst mode<br> - delta_width : (float >= 0) Add width to fast shutter signal to make sure its open during acquisition<br> - additional_triggers : (int) add additional triggers to burst mode (mcs card needs +1 triggers per line)<br> - polarity : (list of 0/1) polarity for different channels<br> - amplitude : (float) amplitude voltage of TTLs<br> - offset : (float) offset for ampltitude<br> - thres_trig_level : (float) threshold of trigger amplitude<br><br> Custom signals for logic in different DDGs during scans (for custom_prepare.prepare_ddg):<br><br> - set_high_on_exposure : (bool): if True, then TTL signal should go high during the full acquisition time of a scan.<br> # TODO trigger_width and fixed_ttl could be combined into single list.<br> - fixed_ttl_width : (list of either 1 or 0), one for each channel.<br> - trigger_width : (float) if fixed_ttl_width is True, then the width of the TTL pulse is set to this value.<br> - set_trigger_source : (TriggerSource) specifies the default trigger source for the DDG.<br> - premove_trigger : (bool) if True, then a trigger should be executed before the scan starts (to be implemented in on_pre_scan).<br> - set_high_on_stage : (bool) if True, then TTL signal should go high already on stage.<br> | [csaxs_bec.devices.epics.delay_generator_csaxs](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/delay_generator_csaxs.py) |
|
||||
| Eiger1p5MDetector | | [csaxs_bec.devices.omny.eiger1p5m](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/eiger1p5m.py) |
|
||||
| Eiger9McSAXS | <br> Eiger9M detector for CSAXS<br><br> Parent class: PSIDetectorBase<br><br> class attributes:<br> custom_prepare_cls (FalconSetup) : Custom detector setup class for cSAXS,<br> inherits from CustomDetectorMixin<br> PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector<br> Various EpicsPVs for controlling the detector<br> | [csaxs_bec.devices.epics.eiger9m_csaxs](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/eiger9m_csaxs.py) |
|
||||
| EpicsDXPFalcon | <br> DXP parameters for Falcon detector<br><br> Base class to map EPICS PVs from DXP parameters to ophyd signals.<br> | [csaxs_bec.devices.epics.falcon_csaxs](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/falcon_csaxs.py) |
|
||||
| FalconcSAXS | <br> Falcon Sitoro detector for CSAXS<br><br> Parent class: PSIDetectorBase<br><br> class attributes:<br> custom_prepare_cls (FalconSetup) : Custom detector setup class for cSAXS,<br> inherits from CustomDetectorMixin<br> PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector<br> dxp (EpicsDXPFalcon) : DXP parameters for Falcon detector<br> mca (EpicsMCARecord) : MCA parameters for Falcon detector<br> hdf5 (FalconHDF5Plugins) : HDF5 parameters for Falcon detector<br> MIN_READOUT (float) : Minimum readout time for the detector<br> | [csaxs_bec.devices.epics.falcon_csaxs](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/falcon_csaxs.py) |
|
||||
| FalconHDF5Plugins | <br> HDF5 parameters for Falcon detector<br><br> Base class to map EPICS PVs from HDF5 Plugin to ophyd signals.<br> | [csaxs_bec.devices.epics.falcon_csaxs](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/falcon_csaxs.py) |
|
||||
| FlomniGalilMotor | | [csaxs_bec.devices.omny.galil.fgalil_ophyd](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/galil/fgalil_ophyd.py) |
|
||||
| FlomniSampleStorage | | [csaxs_bec.devices.omny.flomni_sample_storage](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/flomni_sample_storage.py) |
|
||||
| FuprGalilMotor | | [csaxs_bec.devices.omny.galil.fupr_ophyd](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/galil/fupr_ophyd.py) |
|
||||
| GirderMotorPITCH | Girder YAW pseudo motor | [csaxs_bec.devices.epics.specMotors](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/specMotors.py) |
|
||||
| GirderMotorROLL | Girder ROLL pseudo motor | [csaxs_bec.devices.epics.specMotors](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/specMotors.py) |
|
||||
| GirderMotorX1 | Girder X translation pseudo motor | [csaxs_bec.devices.epics.specMotors](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/specMotors.py) |
|
||||
| GirderMotorY1 | Girder Y translation pseudo motor | [csaxs_bec.devices.epics.specMotors](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/specMotors.py) |
|
||||
| GirderMotorYAW | Girder YAW pseudo motor | [csaxs_bec.devices.epics.specMotors](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/specMotors.py) |
|
||||
| IDSCamera | | [csaxs_bec.devices.ids_cameras.ids_camera](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/ids_cameras/ids_camera.py) |
|
||||
| InsertionDevice | Python wrapper for the CSAXS insertion device control<br><br> This wrapper provides a positioner interface for the ID control.<br> is completely custom XBPM with templates directly in the<br> VME repo. Thus it needs a custom ophyd template as well...<br><br> WARN: The x and y are not updated by the IOC<br> | [csaxs_bec.devices.epics.InsertionDevice](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/InsertionDevice.py) |
|
||||
| LamniGalilMotor | | [csaxs_bec.devices.omny.galil.lgalil_ophyd](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/galil/lgalil_ophyd.py) |
|
||||
| MCScSAXS | MCS card for cSAXS for implementation at cSAXS beamline | [csaxs_bec.devices.epics.mcs_csaxs](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/mcs_csaxs.py) |
|
||||
| NPointAxis | <br> NPointAxis class, which inherits from Device and PositionerBase. This class<br> represents an axis of an nPoint piezo stage and provides the necessary<br> functionality to move the axis and read its current position.<br> | [csaxs_bec.devices.npoint.npoint](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/npoint/npoint.py) |
|
||||
| OMNYDewar | | [csaxs_bec.devices.omny.omny_dewar](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/omny_dewar.py) |
|
||||
| OMNYGalilMotor | | [csaxs_bec.devices.omny.galil.ogalil_ophyd](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/galil/ogalil_ophyd.py) |
|
||||
| OMNYSampleStorage | | [csaxs_bec.devices.omny.omny_sample_storage](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/omny_sample_storage.py) |
|
||||
| OMNYTemperatures | | [csaxs_bec.devices.omny.omny_temperatures](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/omny_temperatures.py) |
|
||||
| OMNYVCS | | [csaxs_bec.devices.omny.omny_vcs](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/omny_vcs.py) |
|
||||
| PilatuscSAXS | Pilatus_2 300k detector for CSAXS<br><br> Parent class: PSIDetectorBase<br><br> class attributes:<br> custom_prepare_cls (Eiger9MSetup) : Custom detector setup class for cSAXS,<br> inherits from CustomDetectorMixin<br> cam (SLSDetectorCam) : Detector camera<br> MIN_READOUT (float) : Minimum readout time for the detector<br><br> | [csaxs_bec.devices.epics.pilatus_csaxs](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/pilatus_csaxs.py) |
|
||||
| PmDetectorRotation | Detector rotation pseudo motor<br><br> Small wrapper to convert detector pusher position to rotation angle.<br> | [csaxs_bec.devices.epics.specMotors](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/specMotors.py) |
|
||||
| PmMonoBender | Monochromator bender<br><br> Small wrapper to combine the four monochromator bender motors.<br> | [csaxs_bec.devices.epics.specMotors](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/specMotors.py) |
|
||||
| RtFlomniMotor | | [csaxs_bec.devices.omny.rt.rt_flomni_ophyd](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/rt/rt_flomni_ophyd.py) |
|
||||
| RtLamniMotor | | [csaxs_bec.devices.omny.rt.rt_lamni_ophyd](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/rt/rt_lamni_ophyd.py) |
|
||||
| RtOMNYMotor | | [csaxs_bec.devices.omny.rt.rt_omny_ophyd](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/rt/rt_omny_ophyd.py) |
|
||||
| SGalilMotor | "SGalil Motors at cSAXS have a<br> DC motor (y axis - vertical) - implemented as C<br> and a step motor (x-axis horizontal) - implemented as E<br> that require different communication for control<br> | [csaxs_bec.devices.omny.galil.sgalil_ophyd](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/omny/galil/sgalil_ophyd.py) |
|
||||
| SIS38XX | SIS38XX card for access to EPICs PVs at cSAXS beamline | [csaxs_bec.devices.epics.mcs_csaxs](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/mcs_csaxs.py) |
|
||||
| SLSDetectorCam | SLS Detector Camera - Pilatus<br><br> Base class to map EPICS PVs to ophyd signals.<br> | [csaxs_bec.devices.epics.pilatus_csaxs](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/pilatus_csaxs.py) |
|
||||
| SmaractMotor | | [csaxs_bec.devices.smaract.smaract_ophyd](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/smaract/smaract_ophyd.py) |
|
||||
| XbpmBase | Python wrapper for X-ray Beam Position Monitors<br><br> XBPM's consist of a metal-coated diamond window that ejects<br> photoelectrons from the incoming X-ray beam. These electons<br> are collected and their current is measured. Effectively<br> they act as four quadrant photodiodes and are used as BPMs<br> at the undulator beamlines of SLS.<br><br> Note: EPICS provided signals are read only, but the user can<br> change the beam position offset.<br> | [csaxs_bec.devices.epics.XbpmBase](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/XbpmBase.py) |
|
||||
| XbpmCsaxsOp | Python wrapper for custom XBPMs in the cSAXS optics hutch<br><br> This is completely custom XBPM with templates directly in the<br> VME repo. Thus it needs a custom ophyd template as well...<br><br> WARN: The x and y are not updated by the IOC<br> | [csaxs_bec.devices.epics.XbpmBase](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/XbpmBase.py) |
|
||||
| XbpmSim | Python wrapper for simulated X-ray Beam Position Monitors<br><br> XBPM's consist of a metal-coated diamond window that ejects<br> photoelectrons from the incoming X-ray beam. These electons<br> are collected and their current is measured. Effectively<br> they act as four quadrant photodiodes and are used as BPMs<br> at the undulator beamlines of SLS.<br><br> Note: EPICS provided signals are read only, but the user can<br> change the beam position offset.<br><br> This simulation device extends the basic proxy with a script that<br> fills signals with quasi-randomized values.<br> | [csaxs_bec.devices.epics.XbpmBase](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/epics/XbpmBase.py) |
|
||||
| Xeye | | [csaxs_bec.devices.sls_devices.cSAXS.xeye](https://gitlab.psi.ch/bec/csaxs_bec/-/blob/main/csaxs_bec/devices/sls_devices/cSAXS/xeye.py) |
|
||||
@@ -1,10 +1,9 @@
|
||||
import os
|
||||
import time
|
||||
|
||||
from bec_lib import bec_logger, messages
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_lib import MessageEndpoints, bec_logger, messages
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, EpicsSignal, EpicsSignalRO, Signal
|
||||
from ophyd import Device, DeviceStatus, EpicsSignal, EpicsSignalRO, Signal
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
@@ -1 +1,8 @@
|
||||
# Standard ophyd classes
|
||||
from ophyd import EpicsMotor, EpicsSignal, EpicsSignalRO
|
||||
from ophyd.quadem import QuadEM
|
||||
from ophyd.sim import SynAxis, SynPeriodicSignal, SynSignal
|
||||
|
||||
from .devices.delay_generator_csaxs import DelayGeneratorcSAXS
|
||||
from .devices.flomni_sample_storage import FlomniSampleStorage
|
||||
from .devices.InsertionDevice import InsertionDevice
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
""" TODO This class seems to be missing various imports and appears to have not been tested in motion yet."""
|
||||
|
||||
# TABLES_DT_PUSH_DIST_MM = 890
|
||||
|
||||
|
||||
# class DetectorTableTheta(PseudoPositioner):
|
||||
# """Detector table tilt motor
|
||||
|
||||
# Small wrapper to adjust the detector table tilt as angle.
|
||||
# The table is pushed from one side by a single vertical motor.
|
||||
|
||||
# Note: Rarely used!
|
||||
# """
|
||||
|
||||
# # Real axis (in degrees)
|
||||
# pusher = Component(EpicsMotor, "", name="pusher")
|
||||
# # Virtual axis
|
||||
# theta = Component(PseudoSingle, name="theta")
|
||||
|
||||
# _real = ["pusher"]
|
||||
|
||||
# @pseudo_position_argument
|
||||
# def forward(self, pseudo_pos):
|
||||
# return self.RealPosition(
|
||||
# pusher=tan(pseudo_pos.theta * 3.141592 / 180.0) * TABLES_DT_PUSH_DIST_MM
|
||||
# )
|
||||
|
||||
# @real_position_argument
|
||||
# def inverse(self, real_pos):
|
||||
# return self.PseudoPosition(
|
||||
# theta=-180 * atan(real_pos.pusher / TABLES_DT_PUSH_DIST_MM) / 3.141592
|
||||
# )
|
||||
@@ -1,58 +0,0 @@
|
||||
# Delay Generator implementation at the CSAXS beamline
|
||||
|
||||
This module provides an ophyd device implementation for the Stanford Research Systems Delay Generator DDG645, used at the cSAXS beamline as a master timing source for detector triggering and other beamline devices. Detailed information about the DDG manual can be found here:
|
||||
https://www.thinksrs.com/downloads/pdfs/manuals/DG645m.pdf.
|
||||
The implementation is based on a community EPICS driver (https://github.com/epics-modules/delaygen?tab=readme-ov-file).
|
||||
|
||||
**EPICS Interface**
|
||||
|
||||
At the cSAXS beamline, the DDG panel is avaiable via caqtdm on the beamline consoles.
|
||||
|
||||
``` bash
|
||||
caqtdm -noMsg -attach -macro P=X12SA-CPCL-DDG,R=1: srsDG645.ui
|
||||
```
|
||||
with R=1,2,3,4,5 for 5 different DDG units installed at CSAXS.
|
||||
|
||||
# Ophyd Device integration at cSAXS
|
||||
For cSAXS, a custom ophyd device class implementation of the DDG is provided [here](./delay_generator_csaxs.py). This class provides a basic interface to the DDG PVs. The interface provides channels 'A', B', 'C', ... with setpoint, readback and references, as well as high level parameters such as *width* and *delay*. Please check the source code of the class for more details of the implementation.
|
||||
|
||||
In addition, the class provides a set of utility methods to configure sets of channel pairs 'AB', 'CD', ... as commonly needed in operation at the beamline. At the cSAXS beamline, a single DDG device is used as a master timing source for other devices. The general scheme is described in a [PDF document here](./trigger_scheme_ddg1_ddg2.pdf). Below is a description of the configuration of the two DDG units used at cSAXS for detector triggering and beamline shutter control.
|
||||
|
||||
## Master card: DDG1 (X12SA-CPCL-DDG1)
|
||||
The master [delay generator DDG1](./ddg_1.py) is configured to provide the following signals:
|
||||
|
||||
**Connection Scheme**:
|
||||
- EXT/EN: May be connected to external devices, e.g. SGalil motion controller for fly scans.
|
||||
- Operation Mode: Burst mode, but with single burst (burst count = 1). This is for practical reasons as it allows
|
||||
to interrupt and ongoing sequence if needed.
|
||||
- Software Trigger: Controlled through BEC.
|
||||
- State Control: BEC checks the *state* of this DDG to wait for the completion of a timing sequence.
|
||||
|
||||
**Delay Pairs**:
|
||||
- DelayPair 'AB': Provides the external enable (EXT/EN) signal to the second DDG (R=2).
|
||||
- DelayPair 'CD': Controls the beamline shutter.
|
||||
- DelayPair 'EF': Generates pulses for the MCS card, combined with the detector pulse train via an OR gate. This ensures the MCS card receives an additional pulse required for proper operation.
|
||||
|
||||
**Delay Channels**:
|
||||
- a = t0 + 2ms (2ms delay to allow the shutter to open)
|
||||
- b = a + 1us (short pulse)
|
||||
- c = t0
|
||||
- d = a + exp_time * burst_count + 1ms (to allow the shutter to close)
|
||||
- e = d
|
||||
- f = e + 1us (short pulse to OR gate for MCS triggering)
|
||||
|
||||
## Detector card: DDG2 (X12SA-CPCL-DDG2)
|
||||
The second [delay generator DDG2](./ddg_2.py) is configured to provide the following signals:
|
||||
|
||||
**Connection Scheme**:
|
||||
- EXT/EN: Connected to the DelayPair AB of the master DDG (R=1).
|
||||
- Operation Mode: Burst mode: The *burst count* is set to the number of frames per trigger. The *burst delay* is set to 0, and the *burst period* is set to the exposure time.
|
||||
- Software Trigger: Irrelevant, as the device is externally triggered by DDG1.
|
||||
|
||||
**Delay Pairs**:
|
||||
- DelayPair 'AB': Provides the trigger signal to the detector.
|
||||
|
||||
**Delay Channels**:
|
||||
- a = t0
|
||||
- b = a + (exp_time - READOUT_TIMES)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
from .ddg_1 import DDG1
|
||||
from .ddg_2 import DDG2
|
||||
from .delay_generator_csaxs import (
|
||||
BURSTCONFIG,
|
||||
CHANNELREFERENCE,
|
||||
OUTPUTPOLARITY,
|
||||
STATUSBITS,
|
||||
TRIGGERINHIBIT,
|
||||
TRIGGERSOURCE,
|
||||
AllChannelNames,
|
||||
ChannelConfig,
|
||||
DelayChannelNames,
|
||||
)
|
||||
from .error_registry import ERROR_CODES
|
||||
@@ -1,496 +0,0 @@
|
||||
"""
|
||||
DDG1 delay generator
|
||||
|
||||
This module implements the DDG1 delay generator logic for the CSAXS beamline.
|
||||
The attached PDF trigger_scheme_ddg1_ddg2.pdf provides a more detailed overview of
|
||||
the trigger scheme. If the logic changes in the future, it is highly recommended to
|
||||
update the PDF accordingly.
|
||||
|
||||
The DDG1 is the main trigger delay generator for the CSAXS beamline. It will
|
||||
receive either a soft trigger from BEC (depending on the scan type) or a hardware trigger
|
||||
from a beamline device (e.g. the Galil stages). It is responsible for opening the shutter
|
||||
and sending a trigger to the Delay Generator CSAXS (DDG2), which in turn will
|
||||
send the trigger to the detectors. DDG1 will not be witout burst mode, but rather in standard
|
||||
mode creating delays for the channels ab, cd, ef, gh.
|
||||
|
||||
A brief summary of the DDG1 logic:
|
||||
DELAY PAIRS:
|
||||
- DelayPair ab is connected to the EXT/EN of DDG2.
|
||||
- DelayPair cd is connected to the SHUTTER.
|
||||
- DelayPair ef is connected to an OR gate together with the detector
|
||||
PULSE train for the MCS card. The MCS card needs one extra pulse to forward points.
|
||||
|
||||
DELAY CHANNELS:
|
||||
- a = t0 + 2ms (2ms delay to allow the shutter to open)
|
||||
- b = a + 1us (short pulse)
|
||||
- c = t0
|
||||
- d = a + exp_time * burst_count + 1ms (to allow the shutter to close)
|
||||
- e = d
|
||||
- f = e + 1us (short pulse to OR gate for MCS triggering)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd_devices import CompareStatus, DeviceStatus, TransitionStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import (
|
||||
CHANNELREFERENCE,
|
||||
OUTPUTPOLARITY,
|
||||
PROC_EVENT_MODE,
|
||||
STATUSBITS,
|
||||
TRIGGERSOURCE,
|
||||
AllChannelNames,
|
||||
ChannelConfig,
|
||||
DelayGeneratorCSAXS,
|
||||
LiteralChannels,
|
||||
StatusBitsCompareStatus,
|
||||
)
|
||||
from csaxs_bec.devices.epics.mcs_card.mcs_card_csaxs import ACQUIRING
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import DeviceManagerBase, ScanInfo
|
||||
|
||||
from csaxs_bec.devices.epics.mcs_card.mcs_card_csaxs import MCSCardCSAXS
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
########################
|
||||
## DEFAULT SETTINGS ####
|
||||
########################
|
||||
|
||||
# NOTE Default channel configuration for all channels of the DDG1 delay generator
|
||||
# This can be adapted as needed, or fine-tuned per channel. On every reload of the
|
||||
# device configuration in BEC, these values will be set into the DDG1 device.
|
||||
_DEFAULT_CHANNEL_CONFIG: ChannelConfig = {
|
||||
"amplitude": 5.0,
|
||||
"offset": 0.0,
|
||||
"polarity": OUTPUTPOLARITY.POSITIVE,
|
||||
"mode": "ttl",
|
||||
}
|
||||
|
||||
# NOTE Here you can adapt the default IO configuration for all channels of the DDG1
|
||||
# Currently, all channels are set to the same default configuration `_DEFAULT_CHANNEL_CONFIG`.
|
||||
DEFAULT_IO_CONFIG: dict[AllChannelNames, ChannelConfig] = {
|
||||
"t0": _DEFAULT_CHANNEL_CONFIG,
|
||||
"ab": _DEFAULT_CHANNEL_CONFIG,
|
||||
"cd": _DEFAULT_CHANNEL_CONFIG,
|
||||
"ef": _DEFAULT_CHANNEL_CONFIG,
|
||||
"gh": _DEFAULT_CHANNEL_CONFIG,
|
||||
}
|
||||
|
||||
DEFAULT_TRIGGER_SOURCE: TRIGGERSOURCE = TRIGGERSOURCE.SINGLE_SHOT
|
||||
|
||||
# NOTE Default readout times for each channel, can be adapted as needed.
|
||||
# These values are relevant to calculate proper widths of the timing signals.
|
||||
# They also define a minimum exposure time that can be used as they are subtracted
|
||||
# as dead times from the exposure time.
|
||||
DEFAULT_READOUT_TIMES = {"ab": 2e-4, "cd": 2e-4, "ef": 2e-4, "gh": 2e-4} # 0.2 ms 5kHz
|
||||
|
||||
# NOTE Default channel references for each channel of the DDG1 delay generator.
|
||||
# This needs to be carefully adjusted to match the envisioned trigger scheme.
|
||||
# If the trigger scheme changes, adapt the values here together with the README and
|
||||
# PDF `trigger_scheme_ddg1_ddg2.pdf`.
|
||||
DEFAULT_REFERENCES: list[tuple[LiteralChannels, CHANNELREFERENCE]] = [
|
||||
("A", CHANNELREFERENCE.T0), # T0 + 2ms delay
|
||||
("B", CHANNELREFERENCE.A),
|
||||
("C", CHANNELREFERENCE.T0), # T0
|
||||
("D", CHANNELREFERENCE.C),
|
||||
("E", CHANNELREFERENCE.D), # D One extra pulse once shutter closes for MCS
|
||||
("F", CHANNELREFERENCE.E), # E + 1mu s
|
||||
("G", CHANNELREFERENCE.T0),
|
||||
("H", CHANNELREFERENCE.G),
|
||||
]
|
||||
|
||||
###############################
|
||||
## DDG1 IMPLEMENTATION ########
|
||||
###############################
|
||||
|
||||
|
||||
class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
"""
|
||||
|
||||
Implementation of the DelayGenerator DDG1 for the cSAXS beamline. It is the main trigger
|
||||
source for the cSAXS beamline, and will be triggered by BEC through a software trigger or
|
||||
by a hardware trigger from a beamline device (e.g. Galil stages). Specific implementation
|
||||
of the cabling logic expected for this device are described in the module README, the attached
|
||||
PDF 'trigger_scheme_ddg1_ddg2.pdf' and the module docstring.
|
||||
|
||||
The IOC prefix is 'X12SA-CPCL-DDG1:'.
|
||||
|
||||
Args:
|
||||
name (str): Name of the device.
|
||||
prefix (str, optional): EPICS prefix for the device. Defaults to ''.
|
||||
scan_info (ScanInfo | None, optional): Scan info object. Defaults to None.
|
||||
device_manager (DeviceManagerBase | None, optional): Device manager. Defaults to None.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
prefix: str = "",
|
||||
scan_info: ScanInfo | None = None,
|
||||
device_manager: DeviceManagerBase | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
name=name, prefix=prefix, scan_info=scan_info, device_manager=device_manager, **kwargs
|
||||
)
|
||||
self.device_manager = device_manager
|
||||
self._poll_thread = threading.Thread(target=self._poll_event_status, daemon=True)
|
||||
self._poll_thread_run_event = threading.Event()
|
||||
self._poll_thread_poll_loop_done = threading.Event()
|
||||
self._poll_thread_kill_event = threading.Event()
|
||||
self._poll_thread.start()
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
def on_connected(self) -> None:
|
||||
"""
|
||||
|
||||
This method is called after the device is initialized and all signals are connected. This happens
|
||||
when a device configuration is loaded in BEC.
|
||||
|
||||
It sets the default values for this device - intended to overwrite everything to a usable default state.
|
||||
For this purpose, we use the DEFAULT SETTINGS defined at the top of this module.
|
||||
|
||||
To ensure that this process is robust, we follow these steps:
|
||||
- First, we stop any ongoing burst mode operation.
|
||||
- Then, we set the DEFAULT_IO_CONFIG for each channel, the trigger source to DEFAULT_TRIGGER_SOURCE,
|
||||
and the channel references to DEFAULT_REFERENCES.
|
||||
- We set the state proc_status to be event based. This triggers readouts of the EventStatusLI bit
|
||||
based on events. This was empirically found to be a stable solution in combination with the poll
|
||||
loop of the state.
|
||||
- Finally, we set the burst delay to 0, to set it to be of no delay.
|
||||
"""
|
||||
|
||||
# NOTE First we make sure that there is nothing running on the DDG. This seems to
|
||||
# help to tackle that the DDG occasionally freezes during the first scan
|
||||
# after reconnecting to it. Do not remove.
|
||||
self.stop_ddg()
|
||||
|
||||
# NOTE Setting DEFAULT configurations for IO config, trigger config and references.
|
||||
# The three dictionaries above 'DEFAULT_IO_CONFIG', 'DEFAULT_TRIGGER_SOURCE' and
|
||||
# 'DEFAULT_REFERNCES' should be used to adapt configurations if needed.
|
||||
for channel, config in DEFAULT_IO_CONFIG.items():
|
||||
self.set_io_values(channel, **config)
|
||||
self.set_trigger(DEFAULT_TRIGGER_SOURCE)
|
||||
self.set_references_for_channels(DEFAULT_REFERENCES)
|
||||
|
||||
# NOTE Set state proc_status to be event based. This triggers readouts of the EventStatusLI bit
|
||||
# based on events. This was empirically found to be a stable solution in combination with the poll
|
||||
# loop of the state.
|
||||
self.state.proc_status_mode.put(PROC_EVENT_MODE.EVENT)
|
||||
|
||||
# NOTE Burst delay should be set to 0, don't remove as this will not be checked
|
||||
# Also set the burst count to 1 to only have a single pulse for DDG1.
|
||||
self.burst_delay.put(0)
|
||||
self.burst_count.put(1)
|
||||
|
||||
def on_stage(self) -> None:
|
||||
"""
|
||||
|
||||
This method is called in preparation for a scan. All information about the upcoming
|
||||
scan is available in self.scan_info.msg at this point. We use this information to
|
||||
configure the DDG1 for the upcoming scan.
|
||||
|
||||
The DDG is operated in burst mode for the scan, but with only a single burst pulse.
|
||||
THe length of the pulse is set to the expected exposure time for a single trigger,
|
||||
which includes any burst acquisitions if frames_per_trigger > 1.
|
||||
|
||||
The logic is as follows:
|
||||
- We check if any default burst parameters need to be set, and set them if needed.
|
||||
- We calculate the burst pulse width based on the exposure time and frames_per_trigger.
|
||||
- We set the burst_period and the shutter signal (delay pairs cd) to be
|
||||
exposure_time * frames_per_trigger + 3ms (2ms for shutter to open, 1ms to close).
|
||||
- We set the delay pairs ab to be 2ms delayed (to allow the shutter to open) with a width of 1us to trigger DDG2.
|
||||
- We set the delay pairs ef to be triggered after the shutter closes with a width of 1us to trigger the MCS card.
|
||||
- Finally, we add a short sleep to ensure that the IOC and DDG HW process the values properly.
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
########################################
|
||||
### Burst mode settings ################
|
||||
########################################
|
||||
|
||||
# NOTE We check here if the delay generator is not in burst mode. We check these values
|
||||
# and set them to the requried values if they differ from the expected ones.
|
||||
# This has been found empirically to improve stability and avoid HW getting stuck in triggering cycles.
|
||||
if self.burst_mode.get() == 0:
|
||||
self.burst_mode.put(1)
|
||||
|
||||
if self.burst_delay.get() != 0:
|
||||
self.burst_delay.put(0)
|
||||
|
||||
if self.burst_count.get() != 1:
|
||||
self.burst_count.put(1)
|
||||
|
||||
#########################################
|
||||
### Setup timing for burst and delays ###
|
||||
#########################################
|
||||
|
||||
frames_per_trigger = self.scan_info.msg.scan_parameters["frames_per_trigger"]
|
||||
exp_time = self.scan_info.msg.scan_parameters["exp_time"]
|
||||
|
||||
# Burst Period DDG1
|
||||
# Set burst_period to shutter width
|
||||
# c/t0 + 2ms + exp_time * burst_count + 1ms
|
||||
shutter_width = 2e-3 + exp_time * frames_per_trigger + 1e-3
|
||||
if self.burst_period.get() != shutter_width:
|
||||
self.burst_period.put(shutter_width)
|
||||
|
||||
# Trigger DDG2
|
||||
# a = t0 + 2ms, b = a + 1us
|
||||
# a has reference to t0, b has reference to a
|
||||
# Add delay of 2ms to allow shutter to open
|
||||
self.set_delay_pairs(channel="ab", delay=2e-3, width=1e-6)
|
||||
|
||||
# Trigger shutter
|
||||
# d = c/t0 + 2ms + exp_time * burst_count + 1ms
|
||||
# c has reference to t0, d has reference to c
|
||||
# Shutter opens without delay at t0, closes after exp_time * burst_count + 3ms (2ms open, 1ms close)
|
||||
self.set_delay_pairs(channel="cd", delay=0, width=shutter_width)
|
||||
|
||||
# Trigger extra pulse for MCS OR gate
|
||||
# f = e + 1us
|
||||
# e has refernce to d, f has reference to e
|
||||
self.set_delay_pairs(channel="ef", delay=0, width=1e-6)
|
||||
|
||||
# NOTE Add additional sleep to make sure that the IOC and DDG HW process the values properly
|
||||
# This value has been choosen empirically after testing with the HW. It's
|
||||
# also just called once per scan and has been found to improve stability of the HW.
|
||||
time.sleep(0.2)
|
||||
logger.info(f"DDG {self.name} on_stage completed in {time.time() - start_time:.3f}s.")
|
||||
|
||||
def _prepare_mcs_on_trigger(self, mcs: MCSCardCSAXS) -> None:
|
||||
"""
|
||||
|
||||
This method is used by the DDG1 on_trigger method to prepare the MCS card for the next trigger.
|
||||
It checks that the MCS card is properly prepared before BEC sends a software trigger to the DDG1,
|
||||
which is needed for step scans.
|
||||
|
||||
It relies on the MCS card implementation and needs to be adapted if the MCS card logic changes.
|
||||
"""
|
||||
|
||||
# NOTE First we wait that the MCS card is not acquiring. We add here a timeout of 5s to avoid
|
||||
# a deadlock in case the MCS card is stuck for some reason. This should not happen normally.
|
||||
status = CompareStatus(mcs.acquiring, ACQUIRING.DONE)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(timeout=5)
|
||||
|
||||
# NOTE Clear the '_omit_mca_callbacks' flag. This makes sure that data received from the mca1...mca3
|
||||
# counters are forwarded to BEC. Once the flag is set, we create a TransitionStatus DONE->ACQUIRING
|
||||
# and start the acquisition through erase_start.put(1). Finally, we wait for the card to go to ACQUIRING state.
|
||||
mcs._omit_mca_callbacks.clear() # pylint: disable=protected-access
|
||||
status_acquiring = TransitionStatus(mcs.acquiring, [ACQUIRING.DONE, ACQUIRING.ACQUIRING])
|
||||
self.cancel_on_stop(status_acquiring)
|
||||
mcs.erase_start.put(1)
|
||||
|
||||
return status_acquiring
|
||||
|
||||
def _poll_event_status(self) -> None:
|
||||
"""
|
||||
|
||||
Polling loop to retrieve the event status register of the delay generator DDG1.
|
||||
This method runs in a background thread and the polling is controlled through the
|
||||
'_poll_thread_run_event' and '_poll_thread_kill_event'. Polling should only become
|
||||
active when a software trigger was sent in BEC and we are waiting for the burst to complete.
|
||||
"""
|
||||
# Main loop of the polling thread. As long as the kill event is not set, the loop continues.
|
||||
while not self._poll_thread_kill_event.is_set():
|
||||
|
||||
# NOTE Main wait event for the polling thread. If the _poll_thread_run_event is not set,
|
||||
# The thread will wait here. This event is used to start/stop polling from outside the thread,
|
||||
# as used in on_trigger and on_stop. Please make sure to set this event also when the thread
|
||||
# should be killed as its otherwise stuck inside the wait.
|
||||
self._poll_thread_run_event.wait()
|
||||
|
||||
# NOTE Set the event to indicate that we are currently still in the poll_loop. This is needed
|
||||
# as we have to use sleeps of 20ms within the poll loop. These sleeps were empirically detetermined
|
||||
# to ensure that no state changes are missed. However, these sleeps have the side effect that
|
||||
# setting the '_poll_thread_run_event' may not immediately stop the polling. Therefore, we need the
|
||||
# '_poll_thread_poll_loop_done' event to indicate that polling has finished. If this logic is changed,
|
||||
# it requires careful testing as failure rates can be in the 1 out of 500 events rate, which are still
|
||||
# not acceptable for operation. The current implementation has been tested with failure rates smaller then
|
||||
# ~ 1:100000 if failures happened at all.
|
||||
self._poll_thread_poll_loop_done.clear()
|
||||
while (
|
||||
self._poll_thread_run_event.is_set() and not self._poll_thread_kill_event.is_set()
|
||||
):
|
||||
try:
|
||||
self._poll_loop()
|
||||
except Exception: # pylint: disable=broad-except
|
||||
content = traceback.format_exc()
|
||||
logger.error(
|
||||
f"Exception in polling loop thread, polling continues...\n Error content:\n{content}"
|
||||
)
|
||||
# NOTE Set the _poll_thread_poll_loop_done event to indicate that we are done polling. Do not remove!
|
||||
self._poll_thread_poll_loop_done.set()
|
||||
|
||||
def _poll_loop(self) -> None:
|
||||
"""
|
||||
|
||||
This method is the actual poll loop to update the event status from the satus register
|
||||
of the delay generator DDG1.
|
||||
|
||||
It follows a procedure that was established empirically after extended testing with the HW.
|
||||
Any adaptations to this logic need to be carefully tested to avoid that the HW becomes unstable.
|
||||
NOTE: Sleeps are important in this logic, and should not be removed or optimized without extensive testing.
|
||||
20ms has been found to be the minimum sleep time that proofed to be stable in operation.
|
||||
|
||||
The logic is as follows:
|
||||
- Set the 'proc_status' to 1 with use_complete=True to trigger an event based readout of the EventStatusLI.
|
||||
- Sleep 20ms to give the device time to process the command.
|
||||
- Check if the kill event or run event are cleared, and exit the loop if so.
|
||||
- Read the EventStatusLI channel to update the event status.
|
||||
- Check again if the kill event or run event are cleared, and exit the loop if so.
|
||||
|
||||
Please note that any important changes of the status register reading will trigger callbacks
|
||||
if attached to the event status signal. These callbacks hold the logic to resolve status objects
|
||||
when waiting for specific events (e.g. end of burst).
|
||||
|
||||
|
||||
"""
|
||||
self.state.proc_status.put(1, use_complete=True)
|
||||
|
||||
# NOTE: Important sleep that has been empirically determined after testing for a long time
|
||||
# Only remove if absolutely certain that the DDG logic of polling the EventStatusLI works without it.
|
||||
time.sleep(0.02)
|
||||
|
||||
if self._poll_thread_kill_event.is_set() or not self._poll_thread_run_event.is_set():
|
||||
return
|
||||
|
||||
self.state.event_status.get(use_monitor=False)
|
||||
if self._poll_thread_kill_event.is_set() or not self._poll_thread_run_event.is_set():
|
||||
return
|
||||
|
||||
# NOTE: Again important sleep that has been empirically determined after testing for a long time
|
||||
# Only remove if certain that logic can be replaced to not risk HW failures.
|
||||
time.sleep(0.02)
|
||||
|
||||
def _start_polling(self) -> None:
|
||||
"""Start the polling loop in the background thread."""
|
||||
self._poll_thread_run_event.set()
|
||||
|
||||
def _stop_polling(self) -> None:
|
||||
"""Stop the polling loop in the background thread."""
|
||||
self._poll_thread_run_event.clear()
|
||||
|
||||
def _kill_poll_thread(self) -> None:
|
||||
"""Kill the polling thread."""
|
||||
self._poll_thread_kill_event.set()
|
||||
self._stop_polling()
|
||||
self._poll_thread.join(timeout=1)
|
||||
if self._poll_thread.is_alive():
|
||||
logger.warning("Polling thread did not stop gracefully.")
|
||||
else:
|
||||
logger.info("Polling thread stopped.")
|
||||
|
||||
def _prepare_trigger_status_event(
|
||||
self, timeout: float | None = None
|
||||
) -> StatusBitsCompareStatus:
|
||||
"""
|
||||
Method to prepare a status object that indicates the end of a burst cycle.
|
||||
It also sets up a callback to cancel the polling of the event status register
|
||||
if the status is cancelled externally (e.g. by stopping the device). In addition,
|
||||
a timeout can either be specified, or is automatically calculated based on the
|
||||
exposure time, frames_per_trigger and a default extra time of 5 seconds.
|
||||
|
||||
Args:
|
||||
timeout (float | None, optional): Timeout for the status object. If None, a
|
||||
default timeout based on exposure time and frames_per_trigger is used.
|
||||
|
||||
Returns:
|
||||
StatusBitsCompareStatus:
|
||||
"""
|
||||
if timeout is None:
|
||||
# Default timeout of 5 seconds + exposure time * frames_per_trigger
|
||||
timeout = 5 + self.scan_info.msg.scan_parameters.get(
|
||||
"exp_time", 0.1
|
||||
) * self.scan_info.msg.scan_parameters.get("frames_per_trigger", 1)
|
||||
|
||||
# Callback to cancel the status if the device is stopped
|
||||
def cancel_cb(status: CompareStatus) -> None:
|
||||
"""Callback to cancel the status if the device is stopped."""
|
||||
logger.debug("DDG1 end of burst detected, stopping polling loop.")
|
||||
if status.done:
|
||||
self._stop_polling()
|
||||
|
||||
# Run false is important to ensure that the status is only checked on the next event status update
|
||||
status = StatusBitsCompareStatus(
|
||||
self.state.event_status, STATUSBITS.END_OF_BURST, timeout=timeout, run=False
|
||||
)
|
||||
status.add_callback(cancel_cb)
|
||||
self.cancel_on_stop(status)
|
||||
return status
|
||||
|
||||
def on_trigger(self) -> DeviceStatus:
|
||||
"""
|
||||
This method is called from BEC as a software trigger.
|
||||
|
||||
It follows a specific procedure to ensure that the DDG1 and MCS card are properly handled
|
||||
on a trigger event. The established logic is as follows:
|
||||
|
||||
- Stop polling the event status register to avoid that the polling loop is still active
|
||||
before sending the software trigger. This needs to be done to avoid conflicts
|
||||
in reading the event status register.
|
||||
- Wait for the _poll_thread_poll_loop_done event to ensure that the polling loop is no
|
||||
longer active. A timeout of 1s is plenty as sleeps of 20ms are used in the poll loop.
|
||||
- Add an extra sleep of 20ms to make sure that the HW is again ready to process new commands.
|
||||
This has been found empirically after long testing to improve stability.
|
||||
- If the MCS card is present in the current session of BEC, prepare the card for the next trigger.
|
||||
- Prepare a status StatusBitsCompareStatus that will be resolved once the burst is done.
|
||||
- Start the polling loop again to monitor the event status register.
|
||||
- Send the software trigger to the DDG1
|
||||
- Return the status object to BEC which will automatically resolve once the status register has
|
||||
the END_OF_BURST bit set. The callback of the status object will also stop the polling loop.
|
||||
"""
|
||||
self._stop_polling()
|
||||
self._poll_thread_poll_loop_done.wait(timeout=1)
|
||||
# NOTE: This sleep is important to ensure that the HW is ready to process new commands.
|
||||
# It has been empirically determined after long testing that this improves stability.
|
||||
time.sleep(0.02)
|
||||
|
||||
# NOTE If the MCS card is present in the current session of BEC,
|
||||
# we prepare the card for the next trigger. The procedure is implemented
|
||||
# in the '_prepare_mcs_on_trigger' method.
|
||||
# Prepare the MCS card for the next software trigger
|
||||
mcs = self.device_manager.devices.get("mcs", None)
|
||||
if mcs is None or mcs.enabled is False:
|
||||
logger.info("Did not find mcs card with name 'mcs' in current session")
|
||||
else:
|
||||
status_mcs = self._prepare_mcs_on_trigger(mcs)
|
||||
# NOTE Timeout of 3s should be plenty, any longer wait should checked. If this happens to crash
|
||||
# an acquisition regularly with a WaitTimeoutError, the timeout can be increased but it should
|
||||
# be investigated why the EPICS interface is slow to respond.
|
||||
status_mcs.wait(timeout=3)
|
||||
|
||||
# Prepare StatusBitsCompareStatus to resolve once the END_OF_BURST bit was set.
|
||||
status = self._prepare_trigger_status_event()
|
||||
|
||||
# Start polling thread again to monitor event status
|
||||
self._start_polling()
|
||||
# Trigger the DDG1
|
||||
self.trigger_shot.put(1, use_complete=True)
|
||||
return status
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Stop the delay generator HW and polling thread when the device is stopped."""
|
||||
self.stop_ddg()
|
||||
self._stop_polling()
|
||||
|
||||
def on_destroy(self) -> None:
|
||||
"""Clean up resources when the device is destroyed."""
|
||||
self.stop_ddg()
|
||||
self._kill_poll_thread()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ddg = DDG1(name="ddg1", prefix="X12SA-CPCL-DDG1:")
|
||||
ddg.wait_for_connection(all_signals=True, timeout=30)
|
||||
ddg.summary()
|
||||
@@ -1,215 +0,0 @@
|
||||
"""
|
||||
DDG2 delay generator
|
||||
|
||||
This module implements the DDG2 delay generator logic for the CSAXS beamline.
|
||||
Please check also the code for DDG1, aswell as the attached PDF trigger_scheme_ddg1_ddg2.pdf
|
||||
|
||||
The DDG2 is responsible for creating a burst of triggers for all relevant detectors.
|
||||
It will receive a be triggered from the DDG1 through the EXT/EN channel.
|
||||
|
||||
A brief summary of the DDG2 logic:
|
||||
DELAY PAIRS:
|
||||
- EXT/EN is connected to the DDG1 delay pair ab.
|
||||
- DelayPair ab is connected to a multiplexer, multiplexing the trigger to the detectors.
|
||||
|
||||
DELAY CHANNELS:
|
||||
- a = t0
|
||||
- b = a + (exp_time - READOUT_TIMES)
|
||||
|
||||
Burst mode is enabled:
|
||||
- Burst count is set to the number of frames per trigger.
|
||||
- Burst delay is set to 0.
|
||||
- Burst period is set to the exposure time.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd_devices import DeviceStatus, StatusBase
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import (
|
||||
CHANNELREFERENCE,
|
||||
OUTPUTPOLARITY,
|
||||
STATUSBITS,
|
||||
TRIGGERSOURCE,
|
||||
AllChannelNames,
|
||||
ChannelConfig,
|
||||
DelayGeneratorCSAXS,
|
||||
LiteralChannels,
|
||||
)
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
########################
|
||||
## DEFAULT SETTINGS ####
|
||||
########################
|
||||
|
||||
# NOTE Default channel configuration for the DDG2 delay generator channels
|
||||
_DEFAULT_CHANNEL_CONFIG: ChannelConfig = {
|
||||
"amplitude": 5.0,
|
||||
"offset": 0.0,
|
||||
"polarity": OUTPUTPOLARITY.POSITIVE,
|
||||
"mode": "ttl",
|
||||
}
|
||||
|
||||
# NOTE Default IO configuration for all channels in DDG2
|
||||
# Each channel uses the same default configuration as defined above
|
||||
# If needed, individual channel configurations should be modified here.
|
||||
DEFAULT_IO_CONFIG: dict[AllChannelNames, ChannelConfig] = {
|
||||
"t0": _DEFAULT_CHANNEL_CONFIG,
|
||||
"ab": _DEFAULT_CHANNEL_CONFIG,
|
||||
"cd": _DEFAULT_CHANNEL_CONFIG,
|
||||
"ef": _DEFAULT_CHANNEL_CONFIG,
|
||||
"gh": _DEFAULT_CHANNEL_CONFIG,
|
||||
}
|
||||
|
||||
DEFAULT_TRIGGER_SOURCE: TRIGGERSOURCE = TRIGGERSOURCE.EXT_RISING_EDGE
|
||||
|
||||
# NOTE Default readout times for the detectors connected to DDG2
|
||||
# These values are used to calculate the difference between the burst_period and the pulse width of
|
||||
# individual channel pairs. They also mark a lower limit for the exposure time. Needs to be
|
||||
# adjusted if the exposure time should possibly go below 0.2 ms.
|
||||
DEFAULT_READOUT_TIMES = {"ab": 2e-4, "cd": 2e-4, "ef": 2e-4, "gh": 2e-4} # 0.2 ms 5kHz
|
||||
|
||||
# NOTE Default refernce settings for each channel in DDG2
|
||||
DEFAULT_REFERENCES: list[tuple[LiteralChannels, CHANNELREFERENCE]] = [
|
||||
("A", CHANNELREFERENCE.T0),
|
||||
("B", CHANNELREFERENCE.A),
|
||||
("C", CHANNELREFERENCE.T0),
|
||||
("D", CHANNELREFERENCE.C),
|
||||
("E", CHANNELREFERENCE.T0),
|
||||
("F", CHANNELREFERENCE.E),
|
||||
("G", CHANNELREFERENCE.T0),
|
||||
("H", CHANNELREFERENCE.G),
|
||||
]
|
||||
|
||||
###############################
|
||||
## DDG2 IMPLEMENTATION ########
|
||||
###############################
|
||||
|
||||
|
||||
class DDG2(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
"""
|
||||
|
||||
Implementation of the DelayGenerator DDG2 for the cSAXS beamline. This delay generator is
|
||||
reponsible to create triggers for the detectors. It is configured in burst mode. Please
|
||||
check the module docstring, the module README and the attached PDF 'trigger_scheme_ddg1_ddg2.pdf'
|
||||
for more information about the expected cabling and trigger logic.
|
||||
|
||||
The IOC prefix is 'X12SA-CPCL-DDG2:'.
|
||||
|
||||
Args:
|
||||
name (str): Name of the device.
|
||||
prefix (str, optional): EPICS prefix for the device. Defaults to ''.
|
||||
scan_info (ScanInfo | None, optional): Scan info object. Defaults to None.
|
||||
device_manager (DeviceManagerBase | None, optional): Device manager. Defaults to None.
|
||||
|
||||
Implementation of DelayGeneratorCSAXS for the CSAXS master trigger delay generator at X12SA-CPCL-DDG2.
|
||||
This device is responsible for creating triggers in burst mode and is connected to a multiplexer that
|
||||
distributes the trigger to the detectors. The DDG2 is triggered by the DDG1 through the EXT/EN channel.
|
||||
"""
|
||||
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
def on_connected(self) -> None:
|
||||
"""
|
||||
|
||||
This method is called after the device is initialized and all signals are connected. This happens
|
||||
when a device configuration is loaded in BEC.
|
||||
|
||||
It sets the default values for this device - intended to overwrite everything to a usable default state.
|
||||
For this purpose, we use the DEFAULT SETTINGS defined at the top of this module.
|
||||
|
||||
The following procedure is followed:
|
||||
- Stop the DDG to ensure it is not running.
|
||||
- Then, we set the DEFAULT_IO_CONFIG for each channel, the trigger source to DEFAULT_TRIGGER_SOURCE,
|
||||
and the channel references to DEFAULT_REFERENCES.
|
||||
"""
|
||||
self.stop_ddg()
|
||||
|
||||
# NOTE Please adjust the default settings under 'DEFAULT SETTINGS' at the top of this module if needed.
|
||||
# This makes sure that we have a well defined default state for the DDG2 device.
|
||||
for channel, config in DEFAULT_IO_CONFIG.items():
|
||||
self.set_io_values(channel, **config)
|
||||
self.set_trigger(DEFAULT_TRIGGER_SOURCE)
|
||||
self.set_references_for_channels(DEFAULT_REFERENCES)
|
||||
|
||||
def on_stage(self) -> DeviceStatus | StatusBase | None:
|
||||
"""
|
||||
|
||||
This method is called when the device is staged before a scan. All information about the scan
|
||||
is available through self.scan_info.msg at this point. The DDG2 needs to be configured to
|
||||
create a sequence of TTL pulses in burst mode that are sent to the detectors. It therefore needs
|
||||
to know the exposure time and frames per trigger from the self.scan_info.msg.scan_parameters.
|
||||
|
||||
This logic is robust for step scans as well as fly scans, as the DDG2 is triggered by the DDG1
|
||||
through the EXT/EN channel.
|
||||
"""
|
||||
start_time = time.time()
|
||||
########################################
|
||||
### Burst mode settings ################
|
||||
########################################
|
||||
|
||||
# NOTE Only adjust settings if needed. DDG2 should always be in burst mode when used at CSAXS.
|
||||
if self.burst_mode.get() == 0:
|
||||
self.burst_mode.put(1)
|
||||
|
||||
# Ensure that there is no delay for the burst
|
||||
if self.burst_delay.get() != 0:
|
||||
self.burst_delay.put(0)
|
||||
|
||||
exp_time = self.scan_info.msg.scan_parameters["exp_time"]
|
||||
frames_per_trigger = self.scan_info.msg.scan_parameters["frames_per_trigger"]
|
||||
|
||||
# NOTE Check if the exposure time is longer than all readout times.
|
||||
# Raise a ValueError if requested exposure time is too short.
|
||||
if any(exp_time <= rt for rt in DEFAULT_READOUT_TIMES.values()):
|
||||
raise ValueError(
|
||||
f"Exposure time {exp_time} is too short for the readout times {DEFAULT_READOUT_TIMES}"
|
||||
)
|
||||
|
||||
#########################################
|
||||
### Setup timing for burst and delays ###
|
||||
#########################################
|
||||
|
||||
# Burst Period DDG2 settings. Only adjust them if needed.
|
||||
if self.burst_count.get() != frames_per_trigger:
|
||||
self.burst_count.put(frames_per_trigger)
|
||||
if self.burst_period.get() != exp_time:
|
||||
self.burst_period.put(exp_time)
|
||||
|
||||
# Calculate the pulse width for the channel pair 'ab'
|
||||
burst_pulse_width = exp_time - DEFAULT_READOUT_TIMES["ab"]
|
||||
|
||||
# Trigger detectors with delay 0, and pulse width = exp_time - readout_time
|
||||
self.set_delay_pairs(channel="ab", delay=0, width=burst_pulse_width)
|
||||
|
||||
logger.info(f"DDG {self.name} on_stage completed in {time.time() - start_time:.3f}s.")
|
||||
|
||||
def on_pre_scan(self):
|
||||
"""
|
||||
|
||||
Method that is called just before a scan starts. It was observed that a short delay of 50ms
|
||||
improves the overall stability in operation. This may be removed as other parts were adjusted,
|
||||
but for now we will keep it as the delay is short.
|
||||
"""
|
||||
# NOTE Short delay to allow for the HW to process the commands before the scan starts.
|
||||
# This may no longer be needed after other adjustments, and may be removed in the future.
|
||||
time.sleep(0.05)
|
||||
|
||||
def on_trigger(self) -> DeviceStatus | StatusBase | None:
|
||||
"""
|
||||
|
||||
DDG2 does not implement any trigger specific logic as it is triggered by DDG1 through the EXT/EN channel.
|
||||
"""
|
||||
pass
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Stop the delay generator"""
|
||||
self.stop_ddg()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ddg = DDG2(name="ddg2", prefix="X12SA-CPCL-DDG2:")
|
||||
ddg.wait_for_connection(all_signals=True, timeout=30)
|
||||
ddg.summary()
|
||||
@@ -1,769 +0,0 @@
|
||||
"""
|
||||
Delay generator implementation for CSAXS.
|
||||
|
||||
Detailed information can be found in the manual:
|
||||
https://www.thinksrs.com/downloads/pdfs/manuals/DG645m.pdf
|
||||
|
||||
On the beamline consoles, the caqtdm panel can be started via:
|
||||
caqtdm -noMsg -attach -macro P=X12SA-CPCL-DDG,R=1: srsDG645.ui
|
||||
|
||||
R=1,2,3 for 3 different DDG units installed at CSAXS.
|
||||
"""
|
||||
|
||||
import enum
|
||||
import time
|
||||
from typing import Literal, TypedDict
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, EpicsSignal, EpicsSignalRO, Kind, Signal
|
||||
from ophyd_devices import StatusBase, SubscriptionStatus
|
||||
from typeguard import typechecked
|
||||
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.error_registry import ERROR_CODES
|
||||
|
||||
logger = bec_logger.logger
|
||||
DelayChannelNames = Literal["ab", "cd", "ef", "gh"]
|
||||
AllChannelNames = Literal["t0", "ab", "cd", "ef", "gh"]
|
||||
LiteralChannels = Literal["A", "B", "C", "D", "E", "F", "G", "H"]
|
||||
|
||||
|
||||
class CHANNELREFERENCE(enum.Enum):
|
||||
T0 = 0
|
||||
A = 1
|
||||
B = 2
|
||||
C = 3
|
||||
D = 4
|
||||
E = 5
|
||||
F = 6
|
||||
G = 7
|
||||
H = 8
|
||||
|
||||
|
||||
class BURSTCONFIG(enum.Enum):
|
||||
"""Enum option for burst_config signal of the delay generator.
|
||||
|
||||
ALL_CYCLES: T0 triggere for all cycles.
|
||||
FIRST_CYCLE: T0 only triggered for the first cycle.
|
||||
"""
|
||||
|
||||
ALL_CYCLES = 0
|
||||
FIRST_CYCLE = 1
|
||||
|
||||
|
||||
class TRIGGERSOURCE(enum.Enum):
|
||||
"""Enum options for the trigger_source signal of the delay generator."""
|
||||
|
||||
INTERNAL = 0
|
||||
EXT_RISING_EDGE = 1
|
||||
EXT_FALLING_EDGE = 2
|
||||
SS_EXT_RISING_EDGE = 3
|
||||
SS_EXT_FALLING_EDGE = 4
|
||||
SINGLE_SHOT = 5
|
||||
LINE = 6
|
||||
|
||||
|
||||
class TRIGGERINHIBIT(enum.Enum):
|
||||
"""Enum options for the trigger_inhibit signal of the delay generator."""
|
||||
|
||||
OFF = 0
|
||||
TRIGGERS = 1
|
||||
AB = 2
|
||||
AB_CD = 3
|
||||
AB_CD_EF = 4
|
||||
AB_CD_EF_GH = 5
|
||||
|
||||
|
||||
class OUTPUTPOLARITY(enum.Enum):
|
||||
"""Enum options for the polarity signal of the static pair."""
|
||||
|
||||
NEGATIVE = 0
|
||||
POSITIVE = 1
|
||||
|
||||
|
||||
class PROC_EVENT_MODE(int, enum.Enum):
|
||||
"""Read mode for MCS channels."""
|
||||
|
||||
PASSIVE = 0
|
||||
EVENT = 1
|
||||
IO_INTR = 2
|
||||
FREQ_0_1HZ = 3
|
||||
FREQ_0_2HZ = 4
|
||||
FREQ_0_5HZ = 5
|
||||
FREQ_1HZ = 6
|
||||
FREQ_2HZ = 7
|
||||
FREQ_5HZ = 8
|
||||
FREQ_10HZ = 9
|
||||
FREQ_100HZ = 10
|
||||
|
||||
|
||||
class STATUSBITS(enum.IntFlag):
|
||||
"""Bit flags for the status signal of the delay generator."""
|
||||
|
||||
NONE = 0 << 0 # No status bits set.
|
||||
TRIG = 1 << 0 # Got a trigger.
|
||||
RATE = 1 << 1 # Got a trigger while a delay or burst was in progress.
|
||||
END_OF_DELAY = 1 << 2 # A delay cycle has completed.
|
||||
END_OF_BURST = 1 << 3 # A burst cycle has completed.
|
||||
INHIBIT = 1 << 4 # A trigger or output delay cycle was inhibited.
|
||||
ABORT_DELAY = 1 << 5 # A delay cycle was aborted early.
|
||||
PLL_UNLOCK = 1 << 6 # The 100 MHz PLL came unlocked.
|
||||
RB_UNLOCK = 1 << 7 # The installed Rb oscillator is unlocked.
|
||||
|
||||
def describe(self) -> dict:
|
||||
"""Return a description of the status bits."""
|
||||
descriptions = {
|
||||
STATUSBITS.NONE: "No status bits set.",
|
||||
STATUSBITS.TRIG: "Got a trigger.",
|
||||
STATUSBITS.RATE: "Got a trigger while a delay or burst was in progress.",
|
||||
STATUSBITS.END_OF_DELAY: "A delay cycle has completed.",
|
||||
STATUSBITS.END_OF_BURST: "A burst cycle has completed.",
|
||||
STATUSBITS.INHIBIT: "A trigger or output delay cycle was inhibited.",
|
||||
STATUSBITS.ABORT_DELAY: "A delay cycle was aborted early.",
|
||||
STATUSBITS.PLL_UNLOCK: "The 100 MHz PLL came unlocked.",
|
||||
STATUSBITS.RB_UNLOCK: "The installed Rb oscillator is unlocked.",
|
||||
}
|
||||
return {flag.name: descriptions[flag] for flag in STATUSBITS if flag in self}
|
||||
|
||||
|
||||
class StatusBitsCompareStatus(SubscriptionStatus):
|
||||
"""Compare status for STATUSBITS comparison."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
signal: EpicsSignalRO,
|
||||
value: STATUSBITS,
|
||||
raise_states: list[STATUSBITS] | None = None,
|
||||
*args,
|
||||
event_type=None,
|
||||
timeout: float | None = None,
|
||||
add_delay: float | None = None,
|
||||
settle_time: float = 0,
|
||||
run: bool = True,
|
||||
**kwargs,
|
||||
):
|
||||
"""Initialize the compare status with a signal."""
|
||||
self._signal = signal
|
||||
self._value = value
|
||||
self._add_delay = add_delay or 0
|
||||
self._raise_states = raise_states or []
|
||||
super().__init__(
|
||||
obj=signal,
|
||||
callback=self._compare_callback,
|
||||
timeout=timeout,
|
||||
settle_time=settle_time,
|
||||
event_type=event_type,
|
||||
run=run,
|
||||
)
|
||||
|
||||
def _compare_callback(self, *args, value, **kwargs) -> bool:
|
||||
"""Callback for subscription status"""
|
||||
logger.debug(f"StatusBitsCompareStatus: Received value {value}")
|
||||
obj = kwargs.get("obj", None)
|
||||
if obj is None:
|
||||
name = "no object received"
|
||||
else:
|
||||
name = obj.name
|
||||
if any((STATUSBITS(value) & state) == state for state in self._raise_states):
|
||||
self.set_exception(
|
||||
ValueError(
|
||||
f"Status bits {STATUSBITS(value).describe()} raised an exception: {self._raise_states}"
|
||||
)
|
||||
)
|
||||
return False
|
||||
if self._add_delay != 0:
|
||||
time.sleep(self._add_delay)
|
||||
logger.debug(
|
||||
f"Returning comparison for {name}: {(STATUSBITS(value) & self._value) == self._value}"
|
||||
)
|
||||
return (STATUSBITS(value) & self._value) == self._value
|
||||
|
||||
|
||||
class ChannelConfig(TypedDict):
|
||||
amplitude: float | None
|
||||
offset: float | None
|
||||
polarity: OUTPUTPOLARITY | Literal[0, 1] | None
|
||||
mode: Literal["ttl", "nim"] | None
|
||||
|
||||
|
||||
class StaticPair(Device):
|
||||
"""
|
||||
Class to represent a static pair (T0, aswell as all AB, CB, EF, GH channels).
|
||||
It allows setting the logic levels, but the timing is fixed.
|
||||
The signal is high after receiving the trigger until the end of the holdoff period.
|
||||
"""
|
||||
|
||||
ttl_mode = Cpt(
|
||||
EpicsSignal,
|
||||
"OutputModeTtlSS.PROC",
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
doc="Set the output mode to TTL",
|
||||
)
|
||||
nim_mode = Cpt(
|
||||
EpicsSignal,
|
||||
"OutputModeNimSS.PROC",
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
doc="Set the output mode to NIM",
|
||||
)
|
||||
polarity = Cpt(
|
||||
EpicsSignal,
|
||||
"OutputPolarityBI",
|
||||
write_pv="OutputPolarityBO",
|
||||
name="polarity",
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
doc="Control the polarity of the output signal. POS 1 or NEG 0",
|
||||
)
|
||||
|
||||
amplitude = Cpt(
|
||||
EpicsSignal,
|
||||
"OutputAmpAI",
|
||||
write_pv="OutputAmpAO",
|
||||
name="amplitude",
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
doc="Amplitude of the output signal in volts.",
|
||||
)
|
||||
|
||||
offset = Cpt(
|
||||
EpicsSignal,
|
||||
"OutputOffsetAI",
|
||||
write_pv="OutputOffsetAO",
|
||||
name="offset",
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
doc="Offset of the output signal in volts.",
|
||||
)
|
||||
|
||||
|
||||
class Channel(Device):
|
||||
"""
|
||||
Represents a single channel A, B, C, ... of the delay generator.
|
||||
"""
|
||||
|
||||
setpoint = Cpt(
|
||||
EpicsSignal,
|
||||
write_pv="DelayAO",
|
||||
read_pv="DelayAI",
|
||||
put_complete=True,
|
||||
auto_monitor=True,
|
||||
kind=Kind.omitted,
|
||||
doc="Setpoint value for the delay of the channel",
|
||||
)
|
||||
reference = Cpt(
|
||||
EpicsSignal,
|
||||
"ReferenceMO",
|
||||
put_complete=True,
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
doc="Reference channel T0,A,B,.. for the delay of the setpoint",
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Initialize the channel with a setpoint and reference signal.
|
||||
"""
|
||||
# The read PV in EpicsSignal does not receive the prefix.. so we need to add it manually.
|
||||
self.__class__.__dict__["setpoint"].kwargs["read_pv"] = args[0] + "DelayAI"
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class WidthSignal(Signal):
|
||||
"""A signal that represents the width of a channel."""
|
||||
|
||||
def get(self, **kwargs) -> float:
|
||||
"""
|
||||
Get the width of the channel.
|
||||
|
||||
Returns:
|
||||
float: The width of the channel in seconds.
|
||||
"""
|
||||
parent: _DelayPairBase = self._parent # type: ignore
|
||||
return parent.ch2.setpoint.get() - parent.ch1.setpoint.get() # type: ignore
|
||||
|
||||
def check_value(self, value: float) -> float:
|
||||
"""Check if the value is larger equal to 0"""
|
||||
if value >= 0:
|
||||
return value
|
||||
else:
|
||||
raise ValueError(f"Width must be larger ot equal 0, got {value} seconds.")
|
||||
|
||||
def put(self, value: float, **kwargs):
|
||||
"""
|
||||
Set the width of the channel.
|
||||
|
||||
Args:
|
||||
value (float): The width to set in seconds.
|
||||
"""
|
||||
self.check_value(value)
|
||||
parent: _DelayPairBase = self._parent # type: ignore
|
||||
ch1_setpoint: float = parent.ch1.setpoint.get() # type: ignore
|
||||
parent.ch2.setpoint.put(ch1_setpoint + value, **kwargs)
|
||||
|
||||
def set(self, value: float, **kwargs):
|
||||
"""
|
||||
Set the width of the channel.
|
||||
|
||||
Args:
|
||||
value (float): The width to set in seconds.
|
||||
"""
|
||||
status = StatusBase()
|
||||
self.put(value, **kwargs)
|
||||
status.set_finished()
|
||||
return status
|
||||
|
||||
|
||||
class DelaySignal(Signal):
|
||||
"""A signal that represents the delay of a channel."""
|
||||
|
||||
def get(self, **kwargs):
|
||||
"""
|
||||
Get the delay of the channel.
|
||||
|
||||
Returns:
|
||||
float: The delay of the channel in seconds.
|
||||
"""
|
||||
parent: _DelayPairBase = self._parent # type: ignore
|
||||
return parent.ch1.setpoint.get()
|
||||
|
||||
def put(self, value: float, **kwargs):
|
||||
"""
|
||||
Set the delay of the channel.
|
||||
|
||||
Args:
|
||||
value (float): The delay to set in seconds.
|
||||
"""
|
||||
parent: _DelayPairBase = self._parent # type: ignore
|
||||
parent.ch1.setpoint.put(value, **kwargs)
|
||||
parent.ch2.setpoint.put(value + parent.width.get(), **kwargs)
|
||||
|
||||
def set(self, value: float, **kwargs):
|
||||
"""
|
||||
Set the width of the channel.
|
||||
|
||||
Args:
|
||||
value (float): The width to set in seconds.
|
||||
"""
|
||||
status = StatusBase()
|
||||
self.put(value, **kwargs)
|
||||
status.set_finished()
|
||||
return status
|
||||
|
||||
|
||||
class _DelayPairBase(Device):
|
||||
"""Base class for delay pairs. Children have to implement ch1,ch2 for
|
||||
the respective delay channels. The class attributes have to be called
|
||||
ch1, ch2 for width and delay signals to work."""
|
||||
|
||||
ch1: Cpt[Channel]
|
||||
ch2: Cpt[Channel]
|
||||
io: Cpt[StaticPair]
|
||||
width = Cpt(
|
||||
WidthSignal, name="width", kind=Kind.config, doc="Width of TTL pulse for delay pair"
|
||||
)
|
||||
delay = Cpt(
|
||||
DelaySignal, name="delay", kind=Kind.config, doc="Delay of TTL pulse for delay pair"
|
||||
)
|
||||
|
||||
|
||||
class DelayPairAB(_DelayPairBase):
|
||||
|
||||
ch1 = Cpt(Channel, "A", name="A", kind=Kind.omitted, doc="Channel A")
|
||||
ch2 = Cpt(Channel, "B", name="B", kind=Kind.omitted, doc="Channel B")
|
||||
io = Cpt(StaticPair, "AB", name="io", kind=Kind.omitted, doc="IO for delay pair AB")
|
||||
|
||||
|
||||
class DelayPairCD(_DelayPairBase):
|
||||
|
||||
ch1 = Cpt(Channel, "C", name="C", kind=Kind.omitted, doc="Channel C")
|
||||
ch2 = Cpt(Channel, "D", name="D", kind=Kind.omitted, doc="Channel D")
|
||||
io = Cpt(StaticPair, "CD", name="io", kind=Kind.omitted, doc="IO for delay pair CD")
|
||||
|
||||
|
||||
class DelayPairEF(_DelayPairBase):
|
||||
|
||||
ch1 = Cpt(Channel, "E", name="E", kind=Kind.omitted, doc="Channel E")
|
||||
ch2 = Cpt(Channel, "F", name="F", kind=Kind.omitted, doc="Channel F")
|
||||
io = Cpt(StaticPair, "EF", name="io", kind=Kind.omitted, doc="IO for delay pair EF")
|
||||
|
||||
|
||||
class DelayPairGH(_DelayPairBase):
|
||||
|
||||
ch1 = Cpt(Channel, "G", name="G", kind=Kind.omitted, doc="Channel G")
|
||||
ch2 = Cpt(Channel, "H", name="H", kind=Kind.omitted, doc="Channel H")
|
||||
io = Cpt(StaticPair, "GH", name="io", kind=Kind.omitted, doc="IO for delay pair GH")
|
||||
|
||||
|
||||
class DelayGeneratorEventStatus(Device):
|
||||
"""Subdevice to represent the event state of the delay generator."""
|
||||
|
||||
event_status = Cpt(
|
||||
EpicsSignalRO,
|
||||
"EventStatusLI",
|
||||
name="event_status",
|
||||
kind=Kind.omitted,
|
||||
doc="Event status register for the delay generator",
|
||||
)
|
||||
proc_status = Cpt(
|
||||
EpicsSignal,
|
||||
"EventStatusLI.PROC",
|
||||
name="proc_status",
|
||||
kind=Kind.omitted,
|
||||
doc="Poll and flush the latest event status register entry from the HW to the event_status signal",
|
||||
)
|
||||
|
||||
proc_status_mode = Cpt(
|
||||
EpicsSignal,
|
||||
"EventStatusLI.SCAN",
|
||||
kind=Kind.omitted,
|
||||
doc="Readout mode for transferring data from status buffer to the event_status signal.",
|
||||
)
|
||||
|
||||
|
||||
class DelayGeneratorCSAXS(Device):
|
||||
"""
|
||||
Delay Generator Stanford Research DG645. This implements an interface for the DG645 delay generator.
|
||||
|
||||
Detailed information can be found in the manual:
|
||||
https://www.thinksrs.com/downloads/pdfs/manuals/DG645m.pdf
|
||||
|
||||
The DG645 has 8 channels, each with a delay and pulse width. The channels are implemented as DelayPair objects (AB etc.).
|
||||
|
||||
Each pair has a TTL pulse width, delay and a reference signal to which they are being triggered.
|
||||
In addition, the io layer allows setting amplitude, offset and polarity for each pair.
|
||||
"""
|
||||
|
||||
# USER_ACCESS = [
|
||||
# "set_channel_reference",
|
||||
# "set_references_for_channels",
|
||||
# "set_io_values",
|
||||
# "set_trigger",
|
||||
# ]
|
||||
|
||||
_pv_timeout: float = 5 # Default timeout for PV operations in seconds
|
||||
|
||||
# Front Panel
|
||||
t0 = Cpt(StaticPair, "T0", name="t0", doc="T0 static pair")
|
||||
ab = Cpt(DelayPairAB, "", name="ab", doc="Delay pair AB")
|
||||
cd = Cpt(DelayPairCD, "", name="cd", doc="Delay pair CD")
|
||||
ef = Cpt(DelayPairEF, "", name="ef", doc="Delay pair EF")
|
||||
gh = Cpt(DelayPairGH, "", name="gh", doc="Delay pair GH")
|
||||
state = Cpt(DelayGeneratorEventStatus, "", name="state", doc="Subdevice for event status")
|
||||
status_msg = Cpt(
|
||||
EpicsSignalRO,
|
||||
"StatusSI",
|
||||
name="status_msg",
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
doc="Status message from the delay generator",
|
||||
)
|
||||
status_msg_clear = Cpt(
|
||||
EpicsSignal,
|
||||
"StatusClearBO",
|
||||
name="status_msg_clear",
|
||||
kind=Kind.omitted,
|
||||
doc="Clear the status message",
|
||||
)
|
||||
|
||||
trigger_holdoff = Cpt(
|
||||
EpicsSignal,
|
||||
"TriggerHoldoffAI",
|
||||
write_pv="TriggerHoldoffAO",
|
||||
name="trigger_holdoff",
|
||||
kind=Kind.config,
|
||||
)
|
||||
trigger_inhibit = Cpt(
|
||||
EpicsSignal,
|
||||
"TriggerInhibitMI",
|
||||
write_pv="TriggerInhibitMO",
|
||||
name="trigger_inhibit",
|
||||
kind=Kind.omitted,
|
||||
)
|
||||
trigger_source = Cpt(
|
||||
EpicsSignal,
|
||||
"TriggerSourceMI",
|
||||
write_pv="TriggerSourceMO",
|
||||
name="trigger_source",
|
||||
kind=Kind.omitted,
|
||||
doc="Trigger Source for the DDG, options in TRIGGERSOURCE",
|
||||
)
|
||||
trigger_level = Cpt(
|
||||
EpicsSignal,
|
||||
"TriggerLevelAI",
|
||||
write_pv="TriggerLevelAO",
|
||||
name="trigger_level",
|
||||
kind=Kind.omitted,
|
||||
)
|
||||
trigger_rate = Cpt(
|
||||
EpicsSignal,
|
||||
"TriggerRateAI",
|
||||
write_pv="TriggerRateAO",
|
||||
name="trigger_rate",
|
||||
kind=Kind.omitted,
|
||||
)
|
||||
trigger_shot = Cpt(
|
||||
EpicsSignal,
|
||||
"TriggerDelayBO",
|
||||
name="trigger_shot",
|
||||
kind=Kind.omitted,
|
||||
doc="Software trigger, needs to be in correct mode to work",
|
||||
)
|
||||
burst_mode = Cpt(
|
||||
EpicsSignal,
|
||||
"BurstModeBI",
|
||||
write_pv="BurstModeBO",
|
||||
name="burst_mode",
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
doc="Enable or disable burst mode. 1 = enabled, 0 = disabled.",
|
||||
)
|
||||
burst_config = Cpt(
|
||||
EpicsSignal,
|
||||
"BurstConfigBI",
|
||||
write_pv="BurstConfigBO",
|
||||
name="burst_config",
|
||||
kind=Kind.omitted,
|
||||
doc="Configuration of T0 during burst. Can be ALL_CYCLES (0) or FIRST_CYCLE (1) .",
|
||||
)
|
||||
burst_count = Cpt(
|
||||
EpicsSignal,
|
||||
"BurstCountLI",
|
||||
write_pv="BurstCountLO",
|
||||
name="burst_count",
|
||||
kind=Kind.omitted,
|
||||
doc="Number of bursts to trigger in burst mode. Must be >0.",
|
||||
)
|
||||
burst_delay = Cpt(
|
||||
EpicsSignal,
|
||||
"BurstDelayAI",
|
||||
write_pv="BurstDelayAO",
|
||||
name="burst_delay",
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
doc="Delay before bursts start in seconds. Must be >=0.",
|
||||
)
|
||||
burst_period = Cpt(
|
||||
EpicsSignal,
|
||||
"BurstPeriodAI",
|
||||
write_pv="BurstPeriodAO",
|
||||
name="burst_period",
|
||||
kind=Kind.omitted,
|
||||
doc="Period of the bursts in seconds. Must be >0.",
|
||||
)
|
||||
|
||||
def proc_event_status(self) -> None:
|
||||
"""The reading must be manually triggered to update the event status."""
|
||||
self.state.proc_status.put(1)
|
||||
|
||||
def wait_for_event_status(
|
||||
self, value: STATUSBITS, timeout: float | None = None
|
||||
) -> StatusBitsCompareStatus:
|
||||
"""
|
||||
Wait for a specific event status.
|
||||
|
||||
Args:
|
||||
value (STATUSBITS): The status bits to wait for.
|
||||
timeout (float): The maximum time to wait in seconds.
|
||||
"""
|
||||
return StatusBitsCompareStatus(
|
||||
signal=self.state.event_status, value=value, timeout=timeout, run=True
|
||||
)
|
||||
|
||||
def set_trigger(self, source: TRIGGERSOURCE | int) -> None:
|
||||
"""
|
||||
Set the trigger source.
|
||||
|
||||
Args:
|
||||
source (TriggerSource | int): The trigger source
|
||||
INTERNAL = 0
|
||||
EXT_RISING_EDGE = 1
|
||||
EXT_FALLING_EDGE = 2
|
||||
SS_EXT_RISING_EDGE = 3
|
||||
SS_EXT_FALLING_EDGE = 4
|
||||
SINGLE_SHOT = 5
|
||||
LINE = 6
|
||||
"""
|
||||
if isinstance(source, TRIGGERSOURCE):
|
||||
self.trigger_source.set(source.value).wait(self._pv_timeout)
|
||||
else:
|
||||
self.trigger_source.set(int(source)).wait(self._pv_timeout)
|
||||
|
||||
@typechecked
|
||||
def burst_enable(
|
||||
self,
|
||||
count: int,
|
||||
delay: float,
|
||||
period: float,
|
||||
config: Literal["all", "first"] | BURSTCONFIG = "first",
|
||||
) -> None:
|
||||
"""Enable burst mode with valid parameters.
|
||||
|
||||
Args:
|
||||
count (int): Number of bursts >0
|
||||
delay (float): Delay before bursts start in seconds >=0
|
||||
period (float): Period of the bursts in seconds >0
|
||||
config (str): Configuration of T0 duiring burst.
|
||||
In addition, to simplify triggering of other instruments synchronously with the burst,
|
||||
the T0 output may be configured to fire on the first delay cycle of the burst,
|
||||
rather than for all delay cycles as is normally the case. BURSTCONFIG
|
||||
"""
|
||||
|
||||
# Check inputs first
|
||||
if count <= 0:
|
||||
raise ValueError(f"Count must be >0, provided: {count}")
|
||||
if delay < 0:
|
||||
raise ValueError(f"Delay must be >=0, provided: {delay}")
|
||||
if period <= 0:
|
||||
raise ValueError(f"Period must be >0, provided: {period}")
|
||||
|
||||
self.burst_mode.set(1).wait(timeout=self._pv_timeout)
|
||||
self.burst_count.set(count).wait(timeout=self._pv_timeout)
|
||||
self.burst_delay.set(delay).wait(timeout=self._pv_timeout)
|
||||
self.burst_period.set(period).wait(timeout=self._pv_timeout)
|
||||
|
||||
if config == "all":
|
||||
self.burst_config.set(BURSTCONFIG.ALL_CYCLES.value).wait(timeout=self._pv_timeout)
|
||||
elif config == "first":
|
||||
self.burst_config.set(BURSTCONFIG.FIRST_CYCLE.value).wait(timeout=self._pv_timeout)
|
||||
|
||||
def burst_disable(self) -> None:
|
||||
"""Disable burst mode"""
|
||||
self.burst_mode.set(0).wait(timeout=self._pv_timeout)
|
||||
|
||||
@typechecked
|
||||
def set_io_values(
|
||||
self,
|
||||
channel: AllChannelNames | list[AllChannelNames],
|
||||
amplitude: float | None = None,
|
||||
offset: float | None = None,
|
||||
polarity: OUTPUTPOLARITY | Literal[0, 1] | None = None,
|
||||
mode: Literal["ttl", "nim"] | None = None,
|
||||
) -> None:
|
||||
"""Set the IO values for the static pair.
|
||||
|
||||
Args:
|
||||
channel (str | list[str]): Channel(s) to set the IO values for.
|
||||
Can be "t0", "ab", "cd", "ef", "gh" or a list of these.
|
||||
If a list is provided, the same values will be set for all channels.
|
||||
amplitude (float): Amplitude of the output signal in volts.
|
||||
offset (float): Offset of the output signal in volts.
|
||||
polarity (OUTPUTPOLARITY | int): Polarity of the output signal.
|
||||
ttl_mode (bool): If True, set the output to TTL mode.
|
||||
nim_mode (bool): If True, set the output to NIM mode.
|
||||
If both ttl_mode and nim_mode are set to True,
|
||||
a ValueError is raised.
|
||||
"""
|
||||
if isinstance(channel, str):
|
||||
channel = [channel]
|
||||
for ch in channel:
|
||||
if ch == "t0":
|
||||
io_channel = self.t0
|
||||
else:
|
||||
io_channel = getattr(getattr(self, ch), "io")
|
||||
if amplitude is not None:
|
||||
io_channel.amplitude.set(amplitude).wait(timeout=self._pv_timeout)
|
||||
if offset is not None:
|
||||
io_channel.offset.set(offset).wait(timeout=self._pv_timeout)
|
||||
if polarity is not None:
|
||||
if isinstance(polarity, OUTPUTPOLARITY):
|
||||
io_channel.polarity.set(polarity.value).wait(timeout=self._pv_timeout)
|
||||
else:
|
||||
io_channel.polarity.set(int(polarity)).wait(timeout=self._pv_timeout)
|
||||
if mode == "ttl":
|
||||
io_channel.ttl_mode.set(1).wait(timeout=self._pv_timeout)
|
||||
if mode == "nim":
|
||||
io_channel.nim_mode.set(1).wait(timeout=self._pv_timeout)
|
||||
|
||||
def set_delay_pairs(
|
||||
self,
|
||||
channel: DelayChannelNames | list[DelayChannelNames],
|
||||
delay: float | list[float] | None = None,
|
||||
width: float | list[float] | None = None,
|
||||
) -> None:
|
||||
"""Set the delay and width for a specific channel pair.
|
||||
|
||||
Args:
|
||||
channel (str): Channel pair to set the delay and width for.
|
||||
Can be "ab", "cd", "ef", "gh".
|
||||
delay (float): Delay in seconds to set for the channel pair.
|
||||
width (float): Width in seconds to set for the channel pair.
|
||||
"""
|
||||
if isinstance(channel, str):
|
||||
channel = [channel]
|
||||
if isinstance(delay, (float, int)):
|
||||
delay = [float(delay)] * len(channel)
|
||||
if isinstance(width, (float, int)):
|
||||
width = [float(width)] * len(channel)
|
||||
if delay is not None:
|
||||
if len(delay) != len(channel):
|
||||
raise ValueError(
|
||||
f"Length of delay {len(delay)} must match length of channel {len(channel)}."
|
||||
)
|
||||
for ii, ch in enumerate(channel):
|
||||
delay_channel = getattr(self, ch)
|
||||
delay_channel.delay.put(delay[ii])
|
||||
if width is not None:
|
||||
if len(width) != len(channel):
|
||||
raise ValueError(
|
||||
f"Length of width {len(width)} must match length of channel {len(channel)}."
|
||||
)
|
||||
for ii, ch in enumerate(channel):
|
||||
delay_channel = getattr(self, ch)
|
||||
delay_channel.width.put(width[ii])
|
||||
|
||||
def _get_literal_channel(self, channel: LiteralChannels) -> Channel:
|
||||
return {
|
||||
"A": self.ab.ch1,
|
||||
"B": self.ab.ch2,
|
||||
"C": self.cd.ch1,
|
||||
"D": self.cd.ch2,
|
||||
"E": self.ef.ch1,
|
||||
"F": self.ef.ch2,
|
||||
"G": self.gh.ch1,
|
||||
"H": self.gh.ch2,
|
||||
}[channel]
|
||||
|
||||
def set_channel_reference(self, channel: LiteralChannels, reference_channel: CHANNELREFERENCE):
|
||||
"""Set the reference channel for a specific channel.
|
||||
|
||||
Args:
|
||||
channel (LiteralChannels): The channel to set the reference for.
|
||||
reference_channel (CHANNELREFERENCE): The reference channel to set.
|
||||
"""
|
||||
self._get_literal_channel(channel).reference.put(reference_channel.value)
|
||||
|
||||
def set_references_for_channels(
|
||||
self, channels_and_refs: list[tuple[LiteralChannels, CHANNELREFERENCE]]
|
||||
):
|
||||
"""Set the reference channels for multiple channels.
|
||||
|
||||
Args:
|
||||
channels_and_refs (list[tuple[LiteralChannels, CHANNELREFERENCE]]): A list of
|
||||
tuples where each tuple contains a channel and its corresponding reference channel.
|
||||
"""
|
||||
for ch, ref in channels_and_refs:
|
||||
self.set_channel_reference(ch, ref)
|
||||
|
||||
def stop_ddg(self) -> None:
|
||||
"""Stop the delay generator by setting the burst mode to 0"""
|
||||
self.burst_mode.put(0)
|
||||
|
||||
def reset_error(self) -> None:
|
||||
"""Reset the error status message of the delay generator."""
|
||||
self.status_msg_clear.put(1)
|
||||
|
||||
def get_error_msg(self) -> str:
|
||||
"""Get the error message from the delay generator."""
|
||||
msg = self.status_msg.get()
|
||||
if msg in ERROR_CODES:
|
||||
return ERROR_CODES[msg]
|
||||
else:
|
||||
return f"Unknown error code: {msg}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
ddg = DelayGeneratorCSAXS(name="ddg", prefix="X12SA-CPCL-DDG1:")
|
||||
ddg.wait_for_connection(all_signals=True, timeout=30)
|
||||
ddg.summary()
|
||||
@@ -1,73 +0,0 @@
|
||||
ERROR_CODES: dict[str, str] = {
|
||||
"STATUS OK": "No more errors left in the queue.", # renamed apparently from the IOC for 'No Error' to 'STATUS OK'
|
||||
"Illegal Value": "A parameter was out of range.",
|
||||
"Illegal Mode": "The action is illegal in the current mode.",
|
||||
"Illegal Delay": "The requested delay is out of range.",
|
||||
"Illegal Link": "The requested delay linkage is illegal.",
|
||||
"Recall Failed": "Recall of instrument settings failed; settings were invalid.",
|
||||
"Not Allowed": "Action not allowed: instrument is locked by another interface.",
|
||||
"Failed Self Test": "The DG645 self test failed.",
|
||||
"Failed Auto Calibration": "The DG645 auto calibration failed.",
|
||||
"Lost Data": "Output buffer overflow or data lost due to communication error.",
|
||||
"No Listener": "No GPIB listeners; pending output discarded.",
|
||||
"Failed ROM Check": "ROM checksum failed; firmware likely corrupted.",
|
||||
"Failed Offset T0 Test": "Self test of offset functionality for T0 failed.",
|
||||
"Failed Offset AB Test": "Self test of offset functionality for AB failed.",
|
||||
"Failed Offset CD Test": "Self test of offset functionality for CD failed.",
|
||||
"Failed Offset EF Test": "Self test of offset functionality for EF failed.",
|
||||
"Failed Offset GH Test": "Self test of offset functionality for GH failed.",
|
||||
"Failed Amplitude T0 Test": "Self test of amplitude functionality for T0 failed.",
|
||||
"Failed Amplitude AB Test": "Self test of amplitude functionality for AB failed.",
|
||||
"Failed Amplitude CD Test": "Self test of amplitude functionality for CD failed.",
|
||||
"Failed Amplitude EF Test": "Self test of amplitude functionality for EF failed.",
|
||||
"Failed Amplitude GH Test": "Self test of amplitude functionality for GH failed.",
|
||||
"Failed FPGA Communications Test": "Self test of FPGA communications failed.",
|
||||
"Failed GPIB Communications Test": "Self test of GPIB communications failed.",
|
||||
"Failed DDS Communications Test": "Self test of DDS communications failed.",
|
||||
"Failed Serial EEPROM Communications Test": "Self test of serial EEPROM failed.",
|
||||
"Failed Temperature Sensor Communications Test": "Temp sensor communication failed.",
|
||||
"Failed PLL Communications Test": "PLL communication self test failed.",
|
||||
"Failed DAC 0 Communications Test": "Self test of DAC 0 failed.",
|
||||
"Failed DAC 1 Communications Test": "Self test of DAC 1 failed.",
|
||||
"Failed DAC 2 Communications Test": "Self test of DAC 2 failed.",
|
||||
"Failed Sample and Hold Operations Test": "Sample and hold self test failed.",
|
||||
"Failed Vjitter Operations Test": "Vjitter operation self test failed.",
|
||||
"Failed Channel T0 Analog Delay Test": "Analog delay test for T0 failed.",
|
||||
"Failed Channel T1 Analog Delay Test": "Analog delay test for T1 failed.",
|
||||
"Failed Channel A Analog Delay Test": "Analog delay test for A failed.",
|
||||
"Failed Channel B Analog Delay Test": "Analog delay test for B failed.",
|
||||
"Failed Channel C Analog Delay Test": "Analog delay test for C failed.",
|
||||
"Failed Channel D Analog Delay Test": "Analog delay test for D failed.",
|
||||
"Failed Channel E Analog Delay Test": "Analog delay test for E failed.",
|
||||
"Failed Channel F Analog Delay Test": "Analog delay test for F failed.",
|
||||
"Failed Channel G Analog Delay Test": "Analog delay test for G failed.",
|
||||
"Failed Channel H Analog Delay Test": "Analog delay test for H failed.",
|
||||
"Failed Sample and Hold Calibration": "Auto calibration of sample and hold failed.",
|
||||
"Failed T0 Calibration": "Auto calibration of channel T0 failed.",
|
||||
"Failed T1 Calibration": "Auto calibration of channel T1 failed.",
|
||||
"Failed A Calibration": "Auto calibration of channel A failed.",
|
||||
"Failed B Calibration": "Auto calibration of channel B failed.",
|
||||
"Failed C Calibration": "Auto calibration of channel C failed.",
|
||||
"Failed D Calibration": "Auto calibration of channel D failed.",
|
||||
"Failed E Calibration": "Auto calibration of channel E failed.",
|
||||
"Failed F Calibration": "Auto calibration of channel F failed.",
|
||||
"Failed G Calibration": "Auto calibration of channel G failed.",
|
||||
"Failed H Calibration": "Auto calibration of channel H failed.",
|
||||
"Failed Vjitter Calibration": "Auto calibration of Vjitter failed.",
|
||||
"Illegal Command": "The command syntax used was illegal.",
|
||||
"Undefined Command": "The specified command does not exist.",
|
||||
"Illegal Query": "The specified command does not permit queries.",
|
||||
"Illegal Set": "The specified command can only be queried.",
|
||||
"Null Parameter": "The parser detected an empty parameter.",
|
||||
"Extra Parameters": "Too many parameters were provided.",
|
||||
"Missing Parameters": "Some required parameters are missing.",
|
||||
"Parameter Overflow": "Buffer overflow while parsing parameters.",
|
||||
"Invalid Floating Point Number": "Expected a float but couldn't parse it.",
|
||||
"Invalid Integer": "Expected an integer but couldn't parse it.",
|
||||
"Integer Overflow": "Parsed integer is too large.",
|
||||
"Invalid Hexadecimal": "Failed to parse expected hexadecimal input.",
|
||||
"Syntax Error": "The parser detected a syntax error.",
|
||||
"Communication Error": "Framing or parity error detected.",
|
||||
"Over run": "Input buffer overflowed.",
|
||||
"Too Many Errors": "Error buffer is full; some errors dropped.",
|
||||
}
|
||||
Binary file not shown.
14
csaxs_bec/devices/epics/devices/__init__.py
Normal file
14
csaxs_bec/devices/epics/devices/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# Standard ophyd classes
|
||||
from ophyd import EpicsMotor, EpicsSignal, EpicsSignalRO
|
||||
from ophyd.quadem import QuadEM
|
||||
from ophyd.sim import SynAxis, SynPeriodicSignal, SynSignal
|
||||
|
||||
from .delay_generator_csaxs import DelayGeneratorcSAXS
|
||||
from .eiger9m_csaxs import Eiger9McSAXS
|
||||
|
||||
# cSAXS
|
||||
from .falcon_csaxs import FalconcSAXS
|
||||
from .flomni_sample_storage import FlomniSampleStorage
|
||||
from .InsertionDevice import InsertionDevice
|
||||
from .mcs_csaxs import MCScSAXS
|
||||
from .pilatus_csaxs import PilatuscSAXS
|
||||
32
csaxs_bec/devices/epics/devices/cSaxsVirtualMotors.py
Normal file
32
csaxs_bec/devices/epics/devices/cSaxsVirtualMotors.py
Normal file
@@ -0,0 +1,32 @@
|
||||
""" TODO This class seems to be missing various imports and appears to have not been tested in motion yet."""
|
||||
|
||||
TABLES_DT_PUSH_DIST_MM = 890
|
||||
|
||||
|
||||
class DetectorTableTheta(PseudoPositioner):
|
||||
"""Detector table tilt motor
|
||||
|
||||
Small wrapper to adjust the detector table tilt as angle.
|
||||
The table is pushed from one side by a single vertical motor.
|
||||
|
||||
Note: Rarely used!
|
||||
"""
|
||||
|
||||
# Real axis (in degrees)
|
||||
pusher = Component(EpicsMotor, "", name="pusher")
|
||||
# Virtual axis
|
||||
theta = Component(PseudoSingle, name="theta")
|
||||
|
||||
_real = ["pusher"]
|
||||
|
||||
@pseudo_position_argument
|
||||
def forward(self, pseudo_pos):
|
||||
return self.RealPosition(
|
||||
pusher=tan(pseudo_pos.theta * 3.141592 / 180.0) * TABLES_DT_PUSH_DIST_MM
|
||||
)
|
||||
|
||||
@real_position_argument
|
||||
def inverse(self, real_pos):
|
||||
return self.PseudoPosition(
|
||||
theta=-180 * atan(real_pos.pusher / TABLES_DT_PUSH_DIST_MM) / 3.141592
|
||||
)
|
||||
345
csaxs_bec/devices/epics/devices/delay_generator_csaxs.py
Normal file
345
csaxs_bec/devices/epics/devices/delay_generator_csaxs.py
Normal file
@@ -0,0 +1,345 @@
|
||||
from bec_lib import bec_logger
|
||||
from ophyd import Component
|
||||
from ophyd_devices.epics.devices.psi_delay_generator_base import (
|
||||
DDGCustomMixin,
|
||||
PSIDelayGeneratorBase,
|
||||
TriggerSource,
|
||||
)
|
||||
from ophyd_devices.utils import bec_utils
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class DelayGeneratorError(Exception):
|
||||
"""Exception raised for errors."""
|
||||
|
||||
|
||||
class DDGSetup(DDGCustomMixin):
|
||||
"""
|
||||
Mixin class for DelayGenerator logic at cSAXS.
|
||||
|
||||
At cSAXS, multiple DDGs were operated at the same time. There different behaviour is
|
||||
implemented in the ddg_config signals that are passed via the device config.
|
||||
"""
|
||||
|
||||
def initialize_default_parameter(self) -> None:
|
||||
"""Method to initialize default parameters."""
|
||||
for ii, channel in enumerate(self.parent.all_channels):
|
||||
self.parent.set_channels("polarity", self.parent.polarity.get()[ii], [channel])
|
||||
|
||||
self.parent.set_channels("amplitude", self.parent.amplitude.get())
|
||||
self.parent.set_channels("offset", self.parent.offset.get())
|
||||
# Setup reference
|
||||
self.parent.set_channels(
|
||||
"reference", 0, [f"channel{pair}.ch1" for pair in self.parent.all_delay_pairs]
|
||||
)
|
||||
self.parent.set_channels(
|
||||
"reference", 0, [f"channel{pair}.ch2" for pair in self.parent.all_delay_pairs]
|
||||
)
|
||||
self.parent.set_trigger(getattr(TriggerSource, self.parent.set_trigger_source.get()))
|
||||
# Set threshold level for ext. pulses
|
||||
self.parent.level.put(self.parent.thres_trig_level.get())
|
||||
|
||||
def prepare_ddg(self) -> None:
|
||||
"""
|
||||
Method to prepare scan logic of cSAXS
|
||||
|
||||
Two scantypes are supported: "step" and "fly":
|
||||
- step: Scan is performed by stepping the motor and acquiring data at each step
|
||||
- fly: Scan is performed by moving the motor with a constant velocity and acquiring data
|
||||
|
||||
Custom logic for different DDG behaviour during scans.
|
||||
|
||||
- set_high_on_exposure : If True, then TTL signal is high during
|
||||
the full exposure time of the scan (all frames).
|
||||
E.g. Keep shutter open for the full scan.
|
||||
- fixed_ttl_width : fixed_ttl_width is a list of 5 values, one for each channel.
|
||||
If the value is 0, then the width of the TTL pulse is determined,
|
||||
no matter which parameters are passed from the scaninfo for exposure time
|
||||
- set_trigger_source : Specifies the default trigger source for the DDG. For cSAXS, relevant ones
|
||||
were: SINGLE_SHOT, EXT_RISING_EDGE
|
||||
"""
|
||||
self.parent.set_trigger(getattr(TriggerSource, self.parent.set_trigger_source.get()))
|
||||
# scantype "step"
|
||||
if self.parent.scaninfo.scan_type == "step":
|
||||
# High on exposure means that the signal
|
||||
if self.parent.set_high_on_exposure.get():
|
||||
# caluculate parameters
|
||||
num_burst_cycle = 1 + self.parent.additional_triggers.get()
|
||||
|
||||
exp_time = (
|
||||
self.parent.delta_width.get()
|
||||
+ self.parent.scaninfo.frames_per_trigger
|
||||
* (self.parent.scaninfo.exp_time + self.parent.scaninfo.readout_time)
|
||||
)
|
||||
total_exposure = exp_time
|
||||
delay_burst = self.parent.delay_burst.get()
|
||||
|
||||
# Set individual channel widths, if fixed_ttl_width and trigger_width are combined, this can be a common call too
|
||||
if not self.parent.trigger_width.get():
|
||||
self.parent.set_channels("width", exp_time)
|
||||
else:
|
||||
self.parent.set_channels("width", self.parent.trigger_width.get())
|
||||
for value, channel in zip(
|
||||
self.parent.fixed_ttl_width.get(), self.parent.all_channels
|
||||
):
|
||||
logger.debug(f"Trying to set DDG {channel} to {value}")
|
||||
if value != 0:
|
||||
self.parent.set_channels("width", value, channels=[channel])
|
||||
else:
|
||||
# caluculate parameters
|
||||
exp_time = self.parent.delta_width.get() + self.parent.scaninfo.exp_time
|
||||
total_exposure = exp_time + self.parent.scaninfo.readout_time
|
||||
delay_burst = self.parent.delay_burst.get()
|
||||
num_burst_cycle = (
|
||||
self.parent.scaninfo.frames_per_trigger + self.parent.additional_triggers.get()
|
||||
)
|
||||
|
||||
# Set individual channel widths, if fixed_ttl_width and trigger_width are combined, this can be a common call too
|
||||
if not self.parent.trigger_width.get():
|
||||
self.parent.set_channels("width", exp_time)
|
||||
else:
|
||||
self.parent.set_channels("width", self.parent.trigger_width.get())
|
||||
# scantype "fly"
|
||||
elif self.parent.scaninfo.scan_type == "fly":
|
||||
if self.parent.set_high_on_exposure.get():
|
||||
# caluculate parameters
|
||||
exp_time = (
|
||||
self.parent.delta_width.get()
|
||||
+ self.parent.scaninfo.exp_time * self.parent.scaninfo.num_points
|
||||
+ self.parent.scaninfo.readout_time * (self.parent.scaninfo.num_points - 1)
|
||||
)
|
||||
total_exposure = exp_time
|
||||
delay_burst = self.parent.delay_burst.get()
|
||||
num_burst_cycle = 1 + self.parent.additional_triggers.get()
|
||||
|
||||
# Set individual channel widths, if fixed_ttl_width and trigger_width are combined, this can be a common call too
|
||||
if not self.parent.trigger_width.get():
|
||||
self.parent.set_channels("width", exp_time)
|
||||
else:
|
||||
self.parent.set_channels("width", self.parent.trigger_width.get())
|
||||
for value, channel in zip(
|
||||
self.parent.fixed_ttl_width.get(), self.parent.all_channels
|
||||
):
|
||||
logger.debug(f"Trying to set DDG {channel} to {value}")
|
||||
if value != 0:
|
||||
self.parent.set_channels("width", value, channels=[channel])
|
||||
else:
|
||||
# caluculate parameters
|
||||
exp_time = self.parent.delta_width.get() + self.parent.scaninfo.exp_time
|
||||
total_exposure = exp_time + self.parent.scaninfo.readout_time
|
||||
delay_burst = self.parent.delay_burst.get()
|
||||
num_burst_cycle = (
|
||||
self.parent.scaninfo.num_points + self.parent.additional_triggers.get()
|
||||
)
|
||||
|
||||
# Set individual channel widths, if fixed_ttl_width and trigger_width are combined, this can be a common call too
|
||||
if not self.parent.trigger_width.get():
|
||||
self.parent.set_channels("width", exp_time)
|
||||
else:
|
||||
self.parent.set_channels("width", self.parent.trigger_width.get())
|
||||
|
||||
else:
|
||||
raise Exception(f"Unknown scan type {self.parent.scaninfo.scan_type}")
|
||||
# Set common DDG parameters
|
||||
self.parent.burst_enable(num_burst_cycle, delay_burst, total_exposure, config="first")
|
||||
self.parent.set_channels("delay", 0.0)
|
||||
|
||||
def on_trigger(self) -> None:
|
||||
"""Method to be executed upon trigger"""
|
||||
if self.parent.source.read()[self.parent.source.name]["value"] == TriggerSource.SINGLE_SHOT:
|
||||
self.parent.trigger_shot.put(1)
|
||||
|
||||
def check_scan_id(self) -> None:
|
||||
"""
|
||||
Method to check if scan_id has changed.
|
||||
|
||||
If yes, then it changes parent.stopped to True, which will stop further actions.
|
||||
"""
|
||||
old_scan_id = self.parent.scaninfo.scan_id
|
||||
self.parent.scaninfo.load_scan_metadata()
|
||||
if self.parent.scaninfo.scan_id != old_scan_id:
|
||||
self.parent.stopped = True
|
||||
|
||||
def finished(self) -> None:
|
||||
"""Method checks if DDG finished acquisition"""
|
||||
|
||||
def on_pre_scan(self) -> None:
|
||||
"""
|
||||
Method called by pre_scan hook in parent class.
|
||||
|
||||
Executes trigger if premove_trigger is Trus.
|
||||
"""
|
||||
if self.parent.premove_trigger.get() is True:
|
||||
self.parent.trigger_shot.put(1)
|
||||
|
||||
|
||||
class DelayGeneratorcSAXS(PSIDelayGeneratorBase):
|
||||
"""
|
||||
DG645 delay generator at cSAXS (multiple can be in use depending on the setup)
|
||||
|
||||
Default values for setting up DDG.
|
||||
Note: checks of set calues are not (only partially) included, check manual for details on possible settings.
|
||||
https://www.thinksrs.com/downloads/pdfs/manuals/DG645m.pdf
|
||||
|
||||
- delay_burst : (float >=0) Delay between trigger and first pulse in burst mode
|
||||
- delta_width : (float >= 0) Add width to fast shutter signal to make sure its open during acquisition
|
||||
- additional_triggers : (int) add additional triggers to burst mode (mcs card needs +1 triggers per line)
|
||||
- polarity : (list of 0/1) polarity for different channels
|
||||
- amplitude : (float) amplitude voltage of TTLs
|
||||
- offset : (float) offset for ampltitude
|
||||
- thres_trig_level : (float) threshold of trigger amplitude
|
||||
|
||||
Custom signals for logic in different DDGs during scans (for custom_prepare.prepare_ddg):
|
||||
|
||||
- set_high_on_exposure : (bool): if True, then TTL signal should go high during the full acquisition time of a scan.
|
||||
# TODO trigger_width and fixed_ttl could be combined into single list.
|
||||
- fixed_ttl_width : (list of either 1 or 0), one for each channel.
|
||||
- trigger_width : (float) if fixed_ttl_width is True, then the width of the TTL pulse is set to this value.
|
||||
- set_trigger_source : (TriggerSource) specifies the default trigger source for the DDG.
|
||||
- premove_trigger : (bool) if True, then a trigger should be executed before the scan starts (to be implemented in on_pre_scan).
|
||||
- set_high_on_stage : (bool) if True, then TTL signal should go high already on stage.
|
||||
"""
|
||||
|
||||
custom_prepare_cls = DDGSetup
|
||||
|
||||
delay_burst = Component(
|
||||
bec_utils.ConfigSignal, name="delay_burst", kind="config", config_storage_name="ddg_config"
|
||||
)
|
||||
|
||||
delta_width = Component(
|
||||
bec_utils.ConfigSignal, name="delta_width", kind="config", config_storage_name="ddg_config"
|
||||
)
|
||||
|
||||
additional_triggers = Component(
|
||||
bec_utils.ConfigSignal,
|
||||
name="additional_triggers",
|
||||
kind="config",
|
||||
config_storage_name="ddg_config",
|
||||
)
|
||||
|
||||
polarity = Component(
|
||||
bec_utils.ConfigSignal, name="polarity", kind="config", config_storage_name="ddg_config"
|
||||
)
|
||||
|
||||
fixed_ttl_width = Component(
|
||||
bec_utils.ConfigSignal,
|
||||
name="fixed_ttl_width",
|
||||
kind="config",
|
||||
config_storage_name="ddg_config",
|
||||
)
|
||||
|
||||
amplitude = Component(
|
||||
bec_utils.ConfigSignal, name="amplitude", kind="config", config_storage_name="ddg_config"
|
||||
)
|
||||
|
||||
offset = Component(
|
||||
bec_utils.ConfigSignal, name="offset", kind="config", config_storage_name="ddg_config"
|
||||
)
|
||||
|
||||
thres_trig_level = Component(
|
||||
bec_utils.ConfigSignal,
|
||||
name="thres_trig_level",
|
||||
kind="config",
|
||||
config_storage_name="ddg_config",
|
||||
)
|
||||
|
||||
set_high_on_exposure = Component(
|
||||
bec_utils.ConfigSignal,
|
||||
name="set_high_on_exposure",
|
||||
kind="config",
|
||||
config_storage_name="ddg_config",
|
||||
)
|
||||
|
||||
set_high_on_stage = Component(
|
||||
bec_utils.ConfigSignal,
|
||||
name="set_high_on_stage",
|
||||
kind="config",
|
||||
config_storage_name="ddg_config",
|
||||
)
|
||||
|
||||
set_trigger_source = Component(
|
||||
bec_utils.ConfigSignal,
|
||||
name="set_trigger_source",
|
||||
kind="config",
|
||||
config_storage_name="ddg_config",
|
||||
)
|
||||
|
||||
trigger_width = Component(
|
||||
bec_utils.ConfigSignal,
|
||||
name="trigger_width",
|
||||
kind="config",
|
||||
config_storage_name="ddg_config",
|
||||
)
|
||||
premove_trigger = Component(
|
||||
bec_utils.ConfigSignal,
|
||||
name="premove_trigger",
|
||||
kind="config",
|
||||
config_storage_name="ddg_config",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
prefix="",
|
||||
*,
|
||||
name,
|
||||
kind=None,
|
||||
read_attrs=None,
|
||||
configuration_attrs=None,
|
||||
parent=None,
|
||||
device_manager=None,
|
||||
sim_mode=False,
|
||||
ddg_config=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
prefix (str, optional): Prefix of the device. Defaults to "".
|
||||
name (str): Name of the device.
|
||||
kind (str, optional): Kind of the device. Defaults to None.
|
||||
read_attrs (list, optional): List of attributes to read. Defaults to None.
|
||||
configuration_attrs (list, optional): List of attributes to configure. Defaults to None.
|
||||
parent (Device, optional): Parent device. Defaults to None.
|
||||
device_manager (DeviceManagerBase, optional): DeviceManagerBase object. Defaults to None.
|
||||
sim_mode (bool, optional): Simulation mode flag. Defaults to False.
|
||||
ddg_config (dict, optional): Dictionary of ddg_config signals. Defaults to None.
|
||||
|
||||
"""
|
||||
# Default values for ddg_config signals
|
||||
self.ddg_config = {
|
||||
# Setup default values
|
||||
f"{name}_delay_burst": 0,
|
||||
f"{name}_delta_width": 0,
|
||||
f"{name}_additional_triggers": 0,
|
||||
f"{name}_polarity": [1, 1, 1, 1, 1],
|
||||
f"{name}_amplitude": 4.5,
|
||||
f"{name}_offset": 0,
|
||||
f"{name}_thres_trig_level": 2.5,
|
||||
# Values for different behaviour during scans
|
||||
f"{name}_fixed_ttl_width": [0, 0, 0, 0, 0],
|
||||
f"{name}_trigger_width": None,
|
||||
f"{name}_set_high_on_exposure": False,
|
||||
f"{name}_set_high_on_stage": False,
|
||||
f"{name}_set_trigger_source": "SINGLE_SHOT",
|
||||
f"{name}_premove_trigger": False,
|
||||
}
|
||||
if ddg_config is not None:
|
||||
# pylint: disable=expression-not-assigned
|
||||
[self.ddg_config.update({f"{name}_{key}": value}) for key, value in ddg_config.items()]
|
||||
super().__init__(
|
||||
prefix=prefix,
|
||||
name=name,
|
||||
kind=kind,
|
||||
read_attrs=read_attrs,
|
||||
configuration_attrs=configuration_attrs,
|
||||
parent=parent,
|
||||
device_manager=device_manager,
|
||||
sim_mode=sim_mode,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Start delay generator in simulation mode.
|
||||
# Note: To run, access to Epics must be available.
|
||||
dgen = DelayGeneratorcSAXS("delaygen:DG1:", name="dgen", sim_mode=True)
|
||||
427
csaxs_bec/devices/epics/devices/eiger9m_csaxs.py
Normal file
427
csaxs_bec/devices/epics/devices/eiger9m_csaxs.py
Normal file
@@ -0,0 +1,427 @@
|
||||
import enum
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from bec_lib import messages, threadlocked
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import ADComponent as ADCpt
|
||||
from ophyd import Device, EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV
|
||||
from ophyd_devices.epics.devices.psi_detector_base import CustomDetectorMixin, PSIDetectorBase
|
||||
from std_daq_client import StdDaqClient
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class EigerError(Exception):
|
||||
"""Base class for exceptions in this module."""
|
||||
|
||||
|
||||
class EigerTimeoutError(EigerError):
|
||||
"""Raised when the Eiger does not respond in time."""
|
||||
|
||||
|
||||
class Eiger9MSetup(CustomDetectorMixin):
|
||||
"""Eiger setup class
|
||||
|
||||
Parent class: CustomDetectorMixin
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *args, parent: Device = None, **kwargs) -> None:
|
||||
super().__init__(*args, parent=parent, **kwargs)
|
||||
self.std_rest_server_url = (
|
||||
kwargs["file_writer_url"] if "file_writer_url" in kwargs else "http://xbl-daq-29:5000"
|
||||
)
|
||||
self.std_client = None
|
||||
self._lock = threading.RLock()
|
||||
|
||||
def initialize_default_parameter(self) -> None:
|
||||
"""Set default parameters for Eiger9M detector"""
|
||||
self.update_readout_time()
|
||||
|
||||
def update_readout_time(self) -> None:
|
||||
"""Set readout time for Eiger9M detector"""
|
||||
readout_time = (
|
||||
self.parent.scaninfo.readout_time
|
||||
if hasattr(self.parent.scaninfo, "readout_time")
|
||||
else self.parent.MIN_READOUT
|
||||
)
|
||||
self.parent.readout_time = max(readout_time, self.parent.MIN_READOUT)
|
||||
|
||||
def initialize_detector(self) -> None:
|
||||
"""Initialize detector"""
|
||||
# Stops the detector
|
||||
self.stop_detector()
|
||||
# Sets the trigger source to GATING
|
||||
self.parent.set_trigger(TriggerSource.GATING)
|
||||
|
||||
def initialize_detector_backend(self) -> None:
|
||||
"""Initialize detector backend"""
|
||||
|
||||
# Std client
|
||||
self.std_client = StdDaqClient(url_base=self.std_rest_server_url)
|
||||
|
||||
# Stop writer
|
||||
self.std_client.stop_writer()
|
||||
|
||||
# Change e-account
|
||||
eacc = self.parent.scaninfo.username
|
||||
self.update_std_cfg("writer_user_id", int(eacc.strip(" e")))
|
||||
|
||||
signal_conditions = [(lambda: self.std_client.get_status()["state"], "READY")]
|
||||
if not self.wait_for_signals(
|
||||
signal_conditions=signal_conditions, timeout=self.parent.timeout, all_signals=True
|
||||
):
|
||||
raise EigerTimeoutError(
|
||||
f"Std client not in READY state, returns: {self.std_client.get_status()}"
|
||||
)
|
||||
|
||||
def update_std_cfg(self, cfg_key: str, value: Any) -> None:
|
||||
"""
|
||||
Update std_daq config
|
||||
|
||||
Checks that the new value matches the type of the former entry.
|
||||
|
||||
Args:
|
||||
cfg_key (str) : config key of value to be updated
|
||||
value (Any) : value to be updated for the specified key
|
||||
|
||||
Raises:
|
||||
Raises EigerError if the key was not in the config before and if the new value does not match the type of the old value
|
||||
|
||||
"""
|
||||
|
||||
# Load config from client and check old value
|
||||
cfg = self.std_client.get_config()
|
||||
old_value = cfg.get(cfg_key)
|
||||
if old_value is None:
|
||||
raise EigerError(
|
||||
f"Tried to change entry for key {cfg_key} in std_config that does not exist"
|
||||
)
|
||||
if not isinstance(value, type(old_value)):
|
||||
raise EigerError(
|
||||
f"Type of new value {type(value)}:{value} does not match old value"
|
||||
f" {type(old_value)}:{old_value}"
|
||||
)
|
||||
|
||||
# Update config with new value and send back to client
|
||||
cfg.update({cfg_key: value})
|
||||
logger.debug(cfg)
|
||||
self.std_client.set_config(cfg)
|
||||
logger.debug(f"Updated std_daq config for key {cfg_key} from {old_value} to {value}")
|
||||
|
||||
def stop_detector(self) -> None:
|
||||
"""Stop the detector"""
|
||||
|
||||
# Stop detector
|
||||
self.parent.cam.acquire.put(0)
|
||||
|
||||
# Check if detector returned in idle state
|
||||
signal_conditions = [
|
||||
(
|
||||
lambda: self.parent.cam.detector_state.read()[self.parent.cam.detector_state.name][
|
||||
"value"
|
||||
],
|
||||
DetectorState.IDLE,
|
||||
)
|
||||
]
|
||||
|
||||
if not self.wait_for_signals(
|
||||
signal_conditions=signal_conditions,
|
||||
timeout=self.parent.timeout - self.parent.timeout // 2,
|
||||
check_stopped=True,
|
||||
all_signals=False,
|
||||
):
|
||||
# Retry stop detector and wait for remaining time
|
||||
self.parent.cam.acquire.put(0)
|
||||
if not self.wait_for_signals(
|
||||
signal_conditions=signal_conditions,
|
||||
timeout=self.parent.timeout - self.parent.timeout // 2,
|
||||
check_stopped=True,
|
||||
all_signals=False,
|
||||
):
|
||||
raise EigerTimeoutError(
|
||||
f"Failed to stop detector, detector state {signal_conditions[0][0]}"
|
||||
)
|
||||
|
||||
def stop_detector_backend(self) -> None:
|
||||
"""Close file writer"""
|
||||
self.std_client.stop_writer()
|
||||
|
||||
def prepare_detector(self) -> None:
|
||||
"""Prepare detector for scan"""
|
||||
self.set_detector_threshold()
|
||||
self.set_acquisition_params()
|
||||
self.parent.set_trigger(TriggerSource.GATING)
|
||||
|
||||
def set_detector_threshold(self) -> None:
|
||||
"""
|
||||
Set the detector threshold
|
||||
|
||||
The function sets the detector threshold automatically to 1/2 of the beam energy.
|
||||
"""
|
||||
|
||||
# get current beam energy from device manageer
|
||||
mokev = self.parent.device_manager.devices.mokev.obj.read()[
|
||||
self.parent.device_manager.devices.mokev.name
|
||||
]["value"]
|
||||
factor = 1
|
||||
|
||||
# Check if energies are eV or keV, assume keV as the default
|
||||
unit = getattr(self.parent.cam.threshold_energy, "units", None)
|
||||
if unit is not None and unit == "eV":
|
||||
factor = 1000
|
||||
|
||||
# set energy on detector
|
||||
setpoint = int(mokev * factor)
|
||||
energy = self.parent.cam.beam_energy.read()[self.parent.cam.beam_energy.name]["value"]
|
||||
if setpoint != energy:
|
||||
self.parent.cam.beam_energy.set(setpoint)
|
||||
|
||||
# set threshold on detector
|
||||
threshold = self.parent.cam.threshold_energy.read()[self.parent.cam.threshold_energy.name][
|
||||
"value"
|
||||
]
|
||||
if not np.isclose(setpoint / 2, threshold, rtol=0.05):
|
||||
self.parent.cam.threshold_energy.set(setpoint / 2)
|
||||
|
||||
def set_acquisition_params(self) -> None:
|
||||
"""Set acquisition parameters for the detector"""
|
||||
|
||||
# Set number of images and frames (frames is for internal burst of detector)
|
||||
self.parent.cam.num_images.put(
|
||||
int(self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger)
|
||||
)
|
||||
self.parent.cam.num_frames.put(1)
|
||||
|
||||
# Update the readout time of the detector
|
||||
self.update_readout_time()
|
||||
|
||||
def prepare_data_backend(self) -> None:
|
||||
"""Prepare the data backend for the scan"""
|
||||
self.parent.filepath = self.parent.filewriter.compile_full_filename(
|
||||
f"{self.parent.name}.h5"
|
||||
)
|
||||
self.filepath_exists(self.parent.filepath)
|
||||
self.stop_detector_backend()
|
||||
try:
|
||||
self.std_client.start_writer_async(
|
||||
{
|
||||
"output_file": self.parent.filepath,
|
||||
"n_images": int(
|
||||
self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger
|
||||
),
|
||||
}
|
||||
)
|
||||
except Exception as exc:
|
||||
time.sleep(5)
|
||||
if self.std_client.get_status()["state"] == "READY":
|
||||
raise EigerTimeoutError(f"Timeout of start_writer_async with {exc}") from exc
|
||||
|
||||
# Check status of std_daq
|
||||
signal_conditions = [
|
||||
(lambda: self.std_client.get_status()["acquisition"]["state"], "WAITING_IMAGES")
|
||||
]
|
||||
if not self.wait_for_signals(
|
||||
signal_conditions=signal_conditions,
|
||||
timeout=self.parent.timeout,
|
||||
check_stopped=False,
|
||||
all_signals=True,
|
||||
):
|
||||
raise EigerTimeoutError(
|
||||
"Timeout of 5s reached for std_daq start_writer_async with std_daq client status"
|
||||
f" {self.std_client.get_status()}"
|
||||
)
|
||||
|
||||
def filepath_exists(self, filepath: str) -> None:
|
||||
"""Check if filepath exists"""
|
||||
signal_conditions = [(lambda: os.path.exists(os.path.dirname(filepath)), True)]
|
||||
if not self.wait_for_signals(
|
||||
signal_conditions=signal_conditions,
|
||||
timeout=self.parent.timeout,
|
||||
check_stopped=False,
|
||||
all_signals=True,
|
||||
):
|
||||
raise EigerError(f"Timeout of 3s reached for filepath {filepath}")
|
||||
|
||||
def arm_acquisition(self) -> None:
|
||||
"""Arm Eiger detector for acquisition"""
|
||||
self.parent.cam.acquire.put(1)
|
||||
signal_conditions = [
|
||||
(
|
||||
lambda: self.parent.cam.detector_state.read()[self.parent.cam.detector_state.name][
|
||||
"value"
|
||||
],
|
||||
DetectorState.RUNNING,
|
||||
)
|
||||
]
|
||||
if not self.wait_for_signals(
|
||||
signal_conditions=signal_conditions,
|
||||
timeout=self.parent.timeout,
|
||||
check_stopped=True,
|
||||
all_signals=False,
|
||||
):
|
||||
raise EigerTimeoutError(
|
||||
f"Failed to arm the acquisition. Detector state {signal_conditions[0][0]}"
|
||||
)
|
||||
|
||||
def check_scan_id(self) -> None:
|
||||
"""Checks if scan_id has changed and stops the scan if it has"""
|
||||
old_scan_id = self.parent.scaninfo.scan_id
|
||||
self.parent.scaninfo.load_scan_metadata()
|
||||
if self.parent.scaninfo.scan_id != old_scan_id:
|
||||
self.parent.stopped = True
|
||||
|
||||
def publish_file_location(self, done: bool = False, successful: bool = None) -> None:
|
||||
"""
|
||||
Publish the filepath to REDIS.
|
||||
|
||||
We publish two events here:
|
||||
- file_event: event for the filewriter
|
||||
- public_file: event for any secondary service (e.g. radial integ code)
|
||||
|
||||
Args:
|
||||
done (bool): True if scan is finished
|
||||
successful (bool): True if scan was successful
|
||||
"""
|
||||
pipe = self.parent.connector.pipeline()
|
||||
if successful is None:
|
||||
msg = messages.FileMessage(file_path=self.parent.filepath, done=done)
|
||||
else:
|
||||
msg = messages.FileMessage(
|
||||
file_path=self.parent.filepath, done=done, successful=successful
|
||||
)
|
||||
self.parent.connector.set_and_publish(
|
||||
MessageEndpoints.public_file(self.parent.scaninfo.scan_id, self.parent.name),
|
||||
msg,
|
||||
pipe=pipe,
|
||||
)
|
||||
self.parent.connector.set_and_publish(
|
||||
MessageEndpoints.file_event(self.parent.name), msg, pipe=pipe
|
||||
)
|
||||
pipe.execute()
|
||||
|
||||
@threadlocked
|
||||
def finished(self):
|
||||
"""Check if acquisition is finished."""
|
||||
signal_conditions = [
|
||||
(
|
||||
lambda: self.parent.cam.acquire.read()[self.parent.cam.acquire.name]["value"],
|
||||
DetectorState.IDLE,
|
||||
),
|
||||
(lambda: self.std_client.get_status()["acquisition"]["state"], "FINISHED"),
|
||||
(
|
||||
lambda: self.std_client.get_status()["acquisition"]["stats"]["n_write_completed"],
|
||||
int(self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger),
|
||||
),
|
||||
]
|
||||
if not self.wait_for_signals(
|
||||
signal_conditions=signal_conditions,
|
||||
timeout=self.parent.timeout,
|
||||
check_stopped=True,
|
||||
all_signals=True,
|
||||
):
|
||||
raise EigerTimeoutError(
|
||||
f"Reached timeout with detector state {signal_conditions[0][0]}, std_daq state"
|
||||
f" {signal_conditions[1][0]} and received frames of {signal_conditions[2][0]} for"
|
||||
" the file writer"
|
||||
)
|
||||
self.stop_detector()
|
||||
self.stop_detector_backend()
|
||||
|
||||
|
||||
class SLSDetectorCam(Device):
|
||||
"""
|
||||
SLS Detector Camera - Eiger9M
|
||||
|
||||
Base class to map EPICS PVs to ophyd signals.
|
||||
"""
|
||||
|
||||
threshold_energy = ADCpt(EpicsSignalWithRBV, "ThresholdEnergy")
|
||||
beam_energy = ADCpt(EpicsSignalWithRBV, "BeamEnergy")
|
||||
bit_depth = ADCpt(EpicsSignalWithRBV, "BitDepth")
|
||||
num_images = ADCpt(EpicsSignalWithRBV, "NumCycles")
|
||||
num_frames = ADCpt(EpicsSignalWithRBV, "NumFrames")
|
||||
trigger_mode = ADCpt(EpicsSignalWithRBV, "TimingMode")
|
||||
trigger_software = ADCpt(EpicsSignal, "TriggerSoftware")
|
||||
acquire = ADCpt(EpicsSignal, "Acquire")
|
||||
detector_state = ADCpt(EpicsSignalRO, "DetectorState_RBV")
|
||||
|
||||
|
||||
class TriggerSource(enum.IntEnum):
|
||||
"""Trigger signals for Eiger9M detector"""
|
||||
|
||||
AUTO = 0
|
||||
TRIGGER = 1
|
||||
GATING = 2
|
||||
BURST_TRIGGER = 3
|
||||
|
||||
|
||||
class DetectorState(enum.IntEnum):
|
||||
"""Detector states for Eiger9M detector"""
|
||||
|
||||
IDLE = 0
|
||||
ERROR = 1
|
||||
WAITING = 2
|
||||
FINISHED = 3
|
||||
TRANSMITTING = 4
|
||||
RUNNING = 5
|
||||
STOPPED = 6
|
||||
STILL_WAITING = 7
|
||||
INITIALIZING = 8
|
||||
DISCONNECTED = 9
|
||||
ABORTED = 10
|
||||
|
||||
|
||||
class Eiger9McSAXS(PSIDetectorBase):
|
||||
"""
|
||||
Eiger9M detector for CSAXS
|
||||
|
||||
Parent class: PSIDetectorBase
|
||||
|
||||
class attributes:
|
||||
custom_prepare_cls (FalconSetup) : Custom detector setup class for cSAXS,
|
||||
inherits from CustomDetectorMixin
|
||||
PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector
|
||||
Various EpicsPVs for controlling the detector
|
||||
"""
|
||||
|
||||
# Specify which functions are revealed to the user in BEC client
|
||||
USER_ACCESS = ["describe"]
|
||||
|
||||
# specify Setup class
|
||||
custom_prepare_cls = Eiger9MSetup
|
||||
# specify minimum readout time for detector
|
||||
MIN_READOUT = 3e-3
|
||||
# specify class attributes
|
||||
cam = ADCpt(SLSDetectorCam, "cam1:")
|
||||
|
||||
def set_trigger(self, trigger_source: TriggerSource) -> None:
|
||||
"""Set trigger source for the detector.
|
||||
Check the TriggerSource enum for possible values
|
||||
|
||||
Args:
|
||||
trigger_source (TriggerSource): Trigger source for the detector
|
||||
|
||||
"""
|
||||
value = trigger_source
|
||||
self.cam.trigger_mode.put(value)
|
||||
|
||||
def stage(self) -> list[object]:
|
||||
"""
|
||||
Add functionality to stage, and arm the detector
|
||||
|
||||
Additional call to:
|
||||
- custom_prepare.arm_acquisition()
|
||||
"""
|
||||
rtr = super().stage()
|
||||
self.custom_prepare.arm_acquisition()
|
||||
return rtr
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
eiger = Eiger9McSAXS(name="eiger", prefix="X12SA-ES-EIGER9M:", sim_mode=True)
|
||||
356
csaxs_bec/devices/epics/devices/falcon_csaxs.py
Normal file
356
csaxs_bec/devices/epics/devices/falcon_csaxs.py
Normal file
@@ -0,0 +1,356 @@
|
||||
import enum
|
||||
import os
|
||||
|
||||
from bec_lib import messages
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV
|
||||
from ophyd.mca import EpicsMCARecord
|
||||
from ophyd_devices.epics.devices.psi_detector_base import CustomDetectorMixin, PSIDetectorBase
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class FalconError(Exception):
|
||||
"""Base class for exceptions in this module."""
|
||||
|
||||
|
||||
class FalconTimeoutError(FalconError):
|
||||
"""Raised when the Falcon does not respond in time."""
|
||||
|
||||
|
||||
class DetectorState(enum.IntEnum):
|
||||
"""Detector states for Falcon detector"""
|
||||
|
||||
DONE = 0
|
||||
ACQUIRING = 1
|
||||
|
||||
|
||||
class TriggerSource(enum.IntEnum):
|
||||
"""Trigger source for Falcon detector"""
|
||||
|
||||
USER = 0
|
||||
GATE = 1
|
||||
SYNC = 2
|
||||
|
||||
|
||||
class MappingSource(enum.IntEnum):
|
||||
"""Mapping source for Falcon detector"""
|
||||
|
||||
SPECTRUM = 0
|
||||
MAPPING = 1
|
||||
|
||||
|
||||
class EpicsDXPFalcon(Device):
|
||||
"""
|
||||
DXP parameters for Falcon detector
|
||||
|
||||
Base class to map EPICS PVs from DXP parameters to ophyd signals.
|
||||
"""
|
||||
|
||||
elapsed_live_time = Cpt(EpicsSignal, "ElapsedLiveTime")
|
||||
elapsed_real_time = Cpt(EpicsSignal, "ElapsedRealTime")
|
||||
elapsed_trigger_live_time = Cpt(EpicsSignal, "ElapsedTriggerLiveTime")
|
||||
|
||||
# Energy Filter PVs
|
||||
energy_threshold = Cpt(EpicsSignalWithRBV, "DetectionThreshold")
|
||||
min_pulse_separation = Cpt(EpicsSignalWithRBV, "MinPulsePairSeparation")
|
||||
detection_filter = Cpt(EpicsSignalWithRBV, "DetectionFilter", string=True)
|
||||
scale_factor = Cpt(EpicsSignalWithRBV, "ScaleFactor")
|
||||
risetime_optimisation = Cpt(EpicsSignalWithRBV, "RisetimeOptimization")
|
||||
|
||||
# Misc PVs
|
||||
detector_polarity = Cpt(EpicsSignalWithRBV, "DetectorPolarity")
|
||||
decay_time = Cpt(EpicsSignalWithRBV, "DecayTime")
|
||||
|
||||
current_pixel = Cpt(EpicsSignalRO, "CurrentPixel")
|
||||
|
||||
|
||||
class FalconHDF5Plugins(Device):
|
||||
"""
|
||||
HDF5 parameters for Falcon detector
|
||||
|
||||
Base class to map EPICS PVs from HDF5 Plugin to ophyd signals.
|
||||
"""
|
||||
|
||||
capture = Cpt(EpicsSignalWithRBV, "Capture")
|
||||
enable = Cpt(EpicsSignalWithRBV, "EnableCallbacks", string=True, kind="config")
|
||||
xml_file_name = Cpt(EpicsSignalWithRBV, "XMLFileName", string=True, kind="config")
|
||||
lazy_open = Cpt(EpicsSignalWithRBV, "LazyOpen", string=True, doc="0='No' 1='Yes'")
|
||||
temp_suffix = Cpt(EpicsSignalWithRBV, "TempSuffix", string=True)
|
||||
file_path = Cpt(EpicsSignalWithRBV, "FilePath", string=True, kind="config")
|
||||
file_name = Cpt(EpicsSignalWithRBV, "FileName", string=True, kind="config")
|
||||
file_template = Cpt(EpicsSignalWithRBV, "FileTemplate", string=True, kind="config")
|
||||
num_capture = Cpt(EpicsSignalWithRBV, "NumCapture", kind="config")
|
||||
file_write_mode = Cpt(EpicsSignalWithRBV, "FileWriteMode", kind="config")
|
||||
queue_size = Cpt(EpicsSignalWithRBV, "QueueSize", kind="config")
|
||||
array_counter = Cpt(EpicsSignalWithRBV, "ArrayCounter", kind="config")
|
||||
|
||||
|
||||
class FalconSetup(CustomDetectorMixin):
|
||||
"""
|
||||
Falcon setup class for cSAXS
|
||||
|
||||
Parent class: CustomDetectorMixin
|
||||
|
||||
"""
|
||||
|
||||
def initialize_default_parameter(self) -> None:
|
||||
"""
|
||||
Set default parameters for Falcon
|
||||
|
||||
This will set:
|
||||
- readout (float): readout time in seconds
|
||||
- value_pixel_per_buffer (int): number of spectra in buffer of Falcon Sitoro
|
||||
|
||||
"""
|
||||
self.parent.value_pixel_per_buffer = 20
|
||||
self.update_readout_time()
|
||||
|
||||
def update_readout_time(self) -> None:
|
||||
"""Set readout time for Eiger9M detector"""
|
||||
readout_time = (
|
||||
self.parent.scaninfo.readout_time
|
||||
if hasattr(self.parent.scaninfo, "readout_time")
|
||||
else self.parent.MIN_READOUT
|
||||
)
|
||||
self.parent.readout_time = max(readout_time, self.parent.MIN_READOUT)
|
||||
|
||||
def initialize_detector(self) -> None:
|
||||
"""Initialize Falcon detector"""
|
||||
self.stop_detector()
|
||||
self.stop_detector_backend()
|
||||
self.parent.set_trigger(
|
||||
mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0
|
||||
)
|
||||
# 1 Realtime
|
||||
self.parent.preset_mode.put(1)
|
||||
# 0 Normal, 1 Inverted
|
||||
self.parent.input_logic_polarity.put(0)
|
||||
# 0 Manual 1 Auto
|
||||
self.parent.auto_pixels_per_buffer.put(0)
|
||||
# Sets the number of pixels/spectra in the buffer
|
||||
self.parent.pixels_per_buffer.put(self.parent.value_pixel_per_buffer)
|
||||
|
||||
def stop_detector(self) -> None:
|
||||
"""Stops detector"""
|
||||
|
||||
self.parent.stop_all.put(1)
|
||||
self.parent.erase_all.put(1)
|
||||
|
||||
signal_conditions = [
|
||||
(lambda: self.parent.state.read()[self.parent.state.name]["value"], DetectorState.DONE)
|
||||
]
|
||||
|
||||
if not self.wait_for_signals(
|
||||
signal_conditions=signal_conditions,
|
||||
timeout=self.parent.timeout - self.parent.timeout // 2,
|
||||
all_signals=False,
|
||||
):
|
||||
# Retry stop detector and wait for remaining time
|
||||
raise FalconTimeoutError(
|
||||
f"Failed to stop detector, timeout with state {signal_conditions[0][0]}"
|
||||
)
|
||||
|
||||
def stop_detector_backend(self) -> None:
|
||||
"""Stop the detector backend"""
|
||||
self.parent.hdf5.capture.put(0)
|
||||
|
||||
def initialize_detector_backend(self) -> None:
|
||||
"""Initialize the detector backend for Falcon."""
|
||||
self.parent.hdf5.enable.put(1)
|
||||
# file location of h5 layout for cSAXS
|
||||
self.parent.hdf5.xml_file_name.put("layout.xml")
|
||||
# TODO Check if lazy open is needed and wanted!
|
||||
self.parent.hdf5.lazy_open.put(1)
|
||||
self.parent.hdf5.temp_suffix.put("")
|
||||
# size of queue for number of spectra allowed in the buffer, if too small at high throughput, data is lost
|
||||
self.parent.hdf5.queue_size.put(2000)
|
||||
# Segmentation into Spectra within EPICS, 1 is activate, 0 is deactivate
|
||||
self.parent.nd_array_mode.put(1)
|
||||
|
||||
def prepare_detector(self) -> None:
|
||||
"""Prepare detector for acquisition"""
|
||||
self.parent.set_trigger(
|
||||
mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0
|
||||
)
|
||||
self.parent.preset_real.put(self.parent.scaninfo.exp_time)
|
||||
self.parent.pixels_per_run.put(
|
||||
int(self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger)
|
||||
)
|
||||
|
||||
def prepare_data_backend(self) -> None:
|
||||
"""Prepare data backend for acquisition"""
|
||||
self.parent.filepath = self.parent.filewriter.compile_full_filename(
|
||||
f"{self.parent.name}.h5"
|
||||
)
|
||||
file_path, file_name = os.path.split(self.parent.filepath)
|
||||
self.parent.hdf5.file_path.put(file_path)
|
||||
self.parent.hdf5.file_name.put(file_name)
|
||||
self.parent.hdf5.file_template.put("%s%s")
|
||||
self.parent.hdf5.num_capture.put(
|
||||
int(self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger)
|
||||
)
|
||||
self.parent.hdf5.file_write_mode.put(2)
|
||||
# Reset spectrum counter in filewriter, used for indexing & identifying missing triggers
|
||||
self.parent.hdf5.array_counter.put(0)
|
||||
# Start file writing
|
||||
self.parent.hdf5.capture.put(1)
|
||||
|
||||
def arm_acquisition(self) -> None:
|
||||
"""Arm detector for acquisition"""
|
||||
self.parent.start_all.put(1)
|
||||
signal_conditions = [
|
||||
(
|
||||
lambda: self.parent.state.read()[self.parent.state.name]["value"],
|
||||
DetectorState.ACQUIRING,
|
||||
)
|
||||
]
|
||||
if not self.wait_for_signals(
|
||||
signal_conditions=signal_conditions,
|
||||
timeout=self.parent.timeout,
|
||||
check_stopped=True,
|
||||
all_signals=False,
|
||||
):
|
||||
raise FalconTimeoutError(
|
||||
f"Failed to arm the acquisition. Detector state {signal_conditions[0][0]}"
|
||||
)
|
||||
|
||||
def check_scan_id(self) -> None:
|
||||
"""Checks if scan_id has changed and stops the scan if it has"""
|
||||
old_scan_id = self.parent.scaninfo.scan_id
|
||||
self.parent.scaninfo.load_scan_metadata()
|
||||
if self.parent.scaninfo.scan_id != old_scan_id:
|
||||
self.parent.stopped = True
|
||||
|
||||
def publish_file_location(self, done: bool = False, successful: bool = None) -> None:
|
||||
"""
|
||||
Publish the filepath to REDIS.
|
||||
|
||||
We publish two events here:
|
||||
- file_event: event for the filewriter
|
||||
- public_file: event for any secondary service (e.g. radial integ code)
|
||||
|
||||
Args:
|
||||
done (bool): True if scan is finished
|
||||
successful (bool): True if scan was successful
|
||||
"""
|
||||
pipe = self.parent.connector.pipeline()
|
||||
if successful is None:
|
||||
msg = messages.FileMessage(file_path=self.parent.filepath, done=done)
|
||||
else:
|
||||
msg = messages.FileMessage(
|
||||
file_path=self.parent.filepath, done=done, successful=successful
|
||||
)
|
||||
self.parent.connector.set_and_publish(
|
||||
MessageEndpoints.public_file(self.parent.scaninfo.scan_id, self.parent.name),
|
||||
msg,
|
||||
pipe=pipe,
|
||||
)
|
||||
self.parent.connector.set_and_publish(
|
||||
MessageEndpoints.file_event(self.parent.name), msg, pipe=pipe
|
||||
)
|
||||
pipe.execute()
|
||||
|
||||
def finished(self) -> None:
|
||||
"""Check if scan finished succesfully"""
|
||||
total_frames = int(
|
||||
self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger
|
||||
)
|
||||
signal_conditions = [
|
||||
(self.parent.dxp.current_pixel.get, total_frames),
|
||||
(self.parent.hdf5.array_counter.get, total_frames),
|
||||
]
|
||||
if not self.wait_for_signals(
|
||||
signal_conditions=signal_conditions,
|
||||
timeout=self.parent.timeout,
|
||||
check_stopped=True,
|
||||
all_signals=True,
|
||||
):
|
||||
logger.debug(
|
||||
f"Falcon missed a trigger: received trigger {self.parent.dxp.current_pixel.get()},"
|
||||
f" send data {self.parent.hdf5.array_counter.get()} from total_frames"
|
||||
f" {total_frames}"
|
||||
)
|
||||
self.stop_detector()
|
||||
self.stop_detector_backend()
|
||||
|
||||
|
||||
class FalconcSAXS(PSIDetectorBase):
|
||||
"""
|
||||
Falcon Sitoro detector for CSAXS
|
||||
|
||||
Parent class: PSIDetectorBase
|
||||
|
||||
class attributes:
|
||||
custom_prepare_cls (FalconSetup) : Custom detector setup class for cSAXS,
|
||||
inherits from CustomDetectorMixin
|
||||
PSIDetectorBase.set_min_readout (float) : Minimum readout time for the detector
|
||||
dxp (EpicsDXPFalcon) : DXP parameters for Falcon detector
|
||||
mca (EpicsMCARecord) : MCA parameters for Falcon detector
|
||||
hdf5 (FalconHDF5Plugins) : HDF5 parameters for Falcon detector
|
||||
MIN_READOUT (float) : Minimum readout time for the detector
|
||||
"""
|
||||
|
||||
# Specify which functions are revealed to the user in BEC client
|
||||
USER_ACCESS = ["describe"]
|
||||
|
||||
# specify Setup class
|
||||
custom_prepare_cls = FalconSetup
|
||||
# specify minimum readout time for detector
|
||||
MIN_READOUT = 3e-3
|
||||
|
||||
# specify class attributes
|
||||
dxp = Cpt(EpicsDXPFalcon, "dxp1:")
|
||||
mca = Cpt(EpicsMCARecord, "mca1")
|
||||
hdf5 = Cpt(FalconHDF5Plugins, "HDF1:")
|
||||
|
||||
stop_all = Cpt(EpicsSignal, "StopAll")
|
||||
erase_all = Cpt(EpicsSignal, "EraseAll")
|
||||
start_all = Cpt(EpicsSignal, "StartAll")
|
||||
state = Cpt(EpicsSignal, "Acquiring")
|
||||
preset_mode = Cpt(EpicsSignal, "PresetMode") # 0 No preset 1 Real time 2 Events 3 Triggers
|
||||
preset_real = Cpt(EpicsSignal, "PresetReal")
|
||||
preset_events = Cpt(EpicsSignal, "PresetEvents")
|
||||
preset_triggers = Cpt(EpicsSignal, "PresetTriggers")
|
||||
triggers = Cpt(EpicsSignalRO, "MaxTriggers", lazy=True)
|
||||
events = Cpt(EpicsSignalRO, "MaxEvents", lazy=True)
|
||||
input_count_rate = Cpt(EpicsSignalRO, "MaxInputCountRate", lazy=True)
|
||||
output_count_rate = Cpt(EpicsSignalRO, "MaxOutputCountRate", lazy=True)
|
||||
collect_mode = Cpt(EpicsSignal, "CollectMode") # 0 MCA spectra, 1 MCA mapping
|
||||
pixel_advance_mode = Cpt(EpicsSignal, "PixelAdvanceMode")
|
||||
ignore_gate = Cpt(EpicsSignal, "IgnoreGate")
|
||||
input_logic_polarity = Cpt(EpicsSignal, "InputLogicPolarity")
|
||||
auto_pixels_per_buffer = Cpt(EpicsSignal, "AutoPixelsPerBuffer")
|
||||
pixels_per_buffer = Cpt(EpicsSignal, "PixelsPerBuffer")
|
||||
pixels_per_run = Cpt(EpicsSignal, "PixelsPerRun")
|
||||
nd_array_mode = Cpt(EpicsSignal, "NDArrayMode")
|
||||
|
||||
def set_trigger(
|
||||
self, mapping_mode: MappingSource, trigger_source: TriggerSource, ignore_gate: int = 0
|
||||
) -> None:
|
||||
"""
|
||||
Set triggering mode for detector
|
||||
|
||||
Args:
|
||||
mapping_mode (MappingSource): Mapping mode for the detector
|
||||
trigger_source (TriggerSource): Trigger source for the detector, pixel_advance_signal
|
||||
ignore_gate (int): Ignore gate from TTL signal; defaults to 0
|
||||
|
||||
"""
|
||||
mapping = int(mapping_mode)
|
||||
trigger = trigger_source
|
||||
self.collect_mode.put(mapping)
|
||||
self.pixel_advance_mode.put(trigger)
|
||||
self.ignore_gate.put(ignore_gate)
|
||||
|
||||
def stage(self) -> list[object]:
|
||||
"""Stage"""
|
||||
rtr = super().stage()
|
||||
self.custom_prepare.arm_acquisition()
|
||||
return rtr
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
falcon = FalconcSAXS(name="falcon", prefix="X12SA-SITORO:", sim_mode=True)
|
||||
@@ -24,25 +24,24 @@ class FlomniSampleStorage(Device):
|
||||
SUB_VALUE = "value"
|
||||
_default_sub = SUB_VALUE
|
||||
sample_placed = {
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_flomni{i}:GET", {"auto_monitor": True}) for i in range(21)
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_flomni{i}:GET", {}) for i in range(21)
|
||||
}
|
||||
sample_placed = Dcpt(sample_placed)
|
||||
|
||||
sample_names = {
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_flomni{i}:GET.DESC", {"string": True, "auto_monitor": True})
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_flomni{i}:GET.DESC", {"string": True})
|
||||
for i in range(21)
|
||||
}
|
||||
sample_names = Dcpt(sample_names)
|
||||
|
||||
sample_in_gripper = Cpt(
|
||||
EpicsSignal, name="sample_in_gripper", read_pv="XOMNY-SAMPLE_DB_flomni100:GET", auto_monitor=True
|
||||
EpicsSignal, name="sample_in_gripper", read_pv="XOMNY-SAMPLE_DB_flomni100:GET"
|
||||
)
|
||||
sample_in_gripper_name = Cpt(
|
||||
EpicsSignal,
|
||||
name="sample_in_gripper_name",
|
||||
read_pv="XOMNY-SAMPLE_DB_flomni100:GET.DESC",
|
||||
string=True,
|
||||
auto_monitor=True
|
||||
)
|
||||
|
||||
def __init__(self, prefix="", *, name, **kwargs):
|
||||
310
csaxs_bec/devices/epics/devices/mcs_csaxs.py
Normal file
310
csaxs_bec/devices/epics/devices/mcs_csaxs.py
Normal file
@@ -0,0 +1,310 @@
|
||||
import enum
|
||||
import threading
|
||||
from collections import defaultdict
|
||||
|
||||
import numpy as np
|
||||
from bec_lib import MessageEndpoints, bec_logger, messages, threadlocked
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, EpicsSignal, EpicsSignalRO
|
||||
from ophyd_devices.epics.devices.psi_detector_base import CustomDetectorMixin, PSIDetectorBase
|
||||
from ophyd_devices.utils import bec_utils
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class MCSError(Exception):
|
||||
"""Base class for exceptions in this module."""
|
||||
|
||||
|
||||
class MCSTimeoutError(MCSError):
|
||||
"""Raise when MCS card runs into a timeout"""
|
||||
|
||||
|
||||
class TriggerSource(int, enum.Enum):
|
||||
"""Trigger source for mcs card - see manual for more information"""
|
||||
|
||||
MODE0 = 0
|
||||
MODE1 = 1
|
||||
MODE2 = 2
|
||||
MODE3 = 3
|
||||
MODE4 = 4
|
||||
MODE5 = 5
|
||||
MODE6 = 6
|
||||
|
||||
|
||||
class ChannelAdvance(int, enum.Enum):
|
||||
"""Channel advance pixel mode for mcs card - see manual for more information"""
|
||||
|
||||
INTERNAL = 0
|
||||
EXTERNAL = 1
|
||||
|
||||
|
||||
class ReadoutMode(int, enum.Enum):
|
||||
"""Readout mode for mcs card - see manual for more information"""
|
||||
|
||||
PASSIVE = 0
|
||||
EVENT = 1
|
||||
IO_INTR = 2
|
||||
FREQ_0_1HZ = 3
|
||||
FREQ_0_2HZ = 4
|
||||
FREQ_0_5HZ = 5
|
||||
FREQ_1HZ = 6
|
||||
FREQ_2HZ = 7
|
||||
FREQ_5HZ = 8
|
||||
FREQ_10HZ = 9
|
||||
FREQ_100HZ = 10
|
||||
|
||||
|
||||
class MCSSetup(CustomDetectorMixin):
|
||||
"""Setup mixin class for the MCS card"""
|
||||
|
||||
def __init__(self, *args, parent: Device = None, **kwargs) -> None:
|
||||
super().__init__(*args, parent=parent, **kwargs)
|
||||
self._lock = threading.RLock()
|
||||
self._stream_ttl = 1800
|
||||
self.acquisition_done = False
|
||||
self.counter = 0
|
||||
self.n_points = 0
|
||||
self.mca_names = [
|
||||
signal for signal in self.parent.component_names if signal.startswith("mca")
|
||||
]
|
||||
self.mca_data = defaultdict(lambda: [])
|
||||
|
||||
def initialize_detector(self) -> None:
|
||||
"""Initialize detector"""
|
||||
# External trigger for pixel advance
|
||||
self.parent.channel_advance.set(ChannelAdvance.EXTERNAL)
|
||||
# Use internal clock for channel 1
|
||||
self.parent.channel1_source.set(ChannelAdvance.INTERNAL)
|
||||
self.parent.user_led.set(0)
|
||||
# Set number of channels to 5
|
||||
self.parent.mux_output.set(5)
|
||||
# Trigger Mode used for cSAXS
|
||||
self.parent.set_trigger(TriggerSource.MODE3)
|
||||
# specify polarity of trigger signals
|
||||
self.parent.input_polarity.set(0)
|
||||
self.parent.output_polarity.set(1)
|
||||
# do not start counting on start
|
||||
self.parent.count_on_start.set(0)
|
||||
self.stop_detector()
|
||||
|
||||
def initialize_detector_backend(self) -> None:
|
||||
"""Initialize detector backend"""
|
||||
for mca in self.mca_names:
|
||||
signal = getattr(self.parent, mca)
|
||||
signal.subscribe(self._on_mca_data, run=False)
|
||||
self.parent.current_channel.subscribe(self._progress_update, run=False)
|
||||
|
||||
def _progress_update(self, value, **kwargs) -> None:
|
||||
"""Progress update on the scan"""
|
||||
num_lines = self.parent.num_lines.get()
|
||||
max_value = self.parent.scaninfo.num_points
|
||||
# self.counter seems to be a deprecated variable from a former implementation of the mcs card
|
||||
# pylint: disable=protected-access
|
||||
self.parent._run_subs(
|
||||
sub_type=self.parent.SUB_PROGRESS,
|
||||
value=self.counter * int(self.parent.scaninfo.num_points / num_lines) + value,
|
||||
max_value=max_value,
|
||||
# TODO check if that is correct with
|
||||
done=bool(max_value == value), # == self.counter),
|
||||
)
|
||||
|
||||
@threadlocked
|
||||
def _on_mca_data(self, *args, obj=None, value=None, **kwargs) -> None:
|
||||
"""Callback function for scan progress"""
|
||||
if not isinstance(value, (list, np.ndarray)):
|
||||
return
|
||||
self.mca_data[obj.attr_name] = value
|
||||
if len(self.mca_names) != len(self.mca_data):
|
||||
return
|
||||
self.acquisition_done = True
|
||||
self._send_data_to_bec()
|
||||
self.mca_data = defaultdict(lambda: [])
|
||||
|
||||
def _send_data_to_bec(self) -> None:
|
||||
"""Sends bundled data to BEC"""
|
||||
if self.parent.scaninfo.scan_msg is None:
|
||||
return
|
||||
metadata = self.parent.scaninfo.scan_msg.metadata
|
||||
metadata.update({"async_update": "append", "num_lines": self.parent.num_lines.get()})
|
||||
msg = messages.DeviceMessage(
|
||||
signals=dict(self.mca_data), metadata=self.parent.scaninfo.scan_msg.metadata
|
||||
)
|
||||
self.parent.connector.xadd(
|
||||
topic=MessageEndpoints.device_async_readback(
|
||||
scan_id=self.parent.scaninfo.scan_id, device=self.parent.name
|
||||
),
|
||||
msg={"data": msg},
|
||||
expire=self._stream_ttl,
|
||||
)
|
||||
|
||||
def prepare_detector(self) -> None:
|
||||
"""Prepare detector for scan"""
|
||||
self.set_acquisition_params()
|
||||
self.parent.set_trigger(TriggerSource.MODE3)
|
||||
|
||||
def set_acquisition_params(self) -> None:
|
||||
"""Set acquisition parameters for scan"""
|
||||
if self.parent.scaninfo.scan_type == "step":
|
||||
self.n_points = int(self.parent.scaninfo.frames_per_trigger) * int(
|
||||
self.parent.scaninfo.num_points
|
||||
)
|
||||
elif self.parent.scaninfo.scan_type == "fly":
|
||||
self.n_points = int(self.parent.scaninfo.num_points) # / int(self.num_lines.get()))
|
||||
else:
|
||||
raise MCSError(f"Scantype {self.parent.scaninfo} not implemented for MCS card")
|
||||
if self.n_points > 10000:
|
||||
raise MCSError(
|
||||
f"Requested number of points N={self.n_points} exceeds hardware limit of mcs card"
|
||||
" 10000 (N-1)"
|
||||
)
|
||||
self.parent.num_use_all.set(self.n_points)
|
||||
self.parent.preset_real.set(0)
|
||||
|
||||
def prepare_detector_backend(self) -> None:
|
||||
"""Prepare detector backend for scan"""
|
||||
self.parent.erase_all.set(1)
|
||||
self.parent.read_mode.set(ReadoutMode.EVENT)
|
||||
|
||||
def arm_acquisition(self) -> None:
|
||||
"""Arm detector for acquisition"""
|
||||
self.counter = 0
|
||||
self.parent.erase_start.set(1)
|
||||
|
||||
def finished(self) -> None:
|
||||
"""Check if acquisition is finished, if not successful, rais MCSTimeoutError"""
|
||||
signal_conditions = [
|
||||
(lambda: self.acquisition_done, True),
|
||||
(self.parent.acquiring.get, 0), # Considering making a enum.Int class for this state
|
||||
]
|
||||
if not self.wait_for_signals(
|
||||
signal_conditions=signal_conditions,
|
||||
timeout=self.parent.timeout,
|
||||
check_stopped=True,
|
||||
all_signals=True,
|
||||
):
|
||||
total_frames = self.counter * int(
|
||||
self.parent.scaninfo.num_points / self.parent.num_lines.get()
|
||||
) + max(self.parent.current_channel.get(), 0)
|
||||
raise MCSTimeoutError(
|
||||
f"Reached timeout with mcs in state {self.parent.acquiring.get()} and"
|
||||
f" {total_frames} frames arriving at the mcs card"
|
||||
)
|
||||
|
||||
def stop_detector(self) -> None:
|
||||
"""Stop detector"""
|
||||
self.parent.stop_all.set(1)
|
||||
|
||||
return super().stop_detector()
|
||||
|
||||
def stop_detector_backend(self) -> None:
|
||||
"""Stop acquisition of data"""
|
||||
self.acquisition_done = True
|
||||
|
||||
|
||||
class SIS38XX(Device):
|
||||
"""SIS38XX card for access to EPICs PVs at cSAXS beamline"""
|
||||
|
||||
|
||||
class MCScSAXS(PSIDetectorBase):
|
||||
"""MCS card for cSAXS for implementation at cSAXS beamline"""
|
||||
|
||||
USER_ACCESS = ["describe", "_init_mcs"]
|
||||
SUB_PROGRESS = "progress"
|
||||
SUB_VALUE = "value"
|
||||
_default_sub = SUB_VALUE
|
||||
|
||||
# specify Setup class
|
||||
custom_prepare_cls = MCSSetup
|
||||
# specify minimum readout time for detector
|
||||
MIN_READOUT = 0
|
||||
|
||||
# PV access to SISS38XX card
|
||||
# Acquisition
|
||||
erase_all = Cpt(EpicsSignal, "EraseAll")
|
||||
erase_start = Cpt(EpicsSignal, "EraseStart") # ,trigger_value=1
|
||||
start_all = Cpt(EpicsSignal, "StartAll")
|
||||
stop_all = Cpt(EpicsSignal, "StopAll")
|
||||
acquiring = Cpt(EpicsSignal, "Acquiring")
|
||||
preset_real = Cpt(EpicsSignal, "PresetReal")
|
||||
elapsed_real = Cpt(EpicsSignal, "ElapsedReal")
|
||||
read_mode = Cpt(EpicsSignal, "ReadAll.SCAN")
|
||||
read_all = Cpt(EpicsSignal, "DoReadAll.VAL") # ,trigger_value=1
|
||||
num_use_all = Cpt(EpicsSignal, "NuseAll")
|
||||
current_channel = Cpt(EpicsSignal, "CurrentChannel")
|
||||
dwell = Cpt(EpicsSignal, "Dwell")
|
||||
channel_advance = Cpt(EpicsSignal, "ChannelAdvance")
|
||||
count_on_start = Cpt(EpicsSignal, "CountOnStart")
|
||||
software_channel_advance = Cpt(EpicsSignal, "SoftwareChannelAdvance")
|
||||
channel1_source = Cpt(EpicsSignal, "Channel1Source")
|
||||
prescale = Cpt(EpicsSignal, "Prescale")
|
||||
enable_client_wait = Cpt(EpicsSignal, "EnableClientWait")
|
||||
client_wait = Cpt(EpicsSignal, "ClientWait")
|
||||
acquire_mode = Cpt(EpicsSignal, "AcquireMode")
|
||||
mux_output = Cpt(EpicsSignal, "MUXOutput")
|
||||
user_led = Cpt(EpicsSignal, "UserLED")
|
||||
input_mode = Cpt(EpicsSignal, "InputMode")
|
||||
input_polarity = Cpt(EpicsSignal, "InputPolarity")
|
||||
output_mode = Cpt(EpicsSignal, "OutputMode")
|
||||
output_polarity = Cpt(EpicsSignal, "OutputPolarity")
|
||||
model = Cpt(EpicsSignalRO, "Model", string=True)
|
||||
firmware = Cpt(EpicsSignalRO, "Firmware")
|
||||
max_channels = Cpt(EpicsSignalRO, "MaxChannels")
|
||||
|
||||
# PV access to MCA signals
|
||||
mca1 = Cpt(EpicsSignalRO, "mca1.VAL", auto_monitor=True)
|
||||
mca3 = Cpt(EpicsSignalRO, "mca3.VAL", auto_monitor=True)
|
||||
mca4 = Cpt(EpicsSignalRO, "mca4.VAL", auto_monitor=True)
|
||||
current_channel = Cpt(EpicsSignalRO, "CurrentChannel", auto_monitor=True)
|
||||
|
||||
# Custom signal readout from device config
|
||||
num_lines = Cpt(
|
||||
bec_utils.ConfigSignal, name="num_lines", kind="config", config_storage_name="mcs_config"
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
prefix="",
|
||||
*,
|
||||
name,
|
||||
kind=None,
|
||||
read_attrs=None,
|
||||
configuration_attrs=None,
|
||||
parent=None,
|
||||
device_manager=None,
|
||||
sim_mode=False,
|
||||
mcs_config=None,
|
||||
**kwargs,
|
||||
):
|
||||
self.mcs_config = {f"{name}_num_lines": 1}
|
||||
if mcs_config is not None:
|
||||
# pylint: disable=expression-not-assigned
|
||||
[self.mcs_config.update({f"{name}_{key}": value}) for key, value in mcs_config.items()]
|
||||
|
||||
super().__init__(
|
||||
prefix=prefix,
|
||||
name=name,
|
||||
kind=kind,
|
||||
read_attrs=read_attrs,
|
||||
configuration_attrs=configuration_attrs,
|
||||
parent=parent,
|
||||
device_manager=device_manager,
|
||||
sim_mode=sim_mode,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def set_trigger(self, trigger_source: TriggerSource) -> None:
|
||||
"""Set trigger mode from TriggerSource"""
|
||||
value = int(trigger_source)
|
||||
self.input_mode.set(value)
|
||||
|
||||
def stage(self) -> list[object]:
|
||||
"""stage the detector for upcoming acquisition"""
|
||||
rtr = super().stage()
|
||||
self.custom_prepare.arm_acquisition()
|
||||
return rtr
|
||||
|
||||
|
||||
# Automatically connect to test environmenr if directly invoked
|
||||
if __name__ == "__main__":
|
||||
mcs = MCScSAXS(name="mcs", prefix="X12SA-MCS:", sim_mode=True)
|
||||
@@ -22,101 +22,96 @@ class OMNYSampleStorage(Device):
|
||||
"set_sample_in_samplestage",
|
||||
"unset_sample_in_samplestage",
|
||||
"get_sample_name_in_samplestage",
|
||||
"get_sample_name_in_gripper",
|
||||
"get_sample_name",
|
||||
"is_sample_in_samplestage",
|
||||
"set_shuttle_slot",
|
||||
"unset_shuttle_slot",
|
||||
"get_shuttle_name_slot",
|
||||
"is_shuttle_slot_used",
|
||||
"search_shuttle_in_slot",
|
||||
"show_all",
|
||||
"help",
|
||||
]
|
||||
SUB_VALUE = "value"
|
||||
_default_sub = SUB_VALUE
|
||||
|
||||
sample_shuttle_A_placed = {
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_A:{i}", {"auto_monitor": True}) for i in range(1, 7)
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_A:{i}", {}) for i in range(1, 7)
|
||||
}
|
||||
sample_shuttle_A_placed = Dcpt(sample_shuttle_A_placed)
|
||||
|
||||
sample_shuttle_B_placed = {
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_B:{i}", {"auto_monitor": True}) for i in range(1, 7)
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_B:{i}", {}) for i in range(1, 7)
|
||||
}
|
||||
sample_shuttle_B_placed = Dcpt(sample_shuttle_B_placed)
|
||||
|
||||
sample_shuttle_C_placed = {
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_C:{i}", {"auto_monitor": True}) for i in range(1, 7)
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_C:{i}", {}) for i in range(1, 7)
|
||||
}
|
||||
sample_shuttle_C_placed = Dcpt(sample_shuttle_C_placed)
|
||||
|
||||
sample_shuttle_C_placed = {
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_C:{i}", {"auto_monitor": True}) for i in range(1, 7)
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_C:{i}", {}) for i in range(1, 7)
|
||||
}
|
||||
sample_shuttle_C_placed = Dcpt(sample_shuttle_C_placed)
|
||||
|
||||
parking_placed = {
|
||||
f"parking{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_parking:{i}", {"auto_monitor": True}) for i in range(1, 7)
|
||||
f"parking{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_parking:{i}", {}) for i in range(1, 7)
|
||||
}
|
||||
parking_placed = Dcpt(parking_placed)
|
||||
|
||||
sample_placed = {
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_omny:{i}", {"auto_monitor": True})
|
||||
f"parking{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_omny:{i}", {})
|
||||
for i in [10, 11, 12, 13, 14, 32, 33, 34, 100, 101]
|
||||
}
|
||||
sample_placed = Dcpt(sample_placed)
|
||||
|
||||
sample_shuttle_A_names = {
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_A:{i}.DESC", {"string": True, "auto_monitor": True})
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_A:{i}.DESC", {"string": True})
|
||||
for i in range(1, 7)
|
||||
}
|
||||
sample_shuttle_A_names = Dcpt(sample_shuttle_A_names)
|
||||
|
||||
sample_shuttle_B_names = {
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_B:{i}.DESC", {"string": True, "auto_monitor": True})
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_B:{i}.DESC", {"string": True})
|
||||
for i in range(1, 7)
|
||||
}
|
||||
sample_shuttle_B_names = Dcpt(sample_shuttle_B_names)
|
||||
|
||||
sample_shuttle_C_names = {
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_C:{i}.DESC", {"string": True, "auto_monitor": True})
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_shuttle_C:{i}.DESC", {"string": True})
|
||||
for i in range(1, 7)
|
||||
}
|
||||
sample_shuttle_C_names = Dcpt(sample_shuttle_C_names)
|
||||
|
||||
parking_names = {
|
||||
f"parking{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_parking:{i}.DESC", {"string": True, "auto_monitor": True})
|
||||
f"parking{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_parking:{i}.DESC", {"string": True})
|
||||
for i in range(1, 7)
|
||||
}
|
||||
parking_names = Dcpt(parking_names)
|
||||
|
||||
sample_names = {
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_omny:{i}.DESC", {"string": True, "auto_monitor": True})
|
||||
f"sample{i}": (EpicsSignal, f"XOMNY-SAMPLE_DB_omny:{i}.DESC", {"string": True})
|
||||
for i in [10, 11, 12, 13, 14, 32, 33, 34, 100, 101]
|
||||
}
|
||||
sample_names = Dcpt(sample_names)
|
||||
|
||||
sample_in_gripper = Cpt(
|
||||
EpicsSignal, name="sample_in_gripper", read_pv="XOMNY-SAMPLE_DB_omny:110.VAL", auto_monitor=True
|
||||
EpicsSignal, name="sample_in_gripper", read_pv="XOMNY-SAMPLE_DB_omny:110.VAL"
|
||||
)
|
||||
sample_in_gripper_name = Cpt(
|
||||
EpicsSignal,
|
||||
name="sample_in_gripper_name",
|
||||
read_pv="XOMNY-SAMPLE_DB_omny:110.DESC",
|
||||
string=True,
|
||||
auto_monitor=True
|
||||
)
|
||||
|
||||
sample_in_samplestage = Cpt(
|
||||
EpicsSignal, name="sample_in_samplestage", read_pv="XOMNY-SAMPLE_DB_omny:0.VAL", auto_monitor=True
|
||||
EpicsSignal, name="sample_in_samplestage", read_pv="XOMNY-SAMPLE_DB_omny:0.VAL"
|
||||
)
|
||||
sample_in_samplestage_name = Cpt(
|
||||
EpicsSignal,
|
||||
name="sample_in_samplestage_name",
|
||||
read_pv="XOMNY-SAMPLE_DB_omny:0.DESC",
|
||||
string=True,
|
||||
auto_monitor=True
|
||||
)
|
||||
|
||||
def __init__(self, prefix="", *, name, **kwargs):
|
||||
@@ -141,28 +136,20 @@ class OMNYSampleStorage(Device):
|
||||
elif container == "C":
|
||||
getattr(self.sample_shuttle_C_placed, f"sample{slot_nr}").set(1)
|
||||
getattr(self.sample_shuttle_C_names, f"sample{slot_nr}").set(name)
|
||||
elif container == "O":
|
||||
getattr(self.sample_placed, f"sample{slot_nr}").set(1)
|
||||
getattr(self.sample_names, f"sample{slot_nr}").set(name)
|
||||
|
||||
|
||||
def unset_sample_slot(self, container: str, slot_nr: int) -> bool:
|
||||
def unset_sample_slot(self, shuttle: str, slot_nr: int) -> bool:
|
||||
if slot_nr > 20:
|
||||
raise OMNYSampleStorageError(f"Invalid slot number {slot_nr}.")
|
||||
|
||||
if container == "A":
|
||||
if shuttle == "A":
|
||||
getattr(self.sample_shuttle_A_placed, f"sample{slot_nr}").set(0)
|
||||
getattr(self.sample_shuttle_A_names, f"sample{slot_nr}").set("-")
|
||||
elif container == "B":
|
||||
if shuttle == "B":
|
||||
getattr(self.sample_shuttle_B_placed, f"sample{slot_nr}").set(0)
|
||||
getattr(self.sample_shuttle_B_names, f"sample{slot_nr}").set("-")
|
||||
elif container == "C":
|
||||
if shuttle == "C":
|
||||
getattr(self.sample_shuttle_C_placed, f"sample{slot_nr}").set(0)
|
||||
getattr(self.sample_shuttle_C_names, f"sample{slot_nr}").set("-")
|
||||
elif container == "O":
|
||||
getattr(self.sample_placed, f"sample{slot_nr}").set(0)
|
||||
getattr(self.sample_names, f"sample{slot_nr}").set("-")
|
||||
|
||||
|
||||
def set_shuttle_slot(self, container: str, slot_nr: int) -> bool:
|
||||
if slot_nr > 6:
|
||||
@@ -240,25 +227,11 @@ class OMNYSampleStorage(Device):
|
||||
val = self.sample_in_samplestage_name.get()
|
||||
return str(val)
|
||||
|
||||
def search_shuttle_in_slot(self, shuttle: str) -> int:
|
||||
returnvalue = 0
|
||||
for i in range(1, 7):
|
||||
if self.get_shuttle_name_slot(i) == shuttle:
|
||||
returnvalue = i
|
||||
return returnvalue
|
||||
|
||||
def show_all(self):
|
||||
t = PrettyTable()
|
||||
red = "\x1b[91m"
|
||||
green = "\x1b[92m"
|
||||
white = "\x1b[0m"
|
||||
for ch in ["A", "B", "C"]:
|
||||
t.clear()
|
||||
shuttle_in_slot = self.search_shuttle_in_slot(ch)
|
||||
if shuttle_in_slot > 0:
|
||||
t.title = green + "Shuttle " + ch + " in OMNY slot " + str(shuttle_in_slot) + white
|
||||
else:
|
||||
t.title = red + "Shuttle " + ch + white
|
||||
t.title = "Shuttle " + ch
|
||||
field_names = [""]
|
||||
for ax in [1, 3, 5]:
|
||||
row = []
|
||||
@@ -287,24 +260,8 @@ class OMNYSampleStorage(Device):
|
||||
row = []
|
||||
row.extend([f"Position {i:3d}"])
|
||||
if self.is_sample_slot_used("O", i):
|
||||
name = self.get_sample_name("O", i)
|
||||
row.extend([name])
|
||||
row.extend(self.get_sample_name("O", i))
|
||||
else:
|
||||
row.extend(["free"])
|
||||
t.add_row(row)
|
||||
print(t)
|
||||
|
||||
print("Use dev.omny_samples.help() for assistance.")
|
||||
|
||||
def help(self):
|
||||
print("Help for OMNY sample storage:")
|
||||
print(" To get an overview use dev.omny_samples.show_all()")
|
||||
print(" Modify a slot:")
|
||||
print(" dev.omny_samples.unset_sample_slot('system',position)")
|
||||
print(" dev.omny_samples.set_sample_slot('system',position,'name')")
|
||||
print(" system can be A, B, C, O")
|
||||
print(" dev.omny_samples.set_sample_in_gripper('name') / unset_sample_in_gripper()")
|
||||
print(" dev.omny_samples.set_sample_in_samplestage('name'), unset_sample_in_samplestage()")
|
||||
print(" dev.omny_samples.set_shuttle_slot(container, slot_nr) / unset_shuttle_slot(slot_nr)")
|
||||
print(" dev.omny_samples.set_shuttle_slot('A',2)")
|
||||
print(" omny.otransfer_help()")
|
||||
416
csaxs_bec/devices/epics/devices/pilatus_csaxs.py
Normal file
416
csaxs_bec/devices/epics/devices/pilatus_csaxs.py
Normal file
@@ -0,0 +1,416 @@
|
||||
import enum
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
|
||||
import numpy as np
|
||||
import requests
|
||||
from bec_lib import MessageEndpoints, bec_logger, messages
|
||||
from ophyd import ADComponent as ADCpt
|
||||
from ophyd import Device, EpicsSignal, EpicsSignalRO, EpicsSignalWithRBV, Staged
|
||||
from ophyd_devices.epics.devices.psi_detector_base import CustomDetectorMixin, PSIDetectorBase
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
MIN_READOUT = 3e-3
|
||||
|
||||
|
||||
class PilatusError(Exception):
|
||||
"""Base class for exceptions in this module."""
|
||||
|
||||
|
||||
class PilatusTimeoutError(PilatusError):
|
||||
"""Raised when the Pilatus does not respond in time during unstage."""
|
||||
|
||||
|
||||
class TriggerSource(enum.IntEnum):
|
||||
"""Trigger source options for the detector"""
|
||||
|
||||
INTERNAL = 0
|
||||
EXT_ENABLE = 1
|
||||
EXT_TRIGGER = 2
|
||||
MULTI_TRIGGER = 3
|
||||
ALGINMENT = 4
|
||||
|
||||
|
||||
class SLSDetectorCam(Device):
|
||||
"""SLS Detector Camera - Pilatus
|
||||
|
||||
Base class to map EPICS PVs to ophyd signals.
|
||||
"""
|
||||
|
||||
num_images = ADCpt(EpicsSignalWithRBV, "NumImages")
|
||||
num_frames = ADCpt(EpicsSignalWithRBV, "NumExposures")
|
||||
delay_time = ADCpt(EpicsSignalWithRBV, "NumExposures")
|
||||
trigger_mode = ADCpt(EpicsSignalWithRBV, "TriggerMode")
|
||||
acquire = ADCpt(EpicsSignal, "Acquire")
|
||||
armed = ADCpt(EpicsSignalRO, "Armed")
|
||||
|
||||
read_file_timeout = ADCpt(EpicsSignal, "ImageFileTmot")
|
||||
detector_state = ADCpt(EpicsSignalRO, "StatusMessage_RBV")
|
||||
status_message_camserver = ADCpt(EpicsSignalRO, "StringFromServer_RBV", string=True)
|
||||
acquire_time = ADCpt(EpicsSignal, "AcquireTime")
|
||||
acquire_period = ADCpt(EpicsSignal, "AcquirePeriod")
|
||||
threshold_energy = ADCpt(EpicsSignalWithRBV, "ThresholdEnergy")
|
||||
file_path = ADCpt(EpicsSignalWithRBV, "FilePath")
|
||||
file_name = ADCpt(EpicsSignalWithRBV, "FileName")
|
||||
file_number = ADCpt(EpicsSignalWithRBV, "FileNumber")
|
||||
auto_increment = ADCpt(EpicsSignalWithRBV, "AutoIncrement")
|
||||
file_template = ADCpt(EpicsSignalWithRBV, "FileTemplate")
|
||||
file_format = ADCpt(EpicsSignalWithRBV, "FileNumber")
|
||||
gap_fill = ADCpt(EpicsSignalWithRBV, "GapFill")
|
||||
|
||||
|
||||
class PilatusSetup(CustomDetectorMixin):
|
||||
"""Pilatus setup class for cSAXS
|
||||
|
||||
Parent class: CustomDetectorMixin
|
||||
|
||||
"""
|
||||
|
||||
def initialize_default_parameter(self) -> None:
|
||||
"""Set default parameters for Eiger9M detector"""
|
||||
self.update_readout_time()
|
||||
|
||||
def update_readout_time(self) -> None:
|
||||
"""Set readout time for Eiger9M detector"""
|
||||
readout_time = (
|
||||
self.parent.scaninfo.readout_time
|
||||
if hasattr(self.parent.scaninfo, "readout_time")
|
||||
else self.parent.MIN_READOUT
|
||||
)
|
||||
self.parent.readout_time = max(readout_time, self.parent.MIN_READOUT)
|
||||
|
||||
def initialize_detector(self) -> None:
|
||||
"""Initialize detector"""
|
||||
# Stops the detector
|
||||
self.stop_detector()
|
||||
# Sets the trigger source to GATING
|
||||
self.parent.set_trigger(TriggerSource.EXT_ENABLE)
|
||||
|
||||
def prepare_detector(self) -> None:
|
||||
"""
|
||||
Prepare detector for scan.
|
||||
|
||||
Includes checking the detector threshold,
|
||||
setting the acquisition parameters and setting the trigger source
|
||||
"""
|
||||
self.set_detector_threshold()
|
||||
self.set_acquisition_params()
|
||||
self.parent.set_trigger(TriggerSource.EXT_ENABLE)
|
||||
|
||||
def set_detector_threshold(self) -> None:
|
||||
"""
|
||||
Set correct detector threshold to 1/2 of current X-ray energy, allow 5% tolerance
|
||||
|
||||
Threshold might be in ev or keV
|
||||
"""
|
||||
|
||||
# get current beam energy from device manageer
|
||||
mokev = self.parent.device_manager.devices.mokev.obj.read()[
|
||||
self.parent.device_manager.devices.mokev.name
|
||||
]["value"]
|
||||
factor = 1
|
||||
|
||||
# Check if energies are eV or keV, assume keV as the default
|
||||
unit = getattr(self.parent.cam.threshold_energy, "units", None)
|
||||
if unit is not None and unit == "eV":
|
||||
factor = 1000
|
||||
|
||||
# set energy on detector
|
||||
setpoint = int(mokev * factor)
|
||||
|
||||
# set threshold on detector
|
||||
threshold = self.parent.cam.threshold_energy.read()[self.parent.cam.threshold_energy.name][
|
||||
"value"
|
||||
]
|
||||
if not np.isclose(setpoint / 2, threshold, rtol=0.05):
|
||||
self.parent.cam.threshold_energy.set(setpoint / 2)
|
||||
|
||||
def set_acquisition_params(self) -> None:
|
||||
"""Set acquisition parameters for the detector"""
|
||||
|
||||
# Set number of images and frames (frames is for internal burst of detector)
|
||||
self.parent.cam.num_images.put(
|
||||
int(self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger)
|
||||
)
|
||||
self.parent.cam.num_frames.put(1)
|
||||
|
||||
# Update the readout time of the detector
|
||||
self.update_readout_time()
|
||||
|
||||
def create_directory(self, filepath: str) -> None:
|
||||
"""Create directory if it does not exist"""
|
||||
os.makedirs(filepath, exist_ok=True)
|
||||
|
||||
def stop_detector_backend(self) -> None:
|
||||
"""Stop the file writer zmq service for pilatus_2"""
|
||||
self.close_file_writer()
|
||||
time.sleep(0.1)
|
||||
self.stop_file_writer()
|
||||
time.sleep(0.1)
|
||||
|
||||
def close_file_writer(self) -> None:
|
||||
"""
|
||||
Close the file writer for pilatus_2
|
||||
|
||||
Delete the data from x12sa-pd-2
|
||||
|
||||
"""
|
||||
url = "http://x12sa-pd-2:8080/stream/pilatus_2"
|
||||
try:
|
||||
res = self.send_requests_delete(url=url)
|
||||
if not res.ok:
|
||||
res.raise_for_status()
|
||||
except Exception as exc:
|
||||
logger.info(f"Pilatus2 close threw Exception: {exc}")
|
||||
|
||||
def stop_file_writer(self) -> None:
|
||||
"""
|
||||
Stop the file writer for pilatus_2
|
||||
|
||||
Runs on xbl-daq-34
|
||||
"""
|
||||
url = "http://xbl-daq-34:8091/pilatus_2/stop"
|
||||
res = self.send_requests_put(url=url)
|
||||
if not res.ok:
|
||||
res.raise_for_status()
|
||||
|
||||
def prepare_data_backend(self) -> None:
|
||||
"""
|
||||
Prepare the detector backend of pilatus for a scan
|
||||
|
||||
A zmq service is running on xbl-daq-34 that is waiting
|
||||
for a zmq message to start the writer for the pilatus_2 x12sa-pd-2
|
||||
|
||||
"""
|
||||
|
||||
self.stop_detector_backend()
|
||||
|
||||
self.parent.filepath = self.parent.filewriter.compile_full_filename("pilatus_2.h5")
|
||||
self.parent.cam.file_path.put("/dev/shm/zmq/")
|
||||
self.parent.cam.file_name.put(
|
||||
f"{self.parent.scaninfo.username}_2_{self.parent.scaninfo.scan_number:05d}"
|
||||
)
|
||||
self.parent.cam.auto_increment.put(1) # auto increment
|
||||
self.parent.cam.file_number.put(0) # first iter
|
||||
self.parent.cam.file_format.put(0) # 0: TIFF
|
||||
self.parent.cam.file_template.put("%s%s_%5.5d.cbf")
|
||||
|
||||
# TODO better to remove hard coded path with link to home directory/pilatus_2
|
||||
basepath = f"/sls/X12SA/data/{self.parent.scaninfo.username}/Data10/pilatus_2/"
|
||||
self.parent.filepath_raw = os.path.join(
|
||||
basepath,
|
||||
self.parent.filewriter.get_scan_directory(self.parent.scaninfo.scan_number, 1000, 5),
|
||||
)
|
||||
# Make directory if needed
|
||||
self.create_directory(self.parent.filepath_raw)
|
||||
|
||||
headers = {"Content-Type": "application/json", "Accept": "application/json"}
|
||||
# start the stream on x12sa-pd-2
|
||||
url = "http://x12sa-pd-2:8080/stream/pilatus_2"
|
||||
data_msg = {
|
||||
"source": [
|
||||
{
|
||||
"searchPath": "/",
|
||||
"searchPattern": "glob:*.cbf",
|
||||
"destinationPath": self.parent.filepath_raw,
|
||||
}
|
||||
]
|
||||
}
|
||||
res = self.send_requests_put(url=url, data=data_msg, headers=headers)
|
||||
logger.info(f"{res.status_code} - {res.text} - {res.content}")
|
||||
|
||||
if not res.ok:
|
||||
res.raise_for_status()
|
||||
|
||||
# start the data receiver on xbl-daq-34
|
||||
url = "http://xbl-daq-34:8091/pilatus_2/run"
|
||||
data_msg = [
|
||||
"zmqWriter",
|
||||
self.parent.scaninfo.username,
|
||||
{
|
||||
"addr": "tcp://x12sa-pd-2:8888",
|
||||
"dst": ["file"],
|
||||
"numFrm": int(
|
||||
self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger
|
||||
),
|
||||
"timeout": 2000,
|
||||
"ifType": "PULL",
|
||||
"user": self.parent.scaninfo.username,
|
||||
},
|
||||
]
|
||||
res = self.send_requests_put(url=url, data=data_msg, headers=headers)
|
||||
logger.info(f"{res.status_code} - {res.text} - {res.content}")
|
||||
|
||||
if not res.ok:
|
||||
res.raise_for_status()
|
||||
|
||||
# Wait for server to become available again
|
||||
time.sleep(0.1)
|
||||
logger.info(f"{res.status_code} -{res.text} - {res.content}")
|
||||
|
||||
# Send requests.put to xbl-daq-34 to wait for data
|
||||
url = "http://xbl-daq-34:8091/pilatus_2/wait"
|
||||
data_msg = [
|
||||
"zmqWriter",
|
||||
self.parent.scaninfo.username,
|
||||
{
|
||||
"frmCnt": int(
|
||||
self.parent.scaninfo.num_points * self.parent.scaninfo.frames_per_trigger
|
||||
),
|
||||
"timeout": 2000,
|
||||
},
|
||||
]
|
||||
try:
|
||||
res = self.send_requests_put(url=url, data=data_msg, headers=headers)
|
||||
logger.info(f"{res}")
|
||||
|
||||
if not res.ok:
|
||||
res.raise_for_status()
|
||||
except Exception as exc:
|
||||
logger.info(f"Pilatus2 wait threw Exception: {exc}")
|
||||
|
||||
def send_requests_put(self, url: str, data: list = None, headers: dict = None) -> object:
|
||||
"""
|
||||
Send a put request to the given url
|
||||
|
||||
Args:
|
||||
url (str): url to send the request to
|
||||
data (dict): data to be sent with the request (optional)
|
||||
headers (dict): headers to be sent with the request (optional)
|
||||
|
||||
Returns:
|
||||
status code of the request
|
||||
"""
|
||||
return requests.put(url=url, data=json.dumps(data), headers=headers, timeout=5)
|
||||
|
||||
def send_requests_delete(self, url: str, headers: dict = None) -> object:
|
||||
"""
|
||||
Send a delete request to the given url
|
||||
|
||||
Args:
|
||||
url (str): url to send the request to
|
||||
headers (dict): headers to be sent with the request (optional)
|
||||
|
||||
Returns:
|
||||
status code of the request
|
||||
"""
|
||||
return requests.delete(url=url, headers=headers, timeout=5)
|
||||
|
||||
def pre_scan(self) -> None:
|
||||
"""
|
||||
Pre_scan function call
|
||||
|
||||
This function is called just before the scan core.
|
||||
Here it is used to arm the detector for the acquisition
|
||||
|
||||
"""
|
||||
self.arm_acquisition()
|
||||
|
||||
def arm_acquisition(self) -> None:
|
||||
"""Arms the detector for the acquisition"""
|
||||
self.parent.cam.acquire.put(1)
|
||||
# TODO is this sleep needed? to be tested with detector and for how long
|
||||
time.sleep(0.5)
|
||||
|
||||
def publish_file_location(self, done: bool = False, successful: bool = None) -> None:
|
||||
"""
|
||||
Publish the filepath to REDIS and publish the event for the h5_converter
|
||||
|
||||
We publish two events here:
|
||||
- file_event: event for the filewriter
|
||||
- public_file: event for any secondary service (e.g. radial integ code)
|
||||
|
||||
Args:
|
||||
done (bool): True if scan is finished
|
||||
successful (bool): True if scan was successful
|
||||
"""
|
||||
pipe = self.parent.connector.pipeline()
|
||||
if successful is None:
|
||||
msg = messages.FileMessage(
|
||||
file_path=self.parent.filepath,
|
||||
done=done,
|
||||
metadata={"input_path": self.parent.filepath_raw},
|
||||
)
|
||||
else:
|
||||
msg = messages.FileMessage(
|
||||
file_path=self.parent.filepath,
|
||||
done=done,
|
||||
successful=successful,
|
||||
metadata={"input_path": self.parent.filepath_raw},
|
||||
)
|
||||
self.parent.connector.set_and_publish(
|
||||
MessageEndpoints.public_file(self.parent.scaninfo.scan_id, self.parent.name),
|
||||
msg,
|
||||
pipe=pipe,
|
||||
)
|
||||
self.parent.connector.set_and_publish(
|
||||
MessageEndpoints.file_event(self.parent.name), msg, pipe=pipe
|
||||
)
|
||||
pipe.execute()
|
||||
|
||||
def finished(self) -> None:
|
||||
"""Check if acquisition is finished."""
|
||||
# pylint: disable=protected-access
|
||||
# TODO: at the moment this relies on device.mcs.obj._staged attribute
|
||||
signal_conditions = [
|
||||
(lambda: self.parent.device_manager.devices.mcs.obj._staged, Staged.no)
|
||||
]
|
||||
if not self.wait_for_signals(
|
||||
signal_conditions=signal_conditions,
|
||||
timeout=self.parent.timeout,
|
||||
check_stopped=True,
|
||||
all_signals=True,
|
||||
):
|
||||
raise PilatusTimeoutError(
|
||||
f"Reached timeout with detector state {signal_conditions[0][0]}, std_daq state"
|
||||
f" {signal_conditions[1][0]} and received frames of {signal_conditions[2][0]} for"
|
||||
" the file writer"
|
||||
)
|
||||
self.stop_detector()
|
||||
self.stop_detector_backend()
|
||||
|
||||
def stop_detector(self) -> None:
|
||||
"""Stop detector"""
|
||||
self.parent.cam.acquire.put(0)
|
||||
|
||||
def check_scan_id(self) -> None:
|
||||
"""Checks if scan_id has changed and stops the scan if it has"""
|
||||
old_scan_id = self.parent.scaninfo.scan_id
|
||||
self.parent.scaninfo.load_scan_metadata()
|
||||
if self.parent.scaninfo.scan_id != old_scan_id:
|
||||
self.parent.stopped = True
|
||||
|
||||
|
||||
class PilatuscSAXS(PSIDetectorBase):
|
||||
"""Pilatus_2 300k detector for CSAXS
|
||||
|
||||
Parent class: PSIDetectorBase
|
||||
|
||||
class attributes:
|
||||
custom_prepare_cls (Eiger9MSetup) : Custom detector setup class for cSAXS,
|
||||
inherits from CustomDetectorMixin
|
||||
cam (SLSDetectorCam) : Detector camera
|
||||
MIN_READOUT (float) : Minimum readout time for the detector
|
||||
|
||||
"""
|
||||
|
||||
# Specify which functions are revealed to the user in BEC client
|
||||
USER_ACCESS = ["describe"]
|
||||
|
||||
# specify Setup class
|
||||
custom_prepare_cls = PilatusSetup
|
||||
# specify minimum readout time for detector
|
||||
MIN_READOUT = 3e-3
|
||||
# specify class attributes
|
||||
cam = ADCpt(SLSDetectorCam, "cam1:")
|
||||
|
||||
def set_trigger(self, trigger_source: TriggerSource) -> None:
|
||||
"""Set trigger source for the detector"""
|
||||
value = trigger_source
|
||||
self.cam.trigger_mode.put(value)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pilatus_2 = PilatuscSAXS(name="pilatus_2", prefix="X12SA-ES-PILATUS300K:", sim_mode=True)
|
||||
@@ -1,242 +0,0 @@
|
||||
"""Falcon Sitoro detector class for cSAXS beamline."""
|
||||
|
||||
import enum
|
||||
import os
|
||||
import threading
|
||||
from typing import Literal
|
||||
|
||||
from bec_lib.file_utils import get_full_path
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd_devices import CompareStatus, FileEventSignal
|
||||
from ophyd_devices.devices.areadetector.plugins import HDF5Plugin_V35 as HDF5Plugin
|
||||
from ophyd_devices.devices.dxp import EpicsDXPFalcon, EpicsMCARecord, Falcon
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class FalconError(Exception):
|
||||
"""Base class for exceptions in this module."""
|
||||
|
||||
|
||||
class ACQUIRESTATUS(enum.IntEnum):
|
||||
"""Detector states for Falcon detector"""
|
||||
|
||||
DONE = 0
|
||||
ACQUIRING = 1 # or Capturing
|
||||
|
||||
|
||||
class TriggerSource(enum.IntEnum):
|
||||
"""Trigger source for Falcon detector"""
|
||||
|
||||
USER = 0
|
||||
GATE = 1
|
||||
SYNC = 2
|
||||
|
||||
|
||||
class MappingSource(enum.IntEnum):
|
||||
"""Mapping source for Falcon detector"""
|
||||
|
||||
SPECTRUM = 0
|
||||
MAPPING = 1
|
||||
|
||||
|
||||
class FalconControl(Falcon):
|
||||
"""Falcon Control class at cSAXS. prefix: 'X12SA-SITORO:'"""
|
||||
|
||||
dxp = Cpt(EpicsDXPFalcon, "dxp1:")
|
||||
mca = Cpt(EpicsMCARecord, "mca1")
|
||||
hdf5 = Cpt(HDF5Plugin, "HDF1:")
|
||||
|
||||
|
||||
class FalconcSAXS(PSIDeviceBase, FalconControl):
|
||||
"""
|
||||
Falcon Sitoro detector for CSAXS
|
||||
|
||||
|
||||
class attributes:
|
||||
dxp (EpicsDXPFalcon) : DXP parameters for Falcon detector
|
||||
mca (EpicsMCARecord) : MCA parameters for Falcon detector
|
||||
hdf5 (FalconHDF5Plugins) : HDF5 parameters for Falcon detector
|
||||
MIN_READOUT (float) : Minimum readout time for the detector
|
||||
"""
|
||||
|
||||
# specify minimum readout time for detector
|
||||
MIN_READOUT = 3e-3
|
||||
_pv_timeout = 3 # Timeout for PV operations in seconds
|
||||
|
||||
file_event = Cpt(FileEventSignal, name="file_event")
|
||||
|
||||
def on_init(self) -> None:
|
||||
"""Initialize Falcon Sitoro detector"""
|
||||
self._lock = threading.RLock()
|
||||
self._readout_time = self.MIN_READOUT
|
||||
self._value_pixel_per_buffer = 20
|
||||
self._queue_size = 2000
|
||||
self._full_path = ""
|
||||
|
||||
def on_connected(self):
|
||||
"""
|
||||
Setup Falcon Sitoro detector default parameters once signals are connected
|
||||
"""
|
||||
self.on_stop()
|
||||
self._initialize_detector()
|
||||
self._initialize_detector_backend()
|
||||
self.set_trigger(
|
||||
mapping_mode=MappingSource.MAPPING, trigger_source=TriggerSource.GATE, ignore_gate=0
|
||||
)
|
||||
|
||||
def set_trigger(
|
||||
self,
|
||||
mapping_mode: MappingSource,
|
||||
trigger_source: TriggerSource,
|
||||
ignore_gate: Literal[0, 1] = 0,
|
||||
) -> None:
|
||||
"""
|
||||
Set triggering mode for detector
|
||||
|
||||
Args:
|
||||
mapping_mode (MappingSource): Mapping mode for the detector
|
||||
trigger_source (TriggerSource): Trigger source for the detector, pixel_advance_signal
|
||||
ignore_gate (int): Ignore gate from TTL signal; defaults to 0
|
||||
|
||||
"""
|
||||
mapping = int(mapping_mode)
|
||||
trigger = int(trigger_source)
|
||||
self.collect_mode.put(mapping)
|
||||
self.pixel_advance_mode.put(trigger)
|
||||
self.ignore_gate.put(ignore_gate)
|
||||
|
||||
def _initialize_detector(self) -> None:
|
||||
"""Initialize Falcon detector"""
|
||||
|
||||
# 1 Realtime
|
||||
self.preset_mode.put(1)
|
||||
|
||||
# 0 Normal, 1 Inverted
|
||||
self.input_logic_polarity.put(0)
|
||||
|
||||
# 0 Manual 1 Auto
|
||||
self.auto_pixels_per_buffer.put(0)
|
||||
|
||||
# Sets the number of pixels/spectra in the buffer
|
||||
self.pixels_per_buffer.put(self._value_pixel_per_buffer)
|
||||
|
||||
def _initialize_detector_backend(self) -> None:
|
||||
"""Initialize the detector backend for Falcon."""
|
||||
# Enable HDF5 plugin
|
||||
self.hdf5.enable.put(1)
|
||||
|
||||
# Use layout.xml file for cSAXS Falcon. FIXME:Should be checked if IOC runs on different host.
|
||||
self.hdf5.xml_file_name.put("layout.xml")
|
||||
|
||||
# TODO Check if lazy open is needed and wanted!
|
||||
self.hdf5.lazy_open.put(1)
|
||||
self.hdf5.temp_suffix.put("")
|
||||
|
||||
# Size of the queue for the number of spectra allowed in the buffer. If too small, data is lost at high throughput
|
||||
self.hdf5.queue_size.put(self._queue_size)
|
||||
self.hdf5.file_template.put("%s%s")
|
||||
self.hdf5.file_write_mode.put(2)
|
||||
|
||||
# Set nd_array mode to 1: This means segmentation into Spectra within EPICS, 1 is activate, 0 is deactivate
|
||||
self.nd_array_mode.put(1)
|
||||
|
||||
def on_stage(self):
|
||||
"""
|
||||
This method is called when the detector is staged for acquisition.
|
||||
We use the information in scan_info.msg about the upcoming scan to set all relevant parameters on the detector.
|
||||
"""
|
||||
# Calculate relevant parameters
|
||||
num_points = self.scan_info.msg.num_points
|
||||
frames_per_trigger = self.scan_info.msg.scan_parameters.get("frames_per_trigger", 1)
|
||||
overall_frames = int(num_points * frames_per_trigger)
|
||||
exp_time = self.scan_info.msg.scan_parameters["exp_time"]
|
||||
self._full_path = get_full_path(self.scan_info.msg, self.name)
|
||||
|
||||
# Check that exposure time is larger than readout time
|
||||
readout_time = max(
|
||||
self.scan_info.msg.scan_parameters.get("readout_time", self.MIN_READOUT),
|
||||
self.MIN_READOUT,
|
||||
)
|
||||
if exp_time < readout_time:
|
||||
raise ValueError(
|
||||
f"Exposure time {exp_time} is less than minimum readout time {readout_time}"
|
||||
)
|
||||
|
||||
# TODO: Add h5_entries for linking the Falcon NEXUS entries with the master file
|
||||
self.file_event.put(file_path=self._full_path, done=False, successful=False)
|
||||
|
||||
self.preset_real_time.put(exp_time)
|
||||
self.pixels_per_run.put(overall_frames)
|
||||
|
||||
# Prepare detector backend PVs
|
||||
file_path, file_name = os.path.split(self._full_path)
|
||||
self.hdf5.file_path.put(file_path)
|
||||
self.hdf5.file_name.put(file_name)
|
||||
|
||||
self.hdf5.num_capture.put(overall_frames)
|
||||
# Reset spectrum counter in filewriter, used for indexing & identifying missing triggers
|
||||
self.hdf5.array_counter.put(0)
|
||||
|
||||
# Start file writing
|
||||
self.hdf5.capture.put(1)
|
||||
# Start the acquisition
|
||||
self.start_all.put(1)
|
||||
|
||||
def on_pre_scan(self):
|
||||
"""
|
||||
Method for actions just before the scan starts.
|
||||
"""
|
||||
status_camera = CompareStatus(
|
||||
self.acquire_busy, ACQUIRESTATUS.ACQUIRING, timeout=self._pv_timeout
|
||||
)
|
||||
status_writer = CompareStatus(
|
||||
self.hdf5.capture, ACQUIRESTATUS.ACQUIRING, timeout=self._pv_timeout
|
||||
)
|
||||
# Logical combine of statuses
|
||||
status = status_camera & status_writer
|
||||
self.cancel_on_stop(status)
|
||||
return status
|
||||
|
||||
def _complete_callback(self, status: CompareStatus) -> None:
|
||||
"""Callback for when the device completes a scan."""
|
||||
# FIXME Add proper h5 entries once checked
|
||||
if status.success:
|
||||
self.file_event.put(
|
||||
file_path=self._full_path, # pylint: disable:protected-access
|
||||
done=True,
|
||||
successful=True,
|
||||
)
|
||||
else:
|
||||
self.file_event.put(
|
||||
file_path=self._full_path, # pylint: disable:protected-access
|
||||
done=True,
|
||||
successful=False,
|
||||
)
|
||||
|
||||
def on_complete(self) -> None:
|
||||
"""Complete detector and backend"""
|
||||
# Calculate relevant parameters
|
||||
num_points = self.scan_info.msg.num_points
|
||||
frames_per_trigger = self.scan_info.msg.scan_parameters.get("frames_per_trigger", 1)
|
||||
overall_frames = int(num_points * frames_per_trigger)
|
||||
|
||||
status_detector = CompareStatus(self.dxp.current_pixel, overall_frames, run=True)
|
||||
status_backend = CompareStatus(self.hdf5.array_counter, overall_frames, run=True)
|
||||
|
||||
status = status_detector & status_backend
|
||||
self.cancel_on_stop(status)
|
||||
status.add_callback(self._complete_callback)
|
||||
return status
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Stop detector and backend"""
|
||||
self.stop_all.put(1)
|
||||
self.hdf5.capture.put(0)
|
||||
self.erase_all.put(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
falcon = FalconcSAXS(name="falcon", prefix="X12SA-SITORO:")
|
||||
@@ -1,13 +0,0 @@
|
||||
# MCS Card implementation at the CSAXS beamline
|
||||
|
||||
This module provides an ophyd device implementation for the SIS3820 Multi-Channel Scaler (MCS) card, used at the cSAXS beamline for time-resolved data acquisition. It interfaces with the EPICS IOC for the SIS3820 MCS card.
|
||||
Information about the EPICS driver can be found here (https://millenia.cars.aps.anl.gov/software/epics/mcaStruck.html).
|
||||
|
||||
# Important Notes
|
||||
Operation of the MCS card requires proper configuration as some of the parameters are interdependent. In addition, empirical adjustments have been found to be necessary for optimal performance at the beamline. In its current implementation, comments about these dependencies are highlighted in the source code of the ophyd device classes [MCSCard](./mcs_card.py) and [MCSCardCSAXS](./mcs_card_csaxs.py). It is highly recommended to review these comments before refactoring, modifying, or extending the code.
|
||||
|
||||
## Ophyd Device Implementation
|
||||
|
||||
The ophyd device implementation is provided [MCSCard](./mcs_card.py). This class provides a basic interface to the MCS PVs, including configuration of parameters such as number of channels, dwell time, and control of acquisition start/stop. Please check the source code of the class for more details of the implementation.
|
||||
|
||||
The [MCSCardCSAXS](./mcs_card_csaxs.py) class extends the basic MCSCard implementation with cSAXS-specific logic and configurations. Please be aware that this is also linked to the implementation of other devices, most notably the [delay generator integration](../delay_generator_csaxs/README.md), which is used as the trigger source for the MCS card during operation.
|
||||
@@ -1 +0,0 @@
|
||||
from .mcs_card import MCSCard
|
||||
@@ -1,344 +0,0 @@
|
||||
"""
|
||||
EPICS SIS38XX Multichannel Scaler (MCS) Interface
|
||||
|
||||
This module provides an interface to the SIS3801/SIS3820 multichannel scaler (MCS) cards via EPICS.
|
||||
It focuses on the implementation for the SIS3820 model, as input/output modes differ between SIS3801
|
||||
and SIS3820. It supports both MCS and scaler record operations, enabling configuration and control of
|
||||
acquisition parameters such as dwell time, channel advance mode, and input/output settings.
|
||||
The module facilitates data acquisition by managing FIFO buffers and simulating conventional
|
||||
MCS behavior through memory buffers.
|
||||
|
||||
At cSAXS, the SIS3820 model is used, which supports 32 channels.
|
||||
|
||||
References:
|
||||
- EPICS SIS3801 and SIS3820 Drivers: https://millenia.cars.aps.anl.gov/software/epics/mcaStruck.html
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, DynamicDeviceComponent, EpicsSignal, EpicsSignalRO, Kind
|
||||
|
||||
|
||||
class CHANNELADVANCE(int, enum.Enum):
|
||||
"""Channel advance pixel mode for MCS card."""
|
||||
|
||||
INTERNAL = 0
|
||||
EXTERNAL = 1
|
||||
|
||||
|
||||
class ACQUIRING(int, enum.Enum):
|
||||
"""Acquisition status for MCS card."""
|
||||
|
||||
DONE = 0
|
||||
ACQUIRING = 1
|
||||
|
||||
|
||||
class READMODE(int, enum.Enum):
|
||||
"""Read mode for MCS channels."""
|
||||
|
||||
PASSIVE = 0
|
||||
EVENT = 1
|
||||
IO_INTR = 2
|
||||
FREQ_0_1HZ = 3
|
||||
FREQ_0_2HZ = 4
|
||||
FREQ_0_5HZ = 5
|
||||
FREQ_1HZ = 6
|
||||
FREQ_2HZ = 7
|
||||
FREQ_5HZ = 8
|
||||
FREQ_10HZ = 9
|
||||
FREQ_100HZ = 10
|
||||
|
||||
|
||||
class CHANNEL1SOURCE(int, enum.Enum):
|
||||
"""Source for first counter pulses."""
|
||||
|
||||
INTERNAL_CLOCK = 0
|
||||
EXTERNAL = 1
|
||||
|
||||
|
||||
class POLARITY(int, enum.Enum):
|
||||
"""Polarity of input_polarity/output_polarity for MCS card."""
|
||||
|
||||
NORMAL = 0
|
||||
INVERTED = 1
|
||||
|
||||
|
||||
class ACQUIREMODE(int, enum.Enum):
|
||||
"""Acquire mode for the card. Allowed modes are Scaler and MCS."""
|
||||
|
||||
MCS = 0
|
||||
SCALER = 1
|
||||
|
||||
|
||||
class MODELS(int, enum.Enum):
|
||||
|
||||
SIS3801 = 0
|
||||
SIS3820 = 1
|
||||
|
||||
|
||||
class INPUTMODE(int, enum.Enum):
|
||||
"""SIS3820 input mode definitions, in total there are 8 modes (0-7).
|
||||
|
||||
Each mode defines the function of external inputs 1-4.
|
||||
Note: SIS3820 has extended input modes compared to SIS3801.
|
||||
Please check the EPICS documentation for details on the specific input modes supported by SIS3801.
|
||||
"""
|
||||
|
||||
MODE_0 = 0
|
||||
MODE_1 = 1
|
||||
MODE_2 = 2
|
||||
MODE_3 = 3
|
||||
MODE_4 = 4
|
||||
MODE_5 = 5
|
||||
MODE_6 = 6
|
||||
MODE_7 = 7
|
||||
|
||||
def describe(self) -> str:
|
||||
"""Return a description of the input mode."""
|
||||
descriptions = {
|
||||
self.MODE_0: "Inputs 1-4: No function (default idle mode)",
|
||||
self.MODE_1: "Inputs 1-4: Next pulse, User bit 1, User bit 2, Inhibit next pulse",
|
||||
self.MODE_2: "Inputs 1-4: Next pulse, User bit 1, Inhibit counting, Inhibit next pulse",
|
||||
self.MODE_3: "Inputs 1-4: Next pulse, User bit 1, User bit 2, Inhibit counting",
|
||||
self.MODE_4: "Inputs 1-4: Inhibit counting channels 1-8, 9-16, 17-24, 25-32",
|
||||
self.MODE_5: "Inputs 1-4: Next pulse, HISCAL_START, No function, No function",
|
||||
self.MODE_6: "Inputs 1-4: Next pulse, Inhibit counting, Clear counters, User bit 1",
|
||||
self.MODE_7: "Inputs 1-4: Encoder A, Encoder B, Encoder I, Inhibit counting",
|
||||
}
|
||||
return descriptions.get(self, "Unknown input mode")
|
||||
|
||||
|
||||
class OUTPUTMODE(int, enum.Enum):
|
||||
"""SIS3820 output mode definitions, in total there are 4 modes (0-3).
|
||||
|
||||
Each mode configures output signals 5-8.
|
||||
Note: SIS3820 supports 4 output modes (0-3), SIS3801 supports only Mode 0 with differen functionality.
|
||||
Please check the EPICS documentation for details on the specific output modes supported by SIS3801.
|
||||
"""
|
||||
|
||||
MODE_0 = 0
|
||||
MODE_1 = 1
|
||||
MODE_2 = 2
|
||||
MODE_3 = 3
|
||||
|
||||
def describe(self) -> str:
|
||||
"""Return a description of the output mode."""
|
||||
descriptions = {
|
||||
self.MODE_0: "Outputs 5-8: LNE/CIP, SDRAM empty, SDRAM threshold, User LED",
|
||||
self.MODE_1: "Outputs 5-8: LNE/CIP, Enabled, 50 MHz, User LED",
|
||||
self.MODE_2: "Outputs 5-8: LNE/CIP, 10 MHz (20ns), 10 MHz (20ns), User LED",
|
||||
self.MODE_3: "Outputs 5-8: LNE/CIP, 10 MHz (20ns), MUX OUT channel, User LED (requires firmware ≥ 0x10A)",
|
||||
}
|
||||
return descriptions.get(self, "Unknown output mode")
|
||||
|
||||
|
||||
def _create_mca_channels(num_channels: int) -> dict[str, tuple]:
|
||||
"""
|
||||
Create a dictionary of MCA channel definitions for the DynamicDeviceComponent.
|
||||
Starts from channel 1 to num_channels.
|
||||
|
||||
Args:
|
||||
num_channels (int): The number of MCA channels to create.
|
||||
"""
|
||||
mcs_channels = {}
|
||||
for i in range(1, num_channels + 1):
|
||||
mcs_channels[f"mca{i}"] = (
|
||||
EpicsSignalRO,
|
||||
f"mca{i}.VAL",
|
||||
{"kind": Kind.omitted, "auto_monitor": True, "doc": f"MCA channel {i}."},
|
||||
)
|
||||
return mcs_channels
|
||||
|
||||
|
||||
class MCSCard(Device):
|
||||
"""
|
||||
Ophyd implementation for the interface to the SIS3801/SIS3820 multichannel scaler (MCS) cards via EPICS.
|
||||
|
||||
This class provides signals to expose EPICS PVs of the MCS card. More details can be found in the
|
||||
documentation of the EPICS drivers for SIS3801 and SIS3820.
|
||||
|
||||
References:
|
||||
- EPICS SIS3801 and SIS3820 Drivers: https://millenia.cars.aps.anl.gov/software/epics/mcaStruck.html
|
||||
"""
|
||||
|
||||
snl_connected = Cpt(
|
||||
EpicsSignalRO,
|
||||
"SNL_Connected",
|
||||
kind=Kind.omitted,
|
||||
doc="Indicates whether the SNL program has connected to all PVs.",
|
||||
)
|
||||
# NOTE: Please note that the erase_all command sends the mca or waveform records to process after erasing, potentially also values of 0. This logic needs to be considered when running callbacks on the mca channels.
|
||||
erase_all = Cpt(
|
||||
EpicsSignal,
|
||||
"EraseAll",
|
||||
kind=Kind.omitted,
|
||||
doc="Erases all mca or waveform records, setting elapsed times and counts in all channels to 0. Please note that this operation sends the mca or waveform records to process after erasing, potentially also 0s.",
|
||||
)
|
||||
erase_start = Cpt(
|
||||
EpicsSignal,
|
||||
"EraseStart",
|
||||
kind=Kind.omitted,
|
||||
doc="Erases all mca or waveform records and starts acquisition.",
|
||||
)
|
||||
start_all = Cpt(
|
||||
EpicsSignal,
|
||||
"StartAll",
|
||||
kind=Kind.omitted,
|
||||
doc="Starts or resumes acquisition without erasing first.",
|
||||
)
|
||||
acquiring = Cpt(
|
||||
EpicsSignalRO,
|
||||
"Acquiring",
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
doc="Acquiring (=1) when acquisition is in progress and Done (=0) when acquisition is complete.",
|
||||
)
|
||||
stop_all = Cpt(EpicsSignal, "StopAll", kind=Kind.omitted, doc="Stops acquisition.")
|
||||
preset_real = Cpt(
|
||||
EpicsSignal,
|
||||
"PresetReal",
|
||||
kind=Kind.omitted,
|
||||
doc="Preset real time. If non-zero then acquisition will stop when this time is reached.",
|
||||
)
|
||||
elapsed_real = Cpt(
|
||||
EpicsSignalRO,
|
||||
"ElapsedReal",
|
||||
kind=Kind.omitted,
|
||||
doc="Elapsed time since acquisition started.",
|
||||
)
|
||||
read_all = Cpt(
|
||||
EpicsSignal,
|
||||
"DoReadAll.VAL",
|
||||
kind=Kind.omitted,
|
||||
doc="Forces a read of all mca or waveform records from the hardware. This record can be set to periodically process to update the records during acquisition. Note that even if this record has SCAN=Passive the mca or waveform records will always process once when acquisition completes.",
|
||||
)
|
||||
read_mode = Cpt(
|
||||
EpicsSignal,
|
||||
"ReadAll.SCAN",
|
||||
kind=Kind.omitted,
|
||||
doc="Readout mode for transferring data from FIFO buffer to mca EPICS scalars.",
|
||||
)
|
||||
num_use_all = Cpt(
|
||||
EpicsSignal,
|
||||
"NuseAll",
|
||||
kind=Kind.omitted,
|
||||
doc="The number of channels to use for the mca or waveform records. Acquisition will automatically stop when the number of channel advances reaches this value.",
|
||||
)
|
||||
dwell = Cpt(
|
||||
EpicsSignal,
|
||||
"Dwell",
|
||||
kind=Kind.omitted,
|
||||
doc="The dwell time per channel when using internal channel advance mode.",
|
||||
)
|
||||
channel_advance = Cpt(
|
||||
EpicsSignal,
|
||||
"ChannelAdvance",
|
||||
kind=Kind.omitted,
|
||||
doc="The channel advance mode. Choices are 'Internal' (count for a preset time per channel) or 'External' (advance on external hardware channel advance signal).",
|
||||
)
|
||||
count_on_start = Cpt(
|
||||
EpicsSignal,
|
||||
"CountOnStart",
|
||||
kind=Kind.omitted,
|
||||
doc="Flag controlling whether the module begins counting immediately when acquisition starts. This record only applies in External channel advance mode. If No (=0) then counting does not start in channel 0 until receipt of the first external channel advance pulse. If Yes (=1) then counting in channel 0 starts immediately when acquisition starts, without waiting for the first external channel advance pulse.",
|
||||
)
|
||||
software_channel_advance = Cpt(
|
||||
EpicsSignal,
|
||||
"SoftwareChannelAdvance",
|
||||
kind=Kind.omitted,
|
||||
doc="Processing this record causes a channel advance to occur immediately, without waiting for the current dwell time to be reached or the next external channel advance pulse to arrive.",
|
||||
)
|
||||
channel1_source = Cpt(
|
||||
EpicsSignal,
|
||||
"Channel1Source",
|
||||
kind=Kind.omitted,
|
||||
doc="Controls the source of pulses into the first counter. The choices are 'Int. clock' which selects the internal clock, and 'External' which selects the external pulse input to counter 1.",
|
||||
)
|
||||
prescale = Cpt(
|
||||
EpicsSignal,
|
||||
"Prescale",
|
||||
kind=Kind.omitted,
|
||||
doc="The prescale factor for external channel advance pulses. If the prescale factor is N then N external channel advance pulses must be received before a channel advance will occur.",
|
||||
)
|
||||
enable_client_wait = Cpt(
|
||||
EpicsSignal,
|
||||
"EnableClientWait",
|
||||
kind=Kind.omitted,
|
||||
doc="Flag to force acquisition to wait until a client clears the ClientWait busy record before proceeding to the next acquisition. This can be useful with the scan record.",
|
||||
)
|
||||
client_wait = Cpt(
|
||||
EpicsSignal,
|
||||
"ClientWait",
|
||||
kind=Kind.omitted,
|
||||
doc="Flag that will be set to 1 when acquisition completes, and which a client must set back to 0 to allow acquisition to proceed. This only has an effect if EnableClientWait is 1.",
|
||||
)
|
||||
acquire_mode = Cpt(
|
||||
EpicsSignal,
|
||||
"AcquireMode",
|
||||
kind=Kind.omitted,
|
||||
doc="The current acquisition mode (MCS=0 or Scaler=1). This record is used to turn off the scaler record Autocount in MCS mode.",
|
||||
)
|
||||
# NOTE: Setting mux_output programmatically results in occasional errors on the IOC; it is recommended to avoid using it.
|
||||
mux_output = Cpt(
|
||||
EpicsSignal,
|
||||
"MUXOutput",
|
||||
kind=Kind.omitted,
|
||||
doc="Value of 0-32 used to select which input signal is routed to output signal 7 on the SIS3820 in output mode 3. NOTE: This settings seems to occasionally result in errors on the IOC; it is recommended to avoid using it.",
|
||||
)
|
||||
user_led = Cpt(
|
||||
EpicsSignal,
|
||||
"UserLED",
|
||||
kind=Kind.omitted,
|
||||
doc="Toggles the user LED and also output signal 8 on the SIS3820.",
|
||||
)
|
||||
input_mode = Cpt(
|
||||
EpicsSignal,
|
||||
"InputMode",
|
||||
kind=Kind.omitted,
|
||||
doc="The input mode. Supported input modes vary for SIS3801 and SIS3820.",
|
||||
)
|
||||
input_polarity = Cpt(
|
||||
EpicsSignal,
|
||||
"InputPolarity",
|
||||
kind=Kind.omitted,
|
||||
doc="The polarity of the input control signals on the SIS3820. Choices are Normal and Inverted.",
|
||||
)
|
||||
output_mode = Cpt(
|
||||
EpicsSignal,
|
||||
"OutputMode",
|
||||
kind=Kind.omitted,
|
||||
doc="The output mode. Supported output modes vary for SIS3801 and SIS3820.",
|
||||
)
|
||||
output_polarity = Cpt(
|
||||
EpicsSignal,
|
||||
"OutputPolarity",
|
||||
kind=Kind.omitted,
|
||||
doc="The polarity of the output control signals on the SIS3820. Choices are Normal and Inverted.",
|
||||
)
|
||||
model = Cpt(
|
||||
EpicsSignalRO,
|
||||
"Model",
|
||||
kind=Kind.omitted,
|
||||
doc="The scaler model. Values are 'SIS3801' and 'SIS3820'.",
|
||||
)
|
||||
firmware = Cpt(EpicsSignalRO, "Firmware", kind=Kind.omitted, doc="The firmware version.")
|
||||
max_channels = Cpt(
|
||||
EpicsSignalRO, "MaxChannels", kind=Kind.omitted, doc="The maximum number of channels."
|
||||
)
|
||||
|
||||
# Relevant counters
|
||||
current_channel = Cpt(
|
||||
EpicsSignalRO,
|
||||
"CurrentChannel",
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
doc="The current channel number, i.e. the number of channel advances that have occurred minus 1.",
|
||||
)
|
||||
counters = DynamicDeviceComponent(
|
||||
_create_mca_channels(32),
|
||||
kind=Kind.omitted,
|
||||
doc="Sub-device with the mca counters 1-32 for SIS3820.",
|
||||
)
|
||||
@@ -1,538 +0,0 @@
|
||||
"""
|
||||
Module for the MCSCard CSAXS implementation at cSAXS.
|
||||
|
||||
Please respect the comments regarding timing and procedures of the MCS card. These
|
||||
are highlighted with NOTE comments directly in the code, indicating requirements
|
||||
for stable device operation. Most of these constraints were identified
|
||||
empirically through extensive testing with the SIS3820 MCS card IOC and are intended
|
||||
to prevent unexpected hardware or IOC behavior.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
from threading import RLock
|
||||
from typing import TYPE_CHECKING, Callable, Literal
|
||||
|
||||
import numpy as np
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import EpicsSignalRO, Kind
|
||||
from ophyd_devices import AsyncMultiSignal, CompareStatus, ProgressSignal, StatusBase
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
from csaxs_bec.devices.epics.mcs_card.mcs_card import (
|
||||
ACQUIREMODE,
|
||||
ACQUIRING,
|
||||
CHANNEL1SOURCE,
|
||||
CHANNELADVANCE,
|
||||
INPUTMODE,
|
||||
OUTPUTMODE,
|
||||
POLARITY,
|
||||
READMODE,
|
||||
MCSCard,
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def suppress_mca_callbacks(mcs_card: MCSCard, restore_after_timeout: None | float = None):
|
||||
"""
|
||||
Utility context manager to suppress MCA channel callbacks temporarily.
|
||||
It is required because erasing all channels via 'erase_all' PV triggers
|
||||
callbacks for each channel. Depending on timing, this can interfere with
|
||||
ongoing data acquisition so this context manager can be used to suppress
|
||||
those callbacks temporarily. If used with restore_after_timeout, the suppression
|
||||
will be automatically cleared after the specified timeout in seconds.
|
||||
|
||||
NOTE: Please be aware that it does not restore previous state, which means
|
||||
that _omit_mca_callbacks will remain set after exiting the context. It has
|
||||
to be cleared manually if needed. This can be improved in the future, but
|
||||
should be carefully coordinated with the logic implemented within '_on_counter_update'.
|
||||
|
||||
Args:
|
||||
mcs_card (MCSCard): The MCSCard instance to suppress callbacks for.
|
||||
restore_after_timeout (float | None): Optional timeout in seconds to automatically
|
||||
clear the suppression after the specified time. If None, the original state
|
||||
is not restored.
|
||||
"""
|
||||
mcs_card._omit_mca_callbacks.set() # pylint: disable=protected-access
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if restore_after_timeout is not None:
|
||||
time.sleep(restore_after_timeout)
|
||||
mcs_card._omit_mca_callbacks.clear() # pylint: disable=protected-access
|
||||
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import DeviceManagerBase, ScanInfo
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
||||
"""
|
||||
Implementation of the MCSCard SIS3820 for CSAXS, prefix 'X12SA-MCS:'.
|
||||
The basic functionality is inherited from the MCSCard class.
|
||||
|
||||
Please note that the number of channels is fixed to 32, so there will be data for all
|
||||
32 channels. In addition, the logic of the card is linked to the timing system (DDG)
|
||||
and therefore changes have to be coordinated with the logic on the DDG side.
|
||||
|
||||
Args:
|
||||
name (str): Name of the device.
|
||||
prefix (str, optional): Prefix for the EPICS PVs. Defaults to "".
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["mcs_recovery"]
|
||||
|
||||
# NOTE The number of MCA channels is fixed to 32 for the CSAXS MCS card.
|
||||
# On the IOC, we receive a 'warning' or 'error' once we set this channel for the
|
||||
# envisioned input/output mode settings of the card. However, we need to know the
|
||||
# channels set as callback timing relies on the channels to be set.
|
||||
# For the future, we may consider adding an initialization parameter to set
|
||||
# the number of channels, which in return limits the number of subscriptions
|
||||
# on the channels. However, mux_output should still be set to 32 on the IOC side.
|
||||
# If this limits performance, this should be investigated with Controls engineers and
|
||||
# the IOC.
|
||||
NUM_MCA_CHANNELS: int = 32
|
||||
|
||||
# MCA counters for the card. Channels 1-32 will be sent to BEC.
|
||||
mca = Cpt(
|
||||
AsyncMultiSignal,
|
||||
name="counters",
|
||||
signals=[
|
||||
f"mca{i}" for i in range(1, 33)
|
||||
], # NOTE Channels 1-32, they need to be in sync with the 'counters' component (DynamicDeviceComponent) of the MCSCard
|
||||
ndim=1,
|
||||
async_update={"type": "add", "max_shape": [None]},
|
||||
max_size=1000,
|
||||
kind=Kind.normal,
|
||||
doc=(
|
||||
"AsyncMultiSignal for MCA card channels 1-32."
|
||||
"Cabling of the MCS card determines which channel corresponds to which input."
|
||||
),
|
||||
)
|
||||
progress = Cpt(ProgressSignal, doc="ProgressSignal indicating the progress of the device")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
prefix: str = "",
|
||||
scan_info: ScanInfo | None = None,
|
||||
device_manager: DeviceManagerBase | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(
|
||||
name=name, prefix=prefix, scan_info=scan_info, device_manager=device_manager, **kwargs
|
||||
)
|
||||
# NOTE MCS Clock frequency. This is linked to the settings of the SIS3820 IOC and
|
||||
# cabeling of the card. Currently, the 'output_mode' is set to MODE_2 and one of the outputs
|
||||
# 6 or 7 (both 10MHz clocks) is used on channel 5 input for the timing signal of the IOC.
|
||||
# Please adjust this comment if the cabling or IOC settings change.
|
||||
self._mcs_clock = 1e7 # 10MHz clock -> 1e7 Hz
|
||||
self._pv_timeout = 2.0 # seconds
|
||||
self._rlock = RLock()
|
||||
|
||||
# NOTE This parameter will be sent with async data of the mcs counters.
|
||||
# Based on scan-paramters, e.g. frames_per_trigger, this will be either
|
||||
# 'monitored' or 'burst_group'. This means whether data from this channel
|
||||
# is in sync with monitored devices or another group. In this scenario,
|
||||
# the other group is called burst_group. Other detectors connected and
|
||||
# triggered through the same timing system should implement the same logic
|
||||
# to allow data to be properly grouped afterwards.
|
||||
self._acquisition_group: str = "monitored" # default value, will be updated in on_stage
|
||||
self._num_total_triggers: int = 0
|
||||
|
||||
# Thread and event logic for monitoring async data emission after scan is done
|
||||
# These are mostly internal variables for which values should not be changed externally.
|
||||
# Adjusting the logic of them should also be handled with care and proper testing.
|
||||
self._scan_done_thread_kill_event: threading.Event = threading.Event()
|
||||
self._start_monitor_async_data_emission: threading.Event = threading.Event()
|
||||
self._scan_done_callbacks: list[Callable[[], None]] = []
|
||||
self._scan_done_thread: threading.Thread = threading.Thread(
|
||||
target=self._monitor_async_data_emission, daemon=True
|
||||
)
|
||||
self._current_data_index: int = 0
|
||||
self._mca_counter_index: int = 0
|
||||
self._current_data: dict[str, dict[Literal["value", "timestamp"], list[int] | float]] = {}
|
||||
self._omit_mca_callbacks: threading.Event = threading.Event()
|
||||
|
||||
def on_connected(self):
|
||||
"""
|
||||
This method is called once the device and all its PVs are connected. Any initial
|
||||
setup of PVs should be managed here. Please be aware that settings of the MCS card
|
||||
correlate with its operation mode, input/output modes, and timing. Changing single
|
||||
parameters without understanding the overall logic may lead to unexpected behavior
|
||||
of the device.Therefore, any modification of these parameters should be handled
|
||||
with care and tested.
|
||||
|
||||
A brief summary of the procesdure that is implemented here:
|
||||
- Stop any ongoing acquisiton.
|
||||
- Setup the Initial initial settings of the MCS card with respective operation modes
|
||||
- Run 'mcs_recovery' procedure to ensure that no pending acquisition data is scheduled
|
||||
to be pushed through mcs channels
|
||||
- Subscribe a callback '_on_counter_update' to mcs counter PVs to forward
|
||||
data through AsyncMultiSignal to BEC
|
||||
- Start the monitoring thread for async data emission after scan is done
|
||||
"""
|
||||
# NOTE Stop any ongoing acquisition first. This shut be done before setting any PVs.
|
||||
self.stop_all.put(1)
|
||||
|
||||
#########################
|
||||
### Setup MCS Card ###
|
||||
#########################
|
||||
# Setup the MCS card settings. Please note that any runtime modification
|
||||
# these parameter may lead to unexpected behavior of the device.
|
||||
# Therefore this has to be set up correctly.
|
||||
self.channel_advance.set(CHANNELADVANCE.EXTERNAL).wait(timeout=self._pv_timeout)
|
||||
self.channel1_source.set(CHANNEL1SOURCE.EXTERNAL).wait(timeout=self._pv_timeout)
|
||||
self.prescale.set(1).wait(timeout=self._pv_timeout)
|
||||
self.user_led.set(0).wait(timeout=self._pv_timeout)
|
||||
|
||||
# NOTE The number of output channels has to be set to NUM_MCA_CHANNELS.
|
||||
# The logic to send data to BEC relies on knowing how many channels are active.
|
||||
self.mux_output.put(self.NUM_MCA_CHANNELS)
|
||||
|
||||
# Set the input and output modes & polarities
|
||||
self.input_mode.set(INPUTMODE.MODE_3).wait(timeout=self._pv_timeout)
|
||||
self.input_polarity.set(POLARITY.NORMAL).wait(timeout=self._pv_timeout)
|
||||
self.output_mode.set(OUTPUTMODE.MODE_2).wait(timeout=self._pv_timeout)
|
||||
self.output_polarity.set(POLARITY.NORMAL).wait(timeout=self._pv_timeout)
|
||||
self.count_on_start.set(0).wait(timeout=self._pv_timeout)
|
||||
|
||||
# NOTE Data is read out when the MCS card finishes an acquisition. The logic for this
|
||||
# is also linked to triggering on the DDG.
|
||||
# Set ReadMode to PASSIVE, the card will wait either wait for readout command or
|
||||
# automatically readout once acquisition is done.
|
||||
self.read_mode.set(READMODE.PASSIVE).wait(timeout=self._pv_timeout)
|
||||
|
||||
# Set the acquire mode
|
||||
self.acquire_mode.set(ACQUIREMODE.MCS).wait(timeout=self._pv_timeout)
|
||||
|
||||
# Subscribe the progress signal
|
||||
self.current_channel.subscribe(self._progress_update, run=False)
|
||||
|
||||
# NOTE: Run a recovery procedure to ensure that the card has no pending data
|
||||
# that needs to be pushed through the mca channels. The procedure involves
|
||||
# stopping any ongoing acquisition and erasing all data on the card. Including
|
||||
# a short sleep to allow the IOC to process the commands.
|
||||
self.mcs_recovery(timeout=1)
|
||||
|
||||
####################################
|
||||
### Setup MCS Subscriptions ###
|
||||
####################################
|
||||
for sig in self.counters.component_names:
|
||||
sig_obj: EpicsSignalRO = getattr(self.counters, sig)
|
||||
sig_obj.subscribe(self._on_counter_update, run=False)
|
||||
|
||||
# Start monitoring thread
|
||||
self._scan_done_thread.start()
|
||||
|
||||
def _on_counter_update(self, value: float | np.ndarray, **kwargs) -> None:
|
||||
"""
|
||||
Callback for counter updates of the mca channels (1-32). This callback is attached
|
||||
to each mca channel PV on the MCS card. It collects data from all channels
|
||||
and once all channels have been updated for a given acquisition, it pushes
|
||||
the data to BEC through the AsyncMultiSignal 'mca'.
|
||||
|
||||
It is important that mux_output is set to the correct number of channels in on_connected,
|
||||
because the callback here waits for updates on all channels before pushing data to BEC.
|
||||
|
||||
The _rlock is used to ensure thread safety as multiple callbacks may be executed
|
||||
simultaneously from different threads.
|
||||
|
||||
If _omit_mca_callbacks is set, the callback will return immediately without processing the
|
||||
data. This is used when erasing all channels to avoid interference with ongoing acquisition.
|
||||
It has to manually cleared after the context manager 'suppress_mca_callbacks' is used.
|
||||
|
||||
Args:
|
||||
value: The new value from the counter PV.
|
||||
**kwargs: Additional keyword arguments from the subscription, including 'obj' (the EpicsSignalRO instance).
|
||||
"""
|
||||
with self._rlock:
|
||||
if self._omit_mca_callbacks.is_set():
|
||||
return # Suppress callbacks when erasing all channels
|
||||
self._mca_counter_index += 1
|
||||
signal: EpicsSignalRO | None = kwargs.get("obj", None)
|
||||
if signal is None:
|
||||
logger.error(f"Called without 'obj' in kwargs: {kwargs}")
|
||||
return
|
||||
|
||||
# NOTE: This relies on the naming convention of the mca channels being 'mca1', 'mca2', ..., 'mca32'.
|
||||
# for the MCSCard class with the 'counters' DynamicDeviceComponent.
|
||||
# Ignore any updates from channels beyond NUM_MCA_CHANNELS
|
||||
attr_name = signal.attr_name
|
||||
index = int(attr_name[3:]) # Extract index from 'mcaX'
|
||||
if index > self.NUM_MCA_CHANNELS:
|
||||
return
|
||||
|
||||
# NOTE Depending on the scan parameters, we may either receive single values or numpy arrays.
|
||||
# Therefore, we need to handle both cases here to ensure that data is always stored. We do
|
||||
# this by converting single values to a list with one element, and numpy arrays to lists.
|
||||
if isinstance(value, np.ndarray):
|
||||
value = value.tolist() # Convert numpy array to list
|
||||
else:
|
||||
value = [value] # Received single value, convert to list
|
||||
|
||||
# Store the value with timestamp. If available in kwargs, use provided timestamp from CA,
|
||||
# otherwise use current time when received.
|
||||
self._current_data.update(
|
||||
{attr_name: {"value": value, "timestamp": kwargs.get("timestamp") or time.time()}}
|
||||
)
|
||||
|
||||
# Once we have received all channels, push data to BEC and reset for next accumulation
|
||||
logger.debug(
|
||||
f"Received update for {attr_name}, index {self._mca_counter_index}/{self.NUM_MCA_CHANNELS}"
|
||||
)
|
||||
if len(self._current_data) == self.NUM_MCA_CHANNELS:
|
||||
logger.debug(
|
||||
f"Current data index {self._current_data_index} complete, pushing to BEC."
|
||||
)
|
||||
self.mca.put(self._current_data, acquisition_group=self._acquisition_group)
|
||||
self._current_data.clear()
|
||||
self._mca_counter_index = 0
|
||||
self._current_data_index += 1
|
||||
|
||||
# NOTE The logic for the device progress is not yet fully refined for all scan types.
|
||||
# This has to be adjusted once fly scan and step scan logic is fully implemented.
|
||||
# pylint: disable=unused-argument
|
||||
def _progress_update(self, *args, old_value: any, value: any, **kwargs) -> None:
|
||||
"""
|
||||
Callback to update the progress signals base on values of current_channel in respect to expected total triggers.
|
||||
Logic for these updates need to be extended once fly and step scan logic is fully implemented.
|
||||
|
||||
Args:
|
||||
old_value: Previous value of the signal.
|
||||
value: New value of the signal.
|
||||
"""
|
||||
scan_done = bool(value == self._num_total_triggers)
|
||||
self.progress.put(value=value, max_value=self._num_total_triggers, done=scan_done)
|
||||
if scan_done:
|
||||
self._scan_done_event.set()
|
||||
|
||||
def on_stage(self) -> None:
|
||||
"""
|
||||
This method is called when the device is staged before a scan. Any bootstrapping required
|
||||
for the scan should be handled here. We also need to handle MCS card specific logic to ensure
|
||||
that the card is properly prepared for the scan.
|
||||
|
||||
The following procedure is implemented here:
|
||||
- Ensure that any ongoing acquisition is stopped (should never happen if not interfered with manually)
|
||||
- Erase all data on the MCS card to ensure a clean start (should never
|
||||
- Set acquisition parameters based on scan parameters (frames_per_trigger, num_points, acquisition_group)
|
||||
- Clear any events and buffers related to async data emission. This includes '_omit_mca_callbacks',
|
||||
'_start_monitor_async_data_emission', '_scan_done_callbacks', and '_current_data'.
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
# NOTE: If for some reason, the card is still acquiring, we need to stop it first
|
||||
# This should never happen as the card is properly stopped during unstage
|
||||
# Can only happen if user manually interferes with the IOC through other means
|
||||
if self.acquiring.get() == ACQUIRING.ACQUIRING:
|
||||
logger.warning(
|
||||
f"MCS Card {self.name} was still acquiring on staging. Stopping acquisition."
|
||||
)
|
||||
self.stop_all.put(1)
|
||||
status = CompareStatus(self.acquiring, ACQUIRING.DONE)
|
||||
status.wait(timeout=10)
|
||||
|
||||
# NOTE: If current_channel != 0, erase all data on the card. This
|
||||
# needs to be done with the 'suppress_mca_callbacks' context manager as erase_all will result
|
||||
# in data emission through mca callback subscriptions.
|
||||
# The buffer needs to be cleared as this will otherwise lead to missing
|
||||
# triggers during the scan. Again, this should not happen if unstage is properly called.
|
||||
# But user interference or a restart of the device_server may lead to this situation.
|
||||
if self.current_channel.get() != 0:
|
||||
with suppress_mca_callbacks(self, restore_after_timeout=1.0):
|
||||
logger.warning(
|
||||
f"MCS Card {self.name} had still data in buffer Erased all data on staging and sleeping for 1 second."
|
||||
)
|
||||
# Erase all data on the MCS card
|
||||
self.erase_all.put(1)
|
||||
|
||||
#####################################
|
||||
### Setup Acquisition Parameters ###
|
||||
#####################################
|
||||
triggers = self.scan_info.msg.scan_parameters.get("frames_per_trigger", 1)
|
||||
num_points = self.scan_info.msg.num_points
|
||||
self._num_total_triggers = triggers * num_points
|
||||
self._acquisition_group = "monitored" if triggers == 1 else "burst_group"
|
||||
self.preset_real.set(0).wait(timeout=self._pv_timeout)
|
||||
self.num_use_all.set(triggers).wait(timeout=self._pv_timeout)
|
||||
|
||||
# Clear any previous data, just to be sure
|
||||
with self._rlock:
|
||||
self._current_data.clear()
|
||||
self._mca_counter_index = 0
|
||||
|
||||
# NOTE Reset events for monitoring async_data_emission thread which is
|
||||
# running during complete to wait for all data from the card
|
||||
# to be emitted to BEC.
|
||||
self._start_monitor_async_data_emission.clear()
|
||||
|
||||
# Clear any previous scan done callbacks
|
||||
self._scan_done_callbacks.clear()
|
||||
|
||||
# Reset counter for data index of emitted data, NOTE for fly scans, this logic may have to be adjusted.
|
||||
self._current_data_index = 0
|
||||
|
||||
# NOTE Make sure that the signal that omits mca callbacks is cleared
|
||||
self._omit_mca_callbacks.clear()
|
||||
|
||||
logger.info(f"MCS Card {self.name} on_stage completed in {time.time() - start_time:.3f}s.")
|
||||
|
||||
def on_unstage(self) -> None:
|
||||
"""
|
||||
Called when the device is unstaged. This method should be omnipotent and resolve fast.
|
||||
It stops any ongoing acquisition, erases all data on the MCS and clears the local buffer '_current_data'.
|
||||
|
||||
NOTE: It is important that the logic for on_complete is solid and properly waiting for mca data to be emitted
|
||||
to BEC. Otherwise, unstage may interfere with ongoing data emission. Unstage is called after complete during scans.
|
||||
It is crucial that the device itself calls '_omit_mca_callbacks' in its on_stage method to make sure
|
||||
that data is emitted once the card is properly staged.
|
||||
"""
|
||||
self.stop_all.put(1)
|
||||
with suppress_mca_callbacks(self):
|
||||
with self._rlock:
|
||||
self._current_data.clear()
|
||||
self._current_data_index = 0
|
||||
self.erase_all.put(1)
|
||||
|
||||
def _monitor_async_data_emission(self) -> None:
|
||||
"""
|
||||
Monitoring loop that runs in a separate thread to check if all async data has been emitted to BEC.
|
||||
It is IDLE most of the time, but activate in the 'on_complete' method called by 'complete'.
|
||||
|
||||
The check is done by comparing the number of data updates '_current_data_index' received through
|
||||
mca channel callbacks with the expected number of points in the scan. Once they match, all
|
||||
callbacks in _scan_done_callbacks are called to indicate that data emission is done.
|
||||
Callbacks need to also accept and handle exceptions to properly report failure.
|
||||
NOTE! This logic currently works for any step scan, but has to be extended for fly scans.
|
||||
"""
|
||||
while not self._scan_done_thread_kill_event.is_set():
|
||||
while self._start_monitor_async_data_emission.wait():
|
||||
try:
|
||||
logger.debug(f"Monitoring async data emission for {self.name}...")
|
||||
if (
|
||||
hasattr(self.scan_info.msg, "num_points")
|
||||
and self.scan_info.msg.num_points is not None
|
||||
):
|
||||
if self._current_data_index == self.scan_info.msg.num_points:
|
||||
for callback in self._scan_done_callbacks:
|
||||
callback(exception=None)
|
||||
time.sleep(0.02) # 20ms delay to avoid busy loop
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
content = traceback.format_exc()
|
||||
logger.error(
|
||||
f"Exception in monitoring thread of complete for {self.name}:\n{content}"
|
||||
"Running callbacks to avoid deadlock."
|
||||
)
|
||||
for callback in self._scan_done_callbacks:
|
||||
callback(exception=exc)
|
||||
|
||||
def _status_callback(self, status: StatusBase, exception=None) -> None:
|
||||
"""Callback for status completion."""
|
||||
self._start_monitor_async_data_emission.clear() # Stop monitoring
|
||||
# NOTE Important check as set_finished or set_exception should not be called
|
||||
# if the status is already done (e.g. cancelled externally)
|
||||
with self._rlock:
|
||||
if status.done:
|
||||
return # Already done and cancelled externally.
|
||||
if exception is not None:
|
||||
status.set_exception(exception)
|
||||
else:
|
||||
status.set_finished()
|
||||
|
||||
def _status_failed_callback(self, status: StatusBase) -> None:
|
||||
"""Callback for status failure, the monitoring thread should be stopped."""
|
||||
# NOTE Check for status.done and status.success is important to avoid
|
||||
if status.done:
|
||||
|
||||
self._start_monitor_async_data_emission.clear() # Stop monitoring
|
||||
|
||||
def on_complete(self) -> CompareStatus:
|
||||
"""
|
||||
|
||||
Method that is called at the end of scan core, but before unstage. This method is
|
||||
used to report whether the device successfully completed its data acquisition for the scan.
|
||||
The check has to be implemented asynchronously and resolve through a status (future) object
|
||||
returned by this method.
|
||||
NOTE: For the MCS card, we need to ensure that all data has been acquired
|
||||
and emitted to BEC as updates after 'on_complete' resolved will be rejected by BEC.
|
||||
Therefore, we need to ensure that all data has been emitted to BEC before
|
||||
reporting completion of the device.
|
||||
|
||||
This method implements the following procedure:
|
||||
- Starts the IDLE async data monitoring thread that checks if all expected data
|
||||
has been emitted to BEC through the mca channel callbacks.
|
||||
- Use a CompareStatus to monitor when the MCS card becomes DONE. Please note that this
|
||||
only indicates that the card has finished acquisition, but not that all data has been
|
||||
emitted to BEC.
|
||||
- Return combined status object. A callback is registered to handle failure of the status
|
||||
if it is stopped externally, e.g. through scan abort. This should ensure that the
|
||||
monitoring thread is stopped properly.
|
||||
|
||||
"""
|
||||
# Prepare and register status callback for the async monitoring loop
|
||||
status_async_data = StatusBase(obj=self)
|
||||
self._scan_done_callbacks.append(partial(self._status_callback, status_async_data))
|
||||
|
||||
# Set the event to start monitoring async data emission
|
||||
logger.debug(f"Starting to monitor async data emission for {self.name}...")
|
||||
self._start_monitor_async_data_emission.set()
|
||||
|
||||
# Add CompareStatus for Acquiring DONE
|
||||
status = CompareStatus(self.acquiring, ACQUIRING.DONE)
|
||||
|
||||
# Combine both statuses
|
||||
ret_status = status & status_async_data
|
||||
# Handle external stop/cancel, and stop monitoring
|
||||
ret_status.add_callback(self._status_failed_callback)
|
||||
self.cancel_on_stop(ret_status)
|
||||
return ret_status
|
||||
|
||||
def on_destroy(self):
|
||||
"""
|
||||
The on destroy hook is called when the device is destroyed, but also reloaded.
|
||||
Here, we need to clean up all resources used up by the device, including running threads.
|
||||
"""
|
||||
self._scan_done_thread_kill_event.set()
|
||||
self._start_monitor_async_data_emission.set()
|
||||
if self._scan_done_thread.is_alive():
|
||||
self._scan_done_thread.join(timeout=2.0)
|
||||
if self._scan_done_thread.is_alive():
|
||||
logger.warning(f"Thread for device {self.name} did not terminate properly.")
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Hook called when the device is stopped. In addition, any status that is registered through cancel_on_stop will be cancelled here."""
|
||||
self.stop_all.put(1)
|
||||
self.erase_all.put(1)
|
||||
|
||||
def mcs_recovery(self, timeout: int = 1) -> None:
|
||||
"""
|
||||
Recovery procedure for the mcs card. This procedure has been empirically found and can
|
||||
be used to ensure that the MCS card is stopped and has no pending data to be emitted.
|
||||
It involves stopping any ongoing acquisition and erasing all data on the card, with
|
||||
a sleep in between to allow the IOC to process the commands.
|
||||
|
||||
Args:
|
||||
timeout (int): Total timeout for the recovery procedure. Defaults to 1 second.
|
||||
"""
|
||||
sleep_time = timeout / 2 # 2 sleeps
|
||||
logger.debug(
|
||||
f"Running recovery procedure for MCS card {self.name} with {sleep_time}s sleep, calling stop_all and erase_all, and another {sleep_time}s sleep"
|
||||
)
|
||||
# First erase and start ongoing acquisition.
|
||||
self.erase_start.put(1)
|
||||
time.sleep(sleep_time)
|
||||
# After a brief processing time, we stop any ongoing acquisition.
|
||||
self.stop_all.put(1)
|
||||
# Finally, we erase all data while suppressing mca callbacks to avoid interference.
|
||||
# We restore the callback suppression after timeout to ensure proper operation afterwards.
|
||||
with suppress_mca_callbacks(self, restore_after_timeout=sleep_time):
|
||||
self.erase_all.put(1)
|
||||
@@ -1,127 +0,0 @@
|
||||
import time
|
||||
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, EpicsSignalRO, Signal
|
||||
|
||||
|
||||
class SumSignal(Signal):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._metadata.update(write_access=False)
|
||||
|
||||
def wait_for_connection(self, timeout=0):
|
||||
super().wait_for_connection(timeout)
|
||||
self._metadata.update(connected=True)
|
||||
|
||||
def get(self, **kwargs):
|
||||
self._metadata["timestamp"] = time.time()
|
||||
val1 = self.parent.current1.get()
|
||||
val2 = self.parent.current2.get()
|
||||
val3 = self.parent.current3.get()
|
||||
val4 = self.parent.current4.get()
|
||||
return val1 + val2 + val3 + val4
|
||||
|
||||
def describe(self):
|
||||
source = [
|
||||
self.parent.current1.describe()[self.parent.current1.name]["source"],
|
||||
self.parent.current2.describe()[self.parent.current2.name]["source"],
|
||||
self.parent.current3.describe()[self.parent.current3.name]["source"],
|
||||
self.parent.current4.describe()[self.parent.current4.name]["source"],
|
||||
]
|
||||
source = " / ".join(source)
|
||||
desc = {
|
||||
"shape": [],
|
||||
"dtype": "number",
|
||||
"source": f"PV: {source}",
|
||||
"units": "",
|
||||
"precision": (
|
||||
self.parent.current1.precision if hasattr(self.parent.current1, "precision") else 0
|
||||
),
|
||||
}
|
||||
return desc
|
||||
|
||||
|
||||
class DiffXYSignal(Signal):
|
||||
def __init__(self, sum1, sum2, *args, **kwargs):
|
||||
self.sum1 = sum1
|
||||
self.sum2 = sum2
|
||||
super().__init__(*args, **kwargs)
|
||||
self._metadata.update(write_access=False)
|
||||
|
||||
def wait_for_connection(self, timeout=0):
|
||||
super().wait_for_connection(timeout)
|
||||
self._metadata.update(connected=True)
|
||||
|
||||
def get(self, **kwargs):
|
||||
self._metadata["timestamp"] = time.time()
|
||||
summed_1 = 0
|
||||
summed_2 = 0
|
||||
for signal in self.sum1:
|
||||
summed_1 += getattr(self.parent, signal).get()
|
||||
for signal in self.sum2:
|
||||
summed_2 += getattr(self.parent, signal).get()
|
||||
|
||||
_sum = summed_1 + summed_2
|
||||
if _sum == 0:
|
||||
return 0.0
|
||||
return (summed_1 - summed_2) / _sum
|
||||
|
||||
def describe(self):
|
||||
source = [
|
||||
getattr(self.parent, signal).describe()[getattr(self.parent, signal).name]["source"]
|
||||
for signal in self.sum1 + self.sum2
|
||||
]
|
||||
source = " / ".join(source)
|
||||
desc = {
|
||||
"shape": [],
|
||||
"dtype": "number",
|
||||
"source": f"PV: {source}",
|
||||
"units": "",
|
||||
"precision": (
|
||||
self.parent.current1.precision if hasattr(self.parent.current1, "precision") else 0
|
||||
),
|
||||
}
|
||||
return desc
|
||||
|
||||
|
||||
class BPMDevice(Device):
|
||||
current1 = Cpt(
|
||||
EpicsSignalRO, ":Current1:MeanValue_RBV", kind="normal", doc="Current 1", auto_monitor=True
|
||||
)
|
||||
current2 = Cpt(
|
||||
EpicsSignalRO, ":Current2:MeanValue_RBV", kind="normal", doc="Current 2", auto_monitor=True
|
||||
)
|
||||
current3 = Cpt(
|
||||
EpicsSignalRO, ":Current3:MeanValue_RBV", kind="normal", doc="Current 3", auto_monitor=True
|
||||
)
|
||||
current4 = Cpt(
|
||||
EpicsSignalRO, ":Current4:MeanValue_RBV", kind="normal", doc="Current 4", auto_monitor=True
|
||||
)
|
||||
sum = Cpt(SumSignal, kind="hinted", doc="Sum of all currents")
|
||||
x = Cpt(
|
||||
DiffXYSignal,
|
||||
sum1=["current1", "current2"],
|
||||
sum2=["current3", "current4"],
|
||||
doc="X difference signal",
|
||||
)
|
||||
y = Cpt(
|
||||
DiffXYSignal,
|
||||
sum1=["current1", "current3"],
|
||||
sum2=["current2", "current4"],
|
||||
doc="Y difference signal",
|
||||
)
|
||||
diag = Cpt(
|
||||
DiffXYSignal,
|
||||
sum1=["current1", "current4"],
|
||||
sum2=["current2", "current3"],
|
||||
doc="Diagonal difference signal",
|
||||
)
|
||||
|
||||
def __init__(self, prefix="", *args, **kwargs):
|
||||
super().__init__(*args, prefix=prefix, **kwargs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
dev = BPMDevice(name="bpm", prefix="X12SA-FE-XBPM1")
|
||||
dev.wait_for_connection()
|
||||
print(dev.read())
|
||||
@@ -1,4 +1,4 @@
|
||||
from .fgalil_ophyd import FlomniGalilController, FlomniGalilMotor
|
||||
from .fupr_ophyd import FuprGalilController, FuprGalilMotor
|
||||
from .lgalil_ophyd import LamniGalilController, LamniGalilMotor
|
||||
from .galil_ophyd import GalilController, GalilMotor
|
||||
from .sgalil_ophyd import SGalilMotor
|
||||
|
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 157 KiB |
@@ -10,7 +10,7 @@ from ophyd.utils import LimitError
|
||||
from ophyd_devices.utils.controller import threadlocked
|
||||
from ophyd_devices.utils.socket import SocketIO, raise_if_disconnected
|
||||
|
||||
from csaxs_bec.devices.omny.galil.galil_ophyd import (
|
||||
from csaxs_bec.devices.galil.galil_ophyd import (
|
||||
BECConfigError,
|
||||
GalilAxesReferenced,
|
||||
GalilController,
|
||||
@@ -141,7 +141,7 @@ class FlomniGalilAxesReferenced(GalilAxesReferenced):
|
||||
|
||||
|
||||
class FlomniGalilMotor(Device, PositionerBase):
|
||||
USER_ACCESS = ["controller", "drive_axis_to_limit"]
|
||||
USER_ACCESS = ["controller"]
|
||||
readback = Cpt(FlomniGalilReadbackSignal, signal_name="readback", kind="hinted")
|
||||
user_setpoint = Cpt(FlomniGalilSetpointSignal, signal_name="setpoint")
|
||||
motor_resolution = Cpt(FlomniGalilMotorResolution, signal_name="resolution", kind="config")
|
||||
@@ -175,7 +175,7 @@ class FlomniGalilMotor(Device, PositionerBase):
|
||||
**kwargs,
|
||||
):
|
||||
self.controller = FlomniGalilController(
|
||||
socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager
|
||||
socket_cls=socket_cls, socket_host=host, socket_port=port
|
||||
)
|
||||
self.axis_Id = axis_Id
|
||||
self.controller.set_axis(axis=self, axis_nr=self.axis_Id_numeric)
|
||||
@@ -212,9 +212,6 @@ class FlomniGalilMotor(Device, PositionerBase):
|
||||
self.low_limit_travel.put(limits[0])
|
||||
self.high_limit_travel.put(limits[1])
|
||||
|
||||
def wait_for_connection(self, all_signals=False, timeout: float = 30.0) -> bool:
|
||||
self.controller.on(timeout=timeout)
|
||||
|
||||
@property
|
||||
def limits(self):
|
||||
return (self.low_limit_travel.get(), self.high_limit_travel.get())
|
||||
@@ -340,18 +337,6 @@ class FlomniGalilMotor(Device, PositionerBase):
|
||||
def unstage(self) -> list[object]:
|
||||
return super().unstage()
|
||||
|
||||
def drive_axis_to_limit(self, direction: str) -> None:
|
||||
"""
|
||||
Drive an axis to the limit in a specified direction.
|
||||
|
||||
Args:
|
||||
direction (str): Direction in which the axis should be driven to the limit. Either 'forward' or 'reverse'.
|
||||
"""
|
||||
self.controller.drive_axis_to_limit(self.axis_Id_numeric, direction)
|
||||
# now force position read to cache
|
||||
val = self.readback.read()
|
||||
self._run_subs(sub_type=self.SUB_READBACK, value=val, timestamp=time.time())
|
||||
|
||||
def stop(self, *, success=False):
|
||||
self.controller.stop_all_axes()
|
||||
return super().stop(success=success)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user