Compare commits
37 Commits
refactor/m
...
feat/enhan
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db47f72c0e | ||
|
|
8808996c71 | ||
|
|
03e6cf3451 | ||
| 09425f7651 | |||
| 821917f7f8 | |||
| b210f1cb50 | |||
| e2ab447c8d | |||
| 648fac1c38 | |||
|
|
c67aac242a | ||
| 9615552f40 | |||
|
|
625651247c | ||
|
|
055542c216 | ||
| 24ddb30cad | |||
|
|
bbb7dfe48c | ||
| 6d1cd1f488 | |||
| 1256b603de | |||
|
4d69f8f90f
|
|||
| 0f072a786e | |||
| 05a1e3d8be | |||
| e9fd9084b8 | |||
| 40ef387134 | |||
|
|
6ed84664f2 | ||
|
|
e5e3343da7 | ||
|
|
c8866faccc | ||
|
|
3b561c251c | ||
|
|
bc187040ad | ||
|
|
efd27a27e8 | ||
|
|
7096ef3323 | ||
| 13378f24dd | |||
|
|
f5b898ea1c
|
||
|
3d62bea04b
|
|||
|
1518845d25
|
|||
|
ff3b6686db
|
|||
| afdc64e296 | |||
| bc31c00e1f | |||
|
|
38671f074e | ||
|
|
92e39a5f75 |
@@ -2,7 +2,7 @@
|
|||||||
# It is needed to track the repo template version, and editing may break things.
|
# It is needed to track the repo template version, and editing may break things.
|
||||||
# This file will be overwritten by copier on template updates.
|
# This file will be overwritten by copier on template updates.
|
||||||
|
|
||||||
_commit: v1.2.2
|
_commit: v1.2.8
|
||||||
_src_path: https://github.com/bec-project/plugin_copier_template.git
|
_src_path: https://github.com/bec-project/plugin_copier_template.git
|
||||||
make_commit: false
|
make_commit: false
|
||||||
project_name: csaxs_bec
|
project_name: csaxs_bec
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ on:
|
|||||||
description: "Python version to use"
|
description: "Python version to use"
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
default: "3.11"
|
default: "3.12"
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
pull-requests: write
|
||||||
@@ -44,7 +44,19 @@ jobs:
|
|||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "${{ inputs.PYTHON_VERSION || '3.11' }}"
|
python-version: "${{ inputs.PYTHON_VERSION || '3.12' }}"
|
||||||
|
|
||||||
|
- 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: Lint for merge conflicts from template updates
|
||||||
|
shell: bash
|
||||||
|
# Find all Copier conflicts except this line
|
||||||
|
run: '! grep -r "<<<<<<< before updating" | grep -v "grep -r \"<<<<<<< before updating"'
|
||||||
|
|
||||||
- name: Checkout BEC Core
|
- name: Checkout BEC Core
|
||||||
run: git clone --depth 1 --branch "${{ inputs.BEC_CORE_BRANCH || 'main' }}" https://github.com/bec-project/bec.git ./bec
|
run: git clone --depth 1 --branch "${{ inputs.BEC_CORE_BRANCH || 'main' }}" https://github.com/bec-project/bec.git ./bec
|
||||||
@@ -55,13 +67,6 @@ jobs:
|
|||||||
- name: Checkout BEC Widgets
|
- 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
|
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
|
- name: Install dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
62
.gitea/workflows/create_update_pr.yml
Normal file
62
.gitea/workflows/create_update_pr.yml
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
name: Create template upgrade PR for csaxs_bec
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create_update_branch_and_pr:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Setup Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
|
||||||
|
- name: Install tools
|
||||||
|
run: |
|
||||||
|
pip install copier PySide6
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Perform update
|
||||||
|
run: |
|
||||||
|
git config --global user.email "bec_ci_staging@psi.ch"
|
||||||
|
git config --global user.name "BEC automated CI"
|
||||||
|
|
||||||
|
branch="chore/update-template-$(python -m uuid)"
|
||||||
|
echo "switching to branch $branch"
|
||||||
|
git checkout -b $branch
|
||||||
|
|
||||||
|
echo "Running copier update..."
|
||||||
|
output="$(copier update --trust --defaults --conflict inline 2>&1)"
|
||||||
|
echo "$output"
|
||||||
|
msg="$(printf '%s\n' "$output" | head -n 1)"
|
||||||
|
|
||||||
|
if ! grep -q "make_commit: true" .copier-answers.yml ; then
|
||||||
|
echo "Autocommit not made, committing..."
|
||||||
|
git add -A
|
||||||
|
git commit -a -m "$msg"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if diff-index --quiet HEAD ; then
|
||||||
|
echo "No changes detected"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
git push -u origin $branch
|
||||||
|
curl -X POST "https://gitea.psi.ch/api/v1/repos/${{ gitea.repository }}/pulls" \
|
||||||
|
-H "Authorization: token ${{ secrets.CI_REPO_WRITE }}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"title\": \"Template: $(echo $msg)\",
|
||||||
|
\"body\": \"This PR was created by Gitea Actions\",
|
||||||
|
\"head\": \"$(echo $branch)\",
|
||||||
|
\"base\": \"main\"
|
||||||
|
}"
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
include:
|
|
||||||
- project: bec/awi_utils
|
|
||||||
file: /templates/plugin-repo-template.yml
|
|
||||||
inputs:
|
|
||||||
name: "csaxs"
|
|
||||||
target: "csaxs_bec"
|
|
||||||
branch: $CHILD_PIPELINE_BRANCH
|
|
||||||
|
|
||||||
pages:
|
|
||||||
stage: Deploy
|
|
||||||
needs: []
|
|
||||||
variables:
|
|
||||||
TARGET_BRANCH: $CI_COMMIT_REF_NAME
|
|
||||||
rules:
|
|
||||||
- if: "$CI_COMMIT_TAG != null"
|
|
||||||
variables:
|
|
||||||
TARGET_BRANCH: $CI_COMMIT_TAG
|
|
||||||
- if: '$CI_COMMIT_REF_NAME == "main" && $CI_PROJECT_PATH == "bec/csaxs_bec"'
|
|
||||||
script:
|
|
||||||
- curl -X POST -d "branches=$CI_COMMIT_REF_NAME" -d "token=$RTD_TOKEN" https://readthedocs.org/api/v2/webhook/sls-csaxs/270162/
|
|
||||||
@@ -210,13 +210,11 @@ class LamNI(LamNIOpticsMixin):
|
|||||||
self.feedback_status()
|
self.feedback_status()
|
||||||
|
|
||||||
def feedback_status(self):
|
def feedback_status(self):
|
||||||
if self.device_manager.devices.rtx.controller.feedback_is_running():
|
self.device_manager.devices.rtx.controller.show_feedback_status()
|
||||||
print("The rt feedback is \x1b[92mrunning\x1b[0m.")
|
|
||||||
else:
|
|
||||||
print("The rt feedback is \x1b[91mNOT\x1b[0m running.")
|
|
||||||
|
|
||||||
def show_interferometer_positions(self):
|
def show_interferometer_positions(self):
|
||||||
self.device_manager.devices.rtx.controller.show_interferometer_positions()
|
self.device_manager.devices.rtx.controller.show_feedback_status()
|
||||||
|
|
||||||
def show_signal_strength(self):
|
def show_signal_strength(self):
|
||||||
self.device_manager.devices.rtx.controller.show_signal_strength_interferometer()
|
self.device_manager.devices.rtx.controller.show_signal_strength_interferometer()
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_put, fshclose
|
|||||||
from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import OMNYTools
|
from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import OMNYTools
|
||||||
|
|
||||||
dev = builtins.__dict__.get("dev")
|
dev = builtins.__dict__.get("dev")
|
||||||
umv = builtins.__dict__.get("umv")
|
|
||||||
bec = builtins.__dict__.get("bec")
|
bec = builtins.__dict__.get("bec")
|
||||||
|
scans = builtins.__dict__.get("scans")
|
||||||
|
|
||||||
|
def umv(*args):
|
||||||
|
return scans.umv(*args, relative=False)
|
||||||
|
|
||||||
class LamNIInitError(Exception):
|
class LamNIInitError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|||||||
442
csaxs_bec/bec_ipython_client/plugins/cSAXS/cSAXSDLPCA200.py
Normal file
442
csaxs_bec/bec_ipython_client/plugins/cSAXS/cSAXSDLPCA200.py
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
"""
|
||||||
|
csaxs_dlpca200.py
|
||||||
|
=================
|
||||||
|
BEC control script for FEMTO DLPCA-200 Variable Gain Low Noise Current Amplifiers
|
||||||
|
connected to Galil RIO digital outputs.
|
||||||
|
|
||||||
|
DLPCA-200 Remote Control (datasheet page 4)
|
||||||
|
-------------------------------------------
|
||||||
|
Sub-D pin -> function:
|
||||||
|
Pin 10 -> gain LSB (digital out channel, index 0 in bit-tuple)
|
||||||
|
Pin 11 -> gain MID (digital out channel, index 1 in bit-tuple)
|
||||||
|
Pin 12 -> gain MSB (digital out channel, index 2 in bit-tuple)
|
||||||
|
Pin 13 -> coupling LOW = AC, HIGH = DC
|
||||||
|
Pin 14 -> speed mode HIGH = low noise (Pin14=1), LOW = high speed (Pin14=0)
|
||||||
|
|
||||||
|
Gain truth table (MSB, MID, LSB):
|
||||||
|
0,0,0 -> low-noise: 1e3 high-speed: 1e5
|
||||||
|
0,0,1 -> low-noise: 1e4 high-speed: 1e6
|
||||||
|
0,1,0 -> low-noise: 1e5 high-speed: 1e7
|
||||||
|
0,1,1 -> low-noise: 1e6 high-speed: 1e8
|
||||||
|
1,0,0 -> low-noise: 1e7 high-speed: 1e9
|
||||||
|
1,0,1 -> low-noise: 1e8 high-speed: 1e10
|
||||||
|
1,1,0 -> low-noise: 1e9 high-speed: 1e11
|
||||||
|
|
||||||
|
Strategy: prefer low-noise mode (1e3-1e9). For 1e10 and 1e11,
|
||||||
|
automatically fall back to high-speed mode.
|
||||||
|
|
||||||
|
Device wiring example (galilrioesxbox):
|
||||||
|
bpm4: Pin10->ch0, Pin11->ch1, Pin12->ch2, Pin13->ch3, Pin14->ch4
|
||||||
|
bim: Pin10->ch6, Pin11->ch7, Pin12->ch8, Pin13->ch9, Pin14->ch10
|
||||||
|
|
||||||
|
Usage examples
|
||||||
|
--------------
|
||||||
|
csaxs_amp = cSAXSDLPCA200(client)
|
||||||
|
|
||||||
|
csaxs_amp.set_gain("bpm4", 1e7) # low-noise if possible
|
||||||
|
csaxs_amp.set_gain("bim", 1e10) # auto high-speed
|
||||||
|
csaxs_amp.set_coupling("bpm4", "DC")
|
||||||
|
csaxs_amp.set_coupling("bim", "AC")
|
||||||
|
csaxs_amp.info("bpm4") # print current settings
|
||||||
|
csaxs_amp.info_all() # print all configured amplifiers
|
||||||
|
"""
|
||||||
|
|
||||||
|
import builtins
|
||||||
|
|
||||||
|
from bec_lib import bec_logger
|
||||||
|
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
bec = builtins.__dict__.get("bec")
|
||||||
|
dev = builtins.__dict__.get("dev")
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Amplifier registry
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Each entry describes one DLPCA-200 amplifier connected to a Galil RIO.
|
||||||
|
#
|
||||||
|
# Keys inside "channels":
|
||||||
|
# gain_lsb -> digital output channel number wired to DLPCA-200 Pin 10
|
||||||
|
# gain_mid -> digital output channel number wired to DLPCA-200 Pin 11
|
||||||
|
# gain_msb -> digital output channel number wired to DLPCA-200 Pin 12
|
||||||
|
# coupling -> digital output channel number wired to DLPCA-200 Pin 13
|
||||||
|
# speed_mode -> digital output channel number wired to DLPCA-200 Pin 14
|
||||||
|
#
|
||||||
|
# To add a new amplifier, simply extend this dict.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
DLPCA200_AMPLIFIER_CONFIG: dict[str, dict] = {
|
||||||
|
"bpm4": {
|
||||||
|
"rio_device": "galilrioesxbox",
|
||||||
|
"description": "Beam Position Monitor 4 current amplifier",
|
||||||
|
"channels": {
|
||||||
|
"gain_lsb": 0, # Pin 10 -> Galil ch0
|
||||||
|
"gain_mid": 1, # Pin 11 -> Galil ch1
|
||||||
|
"gain_msb": 2, # Pin 12 -> Galil ch2
|
||||||
|
"coupling": 3, # Pin 13 -> Galil ch3
|
||||||
|
"speed_mode": 4, # Pin 14 -> Galil ch4
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"bim": {
|
||||||
|
"rio_device": "galilrioesxbox",
|
||||||
|
"description": "Beam Intensity Monitor current amplifier",
|
||||||
|
"channels": {
|
||||||
|
"gain_lsb": 6, # Pin 10 -> Galil ch6
|
||||||
|
"gain_mid": 7, # Pin 11 -> Galil ch7
|
||||||
|
"gain_msb": 8, # Pin 12 -> Galil ch8
|
||||||
|
"coupling": 9, # Pin 13 -> Galil ch9
|
||||||
|
"speed_mode": 10, # Pin 14 -> Galil ch10
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# DLPCA-200 gain encoding tables
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# (msb, mid, lsb) -> gain in V/A
|
||||||
|
_GAIN_BITS_LOW_NOISE: dict[tuple, int] = {
|
||||||
|
(0, 0, 0): int(1e3),
|
||||||
|
(0, 0, 1): int(1e4),
|
||||||
|
(0, 1, 0): int(1e5),
|
||||||
|
(0, 1, 1): int(1e6),
|
||||||
|
(1, 0, 0): int(1e7),
|
||||||
|
(1, 0, 1): int(1e8),
|
||||||
|
(1, 1, 0): int(1e9),
|
||||||
|
}
|
||||||
|
|
||||||
|
_GAIN_BITS_HIGH_SPEED: dict[tuple, int] = {
|
||||||
|
(0, 0, 0): int(1e5),
|
||||||
|
(0, 0, 1): int(1e6),
|
||||||
|
(0, 1, 0): int(1e7),
|
||||||
|
(0, 1, 1): int(1e8),
|
||||||
|
(1, 0, 0): int(1e9),
|
||||||
|
(1, 0, 1): int(1e10),
|
||||||
|
(1, 1, 0): int(1e11),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Inverse maps: gain -> (msb, mid, lsb, low_noise_flag)
|
||||||
|
# low_noise_flag: True = Pin14 HIGH, False = Pin14 LOW
|
||||||
|
_GAIN_TO_BITS: dict[int, tuple] = {}
|
||||||
|
for _bits, _gain in _GAIN_BITS_LOW_NOISE.items():
|
||||||
|
_GAIN_TO_BITS[_gain] = (*_bits, True)
|
||||||
|
for _bits, _gain in _GAIN_BITS_HIGH_SPEED.items():
|
||||||
|
if _gain not in _GAIN_TO_BITS: # low-noise takes priority
|
||||||
|
_GAIN_TO_BITS[_gain] = (*_bits, False)
|
||||||
|
|
||||||
|
VALID_GAINS = sorted(_GAIN_TO_BITS.keys())
|
||||||
|
|
||||||
|
|
||||||
|
class cSAXSDLPCA200Error(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class cSAXSDLPCA200:
|
||||||
|
"""
|
||||||
|
Control class for FEMTO DLPCA-200 current amplifiers connected via Galil RIO
|
||||||
|
digital outputs in a BEC environment.
|
||||||
|
|
||||||
|
Supports:
|
||||||
|
- Forward control: set_gain(), set_coupling()
|
||||||
|
- Readback reporting: info(), info_all(), read_settings()
|
||||||
|
- Robust error handling and logging following cSAXS conventions.
|
||||||
|
"""
|
||||||
|
|
||||||
|
TAG = "[DLPCA200]"
|
||||||
|
|
||||||
|
def __init__(self, client, config: dict | None = None) -> None:
|
||||||
|
"""
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
client : BEC client object (passed through for future use)
|
||||||
|
config : optional override for DLPCA200_AMPLIFIER_CONFIG.
|
||||||
|
Falls back to the module-level dict if not provided.
|
||||||
|
"""
|
||||||
|
self.client = client
|
||||||
|
self._config: dict[str, dict] = config if config is not None else DLPCA200_AMPLIFIER_CONFIG
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Internal helpers
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _require_dev(self) -> None:
|
||||||
|
if dev is None:
|
||||||
|
raise cSAXSDLPCA200Error(
|
||||||
|
f"{self.TAG} BEC 'dev' namespace is not available in this session."
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_cfg(self, amp_name: str) -> dict:
|
||||||
|
"""Return config dict for a named amplifier, raising on unknown names."""
|
||||||
|
if amp_name not in self._config:
|
||||||
|
known = ", ".join(sorted(self._config.keys()))
|
||||||
|
raise cSAXSDLPCA200Error(f"{self.TAG} Unknown amplifier '{amp_name}'. Known: [{known}]")
|
||||||
|
return self._config[amp_name]
|
||||||
|
|
||||||
|
def _get_rio(self, amp_name: str):
|
||||||
|
"""Return the live RIO device object for a given amplifier."""
|
||||||
|
self._require_dev()
|
||||||
|
cfg = self._get_cfg(amp_name)
|
||||||
|
rio_name = cfg["rio_device"]
|
||||||
|
try:
|
||||||
|
rio = getattr(dev, rio_name)
|
||||||
|
except AttributeError:
|
||||||
|
raise cSAXSDLPCA200Error(f"{self.TAG} RIO device '{rio_name}' not found in BEC 'dev'.")
|
||||||
|
return rio
|
||||||
|
|
||||||
|
def _dout_get(self, rio, ch: int) -> int:
|
||||||
|
"""Read one digital output channel (returns 0 or 1)."""
|
||||||
|
attr = getattr(rio.digital_out, f"ch{ch}")
|
||||||
|
val = attr.get()
|
||||||
|
return int(val)
|
||||||
|
|
||||||
|
def _dout_set(self, rio, ch: int, value: bool) -> None:
|
||||||
|
"""Write one digital output channel (True=HIGH=1, False=LOW=0)."""
|
||||||
|
attr = getattr(rio.digital_out, f"ch{ch}")
|
||||||
|
attr.set(value)
|
||||||
|
|
||||||
|
def _read_gain_bits(self, amp_name: str) -> tuple[int, int, int, int]:
|
||||||
|
"""
|
||||||
|
Read current gain bit-state from hardware.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
(msb, mid, lsb, speed_mode)
|
||||||
|
speed_mode: 1 = low-noise (Pin14=HIGH), 0 = high-speed (Pin14=LOW)
|
||||||
|
"""
|
||||||
|
rio = self._get_rio(amp_name)
|
||||||
|
ch = self._get_cfg(amp_name)["channels"]
|
||||||
|
msb = self._dout_get(rio, ch["gain_msb"])
|
||||||
|
mid = self._dout_get(rio, ch["gain_mid"])
|
||||||
|
lsb = self._dout_get(rio, ch["gain_lsb"])
|
||||||
|
speed_mode = self._dout_get(rio, ch["speed_mode"])
|
||||||
|
return msb, mid, lsb, speed_mode
|
||||||
|
|
||||||
|
def _decode_gain(self, msb: int, mid: int, lsb: int, speed_mode: int) -> int | None:
|
||||||
|
"""
|
||||||
|
Decode hardware bit-state into gain value (V/A).
|
||||||
|
|
||||||
|
speed_mode=1 -> low-noise table, speed_mode=0 -> high-speed table.
|
||||||
|
Returns None if the bit combination is not in the table.
|
||||||
|
"""
|
||||||
|
bits = (msb, mid, lsb)
|
||||||
|
if speed_mode:
|
||||||
|
return _GAIN_BITS_LOW_NOISE.get(bits)
|
||||||
|
else:
|
||||||
|
return _GAIN_BITS_HIGH_SPEED.get(bits)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Public API - control
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def set_gain(self, amp_name: str, gain: float, force_high_speed: bool = False) -> None:
|
||||||
|
"""
|
||||||
|
Set the transimpedance gain of a DLPCA-200 amplifier.
|
||||||
|
|
||||||
|
The method automatically selects low-noise mode (Pin14=HIGH) whenever
|
||||||
|
the requested gain is achievable in low-noise mode (1e3 - 1e9 V/A).
|
||||||
|
For gains of 1e10 and 1e11 V/A, high-speed mode is used automatically.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
amp_name : str
|
||||||
|
Amplifier name as defined in DLPCA200_AMPLIFIER_CONFIG (e.g. "bpm4").
|
||||||
|
gain : float or int
|
||||||
|
Target gain in V/A. Must be one of:
|
||||||
|
1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11.
|
||||||
|
force_high_speed : bool, optional
|
||||||
|
If True, force high-speed (low-noise=False) mode even for gains
|
||||||
|
below 1e10. Default: False (prefer low-noise).
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
csaxs_amp.set_gain("bpm4", 1e7) # low-noise mode (automatic)
|
||||||
|
csaxs_amp.set_gain("bim", 1e10) # high-speed mode (automatic)
|
||||||
|
csaxs_amp.set_gain("bpm4", 1e7, force_high_speed=True) # override to high-speed
|
||||||
|
"""
|
||||||
|
gain_int = int(gain)
|
||||||
|
if gain_int not in _GAIN_TO_BITS:
|
||||||
|
valid_str = ", ".join(
|
||||||
|
f"1e{int(round(__import__('math').log10(g)))}" for g in VALID_GAINS
|
||||||
|
)
|
||||||
|
raise cSAXSDLPCA200Error(
|
||||||
|
f"{self.TAG} Invalid gain {gain:.2e} V/A for '{amp_name}'. "
|
||||||
|
f"Valid values: {valid_str}"
|
||||||
|
)
|
||||||
|
|
||||||
|
msb, mid, lsb, low_noise_preferred = _GAIN_TO_BITS[gain_int]
|
||||||
|
|
||||||
|
# Apply force_high_speed override
|
||||||
|
if force_high_speed and low_noise_preferred:
|
||||||
|
# Check if this gain is achievable in high-speed mode
|
||||||
|
hs_entry = next(
|
||||||
|
(bits for bits, g in _GAIN_BITS_HIGH_SPEED.items() if g == gain_int), None
|
||||||
|
)
|
||||||
|
if hs_entry is None:
|
||||||
|
raise cSAXSDLPCA200Error(
|
||||||
|
f"{self.TAG} Gain {gain:.2e} V/A is not achievable in high-speed mode "
|
||||||
|
f"for '{amp_name}'."
|
||||||
|
)
|
||||||
|
msb, mid, lsb = hs_entry
|
||||||
|
low_noise_preferred = False
|
||||||
|
|
||||||
|
use_low_noise = low_noise_preferred and not force_high_speed
|
||||||
|
|
||||||
|
try:
|
||||||
|
rio = self._get_rio(amp_name)
|
||||||
|
ch = self._get_cfg(amp_name)["channels"]
|
||||||
|
|
||||||
|
self._dout_set(rio, ch["gain_msb"], bool(msb))
|
||||||
|
self._dout_set(rio, ch["gain_mid"], bool(mid))
|
||||||
|
self._dout_set(rio, ch["gain_lsb"], bool(lsb))
|
||||||
|
self._dout_set(rio, ch["speed_mode"], use_low_noise) # True=low-noise
|
||||||
|
|
||||||
|
mode_str = "low-noise" if use_low_noise else "high-speed"
|
||||||
|
logger.info(
|
||||||
|
f"{self.TAG} [{amp_name}] gain set to {gain_int:.2e} V/A "
|
||||||
|
f"({mode_str} mode, bits MSB={msb} MID={mid} LSB={lsb})"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
f"{amp_name}: gain -> {gain_int:.2e} V/A [{mode_str}] "
|
||||||
|
f"(bits: MSB={msb} MID={mid} LSB={lsb})"
|
||||||
|
)
|
||||||
|
|
||||||
|
except cSAXSDLPCA200Error:
|
||||||
|
raise
|
||||||
|
except Exception as exc:
|
||||||
|
raise cSAXSDLPCA200Error(
|
||||||
|
f"{self.TAG} Failed to set gain on '{amp_name}': {exc}"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
def set_coupling(self, amp_name: str, coupling: str) -> None:
|
||||||
|
"""
|
||||||
|
Set AC or DC coupling on a DLPCA-200 amplifier.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
amp_name : str
|
||||||
|
Amplifier name (e.g. "bpm4", "bim").
|
||||||
|
coupling : str
|
||||||
|
"AC" or "DC" (case-insensitive).
|
||||||
|
DC -> Pin13 HIGH, AC -> Pin13 LOW.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
--------
|
||||||
|
csaxs_amp.set_coupling("bpm4", "DC")
|
||||||
|
csaxs_amp.set_coupling("bim", "AC")
|
||||||
|
"""
|
||||||
|
coupling_upper = coupling.strip().upper()
|
||||||
|
if coupling_upper not in ("AC", "DC"):
|
||||||
|
raise cSAXSDLPCA200Error(
|
||||||
|
f"{self.TAG} Invalid coupling '{coupling}' for '{amp_name}'. " f"Use 'AC' or 'DC'."
|
||||||
|
)
|
||||||
|
|
||||||
|
pin13_high = coupling_upper == "DC"
|
||||||
|
|
||||||
|
try:
|
||||||
|
rio = self._get_rio(amp_name)
|
||||||
|
ch = self._get_cfg(amp_name)["channels"]
|
||||||
|
self._dout_set(rio, ch["coupling"], pin13_high)
|
||||||
|
|
||||||
|
logger.info(f"{self.TAG} [{amp_name}] coupling set to {coupling_upper}")
|
||||||
|
print(f"{amp_name}: coupling -> {coupling_upper}")
|
||||||
|
|
||||||
|
except cSAXSDLPCA200Error:
|
||||||
|
raise
|
||||||
|
except Exception as exc:
|
||||||
|
raise cSAXSDLPCA200Error(
|
||||||
|
f"{self.TAG} Failed to set coupling on '{amp_name}': {exc}"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Public API - readback / reporting
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
|
def read_settings(self, amp_name: str) -> dict:
|
||||||
|
"""
|
||||||
|
Read back the current settings from hardware digital outputs.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
dict with keys:
|
||||||
|
"amp_name" : str
|
||||||
|
"gain" : int or None - gain in V/A (None if unknown bit pattern)
|
||||||
|
"mode" : str - "low-noise" or "high-speed"
|
||||||
|
"coupling" : str - "AC" or "DC"
|
||||||
|
"bits" : dict - raw bit values {msb, mid, lsb, speed_mode, coupling}
|
||||||
|
"""
|
||||||
|
rio = self._get_rio(amp_name)
|
||||||
|
ch = self._get_cfg(amp_name)["channels"]
|
||||||
|
|
||||||
|
msb = self._dout_get(rio, ch["gain_msb"])
|
||||||
|
mid = self._dout_get(rio, ch["gain_mid"])
|
||||||
|
lsb = self._dout_get(rio, ch["gain_lsb"])
|
||||||
|
speed_mode = self._dout_get(rio, ch["speed_mode"])
|
||||||
|
coupling_bit = self._dout_get(rio, ch["coupling"])
|
||||||
|
|
||||||
|
gain = self._decode_gain(msb, mid, lsb, speed_mode)
|
||||||
|
mode = "low-noise" if speed_mode else "high-speed"
|
||||||
|
coupling = "DC" if coupling_bit else "AC"
|
||||||
|
|
||||||
|
return {
|
||||||
|
"amp_name": amp_name,
|
||||||
|
"gain": gain,
|
||||||
|
"mode": mode,
|
||||||
|
"coupling": coupling,
|
||||||
|
"bits": {
|
||||||
|
"msb": msb,
|
||||||
|
"mid": mid,
|
||||||
|
"lsb": lsb,
|
||||||
|
"speed_mode": speed_mode,
|
||||||
|
"coupling": coupling_bit,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
def info(self, amp_name: str) -> None:
|
||||||
|
"""
|
||||||
|
Print a plain summary of the current settings for one amplifier.
|
||||||
|
|
||||||
|
Example output
|
||||||
|
--------------
|
||||||
|
Amplifier : bpm4
|
||||||
|
Description : Beam Position Monitor 4 current amplifier
|
||||||
|
RIO device : galilrioesxbox
|
||||||
|
Gain : 1.00e+07 V/A
|
||||||
|
Mode : low-noise
|
||||||
|
Coupling : DC
|
||||||
|
Raw bits : MSB=1 MID=0 LSB=0 speed=1 coup=1
|
||||||
|
"""
|
||||||
|
cfg = self._get_cfg(amp_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
s = self.read_settings(amp_name)
|
||||||
|
except Exception as exc:
|
||||||
|
print(f"{self.TAG} [{amp_name}] Could not read settings: {exc}")
|
||||||
|
return
|
||||||
|
|
||||||
|
gain_str = (
|
||||||
|
f"{s['gain']:.2e} V/A" if s["gain"] is not None else "UNKNOWN (invalid bit pattern)"
|
||||||
|
)
|
||||||
|
bits = s["bits"]
|
||||||
|
|
||||||
|
print(f" {'Amplifier':<12}: {amp_name}")
|
||||||
|
print(f" {'Description':<12}: {cfg.get('description', '')}")
|
||||||
|
print(f" {'RIO device':<12}: {cfg['rio_device']}")
|
||||||
|
print(f" {'Gain':<12}: {gain_str}")
|
||||||
|
print(f" {'Mode':<12}: {s['mode']}")
|
||||||
|
print(f" {'Coupling':<12}: {s['coupling']}")
|
||||||
|
print(
|
||||||
|
f" {'Raw bits':<12}: MSB={bits['msb']} MID={bits['mid']} LSB={bits['lsb']} speed={bits['speed_mode']} coup={bits['coupling']}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def info_all(self) -> None:
|
||||||
|
"""
|
||||||
|
Print a plain summary for ALL configured amplifiers.
|
||||||
|
"""
|
||||||
|
print("\nDLPCA-200 Amplifier Status Report")
|
||||||
|
print("-" * 40)
|
||||||
|
for amp_name in sorted(self._config.keys()):
|
||||||
|
self.info(amp_name)
|
||||||
|
print()
|
||||||
|
|
||||||
|
def list_amplifiers(self) -> list[str]:
|
||||||
|
"""Return sorted list of configured amplifier names."""
|
||||||
|
return sorted(self._config.keys())
|
||||||
@@ -41,8 +41,10 @@ import builtins
|
|||||||
if builtins.__dict__.get("bec") is not None:
|
if builtins.__dict__.get("bec") is not None:
|
||||||
bec = builtins.__dict__.get("bec")
|
bec = builtins.__dict__.get("bec")
|
||||||
dev = builtins.__dict__.get("dev")
|
dev = builtins.__dict__.get("dev")
|
||||||
umv = builtins.__dict__.get("umv")
|
scans = builtins.__dict__.get("scans")
|
||||||
umvr = builtins.__dict__.get("umvr")
|
|
||||||
|
def umv(*args):
|
||||||
|
return scans.umv(*args, relative=False)
|
||||||
|
|
||||||
class cSAXSFilterTransmission:
|
class cSAXSFilterTransmission:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ from bec_lib import bec_logger
|
|||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
|
|
||||||
# Pull BEC globals if present
|
# Pull BEC globals if present
|
||||||
bec = builtins.__dict__.get("bec")
|
if builtins.__dict__.get("bec") is not None:
|
||||||
dev = builtins.__dict__.get("dev")
|
bec = builtins.__dict__.get("bec")
|
||||||
umv = builtins.__dict__.get("umv")
|
dev = builtins.__dict__.get("dev")
|
||||||
umvr = builtins.__dict__.get("umvr")
|
scans = builtins.__dict__.get("scans")
|
||||||
|
|
||||||
|
def umv(*args):
|
||||||
|
return scans.umv(*args, relative=False)
|
||||||
|
|
||||||
|
|
||||||
class cSAXSInitSmaractStagesError(Exception):
|
class cSAXSInitSmaractStagesError(Exception):
|
||||||
@@ -383,7 +386,6 @@ class cSAXSInitSmaractStages:
|
|||||||
if not self._yesno("Proceed with the motions listed above?", "y"):
|
if not self._yesno("Proceed with the motions listed above?", "y"):
|
||||||
logger.info("[cSAXS] Motion to initial position aborted by user.")
|
logger.info("[cSAXS] Motion to initial position aborted by user.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# --- Execution phase (SIMULTANEOUS MOTION) ---
|
# --- Execution phase (SIMULTANEOUS MOTION) ---
|
||||||
if umv is None:
|
if umv is None:
|
||||||
logger.error("[cSAXS] 'umv' is not available in this session.")
|
logger.error("[cSAXS] 'umv' is not available in this session.")
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ logger = bec_logger.logger
|
|||||||
if builtins.__dict__.get("bec") is not None:
|
if builtins.__dict__.get("bec") is not None:
|
||||||
bec = builtins.__dict__.get("bec")
|
bec = builtins.__dict__.get("bec")
|
||||||
dev = builtins.__dict__.get("dev")
|
dev = builtins.__dict__.get("dev")
|
||||||
umv = builtins.__dict__.get("umv")
|
scans = builtins.__dict__.get("scans")
|
||||||
umvr = builtins.__dict__.get("umvr")
|
|
||||||
|
def umv(*args):
|
||||||
|
return scans.umv(*args, relative=False)
|
||||||
|
|
||||||
|
|
||||||
class FlomniToolsError(Exception):
|
class FlomniToolsError(Exception):
|
||||||
@@ -973,36 +975,49 @@ class FlomniAlignmentMixin:
|
|||||||
use_vertical_default_values=True,
|
use_vertical_default_values=True,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Read the alignment offset from the given directory and set the global parameter
|
Read the alignment parameters from xray eye fit.
|
||||||
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))
|
tomo_alignment_fit = np.zeros((2, 5))
|
||||||
with open(os.path.join(dir_path, "ptychotomoalign_Ax.txt"), "r") as file:
|
# with open(os.path.join(dir_path, "ptychotomoalign_Ax.txt"), "r") as file:
|
||||||
tomo_alignment_fit[0][0] = file.readline()
|
# tomo_alignment_fit[0][0] = file.readline()
|
||||||
|
|
||||||
with open(os.path.join(dir_path, "ptychotomoalign_Bx.txt"), "r") as file:
|
# with open(os.path.join(dir_path, "ptychotomoalign_Bx.txt"), "r") as file:
|
||||||
tomo_alignment_fit[0][1] = file.readline()
|
# tomo_alignment_fit[0][1] = file.readline()
|
||||||
|
|
||||||
with open(os.path.join(dir_path, "ptychotomoalign_Cx.txt"), "r") as file:
|
# with open(os.path.join(dir_path, "ptychotomoalign_Cx.txt"), "r") as file:
|
||||||
tomo_alignment_fit[0][2] = file.readline()
|
# tomo_alignment_fit[0][2] = file.readline()
|
||||||
|
|
||||||
with open(os.path.join(dir_path, "ptychotomoalign_Ay.txt"), "r") as file:
|
# with open(os.path.join(dir_path, "ptychotomoalign_Ay.txt"), "r") as file:
|
||||||
tomo_alignment_fit[1][0] = file.readline()
|
# tomo_alignment_fit[1][0] = file.readline()
|
||||||
|
|
||||||
with open(os.path.join(dir_path, "ptychotomoalign_By.txt"), "r") as file:
|
# with open(os.path.join(dir_path, "ptychotomoalign_By.txt"), "r") as file:
|
||||||
tomo_alignment_fit[1][1] = file.readline()
|
# tomo_alignment_fit[1][1] = file.readline()
|
||||||
|
|
||||||
with open(os.path.join(dir_path, "ptychotomoalign_Cy.txt"), "r") as file:
|
# with open(os.path.join(dir_path, "ptychotomoalign_Cy.txt"), "r") as file:
|
||||||
tomo_alignment_fit[1][2] = file.readline()
|
# tomo_alignment_fit[1][2] = file.readline()
|
||||||
|
|
||||||
with open(os.path.join(dir_path, "ptychotomoalign_Ay3.txt"), "r") as file:
|
# with open(os.path.join(dir_path, "ptychotomoalign_Ay3.txt"), "r") as file:
|
||||||
tomo_alignment_fit[1][3] = file.readline()
|
# 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()
|
||||||
|
|
||||||
|
params = dev.omny_xray_gui.fit_params_x.get()
|
||||||
|
|
||||||
|
#amplitude
|
||||||
|
tomo_alignment_fit[0][0] = params['SineModel_0_amplitude']
|
||||||
|
#phase
|
||||||
|
tomo_alignment_fit[0][1] = params['SineModel_0_shift']
|
||||||
|
#offset
|
||||||
|
tomo_alignment_fit[0][2] = params['LinearModel_1_intercept']
|
||||||
|
print("applying vertical default values from mirror calibration, not from fit!")
|
||||||
|
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
|
||||||
|
|
||||||
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("New alignment parameters loaded:")
|
||||||
print(
|
print(
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import builtins
|
import builtins
|
||||||
|
|
||||||
from bec_widgets.cli.client import BECDockArea
|
|
||||||
|
|
||||||
# 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, fshclose
|
||||||
|
|
||||||
if builtins.__dict__.get("bec") is not None:
|
if builtins.__dict__.get("bec") is not None:
|
||||||
bec = builtins.__dict__.get("bec")
|
bec = builtins.__dict__.get("bec")
|
||||||
dev = builtins.__dict__.get("dev")
|
dev = builtins.__dict__.get("dev")
|
||||||
umv = builtins.__dict__.get("umv")
|
scans = builtins.__dict__.get("scans")
|
||||||
umvr = builtins.__dict__.get("umvr")
|
|
||||||
|
|
||||||
|
def umv(*args):
|
||||||
|
return scans.umv(*args, relative=False)
|
||||||
|
|
||||||
|
|
||||||
class flomniGuiToolsError(Exception):
|
class flomniGuiToolsError(Exception):
|
||||||
@@ -27,7 +28,7 @@ class flomniGuiTools:
|
|||||||
|
|
||||||
def flomnigui_show_gui(self):
|
def flomnigui_show_gui(self):
|
||||||
if "flomni" in self.gui.windows:
|
if "flomni" in self.gui.windows:
|
||||||
self.gui.flomni.show()
|
self.gui.flomni.raise_window()
|
||||||
else:
|
else:
|
||||||
self.gui.new("flomni")
|
self.gui.new("flomni")
|
||||||
|
|
||||||
@@ -37,39 +38,39 @@ class flomniGuiTools:
|
|||||||
def flomnigui_raise(self):
|
def flomnigui_raise(self):
|
||||||
self.gui.flomni.raise_window()
|
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):
|
def flomnigui_show_xeyealign(self):
|
||||||
self.flomnigui_show_gui()
|
self.flomnigui_show_gui()
|
||||||
if self._flomnigui_check_attribute_not_exists("xeyegui"):
|
if self._flomnigui_check_attribute_not_exists("xeyegui"):
|
||||||
self.flomnigui_remove_all_docks()
|
self.flomnigui_remove_all_docks()
|
||||||
self.xeyegui = self.gui.flomni.new("xeyegui").new("XRayEye")
|
self.xeyegui = self.gui.flomni.new("XRayEye", object_name="xrayeye")
|
||||||
# start live
|
# start live
|
||||||
if not dev.cam_xeye.live_mode:
|
if not dev.cam_xeye.live_mode_enabled.get():
|
||||||
dev.cam_xeye.live_mode = True
|
# dev.cam_xeye.live_mode = True
|
||||||
|
dev.cam_xeye.live_mode_enabled.put(True)
|
||||||
|
self.xeyegui.switch_tab("alignment")
|
||||||
|
|
||||||
|
def flomnigui_show_xeyealign_fittab(self):
|
||||||
|
self.flomnigui_show_gui()
|
||||||
|
if self._flomnigui_check_attribute_not_exists("xeyegui"):
|
||||||
|
self.flomnigui_remove_all_docks()
|
||||||
|
self.xeyegui = self.gui.flomni.new("XRayEye")
|
||||||
|
self.xeyegui.switch_tab("fit")
|
||||||
|
|
||||||
def _flomnigui_check_attribute_not_exists(self, attribute_name):
|
def _flomnigui_check_attribute_not_exists(self, attribute_name):
|
||||||
if hasattr(self.gui,"flomni"):
|
if hasattr(self.gui, "flomni"):
|
||||||
if hasattr(self.gui.flomni,attribute_name):
|
if hasattr(self.gui.flomni, attribute_name):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def flomnigui_show_cameras(self):
|
def flomnigui_show_cameras(self):
|
||||||
self.flomnigui_show_gui()
|
self.flomnigui_show_gui()
|
||||||
if self._flomnigui_check_attribute_not_exists("camera_gripper") or self._flomnigui_check_attribute_not_exists("camera_overview"):
|
if self._flomnigui_check_attribute_not_exists(
|
||||||
|
"camera_gripper"
|
||||||
|
) or self._flomnigui_check_attribute_not_exists("camera_overview"):
|
||||||
self.flomnigui_remove_all_docks()
|
self.flomnigui_remove_all_docks()
|
||||||
camera_gripper_image = self.gui.flomni.new("camera_gripper").new("Image")
|
camera_gripper_image = self.gui.flomni.new("Image")
|
||||||
if self._flomnicam_check_device_exists(dev.cam_flomni_gripper):
|
if self._flomnicam_check_device_exists(dev.cam_flomni_gripper):
|
||||||
camera_gripper_image.image(("cam_flomni_gripper", "preview"))
|
camera_gripper_image.image(device="cam_flomni_gripper", signal="preview")
|
||||||
camera_gripper_image.lock_aspect_ratio = True
|
camera_gripper_image.lock_aspect_ratio = True
|
||||||
camera_gripper_image.enable_fps_monitor = True
|
camera_gripper_image.enable_fps_monitor = True
|
||||||
camera_gripper_image.enable_toolbar = False
|
camera_gripper_image.enable_toolbar = False
|
||||||
@@ -78,9 +79,9 @@ class flomniGuiTools:
|
|||||||
dev.cam_flomni_gripper.start_live_mode()
|
dev.cam_flomni_gripper.start_live_mode()
|
||||||
else:
|
else:
|
||||||
print("Cannot open camera_gripper. Device does not exist.")
|
print("Cannot open camera_gripper. Device does not exist.")
|
||||||
camera_overview_image = self.gui.flomni.new("camera_overview").new("Image")
|
camera_overview_image = self.gui.flomni.new("Image")
|
||||||
if self._flomnicam_check_device_exists(dev.cam_flomni_overview):
|
if self._flomnicam_check_device_exists(dev.cam_flomni_overview):
|
||||||
camera_overview_image.image(("cam_flomni_overview", "preview"))
|
camera_overview_image.image(device="cam_flomni_overview", signal="preview")
|
||||||
camera_overview_image.lock_aspect_ratio = True
|
camera_overview_image.lock_aspect_ratio = True
|
||||||
camera_overview_image.enable_fps_monitor = True
|
camera_overview_image.enable_fps_monitor = True
|
||||||
camera_overview_image.enable_toolbar = False
|
camera_overview_image.enable_toolbar = False
|
||||||
@@ -91,9 +92,9 @@ class flomniGuiTools:
|
|||||||
print("Cannot open camera_overview. Device does not exist.")
|
print("Cannot open camera_overview. Device does not exist.")
|
||||||
|
|
||||||
def flomnigui_remove_all_docks(self):
|
def flomnigui_remove_all_docks(self):
|
||||||
#dev.cam_flomni_overview.stop_live_mode()
|
# dev.cam_flomni_overview.stop_live_mode()
|
||||||
#dev.cam_flomni_gripper.stop_live_mode()
|
# dev.cam_flomni_gripper.stop_live_mode()
|
||||||
#dev.cam_xeye.live_mode = False
|
# dev.cam_xeye.live_mode = False
|
||||||
self.gui.flomni.delete_all()
|
self.gui.flomni.delete_all()
|
||||||
self.progressbar = None
|
self.progressbar = None
|
||||||
self.text_box = None
|
self.text_box = None
|
||||||
@@ -102,7 +103,7 @@ class flomniGuiTools:
|
|||||||
self.flomnigui_show_gui()
|
self.flomnigui_show_gui()
|
||||||
if self._flomnigui_check_attribute_not_exists("idle_text_box"):
|
if self._flomnigui_check_attribute_not_exists("idle_text_box"):
|
||||||
self.flomnigui_remove_all_docks()
|
self.flomnigui_remove_all_docks()
|
||||||
idle_text_box = self.gui.flomni.new("idle_textbox").new("TextBox")
|
idle_text_box = self.gui.flomni.new("TextBox")
|
||||||
text = (
|
text = (
|
||||||
"<pre>"
|
"<pre>"
|
||||||
+ " ,---.,--. ,-----. ,--. ,--.,--. ,--.,--. \n"
|
+ " ,---.,--. ,-----. ,--. ,--.,--. ,--.,--. \n"
|
||||||
@@ -118,13 +119,12 @@ class flomniGuiTools:
|
|||||||
import csaxs_bec
|
import csaxs_bec
|
||||||
from pathlib import Path
|
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")
|
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
|
csaxs_bec_basepath = Path(csaxs_bec.__file__).parent
|
||||||
docs_folder = (
|
docs_folder = csaxs_bec_basepath / "bec_ipython_client" / "plugins" / "flomni" / "docs"
|
||||||
csaxs_bec_basepath /
|
|
||||||
"bec_ipython_client" / "plugins" / "flomni" / "docs"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not docs_folder.is_dir():
|
if not docs_folder.is_dir():
|
||||||
raise NotADirectoryError(f"Docs folder not found: {docs_folder}")
|
raise NotADirectoryError(f"Docs folder not found: {docs_folder}")
|
||||||
@@ -162,10 +162,9 @@ class flomniGuiTools:
|
|||||||
self.pdf_viewer = self.gui.flomni.new(widget="PdfViewerWidget")
|
self.pdf_viewer = self.gui.flomni.new(widget="PdfViewerWidget")
|
||||||
|
|
||||||
# --- Load PDF ---------------------------------------------------------
|
# --- Load PDF ---------------------------------------------------------
|
||||||
self.pdf_viewer.PdfViewerWidget.load_pdf(str(pdf_file.resolve()))
|
self.pdf_viewer.load_pdf(str(pdf_file.resolve()))
|
||||||
print(f"\nLoaded: {pdf_file.name}\n")
|
print(f"\nLoaded: {pdf_file.name}\n")
|
||||||
|
|
||||||
|
|
||||||
def _flomnicam_check_device_exists(self, device):
|
def _flomnicam_check_device_exists(self, device):
|
||||||
try:
|
try:
|
||||||
device
|
device
|
||||||
@@ -179,26 +178,18 @@ class flomniGuiTools:
|
|||||||
if self._flomnigui_check_attribute_not_exists("progressbar"):
|
if self._flomnigui_check_attribute_not_exists("progressbar"):
|
||||||
self.flomnigui_remove_all_docks()
|
self.flomnigui_remove_all_docks()
|
||||||
# Add a new dock with a RingProgressBar widget
|
# Add a new dock with a RingProgressBar widget
|
||||||
self.progressbar = self.gui.flomni.new("progressbar").new("RingProgressBar")
|
self.progressbar = self.gui.flomni.new("RingProgressBar")
|
||||||
# Customize the size of the progress ring
|
|
||||||
self.progressbar.set_line_widths(20)
|
# Setting multiple rings with different values
|
||||||
# Disable automatic updates and manually set the self.progressbar value
|
self.progressbar.add_ring().set_update("manual")
|
||||||
self.progressbar.enable_auto_updates(False)
|
self.progressbar.add_ring().set_update("manual")
|
||||||
# Set precision for the self.progressbar display
|
self.progressbar.add_ring().set_update("scan")
|
||||||
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()
|
self._flomnigui_update_progress()
|
||||||
|
|
||||||
def _flomnigui_update_progress(self):
|
def _flomnigui_update_progress(self):
|
||||||
|
main_progress_ring = self.progressbar.rings[0]
|
||||||
|
subtomo_progress_ring = self.progressbar.rings[1]
|
||||||
if self.progressbar is not None:
|
if self.progressbar is not None:
|
||||||
progress = self.progress["projection"] / self.progress["total_projections"] * 100
|
progress = self.progress["projection"] / self.progress["total_projections"] * 100
|
||||||
subtomo_progress = (
|
subtomo_progress = (
|
||||||
@@ -206,10 +197,11 @@ class flomniGuiTools:
|
|||||||
/ self.progress["subtomo_total_projections"]
|
/ self.progress["subtomo_total_projections"]
|
||||||
* 100
|
* 100
|
||||||
)
|
)
|
||||||
self.progressbar.set_value([progress, subtomo_progress, 0])
|
main_progress_ring.set_value(progress)
|
||||||
if self.text_box is not None:
|
subtomo_progress_ring.set_value(subtomo_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)
|
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.progressbar.set_center_label(text)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -220,6 +212,7 @@ if __name__ == "__main__":
|
|||||||
client.start()
|
client.start()
|
||||||
client.gui = BECGuiClient()
|
client.gui = BECGuiClient()
|
||||||
|
|
||||||
flomni_gui = flomniGuiTools(client)
|
flomni_gui = flomniGuiTools()
|
||||||
|
flomni_gui.set_client(client)
|
||||||
flomni_gui.flomnigui_show_gui()
|
flomni_gui.flomnigui_show_gui()
|
||||||
flomni_gui.flomnigui_show_progress()
|
flomni_gui.flomnigui_show_progress()
|
||||||
|
|||||||
@@ -5,16 +5,20 @@ import os
|
|||||||
import time
|
import time
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
from bec_lib import bec_logger
|
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, fshclose
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
# import builtins to avoid linter errors
|
# import builtins to avoid linter errors
|
||||||
bec = builtins.__dict__.get("bec")
|
bec = builtins.__dict__.get("bec")
|
||||||
dev = builtins.__dict__.get("dev")
|
dev = builtins.__dict__.get("dev")
|
||||||
umv = builtins.__dict__.get("umv")
|
scans = builtins.__dict__.get("scans")
|
||||||
umvr = builtins.__dict__.get("umvr")
|
|
||||||
|
def umv(*args):
|
||||||
|
return scans.umv(*args, relative=False)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from bec_ipython_client.plugins.flomni import Flomni
|
from bec_ipython_client.plugins.flomni import Flomni
|
||||||
@@ -22,7 +26,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
class XrayEyeAlign:
|
class XrayEyeAlign:
|
||||||
# pixel calibration, multiply to get mm
|
# pixel calibration, multiply to get mm
|
||||||
labview=False
|
test_wo_movements = True
|
||||||
PIXEL_CALIBRATION = 0.1 / 113 # .2 with binning
|
PIXEL_CALIBRATION = 0.1 / 113 # .2 with binning
|
||||||
|
|
||||||
def __init__(self, client, flomni: Flomni) -> None:
|
def __init__(self, client, flomni: Flomni) -> None:
|
||||||
@@ -34,209 +38,194 @@ class XrayEyeAlign:
|
|||||||
self.flomni.reset_correction()
|
self.flomni.reset_correction()
|
||||||
self.flomni.reset_tomo_alignment_fit()
|
self.flomni.reset_tomo_alignment_fit()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def gui(self):
|
||||||
|
return self.flomni.xeyegui
|
||||||
|
|
||||||
def _reset_init_values(self):
|
def _reset_init_values(self):
|
||||||
self.shift_xy = [0, 0]
|
self.shift_xy = [0, 0]
|
||||||
self._xray_fov_xy = [0, 0]
|
self._xray_fov_xy = [0, 0]
|
||||||
|
|
||||||
def save_frame(self):
|
def update_frame(self, keep_shutter_open=False):
|
||||||
epics_put("XOMNYI-XEYE-SAVFRAME:0", 1)
|
|
||||||
|
|
||||||
def update_frame(self,keep_shutter_open=False):
|
# self.flomni.flomnigui_show_xeyealign()
|
||||||
if self.labview:
|
if not dev.cam_xeye.live_mode_enabled.get():
|
||||||
epics_put("XOMNYI-XEYE-ACQDONE:0", 0)
|
dev.cam_xeye.live_mode_enabled.put(True)
|
||||||
|
|
||||||
if not self.labview:
|
self.gui.on_live_view_enabled(True)
|
||||||
self.flomni.flomnigui_show_xeyealign()
|
|
||||||
if not dev.cam_xeye.live_mode:
|
|
||||||
dev.cam_xeye.live_mode = True
|
|
||||||
|
|
||||||
epics_put("XOMNYI-XEYE-ACQ:0", 1)
|
dev.omnyfsh.fshopen()
|
||||||
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...")
|
|
||||||
|
|
||||||
fshopen()
|
|
||||||
|
|
||||||
if self.labview:
|
|
||||||
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)
|
time.sleep(0.5)
|
||||||
# stop live view
|
# stop live view
|
||||||
if not keep_shutter_open:
|
if not keep_shutter_open:
|
||||||
epics_put("XOMNYI-XEYE-ACQ:0", 0)
|
self.gui.on_live_view_enabled(False)
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
fshclose()
|
dev.omnyfsh.fshclose()
|
||||||
print("got new frame")
|
print("Received new frame.")
|
||||||
else:
|
else:
|
||||||
print("Staying in live view, shutter is and remains open!")
|
print("Staying in live view, shutter is and remains open!")
|
||||||
|
|
||||||
def tomo_rotate(self, val: float):
|
def tomo_rotate(self, val: float):
|
||||||
# pylint: disable=undefined-variable
|
if not self.test_wo_movements:
|
||||||
umv(self.device_manager.devices.fsamroy, val)
|
umv(self.device_manager.devices.fsamroy, val)
|
||||||
|
|
||||||
def get_tomo_angle(self):
|
def get_tomo_angle(self):
|
||||||
return self.device_manager.devices.fsamroy.readback.get()
|
return self.device_manager.devices.fsamroy.readback.get()
|
||||||
|
|
||||||
def update_fov(self, k: int):
|
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[0] = max(
|
||||||
self._xray_fov_xy[1] = max(0, self._xray_fov_xy[0])
|
getattr(dev.omny_xray_gui, f"width_x_{k}").get(), self._xray_fov_xy[0]
|
||||||
|
)
|
||||||
|
self._xray_fov_xy[1] = max(
|
||||||
|
getattr(dev.omny_xray_gui, f"width_y_{k}").get(), self._xray_fov_xy[1]
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
def movement_buttons_enabled(self, enablex: bool, enabley: bool):
|
||||||
def movement_buttons_enabled(self):
|
self.gui.on_motors_enable(enablex, enabley)
|
||||||
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):
|
def send_message(self, msg: str):
|
||||||
epics_put("XOMNYI-XEYE-MESSAGE:0.DESC", msg)
|
print(f"In alginment GUI: {msg}")
|
||||||
|
self.gui.user_message = msg
|
||||||
|
|
||||||
def align(self,keep_shutter_open=False):
|
def align(self, keep_shutter_open=False):
|
||||||
|
self.flomni.flomnigui_show_xeyealign()
|
||||||
if not keep_shutter_open:
|
if not keep_shutter_open:
|
||||||
print("This routine can be called with paramter keep_shutter_open=True to keep the shutter always 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...")
|
self.send_message("Getting things ready. Please wait...")
|
||||||
|
|
||||||
#potential unresolved movement requests to zero
|
self.gui.enable_submit_button(False)
|
||||||
epics_put("XOMNYI-XEYE-MVX:0", 0)
|
|
||||||
epics_put("XOMNYI-XEYE-MVY:0", 0)
|
# Initialize xray align device
|
||||||
|
# clear potential pending movement requests
|
||||||
|
dev.omny_xray_gui.mvx.set(0)
|
||||||
|
dev.omny_xray_gui.mvy.set(0)
|
||||||
|
# reset submit channel
|
||||||
|
dev.omny_xray_gui.submit.set(0)
|
||||||
|
|
||||||
|
self.movement_buttons_enabled(False, False)
|
||||||
|
|
||||||
# reset shift xy and fov params
|
# reset shift xy and fov params
|
||||||
self._reset_init_values()
|
self._reset_init_values()
|
||||||
|
|
||||||
self.flomni.lights_off()
|
self.flomni.lights_off()
|
||||||
|
|
||||||
self.flomni.flomnigui_show_xeyealign()
|
# self.flomni.flomnigui_show_xeyealign()
|
||||||
self.flomni.flomnigui_raise()
|
# self.flomni.flomnigui_raise()
|
||||||
|
|
||||||
self.tomo_rotate(0)
|
if not self.test_wo_movements:
|
||||||
epics_put("XOMNYI-XEYE-ANGLE:0", 0)
|
self.tomo_rotate(0)
|
||||||
|
|
||||||
self.flomni.feye_in()
|
self.flomni.feye_in()
|
||||||
|
|
||||||
self.flomni.laser_tracker_on()
|
self.flomni.laser_tracker_on()
|
||||||
|
|
||||||
self.flomni.feedback_enable_with_reset()
|
self.flomni.feedback_enable_with_reset()
|
||||||
|
|
||||||
# disable movement buttons
|
# disable movement buttons
|
||||||
self.movement_buttons_enabled = False
|
self.movement_buttons_enabled(False, False)
|
||||||
|
|
||||||
sample_name = self.flomni.sample_get_name(0)
|
sample_name = self.flomni.sample_get_name(0)
|
||||||
epics_put("XOMNYI-XEYE-SAMPLENAME:0.DESC", sample_name)
|
self.gui.sample_name = sample_name
|
||||||
|
|
||||||
# this makes sure we are in a defined state
|
# this makes sure we are in a defined state
|
||||||
self.flomni.feedback_disable()
|
self.flomni.feedback_disable()
|
||||||
|
|
||||||
epics_put("XOMNYI-XEYE-PIXELSIZE:0", self.PIXEL_CALIBRATION)
|
if not self.test_wo_movements:
|
||||||
|
self.flomni.fosa_out()
|
||||||
|
|
||||||
self.flomni.fosa_out()
|
fsamx_in = self.flomni._get_user_param_safe("fsamx", "in")
|
||||||
|
umv(dev.fsamx, fsamx_in - 0.25)
|
||||||
|
|
||||||
fsamx_in = self.flomni._get_user_param_safe("fsamx", "in")
|
self.flomni.ffzp_in()
|
||||||
umv(dev.fsamx, fsamx_in - 0.25)
|
|
||||||
|
|
||||||
self.flomni.ffzp_in()
|
|
||||||
self.update_frame(keep_shutter_open)
|
self.update_frame(keep_shutter_open)
|
||||||
|
|
||||||
# enable submit buttons
|
self.gui.enable_submit_button(True)
|
||||||
self.movement_buttons_enabled = False
|
dev.omny_xray_gui.step.set(0).wait()
|
||||||
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
|
|
||||||
epics_put("XOMNYI-XEYE-STEP:0", 0)
|
|
||||||
self.send_message("Submit center value of FZP.")
|
self.send_message("Submit center value of FZP.")
|
||||||
|
|
||||||
k = 0
|
k = 0
|
||||||
while True:
|
while True:
|
||||||
if epics_get("XOMNYI-XEYE-SUBMIT:0") == 1:
|
if dev.omny_xray_gui.submit.get() == 1:
|
||||||
val_x = epics_get(f"XOMNYI-XEYE-XVAL_X:{k}") / 2 * self.PIXEL_CALIBRATION # in mm
|
|
||||||
self.alignment_values[k] = val_x
|
self.alignment_values[k] = (
|
||||||
|
getattr(dev.omny_xray_gui, f"xval_x_{k}").get() / 2 * self.PIXEL_CALIBRATION
|
||||||
|
) # in mm
|
||||||
print(f"Clicked position {k}: x {self.alignment_values[k]}")
|
print(f"Clicked position {k}: x {self.alignment_values[k]}")
|
||||||
rtx_position = dev.rtx.readback.get() / 1000
|
rtx_position = dev.rtx.readback.get() / 1000
|
||||||
print(f"Current rtx position {rtx_position}")
|
print(f"Current rtx position {rtx_position}")
|
||||||
self.alignment_values[k] -= rtx_position
|
self.alignment_values[k] -= rtx_position
|
||||||
print(f"Corrected position {k}: x {self.alignment_values[k]}")
|
print(f"Corrected position {k}: x {self.alignment_values[k]}")
|
||||||
|
# reset submit channel
|
||||||
|
dev.omny_xray_gui.submit.set(0)
|
||||||
if k == 0: # received center value of FZP
|
if k == 0: # received center value of FZP
|
||||||
self.send_message("please wait ...")
|
self.send_message("please wait ...")
|
||||||
self.movement_buttons_enabled = False
|
self.movement_buttons_enabled(False, False)
|
||||||
epics_put("XOMNYI-XEYE-SUBMIT:0", -1) # disable submit button
|
self.gui.enable_submit_button(False)
|
||||||
|
|
||||||
self.flomni.feedback_disable()
|
self.flomni.feedback_disable()
|
||||||
fsamx_in = self.flomni._get_user_param_safe("fsamx", "in")
|
if not self.test_wo_movements:
|
||||||
umv(dev.fsamx, fsamx_in)
|
fsamx_in = self.flomni._get_user_param_safe("fsamx", "in")
|
||||||
|
umv(dev.fsamx, fsamx_in)
|
||||||
|
|
||||||
self.flomni.foptics_out()
|
self.flomni.foptics_out()
|
||||||
|
|
||||||
self.flomni.feedback_disable()
|
time.sleep(0.5)
|
||||||
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...")
|
|
||||||
|
|
||||||
umv(dev.fsamx, fsamx_in)
|
|
||||||
time.sleep(0.5)
|
|
||||||
self.flomni.feedback_enable_with_reset()
|
self.flomni.feedback_enable_with_reset()
|
||||||
|
|
||||||
self.update_frame(keep_shutter_open)
|
self.update_frame(keep_shutter_open)
|
||||||
self.send_message("Adjust sample height and submit center")
|
self.send_message("Step 1/5: Adjust sample height and submit center")
|
||||||
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
|
self.gui.enable_submit_button(True)
|
||||||
self.movement_buttons_enabled = True
|
self.movement_buttons_enabled(True, True)
|
||||||
|
|
||||||
elif 1 <= k < 5: # received sample center value at samroy 0 ... 315
|
elif 1 <= k < 5: # received sample center value at samroy 0 ... 315
|
||||||
self.send_message("please wait ...")
|
self.send_message("please wait ...")
|
||||||
epics_put("XOMNYI-XEYE-SUBMIT:0", -1)
|
self.gui.enable_submit_button(False)
|
||||||
self.movement_buttons_enabled = False
|
self.movement_buttons_enabled(False, False)
|
||||||
|
|
||||||
umv(dev.rtx, 0)
|
umv(dev.rtx, 0)
|
||||||
self.tomo_rotate(k * 45)
|
self.tomo_rotate(k * 45)
|
||||||
epics_put("XOMNYI-XEYE-ANGLE:0", self.get_tomo_angle())
|
dev.omny_xray_gui.angle.set(self.get_tomo_angle())
|
||||||
self.update_frame(keep_shutter_open)
|
self.update_frame(keep_shutter_open)
|
||||||
self.send_message("Submit sample center")
|
self.send_message(f"Step {k+1}/5: Submit sample center")
|
||||||
epics_put("XOMNYI-XEYE-SUBMIT:0", 0)
|
self.gui.enable_submit_button(True)
|
||||||
epics_put("XOMNYI-XEYE-ENAMVX:0", 1)
|
self.movement_buttons_enabled(True, False)
|
||||||
self.update_fov(k)
|
self.update_fov(k)
|
||||||
|
|
||||||
elif k == 5: # received sample center value at samroy 270 and done
|
elif k == 5: # received sample center value at samroy 270 and done
|
||||||
self.send_message("done...")
|
self.send_message("done...")
|
||||||
epics_put("XOMNYI-XEYE-SUBMIT:0", -1) # disable submit button
|
self.gui.enable_submit_button(False)
|
||||||
self.movement_buttons_enabled = False
|
self.movement_buttons_enabled(False, False)
|
||||||
self.update_fov(k)
|
self.update_fov(k)
|
||||||
break
|
break
|
||||||
|
|
||||||
k += 1
|
k += 1
|
||||||
epics_put("XOMNYI-XEYE-STEP:0", k)
|
dev.omny_xray_gui.step.set(k)
|
||||||
|
|
||||||
_xrayeyalignmvx = epics_get("XOMNYI-XEYE-MVX:0")
|
_xrayeyalignmvx = dev.omny_xray_gui.mvx.get()
|
||||||
if _xrayeyalignmvx != 0:
|
if _xrayeyalignmvx != 0:
|
||||||
umvr(dev.rtx, _xrayeyalignmvx)
|
umvr(dev.rtx, _xrayeyalignmvx)
|
||||||
print(f"Current rtx position {dev.rtx.readback.get() / 1000}")
|
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)
|
time.sleep(3)
|
||||||
|
dev.omny_xray_gui.mvx.set(0)
|
||||||
self.update_frame(keep_shutter_open)
|
self.update_frame(keep_shutter_open)
|
||||||
|
|
||||||
if k < 2:
|
if k < 2:
|
||||||
# allow movements, store movements to calculate center
|
# allow movements, store movements to calculate center
|
||||||
_xrayeyalignmvy = epics_get("XOMNYI-XEYE-MVY:0")
|
_xrayeyalignmvy = dev.omny_xray_gui.mvy.get()
|
||||||
if _xrayeyalignmvy != 0:
|
if _xrayeyalignmvy != 0:
|
||||||
self.flomni.feedback_disable()
|
self.flomni.feedback_disable()
|
||||||
umvr(dev.fsamy, _xrayeyalignmvy / 1000)
|
if not self.test_wo_movements:
|
||||||
|
umvr(dev.fsamy, _xrayeyalignmvy / 1000)
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
epics_put("XOMNYI-XEYE-MVY:0", 0)
|
dev.omny_xray_gui.mvy.set(0)
|
||||||
self.flomni.feedback_enable_with_reset()
|
self.flomni.feedback_enable_with_reset()
|
||||||
self.update_frame(keep_shutter_open)
|
self.update_frame(keep_shutter_open)
|
||||||
time.sleep(0.2)
|
time.sleep(0.1)
|
||||||
|
|
||||||
self.write_output()
|
self.write_output()
|
||||||
fovx = self._xray_fov_xy[0] * self.PIXEL_CALIBRATION * 1000 / 2
|
fovx = self._xray_fov_xy[0] * self.PIXEL_CALIBRATION * 1000 / 2
|
||||||
@@ -246,22 +235,17 @@ class XrayEyeAlign:
|
|||||||
|
|
||||||
umv(dev.rtx, 0)
|
umv(dev.rtx, 0)
|
||||||
|
|
||||||
# free camera
|
if keep_shutter_open:
|
||||||
if self.labview:
|
if self.flomni.OMNYTools.yesno("Close the shutter now?", "y"):
|
||||||
epics_put("XOMNYI-XEYE-ACQ:0", 2)
|
dev.omnyfsh.fshclose()
|
||||||
if keep_shutter_open and not self.labview:
|
self.gui.on_live_view_enabled(False)
|
||||||
if self.flomni.OMNYTools.yesno("Close the shutter now?","y"):
|
print("setting 'XOMNYI-XEYE-ACQ:0'")
|
||||||
fshclose()
|
|
||||||
epics_put("XOMNYI-XEYE-ACQ:0", 0)
|
|
||||||
if not self.labview:
|
|
||||||
self.flomni.flomnigui_idle()
|
|
||||||
|
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"The largest field of view from the xrayeyealign was \nfovx = {fovx:.0f} microns, fovy"
|
f"The largest field of view from the xrayeyealign was \nfovx = {fovx:.0f} microns, fovy"
|
||||||
f" = {fovy:.0f} microns"
|
f" = {fovy:.0f} microns"
|
||||||
)
|
)
|
||||||
print("Use the matlab routine to FIT the current alignment...")
|
print("Check the fit in the GUI...")
|
||||||
|
|
||||||
print("Then LOAD ALIGNMENT PARAMETERS by running flomni.read_alignment_offset()\n")
|
print("Then LOAD ALIGNMENT PARAMETERS by running flomni.read_alignment_offset()\n")
|
||||||
|
|
||||||
@@ -269,9 +253,34 @@ class XrayEyeAlign:
|
|||||||
file = os.path.expanduser("~/Data10/specES1/internal/xrayeye_alignmentvalues")
|
file = os.path.expanduser("~/Data10/specES1/internal/xrayeye_alignmentvalues")
|
||||||
if not os.path.exists(file):
|
if not os.path.exists(file):
|
||||||
os.makedirs(os.path.dirname(file), exist_ok=True)
|
os.makedirs(os.path.dirname(file), exist_ok=True)
|
||||||
|
|
||||||
with open(file, "w") as alignment_values_file:
|
with open(file, "w") as alignment_values_file:
|
||||||
alignment_values_file.write("angle\thorizontal\n")
|
alignment_values_file.write("angle\thorizontal\n")
|
||||||
|
|
||||||
|
# Initialize an empty list to store fovx values
|
||||||
|
fovx_list = []
|
||||||
|
fovx_offsets = np.zeros(5) # holds offsets for k = 1..5
|
||||||
|
|
||||||
for k in range(1, 6):
|
for k in range(1, 6):
|
||||||
fovx_offset = self.alignment_values[0] - self.alignment_values[k]
|
fovx_offset = self.alignment_values[0] - self.alignment_values[k]
|
||||||
|
fovx_offsets[k - 1] = fovx_offset # store in array
|
||||||
|
|
||||||
|
fovx_x = (k - 1) * 45
|
||||||
|
fovx_list.append([fovx_x, fovx_offset * 1000]) # Append the data to the list
|
||||||
|
|
||||||
print(f"Writing to file new alignment: number {k}, value x {fovx_offset}")
|
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")
|
alignment_values_file.write(f"{fovx_x}\t{fovx_offset * 1000}\n")
|
||||||
|
|
||||||
|
# Now build final numpy array:
|
||||||
|
data = np.array(
|
||||||
|
[
|
||||||
|
[0, 45, 90, 135, 180], # angles
|
||||||
|
fovx_offsets * 1000, # fovx_offset values
|
||||||
|
[0, 0, 0, 0, 0],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.gui.submit_fit_array(data)
|
||||||
|
print(f"fit submited with {data}")
|
||||||
|
# self.flomni.flomnigui_show_xeyealign_fittab()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import builtins
|
import builtins
|
||||||
|
|
||||||
from bec_widgets.cli.client import BECDockArea
|
|
||||||
|
|
||||||
# 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, fshclose
|
||||||
|
|
||||||
if builtins.__dict__.get("bec") is not None:
|
if builtins.__dict__.get("bec") is not None:
|
||||||
bec = builtins.__dict__.get("bec")
|
bec = builtins.__dict__.get("bec")
|
||||||
dev = builtins.__dict__.get("dev")
|
dev = builtins.__dict__.get("dev")
|
||||||
umv = builtins.__dict__.get("umv")
|
scans = builtins.__dict__.get("scans")
|
||||||
umvr = builtins.__dict__.get("umvr")
|
|
||||||
|
def umv(*args):
|
||||||
|
return scans.umv(*args, relative=False)
|
||||||
|
|
||||||
|
|
||||||
class OMNYGuiToolsError(Exception):
|
class OMNYGuiToolsError(Exception):
|
||||||
@@ -19,7 +19,7 @@ class OMNYGuiTools:
|
|||||||
|
|
||||||
def __init__(self, client):
|
def __init__(self, client):
|
||||||
self.gui = getattr(client, "gui", None)
|
self.gui = getattr(client, "gui", None)
|
||||||
self.gui_window = self.gui.windows['main'].widget
|
self.gui_window = self.gui.windows["main"].widget
|
||||||
self.fig200 = None
|
self.fig200 = None
|
||||||
self.fig201 = None
|
self.fig201 = None
|
||||||
self.fig202 = None
|
self.fig202 = None
|
||||||
@@ -137,7 +137,9 @@ class OMNYGuiTools:
|
|||||||
if self.progressbar is None:
|
if self.progressbar is None:
|
||||||
self.omnygui_remove_all_docks()
|
self.omnygui_remove_all_docks()
|
||||||
# Add a new dock with a RingProgressBar widget
|
# Add a new dock with a RingProgressBar widget
|
||||||
self.progressbar = self.gui_window.add_dock(name="progress").add_widget("RingProgressBar")
|
self.progressbar = self.gui_window.add_dock(name="progress").add_widget(
|
||||||
|
"RingProgressBar"
|
||||||
|
)
|
||||||
# Customize the size of the progress ring
|
# Customize the size of the progress ring
|
||||||
self.progressbar.set_line_widths(20)
|
self.progressbar.set_line_widths(20)
|
||||||
# Disable automatic updates and manually set the self.progressbar value
|
# Disable automatic updates and manually set the self.progressbar value
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ logger = bec_logger.logger
|
|||||||
if builtins.__dict__.get("bec") is not None:
|
if builtins.__dict__.get("bec") is not None:
|
||||||
bec = builtins.__dict__.get("bec")
|
bec = builtins.__dict__.get("bec")
|
||||||
dev = builtins.__dict__.get("dev")
|
dev = builtins.__dict__.get("dev")
|
||||||
umv = builtins.__dict__.get("umv")
|
scans = builtins.__dict__.get("scans")
|
||||||
umvr = builtins.__dict__.get("umvr")
|
|
||||||
|
|
||||||
|
def umv(*args):
|
||||||
|
return scans.umv(*args, relative=False)
|
||||||
|
|
||||||
class OMNYInitError(Exception):
|
class OMNYInitError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ from rich.table import Table
|
|||||||
if builtins.__dict__.get("bec") is not None:
|
if builtins.__dict__.get("bec") is not None:
|
||||||
bec = builtins.__dict__.get("bec")
|
bec = builtins.__dict__.get("bec")
|
||||||
dev = builtins.__dict__.get("dev")
|
dev = builtins.__dict__.get("dev")
|
||||||
umv = builtins.__dict__.get("umv")
|
scans = builtins.__dict__.get("scans")
|
||||||
umvr = builtins.__dict__.get("umvr")
|
|
||||||
|
def umv(*args):
|
||||||
|
return scans.umv(*args, relative=False)
|
||||||
|
|
||||||
|
|
||||||
class OMNYToolsError(Exception):
|
class OMNYToolsError(Exception):
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fsh
|
|||||||
if builtins.__dict__.get("bec") is not None:
|
if builtins.__dict__.get("bec") is not None:
|
||||||
bec = builtins.__dict__.get("bec")
|
bec = builtins.__dict__.get("bec")
|
||||||
dev = builtins.__dict__.get("dev")
|
dev = builtins.__dict__.get("dev")
|
||||||
umv = builtins.__dict__.get("umv")
|
scans = builtins.__dict__.get("scans")
|
||||||
umvr = builtins.__dict__.get("umvr")
|
|
||||||
|
def umv(*args):
|
||||||
|
return scans.umv(*args, relative=False)
|
||||||
|
|
||||||
|
|
||||||
class OMNYTransferError(Exception):
|
class OMNYTransferError(Exception):
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ logger = bec_logger.logger
|
|||||||
# import builtins to avoid linter errors
|
# import builtins to avoid linter errors
|
||||||
bec = builtins.__dict__.get("bec")
|
bec = builtins.__dict__.get("bec")
|
||||||
dev = builtins.__dict__.get("dev")
|
dev = builtins.__dict__.get("dev")
|
||||||
umv = builtins.__dict__.get("umv")
|
scans = builtins.__dict__.get("scans")
|
||||||
umvr = builtins.__dict__.get("umvr")
|
|
||||||
|
def umv(*args):
|
||||||
|
return scans.umv(*args, relative=False)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from bec_ipython_client.plugins.omny import OMNY
|
from bec_ipython_client.plugins.omny import OMNY
|
||||||
|
|||||||
@@ -30,29 +30,74 @@ logger = bec_logger.logger
|
|||||||
|
|
||||||
logger.info("Using the cSAXS startup script.")
|
logger.info("Using the cSAXS startup script.")
|
||||||
|
|
||||||
# pylint: disable=import-error
|
|
||||||
_args = _main_dict["args"]
|
|
||||||
|
|
||||||
_session_name = "cSAXS"
|
|
||||||
if _args.session.lower() == "lamni":
|
|
||||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import *
|
|
||||||
from csaxs_bec.bec_ipython_client.plugins.LamNI import *
|
|
||||||
|
|
||||||
_session_name = "LamNI"
|
|
||||||
lamni = LamNI(bec)
|
|
||||||
logger.success("LamNI session loaded.")
|
|
||||||
|
|
||||||
elif _args.session.lower() == "csaxs":
|
|
||||||
print("Loading cSAXS session")
|
|
||||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import *
|
|
||||||
|
|
||||||
logger.success("cSAXS session loaded.")
|
|
||||||
|
|
||||||
from csaxs_bec.bec_ipython_client.plugins.tool_box.debug_tools import DebugTools
|
from csaxs_bec.bec_ipython_client.plugins.tool_box.debug_tools import DebugTools
|
||||||
|
|
||||||
debug = DebugTools()
|
debug = DebugTools()
|
||||||
logger.success("Debug tools loaded. Use 'debug' to access them.")
|
logger.success("Debug tools loaded. Use 'debug' to access them.")
|
||||||
|
|
||||||
|
# pylint: disable=import-error
|
||||||
|
_args = _main_dict["args"]
|
||||||
|
|
||||||
|
_session_name = "cSAXS"
|
||||||
|
|
||||||
|
print("Loading cSAXS session")
|
||||||
|
from csaxs_bec.bec_ipython_client.plugins.cSAXS.cSAXS import cSAXS
|
||||||
|
csaxs = cSAXS(bec)
|
||||||
|
logger.success("cSAXS session loaded.")
|
||||||
|
|
||||||
|
|
||||||
|
if _args.session.lower() == "lamni":
|
||||||
|
from csaxs_bec.bec_ipython_client.plugins.LamNI import LamNI
|
||||||
|
|
||||||
|
_session_name = "LamNI"
|
||||||
|
lamni = LamNI(bec)
|
||||||
|
logger.success("LamNI session loaded.")
|
||||||
|
print(r"""
|
||||||
|
██████╗ ███████╗ ██████╗ ██╗ █████╗ ███╗ ███╗███╗ ██╗██╗
|
||||||
|
██╔══██╗██╔════╝██╔════╝ ██║ ██╔══██╗████╗ ████║████╗ ██║██║
|
||||||
|
██████╔╝█████╗ ██║ ██║ ███████║██╔████╔██║██╔██╗ ██║██║
|
||||||
|
██╔══██╗██╔══╝ ██║ ██║ ██╔══██║██║╚██╔╝██║██║╚██╗██║██║
|
||||||
|
██████╔╝███████╗╚██████╗ ███████╗██║ ██║██║ ╚═╝ ██║██║ ╚████║██║
|
||||||
|
╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝
|
||||||
|
|
||||||
|
B E C L a m N I
|
||||||
|
""")
|
||||||
|
|
||||||
|
elif _args.session.lower() == "omny":
|
||||||
|
from csaxs_bec.bec_ipython_client.plugins.flomni import OMNY
|
||||||
|
|
||||||
|
_session_name = "OMNY"
|
||||||
|
omny = OMNY(bec)
|
||||||
|
logger.success("OMNY session loaded.")
|
||||||
|
print(r"""
|
||||||
|
██████╗ ███████╗ ██████╗ ██████╗ ███╗ ███╗███╗ ██╗██╗ ██╗
|
||||||
|
██╔══██╗██╔════╝██╔════╝ ██╔═══██╗████╗ ████║████╗ ██║╚██╗ ██╔╝
|
||||||
|
██████╔╝█████╗ ██║ ██║ ██║██╔████╔██║██╔██╗ ██║ ╚████╔╝
|
||||||
|
██╔══██╗██╔══╝ ██║ ██║ ██║██║╚██╔╝██║██║╚██╗██║ ╚██╔╝
|
||||||
|
██████╔╝███████╗╚██████╗ ╚██████╔╝██║ ╚═╝ ██║██║ ╚████║ ██║
|
||||||
|
╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝
|
||||||
|
|
||||||
|
B E C O M N Y
|
||||||
|
""")
|
||||||
|
|
||||||
|
elif _args.session.lower() == "flomni":
|
||||||
|
from csaxs_bec.bec_ipython_client.plugins.flomni import Flomni
|
||||||
|
|
||||||
|
_session_name = "flomni"
|
||||||
|
flomni = Flomni(bec)
|
||||||
|
logger.success("flomni session loaded.")
|
||||||
|
print(r"""
|
||||||
|
██████╗ ███████╗ ██████╗ ███████╗██╗ ██████╗ ███╗ ███╗███╗ ██╗██╗
|
||||||
|
██╔══██╗██╔════╝██╔════╝ ██╔════╝██║ ██╔═══██╗████╗ ████║████╗ ██║██║
|
||||||
|
██████╔╝█████╗ ██║ █████╗ ██║ ██║ ██║██╔████╔██║██╔██╗ ██║██║
|
||||||
|
██╔══██╗██╔══╝ ██║ ██╔══╝ ██║ ██║ ██║██║╚██╔╝██║██║╚██╗██║██║
|
||||||
|
██████╔╝███████╗╚██████╗ ██║ ███████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚████║██║
|
||||||
|
╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝
|
||||||
|
|
||||||
|
B E C f l O M N I
|
||||||
|
""")
|
||||||
|
|
||||||
|
|
||||||
# SETUP BEAMLINE INFO
|
# SETUP BEAMLINE INFO
|
||||||
from bec_ipython_client.plugins.SLS.sls_info import OperatorInfo, SLSInfo
|
from bec_ipython_client.plugins.SLS.sls_info import OperatorInfo, SLSInfo
|
||||||
|
|||||||
@@ -13,69 +13,10 @@ logger = bec_logger.logger
|
|||||||
|
|
||||||
|
|
||||||
_Widgets = {
|
_Widgets = {
|
||||||
"OmnyAlignment": "OmnyAlignment",
|
|
||||||
"XRayEye": "XRayEye",
|
"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):
|
class XRayEye(RPCBase):
|
||||||
@rpc_call
|
@rpc_call
|
||||||
def active_roi(self) -> "BaseROI | None":
|
def active_roi(self) -> "BaseROI | None":
|
||||||
@@ -83,20 +24,6 @@ class XRayEye(RPCBase):
|
|||||||
Return the currently active ROI, or None if no ROI is active.
|
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
|
@property
|
||||||
@rpc_call
|
@rpc_call
|
||||||
def user_message(self):
|
def user_message(self):
|
||||||
@@ -111,6 +38,30 @@ class XRayEye(RPCBase):
|
|||||||
None
|
None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@rpc_call
|
||||||
|
def on_live_view_enabled(self, enabled: "bool"):
|
||||||
|
"""
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
|
||||||
|
@rpc_call
|
||||||
|
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
|
||||||
|
"""
|
||||||
|
|
||||||
|
@rpc_call
|
||||||
|
def enable_submit_button(self, enable: "bool"):
|
||||||
|
"""
|
||||||
|
Enable/disable submit button.
|
||||||
|
Args:
|
||||||
|
enable(int): -1 disable else enable
|
||||||
|
"""
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@rpc_call
|
@rpc_call
|
||||||
def sample_name(self):
|
def sample_name(self):
|
||||||
@@ -139,6 +90,18 @@ class XRayEye(RPCBase):
|
|||||||
None
|
None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@rpc_call
|
||||||
|
def switch_tab(self, tab: "str"):
|
||||||
|
"""
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
|
||||||
|
@rpc_call
|
||||||
|
def submit_fit_array(self, fit_array):
|
||||||
|
"""
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class XRayEye2DControl(RPCBase):
|
class XRayEye2DControl(RPCBase):
|
||||||
@rpc_call
|
@rpc_call
|
||||||
@@ -146,3 +109,15 @@ class XRayEye2DControl(RPCBase):
|
|||||||
"""
|
"""
|
||||||
Cleanup the BECConnector
|
Cleanup the BECConnector
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@rpc_call
|
||||||
|
def attach(self):
|
||||||
|
"""
|
||||||
|
None
|
||||||
|
"""
|
||||||
|
|
||||||
|
@rpc_call
|
||||||
|
def detach(self):
|
||||||
|
"""
|
||||||
|
Detach the widget from its parent dock widget (if widget is in the dock), making it a floating widget.
|
||||||
|
"""
|
||||||
|
|||||||
@@ -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()
|
|
||||||
@@ -5,6 +5,7 @@ from bec_lib.endpoints import MessageEndpoints
|
|||||||
from bec_qthemes import material_icon
|
from bec_qthemes import material_icon
|
||||||
from bec_widgets import BECWidget, SafeProperty, SafeSlot
|
from bec_widgets import BECWidget, SafeProperty, SafeSlot
|
||||||
from bec_widgets.widgets.plots.image.image import Image
|
from bec_widgets.widgets.plots.image.image import Image
|
||||||
|
from bec_widgets.widgets.plots.waveform.waveform import Waveform
|
||||||
from bec_widgets.widgets.plots.image.setting_widgets.image_roi_tree import ROIPropertyTree
|
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.plots.roi.image_roi import BaseROI, CircularROI, RectangularROI
|
||||||
from bec_widgets.widgets.utility.toggle.toggle import ToggleSwitch
|
from bec_widgets.widgets.utility.toggle.toggle import ToggleSwitch
|
||||||
@@ -21,7 +22,10 @@ from qtpy.QtWidgets import (
|
|||||||
QToolButton,
|
QToolButton,
|
||||||
QVBoxLayout,
|
QVBoxLayout,
|
||||||
QWidget,
|
QWidget,
|
||||||
|
QTextEdit,
|
||||||
|
QTabWidget,
|
||||||
)
|
)
|
||||||
|
import time
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
CAMERA = ("cam_xeye", "image")
|
CAMERA = ("cam_xeye", "image")
|
||||||
@@ -33,46 +37,48 @@ class XRayEye2DControl(BECWidget, QWidget):
|
|||||||
self.get_bec_shortcuts()
|
self.get_bec_shortcuts()
|
||||||
self._step_size = step_size
|
self._step_size = step_size
|
||||||
self.root_layout = QGridLayout(self)
|
self.root_layout = QGridLayout(self)
|
||||||
self.setStyleSheet("""
|
self.setStyleSheet(
|
||||||
|
"""
|
||||||
QToolButton {
|
QToolButton {
|
||||||
border: 1px solid;
|
border: 1px solid;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
# Up
|
# Up
|
||||||
self.move_up_button = QToolButton(parent=self)
|
self.move_up_button = QToolButton(parent=self)
|
||||||
self.move_up_button.setIcon(material_icon('keyboard_double_arrow_up'))
|
self.move_up_button.setIcon(material_icon("keyboard_double_arrow_up"))
|
||||||
self.root_layout.addWidget(self.move_up_button, 0, 2)
|
self.root_layout.addWidget(self.move_up_button, 0, 2)
|
||||||
# Up tweak button
|
# Up tweak button
|
||||||
self.move_up_tweak_button = QToolButton(parent=self)
|
self.move_up_tweak_button = QToolButton(parent=self)
|
||||||
self.move_up_tweak_button.setIcon(material_icon('keyboard_arrow_up'))
|
self.move_up_tweak_button.setIcon(material_icon("keyboard_arrow_up"))
|
||||||
self.root_layout.addWidget(self.move_up_tweak_button, 1, 2)
|
self.root_layout.addWidget(self.move_up_tweak_button, 1, 2)
|
||||||
|
|
||||||
# Left
|
# Left
|
||||||
self.move_left_button = QToolButton(parent=self)
|
self.move_left_button = QToolButton(parent=self)
|
||||||
self.move_left_button.setIcon(material_icon('keyboard_double_arrow_left'))
|
self.move_left_button.setIcon(material_icon("keyboard_double_arrow_left"))
|
||||||
self.root_layout.addWidget(self.move_left_button, 2, 0)
|
self.root_layout.addWidget(self.move_left_button, 2, 0)
|
||||||
# Left tweak button
|
# Left tweak button
|
||||||
self.move_left_tweak_button = QToolButton(parent=self)
|
self.move_left_tweak_button = QToolButton(parent=self)
|
||||||
self.move_left_tweak_button.setIcon(material_icon('keyboard_arrow_left'))
|
self.move_left_tweak_button.setIcon(material_icon("keyboard_arrow_left"))
|
||||||
self.root_layout.addWidget(self.move_left_tweak_button, 2, 1)
|
self.root_layout.addWidget(self.move_left_tweak_button, 2, 1)
|
||||||
|
|
||||||
# Right
|
# Right
|
||||||
self.move_right_button = QToolButton(parent=self)
|
self.move_right_button = QToolButton(parent=self)
|
||||||
self.move_right_button.setIcon(material_icon('keyboard_double_arrow_right'))
|
self.move_right_button.setIcon(material_icon("keyboard_double_arrow_right"))
|
||||||
self.root_layout.addWidget(self.move_right_button, 2, 4)
|
self.root_layout.addWidget(self.move_right_button, 2, 4)
|
||||||
# Right tweak button
|
# Right tweak button
|
||||||
self.move_right_tweak_button = QToolButton(parent=self)
|
self.move_right_tweak_button = QToolButton(parent=self)
|
||||||
self.move_right_tweak_button.setIcon(material_icon('keyboard_arrow_right'))
|
self.move_right_tweak_button.setIcon(material_icon("keyboard_arrow_right"))
|
||||||
self.root_layout.addWidget(self.move_right_tweak_button, 2, 3)
|
self.root_layout.addWidget(self.move_right_tweak_button, 2, 3)
|
||||||
|
|
||||||
# Down
|
# Down
|
||||||
self.move_down_button = QToolButton(parent=self)
|
self.move_down_button = QToolButton(parent=self)
|
||||||
self.move_down_button.setIcon(material_icon('keyboard_double_arrow_down'))
|
self.move_down_button.setIcon(material_icon("keyboard_double_arrow_down"))
|
||||||
self.root_layout.addWidget(self.move_down_button, 4, 2)
|
self.root_layout.addWidget(self.move_down_button, 4, 2)
|
||||||
# Down tweak button
|
# Down tweak button
|
||||||
self.move_down_tweak_button = QToolButton(parent=self)
|
self.move_down_tweak_button = QToolButton(parent=self)
|
||||||
self.move_down_tweak_button.setIcon(material_icon('keyboard_arrow_down'))
|
self.move_down_tweak_button.setIcon(material_icon("keyboard_arrow_down"))
|
||||||
self.root_layout.addWidget(self.move_down_tweak_button, 3, 2)
|
self.root_layout.addWidget(self.move_down_tweak_button, 3, 2)
|
||||||
|
|
||||||
# Connections
|
# Connections
|
||||||
@@ -124,8 +130,20 @@ class XRayEye2DControl(BECWidget, QWidget):
|
|||||||
|
|
||||||
|
|
||||||
class XRayEye(BECWidget, QWidget):
|
class XRayEye(BECWidget, QWidget):
|
||||||
USER_ACCESS = ["active_roi", "enable_live_view", "enable_live_view.setter", "user_message", "user_message.setter",
|
USER_ACCESS = [
|
||||||
"sample_name", "sample_name.setter", "enable_move_buttons", "enable_move_buttons.setter"]
|
"active_roi",
|
||||||
|
"user_message",
|
||||||
|
"user_message.setter",
|
||||||
|
"on_live_view_enabled",
|
||||||
|
"on_motors_enable",
|
||||||
|
"enable_submit_button",
|
||||||
|
"sample_name",
|
||||||
|
"sample_name.setter",
|
||||||
|
"enable_move_buttons",
|
||||||
|
"enable_move_buttons.setter",
|
||||||
|
"switch_tab",
|
||||||
|
"submit_fit_array",
|
||||||
|
]
|
||||||
PLUGIN = True
|
PLUGIN = True
|
||||||
|
|
||||||
def __init__(self, parent=None, **kwargs):
|
def __init__(self, parent=None, **kwargs):
|
||||||
@@ -136,28 +154,44 @@ class XRayEye(BECWidget, QWidget):
|
|||||||
self._make_connections()
|
self._make_connections()
|
||||||
|
|
||||||
# Connection to redis endpoints
|
# Connection to redis endpoints
|
||||||
self.bec_dispatcher.connect_slot(self.device_updates, MessageEndpoints.device_readback("omny_xray_gui"))
|
self.bec_dispatcher.connect_slot(
|
||||||
|
self.getting_shutter_status, MessageEndpoints.device_readback("omnyfsh")
|
||||||
|
)
|
||||||
|
self.bec_dispatcher.connect_slot(
|
||||||
|
self.getting_camera_status, MessageEndpoints.device_read_configuration(CAMERA[0])
|
||||||
|
)
|
||||||
|
|
||||||
self.connect_motors()
|
self.connect_motors()
|
||||||
self.resize(800, 600)
|
self.resize(800, 600)
|
||||||
QTimer.singleShot(0, self._init_gui_trigger)
|
QTimer.singleShot(0, self._init_gui_trigger)
|
||||||
|
|
||||||
def _init_ui(self):
|
def _init_ui(self):
|
||||||
self.core_layout = QHBoxLayout(self)
|
self.root_layout = QVBoxLayout(self)
|
||||||
|
self.tab_widget = QTabWidget(parent=self)
|
||||||
|
self.root_layout.addWidget(self.tab_widget)
|
||||||
|
|
||||||
self.image = Image(parent=self)
|
self.alignment_tab = QWidget(parent=self)
|
||||||
self.image.enable_toolbar = False # Disable default toolbar to not allow to user set anything
|
self.core_layout = QHBoxLayout(self.alignment_tab)
|
||||||
|
|
||||||
|
self.image = Image(parent=self.alignment_tab)
|
||||||
|
self.image.color_map = "CET-L2"
|
||||||
|
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.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
|
self.image.enable_full_colorbar = True
|
||||||
|
self.image.invert_y = True # Invert y axis to match image coordinates
|
||||||
|
|
||||||
# Control panel on the right: vertical layout inside a fixed-width widget
|
# Control panel on the right: vertical layout inside a fixed-width widget
|
||||||
self.control_panel = QWidget(parent=self)
|
self.control_panel = QWidget(parent=self.alignment_tab)
|
||||||
self.control_panel_layout = QVBoxLayout(self.control_panel)
|
self.control_panel_layout = QVBoxLayout(self.control_panel)
|
||||||
self.control_panel_layout.setContentsMargins(0, 0, 0, 0)
|
self.control_panel_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
self.control_panel_layout.setSpacing(10)
|
self.control_panel_layout.setSpacing(10)
|
||||||
|
|
||||||
# ROI toolbar + Live toggle (header row)
|
# ROI toolbar + Live toggle (header row)
|
||||||
self.roi_manager = ROIPropertyTree(parent=self, image_widget=self.image, compact=True,
|
self.roi_manager = ROIPropertyTree(
|
||||||
compact_orientation="horizontal")
|
parent=self, image_widget=self.image, compact=True, compact_orientation="horizontal"
|
||||||
|
)
|
||||||
header_row = QHBoxLayout()
|
header_row = QHBoxLayout()
|
||||||
header_row.setContentsMargins(0, 0, 0, 0)
|
header_row.setContentsMargins(0, 0, 0, 0)
|
||||||
header_row.setSpacing(8)
|
header_row.setSpacing(8)
|
||||||
@@ -166,16 +200,36 @@ class XRayEye(BECWidget, QWidget):
|
|||||||
self.live_preview_label = QLabel("Live Preview", parent=self)
|
self.live_preview_label = QLabel("Live Preview", parent=self)
|
||||||
self.live_preview_toggle = ToggleSwitch(parent=self)
|
self.live_preview_toggle = ToggleSwitch(parent=self)
|
||||||
self.live_preview_toggle.checked = False
|
self.live_preview_toggle.checked = False
|
||||||
header_row.addWidget(self.live_preview_label, 0, Qt.AlignVCenter)
|
header_row.addWidget(self.live_preview_label, 0, Qt.AlignmentFlag.AlignVCenter)
|
||||||
header_row.addWidget(self.live_preview_toggle, 0, Qt.AlignVCenter)
|
header_row.addWidget(self.live_preview_toggle, 0, Qt.AlignmentFlag.AlignVCenter)
|
||||||
self.control_panel_layout.addLayout(header_row)
|
self.control_panel_layout.addLayout(header_row)
|
||||||
|
|
||||||
|
switch_row = QHBoxLayout()
|
||||||
|
switch_row.setContentsMargins(0, 0, 0, 0)
|
||||||
|
switch_row.setSpacing(8)
|
||||||
|
switch_row.addStretch()
|
||||||
|
self.camera_running_label = QLabel("Camera running", parent=self)
|
||||||
|
self.camera_running_toggle = ToggleSwitch(parent=self)
|
||||||
|
# self.camera_running_toggle.checked = False
|
||||||
|
self.camera_running_toggle.enabled.connect(self.camera_running_enabled)
|
||||||
|
self.shutter_label = QLabel("Shutter open", parent=self)
|
||||||
|
self.shutter_toggle = ToggleSwitch(parent=self)
|
||||||
|
# self.shutter_toggle.checked = False
|
||||||
|
self.shutter_toggle.enabled.connect(self.opening_shutter)
|
||||||
|
switch_row.addWidget(self.shutter_label, 0, Qt.AlignmentFlag.AlignVCenter)
|
||||||
|
switch_row.addWidget(self.shutter_toggle, 0, Qt.AlignmentFlag.AlignVCenter)
|
||||||
|
switch_row.addWidget(self.camera_running_label, 0, Qt.AlignmentFlag.AlignVCenter)
|
||||||
|
switch_row.addWidget(self.camera_running_toggle, 0, Qt.AlignmentFlag.AlignVCenter)
|
||||||
|
self.control_panel_layout.addLayout(switch_row)
|
||||||
|
|
||||||
# separator
|
# separator
|
||||||
self.control_panel_layout.addWidget(self._create_separator())
|
self.control_panel_layout.addWidget(self._create_separator())
|
||||||
|
|
||||||
# 2D Positioner (fixed size)
|
# 2D Positioner (fixed size)
|
||||||
self.motor_control_2d = XRayEye2DControl(parent=self)
|
self.motor_control_2d = XRayEye2DControl(parent=self)
|
||||||
self.control_panel_layout.addWidget(self.motor_control_2d, 0, Qt.AlignTop | Qt.AlignCenter)
|
self.control_panel_layout.addWidget(
|
||||||
|
self.motor_control_2d, 0, Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignCenter
|
||||||
|
)
|
||||||
|
|
||||||
# separator
|
# separator
|
||||||
self.control_panel_layout.addWidget(self._create_separator())
|
self.control_panel_layout.addWidget(self._create_separator())
|
||||||
@@ -190,9 +244,8 @@ class XRayEye(BECWidget, QWidget):
|
|||||||
# Submit button
|
# Submit button
|
||||||
self.submit_button = QPushButton("Submit", parent=self)
|
self.submit_button = QPushButton("Submit", parent=self)
|
||||||
# Add to layout form
|
# Add to layout form
|
||||||
step_size_form.addWidget(QLabel("Horizontal", parent=self), 0, 0)
|
step_size_form.addWidget(QLabel("Step Size", parent=self), 0, 0)
|
||||||
step_size_form.addWidget(self.step_size, 0, 1)
|
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)
|
step_size_form.addWidget(self.submit_button, 2, 0, 1, 2)
|
||||||
|
|
||||||
# Add form to control panel
|
# Add form to control panel
|
||||||
@@ -207,7 +260,8 @@ class XRayEye(BECWidget, QWidget):
|
|||||||
self.sample_name_line_edit.setReadOnly(True)
|
self.sample_name_line_edit.setReadOnly(True)
|
||||||
form.addWidget(QLabel("Sample", parent=self), 0, 0)
|
form.addWidget(QLabel("Sample", parent=self), 0, 0)
|
||||||
form.addWidget(self.sample_name_line_edit, 0, 1)
|
form.addWidget(self.sample_name_line_edit, 0, 1)
|
||||||
self.message_line_edit = QLineEdit(parent=self)
|
self.message_line_edit = QTextEdit(parent=self)
|
||||||
|
self.message_line_edit.setFixedHeight(60)
|
||||||
self.message_line_edit.setReadOnly(True)
|
self.message_line_edit.setReadOnly(True)
|
||||||
form.addWidget(QLabel("Message", parent=self), 1, 0)
|
form.addWidget(QLabel("Message", parent=self), 1, 0)
|
||||||
form.addWidget(self.message_line_edit, 1, 1)
|
form.addWidget(self.message_line_edit, 1, 1)
|
||||||
@@ -217,12 +271,53 @@ class XRayEye(BECWidget, QWidget):
|
|||||||
self.control_panel.adjustSize()
|
self.control_panel.adjustSize()
|
||||||
p_hint = self.control_panel.sizeHint()
|
p_hint = self.control_panel.sizeHint()
|
||||||
self.control_panel.setFixedWidth(p_hint.width())
|
self.control_panel.setFixedWidth(p_hint.width())
|
||||||
self.control_panel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
|
self.control_panel.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding)
|
||||||
|
|
||||||
# Core Layout: image (expanding) | control panel (fixed)
|
# Core Layout: image (expanding) | control panel (fixed)
|
||||||
self.core_layout.addWidget(self.image)
|
self.core_layout.addWidget(self.image)
|
||||||
self.core_layout.addWidget(self.control_panel)
|
self.core_layout.addWidget(self.control_panel)
|
||||||
|
|
||||||
|
self.tab_widget.addTab(self.alignment_tab, "Alignment")
|
||||||
|
|
||||||
|
self.fit_tab = QWidget(parent=self)
|
||||||
|
self.fit_layout = QVBoxLayout(self.fit_tab)
|
||||||
|
self.waveform_x = Waveform(parent=self.fit_tab)
|
||||||
|
self.waveform_y = Waveform(parent=self.fit_tab)
|
||||||
|
|
||||||
|
self.waveform_x.plot(
|
||||||
|
x=[0],
|
||||||
|
y=[1],
|
||||||
|
label="fit-x",
|
||||||
|
dap=["SineModel","LinearModel"],
|
||||||
|
dap_parameters=[
|
||||||
|
{"frequency": {"value": 0.0174533, "vary": False, "min": 0.01, "max": 0.02}},{"slope": {"value": 0, "vary": False, "min": 0.0, "max": 0.02}}],
|
||||||
|
dap_oversample=5,
|
||||||
|
)
|
||||||
|
self.waveform_y.plot(
|
||||||
|
x=[0],
|
||||||
|
y=[2],
|
||||||
|
label="fit-y",
|
||||||
|
dap=["SineModel","LinearModel"],
|
||||||
|
dap_parameters=[
|
||||||
|
{"frequency": {"value": 0.0174533, "vary": False, "min": 0.01, "max": 0.02}},{"slope": {"value": 0, "vary": False, "min": 0.0, "max": 0.02}}],
|
||||||
|
dap_oversample=5,
|
||||||
|
)
|
||||||
|
self.fit_x = self.waveform_x.curves[0]
|
||||||
|
self.fit_y = self.waveform_y.curves[0]
|
||||||
|
|
||||||
|
self.waveform_x.dap_params_update.connect(self.on_dap_params)
|
||||||
|
self.waveform_y.dap_params_update.connect(self.on_dap_params)
|
||||||
|
|
||||||
|
for wave in (self.waveform_x, self.waveform_y):
|
||||||
|
wave.x_label = "Angle (deg)"
|
||||||
|
wave.x_grid = True
|
||||||
|
wave.y_grid = True
|
||||||
|
wave.enable_toolbar = True
|
||||||
|
|
||||||
|
self.fit_layout.addWidget(self.waveform_x)
|
||||||
|
self.fit_layout.addWidget(self.waveform_y)
|
||||||
|
self.tab_widget.addTab(self.fit_tab, "Fit")
|
||||||
|
|
||||||
def _make_connections(self):
|
def _make_connections(self):
|
||||||
# Fetch initial state
|
# Fetch initial state
|
||||||
self.on_live_view_enabled(True)
|
self.on_live_view_enabled(True)
|
||||||
@@ -230,31 +325,36 @@ class XRayEye(BECWidget, QWidget):
|
|||||||
|
|
||||||
# Make connections
|
# Make connections
|
||||||
self.live_preview_toggle.enabled.connect(self.on_live_view_enabled)
|
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.step_size.valueChanged.connect(
|
||||||
|
lambda x: self.motor_control_2d.setProperty("step_size", x)
|
||||||
|
)
|
||||||
self.submit_button.clicked.connect(self.submit)
|
self.submit_button.clicked.connect(self.submit)
|
||||||
|
|
||||||
def _create_separator(self):
|
def _create_separator(self):
|
||||||
sep = QFrame(parent=self)
|
sep = QFrame(parent=self)
|
||||||
sep.setFrameShape(QFrame.HLine)
|
sep.setFrameShape(QFrame.Shape.HLine)
|
||||||
sep.setFrameShadow(QFrame.Sunken)
|
sep.setFrameShadow(QFrame.Shadow.Sunken)
|
||||||
sep.setLineWidth(1)
|
sep.setLineWidth(1)
|
||||||
return sep
|
return sep
|
||||||
|
|
||||||
def _init_gui_trigger(self):
|
def _init_gui_trigger(self):
|
||||||
self.dev.omny_xray_gui.read()
|
self.dev.omny_xray_gui.read()
|
||||||
|
self.dev.omnyfsh.read()
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Device Connection logic
|
# Device Connection logic
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
def connect_motors(self):
|
def connect_motors(self):
|
||||||
""" Checks one of the possible motors for flomni, omny and lamni setup."""
|
"""Checks one of the possible motors for flomni, omny and lamni setup."""
|
||||||
possible_motors = ['osamroy', 'lsamrot', 'fsamroy']
|
possible_motors = ["osamroy", "lsamrot", "fsamroy"]
|
||||||
|
|
||||||
for motor in possible_motors:
|
for motor in possible_motors:
|
||||||
if motor in self.dev:
|
if motor in self.dev:
|
||||||
self.bec_dispatcher.connect_slot(self.on_tomo_angle_readback, MessageEndpoints.device_readback(motor))
|
self.bec_dispatcher.connect_slot(
|
||||||
logger.info(f"Succesfully connected to {motor}")
|
self.on_tomo_angle_readback, MessageEndpoints.device_readback(motor)
|
||||||
|
)
|
||||||
|
logger.info(f"Successfully connected to {motor}")
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Properties ported from the original OmnyAlignment, can be adjusted as needed
|
# Properties ported from the original OmnyAlignment, can be adjusted as needed
|
||||||
@@ -291,6 +391,13 @@ class XRayEye(BECWidget, QWidget):
|
|||||||
# Slots ported from the original OmnyAlignment, can be adjusted as needed
|
# Slots ported from the original OmnyAlignment, can be adjusted as needed
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
|
@SafeSlot(str)
|
||||||
|
def switch_tab(self, tab: str):
|
||||||
|
if tab == "fit":
|
||||||
|
self.tab_widget.setCurrentIndex(1)
|
||||||
|
else:
|
||||||
|
self.tab_widget.setCurrentIndex(0)
|
||||||
|
|
||||||
@SafeSlot()
|
@SafeSlot()
|
||||||
def get_roi_coordinates(self) -> dict | None:
|
def get_roi_coordinates(self) -> dict | None:
|
||||||
"""Get the coordinates of the currently active ROI."""
|
"""Get the coordinates of the currently active ROI."""
|
||||||
@@ -307,14 +414,48 @@ class XRayEye(BECWidget, QWidget):
|
|||||||
self.live_preview_toggle.blockSignals(True)
|
self.live_preview_toggle.blockSignals(True)
|
||||||
if enabled:
|
if enabled:
|
||||||
self.live_preview_toggle.checked = enabled
|
self.live_preview_toggle.checked = enabled
|
||||||
self.image.image(CAMERA)
|
self.image.image(device=CAMERA[0], signal=CAMERA[1])
|
||||||
self.live_preview_toggle.blockSignals(False)
|
self.live_preview_toggle.blockSignals(False)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.image.disconnect_monitor(CAMERA)
|
self.image.disconnect_monitor(CAMERA[0], CAMERA[1])
|
||||||
self.live_preview_toggle.checked = enabled
|
self.live_preview_toggle.checked = enabled
|
||||||
self.live_preview_toggle.blockSignals(False)
|
self.live_preview_toggle.blockSignals(False)
|
||||||
|
|
||||||
|
@SafeSlot(bool)
|
||||||
|
def camera_running_enabled(self, enabled: bool):
|
||||||
|
logger.info(f"Camera running: {enabled}")
|
||||||
|
self.camera_running_toggle.blockSignals(True)
|
||||||
|
self.dev.get(CAMERA[0]).live_mode_enabled.put(enabled)
|
||||||
|
self.camera_running_toggle.checked = enabled
|
||||||
|
self.camera_running_toggle.blockSignals(False)
|
||||||
|
|
||||||
|
@SafeSlot(dict, dict)
|
||||||
|
def getting_camera_status(self, data, meta):
|
||||||
|
print(f"msg:{data}")
|
||||||
|
live_mode_enabled = data.get("signals").get(f"{CAMERA[0]}_live_mode_enabled").get("value")
|
||||||
|
self.camera_running_toggle.blockSignals(True)
|
||||||
|
self.camera_running_toggle.checked = live_mode_enabled
|
||||||
|
self.camera_running_toggle.blockSignals(False)
|
||||||
|
|
||||||
|
@SafeSlot(bool)
|
||||||
|
def opening_shutter(self, enabled: bool):
|
||||||
|
logger.info(f"Shutter changed from GUI to: {enabled}")
|
||||||
|
self.shutter_toggle.blockSignals(True)
|
||||||
|
if enabled:
|
||||||
|
self.dev.omnyfsh.fshopen()
|
||||||
|
else:
|
||||||
|
self.dev.omnyfsh.fshclose()
|
||||||
|
# self.shutter_toggle.checked = enabled
|
||||||
|
self.shutter_toggle.blockSignals(False)
|
||||||
|
|
||||||
|
@SafeSlot(dict, dict)
|
||||||
|
def getting_shutter_status(self, data, meta):
|
||||||
|
shutter_open = bool(data.get("signals").get("omnyfsh_shutter").get("value"))
|
||||||
|
self.shutter_toggle.blockSignals(True)
|
||||||
|
self.shutter_toggle.checked = shutter_open
|
||||||
|
self.shutter_toggle.blockSignals(False)
|
||||||
|
|
||||||
@SafeSlot(bool, bool)
|
@SafeSlot(bool, bool)
|
||||||
def on_motors_enable(self, x_enable: bool, y_enable: bool):
|
def on_motors_enable(self, x_enable: bool, y_enable: bool):
|
||||||
"""
|
"""
|
||||||
@@ -327,98 +468,109 @@ class XRayEye(BECWidget, QWidget):
|
|||||||
self.motor_control_2d.enable_controls_hor(x_enable)
|
self.motor_control_2d.enable_controls_hor(x_enable)
|
||||||
self.motor_control_2d.enable_controls_ver(y_enable)
|
self.motor_control_2d.enable_controls_ver(y_enable)
|
||||||
|
|
||||||
@SafeSlot(int)
|
@SafeSlot(bool)
|
||||||
def enable_submit_button(self, enable: int):
|
def enable_submit_button(self, enable: bool):
|
||||||
"""
|
"""
|
||||||
Enable/disable submit button.
|
Enable/disable submit button.
|
||||||
Args:
|
Args:
|
||||||
enable(int): -1 disable else enable
|
enable(int): -1 disable else enable
|
||||||
"""
|
"""
|
||||||
if enable == -1:
|
if enable:
|
||||||
self.submit_button.setEnabled(False)
|
|
||||||
else:
|
|
||||||
self.submit_button.setEnabled(True)
|
self.submit_button.setEnabled(True)
|
||||||
|
else:
|
||||||
|
self.submit_button.setEnabled(False)
|
||||||
|
|
||||||
|
@SafeSlot(dict, dict)
|
||||||
|
def on_dap_params(self, data, meta):
|
||||||
|
print("#######################################")
|
||||||
|
print("getting dap parameters")
|
||||||
|
print(f"data: {data}")
|
||||||
|
print(f"meta: {meta}")
|
||||||
|
self.waveform_x.auto_range(True)
|
||||||
|
self.waveform_y.auto_range(True)
|
||||||
|
# self.bec_dispatcher.disconnect_slot(self.device_updates, MessageEndpoints.device_readback("omny_xray_gui"))
|
||||||
|
curve_id = meta.get("curve_id")
|
||||||
|
|
||||||
|
if curve_id == "fit-x-SineModel+LinearModel":
|
||||||
|
self.dev.omny_xray_gui.fit_params_x.set(data).wait()
|
||||||
|
print(f"setting x data to {data}")
|
||||||
|
else:
|
||||||
|
self.dev.omny_xray_gui.fit_params_y.set(data).wait()
|
||||||
|
print(f"setting y data to {data}")
|
||||||
|
# self.bec_dispatcher.connect_slot(self.device_updates, MessageEndpoints.device_readback("omny_xray_gui"))
|
||||||
|
|
||||||
@SafeSlot(bool, bool)
|
@SafeSlot(bool, bool)
|
||||||
def on_tomo_angle_readback(self, data: dict, meta: dict):
|
def on_tomo_angle_readback(self, data: dict, meta: dict):
|
||||||
#TODO implement if needed
|
# TODO implement if needed
|
||||||
print(f"data: {data}")
|
print(f"data: {data}")
|
||||||
print(f"meta: {meta}")
|
print(f"meta: {meta}")
|
||||||
|
|
||||||
@SafeSlot(dict, dict)
|
@SafeSlot()
|
||||||
def device_updates(self, data: dict, meta: dict):
|
def submit_fit_array(self, fit_array):
|
||||||
"""
|
self.tab_widget.setCurrentIndex(1)
|
||||||
Slot to handle device updates from omny_xray_gui device.
|
# self.fix_x.title = " got fit array"
|
||||||
|
print(f"got fit array {fit_array}")
|
||||||
Args:
|
self.waveform_x.curves[0].set_data(x=fit_array[0], y=fit_array[1])
|
||||||
data(dict): data from device
|
self.waveform_y.curves[0].set_data(x=fit_array[0], y=fit_array[2])
|
||||||
meta(dict): metadata from device
|
# self.fit_x.set_data(x=fit_array[0],y=fit_array[1])
|
||||||
"""
|
# self.fit_y.set_data(x=fit_array[0],y=fit_array[2])
|
||||||
|
|
||||||
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()
|
@SafeSlot()
|
||||||
def submit(self):
|
def submit(self):
|
||||||
"""Execute submit action by submit button."""
|
"""Execute submit action by submit button."""
|
||||||
|
print("submit pushed")
|
||||||
|
self.submit_button.blockSignals(True)
|
||||||
if self.roi_manager.single_active_roi is None:
|
if self.roi_manager.single_active_roi is None:
|
||||||
logger.warning("No active ROI")
|
logger.warning("No active ROI")
|
||||||
return
|
return
|
||||||
roi_coordinates = self.roi_manager.single_active_roi.get_coordinates()
|
roi_coordinates = self.roi_manager.single_active_roi.get_coordinates()
|
||||||
roi_center_x = roi_coordinates['center_x']
|
roi_center_x = roi_coordinates["center_x"]
|
||||||
roi_center_y = roi_coordinates['center_y']
|
roi_center_y = roi_coordinates["center_y"]
|
||||||
# Case of rectangular ROI
|
# Case of rectangular ROI
|
||||||
if isinstance(self.roi_manager.single_active_roi, RectangularROI):
|
if isinstance(self.roi_manager.single_active_roi, RectangularROI):
|
||||||
roi_width = roi_coordinates['width']
|
roi_width = roi_coordinates["width"]
|
||||||
roi_height = roi_coordinates['height']
|
roi_height = roi_coordinates["height"]
|
||||||
elif isinstance(self.roi_manager.single_active_roi, CircularROI):
|
elif isinstance(self.roi_manager.single_active_roi, CircularROI):
|
||||||
roi_width = roi_coordinates['diameter']
|
roi_width = roi_coordinates["diameter"]
|
||||||
roi_height = roi_coordinates['radius']
|
roi_height = roi_coordinates["radius"]
|
||||||
else:
|
else:
|
||||||
logger.warning("Unsupported ROI type for submit action.")
|
logger.warning("Unsupported ROI type for submit action.")
|
||||||
return
|
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
|
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
|
# submit roi coordinates
|
||||||
step = int(self.dev.omny_xray_gui.step.read().get("omny_xray_gui_step").get('value'))
|
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_x = getattr(self.dev.omny_xray_gui, 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)
|
xval_y = getattr(self.dev.omny_xray_gui, 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_x = getattr(self.dev.omny_xray_gui, f"width_x_{step}").set(roi_width)
|
||||||
width_y = getattr(self.dev.omny_xray_gui.width_y, f"width_y_{step}").set(roi_height)
|
width_y = getattr(self.dev.omny_xray_gui, f"width_y_{step}").set(roi_height)
|
||||||
self.dev.omny_xray_gui.submit.set(1)
|
self.dev.omny_xray_gui.submit.set(1)
|
||||||
|
print("submit done")
|
||||||
|
self.submit_button.blockSignals(False)
|
||||||
|
|
||||||
|
|
||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
"""Cleanup connections on widget close -> disconnect slots and stop live mode of camera."""
|
"""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"))
|
self.bec_dispatcher.disconnect_slot(
|
||||||
getattr(self.dev,CAMERA[0]).live_mode = False
|
self.device_updates, MessageEndpoints.device_readback("omny_xray_gui")
|
||||||
|
)
|
||||||
|
getattr(self.dev, CAMERA[0]).live_mode = False
|
||||||
super().cleanup()
|
super().cleanup()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from qtpy.QtWidgets import QApplication
|
from qtpy.QtWidgets import QApplication
|
||||||
|
from bec_widgets.utils import BECDispatcher
|
||||||
|
from bec_widgets.utils.colors import apply_theme
|
||||||
|
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
|
apply_theme("light")
|
||||||
|
dispatcher = BECDispatcher(gui_id="xray")
|
||||||
win = XRayEye()
|
win = XRayEye()
|
||||||
|
|
||||||
win.resize(1000, 800)
|
win.resize(1000, 800)
|
||||||
|
|||||||
@@ -9,27 +9,27 @@ eiger_1_5:
|
|||||||
readoutPriority: async
|
readoutPriority: async
|
||||||
softwareTrigger: False
|
softwareTrigger: False
|
||||||
|
|
||||||
eiger_9:
|
# eiger_9:
|
||||||
description: Eiger 9M detector
|
# description: Eiger 9M detector
|
||||||
deviceClass: csaxs_bec.devices.jungfraujoch.eiger_9m.Eiger9M
|
# deviceClass: csaxs_bec.devices.jungfraujoch.eiger_9m.Eiger9M
|
||||||
deviceConfig:
|
# deviceConfig:
|
||||||
detector_distance: 100
|
# detector_distance: 100
|
||||||
beam_center: [0, 0]
|
# beam_center: [0, 0]
|
||||||
onFailure: raise
|
# onFailure: raise
|
||||||
enabled: true
|
# enabled: true
|
||||||
readoutPriority: async
|
# readoutPriority: async
|
||||||
softwareTrigger: False
|
# softwareTrigger: False
|
||||||
|
|
||||||
ids_cam:
|
# ids_cam:
|
||||||
description: IDS camera for live image acquisition
|
# description: IDS camera for live image acquisition
|
||||||
deviceClass: csaxs_bec.devices.ids_cameras.IDSCamera
|
# deviceClass: csaxs_bec.devices.ids_cameras.IDSCamera
|
||||||
deviceConfig:
|
# deviceConfig:
|
||||||
camera_id: 201
|
# camera_id: 201
|
||||||
bits_per_pixel: 24
|
# bits_per_pixel: 24
|
||||||
m_n_colormode: 1
|
# m_n_colormode: 1
|
||||||
live_mode: True
|
# live_mode: True
|
||||||
onFailure: raise
|
# onFailure: raise
|
||||||
enabled: true
|
# enabled: true
|
||||||
readoutPriority: async
|
# readoutPriority: async
|
||||||
softwareTrigger: True
|
# softwareTrigger: True
|
||||||
|
|
||||||
|
|||||||
25
csaxs_bec/device_configs/bl_general.yaml
Normal file
25
csaxs_bec/device_configs/bl_general.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
############################################################
|
||||||
|
##################### EPS ##################################
|
||||||
|
############################################################
|
||||||
|
x12saEPS:
|
||||||
|
description: X12SA EPS info and control
|
||||||
|
deviceClass: csaxs_bec.devices.epics.eps.EPS
|
||||||
|
deviceConfig: {}
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
############################################################
|
||||||
|
##################### GalilRIO #############################
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
galilrioesxbox:
|
||||||
|
description: Galil RIO for remote gain switching and slow reading ES XBox
|
||||||
|
deviceClass: csaxs_bec.devices.omny.galil.galil_rio.GalilRIO
|
||||||
|
deviceConfig:
|
||||||
|
host: galilrioesxbox.psi.ch
|
||||||
|
enabled: true
|
||||||
|
onFailure: raise
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
# This is the main configuration file that is
|
# This is the main configuration file that is
|
||||||
# commented or uncommented according to the type of experiment
|
# commented or uncommented according to the type of experiment
|
||||||
|
|
||||||
optics:
|
# optics:
|
||||||
- !include ./bl_optics_hutch.yaml
|
# - !include ./bl_optics_hutch.yaml
|
||||||
|
|
||||||
frontend:
|
# frontend:
|
||||||
- !include ./bl_frontend.yaml
|
# - !include ./bl_frontend.yaml
|
||||||
|
|
||||||
endstation:
|
endstation:
|
||||||
- !include ./bl_endstation.yaml
|
- !include ./bl_endstation.yaml
|
||||||
@@ -16,8 +16,8 @@ detectors:
|
|||||||
#sastt:
|
#sastt:
|
||||||
# - !include ./sastt.yaml
|
# - !include ./sastt.yaml
|
||||||
|
|
||||||
#flomni:
|
flomni:
|
||||||
# - !include ./ptycho_flomni.yaml
|
- !include ./ptycho_flomni.yaml
|
||||||
|
|
||||||
#omny:
|
#omny:
|
||||||
# - !include ./ptycho_omny.yaml
|
# - !include ./ptycho_omny.yaml
|
||||||
|
|||||||
532
csaxs_bec/device_configs/ptycho_floimni_sim.yaml
Normal file
532
csaxs_bec/device_configs/ptycho_floimni_sim.yaml
Normal file
@@ -0,0 +1,532 @@
|
|||||||
|
############################################################
|
||||||
|
#################### flOMNI Galil motors ###################
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
feyex:
|
||||||
|
description: Xray eye X
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: D
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- -30
|
||||||
|
- -1
|
||||||
|
port: 8082
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
userParameter:
|
||||||
|
in: -16.267
|
||||||
|
out: -1
|
||||||
|
feyey:
|
||||||
|
description: Xray eye Y
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: E
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- -1
|
||||||
|
- -10
|
||||||
|
port: 8082
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
userParameter:
|
||||||
|
in: -10.467
|
||||||
|
fheater:
|
||||||
|
description: Heater Y
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: C
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- -15
|
||||||
|
- 0
|
||||||
|
port: 8082
|
||||||
|
sign: -1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
foptx:
|
||||||
|
description: Optics X
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: B
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- -17
|
||||||
|
- -12
|
||||||
|
port: 8082
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
userParameter:
|
||||||
|
in: -13.761
|
||||||
|
fopty:
|
||||||
|
description: Optics Y
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: F
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- 0
|
||||||
|
- 4
|
||||||
|
port: 8082
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
userParameter:
|
||||||
|
in: 0.552
|
||||||
|
out: 0.752
|
||||||
|
foptz:
|
||||||
|
description: Optics Z
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: A
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- 0
|
||||||
|
- 27
|
||||||
|
port: 8082
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
userParameter:
|
||||||
|
in: 23
|
||||||
|
fsamroy:
|
||||||
|
description: Sample rotation
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: A
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- -5
|
||||||
|
- 365
|
||||||
|
port: 8084
|
||||||
|
sign: -1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
fsamx:
|
||||||
|
description: Sample coarse X
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: E
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- -162
|
||||||
|
- 0
|
||||||
|
port: 8081
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
userParameter:
|
||||||
|
in: -1.1
|
||||||
|
fsamy:
|
||||||
|
description: Sample coarse Y
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: F
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- 2
|
||||||
|
- 3.1
|
||||||
|
port: 8081
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
userParameter:
|
||||||
|
in: 2.75
|
||||||
|
ftracky:
|
||||||
|
description: Laser Tracker coarse Y
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
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
|
||||||
|
connectionTimeout: 20
|
||||||
|
ftrackz:
|
||||||
|
description: Laser Tracker coarse Z
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
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
|
||||||
|
connectionTimeout: 20
|
||||||
|
ftransx:
|
||||||
|
description: Sample transer X
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: C
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- 0
|
||||||
|
- 50
|
||||||
|
port: 8081
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
ftransy:
|
||||||
|
description: Sample transer Y
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: A
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- -100
|
||||||
|
- 0
|
||||||
|
port: 8081
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
userParameter:
|
||||||
|
sensor_voltage: -2.4
|
||||||
|
ftransz:
|
||||||
|
description: Sample transer Z
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: B
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- 0
|
||||||
|
- 145
|
||||||
|
port: 8081
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
ftray:
|
||||||
|
description: Sample transfer tray
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: D
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- -200
|
||||||
|
- 0
|
||||||
|
port: 8081
|
||||||
|
sign: -1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#################### flOMNI Sample Names ###################
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
flomni_samples:
|
||||||
|
description: Sample names and storage
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimSampleStorage
|
||||||
|
deviceConfig: {}
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#################### flOMNI Smaract motors #################
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
fosax:
|
||||||
|
description: OSA X
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: A
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- 10.2
|
||||||
|
- 10.6
|
||||||
|
port: 3334
|
||||||
|
sign: -1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
userParameter:
|
||||||
|
in: 9.124
|
||||||
|
out: 5.3
|
||||||
|
fosay:
|
||||||
|
description: OSA Y
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: B
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- -3.1
|
||||||
|
- -2.9
|
||||||
|
port: 3334
|
||||||
|
sign: -1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
userParameter:
|
||||||
|
in: 0.367
|
||||||
|
fosaz:
|
||||||
|
description: OSA Z
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: C
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
limits:
|
||||||
|
- -6
|
||||||
|
- -4
|
||||||
|
port: 3334
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
userParameter:
|
||||||
|
in: 8.5
|
||||||
|
out: 6
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#################### flOMNI RT motors ######################
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
rtx:
|
||||||
|
description: flomni rt
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: A
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
port: 2222
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: on_request
|
||||||
|
connectionTimeout: 20
|
||||||
|
userParameter:
|
||||||
|
low_signal: 10000
|
||||||
|
min_signal: 9000
|
||||||
|
rt_pid_voltage: -0.06219
|
||||||
|
rty:
|
||||||
|
description: flomni rt
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: B
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
port: 2222
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: on_request
|
||||||
|
connectionTimeout: 20
|
||||||
|
userParameter:
|
||||||
|
tomo_additional_offsety: 0
|
||||||
|
rtz:
|
||||||
|
description: flomni rt
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimPositioner
|
||||||
|
deviceConfig:
|
||||||
|
axis_Id: C
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
port: 2222
|
||||||
|
sign: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: on_request
|
||||||
|
connectionTimeout: 20
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
####################### Cameras ############################
|
||||||
|
############################################################
|
||||||
|
|
||||||
|
cam200:
|
||||||
|
description: Camera200
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimCamera
|
||||||
|
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.sim.flomni_sim.FlomniSimCamera
|
||||||
|
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.sim.flomni_sim.FlomniSimCamera
|
||||||
|
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.sim.flomni_sim.FlomniSimCamera
|
||||||
|
deviceConfig:
|
||||||
|
camera_ID: 203
|
||||||
|
bits_per_pixel: 24
|
||||||
|
channels: 3
|
||||||
|
m_n_colormode: 1
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: async
|
||||||
|
|
||||||
|
cam_flomni_gripper:
|
||||||
|
description: Camera sample changer
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimCamera
|
||||||
|
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.sim.flomni_sim.FlomniSimCamera
|
||||||
|
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.sim.flomni_sim.FlomniSimCamera
|
||||||
|
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.sim.flomni_sim.FlomniSimCamera
|
||||||
|
# deviceConfig:
|
||||||
|
# camera_id: 203
|
||||||
|
# bits_per_pixel: 24
|
||||||
|
# num_rotation_90: 2
|
||||||
|
# transpose: false
|
||||||
|
# force_monochrome: false
|
||||||
|
# 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.sim.flomni_sim.FlomniSimTempHum
|
||||||
|
deviceConfig: {}
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
# ############################################################
|
||||||
|
# ########## OMNY / flOMNI / LamNI fast shutter ##############
|
||||||
|
# ############################################################
|
||||||
|
omnyfsh:
|
||||||
|
description: omnyfsh connects to fast shutter at X12 if device fsh exists
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimFastShutter
|
||||||
|
deviceConfig: {}
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
############################################################
|
||||||
|
#################### GUI Signals ###########################
|
||||||
|
############################################################
|
||||||
|
omny_xray_gui:
|
||||||
|
description: Gui signals
|
||||||
|
deviceClass: csaxs_bec.devices.sim.flomni_sim.FlomniSimXRayAlignGUI
|
||||||
|
deviceConfig: {}
|
||||||
|
enabled: true
|
||||||
|
onFailure: buffer
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: on_request
|
||||||
@@ -435,9 +435,9 @@ cam_xeye:
|
|||||||
# deviceConfig:
|
# deviceConfig:
|
||||||
# camera_id: 203
|
# camera_id: 203
|
||||||
# bits_per_pixel: 24
|
# bits_per_pixel: 24
|
||||||
# num_rotation_90: 3
|
# num_rotation_90: 2
|
||||||
# transpose: false
|
# transpose: false
|
||||||
# force_monochrome: true
|
# force_monochrome: false
|
||||||
# m_n_colormode: 1
|
# m_n_colormode: 1
|
||||||
# enabled: true
|
# enabled: true
|
||||||
# onFailure: buffer
|
# onFailure: buffer
|
||||||
@@ -471,8 +471,8 @@ omnyfsh:
|
|||||||
#################### GUI Signals ###########################
|
#################### GUI Signals ###########################
|
||||||
############################################################
|
############################################################
|
||||||
omny_xray_gui:
|
omny_xray_gui:
|
||||||
description: Gui Epics signals
|
description: Gui signals
|
||||||
deviceClass: csaxs_bec.devices.omny.xray_epics_gui.OMNYXRayEpicsGUI
|
deviceClass: csaxs_bec.devices.omny.xray_epics_gui.OMNYXRayAlignGUI
|
||||||
deviceConfig: {}
|
deviceConfig: {}
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: buffer
|
||||||
@@ -487,3 +487,24 @@ calculated_signal:
|
|||||||
enabled: true
|
enabled: true
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
|
|
||||||
|
############################################################
|
||||||
|
#################### OMNY Pandabox #########################
|
||||||
|
############################################################
|
||||||
|
omny_panda:
|
||||||
|
readoutPriority: async
|
||||||
|
deviceClass: csaxs_bec.devices.panda_box.panda_box_omny.PandaBoxOMNY
|
||||||
|
deviceConfig:
|
||||||
|
host: omny-panda.psi.ch
|
||||||
|
signal_alias:
|
||||||
|
FMC_IN.VAL1.Min: cap_voltage_fzp_y_min
|
||||||
|
FMC_IN.VAL1.Max: cap_voltage_fzp_y_max
|
||||||
|
FMC_IN.VAL1.Mean: cap_voltage_fzp_y_mean
|
||||||
|
FMC_IN.VAL2.Min: cap_voltage_fzp_x_min
|
||||||
|
FMC_IN.VAL2.Max: cap_voltage_fzp_x_max
|
||||||
|
FMC_IN.VAL2.Mean: cap_voltage_fzp_x_mean
|
||||||
|
deviceTags:
|
||||||
|
- detector
|
||||||
|
enabled: true
|
||||||
|
readOnly: false
|
||||||
|
softwareTrigger: false
|
||||||
|
|||||||
154
csaxs_bec/devices/epics/allied_vision_camera.py
Normal file
154
csaxs_bec/devices/epics/allied_vision_camera.py
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
"""Module for the EPICS integration of the AlliedVision Camera via Vimba SDK."""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
import traceback
|
||||||
|
from enum import IntEnum
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from bec_lib.logger import bec_logger
|
||||||
|
from ophyd import Component as Cpt, Kind, Signal
|
||||||
|
from ophyd.areadetector import ADComponent as ADCpt
|
||||||
|
from ophyd.areadetector import DetectorBase
|
||||||
|
from ophyd_devices import PreviewSignal
|
||||||
|
from ophyd_devices.devices.areadetector.cam import VimbaDetectorCam
|
||||||
|
from ophyd_devices.devices.areadetector.plugins import ImagePlugin_V35 as ImagePlugin
|
||||||
|
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||||
|
from typeguard import typechecked
|
||||||
|
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
|
||||||
|
class ACQUIRE_MODES(IntEnum):
|
||||||
|
"""Acquiring enums for Allied Vision Camera"""
|
||||||
|
|
||||||
|
ACQUIRING = 1
|
||||||
|
DONE = 0
|
||||||
|
|
||||||
|
|
||||||
|
class AlliedVisionCamera(PSIDeviceBase, DetectorBase):
|
||||||
|
"""
|
||||||
|
Epics Area Detector interface for the Allied Vision Alvium G1-507m camera via Vimba SDK.
|
||||||
|
The IOC runs with under the prefix: 'X12SA-GIGECAM-AV1:'.
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_ACCESS = ["start_live_mode", "stop_live_mode"]
|
||||||
|
|
||||||
|
cam = ADCpt(VimbaDetectorCam, "cam1:")
|
||||||
|
image = ADCpt(ImagePlugin, "image1:")
|
||||||
|
|
||||||
|
preview = Cpt(
|
||||||
|
PreviewSignal,
|
||||||
|
name="preview",
|
||||||
|
ndim=2,
|
||||||
|
num_rotation_90=0,
|
||||||
|
transpose=False,
|
||||||
|
doc="Preview signal of the AlliedVision camera.",
|
||||||
|
)
|
||||||
|
|
||||||
|
live_mode_enabled = Cpt(
|
||||||
|
Signal,
|
||||||
|
name="live_mode_enabled",
|
||||||
|
value=False,
|
||||||
|
doc="Enable or disable live mode.",
|
||||||
|
kind=Kind.config,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
name: str,
|
||||||
|
prefix: str,
|
||||||
|
poll_rate: int = 5,
|
||||||
|
num_rotation_90: int = 0,
|
||||||
|
transpose: bool = False,
|
||||||
|
scan_info=None,
|
||||||
|
device_manager=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(
|
||||||
|
name=name, prefix=prefix, scan_info=scan_info, device_manager=device_manager, **kwargs
|
||||||
|
)
|
||||||
|
self._poll_thread = threading.Thread(
|
||||||
|
target=self._poll_array_data, daemon=True, name=f"{self.name}_poll_thread"
|
||||||
|
)
|
||||||
|
self._poll_thread_kill_event = threading.Event()
|
||||||
|
self._poll_start_event = threading.Event()
|
||||||
|
if poll_rate > 10:
|
||||||
|
logger.warning(f"Poll rate too high for Camera {self.name}, setting to 10 Hz max.")
|
||||||
|
poll_rate = 10
|
||||||
|
self._poll_rate = poll_rate
|
||||||
|
self._unique_array_id = 0
|
||||||
|
self._pv_timeout = 2.0
|
||||||
|
self.image: ImagePlugin
|
||||||
|
self.preview.num_rotation_90 = num_rotation_90
|
||||||
|
self.preview.transpose = transpose
|
||||||
|
self._live_mode_lock = threading.RLock()
|
||||||
|
self.live_mode_enabled.subscribe(self._on_live_mode_enabled_changed, run=False)
|
||||||
|
|
||||||
|
def start_live_mode(self) -> None:
|
||||||
|
"""Start live mode."""
|
||||||
|
self.live_mode_enabled.put(True)
|
||||||
|
|
||||||
|
def stop_live_mode(self) -> None:
|
||||||
|
"""Stop live mode."""
|
||||||
|
self.live_mode_enabled.put(False)
|
||||||
|
|
||||||
|
def _on_live_mode_enabled_changed(self, *args, value, **kwargs) -> None:
|
||||||
|
self._apply_live_mode(bool(value))
|
||||||
|
|
||||||
|
def _apply_live_mode(self, enabled: bool) -> None:
|
||||||
|
with self._live_mode_lock:
|
||||||
|
if enabled:
|
||||||
|
if not self._poll_start_event.is_set():
|
||||||
|
self._poll_start_event.set()
|
||||||
|
self.cam.acquire.put(ACQUIRE_MODES.ACQUIRING.value) # Start acquisition
|
||||||
|
else:
|
||||||
|
logger.info(f"Live mode already started for {self.name}.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._poll_start_event.is_set():
|
||||||
|
self._poll_start_event.clear()
|
||||||
|
self.cam.acquire.put(ACQUIRE_MODES.DONE.value) # Stop acquisition
|
||||||
|
else:
|
||||||
|
logger.info(f"Live mode already stopped for {self.name}.")
|
||||||
|
|
||||||
|
def on_connected(self):
|
||||||
|
"""Reset the unique array ID on connection."""
|
||||||
|
self.cam.array_counter.set(0).wait(timeout=self._pv_timeout)
|
||||||
|
self.cam.array_callbacks.set(1).wait(timeout=self._pv_timeout)
|
||||||
|
self._poll_thread.start()
|
||||||
|
|
||||||
|
def _poll_array_data(self):
|
||||||
|
"""Poll the array data for preview updates."""
|
||||||
|
while not self._poll_thread_kill_event.wait(1 / self._poll_rate):
|
||||||
|
while self._poll_start_event.wait():
|
||||||
|
try:
|
||||||
|
# First check if there is a new image
|
||||||
|
if self.image.unique_id.get() != self._unique_array_id:
|
||||||
|
self._unique_array_id = self.image.unique_id.get()
|
||||||
|
else:
|
||||||
|
continue # No new image, skip update
|
||||||
|
# Get new image data
|
||||||
|
value = self.image.array_data.get()
|
||||||
|
if value is None:
|
||||||
|
logger.info(f"No image data available for preview of {self.name}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
array_size = self.image.array_size.get()
|
||||||
|
if array_size[0] == 0: # 2D image, not color image
|
||||||
|
array_size = array_size[1:]
|
||||||
|
# Geometry correction for the image
|
||||||
|
data = np.reshape(value, array_size)
|
||||||
|
self.preview.put(data)
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
content = traceback.format_exc()
|
||||||
|
logger.error(
|
||||||
|
f"Error while polling array data for preview of {self.name}: {content}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def on_destroy(self):
|
||||||
|
"""Stop the polling thread on destruction."""
|
||||||
|
self._poll_thread_kill_event.set()
|
||||||
|
self._poll_start_event.set()
|
||||||
|
if self._poll_thread.is_alive():
|
||||||
|
self._poll_thread.join(timeout=2)
|
||||||
@@ -309,7 +309,11 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
|||||||
# Shutter opens without delay at t0, closes after exp_time * burst_count + 2ms (self._shutter_to_open_delay)
|
# Shutter opens without delay at t0, closes after exp_time * burst_count + 2ms (self._shutter_to_open_delay)
|
||||||
self.set_delay_pairs(channel="cd", delay=0, width=shutter_width)
|
self.set_delay_pairs(channel="cd", delay=0, width=shutter_width)
|
||||||
|
|
||||||
self.set_delay_pairs(channel="gh", delay=self._shutter_to_open_delay, width=(shutter_width-self._shutter_to_open_delay))
|
self.set_delay_pairs(
|
||||||
|
channel="gh",
|
||||||
|
delay=self._shutter_to_open_delay,
|
||||||
|
width=(shutter_width - self._shutter_to_open_delay),
|
||||||
|
)
|
||||||
|
|
||||||
# Trigger extra pulse for MCS OR gate
|
# Trigger extra pulse for MCS OR gate
|
||||||
# f = e + 1us
|
# f = e + 1us
|
||||||
@@ -520,7 +524,6 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
|||||||
- Return the status object to BEC which will automatically resolve once the status register has
|
- 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.
|
the END_OF_BURST bit set. The callback of the status object will also stop the polling loop.
|
||||||
"""
|
"""
|
||||||
overall_start = time.time()
|
|
||||||
self._stop_polling()
|
self._stop_polling()
|
||||||
|
|
||||||
# NOTE If the trigger source is not SINGLE_SHOT, the DDG is triggered by an external source
|
# NOTE If the trigger source is not SINGLE_SHOT, the DDG is triggered by an external source
|
||||||
@@ -559,7 +562,6 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
|||||||
# Send trigger
|
# Send trigger
|
||||||
self.trigger_shot.put(1, use_complete=True)
|
self.trigger_shot.put(1, use_complete=True)
|
||||||
self.cancel_on_stop(status)
|
self.cancel_on_stop(status)
|
||||||
logger.info(f"Configured ddg in {time.time()-overall_start}")
|
|
||||||
return status
|
return status
|
||||||
|
|
||||||
def on_stop(self) -> None:
|
def on_stop(self) -> None:
|
||||||
|
|||||||
@@ -261,7 +261,6 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
|||||||
**kwargs: Additional keyword arguments from the subscription, including 'obj' (the EpicsSignalRO instance).
|
**kwargs: Additional keyword arguments from the subscription, including 'obj' (the EpicsSignalRO instance).
|
||||||
"""
|
"""
|
||||||
with self._rlock:
|
with self._rlock:
|
||||||
logger.info(f"Received update on mcs card {self.name}")
|
|
||||||
if self._omit_mca_callbacks.is_set():
|
if self._omit_mca_callbacks.is_set():
|
||||||
return # Suppress callbacks when erasing all channels
|
return # Suppress callbacks when erasing all channels
|
||||||
self._mca_counter_index += 1
|
self._mca_counter_index += 1
|
||||||
@@ -293,9 +292,6 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Once we have received all channels, push data to BEC and reset for next accumulation
|
# Once we have received all channels, push data to BEC and reset for next accumulation
|
||||||
logger.info(
|
|
||||||
f"Received update for {attr_name}, index {self._mca_counter_index}/{self.NUM_MCA_CHANNELS}"
|
|
||||||
)
|
|
||||||
if len(self._current_data) == self.NUM_MCA_CHANNELS:
|
if len(self._current_data) == self.NUM_MCA_CHANNELS:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Current data index {self._current_data_index} complete, pushing to BEC."
|
f"Current data index {self._current_data_index} complete, pushing to BEC."
|
||||||
@@ -398,11 +394,12 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
|||||||
# NOTE Make sure that the signal that omits mca callbacks is cleared
|
# NOTE Make sure that the signal that omits mca callbacks is cleared
|
||||||
self._omit_mca_callbacks.clear()
|
self._omit_mca_callbacks.clear()
|
||||||
|
|
||||||
logger.info(f"MCS Card {self.name} on_stage completed in {time.time() - start_time:.3f}s.")
|
|
||||||
# For a fly scan we need to start the mcs card ourselves
|
# For a fly scan we need to start the mcs card ourselves
|
||||||
if self.scan_info.msg.scan_type == "fly":
|
if self.scan_info.msg.scan_type == "fly":
|
||||||
self.erase_start.put(1)
|
self.erase_start.put(1)
|
||||||
|
|
||||||
|
logger.info(f"MCS Card {self.name} on_stage completed in {time.time() - start_time:.3f}s.")
|
||||||
|
|
||||||
def on_prescan(self) -> None | StatusBase:
|
def on_prescan(self) -> None | StatusBase:
|
||||||
"""
|
"""
|
||||||
This method is called after on_stage and before the scan starts. For the MCS card, we need to make sure
|
This method is called after on_stage and before the scan starts. For the MCS card, we need to make sure
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ class Camera:
|
|||||||
camera_id (int): The ID of the camera device.
|
camera_id (int): The ID of the camera device.
|
||||||
m_n_colormode (Literal[0, 1, 2, 3]): Color mode for the camera.
|
m_n_colormode (Literal[0, 1, 2, 3]): Color mode for the camera.
|
||||||
bits_per_pixel (Literal[8, 24]): Number of bits per pixel for the camera.
|
bits_per_pixel (Literal[8, 24]): Number of bits per pixel for the camera.
|
||||||
live_mode (bool): Whether to enable live mode for the camera.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|||||||
@@ -3,21 +3,19 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
from typing import TYPE_CHECKING, Literal
|
||||||
from typing import TYPE_CHECKING, Literal, Tuple, TypedDict
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from ophyd import Component as Cpt, Signal, Kind
|
||||||
|
|
||||||
from bec_lib import messages
|
from bec_lib import messages
|
||||||
from bec_lib.logger import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
from ophyd import Component as Cpt
|
from csaxs_bec.devices.ids_cameras.base_integration.camera import Camera
|
||||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||||
from ophyd_devices.utils.bec_signals import AsyncSignal, PreviewSignal
|
from ophyd_devices.utils.bec_signals import AsyncSignal, PreviewSignal
|
||||||
|
|
||||||
from csaxs_bec.devices.ids_cameras.base_integration.camera import Camera
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from bec_lib.devicemanager import ScanInfo
|
from bec_lib.devicemanager import ScanInfo
|
||||||
from pydantic import ValidationInfo
|
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
|
|
||||||
@@ -45,8 +43,15 @@ class IDSCamera(PSIDeviceBase):
|
|||||||
doc="Signal for the region of interest (ROI).",
|
doc="Signal for the region of interest (ROI).",
|
||||||
async_update={"type": "add", "max_shape": [None]},
|
async_update={"type": "add", "max_shape": [None]},
|
||||||
)
|
)
|
||||||
|
live_mode_enabled = Cpt(
|
||||||
|
Signal,
|
||||||
|
name="live_mode_enabled",
|
||||||
|
value=False,
|
||||||
|
doc="Enable or disable live mode.",
|
||||||
|
kind=Kind.config,
|
||||||
|
)
|
||||||
|
|
||||||
USER_ACCESS = ["live_mode", "mask", "set_rect_roi", "get_last_image"]
|
USER_ACCESS = ["start_live_mode", "stop_live_mode", "mask", "set_rect_roi", "get_last_image"]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -83,15 +88,22 @@ class IDSCamera(PSIDeviceBase):
|
|||||||
bits_per_pixel=bits_per_pixel,
|
bits_per_pixel=bits_per_pixel,
|
||||||
connect=False,
|
connect=False,
|
||||||
)
|
)
|
||||||
self._live_mode = False
|
|
||||||
self._inputs = {"live_mode": live_mode}
|
self._inputs = {"live_mode": live_mode}
|
||||||
self._mask = np.zeros((1, 1), dtype=np.uint8)
|
self._mask = np.zeros((1, 1), dtype=np.uint8)
|
||||||
self.image.num_rotation_90 = num_rotation_90
|
self.image.num_rotation_90 = num_rotation_90
|
||||||
self.image.transpose = transpose
|
self.image.transpose = transpose
|
||||||
self._force_monochrome = force_monochrome
|
self._force_monochrome = force_monochrome
|
||||||
|
self.live_mode_enabled.subscribe(self._on_live_mode_enabled_changed, run=False)
|
||||||
|
self.live_mode_enabled.put(bool(live_mode))
|
||||||
|
|
||||||
############## Live Mode Methods ##############
|
############## Live Mode Methods ##############
|
||||||
|
|
||||||
|
def start_live_mode(self) -> None:
|
||||||
|
self.live_mode_enabled.put(True)
|
||||||
|
|
||||||
|
def stop_live_mode(self) -> None:
|
||||||
|
self.live_mode_enabled.put(False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mask(self) -> np.ndarray:
|
def mask(self) -> np.ndarray:
|
||||||
"""Return the current region of interest (ROI) for the camera."""
|
"""Return the current region of interest (ROI) for the camera."""
|
||||||
@@ -114,22 +126,15 @@ class IDSCamera(PSIDeviceBase):
|
|||||||
)
|
)
|
||||||
self._mask = value
|
self._mask = value
|
||||||
|
|
||||||
@property
|
def _on_live_mode_enabled_changed(self, *args, value, **kwargs):
|
||||||
def live_mode(self) -> bool:
|
"""Callback for when live mode is changed."""
|
||||||
"""Return whether the camera is in live mode."""
|
enabled = bool(value)
|
||||||
return self._live_mode
|
if enabled and self.cam._connected is False: # pylint: disable=protected-access
|
||||||
|
self.cam.on_connect()
|
||||||
@live_mode.setter
|
if enabled:
|
||||||
def live_mode(self, value: bool):
|
self._start_live()
|
||||||
"""Set the live mode for the camera."""
|
else:
|
||||||
if value != self._live_mode:
|
self._stop_live()
|
||||||
if self.cam._connected is False: # $ pylint: disable=protected-access
|
|
||||||
self.cam.on_connect()
|
|
||||||
self._live_mode = value
|
|
||||||
if value:
|
|
||||||
self._start_live()
|
|
||||||
else:
|
|
||||||
self._stop_live()
|
|
||||||
|
|
||||||
def set_rect_roi(self, x: int, y: int, width: int, height: int):
|
def set_rect_roi(self, x: int, y: int, width: int, height: int):
|
||||||
"""Set the rectangular region of interest (ROI) for the camera."""
|
"""Set the rectangular region of interest (ROI) for the camera."""
|
||||||
@@ -196,7 +201,7 @@ class IDSCamera(PSIDeviceBase):
|
|||||||
"""Connect to the camera."""
|
"""Connect to the camera."""
|
||||||
self.cam.force_monochrome = self._force_monochrome
|
self.cam.force_monochrome = self._force_monochrome
|
||||||
self.cam.on_connect()
|
self.cam.on_connect()
|
||||||
self.live_mode = self._inputs.get("live_mode", False)
|
self.live_mode_enabled.put(bool(self._inputs.get("live_mode", False)))
|
||||||
self.set_rect_roi(0, 0, self.cam.cam.width.value, self.cam.cam.height.value)
|
self.set_rect_roi(0, 0, self.cam.cam.width.value, self.cam.cam.height.value)
|
||||||
|
|
||||||
def on_destroy(self):
|
def on_destroy(self):
|
||||||
@@ -206,7 +211,7 @@ class IDSCamera(PSIDeviceBase):
|
|||||||
|
|
||||||
def on_trigger(self):
|
def on_trigger(self):
|
||||||
"""Handle the trigger event."""
|
"""Handle the trigger event."""
|
||||||
if not self.live_mode:
|
if not bool(self.live_mode_enabled.get()):
|
||||||
return
|
return
|
||||||
image = self.image.get()
|
image = self.image.get()
|
||||||
if image is not None:
|
if image is not None:
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ class Eiger(PSIDeviceBase):
|
|||||||
if data is None:
|
if data is None:
|
||||||
logger.error(f"Received image message on device {self.name} without data.")
|
logger.error(f"Received image message on device {self.name} without data.")
|
||||||
return
|
return
|
||||||
logger.info(f"Received preview image on device {self.name}")
|
|
||||||
self.preview_image.put(data)
|
self.preview_image.put(data)
|
||||||
|
|
||||||
# pylint: disable=missing-function-docstring
|
# pylint: disable=missing-function-docstring
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ which can be easily supported by changing the _NUM_DIGITAL_OUTPUT_CHANNELS varia
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
from typing import TYPE_CHECKING, Literal
|
from typing import TYPE_CHECKING, Literal
|
||||||
|
|
||||||
from bec_lib.logger import bec_logger
|
from bec_lib.logger import bec_logger
|
||||||
@@ -78,12 +79,38 @@ class GalilRIOAnalogSignalRO(GalilSignalBase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
_NUM_ANALOG_CHANNELS = 8
|
_NUM_ANALOG_CHANNELS = 8
|
||||||
|
READBACK_TIMEOUT = 0.1 # time to wait in between two readback attemps in seconds, otherwise return cached value
|
||||||
|
|
||||||
def __init__(self, signal_name: str, channel: int, parent: GalilRIO, **kwargs):
|
def __init__(
|
||||||
|
self,
|
||||||
|
signal_name: str,
|
||||||
|
channel: int,
|
||||||
|
parent: GalilRIO,
|
||||||
|
readback_timeout: float = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
super().__init__(signal_name=signal_name, parent=parent, **kwargs)
|
super().__init__(signal_name=signal_name, parent=parent, **kwargs)
|
||||||
self._channel = channel
|
self._channel = channel
|
||||||
self._metadata["connected"] = False
|
self._metadata["connected"] = False
|
||||||
|
self._readback_timeout = (
|
||||||
|
readback_timeout if readback_timeout is not None else self.READBACK_TIMEOUT
|
||||||
|
)
|
||||||
self._metadata["write_access"] = False
|
self._metadata["write_access"] = False
|
||||||
|
self._last_readback = 0.0
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
current_time = time.monotonic()
|
||||||
|
if current_time - self._last_readback > self._readback_timeout:
|
||||||
|
old_value = self._readback
|
||||||
|
self._last_readback = current_time # _socket_get may rely on this value to be set.
|
||||||
|
self._readback = self._socket_get()
|
||||||
|
self._run_subs(
|
||||||
|
sub_type=self.SUB_VALUE,
|
||||||
|
old_value=old_value,
|
||||||
|
value=self._readback,
|
||||||
|
timestamp=current_time,
|
||||||
|
)
|
||||||
|
return self._readback
|
||||||
|
|
||||||
def _socket_set(self, val):
|
def _socket_set(self, val):
|
||||||
"""Read-only signal, so set method raises an error."""
|
"""Read-only signal, so set method raises an error."""
|
||||||
@@ -136,6 +163,8 @@ class GalilRIOAnalogSignalRO(GalilSignalBase):
|
|||||||
|
|
||||||
# Run subscriptions after all readbacks have been updated
|
# Run subscriptions after all readbacks have been updated
|
||||||
# on all channels except the one that triggered the update
|
# on all channels except the one that triggered the update
|
||||||
|
# TODO for now skip running subscribers, this should be re-implemented
|
||||||
|
# once we properly handle subscriptions from bec running "read"
|
||||||
for walk in self.parent.walk_signals():
|
for walk in self.parent.walk_signals():
|
||||||
if walk.item.attr_name in updates:
|
if walk.item.attr_name in updates:
|
||||||
new_val, old_val = updates[walk.item.attr_name]
|
new_val, old_val = updates[walk.item.attr_name]
|
||||||
@@ -185,7 +214,7 @@ def _create_analog_channels(num_channels: int) -> dict[str, tuple]:
|
|||||||
an_channels[f"ch{i}"] = (
|
an_channels[f"ch{i}"] = (
|
||||||
GalilRIOAnalogSignalRO,
|
GalilRIOAnalogSignalRO,
|
||||||
f"ch{i}",
|
f"ch{i}",
|
||||||
{"kind": Kind.normal, "notify_bec": True, "channel": i, "doc": f"Analog channel {i}."},
|
{"kind": Kind.normal, "channel": i, "doc": f"Analog channel {i}."},
|
||||||
)
|
)
|
||||||
return an_channels
|
return an_channels
|
||||||
|
|
||||||
@@ -202,12 +231,7 @@ def _create_digital_output_channels(num_channels: int) -> dict[str, tuple]:
|
|||||||
di_out_channels[f"ch{i}"] = (
|
di_out_channels[f"ch{i}"] = (
|
||||||
GalilRIODigitalOutSignal,
|
GalilRIODigitalOutSignal,
|
||||||
f"ch{i}",
|
f"ch{i}",
|
||||||
{
|
{"kind": Kind.config, "channel": i, "doc": f"Digital output channel {i}."},
|
||||||
"kind": Kind.config,
|
|
||||||
"notify_bec": True,
|
|
||||||
"channel": i,
|
|
||||||
"doc": f"Digital output channel {i}.",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
return di_out_channels
|
return di_out_channels
|
||||||
|
|
||||||
|
|||||||
@@ -65,10 +65,8 @@ class RtLamniController(Controller):
|
|||||||
"_position_sampling_single_read",
|
"_position_sampling_single_read",
|
||||||
"_position_sampling_single_reset_and_start_sampling",
|
"_position_sampling_single_reset_and_start_sampling",
|
||||||
"show_signal_strength_interferometer",
|
"show_signal_strength_interferometer",
|
||||||
"show_interferometer_positions",
|
|
||||||
"show_analog_signals",
|
"show_analog_signals",
|
||||||
"show_feedback_status",
|
"show_feedback_status",
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import requests
|
|||||||
import threading
|
import threading
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from ophyd import Device, Component as Cpt
|
from ophyd import Device, Component as Cpt, Kind, Signal
|
||||||
from ophyd_devices import PreviewSignal
|
from ophyd_devices import PreviewSignal
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@@ -13,6 +13,13 @@ logger = bec_logger.logger
|
|||||||
class WebcamViewer(Device):
|
class WebcamViewer(Device):
|
||||||
USER_ACCESS = ["start_live_mode", "stop_live_mode"]
|
USER_ACCESS = ["start_live_mode", "stop_live_mode"]
|
||||||
preview = Cpt(PreviewSignal, ndim=2, num_rotation_90=0, transpose=False)
|
preview = Cpt(PreviewSignal, ndim=2, num_rotation_90=0, transpose=False)
|
||||||
|
live_mode_enabled = Cpt(
|
||||||
|
Signal,
|
||||||
|
name="live_mode_enabled",
|
||||||
|
value=False,
|
||||||
|
doc="Enable or disable live mode.",
|
||||||
|
kind=Kind.config,
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, url:str, name:str, num_rotation_90=0, transpose=False, **kwargs) -> None:
|
def __init__(self, url:str, name:str, num_rotation_90=0, transpose=False, **kwargs) -> None:
|
||||||
super().__init__(name=name, **kwargs)
|
super().__init__(name=name, **kwargs)
|
||||||
@@ -21,20 +28,54 @@ class WebcamViewer(Device):
|
|||||||
self._update_thread = None
|
self._update_thread = None
|
||||||
self._buffer = b""
|
self._buffer = b""
|
||||||
self._shutdown_event = threading.Event()
|
self._shutdown_event = threading.Event()
|
||||||
|
self._live_mode_lock = threading.RLock()
|
||||||
self.preview.num_rotation_90 = num_rotation_90
|
self.preview.num_rotation_90 = num_rotation_90
|
||||||
self.preview.transpose = transpose
|
self.preview.transpose = transpose
|
||||||
|
self.live_mode_enabled.subscribe(self._on_live_mode_enabled_changed, run=False)
|
||||||
|
|
||||||
def start_live_mode(self) -> None:
|
def start_live_mode(self) -> None:
|
||||||
if self._connection is not None:
|
self.live_mode_enabled.put(True)
|
||||||
return
|
|
||||||
self._update_thread = threading.Thread(target=self._update_loop, daemon=True)
|
def stop_live_mode(self) -> None:
|
||||||
self._update_thread.start()
|
self.live_mode_enabled.put(False)
|
||||||
|
|
||||||
|
def _on_live_mode_enabled_changed(self, *args, value, **kwargs) -> None:
|
||||||
|
self._apply_live_mode(bool(value))
|
||||||
|
|
||||||
|
def _apply_live_mode(self, enabled: bool) -> None:
|
||||||
|
with self._live_mode_lock:
|
||||||
|
if enabled:
|
||||||
|
if self._update_thread is not None and self._update_thread.is_alive():
|
||||||
|
return
|
||||||
|
self._shutdown_event.clear()
|
||||||
|
self._update_thread = threading.Thread(target=self._update_loop, daemon=True)
|
||||||
|
self._update_thread.start()
|
||||||
|
return
|
||||||
|
|
||||||
|
if self._update_thread is None:
|
||||||
|
return
|
||||||
|
self._shutdown_event.set()
|
||||||
|
if self._connection is not None:
|
||||||
|
try:
|
||||||
|
self._connection.close()
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
pass
|
||||||
|
self._connection = None
|
||||||
|
self._update_thread.join(timeout=2)
|
||||||
|
if self._update_thread.is_alive():
|
||||||
|
logger.warning("Webcam live mode thread did not stop within timeout.")
|
||||||
|
return
|
||||||
|
self._update_thread = None
|
||||||
|
self._buffer = b""
|
||||||
|
self._shutdown_event.clear()
|
||||||
|
|
||||||
def _update_loop(self) -> None:
|
def _update_loop(self) -> None:
|
||||||
while not self._shutdown_event.is_set():
|
while not self._shutdown_event.is_set():
|
||||||
try:
|
try:
|
||||||
self._connection = requests.get(self.url, stream=True)
|
self._connection = requests.get(self.url, stream=True, timeout=5)
|
||||||
for chunk in self._connection.iter_content(chunk_size=1024):
|
for chunk in self._connection.iter_content(chunk_size=1024):
|
||||||
|
if self._shutdown_event.is_set():
|
||||||
|
break
|
||||||
self._buffer += chunk
|
self._buffer += chunk
|
||||||
start = self._buffer.find(b'\xff\xd8') # JPEG start
|
start = self._buffer.find(b'\xff\xd8') # JPEG start
|
||||||
end = self._buffer.find(b'\xff\xd9') # JPEG end
|
end = self._buffer.find(b'\xff\xd9') # JPEG end
|
||||||
@@ -50,16 +91,3 @@ class WebcamViewer(Device):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
content = traceback.format_exc()
|
content = traceback.format_exc()
|
||||||
logger.error(f"Image update loop failed: {content}")
|
logger.error(f"Image update loop failed: {content}")
|
||||||
|
|
||||||
def stop_live_mode(self) -> None:
|
|
||||||
if self._connection is None:
|
|
||||||
return
|
|
||||||
self._shutdown_event.set()
|
|
||||||
if self._connection is not None:
|
|
||||||
self._connection.close()
|
|
||||||
self._connection = None
|
|
||||||
if self._update_thread is not None:
|
|
||||||
self._update_thread.join()
|
|
||||||
self._update_thread = None
|
|
||||||
|
|
||||||
self._shutdown_event.clear()
|
|
||||||
@@ -1,74 +1,44 @@
|
|||||||
|
import numpy as np
|
||||||
from ophyd import Component as Cpt
|
from ophyd import Component as Cpt, Signal, EpicsSignal
|
||||||
from ophyd import Device
|
from ophyd import Device
|
||||||
from ophyd import DynamicDeviceComponent as Dcpt
|
|
||||||
from ophyd import EpicsSignal
|
|
||||||
|
|
||||||
|
|
||||||
|
class OMNYXRayAlignGUI(Device):
|
||||||
|
|
||||||
class OMNYXRayEpicsGUI(Device):
|
update_frame_acqdone = Cpt(Signal, value=0)
|
||||||
|
update_frame_acq = Cpt(Signal, value=0)
|
||||||
|
enable_mv_x = Cpt(Signal, value=0)
|
||||||
|
enable_mv_y = Cpt(Signal, value=0)
|
||||||
|
send_message = Cpt(Signal, value=0)
|
||||||
|
sample_name = Cpt(Signal, value=0)
|
||||||
|
angle = Cpt(Signal, value=0)
|
||||||
|
pixel_size = Cpt(Signal, value=0)
|
||||||
|
submit = Cpt(EpicsSignal, name="submit", read_pv="XOMNYI-XEYE-SUBMIT:0", auto_monitor=True)
|
||||||
|
step = Cpt(Signal, value=0)
|
||||||
|
recbg = Cpt(Signal, value=0)
|
||||||
|
mvx = Cpt(Signal, value=0)
|
||||||
|
mvy = Cpt(Signal, value=0)
|
||||||
|
|
||||||
save_frame = Cpt(
|
fit_array = Cpt(Signal, value=np.zeros((3, 10)))
|
||||||
EpicsSignal, name="save_frame", read_pv="XOMNYI-XEYE-SAVFRAME:0",auto_monitor=True
|
fit_params_x = Cpt(Signal, value=np.zeros((2, 3)))
|
||||||
)
|
fit_params_y = Cpt(Signal, value=np.zeros((2, 3)))
|
||||||
update_frame_acqdone = Cpt(
|
|
||||||
EpicsSignal, name="update_frame_acqdone", read_pv="XOMNYI-XEYE-ACQDONE:0",auto_monitor=True
|
|
||||||
)
|
|
||||||
update_frame_acq = Cpt(
|
|
||||||
EpicsSignal, name="update_frame_acq", read_pv="XOMNYI-XEYE-ACQ:0",auto_monitor=True
|
|
||||||
)
|
|
||||||
width_y_dynamic = {
|
|
||||||
f"width_y_{i}": (EpicsSignal, f"XOMNYI-XEYE-YWIDTH_Y:{i}", {"auto_monitor": True}) for i in range(0, 11)
|
|
||||||
}
|
|
||||||
width_y = Dcpt(width_y_dynamic)
|
|
||||||
width_x_dynamic = {
|
|
||||||
f"width_x_{i}": (EpicsSignal, f"XOMNYI-XEYE-XWIDTH_X:{i}", {"auto_monitor": True}) for i in range(0, 11)
|
|
||||||
}
|
|
||||||
width_x = Dcpt(width_x_dynamic)
|
|
||||||
enable_mv_x = Cpt(
|
|
||||||
EpicsSignal, name="enable_mv_x", read_pv="XOMNYI-XEYE-ENAMVX:0",auto_monitor=True
|
|
||||||
)
|
|
||||||
enable_mv_y = Cpt(
|
|
||||||
EpicsSignal, name="enable_mv_y", read_pv="XOMNYI-XEYE-ENAMVY:0",auto_monitor=True
|
|
||||||
)
|
|
||||||
send_message = Cpt(
|
|
||||||
EpicsSignal, name="send_message", read_pv="XOMNYI-XEYE-MESSAGE:0.DESC",auto_monitor=True
|
|
||||||
)
|
|
||||||
sample_name = Cpt(
|
|
||||||
EpicsSignal, name="sample_name", read_pv="XOMNYI-XEYE-SAMPLENAME:0.DESC",auto_monitor=True
|
|
||||||
)
|
|
||||||
angle = Cpt(
|
|
||||||
EpicsSignal, name="angle", read_pv="XOMNYI-XEYE-ANGLE:0",auto_monitor=True
|
|
||||||
)
|
|
||||||
pixel_size = Cpt(
|
|
||||||
EpicsSignal, name="pixel_size", read_pv="XOMNYI-XEYE-PIXELSIZE:0",auto_monitor=True
|
|
||||||
)
|
|
||||||
submit = Cpt(
|
|
||||||
EpicsSignal, name="submit", read_pv="XOMNYI-XEYE-SUBMIT:0",auto_monitor=True
|
|
||||||
)
|
|
||||||
step = Cpt(
|
|
||||||
EpicsSignal, name="step", read_pv="XOMNYI-XEYE-STEP:0",auto_monitor=True
|
|
||||||
)
|
|
||||||
xval_x_dynamic = {
|
|
||||||
f"xval_x_{i}": (EpicsSignal, f"XOMNYI-XEYE-XVAL_X:{i}", {"auto_monitor": True}) for i in range(0, 11)
|
|
||||||
}
|
|
||||||
xval_x = Dcpt(xval_x_dynamic)
|
|
||||||
yval_y_dynamic = {
|
|
||||||
f"yval_y_{i}": (EpicsSignal, f"XOMNYI-XEYE-YVAL_Y:{i}", {"auto_monitor": True}) for i in range(0, 11)
|
|
||||||
}
|
|
||||||
yval_y = Dcpt(yval_y_dynamic)
|
|
||||||
recbg = Cpt(
|
|
||||||
EpicsSignal, name="recbg", read_pv="XOMNYI-XEYE-RECBG:0",auto_monitor=True
|
|
||||||
)
|
|
||||||
stage_pos_x_dynamic = {
|
|
||||||
f"stage_pos_x_{i}": (EpicsSignal, f"XOMNYI-XEYE-STAGEPOSX:{i}", {"auto_monitor": True}) for i in range(1, 6)
|
|
||||||
}
|
|
||||||
stage_pos_x = Dcpt(stage_pos_x_dynamic)
|
|
||||||
mvx = Cpt(
|
|
||||||
EpicsSignal, name="mvx", read_pv="XOMNYI-XEYE-MVX:0",auto_monitor=True
|
|
||||||
)
|
|
||||||
mvy = Cpt(
|
|
||||||
EpicsSignal, name="mvy", read_pv="XOMNYI-XEYE-MVY:0",auto_monitor=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# Generate width_y_0 to width_y_10
|
||||||
|
for i in range(11):
|
||||||
|
locals()[f"width_y_{i}"] = Cpt(Signal, value=0)
|
||||||
|
|
||||||
|
# Generate width_x_0 to width_x_10
|
||||||
|
for i in range(11):
|
||||||
|
locals()[f"width_x_{i}"] = Cpt(Signal, value=0)
|
||||||
|
|
||||||
|
# Generate xval_x_0 to xval_x_10
|
||||||
|
for i in range(11):
|
||||||
|
locals()[f"xval_x_{i}"] = Cpt(Signal, value=0)
|
||||||
|
|
||||||
|
# Generate yval_y_0 to yval_y_10
|
||||||
|
for i in range(11):
|
||||||
|
locals()[f"yval_y_{i}"] = Cpt(Signal, value=0)
|
||||||
|
|
||||||
|
# Generate stage_pos_x_1 to stage_pos_x_5
|
||||||
|
for i in range(1, 6):
|
||||||
|
locals()[f"stage_pos_x_{i}"] = Cpt(Signal, value=0)
|
||||||
|
|||||||
103
csaxs_bec/devices/panda_box/panda_box.py
Normal file
103
csaxs_bec/devices/panda_box/panda_box.py
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
"""Module to integrate the PandaBox for cSAXS measurements."""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from bec_lib.logger import bec_logger
|
||||||
|
from ophyd_devices import StatusBase
|
||||||
|
from ophyd_devices.devices.panda_box.panda_box import PandaBox, PandaState
|
||||||
|
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
|
||||||
|
class PandaBoxCSAXS(PandaBox):
|
||||||
|
"""
|
||||||
|
PandaBox integration for cSAXS. This class implements cSAXS specific logic for the PandaBox integration.
|
||||||
|
|
||||||
|
TODO: This logic is not yet mapped to any existing hardware. Adapt Docstring once the hardware is defined and integrated.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def on_init(self):
|
||||||
|
super().on_init()
|
||||||
|
self._acquisition_group = "burst"
|
||||||
|
self._timeout_on_completed = 10
|
||||||
|
|
||||||
|
def on_stage(self):
|
||||||
|
start_time = time.time()
|
||||||
|
super().on_stage()
|
||||||
|
# TODO, adjust as seen fit.
|
||||||
|
# Adjust the acquisition group based on scan parameters if needed
|
||||||
|
if self.scan_info.msg.scan_type == "fly":
|
||||||
|
self._acquisition_group = "fly"
|
||||||
|
elif self.scan_info.msg.scan_type == "step":
|
||||||
|
if self.scan_info.msg.scan_parameters["frames_per_trigger"] == 1:
|
||||||
|
self._acquisition_group = "monitored"
|
||||||
|
else:
|
||||||
|
self._acquisition_group = "burst"
|
||||||
|
|
||||||
|
logger.info(f"PandaBox {self.name} on_stage completed in {time.time() - start_time:.3f}s.")
|
||||||
|
|
||||||
|
def on_complete(self):
|
||||||
|
"""On complete is called after the scan is complete. We need to wait for the capture to complete before we can disarm the PandaBox."""
|
||||||
|
|
||||||
|
def _check_capture_complete():
|
||||||
|
captured = 0
|
||||||
|
start_time = time.monotonic()
|
||||||
|
try:
|
||||||
|
expected_points = int(
|
||||||
|
self.scan_info.msg.num_points
|
||||||
|
* self.scan_info.msg.scan_parameters.get("frames_per_trigger", 1)
|
||||||
|
)
|
||||||
|
while captured < expected_points:
|
||||||
|
ret = self.send_raw("*PCAP.CAPTURED?")
|
||||||
|
captured = int(ret[0].split("=")[-1])
|
||||||
|
time.sleep(0.01)
|
||||||
|
if (time.monotonic() - start_time) > self._timeout_on_completed / 2:
|
||||||
|
logger.info(
|
||||||
|
f"Waiting for capture on device {self.name} to complete: captured {captured}/{expected_points} points."
|
||||||
|
)
|
||||||
|
if (time.monotonic() - start_time) > self._timeout_on_completed:
|
||||||
|
raise TimeoutError(
|
||||||
|
f"Pandabox {self.name} did not complete after {self._timeout_on_completed} with points captured {captured}/{expected_points}"
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
self._disarm()
|
||||||
|
|
||||||
|
status_captured = self.task_handler.submit_task(_check_capture_complete, run=True)
|
||||||
|
self.cancel_on_stop(status_captured)
|
||||||
|
return status_captured
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import time
|
||||||
|
|
||||||
|
panda = PandaBoxCSAXS(
|
||||||
|
name="omny_panda",
|
||||||
|
host="omny-panda.psi.ch",
|
||||||
|
signal_alias={
|
||||||
|
"FMC_IN.VAL2.Value": "alias",
|
||||||
|
"FMC_IN.VAL1.Min": "alias2",
|
||||||
|
"FMC_IN.VAL1.Max": "alias3",
|
||||||
|
"FMC_IN.VAL1.Mean": "alias4",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
panda.on_connected()
|
||||||
|
status = StatusBase(obj=panda)
|
||||||
|
panda.add_status_callback(
|
||||||
|
status=status, success=[PandaState.DISARMED], failure=[PandaState.READY]
|
||||||
|
)
|
||||||
|
panda.stop()
|
||||||
|
status.wait(timeout=2)
|
||||||
|
panda.unstage()
|
||||||
|
logger.info(f"Panda connected")
|
||||||
|
ret = panda.stage()
|
||||||
|
logger.info(f"Panda staged")
|
||||||
|
ret = panda.pre_scan()
|
||||||
|
ret.wait(timeout=5)
|
||||||
|
logger.info(f"Panda pre scan done")
|
||||||
|
time.sleep(5)
|
||||||
|
panda.stop()
|
||||||
|
st = panda.complete()
|
||||||
|
st.wait(timeout=5)
|
||||||
|
logger.info(f"Measurement completed")
|
||||||
|
panda.unstage()
|
||||||
|
logger.info(f"Panda Unstaged")
|
||||||
99
csaxs_bec/devices/panda_box/panda_box_omny.py
Normal file
99
csaxs_bec/devices/panda_box/panda_box_omny.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
"""Module to integrate the PandaBox for cSAXS measurements."""
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from bec_lib.logger import bec_logger
|
||||||
|
from ophyd_devices import StatusBase
|
||||||
|
from ophyd_devices.devices.panda_box.panda_box import PandaBox, PandaState
|
||||||
|
|
||||||
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
|
||||||
|
class PandaBoxOMNY(PandaBox):
|
||||||
|
"""PandaBox integration for OMNY. This class implements OMNY specific logic for the PandaBox integration."""
|
||||||
|
|
||||||
|
def on_init(self):
|
||||||
|
super().on_init()
|
||||||
|
self._acquisition_group = "burst"
|
||||||
|
self._timeout_on_completed = 10
|
||||||
|
|
||||||
|
def on_stage(self):
|
||||||
|
start_time = time.time()
|
||||||
|
super().on_stage()
|
||||||
|
# TODO, adjust as seen fit.
|
||||||
|
# Adjust the acquisition group based on scan parameters if needed
|
||||||
|
if self.scan_info.msg.scan_type == "fly":
|
||||||
|
self._acquisition_group = "fly"
|
||||||
|
elif self.scan_info.msg.scan_type == "step":
|
||||||
|
if self.scan_info.msg.scan_parameters["frames_per_trigger"] == 1:
|
||||||
|
self._acquisition_group = "monitored"
|
||||||
|
else:
|
||||||
|
self._acquisition_group = "burst"
|
||||||
|
|
||||||
|
logger.info(f"PandaBox {self.name} on_stage completed in {time.time() - start_time:.3f}s.")
|
||||||
|
|
||||||
|
def on_complete(self):
|
||||||
|
"""On complete is called after the scan is complete. We need to wait for the capture to complete before we can disarm the PandaBox."""
|
||||||
|
|
||||||
|
def _check_capture_complete():
|
||||||
|
captured = 0
|
||||||
|
start_time = time.monotonic()
|
||||||
|
try:
|
||||||
|
expected_points = int(
|
||||||
|
self.scan_info.msg.num_points
|
||||||
|
* self.scan_info.msg.scan_parameters.get("frames_per_trigger", 1)
|
||||||
|
)
|
||||||
|
while captured < expected_points:
|
||||||
|
ret = self.send_raw("*PCAP.CAPTURED?")
|
||||||
|
captured = int(ret[0].split("=")[-1])
|
||||||
|
time.sleep(0.01)
|
||||||
|
if (time.monotonic() - start_time) > self._timeout_on_completed / 2:
|
||||||
|
logger.info(
|
||||||
|
f"Waiting for capture on device {self.name} to complete: captured {captured}/{expected_points} points."
|
||||||
|
)
|
||||||
|
if (time.monotonic() - start_time) > self._timeout_on_completed:
|
||||||
|
raise TimeoutError(
|
||||||
|
f"Pandabox {self.name} did not complete after {self._timeout_on_completed} with points captured {captured}/{expected_points}"
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
self._disarm()
|
||||||
|
|
||||||
|
status_captured = self.task_handler.submit_task(_check_capture_complete, run=True)
|
||||||
|
self.cancel_on_stop(status_captured)
|
||||||
|
return status_captured
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import time
|
||||||
|
|
||||||
|
panda = PandaBoxOMNY(
|
||||||
|
name="omny_panda",
|
||||||
|
host="omny-panda.psi.ch",
|
||||||
|
signal_alias={
|
||||||
|
"FMC_IN.VAL2.Value": "alias",
|
||||||
|
"FMC_IN.VAL1.Min": "alias2",
|
||||||
|
"FMC_IN.VAL1.Max": "alias3",
|
||||||
|
"FMC_IN.VAL1.Mean": "alias4",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
panda.on_connected()
|
||||||
|
status = StatusBase(obj=panda)
|
||||||
|
panda.add_status_callback(
|
||||||
|
status=status, success=[PandaState.DISARMED], failure=[PandaState.READY]
|
||||||
|
)
|
||||||
|
panda.stop()
|
||||||
|
status.wait(timeout=2)
|
||||||
|
panda.unstage()
|
||||||
|
logger.info(f"Panda connected")
|
||||||
|
ret = panda.stage()
|
||||||
|
logger.info(f"Panda staged")
|
||||||
|
ret = panda.pre_scan()
|
||||||
|
ret.wait(timeout=5)
|
||||||
|
logger.info(f"Panda pre scan done")
|
||||||
|
time.sleep(5)
|
||||||
|
panda.stop()
|
||||||
|
st = panda.complete()
|
||||||
|
st.wait(timeout=5)
|
||||||
|
logger.info(f"Measurement completed")
|
||||||
|
panda.unstage()
|
||||||
|
logger.info(f"Panda Unstaged")
|
||||||
2
csaxs_bec/devices/sim/__init__.py
Normal file
2
csaxs_bec/devices/sim/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
"""Simulation devices for local csaxs_bec development."""
|
||||||
|
|
||||||
474
csaxs_bec/devices/sim/flomni_sim.py
Normal file
474
csaxs_bec/devices/sim/flomni_sim.py
Normal file
@@ -0,0 +1,474 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from ophyd import Component as Cpt
|
||||||
|
from ophyd import Device, Kind, Signal
|
||||||
|
from ophyd import DynamicDeviceComponent as Dcpt
|
||||||
|
from ophyd_devices import SimPositioner
|
||||||
|
from ophyd_devices.utils.bec_signals import PreviewSignal
|
||||||
|
from prettytable import PrettyTable
|
||||||
|
|
||||||
|
|
||||||
|
class FlomniSimController:
|
||||||
|
"""Controller shim that exposes FLOMNI-specific controller APIs for local simulation."""
|
||||||
|
|
||||||
|
USER_ACCESS = [
|
||||||
|
"socket_put_and_receive",
|
||||||
|
"socket_put_confirmed",
|
||||||
|
"drive_axis_to_limit",
|
||||||
|
"get_motor_limit_switch",
|
||||||
|
"all_axes_referenced",
|
||||||
|
"axis_is_referenced",
|
||||||
|
"is_motor_on",
|
||||||
|
"feedback_disable",
|
||||||
|
"feedback_enable_with_reset",
|
||||||
|
"feedback_enable_without_reset",
|
||||||
|
"feedback_is_running",
|
||||||
|
"laser_tracker_on",
|
||||||
|
"laser_tracker_off",
|
||||||
|
"laser_tracker_show_all",
|
||||||
|
"laser_tracker_check_signalstrength",
|
||||||
|
"laser_tracker_check_enabled",
|
||||||
|
"show_signal_strength_interferometer",
|
||||||
|
"read_ssi_interferometer",
|
||||||
|
"show_cyclic_error_compensation",
|
||||||
|
"get_pid_x",
|
||||||
|
"set_rotation_angle",
|
||||||
|
"fosaz_light_curtain_is_triggered",
|
||||||
|
"lights_off",
|
||||||
|
"lights_on",
|
||||||
|
"galil_show_all",
|
||||||
|
"move_open_loop_steps",
|
||||||
|
"find_reference_mark",
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, device: FlomniSimPositioner):
|
||||||
|
self.device = device
|
||||||
|
self.connected = True
|
||||||
|
self._feedback_running = False
|
||||||
|
self._laser_tracker_enabled = False
|
||||||
|
self._lights_on = True
|
||||||
|
self._mount_mode = False
|
||||||
|
self._confirm = 0
|
||||||
|
self._pid_x = -0.06
|
||||||
|
self._rt_angle = 0.0
|
||||||
|
self._sensor_voltage = -2.4
|
||||||
|
self.tracker_info = {
|
||||||
|
"enabled_y": True,
|
||||||
|
"enabled_z": True,
|
||||||
|
"tracker_intensity": 15000,
|
||||||
|
"threshold_intensity_y": 9000,
|
||||||
|
"threshold_intensity_z": 9000,
|
||||||
|
}
|
||||||
|
|
||||||
|
def socket_put_and_receive(self, cmd: str) -> str:
|
||||||
|
cmd_norm = cmd.strip().lower()
|
||||||
|
if "mg mntmod" in cmd_norm:
|
||||||
|
return "1" if self._mount_mode else "0"
|
||||||
|
if "mg mntprgs" in cmd_norm:
|
||||||
|
return "0"
|
||||||
|
if "mg confirm" in cmd_norm:
|
||||||
|
return str(self._confirm)
|
||||||
|
if "mg @out[9]" in cmd_norm:
|
||||||
|
return "0"
|
||||||
|
if "mg@an[1]" in cmd_norm:
|
||||||
|
return str(self._sensor_voltage)
|
||||||
|
if "mg @in[14]" in cmd_norm:
|
||||||
|
return "0"
|
||||||
|
if "axisref" in cmd_norm:
|
||||||
|
return "1"
|
||||||
|
if cmd_norm == "g":
|
||||||
|
return str(self._pid_x)
|
||||||
|
if cmd_norm == "o":
|
||||||
|
return "1"
|
||||||
|
if cmd_norm.startswith("tp") or cmd_norm.startswith("td"):
|
||||||
|
return str(float(self.device.readback.get()))
|
||||||
|
if cmd_norm.startswith("mg_bg"):
|
||||||
|
return "0"
|
||||||
|
return "0"
|
||||||
|
|
||||||
|
def socket_put_confirmed(self, cmd: str) -> None:
|
||||||
|
cmd_norm = cmd.strip().lower()
|
||||||
|
if "xq#mntmode" in cmd_norm:
|
||||||
|
self._mount_mode = True
|
||||||
|
elif "xq#posmode" in cmd_norm:
|
||||||
|
self._mount_mode = False
|
||||||
|
elif cmd_norm.startswith("confirm="):
|
||||||
|
try:
|
||||||
|
self._confirm = int(float(cmd_norm.split("=")[1]))
|
||||||
|
except ValueError:
|
||||||
|
self._confirm = 0
|
||||||
|
elif cmd_norm == "l0":
|
||||||
|
self._feedback_running = False
|
||||||
|
elif cmd_norm in ("l1", "l3"):
|
||||||
|
self._feedback_running = True
|
||||||
|
elif cmd_norm == "t0":
|
||||||
|
self._laser_tracker_enabled = False
|
||||||
|
elif cmd_norm == "t1":
|
||||||
|
self._laser_tracker_enabled = True
|
||||||
|
elif cmd_norm == "cb15":
|
||||||
|
self._lights_on = False
|
||||||
|
elif cmd_norm == "sb15":
|
||||||
|
self._lights_on = True
|
||||||
|
|
||||||
|
def drive_axis_to_limit(self, axis_Id_numeric=None, direction: str = "forward") -> None:
|
||||||
|
low, high = self.device.limits
|
||||||
|
if low >= high:
|
||||||
|
return
|
||||||
|
target = high if direction == "forward" else low
|
||||||
|
status = self.device.move(target)
|
||||||
|
status.wait(timeout=2)
|
||||||
|
|
||||||
|
def get_motor_limit_switch(self, axis_id=None) -> tuple[bool, bool]:
|
||||||
|
low, high = self.device.limits
|
||||||
|
if low >= high:
|
||||||
|
return (False, False)
|
||||||
|
pos = float(self.device.readback.get())
|
||||||
|
eps = max(self.device.tolerance.get(), 0.05)
|
||||||
|
return (abs(pos - low) <= eps, abs(pos - high) <= eps)
|
||||||
|
|
||||||
|
def all_axes_referenced(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def axis_is_referenced(self, axis_id_numeric=None) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_motor_on(self, axis_id=None) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def feedback_disable(self) -> None:
|
||||||
|
self._feedback_running = False
|
||||||
|
|
||||||
|
def feedback_enable_with_reset(self) -> None:
|
||||||
|
self._feedback_running = True
|
||||||
|
|
||||||
|
def feedback_enable_without_reset(self) -> None:
|
||||||
|
self._feedback_running = True
|
||||||
|
|
||||||
|
def feedback_is_running(self) -> bool:
|
||||||
|
return self._feedback_running
|
||||||
|
|
||||||
|
def laser_tracker_on(self) -> None:
|
||||||
|
self._laser_tracker_enabled = True
|
||||||
|
|
||||||
|
def laser_tracker_off(self) -> None:
|
||||||
|
self._laser_tracker_enabled = False
|
||||||
|
|
||||||
|
def laser_tracker_show_all(self) -> None:
|
||||||
|
t = PrettyTable()
|
||||||
|
t.title = "Simulated Laser Tracker"
|
||||||
|
t.field_names = ["name", "value"]
|
||||||
|
for key, val in self.tracker_info.items():
|
||||||
|
t.add_row([key, val])
|
||||||
|
print(t)
|
||||||
|
|
||||||
|
def laser_tracker_check_signalstrength(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def laser_tracker_check_enabled(self) -> bool:
|
||||||
|
return self._laser_tracker_enabled
|
||||||
|
|
||||||
|
def show_signal_strength_interferometer(self) -> None:
|
||||||
|
print("Simulated interferometer signal strength: OK")
|
||||||
|
|
||||||
|
def read_ssi_interferometer(self) -> float:
|
||||||
|
return 12000.0
|
||||||
|
|
||||||
|
def show_cyclic_error_compensation(self) -> None:
|
||||||
|
print("Simulated cyclic error compensation: initialized")
|
||||||
|
|
||||||
|
def get_pid_x(self) -> float:
|
||||||
|
return self._pid_x
|
||||||
|
|
||||||
|
def set_rotation_angle(self, val: float) -> None:
|
||||||
|
self._rt_angle = float(val)
|
||||||
|
|
||||||
|
def fosaz_light_curtain_is_triggered(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def lights_off(self) -> None:
|
||||||
|
self._lights_on = False
|
||||||
|
|
||||||
|
def lights_on(self) -> None:
|
||||||
|
self._lights_on = True
|
||||||
|
|
||||||
|
def galil_show_all(self) -> None:
|
||||||
|
print(f"Simulated controller for {self.device.name} connected: {self.connected}")
|
||||||
|
|
||||||
|
def move_open_loop_steps(self, axis_id_numeric: int, steps: int, **_kwargs) -> None:
|
||||||
|
# no-op in simulation
|
||||||
|
_ = axis_id_numeric, steps
|
||||||
|
|
||||||
|
def find_reference_mark(self, axis_id_numeric: int, *_args) -> None:
|
||||||
|
# no-op in simulation
|
||||||
|
_ = axis_id_numeric
|
||||||
|
|
||||||
|
|
||||||
|
class JSONSafeSignal(Signal):
|
||||||
|
"""Signal that coerces complex Python objects to JSON strings for ophyd describe()."""
|
||||||
|
|
||||||
|
def _coerce(self, value):
|
||||||
|
if isinstance(value, np.ndarray):
|
||||||
|
value = value.tolist()
|
||||||
|
if isinstance(value, (dict, list, tuple)):
|
||||||
|
return json.dumps(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def put(self, value, *args, **kwargs):
|
||||||
|
return super().put(self._coerce(value), *args, **kwargs)
|
||||||
|
|
||||||
|
def set(self, value, *args, **kwargs):
|
||||||
|
return super().set(self._coerce(value), *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class FlomniSimPositioner(SimPositioner):
|
||||||
|
"""Simulated positioner that preserves FLOMNI controller-style API."""
|
||||||
|
|
||||||
|
USER_ACCESS = ["sim", "readback", "registered_proxies", "controller"]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
*,
|
||||||
|
axis_Id: str | None = None,
|
||||||
|
host: str | None = None,
|
||||||
|
port: int | None = None,
|
||||||
|
sign: int = 1,
|
||||||
|
limits: list[float] | tuple[float, float] | None = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(name=name, limits=limits, **kwargs)
|
||||||
|
self.axis_Id = axis_Id
|
||||||
|
self.host = host
|
||||||
|
self.port = port
|
||||||
|
self.sign = sign
|
||||||
|
self.controller = FlomniSimController(self)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def axis_Id_numeric(self) -> int | None:
|
||||||
|
if not self.axis_Id:
|
||||||
|
return None
|
||||||
|
return ord(self.axis_Id.lower()) - 97
|
||||||
|
|
||||||
|
|
||||||
|
class FlomniSimCamera(Device):
|
||||||
|
"""Lightweight camera simulation with live mode controls for FLOMNI/OMNY GUI usage."""
|
||||||
|
|
||||||
|
USER_ACCESS = ["start_live_mode", "stop_live_mode", "live_mode_enabled", "image", "preview"]
|
||||||
|
|
||||||
|
image = Cpt(PreviewSignal, name="image", ndim=2, num_rotation_90=0, transpose=False)
|
||||||
|
preview = Cpt(PreviewSignal, name="preview", ndim=2, num_rotation_90=0, transpose=False)
|
||||||
|
live_mode_enabled = Cpt(
|
||||||
|
Signal,
|
||||||
|
name="live_mode_enabled",
|
||||||
|
value=False,
|
||||||
|
kind=Kind.config,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
*,
|
||||||
|
camera_id: int | None = None,
|
||||||
|
camera_ID: int | None = None,
|
||||||
|
url: str | None = None,
|
||||||
|
bits_per_pixel: int = 8,
|
||||||
|
channels: int = 1,
|
||||||
|
m_n_colormode: int = 0,
|
||||||
|
num_rotation_90: int = 0,
|
||||||
|
transpose: bool = False,
|
||||||
|
force_monochrome: bool = False,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(name=name, **kwargs)
|
||||||
|
self.camera_id = camera_id if camera_id is not None else camera_ID
|
||||||
|
self.url = url
|
||||||
|
self.bits_per_pixel = bits_per_pixel
|
||||||
|
self.channels = channels
|
||||||
|
self.m_n_colormode = m_n_colormode
|
||||||
|
self.force_monochrome = force_monochrome
|
||||||
|
self.image.num_rotation_90 = num_rotation_90
|
||||||
|
self.image.transpose = transpose
|
||||||
|
self.preview.num_rotation_90 = num_rotation_90
|
||||||
|
self.preview.transpose = transpose
|
||||||
|
self._frame_number = 0
|
||||||
|
self._shutdown_event = threading.Event()
|
||||||
|
self._live_mode_thread: threading.Thread | None = None
|
||||||
|
self.live_mode_enabled.subscribe(self._on_live_mode_enabled_changed, run=False)
|
||||||
|
|
||||||
|
def start_live_mode(self) -> None:
|
||||||
|
self.live_mode_enabled.put(True)
|
||||||
|
|
||||||
|
def stop_live_mode(self) -> None:
|
||||||
|
self.live_mode_enabled.put(False)
|
||||||
|
|
||||||
|
def _on_live_mode_enabled_changed(self, *args, value, **kwargs):
|
||||||
|
_ = args, kwargs
|
||||||
|
if bool(value):
|
||||||
|
self._start_live_mode()
|
||||||
|
else:
|
||||||
|
self._stop_live_mode()
|
||||||
|
|
||||||
|
def _start_live_mode(self) -> None:
|
||||||
|
if self._live_mode_thread and self._live_mode_thread.is_alive():
|
||||||
|
return
|
||||||
|
self._shutdown_event.clear()
|
||||||
|
self._live_mode_thread = threading.Thread(target=self._live_mode_loop, daemon=True)
|
||||||
|
self._live_mode_thread.start()
|
||||||
|
|
||||||
|
def _stop_live_mode(self) -> None:
|
||||||
|
if self._live_mode_thread is None:
|
||||||
|
return
|
||||||
|
self._shutdown_event.set()
|
||||||
|
self._live_mode_thread.join(timeout=1)
|
||||||
|
self._live_mode_thread = None
|
||||||
|
self._shutdown_event.clear()
|
||||||
|
|
||||||
|
def _live_mode_loop(self) -> None:
|
||||||
|
while not self._shutdown_event.is_set():
|
||||||
|
frame = self._generate_frame()
|
||||||
|
self.image.put(frame)
|
||||||
|
self.preview.put(frame)
|
||||||
|
self._shutdown_event.wait(0.2)
|
||||||
|
|
||||||
|
def _generate_frame(self) -> np.ndarray:
|
||||||
|
self._frame_number += 1
|
||||||
|
x = np.linspace(0, 255, 320, dtype=np.uint8)
|
||||||
|
y = np.linspace(0, 255, 240, dtype=np.uint8)[:, None]
|
||||||
|
frame = ((x + y + self._frame_number) % 255).astype(np.uint8)
|
||||||
|
return frame
|
||||||
|
|
||||||
|
def destroy(self):
|
||||||
|
self._stop_live_mode()
|
||||||
|
return super().destroy()
|
||||||
|
|
||||||
|
|
||||||
|
class FlomniSimFastShutter(Device):
|
||||||
|
USER_ACCESS = ["fshopen", "fshclose", "fshstatus", "fshinfo", "fshstatus_readback"]
|
||||||
|
shutter = Cpt(Signal, name="shutter", value=0)
|
||||||
|
|
||||||
|
def fshopen(self):
|
||||||
|
self.shutter.put(1)
|
||||||
|
|
||||||
|
def fshclose(self):
|
||||||
|
self.shutter.put(0)
|
||||||
|
|
||||||
|
def fshstatus(self):
|
||||||
|
return int(self.shutter.get())
|
||||||
|
|
||||||
|
def fshstatus_readback(self):
|
||||||
|
return int(self.shutter.get())
|
||||||
|
|
||||||
|
def fshinfo(self):
|
||||||
|
print("Using simulated fast shutter.")
|
||||||
|
|
||||||
|
|
||||||
|
class FlomniSimXRayAlignGUI(Device):
|
||||||
|
"""Signal-only X-ray alignment GUI backend for simulation."""
|
||||||
|
|
||||||
|
update_frame_acqdone = Cpt(Signal, value=0)
|
||||||
|
update_frame_acq = Cpt(Signal, value=0)
|
||||||
|
enable_mv_x = Cpt(Signal, value=0)
|
||||||
|
enable_mv_y = Cpt(Signal, value=0)
|
||||||
|
send_message = Cpt(Signal, value="")
|
||||||
|
sample_name = Cpt(Signal, value="")
|
||||||
|
angle = Cpt(Signal, value=0)
|
||||||
|
pixel_size = Cpt(Signal, value=0)
|
||||||
|
submit = Cpt(Signal, name="submit", value=0)
|
||||||
|
step = Cpt(Signal, value=0)
|
||||||
|
recbg = Cpt(Signal, value=0)
|
||||||
|
mvx = Cpt(Signal, value=0)
|
||||||
|
mvy = Cpt(Signal, value=0)
|
||||||
|
fit_array = Cpt(Signal, value=np.zeros((3, 10)))
|
||||||
|
fit_params_x = Cpt(JSONSafeSignal, value="{}")
|
||||||
|
fit_params_y = Cpt(JSONSafeSignal, value="{}")
|
||||||
|
|
||||||
|
for i in range(11):
|
||||||
|
locals()[f"width_y_{i}"] = Cpt(Signal, value=0)
|
||||||
|
for i in range(11):
|
||||||
|
locals()[f"width_x_{i}"] = Cpt(Signal, value=0)
|
||||||
|
for i in range(11):
|
||||||
|
locals()[f"xval_x_{i}"] = Cpt(Signal, value=0)
|
||||||
|
for i in range(11):
|
||||||
|
locals()[f"yval_y_{i}"] = Cpt(Signal, value=0)
|
||||||
|
for i in range(1, 6):
|
||||||
|
locals()[f"stage_pos_x_{i}"] = Cpt(Signal, value=0)
|
||||||
|
|
||||||
|
|
||||||
|
class FlomniSimSampleStorage(Device):
|
||||||
|
USER_ACCESS = [
|
||||||
|
"is_sample_slot_used",
|
||||||
|
"is_sample_in_gripper",
|
||||||
|
"set_sample_slot",
|
||||||
|
"unset_sample_slot",
|
||||||
|
"set_sample_in_gripper",
|
||||||
|
"unset_sample_in_gripper",
|
||||||
|
"get_sample_name",
|
||||||
|
"show_all",
|
||||||
|
]
|
||||||
|
|
||||||
|
sample_placed = Dcpt({f"sample{i}": (Signal, None, {"value": 0}) for i in range(21)})
|
||||||
|
sample_names = Dcpt({f"sample{i}": (Signal, None, {"value": "-"}) for i in range(21)})
|
||||||
|
sample_in_gripper = Cpt(Signal, name="sample_in_gripper", value=0)
|
||||||
|
sample_in_gripper_name = Cpt(Signal, name="sample_in_gripper_name", value="-")
|
||||||
|
|
||||||
|
def set_sample_slot(self, slot_nr: int, name: str) -> None:
|
||||||
|
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, slot_nr: int) -> None:
|
||||||
|
getattr(self.sample_placed, f"sample{slot_nr}").set(0)
|
||||||
|
getattr(self.sample_names, f"sample{slot_nr}").set("-")
|
||||||
|
|
||||||
|
def set_sample_in_gripper(self, name: str) -> None:
|
||||||
|
self.sample_in_gripper.set(1)
|
||||||
|
self.sample_in_gripper_name.set(name)
|
||||||
|
|
||||||
|
def unset_sample_in_gripper(self) -> None:
|
||||||
|
self.sample_in_gripper.set(0)
|
||||||
|
self.sample_in_gripper_name.set("-")
|
||||||
|
|
||||||
|
def is_sample_slot_used(self, slot_nr: int) -> bool:
|
||||||
|
return bool(getattr(self.sample_placed, f"sample{slot_nr}").get())
|
||||||
|
|
||||||
|
def is_sample_in_gripper(self) -> bool:
|
||||||
|
return bool(self.sample_in_gripper.get())
|
||||||
|
|
||||||
|
def get_sample_name(self, slot_nr: int) -> str:
|
||||||
|
return str(getattr(self.sample_names, f"sample{slot_nr}").get())
|
||||||
|
|
||||||
|
def show_all(self) -> None:
|
||||||
|
t = PrettyTable()
|
||||||
|
t.title = "flOMNI sample storage (sim)"
|
||||||
|
t.field_names = ["slot", "used", "name"]
|
||||||
|
for slot in range(21):
|
||||||
|
t.add_row([slot, int(self.is_sample_slot_used(slot)), self.get_sample_name(slot)])
|
||||||
|
print(t)
|
||||||
|
|
||||||
|
|
||||||
|
class FlomniSimTempHum(Device):
|
||||||
|
USER_ACCESS = ["show_all", "help"]
|
||||||
|
|
||||||
|
temperature_mirror = Cpt(Signal, value=22.0)
|
||||||
|
temperature_osa = Cpt(Signal, value=22.0)
|
||||||
|
temperature_heater = Cpt(Signal, value=23.0)
|
||||||
|
humidity_sensor1 = Cpt(Signal, value=35.0)
|
||||||
|
humidity_sensor2 = Cpt(Signal, value=34.0)
|
||||||
|
flow = Cpt(Signal, value=2.0)
|
||||||
|
suction = Cpt(Signal, value=1.0)
|
||||||
|
|
||||||
|
def show_all(self):
|
||||||
|
print("=== flOMNI Temperature & Humidity (sim) ===")
|
||||||
|
print(f"Mirror temperature: {self.temperature_mirror.get():.1f} C")
|
||||||
|
print(f"OSA temperature: {self.temperature_osa.get():.1f} C")
|
||||||
|
print(f"Heater temperature: {self.temperature_heater.get():.1f} C")
|
||||||
|
print(f"Humidity sensor 1: {self.humidity_sensor1.get():.1f} %RH")
|
||||||
|
print(f"Humidity sensor 2: {self.humidity_sensor2.get():.1f} %RH")
|
||||||
|
print(f"Flow: {self.flow.get():.1f} sccm")
|
||||||
|
print(f"Suction: {self.suction.get():.1f}")
|
||||||
|
|
||||||
|
def help(self):
|
||||||
|
print("show_all() - display current simulated values")
|
||||||
@@ -210,7 +210,7 @@ class LamNIFermatScan(ScanBase, LamNIMixin):
|
|||||||
arg_input = {}
|
arg_input = {}
|
||||||
arg_bundle_size = {"bundle": len(arg_input), "min": None, "max": None}
|
arg_bundle_size = {"bundle": len(arg_input), "min": None, "max": None}
|
||||||
|
|
||||||
def __init__(self, *args, parameter: dict = None, **kwargs):
|
def __init__(self, *args, parameter: dict = None, frames_per_trigger:int=1, exp_time:float=0,**kwargs):
|
||||||
"""
|
"""
|
||||||
A LamNI scan following Fermat's spiral.
|
A LamNI scan following Fermat's spiral.
|
||||||
|
|
||||||
@@ -230,10 +230,10 @@ class LamNIFermatScan(ScanBase, LamNIMixin):
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
>>> scans.lamni_fermat_scan(fov_size=[20], step=0.5, exp_time=0.1)
|
>>> scans.lamni_fermat_scan(fov_size=[20], step=0.5, exp_time=0.1)
|
||||||
>>> scans.lamni_fermat_scan(fov_size=[20, 25], center_x=0.02, center_y=0, shift_x=0, shift_y=0, angle=0, step=0.5, fov_circular=0, exp_time=0.1)
|
>>> scans.lamni_fermat_scan(fov_size=[20, 25], center_x=0.02, center_y=0, shift_x=0, shift_y=0, angle=0, step=0.5, fov_circular=0, exp_time=0.1, frames_per_trigger=1)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(parameter=parameter, **kwargs)
|
super().__init__(parameter=parameter, frames_per_trigger=frames_per_trigger, exp_time=exp_time,**kwargs)
|
||||||
self.axis = []
|
self.axis = []
|
||||||
scan_kwargs = parameter.get("kwargs", {})
|
scan_kwargs = parameter.get("kwargs", {})
|
||||||
self.fov_size = scan_kwargs.get("fov_size")
|
self.fov_size = scan_kwargs.get("fov_size")
|
||||||
@@ -482,6 +482,7 @@ class LamNIFermatScan(ScanBase, LamNIMixin):
|
|||||||
yield from self.open_scan()
|
yield from self.open_scan()
|
||||||
yield from self.stage()
|
yield from self.stage()
|
||||||
yield from self.run_baseline_reading()
|
yield from self.run_baseline_reading()
|
||||||
|
yield from self.pre_scan()
|
||||||
yield from self.scan_core()
|
yield from self.scan_core()
|
||||||
yield from self.finalize()
|
yield from self.finalize()
|
||||||
yield from self.unstage()
|
yield from self.unstage()
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class FlomniFermatScan(SyncFlyScanBase):
|
|||||||
angle: float = None,
|
angle: float = None,
|
||||||
corridor_size: float = 3,
|
corridor_size: float = 3,
|
||||||
parameter: dict = None,
|
parameter: dict = None,
|
||||||
|
frames_per_trigger:int=1,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -62,7 +63,8 @@ class FlomniFermatScan(SyncFlyScanBase):
|
|||||||
fovy(float) [um]: Fov in the piezo plane (i.e. piezo range). Max 100 um
|
fovy(float) [um]: Fov in the piezo plane (i.e. piezo range). Max 100 um
|
||||||
cenx(float) [um]: center position in x.
|
cenx(float) [um]: center position in x.
|
||||||
ceny(float) [um]: center position in y.
|
ceny(float) [um]: center position in y.
|
||||||
exp_time(float) [s]: exposure time
|
exp_time(float) [s]: exposure time per burst frame
|
||||||
|
frames_per_trigger(int) : Number of burst frames per point
|
||||||
step(float) [um]: stepsize
|
step(float) [um]: stepsize
|
||||||
zshift(float) [um]: shift in z
|
zshift(float) [um]: shift in z
|
||||||
angle(float) [deg]: rotation angle (will rotate first)
|
angle(float) [deg]: rotation angle (will rotate first)
|
||||||
@@ -71,10 +73,10 @@ class FlomniFermatScan(SyncFlyScanBase):
|
|||||||
Returns:
|
Returns:
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
>>> scans.flomni_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01)
|
>>> scans.flomni_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01, frames_per_trigger=1)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(parameter=parameter, exp_time=exp_time, **kwargs)
|
super().__init__(parameter=parameter, exp_time=exp_time, frames_per_trigger=frames_per_trigger, **kwargs)
|
||||||
self.show_live_table = False
|
self.show_live_table = False
|
||||||
self.axis = []
|
self.axis = []
|
||||||
self.fovx = fovx
|
self.fovx = fovx
|
||||||
@@ -323,6 +325,7 @@ class FlomniFermatScan(SyncFlyScanBase):
|
|||||||
yield from self.stage()
|
yield from self.stage()
|
||||||
yield from self.run_baseline_reading()
|
yield from self.run_baseline_reading()
|
||||||
yield from self._prepare_setup_part2()
|
yield from self._prepare_setup_part2()
|
||||||
|
yield from self.pre_scan()
|
||||||
yield from self.scan_core()
|
yield from self.scan_core()
|
||||||
yield from self.finalize()
|
yield from self.finalize()
|
||||||
yield from self.unstage()
|
yield from self.unstage()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
""" Module with JungfrauJochTestScan class. """
|
"""Module with JungfrauJochTestScan class."""
|
||||||
|
|
||||||
from bec_lib import bec_logger
|
from bec_lib import bec_logger
|
||||||
from bec_server.scan_server.scans import AsyncFlyScanBase, ScanAbortion
|
from bec_server.scan_server.scans import AsyncFlyScanBase, ScanAbortion
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class OMNYFermatScan(SyncFlyScanBase):
|
|||||||
angle: float = None,
|
angle: float = None,
|
||||||
corridor_size: float = 3,
|
corridor_size: float = 3,
|
||||||
parameter: dict = None,
|
parameter: dict = None,
|
||||||
|
frames_per_trigger:int=1,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
@@ -62,6 +63,7 @@ class OMNYFermatScan(SyncFlyScanBase):
|
|||||||
cenx(float) [um]: center position in x.
|
cenx(float) [um]: center position in x.
|
||||||
ceny(float) [um]: center position in y.
|
ceny(float) [um]: center position in y.
|
||||||
exp_time(float) [s]: exposure time
|
exp_time(float) [s]: exposure time
|
||||||
|
frames_per_trigger:int: Number of burst frames per trigger, defaults to 1.
|
||||||
step(float) [um]: stepsize
|
step(float) [um]: stepsize
|
||||||
zshift(float) [um]: shift in z
|
zshift(float) [um]: shift in z
|
||||||
angle(float) [deg]: rotation angle (will rotate first)
|
angle(float) [deg]: rotation angle (will rotate first)
|
||||||
@@ -73,7 +75,7 @@ class OMNYFermatScan(SyncFlyScanBase):
|
|||||||
>>> scans.omny_fermat_scan(fovx=20, fovy=25, cenx=10, ceny=0, zshift=0, angle=0, step=2, exp_time=0.01)
|
>>> scans.omny_fermat_scan(fovx=20, fovy=25, cenx=10, ceny=0, zshift=0, angle=0, step=2, exp_time=0.01)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
super().__init__(parameter=parameter, **kwargs)
|
super().__init__(parameter=parameter, exp_time=exp_time, frames_per_trigger=frames_per_trigger, **kwargs)
|
||||||
self.axis = []
|
self.axis = []
|
||||||
self.fovx = fovx
|
self.fovx = fovx
|
||||||
self.fovy = fovy
|
self.fovy = fovy
|
||||||
@@ -299,6 +301,7 @@ class OMNYFermatScan(SyncFlyScanBase):
|
|||||||
yield from self.stage()
|
yield from self.stage()
|
||||||
yield from self.run_baseline_reading()
|
yield from self.run_baseline_reading()
|
||||||
yield from self._prepare_setup_part2()
|
yield from self._prepare_setup_part2()
|
||||||
|
yield from self.pre_scan()
|
||||||
yield from self.scan_core()
|
yield from self.scan_core()
|
||||||
yield from self.finalize()
|
yield from self.finalize()
|
||||||
yield from self.unstage()
|
yield from self.unstage()
|
||||||
|
|||||||
@@ -193,14 +193,15 @@ The basic scan function can be called by `scans.flomni_fermat_scan()` and offers
|
|||||||
| fovy (float) | Fov in the piezo plane (i.e. piezo range). Max 100 um |
|
| fovy (float) | Fov in the piezo plane (i.e. piezo range). Max 100 um |
|
||||||
| cenx (float) | center position in x |
|
| cenx (float) | center position in x |
|
||||||
| ceny (float) | center position in y |
|
| ceny (float) | center position in y |
|
||||||
| exp_time (float) | exposure time |
|
| exp_time (float) | exposure time per frame |
|
||||||
|
| frames_per_trigger(int) | Number of burst frames per position |
|
||||||
| step (float) | stepsize |
|
| step (float) | stepsize |
|
||||||
| zshift (float) | shift in z |
|
| zshift (float) | shift in z |
|
||||||
| angle (float) | rotation angle (will rotate first) |
|
| angle (float) | rotation angle (will rotate first) |
|
||||||
| corridor_size (float) | corridor size for the corridor optimization. Default 3 um |
|
| corridor_size (float) | corridor size for the corridor optimization. Default 3 um |
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
`scans.flomni_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01)`
|
`scans.flomni_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01, frames_per_trigger=1)`
|
||||||
|
|
||||||
#### Overview of the alignment steps
|
#### Overview of the alignment steps
|
||||||
|
|
||||||
|
|||||||
@@ -327,14 +327,15 @@ The basic scan function can be called by `scans.omny_fermat_scan()` and offers a
|
|||||||
| fovy (float) | Fov in the piezo plane (i.e. piezo range). Max 100 um |
|
| fovy (float) | Fov in the piezo plane (i.e. piezo range). Max 100 um |
|
||||||
| cenx (float) | center position in x |
|
| cenx (float) | center position in x |
|
||||||
| ceny (float) | center position in y |
|
| ceny (float) | center position in y |
|
||||||
| exp_time (float) | exposure time |
|
| exp_time (float) | exposure time per frame |
|
||||||
|
| frames_per_trigger(int) | Number of burst frames per position |
|
||||||
| step (float) | stepsize |
|
| step (float) | stepsize |
|
||||||
| zshift (float) | shift in z |
|
| zshift (float) | shift in z |
|
||||||
| angle (float) | rotation angle (will rotate first) |
|
| angle (float) | rotation angle (will rotate first) |
|
||||||
| corridor_size (float) | corridor size for the corridor optimization. Default 3 um |
|
| corridor_size (float) | corridor size for the corridor optimization. Default 3 um |
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
`scans.omny_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01)`
|
`scans.omny_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01, frames_per_trigger=1)`
|
||||||
|
|
||||||
#### Overview of the alignment steps
|
#### Overview of the alignment steps
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ build-backend = "hatchling.build"
|
|||||||
name = "csaxs_bec"
|
name = "csaxs_bec"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
description = "The cSAXS plugin repository for BEC"
|
description = "The cSAXS plugin repository for BEC"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.11"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 3 - Alpha",
|
"Development Status :: 3 - Alpha",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
|
|||||||
@@ -35,16 +35,16 @@ def test_save_frame(bec_client_mock):
|
|||||||
lamni = LamNI(client)
|
lamni = LamNI(client)
|
||||||
align = XrayEyeAlign(client, lamni)
|
align = XrayEyeAlign(client, lamni)
|
||||||
with mock.patch(
|
with mock.patch(
|
||||||
"csaxs_bec.bec_ipython_client.plugins.LamNI.x_ray_eye_align.epics_put"
|
"csaxs_bec.bec_ipython_client.plugins.LamNI.alignment.epics_put"
|
||||||
) as epics_put_mock:
|
) as epics_put_mock:
|
||||||
align.save_frame()
|
align.save_frame()
|
||||||
epics_put_mock.assert_called_once_with("XOMNYI-XEYE-SAVFRAME:0", 1)
|
epics_put_mock.assert_called_once_with("XOMNYI-XEYE-SAVFRAME:0", 1)
|
||||||
|
|
||||||
|
|
||||||
def test_update_frame(bec_client_mock):
|
def test_update_frame(bec_client_mock):
|
||||||
epics_put = "csaxs_bec.bec_ipython_client.plugins.LamNI.x_ray_eye_align.epics_put"
|
epics_put = "csaxs_bec.bec_ipython_client.plugins.LamNI.alignment.epics_put"
|
||||||
epics_get = "csaxs_bec.bec_ipython_client.plugins.LamNI.x_ray_eye_align.epics_get"
|
epics_get = "csaxs_bec.bec_ipython_client.plugins.LamNI.alignment.epics_get"
|
||||||
fshopen = "csaxs_bec.bec_ipython_client.plugins.LamNI.x_ray_eye_align.fshopen"
|
fshopen = "csaxs_bec.bec_ipython_client.plugins.LamNI.alignment.fshopen"
|
||||||
client = bec_client_mock
|
client = bec_client_mock
|
||||||
client.device_manager.devices.xeye = DeviceBase(
|
client.device_manager.devices.xeye = DeviceBase(
|
||||||
name="xeye",
|
name="xeye",
|
||||||
|
|||||||
190
tests/tests_devices/test_panda.py
Normal file
190
tests/tests_devices/test_panda.py
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
"""Module for testing the PandaBoxCSAXS and PandaBoxOMNY devices."""
|
||||||
|
|
||||||
|
# pylint: skip-file
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from ophyd import Staged
|
||||||
|
|
||||||
|
from csaxs_bec.devices.panda_box.panda_box import PandaBoxCSAXS
|
||||||
|
from csaxs_bec.devices.panda_box.panda_box_omny import PandaBoxOMNY
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def panda_omny():
|
||||||
|
dev_name = "panda_omny"
|
||||||
|
dev = PandaBoxOMNY(
|
||||||
|
name=dev_name,
|
||||||
|
host="omny-panda-box.psi.ch",
|
||||||
|
signal_alias={
|
||||||
|
"FMC_IN.VAL1.Min": "cap_voltage_fzp_y_min",
|
||||||
|
"FMC_IN.VAL1.Max": "cap_voltage_fzp_y_max",
|
||||||
|
"FMC_IN.VAL1.Mean": "cap_voltage_fzp_y_mean",
|
||||||
|
"FMC_IN.VAL2.Min": "cap_voltage_fzp_x_min",
|
||||||
|
"FMC_IN.VAL2.Max": "cap_voltage_fzp_x_max",
|
||||||
|
"FMC_IN.VAL2.Mean": "cap_voltage_fzp_x_mean",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
yield dev
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def panda_csaxs():
|
||||||
|
dev_name = "panda_csaxs"
|
||||||
|
dev = PandaBoxCSAXS(name=dev_name, host="csaxs-panda-box.psi.ch")
|
||||||
|
yield dev
|
||||||
|
|
||||||
|
|
||||||
|
def test_panda_omny(panda_omny):
|
||||||
|
assert panda_omny.name == "panda_omny"
|
||||||
|
assert panda_omny.host == "omny-panda-box.psi.ch"
|
||||||
|
|
||||||
|
all_signal_names = [name for name, _ in panda_omny.data.signals]
|
||||||
|
# Check that the signal aliases are correctly set up
|
||||||
|
assert "cap_voltage_fzp_y_min" in all_signal_names
|
||||||
|
assert "cap_voltage_fzp_y_max" in all_signal_names
|
||||||
|
assert "cap_voltage_fzp_y_mean" in all_signal_names
|
||||||
|
assert "cap_voltage_fzp_x_min" in all_signal_names
|
||||||
|
assert "cap_voltage_fzp_x_max" in all_signal_names
|
||||||
|
assert "cap_voltage_fzp_x_mean" in all_signal_names
|
||||||
|
|
||||||
|
# Check that the original signal names are not present
|
||||||
|
assert "FMC_IN.VAL1.Min" not in all_signal_names
|
||||||
|
assert "FMC_IN.VAL1.Max" not in all_signal_names
|
||||||
|
assert "FMC_IN.VAL1.Mean" not in all_signal_names
|
||||||
|
assert "FMC_IN.VAL2.Min" not in all_signal_names
|
||||||
|
assert "FMC_IN.VAL2.Max" not in all_signal_names
|
||||||
|
assert "FMC_IN.VAL2.Mean" not in all_signal_names
|
||||||
|
|
||||||
|
assert panda_omny._acquisition_group == "burst"
|
||||||
|
assert panda_omny._timeout_on_completed == 10
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"scan_type, frames_per_trigger, expected_acquisition_group",
|
||||||
|
[
|
||||||
|
("fly", 1, "fly"),
|
||||||
|
("fly", 5, "fly"),
|
||||||
|
("step", 10, "burst"),
|
||||||
|
("step", 1, "monitored"), # Default case
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_panda_omny_stage(panda_omny, scan_type, frames_per_trigger, expected_acquisition_group):
|
||||||
|
# Check that the stage signal is present and has the correct PV
|
||||||
|
assert len(panda_omny._status_callbacks) == 0
|
||||||
|
|
||||||
|
panda_omny.scan_info.msg.scan_type = scan_type
|
||||||
|
panda_omny.scan_info.msg.scan_parameters["frames_per_trigger"] = frames_per_trigger
|
||||||
|
panda_omny.stage()
|
||||||
|
|
||||||
|
assert panda_omny._acquisition_group == expected_acquisition_group
|
||||||
|
assert panda_omny.staged == Staged.yes
|
||||||
|
|
||||||
|
|
||||||
|
def test_panda_omny_complete(panda_omny):
|
||||||
|
"""Test the on_complete method of the PandaBoxCSAXS device."""
|
||||||
|
panda_omny.scan_info.msg.num_points = 1
|
||||||
|
panda_omny.scan_info.msg.scan_parameters["frames_per_trigger"] = 1
|
||||||
|
|
||||||
|
panda_omny._timeout_on_completed = 0.5 # Set a short timeout for testing
|
||||||
|
|
||||||
|
def _mock_return_captured(*args, **kwargs):
|
||||||
|
return ["=0"]
|
||||||
|
|
||||||
|
# Timeout Error on complete
|
||||||
|
with (
|
||||||
|
mock.patch.object(panda_omny, "send_raw", side_effect=_mock_return_captured),
|
||||||
|
mock.patch.object(panda_omny, "_disarm", return_value=None) as mock_disarm,
|
||||||
|
):
|
||||||
|
status = panda_omny.on_complete()
|
||||||
|
assert status.done is False
|
||||||
|
assert status.success is False
|
||||||
|
|
||||||
|
with pytest.raises(TimeoutError):
|
||||||
|
status.wait(timeout=4)
|
||||||
|
mock_disarm.assert_called_once()
|
||||||
|
|
||||||
|
# Successful complete
|
||||||
|
panda_omny._timeout_on_completed = 5
|
||||||
|
with (
|
||||||
|
mock.patch.object(panda_omny, "send_raw", side_effect=[["=0"], ["=0"], ["=1"]]),
|
||||||
|
mock.patch.object(panda_omny, "_disarm", return_value=None) as mock_disarm,
|
||||||
|
):
|
||||||
|
status = panda_omny.on_complete()
|
||||||
|
assert status.done is False
|
||||||
|
assert status.success is False
|
||||||
|
|
||||||
|
status.wait(timeout=4)
|
||||||
|
mock_disarm.assert_called_once()
|
||||||
|
assert status.done is True
|
||||||
|
assert status.success is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_panda_csaxs(panda_csaxs):
|
||||||
|
assert panda_csaxs.name == "panda_csaxs"
|
||||||
|
assert panda_csaxs.host == "csaxs-panda-box.psi.ch"
|
||||||
|
|
||||||
|
assert panda_csaxs._acquisition_group == "burst"
|
||||||
|
assert panda_csaxs._timeout_on_completed == 10
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"scan_type, frames_per_trigger, expected_acquisition_group",
|
||||||
|
[
|
||||||
|
("fly", 1, "fly"),
|
||||||
|
("fly", 5, "fly"),
|
||||||
|
("step", 10, "burst"),
|
||||||
|
("step", 1, "monitored"), # Default case
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_panda_csaxs_stage(panda_csaxs, scan_type, frames_per_trigger, expected_acquisition_group):
|
||||||
|
"""Test the on_stage method of the PandaBoxCSAXS device for different scan types and frames per trigger."""
|
||||||
|
assert len(panda_csaxs._status_callbacks) == 0
|
||||||
|
|
||||||
|
panda_csaxs.scan_info.msg.scan_type = scan_type
|
||||||
|
panda_csaxs.scan_info.msg.scan_parameters["frames_per_trigger"] = frames_per_trigger
|
||||||
|
panda_csaxs.stage()
|
||||||
|
|
||||||
|
assert panda_csaxs._acquisition_group == expected_acquisition_group
|
||||||
|
assert panda_csaxs.staged == Staged.yes
|
||||||
|
|
||||||
|
|
||||||
|
def test_panda_csaxs_complete(panda_csaxs):
|
||||||
|
"""Test the on_complete method of the PandaBoxCSAXS device."""
|
||||||
|
panda_csaxs.scan_info.msg.num_points = 1
|
||||||
|
panda_csaxs.scan_info.msg.scan_parameters["frames_per_trigger"] = 1
|
||||||
|
|
||||||
|
panda_csaxs._timeout_on_completed = 0.5 # Set a short timeout for testing
|
||||||
|
|
||||||
|
def _mock_return_captured(*args, **kwargs):
|
||||||
|
return ["=0"]
|
||||||
|
|
||||||
|
# Timeout Error on complete
|
||||||
|
with (
|
||||||
|
mock.patch.object(panda_csaxs, "send_raw", side_effect=_mock_return_captured),
|
||||||
|
mock.patch.object(panda_csaxs, "_disarm", return_value=None) as mock_disarm,
|
||||||
|
):
|
||||||
|
status = panda_csaxs.on_complete()
|
||||||
|
assert status.done is False
|
||||||
|
assert status.success is False
|
||||||
|
|
||||||
|
with pytest.raises(TimeoutError):
|
||||||
|
status.wait(timeout=4)
|
||||||
|
mock_disarm.assert_called_once()
|
||||||
|
|
||||||
|
# Successful complete
|
||||||
|
panda_csaxs._timeout_on_completed = 5
|
||||||
|
with (
|
||||||
|
mock.patch.object(panda_csaxs, "send_raw", side_effect=[["=0"], ["=0"], ["=1"]]),
|
||||||
|
mock.patch.object(panda_csaxs, "_disarm", return_value=None) as mock_disarm,
|
||||||
|
):
|
||||||
|
status = panda_csaxs.on_complete()
|
||||||
|
assert status.done is False
|
||||||
|
assert status.success is False
|
||||||
|
|
||||||
|
status.wait(timeout=4)
|
||||||
|
mock_disarm.assert_called_once()
|
||||||
|
assert status.done is True
|
||||||
|
assert status.success is True
|
||||||
@@ -302,6 +302,36 @@ def device_manager_mock():
|
|||||||
action="set",
|
action="set",
|
||||||
parameter={"value": 2.1508313829565293},
|
parameter={"value": 2.1508313829565293},
|
||||||
),
|
),
|
||||||
|
messages.DeviceInstructionMessage(
|
||||||
|
metadata={
|
||||||
|
"readout_priority": "monitored",
|
||||||
|
"RID": "1234",
|
||||||
|
"device_instr_id": "diid",
|
||||||
|
},
|
||||||
|
device=["bpm4i", "lsamx", "lsamy", "samx", "samy"],
|
||||||
|
action="pre_scan",
|
||||||
|
parameter={},
|
||||||
|
),
|
||||||
|
messages.DeviceInstructionMessage(
|
||||||
|
metadata={
|
||||||
|
"readout_priority": "monitored",
|
||||||
|
"RID": "1234",
|
||||||
|
"device_instr_id": "diid",
|
||||||
|
},
|
||||||
|
device="rtx",
|
||||||
|
action="set",
|
||||||
|
parameter={"value": 1.3681828686580249},
|
||||||
|
),
|
||||||
|
messages.DeviceInstructionMessage(
|
||||||
|
metadata={
|
||||||
|
"readout_priority": "monitored",
|
||||||
|
"RID": "1234",
|
||||||
|
"device_instr_id": "diid",
|
||||||
|
},
|
||||||
|
device="rty",
|
||||||
|
action="set",
|
||||||
|
parameter={"value": 2.1508313829565293},
|
||||||
|
),
|
||||||
None,
|
None,
|
||||||
messages.DeviceInstructionMessage(
|
messages.DeviceInstructionMessage(
|
||||||
metadata={
|
metadata={
|
||||||
|
|||||||
Reference in New Issue
Block a user