Compare commits
32 Commits
fixflomni-
...
test_pseud
| Author | SHA1 | Date | |
|---|---|---|---|
| b07ac52371 | |||
| 9557d98f30 | |||
|
|
fecd4b84a4 | ||
|
|
b67e1c012c | ||
|
|
cbbec12d9b | ||
|
|
8f4a9f025e | ||
|
|
1b9b983ab2 | ||
|
|
d7b442969a | ||
|
|
f92db3f169
|
||
|
|
55531c8a65
|
||
|
|
1d408818cc
|
||
|
|
ae2045dd10
|
||
|
|
fd4d455a5b
|
||
|
|
3411aaaeb4
|
||
|
|
d9fc3094b6 | ||
|
|
88df4781ec | ||
|
|
3b474c89c8 | ||
|
|
68cc13e1d3 | ||
|
|
700f3f9bb9 | ||
|
|
15a4d45f68 | ||
|
|
7c7f877d78 | ||
|
|
5d61d756c9 | ||
|
|
b37ae3ef57 | ||
|
|
76ed858e5c | ||
|
|
a0555def4d | ||
|
|
c1ad2fc4c3 | ||
|
|
9eee4ee1f7
|
||
|
c97b00cc8c
|
|||
|
d6a4fd37fc
|
|||
|
6d4c9d90fc
|
|||
| 87163cc3f1 | |||
| 7c17a3ae40 |
21
.gitea/workflows/rtd_deploy.yml
Normal file
21
.gitea/workflows/rtd_deploy.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
name: Read the Docs Deploy Trigger
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
trigger-rtd-webhook:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Trigger Read the Docs webhook
|
||||||
|
env:
|
||||||
|
RTD_TOKEN: ${{ secrets.RTD_TOKEN }}
|
||||||
|
run: |
|
||||||
|
curl --fail --show-error --silent \
|
||||||
|
-X POST \
|
||||||
|
-d "branches=${{ github.ref_name }}" \
|
||||||
|
-d "token=${RTD_TOKEN}" \
|
||||||
|
"https://readthedocs.org/api/v2/webhook/sls-csaxs/270162/"
|
||||||
@@ -8,15 +8,14 @@ version: 2
|
|||||||
build:
|
build:
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
tools:
|
tools:
|
||||||
python: "3.10"
|
python: "3.12"
|
||||||
jobs:
|
jobs:
|
||||||
pre_install:
|
pre_install:
|
||||||
- pip install .
|
- pip install .
|
||||||
|
|
||||||
|
|
||||||
# Build documentation in the docs/ directory with Sphinx
|
# Build documentation in the docs/ directory with Sphinx
|
||||||
sphinx:
|
sphinx:
|
||||||
configuration: docs/conf.py
|
configuration: docs/conf.py
|
||||||
|
|
||||||
# If using Sphinx, optionally build your docs in additional formats such as PDF
|
# If using Sphinx, optionally build your docs in additional formats such as PDF
|
||||||
# formats:
|
# formats:
|
||||||
@@ -24,6 +23,5 @@ sphinx:
|
|||||||
|
|
||||||
# Optionally declare the Python requirements required to build your docs
|
# Optionally declare the Python requirements required to build your docs
|
||||||
python:
|
python:
|
||||||
install:
|
install:
|
||||||
- requirements: docs/requirements.txt
|
- requirements: docs/requirements.txt
|
||||||
|
|
||||||
|
|||||||
BIN
csaxs_bec/bec_ipython_client/plugins/LamNI/LamNI.png
Normal file
BIN
csaxs_bec/bec_ipython_client/plugins/LamNI/LamNI.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 562 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 48 KiB |
@@ -0,0 +1,89 @@
|
|||||||
|
"""
|
||||||
|
LamNI/webpage_generator.py
|
||||||
|
===========================
|
||||||
|
LamNI-specific webpage generator subclass.
|
||||||
|
|
||||||
|
Integration (inside the LamNI __init__ / startup):
|
||||||
|
---------------------------------------------------
|
||||||
|
from csaxs_bec.bec_ipython_client.plugins.LamNI.webpage_generator import (
|
||||||
|
LamniWebpageGenerator,
|
||||||
|
)
|
||||||
|
self._webpage_gen = LamniWebpageGenerator(
|
||||||
|
bec_client=client,
|
||||||
|
output_dir="~/data/raw/webpage/",
|
||||||
|
)
|
||||||
|
self._webpage_gen.start()
|
||||||
|
|
||||||
|
Or use the factory (auto-selects by session name "lamni"):
|
||||||
|
----------------------------------------------------------
|
||||||
|
from csaxs_bec.bec_ipython_client.plugins.flomni.webpage_generator import (
|
||||||
|
make_webpage_generator,
|
||||||
|
)
|
||||||
|
self._webpage_gen = make_webpage_generator(bec, output_dir="~/data/raw/webpage/")
|
||||||
|
self._webpage_gen.start()
|
||||||
|
|
||||||
|
Interactive helpers:
|
||||||
|
--------------------
|
||||||
|
lamni._webpage_gen.status()
|
||||||
|
lamni._webpage_gen.verbosity = 2
|
||||||
|
lamni._webpage_gen.stop()
|
||||||
|
lamni._webpage_gen.start()
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from csaxs_bec.bec_ipython_client.plugins.flomni.webpage_generator import (
|
||||||
|
WebpageGeneratorBase,
|
||||||
|
_safe_get,
|
||||||
|
_safe_float,
|
||||||
|
_gvar,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class LamniWebpageGenerator(WebpageGeneratorBase):
|
||||||
|
"""
|
||||||
|
LamNI-specific webpage generator.
|
||||||
|
Logo: LamNI.png from the same directory as this file.
|
||||||
|
|
||||||
|
Override _collect_setup_data() to add LamNI-specific temperatures,
|
||||||
|
sample name, and measurement settings.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO: fill in LamNI-specific device paths
|
||||||
|
# label -> dotpath under device_manager.devices
|
||||||
|
_TEMP_MAP = {
|
||||||
|
# "Sample": "lamni_temphum.temperature_sample",
|
||||||
|
# "OSA": "lamni_temphum.temperature_osa",
|
||||||
|
}
|
||||||
|
|
||||||
|
def _logo_path(self):
|
||||||
|
return Path(__file__).parent / "LamNI.png"
|
||||||
|
|
||||||
|
def _collect_setup_data(self) -> dict:
|
||||||
|
# ── LamNI-specific data goes here ─────────────────────────────
|
||||||
|
# Uncomment and adapt when device names are known:
|
||||||
|
#
|
||||||
|
# dm = self._bec.device_manager
|
||||||
|
# sample_name = _safe_get(dm, "lamni_samples.sample_names.sample0") or "N/A"
|
||||||
|
# temperatures = {
|
||||||
|
# label: _safe_float(_safe_get(dm, path))
|
||||||
|
# for label, path in self._TEMP_MAP.items()
|
||||||
|
# }
|
||||||
|
# settings = {
|
||||||
|
# "Sample name": sample_name,
|
||||||
|
# "FOV x / y": ...,
|
||||||
|
# "Exposure time": _gvar(self._bec, "tomo_countingtime", ".3f", " s"),
|
||||||
|
# "Angle step": _gvar(self._bec, "tomo_angle_stepsize", ".2f", "\u00b0"),
|
||||||
|
# }
|
||||||
|
# return {
|
||||||
|
# "type": "lamni",
|
||||||
|
# "sample_name": sample_name,
|
||||||
|
# "temperatures": temperatures,
|
||||||
|
# "settings": settings,
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Placeholder — returns minimal info until implemented
|
||||||
|
return {
|
||||||
|
"type": "lamni",
|
||||||
|
# LamNI-specific data here
|
||||||
|
}
|
||||||
@@ -70,7 +70,7 @@ DLPCA200_AMPLIFIER_CONFIG: dict[str, dict] = {
|
|||||||
"rio_device": "galilrioesxbox",
|
"rio_device": "galilrioesxbox",
|
||||||
"description": "Beam Position Monitor 4 current amplifier",
|
"description": "Beam Position Monitor 4 current amplifier",
|
||||||
"channels": {
|
"channels": {
|
||||||
"gain_lsb": 0, # Pin 10 -> Galil ch0
|
"gain_lsb": rio_optics.analog_in.ch0, # Pin 10 -> Galil ch0
|
||||||
"gain_mid": 1, # Pin 11 -> Galil ch1
|
"gain_mid": 1, # Pin 11 -> Galil ch1
|
||||||
"gain_msb": 2, # Pin 12 -> Galil ch2
|
"gain_msb": 2, # Pin 12 -> Galil ch2
|
||||||
"coupling": 3, # Pin 13 -> Galil ch3
|
"coupling": 3, # Pin 13 -> Galil ch3
|
||||||
|
|||||||
BIN
csaxs_bec/bec_ipython_client/plugins/flomni/flOMNI.png
Normal file
BIN
csaxs_bec/bec_ipython_client/plugins/flomni/flOMNI.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
@@ -21,6 +21,14 @@ from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import (
|
|||||||
TomoIDManager,
|
TomoIDManager,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# from csaxs_bec.bec_ipython_client.plugins.flomni.webpage_generator import (
|
||||||
|
# FlomniWebpageGenerator,
|
||||||
|
# VERBOSITY_SILENT, # 0 — no output
|
||||||
|
# VERBOSITY_NORMAL, # 1 — startup / stop messages only (default)
|
||||||
|
# VERBOSITY_VERBOSE, # 2 — one-line summary per cycle
|
||||||
|
# VERBOSITY_DEBUG, # 3 — full JSON payload per cycle
|
||||||
|
# )
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
|
|
||||||
if builtins.__dict__.get("bec") is not None:
|
if builtins.__dict__.get("bec") is not None:
|
||||||
@@ -778,7 +786,9 @@ class FlomniSampleTransferMixin:
|
|||||||
dev.ftransy.controller.socket_put_confirmed("confirm=1")
|
dev.ftransy.controller.socket_put_confirmed("confirm=1")
|
||||||
else:
|
else:
|
||||||
print("Stopping.")
|
print("Stopping.")
|
||||||
exit
|
raise FlomniError(
|
||||||
|
"User abort sample transfer."
|
||||||
|
)
|
||||||
|
|
||||||
def ftransfer_gripper_is_open(self) -> bool:
|
def ftransfer_gripper_is_open(self) -> bool:
|
||||||
status = bool(float(dev.ftransy.controller.socket_put_and_receive("MG @OUT[9]").strip()))
|
status = bool(float(dev.ftransy.controller.socket_put_and_receive("MG @OUT[9]").strip()))
|
||||||
@@ -801,7 +811,8 @@ class FlomniSampleTransferMixin:
|
|||||||
def ftransfer_gripper_move(self, position: int):
|
def ftransfer_gripper_move(self, position: int):
|
||||||
self.check_position_is_valid(position)
|
self.check_position_is_valid(position)
|
||||||
|
|
||||||
self._ftransfer_shiftx = -0.2
|
#this is not used for sample stage position!
|
||||||
|
self._ftransfer_shiftx = -0.15
|
||||||
self._ftransfer_shiftz = -0.5
|
self._ftransfer_shiftz = -0.5
|
||||||
|
|
||||||
fsamx_pos = dev.fsamx.readback.get()
|
fsamx_pos = dev.fsamx.readback.get()
|
||||||
@@ -821,7 +832,7 @@ class FlomniSampleTransferMixin:
|
|||||||
self.check_tray_in()
|
self.check_tray_in()
|
||||||
|
|
||||||
if position == 0:
|
if position == 0:
|
||||||
umv(dev.ftransx, 10.715 + 0.2, dev.ftransz, 3.5950)
|
umv(dev.ftransx, 11, dev.ftransz, 3.5950)
|
||||||
if position == 1:
|
if position == 1:
|
||||||
umv(
|
umv(
|
||||||
dev.ftransx,
|
dev.ftransx,
|
||||||
@@ -966,8 +977,6 @@ class FlomniSampleTransferMixin:
|
|||||||
|
|
||||||
class FlomniAlignmentMixin:
|
class FlomniAlignmentMixin:
|
||||||
import csaxs_bec
|
import csaxs_bec
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Ensure this is a Path object, not a string
|
# Ensure this is a Path object, not a string
|
||||||
csaxs_bec_basepath = Path(csaxs_bec.__file__)
|
csaxs_bec_basepath = Path(csaxs_bec.__file__)
|
||||||
@@ -1208,6 +1217,76 @@ class FlomniAlignmentMixin:
|
|||||||
return additional_correction_shift
|
return additional_correction_shift
|
||||||
|
|
||||||
|
|
||||||
|
class _ProgressProxy:
|
||||||
|
"""Dict-like proxy that persists the flOMNI progress dict as a BEC global variable.
|
||||||
|
|
||||||
|
Every read (`proxy["key"]`) fetches the current dict from the global var store,
|
||||||
|
and every write (`proxy["key"] = val`) fetches, updates, and saves it back.
|
||||||
|
This makes the progress state visible to all BEC client sessions via
|
||||||
|
``client.get_global_var("tomo_progress")``.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_GLOBAL_VAR_KEY = "tomo_progress"
|
||||||
|
_DEFAULTS: dict = {
|
||||||
|
"subtomo": 0,
|
||||||
|
"subtomo_projection": 0,
|
||||||
|
"subtomo_total_projections": 1,
|
||||||
|
"projection": 0,
|
||||||
|
"total_projections": 1,
|
||||||
|
"angle": 0,
|
||||||
|
"tomo_type": 0,
|
||||||
|
"tomo_start_time": None,
|
||||||
|
"estimated_remaining_time": None,
|
||||||
|
"heartbeat": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, client):
|
||||||
|
self._client = client
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Internal helpers
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def _load(self) -> dict:
|
||||||
|
val = self._client.get_global_var(self._GLOBAL_VAR_KEY)
|
||||||
|
if val is None:
|
||||||
|
return dict(self._DEFAULTS)
|
||||||
|
return val
|
||||||
|
|
||||||
|
def _save(self, data: dict) -> None:
|
||||||
|
self._client.set_global_var(self._GLOBAL_VAR_KEY, data)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# Dict-like interface
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self._load()[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value) -> None:
|
||||||
|
data = self._load()
|
||||||
|
data[key] = value
|
||||||
|
self._save(data)
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"{self.__class__.__name__}({self._load()!r})"
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return self._load().get(key, default)
|
||||||
|
|
||||||
|
def update(self, *args, **kwargs) -> None:
|
||||||
|
"""Update multiple fields in a single round-trip."""
|
||||||
|
data = self._load()
|
||||||
|
data.update(*args, **kwargs)
|
||||||
|
self._save(data)
|
||||||
|
|
||||||
|
def reset(self) -> None:
|
||||||
|
"""Reset all progress fields to their default values."""
|
||||||
|
self._save(dict(self._DEFAULTS))
|
||||||
|
|
||||||
|
def as_dict(self) -> dict:
|
||||||
|
"""Return a plain copy of the current progress state."""
|
||||||
|
return self._load()
|
||||||
|
|
||||||
|
|
||||||
class Flomni(
|
class Flomni(
|
||||||
FlomniInitStagesMixin,
|
FlomniInitStagesMixin,
|
||||||
FlomniSampleTransferMixin,
|
FlomniSampleTransferMixin,
|
||||||
@@ -1230,14 +1309,18 @@ class Flomni(
|
|||||||
self.corr_angle_y = []
|
self.corr_angle_y = []
|
||||||
self.corr_pos_y_2 = []
|
self.corr_pos_y_2 = []
|
||||||
self.corr_angle_y_2 = []
|
self.corr_angle_y_2 = []
|
||||||
self.progress = {}
|
self._progress_proxy = _ProgressProxy(self.client)
|
||||||
self.progress["subtomo"] = 0
|
self._progress_proxy.reset()
|
||||||
self.progress["subtomo_projection"] = 0
|
from csaxs_bec.bec_ipython_client.plugins.flomni.flomni_webpage_generator import (
|
||||||
self.progress["subtomo_total_projections"] = 1
|
FlomniWebpageGenerator,
|
||||||
self.progress["projection"] = 0
|
)
|
||||||
self.progress["total_projections"] = 1
|
self._webpage_gen = FlomniWebpageGenerator(
|
||||||
self.progress["angle"] = 0
|
bec_client=client,
|
||||||
self.progress["tomo_type"] = 0
|
output_dir="~/data/raw/webpage/",
|
||||||
|
upload_url="http://s1090968537.online.de/upload.php", # optional
|
||||||
|
)
|
||||||
|
self._webpage_gen.start()
|
||||||
|
|
||||||
self.OMNYTools = OMNYTools(self.client)
|
self.OMNYTools = OMNYTools(self.client)
|
||||||
self.reconstructor = PtychoReconstructor(self.ptycho_reconstruct_foldername)
|
self.reconstructor = PtychoReconstructor(self.ptycho_reconstruct_foldername)
|
||||||
self.tomo_id_manager = TomoIDManager()
|
self.tomo_id_manager = TomoIDManager()
|
||||||
@@ -1293,6 +1376,42 @@ class Flomni(
|
|||||||
self.special_angles = []
|
self.special_angles = []
|
||||||
self.special_angle_repeats = 1
|
self.special_angle_repeats = 1
|
||||||
|
|
||||||
|
@property
|
||||||
|
def progress(self) -> _ProgressProxy:
|
||||||
|
"""Proxy dict backed by the BEC global variable ``tomo_progress``.
|
||||||
|
|
||||||
|
Readable from any BEC client session via::
|
||||||
|
|
||||||
|
client.get_global_var("tomo_progress")
|
||||||
|
|
||||||
|
Individual fields can be read and written just like a regular dict::
|
||||||
|
|
||||||
|
flomni.progress["projection"] # read
|
||||||
|
flomni.progress["projection"] = 42 # write (persists immediately)
|
||||||
|
|
||||||
|
To update multiple fields atomically use :py:meth:`_ProgressProxy.update`::
|
||||||
|
|
||||||
|
flomni.progress.update(projection=42, angle=90.0)
|
||||||
|
|
||||||
|
To reset all fields to their defaults::
|
||||||
|
|
||||||
|
flomni.progress.reset()
|
||||||
|
"""
|
||||||
|
return self._progress_proxy
|
||||||
|
|
||||||
|
@progress.setter
|
||||||
|
def progress(self, val: dict) -> None:
|
||||||
|
"""Replace the entire progress dict.
|
||||||
|
|
||||||
|
Accepts a plain :class:`dict` and persists it to the global var store.
|
||||||
|
Example::
|
||||||
|
|
||||||
|
flomni.progress = {"projection": 0, "total_projections": 100, ...}
|
||||||
|
"""
|
||||||
|
if not isinstance(val, dict):
|
||||||
|
raise TypeError(f"progress must be a dict, got {type(val).__name__!r}")
|
||||||
|
self._progress_proxy._save(val)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def tomo_shellstep(self):
|
def tomo_shellstep(self):
|
||||||
val = self.client.get_global_var("tomo_shellstep")
|
val = self.client.get_global_var("tomo_shellstep")
|
||||||
@@ -1479,21 +1598,11 @@ class Flomni(
|
|||||||
def sample_name(self):
|
def sample_name(self):
|
||||||
return self.sample_get_name(0)
|
return self.sample_get_name(0)
|
||||||
|
|
||||||
def write_to_scilog(self, content, tags: list = None):
|
|
||||||
try:
|
|
||||||
if tags is not None:
|
|
||||||
tags.append("BEC")
|
|
||||||
else:
|
|
||||||
tags = ["BEC"]
|
|
||||||
msg = bec.logbook.LogbookMessage()
|
|
||||||
msg.add_text(content).add_tag(tags)
|
|
||||||
self.client.logbook.send_logbook_message(msg)
|
|
||||||
except Exception:
|
|
||||||
logger.warning("Failed to write to scilog.")
|
|
||||||
|
|
||||||
def tomo_alignment_scan(self):
|
def tomo_alignment_scan(self):
|
||||||
"""
|
"""
|
||||||
Performs a tomogram alignment scan.
|
Performs a tomogram alignment scan.
|
||||||
|
Collects all scan numbers acquired during the alignment, prints them at the end,
|
||||||
|
and creates a BEC scilog text entry summarising the alignment scan numbers.
|
||||||
"""
|
"""
|
||||||
if self.get_alignment_offset(0) == (0, 0, 0):
|
if self.get_alignment_offset(0) == (0, 0, 0):
|
||||||
print("It appears that the xrayeye alignemtn was not performend or loaded. Aborting.")
|
print("It appears that the xrayeye alignemtn was not performend or loaded. Aborting.")
|
||||||
@@ -1503,11 +1612,9 @@ class Flomni(
|
|||||||
|
|
||||||
self.feye_out()
|
self.feye_out()
|
||||||
tags = ["BEC_alignment_tomo", self.sample_name]
|
tags = ["BEC_alignment_tomo", self.sample_name]
|
||||||
self.write_to_scilog(
|
|
||||||
f"Starting alignment scan. First scan number: {bec.queue.next_scan_number}.", tags
|
|
||||||
)
|
|
||||||
|
|
||||||
start_angle = 0
|
start_angle = 0
|
||||||
|
alignment_scan_numbers = []
|
||||||
|
|
||||||
angle_end = start_angle + 180
|
angle_end = start_angle + 180
|
||||||
for angle in np.linspace(start_angle, angle_end, num=int(180 / 45) + 1, endpoint=True):
|
for angle in np.linspace(start_angle, angle_end, num=int(180 / 45) + 1, endpoint=True):
|
||||||
@@ -1519,7 +1626,6 @@ class Flomni(
|
|||||||
try:
|
try:
|
||||||
start_scan_number = bec.queue.next_scan_number
|
start_scan_number = bec.queue.next_scan_number
|
||||||
self.tomo_scan_projection(angle)
|
self.tomo_scan_projection(angle)
|
||||||
self.tomo_reconstruct()
|
|
||||||
error_caught = False
|
error_caught = False
|
||||||
except AlarmBase as exc:
|
except AlarmBase as exc:
|
||||||
if exc.alarm_type == "TimeoutError":
|
if exc.alarm_type == "TimeoutError":
|
||||||
@@ -1533,24 +1639,27 @@ class Flomni(
|
|||||||
|
|
||||||
end_scan_number = bec.queue.next_scan_number
|
end_scan_number = bec.queue.next_scan_number
|
||||||
for scan_nr in range(start_scan_number, end_scan_number):
|
for scan_nr in range(start_scan_number, end_scan_number):
|
||||||
self._write_tomo_scan_number(scan_nr, angle, 0)
|
#self._write_tomo_scan_number(scan_nr, angle, 0)
|
||||||
|
alignment_scan_numbers.append(scan_nr)
|
||||||
|
|
||||||
umv(dev.fsamroy, 0)
|
umv(dev.fsamroy, 0)
|
||||||
self.OMNYTools.printgreenbold(
|
self.OMNYTools.printgreenbold(
|
||||||
"\n\nAlignment scan finished. Please run SPEC_ptycho_align and load the new fit."
|
"\n\nAlignment scan finished. Please run SPEC_ptycho_align and load the new fit by flomni.read_alignment_offset() ."
|
||||||
)
|
)
|
||||||
|
|
||||||
def _write_subtomo_to_scilog(self, subtomo_number):
|
# summary of alignment scan numbers
|
||||||
dev = builtins.__dict__.get("dev")
|
scan_list_str = ", ".join(str(s) for s in alignment_scan_numbers)
|
||||||
bec = builtins.__dict__.get("bec")
|
#print(f"\nAlignment scan numbers ({len(alignment_scan_numbers)} total): {scan_list_str}")
|
||||||
if self.tomo_id > 0:
|
|
||||||
tags = ["BEC_subtomo", self.sample_name, f"tomo_id_{self.tomo_id}"]
|
# BEC scilog entry (no logo)
|
||||||
else:
|
scilog_content = (
|
||||||
tags = ["BEC_subtomo", self.sample_name]
|
f"Alignment scan finished.\n"
|
||||||
self.write_to_scilog(
|
f"Sample: {self.sample_name}\n"
|
||||||
f"Starting subtomo: {subtomo_number}. First scan number: {bec.queue.next_scan_number}.",
|
f"Number of alignment scans: {len(alignment_scan_numbers)}\n"
|
||||||
tags,
|
f"Alignment scan numbers: {scan_list_str}\n"
|
||||||
)
|
)
|
||||||
|
print(scliog_content)
|
||||||
|
bec.messaging.scilog.new().add_text(scilog_content.replace("\n", "<br>")).add_tags("alignmentscan").send()
|
||||||
|
|
||||||
def sub_tomo_scan(self, subtomo_number, start_angle=None):
|
def sub_tomo_scan(self, subtomo_number, start_angle=None):
|
||||||
"""
|
"""
|
||||||
@@ -1559,18 +1668,6 @@ class Flomni(
|
|||||||
subtomo_number (int): The sub tomogram number.
|
subtomo_number (int): The sub tomogram number.
|
||||||
start_angle (float, optional): The start angle of the scan. Defaults to None.
|
start_angle (float, optional): The start angle of the scan. Defaults to None.
|
||||||
"""
|
"""
|
||||||
# dev = builtins.__dict__.get("dev")
|
|
||||||
# bec = builtins.__dict__.get("bec")
|
|
||||||
# if self.tomo_id > 0:
|
|
||||||
# tags = ["BEC_subtomo", self.sample_name, f"tomo_id_{self.tomo_id}"]
|
|
||||||
# else:
|
|
||||||
# tags = ["BEC_subtomo", self.sample_name]
|
|
||||||
# self.write_to_scilog(
|
|
||||||
# f"Starting subtomo: {subtomo_number}. First scan number: {bec.queue.next_scan_number}.",
|
|
||||||
# tags,
|
|
||||||
# )
|
|
||||||
|
|
||||||
self._write_subtomo_to_scilog(subtomo_number)
|
|
||||||
|
|
||||||
if start_angle is not None:
|
if start_angle is not None:
|
||||||
print(f"Sub tomo scan with start angle {start_angle} requested.")
|
print(f"Sub tomo scan with start angle {start_angle} requested.")
|
||||||
@@ -1670,6 +1767,7 @@ class Flomni(
|
|||||||
successful = False
|
successful = False
|
||||||
error_caught = False
|
error_caught = False
|
||||||
if 0 <= angle < 180.05:
|
if 0 <= angle < 180.05:
|
||||||
|
self.progress["heartbeat"] = datetime.datetime.now().isoformat()
|
||||||
print(f"Starting flOMNI scan for angle {angle} in subtomo {subtomo_number}")
|
print(f"Starting flOMNI scan for angle {angle} in subtomo {subtomo_number}")
|
||||||
self._print_progress()
|
self._print_progress()
|
||||||
while not successful:
|
while not successful:
|
||||||
@@ -1713,9 +1811,9 @@ class Flomni(
|
|||||||
)
|
)
|
||||||
if self.OMNYTools.yesno("Shall I continue?", "n"):
|
if self.OMNYTools.yesno("Shall I continue?", "n"):
|
||||||
print("OK")
|
print("OK")
|
||||||
else:
|
else:
|
||||||
print("Stopping.")
|
print("Stopping.")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.flomnigui_show_progress()
|
self.flomnigui_show_progress()
|
||||||
|
|
||||||
@@ -1743,6 +1841,8 @@ class Flomni(
|
|||||||
# self.write_pdf_report()
|
# self.write_pdf_report()
|
||||||
# else:
|
# else:
|
||||||
self.tomo_id = 0
|
self.tomo_id = 0
|
||||||
|
self.write_pdf_report()
|
||||||
|
self.progress["tomo_start_time"] = datetime.datetime.now().isoformat()
|
||||||
|
|
||||||
with scans.dataset_id_on_hold:
|
with scans.dataset_id_on_hold:
|
||||||
if self.tomo_type == 1:
|
if self.tomo_type == 1:
|
||||||
@@ -1762,7 +1862,6 @@ class Flomni(
|
|||||||
while True:
|
while True:
|
||||||
angle, subtomo_number = self._golden(ii, self.golden_ratio_bunch_size, 180, 1)
|
angle, subtomo_number = self._golden(ii, self.golden_ratio_bunch_size, 180, 1)
|
||||||
if previous_subtomo_number != subtomo_number:
|
if previous_subtomo_number != subtomo_number:
|
||||||
self._write_subtomo_to_scilog(subtomo_number)
|
|
||||||
if (
|
if (
|
||||||
subtomo_number % 2 == 1
|
subtomo_number % 2 == 1
|
||||||
and ii > 10
|
and ii > 10
|
||||||
@@ -1810,7 +1909,6 @@ class Flomni(
|
|||||||
ii, int(180 / self.tomo_angle_stepsize), 180, 1, 0
|
ii, int(180 / self.tomo_angle_stepsize), 180, 1, 0
|
||||||
)
|
)
|
||||||
if previous_subtomo_number != subtomo_number:
|
if previous_subtomo_number != subtomo_number:
|
||||||
self._write_subtomo_to_scilog(subtomo_number)
|
|
||||||
if (
|
if (
|
||||||
subtomo_number % 2 == 1
|
subtomo_number % 2 == 1
|
||||||
and ii > 10
|
and ii > 10
|
||||||
@@ -1852,14 +1950,42 @@ class Flomni(
|
|||||||
self._print_progress()
|
self._print_progress()
|
||||||
self.OMNYTools.printgreenbold("Tomoscan finished")
|
self.OMNYTools.printgreenbold("Tomoscan finished")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _format_duration(seconds: float) -> str:
|
||||||
|
"""Format a duration in seconds as a human-readable string, e.g. '2h 03m 15s'."""
|
||||||
|
seconds = int(seconds)
|
||||||
|
h, remainder = divmod(seconds, 3600)
|
||||||
|
m, s = divmod(remainder, 60)
|
||||||
|
if h > 0:
|
||||||
|
return f"{h}h {m:02d}m {s:02d}s"
|
||||||
|
if m > 0:
|
||||||
|
return f"{m}m {s:02d}s"
|
||||||
|
return f"{s}s"
|
||||||
|
|
||||||
def _print_progress(self):
|
def _print_progress(self):
|
||||||
|
# --- compute and store estimated remaining time -----------------------
|
||||||
|
start_str = self.progress.get("tomo_start_time")
|
||||||
|
projection = self.progress["projection"]
|
||||||
|
total = self.progress["total_projections"]
|
||||||
|
if start_str is not None and total > 0 and projection > 9:
|
||||||
|
elapsed = (
|
||||||
|
datetime.datetime.now() - datetime.datetime.fromisoformat(start_str)
|
||||||
|
).total_seconds()
|
||||||
|
rate = projection / elapsed # projections per second
|
||||||
|
remaining_s = (total - projection) / rate
|
||||||
|
self.progress["estimated_remaining_time"] = remaining_s
|
||||||
|
eta_str = self._format_duration(remaining_s)
|
||||||
|
else:
|
||||||
|
eta_str = "N/A"
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
print("\x1b[95mProgress report:")
|
print("\x1b[95mProgress report:")
|
||||||
print(f"Tomo type: ....................... {self.progress['tomo_type']}")
|
print(f"Tomo type: ....................... {self.progress['tomo_type']}")
|
||||||
print(f"Projection: ...................... {self.progress['projection']:.0f}")
|
print(f"Projection: ...................... {self.progress['projection']:.0f}")
|
||||||
print(f"Total projections expected ....... {self.progress['total_projections']}")
|
print(f"Total projections expected ....... {self.progress['total_projections']}")
|
||||||
print(f"Angle: ........................... {self.progress['angle']}")
|
print(f"Angle: ........................... {self.progress['angle']}")
|
||||||
print(f"Current subtomo: ................. {self.progress['subtomo']}")
|
print(f"Current subtomo: ................. {self.progress['subtomo']}")
|
||||||
print(f"Current projection within subtomo: {self.progress['subtomo_projection']}\x1b[0m")
|
print(f"Current projection within subtomo: {self.progress['subtomo_projection']}")
|
||||||
|
print(f"Estimated remaining time: ........ {eta_str}\x1b[0m")
|
||||||
self._flomnigui_update_progress()
|
self._flomnigui_update_progress()
|
||||||
|
|
||||||
def add_sample_database(
|
def add_sample_database(
|
||||||
@@ -1883,7 +2009,6 @@ class Flomni(
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.tomo_scan_projection(angle)
|
self.tomo_scan_projection(angle)
|
||||||
self.tomo_reconstruct()
|
|
||||||
|
|
||||||
def _golden(self, ii, howmany_sorted, maxangle, reverse=False):
|
def _golden(self, ii, howmany_sorted, maxangle, reverse=False):
|
||||||
"""returns the iis golden ratio angle of sorted bunches of howmany_sorted and its subtomo number"""
|
"""returns the iis golden ratio angle of sorted bunches of howmany_sorted and its subtomo number"""
|
||||||
@@ -1988,7 +2113,7 @@ class Flomni(
|
|||||||
f"{str(datetime.datetime.now())}: flomni scan projection at angle {angle}, scan"
|
f"{str(datetime.datetime.now())}: flomni scan projection at angle {angle}, scan"
|
||||||
f" number {bec.queue.next_scan_number}.\n"
|
f" number {bec.queue.next_scan_number}.\n"
|
||||||
)
|
)
|
||||||
# self.write_to_scilog(log_message, ["BEC_scans", self.sample_name])
|
|
||||||
scans.flomni_fermat_scan(
|
scans.flomni_fermat_scan(
|
||||||
fovx=self.fovx,
|
fovx=self.fovx,
|
||||||
fovy=self.fovy,
|
fovy=self.fovy,
|
||||||
@@ -2001,6 +2126,9 @@ class Flomni(
|
|||||||
corridor_size=corridor_size,
|
corridor_size=corridor_size,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.tomo_reconstruct()
|
||||||
|
|
||||||
|
|
||||||
def tomo_parameters(self):
|
def tomo_parameters(self):
|
||||||
"""print and update the tomo parameters"""
|
"""print and update the tomo parameters"""
|
||||||
print("Current settings:")
|
print("Current settings:")
|
||||||
@@ -2139,19 +2267,21 @@ class Flomni(
|
|||||||
+ ' 888 888 "Y88888P" 888 888 888 Y888 8888888 \n'
|
+ ' 888 888 "Y88888P" 888 888 888 Y888 8888888 \n'
|
||||||
)
|
)
|
||||||
padding = 20
|
padding = 20
|
||||||
fovxy = f"{self.fovx:.2f}/{self.fovy:.2f}"
|
fovxy = f"{self.fovx:.1f}/{self.fovy:.1f}"
|
||||||
stitching = f"{self.stitch_x:.2f}/{self.stitch_y:.2f}"
|
stitching = f"{self.stitch_x:.0f}/{self.stitch_y:.0f}"
|
||||||
dataset_id = str(self.client.queue.next_dataset_number)
|
dataset_id = str(self.client.queue.next_dataset_number)
|
||||||
|
account = bec.active_account
|
||||||
content = [
|
content = [
|
||||||
f"{'Sample Name:':<{padding}}{self.sample_name:>{padding}}\n",
|
f"{'Sample Name:':<{padding}}{self.sample_name:>{padding}}\n",
|
||||||
f"{'Measurement ID:':<{padding}}{str(self.tomo_id):>{padding}}\n",
|
f"{'Measurement ID:':<{padding}}{str(self.tomo_id):>{padding}}\n",
|
||||||
f"{'Dataset ID:':<{padding}}{dataset_id:>{padding}}\n",
|
f"{'Dataset ID:':<{padding}}{dataset_id:>{padding}}\n",
|
||||||
f"{'Sample Info:':<{padding}}{'Sample Info':>{padding}}\n",
|
f"{'Sample Info:':<{padding}}{'Sample Info':>{padding}}\n",
|
||||||
f"{'e-account:':<{padding}}{str(self.client.username):>{padding}}\n",
|
f"{'e-account:':<{padding}}{str(account):>{padding}}\n",
|
||||||
f"{'Number of projections:':<{padding}}{int(180 / self.tomo_angle_stepsize * 8):>{padding}}\n",
|
f"{'Number of projections:':<{padding}}{int(180 / self.tomo_angle_stepsize * 8):>{padding}}\n",
|
||||||
f"{'First scan number:':<{padding}}{self.client.queue.next_scan_number:>{padding}}\n",
|
f"{'First scan number:':<{padding}}{self.client.queue.next_scan_number:>{padding}}\n",
|
||||||
f"{'Last scan number approx.:':<{padding}}{self.client.queue.next_scan_number + int(180 / self.tomo_angle_stepsize * 8) + 10:>{padding}}\n",
|
f"{'Last scan number approx.:':<{padding}}{self.client.queue.next_scan_number + int(180 / self.tomo_angle_stepsize * 8) + 10:>{padding}}\n",
|
||||||
f"{'Current photon energy:':<{padding}}{dev.mokev.read()['mokev']['value']:>{padding}.4f}\n",
|
f"{'Current photon energy:':<{padding}}To be implemented\n",
|
||||||
|
#f"{'Current photon energy:':<{padding}}{dev.mokev.read()['mokev']['value']:>{padding}.4f}\n",
|
||||||
f"{'Exposure time:':<{padding}}{self.tomo_countingtime:>{padding}.2f}\n",
|
f"{'Exposure time:':<{padding}}{self.tomo_countingtime:>{padding}.2f}\n",
|
||||||
f"{'Fermat spiral step size:':<{padding}}{self.tomo_shellstep:>{padding}.2f}\n",
|
f"{'Fermat spiral step size:':<{padding}}{self.tomo_shellstep:>{padding}.2f}\n",
|
||||||
f"{'FOV:':<{padding}}{fovxy:>{padding}}\n",
|
f"{'FOV:':<{padding}}{fovxy:>{padding}}\n",
|
||||||
@@ -2160,20 +2290,38 @@ class Flomni(
|
|||||||
f"{'Angular step within sub-tomogram:':<{padding}}{self.tomo_angle_stepsize:>{padding}.2f}\n",
|
f"{'Angular step within sub-tomogram:':<{padding}}{self.tomo_angle_stepsize:>{padding}.2f}\n",
|
||||||
]
|
]
|
||||||
content = "".join(content)
|
content = "".join(content)
|
||||||
user_target = os.path.expanduser(f"~/Data10/documentation/tomo_scan_ID_{self.tomo_id}.pdf")
|
user_target = os.path.expanduser(f"~/data/raw/documentation/tomo_scan_ID_{self.tomo_id}.pdf")
|
||||||
with PDFWriter(user_target) as file:
|
with PDFWriter(user_target) as file:
|
||||||
file.write(header)
|
file.write(header)
|
||||||
file.write(content)
|
file.write(content)
|
||||||
subprocess.run(
|
# subprocess.run(
|
||||||
"xterm /work/sls/spec/local/XOMNY/bin/upload/upload_last_pon.sh &", shell=True
|
# "xterm /work/sls/spec/local/XOMNY/bin/upload/upload_last_pon.sh &", shell=True
|
||||||
)
|
# )
|
||||||
# status = subprocess.run(f"cp /tmp/spec-e20131-specES1.pdf {user_target}", shell=True)
|
# status = subprocess.run(f"cp /tmp/spec-e20131-specES1.pdf {user_target}", shell=True)
|
||||||
msg = bec.logbook.LogbookMessage()
|
# msg = bec.tomo_progress.tomo_progressMessage()
|
||||||
logo_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "LamNI_logo.png")
|
# logo_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "LamNI_logo.png")
|
||||||
msg.add_file(logo_path).add_text("".join(content).replace("\n", "</p><p>")).add_tag(
|
# msg.add_file(logo_path).add_text("".join(content).replace("\n", "</p><p>")).add_tag(
|
||||||
["BEC", "tomo_parameters", f"dataset_id_{dataset_id}", "LamNI", self.sample_name]
|
# ["BEC", "tomo_parameters", f"dataset_id_{dataset_id}", "flOMNI", self.sample_name]
|
||||||
)
|
# )
|
||||||
self.client.logbook.send_logbook_message(msg)
|
# self.client.tomo_progress.send_tomo_progress_message("~/data/raw/documentation/tomo_scan_ID_{self.tomo_id}.pdf").send()
|
||||||
|
import csaxs_bec
|
||||||
|
|
||||||
|
|
||||||
|
# Ensure this is a Path object, not a string
|
||||||
|
csaxs_bec_basepath = Path(csaxs_bec.__file__)
|
||||||
|
|
||||||
|
logo_file_rel = "flOMNI.png"
|
||||||
|
|
||||||
|
# Build the absolute path correctly
|
||||||
|
logo_file = (
|
||||||
|
csaxs_bec_basepath.parent
|
||||||
|
/ "bec_ipython_client"
|
||||||
|
/ "plugins"
|
||||||
|
/ "flomni"
|
||||||
|
/ logo_file_rel
|
||||||
|
).resolve()
|
||||||
|
print(logo_file)
|
||||||
|
bec.messaging.scilog.new().add_attachment(logo_file, width=200).add_text(content.replace("\n", "<br>")).add_tags("tomoscan").send()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ class FlomniOpticsMixin:
|
|||||||
# move both axes to the desired "in" positions
|
# move both axes to the desired "in" positions
|
||||||
umv(dev.feyex, feyex_in, dev.feyey, feyey_in)
|
umv(dev.feyex, feyex_in, dev.feyey, feyey_in)
|
||||||
|
|
||||||
self.xrayeye_update_frame()
|
|
||||||
|
|
||||||
def _ffzp_in(self):
|
def _ffzp_in(self):
|
||||||
foptx_in = self._get_user_param_safe("foptx", "in")
|
foptx_in = self._get_user_param_safe("foptx", "in")
|
||||||
fopty_in = self._get_user_param_safe("fopty", "in")
|
fopty_in = self._get_user_param_safe("fopty", "in")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -223,6 +223,14 @@ class flomniGuiTools:
|
|||||||
self._flomnigui_update_progress()
|
self._flomnigui_update_progress()
|
||||||
|
|
||||||
def _flomnigui_update_progress(self):
|
def _flomnigui_update_progress(self):
|
||||||
|
"""Update the progress ring bar and center label from the current progress state.
|
||||||
|
|
||||||
|
``self.progress`` is backed by the BEC global variable ``tomo_progress``
|
||||||
|
(see :class:`_ProgressProxy` in ``flomni.py``), so this method reflects
|
||||||
|
the live state that is also accessible from other BEC client sessions via::
|
||||||
|
|
||||||
|
client.get_global_var("tomo_progress")
|
||||||
|
"""
|
||||||
main_progress_ring = self.progressbar.rings[0]
|
main_progress_ring = self.progressbar.rings[0]
|
||||||
subtomo_progress_ring = self.progressbar.rings[1]
|
subtomo_progress_ring = self.progressbar.rings[1]
|
||||||
if self.progressbar is not None:
|
if self.progressbar is not None:
|
||||||
@@ -235,6 +243,31 @@ class flomniGuiTools:
|
|||||||
main_progress_ring.set_value(progress)
|
main_progress_ring.set_value(progress)
|
||||||
subtomo_progress_ring.set_value(subtomo_progress)
|
subtomo_progress_ring.set_value(subtomo_progress)
|
||||||
|
|
||||||
|
# --- format start time for display --------------------------------
|
||||||
|
start_str = self.progress.get("tomo_start_time")
|
||||||
|
if start_str is not None:
|
||||||
|
import datetime as _dt
|
||||||
|
start_display = _dt.datetime.fromisoformat(start_str).strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
else:
|
||||||
|
start_display = "N/A"
|
||||||
|
|
||||||
|
# --- format estimated remaining time ------------------------------
|
||||||
|
remaining_s = self.progress.get("estimated_remaining_time")
|
||||||
|
if remaining_s is not None and remaining_s >= 0:
|
||||||
|
import datetime as _dt
|
||||||
|
remaining_s = int(remaining_s)
|
||||||
|
h, rem = divmod(remaining_s, 3600)
|
||||||
|
m, s = divmod(rem, 60)
|
||||||
|
if h > 0:
|
||||||
|
eta_display = f"{h}h {m:02d}m {s:02d}s"
|
||||||
|
elif m > 0:
|
||||||
|
eta_display = f"{m}m {s:02d}s"
|
||||||
|
else:
|
||||||
|
eta_display = f"{s}s"
|
||||||
|
else:
|
||||||
|
eta_display = "N/A"
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
text = (
|
text = (
|
||||||
f"Progress report:\n"
|
f"Progress report:\n"
|
||||||
f" Tomo type: {self.progress['tomo_type']}\n"
|
f" Tomo type: {self.progress['tomo_type']}\n"
|
||||||
@@ -243,7 +276,9 @@ class flomniGuiTools:
|
|||||||
f" Angle: {self.progress['angle']:.1f}\n"
|
f" Angle: {self.progress['angle']:.1f}\n"
|
||||||
f" Current subtomo: {self.progress['subtomo']}\n"
|
f" Current subtomo: {self.progress['subtomo']}\n"
|
||||||
f" Current projection within subtomo: {self.progress['subtomo_projection']}\n"
|
f" Current projection within subtomo: {self.progress['subtomo_projection']}\n"
|
||||||
f" Total projections per subtomo: {int(self.progress['subtomo_total_projections'])}"
|
f" Total projections per subtomo: {int(self.progress['subtomo_total_projections'])}\n"
|
||||||
|
f" Scan started: {start_display}\n"
|
||||||
|
f" Est. remaining: {eta_display}"
|
||||||
)
|
)
|
||||||
self.progressbar.set_center_label(text)
|
self.progressbar.set_center_label(text)
|
||||||
|
|
||||||
|
|||||||
@@ -253,6 +253,8 @@ class XrayEyeAlign:
|
|||||||
|
|
||||||
umv(dev.rtx, 0)
|
umv(dev.rtx, 0)
|
||||||
print("You are ready to remove the xray eye and start ptychography scans.")
|
print("You are ready to remove the xray eye and start ptychography scans.")
|
||||||
|
print("Fine alignment: flomni.tomo_parameters() , then flomni.tomo_alignment_scan()")
|
||||||
|
print("After that, run the fit in Matlab and load the new fit flomni.read_alignment_offset()")
|
||||||
|
|
||||||
def write_output(self):
|
def write_output(self):
|
||||||
file = os.path.expanduser("~/Data10/specES1/internal/xrayeye_alignmentvalues")
|
file = os.path.expanduser("~/Data10/specES1/internal/xrayeye_alignmentvalues")
|
||||||
|
|||||||
BIN
csaxs_bec/bec_ipython_client/plugins/omny/OMNY.png
Normal file
BIN
csaxs_bec/bec_ipython_client/plugins/omny/OMNY.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 359 KiB |
@@ -0,0 +1,96 @@
|
|||||||
|
"""
|
||||||
|
omny/webpage_generator.py
|
||||||
|
==========================
|
||||||
|
OMNY-specific webpage generator subclass.
|
||||||
|
|
||||||
|
Integration (inside the OMNY __init__ / startup):
|
||||||
|
--------------------------------------------------
|
||||||
|
from csaxs_bec.bec_ipython_client.plugins.omny.webpage_generator import (
|
||||||
|
OmnyWebpageGenerator,
|
||||||
|
)
|
||||||
|
self._webpage_gen = OmnyWebpageGenerator(
|
||||||
|
bec_client=client,
|
||||||
|
output_dir="~/data/raw/webpage/",
|
||||||
|
)
|
||||||
|
self._webpage_gen.start()
|
||||||
|
|
||||||
|
Or use the factory (auto-selects by session name "omny"):
|
||||||
|
---------------------------------------------------------
|
||||||
|
from csaxs_bec.bec_ipython_client.plugins.flomni.webpage_generator import (
|
||||||
|
make_webpage_generator,
|
||||||
|
)
|
||||||
|
self._webpage_gen = make_webpage_generator(bec, output_dir="~/data/raw/webpage/")
|
||||||
|
self._webpage_gen.start()
|
||||||
|
|
||||||
|
Interactive helpers:
|
||||||
|
--------------------
|
||||||
|
omny._webpage_gen.status()
|
||||||
|
omny._webpage_gen.verbosity = 2
|
||||||
|
omny._webpage_gen.stop()
|
||||||
|
omny._webpage_gen.start()
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from csaxs_bec.bec_ipython_client.plugins.flomni.webpage_generator import (
|
||||||
|
WebpageGeneratorBase,
|
||||||
|
_safe_get,
|
||||||
|
_safe_float,
|
||||||
|
_gvar,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OmnyWebpageGenerator(WebpageGeneratorBase):
|
||||||
|
"""
|
||||||
|
OMNY-specific webpage generator.
|
||||||
|
Logo: OMNY.png from the same directory as this file.
|
||||||
|
|
||||||
|
Override _collect_setup_data() to add OMNY-specific temperatures,
|
||||||
|
sample name, and measurement settings.
|
||||||
|
|
||||||
|
The old OMNY spec webpage showed:
|
||||||
|
- Cryo temperatures (XOMNY-TEMP-CRYO-A/B)
|
||||||
|
- Per-channel temperatures (XOMNY-TEMP1..48)
|
||||||
|
- Dewar pressure / LN2 flow
|
||||||
|
- Interferometer strengths (OINTERF)
|
||||||
|
Map these to BEC device paths below once available.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO: fill in OMNY-specific device paths
|
||||||
|
# label -> dotpath under device_manager.devices
|
||||||
|
_TEMP_MAP = {
|
||||||
|
# "Sample (cryo A)": "omny_temp.cryo_a",
|
||||||
|
# "Cryo head (B)": "omny_temp.cryo_b",
|
||||||
|
}
|
||||||
|
|
||||||
|
def _logo_path(self):
|
||||||
|
return Path(__file__).parent / "OMNY.png"
|
||||||
|
|
||||||
|
def _collect_setup_data(self) -> dict:
|
||||||
|
# ── OMNY-specific data goes here ──────────────────────────────
|
||||||
|
# Uncomment and adapt when device names are known:
|
||||||
|
#
|
||||||
|
# dm = self._bec.device_manager
|
||||||
|
# sample_name = _safe_get(dm, "omny_samples.sample_names.sample0") or "N/A"
|
||||||
|
# temperatures = {
|
||||||
|
# label: _safe_float(_safe_get(dm, path))
|
||||||
|
# for label, path in self._TEMP_MAP.items()
|
||||||
|
# }
|
||||||
|
# settings = {
|
||||||
|
# "Sample name": sample_name,
|
||||||
|
# "FOV x / y": ...,
|
||||||
|
# "Exposure time": _gvar(self._bec, "tomo_countingtime", ".3f", " s"),
|
||||||
|
# "Angle step": _gvar(self._bec, "tomo_angle_stepsize", ".2f", "\u00b0"),
|
||||||
|
# }
|
||||||
|
# return {
|
||||||
|
# "type": "omny",
|
||||||
|
# "sample_name": sample_name,
|
||||||
|
# "temperatures": temperatures,
|
||||||
|
# "settings": settings,
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Placeholder — returns minimal info until implemented
|
||||||
|
return {
|
||||||
|
"type": "omny",
|
||||||
|
# OMNY-specific data here
|
||||||
|
}
|
||||||
@@ -72,7 +72,7 @@ xbpm3x:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -95,7 +95,7 @@ xbpm3y:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -118,7 +118,7 @@ sl3trxi:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -141,7 +141,7 @@ sl3trxo:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -164,7 +164,7 @@ sl3trxb:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -187,7 +187,7 @@ sl3trxt:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -210,7 +210,7 @@ fast_shutter_n1_x:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -234,7 +234,7 @@ fast_shutter_o1_x:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -257,7 +257,7 @@ fast_shutter_o2_x:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -280,7 +280,7 @@ filter_array_1_x:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -303,7 +303,7 @@ filter_array_2_x:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -326,7 +326,7 @@ filter_array_3_x:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -349,7 +349,7 @@ filter_array_4_x:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -372,7 +372,7 @@ sl4trxi:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -395,7 +395,7 @@ sl4trxo:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -418,7 +418,7 @@ sl4trxb:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -441,7 +441,7 @@ sl4trxt:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -466,7 +466,7 @@ sl5trxi:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -489,7 +489,7 @@ sl5trxo:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -512,7 +512,7 @@ sl5trxb:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -535,7 +535,7 @@ sl5trxt:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -544,6 +544,66 @@ sl5trxt:
|
|||||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||||
bl_smar_stage: 5
|
bl_smar_stage: 5
|
||||||
|
|
||||||
|
sl5ch:
|
||||||
|
description: ESbox1 slit 5 center horizontal
|
||||||
|
deviceClass: ophyd_devices.devices.virtual_slit.VirtualSlitCenter
|
||||||
|
deviceConfig:
|
||||||
|
left_slit: sl5trxi
|
||||||
|
right_slit: sl5trxo
|
||||||
|
offset: 0
|
||||||
|
enabled: true
|
||||||
|
onFailure: retry
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
needs:
|
||||||
|
- sl5trxi
|
||||||
|
- sl5trxo
|
||||||
|
|
||||||
|
sl5wh:
|
||||||
|
description: ESbox1 slit 5 width horizontal
|
||||||
|
deviceClass: ophyd_devices.devices.virtual_slit.VirtualSlitWidth
|
||||||
|
deviceConfig:
|
||||||
|
left_slit: sl5trxi
|
||||||
|
right_slit: sl5trxo
|
||||||
|
offset: 0
|
||||||
|
enabled: true
|
||||||
|
onFailure: retry
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
needs:
|
||||||
|
- sl5trxi
|
||||||
|
- sl5trxo
|
||||||
|
|
||||||
|
sl5cv:
|
||||||
|
description: ESbox1 slit 5 center vertical
|
||||||
|
deviceClass: ophyd_devices.devices.virtual_slit.VirtualSlitCenter
|
||||||
|
deviceConfig:
|
||||||
|
left_slit: sl5trxb
|
||||||
|
right_slit: sl5trxt
|
||||||
|
offset: 0
|
||||||
|
enabled: true
|
||||||
|
onFailure: retry
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
needs:
|
||||||
|
- sl5trxb
|
||||||
|
- sl5trxt
|
||||||
|
|
||||||
|
sl5wv:
|
||||||
|
description: ESbox1 slit 5 width vertical
|
||||||
|
deviceClass: ophyd_devices.devices.virtual_slit.VirtualSlitWidth
|
||||||
|
deviceConfig:
|
||||||
|
left_slit: sl5trxb
|
||||||
|
right_slit: sl5trxt
|
||||||
|
offset: 0
|
||||||
|
enabled: true
|
||||||
|
onFailure: retry
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
needs:
|
||||||
|
- sl5trxb
|
||||||
|
- sl5trxt
|
||||||
|
|
||||||
xbimtrx:
|
xbimtrx:
|
||||||
description: ESbox2 beam intensity monitor x movement
|
description: ESbox2 beam intensity monitor x movement
|
||||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||||
@@ -558,7 +618,7 @@ xbimtrx:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -581,7 +641,7 @@ xbimtry:
|
|||||||
# precision: 3
|
# precision: 3
|
||||||
# tolerance: 0.005
|
# tolerance: 0.005
|
||||||
enabled: true
|
enabled: true
|
||||||
onFailure: buffer
|
onFailure: retry
|
||||||
readOnly: false
|
readOnly: false
|
||||||
readoutPriority: baseline
|
readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
@@ -589,3 +649,270 @@ xbimtry:
|
|||||||
init_position: 0
|
init_position: 0
|
||||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||||
bl_smar_stage: 1
|
bl_smar_stage: 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
################### XBOX related ###################
|
||||||
|
# we assue the epics settings for resolution, velocity etc. are correct
|
||||||
|
# we do not overwrite from here
|
||||||
|
|
||||||
|
aptrx:
|
||||||
|
description: Aperture pinhole X
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-PIN1:TRX1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
aptry:
|
||||||
|
description: Aperture pinhole Y
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-PIN1:TRY1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
ebtrx:
|
||||||
|
description: Exposure box aperture X
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-EB:TRX1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
ebtry:
|
||||||
|
description: Exposure box aperture Y
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-EB:TRY1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
ebtrz:
|
||||||
|
description: Exposure box aperture Z
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-EB:TRZ1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
ebsupport:
|
||||||
|
description: Exposure box granite support Y
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-EH1-EB:TRY1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
fttrx1:
|
||||||
|
description: FTS1 translation X
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-FTS1:TRX1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
fttry1:
|
||||||
|
description: FTS1 translation Y
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-FTS1:TRY1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
fttrx2:
|
||||||
|
description: FTS2 translation X
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-FTS2:TRX1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
fttry2:
|
||||||
|
description: FTS2 translation Y
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-FTS2:TRY1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
fttrz:
|
||||||
|
description: FTS1 translation Z
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-FTS1:TRZ1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
bs1x:
|
||||||
|
description: Beamstop 1 X
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-BS1:TRX1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
bs1y:
|
||||||
|
description: Beamstop 1 Y
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-BS1:TRY1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
bs2x:
|
||||||
|
description: Beamstop 2 X
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-BS2:TRX1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
bs2y:
|
||||||
|
description: Beamstop 2 Y
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-BS2:TRY1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
dttrx:
|
||||||
|
description: Detector table X
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-DETT:TRX1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
dttry:
|
||||||
|
description: Detector table Y
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-DETT:TRY1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
dttrz:
|
||||||
|
description: Detector table Z
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-DETT:TRZ1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
dettrx:
|
||||||
|
description: Detector 1 X
|
||||||
|
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||||
|
deviceConfig:
|
||||||
|
prefix: X12SA-ES1-DET1:TRX1
|
||||||
|
deviceTags:
|
||||||
|
- cSAXS_ES
|
||||||
|
onFailure: retry
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
### Beamstop control for flight tube
|
||||||
|
####################
|
||||||
|
|
||||||
|
beamstop_control:
|
||||||
|
description: Gain control for beamstop flightube
|
||||||
|
deviceClass: csaxs_bec.devices.pseudo_devices.bpm_control.BPMControl
|
||||||
|
deviceConfig:
|
||||||
|
gain_lsb: galilrioesft.digital_out.ch0 # Pin 10 -> Galil ch0
|
||||||
|
gain_mid: galilrioesft.digital_out.ch1 # Pin 11 -> Galil ch1
|
||||||
|
gain_msb: galilrioesft.digital_out.ch2 # Pin 12 -> Galil ch2
|
||||||
|
coupling: galilrioesft.digital_out.ch3 # Pin 13 -> Galil ch3
|
||||||
|
speed_mode: galilrioesft.digital_out.ch4 # Pin 14 -> Galil ch4
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
onFailure: retry
|
||||||
|
needs:
|
||||||
|
- galilrioesft
|
||||||
|
|
||||||
|
galilrioesft:
|
||||||
|
description: Galil RIO for remote gain switching and slow reading FlightTube
|
||||||
|
deviceClass: csaxs_bec.devices.omny.galil.galil_rio.GalilRIO
|
||||||
|
deviceConfig:
|
||||||
|
host: galilrioesft.psi.ch
|
||||||
|
enabled: true
|
||||||
|
onFailure: retry
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -199,6 +199,25 @@ xbpm1c4:
|
|||||||
readOnly: true
|
readOnly: true
|
||||||
softwareTrigger: false
|
softwareTrigger: false
|
||||||
|
|
||||||
|
bpm1:
|
||||||
|
description: 'XBPM1 (frontend)'
|
||||||
|
deviceClass: csaxs_bec.devices.pseudo_devices.bpm.BPM
|
||||||
|
deviceConfig:
|
||||||
|
left_top: xbpm1c1
|
||||||
|
right_top: xbpm1c2
|
||||||
|
right_bot: xbpm1c3
|
||||||
|
left_bot: xbpm1c4
|
||||||
|
onFailure: raise
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: monitored
|
||||||
|
readOnly: true
|
||||||
|
softwareTrigger: false
|
||||||
|
needs:
|
||||||
|
- xbpm1c1
|
||||||
|
- xbpm1c2
|
||||||
|
- xbpm1c3
|
||||||
|
- xbpm1c4
|
||||||
|
|
||||||
############################################
|
############################################
|
||||||
######### End of xbpm sub devices ##########
|
######### End of xbpm sub devices ##########
|
||||||
############################################
|
############################################
|
||||||
|
|||||||
@@ -68,91 +68,110 @@ ccmx:
|
|||||||
- cSAXS
|
- cSAXS
|
||||||
- optics
|
- optics
|
||||||
|
|
||||||
|
# TO BE REVIEWED, REMOVE VELOCITY WITH NEW CLASS!
|
||||||
|
ccm_energy:
|
||||||
|
description: 'test'
|
||||||
|
deviceClass: ophyd_devices.devices.simple_positioner.PSISimplePositioner
|
||||||
|
deviceConfig:
|
||||||
|
prefix: 'X12SA-OP-CCM1:'
|
||||||
|
override_suffixes:
|
||||||
|
user_readback: "ENERGY-GET"
|
||||||
|
user_setpoint: "ENERGY-SET"
|
||||||
|
velocity: "ROTY.VELO"
|
||||||
|
motor_done_move: "ROTY.DMOV"
|
||||||
|
onFailure: buffer
|
||||||
|
enabled: true
|
||||||
|
readoutPriority: baseline
|
||||||
|
readOnly: false
|
||||||
|
softwareTrigger: false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
######################## SMARACT STAGES ##################################
|
######################## SMARACT STAGES ##################################
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
xbpm2x:
|
# xbpm2x:
|
||||||
description: X-ray beam position monitor 1 in OPbox
|
# description: X-ray beam position monitor 1 in OPbox
|
||||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
# deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||||
deviceConfig:
|
# deviceConfig:
|
||||||
axis_Id: A
|
# axis_Id: A
|
||||||
host: x12sa-eb-smaract-mcs-03.psi.ch
|
# host: x12sa-eb-smaract-mcs-03.psi.ch
|
||||||
limits:
|
# limits:
|
||||||
- -200
|
# - -200
|
||||||
- 200
|
# - 200
|
||||||
port: 5000
|
# port: 5000
|
||||||
sign: 1
|
# sign: 1
|
||||||
enabled: true
|
# enabled: true
|
||||||
onFailure: buffer
|
# onFailure: buffer
|
||||||
readOnly: false
|
# readOnly: false
|
||||||
readoutPriority: baseline
|
# readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
# connectionTimeout: 20
|
||||||
userParameter:
|
# userParameter:
|
||||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
# # bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||||
bl_smar_stage: 0
|
# bl_smar_stage: 0
|
||||||
|
|
||||||
xbpm2y:
|
# xbpm2y:
|
||||||
description: X-ray beam position monitor 1 in OPbox
|
# description: X-ray beam position monitor 1 in OPbox
|
||||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
# deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||||
deviceConfig:
|
# deviceConfig:
|
||||||
axis_Id: B
|
# axis_Id: B
|
||||||
host: x12sa-eb-smaract-mcs-03.psi.ch
|
# host: x12sa-eb-smaract-mcs-03.psi.ch
|
||||||
limits:
|
# limits:
|
||||||
- -200
|
# - -200
|
||||||
- 200
|
# - 200
|
||||||
port: 5000
|
# port: 5000
|
||||||
sign: 1
|
# sign: 1
|
||||||
enabled: true
|
# enabled: true
|
||||||
onFailure: buffer
|
# onFailure: buffer
|
||||||
readOnly: false
|
# readOnly: false
|
||||||
readoutPriority: baseline
|
# readoutPriority: baseline
|
||||||
connectionTimeout: 20
|
# connectionTimeout: 20
|
||||||
userParameter:
|
# userParameter:
|
||||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
# # bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||||
bl_smar_stage: 1
|
# bl_smar_stage: 1
|
||||||
|
|
||||||
|
# cu_foilx:
|
||||||
|
# description: Cu foil in OPbox
|
||||||
|
# deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||||
|
# deviceConfig:
|
||||||
|
# axis_Id: C
|
||||||
|
# host: x12sa-eb-smaract-mcs-03.psi.ch
|
||||||
|
# limits:
|
||||||
|
# - -200
|
||||||
|
# - 200
|
||||||
|
# port: 5000
|
||||||
|
# sign: 1
|
||||||
|
# enabled: true
|
||||||
|
# onFailure: buffer
|
||||||
|
# readOnly: false
|
||||||
|
# readoutPriority: baseline
|
||||||
|
# connectionTimeout: 20
|
||||||
|
# userParameter:
|
||||||
|
# # bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||||
|
# bl_smar_stage: 2
|
||||||
|
|
||||||
|
# scinx:
|
||||||
|
# description: scintillator in OPbox
|
||||||
|
# deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||||
|
# deviceConfig:
|
||||||
|
# axis_Id: D
|
||||||
|
# host: x12sa-eb-smaract-mcs-03.psi.ch
|
||||||
|
# limits:
|
||||||
|
# - -200
|
||||||
|
# - 200
|
||||||
|
# port: 5000
|
||||||
|
# sign: 1
|
||||||
|
# enabled: true
|
||||||
|
# onFailure: buffer
|
||||||
|
# readOnly: false
|
||||||
|
# readoutPriority: baseline
|
||||||
|
# connectionTimeout: 20
|
||||||
|
# userParameter:
|
||||||
|
# # bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||||
|
# bl_smar_stage: 3
|
||||||
|
|
||||||
cu_foilx:
|
|
||||||
description: Cu foil in OPbox
|
|
||||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
|
||||||
deviceConfig:
|
|
||||||
axis_Id: C
|
|
||||||
host: x12sa-eb-smaract-mcs-03.psi.ch
|
|
||||||
limits:
|
|
||||||
- -200
|
|
||||||
- 200
|
|
||||||
port: 5000
|
|
||||||
sign: 1
|
|
||||||
enabled: true
|
|
||||||
onFailure: buffer
|
|
||||||
readOnly: false
|
|
||||||
readoutPriority: baseline
|
|
||||||
connectionTimeout: 20
|
|
||||||
userParameter:
|
|
||||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
|
||||||
bl_smar_stage: 2
|
|
||||||
|
|
||||||
scinx:
|
|
||||||
description: scintillator in OPbox
|
|
||||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
|
||||||
deviceConfig:
|
|
||||||
axis_Id: D
|
|
||||||
host: x12sa-eb-smaract-mcs-03.psi.ch
|
|
||||||
limits:
|
|
||||||
- -200
|
|
||||||
- 200
|
|
||||||
port: 5000
|
|
||||||
sign: 1
|
|
||||||
enabled: true
|
|
||||||
onFailure: buffer
|
|
||||||
readOnly: false
|
|
||||||
readoutPriority: baseline
|
|
||||||
connectionTimeout: 20
|
|
||||||
userParameter:
|
|
||||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
|
||||||
bl_smar_stage: 3
|
|
||||||
|
|
||||||
|
|
||||||
# dmm1_trx_readback_example: # This is the same template as for i.e. bpm4i
|
# dmm1_trx_readback_example: # This is the same template as for i.e. bpm4i
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
endstation:
|
endstation:
|
||||||
- !include ./bl_endstation.yaml
|
- !include ./bl_endstation.yaml
|
||||||
|
|
||||||
detectors:
|
# detectors:
|
||||||
- !include ./bl_detectors.yaml
|
# - !include ./bl_detectors.yaml
|
||||||
|
|
||||||
#sastt:
|
#sastt:
|
||||||
# - !include ./sastt.yaml
|
# - !include ./sastt.yaml
|
||||||
|
|||||||
@@ -395,6 +395,16 @@ rtz:
|
|||||||
readoutPriority: on_request
|
readoutPriority: on_request
|
||||||
connectionTimeout: 20
|
connectionTimeout: 20
|
||||||
|
|
||||||
|
rt_positions:
|
||||||
|
deviceClass: csaxs_bec.devices.omny.rt.rt_flomni_ophyd.RtFlomniFlyer
|
||||||
|
deviceConfig:
|
||||||
|
host: mpc2844.psi.ch
|
||||||
|
port: 2222
|
||||||
|
readoutPriority: async
|
||||||
|
connectionTimeout: 20
|
||||||
|
enabled: true
|
||||||
|
readOnly: False
|
||||||
|
|
||||||
############################################################
|
############################################################
|
||||||
####################### Cameras ############################
|
####################### Cameras ############################
|
||||||
############################################################
|
############################################################
|
||||||
@@ -512,6 +522,18 @@ omny_panda:
|
|||||||
FMC_IN.VAL2.Min: cap_voltage_fzp_x_min
|
FMC_IN.VAL2.Min: cap_voltage_fzp_x_min
|
||||||
FMC_IN.VAL2.Max: cap_voltage_fzp_x_max
|
FMC_IN.VAL2.Max: cap_voltage_fzp_x_max
|
||||||
FMC_IN.VAL2.Mean: cap_voltage_fzp_x_mean
|
FMC_IN.VAL2.Mean: cap_voltage_fzp_x_mean
|
||||||
|
INENC1.VAL.Max: interf_st_fzp_y_max
|
||||||
|
INENC1.VAL.Mean: interf_st_fzp_y_mean
|
||||||
|
INENC1.VAL.Min: interf_st_fzp_y_min
|
||||||
|
INENC2.VAL.Max: interf_st_fzp_x_max
|
||||||
|
INENC2.VAL.Mean: interf_st_fzp_x_mean
|
||||||
|
INENC2.VAL.Min: interf_st_fzp_x_min
|
||||||
|
INENC3.VAL.Max: interf_st_rotz_max
|
||||||
|
INENC3.VAL.Mean: interf_st_rotz_mean
|
||||||
|
INENC3.VAL.Min: interf_st_rotz_min
|
||||||
|
INENC4.VAL.Max: interf_st_rotx_max
|
||||||
|
INENC4.VAL.Mean: interf_st_rotx_mean
|
||||||
|
INENC4.VAL.Min: interf_st_rotx_min
|
||||||
deviceTags:
|
deviceTags:
|
||||||
- detector
|
- detector
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
24
csaxs_bec/device_configs/test_config.yaml
Normal file
24
csaxs_bec/device_configs/test_config.yaml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
galilrioesxbox:
|
||||||
|
description: Galil RIO for remote gain switching and slow reading ES XBox
|
||||||
|
deviceClass: csaxs_bec.devices.omny.galil.galil_rio.GalilRIO
|
||||||
|
deviceConfig:
|
||||||
|
host: galilrioesft.psi.ch
|
||||||
|
enabled: true
|
||||||
|
onFailure: raise
|
||||||
|
readOnly: false
|
||||||
|
readoutPriority: baseline
|
||||||
|
connectionTimeout: 20
|
||||||
|
bpm1:
|
||||||
|
readoutPriority: baseline
|
||||||
|
deviceClass: csaxs_bec.devices.pseudo_devices.bpm.BPM
|
||||||
|
deviceConfig:
|
||||||
|
blade_t: galilrioesxbox.analog_in.ch0
|
||||||
|
blade_r: galilrioesxbox.analog_in.ch1
|
||||||
|
blade_b: galilrioesxbox.analog_in.ch2
|
||||||
|
blade_l: galilrioesxbox.analog_in.ch3
|
||||||
|
enabled: true
|
||||||
|
readOnly: false
|
||||||
|
softwareTrigger: true
|
||||||
|
needs:
|
||||||
|
- galilrioesxbox
|
||||||
|
|
||||||
@@ -317,8 +317,6 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
|||||||
try:
|
try:
|
||||||
scan_done = bool(value == self._num_total_triggers)
|
scan_done = bool(value == self._num_total_triggers)
|
||||||
self.progress.put(value=value, max_value=self._num_total_triggers, done=scan_done)
|
self.progress.put(value=value, max_value=self._num_total_triggers, done=scan_done)
|
||||||
if scan_done:
|
|
||||||
self._scan_done_event.set()
|
|
||||||
except Exception:
|
except Exception:
|
||||||
content = traceback.format_exc()
|
content = traceback.format_exc()
|
||||||
logger.info(f"Device {self.name} error: {content}")
|
logger.info(f"Device {self.name} error: {content}")
|
||||||
@@ -393,6 +391,7 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
|||||||
self._current_data_index = 0
|
self._current_data_index = 0
|
||||||
|
|
||||||
# NOTE Make sure that the signal that omits mca callbacks is cleared
|
# NOTE Make sure that the signal that omits mca callbacks is cleared
|
||||||
|
# DO NOT REMOVE!!
|
||||||
self._omit_mca_callbacks.clear()
|
self._omit_mca_callbacks.clear()
|
||||||
|
|
||||||
# For a fly scan we need to start the mcs card ourselves
|
# For a fly scan we need to start the mcs card ourselves
|
||||||
@@ -563,8 +562,9 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
|||||||
|
|
||||||
def on_stop(self) -> None:
|
def on_stop(self) -> None:
|
||||||
"""Hook called when the device is stopped. In addition, any status that is registered through cancel_on_stop will be cancelled here."""
|
"""Hook called when the device is stopped. In addition, any status that is registered through cancel_on_stop will be cancelled here."""
|
||||||
self.stop_all.put(1)
|
with suppress_mca_callbacks(self):
|
||||||
self.erase_all.put(1)
|
self.stop_all.put(1)
|
||||||
|
self.erase_all.put(1)
|
||||||
|
|
||||||
def mcs_recovery(self, timeout: int = 1) -> None:
|
def mcs_recovery(self, timeout: int = 1) -> None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import List
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from bec_lib import bec_logger, messages
|
from bec_lib import bec_logger
|
||||||
from bec_lib.endpoints import MessageEndpoints
|
|
||||||
from ophyd import Component as Cpt
|
from ophyd import Component as Cpt
|
||||||
from ophyd import Device, PositionerBase, Signal
|
from ophyd import Device, PositionerBase, Signal
|
||||||
from ophyd.status import wait as status_wait
|
from ophyd.status import wait as status_wait
|
||||||
from ophyd.utils import LimitError
|
from ophyd.utils import LimitError
|
||||||
|
from ophyd_devices import AsyncMultiSignal, DeviceStatus, ProgressSignal
|
||||||
from ophyd_devices.utils.controller import Controller, threadlocked
|
from ophyd_devices.utils.controller import Controller, threadlocked
|
||||||
from ophyd_devices.utils.socket import SocketIO, raise_if_disconnected
|
from ophyd_devices.utils.socket import SocketIO, raise_if_disconnected
|
||||||
from prettytable import PrettyTable
|
from prettytable import PrettyTable
|
||||||
|
|
||||||
from csaxs_bec.devices.omny.rt.rt_ophyd import (
|
from csaxs_bec.devices.omny.rt.rt_ophyd import (
|
||||||
BECConfigError,
|
|
||||||
RtCommunicationError,
|
RtCommunicationError,
|
||||||
RtError,
|
RtError,
|
||||||
RtReadbackSignal,
|
RtReadbackSignal,
|
||||||
@@ -432,27 +430,6 @@ class RtFlomniController(Controller):
|
|||||||
t.add_row([i, self.read_ssi_interferometer(i)])
|
t.add_row([i, self.read_ssi_interferometer(i)])
|
||||||
print(t)
|
print(t)
|
||||||
|
|
||||||
def _get_signals_from_table(self, return_table) -> dict:
|
|
||||||
self.average_stdeviations_x_st_fzp += float(return_table[4])
|
|
||||||
self.average_stdeviations_y_st_fzp += float(return_table[7])
|
|
||||||
signals = {
|
|
||||||
"target_x": {"value": float(return_table[2])},
|
|
||||||
"average_x_st_fzp": {"value": float(return_table[3])},
|
|
||||||
"stdev_x_st_fzp": {"value": float(return_table[4])},
|
|
||||||
"target_y": {"value": float(return_table[5])},
|
|
||||||
"average_y_st_fzp": {"value": float(return_table[6])},
|
|
||||||
"stdev_y_st_fzp": {"value": float(return_table[7])},
|
|
||||||
"average_rotz": {"value": float(return_table[8])},
|
|
||||||
"stdev_rotz": {"value": float(return_table[9])},
|
|
||||||
"average_stdeviations_x_st_fzp": {
|
|
||||||
"value": self.average_stdeviations_x_st_fzp / (int(return_table[0]) + 1)
|
|
||||||
},
|
|
||||||
"average_stdeviations_y_st_fzp": {
|
|
||||||
"value": self.average_stdeviations_y_st_fzp / (int(return_table[0]) + 1)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return signals
|
|
||||||
|
|
||||||
@threadlocked
|
@threadlocked
|
||||||
def start_scan(self):
|
def start_scan(self):
|
||||||
if not self.feedback_is_running():
|
if not self.feedback_is_running():
|
||||||
@@ -492,91 +469,6 @@ class RtFlomniController(Controller):
|
|||||||
current_position_in_scan = int(float(return_table[2]))
|
current_position_in_scan = int(float(return_table[2]))
|
||||||
return (mode, number_of_positions_planned, current_position_in_scan)
|
return (mode, number_of_positions_planned, current_position_in_scan)
|
||||||
|
|
||||||
def read_positions_from_sampler(self):
|
|
||||||
# this was for reading after the scan completed
|
|
||||||
number_of_samples_to_read = 1 # self.get_scan_status()[1] #number of valid samples, will be updated upon first data read
|
|
||||||
|
|
||||||
read_counter = 0
|
|
||||||
|
|
||||||
self.average_stdeviations_x_st_fzp = 0
|
|
||||||
self.average_stdeviations_y_st_fzp = 0
|
|
||||||
self.average_lamni_angle = 0
|
|
||||||
|
|
||||||
mode, number_of_positions_planned, current_position_in_scan = self.get_scan_status()
|
|
||||||
|
|
||||||
# if not (mode==2 or mode==3):
|
|
||||||
# error
|
|
||||||
self.device_manager.connector.set(
|
|
||||||
MessageEndpoints.device_status("rt_scan"),
|
|
||||||
messages.DeviceStatusMessage(
|
|
||||||
device="rt_scan", status=1, metadata=self.readout_metadata
|
|
||||||
),
|
|
||||||
)
|
|
||||||
# while scan is running
|
|
||||||
while mode > 0:
|
|
||||||
|
|
||||||
# TODO here?: scan abortion if no progress in scan *raise error
|
|
||||||
|
|
||||||
# logger.info(f"Current scan position {current_position_in_scan} out of {number_of_positions_planned}")
|
|
||||||
mode, number_of_positions_planned, current_position_in_scan = self.get_scan_status()
|
|
||||||
time.sleep(0.01)
|
|
||||||
if current_position_in_scan > 5:
|
|
||||||
while current_position_in_scan > read_counter + 1:
|
|
||||||
return_table = (self.socket_put_and_receive(f"r{read_counter}")).split(",")
|
|
||||||
# logger.info(f"{return_table}")
|
|
||||||
logger.info(f"Read {read_counter} out of {number_of_positions_planned}")
|
|
||||||
|
|
||||||
read_counter = read_counter + 1
|
|
||||||
|
|
||||||
signals = self._get_signals_from_table(return_table)
|
|
||||||
|
|
||||||
self.publish_device_data(signals=signals, point_id=int(return_table[0]))
|
|
||||||
|
|
||||||
time.sleep(0.05)
|
|
||||||
|
|
||||||
# read the last samples even though scan is finished already
|
|
||||||
while number_of_positions_planned > read_counter:
|
|
||||||
return_table = (self.socket_put_and_receive(f"r{read_counter}")).split(",")
|
|
||||||
logger.info(f"Read {read_counter} out of {number_of_positions_planned}")
|
|
||||||
# logger.info(f"{return_table}")
|
|
||||||
read_counter = read_counter + 1
|
|
||||||
|
|
||||||
signals = self._get_signals_from_table(return_table)
|
|
||||||
self.publish_device_data(signals=signals, point_id=int(return_table[0]))
|
|
||||||
|
|
||||||
self.device_manager.connector.set(
|
|
||||||
MessageEndpoints.device_status("rt_scan"),
|
|
||||||
messages.DeviceStatusMessage(
|
|
||||||
device="rt_scan", status=0, metadata=self.readout_metadata
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
"Flomni statistics: Average of all standard deviations: x"
|
|
||||||
f" {self.average_stdeviations_x_st_fzp/read_counter*1000:.1f}, y"
|
|
||||||
f" {self.average_stdeviations_y_st_fzp/read_counter*1000:.1f}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def publish_device_data(self, signals, point_id):
|
|
||||||
self.device_manager.connector.set_and_publish(
|
|
||||||
MessageEndpoints.device_read("rt_flomni"),
|
|
||||||
messages.DeviceMessage(
|
|
||||||
signals=signals, metadata={"point_id": point_id, **self.readout_metadata}
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
def start_readout(self):
|
|
||||||
readout = threading.Thread(target=self.read_positions_from_sampler)
|
|
||||||
readout.start()
|
|
||||||
|
|
||||||
def kickoff(self, metadata):
|
|
||||||
self.readout_metadata = metadata
|
|
||||||
while not self._min_scan_buffer_reached:
|
|
||||||
time.sleep(0.001)
|
|
||||||
self.start_scan()
|
|
||||||
time.sleep(0.1)
|
|
||||||
self.start_readout()
|
|
||||||
|
|
||||||
|
|
||||||
class RtFlomniReadbackSignal(RtReadbackSignal):
|
class RtFlomniReadbackSignal(RtReadbackSignal):
|
||||||
@retry_once
|
@retry_once
|
||||||
@@ -844,6 +736,185 @@ class RtFlomniMotor(Device, PositionerBase):
|
|||||||
return super().stop(success=success)
|
return super().stop(success=success)
|
||||||
|
|
||||||
|
|
||||||
|
class RtFlomniFlyer(Device):
|
||||||
|
USER_ACCESS = ["controller"]
|
||||||
|
data = Cpt(
|
||||||
|
AsyncMultiSignal,
|
||||||
|
name="data",
|
||||||
|
signals=[
|
||||||
|
"target_x",
|
||||||
|
"average_x_st_fzp",
|
||||||
|
"stdev_x_st_fzp",
|
||||||
|
"target_y",
|
||||||
|
"average_y_st_fzp",
|
||||||
|
"stdev_y_st_fzp",
|
||||||
|
"average_rotz",
|
||||||
|
"stdev_rotz",
|
||||||
|
"average_stdeviations_x_st_fzp",
|
||||||
|
"average_stdeviations_y_st_fzp",
|
||||||
|
],
|
||||||
|
ndim=1,
|
||||||
|
async_update={"type": "add", "max_shape": [None]},
|
||||||
|
max_size=1000,
|
||||||
|
)
|
||||||
|
progress = Cpt(
|
||||||
|
ProgressSignal, doc="ProgressSignal indicating the progress of the device during a scan."
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
prefix="",
|
||||||
|
*,
|
||||||
|
name,
|
||||||
|
kind=None,
|
||||||
|
read_attrs=None,
|
||||||
|
configuration_attrs=None,
|
||||||
|
parent=None,
|
||||||
|
host="mpc2844.psi.ch",
|
||||||
|
port=2222,
|
||||||
|
socket_cls=SocketIO,
|
||||||
|
device_manager=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(prefix=prefix, name=name, parent=parent, **kwargs)
|
||||||
|
self.shutdown_event = threading.Event()
|
||||||
|
self.controller = RtFlomniController(
|
||||||
|
socket_cls=socket_cls, socket_host=host, socket_port=port, device_manager=device_manager
|
||||||
|
)
|
||||||
|
self.average_stdeviations_x_st_fzp = 0
|
||||||
|
self.average_stdeviations_y_st_fzp = 0
|
||||||
|
self.average_lamni_angle = 0
|
||||||
|
self.readout_thread = None
|
||||||
|
self.scan_done_event = threading.Event()
|
||||||
|
self.scan_done_event.set()
|
||||||
|
|
||||||
|
def read_positions_from_sampler(self, status: DeviceStatus):
|
||||||
|
"""
|
||||||
|
Read the positions from the sampler and update the data signal.
|
||||||
|
This function runs in a separate thread and continuously checks the
|
||||||
|
scan status.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
status (DeviceStatus): The status object to update when the readout is complete.
|
||||||
|
"""
|
||||||
|
read_counter = 0
|
||||||
|
self.average_stdeviations_x_st_fzp = 0
|
||||||
|
self.average_stdeviations_y_st_fzp = 0
|
||||||
|
self.average_lamni_angle = 0
|
||||||
|
|
||||||
|
mode, number_of_positions_planned, current_position_in_scan = (
|
||||||
|
self.controller.get_scan_status()
|
||||||
|
)
|
||||||
|
|
||||||
|
# while scan is running
|
||||||
|
while mode > 0 and not self.shutdown_event.wait(0.01):
|
||||||
|
# logger.info(f"Current scan position {current_position_in_scan} out of {number_of_positions_planned}")
|
||||||
|
mode, number_of_positions_planned, current_position_in_scan = (
|
||||||
|
self.controller.get_scan_status()
|
||||||
|
)
|
||||||
|
if current_position_in_scan > 5:
|
||||||
|
while current_position_in_scan > read_counter + 1:
|
||||||
|
return_table = (
|
||||||
|
self.controller.socket_put_and_receive(f"r{read_counter}")
|
||||||
|
).split(",")
|
||||||
|
logger.info(f"Read {read_counter} out of {number_of_positions_planned}")
|
||||||
|
self.progress.put(
|
||||||
|
value=read_counter, max_value=number_of_positions_planned, done=False
|
||||||
|
)
|
||||||
|
read_counter = read_counter + 1
|
||||||
|
signals = self._get_signals_from_table(return_table)
|
||||||
|
self.data.set(signals)
|
||||||
|
|
||||||
|
if self.shutdown_event.wait(0.05):
|
||||||
|
logger.info("Shutdown event set, stopping readout.")
|
||||||
|
# if we are here, the shutdown_event is set. We can exit the readout loop.
|
||||||
|
status.set_finished()
|
||||||
|
return
|
||||||
|
|
||||||
|
# read the last samples even though scan is finished already
|
||||||
|
while number_of_positions_planned > read_counter and not self.shutdown_event.is_set():
|
||||||
|
return_table = (self.controller.socket_put_and_receive(f"r{read_counter}")).split(",")
|
||||||
|
logger.info(f"Read {read_counter} out of {number_of_positions_planned}")
|
||||||
|
self.progress.put(value=read_counter, max_value=number_of_positions_planned, done=False)
|
||||||
|
read_counter = read_counter + 1
|
||||||
|
|
||||||
|
signals = self._get_signals_from_table(return_table)
|
||||||
|
self.data.set(signals)
|
||||||
|
|
||||||
|
# NOTE: No need to set the status to failed if the shutdown_event is set.
|
||||||
|
# The stop() method will take care of that.
|
||||||
|
status.set_finished()
|
||||||
|
self.progress.put(value=read_counter, max_value=number_of_positions_planned, done=True)
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
"Flomni statistics: Average of all standard deviations: x"
|
||||||
|
f" {self.average_stdeviations_x_st_fzp/read_counter*1000:.1f}, y"
|
||||||
|
f" {self.average_stdeviations_y_st_fzp/read_counter*1000:.1f}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_signals_from_table(self, return_table) -> dict:
|
||||||
|
self.average_stdeviations_x_st_fzp += float(return_table[4])
|
||||||
|
self.average_stdeviations_y_st_fzp += float(return_table[7])
|
||||||
|
signals = {
|
||||||
|
"target_x": {"value": float(return_table[2])},
|
||||||
|
"average_x_st_fzp": {"value": float(return_table[3])},
|
||||||
|
"stdev_x_st_fzp": {"value": float(return_table[4])},
|
||||||
|
"target_y": {"value": float(return_table[5])},
|
||||||
|
"average_y_st_fzp": {"value": float(return_table[6])},
|
||||||
|
"stdev_y_st_fzp": {"value": float(return_table[7])},
|
||||||
|
"average_rotz": {"value": float(return_table[8])},
|
||||||
|
"stdev_rotz": {"value": float(return_table[9])},
|
||||||
|
"average_stdeviations_x_st_fzp": {
|
||||||
|
"value": self.average_stdeviations_x_st_fzp / (int(return_table[0]) + 1)
|
||||||
|
},
|
||||||
|
"average_stdeviations_y_st_fzp": {
|
||||||
|
"value": self.average_stdeviations_y_st_fzp / (int(return_table[0]) + 1)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return signals
|
||||||
|
|
||||||
|
def stage(self):
|
||||||
|
self.shutdown_event.clear()
|
||||||
|
self.scan_done_event.set()
|
||||||
|
return super().stage()
|
||||||
|
|
||||||
|
def start_readout(self, status: DeviceStatus):
|
||||||
|
self.readout_thread = threading.Thread(
|
||||||
|
target=self.read_positions_from_sampler, args=(status,)
|
||||||
|
)
|
||||||
|
self.readout_thread.start()
|
||||||
|
|
||||||
|
def kickoff(self) -> DeviceStatus:
|
||||||
|
self.shutdown_event.clear()
|
||||||
|
self.scan_done_event.clear()
|
||||||
|
while not self.controller._min_scan_buffer_reached and not self.shutdown_event.wait(0.001):
|
||||||
|
...
|
||||||
|
self.controller.start_scan()
|
||||||
|
self.shutdown_event.wait(0.1)
|
||||||
|
status = DeviceStatus(self)
|
||||||
|
status.set_finished()
|
||||||
|
return status
|
||||||
|
|
||||||
|
def complete(self) -> DeviceStatus:
|
||||||
|
"""Wait until the flyer is done."""
|
||||||
|
if self.scan_done_event.is_set():
|
||||||
|
# if the scan_done_event is already set, we can return a finished status immediately
|
||||||
|
status = DeviceStatus(self)
|
||||||
|
status.set_finished()
|
||||||
|
return status
|
||||||
|
status = DeviceStatus(self)
|
||||||
|
self.start_readout(status)
|
||||||
|
status.add_callback(lambda *args, **kwargs: self.scan_done_event.set())
|
||||||
|
return status
|
||||||
|
|
||||||
|
def stop(self, *, success=False):
|
||||||
|
self.shutdown_event.set()
|
||||||
|
self.scan_done_event.set()
|
||||||
|
if self.readout_thread is not None:
|
||||||
|
self.readout_thread.join()
|
||||||
|
return super().stop(success=success)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
rtcontroller = RtFlomniController(
|
rtcontroller = RtFlomniController(
|
||||||
socket_cls=SocketIO, socket_host="mpc2844.psi.ch", socket_port=2222, device_manager=None
|
socket_cls=SocketIO, socket_host="mpc2844.psi.ch", socket_port=2222, device_manager=None
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ class OMNYFastShutter(PSIDeviceBase, Device):
|
|||||||
def fshopen(self):
|
def fshopen(self):
|
||||||
"""Open the fast shutter."""
|
"""Open the fast shutter."""
|
||||||
if self._check_if_cSAXS_shutter_exists_in_config():
|
if self._check_if_cSAXS_shutter_exists_in_config():
|
||||||
self.shutter.put(1)
|
|
||||||
return self.device_manager.devices["fsh"].fshopen()
|
return self.device_manager.devices["fsh"].fshopen()
|
||||||
else:
|
else:
|
||||||
self.shutter.put(1)
|
self.shutter.put(1)
|
||||||
@@ -56,7 +55,6 @@ class OMNYFastShutter(PSIDeviceBase, Device):
|
|||||||
def fshclose(self):
|
def fshclose(self):
|
||||||
"""Close the fast shutter."""
|
"""Close the fast shutter."""
|
||||||
if self._check_if_cSAXS_shutter_exists_in_config():
|
if self._check_if_cSAXS_shutter_exists_in_config():
|
||||||
self.shutter.put(0)
|
|
||||||
return self.device_manager.devices["fsh"].fshclose()
|
return self.device_manager.devices["fsh"].fshclose()
|
||||||
else:
|
else:
|
||||||
self.shutter.put(0)
|
self.shutter.put(0)
|
||||||
|
|||||||
0
csaxs_bec/devices/pseudo_devices/__init__.py
Normal file
0
csaxs_bec/devices/pseudo_devices/__init__.py
Normal file
172
csaxs_bec/devices/pseudo_devices/bpm.py
Normal file
172
csaxs_bec/devices/pseudo_devices/bpm.py
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
"""Module for a BPM pseudo device that computes the position and intensity from the blade signals."""
|
||||||
|
|
||||||
|
from ophyd import Component as Cpt
|
||||||
|
from ophyd import Kind, Signal
|
||||||
|
from ophyd_devices.interfaces.base_classes.psi_pseudo_device_base import PSIPseudoDeviceBase
|
||||||
|
from ophyd_devices.utils.bec_processed_signal import BECProcessedSignal
|
||||||
|
|
||||||
|
|
||||||
|
class BPM(PSIPseudoDeviceBase):
|
||||||
|
"""BPM positioner pseudo device."""
|
||||||
|
|
||||||
|
# Blade signals, a,b,c,d
|
||||||
|
left_top = Cpt(
|
||||||
|
BECProcessedSignal,
|
||||||
|
name="left_top",
|
||||||
|
model_config=None,
|
||||||
|
kind=Kind.config,
|
||||||
|
doc="BPM left_top blade",
|
||||||
|
)
|
||||||
|
right_top = Cpt(
|
||||||
|
BECProcessedSignal,
|
||||||
|
name="right_top",
|
||||||
|
model_config=None,
|
||||||
|
kind=Kind.config,
|
||||||
|
doc="BPM right_top blade",
|
||||||
|
)
|
||||||
|
right_bot = Cpt(
|
||||||
|
BECProcessedSignal,
|
||||||
|
name="right_bot",
|
||||||
|
model_config=None,
|
||||||
|
kind=Kind.config,
|
||||||
|
doc="BPM right_bottom blade",
|
||||||
|
)
|
||||||
|
left_bot = Cpt(
|
||||||
|
BECProcessedSignal,
|
||||||
|
name="left_bot",
|
||||||
|
model_config=None,
|
||||||
|
kind=Kind.config,
|
||||||
|
doc="BPM left_bot blade",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Virtual signals
|
||||||
|
pos_x = Cpt(
|
||||||
|
BECProcessedSignal,
|
||||||
|
name="pos_x",
|
||||||
|
model_config=None,
|
||||||
|
kind=Kind.config,
|
||||||
|
doc="BPM X position, -1 fully left, 1 fully right",
|
||||||
|
)
|
||||||
|
pos_y = Cpt(
|
||||||
|
BECProcessedSignal,
|
||||||
|
name="pos_y",
|
||||||
|
model_config=None,
|
||||||
|
kind=Kind.config,
|
||||||
|
doc="BPM Y position, -1 fully bottom, 1 fully top",
|
||||||
|
)
|
||||||
|
diagonal = Cpt(
|
||||||
|
BECProcessedSignal,
|
||||||
|
name="diagonal",
|
||||||
|
model_config=None,
|
||||||
|
kind=Kind.config,
|
||||||
|
doc="BPM diagonal, -1 fully diagonal left_top-right_bot, 1 fully diagonal right_top-left_bot",
|
||||||
|
)
|
||||||
|
intensity = Cpt(
|
||||||
|
BECProcessedSignal,
|
||||||
|
name="intensity",
|
||||||
|
model_config=None,
|
||||||
|
kind=Kind.config,
|
||||||
|
doc="BPM intensity",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name,
|
||||||
|
left_top: str,
|
||||||
|
right_top: str,
|
||||||
|
right_bot: str,
|
||||||
|
left_bot: str,
|
||||||
|
device_manager=None,
|
||||||
|
scan_info=None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(name=name, device_manager=device_manager, scan_info=scan_info, **kwargs)
|
||||||
|
# Get all blade signal objects from utility method
|
||||||
|
signal_t = self.left_top.get_device_object_from_bec(
|
||||||
|
object_name=left_top, signal_name=self.name, device_manager=device_manager
|
||||||
|
)
|
||||||
|
signal_r = self.right_top.get_device_object_from_bec(
|
||||||
|
object_name=right_top, signal_name=self.name, device_manager=device_manager
|
||||||
|
)
|
||||||
|
signal_b = self.right_bot.get_device_object_from_bec(
|
||||||
|
object_name=right_bot, signal_name=self.name, device_manager=device_manager
|
||||||
|
)
|
||||||
|
signal_l = self.left_bot.get_device_object_from_bec(
|
||||||
|
object_name=left_bot, signal_name=self.name, device_manager=device_manager
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set compute methods for blade signals and virtual signals
|
||||||
|
self.left_top.set_compute_method(self._compute_blade_signal, signal=signal_t)
|
||||||
|
self.right_top.set_compute_method(self._compute_blade_signal, signal=signal_r)
|
||||||
|
self.right_bot.set_compute_method(self._compute_blade_signal, signal=signal_b)
|
||||||
|
self.left_bot.set_compute_method(self._compute_blade_signal, signal=signal_l)
|
||||||
|
|
||||||
|
self.intensity.set_compute_method(
|
||||||
|
self._compute_intensity,
|
||||||
|
left_top=self.left_top,
|
||||||
|
right_top=self.right_top,
|
||||||
|
right_bot=self.right_bot,
|
||||||
|
left_bot=self.left_bot,
|
||||||
|
)
|
||||||
|
self.pos_x.set_compute_method(
|
||||||
|
self._compute_pos_x,
|
||||||
|
left_bot=self.left_bot,
|
||||||
|
left_top=self.left_top,
|
||||||
|
right_top=self.right_top,
|
||||||
|
right_bot=self.right_bot,
|
||||||
|
)
|
||||||
|
self.pos_y.set_compute_method(
|
||||||
|
self._compute_pos_y,
|
||||||
|
left_bot=self.left_bot,
|
||||||
|
left_top=self.left_top,
|
||||||
|
right_top=self.right_top,
|
||||||
|
right_bot=self.right_bot,
|
||||||
|
)
|
||||||
|
self.diagonal.set_compute_method(
|
||||||
|
self._compute_diagonal,
|
||||||
|
left_bot=self.left_bot,
|
||||||
|
left_top=self.left_top,
|
||||||
|
right_top=self.right_top,
|
||||||
|
right_bot=self.right_bot,
|
||||||
|
)
|
||||||
|
|
||||||
|
def _compute_blade_signal(self, signal: Signal) -> float:
|
||||||
|
return signal.get()
|
||||||
|
|
||||||
|
def _compute_intensity(
|
||||||
|
self, left_top: Signal, right_top: Signal, right_bot: Signal, left_bot: Signal
|
||||||
|
) -> float:
|
||||||
|
intensity = left_top.get() + right_top.get() + right_bot.get() + left_bot.get()
|
||||||
|
return intensity
|
||||||
|
|
||||||
|
def _compute_pos_x(
|
||||||
|
self, left_bot: Signal, left_top: Signal, right_top: Signal, right_bot: Signal
|
||||||
|
) -> float:
|
||||||
|
"""X position from -1 to 1, where -1 means beam fully on the left side, 1 means beam fully on the right side."""
|
||||||
|
sum_left = left_bot.get() + left_top.get()
|
||||||
|
sum_right = right_top.get() + right_bot.get()
|
||||||
|
sum_total = sum_left + sum_right
|
||||||
|
if sum_total == 0:
|
||||||
|
return 0.0
|
||||||
|
return (sum_right - sum_left) / sum_total
|
||||||
|
|
||||||
|
def _compute_pos_y(
|
||||||
|
self, left_bot: Signal, left_top: Signal, right_top: Signal, right_bot: Signal
|
||||||
|
) -> float:
|
||||||
|
"""Y position from -1 to 1, where -1 means beam fully on the bottom side, 1 means beam fully on the top side."""
|
||||||
|
sum_top = left_top.get() + right_top.get()
|
||||||
|
sum_bot = right_bot.get() + left_bot.get()
|
||||||
|
sum_total = sum_top + sum_bot
|
||||||
|
if sum_total == 0:
|
||||||
|
return 0.0
|
||||||
|
return (sum_top - sum_bot) / sum_total
|
||||||
|
|
||||||
|
def _compute_diagonal(
|
||||||
|
self, left_bot: Signal, left_top: Signal, right_top: Signal, right_bot: Signal
|
||||||
|
) -> float:
|
||||||
|
sum_diag1 = left_bot.get() + right_top.get()
|
||||||
|
sum_diag2 = left_top.get() + right_bot.get()
|
||||||
|
sum_total = sum_diag1 + sum_diag2
|
||||||
|
if sum_total == 0:
|
||||||
|
return 0.0
|
||||||
|
return (sum_diag1 - sum_diag2) / sum_total
|
||||||
189
csaxs_bec/devices/pseudo_devices/bpm_control.py
Normal file
189
csaxs_bec/devices/pseudo_devices/bpm_control.py
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
"""
|
||||||
|
Module for controlling the BPM amplifier settings, such as gain and coupling.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Literal
|
||||||
|
|
||||||
|
from ophyd import Component as Cpt
|
||||||
|
from ophyd import Kind
|
||||||
|
from ophyd_devices.interfaces.base_classes.psi_pseudo_device_base import PSIPseudoDeviceBase
|
||||||
|
from ophyd_devices.utils.bec_processed_signal import BECProcessedSignal
|
||||||
|
|
||||||
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
|
from bec_lib.devicemanager import ScanInfo
|
||||||
|
from bec_server.device_server.devices.devicemanager import DeviceManagerDS
|
||||||
|
from ophyd import Signal
|
||||||
|
|
||||||
|
_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),
|
||||||
|
}
|
||||||
|
|
||||||
|
_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 BPMControl(PSIPseudoDeviceBase):
|
||||||
|
"""
|
||||||
|
BPM amplifier control pseudo device. It is responsible for controlling the
|
||||||
|
gain and coupling for the BPM amplifier. It relies on signals from a device
|
||||||
|
in BEC to be available. For cSAXS, these are most liikely to be from the
|
||||||
|
GalilRIO device that controls the BPM amplifier.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): Name of the pseudo device.
|
||||||
|
gain_lsb (str): Name of the signal in BEC that controls the LSB
|
||||||
|
of the gain setting.
|
||||||
|
gain_mid (str): Name of the signal in BEC that controls the MID
|
||||||
|
bit of the gain setting.
|
||||||
|
gain_msb (str): Name of the signal in BEC that controls the MSB
|
||||||
|
of the gain setting.
|
||||||
|
coupling (str): Name of the signal in BEC that controls the coupling
|
||||||
|
setting.
|
||||||
|
speed_mode (str): Name of the signal in BEC that controls the speed mode
|
||||||
|
(low-noise vs high-speed) of the amplifier.
|
||||||
|
"""
|
||||||
|
|
||||||
|
USER_ACCESS = ["set_gain", "set_coupling"]
|
||||||
|
|
||||||
|
gain = Cpt(
|
||||||
|
BECProcessedSignal,
|
||||||
|
name="gain",
|
||||||
|
model_config=None,
|
||||||
|
kind=Kind.config,
|
||||||
|
doc="Gain of the amplifier",
|
||||||
|
)
|
||||||
|
coupling = Cpt(
|
||||||
|
BECProcessedSignal,
|
||||||
|
name="coupling",
|
||||||
|
model_config=None,
|
||||||
|
kind=Kind.config,
|
||||||
|
doc="Coupling of the amplifier",
|
||||||
|
)
|
||||||
|
speed = Cpt(
|
||||||
|
BECProcessedSignal,
|
||||||
|
name="speed",
|
||||||
|
model_config=None,
|
||||||
|
kind=Kind.config,
|
||||||
|
doc="Speed of the amplifier",
|
||||||
|
)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
gain_lsb: str,
|
||||||
|
gain_mid: str,
|
||||||
|
gain_msb: str,
|
||||||
|
coupling: str,
|
||||||
|
speed_mode: str,
|
||||||
|
device_manager: DeviceManagerDS | None = None,
|
||||||
|
scan_info: ScanInfo | None = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
|
super().__init__(name=name, device_manager=device_manager, scan_info=scan_info, **kwargs)
|
||||||
|
|
||||||
|
# First we get all signal objects from BEC using the utility method provided by the BECProcessedSignal class.
|
||||||
|
self._gain_lsb = self.gain.get_device_object_from_bec(
|
||||||
|
object_name=gain_lsb, signal_name=self.name, device_manager=device_manager
|
||||||
|
)
|
||||||
|
self._gain_mid = self.gain.get_device_object_from_bec(
|
||||||
|
object_name=gain_mid, signal_name=self.name, device_manager=device_manager
|
||||||
|
)
|
||||||
|
self._gain_msb = self.gain.get_device_object_from_bec(
|
||||||
|
object_name=gain_msb, signal_name=self.name, device_manager=device_manager
|
||||||
|
)
|
||||||
|
self._coupling = self.gain.get_device_object_from_bec(
|
||||||
|
object_name=coupling, signal_name=self.name, device_manager=device_manager
|
||||||
|
)
|
||||||
|
self._speed_mode = self.gain.get_device_object_from_bec(
|
||||||
|
object_name=speed_mode, signal_name=self.name, device_manager=device_manager
|
||||||
|
)
|
||||||
|
|
||||||
|
# Set the compute methods for the virtual signals.
|
||||||
|
self.gain.set_compute_method(
|
||||||
|
self._compute_gain,
|
||||||
|
msb=self._gain_msb,
|
||||||
|
mid=self._gain_mid,
|
||||||
|
lsb=self._gain_lsb,
|
||||||
|
speed_mode=self._speed_mode,
|
||||||
|
)
|
||||||
|
self.coupling.set_compute_method(self._compute_coupling, coupling=self._coupling)
|
||||||
|
self.speed.set_compute_method(self._compute_speed, speed=self._speed_mode)
|
||||||
|
|
||||||
|
def set_gain(
|
||||||
|
self,
|
||||||
|
gain: Literal[
|
||||||
|
1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000, 100000000000
|
||||||
|
],
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Set the gain of the amplifier.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
gain (Literal): Must be one of 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000.
|
||||||
|
"""
|
||||||
|
gain_int = int(gain)
|
||||||
|
if gain_int not in VALID_GAINS:
|
||||||
|
raise ValueError(
|
||||||
|
f"{self.name} received invalid gain {gain_int}, must be in {VALID_GAINS}"
|
||||||
|
)
|
||||||
|
|
||||||
|
msb, mid, lsb, use_low_noise = _GAIN_TO_BITS[gain_int]
|
||||||
|
|
||||||
|
self._gain_msb.set(bool(msb)).wait(timeout=2)
|
||||||
|
self._gain_lsb.set(bool(lsb)).wait(timeout=2)
|
||||||
|
self._gain_mid.set(bool(mid)).wait(timeout=2)
|
||||||
|
self._speed_mode.set(bool(use_low_noise))
|
||||||
|
|
||||||
|
def set_coupling(self, coupling: Literal["AC", "DC"]) -> None:
|
||||||
|
"""
|
||||||
|
Set the coupling of the amplifier.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
coupling (Literal): Must be either "AC" or "DC".
|
||||||
|
"""
|
||||||
|
if coupling not in ["AC", "DC"]:
|
||||||
|
raise ValueError(
|
||||||
|
f"{self.name} received invalid coupling value {coupling}, please use 'AC' or 'DC'"
|
||||||
|
)
|
||||||
|
self._coupling.set(coupling == "DC").wait(timeout=2)
|
||||||
|
|
||||||
|
def _compute_gain(self, msb: Signal, mid: Signal, lsb: Signal, speed_mode: Signal) -> int:
|
||||||
|
"""Compute the gain based on the bits and speed mode."""
|
||||||
|
bits = (msb.get(), mid.get(), lsb.get())
|
||||||
|
speed_mode = speed_mode.get()
|
||||||
|
if speed_mode:
|
||||||
|
return _GAIN_BITS_LOW_NOISE.get(bits)
|
||||||
|
else:
|
||||||
|
return _GAIN_BITS_HIGH_SPEED.get(bits)
|
||||||
|
|
||||||
|
def _compute_coupling(self, coupling: Signal) -> str:
|
||||||
|
"""Compute the coupling based on the signal."""
|
||||||
|
return "DC" if coupling.get() else "AC"
|
||||||
|
|
||||||
|
def _compute_speed(self, speed: Signal) -> str:
|
||||||
|
"""Compute the speed based on the signal."""
|
||||||
|
return "low_speed" if speed.get() else "high_speed"
|
||||||
1
csaxs_bec/devices/pseudo_devices/dlpca200_settings.py
Normal file
1
csaxs_bec/devices/pseudo_devices/dlpca200_settings.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# from ophyd
|
||||||
@@ -27,20 +27,19 @@ from bec_lib import bec_logger, messages
|
|||||||
from bec_lib.alarm_handler import Alarms
|
from bec_lib.alarm_handler import Alarms
|
||||||
from bec_lib.endpoints import MessageEndpoints
|
from bec_lib.endpoints import MessageEndpoints
|
||||||
from bec_server.scan_server.errors import ScanAbortion
|
from bec_server.scan_server.errors import ScanAbortion
|
||||||
from bec_server.scan_server.scans import SyncFlyScanBase
|
from bec_server.scan_server.scans import AsyncFlyScanBase
|
||||||
|
|
||||||
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import TRIGGERSOURCE
|
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import TRIGGERSOURCE
|
||||||
|
|
||||||
logger = bec_logger.logger
|
logger = bec_logger.logger
|
||||||
|
|
||||||
|
|
||||||
class FlomniFermatScan(SyncFlyScanBase):
|
class FlomniFermatScan(AsyncFlyScanBase):
|
||||||
scan_name = "flomni_fermat_scan"
|
scan_name = "flomni_fermat_scan"
|
||||||
scan_type = "fly"
|
scan_type = "fly"
|
||||||
required_kwargs = ["fovx", "fovy", "exp_time", "step", "angle"]
|
required_kwargs = ["fovx", "fovy", "exp_time", "step", "angle"]
|
||||||
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}
|
||||||
use_scan_progress_report = True
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -104,6 +103,14 @@ class FlomniFermatScan(SyncFlyScanBase):
|
|||||||
self.zshift = -100
|
self.zshift = -100
|
||||||
self.flomni_rotation_status = None
|
self.flomni_rotation_status = None
|
||||||
|
|
||||||
|
def scan_report_instructions(self):
|
||||||
|
"""Scan report instructions for the progress bar"""
|
||||||
|
yield from self.stubs.scan_report_instruction({"device_progress": ["rt_positions"]})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def monitor_sync(self) -> str:
|
||||||
|
return "rt_positions"
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
self.scan_motors = []
|
self.scan_motors = []
|
||||||
self.update_readout_priority()
|
self.update_readout_priority()
|
||||||
@@ -113,10 +120,6 @@ class FlomniFermatScan(SyncFlyScanBase):
|
|||||||
self.positions, corridor_size=self.optim_trajectory_corridor
|
self.positions, corridor_size=self.optim_trajectory_corridor
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def monitor_sync(self):
|
|
||||||
return "rt_flomni"
|
|
||||||
|
|
||||||
def reverse_trajectory(self):
|
def reverse_trajectory(self):
|
||||||
"""
|
"""
|
||||||
Reverse the trajectory. Every other scan should be reversed to
|
Reverse the trajectory. Every other scan should be reversed to
|
||||||
@@ -290,26 +293,18 @@ class FlomniFermatScan(SyncFlyScanBase):
|
|||||||
return np.array(positions)
|
return np.array(positions)
|
||||||
|
|
||||||
def scan_core(self):
|
def scan_core(self):
|
||||||
# use a device message to receive the scan number and
|
# send off the flyer
|
||||||
# scan ID before sending the message to the device server
|
yield from self.stubs.kickoff(device="rt_positions")
|
||||||
yield from self.stubs.kickoff(device="rtx")
|
|
||||||
while True:
|
|
||||||
yield from self.stubs.read(group="monitored")
|
|
||||||
status = self.connector.get(MessageEndpoints.device_status("rt_scan"))
|
|
||||||
if status:
|
|
||||||
status_id = status.content.get("status", 1)
|
|
||||||
request_id = status.metadata.get("RID")
|
|
||||||
if status_id == 0 and self.metadata.get("RID") == request_id:
|
|
||||||
break
|
|
||||||
if status_id == 2 and self.metadata.get("RID") == request_id:
|
|
||||||
raise ScanAbortion(
|
|
||||||
"An error occured during the flomni readout:"
|
|
||||||
f" {status.metadata.get('error')}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# start the readout loop of the flyer
|
||||||
|
status = yield from self.stubs.complete(device="rt_positions", wait=False)
|
||||||
|
|
||||||
|
# read the monitors until the flyer is done
|
||||||
|
while not status.done:
|
||||||
|
yield from self.stubs.read(group="monitored", point_id=self.point_id)
|
||||||
|
self.point_id += 1
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
logger.debug("reading monitors")
|
logger.debug("reading monitors")
|
||||||
# yield from self.device_rpc("rtx", "controller.kickoff")
|
|
||||||
|
|
||||||
def move_to_start(self):
|
def move_to_start(self):
|
||||||
"""return to the start position"""
|
"""return to the start position"""
|
||||||
@@ -336,6 +331,7 @@ class FlomniFermatScan(SyncFlyScanBase):
|
|||||||
yield from self.read_scan_motors()
|
yield from self.read_scan_motors()
|
||||||
self.prepare_positions()
|
self.prepare_positions()
|
||||||
yield from self._prepare_setup()
|
yield from self._prepare_setup()
|
||||||
|
yield from self.scan_report_instructions()
|
||||||
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()
|
||||||
|
|||||||
@@ -217,6 +217,16 @@ def test_mcs_card_csaxs_complete_and_stop(mock_mcs_csaxs: MCSCardCSAXS):
|
|||||||
assert not mcs._start_monitor_async_data_emission.is_set()
|
assert not mcs._start_monitor_async_data_emission.is_set()
|
||||||
|
|
||||||
|
|
||||||
|
def test_mcs_on_stop(mock_mcs_csaxs: MCSCardCSAXS):
|
||||||
|
"""Test that on stop sets the omit_mca_callbacks flag. Also test that on stage clears the omit_mca_callbacks flag."""
|
||||||
|
mcs = mock_mcs_csaxs
|
||||||
|
assert mcs._omit_mca_callbacks.is_set() is False
|
||||||
|
mcs.stop()
|
||||||
|
assert mcs._omit_mca_callbacks.is_set() is True
|
||||||
|
mcs.stage()
|
||||||
|
assert mcs._omit_mca_callbacks.is_set() is False
|
||||||
|
|
||||||
|
|
||||||
def test_mcs_recovery(mock_mcs_csaxs: MCSCardCSAXS):
|
def test_mcs_recovery(mock_mcs_csaxs: MCSCardCSAXS):
|
||||||
mcs = mock_mcs_csaxs
|
mcs = mock_mcs_csaxs
|
||||||
# Simulate ongoing acquisition
|
# Simulate ongoing acquisition
|
||||||
|
|||||||
241
tests/tests_devices/test_pseudo_devices.py
Normal file
241
tests/tests_devices/test_pseudo_devices.py
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
"""Module to test the pseudo_device module."""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from bec_lib.atlas_models import Device
|
||||||
|
from ophyd_devices.sim.sim_signals import SetableSignal
|
||||||
|
|
||||||
|
from csaxs_bec.devices.pseudo_devices.bpm import BPM
|
||||||
|
from csaxs_bec.devices.pseudo_devices.bpm_control import _GAIN_TO_BITS, BPMControl
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def patched_dm(dm_with_devices):
|
||||||
|
# Patch missing current_session attribute in the device manager
|
||||||
|
dm = dm_with_devices
|
||||||
|
setattr(dm, "current_session", dm._session)
|
||||||
|
#
|
||||||
|
signal_lsb = SetableSignal(name="gain_lsb", value=0, kind="config")
|
||||||
|
signal_mid = SetableSignal(name="gain_mid", value=0, kind="config")
|
||||||
|
signal_msb = SetableSignal(name="gain_msb", value=0, kind="config")
|
||||||
|
signal_coupling = SetableSignal(name="coupling", value=0, kind="config")
|
||||||
|
signal_speed = SetableSignal(name="speed_mode", value=0, kind="config")
|
||||||
|
for signal in [signal_lsb, signal_mid, signal_msb, signal_coupling, signal_speed]:
|
||||||
|
dev_cfg = Device(
|
||||||
|
name=signal.name,
|
||||||
|
deviceClass="ophyd_devices.sim.sim_signals.SetableSignal",
|
||||||
|
enabled=True,
|
||||||
|
readoutPriority="baseline",
|
||||||
|
)
|
||||||
|
dm._session["devices"].append(dev_cfg.model_dump())
|
||||||
|
dm.devices._add_device(signal.name, signal)
|
||||||
|
return dm
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def bpm_control(patched_dm):
|
||||||
|
name = "bpm_control"
|
||||||
|
control_config = Device(
|
||||||
|
name=name,
|
||||||
|
deviceClass="csaxs_bec.devices.pseudo_devices.bpm_control.BPMControl",
|
||||||
|
enabled=True,
|
||||||
|
readoutPriority="baseline",
|
||||||
|
deviceConfig={
|
||||||
|
"gain_lsb": "gain_lsb",
|
||||||
|
"gain_mid": "gain_mid",
|
||||||
|
"gain_msb": "gain_msb",
|
||||||
|
"coupling": "coupling",
|
||||||
|
"speed_mode": "speed_mode",
|
||||||
|
},
|
||||||
|
needs=["gain_lsb", "gain_mid", "gain_msb", "coupling", "speed_mode"],
|
||||||
|
)
|
||||||
|
patched_dm._session["devices"].append(control_config.model_dump())
|
||||||
|
try:
|
||||||
|
control = BPMControl(
|
||||||
|
name=name,
|
||||||
|
gain_lsb="gain_lsb",
|
||||||
|
gain_mid="gain_mid",
|
||||||
|
gain_msb="gain_msb",
|
||||||
|
coupling="coupling",
|
||||||
|
speed_mode="speed_mode",
|
||||||
|
device_manager=patched_dm,
|
||||||
|
)
|
||||||
|
patched_dm.devices._add_device(control.name, control)
|
||||||
|
control.wait_for_connection()
|
||||||
|
yield control
|
||||||
|
finally:
|
||||||
|
control.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
def test_bpm_control_set_gain(bpm_control):
|
||||||
|
gain_lsb = bpm_control.device_manager.devices["gain_lsb"]
|
||||||
|
gain_mid = bpm_control.device_manager.devices["gain_mid"]
|
||||||
|
gain_msb = bpm_control.device_manager.devices["gain_msb"]
|
||||||
|
coupling = bpm_control.device_manager.devices["coupling"]
|
||||||
|
speed_mode = bpm_control.device_manager.devices["speed_mode"]
|
||||||
|
gain_lsb.put(0)
|
||||||
|
gain_mid.put(0)
|
||||||
|
gain_msb.put(0)
|
||||||
|
coupling.put(0)
|
||||||
|
speed_mode.put(1)
|
||||||
|
|
||||||
|
gain = bpm_control.gain.get()
|
||||||
|
assert _GAIN_TO_BITS.get(gain) == (0, 0, 0, speed_mode.get() == 1)
|
||||||
|
|
||||||
|
gain_val = 10000000
|
||||||
|
bpm_control.set_gain(gain_val)
|
||||||
|
assert _GAIN_TO_BITS.get(gain_val, ()) == (
|
||||||
|
gain_msb.get(),
|
||||||
|
gain_mid.get(),
|
||||||
|
gain_lsb.get(),
|
||||||
|
speed_mode.get(),
|
||||||
|
)
|
||||||
|
|
||||||
|
gain_val = 100000000000
|
||||||
|
bpm_control.set_gain(gain_val)
|
||||||
|
assert _GAIN_TO_BITS.get(gain_val, ()) == (
|
||||||
|
gain_msb.get(),
|
||||||
|
gain_mid.get(),
|
||||||
|
gain_lsb.get(),
|
||||||
|
speed_mode.get(),
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
bpm_control.set_gain(1005.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bpm_control_set_coupling(bpm_control):
|
||||||
|
coupling = bpm_control.device_manager.devices["coupling"]
|
||||||
|
coupling.put(0)
|
||||||
|
|
||||||
|
bpm_control.coupling.get() == "AC"
|
||||||
|
coupling.put(1)
|
||||||
|
bpm_control.coupling.get() == "DC"
|
||||||
|
|
||||||
|
bpm_control.set_coupling("AC")
|
||||||
|
assert coupling.get() == 0
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
bpm_control.set_coupling("wrong")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def patched_dm_bpm(dm_with_devices):
|
||||||
|
# Patch missing current_session attribute in the device manager
|
||||||
|
dm = dm_with_devices
|
||||||
|
setattr(dm, "current_session", dm._session)
|
||||||
|
#
|
||||||
|
left_top = SetableSignal(name="left_top", value=0, kind="config")
|
||||||
|
right_top = SetableSignal(name="right_top", value=0, kind="config")
|
||||||
|
right_bot = SetableSignal(name="right_bot", value=0, kind="config")
|
||||||
|
left_bot = SetableSignal(name="left_bot", value=0, kind="config")
|
||||||
|
for signal in [left_top, right_top, right_bot, left_bot]:
|
||||||
|
|
||||||
|
dev_cfg = Device(
|
||||||
|
name=signal.name,
|
||||||
|
deviceClass="ophyd_devices.sim.sim_signals.SetableSignal",
|
||||||
|
enabled=True,
|
||||||
|
readoutPriority="baseline",
|
||||||
|
)
|
||||||
|
dm._session["devices"].append(dev_cfg.model_dump())
|
||||||
|
dm.devices._add_device(signal.name, signal)
|
||||||
|
return dm
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def bpm(patched_dm_bpm):
|
||||||
|
name = "bpm"
|
||||||
|
bpm_config = Device(
|
||||||
|
name=name,
|
||||||
|
deviceClass="csaxs_bec.devices.pseudo_devices.bpm.BPM",
|
||||||
|
enabled=True,
|
||||||
|
readoutPriority="baseline",
|
||||||
|
deviceConfig={
|
||||||
|
"left_top": "left_top",
|
||||||
|
"right_top": "right_top",
|
||||||
|
"right_bot": "right_bot",
|
||||||
|
"left_bot": "left_bot",
|
||||||
|
},
|
||||||
|
needs=["left_top", "right_top", "right_bot", "left_bot"],
|
||||||
|
)
|
||||||
|
patched_dm_bpm._session["devices"].append(bpm_config.model_dump())
|
||||||
|
try:
|
||||||
|
bpm = BPM(
|
||||||
|
name=name,
|
||||||
|
left_top="left_top",
|
||||||
|
right_top="right_top",
|
||||||
|
right_bot="right_bot",
|
||||||
|
left_bot="left_bot",
|
||||||
|
device_manager=patched_dm_bpm,
|
||||||
|
)
|
||||||
|
patched_dm_bpm.devices._add_device(bpm.name, bpm)
|
||||||
|
bpm.wait_for_connection()
|
||||||
|
yield bpm
|
||||||
|
finally:
|
||||||
|
bpm.destroy()
|
||||||
|
|
||||||
|
|
||||||
|
def test_bpm_positions(bpm):
|
||||||
|
left_top = bpm.device_manager.devices["left_top"]
|
||||||
|
right_top = bpm.device_manager.devices["right_top"]
|
||||||
|
right_bot = bpm.device_manager.devices["right_bot"]
|
||||||
|
left_bot = bpm.device_manager.devices["left_bot"]
|
||||||
|
|
||||||
|
# Test center position
|
||||||
|
for signal in [left_top, right_top, right_bot, left_bot]:
|
||||||
|
signal.put(1)
|
||||||
|
assert bpm.pos_x.get() == 0
|
||||||
|
assert bpm.pos_y.get() == 0
|
||||||
|
|
||||||
|
# Test fully left
|
||||||
|
left_top.put(1)
|
||||||
|
right_top.put(0)
|
||||||
|
right_bot.put(0)
|
||||||
|
left_bot.put(1)
|
||||||
|
assert bpm.pos_x.get() == -1
|
||||||
|
assert bpm.pos_y.get() == 0
|
||||||
|
assert bpm.diagonal.get() == 0
|
||||||
|
assert bpm.intensity.get() == 2
|
||||||
|
|
||||||
|
# Test fully right
|
||||||
|
left_top.put(0)
|
||||||
|
right_top.put(1)
|
||||||
|
right_bot.put(1)
|
||||||
|
left_bot.put(0)
|
||||||
|
assert bpm.pos_x.get() == 1
|
||||||
|
assert bpm.pos_y.get() == 0
|
||||||
|
assert bpm.diagonal.get() == 0
|
||||||
|
|
||||||
|
# Test fully top
|
||||||
|
left_top.put(1)
|
||||||
|
right_top.put(1)
|
||||||
|
right_bot.put(0)
|
||||||
|
left_bot.put(0)
|
||||||
|
assert bpm.pos_x.get() == 0
|
||||||
|
assert bpm.pos_y.get() == 1
|
||||||
|
assert bpm.diagonal.get() == 0
|
||||||
|
|
||||||
|
# Test fully bottom
|
||||||
|
left_top.put(0)
|
||||||
|
right_top.put(0)
|
||||||
|
right_bot.put(1)
|
||||||
|
left_bot.put(1)
|
||||||
|
assert bpm.pos_x.get() == 0
|
||||||
|
assert bpm.pos_y.get() == -1
|
||||||
|
assert bpm.diagonal.get() == 0
|
||||||
|
|
||||||
|
# Diagonal beam
|
||||||
|
left_top.put(1)
|
||||||
|
right_top.put(0)
|
||||||
|
right_bot.put(1)
|
||||||
|
left_bot.put(0)
|
||||||
|
assert bpm.pos_x.get() == 0
|
||||||
|
assert bpm.pos_y.get() == 0
|
||||||
|
assert bpm.diagonal.get() == -1
|
||||||
|
|
||||||
|
left_top.put(0)
|
||||||
|
right_top.put(1)
|
||||||
|
right_bot.put(0)
|
||||||
|
left_bot.put(1)
|
||||||
|
assert bpm.pos_x.get() == 0
|
||||||
|
assert bpm.pos_y.get() == 0
|
||||||
|
assert bpm.diagonal.get() == 1
|
||||||
Reference in New Issue
Block a user