Compare commits
2 Commits
feat/add_p
...
fix/contro
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c5a5c3d98 | |||
| ae20de9391 |
@@ -9,11 +9,9 @@ from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_put, fshclose
|
||||
|
||||
# import builtins to avoid linter errors
|
||||
dev = builtins.__dict__.get("dev")
|
||||
umv = builtins.__dict__.get("umv")
|
||||
bec = builtins.__dict__.get("bec")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
|
||||
class LamNIInitError(Exception):
|
||||
pass
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
# import builtins
|
||||
# import datetime
|
||||
# import os
|
||||
# import subprocess
|
||||
# import time
|
||||
# from pathlib import Path
|
||||
|
||||
# import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
# from bec_lib.alarm_handler import AlarmBase
|
||||
# from bec_lib.pdf_writer import PDFWriter
|
||||
from typeguard import typechecked
|
||||
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS.smaract import cSAXSInitSmaractStages
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS.smaract import cSAXSSmaract
|
||||
from csaxs_bec.bec_ipython_client.plugins.omny.omny_general_tools import OMNYTools
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS.filter_transmission import cSAXSFilterTransmission
|
||||
class cSAXSError(Exception):
|
||||
pass
|
||||
|
||||
class cSAXS(
|
||||
cSAXSInitSmaractStages,
|
||||
cSAXSSmaract,
|
||||
cSAXSFilterTransmission,
|
||||
):
|
||||
def __init__(self, client):
|
||||
self.client = client
|
||||
self.device_manager = client.device_manager
|
||||
self.OMNYTools = OMNYTools(self.client)
|
||||
super().__init__(client=client)
|
||||
|
||||
|
||||
# this is the csaxs master file that imports all routines from csaxs
|
||||
# can be imported in the bec client by
|
||||
# run in bec from folder /sls/x12sa/config/bec/production/csaxs_bec
|
||||
# from csaxs_bec.bec_ipython_client.plugins.cSAXS.cSAXS import cSAXS
|
||||
# csaxs = cSAXS(bec)
|
||||
#
|
||||
# then all commands can be accessed by for example
|
||||
# csaxs._cSAXS_smaract_stages_.....
|
||||
@@ -1,443 +0,0 @@
|
||||
"""
|
||||
csaxs_dlpca200.py
|
||||
=================
|
||||
BEC control script for FEMTO DLPCA-200 Variable Gain Low Noise Current Amplifiers
|
||||
connected to Galil RIO digital outputs.
|
||||
|
||||
DLPCA-200 Remote Control (datasheet page 4)
|
||||
-------------------------------------------
|
||||
Sub-D pin → function:
|
||||
Pin 10 → gain LSB (digital out channel, index 0 in bit-tuple)
|
||||
Pin 11 → gain MID (digital out channel, index 1 in bit-tuple)
|
||||
Pin 12 → gain MSB (digital out channel, index 2 in bit-tuple)
|
||||
Pin 13 → coupling LOW = AC, HIGH = DC
|
||||
Pin 14 → speed mode HIGH = low noise (Pin14=1), LOW = high speed (Pin14=0)
|
||||
|
||||
Gain truth table (MSB, MID, LSB):
|
||||
0,0,0 → low-noise: 1e3 high-speed: 1e5
|
||||
0,0,1 → low-noise: 1e4 high-speed: 1e6
|
||||
0,1,0 → low-noise: 1e5 high-speed: 1e7
|
||||
0,1,1 → low-noise: 1e6 high-speed: 1e8
|
||||
1,0,0 → low-noise: 1e7 high-speed: 1e9
|
||||
1,0,1 → low-noise: 1e8 high-speed: 1e10
|
||||
1,1,0 → low-noise: 1e9 high-speed: 1e11
|
||||
|
||||
Strategy: prefer low-noise mode (1e3–1e9). For 1e10 and 1e11,
|
||||
automatically fall back to high-speed mode.
|
||||
|
||||
Device wiring example (galilrioesxbox):
|
||||
bpm4: Pin10→ch0, Pin11→ch1, Pin12→ch2, Pin13→ch3, Pin14→ch4
|
||||
bim: Pin10→ch6, Pin11→ch7, Pin12→ch8, Pin13→ch9, Pin14→ch10
|
||||
|
||||
Usage examples
|
||||
--------------
|
||||
csaxs_amp = cSAXSDLPCA200(client)
|
||||
|
||||
csaxs_amp.set_gain("bpm4", 1e7) # low-noise if possible
|
||||
csaxs_amp.set_gain("bim", 1e10) # auto high-speed
|
||||
csaxs_amp.set_coupling("bpm4", "DC")
|
||||
csaxs_amp.set_coupling("bim", "AC")
|
||||
csaxs_amp.info("bpm4") # print current settings
|
||||
csaxs_amp.info_all() # print all configured amplifiers
|
||||
"""
|
||||
|
||||
import builtins
|
||||
|
||||
from bec_lib import bec_logger
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Amplifier registry
|
||||
# ---------------------------------------------------------------------------
|
||||
# Each entry describes one DLPCA-200 amplifier connected to a Galil RIO.
|
||||
#
|
||||
# Keys inside "channels":
|
||||
# gain_lsb → digital output channel number wired to DLPCA-200 Pin 10
|
||||
# gain_mid → digital output channel number wired to DLPCA-200 Pin 11
|
||||
# gain_msb → digital output channel number wired to DLPCA-200 Pin 12
|
||||
# coupling → digital output channel number wired to DLPCA-200 Pin 13
|
||||
# speed_mode → digital output channel number wired to DLPCA-200 Pin 14
|
||||
#
|
||||
# To add a new amplifier, simply extend this dict.
|
||||
# ---------------------------------------------------------------------------
|
||||
DLPCA200_AMPLIFIER_CONFIG: dict[str, dict] = {
|
||||
"bpm4": {
|
||||
"rio_device": "galilrioesxbox",
|
||||
"description": "Beam Position Monitor 4 current amplifier",
|
||||
"channels": {
|
||||
"gain_lsb": 0, # Pin 10 → Galil ch0
|
||||
"gain_mid": 1, # Pin 11 → Galil ch1
|
||||
"gain_msb": 2, # Pin 12 → Galil ch2
|
||||
"coupling": 3, # Pin 13 → Galil ch3
|
||||
"speed_mode": 4, # Pin 14 → Galil ch4
|
||||
},
|
||||
},
|
||||
"bim": {
|
||||
"rio_device": "galilrioesxbox",
|
||||
"description": "Beam Intensity Monitor current amplifier",
|
||||
"channels": {
|
||||
"gain_lsb": 6, # Pin 10 → Galil ch6
|
||||
"gain_mid": 7, # Pin 11 → Galil ch7
|
||||
"gain_msb": 8, # Pin 12 → Galil ch8
|
||||
"coupling": 9, # Pin 13 → Galil ch9
|
||||
"speed_mode": 10, # Pin 14 → Galil ch10
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# DLPCA-200 gain encoding tables
|
||||
# ---------------------------------------------------------------------------
|
||||
# (msb, mid, lsb) → gain in V/A
|
||||
_GAIN_BITS_LOW_NOISE: dict[tuple, int] = {
|
||||
(0, 0, 0): int(1e3),
|
||||
(0, 0, 1): int(1e4),
|
||||
(0, 1, 0): int(1e5),
|
||||
(0, 1, 1): int(1e6),
|
||||
(1, 0, 0): int(1e7),
|
||||
(1, 0, 1): int(1e8),
|
||||
(1, 1, 0): int(1e9),
|
||||
}
|
||||
|
||||
_GAIN_BITS_HIGH_SPEED: dict[tuple, int] = {
|
||||
(0, 0, 0): int(1e5),
|
||||
(0, 0, 1): int(1e6),
|
||||
(0, 1, 0): int(1e7),
|
||||
(0, 1, 1): int(1e8),
|
||||
(1, 0, 0): int(1e9),
|
||||
(1, 0, 1): int(1e10),
|
||||
(1, 1, 0): int(1e11),
|
||||
}
|
||||
|
||||
# Inverse maps: gain → (msb, mid, lsb, low_noise_flag)
|
||||
# low_noise_flag: True = Pin14 HIGH, False = Pin14 LOW
|
||||
_GAIN_TO_BITS: dict[int, tuple] = {}
|
||||
for _bits, _gain in _GAIN_BITS_LOW_NOISE.items():
|
||||
_GAIN_TO_BITS[_gain] = (*_bits, True)
|
||||
for _bits, _gain in _GAIN_BITS_HIGH_SPEED.items():
|
||||
if _gain not in _GAIN_TO_BITS: # low-noise takes priority
|
||||
_GAIN_TO_BITS[_gain] = (*_bits, False)
|
||||
|
||||
VALID_GAINS = sorted(_GAIN_TO_BITS.keys())
|
||||
|
||||
|
||||
class cSAXSDLPCA200Error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class cSAXSDLPCA200:
|
||||
"""
|
||||
Control class for FEMTO DLPCA-200 current amplifiers connected via Galil RIO
|
||||
digital outputs in a BEC environment.
|
||||
|
||||
Supports:
|
||||
- Forward control: set_gain(), set_coupling()
|
||||
- Readback reporting: info(), info_all(), read_settings()
|
||||
- Robust error handling and logging following cSAXS conventions.
|
||||
"""
|
||||
|
||||
TAG = "[DLPCA200]"
|
||||
|
||||
def __init__(self, client, config: dict | None = None) -> None:
|
||||
"""
|
||||
Parameters
|
||||
----------
|
||||
client : BEC client object (passed through for future use)
|
||||
config : optional override for DLPCA200_AMPLIFIER_CONFIG.
|
||||
Falls back to the module-level dict if not provided.
|
||||
"""
|
||||
self.client = client
|
||||
self._config: dict[str, dict] = config if config is not None else DLPCA200_AMPLIFIER_CONFIG
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Internal helpers
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def _require_dev(self) -> None:
|
||||
if dev is None:
|
||||
raise cSAXSDLPCA200Error(
|
||||
f"{self.TAG} BEC 'dev' namespace is not available in this session."
|
||||
)
|
||||
|
||||
def _get_cfg(self, amp_name: str) -> dict:
|
||||
"""Return config dict for a named amplifier, raising on unknown names."""
|
||||
if amp_name not in self._config:
|
||||
known = ", ".join(sorted(self._config.keys()))
|
||||
raise cSAXSDLPCA200Error(
|
||||
f"{self.TAG} Unknown amplifier '{amp_name}'. Known: [{known}]"
|
||||
)
|
||||
return self._config[amp_name]
|
||||
|
||||
def _get_rio(self, amp_name: str):
|
||||
"""Return the live RIO device object for a given amplifier."""
|
||||
self._require_dev()
|
||||
cfg = self._get_cfg(amp_name)
|
||||
rio_name = cfg["rio_device"]
|
||||
try:
|
||||
rio = getattr(dev, rio_name)
|
||||
except AttributeError:
|
||||
raise cSAXSDLPCA200Error(
|
||||
f"{self.TAG} RIO device '{rio_name}' not found in BEC 'dev'."
|
||||
)
|
||||
return rio
|
||||
|
||||
def _dout_get(self, rio, ch: int) -> int:
|
||||
"""Read one digital output channel (returns 0 or 1)."""
|
||||
attr = getattr(rio.digital_out, f"ch{ch}")
|
||||
val = attr.get()
|
||||
return int(val)
|
||||
|
||||
def _dout_set(self, rio, ch: int, value: bool) -> None:
|
||||
"""Write one digital output channel (True=HIGH=1, False=LOW=0)."""
|
||||
attr = getattr(rio.digital_out, f"ch{ch}")
|
||||
attr.set(value)
|
||||
|
||||
def _read_gain_bits(self, amp_name: str) -> tuple[int, int, int, int]:
|
||||
"""
|
||||
Read current gain bit-state from hardware.
|
||||
|
||||
Returns
|
||||
-------
|
||||
(msb, mid, lsb, speed_mode)
|
||||
speed_mode: 1 = low-noise (Pin14=HIGH), 0 = high-speed (Pin14=LOW)
|
||||
"""
|
||||
rio = self._get_rio(amp_name)
|
||||
ch = self._get_cfg(amp_name)["channels"]
|
||||
msb = self._dout_get(rio, ch["gain_msb"])
|
||||
mid = self._dout_get(rio, ch["gain_mid"])
|
||||
lsb = self._dout_get(rio, ch["gain_lsb"])
|
||||
speed_mode = self._dout_get(rio, ch["speed_mode"])
|
||||
return msb, mid, lsb, speed_mode
|
||||
|
||||
def _decode_gain(self, msb: int, mid: int, lsb: int, speed_mode: int) -> int | None:
|
||||
"""
|
||||
Decode hardware bit-state into gain value (V/A).
|
||||
|
||||
speed_mode=1 → low-noise table, speed_mode=0 → high-speed table.
|
||||
Returns None if the bit combination is not in the table.
|
||||
"""
|
||||
bits = (msb, mid, lsb)
|
||||
if speed_mode:
|
||||
return _GAIN_BITS_LOW_NOISE.get(bits)
|
||||
else:
|
||||
return _GAIN_BITS_HIGH_SPEED.get(bits)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Public API – control
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def set_gain(self, amp_name: str, gain: float, force_high_speed: bool = False) -> None:
|
||||
"""
|
||||
Set the transimpedance gain of a DLPCA-200 amplifier.
|
||||
|
||||
The method automatically selects low-noise mode (Pin14=HIGH) whenever
|
||||
the requested gain is achievable in low-noise mode (1e3 – 1e9 V/A).
|
||||
For gains of 1e10 and 1e11 V/A, high-speed mode is used automatically.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
amp_name : str
|
||||
Amplifier name as defined in DLPCA200_AMPLIFIER_CONFIG (e.g. "bpm4").
|
||||
gain : float or int
|
||||
Target gain in V/A. Must be one of:
|
||||
1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11.
|
||||
force_high_speed : bool, optional
|
||||
If True, force high-speed (low-noise=False) mode even for gains
|
||||
below 1e10. Default: False (prefer low-noise).
|
||||
|
||||
Examples
|
||||
--------
|
||||
csaxs_amp.set_gain("bpm4", 1e7) # low-noise mode (automatic)
|
||||
csaxs_amp.set_gain("bim", 1e10) # high-speed mode (automatic)
|
||||
csaxs_amp.set_gain("bpm4", 1e7, force_high_speed=True) # override to high-speed
|
||||
"""
|
||||
gain_int = int(gain)
|
||||
if gain_int not in _GAIN_TO_BITS:
|
||||
valid_str = ", ".join(f"1e{int(round(__import__('math').log10(g)))}" for g in VALID_GAINS)
|
||||
raise cSAXSDLPCA200Error(
|
||||
f"{self.TAG} Invalid gain {gain:.2e} V/A for '{amp_name}'. "
|
||||
f"Valid values: {valid_str}"
|
||||
)
|
||||
|
||||
msb, mid, lsb, low_noise_preferred = _GAIN_TO_BITS[gain_int]
|
||||
|
||||
# Apply force_high_speed override
|
||||
if force_high_speed and low_noise_preferred:
|
||||
# Check if this gain is achievable in high-speed mode
|
||||
hs_entry = next(
|
||||
(bits for bits, g in _GAIN_BITS_HIGH_SPEED.items() if g == gain_int), None
|
||||
)
|
||||
if hs_entry is None:
|
||||
raise cSAXSDLPCA200Error(
|
||||
f"{self.TAG} Gain {gain:.2e} V/A is not achievable in high-speed mode "
|
||||
f"for '{amp_name}'."
|
||||
)
|
||||
msb, mid, lsb = hs_entry
|
||||
low_noise_preferred = False
|
||||
|
||||
use_low_noise = low_noise_preferred and not force_high_speed
|
||||
|
||||
try:
|
||||
rio = self._get_rio(amp_name)
|
||||
ch = self._get_cfg(amp_name)["channels"]
|
||||
|
||||
self._dout_set(rio, ch["gain_msb"], bool(msb))
|
||||
self._dout_set(rio, ch["gain_mid"], bool(mid))
|
||||
self._dout_set(rio, ch["gain_lsb"], bool(lsb))
|
||||
self._dout_set(rio, ch["speed_mode"], use_low_noise) # True=low-noise
|
||||
|
||||
mode_str = "low-noise" if use_low_noise else "high-speed"
|
||||
logger.info(
|
||||
f"{self.TAG} [{amp_name}] gain set to {gain_int:.2e} V/A "
|
||||
f"({mode_str} mode, bits MSB={msb} MID={mid} LSB={lsb})"
|
||||
)
|
||||
print(
|
||||
f"{amp_name}: gain → {gain_int:.2e} V/A [{mode_str}] "
|
||||
f"(bits: MSB={msb} MID={mid} LSB={lsb})"
|
||||
)
|
||||
|
||||
except cSAXSDLPCA200Error:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise cSAXSDLPCA200Error(
|
||||
f"{self.TAG} Failed to set gain on '{amp_name}': {exc}"
|
||||
) from exc
|
||||
|
||||
def set_coupling(self, amp_name: str, coupling: str) -> None:
|
||||
"""
|
||||
Set AC or DC coupling on a DLPCA-200 amplifier.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
amp_name : str
|
||||
Amplifier name (e.g. "bpm4", "bim").
|
||||
coupling : str
|
||||
"AC" or "DC" (case-insensitive).
|
||||
DC → Pin13 HIGH, AC → Pin13 LOW.
|
||||
|
||||
Examples
|
||||
--------
|
||||
csaxs_amp.set_coupling("bpm4", "DC")
|
||||
csaxs_amp.set_coupling("bim", "AC")
|
||||
"""
|
||||
coupling_upper = coupling.strip().upper()
|
||||
if coupling_upper not in ("AC", "DC"):
|
||||
raise cSAXSDLPCA200Error(
|
||||
f"{self.TAG} Invalid coupling '{coupling}' for '{amp_name}'. "
|
||||
f"Use 'AC' or 'DC'."
|
||||
)
|
||||
|
||||
pin13_high = coupling_upper == "DC"
|
||||
|
||||
try:
|
||||
rio = self._get_rio(amp_name)
|
||||
ch = self._get_cfg(amp_name)["channels"]
|
||||
self._dout_set(rio, ch["coupling"], pin13_high)
|
||||
|
||||
logger.info(f"{self.TAG} [{amp_name}] coupling set to {coupling_upper}")
|
||||
print(f"{amp_name}: coupling → {coupling_upper}")
|
||||
|
||||
except cSAXSDLPCA200Error:
|
||||
raise
|
||||
except Exception as exc:
|
||||
raise cSAXSDLPCA200Error(
|
||||
f"{self.TAG} Failed to set coupling on '{amp_name}': {exc}"
|
||||
) from exc
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Public API – readback / reporting
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
def read_settings(self, amp_name: str) -> dict:
|
||||
"""
|
||||
Read back the current settings from hardware digital outputs.
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict with keys:
|
||||
"amp_name" : str
|
||||
"gain" : int or None – gain in V/A (None if unknown bit pattern)
|
||||
"mode" : str – "low-noise" or "high-speed"
|
||||
"coupling" : str – "AC" or "DC"
|
||||
"bits" : dict – raw bit values {msb, mid, lsb, speed_mode, coupling}
|
||||
"""
|
||||
rio = self._get_rio(amp_name)
|
||||
ch = self._get_cfg(amp_name)["channels"]
|
||||
|
||||
msb = self._dout_get(rio, ch["gain_msb"])
|
||||
mid = self._dout_get(rio, ch["gain_mid"])
|
||||
lsb = self._dout_get(rio, ch["gain_lsb"])
|
||||
speed_mode = self._dout_get(rio, ch["speed_mode"])
|
||||
coupling_bit = self._dout_get(rio, ch["coupling"])
|
||||
|
||||
gain = self._decode_gain(msb, mid, lsb, speed_mode)
|
||||
mode = "low-noise" if speed_mode else "high-speed"
|
||||
coupling = "DC" if coupling_bit else "AC"
|
||||
|
||||
return {
|
||||
"amp_name": amp_name,
|
||||
"gain": gain,
|
||||
"mode": mode,
|
||||
"coupling": coupling,
|
||||
"bits": {
|
||||
"msb": msb,
|
||||
"mid": mid,
|
||||
"lsb": lsb,
|
||||
"speed_mode": speed_mode,
|
||||
"coupling": coupling_bit,
|
||||
},
|
||||
}
|
||||
|
||||
def info(self, amp_name: str) -> None:
|
||||
"""
|
||||
Print a plain summary of the current settings for one amplifier.
|
||||
|
||||
Example output
|
||||
--------------
|
||||
Amplifier : bpm4
|
||||
Description : Beam Position Monitor 4 current amplifier
|
||||
RIO device : galilrioesxbox
|
||||
Gain : 1.00e+07 V/A
|
||||
Mode : low-noise
|
||||
Coupling : DC
|
||||
Raw bits : MSB=1 MID=0 LSB=0 speed=1 coup=1
|
||||
"""
|
||||
cfg = self._get_cfg(amp_name)
|
||||
|
||||
try:
|
||||
s = self.read_settings(amp_name)
|
||||
except Exception as exc:
|
||||
print(f"{self.TAG} [{amp_name}] Could not read settings: {exc}")
|
||||
return
|
||||
|
||||
gain_str = (
|
||||
f"{s['gain']:.2e} V/A" if s["gain"] is not None else "UNKNOWN (invalid bit pattern)"
|
||||
)
|
||||
bits = s["bits"]
|
||||
|
||||
print(f" {'Amplifier':<12}: {amp_name}")
|
||||
print(f" {'Description':<12}: {cfg.get('description', '')}")
|
||||
print(f" {'RIO device':<12}: {cfg['rio_device']}")
|
||||
print(f" {'Gain':<12}: {gain_str}")
|
||||
print(f" {'Mode':<12}: {s['mode']}")
|
||||
print(f" {'Coupling':<12}: {s['coupling']}")
|
||||
print(f" {'Raw bits':<12}: MSB={bits['msb']} MID={bits['mid']} LSB={bits['lsb']} speed={bits['speed_mode']} coup={bits['coupling']}")
|
||||
|
||||
def info_all(self) -> None:
|
||||
"""
|
||||
Print a plain summary for ALL configured amplifiers.
|
||||
"""
|
||||
print("\nDLPCA-200 Amplifier Status Report")
|
||||
print("-" * 40)
|
||||
for amp_name in sorted(self._config.keys()):
|
||||
self.info(amp_name)
|
||||
print()
|
||||
|
||||
def list_amplifiers(self) -> list[str]:
|
||||
"""Return sorted list of configured amplifier names."""
|
||||
return sorted(self._config.keys())
|
||||
@@ -1,601 +0,0 @@
|
||||
4000.00 10.5509
|
||||
4012.90 10.6470
|
||||
4025.83 10.7440
|
||||
4038.81 10.8418
|
||||
4051.83 10.9406
|
||||
4064.90 11.0404
|
||||
4078.00 11.1410
|
||||
4091.15 11.2426
|
||||
4104.34 11.3451
|
||||
4117.57 11.4487
|
||||
4130.85 11.5532
|
||||
4144.17 11.6587
|
||||
4157.53 11.7652
|
||||
4170.93 11.8726
|
||||
4184.38 11.9811
|
||||
4197.87 12.0906
|
||||
4211.41 12.2011
|
||||
4224.98 12.3126
|
||||
4238.60 12.4252
|
||||
4252.27 12.5389
|
||||
4265.98 12.6536
|
||||
4279.73 12.7694
|
||||
4293.53 12.8863
|
||||
4307.37 13.0042
|
||||
4321.26 13.1234
|
||||
4335.19 13.2436
|
||||
4349.17 13.3650
|
||||
4363.19 13.4875
|
||||
4377.26 13.6111
|
||||
4391.37 13.7360
|
||||
4405.53 13.8620
|
||||
4419.73 13.9892
|
||||
4433.98 14.1175
|
||||
4448.28 14.2470
|
||||
4462.62 14.3779
|
||||
4477.01 14.5099
|
||||
4491.44 14.6432
|
||||
4505.92 14.7777
|
||||
4520.45 14.9135
|
||||
4535.02 15.0507
|
||||
4549.65 15.1891
|
||||
4564.31 15.3289
|
||||
4579.03 15.4699
|
||||
4593.79 15.6123
|
||||
4608.60 15.7560
|
||||
4623.46 15.9010
|
||||
4638.37 16.0473
|
||||
4653.32 16.1950
|
||||
4668.33 16.3442
|
||||
4683.38 16.4948
|
||||
4698.48 16.6468
|
||||
4713.62 16.8002
|
||||
4728.82 16.9551
|
||||
4744.07 17.1114
|
||||
4759.36 17.2693
|
||||
4774.71 17.4287
|
||||
4790.10 17.5895
|
||||
4805.54 17.7519
|
||||
4821.04 17.9157
|
||||
4836.58 18.0811
|
||||
4852.17 18.2481
|
||||
4867.82 18.4166
|
||||
4883.51 18.5866
|
||||
4899.26 18.7583
|
||||
4915.05 18.9317
|
||||
4930.90 19.1069
|
||||
4946.80 19.2836
|
||||
4962.75 19.4619
|
||||
4978.75 19.6419
|
||||
4994.80 19.8237
|
||||
5010.90 20.0072
|
||||
5027.06 20.1924
|
||||
5043.26 20.3793
|
||||
5059.52 20.5680
|
||||
5075.84 20.7585
|
||||
5092.20 20.9507
|
||||
5108.62 21.1448
|
||||
5125.09 21.3406
|
||||
5141.61 21.5384
|
||||
5158.19 21.7383
|
||||
5174.82 21.9400
|
||||
5191.50 22.1436
|
||||
5208.24 22.3491
|
||||
5225.03 22.5565
|
||||
5241.88 22.7659
|
||||
5258.78 22.9772
|
||||
5275.73 23.1905
|
||||
5292.74 23.4057
|
||||
5309.81 23.6231
|
||||
5326.93 23.8425
|
||||
5344.10 24.0641
|
||||
5361.33 24.2876
|
||||
5378.62 24.5133
|
||||
5395.96 24.7411
|
||||
5413.35 24.9712
|
||||
5430.81 25.2034
|
||||
5448.32 25.4377
|
||||
5465.88 25.6742
|
||||
5483.50 25.9131
|
||||
5501.18 26.1546
|
||||
5518.92 26.3982
|
||||
5536.71 26.6441
|
||||
5554.56 26.8923
|
||||
5572.47 27.1429
|
||||
5590.44 27.3957
|
||||
5608.46 27.6508
|
||||
5626.54 27.9084
|
||||
5644.68 28.1683
|
||||
5662.88 28.4308
|
||||
5681.14 28.6958
|
||||
5699.46 28.9633
|
||||
5717.83 29.2334
|
||||
5736.27 29.5059
|
||||
5754.76 29.7811
|
||||
5773.31 30.0590
|
||||
5791.93 30.3395
|
||||
5810.60 30.6226
|
||||
5829.33 30.9083
|
||||
5848.13 31.1968
|
||||
5866.98 31.4883
|
||||
5885.90 31.7823
|
||||
5904.88 32.0791
|
||||
5923.91 32.3787
|
||||
5943.01 32.6814
|
||||
5962.17 32.9873
|
||||
5981.40 33.2960
|
||||
6000.68 33.6076
|
||||
6020.03 33.9221
|
||||
6039.44 34.2394
|
||||
6058.91 34.5595
|
||||
6078.44 34.8826
|
||||
6098.04 35.2086
|
||||
6117.70 35.5378
|
||||
6137.42 35.8704
|
||||
6157.21 36.2065
|
||||
6177.06 36.5457
|
||||
6196.98 36.8881
|
||||
6216.96 37.2338
|
||||
6237.00 37.5828
|
||||
6257.11 37.9350
|
||||
6277.28 38.2906
|
||||
6297.52 38.6496
|
||||
6317.82 39.0118
|
||||
6338.19 39.3777
|
||||
6358.63 39.7471
|
||||
6379.13 40.1200
|
||||
6399.69 40.4964
|
||||
6420.33 40.8763
|
||||
6441.03 41.2599
|
||||
6461.79 41.6472
|
||||
6482.63 42.0382
|
||||
6503.53 42.4328
|
||||
6524.49 42.8311
|
||||
6545.53 43.2333
|
||||
6566.63 43.6396
|
||||
6587.80 44.0496
|
||||
6609.04 44.4635
|
||||
6630.35 44.8813
|
||||
6651.73 45.3031
|
||||
6673.17 45.7289
|
||||
6694.69 46.1588
|
||||
6716.27 46.5926
|
||||
6737.93 47.0306
|
||||
6759.65 47.4730
|
||||
6781.44 47.9197
|
||||
6803.31 48.3706
|
||||
6825.24 48.8258
|
||||
6847.25 49.2852
|
||||
6869.32 49.7491
|
||||
6891.47 50.2174
|
||||
6913.69 50.6902
|
||||
6935.98 51.1675
|
||||
6958.34 51.6491
|
||||
6980.77 52.1356
|
||||
7003.28 52.6268
|
||||
7025.86 53.1226
|
||||
7048.51 53.6231
|
||||
7071.24 54.1282
|
||||
7094.03 54.6384
|
||||
7116.91 55.1537
|
||||
7139.85 55.6736
|
||||
7162.87 56.1985
|
||||
7185.96 56.7283
|
||||
7209.13 57.2635
|
||||
7232.38 57.8038
|
||||
7255.69 58.3491
|
||||
7279.09 58.8996
|
||||
7302.55 59.4554
|
||||
7326.10 60.0165
|
||||
7349.72 60.5832
|
||||
7373.41 61.1551
|
||||
7397.19 61.7324
|
||||
7421.03 62.3153
|
||||
7444.96 62.9039
|
||||
7468.96 63.4982
|
||||
7493.04 64.0981
|
||||
7517.20 64.7037
|
||||
7541.44 65.3149
|
||||
7565.75 65.9323
|
||||
7590.14 66.5556
|
||||
7614.62 67.1849
|
||||
7639.17 67.8201
|
||||
7663.79 68.4612
|
||||
7688.50 69.1087
|
||||
7713.29 69.7625
|
||||
7738.16 70.4224
|
||||
7763.11 71.0884
|
||||
7788.14 71.7608
|
||||
7813.25 72.4400
|
||||
7838.44 73.1259
|
||||
7863.71 73.8182
|
||||
7889.06 74.5170
|
||||
7914.50 75.2224
|
||||
7940.01 75.9348
|
||||
7965.61 76.6538
|
||||
7991.29 77.3796
|
||||
8017.06 78.1123
|
||||
8042.91 78.8520
|
||||
8068.84 79.5991
|
||||
8094.85 80.3534
|
||||
8120.95 81.1149
|
||||
8147.13 81.8836
|
||||
8173.40 82.6595
|
||||
8199.75 83.4434
|
||||
8226.19 84.2349
|
||||
8252.71 85.0338
|
||||
8279.32 85.8404
|
||||
8306.01 86.6546
|
||||
8332.79 87.4766
|
||||
8359.65 88.3065
|
||||
8386.60 89.1443
|
||||
8413.64 89.9900
|
||||
8440.77 90.8436
|
||||
8467.98 91.7062
|
||||
8495.29 92.5774
|
||||
8522.67 93.4567
|
||||
8550.15 94.3443
|
||||
8577.72 95.2403
|
||||
8605.37 96.1451
|
||||
8633.12 97.0584
|
||||
8660.95 97.9803
|
||||
8688.87 98.9109
|
||||
8716.89 99.8503
|
||||
8744.99 100.799
|
||||
8773.19 101.757
|
||||
8801.47 102.724
|
||||
8829.85 103.700
|
||||
8858.32 104.686
|
||||
8886.88 105.681
|
||||
8915.53 106.687
|
||||
8944.27 107.701
|
||||
8973.11 108.726
|
||||
9002.04 109.760
|
||||
9031.06 110.804
|
||||
9060.18 111.859
|
||||
9089.39 112.924
|
||||
9118.69 113.998
|
||||
9148.09 115.083
|
||||
9177.59 116.179
|
||||
9207.18 117.285
|
||||
9236.86 118.401
|
||||
9266.64 119.529
|
||||
9296.52 120.666
|
||||
9326.49 121.816
|
||||
9356.56 122.976
|
||||
9386.72 124.148
|
||||
9416.99 125.330
|
||||
9447.35 126.524
|
||||
9477.81 127.730
|
||||
9508.37 128.946
|
||||
9539.02 130.174
|
||||
9569.78 131.414
|
||||
9600.63 132.666
|
||||
9631.58 133.931
|
||||
9662.63 135.209
|
||||
9693.79 136.498
|
||||
9725.04 137.800
|
||||
9756.39 139.115
|
||||
9787.85 140.441
|
||||
9819.41 141.780
|
||||
9851.07 143.131
|
||||
9882.83 144.496
|
||||
9914.69 145.873
|
||||
9946.65 147.265
|
||||
9978.72 148.671
|
||||
10010.9 150.090
|
||||
10043.2 151.522
|
||||
10075.5 152.968
|
||||
10108.0 154.429
|
||||
10140.6 155.904
|
||||
10173.3 157.393
|
||||
10206.1 158.896
|
||||
10239.0 160.413
|
||||
10272.0 161.946
|
||||
10305.2 163.493
|
||||
10338.4 165.055
|
||||
10371.7 166.632
|
||||
10405.1 168.224
|
||||
10438.7 169.832
|
||||
10472.3 171.455
|
||||
10506.1 173.093
|
||||
10540.0 174.747
|
||||
10574.0 176.417
|
||||
10608.1 178.104
|
||||
10642.3 179.808
|
||||
10676.6 181.527
|
||||
10711.0 183.263
|
||||
10745.5 185.015
|
||||
10780.2 186.785
|
||||
10814.9 188.572
|
||||
10849.8 190.376
|
||||
10884.8 192.197
|
||||
10919.9 194.035
|
||||
10955.1 195.892
|
||||
10990.4 197.767
|
||||
11025.8 199.659
|
||||
11061.4 201.570
|
||||
11097.0 203.498
|
||||
11132.8 205.447
|
||||
11168.7 207.414
|
||||
11204.7 209.400
|
||||
11240.8 211.405
|
||||
11277.1 213.429
|
||||
11313.4 215.474
|
||||
11349.9 217.538
|
||||
11386.5 219.622
|
||||
11423.2 221.725
|
||||
11460.0 223.849
|
||||
11497.0 225.994
|
||||
11534.1 228.159
|
||||
11571.2 230.344
|
||||
11608.6 232.550
|
||||
11646.0 234.778
|
||||
11683.5 237.028
|
||||
11721.2 239.300
|
||||
11759.0 241.593
|
||||
11796.9 243.908
|
||||
11834.9 246.246
|
||||
11873.1 248.607
|
||||
11911.4 250.991
|
||||
11949.8 253.398
|
||||
11988.3 255.827
|
||||
12026.9 258.280
|
||||
12065.7 260.757
|
||||
12104.6 263.257
|
||||
12143.7 265.781
|
||||
12182.8 268.329
|
||||
12222.1 270.902
|
||||
12261.5 273.502
|
||||
12301.0 276.126
|
||||
12340.7 278.776
|
||||
12380.5 281.450
|
||||
12420.4 284.150
|
||||
12460.4 286.877
|
||||
12500.6 289.629
|
||||
12540.9 292.408
|
||||
12581.3 295.212
|
||||
12621.9 298.045
|
||||
12662.6 300.906
|
||||
12703.4 303.794
|
||||
12744.4 306.709
|
||||
12785.5 309.653
|
||||
12826.7 312.624
|
||||
12868.0 315.625
|
||||
12909.5 318.655
|
||||
12951.1 321.713
|
||||
12992.9 324.800
|
||||
13034.8 327.917
|
||||
13076.8 331.067
|
||||
13119.0 334.246
|
||||
13161.3 337.456
|
||||
13203.7 340.696
|
||||
13246.3 343.968
|
||||
13289.0 347.271
|
||||
13331.8 350.607
|
||||
13374.8 353.973
|
||||
13417.9 357.371
|
||||
13461.2 360.802
|
||||
13504.6 364.269
|
||||
13548.1 367.769
|
||||
13591.8 371.301
|
||||
13635.6 374.867
|
||||
13679.6 378.467
|
||||
13723.7 382.103
|
||||
13767.9 385.773
|
||||
13812.3 389.478
|
||||
13856.9 393.217
|
||||
13901.5 396.994
|
||||
13946.4 400.809
|
||||
13991.3 404.660
|
||||
14036.4 408.548
|
||||
14081.7 412.472
|
||||
14127.1 416.434
|
||||
14172.6 420.436
|
||||
14218.3 424.475
|
||||
14264.2 428.552
|
||||
14310.2 432.668
|
||||
14356.3 436.824
|
||||
14402.6 441.022
|
||||
14449.0 445.260
|
||||
14495.6 449.539
|
||||
14542.3 453.858
|
||||
14589.2 458.217
|
||||
14636.2 462.619
|
||||
14683.4 467.064
|
||||
14730.8 471.549
|
||||
14778.3 476.076
|
||||
14825.9 480.648
|
||||
14873.7 485.268
|
||||
14921.7 489.931
|
||||
14969.8 494.637
|
||||
15018.0 499.389
|
||||
15066.5 504.191
|
||||
15115.0 509.040
|
||||
15163.8 513.935
|
||||
15212.7 518.876
|
||||
15261.7 523.864
|
||||
15310.9 528.901
|
||||
15360.3 533.988
|
||||
15409.8 539.123
|
||||
15459.5 544.306
|
||||
15509.3 549.539
|
||||
15559.3 554.823
|
||||
15609.5 560.159
|
||||
15659.8 565.546
|
||||
15710.3 570.984
|
||||
15761.0 576.473
|
||||
15811.8 582.014
|
||||
15862.7 587.611
|
||||
15913.9 593.262
|
||||
15965.2 598.964
|
||||
16016.7 604.719
|
||||
16068.3 610.531
|
||||
16120.1 616.402
|
||||
16172.1 622.326
|
||||
16224.2 628.306
|
||||
16276.5 634.342
|
||||
16329.0 640.437
|
||||
16381.7 646.596
|
||||
16434.5 652.811
|
||||
16487.5 659.084
|
||||
16540.6 665.416
|
||||
16593.9 671.811
|
||||
16647.4 678.268
|
||||
16701.1 684.786
|
||||
16755.0 691.364
|
||||
16809.0 698.004
|
||||
16863.2 704.710
|
||||
16917.5 711.482
|
||||
16972.1 718.318
|
||||
17026.8 725.217
|
||||
17081.7 732.182
|
||||
17136.8 739.214
|
||||
17192.0 746.315
|
||||
17247.4 753.482
|
||||
17303.1 760.717
|
||||
17358.8 768.018
|
||||
17414.8 775.391
|
||||
17470.9 782.836
|
||||
17527.3 790.351
|
||||
17583.8 797.934
|
||||
17640.5 805.587
|
||||
17697.4 813.318
|
||||
17754.4 821.128
|
||||
17811.6 829.008
|
||||
17869.1 836.961
|
||||
17926.7 844.987
|
||||
17984.5 853.094
|
||||
18042.5 861.278
|
||||
18100.6 869.538
|
||||
18159.0 877.874
|
||||
18217.5 886.286
|
||||
18276.3 894.784
|
||||
18335.2 903.364
|
||||
18394.3 912.023
|
||||
18453.6 920.761
|
||||
18513.1 929.582
|
||||
18572.8 938.489
|
||||
18632.7 947.484
|
||||
18692.8 956.561
|
||||
18753.0 965.723
|
||||
18813.5 974.967
|
||||
18874.1 984.303
|
||||
18935.0 993.729
|
||||
18996.0 1003.24
|
||||
19057.3 1012.84
|
||||
19118.7 1022.53
|
||||
19180.4 1032.31
|
||||
19242.2 1042.19
|
||||
19304.2 1052.16
|
||||
19366.5 1062.22
|
||||
19428.9 1072.37
|
||||
19491.6 1082.63
|
||||
19554.4 1092.98
|
||||
19617.4 1103.42
|
||||
19680.7 1113.96
|
||||
19744.1 1124.59
|
||||
19807.8 1135.38
|
||||
19871.7 1146.29
|
||||
19935.7 1157.29
|
||||
20000.0 1168.40
|
||||
20081.3 1182.56
|
||||
20162.8 1196.82
|
||||
20244.8 1211.23
|
||||
20327.0 1225.81
|
||||
20409.6 1240.55
|
||||
20492.5 1255.47
|
||||
20575.8 1270.57
|
||||
20659.4 1285.83
|
||||
20743.3 1301.26
|
||||
20827.6 1316.88
|
||||
20912.2 1332.67
|
||||
20997.2 1348.64
|
||||
21082.5 1364.79
|
||||
21168.1 1381.13
|
||||
21254.1 1397.65
|
||||
21340.5 1414.36
|
||||
21427.2 1431.26
|
||||
21514.3 1448.35
|
||||
21601.7 1465.63
|
||||
21689.4 1483.11
|
||||
21777.6 1500.78
|
||||
21866.0 1518.66
|
||||
21954.9 1536.73
|
||||
22044.1 1555.01
|
||||
22133.6 1573.50
|
||||
22223.6 1592.19
|
||||
22313.9 1611.09
|
||||
22404.5 1630.19
|
||||
22495.5 1649.51
|
||||
22586.9 1669.05
|
||||
22678.7 1688.80
|
||||
22770.8 1708.77
|
||||
22863.4 1728.96
|
||||
22956.3 1749.38
|
||||
23049.5 1770.02
|
||||
23143.2 1790.88
|
||||
23237.2 1811.97
|
||||
23331.6 1833.30
|
||||
23426.4 1854.85
|
||||
23521.6 1876.64
|
||||
23617.1 1898.66
|
||||
23713.1 1920.93
|
||||
23809.4 1943.44
|
||||
23906.2 1966.19
|
||||
24003.3 1989.19
|
||||
24100.8 2012.43
|
||||
24198.7 2035.93
|
||||
24297.1 2059.67
|
||||
24395.8 2083.67
|
||||
24494.9 2107.92
|
||||
24594.4 2132.44
|
||||
24694.3 2157.20
|
||||
24794.7 2182.24
|
||||
24895.4 2207.53
|
||||
24996.6 2233.09
|
||||
25098.1 2258.92
|
||||
25200.1 2285.02
|
||||
25302.5 2311.40
|
||||
25405.3 2338.05
|
||||
25508.5 2364.98
|
||||
25612.1 2392.19
|
||||
25716.2 2419.68
|
||||
25820.7 2447.45
|
||||
25925.6 2475.50
|
||||
26030.9 2503.85
|
||||
26136.7 2532.50
|
||||
26242.9 2561.42
|
||||
26349.5 2590.64
|
||||
26456.5 2620.16
|
||||
26564.0 2649.97
|
||||
26672.0 2680.08
|
||||
26780.3 2710.49
|
||||
26889.1 2741.21
|
||||
26998.4 2772.24
|
||||
27108.1 2803.57
|
||||
27218.2 2835.21
|
||||
27328.8 2867.17
|
||||
27439.8 2899.44
|
||||
27551.3 2932.02
|
||||
27663.2 2964.91
|
||||
27775.6 2998.14
|
||||
27888.5 3031.69
|
||||
28001.8 3065.56
|
||||
28115.6 3099.75
|
||||
28229.8 3134.27
|
||||
28344.5 3169.12
|
||||
28459.6 3204.30
|
||||
28575.3 3239.80
|
||||
28691.4 3275.66
|
||||
28807.9 3311.85
|
||||
28925.0 3348.37
|
||||
29042.5 3385.23
|
||||
29160.5 3422.44
|
||||
29279.0 3459.98
|
||||
29397.9 3497.87
|
||||
29517.4 3536.09
|
||||
29637.3 3574.69
|
||||
29757.7 3613.62
|
||||
29878.6 3652.90
|
||||
30000.0 3692.52
|
||||
@@ -1,713 +0,0 @@
|
||||
cr Density=7.19, Angle=90.deg
|
||||
Photon Energy (eV), Atten Length (microns)
|
||||
4000.00 7.36948
|
||||
4012.90 7.43487
|
||||
4025.83 7.50083
|
||||
4038.81 7.56737
|
||||
4051.83 7.63450
|
||||
4064.90 7.70224
|
||||
4078.00 7.77057
|
||||
4091.15 7.83951
|
||||
4104.34 7.90905
|
||||
4117.57 7.97916
|
||||
4130.85 8.04989
|
||||
4144.17 8.12124
|
||||
4157.53 8.19322
|
||||
4170.93 8.26584
|
||||
4184.38 8.33903
|
||||
4197.87 8.41287
|
||||
4211.41 8.48736
|
||||
4224.98 8.56251
|
||||
4238.60 8.63834
|
||||
4252.27 8.71478
|
||||
4265.98 8.79189
|
||||
4279.73 8.86969
|
||||
4293.53 8.94819
|
||||
4307.37 9.02737
|
||||
4321.26 9.10723
|
||||
4335.19 9.18779
|
||||
4349.17 9.26908
|
||||
4363.19 9.35108
|
||||
4377.26 9.43379
|
||||
4391.37 9.51723
|
||||
4405.53 9.60141
|
||||
4419.73 9.68634
|
||||
4433.98 9.77201
|
||||
4448.28 9.85842
|
||||
4462.62 9.94550
|
||||
4477.01 10.0334
|
||||
4491.44 10.1220
|
||||
4505.92 10.2114
|
||||
4520.45 10.3016
|
||||
4535.02 10.3923
|
||||
4549.65 10.4839
|
||||
4564.31 10.5763
|
||||
4579.03 10.6695
|
||||
4593.79 10.7635
|
||||
4608.60 10.8584
|
||||
4623.46 10.9541
|
||||
4638.37 11.0507
|
||||
4653.32 11.1481
|
||||
4668.33 11.2464
|
||||
4683.38 11.3454
|
||||
4698.48 11.4454
|
||||
4713.62 11.5462
|
||||
4728.82 11.6479
|
||||
4744.07 11.7504
|
||||
4759.36 11.8538
|
||||
4774.71 11.9581
|
||||
4790.10 12.0633
|
||||
4805.54 12.1694
|
||||
4821.04 12.2764
|
||||
4836.58 12.3843
|
||||
4852.17 12.4932
|
||||
4867.82 12.6031
|
||||
4883.51 12.7139
|
||||
4899.26 12.8256
|
||||
4915.05 12.9381
|
||||
4930.90 13.0516
|
||||
4946.80 13.1662
|
||||
4962.75 13.2817
|
||||
4978.75 13.3982
|
||||
4994.80 13.5157
|
||||
5010.90 13.6342
|
||||
5027.06 13.7537
|
||||
5043.26 13.8743
|
||||
5059.52 13.9959
|
||||
5075.84 14.1184
|
||||
5092.20 14.2419
|
||||
5108.62 14.3665
|
||||
5125.09 14.4922
|
||||
5141.61 14.6190
|
||||
5158.19 14.7468
|
||||
5174.82 14.8758
|
||||
5191.50 15.0059
|
||||
5208.24 15.1371
|
||||
5225.03 15.2694
|
||||
5241.88 15.4027
|
||||
5258.78 15.5371
|
||||
5275.73 15.6728
|
||||
5292.74 15.8096
|
||||
5309.81 15.9475
|
||||
5326.93 16.0865
|
||||
5344.10 16.2268
|
||||
5361.33 16.3682
|
||||
5378.62 16.5109
|
||||
5395.96 16.6547
|
||||
5413.35 16.7997
|
||||
5430.81 16.9460
|
||||
5448.32 17.0935
|
||||
5465.88 17.2423
|
||||
5483.50 17.3923
|
||||
5501.18 17.5434
|
||||
5518.92 17.6959
|
||||
5536.71 17.8497
|
||||
5554.56 18.0048
|
||||
5572.47 18.1612
|
||||
5590.44 18.3188
|
||||
5608.46 18.4778
|
||||
5626.54 18.6382
|
||||
5644.68 18.7999
|
||||
5662.88 18.9630
|
||||
5681.14 19.1272
|
||||
5699.46 19.2929
|
||||
5717.83 19.4600
|
||||
5736.27 19.6286
|
||||
5754.76 19.7984
|
||||
5773.31 19.9696
|
||||
5791.93 20.1423
|
||||
5810.60 20.3164
|
||||
5829.33 20.4921
|
||||
5848.13 20.6691
|
||||
5866.98 20.8476
|
||||
5885.90 21.0276
|
||||
5904.88 21.2091
|
||||
5923.91 21.3922
|
||||
5943.01 21.5768
|
||||
5962.17 21.7631
|
||||
5981.40 21.9510
|
||||
5985.00 21.9863
|
||||
5985.10 21.9873
|
||||
5985.20 21.9883
|
||||
5985.30 21.9892
|
||||
5985.40 21.9902
|
||||
5985.50 21.9912
|
||||
5985.60 21.9922
|
||||
5985.70 21.9932
|
||||
5985.80 21.9941
|
||||
5985.90 21.9951
|
||||
5986.00 21.9961
|
||||
5986.10 21.9971
|
||||
5986.20 21.9981
|
||||
5986.30 21.9990
|
||||
5986.40 22.0000
|
||||
5986.50 22.0010
|
||||
5986.60 22.0020
|
||||
5986.70 22.0030
|
||||
5986.80 22.0040
|
||||
5986.90 22.0049
|
||||
5987.00 22.0059
|
||||
5987.10 22.0069
|
||||
5987.20 22.0079
|
||||
5987.30 22.0088
|
||||
5987.40 22.0098
|
||||
5987.50 22.0108
|
||||
5987.60 22.0118
|
||||
5987.70 22.0128
|
||||
5987.80 22.0138
|
||||
5987.90 22.0147
|
||||
5988.00 22.0157
|
||||
5988.10 22.0167
|
||||
5988.20 22.0177
|
||||
5988.30 22.0187
|
||||
5988.40 22.0196
|
||||
5988.50 22.0206
|
||||
5988.60 22.0216
|
||||
5988.70 22.0226
|
||||
5988.80 22.0236
|
||||
5988.90 22.0245
|
||||
5989.00 22.0255
|
||||
5989.10 22.0265
|
||||
5989.11 20.7400
|
||||
5989.12 18.3877
|
||||
5989.13 16.3020
|
||||
5989.14 14.4528
|
||||
5989.15 13.6083
|
||||
5989.16 12.0645
|
||||
5989.17 10.6957
|
||||
5989.18 9.48212
|
||||
5989.19 8.40620
|
||||
5989.20 7.91492
|
||||
5989.30 2.84297
|
||||
5989.40 2.67688
|
||||
5989.50 2.67699
|
||||
5989.60 2.67710
|
||||
5989.70 2.67720
|
||||
5989.80 2.67732
|
||||
5989.90 2.67742
|
||||
5990.00 2.67753
|
||||
5990.10 2.67764
|
||||
5990.20 2.67775
|
||||
5990.30 2.67786
|
||||
5990.40 2.67797
|
||||
5990.50 2.67808
|
||||
5990.60 2.67819
|
||||
5990.70 2.67829
|
||||
5990.80 2.67841
|
||||
5990.90 2.67851
|
||||
5991.00 2.67862
|
||||
5991.10 2.67874
|
||||
5991.20 2.67884
|
||||
5991.30 2.67895
|
||||
5991.40 2.67906
|
||||
5991.50 2.67917
|
||||
5991.60 2.67928
|
||||
5991.70 2.67939
|
||||
5991.80 2.67950
|
||||
5991.90 2.67961
|
||||
5992.00 2.67972
|
||||
5992.10 2.67983
|
||||
5992.20 2.67993
|
||||
5992.30 2.68004
|
||||
5992.40 2.68015
|
||||
5992.50 2.68026
|
||||
5992.60 2.68037
|
||||
5992.70 2.68048
|
||||
5992.80 2.68059
|
||||
5992.90 2.68070
|
||||
5993.00 2.68081
|
||||
5993.10 2.68092
|
||||
5993.20 2.68103
|
||||
5993.30 2.68114
|
||||
5993.40 2.68124
|
||||
5993.50 2.68136
|
||||
5993.60 2.68146
|
||||
5993.70 2.68157
|
||||
5993.80 2.68168
|
||||
5993.90 2.68179
|
||||
5994.00 2.68190
|
||||
5994.10 2.68201
|
||||
5994.20 2.68212
|
||||
5994.30 2.68223
|
||||
5994.40 2.68234
|
||||
5994.50 2.68245
|
||||
5994.60 2.68256
|
||||
5994.70 2.68267
|
||||
5994.80 2.68277
|
||||
5994.90 2.68289
|
||||
5995.00 2.68299
|
||||
6000.68 2.68920
|
||||
6020.03 2.71041
|
||||
6039.44 2.73179
|
||||
6058.91 2.75333
|
||||
6078.44 2.77504
|
||||
6098.04 2.79692
|
||||
6117.70 2.81898
|
||||
6137.42 2.84127
|
||||
6157.21 2.86379
|
||||
6177.06 2.88648
|
||||
6196.98 2.90936
|
||||
6216.96 2.93243
|
||||
6237.00 2.95574
|
||||
6257.11 2.97929
|
||||
6277.28 3.00304
|
||||
6297.52 3.02698
|
||||
6317.82 3.05110
|
||||
6338.19 3.07550
|
||||
6358.63 3.10015
|
||||
6379.13 3.12500
|
||||
6399.69 3.15005
|
||||
6420.33 3.17530
|
||||
6441.03 3.20082
|
||||
6461.79 3.22661
|
||||
6482.63 3.25261
|
||||
6503.53 3.27882
|
||||
6524.49 3.30523
|
||||
6545.53 3.33194
|
||||
6566.63 3.35893
|
||||
6587.80 3.38613
|
||||
6609.04 3.41355
|
||||
6630.35 3.44119
|
||||
6651.73 3.46915
|
||||
6673.17 3.49738
|
||||
6694.69 3.52584
|
||||
6716.27 3.55453
|
||||
6737.93 3.58346
|
||||
6759.65 3.61269
|
||||
6781.44 3.64220
|
||||
6803.31 3.67196
|
||||
6825.24 3.70197
|
||||
6847.25 3.73221
|
||||
6869.32 3.76280
|
||||
6891.47 3.79370
|
||||
6913.69 3.82485
|
||||
6935.98 3.85626
|
||||
6958.34 3.88793
|
||||
6980.77 3.91995
|
||||
7003.28 3.95230
|
||||
7025.86 3.98491
|
||||
7048.51 4.01779
|
||||
7071.24 4.05094
|
||||
7094.03 4.08445
|
||||
7116.91 4.11828
|
||||
7139.85 4.15239
|
||||
7162.87 4.18677
|
||||
7185.96 4.22145
|
||||
7209.13 4.25650
|
||||
7232.38 4.29188
|
||||
7255.69 4.32756
|
||||
7279.09 4.36353
|
||||
7302.55 4.39981
|
||||
7326.10 4.43648
|
||||
7349.72 4.47351
|
||||
7373.41 4.51084
|
||||
7397.19 4.54849
|
||||
7421.03 4.58645
|
||||
7444.96 4.62485
|
||||
7468.96 4.66363
|
||||
7493.04 4.70273
|
||||
7517.20 4.74216
|
||||
7541.44 4.78192
|
||||
7565.75 4.82210
|
||||
7590.14 4.86264
|
||||
7614.62 4.90353
|
||||
7639.17 4.94475
|
||||
7663.79 4.98633
|
||||
7688.50 5.02838
|
||||
7713.29 5.07085
|
||||
7738.16 5.11367
|
||||
7763.11 5.15684
|
||||
7788.14 5.20039
|
||||
7813.25 5.24440
|
||||
7838.44 5.28883
|
||||
7863.71 5.33362
|
||||
7889.06 5.37880
|
||||
7914.50 5.42435
|
||||
7940.01 5.47042
|
||||
7965.61 5.51690
|
||||
7991.29 5.56378
|
||||
8017.06 5.61105
|
||||
8042.91 5.65874
|
||||
8068.84 5.70693
|
||||
8094.85 5.75557
|
||||
8120.95 5.80462
|
||||
8147.13 5.85410
|
||||
8173.40 5.90399
|
||||
8199.75 5.95444
|
||||
8226.19 6.00537
|
||||
8252.71 6.05673
|
||||
8279.32 6.10854
|
||||
8306.01 6.16078
|
||||
8332.79 6.21357
|
||||
8359.65 6.26684
|
||||
8386.60 6.32058
|
||||
8413.64 6.37477
|
||||
8440.77 6.42942
|
||||
8467.98 6.48472
|
||||
8495.29 6.54056
|
||||
8522.67 6.59686
|
||||
8550.15 6.65365
|
||||
8577.72 6.71093
|
||||
8605.37 6.76883
|
||||
8633.12 6.82725
|
||||
8660.95 6.88617
|
||||
8688.87 6.94559
|
||||
8716.89 7.00554
|
||||
8744.99 7.06612
|
||||
8773.19 7.12724
|
||||
8801.47 7.18888
|
||||
8829.85 7.25106
|
||||
8858.32 7.31379
|
||||
8886.88 7.37722
|
||||
8915.53 7.44124
|
||||
8944.27 7.50581
|
||||
8973.11 7.57095
|
||||
9002.04 7.63665
|
||||
9031.06 7.70305
|
||||
9060.18 7.77005
|
||||
9089.39 7.83764
|
||||
9118.69 7.90581
|
||||
9148.09 7.97457
|
||||
9177.59 8.04414
|
||||
9207.18 8.11433
|
||||
9236.86 8.18516
|
||||
9266.64 8.25659
|
||||
9296.52 8.32864
|
||||
9326.49 8.40145
|
||||
9356.56 8.47492
|
||||
9386.72 8.54902
|
||||
9416.99 8.62377
|
||||
9447.35 8.69918
|
||||
9477.81 8.77535
|
||||
9508.37 8.85219
|
||||
9539.02 8.92970
|
||||
9569.78 9.00788
|
||||
9600.63 9.08677
|
||||
9631.58 9.16655
|
||||
9662.63 9.24704
|
||||
9693.79 9.32825
|
||||
9725.04 9.41018
|
||||
9756.39 9.49281
|
||||
9787.85 9.57645
|
||||
9819.41 9.66083
|
||||
9851.07 9.74598
|
||||
9882.83 9.83186
|
||||
9914.69 9.91849
|
||||
9946.65 10.0060
|
||||
9978.72 10.0943
|
||||
10010.9 10.1834
|
||||
10043.2 10.2733
|
||||
10075.5 10.3640
|
||||
10108.0 10.4556
|
||||
10140.6 10.5481
|
||||
10173.3 10.6413
|
||||
10206.1 10.7354
|
||||
10239.0 10.8303
|
||||
10272.0 10.9263
|
||||
10305.2 11.0231
|
||||
10338.4 11.1208
|
||||
10371.7 11.2194
|
||||
10405.1 11.3188
|
||||
10438.7 11.4194
|
||||
10472.3 11.5208
|
||||
10506.1 11.6230
|
||||
10540.0 11.7262
|
||||
10574.0 11.8304
|
||||
10608.1 11.9356
|
||||
10642.3 12.0418
|
||||
10676.6 12.1490
|
||||
10711.0 12.2571
|
||||
10745.5 12.3661
|
||||
10780.2 12.4764
|
||||
10814.9 12.5876
|
||||
10849.8 12.6998
|
||||
10884.8 12.8130
|
||||
10919.9 12.9272
|
||||
10955.1 13.0427
|
||||
10990.4 13.1592
|
||||
11025.8 13.2768
|
||||
11061.4 13.3954
|
||||
11097.0 13.5150
|
||||
11132.8 13.6360
|
||||
11168.7 13.7580
|
||||
11204.7 13.8811
|
||||
11240.8 14.0053
|
||||
11277.1 14.1307
|
||||
11313.4 14.2574
|
||||
11349.9 14.3852
|
||||
11386.5 14.5142
|
||||
11423.2 14.6443
|
||||
11460.0 14.7757
|
||||
11497.0 14.9083
|
||||
11534.1 15.0422
|
||||
11571.2 15.1773
|
||||
11608.6 15.3135
|
||||
11646.0 15.4511
|
||||
11683.5 15.5901
|
||||
11721.2 15.7303
|
||||
11759.0 15.8718
|
||||
11796.9 16.0146
|
||||
11834.9 16.1587
|
||||
11873.1 16.3043
|
||||
11911.4 16.4513
|
||||
11949.8 16.5995
|
||||
11988.3 16.7491
|
||||
12026.9 16.9000
|
||||
12065.7 17.0525
|
||||
12104.6 17.2064
|
||||
12143.7 17.3617
|
||||
12182.8 17.5184
|
||||
12222.1 17.6765
|
||||
12261.5 17.8363
|
||||
12301.0 17.9976
|
||||
12340.7 18.1603
|
||||
12380.5 18.3245
|
||||
12420.4 18.4901
|
||||
12460.4 18.6575
|
||||
12500.6 18.8264
|
||||
12540.9 18.9968
|
||||
12581.3 19.1688
|
||||
12621.9 19.3424
|
||||
12662.6 19.5177
|
||||
12703.4 19.6947
|
||||
12744.4 19.8733
|
||||
12785.5 20.0535
|
||||
12826.7 20.2353
|
||||
12868.0 20.4190
|
||||
12909.5 20.6044
|
||||
12951.1 20.7915
|
||||
12992.9 20.9802
|
||||
13034.8 21.1708
|
||||
13076.8 21.3633
|
||||
13119.0 21.5576
|
||||
13161.3 21.7536
|
||||
13203.7 21.9515
|
||||
13246.3 22.1512
|
||||
13289.0 22.3528
|
||||
13331.8 22.5564
|
||||
13374.8 22.7618
|
||||
13417.9 22.9690
|
||||
13461.2 23.1782
|
||||
13504.6 23.3896
|
||||
13548.1 23.6029
|
||||
13591.8 23.8181
|
||||
13635.6 24.0353
|
||||
13679.6 24.2545
|
||||
13723.7 24.4759
|
||||
13767.9 24.6994
|
||||
13812.3 24.9248
|
||||
13856.9 25.1523
|
||||
13901.5 25.3820
|
||||
13946.4 25.6141
|
||||
13991.3 25.8482
|
||||
14036.4 26.0845
|
||||
14081.7 26.3230
|
||||
14127.1 26.5638
|
||||
14172.6 26.8069
|
||||
14218.3 27.0522
|
||||
14264.2 27.2998
|
||||
14310.2 27.5497
|
||||
14356.3 27.8019
|
||||
14402.6 28.0567
|
||||
14449.0 28.3139
|
||||
14495.6 28.5734
|
||||
14542.3 28.8353
|
||||
14589.2 29.0996
|
||||
14636.2 29.3665
|
||||
14683.4 29.6359
|
||||
14730.8 29.9077
|
||||
14778.3 30.1820
|
||||
14825.9 30.4590
|
||||
14873.7 30.7388
|
||||
14921.7 31.0211
|
||||
14969.8 31.3061
|
||||
15018.0 31.5936
|
||||
15066.5 31.8840
|
||||
15115.0 32.1772
|
||||
15163.8 32.4731
|
||||
15212.7 32.7717
|
||||
15261.7 33.0731
|
||||
15310.9 33.3774
|
||||
15360.3 33.6847
|
||||
15409.8 33.9948
|
||||
15459.5 34.3078
|
||||
15509.3 34.6237
|
||||
15559.3 34.9426
|
||||
15609.5 35.2647
|
||||
15659.8 35.5897
|
||||
15710.3 35.9178
|
||||
15761.0 36.2488
|
||||
15811.8 36.5830
|
||||
15862.7 36.9205
|
||||
15913.9 37.2612
|
||||
15965.2 37.6049
|
||||
16016.7 37.9518
|
||||
16068.3 38.3020
|
||||
16120.1 38.6557
|
||||
16172.1 39.0126
|
||||
16224.2 39.3728
|
||||
16276.5 39.7363
|
||||
16329.0 40.1034
|
||||
16381.7 40.4742
|
||||
16434.5 40.8483
|
||||
16487.5 41.2259
|
||||
16540.6 41.6069
|
||||
16593.9 41.9918
|
||||
16647.4 42.3803
|
||||
16701.1 42.7724
|
||||
16755.0 43.1681
|
||||
16809.0 43.5676
|
||||
16863.2 43.9708
|
||||
16917.5 44.3781
|
||||
16972.1 44.7890
|
||||
17026.8 45.2038
|
||||
17081.7 45.6225
|
||||
17136.8 46.0452
|
||||
17192.0 46.4720
|
||||
17247.4 46.9028
|
||||
17303.1 47.3376
|
||||
17358.8 47.7763
|
||||
17414.8 48.2193
|
||||
17470.9 48.6665
|
||||
17527.3 49.1180
|
||||
17583.8 49.5735
|
||||
17640.5 50.0333
|
||||
17697.4 50.4976
|
||||
17754.4 50.9666
|
||||
17811.6 51.4398
|
||||
17869.1 51.9174
|
||||
17926.7 52.3995
|
||||
17984.5 52.8862
|
||||
18042.5 53.3776
|
||||
18100.6 53.8735
|
||||
18159.0 54.3740
|
||||
18217.5 54.8791
|
||||
18276.3 55.3893
|
||||
18335.2 55.9043
|
||||
18394.3 56.4241
|
||||
18453.6 56.9488
|
||||
18513.1 57.4783
|
||||
18572.8 58.0130
|
||||
18632.7 58.5529
|
||||
18692.8 59.0978
|
||||
18753.0 59.6479
|
||||
18813.5 60.2029
|
||||
18874.1 60.7633
|
||||
18935.0 61.3292
|
||||
18996.0 61.9003
|
||||
19057.3 62.4767
|
||||
19118.7 63.0584
|
||||
19180.4 63.6458
|
||||
19242.2 64.2390
|
||||
19304.2 64.8376
|
||||
19366.5 65.4417
|
||||
19428.9 66.0514
|
||||
19491.6 66.6670
|
||||
19554.4 67.2887
|
||||
19617.4 67.9160
|
||||
19680.7 68.5491
|
||||
19744.1 69.1881
|
||||
19807.8 69.8335
|
||||
19871.7 70.4851
|
||||
19935.7 71.1427
|
||||
20000.0 71.8064
|
||||
20081.3 72.6515
|
||||
20162.8 73.5071
|
||||
20244.8 74.3726
|
||||
20327.0 75.2484
|
||||
20409.6 76.1342
|
||||
20492.5 77.0315
|
||||
20575.8 77.9393
|
||||
20659.4 78.8575
|
||||
20743.3 79.7867
|
||||
20827.6 80.7275
|
||||
20912.2 81.6791
|
||||
20997.2 82.6420
|
||||
21082.5 83.6161
|
||||
21168.1 84.6024
|
||||
21254.1 85.6003
|
||||
21340.5 86.6097
|
||||
21427.2 87.6312
|
||||
21514.3 88.6652
|
||||
21601.7 89.7114
|
||||
21689.4 90.7699
|
||||
21777.6 91.8408
|
||||
21866.0 92.9252
|
||||
21954.9 94.0222
|
||||
22044.1 95.1321
|
||||
22133.6 96.2552
|
||||
22223.6 97.3920
|
||||
22313.9 98.5423
|
||||
22404.5 99.7058
|
||||
22495.5 100.883
|
||||
22586.9 102.075
|
||||
22678.7 103.281
|
||||
22770.8 104.501
|
||||
22863.4 105.736
|
||||
22956.3 106.986
|
||||
23049.5 108.250
|
||||
23143.2 109.529
|
||||
23237.2 110.824
|
||||
23331.6 112.134
|
||||
23426.4 113.459
|
||||
23521.6 114.800
|
||||
23617.1 116.157
|
||||
23713.1 117.531
|
||||
23809.4 118.921
|
||||
23906.2 120.327
|
||||
24003.3 121.750
|
||||
24100.8 123.190
|
||||
24198.7 124.647
|
||||
24297.1 126.121
|
||||
24395.8 127.612
|
||||
24494.9 129.122
|
||||
24594.4 130.649
|
||||
24694.3 132.194
|
||||
24794.7 133.758
|
||||
24895.4 135.340
|
||||
24996.6 136.941
|
||||
25098.1 138.561
|
||||
25200.1 140.200
|
||||
25302.5 141.859
|
||||
25405.3 143.537
|
||||
25508.5 145.235
|
||||
25612.1 146.954
|
||||
25716.2 148.693
|
||||
25820.7 150.452
|
||||
25925.6 152.232
|
||||
26030.9 154.033
|
||||
26136.7 155.856
|
||||
26242.9 157.700
|
||||
26349.5 159.566
|
||||
26456.5 161.454
|
||||
26564.0 163.364
|
||||
26672.0 165.297
|
||||
26780.3 167.252
|
||||
26889.1 169.231
|
||||
26998.4 171.233
|
||||
27108.1 173.258
|
||||
27218.2 175.308
|
||||
27328.8 177.381
|
||||
27439.8 179.479
|
||||
27551.3 181.602
|
||||
27663.2 183.749
|
||||
27775.6 185.923
|
||||
27888.5 188.122
|
||||
28001.8 190.346
|
||||
28115.6 192.597
|
||||
28229.8 194.874
|
||||
28344.5 197.178
|
||||
28459.6 199.509
|
||||
28575.3 201.867
|
||||
28691.4 204.253
|
||||
28807.9 206.667
|
||||
28925.0 209.110
|
||||
29042.5 211.580
|
||||
29160.5 214.080
|
||||
29279.0 216.610
|
||||
29397.9 219.169
|
||||
29517.4 221.758
|
||||
29637.3 224.377
|
||||
29757.7 227.027
|
||||
29878.6 229.707
|
||||
30000.0 232.418
|
||||
@@ -1,636 +0,0 @@
|
||||
4000.00 3.25746
|
||||
4016.15 3.29307
|
||||
4032.37 3.32906
|
||||
4048.65 3.36546
|
||||
4065.00 3.40227
|
||||
4081.41 3.43947
|
||||
4097.89 3.47709
|
||||
4114.44 3.51514
|
||||
4131.06 3.55362
|
||||
4147.74 3.59251
|
||||
4164.48 3.63183
|
||||
4181.30 3.67160
|
||||
4198.18 3.71182
|
||||
4215.14 3.75247
|
||||
4232.16 3.79357
|
||||
4249.25 3.83514
|
||||
4266.40 3.87716
|
||||
4283.63 3.91965
|
||||
4300.93 3.96259
|
||||
4318.29 4.00604
|
||||
4335.73 4.04997
|
||||
4353.24 4.09438
|
||||
4370.82 4.13928
|
||||
4388.47 4.18468
|
||||
4406.19 4.23059
|
||||
4423.98 4.27699
|
||||
4441.84 4.32391
|
||||
4459.78 4.37138
|
||||
4477.79 4.41938
|
||||
4495.87 4.46791
|
||||
4514.02 4.51696
|
||||
4532.25 4.56663
|
||||
4550.55 4.61685
|
||||
4568.93 4.66763
|
||||
4587.37 4.71896
|
||||
4605.90 4.77084
|
||||
4624.50 4.82328
|
||||
4643.17 4.87629
|
||||
4661.92 4.92989
|
||||
4680.74 4.98413
|
||||
4699.64 5.03898
|
||||
4718.62 5.09443
|
||||
4737.67 5.15049
|
||||
4756.80 5.20723
|
||||
4776.01 5.26461
|
||||
4795.30 5.32262
|
||||
4814.66 5.38126
|
||||
4834.10 5.44057
|
||||
4853.62 5.50053
|
||||
4873.22 5.56116
|
||||
4892.90 5.62244
|
||||
4912.65 5.68447
|
||||
4932.49 5.74717
|
||||
4952.41 5.81058
|
||||
4972.41 5.87468
|
||||
4992.48 5.93953
|
||||
5012.64 6.00510
|
||||
5032.88 6.07139
|
||||
5053.21 6.13842
|
||||
5073.61 6.20623
|
||||
5094.10 6.27479
|
||||
5114.67 6.34412
|
||||
5135.32 6.41420
|
||||
5156.06 6.48511
|
||||
5176.88 6.55680
|
||||
5197.78 6.62929
|
||||
5218.77 6.70258
|
||||
5239.84 6.77683
|
||||
5261.00 6.85190
|
||||
5282.24 6.92781
|
||||
5303.57 7.00455
|
||||
5324.99 7.08208
|
||||
5346.49 7.16047
|
||||
5368.08 7.23972
|
||||
5389.76 7.31986
|
||||
5411.52 7.40104
|
||||
5433.37 7.48313
|
||||
5455.31 7.56613
|
||||
5477.34 7.65005
|
||||
5499.46 7.73501
|
||||
5521.66 7.82091
|
||||
5543.96 7.90777
|
||||
5566.34 7.99558
|
||||
5588.82 8.08432
|
||||
5611.39 8.17404
|
||||
5634.05 8.26476
|
||||
5656.80 8.35649
|
||||
5679.64 8.44953
|
||||
5702.57 8.54360
|
||||
5725.60 8.63871
|
||||
5748.72 8.73487
|
||||
5771.93 8.83200
|
||||
5795.24 8.93022
|
||||
5818.64 9.02952
|
||||
5842.13 9.12998
|
||||
5865.72 9.23186
|
||||
5889.41 9.33488
|
||||
5913.19 9.43905
|
||||
5937.07 9.54436
|
||||
5961.04 9.65080
|
||||
5985.11 9.75840
|
||||
6009.28 9.86722
|
||||
6033.54 9.97725
|
||||
6057.91 10.0886
|
||||
6082.37 10.2012
|
||||
6106.93 10.3150
|
||||
6131.59 10.4301
|
||||
6156.35 10.5469
|
||||
6181.21 10.6649
|
||||
6206.17 10.7843
|
||||
6231.23 10.9050
|
||||
6256.39 11.0271
|
||||
6281.65 11.1506
|
||||
6307.01 11.2754
|
||||
6332.48 11.4017
|
||||
6358.05 11.5295
|
||||
6383.73 11.6588
|
||||
6409.50 11.7895
|
||||
6435.38 11.9216
|
||||
6461.37 12.0555
|
||||
6487.46 12.1909
|
||||
6513.66 12.3278
|
||||
6539.96 12.4663
|
||||
6566.37 12.6064
|
||||
6592.88 12.7481
|
||||
6619.50 12.8914
|
||||
6646.23 13.0364
|
||||
6673.07 13.1831
|
||||
6700.01 13.3316
|
||||
6727.07 13.4816
|
||||
6754.23 13.6334
|
||||
6781.50 13.7871
|
||||
6808.89 13.9425
|
||||
6836.38 14.0996
|
||||
6863.99 14.2586
|
||||
6891.70 14.4197
|
||||
6919.53 14.5825
|
||||
6947.47 14.7472
|
||||
6975.53 14.9137
|
||||
7003.69 15.0824
|
||||
7031.97 15.2530
|
||||
7060.37 15.4255
|
||||
7088.88 15.6001
|
||||
7117.50 15.7768
|
||||
7146.24 15.9555
|
||||
7175.10 16.1362
|
||||
7204.07 16.3191
|
||||
7233.16 16.5043
|
||||
7262.37 16.6916
|
||||
7291.69 16.8810
|
||||
7321.13 17.0726
|
||||
7350.70 17.2667
|
||||
7380.38 17.4630
|
||||
7410.18 17.6615
|
||||
7440.10 17.8624
|
||||
7470.14 18.0658
|
||||
7500.31 18.2715
|
||||
7530.59 18.4796
|
||||
7561.00 18.6902
|
||||
7591.53 18.9034
|
||||
7622.19 19.1191
|
||||
7652.96 19.3372
|
||||
7683.87 19.5580
|
||||
7714.89 19.7815
|
||||
7746.05 20.0076
|
||||
7777.32 20.2363
|
||||
7808.73 20.4678
|
||||
7840.26 20.7023
|
||||
7871.92 20.9395
|
||||
7903.70 21.1793
|
||||
7935.62 21.4222
|
||||
7967.66 21.6681
|
||||
7999.84 21.9168
|
||||
8032.14 22.1684
|
||||
8064.57 22.4232
|
||||
8097.14 22.6812
|
||||
8129.83 22.9422
|
||||
8162.66 23.2061
|
||||
8195.62 23.4734
|
||||
8228.71 23.7441
|
||||
8261.94 24.0178
|
||||
8295.30 24.2947
|
||||
8328.80 24.5752
|
||||
8362.43 24.8594
|
||||
8396.20 25.1467
|
||||
8430.10 25.4374
|
||||
8464.14 25.7318
|
||||
8498.32 26.0300
|
||||
8532.63 26.3316
|
||||
8567.09 26.6368
|
||||
8601.68 26.9458
|
||||
8636.41 27.2589
|
||||
8671.29 27.5756
|
||||
8706.30 27.8960
|
||||
8741.46 28.2205
|
||||
8776.75 28.5491
|
||||
8812.19 28.8816
|
||||
8847.78 29.2179
|
||||
8883.50 29.5581
|
||||
8919.37 29.9023
|
||||
8955.00 30.2467
|
||||
8956.00 30.2564
|
||||
8957.00 30.2661
|
||||
8958.00 30.2758
|
||||
8958.99 30.2855
|
||||
8959.99 30.2952
|
||||
8960.99 30.3049
|
||||
8961.99 30.3146
|
||||
8962.99 30.3243
|
||||
8963.99 30.3340
|
||||
8964.99 30.3438
|
||||
8965.99 30.3535
|
||||
8966.98 30.3632
|
||||
8967.98 30.3729
|
||||
8968.98 30.3826
|
||||
8969.98 30.3924
|
||||
8970.98 30.4021
|
||||
8971.98 30.4118
|
||||
8972.98 30.4216
|
||||
8973.98 30.4313
|
||||
8974.98 30.4411
|
||||
8975.98 30.4508
|
||||
8976.98 30.4605
|
||||
8977.98 30.4703
|
||||
8978.00 30.4705
|
||||
8978.01 30.4706
|
||||
8978.02 30.4706
|
||||
8978.03 30.4708
|
||||
8978.04 30.4709
|
||||
8978.05 30.4710
|
||||
8978.06 30.4710
|
||||
8978.07 30.4711
|
||||
8978.08 30.4712
|
||||
8978.09 30.4714
|
||||
8978.10 30.4715
|
||||
8978.11 30.4715
|
||||
8978.12 30.4716
|
||||
8978.13 30.4717
|
||||
8978.14 30.4718
|
||||
8978.15 30.4720
|
||||
8978.16 30.4720
|
||||
8978.17 30.4721
|
||||
8978.18 30.4722
|
||||
8978.19 30.4723
|
||||
8978.20 30.4724
|
||||
8978.21 30.4725
|
||||
8978.22 30.4726
|
||||
8978.23 30.4727
|
||||
8978.24 30.4728
|
||||
8978.25 30.4729
|
||||
8978.26 30.4730
|
||||
8978.27 30.4731
|
||||
8978.28 30.4732
|
||||
8978.29 30.4733
|
||||
8978.30 30.4734
|
||||
8978.31 30.4735
|
||||
8978.32 30.4736
|
||||
8978.33 30.4737
|
||||
8978.34 30.4738
|
||||
8978.35 30.4739
|
||||
8978.36 30.4740
|
||||
8978.37 30.4741
|
||||
8978.38 30.4741
|
||||
8978.39 30.4743
|
||||
8978.40 30.4744
|
||||
8978.41 30.4745
|
||||
8978.42 30.4746
|
||||
8978.43 30.4746
|
||||
8978.44 30.4747
|
||||
8978.45 30.4749
|
||||
8978.46 30.4750
|
||||
8978.47 30.4751
|
||||
8978.48 30.4751
|
||||
8978.49 30.4752
|
||||
8978.50 30.4753
|
||||
8978.51 30.4755
|
||||
8978.52 30.4756
|
||||
8978.53 30.4756
|
||||
8978.54 30.4757
|
||||
8978.55 30.4758
|
||||
8978.56 30.4759
|
||||
8978.57 30.4761
|
||||
8978.58 30.4761
|
||||
8978.59 30.4762
|
||||
8978.60 30.4763
|
||||
8978.61 30.4764
|
||||
8978.62 30.4765
|
||||
8978.63 30.4766
|
||||
8978.64 30.4767
|
||||
8978.65 30.4768
|
||||
8978.66 30.4769
|
||||
8978.67 30.4770
|
||||
8978.68 30.4771
|
||||
8978.69 30.4772
|
||||
8978.70 30.4773
|
||||
8978.71 30.4774
|
||||
8978.72 30.4775
|
||||
8978.73 30.4776
|
||||
8978.74 30.4777
|
||||
8978.75 30.4778
|
||||
8978.76 30.4779
|
||||
8978.77 30.4780
|
||||
8978.78 30.4781
|
||||
8978.79 30.4782
|
||||
8978.80 30.4782
|
||||
8978.81 25.6979
|
||||
8978.82 23.5962
|
||||
8978.83 21.6663
|
||||
8978.84 19.8939
|
||||
8978.85 18.2664
|
||||
8978.86 15.3995
|
||||
8978.87 14.1393
|
||||
8978.88 12.9822
|
||||
8978.89 11.9197
|
||||
8978.90 10.9441
|
||||
8978.91 10.0483
|
||||
8978.92 9.22574
|
||||
8978.93 7.77706
|
||||
8978.94 7.14036
|
||||
8978.95 6.55577
|
||||
8978.96 6.01902
|
||||
8978.97 5.52620
|
||||
8978.98 4.65827
|
||||
8978.99 4.27683
|
||||
8979.00 3.92662
|
||||
8979.99 3.92768
|
||||
8980.99 3.92876
|
||||
8981.99 3.92983
|
||||
8982.99 3.93091
|
||||
8983.99 3.93198
|
||||
8984.99 3.93306
|
||||
8985.99 3.93414
|
||||
8987.00 3.93522
|
||||
8988.00 3.93630
|
||||
8989.00 3.93738
|
||||
8990.00 3.93846
|
||||
8991.55 3.94013
|
||||
9027.86 3.97936
|
||||
9064.31 4.01898
|
||||
9100.91 4.05900
|
||||
9137.66 4.09942
|
||||
9174.56 4.14035
|
||||
9211.61 4.18176
|
||||
9248.80 4.22359
|
||||
9286.15 4.26582
|
||||
9323.65 4.30865
|
||||
9361.29 4.35198
|
||||
9399.09 4.39576
|
||||
9437.05 4.43996
|
||||
9475.15 4.48477
|
||||
9513.41 4.53010
|
||||
9551.83 4.57590
|
||||
9590.40 4.62215
|
||||
9629.12 4.66903
|
||||
9668.00 4.71646
|
||||
9707.04 4.76436
|
||||
9746.24 4.81275
|
||||
9785.59 4.86176
|
||||
9825.11 4.91132
|
||||
9864.78 4.96138
|
||||
9904.61 5.01195
|
||||
9944.61 5.06327
|
||||
9984.76 5.11518
|
||||
10025.1 5.16764
|
||||
10065.6 5.22063
|
||||
10106.2 5.27433
|
||||
10147.0 5.32865
|
||||
10188.0 5.38352
|
||||
10229.1 5.43896
|
||||
10270.4 5.49516
|
||||
10311.9 5.55200
|
||||
10353.5 5.60942
|
||||
10395.3 5.66744
|
||||
10437.3 5.72624
|
||||
10479.5 5.78571
|
||||
10521.8 5.84579
|
||||
10564.3 5.90650
|
||||
10606.9 5.96803
|
||||
10649.8 6.03027
|
||||
10692.8 6.09315
|
||||
10735.9 6.15668
|
||||
10779.3 6.22107
|
||||
10822.8 6.28618
|
||||
10866.5 6.35197
|
||||
10910.4 6.41844
|
||||
10954.5 6.48583
|
||||
10998.7 6.55397
|
||||
11043.1 6.62282
|
||||
11087.7 6.69240
|
||||
11132.5 6.76293
|
||||
11177.4 6.83425
|
||||
11222.5 6.90631
|
||||
11267.9 6.97914
|
||||
11313.4 7.05296
|
||||
11359.0 7.12760
|
||||
11404.9 7.20304
|
||||
11451.0 7.27925
|
||||
11497.2 7.35650
|
||||
11543.6 7.43459
|
||||
11590.2 7.51351
|
||||
11637.0 7.59326
|
||||
11684.0 7.67411
|
||||
11731.2 7.75584
|
||||
11778.6 7.83845
|
||||
11826.1 7.92193
|
||||
11873.9 8.00656
|
||||
11921.8 8.09211
|
||||
11970.0 8.17857
|
||||
12018.3 8.26597
|
||||
12066.8 8.35453
|
||||
12115.6 8.44406
|
||||
12164.5 8.53453
|
||||
12213.6 8.62599
|
||||
12262.9 8.71873
|
||||
12312.4 8.81246
|
||||
12362.2 8.90721
|
||||
12412.1 9.00296
|
||||
12462.2 9.10001
|
||||
12512.5 9.19810
|
||||
12563.0 9.29726
|
||||
12613.8 9.39747
|
||||
12664.7 9.49907
|
||||
12715.8 9.60177
|
||||
12767.2 9.70556
|
||||
12818.7 9.81049
|
||||
12870.5 9.91679
|
||||
12922.5 10.0243
|
||||
12974.7 10.1329
|
||||
13027.0 10.2427
|
||||
13079.6 10.3541
|
||||
13132.5 10.4666
|
||||
13185.5 10.5804
|
||||
13238.7 10.6954
|
||||
13292.2 10.8119
|
||||
13345.9 10.9297
|
||||
13399.8 11.0488
|
||||
13453.9 11.1692
|
||||
13508.2 11.2912
|
||||
13562.7 11.4146
|
||||
13617.5 11.5393
|
||||
13672.5 11.6654
|
||||
13727.7 11.7931
|
||||
13783.1 11.9222
|
||||
13838.8 12.0527
|
||||
13894.7 12.1847
|
||||
13950.8 12.3184
|
||||
14007.1 12.4536
|
||||
14063.7 12.5903
|
||||
14120.4 12.7285
|
||||
14177.5 12.8685
|
||||
14234.7 13.0101
|
||||
14292.2 13.1532
|
||||
14349.9 13.2979
|
||||
14407.8 13.4445
|
||||
14466.0 13.5928
|
||||
14524.4 13.7426
|
||||
14583.1 13.8942
|
||||
14642.0 14.0477
|
||||
14701.1 14.2028
|
||||
14760.5 14.3597
|
||||
14820.1 14.5184
|
||||
14879.9 14.6791
|
||||
14940.0 14.8416
|
||||
15000.3 15.0059
|
||||
15060.9 15.1721
|
||||
15121.7 15.3405
|
||||
15182.8 15.5106
|
||||
15244.1 15.6827
|
||||
15305.6 15.8567
|
||||
15367.4 16.0330
|
||||
15429.5 16.2112
|
||||
15491.8 16.3915
|
||||
15554.3 16.5738
|
||||
15617.1 16.7584
|
||||
15680.2 16.9451
|
||||
15743.5 17.1338
|
||||
15807.1 17.3247
|
||||
15870.9 17.5180
|
||||
15935.0 17.7135
|
||||
15999.3 17.9111
|
||||
16063.9 18.1110
|
||||
16128.8 18.3135
|
||||
16193.9 18.5181
|
||||
16259.3 18.7251
|
||||
16325.0 18.9345
|
||||
16390.9 19.1466
|
||||
16457.1 19.3610
|
||||
16523.5 19.5778
|
||||
16590.3 19.7971
|
||||
16657.3 20.0192
|
||||
16724.5 20.2438
|
||||
16792.0 20.4708
|
||||
16859.9 20.7006
|
||||
16927.9 20.9332
|
||||
16996.3 21.1684
|
||||
17064.9 21.4062
|
||||
17133.8 21.6469
|
||||
17203.0 21.8905
|
||||
17272.5 22.1368
|
||||
17342.2 22.3859
|
||||
17412.2 22.6380
|
||||
17482.6 22.8931
|
||||
17553.1 23.1511
|
||||
17624.0 23.4120
|
||||
17695.2 23.6760
|
||||
17766.6 23.9434
|
||||
17838.4 24.2137
|
||||
17910.4 24.4870
|
||||
17982.7 24.7637
|
||||
18055.3 25.0436
|
||||
18128.3 25.3268
|
||||
18201.5 25.6131
|
||||
18275.0 25.9029
|
||||
18348.7 26.1961
|
||||
18422.8 26.4927
|
||||
18497.2 26.7925
|
||||
18571.9 27.0961
|
||||
18646.9 27.4033
|
||||
18722.2 27.7139
|
||||
18797.8 28.0281
|
||||
18873.7 28.3461
|
||||
18949.9 28.6679
|
||||
19026.4 28.9934
|
||||
19103.3 29.3226
|
||||
19180.4 29.6557
|
||||
19257.8 29.9929
|
||||
19335.6 30.3339
|
||||
19413.7 30.6787
|
||||
19492.1 31.0276
|
||||
19570.8 31.3807
|
||||
19649.8 31.7378
|
||||
19729.2 32.0989
|
||||
19808.8 32.4645
|
||||
19888.8 32.8345
|
||||
19969.1 33.2087
|
||||
20049.8 33.5872
|
||||
20130.7 33.9702
|
||||
20212.0 34.3579
|
||||
20293.6 34.7499
|
||||
20375.6 35.1465
|
||||
20457.8 35.5478
|
||||
20540.4 35.9540
|
||||
20623.4 36.3648
|
||||
20706.7 36.7803
|
||||
20790.3 37.2008
|
||||
20874.2 37.6263
|
||||
20958.5 38.0566
|
||||
21043.1 38.4918
|
||||
21128.1 38.9324
|
||||
21213.4 39.3782
|
||||
21299.1 39.8291
|
||||
21385.1 40.2851
|
||||
21471.4 40.7466
|
||||
21558.1 41.2136
|
||||
21645.2 41.6859
|
||||
21732.6 42.1637
|
||||
21820.3 42.6473
|
||||
21908.5 43.1366
|
||||
21996.9 43.6316
|
||||
22085.7 44.1322
|
||||
22174.9 44.6389
|
||||
22264.5 45.1516
|
||||
22354.4 45.6702
|
||||
22444.6 46.1946
|
||||
22535.3 46.7256
|
||||
22626.3 47.2627
|
||||
22717.6 47.8060
|
||||
22809.4 48.3556
|
||||
22901.5 48.9118
|
||||
22993.9 49.4746
|
||||
23086.8 50.0438
|
||||
23180.0 50.6196
|
||||
23273.6 51.2024
|
||||
23367.6 51.7921
|
||||
23461.9 52.3884
|
||||
23556.7 52.9916
|
||||
23651.8 53.6023
|
||||
23747.3 54.2201
|
||||
23843.2 54.8451
|
||||
23939.5 55.4771
|
||||
24036.1 56.1170
|
||||
24133.2 56.7643
|
||||
24230.6 57.4191
|
||||
24328.5 58.0813
|
||||
24426.7 58.7517
|
||||
24525.3 59.4300
|
||||
24624.4 60.1159
|
||||
24723.8 60.8098
|
||||
24823.6 61.5120
|
||||
24923.9 62.2226
|
||||
25024.5 62.9412
|
||||
25125.6 63.6682
|
||||
25227.0 64.4041
|
||||
25328.9 65.1487
|
||||
25431.2 65.9019
|
||||
25533.9 66.6635
|
||||
25637.0 67.4347
|
||||
25740.5 68.2147
|
||||
25844.4 69.0037
|
||||
25948.8 69.8017
|
||||
26053.6 70.6097
|
||||
26158.8 71.4271
|
||||
26264.4 72.2539
|
||||
26370.4 73.0902
|
||||
26476.9 73.9366
|
||||
26583.8 74.7929
|
||||
26691.2 75.6589
|
||||
26799.0 76.5350
|
||||
26907.2 77.4217
|
||||
27015.8 78.3188
|
||||
27124.9 79.2263
|
||||
27234.4 80.1439
|
||||
27344.4 81.0731
|
||||
27454.8 82.0129
|
||||
27565.7 82.9636
|
||||
27677.0 83.9250
|
||||
27788.7 84.8986
|
||||
27901.0 85.8832
|
||||
28013.6 86.8793
|
||||
28126.7 87.8868
|
||||
28240.3 88.9064
|
||||
28354.3 89.9379
|
||||
28468.8 90.9811
|
||||
28583.8 92.0364
|
||||
28699.2 93.1048
|
||||
28815.1 94.1855
|
||||
28931.4 95.2785
|
||||
29048.3 96.3842
|
||||
29165.6 97.5036
|
||||
29283.3 98.6356
|
||||
29401.6 99.7807
|
||||
29520.3 100.939
|
||||
29639.5 102.111
|
||||
29759.2 103.297
|
||||
29879.4 104.497
|
||||
30000.0 105.710
|
||||
@@ -1,601 +0,0 @@
|
||||
4000.00 75.3048
|
||||
4012.90 76.0470
|
||||
4025.83 76.7964
|
||||
4038.81 77.5538
|
||||
4051.83 78.3243
|
||||
4064.90 79.1027
|
||||
4078.00 79.8886
|
||||
4091.15 80.6822
|
||||
4104.34 81.4839
|
||||
4117.57 82.2946
|
||||
4130.85 83.1132
|
||||
4144.17 83.9400
|
||||
4157.53 84.7749
|
||||
4170.93 85.6183
|
||||
4184.38 86.4702
|
||||
4197.87 87.3304
|
||||
4211.41 88.1993
|
||||
4224.98 89.0767
|
||||
4238.60 89.9631
|
||||
4252.27 90.8593
|
||||
4265.98 91.7644
|
||||
4279.73 92.6785
|
||||
4293.53 93.6018
|
||||
4307.37 94.5342
|
||||
4321.26 95.4763
|
||||
4335.19 96.4277
|
||||
4349.17 97.3888
|
||||
4363.19 98.3592
|
||||
4377.26 99.3393
|
||||
4391.37 100.330
|
||||
4405.53 101.330
|
||||
4419.73 102.341
|
||||
4433.98 103.361
|
||||
4448.28 104.392
|
||||
4462.62 105.433
|
||||
4477.01 106.486
|
||||
4491.44 107.549
|
||||
4505.92 108.622
|
||||
4520.45 109.706
|
||||
4535.02 110.802
|
||||
4549.65 111.909
|
||||
4564.31 113.026
|
||||
4579.03 114.155
|
||||
4593.79 115.296
|
||||
4608.60 116.449
|
||||
4623.46 117.613
|
||||
4638.37 118.789
|
||||
4653.32 119.976
|
||||
4668.33 121.176
|
||||
4683.38 122.388
|
||||
4698.48 123.612
|
||||
4713.62 124.848
|
||||
4728.82 126.097
|
||||
4744.07 127.358
|
||||
4759.36 128.632
|
||||
4774.71 129.920
|
||||
4790.10 131.220
|
||||
4805.54 132.533
|
||||
4821.04 133.859
|
||||
4836.58 135.200
|
||||
4852.17 136.554
|
||||
4867.82 137.921
|
||||
4883.51 139.302
|
||||
4899.26 140.697
|
||||
4915.05 142.107
|
||||
4930.90 143.531
|
||||
4946.80 144.969
|
||||
4962.75 146.421
|
||||
4978.75 147.888
|
||||
4994.80 149.371
|
||||
5010.90 150.869
|
||||
5027.06 152.382
|
||||
5043.26 153.910
|
||||
5059.52 155.454
|
||||
5075.84 157.014
|
||||
5092.20 158.590
|
||||
5108.62 160.181
|
||||
5125.09 161.789
|
||||
5141.61 163.412
|
||||
5158.19 165.053
|
||||
5174.82 166.709
|
||||
5191.50 168.382
|
||||
5208.24 170.072
|
||||
5225.03 171.779
|
||||
5241.88 173.505
|
||||
5258.78 175.249
|
||||
5275.73 177.010
|
||||
5292.74 178.788
|
||||
5309.81 180.584
|
||||
5326.93 182.400
|
||||
5344.10 184.235
|
||||
5361.33 186.087
|
||||
5378.62 187.958
|
||||
5395.96 189.848
|
||||
5413.35 191.759
|
||||
5430.81 193.689
|
||||
5448.32 195.638
|
||||
5465.88 197.606
|
||||
5483.50 199.595
|
||||
5501.18 201.604
|
||||
5518.92 203.634
|
||||
5536.71 205.683
|
||||
5554.56 207.753
|
||||
5572.47 209.844
|
||||
5590.44 211.955
|
||||
5608.46 214.088
|
||||
5626.54 216.241
|
||||
5644.68 218.416
|
||||
5662.88 220.614
|
||||
5681.14 222.837
|
||||
5699.46 225.082
|
||||
5717.83 227.350
|
||||
5736.27 229.639
|
||||
5754.76 231.953
|
||||
5773.31 234.292
|
||||
5791.93 236.655
|
||||
5810.60 239.040
|
||||
5829.33 241.449
|
||||
5848.13 243.884
|
||||
5866.98 246.346
|
||||
5885.90 248.831
|
||||
5904.88 251.341
|
||||
5923.91 253.876
|
||||
5943.01 256.437
|
||||
5962.17 259.024
|
||||
5981.40 261.636
|
||||
6000.68 264.273
|
||||
6020.03 266.939
|
||||
6039.44 269.632
|
||||
6058.91 272.352
|
||||
6078.44 275.100
|
||||
6098.04 277.874
|
||||
6117.70 280.677
|
||||
6137.42 283.510
|
||||
6157.21 286.375
|
||||
6177.06 289.268
|
||||
6196.98 292.190
|
||||
6216.96 295.141
|
||||
6237.00 298.122
|
||||
6257.11 301.134
|
||||
6277.28 304.176
|
||||
6297.52 307.249
|
||||
6317.82 310.352
|
||||
6338.19 313.487
|
||||
6358.63 316.655
|
||||
6379.13 319.855
|
||||
6399.69 323.086
|
||||
6420.33 326.349
|
||||
6441.03 329.647
|
||||
6461.79 332.980
|
||||
6482.63 336.345
|
||||
6503.53 339.744
|
||||
6524.49 343.176
|
||||
6545.53 346.645
|
||||
6566.63 350.150
|
||||
6587.80 353.689
|
||||
6609.04 357.264
|
||||
6630.35 360.873
|
||||
6651.73 364.522
|
||||
6673.17 368.208
|
||||
6694.69 371.931
|
||||
6716.27 375.690
|
||||
6737.93 379.487
|
||||
6759.65 383.324
|
||||
6781.44 387.200
|
||||
6803.31 391.114
|
||||
6825.24 395.068
|
||||
6847.25 399.060
|
||||
6869.32 403.095
|
||||
6891.47 407.171
|
||||
6913.69 411.288
|
||||
6935.98 415.446
|
||||
6958.34 419.644
|
||||
6980.77 423.888
|
||||
7003.28 428.176
|
||||
7025.86 432.507
|
||||
7048.51 436.880
|
||||
7071.24 441.297
|
||||
7094.03 445.759
|
||||
7116.91 450.268
|
||||
7139.85 454.820
|
||||
7162.87 459.417
|
||||
7185.96 464.060
|
||||
7209.13 468.753
|
||||
7232.38 473.493
|
||||
7255.69 478.280
|
||||
7279.09 483.113
|
||||
7302.55 487.996
|
||||
7326.10 492.927
|
||||
7349.72 497.909
|
||||
7373.41 502.939
|
||||
7397.19 508.019
|
||||
7421.03 513.150
|
||||
7444.96 518.336
|
||||
7468.96 523.575
|
||||
7493.04 528.866
|
||||
7517.20 534.209
|
||||
7541.44 539.604
|
||||
7565.75 545.056
|
||||
7590.14 550.562
|
||||
7614.62 556.123
|
||||
7639.17 561.737
|
||||
7663.79 567.406
|
||||
7688.50 573.135
|
||||
7713.29 578.922
|
||||
7738.16 584.765
|
||||
7763.11 590.665
|
||||
7788.14 596.622
|
||||
7813.25 602.644
|
||||
7838.44 608.728
|
||||
7863.71 614.870
|
||||
7889.06 621.071
|
||||
7914.50 627.333
|
||||
7940.01 633.662
|
||||
7965.61 640.054
|
||||
7991.29 646.507
|
||||
8017.06 653.027
|
||||
8042.91 659.613
|
||||
8068.84 666.267
|
||||
8094.85 672.988
|
||||
8120.95 679.774
|
||||
8147.13 686.627
|
||||
8173.40 693.545
|
||||
8199.75 700.536
|
||||
8226.19 707.596
|
||||
8252.71 714.725
|
||||
8279.32 721.924
|
||||
8306.01 729.191
|
||||
8332.79 736.537
|
||||
8359.65 743.956
|
||||
8386.60 751.447
|
||||
8413.64 759.010
|
||||
8440.77 766.645
|
||||
8467.98 774.359
|
||||
8495.29 782.151
|
||||
8522.67 790.016
|
||||
8550.15 797.956
|
||||
8577.72 805.973
|
||||
8605.37 814.075
|
||||
8633.12 822.254
|
||||
8660.95 830.511
|
||||
8688.87 838.848
|
||||
8716.89 847.264
|
||||
8744.99 855.773
|
||||
8773.19 864.364
|
||||
8801.47 873.036
|
||||
8829.85 881.791
|
||||
8858.32 890.632
|
||||
8886.88 899.564
|
||||
8915.53 908.583
|
||||
8944.27 917.687
|
||||
8973.11 926.879
|
||||
9002.04 936.156
|
||||
9031.06 945.529
|
||||
9060.18 954.991
|
||||
9089.39 964.544
|
||||
9118.69 974.186
|
||||
9148.09 983.918
|
||||
9177.59 993.754
|
||||
9207.18 1003.68
|
||||
9236.86 1013.71
|
||||
9266.64 1023.83
|
||||
9296.52 1034.04
|
||||
9326.49 1044.35
|
||||
9356.56 1054.77
|
||||
9386.72 1065.27
|
||||
9416.99 1075.88
|
||||
9447.35 1086.59
|
||||
9477.81 1097.41
|
||||
9508.37 1108.33
|
||||
9539.02 1119.35
|
||||
9569.78 1130.47
|
||||
9600.63 1141.70
|
||||
9631.58 1153.05
|
||||
9662.63 1164.51
|
||||
9693.79 1176.07
|
||||
9725.04 1187.74
|
||||
9756.39 1199.52
|
||||
9787.85 1211.41
|
||||
9819.41 1223.40
|
||||
9851.07 1235.52
|
||||
9882.83 1247.73
|
||||
9914.69 1260.07
|
||||
9946.65 1272.53
|
||||
9978.72 1285.10
|
||||
10010.9 1297.80
|
||||
10043.2 1310.62
|
||||
10075.5 1323.56
|
||||
10108.0 1336.63
|
||||
10140.6 1349.82
|
||||
10173.3 1363.12
|
||||
10206.1 1376.55
|
||||
10239.0 1390.10
|
||||
10272.0 1403.79
|
||||
10305.2 1417.60
|
||||
10338.4 1431.54
|
||||
10371.7 1445.60
|
||||
10405.1 1459.79
|
||||
10438.7 1474.12
|
||||
10472.3 1488.58
|
||||
10506.1 1503.17
|
||||
10540.0 1517.88
|
||||
10574.0 1532.73
|
||||
10608.1 1547.73
|
||||
10642.3 1562.86
|
||||
10676.6 1578.12
|
||||
10711.0 1593.52
|
||||
10745.5 1609.05
|
||||
10780.2 1624.73
|
||||
10814.9 1640.56
|
||||
10849.8 1656.52
|
||||
10884.8 1672.62
|
||||
10919.9 1688.86
|
||||
10955.1 1705.26
|
||||
10990.4 1721.80
|
||||
11025.8 1738.48
|
||||
11061.4 1755.31
|
||||
11097.0 1772.28
|
||||
11132.8 1789.42
|
||||
11168.7 1806.71
|
||||
11204.7 1824.15
|
||||
11240.8 1841.73
|
||||
11277.1 1859.47
|
||||
11313.4 1877.37
|
||||
11349.9 1895.43
|
||||
11386.5 1913.64
|
||||
11423.2 1932.00
|
||||
11460.0 1950.51
|
||||
11497.0 1969.20
|
||||
11534.1 1988.04
|
||||
11571.2 2007.04
|
||||
11608.6 2026.20
|
||||
11646.0 2045.52
|
||||
11683.5 2065.01
|
||||
11721.2 2084.67
|
||||
11759.0 2104.49
|
||||
11796.9 2124.47
|
||||
11834.9 2144.61
|
||||
11873.1 2164.94
|
||||
11911.4 2185.44
|
||||
11949.8 2206.10
|
||||
11988.3 2226.93
|
||||
12026.9 2247.93
|
||||
12065.7 2269.10
|
||||
12104.6 2290.45
|
||||
12143.7 2311.97
|
||||
12182.8 2333.66
|
||||
12222.1 2355.52
|
||||
12261.5 2377.58
|
||||
12301.0 2399.82
|
||||
12340.7 2422.23
|
||||
12380.5 2444.81
|
||||
12420.4 2467.57
|
||||
12460.4 2490.52
|
||||
12500.6 2513.65
|
||||
12540.9 2536.95
|
||||
12581.3 2560.43
|
||||
12621.9 2584.10
|
||||
12662.6 2607.97
|
||||
12703.4 2632.01
|
||||
12744.4 2656.23
|
||||
12785.5 2680.65
|
||||
12826.7 2705.24
|
||||
12868.0 2730.02
|
||||
12909.5 2754.99
|
||||
12951.1 2780.15
|
||||
12992.9 2805.48
|
||||
13034.8 2831.01
|
||||
13076.8 2856.75
|
||||
13119.0 2882.68
|
||||
13161.3 2908.79
|
||||
13203.7 2935.09
|
||||
13246.3 2961.58
|
||||
13289.0 2988.27
|
||||
13331.8 3015.16
|
||||
13374.8 3042.22
|
||||
13417.9 3069.48
|
||||
13461.2 3096.93
|
||||
13504.6 3124.59
|
||||
13548.1 3152.44
|
||||
13591.8 3180.48
|
||||
13635.6 3208.71
|
||||
13679.6 3237.14
|
||||
13723.7 3265.77
|
||||
13767.9 3294.58
|
||||
13812.3 3323.59
|
||||
13856.9 3352.79
|
||||
13901.5 3382.19
|
||||
13946.4 3411.80
|
||||
13991.3 3441.60
|
||||
14036.4 3471.59
|
||||
14081.7 3501.78
|
||||
14127.1 3532.17
|
||||
14172.6 3562.75
|
||||
14218.3 3593.52
|
||||
14264.2 3624.49
|
||||
14310.2 3655.65
|
||||
14356.3 3687.00
|
||||
14402.6 3718.57
|
||||
14449.0 3750.32
|
||||
14495.6 3782.27
|
||||
14542.3 3814.41
|
||||
14589.2 3846.74
|
||||
14636.2 3879.25
|
||||
14683.4 3911.97
|
||||
14730.8 3944.86
|
||||
14778.3 3977.94
|
||||
14825.9 4011.22
|
||||
14873.7 4044.71
|
||||
14921.7 4078.38
|
||||
14969.8 4112.24
|
||||
15018.0 4146.37
|
||||
15066.5 4180.85
|
||||
15115.0 4215.54
|
||||
15163.8 4250.41
|
||||
15212.7 4285.47
|
||||
15261.7 4320.73
|
||||
15310.9 4356.18
|
||||
15360.3 4391.83
|
||||
15409.8 4427.67
|
||||
15459.5 4463.69
|
||||
15509.3 4499.91
|
||||
15559.3 4536.31
|
||||
15609.5 4572.91
|
||||
15659.8 4609.69
|
||||
15710.3 4646.67
|
||||
15761.0 4683.81
|
||||
15811.8 4721.14
|
||||
15862.7 4758.65
|
||||
15913.9 4796.35
|
||||
15965.2 4834.22
|
||||
16016.7 4872.26
|
||||
16068.3 4910.49
|
||||
16120.1 4948.89
|
||||
16172.1 4987.46
|
||||
16224.2 5026.21
|
||||
16276.5 5065.12
|
||||
16329.0 5104.21
|
||||
16381.7 5143.49
|
||||
16434.5 5182.92
|
||||
16487.5 5222.51
|
||||
16540.6 5262.27
|
||||
16593.9 5302.21
|
||||
16647.4 5342.29
|
||||
16701.1 5382.53
|
||||
16755.0 5422.93
|
||||
16809.0 5463.49
|
||||
16863.2 5504.19
|
||||
16917.5 5545.04
|
||||
16972.1 5586.05
|
||||
17026.8 5627.20
|
||||
17081.7 5668.50
|
||||
17136.8 5709.94
|
||||
17192.0 5751.52
|
||||
17247.4 5793.24
|
||||
17303.1 5835.11
|
||||
17358.8 5877.10
|
||||
17414.8 5919.21
|
||||
17470.9 5961.44
|
||||
17527.3 6003.81
|
||||
17583.8 6046.29
|
||||
17640.5 6088.90
|
||||
17697.4 6131.63
|
||||
17754.4 6174.50
|
||||
17811.6 6217.47
|
||||
17869.1 6260.56
|
||||
17926.7 6303.76
|
||||
17984.5 6347.06
|
||||
18042.5 6390.45
|
||||
18100.6 6433.93
|
||||
18159.0 6477.52
|
||||
18217.5 6521.21
|
||||
18276.3 6565.00
|
||||
18335.2 6608.85
|
||||
18394.3 6652.80
|
||||
18453.6 6696.84
|
||||
18513.1 6740.98
|
||||
18572.8 6785.17
|
||||
18632.7 6829.43
|
||||
18692.8 6873.77
|
||||
18753.0 6918.19
|
||||
18813.5 6962.68
|
||||
18874.1 7007.21
|
||||
18935.0 7051.79
|
||||
18996.0 7096.45
|
||||
19057.3 7141.15
|
||||
19118.7 7185.91
|
||||
19180.4 7230.70
|
||||
19242.2 7275.52
|
||||
19304.2 7320.39
|
||||
19366.5 7365.30
|
||||
19428.9 7410.25
|
||||
19491.6 7455.20
|
||||
19554.4 7500.17
|
||||
19617.4 7545.17
|
||||
19680.7 7590.19
|
||||
19744.1 7635.24
|
||||
19807.8 7676.69
|
||||
19871.7 7716.04
|
||||
19935.7 7755.41
|
||||
20000.0 7794.78
|
||||
20081.3 7845.79
|
||||
20162.8 7905.30
|
||||
20244.8 7965.51
|
||||
20327.0 8025.78
|
||||
20409.6 8086.09
|
||||
20492.5 8146.39
|
||||
20575.8 8206.73
|
||||
20659.4 8267.08
|
||||
20743.3 8327.46
|
||||
20827.6 8387.78
|
||||
20912.2 8448.09
|
||||
20997.2 8508.41
|
||||
21082.5 8568.71
|
||||
21168.1 8628.96
|
||||
21254.1 8689.19
|
||||
21340.5 8749.37
|
||||
21427.2 8809.53
|
||||
21514.3 8869.58
|
||||
21601.7 8929.59
|
||||
21689.4 8989.56
|
||||
21777.6 9049.44
|
||||
21866.0 9109.23
|
||||
21954.9 9168.94
|
||||
22044.1 9228.57
|
||||
22133.6 9288.12
|
||||
22223.6 9347.52
|
||||
22313.9 9406.82
|
||||
22404.5 9466.02
|
||||
22495.5 9525.10
|
||||
22586.9 9584.02
|
||||
22678.7 9642.81
|
||||
22770.8 9701.49
|
||||
22863.4 9760.01
|
||||
22956.3 9818.35
|
||||
23049.5 9876.55
|
||||
23143.2 9934.59
|
||||
23237.2 9992.46
|
||||
23331.6 10050.1
|
||||
23426.4 10107.6
|
||||
23521.6 10165.0
|
||||
23617.1 10222.1
|
||||
23713.1 10279.0
|
||||
23809.4 10335.7
|
||||
23906.2 10392.3
|
||||
24003.3 10448.6
|
||||
24100.8 10504.6
|
||||
24198.7 10560.5
|
||||
24297.1 10616.1
|
||||
24395.8 10671.5
|
||||
24494.9 10726.7
|
||||
24594.4 10781.6
|
||||
24694.3 10836.3
|
||||
24794.7 10890.7
|
||||
24895.4 10944.8
|
||||
24996.6 10998.7
|
||||
25098.1 11052.4
|
||||
25200.1 11105.7
|
||||
25302.5 11158.8
|
||||
25405.3 11211.6
|
||||
25508.5 11264.2
|
||||
25612.1 11316.4
|
||||
25716.2 11368.4
|
||||
25820.7 11420.0
|
||||
25925.6 11471.4
|
||||
26030.9 11522.5
|
||||
26136.7 11573.3
|
||||
26242.9 11623.8
|
||||
26349.5 11673.9
|
||||
26456.5 11723.8
|
||||
26564.0 11773.3
|
||||
26672.0 11822.5
|
||||
26780.3 11871.4
|
||||
26889.1 11920.0
|
||||
26998.4 11968.2
|
||||
27108.1 12016.1
|
||||
27218.2 12063.7
|
||||
27328.8 12111.0
|
||||
27439.8 12157.9
|
||||
27551.3 12204.5
|
||||
27663.2 12250.7
|
||||
27775.6 12296.6
|
||||
27888.5 12342.2
|
||||
28001.8 12387.4
|
||||
28115.6 12432.3
|
||||
28229.8 12476.8
|
||||
28344.5 12520.9
|
||||
28459.6 12564.8
|
||||
28575.3 12608.3
|
||||
28691.4 12651.4
|
||||
28807.9 12694.1
|
||||
28925.0 12736.5
|
||||
29042.5 12778.6
|
||||
29160.5 12820.3
|
||||
29279.0 12861.6
|
||||
29397.9 12902.6
|
||||
29517.4 12943.3
|
||||
29637.3 12983.5
|
||||
29757.7 13023.4
|
||||
29878.6 13062.9
|
||||
30000.0 13102.1
|
||||
@@ -1,654 +0,0 @@
|
||||
4000.00 4.18514
|
||||
4032.00 4.27608
|
||||
4064.00 4.36835
|
||||
4096.00 4.46188
|
||||
4128.00 4.55672
|
||||
4160.00 4.65282
|
||||
4192.00 4.75023
|
||||
4224.00 4.84893
|
||||
4256.00 4.94900
|
||||
4288.00 5.05041
|
||||
4320.00 5.15311
|
||||
4352.00 5.25711
|
||||
4384.00 5.36245
|
||||
4416.00 5.46917
|
||||
4448.00 5.57723
|
||||
4480.00 5.68671
|
||||
4512.00 5.79754
|
||||
4544.00 5.90984
|
||||
4576.00 6.02351
|
||||
4608.00 6.13856
|
||||
4640.00 6.25497
|
||||
4672.00 6.37280
|
||||
4704.00 6.49212
|
||||
4736.00 6.61284
|
||||
4768.00 6.73496
|
||||
4800.00 6.85847
|
||||
4832.00 6.98349
|
||||
4864.00 7.10998
|
||||
4896.00 7.23792
|
||||
4928.00 7.36743
|
||||
4960.00 7.49840
|
||||
4992.00 7.63088
|
||||
5024.00 7.76488
|
||||
5056.00 7.90034
|
||||
5088.00 8.03728
|
||||
5120.00 8.17572
|
||||
5152.00 8.31572
|
||||
5184.00 8.45730
|
||||
5216.00 8.60040
|
||||
5248.00 8.74518
|
||||
5280.00 8.89151
|
||||
5312.00 9.03937
|
||||
5344.00 9.18870
|
||||
5376.00 9.33957
|
||||
5408.00 9.49208
|
||||
5440.00 9.64620
|
||||
5472.00 9.80192
|
||||
5504.00 9.95941
|
||||
5536.00 10.1185
|
||||
5568.00 10.2793
|
||||
5600.00 10.4416
|
||||
5632.00 10.6056
|
||||
5664.00 10.7712
|
||||
5696.00 10.9384
|
||||
5728.00 11.1073
|
||||
5760.00 11.2779
|
||||
5792.00 11.4503
|
||||
5824.00 11.6244
|
||||
5856.00 11.8001
|
||||
5888.00 11.9775
|
||||
5920.00 12.1565
|
||||
5952.00 12.3374
|
||||
5984.00 12.5200
|
||||
6016.00 12.7044
|
||||
6048.00 12.8905
|
||||
6080.00 13.0784
|
||||
6112.00 13.2679
|
||||
6144.00 13.4594
|
||||
6176.00 13.6526
|
||||
6208.00 13.8477
|
||||
6240.00 14.0445
|
||||
6272.00 14.2430
|
||||
6304.00 14.4433
|
||||
6336.00 14.6454
|
||||
6368.00 14.8494
|
||||
6400.00 15.0552
|
||||
6432.00 15.2628
|
||||
6464.00 15.4728
|
||||
6496.00 15.6846
|
||||
6528.00 15.8982
|
||||
6560.00 16.1135
|
||||
6592.00 16.3306
|
||||
6624.00 16.5496
|
||||
6656.00 16.7704
|
||||
6688.00 16.9930
|
||||
6720.00 17.2175
|
||||
6752.00 17.4439
|
||||
6784.00 17.6725
|
||||
6816.00 17.9030
|
||||
6848.00 18.1353
|
||||
6880.00 18.3699
|
||||
6912.00 18.6065
|
||||
6944.00 18.8451
|
||||
6976.00 19.0856
|
||||
7008.00 19.3280
|
||||
7040.00 19.5724
|
||||
7072.00 19.8187
|
||||
7104.00 20.0672
|
||||
7136.00 20.3177
|
||||
7168.00 20.5701
|
||||
7200.00 20.8246
|
||||
7232.00 21.0813
|
||||
7264.00 21.3400
|
||||
7296.00 21.6007
|
||||
7328.00 21.8635
|
||||
7360.00 22.1284
|
||||
7392.00 22.3954
|
||||
7424.00 22.6644
|
||||
7456.00 22.9356
|
||||
7488.00 23.2089
|
||||
7520.00 23.4843
|
||||
7552.00 23.7618
|
||||
7584.00 24.0416
|
||||
7616.00 24.3234
|
||||
7648.00 24.6073
|
||||
7680.00 24.8934
|
||||
7712.00 25.1819
|
||||
7744.00 25.4725
|
||||
7776.00 25.7652
|
||||
7808.00 26.0601
|
||||
7840.00 26.3574
|
||||
7872.00 26.6568
|
||||
7904.00 26.9584
|
||||
7936.00 27.2623
|
||||
7968.00 27.5685
|
||||
8000.00 27.8769
|
||||
8032.00 28.1875
|
||||
8064.00 28.5004
|
||||
8096.00 28.8157
|
||||
8128.00 29.1332
|
||||
8160.00 29.4529
|
||||
8192.00 29.7750
|
||||
8224.00 30.0995
|
||||
8256.00 30.4263
|
||||
8288.00 30.7553
|
||||
8320.00 31.0867
|
||||
8352.00 31.4206
|
||||
8384.00 31.7567
|
||||
8416.00 32.0952
|
||||
8448.00 32.4360
|
||||
8480.00 32.7795
|
||||
8512.00 33.1254
|
||||
8544.00 33.4736
|
||||
8576.00 33.8241
|
||||
8608.00 34.1772
|
||||
8640.00 34.5327
|
||||
8672.00 34.8906
|
||||
8704.00 35.2508
|
||||
8736.00 35.6136
|
||||
8768.00 35.9790
|
||||
8800.00 36.3468
|
||||
8832.00 36.7170
|
||||
8864.00 37.0896
|
||||
8896.00 37.4651
|
||||
8928.00 37.8431
|
||||
8960.00 38.2235
|
||||
8992.00 38.6063
|
||||
9024.00 38.9918
|
||||
9056.00 39.3799
|
||||
9088.00 39.7704
|
||||
9120.00 40.1635
|
||||
9152.00 40.5591
|
||||
9184.00 40.9576
|
||||
9216.00 41.3587
|
||||
9248.00 41.7624
|
||||
9280.00 42.1685
|
||||
9312.00 42.5773
|
||||
9344.00 42.9889
|
||||
9376.00 43.4031
|
||||
9408.00 43.8199
|
||||
9440.00 44.2392
|
||||
9472.00 44.6616
|
||||
9504.00 45.0866
|
||||
9536.00 45.5144
|
||||
9568.00 45.9447
|
||||
9600.00 46.3776
|
||||
9632.00 46.8134
|
||||
9664.00 47.2520
|
||||
9696.00 47.6933
|
||||
9728.00 48.1371
|
||||
9760.00 48.5836
|
||||
9792.00 49.0336
|
||||
9824.00 49.4863
|
||||
9856.00 49.9416
|
||||
9888.00 50.3996
|
||||
9920.00 50.8604
|
||||
9952.00 51.3245
|
||||
9984.00 51.7913
|
||||
10016.0 52.2608
|
||||
10048.0 52.7331
|
||||
10080.0 53.2082
|
||||
10112.0 53.6867
|
||||
10144.0 54.1679
|
||||
10176.0 54.6518
|
||||
10208.0 55.1386
|
||||
10240.0 55.6281
|
||||
10272.0 56.1213
|
||||
10304.0 56.6173
|
||||
10336.0 57.1161
|
||||
10368.0 57.6177
|
||||
10400.0 58.1222
|
||||
10432.0 58.6302
|
||||
10464.0 59.1412
|
||||
10496.0 59.6551
|
||||
10528.0 60.1718
|
||||
10560.0 60.6913
|
||||
10592.0 61.2144
|
||||
10624.0 61.7407
|
||||
10656.0 62.2698
|
||||
10688.0 62.8019
|
||||
10720.0 63.3370
|
||||
10752.0 63.8753
|
||||
10784.0 64.4172
|
||||
10816.0 64.9622
|
||||
10848.0 65.5099
|
||||
10880.0 66.0608
|
||||
10912.0 66.6146
|
||||
10944.0 67.1722
|
||||
10976.0 67.7332
|
||||
11008.0 68.2969
|
||||
11040.0 68.8638
|
||||
11072.0 69.4338
|
||||
11090.0 69.7556
|
||||
11090.4 69.7628
|
||||
11090.8 69.7700
|
||||
11091.2 69.7772
|
||||
11091.6 69.7843
|
||||
11092.0 69.7914
|
||||
11092.4 69.7986
|
||||
11092.8 69.8058
|
||||
11093.2 69.8130
|
||||
11093.6 69.8202
|
||||
11094.0 69.8273
|
||||
11094.4 69.8345
|
||||
11094.8 69.8417
|
||||
11095.2 69.8489
|
||||
11095.6 69.8561
|
||||
11096.0 69.8632
|
||||
11096.4 69.8704
|
||||
11096.8 69.8777
|
||||
11097.2 69.8849
|
||||
11097.6 69.8921
|
||||
11098.0 69.8992
|
||||
11098.4 69.9064
|
||||
11098.8 69.9136
|
||||
11099.2 69.9209
|
||||
11099.6 69.9280
|
||||
11100.0 69.9352
|
||||
11100.4 69.9424
|
||||
11100.8 69.9496
|
||||
11101.2 69.9569
|
||||
11101.6 69.9640
|
||||
11102.0 69.9712
|
||||
11102.4 69.9784
|
||||
11102.8 69.9857
|
||||
11103.0 69.9893
|
||||
11103.1 26.8271
|
||||
11103.2 9.23223
|
||||
11103.3 9.23243
|
||||
11103.6 9.23305
|
||||
11104.0 9.23389
|
||||
11104.4 9.23473
|
||||
11104.8 9.23557
|
||||
11105.2 9.23639
|
||||
11105.6 9.23724
|
||||
11106.0 9.23807
|
||||
11106.4 9.23891
|
||||
11106.8 9.23974
|
||||
11107.2 9.24058
|
||||
11107.6 9.24142
|
||||
11108.0 9.24226
|
||||
11108.4 9.24310
|
||||
11108.8 9.24392
|
||||
11109.2 9.24476
|
||||
11109.6 9.24560
|
||||
11110.0 9.24644
|
||||
11136.0 9.30092
|
||||
11168.0 9.36821
|
||||
11200.0 9.43583
|
||||
11232.0 9.50371
|
||||
11264.0 9.57189
|
||||
11296.0 9.64038
|
||||
11328.0 9.70916
|
||||
11360.0 9.77823
|
||||
11392.0 9.84760
|
||||
11424.0 9.91726
|
||||
11456.0 9.98722
|
||||
11488.0 10.0578
|
||||
11520.0 10.1286
|
||||
11552.0 10.1998
|
||||
11584.0 10.2712
|
||||
11616.0 10.3430
|
||||
11648.0 10.4151
|
||||
11680.0 10.4878
|
||||
11712.0 10.5608
|
||||
11744.0 10.6341
|
||||
11776.0 10.7077
|
||||
11808.0 10.7816
|
||||
11840.0 10.8559
|
||||
11872.0 10.9307
|
||||
11904.0 11.0059
|
||||
11936.0 11.0814
|
||||
11968.0 11.1572
|
||||
12000.0 11.2333
|
||||
12032.0 11.3098
|
||||
12064.0 11.3868
|
||||
12096.0 11.4641
|
||||
12128.0 11.5417
|
||||
12160.0 11.6197
|
||||
12192.0 11.6980
|
||||
12224.0 11.7767
|
||||
12256.0 11.8560
|
||||
12288.0 11.9356
|
||||
12320.0 12.0155
|
||||
12352.0 12.0958
|
||||
12384.0 12.1763
|
||||
12416.0 12.2573
|
||||
12448.0 12.3388
|
||||
12480.0 12.4206
|
||||
12512.0 12.5028
|
||||
12544.0 12.5853
|
||||
12576.0 12.6681
|
||||
12608.0 12.7513
|
||||
12640.0 12.8351
|
||||
12672.0 12.9192
|
||||
12704.0 13.0037
|
||||
12736.0 13.0885
|
||||
12768.0 13.1737
|
||||
12800.0 13.2592
|
||||
12832.0 13.3452
|
||||
12864.0 13.4316
|
||||
12896.0 13.5184
|
||||
12928.0 13.6056
|
||||
12960.0 13.6931
|
||||
12992.0 13.7809
|
||||
13024.0 13.8691
|
||||
13056.0 13.9580
|
||||
13088.0 14.0472
|
||||
13120.0 14.1368
|
||||
13152.0 14.2267
|
||||
13184.0 14.3170
|
||||
13216.0 14.4076
|
||||
13248.0 14.4987
|
||||
13280.0 14.5903
|
||||
13312.0 14.6823
|
||||
13344.0 14.7746
|
||||
13376.0 14.8673
|
||||
13408.0 14.9603
|
||||
13440.0 15.0537
|
||||
13472.0 15.1477
|
||||
13504.0 15.2421
|
||||
13536.0 15.3369
|
||||
13568.0 15.4321
|
||||
13600.0 15.5276
|
||||
13632.0 15.6235
|
||||
13664.0 15.7198
|
||||
13696.0 15.8166
|
||||
13728.0 15.9139
|
||||
13760.0 16.0115
|
||||
13792.0 16.1095
|
||||
13824.0 16.2079
|
||||
13856.0 16.3066
|
||||
13888.0 16.4057
|
||||
13920.0 16.5056
|
||||
13952.0 16.6057
|
||||
13984.0 16.7063
|
||||
14016.0 16.8072
|
||||
14048.0 16.9086
|
||||
14080.0 17.0102
|
||||
14112.0 17.1123
|
||||
14144.0 17.2151
|
||||
14176.0 17.3182
|
||||
14208.0 17.4217
|
||||
14240.0 17.5256
|
||||
14272.0 17.6298
|
||||
14304.0 17.7345
|
||||
14336.0 17.8395
|
||||
14368.0 17.9452
|
||||
14400.0 18.0513
|
||||
14432.0 18.1578
|
||||
14464.0 18.2648
|
||||
14496.0 18.3721
|
||||
14528.0 18.4797
|
||||
14560.0 18.5878
|
||||
14592.0 18.6965
|
||||
14624.0 18.8056
|
||||
14656.0 18.9151
|
||||
14688.0 19.0250
|
||||
14720.0 19.1353
|
||||
14752.0 19.2461
|
||||
14784.0 19.3572
|
||||
14816.0 19.4687
|
||||
14848.0 19.5810
|
||||
14880.0 19.6936
|
||||
14912.0 19.8067
|
||||
14944.0 19.9201
|
||||
14976.0 20.0340
|
||||
15008.0 20.1483
|
||||
15040.0 20.2629
|
||||
15072.0 20.3782
|
||||
15104.0 20.4940
|
||||
15136.0 20.6102
|
||||
15168.0 20.7268
|
||||
15200.0 20.8439
|
||||
15232.0 20.9613
|
||||
15264.0 21.0791
|
||||
15296.0 21.1974
|
||||
15328.0 21.3164
|
||||
15360.0 21.4357
|
||||
15392.0 21.5556
|
||||
15424.0 21.6758
|
||||
15456.0 21.7964
|
||||
15488.0 21.9174
|
||||
15520.0 22.0389
|
||||
15552.0 22.1609
|
||||
15584.0 22.2836
|
||||
15616.0 22.4066
|
||||
15648.0 22.5301
|
||||
15680.0 22.6540
|
||||
15712.0 22.7783
|
||||
15744.0 22.9031
|
||||
15776.0 23.0283
|
||||
15808.0 23.1540
|
||||
15840.0 23.2804
|
||||
15872.0 23.4071
|
||||
15904.0 23.5343
|
||||
15936.0 23.6620
|
||||
15968.0 23.7900
|
||||
16000.0 23.9185
|
||||
16032.0 24.0474
|
||||
16064.0 24.1769
|
||||
16096.0 24.3070
|
||||
16128.0 24.4375
|
||||
16160.0 24.5685
|
||||
16192.0 24.6998
|
||||
16224.0 24.8317
|
||||
16256.0 24.9640
|
||||
16288.0 25.0967
|
||||
16320.0 25.2300
|
||||
16352.0 25.3639
|
||||
16384.0 25.4983
|
||||
16416.0 25.6331
|
||||
16448.0 25.7683
|
||||
16480.0 25.9041
|
||||
16512.0 26.0402
|
||||
16544.0 26.1768
|
||||
16576.0 26.3140
|
||||
16608.0 26.4518
|
||||
16640.0 26.5901
|
||||
16672.0 26.7288
|
||||
16704.0 26.8680
|
||||
16736.0 27.0076
|
||||
16768.0 27.1477
|
||||
16800.0 27.2883
|
||||
16832.0 27.4292
|
||||
16864.0 27.5710
|
||||
16896.0 27.7132
|
||||
16928.0 27.8559
|
||||
16960.0 27.9990
|
||||
16992.0 28.1426
|
||||
17024.0 28.2867
|
||||
17056.0 28.4312
|
||||
17088.0 28.5762
|
||||
17120.0 28.7218
|
||||
17152.0 28.8680
|
||||
17184.0 29.0147
|
||||
17216.0 29.1619
|
||||
17248.0 29.3096
|
||||
17280.0 29.4577
|
||||
17312.0 29.6063
|
||||
17344.0 29.7553
|
||||
17376.0 29.9049
|
||||
17408.0 30.0551
|
||||
17440.0 30.2058
|
||||
17472.0 30.3571
|
||||
17504.0 30.5087
|
||||
17536.0 30.6609
|
||||
17568.0 30.8135
|
||||
17600.0 30.9667
|
||||
17632.0 31.1203
|
||||
17664.0 31.2744
|
||||
17696.0 31.4293
|
||||
17728.0 31.5847
|
||||
17760.0 31.7406
|
||||
17792.0 31.8969
|
||||
17824.0 32.0539
|
||||
17856.0 32.2112
|
||||
17888.0 32.3690
|
||||
17920.0 32.5274
|
||||
17952.0 32.6862
|
||||
17984.0 32.8457
|
||||
18016.0 33.0058
|
||||
18048.0 33.1663
|
||||
18080.0 33.3274
|
||||
18112.0 33.4889
|
||||
18144.0 33.6510
|
||||
18176.0 33.8135
|
||||
18208.0 33.9765
|
||||
18240.0 34.1400
|
||||
18272.0 34.3043
|
||||
18304.0 34.4691
|
||||
18336.0 34.6345
|
||||
18368.0 34.8003
|
||||
18400.0 34.9667
|
||||
18432.0 35.1335
|
||||
18464.0 35.3009
|
||||
18496.0 35.4688
|
||||
18528.0 35.6371
|
||||
18560.0 35.8062
|
||||
18592.0 35.9758
|
||||
18624.0 36.1461
|
||||
18656.0 36.3168
|
||||
18688.0 36.4880
|
||||
18720.0 36.6597
|
||||
18752.0 36.8320
|
||||
18784.0 37.0047
|
||||
18816.0 37.1780
|
||||
18848.0 37.3518
|
||||
18880.0 37.5264
|
||||
18912.0 37.7016
|
||||
18944.0 37.8772
|
||||
18976.0 38.0533
|
||||
19008.0 38.2300
|
||||
19040.0 38.4072
|
||||
19072.0 38.5849
|
||||
19104.0 38.7631
|
||||
19136.0 38.9418
|
||||
19168.0 39.1213
|
||||
19200.0 39.3014
|
||||
19232.0 39.4821
|
||||
19264.0 39.6633
|
||||
19296.0 39.8449
|
||||
19328.0 40.0272
|
||||
19360.0 40.2099
|
||||
19392.0 40.3932
|
||||
19424.0 40.5770
|
||||
19456.0 40.7613
|
||||
19488.0 40.9464
|
||||
19520.0 41.1321
|
||||
19552.0 41.3183
|
||||
19584.0 41.5051
|
||||
19616.0 41.6923
|
||||
19648.0 41.8801
|
||||
19680.0 42.0685
|
||||
19712.0 42.2573
|
||||
19744.0 42.4468
|
||||
19776.0 42.6368
|
||||
19808.0 42.8276
|
||||
19840.0 43.0190
|
||||
19872.0 43.2109
|
||||
19904.0 43.4034
|
||||
19936.0 43.5964
|
||||
19968.0 43.7899
|
||||
20000.0 43.9840
|
||||
20081.3 44.4794
|
||||
20162.8 44.9811
|
||||
20244.8 45.4884
|
||||
20327.0 46.0015
|
||||
20409.6 46.5203
|
||||
20492.5 47.0458
|
||||
20575.8 47.5773
|
||||
20659.4 48.1147
|
||||
20743.3 48.6582
|
||||
20827.6 49.2087
|
||||
20912.2 49.7653
|
||||
20997.2 50.3283
|
||||
21082.5 50.8976
|
||||
21168.1 51.4742
|
||||
21254.1 52.0573
|
||||
21340.5 52.6470
|
||||
21427.2 53.2434
|
||||
21514.3 53.8472
|
||||
21601.7 54.4579
|
||||
21689.4 55.0755
|
||||
21777.6 55.7001
|
||||
21866.0 56.3329
|
||||
21954.9 56.9726
|
||||
22044.1 57.6197
|
||||
22133.6 58.2743
|
||||
22223.6 58.9369
|
||||
22313.9 59.6071
|
||||
22404.5 60.2848
|
||||
22495.5 60.9705
|
||||
22586.9 61.6647
|
||||
22678.7 62.3667
|
||||
22770.8 63.0767
|
||||
22863.4 63.7950
|
||||
22956.3 64.5220
|
||||
23049.5 65.2573
|
||||
23143.2 66.0009
|
||||
23237.2 66.7533
|
||||
23331.6 67.5151
|
||||
23426.4 68.2854
|
||||
23521.6 69.0646
|
||||
23617.1 69.8528
|
||||
23713.1 70.6508
|
||||
23809.4 71.4578
|
||||
23906.2 72.2739
|
||||
24003.3 73.0998
|
||||
24100.8 73.9355
|
||||
24198.7 74.7810
|
||||
24297.1 75.6360
|
||||
24395.8 76.5011
|
||||
24494.9 77.3767
|
||||
24594.4 78.2624
|
||||
24694.3 79.1580
|
||||
24794.7 80.0644
|
||||
24895.4 80.9815
|
||||
24996.6 81.9092
|
||||
25098.1 82.8474
|
||||
25200.1 83.7969
|
||||
25302.5 84.7579
|
||||
25405.3 85.7297
|
||||
25508.5 86.7128
|
||||
25612.1 87.7077
|
||||
25716.2 88.7142
|
||||
25820.7 89.7324
|
||||
25925.6 90.7621
|
||||
26030.9 91.8045
|
||||
26136.7 92.8592
|
||||
26242.9 93.9258
|
||||
26349.5 95.0048
|
||||
26456.5 96.0966
|
||||
26564.0 97.2014
|
||||
26672.0 98.3188
|
||||
26780.3 99.4487
|
||||
26889.1 100.593
|
||||
26998.4 101.750
|
||||
27108.1 102.920
|
||||
27218.2 104.104
|
||||
27328.8 105.303
|
||||
27439.8 106.515
|
||||
27551.3 107.741
|
||||
27663.2 108.981
|
||||
27775.6 110.237
|
||||
27888.5 111.507
|
||||
28001.8 112.791
|
||||
28115.6 114.091
|
||||
28229.8 115.406
|
||||
28344.5 116.736
|
||||
28459.6 118.082
|
||||
28575.3 119.443
|
||||
28691.4 120.821
|
||||
28807.9 122.214
|
||||
28925.0 123.624
|
||||
29042.5 125.050
|
||||
29160.5 126.493
|
||||
29279.0 127.953
|
||||
29397.9 129.430
|
||||
29517.4 130.924
|
||||
29637.3 132.436
|
||||
29757.7 133.965
|
||||
29878.6 135.512
|
||||
30000.0 137.076
|
||||
@@ -1,601 +0,0 @@
|
||||
4000.00 9.62277
|
||||
4032.00 9.83842
|
||||
4064.00 10.0573
|
||||
4096.00 10.2794
|
||||
4128.00 10.5051
|
||||
4160.00 10.7339
|
||||
4192.00 10.9660
|
||||
4224.00 11.2014
|
||||
4256.00 11.4403
|
||||
4288.00 11.6826
|
||||
4320.00 11.9284
|
||||
4352.00 12.1776
|
||||
4384.00 12.4304
|
||||
4416.00 12.6865
|
||||
4448.00 12.9464
|
||||
4480.00 13.2098
|
||||
4512.00 13.4767
|
||||
4544.00 13.7474
|
||||
4576.00 14.0215
|
||||
4608.00 14.2994
|
||||
4640.00 14.5809
|
||||
4672.00 14.8664
|
||||
4704.00 15.1555
|
||||
4736.00 15.4484
|
||||
4768.00 15.7450
|
||||
4800.00 16.0453
|
||||
4832.00 16.3495
|
||||
4864.00 16.6575
|
||||
4896.00 16.9695
|
||||
4928.00 17.2853
|
||||
4960.00 17.6050
|
||||
4992.00 17.9287
|
||||
5024.00 18.2562
|
||||
5056.00 18.5883
|
||||
5088.00 18.9244
|
||||
5120.00 19.2644
|
||||
5152.00 19.6086
|
||||
5184.00 19.9567
|
||||
5216.00 20.3089
|
||||
5248.00 20.6651
|
||||
5280.00 21.0254
|
||||
5312.00 21.3903
|
||||
5344.00 21.7594
|
||||
5376.00 22.1326
|
||||
5408.00 22.5101
|
||||
5440.00 22.8918
|
||||
5472.00 23.2781
|
||||
5504.00 23.6687
|
||||
5536.00 24.0635
|
||||
5568.00 24.4631
|
||||
5600.00 24.8670
|
||||
5632.00 25.2752
|
||||
5664.00 25.6881
|
||||
5696.00 26.1054
|
||||
5728.00 26.5272
|
||||
5760.00 26.9536
|
||||
5792.00 27.3845
|
||||
5824.00 27.8199
|
||||
5856.00 28.2600
|
||||
5888.00 28.7046
|
||||
5920.00 29.1541
|
||||
5952.00 29.6085
|
||||
5984.00 30.0675
|
||||
6016.00 30.5313
|
||||
6048.00 31.0001
|
||||
6080.00 31.4734
|
||||
6112.00 31.9515
|
||||
6144.00 32.4345
|
||||
6176.00 32.9223
|
||||
6208.00 33.4148
|
||||
6240.00 33.9124
|
||||
6272.00 34.4149
|
||||
6304.00 34.9221
|
||||
6336.00 35.4342
|
||||
6368.00 35.9524
|
||||
6400.00 36.4762
|
||||
6432.00 37.0050
|
||||
6464.00 37.5379
|
||||
6496.00 38.0757
|
||||
6528.00 38.6185
|
||||
6560.00 39.1667
|
||||
6592.00 39.7202
|
||||
6624.00 40.2787
|
||||
6656.00 40.8428
|
||||
6688.00 41.4125
|
||||
6720.00 41.9874
|
||||
6752.00 42.5676
|
||||
6784.00 43.1532
|
||||
6816.00 43.7441
|
||||
6848.00 44.3403
|
||||
6880.00 44.9420
|
||||
6912.00 45.5490
|
||||
6944.00 46.1614
|
||||
6976.00 46.7793
|
||||
7008.00 47.4032
|
||||
7040.00 48.0324
|
||||
7072.00 48.6671
|
||||
7104.00 49.3076
|
||||
7136.00 49.9537
|
||||
7168.00 50.6054
|
||||
7200.00 51.2625
|
||||
7232.00 51.9256
|
||||
7264.00 52.5944
|
||||
7296.00 53.2688
|
||||
7328.00 53.9491
|
||||
7360.00 54.6357
|
||||
7392.00 55.3279
|
||||
7424.00 56.0259
|
||||
7456.00 56.7298
|
||||
7488.00 57.4395
|
||||
7520.00 58.1551
|
||||
7552.00 58.8764
|
||||
7584.00 59.6041
|
||||
7616.00 60.3376
|
||||
7648.00 61.0770
|
||||
7680.00 61.8224
|
||||
7712.00 62.5745
|
||||
7744.00 63.3325
|
||||
7776.00 64.0965
|
||||
7808.00 64.8666
|
||||
7840.00 65.6432
|
||||
7872.00 66.4257
|
||||
7904.00 67.2144
|
||||
7936.00 68.0094
|
||||
7968.00 68.8110
|
||||
8000.00 69.6188
|
||||
8032.00 70.4328
|
||||
8064.00 71.2532
|
||||
8096.00 72.0802
|
||||
8128.00 72.9134
|
||||
8160.00 73.7529
|
||||
8192.00 74.5991
|
||||
8224.00 75.4518
|
||||
8256.00 76.3108
|
||||
8288.00 77.1762
|
||||
8320.00 78.0482
|
||||
8352.00 78.9273
|
||||
8384.00 79.8130
|
||||
8416.00 80.7051
|
||||
8448.00 81.6036
|
||||
8480.00 82.5091
|
||||
8512.00 83.4214
|
||||
8544.00 84.3400
|
||||
8576.00 85.2654
|
||||
8608.00 86.1981
|
||||
8640.00 87.1375
|
||||
8672.00 88.0838
|
||||
8704.00 89.0367
|
||||
8736.00 89.9967
|
||||
8768.00 90.9639
|
||||
8800.00 91.9378
|
||||
8832.00 92.9185
|
||||
8864.00 93.9060
|
||||
8896.00 94.9009
|
||||
8928.00 95.9025
|
||||
8960.00 96.9111
|
||||
8992.00 97.9265
|
||||
9024.00 98.9496
|
||||
9056.00 99.9804
|
||||
9088.00 101.018
|
||||
9120.00 102.063
|
||||
9152.00 103.115
|
||||
9184.00 104.174
|
||||
9216.00 105.240
|
||||
9248.00 106.314
|
||||
9280.00 107.395
|
||||
9312.00 108.483
|
||||
9344.00 109.578
|
||||
9376.00 110.681
|
||||
9408.00 111.791
|
||||
9440.00 112.909
|
||||
9472.00 114.034
|
||||
9504.00 115.168
|
||||
9536.00 116.308
|
||||
9568.00 117.457
|
||||
9600.00 118.612
|
||||
9632.00 119.776
|
||||
9664.00 120.947
|
||||
9696.00 122.125
|
||||
9728.00 123.311
|
||||
9760.00 124.505
|
||||
9792.00 125.707
|
||||
9824.00 126.917
|
||||
9856.00 128.134
|
||||
9888.00 129.358
|
||||
9920.00 130.591
|
||||
9952.00 131.832
|
||||
9984.00 133.080
|
||||
10016.0 134.336
|
||||
10048.0 135.601
|
||||
10080.0 136.872
|
||||
10112.0 138.154
|
||||
10144.0 139.442
|
||||
10176.0 140.739
|
||||
10208.0 142.044
|
||||
10240.0 143.356
|
||||
10272.0 144.677
|
||||
10304.0 146.006
|
||||
10336.0 147.343
|
||||
10368.0 148.687
|
||||
10400.0 150.040
|
||||
10432.0 151.402
|
||||
10464.0 152.773
|
||||
10496.0 154.152
|
||||
10528.0 155.538
|
||||
10560.0 156.933
|
||||
10592.0 158.336
|
||||
10624.0 159.748
|
||||
10656.0 161.168
|
||||
10688.0 162.596
|
||||
10720.0 164.032
|
||||
10752.0 165.477
|
||||
10784.0 166.932
|
||||
10816.0 168.395
|
||||
10848.0 169.867
|
||||
10880.0 171.346
|
||||
10912.0 172.835
|
||||
10944.0 174.332
|
||||
10976.0 175.837
|
||||
11008.0 177.351
|
||||
11040.0 178.873
|
||||
11072.0 180.404
|
||||
11104.0 181.944
|
||||
11136.0 183.494
|
||||
11168.0 185.052
|
||||
11200.0 186.620
|
||||
11232.0 188.195
|
||||
11264.0 189.780
|
||||
11296.0 191.374
|
||||
11328.0 192.976
|
||||
11360.0 194.588
|
||||
11392.0 196.209
|
||||
11424.0 197.838
|
||||
11456.0 199.475
|
||||
11488.0 201.123
|
||||
11520.0 202.780
|
||||
11552.0 204.446
|
||||
11584.0 206.121
|
||||
11616.0 207.805
|
||||
11648.0 209.497
|
||||
11680.0 211.201
|
||||
11712.0 212.913
|
||||
11744.0 214.634
|
||||
11776.0 216.365
|
||||
11808.0 218.104
|
||||
11840.0 219.853
|
||||
11872.0 221.612
|
||||
11904.0 223.381
|
||||
11936.0 225.158
|
||||
11968.0 226.944
|
||||
12000.0 228.740
|
||||
12032.0 230.545
|
||||
12064.0 232.361
|
||||
12096.0 234.186
|
||||
12128.0 236.020
|
||||
12160.0 237.864
|
||||
12192.0 239.716
|
||||
12224.0 241.579
|
||||
12256.0 243.451
|
||||
12288.0 245.333
|
||||
12320.0 247.226
|
||||
12352.0 249.127
|
||||
12384.0 251.038
|
||||
12416.0 252.958
|
||||
12448.0 254.890
|
||||
12480.0 256.832
|
||||
12512.0 258.783
|
||||
12544.0 260.744
|
||||
12576.0 262.715
|
||||
12608.0 264.695
|
||||
12640.0 266.686
|
||||
12672.0 268.687
|
||||
12704.0 270.697
|
||||
12736.0 272.718
|
||||
12768.0 274.748
|
||||
12800.0 276.788
|
||||
12832.0 278.839
|
||||
12864.0 280.900
|
||||
12896.0 282.971
|
||||
12928.0 285.053
|
||||
12960.0 287.144
|
||||
12992.0 289.246
|
||||
13024.0 291.357
|
||||
13056.0 293.479
|
||||
13088.0 295.612
|
||||
13120.0 297.754
|
||||
13152.0 299.907
|
||||
13184.0 302.069
|
||||
13216.0 304.242
|
||||
13248.0 306.425
|
||||
13280.0 308.621
|
||||
13312.0 310.827
|
||||
13344.0 313.044
|
||||
13376.0 315.270
|
||||
13408.0 317.508
|
||||
13440.0 319.755
|
||||
13472.0 322.013
|
||||
13504.0 324.282
|
||||
13536.0 326.561
|
||||
13568.0 328.850
|
||||
13600.0 331.150
|
||||
13632.0 333.460
|
||||
13664.0 335.781
|
||||
13696.0 338.114
|
||||
13728.0 340.458
|
||||
13760.0 342.813
|
||||
13792.0 345.178
|
||||
13824.0 347.554
|
||||
13856.0 349.940
|
||||
13888.0 352.337
|
||||
13920.0 354.747
|
||||
13952.0 357.167
|
||||
13984.0 359.599
|
||||
14016.0 362.041
|
||||
14048.0 364.494
|
||||
14080.0 366.957
|
||||
14112.0 369.432
|
||||
14144.0 371.917
|
||||
14176.0 374.414
|
||||
14208.0 376.922
|
||||
14240.0 379.440
|
||||
14272.0 381.970
|
||||
14304.0 384.509
|
||||
14336.0 387.061
|
||||
14368.0 389.625
|
||||
14400.0 392.202
|
||||
14432.0 394.789
|
||||
14464.0 397.388
|
||||
14496.0 399.998
|
||||
14528.0 402.618
|
||||
14560.0 405.251
|
||||
14592.0 407.895
|
||||
14624.0 410.550
|
||||
14656.0 413.216
|
||||
14688.0 415.894
|
||||
14720.0 418.584
|
||||
14752.0 421.285
|
||||
14784.0 423.996
|
||||
14816.0 426.719
|
||||
14848.0 429.456
|
||||
14880.0 432.204
|
||||
14912.0 434.963
|
||||
14944.0 437.734
|
||||
14976.0 440.516
|
||||
15008.0 443.310
|
||||
15040.0 446.117
|
||||
15072.0 448.937
|
||||
15104.0 451.769
|
||||
15136.0 454.612
|
||||
15168.0 457.468
|
||||
15200.0 460.335
|
||||
15232.0 463.213
|
||||
15264.0 466.102
|
||||
15296.0 469.003
|
||||
15328.0 471.919
|
||||
15360.0 474.845
|
||||
15392.0 477.785
|
||||
15424.0 480.734
|
||||
15456.0 483.697
|
||||
15488.0 486.670
|
||||
15520.0 489.657
|
||||
15552.0 492.654
|
||||
15584.0 495.668
|
||||
15616.0 498.692
|
||||
15648.0 501.728
|
||||
15680.0 504.777
|
||||
15712.0 507.838
|
||||
15744.0 510.910
|
||||
15776.0 513.994
|
||||
15808.0 517.091
|
||||
15840.0 520.202
|
||||
15872.0 523.325
|
||||
15904.0 526.460
|
||||
15936.0 529.607
|
||||
15968.0 532.766
|
||||
16000.0 535.937
|
||||
16032.0 539.120
|
||||
16064.0 542.317
|
||||
16096.0 545.528
|
||||
16128.0 548.751
|
||||
16160.0 551.986
|
||||
16192.0 555.233
|
||||
16224.0 558.493
|
||||
16256.0 561.765
|
||||
16288.0 565.049
|
||||
16320.0 568.346
|
||||
16352.0 571.658
|
||||
16384.0 574.983
|
||||
16416.0 578.320
|
||||
16448.0 581.669
|
||||
16480.0 585.031
|
||||
16512.0 588.405
|
||||
16544.0 591.791
|
||||
16576.0 595.191
|
||||
16608.0 598.605
|
||||
16640.0 602.032
|
||||
16672.0 605.472
|
||||
16704.0 608.925
|
||||
16736.0 612.389
|
||||
16768.0 615.866
|
||||
16800.0 619.357
|
||||
16832.0 622.859
|
||||
16864.0 626.377
|
||||
16896.0 629.907
|
||||
16928.0 633.450
|
||||
16960.0 637.007
|
||||
16992.0 640.575
|
||||
17024.0 644.157
|
||||
17056.0 647.751
|
||||
17088.0 651.359
|
||||
17120.0 654.980
|
||||
17152.0 658.617
|
||||
17184.0 662.267
|
||||
17216.0 665.931
|
||||
17248.0 669.607
|
||||
17280.0 673.296
|
||||
17312.0 676.998
|
||||
17344.0 680.712
|
||||
17376.0 684.440
|
||||
17408.0 688.184
|
||||
17440.0 691.941
|
||||
17472.0 695.713
|
||||
17504.0 699.495
|
||||
17536.0 703.292
|
||||
17568.0 707.102
|
||||
17600.0 710.925
|
||||
17632.0 714.761
|
||||
17664.0 718.610
|
||||
17696.0 722.475
|
||||
17728.0 726.355
|
||||
17760.0 730.248
|
||||
17792.0 734.153
|
||||
17824.0 738.073
|
||||
17856.0 742.005
|
||||
17888.0 745.950
|
||||
17920.0 749.909
|
||||
17952.0 753.881
|
||||
17984.0 757.869
|
||||
18016.0 761.873
|
||||
18048.0 765.889
|
||||
18080.0 769.919
|
||||
18112.0 773.963
|
||||
18144.0 778.019
|
||||
18176.0 782.090
|
||||
18208.0 786.173
|
||||
18240.0 790.271
|
||||
18272.0 794.384
|
||||
18304.0 798.512
|
||||
18336.0 802.655
|
||||
18368.0 806.810
|
||||
18400.0 810.978
|
||||
18432.0 815.161
|
||||
18464.0 819.358
|
||||
18496.0 823.568
|
||||
18528.0 827.792
|
||||
18560.0 832.031
|
||||
18592.0 836.287
|
||||
18624.0 840.558
|
||||
18656.0 844.842
|
||||
18688.0 849.140
|
||||
18720.0 853.452
|
||||
18752.0 857.777
|
||||
18784.0 862.116
|
||||
18816.0 866.467
|
||||
18848.0 870.834
|
||||
18880.0 875.220
|
||||
18912.0 879.619
|
||||
18944.0 884.030
|
||||
18976.0 888.457
|
||||
19008.0 892.897
|
||||
19040.0 897.352
|
||||
19072.0 901.820
|
||||
19104.0 906.300
|
||||
19136.0 910.796
|
||||
19168.0 915.308
|
||||
19200.0 919.837
|
||||
19232.0 924.381
|
||||
19264.0 928.937
|
||||
19296.0 933.507
|
||||
19328.0 938.093
|
||||
19360.0 942.690
|
||||
19392.0 947.303
|
||||
19424.0 951.930
|
||||
19456.0 956.571
|
||||
19488.0 961.231
|
||||
19520.0 965.908
|
||||
19552.0 970.597
|
||||
19584.0 975.302
|
||||
19616.0 980.021
|
||||
19648.0 984.753
|
||||
19680.0 989.501
|
||||
19712.0 994.261
|
||||
19744.0 999.036
|
||||
19776.0 1003.83
|
||||
19808.0 1008.64
|
||||
19840.0 1013.46
|
||||
19872.0 1018.30
|
||||
19904.0 1023.16
|
||||
19936.0 1028.03
|
||||
19968.0 1032.91
|
||||
20000.0 1037.81
|
||||
20081.3 1050.33
|
||||
20162.8 1063.00
|
||||
20244.8 1075.81
|
||||
20327.0 1088.77
|
||||
20409.6 1101.88
|
||||
20492.5 1115.15
|
||||
20575.8 1128.57
|
||||
20659.4 1142.15
|
||||
20743.3 1155.88
|
||||
20827.6 1169.78
|
||||
20912.2 1183.84
|
||||
20997.2 1198.06
|
||||
21082.5 1212.45
|
||||
21168.1 1227.01
|
||||
21254.1 1241.73
|
||||
21340.5 1256.62
|
||||
21427.2 1271.69
|
||||
21514.3 1286.92
|
||||
21601.7 1302.34
|
||||
21689.4 1317.93
|
||||
21777.6 1333.69
|
||||
21866.0 1349.65
|
||||
21954.9 1365.78
|
||||
22044.1 1382.10
|
||||
22133.6 1398.61
|
||||
22223.6 1415.31
|
||||
22313.9 1432.20
|
||||
22404.5 1449.27
|
||||
22495.5 1466.55
|
||||
22586.9 1484.02
|
||||
22678.7 1501.69
|
||||
22770.8 1519.56
|
||||
22863.4 1537.63
|
||||
22956.3 1555.91
|
||||
23049.5 1574.40
|
||||
23143.2 1593.09
|
||||
23237.2 1611.99
|
||||
23331.6 1631.11
|
||||
23426.4 1650.44
|
||||
23521.6 1669.98
|
||||
23617.1 1689.75
|
||||
23713.1 1709.74
|
||||
23809.4 1729.95
|
||||
23906.2 1750.38
|
||||
24003.3 1771.04
|
||||
24100.8 1791.94
|
||||
24198.7 1813.06
|
||||
24297.1 1834.42
|
||||
24395.8 1856.01
|
||||
24494.9 1877.85
|
||||
24594.4 1899.93
|
||||
24694.3 1922.25
|
||||
24794.7 1944.81
|
||||
24895.4 1967.62
|
||||
24996.6 1990.69
|
||||
25098.1 2014.00
|
||||
25200.1 2037.56
|
||||
25302.5 2061.38
|
||||
25405.3 2085.46
|
||||
25508.5 2109.80
|
||||
25612.1 2134.41
|
||||
25716.2 2159.29
|
||||
25820.7 2184.43
|
||||
25925.6 2209.84
|
||||
26030.9 2235.52
|
||||
26136.7 2261.48
|
||||
26242.9 2287.71
|
||||
26349.5 2314.22
|
||||
26456.5 2341.01
|
||||
26564.0 2368.09
|
||||
26672.0 2395.46
|
||||
26780.3 2423.11
|
||||
26889.1 2451.06
|
||||
26998.4 2479.30
|
||||
27108.1 2507.82
|
||||
27218.2 2536.65
|
||||
27328.8 2565.78
|
||||
27439.8 2595.22
|
||||
27551.3 2624.97
|
||||
27663.2 2655.01
|
||||
27775.6 2685.37
|
||||
27888.5 2716.04
|
||||
28001.8 2747.03
|
||||
28115.6 2778.33
|
||||
28229.8 2809.95
|
||||
28344.5 2841.89
|
||||
28459.6 2874.14
|
||||
28575.3 2906.73
|
||||
28691.4 2939.65
|
||||
28807.9 2972.90
|
||||
28925.0 3006.49
|
||||
29042.5 3040.40
|
||||
29160.5 3074.66
|
||||
29279.0 3109.27
|
||||
29397.9 3144.20
|
||||
29517.4 3179.48
|
||||
29637.3 3215.11
|
||||
29757.7 3251.08
|
||||
29878.6 3287.40
|
||||
30000.0 3324.06
|
||||
@@ -1,689 +0,0 @@
|
||||
4000.00 14.6403
|
||||
4012.90 14.7694
|
||||
4025.83 14.8996
|
||||
4038.81 15.0310
|
||||
4051.83 15.1635
|
||||
4064.90 15.2972
|
||||
4078.00 15.4320
|
||||
4091.15 15.5680
|
||||
4104.34 15.7050
|
||||
4117.57 15.8433
|
||||
4130.85 15.9828
|
||||
4144.17 16.1237
|
||||
4157.53 16.2662
|
||||
4170.93 16.4100
|
||||
4184.38 16.5550
|
||||
4197.87 16.7012
|
||||
4211.41 16.8485
|
||||
4224.98 16.9972
|
||||
4238.60 17.1472
|
||||
4252.27 17.2985
|
||||
4265.98 17.4509
|
||||
4279.73 17.6047
|
||||
4293.53 17.7599
|
||||
4307.37 17.9166
|
||||
4321.26 18.0750
|
||||
4335.19 18.2347
|
||||
4349.17 18.3959
|
||||
4363.19 18.5586
|
||||
4377.26 18.7226
|
||||
4391.37 18.8881
|
||||
4405.53 19.0551
|
||||
4419.73 19.2235
|
||||
4433.98 19.3933
|
||||
4448.28 19.5646
|
||||
4462.62 19.7375
|
||||
4477.01 19.9119
|
||||
4491.44 20.0878
|
||||
4505.92 20.2653
|
||||
4520.45 20.4443
|
||||
4535.02 20.6251
|
||||
4549.65 20.8077
|
||||
4564.31 20.9919
|
||||
4579.03 21.1778
|
||||
4593.79 21.3654
|
||||
4608.60 21.5546
|
||||
4623.46 21.7455
|
||||
4638.37 21.9381
|
||||
4653.32 22.1324
|
||||
4668.33 22.3285
|
||||
4683.38 22.5262
|
||||
4698.48 22.7258
|
||||
4713.62 22.9271
|
||||
4728.82 23.1303
|
||||
4744.07 23.3353
|
||||
4759.36 23.5420
|
||||
4774.71 23.7507
|
||||
4790.10 23.9613
|
||||
4805.54 24.1737
|
||||
4821.04 24.3880
|
||||
4836.58 24.6042
|
||||
4852.17 24.8225
|
||||
4867.82 25.0427
|
||||
4883.51 25.2649
|
||||
4899.26 25.4891
|
||||
4915.05 25.7153
|
||||
4930.90 25.9436
|
||||
4946.80 26.1739
|
||||
4960.00 26.3662
|
||||
4960.20 26.3691
|
||||
4960.40 26.3720
|
||||
4960.60 26.3750
|
||||
4960.80 26.3779
|
||||
4961.00 26.3808
|
||||
4961.20 26.3837
|
||||
4961.40 26.3866
|
||||
4961.60 26.3896
|
||||
4961.80 26.3925
|
||||
4962.00 26.3954
|
||||
4962.20 26.3983
|
||||
4962.40 26.4012
|
||||
4962.60 26.4041
|
||||
4962.80 26.4071
|
||||
4963.00 26.4100
|
||||
4963.20 26.4129
|
||||
4963.40 26.4158
|
||||
4963.60 26.4188
|
||||
4963.80 26.4217
|
||||
4964.00 26.4246
|
||||
4964.20 26.4275
|
||||
4964.40 26.4304
|
||||
4964.60 26.4334
|
||||
4964.80 26.4363
|
||||
4965.00 26.4392
|
||||
4965.20 26.4421
|
||||
4965.40 26.4451
|
||||
4965.60 26.4480
|
||||
4965.80 26.4509
|
||||
4966.00 26.4539
|
||||
4966.20 26.4568
|
||||
4966.21 26.4570
|
||||
4966.22 26.4571
|
||||
4966.23 26.4573
|
||||
4966.24 26.4574
|
||||
4966.25 26.4576
|
||||
4966.26 26.4577
|
||||
4966.27 26.4579
|
||||
4966.28 26.4580
|
||||
4966.29 26.4581
|
||||
4966.30 26.4583
|
||||
4966.31 23.8848
|
||||
4966.32 21.5614
|
||||
4966.33 19.4639
|
||||
4966.34 16.6939
|
||||
4966.35 15.0697
|
||||
4966.36 13.6035
|
||||
4966.37 12.2799
|
||||
4966.38 11.0851
|
||||
4966.39 10.0065
|
||||
4966.40 9.03278
|
||||
4966.41 8.15382
|
||||
4966.42 7.36037
|
||||
4966.43 6.31256
|
||||
4966.44 5.69825
|
||||
4966.45 5.14372
|
||||
4966.46 4.64315
|
||||
4966.47 4.19129
|
||||
4966.48 3.78339
|
||||
4966.49 3.41519
|
||||
4966.50 3.08282
|
||||
4966.51 3.08283
|
||||
4966.52 3.08285
|
||||
4966.53 3.08287
|
||||
4966.54 3.08288
|
||||
4966.55 3.08290
|
||||
4966.56 3.08291
|
||||
4966.57 3.08293
|
||||
4966.58 3.08294
|
||||
4966.59 3.08296
|
||||
4966.60 3.08297
|
||||
4966.80 3.08327
|
||||
4967.00 3.08358
|
||||
4967.20 3.08388
|
||||
4967.40 3.08419
|
||||
4967.60 3.08449
|
||||
4967.80 3.08479
|
||||
4968.00 3.08510
|
||||
4968.20 3.08540
|
||||
4968.40 3.08570
|
||||
4968.60 3.08601
|
||||
4968.80 3.08631
|
||||
4969.00 3.08662
|
||||
4969.20 3.08692
|
||||
4969.40 3.08722
|
||||
4969.60 3.08753
|
||||
4969.80 3.08783
|
||||
4970.00 3.08814
|
||||
4978.75 3.10144
|
||||
4994.80 3.12596
|
||||
5010.90 3.15067
|
||||
5027.06 3.17557
|
||||
5043.26 3.20067
|
||||
5059.52 3.22597
|
||||
5075.84 3.25146
|
||||
5092.20 3.27716
|
||||
5108.62 3.30319
|
||||
5125.09 3.32942
|
||||
5141.61 3.35585
|
||||
5158.19 3.38250
|
||||
5174.82 3.40948
|
||||
5191.50 3.43668
|
||||
5208.24 3.46410
|
||||
5225.03 3.49173
|
||||
5241.88 3.51971
|
||||
5258.78 3.54792
|
||||
5275.73 3.57634
|
||||
5292.74 3.60500
|
||||
5309.81 3.63399
|
||||
5326.93 3.66323
|
||||
5344.10 3.69270
|
||||
5361.33 3.72241
|
||||
5378.62 3.75246
|
||||
5395.96 3.78276
|
||||
5413.35 3.81331
|
||||
5430.81 3.84410
|
||||
5448.32 3.87526
|
||||
5465.88 3.90668
|
||||
5483.50 3.93837
|
||||
5501.18 3.97031
|
||||
5518.92 4.00261
|
||||
5536.71 4.03521
|
||||
5554.56 4.06807
|
||||
5572.47 4.10120
|
||||
5590.44 4.13469
|
||||
5608.46 4.16849
|
||||
5626.54 4.20255
|
||||
5644.68 4.23691
|
||||
5662.88 4.27164
|
||||
5681.14 4.30669
|
||||
5699.46 4.34202
|
||||
5717.83 4.37766
|
||||
5736.27 4.41366
|
||||
5754.76 4.44998
|
||||
5773.31 4.48660
|
||||
5791.93 4.52352
|
||||
5810.60 4.56084
|
||||
5829.33 4.59850
|
||||
5848.13 4.63647
|
||||
5866.98 4.67476
|
||||
5885.90 4.71346
|
||||
5904.88 4.75253
|
||||
5923.91 4.79192
|
||||
5943.01 4.83164
|
||||
5962.17 4.87178
|
||||
5981.40 4.91229
|
||||
6000.68 4.95315
|
||||
6020.03 4.99434
|
||||
6039.44 5.03595
|
||||
6058.91 5.07795
|
||||
6078.44 5.12030
|
||||
6098.04 5.16301
|
||||
6117.70 5.20616
|
||||
6137.42 5.24972
|
||||
6157.21 5.29365
|
||||
6177.06 5.33794
|
||||
6196.98 5.38268
|
||||
6216.96 5.42785
|
||||
6237.00 5.47339
|
||||
6257.11 5.51932
|
||||
6277.28 5.56570
|
||||
6297.52 5.61256
|
||||
6317.82 5.65980
|
||||
6338.19 5.70744
|
||||
6358.63 5.75555
|
||||
6379.13 5.80417
|
||||
6399.69 5.85320
|
||||
6420.33 5.90263
|
||||
6441.03 5.95254
|
||||
6461.79 6.00294
|
||||
6482.63 6.05376
|
||||
6503.53 6.10501
|
||||
6524.49 6.15675
|
||||
6545.53 6.20899
|
||||
6566.63 6.26170
|
||||
6587.80 6.31484
|
||||
6609.04 6.36848
|
||||
6630.35 6.42265
|
||||
6651.73 6.47729
|
||||
6673.17 6.53239
|
||||
6694.69 6.58802
|
||||
6716.27 6.64423
|
||||
6737.93 6.70093
|
||||
6759.65 6.75810
|
||||
6781.44 6.81583
|
||||
6803.31 6.87416
|
||||
6825.24 6.93301
|
||||
6847.25 6.99235
|
||||
6869.32 7.05223
|
||||
6891.47 7.11270
|
||||
6913.69 7.17368
|
||||
6935.98 7.23521
|
||||
6958.34 7.29728
|
||||
6980.77 7.35997
|
||||
7003.28 7.42321
|
||||
7025.86 7.48699
|
||||
7048.51 7.55137
|
||||
7071.24 7.61644
|
||||
7094.03 7.68207
|
||||
7116.91 7.74827
|
||||
7139.85 7.81507
|
||||
7162.87 7.88255
|
||||
7185.96 7.95061
|
||||
7209.13 8.01927
|
||||
7232.38 8.08853
|
||||
7255.69 8.15854
|
||||
7279.09 8.22916
|
||||
7302.55 8.30039
|
||||
7326.10 8.37226
|
||||
7349.72 8.44491
|
||||
7373.41 8.51819
|
||||
7397.19 8.59211
|
||||
7421.03 8.66669
|
||||
7444.96 8.74202
|
||||
7468.96 8.81801
|
||||
7493.04 8.89466
|
||||
7517.20 8.97199
|
||||
7541.44 9.05018
|
||||
7565.75 9.12905
|
||||
7590.14 9.20861
|
||||
7614.62 9.28887
|
||||
7639.17 9.36994
|
||||
7663.79 9.45172
|
||||
7688.50 9.53421
|
||||
7713.29 9.61744
|
||||
7738.16 9.70154
|
||||
7763.11 9.78637
|
||||
7788.14 9.87195
|
||||
7813.25 9.95827
|
||||
7838.44 10.0456
|
||||
7863.71 10.1336
|
||||
7889.06 10.2224
|
||||
7914.50 10.3120
|
||||
7940.01 10.4025
|
||||
7965.61 10.4937
|
||||
7991.29 10.5858
|
||||
8017.06 10.6786
|
||||
8042.91 10.7726
|
||||
8068.84 10.8674
|
||||
8094.85 10.9630
|
||||
8120.95 11.0595
|
||||
8147.13 11.1570
|
||||
8173.40 11.2553
|
||||
8199.75 11.3545
|
||||
8226.19 11.4546
|
||||
8252.71 11.5557
|
||||
8279.32 11.6578
|
||||
8306.01 11.7607
|
||||
8332.79 11.8646
|
||||
8359.65 11.9695
|
||||
8386.60 12.0754
|
||||
8413.64 12.1822
|
||||
8440.77 12.2900
|
||||
8467.98 12.3988
|
||||
8495.29 12.5087
|
||||
8522.67 12.6195
|
||||
8550.15 12.7312
|
||||
8577.72 12.8441
|
||||
8605.37 12.9580
|
||||
8633.12 13.0730
|
||||
8660.95 13.1889
|
||||
8688.87 13.3061
|
||||
8716.89 13.4244
|
||||
8744.99 13.5438
|
||||
8773.19 13.6643
|
||||
8801.47 13.7858
|
||||
8829.85 13.9084
|
||||
8858.32 14.0320
|
||||
8886.88 14.1568
|
||||
8915.53 14.2830
|
||||
8944.27 14.4105
|
||||
8973.11 14.5392
|
||||
9002.04 14.6689
|
||||
9031.06 14.7999
|
||||
9060.18 14.9320
|
||||
9089.39 15.0654
|
||||
9118.69 15.1999
|
||||
9148.09 15.3357
|
||||
9177.59 15.4728
|
||||
9207.18 15.6112
|
||||
9236.86 15.7509
|
||||
9266.64 15.8917
|
||||
9296.52 16.0339
|
||||
9326.49 16.1773
|
||||
9356.56 16.3221
|
||||
9386.72 16.4684
|
||||
9416.99 16.6162
|
||||
9447.35 16.7655
|
||||
9477.81 16.9160
|
||||
9508.37 17.0680
|
||||
9539.02 17.2213
|
||||
9569.78 17.3760
|
||||
9600.63 17.5322
|
||||
9631.58 17.6897
|
||||
9662.63 17.8488
|
||||
9693.79 18.0092
|
||||
9725.04 18.1712
|
||||
9756.39 18.3347
|
||||
9787.85 18.4999
|
||||
9819.41 18.6666
|
||||
9851.07 18.8348
|
||||
9882.83 19.0044
|
||||
9914.69 19.1758
|
||||
9946.65 19.3488
|
||||
9978.72 19.5234
|
||||
10010.9 19.6996
|
||||
10043.2 19.8773
|
||||
10075.5 20.0569
|
||||
10108.0 20.2382
|
||||
10140.6 20.4211
|
||||
10173.3 20.6057
|
||||
10206.1 20.7920
|
||||
10239.0 20.9800
|
||||
10272.0 21.1700
|
||||
10305.2 21.3616
|
||||
10338.4 21.5550
|
||||
10371.7 21.7500
|
||||
10405.1 21.9471
|
||||
10438.7 22.1461
|
||||
10472.3 22.3469
|
||||
10506.1 22.5495
|
||||
10540.0 22.7540
|
||||
10574.0 22.9605
|
||||
10608.1 23.1689
|
||||
10642.3 23.3792
|
||||
10676.6 23.5915
|
||||
10711.0 23.8057
|
||||
10745.5 24.0220
|
||||
10780.2 24.2404
|
||||
10814.9 24.4608
|
||||
10849.8 24.6833
|
||||
10884.8 24.9077
|
||||
10919.9 25.1343
|
||||
10955.1 25.3632
|
||||
10990.4 25.5942
|
||||
11025.8 25.8273
|
||||
11061.4 26.0625
|
||||
11097.0 26.3000
|
||||
11132.8 26.5398
|
||||
11168.7 26.7818
|
||||
11204.7 27.0260
|
||||
11240.8 27.2724
|
||||
11277.1 27.5212
|
||||
11313.4 27.7726
|
||||
11349.9 28.0262
|
||||
11386.5 28.2822
|
||||
11423.2 28.5405
|
||||
11460.0 28.8013
|
||||
11497.0 29.0646
|
||||
11534.1 29.3304
|
||||
11571.2 29.5985
|
||||
11608.6 29.8691
|
||||
11646.0 30.1424
|
||||
11683.5 30.4183
|
||||
11721.2 30.6966
|
||||
11759.0 30.9775
|
||||
11796.9 31.2610
|
||||
11834.9 31.5474
|
||||
11873.1 31.8366
|
||||
11911.4 32.1285
|
||||
11949.8 32.4231
|
||||
11988.3 32.7203
|
||||
12026.9 33.0205
|
||||
12065.7 33.3236
|
||||
12104.6 33.6295
|
||||
12143.7 33.9381
|
||||
12182.8 34.2496
|
||||
12222.1 34.5641
|
||||
12261.5 34.8817
|
||||
12301.0 35.2022
|
||||
12340.7 35.5256
|
||||
12380.5 35.8520
|
||||
12420.4 36.1816
|
||||
12460.4 36.5144
|
||||
12500.6 36.8503
|
||||
12540.9 37.1892
|
||||
12581.3 37.5312
|
||||
12621.9 37.8768
|
||||
12662.6 38.2256
|
||||
12703.4 38.5776
|
||||
12744.4 38.9329
|
||||
12785.5 39.2915
|
||||
12826.7 39.6536
|
||||
12868.0 40.0192
|
||||
12909.5 40.3881
|
||||
12951.1 40.7605
|
||||
12992.9 41.1363
|
||||
13034.8 41.5159
|
||||
13076.8 41.8991
|
||||
13119.0 42.2859
|
||||
13161.3 42.6762
|
||||
13203.7 43.0702
|
||||
13246.3 43.4679
|
||||
13289.0 43.8695
|
||||
13331.8 44.2748
|
||||
13374.8 44.6838
|
||||
13417.9 45.0965
|
||||
13461.2 45.5134
|
||||
13504.6 45.9344
|
||||
13548.1 46.3592
|
||||
13591.8 46.7880
|
||||
13635.6 47.2206
|
||||
13679.6 47.6577
|
||||
13723.7 48.0990
|
||||
13767.9 48.5443
|
||||
13812.3 48.9937
|
||||
13856.9 49.4473
|
||||
13901.5 49.9055
|
||||
13946.4 50.3679
|
||||
13991.3 50.8346
|
||||
14036.4 51.3056
|
||||
14081.7 51.7811
|
||||
14127.1 52.2613
|
||||
14172.6 52.7460
|
||||
14218.3 53.2352
|
||||
14264.2 53.7291
|
||||
14310.2 54.2274
|
||||
14356.3 54.7308
|
||||
14402.6 55.2389
|
||||
14449.0 55.7517
|
||||
14495.6 56.2693
|
||||
14542.3 56.7917
|
||||
14589.2 57.3192
|
||||
14636.2 57.8518
|
||||
14683.4 58.3893
|
||||
14730.8 58.9317
|
||||
14778.3 59.4792
|
||||
14825.9 60.0323
|
||||
14873.7 60.5908
|
||||
14921.7 61.1543
|
||||
14969.8 61.7231
|
||||
15018.0 62.2972
|
||||
15066.5 62.8770
|
||||
15115.0 63.4622
|
||||
15163.8 64.0528
|
||||
15212.7 64.6489
|
||||
15261.7 65.2506
|
||||
15310.9 65.8585
|
||||
15360.3 66.4721
|
||||
15409.8 67.0915
|
||||
15459.5 67.7166
|
||||
15509.3 68.3476
|
||||
15559.3 68.9847
|
||||
15609.5 69.6277
|
||||
15659.8 70.2767
|
||||
15710.3 70.9319
|
||||
15761.0 71.5930
|
||||
15811.8 72.2610
|
||||
15862.7 72.9353
|
||||
15913.9 73.6160
|
||||
15965.2 74.3028
|
||||
16016.7 74.9961
|
||||
16068.3 75.6963
|
||||
16120.1 76.4033
|
||||
16172.1 77.1167
|
||||
16224.2 77.8368
|
||||
16276.5 78.5635
|
||||
16329.0 79.2975
|
||||
16381.7 80.0384
|
||||
16434.5 80.7861
|
||||
16487.5 81.5408
|
||||
16540.6 82.3025
|
||||
16593.9 83.0723
|
||||
16647.4 83.8492
|
||||
16701.1 84.6333
|
||||
16755.0 85.4248
|
||||
16809.0 86.2237
|
||||
16863.2 87.0304
|
||||
16917.5 87.8445
|
||||
16972.1 88.6662
|
||||
17026.8 89.4956
|
||||
17081.7 90.3328
|
||||
17136.8 91.1786
|
||||
17192.0 92.0322
|
||||
17247.4 92.8938
|
||||
17303.1 93.7636
|
||||
17358.8 94.6414
|
||||
17414.8 95.5282
|
||||
17470.9 96.4233
|
||||
17527.3 97.3269
|
||||
17583.8 98.2387
|
||||
17640.5 99.1590
|
||||
17697.4 100.088
|
||||
17754.4 101.027
|
||||
17811.6 101.973
|
||||
17869.1 102.929
|
||||
17926.7 103.893
|
||||
17984.5 104.868
|
||||
18042.5 105.852
|
||||
18100.6 106.844
|
||||
18159.0 107.846
|
||||
18217.5 108.858
|
||||
18276.3 109.879
|
||||
18335.2 110.911
|
||||
18394.3 111.951
|
||||
18453.6 113.002
|
||||
18513.1 114.062
|
||||
18572.8 115.133
|
||||
18632.7 116.214
|
||||
18692.8 117.305
|
||||
18753.0 118.406
|
||||
18813.5 119.518
|
||||
18874.1 120.640
|
||||
18935.0 121.773
|
||||
18996.0 122.917
|
||||
19057.3 124.071
|
||||
19118.7 125.236
|
||||
19180.4 126.413
|
||||
19242.2 127.601
|
||||
19304.2 128.800
|
||||
19366.5 130.010
|
||||
19428.9 131.232
|
||||
19491.6 132.465
|
||||
19554.4 133.710
|
||||
19617.4 134.967
|
||||
19680.7 136.235
|
||||
19744.1 137.516
|
||||
19807.8 138.809
|
||||
19871.7 140.115
|
||||
19935.7 141.432
|
||||
20000.0 142.762
|
||||
20081.3 144.456
|
||||
20162.8 146.170
|
||||
20244.8 147.905
|
||||
20327.0 149.660
|
||||
20409.6 151.436
|
||||
20492.5 153.234
|
||||
20575.8 155.052
|
||||
20659.4 156.892
|
||||
20743.3 158.755
|
||||
20827.6 160.641
|
||||
20912.2 162.549
|
||||
20997.2 164.479
|
||||
21082.5 166.432
|
||||
21168.1 168.409
|
||||
21254.1 170.409
|
||||
21340.5 172.433
|
||||
21427.2 174.481
|
||||
21514.3 176.554
|
||||
21601.7 178.652
|
||||
21689.4 180.774
|
||||
21777.6 182.922
|
||||
21866.0 185.095
|
||||
21954.9 187.294
|
||||
22044.1 189.520
|
||||
22133.6 191.772
|
||||
22223.6 194.051
|
||||
22313.9 196.357
|
||||
22404.5 198.690
|
||||
22495.5 201.051
|
||||
22586.9 203.441
|
||||
22678.7 205.858
|
||||
22770.8 208.305
|
||||
22863.4 210.781
|
||||
22956.3 213.287
|
||||
23049.5 215.822
|
||||
23143.2 218.387
|
||||
23237.2 220.984
|
||||
23331.6 223.611
|
||||
23426.4 226.269
|
||||
23521.6 228.958
|
||||
23617.1 231.679
|
||||
23713.1 234.433
|
||||
23809.4 237.220
|
||||
23906.2 240.038
|
||||
24003.3 242.892
|
||||
24100.8 245.779
|
||||
24198.7 248.701
|
||||
24297.1 251.656
|
||||
24395.8 254.648
|
||||
24494.9 257.675
|
||||
24594.4 260.737
|
||||
24694.3 263.835
|
||||
24794.7 266.971
|
||||
24895.4 270.144
|
||||
24996.6 273.355
|
||||
25098.1 276.603
|
||||
25200.1 279.890
|
||||
25302.5 283.216
|
||||
25405.3 286.580
|
||||
25508.5 289.984
|
||||
25612.1 293.430
|
||||
25716.2 296.915
|
||||
25820.7 300.442
|
||||
25925.6 304.009
|
||||
26030.9 307.621
|
||||
26136.7 311.275
|
||||
26242.9 314.971
|
||||
26349.5 318.710
|
||||
26456.5 322.494
|
||||
26564.0 326.323
|
||||
26672.0 330.195
|
||||
26780.3 334.113
|
||||
26889.1 338.079
|
||||
26998.4 342.092
|
||||
27108.1 346.150
|
||||
27218.2 350.256
|
||||
27328.8 354.412
|
||||
27439.8 358.616
|
||||
27551.3 362.869
|
||||
27663.2 367.171
|
||||
27775.6 371.526
|
||||
27888.5 375.930
|
||||
28001.8 380.385
|
||||
28115.6 384.893
|
||||
28229.8 389.455
|
||||
28344.5 394.070
|
||||
28459.6 398.738
|
||||
28575.3 403.460
|
||||
28691.4 408.239
|
||||
28807.9 413.071
|
||||
28925.0 417.960
|
||||
29042.5 422.906
|
||||
29160.5 427.911
|
||||
29279.0 432.974
|
||||
29397.9 438.094
|
||||
29517.4 443.275
|
||||
29637.3 448.518
|
||||
29757.7 453.821
|
||||
29878.6 459.184
|
||||
30000.0 464.610
|
||||
@@ -1,901 +0,0 @@
|
||||
4000.00 1.79125
|
||||
4012.90 1.80643
|
||||
4025.83 1.82173
|
||||
4038.81 1.83717
|
||||
4051.83 1.85277
|
||||
4064.90 1.86850
|
||||
4078.00 1.88436
|
||||
4091.15 1.90036
|
||||
4104.34 1.91650
|
||||
4117.57 1.93280
|
||||
4130.85 1.94923
|
||||
4144.17 1.96581
|
||||
4157.53 1.98253
|
||||
4170.93 1.99939
|
||||
4184.38 2.01642
|
||||
4197.87 2.03360
|
||||
4211.41 2.05093
|
||||
4224.98 2.06840
|
||||
4238.60 2.08602
|
||||
4252.27 2.10383
|
||||
4265.98 2.12178
|
||||
4279.73 2.13989
|
||||
4293.53 2.15815
|
||||
4307.37 2.17657
|
||||
4321.26 2.19517
|
||||
4335.19 2.21393
|
||||
4349.17 2.23286
|
||||
4363.19 2.25194
|
||||
4377.26 2.27119
|
||||
4391.37 2.29064
|
||||
4405.53 2.31025
|
||||
4419.73 2.33003
|
||||
4433.98 2.34998
|
||||
4448.28 2.37011
|
||||
4462.62 2.39043
|
||||
4477.01 2.41092
|
||||
4491.44 2.43159
|
||||
4505.92 2.45244
|
||||
4520.45 2.47347
|
||||
4535.02 2.49471
|
||||
4549.65 2.51612
|
||||
4564.31 2.53773
|
||||
4579.03 2.55951
|
||||
4593.79 2.58150
|
||||
4608.60 2.60369
|
||||
4623.46 2.62608
|
||||
4638.37 2.64865
|
||||
4653.32 2.67143
|
||||
4668.33 2.69441
|
||||
4683.38 2.71761
|
||||
4698.48 2.74101
|
||||
4713.62 2.76461
|
||||
4728.82 2.78842
|
||||
4744.07 2.81244
|
||||
4759.36 2.83669
|
||||
4774.71 2.86115
|
||||
4790.10 2.88582
|
||||
4805.54 2.91070
|
||||
4821.04 2.93581
|
||||
4836.58 2.96115
|
||||
4852.17 2.98672
|
||||
4867.82 3.01250
|
||||
4883.51 3.03851
|
||||
4899.26 3.06475
|
||||
4915.05 3.09123
|
||||
4930.90 3.11795
|
||||
4946.80 3.14490
|
||||
4962.75 3.17208
|
||||
4978.75 3.19950
|
||||
4994.80 3.22719
|
||||
5010.90 3.25511
|
||||
5027.06 3.28328
|
||||
5043.26 3.31169
|
||||
5059.52 3.34036
|
||||
5075.84 3.36930
|
||||
5092.20 3.39849
|
||||
5108.62 3.42794
|
||||
5125.09 3.45764
|
||||
5141.61 3.48761
|
||||
5158.19 3.51787
|
||||
5174.82 3.54839
|
||||
5191.50 3.57918
|
||||
5208.24 3.61023
|
||||
5225.03 3.64156
|
||||
5241.88 3.67318
|
||||
5258.78 3.70507
|
||||
5275.73 3.73723
|
||||
5292.74 3.76968
|
||||
5309.81 3.80242
|
||||
5326.93 3.83547
|
||||
5344.10 3.86881
|
||||
5361.33 3.90244
|
||||
5378.62 3.93635
|
||||
5395.96 3.97058
|
||||
5413.35 4.00513
|
||||
5430.81 4.03998
|
||||
5448.32 4.07513
|
||||
5465.88 4.11059
|
||||
5483.50 4.14637
|
||||
5501.18 4.18249
|
||||
5518.92 4.21891
|
||||
5536.71 4.25566
|
||||
5554.56 4.29272
|
||||
5572.47 4.33013
|
||||
5590.44 4.36787
|
||||
5608.46 4.40593
|
||||
5626.54 4.44433
|
||||
5644.68 4.48307
|
||||
5662.88 4.52218
|
||||
5681.14 4.56165
|
||||
5699.46 4.60148
|
||||
5717.83 4.64165
|
||||
5736.27 4.68217
|
||||
5754.76 4.72305
|
||||
5773.31 4.76428
|
||||
5791.93 4.80589
|
||||
5810.60 4.84784
|
||||
5829.33 4.89016
|
||||
5848.13 4.93287
|
||||
5866.98 4.97599
|
||||
5885.90 5.01947
|
||||
5904.88 5.06333
|
||||
5923.91 5.10757
|
||||
5943.01 5.15223
|
||||
5962.17 5.19730
|
||||
5981.40 5.24277
|
||||
6000.68 5.28863
|
||||
6020.03 5.33489
|
||||
6039.44 5.38159
|
||||
6058.91 5.42870
|
||||
6078.44 5.47622
|
||||
6098.04 5.52416
|
||||
6117.70 5.57253
|
||||
6137.42 5.62133
|
||||
6157.21 5.67057
|
||||
6177.06 5.72025
|
||||
6196.98 5.77036
|
||||
6216.96 5.82091
|
||||
6237.00 5.87193
|
||||
6257.11 5.92342
|
||||
6277.28 5.97537
|
||||
6297.52 6.02778
|
||||
6317.82 6.08064
|
||||
6338.19 6.13398
|
||||
6358.63 6.18781
|
||||
6379.13 6.24212
|
||||
6399.69 6.29691
|
||||
6420.33 6.35217
|
||||
6441.03 6.40791
|
||||
6461.79 6.46416
|
||||
6482.63 6.52088
|
||||
6503.53 6.57811
|
||||
6524.49 6.63584
|
||||
6545.53 6.69413
|
||||
6566.63 6.75299
|
||||
6587.80 6.81235
|
||||
6609.04 6.87223
|
||||
6630.35 6.93264
|
||||
6651.73 6.99358
|
||||
6673.17 7.05504
|
||||
6694.69 7.11704
|
||||
6716.27 7.17959
|
||||
6737.93 7.24269
|
||||
6759.65 7.30638
|
||||
6781.44 7.37065
|
||||
6803.31 7.43548
|
||||
6825.24 7.50090
|
||||
6847.25 7.56688
|
||||
6869.32 7.63346
|
||||
6891.47 7.70065
|
||||
6913.69 7.76843
|
||||
6935.98 7.83682
|
||||
6958.34 7.90580
|
||||
6980.77 7.97541
|
||||
7003.28 8.04564
|
||||
7025.86 8.11651
|
||||
7048.51 8.18798
|
||||
7071.24 8.26009
|
||||
7094.03 8.33285
|
||||
7116.91 8.40627
|
||||
7139.85 8.48033
|
||||
7162.87 8.55504
|
||||
7185.96 8.63041
|
||||
7209.13 8.70650
|
||||
7232.38 8.78327
|
||||
7255.69 8.86073
|
||||
7279.09 8.93886
|
||||
7302.55 9.01770
|
||||
7326.10 9.09719
|
||||
7349.72 9.17737
|
||||
7373.41 9.25825
|
||||
7397.19 9.33986
|
||||
7421.03 9.42219
|
||||
7444.96 9.50534
|
||||
7468.96 9.58928
|
||||
7493.04 9.67395
|
||||
7517.20 9.75939
|
||||
7541.44 9.84557
|
||||
7565.75 9.93250
|
||||
7590.14 10.0202
|
||||
7614.62 10.1087
|
||||
7639.17 10.1979
|
||||
7663.79 10.2880
|
||||
7688.50 10.3788
|
||||
7713.29 10.4705
|
||||
7738.16 10.5630
|
||||
7763.11 10.6563
|
||||
7788.14 10.7505
|
||||
7813.25 10.8454
|
||||
7838.44 10.9412
|
||||
7863.71 11.0378
|
||||
7889.06 11.1352
|
||||
7914.50 11.2336
|
||||
7940.01 11.3329
|
||||
7965.61 11.4331
|
||||
7991.29 11.5342
|
||||
8017.06 11.6362
|
||||
8042.91 11.7391
|
||||
8068.84 11.8430
|
||||
8094.85 11.9478
|
||||
8120.95 12.0536
|
||||
8147.13 12.1603
|
||||
8173.40 12.2679
|
||||
8199.75 12.3765
|
||||
8226.19 12.4860
|
||||
8252.71 12.5964
|
||||
8279.32 12.7079
|
||||
8306.01 12.8204
|
||||
8332.79 12.9337
|
||||
8359.65 13.0481
|
||||
8386.60 13.1635
|
||||
8413.64 13.2799
|
||||
8440.77 13.3973
|
||||
8467.98 13.5160
|
||||
8495.29 13.6358
|
||||
8522.67 13.7566
|
||||
8550.15 13.8785
|
||||
8577.72 14.0015
|
||||
8605.37 14.1256
|
||||
8633.12 14.2508
|
||||
8660.95 14.3771
|
||||
8688.87 14.5045
|
||||
8716.89 14.6331
|
||||
8744.99 14.7628
|
||||
8773.19 14.8936
|
||||
8801.47 15.0255
|
||||
8829.85 15.1586
|
||||
8858.32 15.2930
|
||||
8886.88 15.4285
|
||||
8915.53 15.5652
|
||||
8944.27 15.7032
|
||||
8973.11 15.8424
|
||||
9002.04 15.9828
|
||||
9031.06 16.1245
|
||||
9060.18 16.2675
|
||||
9089.39 16.4118
|
||||
9118.69 16.5574
|
||||
9148.09 16.7043
|
||||
9177.59 16.8524
|
||||
9207.18 17.0019
|
||||
9236.86 17.1527
|
||||
9266.64 17.3049
|
||||
9296.52 17.4583
|
||||
9326.49 17.6133
|
||||
9356.56 17.7696
|
||||
9386.72 17.9273
|
||||
9416.99 18.0864
|
||||
9447.35 18.2469
|
||||
9477.81 18.4089
|
||||
9508.37 18.5722
|
||||
9539.02 18.7371
|
||||
9569.78 18.9033
|
||||
9600.63 19.0711
|
||||
9631.58 19.2404
|
||||
9662.63 19.4113
|
||||
9693.79 19.5836
|
||||
9725.04 19.7575
|
||||
9756.39 19.9329
|
||||
9787.85 20.1100
|
||||
9819.41 20.2886
|
||||
9851.07 20.4689
|
||||
9882.83 20.6507
|
||||
9914.69 20.8342
|
||||
9946.65 21.0193
|
||||
9978.72 21.2060
|
||||
10010.9 21.3945
|
||||
10043.2 21.5845
|
||||
10075.5 21.7763
|
||||
10108.0 21.9698
|
||||
10140.6 22.1651
|
||||
10173.3 22.3620
|
||||
10206.1 22.5607
|
||||
10239.0 22.7612
|
||||
10272.0 22.9635
|
||||
10305.2 23.1677
|
||||
10338.4 23.3736
|
||||
10371.7 23.5814
|
||||
10405.1 23.7910
|
||||
10438.7 24.0026
|
||||
10472.3 24.2160
|
||||
10506.1 24.4312
|
||||
10540.0 24.6484
|
||||
10574.0 24.8676
|
||||
10608.1 25.0887
|
||||
10642.3 25.3119
|
||||
10676.6 25.5370
|
||||
10711.0 25.7642
|
||||
10745.5 25.9933
|
||||
10780.2 26.2246
|
||||
10814.9 26.4579
|
||||
10849.8 26.6933
|
||||
10884.8 26.9308
|
||||
10919.9 27.1703
|
||||
10955.1 27.4121
|
||||
10990.4 27.6560
|
||||
11025.8 27.9021
|
||||
11061.4 28.1503
|
||||
11097.0 28.4008
|
||||
11132.8 28.6537
|
||||
11168.7 28.9088
|
||||
11204.7 29.1662
|
||||
11240.8 29.4259
|
||||
11277.1 29.6879
|
||||
11313.4 29.9523
|
||||
11349.9 30.2190
|
||||
11386.5 30.4880
|
||||
11423.2 30.7595
|
||||
11460.0 31.0334
|
||||
11497.0 31.3098
|
||||
11534.1 31.5887
|
||||
11571.2 31.8700
|
||||
11608.6 32.1539
|
||||
11646.0 32.4403
|
||||
11683.5 32.7293
|
||||
11721.2 33.0208
|
||||
11759.0 33.3149
|
||||
11796.9 33.6117
|
||||
11834.9 33.9111
|
||||
11873.1 34.2133
|
||||
11911.4 34.5182
|
||||
11949.8 34.8259
|
||||
11988.3 35.1362
|
||||
12026.9 35.4493
|
||||
12065.7 35.7652
|
||||
12104.6 36.0840
|
||||
12143.7 36.4055
|
||||
12182.8 36.7300
|
||||
12222.1 37.0573
|
||||
12261.5 37.3877
|
||||
12301.0 37.7211
|
||||
12340.7 38.0574
|
||||
12380.5 38.3967
|
||||
12420.4 38.7391
|
||||
12460.4 39.0846
|
||||
12500.6 39.4331
|
||||
12540.9 39.7847
|
||||
12581.3 40.1394
|
||||
12621.9 40.4974
|
||||
12662.6 40.8587
|
||||
12703.4 41.2232
|
||||
12744.4 41.5910
|
||||
12785.5 41.9621
|
||||
12826.7 42.3364
|
||||
12868.0 42.7141
|
||||
12909.5 43.0951
|
||||
12951.1 43.4795
|
||||
12992.9 43.8674
|
||||
13034.8 44.2587
|
||||
13076.8 44.6538
|
||||
13119.0 45.0524
|
||||
13161.3 45.4547
|
||||
13203.7 45.8604
|
||||
13246.3 46.2698
|
||||
13289.0 46.6829
|
||||
13331.8 47.0997
|
||||
13374.8 47.5201
|
||||
13417.9 47.9443
|
||||
13461.2 48.3724
|
||||
13504.6 48.8045
|
||||
13548.1 49.2404
|
||||
13591.8 49.6802
|
||||
13635.6 50.1239
|
||||
13679.6 50.5715
|
||||
13723.7 51.0233
|
||||
13767.9 51.4790
|
||||
13812.3 51.9388
|
||||
13856.9 52.4027
|
||||
13901.5 52.8708
|
||||
13946.4 53.3433
|
||||
13991.3 53.8201
|
||||
14036.4 54.3010
|
||||
14081.7 54.7863
|
||||
14127.1 55.2759
|
||||
14172.6 55.7700
|
||||
14218.3 56.2685
|
||||
14264.2 56.7715
|
||||
14310.2 57.2788
|
||||
14356.3 57.7909
|
||||
14402.6 58.3077
|
||||
14449.0 58.8292
|
||||
14495.6 59.3554
|
||||
14542.3 59.8861
|
||||
14589.2 60.4216
|
||||
14636.2 60.9620
|
||||
14683.4 61.5072
|
||||
14730.8 62.0572
|
||||
14778.3 62.6121
|
||||
14825.9 63.1721
|
||||
14873.7 63.7375
|
||||
14921.7 64.3078
|
||||
14969.8 64.8832
|
||||
15018.0 65.4638
|
||||
15066.5 66.0497
|
||||
15115.0 66.6410
|
||||
15163.8 67.2375
|
||||
15212.7 67.8394
|
||||
15261.7 68.4467
|
||||
15310.9 69.0595
|
||||
15360.3 69.6780
|
||||
15409.8 70.3020
|
||||
15459.5 70.9316
|
||||
15509.3 71.5669
|
||||
15559.3 72.2079
|
||||
15609.5 72.8549
|
||||
15659.8 73.5076
|
||||
15710.3 74.1663
|
||||
15761.0 74.8307
|
||||
15811.8 75.5011
|
||||
15862.7 76.1778
|
||||
15913.9 76.8606
|
||||
15965.2 77.5494
|
||||
16016.7 78.2443
|
||||
16068.3 78.9456
|
||||
16120.1 79.6534
|
||||
16172.1 80.3674
|
||||
16224.2 81.0878
|
||||
16276.5 81.8145
|
||||
16329.0 82.5481
|
||||
16381.7 83.2888
|
||||
16434.5 84.0359
|
||||
16487.5 84.7897
|
||||
16540.6 85.5502
|
||||
16593.9 86.3179
|
||||
16647.4 87.0925
|
||||
16701.1 87.8740
|
||||
16755.0 88.6625
|
||||
16809.0 89.4582
|
||||
16863.2 90.2611
|
||||
16917.5 91.0715
|
||||
16972.1 91.8892
|
||||
17026.8 92.7141
|
||||
17081.7 93.5466
|
||||
17136.8 94.3866
|
||||
17192.0 95.2345
|
||||
17247.4 96.0899
|
||||
17303.1 96.9532
|
||||
17358.8 97.8239
|
||||
17414.8 98.7027
|
||||
17470.9 99.5896
|
||||
17527.3 100.485
|
||||
17583.8 101.387
|
||||
17640.5 102.298
|
||||
17697.4 103.218
|
||||
17754.4 104.146
|
||||
17811.6 105.082
|
||||
17869.1 106.027
|
||||
17926.7 106.980
|
||||
17984.0 107.934
|
||||
17984.1 107.936
|
||||
17984.2 107.938
|
||||
17984.3 107.939
|
||||
17984.4 107.941
|
||||
17984.5 107.943
|
||||
17984.6 107.944
|
||||
17984.7 107.946
|
||||
17984.8 107.948
|
||||
17984.9 107.949
|
||||
17985.0 107.951
|
||||
17985.1 107.953
|
||||
17985.2 107.954
|
||||
17985.3 107.956
|
||||
17985.4 107.958
|
||||
17985.5 107.959
|
||||
17985.6 107.961
|
||||
17985.7 107.963
|
||||
17985.8 107.964
|
||||
17985.9 107.966
|
||||
17986.0 107.968
|
||||
17986.1 107.969
|
||||
17986.2 107.971
|
||||
17986.3 107.973
|
||||
17986.4 107.974
|
||||
17986.5 107.976
|
||||
17986.6 107.978
|
||||
17986.7 107.979
|
||||
17986.8 107.981
|
||||
17986.9 107.983
|
||||
17987.0 107.984
|
||||
17987.1 107.986
|
||||
17987.2 107.988
|
||||
17987.3 107.989
|
||||
17987.4 107.991
|
||||
17987.5 107.993
|
||||
17987.6 107.994
|
||||
17987.7 107.996
|
||||
17987.8 107.998
|
||||
17987.9 107.999
|
||||
17988.0 108.001
|
||||
17988.1 108.003
|
||||
17988.2 108.004
|
||||
17988.3 108.006
|
||||
17988.4 108.008
|
||||
17988.5 108.009
|
||||
17988.6 108.011
|
||||
17988.7 108.013
|
||||
17988.8 108.014
|
||||
17988.9 108.016
|
||||
17989.0 108.018
|
||||
17989.1 108.019
|
||||
17989.2 108.021
|
||||
17989.3 108.023
|
||||
17989.4 108.024
|
||||
17989.5 108.026
|
||||
17989.6 108.028
|
||||
17989.7 108.029
|
||||
17989.8 108.031
|
||||
17989.9 108.033
|
||||
17990.0 108.034
|
||||
17990.1 108.036
|
||||
17990.2 108.038
|
||||
17990.3 108.039
|
||||
17990.4 108.041
|
||||
17990.5 108.043
|
||||
17990.6 108.044
|
||||
17990.7 108.046
|
||||
17990.8 108.048
|
||||
17990.9 108.049
|
||||
17991.0 108.051
|
||||
17991.1 108.053
|
||||
17991.2 108.054
|
||||
17991.3 108.056
|
||||
17991.4 108.058
|
||||
17991.5 108.059
|
||||
17991.6 108.061
|
||||
17991.7 108.063
|
||||
17991.8 108.064
|
||||
17991.9 108.066
|
||||
17992.0 108.068
|
||||
17992.1 108.069
|
||||
17992.2 108.071
|
||||
17992.3 108.073
|
||||
17992.4 108.074
|
||||
17992.5 108.076
|
||||
17992.6 108.078
|
||||
17992.7 108.079
|
||||
17992.8 108.081
|
||||
17992.9 108.083
|
||||
17993.0 108.084
|
||||
17993.1 108.086
|
||||
17993.2 108.088
|
||||
17993.3 108.089
|
||||
17993.4 108.091
|
||||
17993.5 108.093
|
||||
17993.6 108.094
|
||||
17993.7 108.096
|
||||
17993.8 108.098
|
||||
17993.9 108.099
|
||||
17994.0 108.101
|
||||
17994.1 108.103
|
||||
17994.2 108.105
|
||||
17994.3 108.106
|
||||
17994.4 108.108
|
||||
17994.5 108.109
|
||||
17994.6 108.111
|
||||
17994.7 108.113
|
||||
17994.8 108.115
|
||||
17994.9 108.116
|
||||
17995.0 108.118
|
||||
17995.1 108.119
|
||||
17995.2 108.121
|
||||
17995.3 108.123
|
||||
17995.4 108.125
|
||||
17995.5 108.126
|
||||
17995.6 108.128
|
||||
17995.7 108.130
|
||||
17995.8 108.131
|
||||
17995.9 108.133
|
||||
17996.0 108.134
|
||||
17996.1 108.136
|
||||
17996.2 108.138
|
||||
17996.3 108.140
|
||||
17996.4 108.141
|
||||
17996.5 108.143
|
||||
17996.6 108.144
|
||||
17996.7 108.146
|
||||
17996.8 108.148
|
||||
17996.9 108.150
|
||||
17997.0 108.151
|
||||
17997.1 108.153
|
||||
17997.2 108.155
|
||||
17997.3 108.156
|
||||
17997.4 108.158
|
||||
17997.5 108.160
|
||||
17997.6 41.5713
|
||||
17997.7 15.9385
|
||||
17997.8 15.9387
|
||||
17997.9 15.9389
|
||||
17998.0 15.9391
|
||||
17998.1 15.9394
|
||||
17998.2 15.9396
|
||||
17998.3 15.9398
|
||||
17998.4 15.9400
|
||||
17998.5 15.9403
|
||||
17998.6 15.9405
|
||||
17998.7 15.9407
|
||||
17998.8 15.9410
|
||||
17998.9 15.9412
|
||||
17999.0 15.9414
|
||||
17999.1 15.9416
|
||||
17999.2 15.9419
|
||||
17999.3 15.9421
|
||||
17999.4 15.9423
|
||||
17999.5 15.9425
|
||||
17999.6 15.9427
|
||||
17999.7 15.9430
|
||||
17999.8 15.9432
|
||||
17999.9 15.9434
|
||||
18000.0 15.9437
|
||||
18000.1 15.9439
|
||||
18000.2 15.9441
|
||||
18000.3 15.9443
|
||||
18000.4 15.9446
|
||||
18000.5 15.9448
|
||||
18000.6 15.9450
|
||||
18000.7 15.9452
|
||||
18000.8 15.9455
|
||||
18000.9 15.9457
|
||||
18001.0 15.9459
|
||||
18001.1 15.9461
|
||||
18001.2 15.9463
|
||||
18001.3 15.9466
|
||||
18001.4 15.9468
|
||||
18001.5 15.9470
|
||||
18001.6 15.9473
|
||||
18001.7 15.9475
|
||||
18001.8 15.9477
|
||||
18001.9 15.9479
|
||||
18002.0 15.9482
|
||||
18002.1 15.9484
|
||||
18002.2 15.9486
|
||||
18002.3 15.9489
|
||||
18002.4 15.9491
|
||||
18002.5 15.9493
|
||||
18002.6 15.9495
|
||||
18002.7 15.9497
|
||||
18002.8 15.9500
|
||||
18002.9 15.9502
|
||||
18003.0 15.9504
|
||||
18003.1 15.9506
|
||||
18003.2 15.9509
|
||||
18003.3 15.9511
|
||||
18003.4 15.9513
|
||||
18003.5 15.9515
|
||||
18003.6 15.9518
|
||||
18003.7 15.9520
|
||||
18003.8 15.9522
|
||||
18003.9 15.9525
|
||||
18004.0 15.9527
|
||||
18004.1 15.9529
|
||||
18004.2 15.9531
|
||||
18004.3 15.9534
|
||||
18004.4 15.9536
|
||||
18004.5 15.9538
|
||||
18004.6 15.9540
|
||||
18004.7 15.9542
|
||||
18004.8 15.9545
|
||||
18004.9 15.9547
|
||||
18005.0 15.9549
|
||||
18005.1 15.9552
|
||||
18005.2 15.9554
|
||||
18005.3 15.9556
|
||||
18005.4 15.9558
|
||||
18005.5 15.9561
|
||||
18005.6 15.9563
|
||||
18005.7 15.9565
|
||||
18005.8 15.9567
|
||||
18005.9 15.9570
|
||||
18006.0 15.9572
|
||||
18006.1 15.9574
|
||||
18006.2 15.9576
|
||||
18006.3 15.9579
|
||||
18006.4 15.9581
|
||||
18006.5 15.9583
|
||||
18006.6 15.9585
|
||||
18006.7 15.9588
|
||||
18006.8 15.9590
|
||||
18006.9 15.9592
|
||||
18007.0 15.9594
|
||||
18007.1 15.9597
|
||||
18007.2 15.9599
|
||||
18007.3 15.9601
|
||||
18007.4 15.9604
|
||||
18007.5 15.9606
|
||||
18007.6 15.9608
|
||||
18007.7 15.9610
|
||||
18007.8 15.9613
|
||||
18007.9 15.9615
|
||||
18008.0 15.9617
|
||||
18008.1 15.9619
|
||||
18008.2 15.9621
|
||||
18008.3 15.9624
|
||||
18008.4 15.9626
|
||||
18008.5 15.9628
|
||||
18008.6 15.9631
|
||||
18008.7 15.9633
|
||||
18008.8 15.9635
|
||||
18008.9 15.9637
|
||||
18009.0 15.9640
|
||||
18009.1 15.9642
|
||||
18009.2 15.9644
|
||||
18009.3 15.9646
|
||||
18009.4 15.9649
|
||||
18009.5 15.9651
|
||||
18009.6 15.9653
|
||||
18009.7 15.9655
|
||||
18009.8 15.9658
|
||||
18009.9 15.9660
|
||||
18010.0 15.9662
|
||||
18010.1 15.9664
|
||||
18010.2 15.9667
|
||||
18010.3 15.9669
|
||||
18010.4 15.9671
|
||||
18010.5 15.9673
|
||||
18010.6 15.9676
|
||||
18010.7 15.9678
|
||||
18010.8 15.9680
|
||||
18010.9 15.9683
|
||||
18011.0 15.9685
|
||||
18011.1 15.9687
|
||||
18011.2 15.9689
|
||||
18011.3 15.9692
|
||||
18011.4 15.9694
|
||||
18011.5 15.9696
|
||||
18011.6 15.9698
|
||||
18011.7 15.9700
|
||||
18011.8 15.9703
|
||||
18011.9 15.9705
|
||||
18012.0 15.9707
|
||||
18012.1 15.9709
|
||||
18012.2 15.9712
|
||||
18012.3 15.9714
|
||||
18012.4 15.9716
|
||||
18012.5 15.9719
|
||||
18012.6 15.9721
|
||||
18012.7 15.9723
|
||||
18012.8 15.9725
|
||||
18012.9 15.9728
|
||||
18013.0 15.9730
|
||||
18013.1 15.9732
|
||||
18013.2 15.9735
|
||||
18013.3 15.9737
|
||||
18013.4 15.9739
|
||||
18013.5 15.9741
|
||||
18013.6 15.9743
|
||||
18013.7 15.9746
|
||||
18013.8 15.9748
|
||||
18013.9 15.9750
|
||||
18014.0 15.9752
|
||||
18042.5 16.0396
|
||||
18100.6 16.1715
|
||||
18159.0 16.3045
|
||||
18217.5 16.4386
|
||||
18276.3 16.5738
|
||||
18335.2 16.7101
|
||||
18394.3 16.8475
|
||||
18453.6 16.9861
|
||||
18513.1 17.1258
|
||||
18572.8 17.2670
|
||||
18632.7 17.4095
|
||||
18692.8 17.5533
|
||||
18753.0 17.6983
|
||||
18813.5 17.8444
|
||||
18874.1 17.9921
|
||||
18935.0 18.1412
|
||||
18996.0 18.2916
|
||||
19057.3 18.4432
|
||||
19118.7 18.5961
|
||||
19180.4 18.7506
|
||||
19242.2 18.9066
|
||||
19304.2 19.0639
|
||||
19366.5 19.2225
|
||||
19428.9 19.3824
|
||||
19491.6 19.5440
|
||||
19554.4 19.7072
|
||||
19617.4 19.8717
|
||||
19680.7 20.0376
|
||||
19744.1 20.2048
|
||||
19807.8 20.3739
|
||||
19871.7 20.5446
|
||||
19935.7 20.7167
|
||||
20000.0 20.8903
|
||||
20081.3 21.1110
|
||||
20162.8 21.3347
|
||||
20244.8 21.5609
|
||||
20327.0 21.7894
|
||||
20409.6 22.0204
|
||||
20492.5 22.2546
|
||||
20575.8 22.4913
|
||||
20659.4 22.7305
|
||||
20743.3 22.9722
|
||||
20827.6 23.2173
|
||||
20912.2 23.4650
|
||||
20997.2 23.7153
|
||||
21082.5 23.9683
|
||||
21168.1 24.2248
|
||||
21254.1 24.4840
|
||||
21340.5 24.7460
|
||||
21427.2 25.0109
|
||||
21514.3 25.2792
|
||||
21601.7 25.5505
|
||||
21689.4 25.8246
|
||||
21777.6 26.1018
|
||||
21866.0 26.3827
|
||||
21954.9 26.6666
|
||||
22044.1 26.9536
|
||||
22133.6 27.2438
|
||||
22223.6 27.5378
|
||||
22313.9 27.8350
|
||||
22404.5 28.1354
|
||||
22495.5 28.4392
|
||||
22586.9 28.7469
|
||||
22678.7 29.0579
|
||||
22770.8 29.3723
|
||||
22863.4 29.6904
|
||||
22956.3 30.0124
|
||||
23049.5 30.3380
|
||||
23143.2 30.6670
|
||||
23237.2 30.9999
|
||||
23331.6 31.3370
|
||||
23426.4 31.6777
|
||||
23521.6 32.0221
|
||||
23617.1 32.3705
|
||||
23713.1 32.7234
|
||||
23809.4 33.0801
|
||||
23906.2 33.4407
|
||||
24003.3 33.8055
|
||||
24100.8 34.1749
|
||||
24198.7 34.5483
|
||||
24297.1 34.9258
|
||||
24395.8 35.3077
|
||||
24494.9 35.6943
|
||||
24594.4 36.0851
|
||||
24694.3 36.4802
|
||||
24794.7 36.8800
|
||||
24895.4 37.2846
|
||||
24996.6 37.6937
|
||||
25098.1 38.1073
|
||||
25200.1 38.5259
|
||||
25302.5 38.9496
|
||||
25405.3 39.3779
|
||||
25508.5 39.8109
|
||||
25612.1 40.2493
|
||||
25716.2 40.6927
|
||||
25820.7 41.1411
|
||||
25925.6 41.5944
|
||||
26030.9 42.0533
|
||||
26136.7 42.5177
|
||||
26242.9 42.9871
|
||||
26349.5 43.4618
|
||||
26456.5 43.9422
|
||||
26564.0 44.4283
|
||||
26672.0 44.9197
|
||||
26780.3 45.4165
|
||||
26889.1 45.9195
|
||||
26998.4 46.4284
|
||||
27108.1 46.9428
|
||||
27218.2 47.4630
|
||||
27328.8 47.9896
|
||||
27439.8 48.5223
|
||||
27551.3 49.0609
|
||||
27663.2 49.6054
|
||||
27775.6 50.1569
|
||||
27888.5 50.7146
|
||||
28001.8 51.2787
|
||||
28115.6 51.8489
|
||||
28229.8 52.4262
|
||||
28344.5 53.0102
|
||||
28459.6 53.6005
|
||||
28575.3 54.1975
|
||||
28691.4 54.8021
|
||||
28807.9 55.4134
|
||||
28925.0 56.0316
|
||||
29042.5 56.6567
|
||||
29160.5 57.2896
|
||||
29279.0 57.9297
|
||||
29397.9 58.5769
|
||||
29517.4 59.2313
|
||||
29637.3 59.8941
|
||||
29757.7 60.5643
|
||||
29878.6 61.2421
|
||||
30000.0 61.9272
|
||||
@@ -1,674 +0,0 @@
|
||||
|
||||
"""
|
||||
cSAXS exposure-box filter transmission utilities.
|
||||
|
||||
This method has been created based on previous spec implementations
|
||||
The translation was mainly done by copilot AI.
|
||||
|
||||
Implements fil_trans physics:
|
||||
- Per-material attenuation-length tables loaded from package 'filter_data/'.
|
||||
- Linear interpolation of attenuation length vs. energy (eV).
|
||||
- Transmission T = exp(-t / lambda), t in micrometers.
|
||||
- Enumeration of all enabled combinations across 4 units x 6 positions.
|
||||
- Selection of the combination with transmission closest to a target.
|
||||
|
||||
Motion is executed using provided position tables for each filter_array_*_x stage.
|
||||
"""
|
||||
|
||||
|
||||
#todo
|
||||
#check dmm is off
|
||||
#X12SA-OP-DMM-EMLS-3010:THRU translation THROUGH
|
||||
#X12SA-OP-DMM-EMLS-3030:THRU bragg through
|
||||
#X12SA-OP-CCM1:ENERGY-GET > 1
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from importlib import resources
|
||||
# Resolve the filter_data/ folder via importlib.resources
|
||||
import csaxs_bec.bec_ipython_client.plugins.cSAXS.filter_transmission as ft_pkg
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get
|
||||
|
||||
from bec_lib import bec_logger
|
||||
|
||||
import builtins
|
||||
|
||||
|
||||
if builtins.__dict__.get("bec") is not None:
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
|
||||
class cSAXSFilterTransmission:
|
||||
"""
|
||||
Mixin providing the fil_trans command.
|
||||
Assumes self.client and self.OMNYTools exist.
|
||||
|
||||
Example:
|
||||
csaxs.fil_trans(0.10, energy_kev=6.2) # dry-run first, then asks to execute
|
||||
"""
|
||||
|
||||
# -----------------------------
|
||||
# Material naming & file mapping
|
||||
# -----------------------------
|
||||
_MATERIAL_FILES: Dict[str, Optional[str]] = {
|
||||
"none": None,
|
||||
"diam": "filter_attenuation-length_diamond.txt",
|
||||
"al": "filter_attenuation-length_al.txt",
|
||||
"si": "filter_attenuation-length_si.txt",
|
||||
"ti": "filter_attenuation-length_ti.txt",
|
||||
"cu": "filter_attenuation-length_cu.txt",
|
||||
"ge": "filter_attenuation-length_ge.txt",
|
||||
"zr": "filter_attenuation-length_zr.txt",
|
||||
# optional; provide file if you enable Fe5
|
||||
"fe": "filter_attenuation-length_fe.txt",
|
||||
}
|
||||
|
||||
# -----------------------------------------
|
||||
# Exposure-box filter configuration (4 x 6)
|
||||
#
|
||||
# Current hardware (per your message):
|
||||
# Unit 1 (filter_array_1_x): out, Si400, Ge300, Ti800, Zr20
|
||||
# Unit 2 (filter_array_2_x): out, Si200, Si3200, Ti400, Cu20
|
||||
# Unit 3 (filter_array_3_x): out, Si100, Si1600, Ti200, Ti3200, Fe5
|
||||
# Unit 4 (filter_array_4_x): out, Si50, Si800, Ti100, Ti1600, Ti20
|
||||
#
|
||||
# Positions 1..6 = [out, m1, m2, m3, m4, m5]
|
||||
# Each entry: ((mat1, th1_um), (mat2, th2_um), enabled_bool)
|
||||
# -----------------------------------------
|
||||
_FILTERS: List[Tuple[Tuple[str, float], Tuple[str, float], bool]] = [
|
||||
# Unit 1
|
||||
(("none", 0.0), ("none", 0.0), True), # out
|
||||
(("si", 400.0), ("none", 0.0), True), # Si400
|
||||
(("ge", 300.0), ("none", 0.0), True), # Ge300
|
||||
(("ti", 800.0), ("none", 0.0), True), # Ti800
|
||||
(("zr", 20.0), ("none", 0.0), True), # Zr20
|
||||
(("none", 0.0), ("none", 0.0), False), # unused
|
||||
|
||||
# Unit 2
|
||||
(("none", 0.0), ("none", 0.0), True), # out
|
||||
(("si", 200.0), ("none", 0.0), True), # Si200
|
||||
(("si", 3200.0), ("none", 0.0), True), # Si3200
|
||||
(("ti", 400.0), ("none", 0.0), True), # Ti400
|
||||
(("cu", 20.0), ("none", 0.0), True), # Cu20
|
||||
(("none", 0.0), ("none", 0.0), False), # unused
|
||||
|
||||
# Unit 3
|
||||
(("none", 0.0), ("none", 0.0), True), # out
|
||||
(("si", 100.0), ("none", 0.0), True), # Si100
|
||||
(("si", 1600.0), ("none", 0.0), True), # Si1600
|
||||
(("ti", 200.0), ("none", 0.0), True), # Ti200
|
||||
(("ti", 3200.0), ("none", 0.0), True), # Ti3200
|
||||
(("fe", 5.0), ("none", 0.0), False), # Fe5 (disabled unless data file provided)
|
||||
|
||||
# Unit 4
|
||||
(("none", 0.0), ("none", 0.0), True), # out
|
||||
(("si", 50.0), ("none", 0.0), True), # Si50
|
||||
(("si", 800.0), ("none", 0.0), True), # Si800
|
||||
(("ti", 100.0), ("none", 0.0), True), # Ti100
|
||||
(("ti", 1600.0), ("none", 0.0), True), # Ti1600
|
||||
(("ti", 20.0), ("none", 0.0), True), # Ti20
|
||||
]
|
||||
|
||||
_UNITS = 4
|
||||
_PER_UNIT = 6
|
||||
|
||||
# -----------------------------------------
|
||||
# Motion mapping: user-scale coordinates
|
||||
# [out, m1, m2, m3, m4, m5]
|
||||
# -----------------------------------------
|
||||
_POSITIONS_USER: List[List[Optional[float]]] = [
|
||||
# Unit 1 (filter_array_1_x)
|
||||
[25.0, 17.9, 7.9, -2.3, -12.1, None],
|
||||
# Unit 2 (filter_array_2_x)
|
||||
[25.5, 17.6, 7.8, -2.3, -12.3, None],
|
||||
# Unit 3 (filter_array_3_x)
|
||||
[25.8, 17.6, 7.8, -2.2, -12.3, -22.3], # Fe5 at -22.3
|
||||
# Unit 4 (filter_array_4_x)
|
||||
[25.0, 17.5, 7.5, -2.2, -12.4, -22.2],
|
||||
]
|
||||
|
||||
# Device axis names (adjust if different)
|
||||
_AXES: List[str] = [
|
||||
"filter_array_1_x",
|
||||
"filter_array_2_x",
|
||||
"filter_array_3_x",
|
||||
"filter_array_4_x",
|
||||
]
|
||||
|
||||
# -----------------------------
|
||||
# Construction / Internals
|
||||
# -----------------------------
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
# In multiple-inheritance setups our __init__ might be skipped; guard lazily too.
|
||||
self._attlen_cache: Dict[str, Tuple[List[float], List[float]]] = {}
|
||||
|
||||
def _ensure_internal_state(self):
|
||||
"""Lazy guard for robustness if __init__ wasn’t called."""
|
||||
if not hasattr(self, "_attlen_cache"):
|
||||
self._attlen_cache = {}
|
||||
|
||||
# -----------------------------
|
||||
# Public API
|
||||
# -----------------------------
|
||||
def fil_trans(
|
||||
self,
|
||||
transmission: Optional[float] = None,
|
||||
energy_kev: Optional[float] = None,
|
||||
print_only: bool = True,
|
||||
) -> Optional[None]:
|
||||
"""
|
||||
Set exposure-box filters to achieve a target transmission.
|
||||
|
||||
If called without 'transmission', prints usage and current status.
|
||||
|
||||
Safety:
|
||||
- fil_trans(1) is always allowed.
|
||||
- fil_trans(<1) is only allowed if:
|
||||
- epics_get("X12SA-OP-DMM-EMLS-3010:THRU") == 1 (DMM translation THROUGH)
|
||||
- epics_get("X12SA-OP-DMM-EMLS-3030:THRU") == 1 (DMM rotation THROUGH)
|
||||
- epics_get("X12SA-OP-CCM1:ENERGY-GET") > 1 (CCM active, energy in keV)
|
||||
Otherwise, prompt with default NO.
|
||||
"""
|
||||
|
||||
# --- No-arg usage helper ---
|
||||
if transmission is None:
|
||||
print("\nUsage example:")
|
||||
print(" csaxs.fil_trans(0.10, energy_kev=6.2)")
|
||||
print(" First parameter is the transmission factor requested.")
|
||||
print(" If energy is not specified it will be read from the CCM energy PV.")
|
||||
print("\nCurrent filter transmission:")
|
||||
self._fil_trans_report(energy_kev=energy_kev)
|
||||
return None
|
||||
|
||||
# --- Validation of transmission ---
|
||||
try:
|
||||
transmission = float(transmission)
|
||||
except Exception:
|
||||
raise ValueError("Transmission must be numeric.")
|
||||
|
||||
if not (0.0 < transmission <= 1.0):
|
||||
raise ValueError("Transmission must be between 0 and 1.")
|
||||
|
||||
# -------------------------------------------------------
|
||||
# SAFETY CHECK (before any calculation/motion):
|
||||
# Only allow fil_trans < 1 when DMM is in THROUGH (both)
|
||||
# and CCM energy > 1 keV. fil_trans(1) is always allowed.
|
||||
# -------------------------------------------------------
|
||||
if transmission < 1.0:
|
||||
try:
|
||||
dmm_trans = float(epics_get("X12SA-OP-DMM-EMLS-3010:THRU"))
|
||||
except Exception:
|
||||
dmm_trans = -1
|
||||
try:
|
||||
dmm_rot = float(epics_get("X12SA-OP-DMM-EMLS-3030:THRU"))
|
||||
except Exception:
|
||||
dmm_rot = -1
|
||||
try:
|
||||
ccm_energy = float(epics_get("X12SA-OP-CCM1:ENERGY-GET"))
|
||||
except Exception:
|
||||
ccm_energy = -1
|
||||
|
||||
allowed = (dmm_trans == 1) and (dmm_rot == 1) and (ccm_energy > 1)
|
||||
|
||||
if not allowed:
|
||||
print("\n⚠️ SAFETY WARNING: Reducing transmission (< 1) typically requires:")
|
||||
print(" - DMM translation in THROUGH (THRU == 1)")
|
||||
print(" - DMM rotation in THROUGH (THRU == 1)")
|
||||
print(" - CCM energy > 1 keV")
|
||||
print("\nCurrent state:")
|
||||
print(f" DMM translation THRU : {dmm_trans}")
|
||||
print(f" DMM rotation THRU : {dmm_rot}")
|
||||
print(f" CCM energy (keV) : {ccm_energy}")
|
||||
|
||||
# Ask user (default = NO)
|
||||
if hasattr(self, "OMNYTools") and hasattr(self.OMNYTools, "yesno"):
|
||||
proceed = self.OMNYTools.yesno(
|
||||
"Conditions not satisfied. Proceed anyway?",
|
||||
default="n",
|
||||
)
|
||||
else:
|
||||
# Safe fallback
|
||||
proceed = False
|
||||
|
||||
if not proceed:
|
||||
print("Aborted. Transmission unchanged.")
|
||||
return None
|
||||
|
||||
# --- Energy handling (EPICS only) ---
|
||||
if energy_kev is None:
|
||||
try:
|
||||
energy_kev = float(epics_get("X12SA-OP-CCM1:ENERGY-GET"))
|
||||
except Exception as exc:
|
||||
raise RuntimeError(
|
||||
"Energy not specified and could not read EPICS PV "
|
||||
"'X12SA-OP-CCM1:ENERGY-GET'."
|
||||
) from exc
|
||||
else:
|
||||
energy_kev = float(energy_kev)
|
||||
|
||||
|
||||
# --- Summary header ---
|
||||
print("\nExposure-box filter transmission request")
|
||||
print("-" * 60)
|
||||
print(f"Target transmission : {transmission:.6e}")
|
||||
print(f"Photon energy : {energy_kev:.3f} keV")
|
||||
print(f"Mode : {'PRINT ONLY' if print_only else 'EXECUTE'}")
|
||||
print("-" * 60)
|
||||
|
||||
# --- Compute best combination ---
|
||||
best = self._find_best_combination(transmission, energy_kev)
|
||||
|
||||
# --- Report selected combination ---
|
||||
self._print_combination(best, energy_kev, header="Selected combination")
|
||||
|
||||
# Nearby: print only code + transmission
|
||||
neighbors = self._neighbors_around_best(transmission, energy_kev, span=5)
|
||||
if neighbors:
|
||||
print("\nNearby combinations (by transmission proximity):")
|
||||
for row in neighbors:
|
||||
print(f"{row['transmission']:9.3e}")
|
||||
|
||||
# --- Dry run prompt ---
|
||||
if print_only:
|
||||
print("\n[DRY RUN] No motion executed yet.")
|
||||
if hasattr(self, "OMNYTools") and hasattr(self.OMNYTools, "yesno"):
|
||||
if self.OMNYTools.yesno(
|
||||
"Execute motion to the selected filter combination now?",
|
||||
"y", # default YES
|
||||
):
|
||||
self._execute_combination(best, energy_kev)
|
||||
else:
|
||||
print("Execution skipped.")
|
||||
else:
|
||||
# If yesno not available, default to 'skip' on print_only
|
||||
print("No interactive prompt available. Execution skipped (print_only=True).")
|
||||
return None
|
||||
|
||||
# --- Execute motion directly ---
|
||||
self._execute_combination(best, energy_kev)
|
||||
return None
|
||||
|
||||
# -----------------------------
|
||||
# Physics helpers
|
||||
# -----------------------------
|
||||
def _load_attlen(self, material: str) -> Tuple[List[float], List[float]]:
|
||||
"""
|
||||
Load and cache attenuation-length table for a material from package resources.
|
||||
|
||||
Returns:
|
||||
energies_eV (list), attlen_um (list)
|
||||
"""
|
||||
self._ensure_internal_state()
|
||||
|
||||
material = material.lower()
|
||||
if material in self._attlen_cache:
|
||||
return self._attlen_cache[material]
|
||||
|
||||
fname = self._MATERIAL_FILES.get(material)
|
||||
if not fname:
|
||||
# 'none' or unsupported mapped to empty
|
||||
self._attlen_cache[material] = ([], [])
|
||||
return ([], [])
|
||||
|
||||
energies_eV: List[float] = []
|
||||
attlen_um: List[float] = []
|
||||
|
||||
# Load from installed package: <this_module>/filter_data/<fname>
|
||||
res = resources.files(ft_pkg) / "filter_data" / fname
|
||||
try:
|
||||
# as_file yields a concrete filesystem path even if package is zipped
|
||||
with resources.as_file(res) as p:
|
||||
with p.open("r") as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
parts = line.split()
|
||||
if len(parts) < 2:
|
||||
continue
|
||||
energies_eV.append(float(parts[0]))
|
||||
attlen_um.append(float(parts[1]))
|
||||
except FileNotFoundError as e:
|
||||
raise FileNotFoundError(
|
||||
f"Attenuation data file not found for '{material}': {fname} "
|
||||
f"(looked in package filter_data)"
|
||||
) from e
|
||||
|
||||
if not energies_eV:
|
||||
raise ValueError(
|
||||
f"Attenuation data for material '{material}' is empty or invalid: {fname}"
|
||||
)
|
||||
|
||||
self._attlen_cache[material] = (energies_eV, attlen_um)
|
||||
return energies_eV, attlen_um
|
||||
|
||||
def _attenuation_length_um(self, energy_kev: float, material: str) -> float:
|
||||
"""
|
||||
Linear interpolation of attenuation length λ(energy) in µm.
|
||||
Input energy in keV; tables are in eV.
|
||||
"""
|
||||
material = material.lower()
|
||||
if material == "none":
|
||||
return float("inf") # No attenuation
|
||||
|
||||
energies_eV, attlen_um = self._load_attlen(material)
|
||||
if not energies_eV:
|
||||
# unsupported mapped above
|
||||
raise ValueError(f"Unsupported material or missing data: '{material}'")
|
||||
|
||||
e_ev = energy_kev * 1000.0
|
||||
|
||||
# Clip to the nearest edge for practicality
|
||||
if e_ev <= energies_eV[0]:
|
||||
bec_logger.logger.warning(
|
||||
f"[cSAXS] energy {energy_kev:.3f} keV below table range for {material}; "
|
||||
f"clipping to {energies_eV[0]/1000.0:.3f} keV."
|
||||
)
|
||||
return attlen_um[0]
|
||||
if e_ev >= energies_eV[-1]:
|
||||
bec_logger.logger.warning(
|
||||
f"[cSAXS] energy {energy_kev:.3f} keV above table range for {material}; "
|
||||
f"clipping to {energies_eV[-1]/1000.0:.3f} keV."
|
||||
)
|
||||
return attlen_um[-1]
|
||||
|
||||
# Binary search for interval
|
||||
lo, hi = 0, len(energies_eV) - 1
|
||||
while hi - lo > 1:
|
||||
mid = (lo + hi) // 2
|
||||
if energies_eV[mid] >= e_ev:
|
||||
hi = mid
|
||||
else:
|
||||
lo = mid
|
||||
|
||||
e_lo, e_hi = energies_eV[lo], energies_eV[hi]
|
||||
lam_lo, lam_hi = attlen_um[lo], attlen_um[hi]
|
||||
lam = (e_ev - e_lo) / (e_hi - e_lo) * (lam_hi - lam_lo) + lam_lo
|
||||
return lam
|
||||
|
||||
def _position_transmission(
|
||||
self,
|
||||
pos_entry: Tuple[Tuple[str, float], Tuple[str, float], bool],
|
||||
energy_kev: float,
|
||||
) -> Optional[float]:
|
||||
"""Transmission for a single position (possibly two layers)."""
|
||||
(mat1, th1), (mat2, th2), enabled = pos_entry
|
||||
if not enabled:
|
||||
return None
|
||||
T1 = 1.0
|
||||
T2 = 1.0
|
||||
if mat1 != "none" and th1 > 0.0:
|
||||
lam1 = self._attenuation_length_um(energy_kev, mat1)
|
||||
T1 = math.exp(-th1 / lam1)
|
||||
if mat2 != "none" and th2 > 0.0:
|
||||
lam2 = self._attenuation_length_um(energy_kev, mat2)
|
||||
T2 = math.exp(-th2 / lam2)
|
||||
return T1 * T2
|
||||
|
||||
def _all_combinations(self, energy_kev: float) -> List[dict]:
|
||||
"""
|
||||
Enumerate all enabled combinations across 4 units.
|
||||
Returns a list of dicts sorted by transmission ascending:
|
||||
{
|
||||
'code': 'abcd' # positions 1..6 per unit
|
||||
'indices': [i0, i1, i2, i3], # 0..5
|
||||
'materials': [ ((m1,t1), (m2,t2)|None ), ... for 4 units ]
|
||||
'transmission': float
|
||||
}
|
||||
"""
|
||||
# Slice filters per unit
|
||||
units = [
|
||||
self._FILTERS[u * self._PER_UNIT : (u + 1) * self._PER_UNIT]
|
||||
for u in range(self._UNITS)
|
||||
]
|
||||
|
||||
# Precompute per-position transmissions
|
||||
per_pos_T: List[List[Optional[float]]] = [
|
||||
[self._position_transmission(pos_entry, energy_kev) for pos_entry in unit]
|
||||
for unit in units
|
||||
]
|
||||
|
||||
combos: List[dict] = []
|
||||
for i0 in range(self._PER_UNIT):
|
||||
T0 = per_pos_T[0][i0]
|
||||
if T0 is None:
|
||||
continue
|
||||
for i1 in range(self._PER_UNIT):
|
||||
T1 = per_pos_T[1][i1]
|
||||
if T1 is None:
|
||||
continue
|
||||
for i2 in range(self._PER_UNIT):
|
||||
T2 = per_pos_T[2][i2]
|
||||
if T2 is None:
|
||||
continue
|
||||
for i3 in range(self._PER_UNIT):
|
||||
T3 = per_pos_T[3][i3]
|
||||
if T3 is None:
|
||||
continue
|
||||
|
||||
T = T0 * T1 * T2 * T3
|
||||
indices = [i0, i1, i2, i3]
|
||||
code = "".join(str(i + 1) for i in indices)
|
||||
|
||||
# Collect materials/thickness for reporting
|
||||
mats = []
|
||||
for u, idx in enumerate(indices):
|
||||
(m1, t1), (m2, t2), _ = units[u][idx]
|
||||
mats.append(((m1, t1), (m2, t2) if (m2 != "none" and t2 > 0.0) else None))
|
||||
|
||||
combos.append(
|
||||
{
|
||||
"code": code,
|
||||
"indices": indices,
|
||||
"materials": mats,
|
||||
"transmission": T,
|
||||
}
|
||||
)
|
||||
|
||||
combos.sort(key=lambda c: c["transmission"]) # ascending
|
||||
return combos
|
||||
|
||||
def _find_best_combination(self, target_T: float, energy_kev: float) -> dict:
|
||||
"""Pick combination with transmission closest to target."""
|
||||
combos = self._all_combinations(energy_kev)
|
||||
best = min(combos, key=lambda c: abs(c["transmission"] - target_T))
|
||||
return best
|
||||
|
||||
def _neighbors_around_best(
|
||||
self, target_T: float, energy_kev: float, span: int = 5
|
||||
) -> List[dict]:
|
||||
"""Return a few combinations around the best, by proximity in transmission."""
|
||||
combos = self._all_combinations(energy_kev)
|
||||
ranked = sorted(combos, key=lambda c: abs(c["transmission"] - target_T))
|
||||
return ranked[1 : 1 + span] # exclude best itself
|
||||
|
||||
# -----------------------------
|
||||
# Formatting & motion
|
||||
# -----------------------------
|
||||
def _print_combination(self, comb: dict, energy_kev: float, header: Optional[str] = None):
|
||||
if header:
|
||||
print(f"\n{header}:")
|
||||
code = comb["code"]
|
||||
T = comb["transmission"]
|
||||
print(f" Filters {code} transmission at {energy_kev:.3f} keV = {T:9.3e}")
|
||||
# Per-unit detail for the selected combination only
|
||||
for u, matinfo in enumerate(comb["materials"], start=1):
|
||||
first, second = matinfo
|
||||
(m1, t1) = first
|
||||
pos = int(code[u - 1])
|
||||
if second is not None:
|
||||
(m2, t2) = second
|
||||
print(
|
||||
f" unit {u}: #{pos} "
|
||||
f"{t1:4.0f} mu {m1:<4} + {t2:4.0f} mu {m2:<4}"
|
||||
)
|
||||
else:
|
||||
if m1 != "none" and t1 > 0.0:
|
||||
print(f" unit {u}: #{pos} {t1:4.0f} mu {m1:<4}")
|
||||
else:
|
||||
print(f" unit {u}: #{pos} ----- out")
|
||||
|
||||
|
||||
|
||||
def _execute_combination(self, comb: dict, energy_kev: float):
|
||||
"""
|
||||
Execute motion to the indices encoded in 'comb["code"]'.
|
||||
|
||||
Mapping:
|
||||
- code 'abcd' → per-unit index (a,b,c,d) ∈ {1..6}
|
||||
- positions are looked up from _POSITIONS_USER
|
||||
- axes are defined in _AXES
|
||||
|
||||
Motion uses: umv(dev.axis, position, dev.axis2, position2, ...)
|
||||
"""
|
||||
|
||||
indices = comb["indices"] # 0-based per unit
|
||||
move_args = [] # Collect (device, position) pairs
|
||||
|
||||
print("\nExecuting combined motion:")
|
||||
for unit_idx, pos_idx in enumerate(indices):
|
||||
pos_list = self._POSITIONS_USER[unit_idx]
|
||||
target_pos = pos_list[pos_idx]
|
||||
if target_pos is None:
|
||||
raise RuntimeError(
|
||||
f"Unit {unit_idx+1} position {pos_idx+1} has no defined coordinate."
|
||||
)
|
||||
|
||||
axis_name = self._AXES[unit_idx]
|
||||
axis_obj = getattr(dev, axis_name)
|
||||
print(f" {axis_name} → {target_pos:.3f}")
|
||||
move_args.extend([axis_obj, target_pos])
|
||||
|
||||
umv(*move_args)
|
||||
|
||||
print("\nVerifying final positions:")
|
||||
for unit_idx in range(self._UNITS):
|
||||
axis_name = self._AXES[unit_idx]
|
||||
axis_obj = getattr(dev, axis_name)
|
||||
try:
|
||||
actual = float(axis_obj.readback.get())
|
||||
print(f" {axis_name} = {actual:.3f}")
|
||||
except Exception:
|
||||
print(f" {axis_name}: readback unavailable")
|
||||
|
||||
achieved_T = comb["transmission"]
|
||||
print(f"\nAchieved transmission (approx.): {achieved_T:9.3e} at {energy_kev:.3f} keV")
|
||||
|
||||
|
||||
|
||||
def _fil_trans_report(self, tol: float = 0.1, energy_kev: Optional[float] = None) -> None:
|
||||
"""
|
||||
Report the currently active exposure‑box filter combination.
|
||||
Determines stage positions via dev.<axis>.readback.get()
|
||||
with a tolerance window of ±tol relative to nominal positions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
tol : float
|
||||
Tolerance for matching positions.
|
||||
energy_kev : float, optional
|
||||
Photon energy in keV. If None, tries to read from dev.mokev or defaults to 6.2 keV.
|
||||
"""
|
||||
|
||||
# Ensure global dev is available
|
||||
if dev is None:
|
||||
print("ERROR: Global 'dev' object not found.")
|
||||
return
|
||||
|
||||
# --- Energy handling (EPICS only) ---
|
||||
if energy_kev is None:
|
||||
try:
|
||||
energy_kev = float(epics_get("X12SA-OP-CCM1:ENERGY-GET"))
|
||||
except Exception as exc:
|
||||
raise RuntimeError(
|
||||
"Energy not specified and could not read EPICS PV "
|
||||
"'X12SA-OP-CCM1:ENERGY-GET'."
|
||||
) from exc
|
||||
else:
|
||||
energy_kev = float(energy_kev)
|
||||
|
||||
|
||||
print("\nCurrent filter transmission report")
|
||||
print("-" * 60)
|
||||
print(f"Photon energy : {energy_kev:.3f} keV")
|
||||
|
||||
indices = [] # 0–5 per unit
|
||||
|
||||
for unit_idx, axis_name in enumerate(self._AXES):
|
||||
axis_obj = getattr(dev, axis_name, None)
|
||||
if axis_obj is None:
|
||||
print(f"ERROR: Device axis '{axis_name}' not found.")
|
||||
return
|
||||
|
||||
try:
|
||||
rb = float(axis_obj.readback.get())
|
||||
except Exception:
|
||||
print(f"ERROR: readback unavailable for axis {axis_name}")
|
||||
return
|
||||
|
||||
# Match readback to nearest nominal
|
||||
pos_list = self._POSITIONS_USER[unit_idx]
|
||||
best_idx = None
|
||||
for i, nominal in enumerate(pos_list):
|
||||
if nominal is None:
|
||||
continue
|
||||
if abs(rb - nominal) <= tol:
|
||||
best_idx = i
|
||||
break
|
||||
|
||||
if best_idx is None:
|
||||
print(f"Unit {unit_idx+1}: readback {rb:.3f} does not match any known position.")
|
||||
return
|
||||
|
||||
indices.append(best_idx)
|
||||
|
||||
# Build combination code
|
||||
code = "".join(str(i + 1) for i in indices)
|
||||
print(f"Matched filter code: {code}")
|
||||
|
||||
# Compute transmission and materials
|
||||
units = [
|
||||
self._FILTERS[u * self._PER_UNIT : (u + 1) * self._PER_UNIT]
|
||||
for u in range(self._UNITS)
|
||||
]
|
||||
|
||||
materials = []
|
||||
total_T = 1.0
|
||||
|
||||
for u, pos_idx in enumerate(indices):
|
||||
(m1, t1), (m2, t2), enabled = units[u][pos_idx]
|
||||
T = self._position_transmission(units[u][pos_idx], energy_kev)
|
||||
if T is None:
|
||||
print(f"Unit {u+1}: position disabled")
|
||||
T = 1.0
|
||||
total_T *= T
|
||||
|
||||
if m2 != "none" and t2 > 0:
|
||||
materials.append(((m1, t1), (m2, t2)))
|
||||
else:
|
||||
materials.append(((m1, t1), None))
|
||||
|
||||
# Print detailed report
|
||||
self.OMNYTools.printgreenbold(f"Total transmission: {total_T:.6e}")
|
||||
print("-" * 60)
|
||||
|
||||
for u, matinfo in enumerate(materials, start=1):
|
||||
(m1, t1), second = matinfo
|
||||
pos = indices[u - 1] + 1
|
||||
if second:
|
||||
m2, t2 = second
|
||||
print(f" unit {u}: #{pos} {t1:4.0f} µm {m1:<4} + {t2:4.0f} µm {m2:<4}")
|
||||
else:
|
||||
if m1 != "none" and t1 > 0:
|
||||
print(f" unit {u}: #{pos} {t1:4.0f} µm {m1:<4}")
|
||||
else:
|
||||
print(f" unit {u}: #{pos} ----- out")
|
||||
@@ -1,449 +0,0 @@
|
||||
|
||||
import builtins
|
||||
import time
|
||||
|
||||
from bec_lib import bec_logger
|
||||
|
||||
# Logger initialization
|
||||
logger = bec_logger.logger
|
||||
|
||||
# Pull BEC globals if present
|
||||
if builtins.__dict__.get("bec") is not None:
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
|
||||
|
||||
class cSAXSInitSmaractStagesError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class cSAXSInitSmaractStages:
|
||||
"""
|
||||
Runtime SmarAct utilities for referencing and moving to initial positions.
|
||||
|
||||
This class no longer relies on static mappings. Instead, it:
|
||||
- discovers available devices from `list(dev.keys())`
|
||||
- reads the numeric channel/axis from each device's `user_parameter['bl_smar_stage']`
|
||||
- reads `init_position` from `user_parameter['init_position']`
|
||||
"""
|
||||
|
||||
def __init__(self, client) -> None:
|
||||
self.client = client
|
||||
|
||||
# ------------------------------
|
||||
# Internal helpers (runtime-based)
|
||||
# ------------------------------
|
||||
def _yesno(self, question: str, default: str = "y") -> bool:
|
||||
"""
|
||||
Use OMNYTools.yesno if available; otherwise default to 'yes' (or fallback to input()).
|
||||
"""
|
||||
try:
|
||||
if hasattr(self, "OMNYTools") and hasattr(self.OMNYTools, "yesno"):
|
||||
return self.OMNYTools.yesno(question, default)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Fallback: default answer without interaction
|
||||
# (Safe default: 'y' proceeds; adjust if you want interactive input)
|
||||
logger.info(f"[cSAXS] (yesno fallback) {question} -> default '{default}'")
|
||||
return (default or "y").lower().startswith("y")
|
||||
|
||||
def _get_user_param_safe(self, device_name: str, key: str):
|
||||
"""
|
||||
Safe access to device user parameters from current BEC session.
|
||||
"""
|
||||
try:
|
||||
return dev[device_name].user_parameter.get(key)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def _iter_session_devices(self):
|
||||
"""
|
||||
Yield device names available in current BEC session.
|
||||
"""
|
||||
if dev is None:
|
||||
return
|
||||
for name in list(dev.keys()):
|
||||
yield name
|
||||
|
||||
def _build_session_axis_map(self, selection: set | None = None) -> dict:
|
||||
"""
|
||||
Build runtime axis map {device_name: channel} for devices that define 'bl_smar_stage'.
|
||||
If 'selection' is provided, restrict to names in selection.
|
||||
"""
|
||||
axis_map = {}
|
||||
missing = []
|
||||
for name in self._iter_session_devices() or []:
|
||||
if selection is not None and name not in selection:
|
||||
continue
|
||||
ch = self._get_user_param_safe(name, "bl_smar_stage")
|
||||
if ch is None:
|
||||
missing.append(name)
|
||||
continue
|
||||
try:
|
||||
axis_map[name] = int(ch)
|
||||
except Exception:
|
||||
missing.append(name)
|
||||
|
||||
if missing and selection is None:
|
||||
logger.info(
|
||||
"[cSAXS] Devices without 'bl_smar_stage' (ignored): " + ", ".join(sorted(missing))
|
||||
)
|
||||
return axis_map
|
||||
|
||||
def _get_device_object(self, device_name: str):
|
||||
"""
|
||||
Return the live device object from BEC 'dev'.
|
||||
"""
|
||||
try:
|
||||
return getattr(dev, device_name)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
# ------------------------------
|
||||
# Public API
|
||||
# ------------------------------
|
||||
def smaract_reference_stages(self, force: bool = False, devices_to_reference=None):
|
||||
"""
|
||||
Reference SmarAct stages using runtime discovery.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
force : bool, optional
|
||||
If True, re-reference ALL selected stages.
|
||||
If False (default), only reference stages that are currently NOT referenced.
|
||||
|
||||
devices_to_reference : iterable of str or str, optional
|
||||
If provided, only these devices will be considered for referencing.
|
||||
If None, all devices in the current session that define 'bl_smar_stage' are considered.
|
||||
|
||||
Behavior
|
||||
--------
|
||||
- Runtime-based: reads axis channel from user_parameter['bl_smar_stage'].
|
||||
- If devices_to_reference is given → restrict referencing to those.
|
||||
- If force=False → skip devices already referenced.
|
||||
- If force=True → re-reference selected devices always.
|
||||
- Only newly referenced devices are passed to
|
||||
smaract_components_to_initial_position(devices_to_move=[...]) afterwards.
|
||||
|
||||
Examples
|
||||
--------
|
||||
Reference only stages that are NOT referenced yet (default)
|
||||
csaxs.smaract_reference_stages()
|
||||
|
||||
Force re-reference of all stages
|
||||
csaxs.smaract_reference_stages(force=True)
|
||||
|
||||
Reference only specific stages
|
||||
csaxs.smaract_reference_stages(
|
||||
devices_to_reference=["sl3trxi", "sl3trxo", "xbpm3x"]
|
||||
)
|
||||
|
||||
Reference selected stages and force re-referencing
|
||||
csaxs.smaract_reference_stages(
|
||||
devices_to_reference=["sl4trxi", "sl4trxo"],
|
||||
force=True
|
||||
)
|
||||
|
||||
Reference a single device
|
||||
csaxs.smaract_reference_stages(
|
||||
devices_to_reference="xbimtrx"
|
||||
)
|
||||
|
||||
Reference only the selected devices (skip already-referenced ones)
|
||||
csaxs.smaract_reference_stages(
|
||||
devices_to_reference=["sl3trxi", "sl4trxo", "sl5trxt"]
|
||||
)
|
||||
|
||||
Check referencing status of all stages
|
||||
csaxs.smaract_check_all_referenced()
|
||||
"""
|
||||
# Normalize selection
|
||||
if isinstance(devices_to_reference, str):
|
||||
devices_to_reference = [devices_to_reference]
|
||||
selection = set(devices_to_reference) if devices_to_reference else None
|
||||
|
||||
# Build axis map for selected devices (or all devices present)
|
||||
axis_map = self._build_session_axis_map(selection=selection)
|
||||
if selection:
|
||||
unknown = sorted(list(selection - set(axis_map.keys())))
|
||||
if unknown:
|
||||
print(f"Unknown devices requested or missing 'bl_smar_stage' (ignored): {unknown}")
|
||||
|
||||
|
||||
newly_referenced = []
|
||||
already_referenced = []
|
||||
failed = []
|
||||
to_verify = [] # devices that need a final verification
|
||||
|
||||
print("\nStarting SmarAct referencing...\n")
|
||||
|
||||
for dev_name in sorted(axis_map.keys()):
|
||||
ch = axis_map[dev_name]
|
||||
d = self._get_device_object(dev_name)
|
||||
|
||||
if d is None:
|
||||
print(f"{dev_name}: device not accessible, skipping.")
|
||||
failed.append(dev_name)
|
||||
continue
|
||||
|
||||
try:
|
||||
is_ref = d.controller.axis_is_referenced(ch)
|
||||
|
||||
# Skip if already referenced and not forcing
|
||||
if is_ref and not force:
|
||||
print(f"{dev_name}: already referenced, skipping.")
|
||||
already_referenced.append(dev_name)
|
||||
continue
|
||||
|
||||
# Start referencing
|
||||
print(f"{dev_name}: referencing axis...")
|
||||
d.controller.set_closed_loop_move_speed(ch, 1)
|
||||
d.controller.find_reference_mark(ch, 0, 1000, 1)
|
||||
time.sleep(0.1)
|
||||
|
||||
# Add to list for final verification
|
||||
to_verify.append((dev_name, ch, d))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error referencing {dev_name} (axis {ch}): {e}")
|
||||
failed.append(dev_name)
|
||||
|
||||
time.sleep(1.0)
|
||||
|
||||
print("\nVerifying referencing state...\n")
|
||||
|
||||
for dev_name, ch, d in to_verify:
|
||||
try:
|
||||
if d.controller.axis_is_referenced(ch):
|
||||
print(f"{dev_name}: successfully referenced.")
|
||||
newly_referenced.append(dev_name)
|
||||
else:
|
||||
print(f"{dev_name}: referencing FAILED.")
|
||||
failed.append(dev_name)
|
||||
except Exception as e:
|
||||
print(f"{dev_name}: verification error: {e}")
|
||||
failed.append(dev_name)
|
||||
|
||||
# --- Summary ---
|
||||
print("\n--- Referencing summary ---")
|
||||
print(f"Newly referenced: {newly_referenced}")
|
||||
print(f"Already referenced (kept): {already_referenced}")
|
||||
print(f"Failed: {failed}")
|
||||
print("-----------------------------------------\n")
|
||||
|
||||
# --- Move newly referenced only ---
|
||||
if newly_referenced:
|
||||
print("Moving newly referenced stages to initial positions...")
|
||||
self.smaract_components_to_initial_position(devices_to_move=newly_referenced)
|
||||
else:
|
||||
print("No newly referenced stages.")
|
||||
|
||||
def smaract_check_all_referenced(self):
|
||||
"""
|
||||
Check reference state for all SmarAct devices that define 'bl_smar_stage'.
|
||||
"""
|
||||
axis_map = self._build_session_axis_map(selection=None)
|
||||
for dev_name in sorted(axis_map.keys()):
|
||||
ch = axis_map[dev_name]
|
||||
d = self._get_device_object(dev_name)
|
||||
if d is None:
|
||||
print(f"{dev_name}: device not accessible or unsupported.")
|
||||
continue
|
||||
try:
|
||||
if d.controller.axis_is_referenced(ch):
|
||||
print(f"{dev_name} (axis {ch}) is referenced.")
|
||||
else:
|
||||
print(f"{dev_name} (axis {ch}) is NOT referenced.")
|
||||
except Exception as e:
|
||||
print(f"Error checking {dev_name} (axis {ch}): {e}")
|
||||
|
||||
def smaract_components_to_initial_position(self, devices_to_move=None):
|
||||
"""
|
||||
Move selected (or all) SmarAct-based components to their configured init_position.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
devices_to_move : iterable of str or str, optional
|
||||
Specific device names to move (e.g. ["xbpm3x", "sl3trxi"]).
|
||||
If None, all devices in the current session that define 'bl_smar_stage' are considered.
|
||||
|
||||
Behavior
|
||||
--------
|
||||
- Runtime-based: uses user_parameter['bl_smar_stage'] (numeric channel) and 'init_position'.
|
||||
- Only axes that are referenced will be moved.
|
||||
- Unreferenced axes are skipped with a WARNING; the operation continues.
|
||||
- Devices missing `init_position` are skipped and listed.
|
||||
- At the end, a summary warns if some stages could not be moved because they were not referenced.
|
||||
"""
|
||||
# Normalize selection
|
||||
if isinstance(devices_to_move, str):
|
||||
devices_to_move = [devices_to_move]
|
||||
selection = set(devices_to_move) if devices_to_move else None
|
||||
|
||||
# Resolve axis map based on selection
|
||||
axis_map = self._build_session_axis_map(selection=selection)
|
||||
unknown_requested = []
|
||||
if selection:
|
||||
unknown_requested = sorted(list(selection - set(axis_map.keys())))
|
||||
if unknown_requested:
|
||||
logger.warning(
|
||||
"[cSAXS] Requested devices unknown or missing 'bl_smar_stage': "
|
||||
+ ", ".join(unknown_requested)
|
||||
)
|
||||
|
||||
# First confirmation: intent
|
||||
scope_desc = "all SmarAct-based components" if selection is None else "the selected SmarAct-based components"
|
||||
if not self._yesno(
|
||||
f"Do you want to move {scope_desc} to the init position as defined in the config file?",
|
||||
"y",
|
||||
):
|
||||
return
|
||||
|
||||
planned_moves = []
|
||||
not_referenced = []
|
||||
missing_params = []
|
||||
inaccessible_devices = []
|
||||
|
||||
# --- Pre-check phase ---
|
||||
for dev_name in sorted(axis_map.keys()):
|
||||
d = self._get_device_object(dev_name)
|
||||
if d is None:
|
||||
logger.warning(f"[cSAXS] Device {dev_name} not accessible, skipping.")
|
||||
inaccessible_devices.append(dev_name)
|
||||
continue
|
||||
|
||||
ch = axis_map[dev_name]
|
||||
try:
|
||||
# Reference check
|
||||
if not d.controller.axis_is_referenced(ch):
|
||||
not_referenced.append(dev_name)
|
||||
continue
|
||||
|
||||
# Fetch init_position (from user parameters)
|
||||
init_pos = self._get_user_param_safe(dev_name, "init_position")
|
||||
if init_pos is None:
|
||||
missing_params.append(dev_name)
|
||||
continue
|
||||
|
||||
planned_moves.append((dev_name, float(init_pos)))
|
||||
|
||||
except Exception as exc:
|
||||
logger.error(f"[cSAXS] Error during pre-check for {dev_name}: {exc}")
|
||||
|
||||
if not planned_moves:
|
||||
# Nothing to move—still summarize why.
|
||||
header = "\nNo motions planned. Summary of issues:"
|
||||
lines = []
|
||||
if not_referenced:
|
||||
lines.append(" - Not referenced: " + ", ".join(sorted(not_referenced)))
|
||||
if missing_params:
|
||||
lines.append(" - Missing init_position: " + ", ".join(sorted(missing_params)))
|
||||
if inaccessible_devices:
|
||||
lines.append(" - Not accessible: " + ", ".join(sorted(inaccessible_devices)))
|
||||
if unknown_requested:
|
||||
lines.append(" - Unknown requested: " + ", ".join(sorted(unknown_requested)))
|
||||
if not lines:
|
||||
lines.append(" - (No eligible devices or nothing to do.)")
|
||||
|
||||
print(header)
|
||||
for line in lines:
|
||||
print(line)
|
||||
logger.warning("[cSAXS] Nothing to do.")
|
||||
return
|
||||
|
||||
# --- Summary table ---
|
||||
print("\nPlanned SmarAct motions to initial position:")
|
||||
print("-" * 60)
|
||||
print(f"{'Device':<35} {'Init position':>20}")
|
||||
print("-" * 60)
|
||||
for dev_name, init_pos in planned_moves:
|
||||
print(f"{dev_name:<35} {init_pos:>20.6g}")
|
||||
print("-" * 60)
|
||||
|
||||
# Notes / diagnostics
|
||||
if selection is not None:
|
||||
print("\nNote: Only the following devices were requested to move:")
|
||||
print(", ".join(sorted(selection)))
|
||||
if unknown_requested:
|
||||
print("\nNote: The following requested devices are unknown and were ignored:")
|
||||
print(", ".join(unknown_requested))
|
||||
if not_referenced:
|
||||
print("\nNote: The following devices are NOT referenced and will be skipped:")
|
||||
print(", ".join(sorted(not_referenced)))
|
||||
if missing_params:
|
||||
print("\nNote: The following devices have no init_position defined and will be skipped:")
|
||||
print(", ".join(sorted(missing_params)))
|
||||
if inaccessible_devices:
|
||||
print("\nNote: The following devices were not accessible and will be skipped:")
|
||||
print(", ".join(sorted(inaccessible_devices)))
|
||||
|
||||
# Second confirmation: execution
|
||||
if not self._yesno("Proceed with the motions listed above?", "y"):
|
||||
logger.info("[cSAXS] Motion to initial position aborted by user.")
|
||||
return
|
||||
# --- Execution phase (SIMULTANEOUS MOTION) ---
|
||||
if umv is None:
|
||||
logger.error("[cSAXS] 'umv' is not available in this session.")
|
||||
return
|
||||
|
||||
# Build a flat argument list: [dev1, pos1, dev2, pos2, ...]
|
||||
move_args = []
|
||||
for dev_name, init_pos in planned_moves:
|
||||
d = self._get_device_object(dev_name)
|
||||
if d is None:
|
||||
logger.error(f"[cSAXS] Could not access {dev_name}, skipping.")
|
||||
continue
|
||||
move_args.append(d)
|
||||
move_args.append(init_pos)
|
||||
logger.info(f"[cSAXS] Preparing move: {dev_name} -> {init_pos}")
|
||||
|
||||
if not move_args:
|
||||
logger.warning("[cSAXS] No valid devices left for simultaneous motion.")
|
||||
return
|
||||
|
||||
# Trigger simultaneous move
|
||||
try:
|
||||
logger.info(f"[cSAXS] Starting simultaneous motion of {len(planned_moves)} devices.")
|
||||
umv(*move_args) # simultaneous move
|
||||
except Exception as exc:
|
||||
logger.error(f"[cSAXS] Simultaneous motion failed: {exc}")
|
||||
return
|
||||
|
||||
logger.info("[cSAXS] Simultaneous SmarAct motion to initial positions completed.")
|
||||
|
||||
# Final warning summary about unreferenced devices
|
||||
if not_referenced:
|
||||
logger.warning(
|
||||
"[cSAXS] Some stages were NOT moved because they are not referenced:\n"
|
||||
+ ", ".join(sorted(not_referenced))
|
||||
+ "\nPlease reference these axes and re-run if needed."
|
||||
)
|
||||
|
||||
|
||||
class cSAXSSmaract:
|
||||
def __init__(self, client) -> None:
|
||||
self.client = client
|
||||
|
||||
def _get_user_param_safe(self, device_name: str, key: str):
|
||||
try:
|
||||
return dev[device_name].user_parameter.get(key)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def fshn1in(self):
|
||||
"""
|
||||
Move fast shutter n1 to its 'in' position defined in user parameters.
|
||||
"""
|
||||
in_pos = self._get_user_param_safe("fast_shutter_n1_x", "in")
|
||||
if in_pos is None:
|
||||
logger.error("[cSAXS] No 'in' position defined for fast_shutter_n1_x.")
|
||||
return
|
||||
if umv is None:
|
||||
logger.error("[cSAXS] 'umv' is not available in this session.")
|
||||
return
|
||||
umv(dev.fast_shutter_n1_x, in_pos)
|
||||
@@ -22,10 +22,8 @@ logger = bec_logger.logger
|
||||
if builtins.__dict__.get("bec") is not None:
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
umv = builtins.__dict__.get("umv")
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
|
||||
class FlomniToolsError(Exception):
|
||||
|
||||
@@ -7,10 +7,8 @@ from bec_widgets.cli.client import BECDockArea
|
||||
if builtins.__dict__.get("bec") is not None:
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
umv = builtins.__dict__.get("umv")
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
|
||||
class flomniGuiToolsError(Exception):
|
||||
|
||||
@@ -13,10 +13,8 @@ logger = bec_logger.logger
|
||||
# import builtins to avoid linter errors
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
umv = builtins.__dict__.get("umv")
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_ipython_client.plugins.flomni import Flomni
|
||||
|
||||
@@ -7,10 +7,8 @@ from bec_widgets.cli.client import BECDockArea
|
||||
if builtins.__dict__.get("bec") is not None:
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
umv = builtins.__dict__.get("umv")
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
|
||||
class OMNYGuiToolsError(Exception):
|
||||
|
||||
@@ -27,10 +27,9 @@ logger = bec_logger.logger
|
||||
if builtins.__dict__.get("bec") is not None:
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
umv = builtins.__dict__.get("umv")
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
|
||||
class OMNYInitError(Exception):
|
||||
pass
|
||||
|
||||
@@ -16,10 +16,8 @@ from rich.table import Table
|
||||
if builtins.__dict__.get("bec") is not None:
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
umv = builtins.__dict__.get("umv")
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
|
||||
class OMNYToolsError(Exception):
|
||||
|
||||
@@ -16,10 +16,8 @@ from csaxs_bec.bec_ipython_client.plugins.cSAXS import epics_get, epics_put, fsh
|
||||
if builtins.__dict__.get("bec") is not None:
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
umv = builtins.__dict__.get("umv")
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
|
||||
class OMNYTransferError(Exception):
|
||||
|
||||
@@ -13,10 +13,8 @@ logger = bec_logger.logger
|
||||
# import builtins to avoid linter errors
|
||||
bec = builtins.__dict__.get("bec")
|
||||
dev = builtins.__dict__.get("dev")
|
||||
scans = builtins.__dict__.get("scans")
|
||||
|
||||
def umv(*args):
|
||||
return scans.umv(*args, relative=False)
|
||||
umv = builtins.__dict__.get("umv")
|
||||
umvr = builtins.__dict__.get("umvr")
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_ipython_client.plugins.omny import OMNY
|
||||
|
||||
@@ -1,250 +0,0 @@
|
||||
"""Module providing debugging tools for the BEC IPython client at cSAXS."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from functools import partial
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
import numpy as np
|
||||
from pydantic import BaseModel
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
from slugify import slugify
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_ipython_client.main import BECIPythonClient
|
||||
from bec_lib.devicemanager import DeviceManagerBase
|
||||
from bec_lib.scans import Scans
|
||||
from bec_widgets.cli.client_utils import BECGuiClient
|
||||
|
||||
scans: Scans # type: ignore[no-redef]
|
||||
|
||||
bec: BECIPythonClient # type: ignore[no-redef]
|
||||
|
||||
dev: DeviceManagerBase # type: ignore[no-redef]
|
||||
|
||||
|
||||
class Detector(BaseModel):
|
||||
"""Model representing a detector configuration."""
|
||||
|
||||
name: str
|
||||
hostnames: list[str]
|
||||
cfg: dict
|
||||
|
||||
|
||||
def to_identifier(text: str) -> str:
|
||||
"""
|
||||
Convert an unsafe string into a valid Python identifier.
|
||||
"""
|
||||
name = slugify(text.strip(), separator="_")
|
||||
name = re.sub(r"[^a-zA-Z0-9_]", "", name)
|
||||
|
||||
if not name:
|
||||
raise ValueError(f"Cannot convert '{text}' to a valid identifier.")
|
||||
|
||||
if name[0].isdigit():
|
||||
name = f"_{name}"
|
||||
|
||||
return name
|
||||
|
||||
|
||||
class DebugTools:
|
||||
"""A collection of debugging tools for the BEC IPython client at cSAXS."""
|
||||
|
||||
_PURPOSE = (
|
||||
"Debugging helpers for the cSAXS BEC IPython client. These tools are intended for advanced users "
|
||||
"and developers to diagnose and troubleshoot issues within the BEC environment. "
|
||||
"Below are the available methods together with a brief description of their functionality."
|
||||
)
|
||||
|
||||
######################
|
||||
## Internal Methods ##
|
||||
######################
|
||||
|
||||
def _describe(self) -> None:
|
||||
"""Pretty-print a description of this debugging tool."""
|
||||
console = Console()
|
||||
|
||||
# Offset for IPython prompt misplacement
|
||||
console.print("\n\n", end="")
|
||||
|
||||
header = Text("DebugTools", style="bold cyan")
|
||||
purpose = Text(self._PURPOSE, style="dim")
|
||||
|
||||
console.print(Panel(purpose, title=header, expand=False))
|
||||
|
||||
table = Table(show_header=True, header_style="bold magenta")
|
||||
table.add_column("Method", style="bold", no_wrap=True)
|
||||
table.add_column("Description")
|
||||
|
||||
for name, member in inspect.getmembers(self, predicate=inspect.ismethod):
|
||||
if name.startswith("_"):
|
||||
continue
|
||||
|
||||
doc = inspect.getdoc(member)
|
||||
short_doc = doc.splitlines()[0] if doc else ""
|
||||
table.add_row(name, short_doc)
|
||||
|
||||
console.print(table)
|
||||
|
||||
def _repr_pretty_(self, p, cycle: bool) -> None:
|
||||
if cycle:
|
||||
p.text("DebugTools(...)")
|
||||
else:
|
||||
self._describe()
|
||||
|
||||
#####################
|
||||
### MCS Card Check ###
|
||||
#####################
|
||||
|
||||
def _check_if_device_is_loaded(self, device_name: str):
|
||||
"""Check if a device is loaded in the current BEC session."""
|
||||
if device_name not in dev:
|
||||
raise RuntimeError(
|
||||
f"Device {device_name} was not loaded in the current active BEC session."
|
||||
)
|
||||
|
||||
def mcs_test_acquire(
|
||||
self, mode: Literal["high_frame", "medium_frame", "low_frame"] = "high_frame"
|
||||
):
|
||||
"""
|
||||
Method to perform a test acquisition with randomized exposure time, burst frames, and cycles
|
||||
on the MCS card using the DDG trigger setup.
|
||||
|
||||
Args:
|
||||
mode (Literal["high_frame", "medium_frame", "low_frame"]): The mode of the test.
|
||||
- 'high_frame': Tests high frame rates with short exposure times.
|
||||
- 'medium_frame': Tests medium frame rates with moderate exposure times.
|
||||
- 'low_frame': Tests low frame rates with longer exposure times.
|
||||
"""
|
||||
self._check_if_device_is_loaded("mcs")
|
||||
self._check_if_device_is_loaded("ddg1")
|
||||
self._check_if_device_is_loaded("ddg2")
|
||||
|
||||
if mode == "high_frame":
|
||||
burst_frames = np.random.randint(10_000, 100_000) # between 10000 and 100000
|
||||
cycles = np.random.randint(5, 20) # between 5 and 20
|
||||
exp_time = (
|
||||
np.random.rand() * (0.001 - 0.201e-3) + 0.201e-3
|
||||
) # between 0.000201 ms and 0.001 s
|
||||
elif mode == "medium_frame":
|
||||
burst_frames = np.random.randint(50, 500) # between 50 and 500
|
||||
cycles = np.random.randint(1, 10) # between 1 and 10
|
||||
exp_time = np.random.rand() * (0.01 - 0.001) + 0.001 # between 0.001 ms and 0.01 s
|
||||
elif mode == "low_frame":
|
||||
burst_frames = np.random.randint(5, 20) # between 5 and 20
|
||||
cycles = np.random.randint(1, 5) # between 1 and 5
|
||||
exp_time = np.random.rand() * (2 - 0.1) + 0.1 # between 0.1 ms and 2 s
|
||||
else:
|
||||
raise ValueError(f"Invalid mode '{mode}' specified for acquire scan test.")
|
||||
print(
|
||||
f"Starting acquire measurement with exp_time={exp_time:.6f}, burst_frames={burst_frames}, cycles={cycles}"
|
||||
)
|
||||
s = scans.acquire(
|
||||
exp_time=exp_time, frames_per_trigger=burst_frames, burst_at_each_point=cycles
|
||||
)
|
||||
s.wait(file_written=True)
|
||||
print("Acquire measurement finished.")
|
||||
print("Checking MCS data...")
|
||||
scan_data = bec.history.get_by_scan_id(s.scan.scan_id)
|
||||
mcs_data = scan_data.devices.mcs
|
||||
print(mcs_data)
|
||||
|
||||
shape = mcs_data._info["mcs_mca_mca1"]["value"]["shape"]
|
||||
expected_shape = (cycles * burst_frames,)
|
||||
# Assert will raise an error if the shapes do not match
|
||||
assert (
|
||||
shape == expected_shape
|
||||
), f"MCS data shape {shape} does not match expected shape {expected_shape}."
|
||||
|
||||
########################
|
||||
### JFJ/Eiger Checks ###
|
||||
########################
|
||||
|
||||
def _get_jfj_eiger_config(self) -> dict[str, Detector]:
|
||||
"""Retrieve the current JFJ/Eiger detector configuration from the BEC client."""
|
||||
# FIXME: Implement REST API call once ready for use from Leo Sala's team.
|
||||
ret = {}
|
||||
base_path = os.path.dirname(__file__)
|
||||
config_path = os.path.join(base_path, "jfj_config.json")
|
||||
with open(config_path, "r", encoding="utf-8") as fh:
|
||||
cfg = json.load(fh)
|
||||
|
||||
for entry in cfg["detector"]:
|
||||
det = Detector(
|
||||
name=to_identifier(entry["description"]), hostnames=entry["hostname"], cfg=cfg
|
||||
)
|
||||
ret[det.name] = det
|
||||
return ret
|
||||
|
||||
def list_detectors(self) -> list[str]:
|
||||
"""
|
||||
List the names of all JFJ/Eiger detectors configured in the BEC client.
|
||||
|
||||
Returns:
|
||||
list[str]: A list of detector names.
|
||||
"""
|
||||
detectors = self._get_jfj_eiger_config()
|
||||
return list(detectors.keys())
|
||||
|
||||
def ping_detector(self, detector_name: str) -> bool:
|
||||
"""
|
||||
Ping a JFJ/Eiger detector to check if it is reachable.
|
||||
|
||||
Args:
|
||||
detector_name (str): The name of the detector to ping.
|
||||
|
||||
Returns:
|
||||
bool: True if the detector is reachable, False otherwise.
|
||||
"""
|
||||
detectors = self._get_jfj_eiger_config()
|
||||
if detector_name not in detectors:
|
||||
raise ValueError(f"Detector '{detector_name}' not found in configuration.")
|
||||
|
||||
det = detectors[detector_name]
|
||||
results = self._ping_many(det.hostnames)
|
||||
|
||||
table = Table(title=f"Ping results for detector '{detector_name}'")
|
||||
table.add_column("Hostname", style="cyan", no_wrap=True)
|
||||
table.add_column("Status", style="magenta")
|
||||
|
||||
for host, alive in results.items():
|
||||
status = "[green]OK[/green]" if alive else "[red]DOWN[/red]"
|
||||
table.add_row(host, status)
|
||||
|
||||
console = Console()
|
||||
console.print(table)
|
||||
|
||||
def _ping_many(self, hosts: list[str], port=22, timeout=2, max_workers=None):
|
||||
max_workers = max_workers or len(hosts)
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
primed_ping = partial(self._ping, port=port, timeout=timeout)
|
||||
pings = executor.map(primed_ping, hosts)
|
||||
return dict(zip(hosts, pings))
|
||||
|
||||
def _ping(self, host: str, port=23, timeout=2): # telnet is port 23
|
||||
address = (host, port)
|
||||
try:
|
||||
with socket.create_connection(address, timeout):
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
def open_it_service_page(self):
|
||||
"""Open the overview of IT services hosted by Science IT Infrastructure and Services for cSAXS."""
|
||||
gui: BECGuiClient = bec.gui
|
||||
dock_area = gui.new()
|
||||
print("Opening IT service page in new dock...")
|
||||
url = "https://metrics.psi.ch/d/saf8mxv/x12sa?orgId=1&from=now-24h&to=now&timezone=browser&var-receiver_hosts=sls-jfjoch-001.psi.ch&var-writer_hosts=xbl-daq-34.psi.ch&var-beamline=X12SA&var-slurm_partitions=csaxs&var-receiver_services=broker&var-writer_services=writer&refresh=15m"
|
||||
# FIXME BEC WIDGETS v3
|
||||
dock = dock_area.new()
|
||||
wb = dock.new(widget=gui.available_widgets.WebsiteWidget)
|
||||
wb.set_url(url)
|
||||
@@ -1,162 +0,0 @@
|
||||
{
|
||||
"zeromq" : {
|
||||
"image_socket": ["tcp://0.0.0.0:5500"]
|
||||
},
|
||||
"zeromq_preview": {
|
||||
"socket_address": "tcp://0.0.0.0:5400",
|
||||
"enabled": true,
|
||||
"period_ms": 1000
|
||||
},
|
||||
"zeromq_metadata" : {
|
||||
"socket_address": "tcp://0.0.0.0:5600",
|
||||
"enabled": true,
|
||||
"period_ms": 100
|
||||
},
|
||||
"instrument" : {
|
||||
"source_name": "Swiss Light Source",
|
||||
"instrument_name": "cSAXS",
|
||||
"source_type": "Synchrotron X-ray Source"
|
||||
},
|
||||
"detector": [
|
||||
{
|
||||
"description": "EIGER 9M",
|
||||
"serial_number": "E1",
|
||||
"type": "EIGER",
|
||||
"mirror_y": true,
|
||||
"base_data_ipv4_address": "10.10.10.10",
|
||||
"calibration_file":["/opt/jfjoch/calibration/"],
|
||||
"standard_geometry" : {
|
||||
"nmodules": 18,
|
||||
"modules_in_row": 3,
|
||||
"gap_x": 8,
|
||||
"gap_y": 36
|
||||
},
|
||||
"hostname": [
|
||||
"beb101",
|
||||
"beb103",
|
||||
"beb014",
|
||||
"beb078",
|
||||
"beb060",
|
||||
"beb030",
|
||||
"beb092",
|
||||
"beb178",
|
||||
"beb009",
|
||||
"beb038",
|
||||
"beb056",
|
||||
"beb058",
|
||||
"beb033",
|
||||
"beb113",
|
||||
"beb005",
|
||||
"beb017",
|
||||
"beb119",
|
||||
"beb095",
|
||||
"beb186",
|
||||
"beb042",
|
||||
"beb106",
|
||||
"beb059",
|
||||
"beb111",
|
||||
"beb203",
|
||||
"beb100",
|
||||
"beb093",
|
||||
"beb123",
|
||||
"beb061",
|
||||
"beb121",
|
||||
"beb055",
|
||||
"beb004",
|
||||
"beb190",
|
||||
"beb054",
|
||||
"beb189",
|
||||
"beb107",
|
||||
"beb115"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "EIGER 8.5M (tmp)",
|
||||
"serial_number": "E1-tmp",
|
||||
"type": "EIGER",
|
||||
"mirror_y": true,
|
||||
"base_data_ipv4_address": "10.10.10.10",
|
||||
"calibration_file":["/opt/jfjoch/calibration/"],
|
||||
"standard_geometry" : {
|
||||
"nmodules": 17,
|
||||
"modules_in_row": 3,
|
||||
"gap_x": 8,
|
||||
"gap_y": 36
|
||||
},
|
||||
"hostname": [
|
||||
"beb101",
|
||||
"beb103",
|
||||
"beb014",
|
||||
"beb078",
|
||||
"beb060",
|
||||
"beb030",
|
||||
"beb092",
|
||||
"beb178",
|
||||
"beb009",
|
||||
"beb038",
|
||||
"beb056",
|
||||
"beb058",
|
||||
"beb033",
|
||||
"beb113",
|
||||
"beb005",
|
||||
"beb017",
|
||||
"beb119",
|
||||
"beb095",
|
||||
"beb186",
|
||||
"beb042",
|
||||
"beb106",
|
||||
"beb059",
|
||||
"beb100",
|
||||
"beb093",
|
||||
"beb123",
|
||||
"beb061",
|
||||
"beb121",
|
||||
"beb055",
|
||||
"beb004",
|
||||
"beb190",
|
||||
"beb054",
|
||||
"beb189",
|
||||
"beb107",
|
||||
"beb115"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "EIGER 1.5M",
|
||||
"serial_number": "E2",
|
||||
"type": "EIGER",
|
||||
"mirror_y": true,
|
||||
"base_data_ipv4_address": "10.10.11.10",
|
||||
"calibration_file":["/opt/jfjoch/calibration_e1p5m/"],
|
||||
"standard_geometry" : {
|
||||
"nmodules": 3,
|
||||
"modules_in_row": 1,
|
||||
"gap_x": 8,
|
||||
"gap_y": 36
|
||||
},
|
||||
"hostname": ["beb062", "beb026", "beb099", "beb084", "beb120", "beb108"]
|
||||
}
|
||||
],
|
||||
"frontend_directory": "/usr/share/jfjoch/frontend/",
|
||||
"image_pusher": "ZeroMQ",
|
||||
"numa_policy": "n2g2",
|
||||
"receiver_threads": 64,
|
||||
"image_buffer_MiB": 96000,
|
||||
"pcie": [
|
||||
{
|
||||
"blk": "/dev/jfjoch0",
|
||||
"ipv4": "10.10.10.1"
|
||||
},
|
||||
{
|
||||
"blk": "/dev/jfjoch1",
|
||||
"ipv4": "10.10.10.2"
|
||||
},
|
||||
{
|
||||
"blk": "/dev/jfjoch2",
|
||||
"ipv4": "10.10.10.3"
|
||||
},
|
||||
{
|
||||
"blk": "/dev/jfjoch3",
|
||||
"ipv4": "10.10.10.4"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -30,73 +30,23 @@ logger = bec_logger.logger
|
||||
|
||||
logger.info("Using the cSAXS startup script.")
|
||||
|
||||
|
||||
from csaxs_bec.bec_ipython_client.plugins.tool_box.debug_tools import DebugTools
|
||||
|
||||
debug = DebugTools()
|
||||
logger.success("Debug tools loaded. Use 'debug' to access them.")
|
||||
|
||||
# pylint: disable=import-error
|
||||
_args = _main_dict["args"]
|
||||
|
||||
_session_name = "cSAXS"
|
||||
|
||||
print("Loading cSAXS session")
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS.cSAXS import cSAXS
|
||||
csaxs = cSAXS(bec)
|
||||
logger.success("cSAXS session loaded.")
|
||||
|
||||
|
||||
if _args.session.lower() == "lamni":
|
||||
from csaxs_bec.bec_ipython_client.plugins.LamNI import LamNI
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import *
|
||||
from csaxs_bec.bec_ipython_client.plugins.LamNI import *
|
||||
|
||||
_session_name = "LamNI"
|
||||
lamni = LamNI(bec)
|
||||
logger.success("LamNI session loaded.")
|
||||
print(r"""
|
||||
██████╗ ███████╗ ██████╗ ██╗ █████╗ ███╗ ███╗███╗ ██╗██╗
|
||||
██╔══██╗██╔════╝██╔════╝ ██║ ██╔══██╗████╗ ████║████╗ ██║██║
|
||||
██████╔╝█████╗ ██║ ██║ ███████║██╔████╔██║██╔██╗ ██║██║
|
||||
██╔══██╗██╔══╝ ██║ ██║ ██╔══██║██║╚██╔╝██║██║╚██╗██║██║
|
||||
██████╔╝███████╗╚██████╗ ███████╗██║ ██║██║ ╚═╝ ██║██║ ╚████║██║
|
||||
╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝
|
||||
|
||||
B E C L a m N I
|
||||
""")
|
||||
|
||||
elif _args.session.lower() == "omny":
|
||||
from csaxs_bec.bec_ipython_client.plugins.flomni import OMNY
|
||||
elif _args.session.lower() == "csaxs":
|
||||
print("Loading cSAXS session")
|
||||
from csaxs_bec.bec_ipython_client.plugins.cSAXS import *
|
||||
|
||||
_session_name = "OMNY"
|
||||
omny = OMNY(bec)
|
||||
logger.success("OMNY session loaded.")
|
||||
print(r"""
|
||||
██████╗ ███████╗ ██████╗ ██████╗ ███╗ ███╗███╗ ██╗██╗ ██╗
|
||||
██╔══██╗██╔════╝██╔════╝ ██╔═══██╗████╗ ████║████╗ ██║╚██╗ ██╔╝
|
||||
██████╔╝█████╗ ██║ ██║ ██║██╔████╔██║██╔██╗ ██║ ╚████╔╝
|
||||
██╔══██╗██╔══╝ ██║ ██║ ██║██║╚██╔╝██║██║╚██╗██║ ╚██╔╝
|
||||
██████╔╝███████╗╚██████╗ ╚██████╔╝██║ ╚═╝ ██║██║ ╚████║ ██║
|
||||
╚═════╝ ╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═╝
|
||||
|
||||
B E C O M N Y
|
||||
""")
|
||||
|
||||
elif _args.session.lower() == "flomni":
|
||||
from csaxs_bec.bec_ipython_client.plugins.flomni import Flomni
|
||||
|
||||
_session_name = "flomni"
|
||||
flomni = Flomni(bec)
|
||||
logger.success("flomni session loaded.")
|
||||
print(r"""
|
||||
██████╗ ███████╗ ██████╗ ███████╗██╗ ██████╗ ███╗ ███╗███╗ ██╗██╗
|
||||
██╔══██╗██╔════╝██╔════╝ ██╔════╝██║ ██╔═══██╗████╗ ████║████╗ ██║██║
|
||||
██████╔╝█████╗ ██║ █████╗ ██║ ██║ ██║██╔████╔██║██╔██╗ ██║██║
|
||||
██╔══██╗██╔══╝ ██║ ██╔══╝ ██║ ██║ ██║██║╚██╔╝██║██║╚██╗██║██║
|
||||
██████╔╝███████╗╚██████╗ ██║ ███████╗╚██████╔╝██║ ╚═╝ ██║██║ ╚████║██║
|
||||
╚═════╝ ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝
|
||||
|
||||
B E C f l O M N I
|
||||
""")
|
||||
logger.success("cSAXS session loaded.")
|
||||
|
||||
|
||||
# SETUP BEAMLINE INFO
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
eiger_1_5:
|
||||
description: Eiger 1.5M in-vacuum detector
|
||||
deviceClass: csaxs_bec.devices.jungfraujoch.eiger_1_5m.Eiger1_5M
|
||||
deviceConfig:
|
||||
detector_distance: 100
|
||||
beam_center: [0, 0]
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: async
|
||||
softwareTrigger: False
|
||||
|
||||
# eiger_9:
|
||||
# description: Eiger 9M detector
|
||||
# deviceClass: csaxs_bec.devices.jungfraujoch.eiger_9m.Eiger9M
|
||||
# deviceConfig:
|
||||
# detector_distance: 100
|
||||
# beam_center: [0, 0]
|
||||
# onFailure: raise
|
||||
# enabled: true
|
||||
# readoutPriority: async
|
||||
# softwareTrigger: False
|
||||
|
||||
# ids_cam:
|
||||
# description: IDS camera for live image acquisition
|
||||
# deviceClass: csaxs_bec.devices.ids_cameras.IDSCamera
|
||||
# deviceConfig:
|
||||
# camera_id: 201
|
||||
# bits_per_pixel: 24
|
||||
# m_n_colormode: 1
|
||||
# live_mode: True
|
||||
# onFailure: raise
|
||||
# enabled: true
|
||||
# readoutPriority: async
|
||||
# softwareTrigger: True
|
||||
|
||||
@@ -1,591 +0,0 @@
|
||||
##########################################################################
|
||||
###################### Delay generators ##################################
|
||||
##########################################################################
|
||||
ddg1:
|
||||
description: Main delay Generator for triggering
|
||||
deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DDG1
|
||||
enabled: true
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-CPCL-DDG1:'
|
||||
onFailure: raise
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: true
|
||||
|
||||
ddg2:
|
||||
description: Detector delay Generator for trigger burst
|
||||
deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DDG2
|
||||
enabled: true
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-CPCL-DDG2:'
|
||||
onFailure: raise
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
|
||||
##########################################################################
|
||||
###################### Multichannel Scaler################################
|
||||
##########################################################################
|
||||
|
||||
mcs:
|
||||
description: Mcs scalar card for transmission readout
|
||||
deviceClass: csaxs_bec.devices.epics.mcs_card.mcs_card_csaxs.MCSCardCSAXS
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-MCS:'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: monitored
|
||||
softwareTrigger: false
|
||||
|
||||
|
||||
|
||||
##########################################################################
|
||||
########################### FAST SHUTTER #################################
|
||||
##########################################################################
|
||||
|
||||
fsh:
|
||||
description: Fast shutter manual control and readback
|
||||
deviceClass: csaxs_bec.devices.epics.fast_shutter.cSAXSFastEpicsShutter
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-ES1-TTL:'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: monitored
|
||||
|
||||
##########################################################################
|
||||
######################## SMARACT STAGES ##################################
|
||||
##########################################################################
|
||||
|
||||
|
||||
################## XBOX 1 ES #####################
|
||||
xbpm3x:
|
||||
description: X-ray beam position x monitor 1 in ESbox1
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: x12sa-eb-smaract-mcs-04.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: -22.5
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 0
|
||||
|
||||
xbpm3y:
|
||||
description: X-ray beam position y monitor 1 in ESbox1
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: x12sa-eb-smaract-mcs-04.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: 1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: -2
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 1
|
||||
|
||||
sl3trxi:
|
||||
description: ESbox1 slit 3 inner blade movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: x12sa-eb-smaract-mcs-04.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: -5.5
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 2
|
||||
|
||||
sl3trxo:
|
||||
description: ESbox1 slit 3 outer blade movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: D
|
||||
host: x12sa-eb-smaract-mcs-04.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: 6
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 3
|
||||
|
||||
sl3trxb:
|
||||
description: ESbox1 slit 3 bottom blade movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: E
|
||||
host: x12sa-eb-smaract-mcs-04.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: 1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: -5.8
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 4
|
||||
|
||||
sl3trxt:
|
||||
description: ESbox1 slit 3 top blade movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: F
|
||||
host: x12sa-eb-smaract-mcs-04.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: 1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: 5.5
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 5
|
||||
|
||||
fast_shutter_n1_x:
|
||||
description: ESbox1 New fast shutter 1 x movment
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: H
|
||||
host: x12sa-eb-smaract-mcs-01.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: -7
|
||||
in: 0
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 7
|
||||
|
||||
fast_shutter_o1_x:
|
||||
description: ESbox1 Old fast shutter 1 x movment
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: G
|
||||
host: x12sa-eb-smaract-mcs-01.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: -15.8
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 6
|
||||
|
||||
fast_shutter_o2_x:
|
||||
description: ESbox1 Old fast shutter 2 x movment
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: F
|
||||
host: x12sa-eb-smaract-mcs-01.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: -15.5
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 5
|
||||
|
||||
filter_array_1_x:
|
||||
description: ESbox1 Filter Array 1 x movment
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: x12sa-eb-smaract-mcs-01.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: 25
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 1
|
||||
|
||||
filter_array_2_x:
|
||||
description: ESbox1 Filter Array 2 x movment
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: x12sa-eb-smaract-mcs-01.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: 25.5
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 2
|
||||
|
||||
filter_array_3_x:
|
||||
description: ESbox1 Filter Array 3 x movment
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: D
|
||||
host: x12sa-eb-smaract-mcs-01.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: 25.8
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 3
|
||||
|
||||
filter_array_4_x:
|
||||
description: ESbox1 Filter Array 4 x movment
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: E
|
||||
host: x12sa-eb-smaract-mcs-01.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: 25
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 4
|
||||
|
||||
sl4trxi:
|
||||
description: ESbox1 slit 4 inner blade movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: G
|
||||
host: x12sa-eb-smaract-mcs-04.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: -5.5
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 6
|
||||
|
||||
sl4trxo:
|
||||
description: ESbox1 slit 4 outer blade movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: H
|
||||
host: x12sa-eb-smaract-mcs-04.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: 6
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 7
|
||||
|
||||
sl4trxb:
|
||||
description: ESbox1 slit 4 bottom blade movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: I
|
||||
host: x12sa-eb-smaract-mcs-04.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: 1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: -5.8
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 8
|
||||
|
||||
sl4trxt:
|
||||
description: ESbox1 slit 4 top blade movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: x12sa-eb-smaract-mcs-01.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: 1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: 5.5
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 0
|
||||
|
||||
################## XBOX 2 ES #####################
|
||||
|
||||
sl5trxi:
|
||||
description: ESbox2 slit 5 inner blade movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: C
|
||||
host: x12sa-eb-smaract-mcs-05.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: -6
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 2
|
||||
|
||||
sl5trxo:
|
||||
description: ESbox2 slit 5 outer blade movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: D
|
||||
host: x12sa-eb-smaract-mcs-05.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: 5.5
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 3
|
||||
|
||||
sl5trxb:
|
||||
description: ESbox2 slit 5 bottom blade movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: E
|
||||
host: x12sa-eb-smaract-mcs-05.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: -5.5
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 4
|
||||
|
||||
sl5trxt:
|
||||
description: ESbox1 slit 5 top blade movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: F
|
||||
host: x12sa-eb-smaract-mcs-05.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: 6
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 5
|
||||
|
||||
xbimtrx:
|
||||
description: ESbox2 beam intensity monitor x movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: x12sa-eb-smaract-mcs-05.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: -14.7
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 0
|
||||
|
||||
xbimtry:
|
||||
description: ESbox2 beam intensity monitor y movement
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: x12sa-eb-smaract-mcs-05.psi.ch
|
||||
limits:
|
||||
- -200
|
||||
- 200
|
||||
port: 5000
|
||||
sign: -1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
init_position: 0
|
||||
# bl_smar_stage to use csaxs reference method. assign number according to axis channel
|
||||
bl_smar_stage: 1
|
||||
@@ -1,25 +0,0 @@
|
||||
############################################################
|
||||
##################### EPS ##################################
|
||||
############################################################
|
||||
x12saEPS:
|
||||
description: X12SA EPS info and control
|
||||
deviceClass: csaxs_bec.devices.epics.eps.EPS
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
############################################################
|
||||
##################### GalilRIO #############################
|
||||
############################################################
|
||||
|
||||
galilrioesxbox:
|
||||
description: Galil RIO for remote gain switching and slow reading ES XBox
|
||||
deviceClass: csaxs_bec.devices.omny.galil.galil_rio.GalilRIO
|
||||
deviceConfig:
|
||||
host: galilrioesxbox.psi.ch
|
||||
enabled: true
|
||||
onFailure: raise
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
55
csaxs_bec/device_configs/endstation.yaml
Normal file
55
csaxs_bec/device_configs/endstation.yaml
Normal file
@@ -0,0 +1,55 @@
|
||||
ddg1:
|
||||
description: Main delay Generator for triggering
|
||||
deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DDG1
|
||||
enabled: true
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-CPCL-DDG1:'
|
||||
onFailure: raise
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: true
|
||||
|
||||
ddg2:
|
||||
description: Detector delay Generator for trigger burst
|
||||
deviceClass: csaxs_bec.devices.epics.delay_generator_csaxs.DDG2
|
||||
enabled: true
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-CPCL-DDG2:'
|
||||
onFailure: raise
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
|
||||
mcs:
|
||||
description: Mcs scalar card for transmission readout
|
||||
deviceClass: csaxs_bec.devices.epics.mcs_card.mcs_card_csaxs.MCSCardCSAXS
|
||||
deviceConfig:
|
||||
prefix: 'X12SA-MCS:'
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: monitored
|
||||
softwareTrigger: false
|
||||
|
||||
ids_cam:
|
||||
description: IDS camera for live image acquisition
|
||||
deviceClass: csaxs_bec.devices.ids_cameras.IDSCamera
|
||||
deviceConfig:
|
||||
camera_id: 201
|
||||
bits_per_pixel: 24
|
||||
m_n_colormode: 1
|
||||
live_mode: True
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: async
|
||||
softwareTrigger: True
|
||||
|
||||
eiger_1_5:
|
||||
description: Eiger 1.5M in-vacuum detector
|
||||
deviceClass: csaxs_bec.devices.jungfraujoch.eiger_1_5m.Eiger1_5M
|
||||
deviceConfig:
|
||||
detector_distance: 100
|
||||
beam_center: [0, 0]
|
||||
onFailure: raise
|
||||
enabled: true
|
||||
readoutPriority: async
|
||||
softwareTrigger: False
|
||||
8
csaxs_bec/device_configs/first_light.yaml
Normal file
8
csaxs_bec/device_configs/first_light.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
optics:
|
||||
- !include ./optics_hutch.yaml
|
||||
|
||||
frontend:
|
||||
- !include ./frontend.yaml
|
||||
|
||||
endstation:
|
||||
- !include ./endstation.yaml
|
||||
@@ -17,7 +17,6 @@ feyex:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: -16.267
|
||||
out: -1
|
||||
@@ -36,7 +35,6 @@ feyey:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: -10.467
|
||||
fheater:
|
||||
@@ -54,7 +52,6 @@ fheater:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
foptx:
|
||||
description: Optics X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
@@ -70,7 +67,6 @@ foptx:
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: -13.761
|
||||
fopty:
|
||||
@@ -88,7 +84,6 @@ fopty:
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 0.552
|
||||
out: 0.752
|
||||
@@ -107,7 +102,6 @@ foptz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 23
|
||||
fsamroy:
|
||||
@@ -125,7 +119,6 @@ fsamroy:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
fsamx:
|
||||
description: Sample coarse X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
@@ -141,7 +134,6 @@ fsamx:
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: -1.1
|
||||
fsamy:
|
||||
@@ -159,7 +151,6 @@ fsamy:
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 2.75
|
||||
ftracky:
|
||||
@@ -177,7 +168,6 @@ ftracky:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
ftrackz:
|
||||
description: Laser Tracker coarse Z
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
@@ -193,7 +183,6 @@ ftrackz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
ftransx:
|
||||
description: Sample transer X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
@@ -209,7 +198,6 @@ ftransx:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
ftransy:
|
||||
description: Sample transer Y
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
@@ -225,7 +213,6 @@ ftransy:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
sensor_voltage: -2.4
|
||||
ftransz:
|
||||
@@ -243,7 +230,6 @@ ftransz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
ftray:
|
||||
description: Sample transfer tray
|
||||
deviceClass: csaxs_bec.devices.omny.galil.fgalil_ophyd.FlomniGalilMotor
|
||||
@@ -259,7 +245,6 @@ ftray:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
|
||||
|
||||
############################################################
|
||||
@@ -294,7 +279,6 @@ fosax:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 9.124
|
||||
out: 5.3
|
||||
@@ -313,7 +297,6 @@ fosay:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 0.367
|
||||
fosaz:
|
||||
@@ -331,7 +314,6 @@ fosaz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 8.5
|
||||
out: 6
|
||||
@@ -352,7 +334,6 @@ rtx:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
low_signal: 10000
|
||||
min_signal: 9000
|
||||
@@ -369,7 +350,6 @@ rty:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
tomo_additional_offsety: 0
|
||||
rtz:
|
||||
@@ -384,7 +364,6 @@ rtz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
connectionTimeout: 20
|
||||
|
||||
############################################################
|
||||
####################### Cameras ############################
|
||||
@@ -429,20 +408,20 @@ cam_xeye:
|
||||
readOnly: false
|
||||
readoutPriority: async
|
||||
|
||||
# cam_ids_rgb:
|
||||
# description: Camera flOMNI Xray eye ID203
|
||||
# deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera
|
||||
# deviceConfig:
|
||||
# camera_id: 203
|
||||
# bits_per_pixel: 24
|
||||
# num_rotation_90: 3
|
||||
# transpose: false
|
||||
# force_monochrome: true
|
||||
# m_n_colormode: 1
|
||||
# enabled: true
|
||||
# onFailure: buffer
|
||||
# readOnly: false
|
||||
# readoutPriority: async
|
||||
cam_ids_rgb:
|
||||
description: Camera flOMNI Xray eye ID203
|
||||
deviceClass: csaxs_bec.devices.ids_cameras.ids_camera.IDSCamera
|
||||
deviceConfig:
|
||||
camera_id: 203
|
||||
bits_per_pixel: 24
|
||||
num_rotation_90: 3
|
||||
transpose: false
|
||||
force_monochrome: true
|
||||
m_n_colormode: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: async
|
||||
|
||||
|
||||
# ############################################################
|
||||
@@ -460,8 +439,8 @@ flomni_temphum:
|
||||
# ########## OMNY / flOMNI / LamNI fast shutter ##############
|
||||
# ############################################################
|
||||
omnyfsh:
|
||||
description: omnyfsh connects to fast shutter at X12 if device fsh exists
|
||||
deviceClass: csaxs_bec.devices.omny.shutter.OMNYFastShutter
|
||||
description: omnyfsh connects to read fast shutter at X12 if in that network
|
||||
deviceClass: csaxs_bec.devices.omny.shutter.OMNYFastEpicsShutter
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
@@ -471,7 +450,7 @@ omnyfsh:
|
||||
#################### GUI Signals ###########################
|
||||
############################################################
|
||||
omny_xray_gui:
|
||||
description: Gui signals
|
||||
description: Gui Epics signals
|
||||
deviceClass: csaxs_bec.devices.omny.xray_epics_gui.OMNYXRayEpicsGUI
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
@@ -486,25 +465,4 @@ calculated_signal:
|
||||
compute_method: "def just_rand():\n return 42"
|
||||
enabled: true
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
|
||||
############################################################
|
||||
#################### OMNY Pandabox #########################
|
||||
############################################################
|
||||
omny_panda:
|
||||
readoutPriority: async
|
||||
deviceClass: csaxs_bec.devices.panda_box.panda_box_omny.PandaBoxOMNY
|
||||
deviceConfig:
|
||||
host: omny-panda.psi.ch
|
||||
signal_alias:
|
||||
FMC_IN.VAL1.Min: cap_voltage_fzp_y_min
|
||||
FMC_IN.VAL1.Max: cap_voltage_fzp_y_max
|
||||
FMC_IN.VAL1.Mean: cap_voltage_fzp_y_mean
|
||||
FMC_IN.VAL2.Min: cap_voltage_fzp_x_min
|
||||
FMC_IN.VAL2.Max: cap_voltage_fzp_x_max
|
||||
FMC_IN.VAL2.Mean: cap_voltage_fzp_x_mean
|
||||
deviceTags:
|
||||
- detector
|
||||
enabled: true
|
||||
readOnly: false
|
||||
softwareTrigger: false
|
||||
readoutPriority: baseline
|
||||
@@ -19,7 +19,6 @@ leyex:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 14.117
|
||||
leyey:
|
||||
@@ -39,7 +38,6 @@ leyey:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 48.069
|
||||
out: 0.5
|
||||
@@ -60,7 +58,6 @@ loptx:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: -0.244
|
||||
out: -0.699
|
||||
@@ -81,7 +78,6 @@ lopty:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 3.724
|
||||
out: 3.53
|
||||
@@ -102,7 +98,6 @@ loptz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
lsamrot:
|
||||
description: Sample rotation
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
@@ -120,7 +115,6 @@ lsamrot:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
lsamx:
|
||||
description: Sample coarse X
|
||||
deviceClass: csaxs_bec.devices.omny.galil.lgalil_ophyd.LamniGalilMotor
|
||||
@@ -138,7 +132,6 @@ lsamx:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
center: 8.768
|
||||
lsamy:
|
||||
@@ -158,7 +151,6 @@ lsamy:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
center: 10.041
|
||||
|
||||
@@ -184,7 +176,6 @@ losax:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: -1.442
|
||||
losay:
|
||||
@@ -204,7 +195,6 @@ losay:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: -0.171
|
||||
out: 3.8
|
||||
@@ -225,7 +215,6 @@ losaz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: -1
|
||||
out: -3
|
||||
@@ -249,7 +238,6 @@ rtx:
|
||||
deviceTags:
|
||||
- lamni
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
enabled: true
|
||||
readOnly: False
|
||||
rty:
|
||||
@@ -267,7 +255,6 @@ rty:
|
||||
deviceTags:
|
||||
- lamni
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
enabled: true
|
||||
readOnly: False
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
# This is the main configuration file that is
|
||||
# commented or uncommented according to the type of experiment
|
||||
|
||||
# optics:
|
||||
# - !include ./bl_optics_hutch.yaml
|
||||
|
||||
# frontend:
|
||||
# - !include ./bl_frontend.yaml
|
||||
|
||||
endstation:
|
||||
- !include ./bl_endstation.yaml
|
||||
|
||||
detectors:
|
||||
- !include ./bl_detectors.yaml
|
||||
|
||||
#sastt:
|
||||
# - !include ./sastt.yaml
|
||||
|
||||
flomni:
|
||||
- !include ./ptycho_flomni.yaml
|
||||
|
||||
#omny:
|
||||
# - !include ./ptycho_omny.yaml
|
||||
|
||||
#lamni:
|
||||
# - !include ./ptycho_lamni.yaml
|
||||
|
||||
#user setup:
|
||||
# - !include ./user_setup.yaml
|
||||
38
csaxs_bec/device_configs/npoint_template.yaml
Normal file
38
csaxs_bec/device_configs/npoint_template.yaml
Normal file
@@ -0,0 +1,38 @@
|
||||
############################################################
|
||||
#################### npoint motors #########################
|
||||
############################################################
|
||||
|
||||
npx:
|
||||
description: nPoint x axis on the big npoint controller
|
||||
deviceClass: csaxs_bec.devices.npoint.npoint.NPointAxis
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: "nPoint000003.psi.ch"
|
||||
limits:
|
||||
- -50
|
||||
- 50
|
||||
port: 23
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
deviceTags:
|
||||
- npoint
|
||||
npy:
|
||||
description: nPoint y axis on the big npoint controller
|
||||
deviceClass: csaxs_bec.devices.npoint.npoint.NPointAxis
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: "nPoint000003.psi.ch"
|
||||
limits:
|
||||
- -50
|
||||
- 50
|
||||
port: 23
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
deviceTags:
|
||||
- npoint
|
||||
@@ -69,7 +69,6 @@ rtx:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
low_signal: 8500
|
||||
min_signal: 8000
|
||||
@@ -85,7 +84,6 @@ rty:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
tomo_additional_offsety: 0
|
||||
rtz:
|
||||
@@ -100,7 +98,6 @@ rtz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: on_request
|
||||
connectionTimeout: 20
|
||||
|
||||
# ############################################################
|
||||
# ##################### OMNY samples #########################
|
||||
@@ -168,7 +165,6 @@ ofzpx:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: -0.4317
|
||||
ofzpy:
|
||||
@@ -188,7 +184,6 @@ ofzpy:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 0.7944
|
||||
out: 0.6377
|
||||
@@ -209,7 +204,6 @@ ofzpz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 0
|
||||
otransx:
|
||||
@@ -229,7 +223,6 @@ otransx:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 0
|
||||
otransy:
|
||||
@@ -249,7 +242,6 @@ otransy:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
up_position: -1.2
|
||||
gripper_sensorvoltagetarget: -2.30
|
||||
@@ -270,7 +262,6 @@ otransz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 0
|
||||
osamx:
|
||||
@@ -290,7 +281,6 @@ osamx:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: -0.1
|
||||
osamz:
|
||||
@@ -310,7 +300,6 @@ osamz:
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 0
|
||||
oosay:
|
||||
@@ -330,7 +319,6 @@ oosay:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
near_field_in: 0.531
|
||||
far_field_in: 0.4122
|
||||
@@ -351,7 +339,6 @@ oosax:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
near_field_in: 3.2044
|
||||
far_field_in: 3.022
|
||||
@@ -372,7 +359,6 @@ oosaz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
near_field_in: -0.4452
|
||||
far_field_in: 6.5
|
||||
@@ -393,7 +379,6 @@ oparkz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 0
|
||||
oshuttleopen:
|
||||
@@ -413,7 +398,6 @@ oshuttleopen:
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 0
|
||||
oshuttlealign:
|
||||
@@ -433,7 +417,6 @@ oshuttlealign:
|
||||
onFailure: buffer
|
||||
readOnly: true
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 0
|
||||
osamy:
|
||||
@@ -453,7 +436,6 @@ osamy:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 0
|
||||
otracky:
|
||||
@@ -473,7 +455,6 @@ otracky:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
start_pos: -4.3431
|
||||
osamroy:
|
||||
@@ -493,7 +474,6 @@ osamroy:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
in: 0
|
||||
otrackz:
|
||||
@@ -513,7 +493,6 @@ otrackz:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
start_pos: -0.6948
|
||||
oeyex:
|
||||
@@ -533,7 +512,6 @@ oeyex:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
xray_in: -45.7394
|
||||
oeyez:
|
||||
@@ -553,7 +531,6 @@ oeyez:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
xray_in: -2
|
||||
oeyey:
|
||||
@@ -573,7 +550,6 @@ oeyey:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
xray_in: 0.0229
|
||||
|
||||
@@ -596,7 +572,6 @@ ocsx:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
nothing: 0
|
||||
ocsy:
|
||||
@@ -614,7 +589,6 @@ ocsy:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
nothing: 0
|
||||
oshield:
|
||||
@@ -632,6 +606,5 @@ oshield:
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
userParameter:
|
||||
nothing: 0
|
||||
@@ -68,12 +68,6 @@ ccmx:
|
||||
- cSAXS
|
||||
- optics
|
||||
|
||||
|
||||
|
||||
##########################################################################
|
||||
######################## SMARACT STAGES ##################################
|
||||
##########################################################################
|
||||
|
||||
xbpm2x:
|
||||
description: X-ray beam position monitor 1 in OPbox
|
||||
deviceClass: csaxs_bec.devices.smaract.smaract_ophyd.SmaractMotor
|
||||
@@ -85,14 +79,12 @@ xbpm2x:
|
||||
- 200
|
||||
port: 5000
|
||||
sign: 1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
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: 0
|
||||
|
||||
xbpm2y:
|
||||
description: X-ray beam position monitor 1 in OPbox
|
||||
@@ -105,14 +97,12 @@ xbpm2y:
|
||||
- 200
|
||||
port: 5000
|
||||
sign: 1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
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: 1
|
||||
|
||||
cu_foilx:
|
||||
description: Cu foil in OPbox
|
||||
@@ -125,14 +115,12 @@ cu_foilx:
|
||||
- 200
|
||||
port: 5000
|
||||
sign: 1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
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
|
||||
@@ -145,15 +133,12 @@ scinx:
|
||||
- 200
|
||||
port: 5000
|
||||
sign: 1
|
||||
# precision: 3
|
||||
# tolerance: 0.005
|
||||
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
|
||||
# description: 'This is an example of a read-only Epics signal'
|
||||
@@ -94,4 +94,3 @@ micfoc:
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
############################################################
|
||||
|
||||
|
||||
|
||||
############################################################
|
||||
##################### EPS ##################################
|
||||
############################################################
|
||||
x12saEPS:
|
||||
description: X12SA EPS info and control
|
||||
deviceClass: csaxs_bec.devices.epics.eps.EPS
|
||||
deviceConfig: {}
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
@@ -1,87 +0,0 @@
|
||||
############################################################
|
||||
#################### OWIS LTM80 ############################
|
||||
############################################################
|
||||
samx:
|
||||
description: Owis motor stage samx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES02
|
||||
motor_resolution: 0.00125
|
||||
base_velocity: 0.0625
|
||||
velocity: 10
|
||||
backlash_distance: 0.125
|
||||
acceleration: 0.2
|
||||
user_offset_dir: 0
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- owis_samx
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
############################################################
|
||||
#################### OWIS Rotation DMT65 ###################
|
||||
############################################################
|
||||
rotx:
|
||||
description: Rotation stage rotx
|
||||
deviceClass: ophyd_devices.devices.psi_motor.EpicsUserMotorVME
|
||||
deviceConfig:
|
||||
prefix: X12SA-ES2-ES03
|
||||
motor_resolution: 0.0025
|
||||
base_velocity: 0.5
|
||||
velocity: 7.5
|
||||
backlash_distance: 0.25
|
||||
acceleration: 0.2
|
||||
user_offset_dir: 1
|
||||
limits:
|
||||
- -0.1
|
||||
- 0.1
|
||||
deviceTags:
|
||||
- cSAXS
|
||||
- rotx
|
||||
onFailure: buffer
|
||||
enabled: true
|
||||
readoutPriority: baseline
|
||||
softwareTrigger: false
|
||||
|
||||
|
||||
############################################################
|
||||
#################### npoint motors #########################
|
||||
############################################################
|
||||
|
||||
npx:
|
||||
description: nPoint x axis on the big npoint controller
|
||||
deviceClass: csaxs_bec.devices.npoint.npoint.NPointAxis
|
||||
deviceConfig:
|
||||
axis_Id: A
|
||||
host: "nPoint000003.psi.ch"
|
||||
limits:
|
||||
- -50
|
||||
- 50
|
||||
port: 23
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
deviceTags:
|
||||
- npoint
|
||||
npy:
|
||||
description: nPoint y axis on the big npoint controller
|
||||
deviceClass: csaxs_bec.devices.npoint.npoint.NPointAxis
|
||||
deviceConfig:
|
||||
axis_Id: B
|
||||
host: "nPoint000003.psi.ch"
|
||||
limits:
|
||||
- -50
|
||||
- 50
|
||||
port: 23
|
||||
sign: 1
|
||||
enabled: true
|
||||
onFailure: buffer
|
||||
readOnly: false
|
||||
readoutPriority: baseline
|
||||
connectionTimeout: 20
|
||||
deviceTags:
|
||||
- npoint
|
||||
@@ -1,58 +0,0 @@
|
||||
# Delay Generator implementation at the CSAXS beamline
|
||||
|
||||
This module provides an ophyd device implementation for the Stanford Research Systems Delay Generator DDG645, used at the cSAXS beamline as a master timing source for detector triggering and other beamline devices. Detailed information about the DDG manual can be found here:
|
||||
https://www.thinksrs.com/downloads/pdfs/manuals/DG645m.pdf.
|
||||
The implementation is based on a community EPICS driver (https://github.com/epics-modules/delaygen?tab=readme-ov-file).
|
||||
|
||||
**EPICS Interface**
|
||||
|
||||
At the cSAXS beamline, the DDG panel is avaiable via caqtdm on the beamline consoles.
|
||||
|
||||
``` bash
|
||||
caqtdm -noMsg -attach -macro P=X12SA-CPCL-DDG,R=1: srsDG645.ui
|
||||
```
|
||||
with R=1,2,3,4,5 for 5 different DDG units installed at CSAXS.
|
||||
|
||||
# Ophyd Device integration at cSAXS
|
||||
For cSAXS, a custom ophyd device class implementation of the DDG is provided [here](./delay_generator_csaxs.py). This class provides a basic interface to the DDG PVs. The interface provides channels 'A', B', 'C', ... with setpoint, readback and references, as well as high level parameters such as *width* and *delay*. Please check the source code of the class for more details of the implementation.
|
||||
|
||||
In addition, the class provides a set of utility methods to configure sets of channel pairs 'AB', 'CD', ... as commonly needed in operation at the beamline. At the cSAXS beamline, a single DDG device is used as a master timing source for other devices. The general scheme is described in a [PDF document here](./trigger_scheme_ddg1_ddg2.pdf). Below is a description of the configuration of the two DDG units used at cSAXS for detector triggering and beamline shutter control.
|
||||
|
||||
## Master card: DDG1 (X12SA-CPCL-DDG1)
|
||||
The master [delay generator DDG1](./ddg_1.py) is configured to provide the following signals:
|
||||
|
||||
**Connection Scheme**:
|
||||
- EXT/EN: May be connected to external devices, e.g. SGalil motion controller for fly scans.
|
||||
- Operation Mode: Burst mode, but with single burst (burst count = 1). This is for practical reasons as it allows
|
||||
to interrupt and ongoing sequence if needed.
|
||||
- Software Trigger: Controlled through BEC.
|
||||
- State Control: BEC checks the *state* of this DDG to wait for the completion of a timing sequence.
|
||||
|
||||
**Delay Pairs**:
|
||||
- DelayPair 'AB': Provides the external enable (EXT/EN) signal to the second DDG (R=2).
|
||||
- DelayPair 'CD': Controls the beamline shutter.
|
||||
- DelayPair 'EF': Generates pulses for the MCS card, combined with the detector pulse train via an OR gate. This ensures the MCS card receives an additional pulse required for proper operation.
|
||||
|
||||
**Delay Channels**:
|
||||
- a = t0 + 2ms (2ms delay to allow the shutter to open)
|
||||
- b = a + 1us (short pulse)
|
||||
- c = t0
|
||||
- d = a + exp_time * burst_count
|
||||
- e = d
|
||||
- f = e + 1us (short pulse to OR gate for MCS triggering)
|
||||
|
||||
## Detector card: DDG2 (X12SA-CPCL-DDG2)
|
||||
The second [delay generator DDG2](./ddg_2.py) is configured to provide the following signals:
|
||||
|
||||
**Connection Scheme**:
|
||||
- EXT/EN: Connected to the DelayPair AB of the master DDG (R=1).
|
||||
- Operation Mode: Burst mode: The *burst count* is set to the number of frames per trigger. The *burst delay* is set to 0, and the *burst period* is set to the exposure time.
|
||||
- Software Trigger: Irrelevant, as the device is externally triggered by DDG1.
|
||||
|
||||
**Delay Pairs**:
|
||||
- DelayPair 'AB': Provides the trigger signal to the detector.
|
||||
|
||||
**Delay Channels**:
|
||||
- a = t0
|
||||
- b = a + (exp_time - READOUT_TIMES)
|
||||
|
||||
@@ -24,7 +24,7 @@ DELAY CHANNELS:
|
||||
- a = t0 + 2ms (2ms delay to allow the shutter to open)
|
||||
- b = a + 1us (short pulse)
|
||||
- c = t0
|
||||
- d = a + exp_time * burst_count
|
||||
- d = a + exp_time * burst_count + 1ms (to allow the shutter to close)
|
||||
- e = d
|
||||
- f = e + 1us (short pulse to OR gate for MCS triggering)
|
||||
"""
|
||||
@@ -37,9 +37,7 @@ import traceback
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import EpicsSignalRO, Kind
|
||||
from ophyd_devices import CompareStatus, DeviceStatus, StatusBase, TransitionStatus
|
||||
from ophyd_devices import CompareStatus, DeviceStatus, TransitionStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import (
|
||||
@@ -54,7 +52,7 @@ from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import
|
||||
LiteralChannels,
|
||||
StatusBitsCompareStatus,
|
||||
)
|
||||
from csaxs_bec.devices.epics.mcs_card.mcs_card_csaxs import ACQUIRING
|
||||
from csaxs_bec.devices.epics.mcs_card.mcs_card_csaxs import ACQUIRING, READYTOREAD
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import DeviceManagerBase, ScanInfo
|
||||
@@ -63,22 +61,13 @@ if TYPE_CHECKING: # pragma: no cover
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
########################
|
||||
## DEFAULT SETTINGS ####
|
||||
########################
|
||||
|
||||
# NOTE Default channel configuration for all channels of the DDG1 delay generator
|
||||
# This can be adapted as needed, or fine-tuned per channel. On every reload of the
|
||||
# device configuration in BEC, these values will be set into the DDG1 device.
|
||||
_DEFAULT_CHANNEL_CONFIG: ChannelConfig = {
|
||||
"amplitude": 4.5,
|
||||
"amplitude": 5.0,
|
||||
"offset": 0.0,
|
||||
"polarity": OUTPUTPOLARITY.POSITIVE,
|
||||
"mode": "ttl",
|
||||
}
|
||||
|
||||
# NOTE Here you can adapt the default IO configuration for all channels of the DDG1
|
||||
# Currently, all channels are set to the same default configuration `_DEFAULT_CHANNEL_CONFIG`.
|
||||
DEFAULT_IO_CONFIG: dict[AllChannelNames, ChannelConfig] = {
|
||||
"t0": _DEFAULT_CHANNEL_CONFIG,
|
||||
"ab": _DEFAULT_CHANNEL_CONFIG,
|
||||
@@ -86,19 +75,9 @@ DEFAULT_IO_CONFIG: dict[AllChannelNames, ChannelConfig] = {
|
||||
"ef": _DEFAULT_CHANNEL_CONFIG,
|
||||
"gh": _DEFAULT_CHANNEL_CONFIG,
|
||||
}
|
||||
|
||||
DEFAULT_TRIGGER_SOURCE: TRIGGERSOURCE = TRIGGERSOURCE.SINGLE_SHOT
|
||||
|
||||
# NOTE Default readout times for each channel, can be adapted as needed.
|
||||
# These values are relevant to calculate proper widths of the timing signals.
|
||||
# They also define a minimum exposure time that can be used as they are subtracted
|
||||
# as dead times from the exposure time.
|
||||
DEFAULT_READOUT_TIMES = {"ab": 2e-4, "cd": 2e-4, "ef": 2e-4, "gh": 2e-4} # 0.2 ms 5kHz
|
||||
|
||||
# NOTE Default channel references for each channel of the DDG1 delay generator.
|
||||
# This needs to be carefully adjusted to match the envisioned trigger scheme.
|
||||
# If the trigger scheme changes, adapt the values here together with the README and
|
||||
# PDF `trigger_scheme_ddg1_ddg2.pdf`.
|
||||
DEFAULT_REFERENCES: list[tuple[LiteralChannels, CHANNELREFERENCE]] = [
|
||||
("A", CHANNELREFERENCE.T0), # T0 + 2ms delay
|
||||
("B", CHANNELREFERENCE.A),
|
||||
@@ -110,50 +89,16 @@ DEFAULT_REFERENCES: list[tuple[LiteralChannels, CHANNELREFERENCE]] = [
|
||||
("H", CHANNELREFERENCE.G),
|
||||
]
|
||||
|
||||
###############################
|
||||
## DDG1 IMPLEMENTATION ########
|
||||
###############################
|
||||
|
||||
|
||||
class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
"""
|
||||
|
||||
Implementation of the DelayGenerator DDG1 for the cSAXS beamline. It is the main trigger
|
||||
source for the cSAXS beamline, and will be triggered by BEC through a software trigger or
|
||||
by a hardware trigger from a beamline device (e.g. Galil stages). Specific implementation
|
||||
of the cabling logic expected for this device are described in the module README, the attached
|
||||
PDF 'trigger_scheme_ddg1_ddg2.pdf' and the module docstring.
|
||||
|
||||
The IOC prefix is 'X12SA-CPCL-DDG1:'.
|
||||
|
||||
Args:
|
||||
name (str): Name of the device.
|
||||
prefix (str, optional): EPICS prefix for the device. Defaults to ''.
|
||||
scan_info (ScanInfo | None, optional): Scan info object. Defaults to None.
|
||||
device_manager (DeviceManagerBase | None, optional): Device manager. Defaults to None.
|
||||
Implementation of DelayGeneratorCSAXS for master trigger delay generator at X12SA-CPCL-DDG1.
|
||||
It will be triggered by a soft trigger from BEC or a hardware trigger from a beamline device
|
||||
(e.g. the Galil stages). It is operated in standard mode, not burst mode and will trigger the
|
||||
EXT/EN of DDG2 (channel ab). It is responsible for opening the shutter (channel cd) and sending
|
||||
an extra trigger to an or gate for the MCS card (channel ef).
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["keep_shutter_open_during_scan", "set_trigger"]
|
||||
|
||||
# TODO Consider using the 'fsh' device instead.
|
||||
fast_shutter_readback = Cpt(
|
||||
EpicsSignalRO,
|
||||
read_pv="X12SA-ES1-TTL:INP_01",
|
||||
add_prefix=("",), # Add this to prevent the prefix to be added to the signal
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
)
|
||||
# The shutter control PV can indicate if the shutter is requested to be kept open. If that
|
||||
# is the case, we can not use the signal shutter_readback signal to check if the delay cycle
|
||||
# finishes but have to use the polling of the event status register to check if the burst finished.
|
||||
fast_shutter_control = Cpt(
|
||||
EpicsSignalRO,
|
||||
read_pv="X12SA-ES1-TTL:OUT_01",
|
||||
add_prefix=("",), # Add this to prevent the prefix to be added to the signal
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
@@ -162,10 +107,12 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
device_manager: DeviceManagerBase | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Initialize the MCSCardCSAXS with the given arguments and keyword arguments.
|
||||
"""
|
||||
super().__init__(
|
||||
name=name, prefix=prefix, scan_info=scan_info, device_manager=device_manager, **kwargs
|
||||
)
|
||||
self._shutter_to_open_delay = 2e-3
|
||||
self.device_manager = device_manager
|
||||
self._poll_thread = threading.Thread(target=self._poll_event_status, daemon=True)
|
||||
self._poll_thread_run_event = threading.Event()
|
||||
@@ -176,206 +123,70 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
def on_connected(self) -> None:
|
||||
"""
|
||||
|
||||
This method is called after the device is initialized and all signals are connected. This happens
|
||||
when a device configuration is loaded in BEC.
|
||||
|
||||
It sets the default values for this device - intended to overwrite everything to a usable default state.
|
||||
For this purpose, we use the DEFAULT SETTINGS defined at the top of this module.
|
||||
|
||||
To ensure that this process is robust, we follow these steps:
|
||||
- First, we stop any ongoing burst mode operation.
|
||||
- Then, we set the DEFAULT_IO_CONFIG for each channel, the trigger source to DEFAULT_TRIGGER_SOURCE,
|
||||
and the channel references to DEFAULT_REFERENCES.
|
||||
- We set the state proc_status to be event based. This triggers readouts of the EventStatusLI bit
|
||||
based on events. This was empirically found to be a stable solution in combination with the poll
|
||||
loop of the state.
|
||||
- Finally, we set the burst delay to 0, to set it to be of no delay.
|
||||
Set the default values on the device - intended to overwrite everything to a usable default state.
|
||||
Sets DEFAULT_IO_CONFIG into each channel, sets the trigger source to DEFAULT_TRIGGER_SOURCE,
|
||||
and turns off burst mode.
|
||||
"""
|
||||
|
||||
# NOTE First we make sure that there is nothing running on the DDG. This seems to
|
||||
# help to tackle that the DDG occasionally freezes during the first scan
|
||||
# after reconnecting to it. Do not remove.
|
||||
self.stop_ddg()
|
||||
|
||||
# NOTE Setting DEFAULT configurations for IO config, trigger config and references.
|
||||
# The three dictionaries above 'DEFAULT_IO_CONFIG', 'DEFAULT_TRIGGER_SOURCE' and
|
||||
# 'DEFAULT_REFERNCES' should be used to adapt configurations if needed.
|
||||
self.burst_disable() # it is possible to miss setting settings if burst is enabled
|
||||
for channel, config in DEFAULT_IO_CONFIG.items():
|
||||
self.set_io_values(channel, **config)
|
||||
self.set_trigger(DEFAULT_TRIGGER_SOURCE)
|
||||
self.set_references_for_channels(DEFAULT_REFERENCES)
|
||||
|
||||
# NOTE Set state proc_status to be event based. This triggers readouts of the EventStatusLI bit
|
||||
# based on events. This was empirically found to be a stable solution in combination with the poll
|
||||
# loop of the state.
|
||||
# Set proc status to passively update with 5Hz (0.2s)
|
||||
self.state.proc_status_mode.put(PROC_EVENT_MODE.EVENT)
|
||||
|
||||
# NOTE Burst delay should be set to 0, don't remove as this will not be checked
|
||||
# Also set the burst count to 1 to only have a single pulse for DDG1.
|
||||
self.burst_delay.put(0)
|
||||
self.burst_count.put(1)
|
||||
|
||||
def keep_shutter_open_during_scan(self, open: True) -> None:
|
||||
"""
|
||||
Method to configure the delay generator for keeping the shutter open during a scans.
|
||||
This means that the additional delay to open the shutter needs to be removed (2e-3)
|
||||
from the timing of the signals.
|
||||
|
||||
Args:
|
||||
open (bool): If True, the shutter will be kept open during the scan.
|
||||
If False, the shutter will be opened and closed for each trigger cycle.
|
||||
"""
|
||||
if open is True:
|
||||
self._shutter_to_open_delay = 0
|
||||
else:
|
||||
self._shutter_to_open_delay = 2e-3
|
||||
|
||||
def on_stage(self) -> None:
|
||||
"""
|
||||
Stage logic for the DDG1 device, being th main trigger delay generator for CSAXS.
|
||||
For standard scans, it will be triggered by a soft trigger from BEC.
|
||||
It also has a hardware trigger feeded into the EXT/EN for fly-scanning, i.e. Galil stages.
|
||||
|
||||
This method is called in preparation for a scan. All information about the upcoming
|
||||
scan is available in self.scan_info.msg at this point. We use this information to
|
||||
configure the DDG1 for the upcoming scan.
|
||||
|
||||
The DDG is operated in burst mode for the scan, but with only a single burst pulse.
|
||||
THe length of the pulse is set to the expected exposure time for a single trigger,
|
||||
which includes any burst acquisitions if frames_per_trigger > 1.
|
||||
|
||||
The logic is as follows:
|
||||
- We check if any default burst parameters need to be set, and set them if needed.
|
||||
- We calculate the burst pulse width based on the exposure time and frames_per_trigger.
|
||||
- We set the burst_period and the shutter signal (delay pairs cd) to be
|
||||
exposure_time * frames_per_trigger + 3ms (2ms for shutter to open, 1ms to close).
|
||||
- We set the delay pairs ab to be 2ms delayed (to allow the shutter to open) with a width of 1us to trigger DDG2.
|
||||
- We set the delay pairs ef to be triggered after the shutter closes with a width of 1us to trigger the MCS card.
|
||||
- Finally, we add a short sleep to ensure that the IOC and DDG HW process the values properly.
|
||||
This DDG is always not in burst mode.
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
########################################
|
||||
### Burst mode settings ################
|
||||
########################################
|
||||
|
||||
# NOTE We check here if the delay generator is not in burst mode. We check these values
|
||||
# and set them to the requried values if they differ from the expected ones.
|
||||
# This has been found empirically to improve stability and avoid HW getting stuck in triggering cycles.
|
||||
if self.burst_mode.get() == 0:
|
||||
self.burst_mode.put(1)
|
||||
|
||||
if self.burst_delay.get() != 0:
|
||||
self.burst_delay.put(0)
|
||||
|
||||
if self.burst_count.get() != 1:
|
||||
self.burst_count.put(1)
|
||||
|
||||
#####################################
|
||||
## Setup trigger source if needed ###
|
||||
#####################################
|
||||
|
||||
# NOTE Some scans may change the trigger source to an external trigger,
|
||||
# so we will make sure that the default trigger source is set for the DDG1
|
||||
# before each scan. If a scan requires a different trigger source, i.e.
|
||||
# external triggers then the scan should implement this change after the
|
||||
# on_stage method was called.
|
||||
if self.trigger_source.get() != DEFAULT_TRIGGER_SOURCE:
|
||||
self.set_trigger(DEFAULT_TRIGGER_SOURCE)
|
||||
|
||||
#########################################
|
||||
### Setup timing for burst and delays ###
|
||||
#########################################
|
||||
|
||||
frames_per_trigger = self.scan_info.msg.scan_parameters["frames_per_trigger"]
|
||||
exp_time = self.scan_info.msg.scan_parameters["exp_time"]
|
||||
|
||||
# Burst Period DDG1
|
||||
# Set burst_period to shutter width
|
||||
# c/t0 + self._shutter_to_open_delay + exp_time * burst_count
|
||||
shutter_width = (
|
||||
self._shutter_to_open_delay + exp_time * frames_per_trigger
|
||||
) # Shutter starts closing at end of exposure
|
||||
if self.burst_period.get() != shutter_width:
|
||||
self.burst_period.put(shutter_width)
|
||||
|
||||
self.burst_enable(1, 0, exp_time)
|
||||
exp_time = self.scan_info.msg.scan_parameters["exp_time"]
|
||||
frames_per_trigger = self.scan_info.msg.scan_parameters["frames_per_trigger"]
|
||||
# Trigger DDG2
|
||||
# a = t0 + 2ms, b = a + 1us
|
||||
# a has reference to t0, b has reference to a
|
||||
# Add delay of self._shutter_to_open_delay to allow shutter to open
|
||||
self.set_delay_pairs(channel="ab", delay=self._shutter_to_open_delay, width=1e-6)
|
||||
|
||||
self.set_delay_pairs(channel="ab", delay=2e-3, width=1e-6)
|
||||
# Trigger shutter
|
||||
# d = c/t0 + self._shutter_to_open_delay + exp_time * burst_count + 1ms
|
||||
shutter_width = 2e-3 + exp_time * frames_per_trigger + 1e-3
|
||||
# d = c/t0 + 2ms + exp_time * burst_count + 1ms
|
||||
# c has reference to t0, d has reference to c
|
||||
# Shutter opens without delay at t0, closes after exp_time * burst_count + 2ms (self._shutter_to_open_delay)
|
||||
self.set_delay_pairs(channel="cd", delay=0, width=shutter_width)
|
||||
|
||||
self.set_delay_pairs(channel="gh", delay=self._shutter_to_open_delay, width=(shutter_width-self._shutter_to_open_delay))
|
||||
|
||||
# Trigger extra pulse for MCS OR gate
|
||||
# f = e + 1us
|
||||
# e has refernce to d, f has reference to e
|
||||
if self.scan_info.msg.scan_type == "fly":
|
||||
self.set_delay_pairs(channel="ef", delay=0, width=0)
|
||||
else:
|
||||
self.set_delay_pairs(channel="ef", delay=0, width=1e-6)
|
||||
|
||||
# NOTE Add additional sleep to make sure that the IOC and DDG HW process the values properly
|
||||
# This value has been choosen empirically after testing with the HW. It's
|
||||
# also just called once per scan and has been found to improve stability of the HW.
|
||||
time.sleep(0.2)
|
||||
logger.info(f"DDG {self.name} on_stage completed in {time.time() - start_time:.3f}s.")
|
||||
self.set_delay_pairs(channel="ef", delay=0, width=1e-6)
|
||||
time.sleep(
|
||||
0.2
|
||||
) # After staging, make sure that the DDG HW has some time to process changes properly.
|
||||
|
||||
def _prepare_mcs_on_trigger(self, mcs: MCSCardCSAXS) -> None:
|
||||
"""Prepare the MCS card for the next trigger.
|
||||
This method holds the logic to ensure that the MCS card is ready to read.
|
||||
It's logic is coupled to the MCS card implementation and the DDG1 trigger logic.
|
||||
"""
|
||||
|
||||
This method is used by the DDG1 on_trigger method to prepare the MCS card for the next trigger.
|
||||
It checks that the MCS card is properly prepared before BEC sends a software trigger to the DDG1,
|
||||
which is needed for step scans.
|
||||
|
||||
It relies on the MCS card implementation and needs to be adapted if the MCS card logic changes.
|
||||
"""
|
||||
|
||||
# NOTE First we wait that the MCS card is not acquiring. We add here a timeout of 5s to avoid
|
||||
# a deadlock in case the MCS card is stuck for some reason. This should not happen normally.
|
||||
status = CompareStatus(mcs.acquiring, ACQUIRING.DONE)
|
||||
self.cancel_on_stop(status)
|
||||
status.wait(timeout=5)
|
||||
|
||||
# NOTE Clear the '_omit_mca_callbacks' flag. This makes sure that data received from the mca1...mca3
|
||||
# counters are forwarded to BEC. Once the flag is set, we create a TransitionStatus DONE->ACQUIRING
|
||||
# and start the acquisition through erase_start.put(1). Finally, we wait for the card to go to ACQUIRING state.
|
||||
mcs._omit_mca_callbacks.clear() # pylint: disable=protected-access
|
||||
status_ready_read = CompareStatus(mcs.ready_to_read, READYTOREAD.DONE)
|
||||
mcs.stop_all.put(1)
|
||||
status_acquiring = TransitionStatus(mcs.acquiring, [ACQUIRING.DONE, ACQUIRING.ACQUIRING])
|
||||
self.cancel_on_stop(status_ready_read)
|
||||
self.cancel_on_stop(status_acquiring)
|
||||
mcs.erase_start.put(1)
|
||||
status_ready_read.wait(10)
|
||||
|
||||
return status_acquiring
|
||||
mcs.ready_to_read.put(READYTOREAD.PROCESSING)
|
||||
mcs.erase_start.put(1)
|
||||
status_acquiring.wait(timeout=10) # Allow 10 seconds in case communication is slow
|
||||
|
||||
def _poll_event_status(self) -> None:
|
||||
"""
|
||||
|
||||
Polling loop to retrieve the event status register of the delay generator DDG1.
|
||||
This method runs in a background thread and the polling is controlled through the
|
||||
'_poll_thread_run_event' and '_poll_thread_kill_event'. Polling should only become
|
||||
active when a software trigger was sent in BEC and we are waiting for the burst to complete.
|
||||
Poll the event status register in a background thread. Control
|
||||
the polling with the _poll_thread_run_event and _poll_thread_kill_event.
|
||||
"""
|
||||
# Main loop of the polling thread. As long as the kill event is not set, the loop continues.
|
||||
while not self._poll_thread_kill_event.is_set():
|
||||
|
||||
# NOTE Main wait event for the polling thread. If the _poll_thread_run_event is not set,
|
||||
# The thread will wait here. This event is used to start/stop polling from outside the thread,
|
||||
# as used in on_trigger and on_stop. Please make sure to set this event also when the thread
|
||||
# should be killed as its otherwise stuck inside the wait.
|
||||
self._poll_thread_run_event.wait()
|
||||
|
||||
# NOTE Set the event to indicate that we are currently still in the poll_loop. This is needed
|
||||
# as we have to use sleeps of 20ms within the poll loop. These sleeps were empirically detetermined
|
||||
# to ensure that no state changes are missed. However, these sleeps have the side effect that
|
||||
# setting the '_poll_thread_run_event' may not immediately stop the polling. Therefore, we need the
|
||||
# '_poll_thread_poll_loop_done' event to indicate that polling has finished. If this logic is changed,
|
||||
# it requires careful testing as failure rates can be in the 1 out of 500 events rate, which are still
|
||||
# not acceptable for operation. The current implementation has been tested with failure rates smaller then
|
||||
# ~ 1:100000 if failures happened at all.
|
||||
self._poll_thread_poll_loop_done.clear()
|
||||
while (
|
||||
self._poll_thread_run_event.is_set() and not self._poll_thread_kill_event.is_set()
|
||||
@@ -387,49 +198,29 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
logger.error(
|
||||
f"Exception in polling loop thread, polling continues...\n Error content:\n{content}"
|
||||
)
|
||||
# NOTE Set the _poll_thread_poll_loop_done event to indicate that we are done polling. Do not remove!
|
||||
|
||||
self._poll_thread_poll_loop_done.set()
|
||||
|
||||
def _poll_loop(self) -> None:
|
||||
"""
|
||||
|
||||
This method is the actual poll loop to update the event status from the satus register
|
||||
of the delay generator DDG1.
|
||||
|
||||
It follows a procedure that was established empirically after extended testing with the HW.
|
||||
Any adaptations to this logic need to be carefully tested to avoid that the HW becomes unstable.
|
||||
NOTE: Sleeps are important in this logic, and should not be removed or optimized without extensive testing.
|
||||
20ms has been found to be the minimum sleep time that proofed to be stable in operation.
|
||||
|
||||
The logic is as follows:
|
||||
- Set the 'proc_status' to 1 with use_complete=True to trigger an event based readout of the EventStatusLI.
|
||||
- Sleep 20ms to give the device time to process the command.
|
||||
- Check if the kill event or run event are cleared, and exit the loop if so.
|
||||
- Read the EventStatusLI channel to update the event status.
|
||||
- Check again if the kill event or run event are cleared, and exit the loop if so.
|
||||
|
||||
Please note that any important changes of the status register reading will trigger callbacks
|
||||
if attached to the event status signal. These callbacks hold the logic to resolve status objects
|
||||
when waiting for specific events (e.g. end of burst).
|
||||
|
||||
|
||||
Poll loop to update event status.
|
||||
The checks ensure that the loop exist after each operation and be stuck in sleep.
|
||||
The 20ms sleep was added to ensure that the event status is not polled too frequently,
|
||||
and to give the device time to process the previous command. This was found empirically
|
||||
to be necessary to avoid missing events.
|
||||
IMPORTANT: Do not remove sleeps or try to optimize this logic. This seems to be a
|
||||
fragile balance between polling frequency and device processing time. Also in between
|
||||
start/stop of polling. Please also consider that there is a sleep in on_trigger and
|
||||
that this might also be necessary to avoid that HW becomes unavailable/unstable.
|
||||
"""
|
||||
self.state.proc_status.put(1, use_complete=True)
|
||||
|
||||
# NOTE: Important sleep that has been empirically determined after testing for a long time
|
||||
# Only remove if absolutely certain that the DDG logic of polling the EventStatusLI works without it.
|
||||
time.sleep(0.02)
|
||||
|
||||
time.sleep(0.02) # 20ms delay for processing, important for not missing events
|
||||
if self._poll_thread_kill_event.is_set() or not self._poll_thread_run_event.is_set():
|
||||
return
|
||||
|
||||
self.state.event_status.get(use_monitor=False)
|
||||
if self._poll_thread_kill_event.is_set() or not self._poll_thread_run_event.is_set():
|
||||
return
|
||||
|
||||
# NOTE: Again important sleep that has been empirically determined after testing for a long time
|
||||
# Only remove if certain that logic can be replaced to not risk HW failures.
|
||||
time.sleep(0.02)
|
||||
time.sleep(0.02) # 20ms delay for processing, important for not missing events
|
||||
|
||||
def _start_polling(self) -> None:
|
||||
"""Start the polling loop in the background thread."""
|
||||
@@ -449,23 +240,8 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
else:
|
||||
logger.info("Polling thread stopped.")
|
||||
|
||||
def _prepare_trigger_status_event(
|
||||
self, timeout: float | None = None
|
||||
) -> StatusBitsCompareStatus:
|
||||
"""
|
||||
Method to prepare a status object that indicates the end of a burst cycle.
|
||||
It also sets up a callback to cancel the polling of the event status register
|
||||
if the status is cancelled externally (e.g. by stopping the device). In addition,
|
||||
a timeout can either be specified, or is automatically calculated based on the
|
||||
exposure time, frames_per_trigger and a default extra time of 5 seconds.
|
||||
|
||||
Args:
|
||||
timeout (float | None, optional): Timeout for the status object. If None, a
|
||||
default timeout based on exposure time and frames_per_trigger is used.
|
||||
|
||||
Returns:
|
||||
StatusBitsCompareStatus:
|
||||
"""
|
||||
def _prepare_trigger_status_event(self, timeout: float | None = None) -> DeviceStatus:
|
||||
"""Prepare the trigger status event for the DDG1, and trigger the de"""
|
||||
if timeout is None:
|
||||
# Default timeout of 5 seconds + exposure time * frames_per_trigger
|
||||
timeout = 5 + self.scan_info.msg.scan_parameters.get(
|
||||
@@ -475,9 +251,7 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
# Callback to cancel the status if the device is stopped
|
||||
def cancel_cb(status: CompareStatus) -> None:
|
||||
"""Callback to cancel the status if the device is stopped."""
|
||||
logger.debug("DDG1 end of burst detected, stopping polling loop.")
|
||||
if status.done:
|
||||
self._stop_polling()
|
||||
self._stop_polling()
|
||||
|
||||
# Run false is important to ensure that the status is only checked on the next event status update
|
||||
status = StatusBitsCompareStatus(
|
||||
@@ -488,88 +262,38 @@ class DDG1(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
return status
|
||||
|
||||
def on_trigger(self) -> DeviceStatus:
|
||||
"""Note, we need to add a delay to the StatusBits callback on the event_status.
|
||||
If we don't then subsequent triggers may reach the DDG too early, and will be ignored. To
|
||||
avoid this, we've added the option to specify a delay via add_delay, default here is 50ms.
|
||||
"""
|
||||
This method is called from BEC as a software trigger. Here the logic is as follows:
|
||||
|
||||
We first check if the trigger_source is set to SINGLE_SHOT. Only then will we received,
|
||||
otherwise we return a status object directly as the DDG is triggered by an external
|
||||
source which will have to implement its own logic to wait for trigger signals to
|
||||
be received.
|
||||
|
||||
I SINGLE_SHOT, the implementation here will send a software trigger. Now there are
|
||||
two options to wait for the trigger (burst) cycle to be done. One is to rely on the
|
||||
signal of the "mcs" card if it is present. However, this is only possible if the
|
||||
scan_type is not "fly" as in fly scans the ef channel is not triggered to send the last
|
||||
pulse to the card (but the card is finishing its acquisition in complete itself). Then
|
||||
we rely on the polling of the event status register to check if the burst cycle is done.
|
||||
|
||||
It follows a specific procedure to ensure that the DDG1 and MCS card are properly handled
|
||||
on a trigger event. The established logic is as follows:
|
||||
|
||||
- Stop polling the event status register to avoid that the polling loop is still active
|
||||
before sending the software trigger. This needs to be done to avoid conflicts
|
||||
in reading the event status register.
|
||||
- Wait for the _poll_thread_poll_loop_done event to ensure that the polling loop is no
|
||||
longer active. A timeout of 1s is plenty as sleeps of 20ms are used in the poll loop.
|
||||
- Add an extra sleep of 20ms to make sure that the HW is again ready to process new commands.
|
||||
This has been found empirically after long testing to improve stability.
|
||||
- If the MCS card is present in the current session of BEC, prepare the card for the next trigger.
|
||||
- Prepare a status StatusBitsCompareStatus that will be resolved once the burst is done.
|
||||
- Start the polling loop again to monitor the event status register.
|
||||
- Send the software trigger to the DDG1
|
||||
- Return the status object to BEC which will automatically resolve once the status register has
|
||||
the END_OF_BURST bit set. The callback of the status object will also stop the polling loop.
|
||||
"""
|
||||
overall_start = time.time()
|
||||
# Stop polling, poll once manually to ensure that the register is clean
|
||||
self._stop_polling()
|
||||
self._poll_thread_poll_loop_done.wait(timeout=1)
|
||||
# IMPORTANT: Keep this sleep setting, as it is necessary to avoid that the HW
|
||||
# becomes unresponsive. This was found empirically and seems to be necessary
|
||||
time.sleep(0.02)
|
||||
|
||||
# NOTE If the trigger source is not SINGLE_SHOT, the DDG is triggered by an external source
|
||||
# thus we can not expect that trigger signals are meant to be awaited for. In this case,
|
||||
# we can directly return.
|
||||
if self.trigger_source.get() != TRIGGERSOURCE.SINGLE_SHOT.value:
|
||||
status = StatusBase(obj=self)
|
||||
status.set_finished()
|
||||
return status
|
||||
|
||||
# NOTE If the MCS card is present in the current session of BEC,
|
||||
# we prepare the card for the next trigger. The procedure is implemented
|
||||
# in the '_prepare_mcs_on_trigger' method. We will also use the mcs card
|
||||
# to indicate when the burst cycle is done. If no mcs card is available
|
||||
# the fallback is to use the polling of the DDG
|
||||
# Prepare the MCS card for the next software trigger
|
||||
mcs = self.device_manager.devices.get("mcs", None)
|
||||
if mcs is None or mcs.enabled is False or self.scan_info.msg.scan_type == "fly":
|
||||
self._poll_thread_poll_loop_done.wait(timeout=1)
|
||||
logger.warning("Did not find mcs card with name 'mcs' in current session")
|
||||
time.sleep(0.02)
|
||||
# Shutter is kept open, we can only rely on the event status register
|
||||
status = self._prepare_trigger_status_event()
|
||||
# Start polling thread again to monitor event status
|
||||
self._start_polling()
|
||||
if mcs is None:
|
||||
logger.info("Did not find mcs card with name 'mcs' in current session")
|
||||
else:
|
||||
start_time = time.time()
|
||||
logger.debug(f"Preparing mcs card ")
|
||||
status_mcs = self._prepare_mcs_on_trigger(mcs)
|
||||
# NOTE Timeout of 3s should be plenty, any longer wait should checked. If this happens to crash
|
||||
# an acquisition regularly with a WaitTimeoutError, the timeout can be increased but it should
|
||||
# be investigated why the EPICS interface is slow to respond.
|
||||
status_mcs.wait(timeout=3)
|
||||
status = TransitionStatus(mcs.acquiring, [ACQUIRING.ACQUIRING, ACQUIRING.DONE])
|
||||
logger.debug(f"Finished preparing mcs card {time.time()-start_time}")
|
||||
|
||||
# Send trigger
|
||||
self._prepare_mcs_on_trigger(mcs)
|
||||
# Prepare status with callback to cancel the polling once finished
|
||||
status = self._prepare_trigger_status_event()
|
||||
# Start polling
|
||||
self._start_polling()
|
||||
# Trigger the DDG1
|
||||
self.trigger_shot.put(1, use_complete=True)
|
||||
self.cancel_on_stop(status)
|
||||
logger.info(f"Configured ddg in {time.time()-overall_start}")
|
||||
return status
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Stop the delay generator HW and polling thread when the device is stopped."""
|
||||
"""Stop the delay generator by setting the burst mode to 0"""
|
||||
self.stop_ddg()
|
||||
self._stop_polling()
|
||||
|
||||
def on_destroy(self) -> None:
|
||||
"""Clean up resources when the device is destroyed."""
|
||||
self.stop_ddg()
|
||||
self._kill_poll_thread()
|
||||
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Burst mode is enabled:
|
||||
import time
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd_devices import DeviceStatus, StatusBase
|
||||
from ophyd import DeviceStatus, StatusBase
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import (
|
||||
@@ -37,26 +37,17 @@ from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import
|
||||
ChannelConfig,
|
||||
DelayGeneratorCSAXS,
|
||||
LiteralChannels,
|
||||
BURSTCONFIG,
|
||||
)
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
########################
|
||||
## DEFAULT SETTINGS ####
|
||||
########################
|
||||
|
||||
# NOTE Default channel configuration for the DDG2 delay generator channels
|
||||
_DEFAULT_CHANNEL_CONFIG: ChannelConfig = {
|
||||
"amplitude": 4.5,
|
||||
"amplitude": 5.0,
|
||||
"offset": 0.0,
|
||||
"polarity": OUTPUTPOLARITY.POSITIVE,
|
||||
"mode": "ttl",
|
||||
}
|
||||
|
||||
# NOTE Default IO configuration for all channels in DDG2
|
||||
# Each channel uses the same default configuration as defined above
|
||||
# If needed, individual channel configurations should be modified here.
|
||||
DEFAULT_IO_CONFIG: dict[AllChannelNames, ChannelConfig] = {
|
||||
"t0": _DEFAULT_CHANNEL_CONFIG,
|
||||
"ab": _DEFAULT_CHANNEL_CONFIG,
|
||||
@@ -64,16 +55,9 @@ DEFAULT_IO_CONFIG: dict[AllChannelNames, ChannelConfig] = {
|
||||
"ef": _DEFAULT_CHANNEL_CONFIG,
|
||||
"gh": _DEFAULT_CHANNEL_CONFIG,
|
||||
}
|
||||
|
||||
DEFAULT_TRIGGER_SOURCE: TRIGGERSOURCE = TRIGGERSOURCE.EXT_RISING_EDGE
|
||||
|
||||
# NOTE Default readout times for the detectors connected to DDG2
|
||||
# These values are used to calculate the difference between the burst_period and the pulse width of
|
||||
# individual channel pairs. They also mark a lower limit for the exposure time. Needs to be
|
||||
# adjusted if the exposure time should possibly go below 0.2 ms.
|
||||
DEFAULT_READOUT_TIMES = {"ab": 2e-4, "cd": 2e-4, "ef": 2e-4, "gh": 2e-4} # 0.2 ms 5kHz
|
||||
|
||||
# NOTE Default refernce settings for each channel in DDG2
|
||||
DEFAULT_REFERENCES: list[tuple[LiteralChannels, CHANNELREFERENCE]] = [
|
||||
("A", CHANNELREFERENCE.T0),
|
||||
("B", CHANNELREFERENCE.A),
|
||||
@@ -85,27 +69,9 @@ DEFAULT_REFERENCES: list[tuple[LiteralChannels, CHANNELREFERENCE]] = [
|
||||
("H", CHANNELREFERENCE.G),
|
||||
]
|
||||
|
||||
###############################
|
||||
## DDG2 IMPLEMENTATION ########
|
||||
###############################
|
||||
|
||||
|
||||
class DDG2(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
"""
|
||||
|
||||
Implementation of the DelayGenerator DDG2 for the cSAXS beamline. This delay generator is
|
||||
reponsible to create triggers for the detectors. It is configured in burst mode. Please
|
||||
check the module docstring, the module README and the attached PDF 'trigger_scheme_ddg1_ddg2.pdf'
|
||||
for more information about the expected cabling and trigger logic.
|
||||
|
||||
The IOC prefix is 'X12SA-CPCL-DDG2:'.
|
||||
|
||||
Args:
|
||||
name (str): Name of the device.
|
||||
prefix (str, optional): EPICS prefix for the device. Defaults to ''.
|
||||
scan_info (ScanInfo | None, optional): Scan info object. Defaults to None.
|
||||
device_manager (DeviceManagerBase | None, optional): Device manager. Defaults to None.
|
||||
|
||||
Implementation of DelayGeneratorCSAXS for the CSAXS master trigger delay generator at X12SA-CPCL-DDG2.
|
||||
This device is responsible for creating triggers in burst mode and is connected to a multiplexer that
|
||||
distributes the trigger to the detectors. The DDG2 is triggered by the DDG1 through the EXT/EN channel.
|
||||
@@ -114,102 +80,77 @@ class DDG2(PSIDeviceBase, DelayGeneratorCSAXS):
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
def on_connected(self) -> None:
|
||||
"""
|
||||
|
||||
This method is called after the device is initialized and all signals are connected. This happens
|
||||
when a device configuration is loaded in BEC.
|
||||
|
||||
It sets the default values for this device - intended to overwrite everything to a usable default state.
|
||||
For this purpose, we use the DEFAULT SETTINGS defined at the top of this module.
|
||||
|
||||
The following procedure is followed:
|
||||
- Stop the DDG to ensure it is not running.
|
||||
- Then, we set the DEFAULT_IO_CONFIG for each channel, the trigger source to DEFAULT_TRIGGER_SOURCE,
|
||||
and the channel references to DEFAULT_REFERENCES.
|
||||
Set the default values on the device - intended to overwrite everything to a usable default state.
|
||||
Sets DEFAULT_IO_CONFIG into each channel, sets the trigger source to DEFAULT_TRIGGER_SOURCE.
|
||||
"""
|
||||
self.stop_ddg()
|
||||
|
||||
# NOTE Please adjust the default settings under 'DEFAULT SETTINGS' at the top of this module if needed.
|
||||
# This makes sure that we have a well defined default state for the DDG2 device.
|
||||
self.burst_disable() # it is possible to miss setting settings if burst is enabled
|
||||
for channel, config in DEFAULT_IO_CONFIG.items():
|
||||
self.set_io_values(channel, **config)
|
||||
self.set_trigger(DEFAULT_TRIGGER_SOURCE)
|
||||
self.set_references_for_channels(DEFAULT_REFERENCES)
|
||||
|
||||
# Set burst config
|
||||
self.burst_config.put(BURSTCONFIG.FIRST_CYCLE.value)
|
||||
|
||||
def on_stage(self) -> DeviceStatus | StatusBase | None:
|
||||
"""
|
||||
Stage logic for the DDG1 device, being th main trigger delay generator for CSAXS.
|
||||
For standard scans, it will be triggered by a soft trigger from BEC.
|
||||
It also has a hardware trigger feeded into the EXT/EN for fly-scanning, i.e. Galil stages.
|
||||
|
||||
This method is called when the device is staged before a scan. All information about the scan
|
||||
is available through self.scan_info.msg at this point. The DDG2 needs to be configured to
|
||||
create a sequence of TTL pulses in burst mode that are sent to the detectors. It therefore needs
|
||||
to know the exposure time and frames per trigger from the self.scan_info.msg.scan_parameters.
|
||||
|
||||
This logic is robust for step scans as well as fly scans, as the DDG2 is triggered by the DDG1
|
||||
through the EXT/EN channel.
|
||||
This DDG is always not in burst mode.
|
||||
"""
|
||||
start_time = time.time()
|
||||
########################################
|
||||
### Burst mode settings ################
|
||||
########################################
|
||||
|
||||
# NOTE Only adjust settings if needed. DDG2 should always be in burst mode when used at CSAXS.
|
||||
if self.burst_mode.get() == 0:
|
||||
self.burst_mode.put(1)
|
||||
|
||||
# Ensure that there is no delay for the burst
|
||||
if self.burst_delay.get() != 0:
|
||||
self.burst_delay.put(0)
|
||||
|
||||
exp_time = self.scan_info.msg.scan_parameters["exp_time"]
|
||||
frames_per_trigger = self.scan_info.msg.scan_parameters["frames_per_trigger"]
|
||||
|
||||
# NOTE Check if the exposure time is longer than all readout times.
|
||||
# Raise a ValueError if requested exposure time is too short.
|
||||
# a = t0
|
||||
# a has reference to t0, b has reference to a
|
||||
if any(exp_time <= rt for rt in DEFAULT_READOUT_TIMES.values()):
|
||||
raise ValueError(
|
||||
f"Exposure time {exp_time} is too short for the readout times {DEFAULT_READOUT_TIMES}"
|
||||
)
|
||||
|
||||
#########################################
|
||||
### Setup timing for burst and delays ###
|
||||
#########################################
|
||||
|
||||
# Burst Period DDG2 settings. Only adjust them if needed.
|
||||
if self.burst_count.get() != frames_per_trigger:
|
||||
self.burst_count.put(frames_per_trigger)
|
||||
if self.burst_period.get() != exp_time:
|
||||
self.burst_period.put(exp_time)
|
||||
|
||||
# Calculate the pulse width for the channel pair 'ab'
|
||||
burst_pulse_width = exp_time - DEFAULT_READOUT_TIMES["ab"]
|
||||
|
||||
# Trigger detectors with delay 0, and pulse width = exp_time - readout_time
|
||||
self.set_delay_pairs(channel="ab", delay=0, width=burst_pulse_width)
|
||||
|
||||
logger.info(f"DDG {self.name} on_stage completed in {time.time() - start_time:.3f}s.")
|
||||
self.burst_enable(count=frames_per_trigger, delay=0, period=exp_time)
|
||||
|
||||
def on_pre_scan(self):
|
||||
"""
|
||||
|
||||
Method that is called just before a scan starts. It was observed that a short delay of 50ms
|
||||
improves the overall stability in operation. This may be removed as other parts were adjusted,
|
||||
but for now we will keep it as the delay is short.
|
||||
The delay generator occasionally needs a bit extra time to process all
|
||||
commands from stage. Therefore, we introduce here a short sleep
|
||||
"""
|
||||
# NOTE Short delay to allow for the HW to process the commands before the scan starts.
|
||||
# This may no longer be needed after other adjustments, and may be removed in the future.
|
||||
# Delay Generator occasionaly needs a bit extra time to process all commands, sleep 50ms
|
||||
time.sleep(0.05)
|
||||
|
||||
def on_trigger(self) -> DeviceStatus | StatusBase | None:
|
||||
"""
|
||||
|
||||
DDG2 does not implement any trigger specific logic as it is triggered by DDG1 through the EXT/EN channel.
|
||||
DDG2 will not receive a trigger from BEC, but will be triggered by the DDG1 through the EXT/EN channel.
|
||||
"""
|
||||
pass
|
||||
|
||||
def wait_for_status(
|
||||
self, status: DeviceStatus, bit_event: STATUSBITS, timeout: float = 5
|
||||
) -> None:
|
||||
"""Wait for a event status bit to be set.
|
||||
|
||||
Args:
|
||||
status (StatusBase): The status object to update.
|
||||
bit_event (STATUSBITS): The event status bit to wait for.
|
||||
timeout (float): Maximum time to wait for the event status bit to be set.
|
||||
"""
|
||||
current_time = time.time()
|
||||
while not status.done:
|
||||
self.state.proc_status.put(1, use_complete=True)
|
||||
event_status = self.state.event_status.get()
|
||||
if (STATUSBITS(event_status) & bit_event) == bit_event:
|
||||
status.set_finished()
|
||||
if time.time() - current_time > timeout:
|
||||
status.set_exception(
|
||||
TimeoutError(
|
||||
f"Timeout waiting for status of device {self.name} for event_status {bit_event}"
|
||||
)
|
||||
)
|
||||
break
|
||||
time.sleep(0.1)
|
||||
time.sleep(0.05) # Give time for the IOC to be ready again
|
||||
return status
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Stop the delay generator"""
|
||||
"""Stop the delay generator by setting the burst mode to 0"""
|
||||
self.stop_ddg()
|
||||
|
||||
|
||||
|
||||
@@ -3,11 +3,6 @@ Delay generator implementation for CSAXS.
|
||||
|
||||
Detailed information can be found in the manual:
|
||||
https://www.thinksrs.com/downloads/pdfs/manuals/DG645m.pdf
|
||||
|
||||
On the beamline consoles, the caqtdm panel can be started via:
|
||||
caqtdm -noMsg -attach -macro P=X12SA-CPCL-DDG,R=1: srsDG645.ui
|
||||
|
||||
R=1,2,3 for 3 different DDG units installed at CSAXS.
|
||||
"""
|
||||
|
||||
import enum
|
||||
@@ -156,9 +151,8 @@ class StatusBitsCompareStatus(SubscriptionStatus):
|
||||
run=run,
|
||||
)
|
||||
|
||||
def _compare_callback(self, *args, value, **kwargs) -> bool:
|
||||
def _compare_callback(self, value, **kwargs) -> bool:
|
||||
"""Callback for subscription status"""
|
||||
logger.debug(f"StatusBitsCompareStatus: Received value {value}")
|
||||
obj = kwargs.get("obj", None)
|
||||
if obj is None:
|
||||
name = "no object received"
|
||||
@@ -173,9 +167,7 @@ class StatusBitsCompareStatus(SubscriptionStatus):
|
||||
return False
|
||||
if self._add_delay != 0:
|
||||
time.sleep(self._add_delay)
|
||||
logger.debug(
|
||||
f"Returning comparison for {name}: {(STATUSBITS(value) & self._value) == self._value}"
|
||||
)
|
||||
|
||||
return (STATUSBITS(value) & self._value) == self._value
|
||||
|
||||
|
||||
@@ -488,7 +480,6 @@ class DelayGeneratorCSAXS(Device):
|
||||
name="trigger_source",
|
||||
kind=Kind.omitted,
|
||||
doc="Trigger Source for the DDG, options in TRIGGERSOURCE",
|
||||
auto_monitor=True,
|
||||
)
|
||||
trigger_level = Cpt(
|
||||
EpicsSignal,
|
||||
@@ -542,7 +533,6 @@ class DelayGeneratorCSAXS(Device):
|
||||
write_pv="BurstDelayAO",
|
||||
name="burst_delay",
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
doc="Delay before bursts start in seconds. Must be >=0.",
|
||||
)
|
||||
burst_period = Cpt(
|
||||
|
||||
@@ -1,435 +0,0 @@
|
||||
"""EPS module for cSAXS beamline: defines the EPS device with its components and methods."""
|
||||
|
||||
# fmt: off
|
||||
# Disable Black formatting for this file to preserve an easier readable structure for the component definitions.
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, EpicsSignal, EpicsSignalRO, Kind
|
||||
from ophyd_devices import PSIDeviceBase
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
# ---------------------------
|
||||
# Registry: sections/channels
|
||||
# ---------------------------
|
||||
|
||||
|
||||
class EPSSubDevices(Device):
|
||||
"""Base class for EPS sub-device components (e.g. alarms, valves, shutters). with common methods if needed."""
|
||||
|
||||
def describe(self) -> dict:
|
||||
desc = super().describe()
|
||||
for walk in self.walk_signals():
|
||||
if walk.item.attr_name not in desc:
|
||||
desc[walk.item.attr_name] = walk.item.describe()
|
||||
return desc
|
||||
|
||||
|
||||
class EPSAlarms(EPSSubDevices):
|
||||
"""EPS alarms at the cSAXS beamline."""
|
||||
|
||||
eps_alarm_cnt = Cpt(EpicsSignalRO, read_pv="X12SA-EPS-PLC:AlarmCnt_EPS", add_prefix=("",), name="eps_alarm_cnt", kind=Kind.omitted, doc="X12SA EPS Alarm count", auto_monitor=True, labels={"alarm"})
|
||||
mis_alarm_cnt = Cpt(EpicsSignalRO, read_pv="ARS00-MIS-PLC-01:AlarmCnt_Frontends", add_prefix=("",), name="mis_alarm_cnt", kind=Kind.omitted, doc="FrontEnd MIS Alarm count", auto_monitor=True, labels={"alarm"})
|
||||
|
||||
|
||||
class ValvesFrontend(EPSSubDevices):
|
||||
"""Valves frontend at the cSAXS beamline."""
|
||||
|
||||
|
||||
fe_vvpg_0000 = Cpt(EpicsSignalRO, read_pv="X12SA-FE-VVPG-0000:PLC_OPEN", add_prefix=("",), name="fevvpg0000", kind=Kind.omitted, doc="FE-VVPG-0000", auto_monitor=True, labels={"valve"})
|
||||
fe_vvpg_1010 = Cpt(EpicsSignalRO, read_pv="X12SA-FE-VVPG-1010:PLC_OPEN", add_prefix=("",), name="fevvpg1010", kind=Kind.omitted, doc="FE-VVPG-1010", auto_monitor=True, labels={"valve"})
|
||||
fe_vvfv_2010 = Cpt(EpicsSignalRO, read_pv="X12SA-FE-VVFV-2010:PLC_OPEN", add_prefix=("",), name="fevvfv2010", kind=Kind.omitted, doc="FE-VVFV-2010", auto_monitor=True, labels={"valve"})
|
||||
fe_vvpg_2010 = Cpt(EpicsSignalRO, read_pv="X12SA-FE-VVPG-2010:PLC_OPEN", add_prefix=("",), name="fevvpg2010", kind=Kind.omitted, doc="FE-VVPG-2010", auto_monitor=True, labels={"valve"})
|
||||
|
||||
class ValvesOptics(EPSSubDevices):
|
||||
"""Valves at the optics hutch."""
|
||||
|
||||
op_vvpg_1010 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-VVPG-1010:PLC_OPEN", add_prefix=("",), name="opvvpg1010", kind=Kind.omitted, doc="OP-VVPG-1010", auto_monitor=True, labels={"valve"})
|
||||
op_vvpg_2010 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-VVPG-2010:PLC_OPEN", add_prefix=("",), name="opvvpg2010", kind=Kind.omitted, doc="OP-VVPG-2010", auto_monitor=True, labels={"valve"})
|
||||
op_vvpg_3010 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-VVPG-3010:PLC_OPEN", add_prefix=("",), name="opvvpg3010", kind=Kind.omitted, doc="OP-VVPG-3010", auto_monitor=True, labels={"valve"})
|
||||
op_vvpg_3020 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-VVPG-3020:PLC_OPEN", add_prefix=("",), name="opvvpg3020", kind=Kind.omitted, doc="OP-VVPG-3020", auto_monitor=True, labels={"valve"})
|
||||
op_vvpg_4010 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-VVPG-4010:PLC_OPEN", add_prefix=("",), name="opvvpg4010", kind=Kind.omitted, doc="OP-VVPG-4010", auto_monitor=True, labels={"valve"})
|
||||
op_vvpg_5010 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-VVPG-5010:PLC_OPEN", add_prefix=("",), name="opvvpg5010", kind=Kind.omitted, doc="OP-VVPG-5010", auto_monitor=True, labels={"valve"})
|
||||
op_vvpg_6010 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-VVPG-6010:PLC_OPEN", add_prefix=("",), name="opvvpg6010", kind=Kind.omitted, doc="OP-VVPG-6010", auto_monitor=True, labels={"valve"})
|
||||
op_vvpg_7010 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-VVPG-7010:PLC_OPEN", add_prefix=("",), name="opvvpg7010", kind=Kind.omitted, doc="OP-VVPG-7010", auto_monitor=True, labels={"valve"})
|
||||
|
||||
|
||||
class ValvesEndstation(EPSSubDevices):
|
||||
"""Endstation valves at the cSAXS beamline."""
|
||||
|
||||
es_vvpg_1010 = Cpt(EpicsSignalRO, read_pv="X12SA-ES-VVPG-1010:PLC_OPEN", add_prefix=("",), name="esvvpg1010", kind=Kind.omitted, doc="ES-VVPG-1010", auto_monitor=True, labels={"valve"})
|
||||
|
||||
|
||||
class ShuttersFrontend(EPSSubDevices):
|
||||
"""Shutters frontend."""
|
||||
|
||||
fe_psh1 = Cpt(EpicsSignalRO, read_pv="X12SA-FE-PSH1-EMLS-0010:OPEN", add_prefix=("",), name="fepsh1", kind=Kind.omitted, doc="FE-PSH1-EMLS-0010", auto_monitor=True, labels={"shutter"})
|
||||
fe_sto1 = Cpt(EpicsSignalRO, read_pv="X12SA-FE-STO1-EMLS-0010:OPEN", add_prefix=("",), name="festo1", kind=Kind.omitted, doc="FE-STO1-EMLS-0010", auto_monitor=True, labels={"shutter"})
|
||||
|
||||
|
||||
class ShuttersEndstation(EPSSubDevices):
|
||||
"""Shutters at the endstation."""
|
||||
|
||||
es_psh17010 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-PSH1-EMLS-7010:OPEN", add_prefix=("",), name="espsh17010", kind=Kind.omitted, doc="OP-PSH1-EMLS-7010", auto_monitor=True, labels={"shutter"})
|
||||
|
||||
|
||||
class DMMMonochromator(EPSSubDevices):
|
||||
"""DMM monochromator signals at the cSAXS beamline."""
|
||||
|
||||
dmm_temp_surface_1 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM-ETTC-3010:TEMP", add_prefix=("",), name="dmm_temp_surface_1", kind=Kind.omitted, doc="DMM Temp Surface 1", auto_monitor=True, labels={"temp"})
|
||||
dmm_temp_surface_2 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM-ETTC-3020:TEMP", add_prefix=("",), name="dmm_temp_surface_2", kind=Kind.omitted, doc="DMM Temp Surface 2", auto_monitor=True, labels={"temp"})
|
||||
dmm_temp_shield_1_disaster = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM-ETTC-3030:TEMP", add_prefix=("",), name="dmm_temp_shield_1_disaster", kind=Kind.omitted, doc="DMM Temp Shield 1 (disaster)", auto_monitor=True, labels={"temp"})
|
||||
dmm_temp_shield_2_disaster = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM-ETTC-3040:TEMP", add_prefix=("",), name="dmm_temp_shield_2_disaster", kind=Kind.omitted, doc="DMM Temp Shield 2 (disaster)", auto_monitor=True, labels={"temp"})
|
||||
|
||||
dmm_translation_thru = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM-EMLS-3010:THRU", add_prefix=("",), name="dmm_translation_thru", kind=Kind.omitted, doc="DMM Translation ThruPos", auto_monitor=True, labels={"switch"})
|
||||
dmm_translation_in = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM-EMLS-3020:IN", add_prefix=("",), name="dmm_translation_in", kind=Kind.omitted, doc="DMM Translation InPos", auto_monitor=True, labels={"switch"})
|
||||
dmm_bragg_thru = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM-EMLS-3030:THRU", add_prefix=("",), name="dmm_bragg_thru", kind=Kind.omitted, doc="DMM Bragg ThruPos", auto_monitor=True, labels={"switch"})
|
||||
dmm_bragg_in = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM-EMLS-3040:IN", add_prefix=("",), name="dmm_bragg_in", kind=Kind.omitted, doc="DMM Bragg InPos", auto_monitor=True, labels={"switch"})
|
||||
|
||||
dmm_heater_fault_xtal_1 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM-EMSW-3050:SWITCH", add_prefix=("",), name="dmm_heater_fault_xtal_1", kind=Kind.omitted, doc="DMM Heater Fault XTAL 1", auto_monitor=True, labels={"fault"})
|
||||
dmm_heater_fault_xtal_2 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM-EMSW-3060:SWITCH", add_prefix=("",), name="dmm_heater_fault_xtal_2", kind=Kind.omitted, doc="DMM Heater Fault XTAL 2", auto_monitor=True, labels={"fault"})
|
||||
dmm_heater_fault_support_1 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM-EMSW-3070:SWITCH", add_prefix=("",), name="dmm_heater_fault_support_1", kind=Kind.omitted, doc="DMM Heater Fault Support 1", auto_monitor=True, labels={"fault"})
|
||||
|
||||
dmm_energy = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM1:ENERGY-GET", add_prefix=("",), name="dmm_energy", kind=Kind.omitted, doc="DMM Energy", auto_monitor=True, labels={"energy"})
|
||||
dmm_position = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM1:POSITION", add_prefix=("",), name="dmm_position", kind=Kind.omitted, doc="DMM Position", auto_monitor=True, labels={"string"})
|
||||
dmm_stripe = Cpt(EpicsSignalRO, read_pv="X12SA-OP-DMM1:STRIPE", add_prefix=("",), name="dmm_stripe", kind=Kind.omitted, doc="DMM Stripe", auto_monitor=True, labels={"string"})
|
||||
|
||||
|
||||
class CCMMonochromator(EPSSubDevices):
|
||||
"""CCM monochromator signals at the cSAXS beamline."""
|
||||
|
||||
ccm_temp_crystal = Cpt(EpicsSignalRO, read_pv="X12SA-OP-CCM-ETTC-4010:TEMP", add_prefix=("",), name="ccm_temp_crystal", kind=Kind.omitted, doc="CCM Temp Crystal", auto_monitor=True, labels={"temp"})
|
||||
ccm_temp_shield_disaster = Cpt(EpicsSignalRO, read_pv="X12SA-OP-CCM-ETTC-4020:TEMP", add_prefix=("",), name="ccm_temp_shield_disaster", kind=Kind.omitted, doc="CCM Temp Shield (disaster)", auto_monitor=True, labels={"temp"})
|
||||
|
||||
ccm_heater_fault_1 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-CCM-EMSW-4010:SWITCH", add_prefix=("",), name="ccm_heater_fault_1", kind=Kind.omitted, doc="CCM Heater Fault 1", auto_monitor=True, labels={"fault"})
|
||||
ccm_heater_fault_2 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-CCM-EMSW-4020:SWITCH", add_prefix=("",), name="ccm_heater_fault_2", kind=Kind.omitted, doc="CCM Heater Fault 2", auto_monitor=True, labels={"fault"})
|
||||
ccm_heater_fault_3 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-CCM-EMSW-4030:SWITCH", add_prefix=("",), name="ccm_heater_fault_3", kind=Kind.omitted, doc="CCM Heater Fault 3", auto_monitor=True, labels={"fault"})
|
||||
|
||||
ccm_energy = Cpt(EpicsSignalRO, read_pv="X12SA-OP-CCM1:ENERGY-GET", add_prefix=("",), name="ccm_energy", kind=Kind.omitted, doc="CCM Energy", auto_monitor=True, labels={"energy"})
|
||||
ccm_position = Cpt(EpicsSignalRO, read_pv="X12SA-OP-CCM1:POSITION", add_prefix=("",), name="ccm_position", kind=Kind.omitted, doc="CCM Position", auto_monitor=True, labels={"string"})
|
||||
|
||||
|
||||
class CoolingWater(EPSSubDevices):
|
||||
"""Cooling water signals at the cSAXS beamline."""
|
||||
|
||||
op_sl1_efsw_2010_flow = Cpt(EpicsSignalRO, read_pv="X12SA-OP-SL1-EFSW-2010:FLOW", add_prefix=("",), name="op_sl1_efsw_2010_flow", kind=Kind.omitted, doc="OP-SL1-EFSW-2010", auto_monitor=True, labels={"flow"})
|
||||
op_sl2_efsw_2010_flow = Cpt(EpicsSignalRO, read_pv="X12SA-OP-SL2-EFSW-2010:FLOW", add_prefix=("",), name="op_sl2_efsw_2010_flow", kind=Kind.omitted, doc="OP-SL2-EFSW-2010", auto_monitor=True, labels={"flow"})
|
||||
op_eb1_efsw_5010_flow = Cpt(EpicsSignalRO, read_pv="X12SA-OP-EB1-EFSW-5010:FLOW", add_prefix=("",), name="op_eb1_efsw_5010_flow", kind=Kind.omitted, doc="OP-EB1-EFSW-5010", auto_monitor=True, labels={"flow"})
|
||||
op_eb1_efsw_5020_flow = Cpt(EpicsSignalRO, read_pv="X12SA-OP-EB1-EFSW-5020:FLOW", add_prefix=("",), name="op_eb1_efsw_5020_flow", kind=Kind.omitted, doc="OP-EB1-EFSW-5020", auto_monitor=True, labels={"flow"})
|
||||
op_sl3_efsw_5010_flow = Cpt(EpicsSignalRO, read_pv="X12SA-OP-SL3-EFSW-5010:FLOW", add_prefix=("",), name="op_sl3_efsw_5010_flow", kind=Kind.omitted, doc="OP-SL3-EFSW-5010", auto_monitor=True, labels={"flow"})
|
||||
op_kb_efsw_6010_flow = Cpt(EpicsSignalRO, read_pv="X12SA-OP-KB-EFSW-6010:FLOW", add_prefix=("",), name="op_kb_efsw_6010_flow", kind=Kind.omitted, doc="OP-KB-EFSW-6010", auto_monitor=True, labels={"flow"})
|
||||
op_psh1_efsw_7010_flow = Cpt(EpicsSignalRO, read_pv="X12SA-OP-PSH1-EFSW-7010:FLOW", add_prefix=("",), name="op_psh1_efsw_7010_flow", kind=Kind.omitted, doc="OP-PSH1-EFSW-7010", auto_monitor=True, labels={"flow"})
|
||||
es_eb2_efsw_1010_flow = Cpt(EpicsSignalRO, read_pv="X12SA-ES-EB2-EFSW-1010:FLOW", add_prefix=("",), name="es_eb2_efsw_1010_flow", kind=Kind.omitted, doc="ES-EB2-EFSW-1010", auto_monitor=True, labels={"flow"})
|
||||
|
||||
op_cs_ecvw_0010 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-CS-ECVW-0010:PLC_OPEN", add_prefix=("",), name="op_cs_ecvw_0010", kind=Kind.omitted, doc="OP-CS-ECVW-0010", auto_monitor=True, labels={"valve"})
|
||||
op_cs_ecvw_0020 = Cpt(EpicsSignalRO, read_pv="X12SA-OP-CS-ECVW-0020:PLC_OPEN", add_prefix=("",), name="op_cs_ecvw_0020", kind=Kind.omitted, doc="OP-CS-ECVW-0020", auto_monitor=True, labels={"valve"})
|
||||
|
||||
|
||||
class EPS(PSIDeviceBase):
|
||||
"""EPS device for the cSAXS beamline."""
|
||||
USER_ACCESS = [
|
||||
"show_all",
|
||||
"water_cooling_op",
|
||||
]
|
||||
alarms = Cpt(EPSAlarms, name="alarms", doc="EPS Alarms")
|
||||
valves_frontend = Cpt(ValvesFrontend, name="valves_frontend", doc="Valves Frontend")
|
||||
valves_optics = Cpt(ValvesOptics, name="valves_optics", doc="Valves Optics Hutch")
|
||||
valves_es = Cpt(ValvesEndstation, name="valves_es", doc="Valves ES Hutch")
|
||||
shutters_frontend = Cpt(ShuttersFrontend, name="shutters_frontend", doc="Shutters Frontend")
|
||||
shutters_es = Cpt(ShuttersEndstation, name="shutters_es", doc="Shutters Endstation")
|
||||
dmm_monochromator = Cpt(DMMMonochromator, name="dmm_monochromator", doc="DMM Monochromator")
|
||||
ccm_monochromator = Cpt(CCMMonochromator, name="ccm_monochromator", doc="CCM Monochromator")
|
||||
cooling_water = Cpt(CoolingWater, name="cooling_water", doc="Cooling Water")
|
||||
|
||||
# Acknowledgment signals for PLC communication (if needed for future use)
|
||||
ackerr = Cpt(EpicsSignal, read_pv="X12SA-EPS-PLC:ACKERR-REQUEST", add_prefix=("",), name="ackerr", kind=Kind.omitted, doc="ACKERR request - OP-CS-ECVW-0020", auto_monitor=True, labels={"request"})
|
||||
request = Cpt(EpicsSignal, read_pv="X12SA-OP-CS-ECVW:PLC_REQUEST", add_prefix=("",), name="op_cs_ecvw_request", kind=Kind.omitted, doc="PLC request - OP-CS-ECVW-PLC_REQUEST", auto_monitor=True, labels={"request"})
|
||||
|
||||
def _notify(self, msg: str, show_as_client_msg: bool = True):
|
||||
"""Utility method to print a message, and optionally send it to the client UI if it should be shown also as a client message."""
|
||||
try:
|
||||
if show_as_client_msg:
|
||||
self.device_manager.connector.send_client_info(msg, scope="", show_asap=True)
|
||||
else:
|
||||
print(msg)
|
||||
except Exception:
|
||||
logger.error(f"Failed to send client message, falling back to print: {msg}")
|
||||
print(str(msg))
|
||||
|
||||
# ----------------------------------------------------------
|
||||
# Water cooling operation
|
||||
# ----------------------------------------------------------
|
||||
|
||||
def safe_get(self, sig, default=None):
|
||||
"""Helper method to safely get a signal value, returning a default if there's an error."""
|
||||
try:
|
||||
return sig.get()
|
||||
except Exception as ex:
|
||||
logger.warning(f"Failed to get signal {sig.pvname}: {ex}")
|
||||
return default
|
||||
|
||||
def water_cooling_op(self):
|
||||
"""
|
||||
Open ECVW valves, reset EPS alarms, monitor for 20s,
|
||||
then ensure stability (valves remain open) for 10s.
|
||||
All messages sent to client.
|
||||
"""
|
||||
|
||||
POLL_PERIOD = 2
|
||||
TIMEOUT = 20
|
||||
STABILITY = 15
|
||||
|
||||
self._notify("=== Water Cooling Operation ===")
|
||||
|
||||
# --- Signals ---
|
||||
eps_alarm_sig = self.alarms.eps_alarm_cnt
|
||||
ackerr = self.ackerr
|
||||
request = self.request
|
||||
|
||||
valves = [self.cooling_water.op_cs_ecvw_0010, self.cooling_water.op_cs_ecvw_0020]
|
||||
|
||||
# Flow channels list extracted from CHANNELS
|
||||
flow_items = [walk.item for walk in self.cooling_water.walk_signals() if "flow" in walk.item._ophyd_labels_]
|
||||
|
||||
# --- Step 1: EPS alarm reset ---
|
||||
alarm_value = self.safe_get(eps_alarm_sig, 0)
|
||||
if alarm_value and alarm_value > 0:
|
||||
self._notify(f"[WaterCooling] EPS alarms present ({alarm_value}) → resetting…")
|
||||
try:
|
||||
ackerr.put(1)
|
||||
except Exception as ex:
|
||||
self._notify(f"[WaterCooling] WARNING: ACKERR write failed: {ex}")
|
||||
time.sleep(0.3)
|
||||
else:
|
||||
self._notify("[WaterCooling] No EPS alarms detected.")
|
||||
|
||||
# --- Step 2: Issue open request ---
|
||||
self._notify("[WaterCooling] Sending cooling-valve OPEN request…")
|
||||
try:
|
||||
request.put(1)
|
||||
except Exception as ex:
|
||||
self._notify(f"[WaterCooling] ERROR: Failed to send OPEN request: {ex}")
|
||||
return False
|
||||
|
||||
# --- Step 3: Monitoring loop (clean client table output) ---
|
||||
start = time.time()
|
||||
end = start + TIMEOUT
|
||||
stable_until = None
|
||||
|
||||
# Print (server-side) header once
|
||||
print("Monitoring valves and flow sensors...")
|
||||
print(f" Valves: {valves[0].attr_name[-4:]}, {valves[1].attr_name[-4:]}")
|
||||
print(f" Note: stability requires valves to remain OPEN for {STABILITY} seconds.")
|
||||
|
||||
# One table header to the client (via device manager)
|
||||
# Fixed-width columns for alignment in monospaced UI
|
||||
table_header = f"{'Time':>6} | {'Valves':<21} | {'Flows (OK/FAIL/N/A)':<20}"
|
||||
self._notify(table_header)
|
||||
|
||||
def snapshot():
|
||||
# Valve snapshot
|
||||
v_states = [self.safe_get(v, None) for v in valves]
|
||||
v1 = f"{valves[0].attr_name[-4:]}=" + ("OPEN " if v_states[0] is True or v_states[0] == 1 else "CLOSED" if v_states[0] is False or v_states[0] == 0 else "N/A ")
|
||||
v2 = f"{valves[1].attr_name[-4:]}=" + ("OPEN " if v_states[1] is True or v_states[1] == 1 else "CLOSED" if v_states[1] is False or v_states[1] == 0 else "N/A ")
|
||||
# 2 valves with a single space between => width ~ 21
|
||||
valve_str = f"{v1} {v2}"
|
||||
|
||||
# Flow summary: OK/FAIL/N/A counts (compact)
|
||||
flow_states = []
|
||||
for fsig in flow_items:
|
||||
fval = self.safe_get(fsig, None)
|
||||
flow_states.append(True if fval in (1, True) else False if fval in (0, False) else None)
|
||||
|
||||
ok = sum(1 for f in flow_states if f is True)
|
||||
fail = sum(1 for f in flow_states if f is False)
|
||||
na = sum(1 for f in flow_states if f is None)
|
||||
flow_summary = f"{ok:>2} / {fail:>2} / {na:>2}"
|
||||
|
||||
return v_states, valve_str, flow_summary
|
||||
|
||||
while True:
|
||||
# TODO Consider adding a timeout to avoid infinite loop.
|
||||
now = time.time()
|
||||
elapsed = int(now - start)
|
||||
|
||||
if now > end:
|
||||
# One last line to client
|
||||
v_states, valves_s, flows_s = snapshot()
|
||||
self._notify(f"{elapsed:>6}s | {valves_s:<21} | {flows_s:<20}")
|
||||
print("→ TIMEOUT: Cooling valves failed to remain OPEN.")
|
||||
return False
|
||||
|
||||
# Live snapshot
|
||||
v_states, valves_s, flows_s = snapshot()
|
||||
# Exactly one concise line to client per cycle
|
||||
self._notify(f"{elapsed:>6}s | {valves_s:<21} | {flows_s:<20}")
|
||||
|
||||
both_open = all(s is not None and bool(s) for s in v_states)
|
||||
|
||||
if both_open:
|
||||
if stable_until is None:
|
||||
stable_until = now + STABILITY
|
||||
print(f"[WaterCooling] Both valves OPEN → starting {STABILITY}s stability window…")
|
||||
else:
|
||||
if now >= stable_until:
|
||||
print("→ SUCCESS: Valves remained OPEN during stability window.")
|
||||
return True
|
||||
else:
|
||||
if stable_until is not None:
|
||||
print("[WaterCooling] Valve closed again → restarting stability window.")
|
||||
stable_until = None
|
||||
|
||||
time.sleep(POLL_PERIOD)
|
||||
|
||||
def show_all(self):
|
||||
red = "\x1b[91m"
|
||||
green = "\x1b[92m"
|
||||
white = "\x1b[0m"
|
||||
bold = "\x1b[1m"
|
||||
cyan = "\x1b[96m"
|
||||
|
||||
# ---- New: enum maps for numeric -> string rendering ----
|
||||
POSITION_ENUM = {0: "out of beam", 1: "in beam"}
|
||||
STRIPE_ENUM = {0: "Stripe 1 W/B4C", 1: "Stripe 2 NiV/B4C"}
|
||||
POSITION_ATTRS = {self.dmm_monochromator.dmm_position.attr_name, self.ccm_monochromator.ccm_position.attr_name}
|
||||
STRIPE_ATTRS = {self.dmm_monochromator.dmm_stripe.attr_name}
|
||||
|
||||
def is_bool_like(v):
|
||||
return isinstance(v, (bool, int)) and v in (0, 1, True, False)
|
||||
|
||||
# ---- Changed: accept attr in formatter so we can apply enum mapping ----
|
||||
def fmt_value(value: any, signal: EpicsSignalRO):
|
||||
if value is None:
|
||||
return f"{red}MISSING{white}"
|
||||
|
||||
attr = signal.attr_name
|
||||
|
||||
# ---------- Explicit enum mappings by attribute ----------
|
||||
if attr in POSITION_ATTRS:
|
||||
# Position comes as numeric 0/1
|
||||
try:
|
||||
iv = int(value)
|
||||
return POSITION_ENUM.get(iv, f"{iv}")
|
||||
except Exception:
|
||||
# Fallback if it’s already a string or unexpected
|
||||
return f"{value}"
|
||||
|
||||
if attr in STRIPE_ATTRS:
|
||||
# Stripe comes as numeric 0/1
|
||||
try:
|
||||
iv = int(value)
|
||||
return STRIPE_ENUM.get(iv, f"{iv}")
|
||||
except Exception:
|
||||
return f"{value}"
|
||||
|
||||
# ------------------- TEMPERATURE -------------------
|
||||
if "temp" in signal._ophyd_labels_ and isinstance(value, (int, float)):
|
||||
return f"{value:.1f}"
|
||||
|
||||
# ------------------- ENERGY ------------------------
|
||||
if "energy" in signal._ophyd_labels_ and isinstance(value, (int, float)):
|
||||
return f"{value:.4f}"
|
||||
|
||||
# ------------------- STRINGS -----------------------
|
||||
if "string" in signal._ophyd_labels_ or "position" in signal._ophyd_labels_:
|
||||
# For other strings, just echo the value
|
||||
return f"{value}"
|
||||
|
||||
# ------------------- SWITCH (ACTIVE/INACTIVE) ------
|
||||
if "switch" in signal._ophyd_labels_ and is_bool_like(value):
|
||||
return f"{green+'ACTIVE'+white if value else red+'INACTIVE'+white}"
|
||||
|
||||
# ------------------- FAULT (OK/FAULT) --------------
|
||||
if "fault" in signal._ophyd_labels_ and is_bool_like(value):
|
||||
return f"{green+'OK'+white if not value else red+'FAULT'+white}"
|
||||
|
||||
# ------------------- VALVE/SHUTTER -----------------
|
||||
if ("valve" in signal._ophyd_labels_ or "shutter" in signal._ophyd_labels_) and is_bool_like(value):
|
||||
return f"{green+'OPEN'+white if value else red+'CLOSED'+white}"
|
||||
|
||||
# ------------------- FLOW (OK/FAIL) ----------------
|
||||
if "flow" in signal._ophyd_labels_ and is_bool_like(value):
|
||||
return f"{green}OK{white}" if bool(value) else f"{red}FAIL{white}"
|
||||
|
||||
# ------------------- FALLBACK -----------------------
|
||||
return f"{value}"
|
||||
|
||||
# ------------------- PRINT START ---------------------
|
||||
print(f"{bold}X12SA EPS status{white}")
|
||||
|
||||
for name, component in self._sig_attrs.items():
|
||||
sub_device = getattr(self, name)
|
||||
rows = []
|
||||
# Only print sub-devices, not individual request signals
|
||||
if not isinstance(sub_device, Device):
|
||||
continue
|
||||
print(f"\n{bold}{component.doc}{white}")
|
||||
for sub_walk in sub_device.walk_components():
|
||||
cpt: Cpt = sub_walk.item
|
||||
it: EpicsSignalRO = getattr(sub_device, cpt.attr)
|
||||
val = self.safe_get(it)
|
||||
rows.append((cpt.doc, val, it))
|
||||
|
||||
label_width = max(32, *(len(label) for (label, _, _) in rows))
|
||||
|
||||
for label, value, it in rows:
|
||||
fv = fmt_value(value, it) # <-- pass attr to formatter
|
||||
print(f" - {label:<{label_width}} {fv}")
|
||||
|
||||
if sub_device.attr_name == "cooling_water":
|
||||
v1 = self.safe_get(self.cooling_water.op_cs_ecvw_0010)
|
||||
v2 = self.safe_get(self.cooling_water.op_cs_ecvw_0020)
|
||||
|
||||
def closed(v):
|
||||
return is_bool_like(v) and not bool(v)
|
||||
|
||||
if closed(v1) and closed(v2):
|
||||
print(f"\n{cyan}Hint:{white} Both water cooling valves are CLOSED.\n" f"You can open them using: {bold}dev.x12saEPS.water_cooling_op(){white}")
|
||||
|
||||
# fmt: on
|
||||
# ----------------------------------------------------------
|
||||
# Consistency report
|
||||
# ----------------------------------------------------------
|
||||
# def consistency_report(self, *, verbose=True):
|
||||
# missing = []
|
||||
# dupes = []
|
||||
# seen = {}
|
||||
|
||||
# for sub_device in self.walk_components():
|
||||
# section = sub_device.name
|
||||
# for walk in sub_device.walk_components():
|
||||
# cpt: Cpt = walk.ancestors[-1]
|
||||
# it: EpicsSignalRO = walk.item
|
||||
# if not hasattr(self, it["attr"]):
|
||||
# missing.append((section, it["attr"], it["label"], it["pv"]))
|
||||
|
||||
# pv = it["pv"]
|
||||
# if pv in seen:
|
||||
# dupes.append((pv, seen[pv], (section, it["attr"], it["label"])))
|
||||
# else:
|
||||
# seen[pv] = (section, it["attr"], it["label"])
|
||||
|
||||
# if verbose:
|
||||
# print("=== Consistency Report ===")
|
||||
|
||||
# if missing:
|
||||
# print("\nMissing attributes:")
|
||||
# for sec, a, lbl, pv in missing:
|
||||
# print(f" - [{sec}] {a} {lbl} pv={pv}")
|
||||
# else:
|
||||
# print("\nNo missing attributes.")
|
||||
|
||||
# if dupes:
|
||||
# print("\nDuplicate PVs:")
|
||||
# for pv, f1, f2 in dupes:
|
||||
# print(f" {pv} → {f1} AND {f2}")
|
||||
# else:
|
||||
# print("\nNo duplicate PVs.")
|
||||
|
||||
# return {"missing_attrs": missing, "duplicate_pvs": dupes}
|
||||
@@ -1,67 +0,0 @@
|
||||
"""
|
||||
Shutter device for the cSAXS beamline with 2 PVs. One is connected to a
|
||||
signal can be set to control the shutter signal, and the other is a readback signal
|
||||
that can be monitored to check the shutter status as it may be controlled directly by
|
||||
the delay generator."""
|
||||
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, EpicsSignal, EpicsSignalRO, Kind
|
||||
|
||||
|
||||
class cSAXSFastEpicsShutter(Device):
|
||||
"""
|
||||
Fast EPICS shutter with automatic PV selection based on host subnet. IOC prefix is 'X12SA-ES1-TTL:'
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["fshopen", "fshclose", "fshstatus", "fshinfo", "fshstatus_readback", "help"]
|
||||
SUB_VALUE = "value"
|
||||
_default_sub = SUB_VALUE
|
||||
|
||||
# PVs
|
||||
shutter = Cpt(EpicsSignal, "OUT_01", kind=Kind.normal, auto_monitor=True)
|
||||
shutter_readback = Cpt(EpicsSignalRO, "INP_01", kind=Kind.normal, auto_monitor=True)
|
||||
|
||||
# -----------------------------------------------------
|
||||
# User-facing shutter control functions
|
||||
# -----------------------------------------------------
|
||||
|
||||
# pylint: disable=protetced-access
|
||||
def fshopen(self) -> None:
|
||||
"""Open the fast shutter."""
|
||||
self.shutter.set(1).wait(timeout=self.shutter._timeout) # 2s default for ES
|
||||
|
||||
# pylint: disable=protetced-access
|
||||
def fshclose(self) -> None:
|
||||
"""Close the fast shutter."""
|
||||
self.shutter.set(0).wait(timeout=self.shutter._timeout) # 2s default for ES
|
||||
|
||||
def fshstatus(self) -> int:
|
||||
"""Return the fast shutter control status (0=closed, 1=open)."""
|
||||
return self.shutter.get() # Ensure we have the latest value from EPICS
|
||||
|
||||
def fshstatus_readback(self) -> int:
|
||||
"""Return the fast shutter status (0=closed, 1=open)."""
|
||||
return self.shutter_readback.get() # Ensure we have the latest value from EPICS
|
||||
|
||||
def fshinfo(self) -> None:
|
||||
"""Print information about which EPICS PV channel is being used."""
|
||||
pvname = self.shutter.pvname
|
||||
shutter_readback_pvname = self.shutter_readback.pvname
|
||||
print(
|
||||
f"Fast shutter connected to EPICS channel: {pvname} with shutter readback: {shutter_readback_pvname}"
|
||||
)
|
||||
|
||||
def stop(self, *, success: bool = False) -> None:
|
||||
"""Stop the shutter device. Make sure to close it."""
|
||||
self.shutter.put(0)
|
||||
super().stop(success=success)
|
||||
|
||||
def help(self):
|
||||
"""Display available user methods."""
|
||||
print("Available methods:")
|
||||
for method in self.USER_ACCESS:
|
||||
print(f" - {method}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
fsh = cSAXSFastEpicsShutter(name="fsh", prefix="X12SA-ES1-TTL:")
|
||||
@@ -1,13 +0,0 @@
|
||||
# MCS Card implementation at the CSAXS beamline
|
||||
|
||||
This module provides an ophyd device implementation for the SIS3820 Multi-Channel Scaler (MCS) card, used at the cSAXS beamline for time-resolved data acquisition. It interfaces with the EPICS IOC for the SIS3820 MCS card.
|
||||
Information about the EPICS driver can be found here (https://millenia.cars.aps.anl.gov/software/epics/mcaStruck.html).
|
||||
|
||||
# Important Notes
|
||||
Operation of the MCS card requires proper configuration as some of the parameters are interdependent. In addition, empirical adjustments have been found to be necessary for optimal performance at the beamline. In its current implementation, comments about these dependencies are highlighted in the source code of the ophyd device classes [MCSCard](./mcs_card.py) and [MCSCardCSAXS](./mcs_card_csaxs.py). It is highly recommended to review these comments before refactoring, modifying, or extending the code.
|
||||
|
||||
## Ophyd Device Implementation
|
||||
|
||||
The ophyd device implementation is provided [MCSCard](./mcs_card.py). This class provides a basic interface to the MCS PVs, including configuration of parameters such as number of channels, dwell time, and control of acquisition start/stop. Please check the source code of the class for more details of the implementation.
|
||||
|
||||
The [MCSCardCSAXS](./mcs_card_csaxs.py) class extends the basic MCSCard implementation with cSAXS-specific logic and configurations. Please be aware that this is also linked to the implementation of other devices, most notably the [delay generator integration](../delay_generator_csaxs/README.md), which is used as the trigger source for the MCS card during operation.
|
||||
@@ -170,12 +170,11 @@ class MCSCard(Device):
|
||||
kind=Kind.omitted,
|
||||
doc="Indicates whether the SNL program has connected to all PVs.",
|
||||
)
|
||||
# NOTE: Please note that the erase_all command sends the mca or waveform records to process after erasing, potentially also values of 0. This logic needs to be considered when running callbacks on the mca channels.
|
||||
erase_all = Cpt(
|
||||
EpicsSignal,
|
||||
"EraseAll",
|
||||
kind=Kind.omitted,
|
||||
doc="Erases all mca or waveform records, setting elapsed times and counts in all channels to 0. Please note that this operation sends the mca or waveform records to process after erasing, potentially also 0s.",
|
||||
doc="Erases all mca or waveform records, setting elapsed times and counts in all channels to 0.",
|
||||
)
|
||||
erase_start = Cpt(
|
||||
EpicsSignal,
|
||||
@@ -193,7 +192,6 @@ class MCSCard(Device):
|
||||
EpicsSignalRO,
|
||||
"Acquiring",
|
||||
kind=Kind.omitted,
|
||||
auto_monitor=True,
|
||||
doc="Acquiring (=1) when acquisition is in progress and Done (=0) when acquisition is complete.",
|
||||
)
|
||||
stop_all = Cpt(EpicsSignal, "StopAll", kind=Kind.omitted, doc="Stops acquisition.")
|
||||
@@ -281,12 +279,11 @@ class MCSCard(Device):
|
||||
kind=Kind.omitted,
|
||||
doc="The current acquisition mode (MCS=0 or Scaler=1). This record is used to turn off the scaler record Autocount in MCS mode.",
|
||||
)
|
||||
# NOTE: Setting mux_output programmatically results in occasional errors on the IOC; it is recommended to avoid using it.
|
||||
mux_output = Cpt(
|
||||
EpicsSignal,
|
||||
"MUXOutput",
|
||||
kind=Kind.omitted,
|
||||
doc="Value of 0-32 used to select which input signal is routed to output signal 7 on the SIS3820 in output mode 3. NOTE: This settings seems to occasionally result in errors on the IOC; it is recommended to avoid using it.",
|
||||
doc="Value of 0-32 used to select which input signal is routed to output signal 7 on the SIS3820 in output mode 3.",
|
||||
)
|
||||
user_led = Cpt(
|
||||
EpicsSignal,
|
||||
|
||||
@@ -1,34 +1,16 @@
|
||||
"""
|
||||
Module for the MCSCard CSAXS implementation at cSAXS.
|
||||
|
||||
Please respect the comments regarding timing and procedures of the MCS card. These
|
||||
are highlighted with NOTE comments directly in the code, indicating requirements
|
||||
for stable device operation. Most of these constraints were identified
|
||||
empirically through extensive testing with the SIS3820 MCS card IOC and are intended
|
||||
to prevent unexpected hardware or IOC behavior.
|
||||
"""
|
||||
"""Module for the MCSCard CSAXS implementation."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from contextlib import contextmanager
|
||||
from functools import partial
|
||||
import enum
|
||||
from threading import RLock
|
||||
from typing import TYPE_CHECKING, Callable, Literal
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import numpy as np
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import EpicsSignalRO, Kind
|
||||
from ophyd_devices import (
|
||||
AsyncMultiSignal,
|
||||
CompareStatus,
|
||||
ProgressSignal,
|
||||
StatusBase,
|
||||
TransitionStatus,
|
||||
)
|
||||
from ophyd import Device, EpicsSignalRO, Kind, Signal
|
||||
from ophyd_devices import CompareStatus, ProgressSignal, TransitionStatus
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import PSIDeviceBase
|
||||
|
||||
from csaxs_bec.devices.epics.mcs_card.mcs_card import (
|
||||
@@ -42,37 +24,7 @@ from csaxs_bec.devices.epics.mcs_card.mcs_card import (
|
||||
READMODE,
|
||||
MCSCard,
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def suppress_mca_callbacks(mcs_card: MCSCard, restore_after_timeout: None | float = None):
|
||||
"""
|
||||
Utility context manager to suppress MCA channel callbacks temporarily.
|
||||
It is required because erasing all channels via 'erase_all' PV triggers
|
||||
callbacks for each channel. Depending on timing, this can interfere with
|
||||
ongoing data acquisition so this context manager can be used to suppress
|
||||
those callbacks temporarily. If used with restore_after_timeout, the suppression
|
||||
will be automatically cleared after the specified timeout in seconds.
|
||||
|
||||
NOTE: Please be aware that it does not restore previous state, which means
|
||||
that _omit_mca_callbacks will remain set after exiting the context. It has
|
||||
to be cleared manually if needed. This can be improved in the future, but
|
||||
should be carefully coordinated with the logic implemented within '_on_counter_update'.
|
||||
|
||||
Args:
|
||||
mcs_card (MCSCard): The MCSCard instance to suppress callbacks for.
|
||||
restore_after_timeout (float | None): Optional timeout in seconds to automatically
|
||||
clear the suppression after the specified time. If None, the original state
|
||||
is not restored.
|
||||
"""
|
||||
mcs_card._omit_mca_callbacks.set() # pylint: disable=protected-access
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
if restore_after_timeout is not None:
|
||||
time.sleep(restore_after_timeout)
|
||||
mcs_card._omit_mca_callbacks.clear() # pylint: disable=protected-access
|
||||
|
||||
from csaxs_bec.devices.epics.xbpms import DiffXYSignal, SumSignal
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import DeviceManagerBase, ScanInfo
|
||||
@@ -80,50 +32,76 @@ if TYPE_CHECKING: # pragma: no cover
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class READYTOREAD(int, enum.Enum):
|
||||
|
||||
PROCESSING = 0
|
||||
DONE = 1
|
||||
|
||||
|
||||
class BPMDevice(Device):
|
||||
"""Class for BPM device of the MCSCard."""
|
||||
|
||||
current1 = Cpt(Signal, kind=Kind.normal, doc="Normalized current 1")
|
||||
current2 = Cpt(Signal, kind=Kind.normal, doc="Normalized current 2")
|
||||
current3 = Cpt(Signal, kind=Kind.normal, doc="Normalized current 3")
|
||||
current4 = Cpt(Signal, kind=Kind.normal, doc="Normalized current 4")
|
||||
count_time = Cpt(Signal, kind=Kind.normal, doc="Count time for bpm signal counts")
|
||||
sum = Cpt(SumSignal, kind="hinted", doc="Sum of all currents")
|
||||
x = Cpt(
|
||||
DiffXYSignal,
|
||||
sum1=["current1", "current2"],
|
||||
sum2=["current3", "current4"],
|
||||
doc="X difference signal",
|
||||
)
|
||||
y = Cpt(
|
||||
DiffXYSignal,
|
||||
sum1=["current1", "current3"],
|
||||
sum2=["current2", "current4"],
|
||||
doc="Y difference signal",
|
||||
)
|
||||
diag = Cpt(
|
||||
DiffXYSignal,
|
||||
sum1=["current1", "current4"],
|
||||
sum2=["current2", "current3"],
|
||||
doc="Diagonal difference signal",
|
||||
)
|
||||
|
||||
|
||||
class MCSRaw(Device):
|
||||
"""Class for BPM device of the MCSCard with normalized currents."""
|
||||
|
||||
mca1 = Cpt(Signal, kind=Kind.normal, doc="Raw counts on mca1 channel")
|
||||
mca2 = Cpt(Signal, kind=Kind.normal, doc="Raw counts on mca2 channel")
|
||||
mca3 = Cpt(Signal, kind=Kind.normal, doc="Raw counts on mca3 channel")
|
||||
mca4 = Cpt(Signal, kind=Kind.normal, doc="Raw counts on mca4 channel")
|
||||
mca5 = Cpt(Signal, kind=Kind.normal, doc="Raw counts on mca5 channel")
|
||||
|
||||
|
||||
class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
||||
"""
|
||||
Implementation of the MCSCard SIS3820 for CSAXS, prefix 'X12SA-MCS:'.
|
||||
The basic functionality is inherited from the MCSCard class.
|
||||
|
||||
Please note that the number of channels is fixed to 32, so there will be data for all
|
||||
32 channels. In addition, the logic of the card is linked to the timing system (DDG)
|
||||
and therefore changes have to be coordinated with the logic on the DDG side.
|
||||
|
||||
Args:
|
||||
name (str): Name of the device.
|
||||
prefix (str, optional): Prefix for the EPICS PVs. Defaults to "".
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["mcs_recovery"]
|
||||
|
||||
# NOTE The number of MCA channels is fixed to 32 for the CSAXS MCS card.
|
||||
# On the IOC, we receive a 'warning' or 'error' once we set this channel for the
|
||||
# envisioned input/output mode settings of the card. However, we need to know the
|
||||
# channels set as callback timing relies on the channels to be set.
|
||||
# For the future, we may consider adding an initialization parameter to set
|
||||
# the number of channels, which in return limits the number of subscriptions
|
||||
# on the channels. However, mux_output should still be set to 32 on the IOC side.
|
||||
# If this limits performance, this should be investigated with Controls engineers and
|
||||
# the IOC.
|
||||
NUM_MCA_CHANNELS: int = 32
|
||||
|
||||
# MCA counters for the card. Channels 1-32 will be sent to BEC.
|
||||
mca = Cpt(
|
||||
AsyncMultiSignal,
|
||||
name="counters",
|
||||
signals=[
|
||||
f"mca{i}" for i in range(1, 33)
|
||||
], # NOTE Channels 1-32, they need to be in sync with the 'counters' component (DynamicDeviceComponent) of the MCSCard
|
||||
ndim=1,
|
||||
async_update={"type": "add", "max_shape": [None]},
|
||||
max_size=1000,
|
||||
kind=Kind.normal,
|
||||
doc=(
|
||||
"AsyncMultiSignal for MCA card channels 1-32."
|
||||
"Cabling of the MCS card determines which channel corresponds to which input."
|
||||
),
|
||||
ready_to_read = Cpt(
|
||||
Signal,
|
||||
kind=Kind.omitted,
|
||||
doc="Signal that indicates if mcs card is ready to be read from after triggers. 0 not ready, 1 ready",
|
||||
)
|
||||
progress: ProgressSignal = Cpt(ProgressSignal, name="progress")
|
||||
# Make this an async signal..
|
||||
mcs = Cpt(
|
||||
MCSRaw,
|
||||
name="mcs",
|
||||
kind=Kind.normal,
|
||||
doc="MCS device with raw current and count time readings",
|
||||
)
|
||||
bpm = Cpt(
|
||||
BPMDevice,
|
||||
name="bpm",
|
||||
kind=Kind.normal,
|
||||
doc="BPM device for MCSCard with count times and normalized currents",
|
||||
)
|
||||
progress = Cpt(ProgressSignal, doc="ProgressSignal indicating the progress of the device")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -133,77 +111,39 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
||||
device_manager: DeviceManagerBase | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Initialize the MCSCardCSAXS with the given arguments and keyword arguments.
|
||||
"""
|
||||
super().__init__(
|
||||
name=name, prefix=prefix, scan_info=scan_info, device_manager=device_manager, **kwargs
|
||||
)
|
||||
# NOTE MCS Clock frequency. This is linked to the settings of the SIS3820 IOC and
|
||||
# cabeling of the card. Currently, the 'output_mode' is set to MODE_2 and one of the outputs
|
||||
# 6 or 7 (both 10MHz clocks) is used on channel 5 input for the timing signal of the IOC.
|
||||
# Please adjust this comment if the cabling or IOC settings change.
|
||||
self._mcs_clock = 1e7 # 10MHz clock -> 1e7 Hz
|
||||
self._pv_timeout = 2.0 # seconds
|
||||
self._rlock = RLock()
|
||||
|
||||
# NOTE This parameter will be sent with async data of the mcs counters.
|
||||
# Based on scan-paramters, e.g. frames_per_trigger, this will be either
|
||||
# 'monitored' or 'burst_group'. This means whether data from this channel
|
||||
# is in sync with monitored devices or another group. In this scenario,
|
||||
# the other group is called burst_group. Other detectors connected and
|
||||
# triggered through the same timing system should implement the same logic
|
||||
# to allow data to be properly grouped afterwards.
|
||||
self._acquisition_group: str = "monitored" # default value, will be updated in on_stage
|
||||
self._num_total_triggers: int = 0
|
||||
|
||||
# Thread and event logic for monitoring async data emission after scan is done
|
||||
# These are mostly internal variables for which values should not be changed externally.
|
||||
# Adjusting the logic of them should also be handled with care and proper testing.
|
||||
self._scan_done_thread_kill_event: threading.Event = threading.Event()
|
||||
self._start_monitor_async_data_emission: threading.Event = threading.Event()
|
||||
self._scan_done_callbacks: list[Callable[[], None]] = []
|
||||
self._scan_done_thread: threading.Thread = threading.Thread(
|
||||
target=self._monitor_async_data_emission, daemon=True
|
||||
)
|
||||
self._current_data_index: int = 0
|
||||
self._mca_counter_index: int = 0
|
||||
self._current_data: dict[str, dict[Literal["value", "timestamp"], list[int] | float]] = {}
|
||||
self._omit_mca_callbacks: threading.Event = threading.Event()
|
||||
self._pv_timeout = 3 # TODO remove timeout once #129 in ophyd_devices is solved
|
||||
self._rlock = RLock() # Needed to ensure thread safety for counter updates
|
||||
self.counter_mapping = { # Any mca counter that should be updated has to be added here
|
||||
f"{self.counters.name}_mca1": "current1",
|
||||
f"{self.counters.name}_mca2": "current2",
|
||||
f"{self.counters.name}_mca3": "current3",
|
||||
f"{self.counters.name}_mca4": "current4",
|
||||
f"{self.counters.name}_mca5": "count_time",
|
||||
}
|
||||
self.counter_updated = []
|
||||
|
||||
def on_connected(self):
|
||||
"""
|
||||
This method is called once the device and all its PVs are connected. Any initial
|
||||
setup of PVs should be managed here. Please be aware that settings of the MCS card
|
||||
correlate with its operation mode, input/output modes, and timing. Changing single
|
||||
parameters without understanding the overall logic may lead to unexpected behavior
|
||||
of the device.Therefore, any modification of these parameters should be handled
|
||||
with care and tested.
|
||||
|
||||
A brief summary of the procesdure that is implemented here:
|
||||
- Stop any ongoing acquisiton.
|
||||
- Setup the Initial initial settings of the MCS card with respective operation modes
|
||||
- Run 'mcs_recovery' procedure to ensure that no pending acquisition data is scheduled
|
||||
to be pushed through mcs channels
|
||||
- Subscribe a callback '_on_counter_update' to mcs counter PVs to forward
|
||||
data through AsyncMultiSignal to BEC
|
||||
- Start the monitoring thread for async data emission after scan is done
|
||||
Called when the device is connected.
|
||||
"""
|
||||
# NOTE Stop any ongoing acquisition first. This shut be done before setting any PVs.
|
||||
# Make sure card is not running
|
||||
self.stop_all.put(1)
|
||||
|
||||
#########################
|
||||
### Setup MCS Card ###
|
||||
#########################
|
||||
# Setup the MCS card settings. Please note that any runtime modification
|
||||
# these parameter may lead to unexpected behavior of the device.
|
||||
# Therefore this has to be set up correctly.
|
||||
# TODO Check channel1_source !!
|
||||
self.channel_advance.set(CHANNELADVANCE.EXTERNAL).wait(timeout=self._pv_timeout)
|
||||
self.channel1_source.set(CHANNEL1SOURCE.EXTERNAL).wait(timeout=self._pv_timeout)
|
||||
self.prescale.set(1).wait(timeout=self._pv_timeout)
|
||||
# Set the user LED to off
|
||||
self.user_led.set(0).wait(timeout=self._pv_timeout)
|
||||
|
||||
# NOTE The number of output channels has to be set to NUM_MCA_CHANNELS.
|
||||
# The logic to send data to BEC relies on knowing how many channels are active.
|
||||
self.mux_output.put(self.NUM_MCA_CHANNELS)
|
||||
|
||||
# Only channel 1-5 are connected so far, adjust if more are needed
|
||||
self.mux_output.set(5).wait(timeout=self._pv_timeout)
|
||||
# Set the input and output modes & polarities
|
||||
self.input_mode.set(INPUTMODE.MODE_3).wait(timeout=self._pv_timeout)
|
||||
self.input_polarity.set(POLARITY.NORMAL).wait(timeout=self._pv_timeout)
|
||||
@@ -211,370 +151,134 @@ class MCSCardCSAXS(PSIDeviceBase, MCSCard):
|
||||
self.output_polarity.set(POLARITY.NORMAL).wait(timeout=self._pv_timeout)
|
||||
self.count_on_start.set(0).wait(timeout=self._pv_timeout)
|
||||
|
||||
# NOTE Data is read out when the MCS card finishes an acquisition. The logic for this
|
||||
# is also linked to triggering on the DDG.
|
||||
# Set ReadMode to PASSIVE, the card will wait either wait for readout command or
|
||||
# automatically readout once acquisition is done.
|
||||
# Set appropriate read mode
|
||||
self.read_mode.set(READMODE.PASSIVE).wait(timeout=self._pv_timeout)
|
||||
|
||||
# Set the acquire mode
|
||||
self.acquire_mode.set(ACQUIREMODE.MCS).wait(timeout=self._pv_timeout)
|
||||
|
||||
# Subscribe the progress signal
|
||||
self.current_channel.subscribe(self._progress_update, run=False)
|
||||
# self.current_channel.subscribe(self._progress_update, run=False)
|
||||
|
||||
# NOTE: Run a recovery procedure to ensure that the card has no pending data
|
||||
# that needs to be pushed through the mca channels. The procedure involves
|
||||
# stopping any ongoing acquisition and erasing all data on the card. Including
|
||||
# a short sleep to allow the IOC to process the commands.
|
||||
self.mcs_recovery(timeout=1)
|
||||
# Subscribe to the mca updates
|
||||
for name in self.counter_mapping.keys():
|
||||
sig: EpicsSignalRO = getattr(self.counters, name.split("_")[-1])
|
||||
sig.subscribe(self._on_counter_update, run=False)
|
||||
|
||||
####################################
|
||||
### Setup MCS Subscriptions ###
|
||||
####################################
|
||||
for sig in self.counters.component_names:
|
||||
sig_obj: EpicsSignalRO = getattr(self.counters, sig)
|
||||
sig_obj.subscribe(self._on_counter_update, run=False)
|
||||
|
||||
# Start monitoring thread
|
||||
self._scan_done_thread.start()
|
||||
|
||||
def _on_counter_update(self, value: float | np.ndarray, **kwargs) -> None:
|
||||
def _on_counter_update(self, value, **kwargs) -> None:
|
||||
"""
|
||||
Callback for counter updates of the mca channels (1-32). This callback is attached
|
||||
to each mca channel PV on the MCS card. It collects data from all channels
|
||||
and once all channels have been updated for a given acquisition, it pushes
|
||||
the data to BEC through the AsyncMultiSignal 'mca'.
|
||||
Callback for counter updates of the mca channels (1-32).
|
||||
|
||||
It is important that mux_output is set to the correct number of channels in on_connected,
|
||||
because the callback here waits for updates on all channels before pushing data to BEC.
|
||||
The raw data is pushed to the mcs sub-device (MCSRaw). We need to ensure that
|
||||
the MCSRaw device has all signals defined for which we want to push the values.
|
||||
|
||||
The _rlock is used to ensure thread safety as multiple callbacks may be executed
|
||||
simultaneously from different threads.
|
||||
As we may receive multiple readings per point, e.g. if frames_per_trigger > 1,
|
||||
we also create a mean value for the counter signals. These are then pushed to the bpm device
|
||||
for plotting and further processing. The signal names are defined and mapped in the
|
||||
self.counter_mapping dictionary & the bpm sub-device.
|
||||
|
||||
If _omit_mca_callbacks is set, the callback will return immediately without processing the
|
||||
data. This is used when erasing all channels to avoid interference with ongoing acquisition.
|
||||
It has to manually cleared after the context manager 'suppress_mca_callbacks' is used.
|
||||
|
||||
Args:
|
||||
value: The new value from the counter PV.
|
||||
**kwargs: Additional keyword arguments from the subscription, including 'obj' (the EpicsSignalRO instance).
|
||||
There are multiple mca channels, each giving individual updates. We want to ensure that
|
||||
each is updated before we signal that we are ready to read. In future, these signals may
|
||||
become asynchronous, but we first need to ensure that we can properly combine monitored
|
||||
signals with async signals for plotting. Until then, we will keep this logic.
|
||||
"""
|
||||
with self._rlock:
|
||||
logger.info(f"Received update on mcs card {self.name}")
|
||||
if self._omit_mca_callbacks.is_set():
|
||||
return # Suppress callbacks when erasing all channels
|
||||
self._mca_counter_index += 1
|
||||
signal: EpicsSignalRO | None = kwargs.get("obj", None)
|
||||
if signal is None:
|
||||
logger.error(f"Called without 'obj' in kwargs: {kwargs}")
|
||||
# Retrieve the signal object which executes this callback
|
||||
signal = kwargs.get("obj", None)
|
||||
if signal is None: # This should never happen, but just in case
|
||||
logger.info(f"Called without 'obj' in kwargs: {kwargs}")
|
||||
return
|
||||
|
||||
# NOTE: This relies on the naming convention of the mca channels being 'mca1', 'mca2', ..., 'mca32'.
|
||||
# for the MCSCard class with the 'counters' DynamicDeviceComponent.
|
||||
# Ignore any updates from channels beyond NUM_MCA_CHANNELS
|
||||
attr_name = signal.attr_name
|
||||
index = int(attr_name[3:]) # Extract index from 'mcaX'
|
||||
if index > self.NUM_MCA_CHANNELS:
|
||||
# Get the maped signal name from the mapping dictionary
|
||||
mapped_signal_name = self.counter_mapping.get(signal.name, None)
|
||||
# If we did not map the signal name in counter_mapping, but receive an update
|
||||
# we will skip it.
|
||||
if mapped_signal_name is None:
|
||||
return
|
||||
|
||||
# NOTE Depending on the scan parameters, we may either receive single values or numpy arrays.
|
||||
# Therefore, we need to handle both cases here to ensure that data is always stored. We do
|
||||
# this by converting single values to a list with one element, and numpy arrays to lists.
|
||||
# Push the raw values of the mca channels. The signal name has to be defined
|
||||
# in the self.mcs sub-device (MCSRaw) to be able to push the values. Otherwise
|
||||
# we will skip the update.
|
||||
mca_raw = getattr(self.mcs, signal.name.split("_")[-1], None)
|
||||
if mca_raw is None:
|
||||
return
|
||||
# In case there was more than one value received, i.e. frames_per_trigger > 1,
|
||||
# we will receive a np.array of values.
|
||||
if isinstance(value, np.ndarray):
|
||||
value = value.tolist() # Convert numpy array to list
|
||||
# We push the raw values as a list to the mca_raw signal
|
||||
# And otherwise compute the mean value for plotting of counter signals
|
||||
mca_raw.put(value.tolist())
|
||||
# compute the count_time in seconds
|
||||
if mapped_signal_name == "count_time":
|
||||
value = value / self._mcs_clock
|
||||
value = float(value.mean())
|
||||
else:
|
||||
value = [value] # Received single value, convert to list
|
||||
# We received a single value, so we can directly push it
|
||||
mca_raw.put(value)
|
||||
# compute the count_time in seconds
|
||||
if mapped_signal_name == "count_time":
|
||||
value = value / self._mcs_clock
|
||||
|
||||
# Store the value with timestamp. If available in kwargs, use provided timestamp from CA,
|
||||
# otherwise use current time when received.
|
||||
self._current_data.update(
|
||||
{attr_name: {"value": value, "timestamp": kwargs.get("timestamp") or time.time()}}
|
||||
)
|
||||
# Get the mapped signal from the bpm device and update it
|
||||
sig = getattr(self.bpm, mapped_signal_name)
|
||||
sig.put(value)
|
||||
self.counter_updated.append(signal.name)
|
||||
# Once all mca channels have been updated, we can signal that we are ready to read
|
||||
received_all_updates = set(self.counter_updated) == set(self.counter_mapping.keys())
|
||||
if received_all_updates:
|
||||
self.ready_to_read.put(READYTOREAD.DONE)
|
||||
# The reset of the signal is done in the on_trigger method of ddg1 for the next trigger
|
||||
self.counter_updated.clear() # Clear the list for the next update cycle
|
||||
|
||||
# Once we have received all channels, push data to BEC and reset for next accumulation
|
||||
logger.info(
|
||||
f"Received update for {attr_name}, index {self._mca_counter_index}/{self.NUM_MCA_CHANNELS}"
|
||||
)
|
||||
if len(self._current_data) == self.NUM_MCA_CHANNELS:
|
||||
logger.debug(
|
||||
f"Current data index {self._current_data_index} complete, pushing to BEC."
|
||||
)
|
||||
self.mca.put(self._current_data, acquisition_group=self._acquisition_group)
|
||||
self._current_data.clear()
|
||||
self._mca_counter_index = 0
|
||||
self._current_data_index += 1
|
||||
|
||||
# NOTE The logic for the device progress is not yet fully refined for all scan types.
|
||||
# This has to be adjusted once fly scan and step scan logic is fully implemented.
|
||||
# pylint: disable=unused-argument
|
||||
def _progress_update(self, *args, old_value: any, value: any, **kwargs) -> None:
|
||||
"""
|
||||
Callback to update the progress signals base on values of current_channel in respect to expected total triggers.
|
||||
Logic for these updates need to be extended once fly and step scan logic is fully implemented.
|
||||
|
||||
Args:
|
||||
old_value: Previous value of the signal.
|
||||
value: New value of the signal.
|
||||
"""
|
||||
try:
|
||||
scan_done = bool(value == self._num_total_triggers)
|
||||
self.progress.put(value=value, max_value=self._num_total_triggers, done=scan_done)
|
||||
if scan_done:
|
||||
self._scan_done_event.set()
|
||||
except Exception:
|
||||
content = traceback.format_exc()
|
||||
logger.info(f"Device {self.name} error: {content}")
|
||||
def _progress_update(self, value, **kwargs) -> None:
|
||||
"""Callback for progress updates from ophyd subscription on current_channel."""
|
||||
# This logic needs to be further refined as this is currently reporting the progress
|
||||
# of a single trigger from BEC within a burst scan.
|
||||
frames_per_trigger = self.scan_info.msg.scan_parameters.get("frames_per_trigger", 1)
|
||||
self.progress.put(
|
||||
value=value, max_value=frames_per_trigger, done=bool(value == frames_per_trigger)
|
||||
)
|
||||
|
||||
def on_stage(self) -> None:
|
||||
"""
|
||||
This method is called when the device is staged before a scan. Any bootstrapping required
|
||||
for the scan should be handled here. We also need to handle MCS card specific logic to ensure
|
||||
that the card is properly prepared for the scan.
|
||||
|
||||
The following procedure is implemented here:
|
||||
- Ensure that any ongoing acquisition is stopped (should never happen if not interfered with manually)
|
||||
- Erase all data on the MCS card to ensure a clean start (should never
|
||||
- Set acquisition parameters based on scan parameters (frames_per_trigger, num_points, acquisition_group)
|
||||
- Clear any events and buffers related to async data emission. This includes '_omit_mca_callbacks',
|
||||
'_start_monitor_async_data_emission', '_scan_done_callbacks', and '_current_data'.
|
||||
Called when the device is staged.
|
||||
"""
|
||||
start_time = time.time()
|
||||
|
||||
# NOTE: If for some reason, the card is still acquiring, we need to stop it first
|
||||
# This should never happen as the card is properly stopped during unstage
|
||||
# Can only happen if user manually interferes with the IOC through other means
|
||||
if self.acquiring.get() == ACQUIRING.ACQUIRING:
|
||||
logger.warning(
|
||||
f"MCS Card {self.name} was still acquiring on staging. Stopping acquisition."
|
||||
)
|
||||
self.stop_all.put(1)
|
||||
status = CompareStatus(self.acquiring, ACQUIRING.DONE)
|
||||
status.wait(timeout=10)
|
||||
|
||||
# NOTE: If current_channel != 0, erase all data on the card. This
|
||||
# needs to be done with the 'suppress_mca_callbacks' context manager as erase_all will result
|
||||
# in data emission through mca callback subscriptions.
|
||||
# The buffer needs to be cleared as this will otherwise lead to missing
|
||||
# triggers during the scan. Again, this should not happen if unstage is properly called.
|
||||
# But user interference or a restart of the device_server may lead to this situation.
|
||||
if self.current_channel.get() != 0:
|
||||
with suppress_mca_callbacks(self, restore_after_timeout=1.0):
|
||||
logger.warning(
|
||||
f"MCS Card {self.name} had still data in buffer Erased all data on staging and sleeping for 1 second."
|
||||
)
|
||||
# Erase all data on the MCS card
|
||||
self.erase_all.put(1)
|
||||
|
||||
#####################################
|
||||
### Setup Acquisition Parameters ###
|
||||
#####################################
|
||||
self.erase_all.set(1).wait(timeout=self._pv_timeout)
|
||||
triggers = self.scan_info.msg.scan_parameters.get("frames_per_trigger", 1)
|
||||
num_points = self.scan_info.msg.num_points
|
||||
self._num_total_triggers = triggers * num_points
|
||||
self._acquisition_group = "monitored" if triggers == 1 else "burst_group"
|
||||
self.preset_real.set(0).wait(timeout=self._pv_timeout)
|
||||
if self.scan_info.msg.scan_type == "step":
|
||||
self.num_use_all.set(triggers).wait(timeout=self._pv_timeout)
|
||||
elif self.scan_info.msg.scan_type == "fly":
|
||||
self.num_use_all.set(self._num_total_triggers).wait(timeout=self._pv_timeout)
|
||||
|
||||
# Clear any previous data, just to be sure
|
||||
with self._rlock:
|
||||
self._current_data.clear()
|
||||
self._mca_counter_index = 0
|
||||
|
||||
# NOTE Reset events for monitoring async_data_emission thread which is
|
||||
# running during complete to wait for all data from the card
|
||||
# to be emitted to BEC.
|
||||
self._start_monitor_async_data_emission.clear()
|
||||
|
||||
# Clear any previous scan done callbacks
|
||||
self._scan_done_callbacks.clear()
|
||||
|
||||
# Reset counter for data index of emitted data, NOTE for fly scans, this logic may have to be adjusted.
|
||||
self._current_data_index = 0
|
||||
|
||||
# NOTE Make sure that the signal that omits mca callbacks is cleared
|
||||
self._omit_mca_callbacks.clear()
|
||||
|
||||
logger.info(f"MCS Card {self.name} on_stage completed in {time.time() - start_time:.3f}s.")
|
||||
# For a fly scan we need to start the mcs card ourselves
|
||||
if self.scan_info.msg.scan_type == "fly":
|
||||
self.erase_start.put(1)
|
||||
|
||||
def on_prescan(self) -> None | StatusBase:
|
||||
"""
|
||||
This method is called after on_stage and before the scan starts. For the MCS card, we need to make sure
|
||||
that the card is properly started for fly scans. For step scans, this will be handled by the DDG,
|
||||
so no action is required here.
|
||||
"""
|
||||
if self.scan_info.msg.scan_type == "fly":
|
||||
status_acquiring = CompareStatus(self.acquiring, ACQUIRING.ACQUIRING)
|
||||
self.cancel_on_stop(status_acquiring)
|
||||
return status_acquiring
|
||||
return None
|
||||
self.num_use_all.set(triggers).wait(timeout=self._pv_timeout)
|
||||
|
||||
def on_unstage(self) -> None:
|
||||
"""
|
||||
Called when the device is unstaged. This method should be omnipotent and resolve fast.
|
||||
It stops any ongoing acquisition, erases all data on the MCS and clears the local buffer '_current_data'.
|
||||
|
||||
NOTE: It is important that the logic for on_complete is solid and properly waiting for mca data to be emitted
|
||||
to BEC. Otherwise, unstage may interfere with ongoing data emission. Unstage is called after complete during scans.
|
||||
It is crucial that the device itself calls '_omit_mca_callbacks' in its on_stage method to make sure
|
||||
that data is emitted once the card is properly staged.
|
||||
Called when the device is unstaged.
|
||||
"""
|
||||
self.stop_all.put(1)
|
||||
with suppress_mca_callbacks(self):
|
||||
with self._rlock:
|
||||
self._current_data.clear()
|
||||
self._current_data_index = 0
|
||||
self.erase_all.put(1)
|
||||
self.ready_to_read.put(READYTOREAD.DONE)
|
||||
# TODO why 0?
|
||||
self.erase_all.set(0).wait(timeout=self._pv_timeout)
|
||||
|
||||
def _monitor_async_data_emission(self) -> None:
|
||||
def on_trigger(self) -> None:
|
||||
status = TransitionStatus(
|
||||
self.ready_to_read, strict=True, transitions=[READYTOREAD.PROCESSING, READYTOREAD.DONE]
|
||||
)
|
||||
self.cancel_on_stop(status)
|
||||
return status
|
||||
|
||||
def on_pre_scan(self) -> None:
|
||||
"""
|
||||
Monitoring loop that runs in a separate thread to check if all async data has been emitted to BEC.
|
||||
It is IDLE most of the time, but activate in the 'on_complete' method called by 'complete'.
|
||||
|
||||
The check is done by comparing the number of data updates '_current_data_index' received through
|
||||
mca channel callbacks with the expected number of points in the scan. Once they match, all
|
||||
callbacks in _scan_done_callbacks are called to indicate that data emission is done.
|
||||
Callbacks need to also accept and handle exceptions to properly report failure.
|
||||
NOTE! This logic currently works for any step scan, but has to be extended for fly scans.
|
||||
Called before the scan starts.
|
||||
"""
|
||||
while not self._scan_done_thread_kill_event.is_set():
|
||||
while self._start_monitor_async_data_emission.wait():
|
||||
try:
|
||||
logger.debug(f"Monitoring async data emission for {self.name}...")
|
||||
if (
|
||||
hasattr(self.scan_info.msg, "num_points")
|
||||
and self.scan_info.msg.num_points is not None
|
||||
):
|
||||
if self.scan_info.msg.scan_type == "step":
|
||||
if self._current_data_index == self.scan_info.msg.num_points:
|
||||
for callback in self._scan_done_callbacks:
|
||||
callback(exception=None)
|
||||
else:
|
||||
logger.info(f"Current data index is {self._current_data_index}")
|
||||
if self._current_data_index >= 1:
|
||||
for callback in self._scan_done_callbacks:
|
||||
callback(exception=None)
|
||||
|
||||
time.sleep(0.02) # 20ms delay to avoid busy loop
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
content = traceback.format_exc()
|
||||
logger.error(
|
||||
f"Exception in monitoring thread of complete for {self.name}:\n{content}"
|
||||
"Running callbacks to avoid deadlock."
|
||||
)
|
||||
for callback in self._scan_done_callbacks:
|
||||
callback(exception=exc)
|
||||
|
||||
def _status_callback(self, status: StatusBase, exception=None) -> None:
|
||||
"""Callback for status completion."""
|
||||
self._start_monitor_async_data_emission.clear() # Stop monitoring
|
||||
# NOTE Important check as set_finished or set_exception should not be called
|
||||
# if the status is already done (e.g. cancelled externally)
|
||||
with self._rlock:
|
||||
if status.done:
|
||||
return # Already done and cancelled externally.
|
||||
if exception is not None:
|
||||
status.set_exception(exception)
|
||||
else:
|
||||
status.set_finished()
|
||||
|
||||
def _status_failed_callback(self, status: StatusBase) -> None:
|
||||
"""Callback for status failure, the monitoring thread should be stopped."""
|
||||
# NOTE Check for status.done and status.success is important to avoid
|
||||
if status.done:
|
||||
self._start_monitor_async_data_emission.clear() # Stop monitoring
|
||||
|
||||
def on_complete(self) -> CompareStatus:
|
||||
"""
|
||||
|
||||
Method that is called at the end of scan core, but before unstage. This method is
|
||||
used to report whether the device successfully completed its data acquisition for the scan.
|
||||
The check has to be implemented asynchronously and resolve through a status (future) object
|
||||
returned by this method.
|
||||
NOTE: For the MCS card, we need to ensure that all data has been acquired
|
||||
and emitted to BEC as updates after 'on_complete' resolved will be rejected by BEC.
|
||||
Therefore, we need to ensure that all data has been emitted to BEC before
|
||||
reporting completion of the device.
|
||||
|
||||
This method implements the following procedure:
|
||||
- Starts the IDLE async data monitoring thread that checks if all expected data
|
||||
has been emitted to BEC through the mca channel callbacks.
|
||||
- Use a CompareStatus to monitor when the MCS card becomes DONE. Please note that this
|
||||
only indicates that the card has finished acquisition, but not that all data has been
|
||||
emitted to BEC.
|
||||
- Return combined status object. A callback is registered to handle failure of the status
|
||||
if it is stopped externally, e.g. through scan abort. This should ensure that the
|
||||
monitoring thread is stopped properly.
|
||||
|
||||
"""
|
||||
# NOTE For fly scans with EXT/EN enabled triggering, the MCS card needs to receive an
|
||||
# additional trigger at the end of the scan to advance the channel. This will ensure
|
||||
# that the acquisition finishes on the card and that data is emitted to BEC. If the acquisition
|
||||
# was already finished (i.e. normal step scan sends 1 extra pulse per burst cycle), this will
|
||||
# not have any effect as the card will already be in DONE state and signal.
|
||||
self.software_channel_advance.put(1)
|
||||
|
||||
# Prepare and register status callback for the async monitoring loop
|
||||
status_async_data = StatusBase(obj=self)
|
||||
self._scan_done_callbacks.append(partial(self._status_callback, status_async_data))
|
||||
|
||||
# Set the event to start monitoring async data emission
|
||||
logger.debug(f"Starting to monitor async data emission for {self.name}...")
|
||||
self._start_monitor_async_data_emission.set()
|
||||
|
||||
# Add CompareStatus for Acquiring DONE
|
||||
"""On scan completion."""
|
||||
# Check if we should get a signal based on updates from the MCA channels
|
||||
status = CompareStatus(self.acquiring, ACQUIRING.DONE)
|
||||
|
||||
# Combine both statuses
|
||||
ret_status = status & status_async_data
|
||||
# NOTE: Handle external stop/cancel, and stop monitoring
|
||||
ret_status.add_callback(self._status_failed_callback)
|
||||
self.cancel_on_stop(ret_status)
|
||||
return ret_status
|
||||
|
||||
def on_destroy(self):
|
||||
"""
|
||||
The on destroy hook is called when the device is destroyed, but also reloaded.
|
||||
Here, we need to clean up all resources used up by the device, including running threads.
|
||||
"""
|
||||
self._scan_done_thread_kill_event.set()
|
||||
self._start_monitor_async_data_emission.set()
|
||||
if self._scan_done_thread.is_alive():
|
||||
self._scan_done_thread.join(timeout=2.0)
|
||||
if self._scan_done_thread.is_alive():
|
||||
logger.warning(f"Thread for device {self.name} did not terminate properly.")
|
||||
self.cancel_on_stop(status)
|
||||
return status
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Hook called when the device is stopped. In addition, any status that is registered through cancel_on_stop will be cancelled here."""
|
||||
self.stop_all.put(1)
|
||||
self.erase_all.put(1)
|
||||
|
||||
def mcs_recovery(self, timeout: int = 1) -> None:
|
||||
"""
|
||||
Recovery procedure for the mcs card. This procedure has been empirically found and can
|
||||
be used to ensure that the MCS card is stopped and has no pending data to be emitted.
|
||||
It involves stopping any ongoing acquisition and erasing all data on the card, with
|
||||
a sleep in between to allow the IOC to process the commands.
|
||||
|
||||
Args:
|
||||
timeout (int): Total timeout for the recovery procedure. Defaults to 1 second.
|
||||
Called when the scan is stopped.
|
||||
"""
|
||||
sleep_time = timeout / 2 # 2 sleeps
|
||||
logger.debug(
|
||||
f"Running recovery procedure for MCS card {self.name} with {sleep_time}s sleep, calling stop_all and erase_all, and another {sleep_time}s sleep"
|
||||
)
|
||||
# First erase and start ongoing acquisition.
|
||||
self.erase_start.put(1)
|
||||
time.sleep(sleep_time)
|
||||
# After a brief processing time, we stop any ongoing acquisition.
|
||||
self.stop_all.put(1)
|
||||
# Finally, we erase all data while suppressing mca callbacks to avoid interference.
|
||||
# We restore the callback suppression after timeout to ensure proper operation afterwards.
|
||||
with suppress_mca_callbacks(self, restore_after_timeout=sleep_time):
|
||||
self.erase_all.put(1)
|
||||
self.ready_to_read.put(READYTOREAD.DONE)
|
||||
# Reset the progress signal
|
||||
# self.progress.put(0, done=True)
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# Overview
|
||||
Integration module for Eiger detectors at the cSAXS beamline with JungfrauJoch backend.
|
||||
There are currently two supported Eiger detectors:
|
||||
- EIGER 1.5M
|
||||
- EIGER 9M
|
||||
|
||||
This module provides a base integration for both detectors. A short list of useful
|
||||
information is also provided below.
|
||||
|
||||
## JungfrauJoch Service
|
||||
The JungfrauJoch WEB UI is available on http://sls-jfjoch-001:8080. This is an interface
|
||||
to the broker which runs on sls-jfjoch-001.psi.ch. The writer service runs on
|
||||
xbl-daq-34.psi.ch. Permissions to get access to these machines and run systemctl or
|
||||
journalctl commands can be requested with the Infrastructure and Services group in AWI.
|
||||
Beamline scientists need to check if they have the necessary permissions to connect
|
||||
to these machines and run the commands below.
|
||||
|
||||
Useful commands for the broker service on sls-jfjoch-001.psi.ch:
|
||||
- sudo systemctl status jfjoch_broker # Check status
|
||||
- sudo systemctl start jfjoch_broker # Start service
|
||||
- sudo systemctl stop jfjoch_broker # Stop service
|
||||
- sudo systemctl restart jfjoch_broker # Restart service
|
||||
|
||||
For the writer service on xbl-daq-34.psi.ch:
|
||||
- sudo journalctl -u jfjoch_writer -f # streams live logs
|
||||
- sudo systemctl status jfjoch_writer # Check status
|
||||
- sudo systemctl start jfjoch_writer # Start service
|
||||
- sudo systemctl stop jfjoch_writer # Stop service
|
||||
- sudo systemctl restart jfjoch_writer # Restart service
|
||||
|
||||
More information about the JungfrauJoch and API client can be found at: (https://jungfraujoch.readthedocs.io/en/latest/index.html)
|
||||
|
||||
### JungfrauJoch API Client
|
||||
A thin wrapper for the JungfrauJoch API client is provided in the [jungfrau_joch_client](./jungfrau_joch_client.py).
|
||||
Details about the specific integration are provided in the code.
|
||||
|
||||
|
||||
## Eiger implementation
|
||||
The Eiger detector integration is provided in the [eiger.py](./eiger.py) module. It provides a base integration for both Eiger 1.5M and Eiger 9M detectors.
|
||||
Logic specific to each detector is implemented in the respective modules:
|
||||
- [eiger_1_5m.py](./eiger_1_5m.py)
|
||||
- [eiger_9m.py](./eiger_9m.py)
|
||||
|
||||
With the current implementation, the detector initialization should be done by a beamline scientist through the JungfrauJoch WEB UI by choosing the
|
||||
appropriate detector (1.5M or 9M) before loading the device config with BEC. BEC will check upon connecting if the selected detector matches the expected one.
|
||||
A preview stream for images is also provided which is forwarded and accessible through the `preview_image` signal.
|
||||
|
||||
For more specific details, please check the code documentation.
|
||||
@@ -1,23 +1,34 @@
|
||||
"""
|
||||
Generic integration of JungfrauJoch backend with Eiger detectors
|
||||
for the cSAXS beamline at the Swiss Light Source.
|
||||
|
||||
Integration module for Eiger detectors at the cSAXS beamline with JungfrauJoch backend.
|
||||
The WEB UI is available on http://sls-jfjoch-001:8080
|
||||
|
||||
A few notes on setup and operation of the Eiger detectors through the JungfrauJoch broker:
|
||||
NOTE: this may not be the best place to store this information. It should be migrated to
|
||||
beamline documentation for debugging of Eiger & JungfrauJoch.
|
||||
|
||||
The JungfrauJoch server for cSAXS runs on sls-jfjoch-001.psi.ch
|
||||
User with sufficient rights may use:
|
||||
- sudo systemctl restart jfjoch_broker
|
||||
- sudo systemctl status jfjoch_broker
|
||||
to check and/or restart the broker for the JungfrauJoch server.
|
||||
|
||||
Some extra notes for setting up the detector:
|
||||
- If the energy on JFJ is set via DetectorSettings, the variable in DatasetSettings will be ignored
|
||||
- Changes in energy may take time, good to implement logic that only resets energy if needed.
|
||||
- For the Eiger, the frame_time_us in DetectorSettings is ignored, only the frame_time_us in
|
||||
the DatasetSettings is relevant
|
||||
- The bit_depth will be adjusted automatically based on the exp_time. Here, we need to ensure
|
||||
that subsequent triggers properly consider the readout_time of the boards. For the Eiger detectors
|
||||
at cSAXS, a readout time of 20us is configured through the JungfrauJoch deployment config. This
|
||||
setting is sufficiently large for the detectors if they run in parallel mode.
|
||||
that subsequent triggers properly
|
||||
consider the readout_time of the boards. For Jungfrau detectors, the difference between
|
||||
count_time_us and frame_time_us is the readout_time of the boards. For the Eiger, this needs
|
||||
to be taken into account during the integration.
|
||||
- beam_center and detector settings are required input arguments, thus, they may be set to wrong
|
||||
values for acquisitions to start. Please keep this in mind.
|
||||
|
||||
Hardware related notes:
|
||||
- If there is an HW issue with the detector, power cycling may help.
|
||||
- The sls_detector package is available on console on /sls/x12sa/applications/erik/micromamba
|
||||
- The sls_detector package is available on console on /sls/X12SA/data/gac-x12sa/erik/micromamba
|
||||
- Run: source setup_9m.sh # Be careful, this connects to the detector, so it should not be
|
||||
used during operation
|
||||
- Useful commands:
|
||||
@@ -28,6 +39,9 @@ Hardware related notes:
|
||||
- cd power_control_user/
|
||||
- ./on
|
||||
- ./off
|
||||
|
||||
Further information that may be relevant for debugging:
|
||||
JungfrauJoch - one needs to connect to the jfj-server (sls-jfjoch-001)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -70,19 +84,10 @@ class EigerError(Exception):
|
||||
|
||||
class Eiger(PSIDeviceBase):
|
||||
"""
|
||||
Base integration of the Eiger1.5M and Eiger9M at cSAXS.
|
||||
|
||||
Args:
|
||||
name (str) : Name of the device
|
||||
detector_name (str): Name of the detector. Supports ["EIGER 9M", "EIGER 8.5M (tmp)", "EIGER 1.5M"]
|
||||
host (str): Hostname of the Jungfrau Joch server.
|
||||
port (int): Port of the Jungfrau Joch server.
|
||||
scan_info (ScanInfo): The scan info to use.
|
||||
device_manager (DeviceManagerDS): The device manager to use.
|
||||
**kwargs: Additional keyword arguments.
|
||||
Base integration of the Eiger1.5M and Eiger9M at cSAXS. All relevant
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["set_detector_distance", "set_beam_center"]
|
||||
USER_ACCESS = ["detector_distance", "beam_center"]
|
||||
|
||||
file_event = Cpt(FileEventSignal, name="file_event")
|
||||
preview_image = Cpt(PreviewSignal, name="preview_image", ndim=2)
|
||||
@@ -100,12 +105,23 @@ class Eiger(PSIDeviceBase):
|
||||
device_manager=None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Initialize the PSI Device Base class.
|
||||
|
||||
Args:
|
||||
name (str) : Name of the device
|
||||
detector_name (str): Name of the detector. Supports ["EIGER 9M", "EIGER 8.5M (tmp)", "EIGER 1.5M"]
|
||||
host (str): Hostname of the Jungfrau Joch server.
|
||||
port (int): Port of the Jungfrau Joch server.
|
||||
scan_info (ScanInfo): The scan info to use.
|
||||
device_manager (DeviceManagerDS): The device manager to use.
|
||||
**kwargs: Additional keyword arguments.
|
||||
"""
|
||||
super().__init__(name=name, scan_info=scan_info, device_manager=device_manager, **kwargs)
|
||||
self._host = f"{host}:{port}"
|
||||
self.jfj_client = JungfrauJochClient(host=self._host, parent=self)
|
||||
# NOTE: fetch this information from JungfrauJochClient during on_connected!
|
||||
self.jfj_preview_client = JungfrauJochPreview(
|
||||
url="tcp://129.129.95.114:5400", cb=self._preview_callback
|
||||
url="tcp://129.129.95.114:5400", cb=self.preview_image.put
|
||||
) # IP of sls-jfjoch-001.psi.ch on port 5400 for ZMQ stream
|
||||
self.device_manager = device_manager
|
||||
self.detector_name = detector_name
|
||||
@@ -113,102 +129,53 @@ class Eiger(PSIDeviceBase):
|
||||
self._beam_center = beam_center
|
||||
self._readout_time = readout_time
|
||||
self._full_path = ""
|
||||
self._num_triggers = 0
|
||||
self._wait_for_on_complete = 20 # seconds
|
||||
if self.device_manager is not None:
|
||||
self.device_manager: DeviceManagerDS
|
||||
|
||||
def _preview_callback(self, message: dict) -> None:
|
||||
"""
|
||||
Callback method for handling preview messages as received from the JungfrauJoch preview stream.
|
||||
These messages are dictionary dumps as described in the JFJ ZMQ preview stream documentation.
|
||||
(https://jungfraujoch.readthedocs.io/en/latest/ZEROMQ_STREAM.html#preview-stream).
|
||||
|
||||
Args:
|
||||
message (dict): The message received from the preview stream.
|
||||
"""
|
||||
if message.get("type", "") == "image":
|
||||
data = message.get("data", {}).get("default", None)
|
||||
if data is None:
|
||||
logger.error(f"Received image message on device {self.name} without data.")
|
||||
return
|
||||
logger.info(f"Received preview image on device {self.name}")
|
||||
self.preview_image.put(data)
|
||||
|
||||
# pylint: disable=missing-function-docstring
|
||||
@property
|
||||
def detector_distance(self) -> float:
|
||||
"""The detector distance in mm."""
|
||||
return self._detector_distance
|
||||
|
||||
@detector_distance.setter
|
||||
def detector_distance(self, value: float) -> None:
|
||||
"""Set the detector distance in mm."""
|
||||
if value <= 0:
|
||||
raise ValueError("Detector distance must be a positive value.")
|
||||
self._detector_distance = value
|
||||
|
||||
def set_detector_distance(self, distance: float) -> None:
|
||||
"""
|
||||
Set the detector distance in mm.
|
||||
|
||||
Args:
|
||||
distance (float): The detector distance in mm.
|
||||
"""
|
||||
self.detector_distance = distance
|
||||
|
||||
# pylint: disable=missing-function-docstring
|
||||
@property
|
||||
def beam_center(self) -> tuple[float, float]:
|
||||
"""The beam center in pixels. (x,y)"""
|
||||
return self._beam_center
|
||||
|
||||
@beam_center.setter
|
||||
def beam_center(self, value: tuple[float, float]) -> None:
|
||||
if any(coord < 0 for coord in value):
|
||||
raise ValueError("Beam center coordinates must be non-negative.")
|
||||
"""Set the beam center in pixels. (x,y)"""
|
||||
self._beam_center = value
|
||||
|
||||
def set_beam_center(self, x: float, y: float) -> None:
|
||||
"""
|
||||
Set the beam center coordinates in pixels.
|
||||
|
||||
Args:
|
||||
x (float): The x coordinate of the beam center in pixels.
|
||||
y (float): The y coordinate of the beam center in pixels.
|
||||
"""
|
||||
self.beam_center = (x, y)
|
||||
|
||||
def on_init(self) -> None:
|
||||
"""Hook called during device initialization."""
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def wait_for_connection(self, timeout: float = 10) -> None:
|
||||
"""
|
||||
Wait for the device to be connected to the JungfrauJoch backend.
|
||||
Called when the device is initialized.
|
||||
|
||||
Args:
|
||||
timeout (float): Timeout in seconds to wait for the connection.
|
||||
No siganls are connected at this point,
|
||||
thus should not be set here but in on_connected instead.
|
||||
"""
|
||||
self.jfj_client.api.status_get(_request_timeout=timeout) # If connected, this responds
|
||||
|
||||
def on_connected(self) -> None:
|
||||
"""
|
||||
Hook called after the device is connected to through the device server.
|
||||
|
||||
Called after the device is connected and its signals are connected.
|
||||
Default values for signals should be set here. Currently, the detector needs to be
|
||||
initialised manually through the WEB UI of JungfrauJoch. Once agreed upon, the automated
|
||||
initialisation can be re-enabled here (code commented below).
|
||||
Default values for signals should be set here.
|
||||
"""
|
||||
start_time = time.time()
|
||||
logger.debug(f"On connected called for {self.name}")
|
||||
self.jfj_client.stop(request_timeout=3)
|
||||
# Check which detector is selected
|
||||
|
||||
# Get available detectors
|
||||
available_detectors = self.jfj_client.api.config_select_detector_get(_request_timeout=5)
|
||||
logger.debug(f"Available detectors {available_detectors}")
|
||||
# Get current detector
|
||||
current_detector_name = ""
|
||||
if available_detectors.current_id is not None:
|
||||
if available_detectors.current_id:
|
||||
detector_selection = [
|
||||
det.description
|
||||
for det in available_detectors.detectors
|
||||
@@ -223,9 +190,8 @@ class Eiger(PSIDeviceBase):
|
||||
raise RuntimeError(
|
||||
f"Detector {self.detector_name} is not in IDLE state, current state: {self.jfj_client.detector_state}. Please initialize the detector in the WEB UI: {self._host}."
|
||||
)
|
||||
|
||||
# TODO - Currently the initialisation of the detector is done manually through the WEB UI. Once adjusted
|
||||
# this can be automated here again.
|
||||
# TODO - check again once Eiger should be initialized automatically, currently human initialization is expected
|
||||
# # Once the automation should be enabled, we may use here
|
||||
# detector_selection = [
|
||||
# det for det in available_detectors.detectors if det.id == self.detector_name
|
||||
# ]
|
||||
@@ -241,51 +207,41 @@ class Eiger(PSIDeviceBase):
|
||||
|
||||
# Setup Detector settings, here we may also set the energy already as this might be time consuming
|
||||
settings = DetectorSettings(frame_time_us=int(500), timing=DetectorTiming.TRIGGER)
|
||||
self.jfj_client.set_detector_settings(settings, timeout=5)
|
||||
|
||||
self.jfj_client.set_detector_settings(settings, timeout=10)
|
||||
# Set the file writer to the appropriate output for the HDF5 file
|
||||
file_writer_settings = FileWriterSettings(overwrite=True, format=FileWriterFormat.NXMXVDS)
|
||||
logger.debug(
|
||||
f"Setting writer_settings: {yaml.dump(file_writer_settings.to_dict(), indent=4)}"
|
||||
)
|
||||
|
||||
# Setup the file writer settings
|
||||
self.jfj_client.api.config_file_writer_put(
|
||||
file_writer_settings=file_writer_settings, _request_timeout=10
|
||||
)
|
||||
|
||||
# Start the preview client
|
||||
self.jfj_preview_client.connect()
|
||||
self.jfj_preview_client.start()
|
||||
logger.info(
|
||||
f"Device {self.name} initialized after {time.time()-start_time:.2f}s. Preview stream connected on url: {self.jfj_preview_client.url}"
|
||||
)
|
||||
logger.info(f"Connected to JungfrauJoch preview stream at {self.jfj_preview_client.url}")
|
||||
|
||||
def on_stage(self) -> DeviceStatus | None:
|
||||
"""
|
||||
Hook called when staging the device. Information about the upcoming scan can be accessed from the scan_info object.
|
||||
scan_msg = self.scan_info.msg
|
||||
Called while staging the device.
|
||||
|
||||
Information about the upcoming scan can be accessed from the scan_info object.
|
||||
"""
|
||||
start_time = time.time()
|
||||
scan_msg = self.scan_info.msg
|
||||
|
||||
# TODO: Check mono energy from device in BEC
|
||||
# Setting incident energy in keV
|
||||
# Set acquisition parameter
|
||||
# TODO add check of mono energy, this can then also be passed to DatasetSettings
|
||||
incident_energy = 12.0
|
||||
# Setting up exp_time and num_triggers acquisition parameter
|
||||
exp_time = scan_msg.scan_parameters.get("exp_time", 0)
|
||||
if exp_time <= self._readout_time: # Exp_time must be at least the readout time
|
||||
if exp_time <= self._readout_time:
|
||||
raise ValueError(
|
||||
f"Value error on device {self.name}: Exposure time {exp_time}s is less than readout time {self._readout_time}s."
|
||||
f"Receive scan request for scan {scan_msg.scan_name} with exp_time {exp_time}s, which must be larger than the readout time {self._readout_time}s of the detector {self.detector_name}."
|
||||
)
|
||||
self._num_triggers = int(
|
||||
scan_msg.num_points * scan_msg.scan_parameters["frames_per_trigger"]
|
||||
)
|
||||
|
||||
# Setting up the full path for file writing
|
||||
frame_time_us = exp_time #
|
||||
ntrigger = int(scan_msg.num_points * scan_msg.scan_parameters["frames_per_trigger"])
|
||||
# Fetch file path
|
||||
self._full_path = get_full_path(scan_msg, name=f"{self.name}_master")
|
||||
self._full_path = os.path.abspath(os.path.expanduser(self._full_path))
|
||||
|
||||
# Inform BEC about upcoming file event
|
||||
self.file_event.put(
|
||||
file_path=self._full_path,
|
||||
@@ -293,14 +249,11 @@ class Eiger(PSIDeviceBase):
|
||||
successful=False,
|
||||
hinted_h5_entries={"data": "entry/data/data"},
|
||||
)
|
||||
|
||||
# JFJ adds _master.h5 automatically
|
||||
path = os.path.relpath(self._full_path, start="/sls/x12sa/data").removesuffix("_master.h5")
|
||||
|
||||
# Create dataset settings for API call.
|
||||
data_settings = DatasetSettings(
|
||||
image_time_us=int(exp_time * 1e6),
|
||||
ntrigger=self._num_triggers,
|
||||
image_time_us=int(frame_time_us * 1e6), # This is currently ignored
|
||||
ntrigger=ntrigger,
|
||||
file_prefix=path,
|
||||
beam_x_pxl=int(self._beam_center[0]),
|
||||
beam_y_pxl=int(self._beam_center[1]),
|
||||
@@ -308,15 +261,11 @@ class Eiger(PSIDeviceBase):
|
||||
incident_energy_ke_v=incident_energy,
|
||||
)
|
||||
logger.debug(f"Setting data_settings: {yaml.dump(data_settings.to_dict(), indent=4)}")
|
||||
prep_time = time.time()
|
||||
self.jfj_client.wait_for_idle(timeout=10) # Ensure we are in IDLE state
|
||||
prep_time = start_time - time.time()
|
||||
logger.debug(f"Prepared information for eiger to start acquisition in {prep_time:.2f}s")
|
||||
self.jfj_client.wait_for_idle(timeout=10, request_timeout=10) # Ensure we are in IDLE state
|
||||
self.jfj_client.start(settings=data_settings) # Takes around ~0.6s
|
||||
|
||||
# Time the stage process
|
||||
logger.info(
|
||||
f"Device {self.name} staged for scan. Time spent {time.time()-start_time:.2f}s,"
|
||||
f" with {time.time()-prep_time:.2f}s spent with communication to JungfrauJoch."
|
||||
)
|
||||
logger.debug(f"Wait for IDLE and start call took {time.time()-start_time-prep_time:.2f}s")
|
||||
|
||||
def on_unstage(self) -> DeviceStatus:
|
||||
"""Called while unstaging the device."""
|
||||
@@ -329,9 +278,7 @@ class Eiger(PSIDeviceBase):
|
||||
|
||||
def _file_event_callback(self, status: DeviceStatus) -> None:
|
||||
"""Callback to update the file_event signal when the acquisition is done."""
|
||||
logger.debug(
|
||||
f"File event callback on complete status for device {self.name}: done={status.done}, successful={status.success}"
|
||||
)
|
||||
logger.info(f"Acquisition done callback called for {self.name} for status {status.success}")
|
||||
self.file_event.put(
|
||||
file_path=self._full_path,
|
||||
done=status.done,
|
||||
@@ -340,44 +287,19 @@ class Eiger(PSIDeviceBase):
|
||||
)
|
||||
|
||||
def on_complete(self) -> DeviceStatus:
|
||||
"""
|
||||
Called at the end of the scan. The method should implement an asynchronous wait for the
|
||||
device to complete the acquisition. A callback to update the file_event signal is
|
||||
attached that resolves the file event when the acquisition is done.
|
||||
|
||||
Returns:
|
||||
DeviceStatus: The status object representing the completion of the acquisition.
|
||||
"""
|
||||
"""Called to inquire if a device has completed a scans."""
|
||||
|
||||
def wait_for_complete():
|
||||
start_time = time.time()
|
||||
# NOTE: This adjust the time (s) that should be waited for completion of the scan.
|
||||
timeout = self._wait_for_on_complete
|
||||
while time.time() - start_time < timeout:
|
||||
if self.jfj_client.wait_for_idle(timeout=1, raise_on_timeout=False):
|
||||
# TODO: Once available, add check for
|
||||
statistics: MeasurementStatistics = (
|
||||
self.jfj_client.api.statistics_data_collection_get(_request_timeout=5)
|
||||
)
|
||||
if statistics.images_collected < self._num_triggers:
|
||||
raise EigerError(
|
||||
f"Device {self.name} acquisition incomplete. "
|
||||
f"Expected {self._num_triggers} triggers, "
|
||||
f"but only {statistics.images_collected} were collected."
|
||||
)
|
||||
timeout = 10
|
||||
for _ in range(timeout):
|
||||
if self.jfj_client.wait_for_idle(timeout=1, request_timeout=10):
|
||||
return
|
||||
logger.info(
|
||||
f"Waiting for device {self.name} to finish complete, time elapsed: "
|
||||
f"{time.time() - start_time}."
|
||||
)
|
||||
statistics: MeasurementStatistics = self.jfj_client.api.statistics_data_collection_get(
|
||||
_request_timeout=5
|
||||
)
|
||||
broker_status = self.jfj_client.jfj_status
|
||||
raise TimeoutError(
|
||||
f"Timeout after waiting for device {self.name} to complete for {time.time()-start_time:.2f}s \n \n"
|
||||
f"Broker status: \n{yaml.dump(broker_status.to_dict(), indent=4)} \n \n"
|
||||
f"Measurement statistics: \n{yaml.dump(statistics.to_dict(), indent=4)}"
|
||||
f"Timeout after waiting for detector {self.name} to complete for {time.time()-start_time:.2f}s, measurement statistics: {yaml.dump(statistics.to_dict(), indent=4)}"
|
||||
)
|
||||
|
||||
status = self.task_handler.submit_task(wait_for_complete, run=True)
|
||||
@@ -390,11 +312,7 @@ class Eiger(PSIDeviceBase):
|
||||
|
||||
def on_stop(self) -> None:
|
||||
"""Called when the device is stopped."""
|
||||
self.jfj_client.stop(request_timeout=0.5)
|
||||
self.jfj_client.stop(
|
||||
request_timeout=0.5
|
||||
) # Call should not block more than 0.5 seconds to stop all devices...
|
||||
self.task_handler.shutdown()
|
||||
|
||||
def on_destroy(self):
|
||||
"""Called when the device is destroyed."""
|
||||
self.jfj_preview_client.stop()
|
||||
self.on_stop()
|
||||
return super().on_destroy()
|
||||
|
||||
@@ -21,18 +21,18 @@ if TYPE_CHECKING: # pragma no cover
|
||||
from bec_server.device_server.device_server import DeviceManagerDS
|
||||
|
||||
EIGER9M_READOUT_TIME_US = 500e-6 # 500 microseconds in s
|
||||
DETECTOR_NAME = "EIGER 9M" # "EIGER 9M""
|
||||
DETECTOR_NAME = "EIGER 8.5M (tmp)" # "EIGER 9M""
|
||||
|
||||
|
||||
# pylint:disable=invalid-name
|
||||
class Eiger9M(Eiger):
|
||||
"""
|
||||
EIGER 9M specific integration for the in-vaccum Eiger.
|
||||
Eiger 1.5M specific integration for the in-vaccum Eiger.
|
||||
|
||||
The logic implemented here is coupled to the DelayGenerator integration,
|
||||
repsonsible for the global triggering of all devices through a single Trigger logic.
|
||||
Please check the eiger.py class for more details about the integration of relevant backend
|
||||
services. The detector_name must be set to "EIGER 9M":
|
||||
services. The detector_name must be set to "EIGER 1.5M:
|
||||
"""
|
||||
|
||||
USER_ACCESS = Eiger.USER_ACCESS + [] # Add more user_access methods here.
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
"""Module with a thin client wrapper around the Jungfrau Joch detector API"""
|
||||
"""Module with client interface for the Jungfrau Joch detector API"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
import threading
|
||||
import time
|
||||
import traceback
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import requests
|
||||
import yaml
|
||||
from bec_lib.logger import bec_logger
|
||||
from jfjoch_client.api.default_api import DefaultApi
|
||||
from jfjoch_client.api_client import ApiClient
|
||||
@@ -20,7 +18,7 @@ from jfjoch_client.models.detector_settings import DetectorSettings
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
if TYPE_CHECKING:
|
||||
from ophyd import Device
|
||||
|
||||
|
||||
@@ -31,10 +29,7 @@ class JungfrauJochClientError(Exception):
|
||||
|
||||
|
||||
class DetectorState(str, enum.Enum):
|
||||
"""
|
||||
Enum states of the BrokerStatus state. The pydantic model validates in runtime,
|
||||
thus we keep the possible states here for a convenient overview and access.
|
||||
"""
|
||||
"""Possible Detector states for Jungfrau Joch detector"""
|
||||
|
||||
INACTIVE = "Inactive"
|
||||
IDLE = "Idle"
|
||||
@@ -45,15 +40,13 @@ class DetectorState(str, enum.Enum):
|
||||
|
||||
|
||||
class JungfrauJochClient:
|
||||
"""
|
||||
Jungfrau Joch API client wrapper. It provides a thin wrapper methods around the API client,
|
||||
that allow to connect, initialise, wait for state changes, set settings, start and stop
|
||||
acquisitions.
|
||||
"""Thin wrapper around the Jungfrau Joch API client.
|
||||
|
||||
Args:
|
||||
host (str): Hostname of the Jungfrau Joch broker service.
|
||||
Default is "http://sls-jfjoch-001:8080"
|
||||
parent (Device, optional): Parent ophyd device, used for logging purposes.
|
||||
sudo systemctl restart jfjoch_broker
|
||||
sudo systemctl status jfjoch_broker
|
||||
|
||||
It looks as if the detector is not being stopped properly.
|
||||
One module remains running, how can we restart the detector?
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
@@ -66,63 +59,50 @@ class JungfrauJochClient:
|
||||
self._parent_name = parent.name if parent else self.__class__.__name__
|
||||
|
||||
@property
|
||||
def jfj_status(self) -> BrokerStatus:
|
||||
"""Broker status of JungfrauJoch."""
|
||||
def jjf_state(self) -> BrokerStatus:
|
||||
"""Get the status of JungfrauJoch"""
|
||||
response = self.api.status_get()
|
||||
return BrokerStatus(**response.to_dict())
|
||||
|
||||
# pylint: disable=missing-function-docstring
|
||||
@property
|
||||
def initialised(self) -> bool:
|
||||
"""Check if jfj is connected and ready to receive commands"""
|
||||
return self._initialised
|
||||
|
||||
@initialised.setter
|
||||
def initialised(self, value: bool) -> None:
|
||||
"""Set the connected status"""
|
||||
self._initialised = value
|
||||
|
||||
# pylint: disable=missing-function-docstring
|
||||
# TODO this is not correct, as it may be that the state in INACTIVE. Models are not in sync...
|
||||
# REMOVE all model enums as most of the validation takes place in the Pydantic models, i.e. BrokerStatus here..
|
||||
@property
|
||||
def detector_state(self) -> DetectorState:
|
||||
return DetectorState(self.jfj_status.state)
|
||||
"""Get the status of JungfrauJoch"""
|
||||
return DetectorState(self.jjf_state.state)
|
||||
|
||||
def connect_and_initialise(self, timeout: int = 10) -> None:
|
||||
"""
|
||||
Connect and initialise the JungfrauJoch detector. The detector must be in
|
||||
IDLE state to become initialised. This is a blocking call, the timeout parameter
|
||||
will be passed to the HTTP requests timeout method of the wait_for_idle method.
|
||||
|
||||
Args:
|
||||
timeout (int): Timeout in seconds for the initialisation and waiting for IDLE state.
|
||||
"""
|
||||
def connect_and_initialise(self, timeout: int = 10, **kwargs) -> None:
|
||||
"""Check if JungfrauJoch is connected and ready to receive commands"""
|
||||
status = self.detector_state
|
||||
# TODO: #135 Check if the detector has to be in INACTIVE state before initialisation
|
||||
if status != DetectorState.IDLE:
|
||||
self.api.initialize_post()
|
||||
self.wait_for_idle(timeout)
|
||||
self.api.initialize_post() # This is a blocking call....
|
||||
self.wait_for_idle(timeout, request_timeout=timeout) # Blocking call
|
||||
self.initialised = True
|
||||
|
||||
def set_detector_settings(self, settings: dict | DetectorSettings, timeout: int = 10) -> None:
|
||||
"""
|
||||
Set the detector settings. The state of JungfrauJoch must be in IDLE,
|
||||
Error or Inactive state. Please note: a full set of setttings has to be provided,
|
||||
otherwise the settings will be overwritten with default values.
|
||||
"""Set the detector settings. JungfrauJoch must be in IDLE, Error or Inactive state.
|
||||
Note, the full settings have to be provided, otherwise the settings will be overwritten with default values.
|
||||
|
||||
Args:
|
||||
settings (dict): dictionary of settings
|
||||
timeout (int): Timeout in seconds for the HTTP request to set the settings.
|
||||
"""
|
||||
state = self.detector_state
|
||||
if state not in [DetectorState.IDLE, DetectorState.ERROR, DetectorState.INACTIVE]:
|
||||
logger.info(
|
||||
f"JungfrauJoch backend fo device {self._parent_name} is not in IDLE state,"
|
||||
" waiting 1s before retrying..."
|
||||
)
|
||||
time.sleep(1) # Give the detector 1s to become IDLE, retry
|
||||
state = self.detector_state
|
||||
if state not in [DetectorState.IDLE, DetectorState.ERROR, DetectorState.INACTIVE]:
|
||||
raise JungfrauJochClientError(
|
||||
f"Error on {self._parent_name}. Detector must be in IDLE, ERROR or INACTIVE"
|
||||
" state to set settings. Current state: {state}"
|
||||
f"Error in {self._parent_name}. Detector must be in IDLE, ERROR or INACTIVE state to set settings. Current state: {state}"
|
||||
)
|
||||
|
||||
if isinstance(settings, dict):
|
||||
@@ -130,36 +110,28 @@ class JungfrauJochClient:
|
||||
try:
|
||||
self.api.config_detector_put(detector_settings=settings, _request_timeout=timeout)
|
||||
except requests.exceptions.Timeout:
|
||||
raise TimeoutError(
|
||||
f"Timeout on device {self._parent_name} while setting detector settings:\n "
|
||||
f"{yaml.dump(settings, indent=4)}."
|
||||
)
|
||||
raise TimeoutError(f"Timeout while setting detector settings for {self._parent_name}")
|
||||
except Exception:
|
||||
content = traceback.format_exc()
|
||||
logger.error(
|
||||
f"Error on device {self._parent_name} while setting detector settings:\n "
|
||||
f"{yaml.dump(settings, indent=4)}. Error traceback: {content}"
|
||||
)
|
||||
raise JungfrauJochClientError(
|
||||
f"Error on device {self._parent_name} while setting detector settings:\n "
|
||||
f"{yaml.dump(settings, indent=4)}. Full traceback: {content}."
|
||||
f"Error while setting detector settings for {self._parent_name}: {content}"
|
||||
)
|
||||
|
||||
def start(self, settings: dict | DatasetSettings, request_timeout: float = 10) -> None:
|
||||
"""
|
||||
Start the acquisition with the provided dataset settings.
|
||||
The detector must be in IDLE state. Settings must always provide a full set of
|
||||
parameters, missing parameters will be set to default values.
|
||||
"""Start the mesaurement. DatasetSettings must be provided, and JungfrauJoch must be in IDLE state.
|
||||
The method call is blocking and JungfrauJoch will be ready to measure after the call resolves.
|
||||
|
||||
Args:
|
||||
settings (dict | DatasetSettings): Dataset settings to start the acquisition with.
|
||||
request_timeout (float): Timeout in sec for the HTTP request to start the acquisition.
|
||||
settings (dict): dictionary of settings
|
||||
|
||||
Please check the DataSettings class for the available settings. Minimum required settings are
|
||||
beam_x_pxl, beam_y_pxl, detector_distance_mm, incident_energy_keV.
|
||||
|
||||
"""
|
||||
state = self.detector_state
|
||||
if state != DetectorState.IDLE:
|
||||
raise JungfrauJochClientError(
|
||||
f"Error on device {self._parent_name}. "
|
||||
f"Detector must be in IDLE state to start acquisition. Current state: {state}"
|
||||
f"Error in {self._parent_name}. Detector must be in IDLE state to set settings. Current state: {state}"
|
||||
)
|
||||
|
||||
if isinstance(settings, dict):
|
||||
@@ -169,80 +141,46 @@ class JungfrauJochClient:
|
||||
dataset_settings=settings, _request_timeout=request_timeout
|
||||
)
|
||||
except requests.exceptions.Timeout:
|
||||
content = traceback.format_exc()
|
||||
logger.error(
|
||||
f"Timeout error after {request_timeout} seconds on device {self._parent_name} "
|
||||
f"during 'start' call with dataset settings: {yaml.dump(settings, indent=4)}. \n"
|
||||
f"Traceback: {content}"
|
||||
)
|
||||
raise TimeoutError(
|
||||
f"Timeout error after {request_timeout} seconds on device {self._parent_name} "
|
||||
f"during 'start' call with dataset settings: {yaml.dump(settings, indent=4)}."
|
||||
f"TimeoutError in JungfrauJochClient for parent device {self._parent_name} for 'start' call"
|
||||
)
|
||||
except Exception:
|
||||
content = traceback.format_exc()
|
||||
logger.error(
|
||||
f"Error on device {self._parent_name} during 'start' post with dataset settings: \n"
|
||||
f"{yaml.dump(settings, indent=4)}. \nTraceback: {content}"
|
||||
)
|
||||
raise JungfrauJochClientError(
|
||||
f"Error on device {self._parent_name} during 'start' post with dataset settings: \n"
|
||||
f"{yaml.dump(settings, indent=4)}. \nTraceback: {content}."
|
||||
f"Error in JungfrauJochClient for parent device {self._parent_name} during 'start' call: {content}"
|
||||
)
|
||||
|
||||
def stop(self, request_timeout: float = 0.5) -> None:
|
||||
"""Stop the acquisition, this only logs errors and is not raising."""
|
||||
try:
|
||||
self.api.cancel_post_with_http_info(_request_timeout=request_timeout)
|
||||
except requests.exceptions.Timeout:
|
||||
content = traceback.format_exc()
|
||||
logger.error(
|
||||
f"Timeout in JungFrauJochClient for device {self._parent_name} during stop: {content}"
|
||||
)
|
||||
except Exception:
|
||||
content = traceback.format_exc()
|
||||
logger.error(
|
||||
f"Error in JungFrauJochClient for device {self._parent_name} during stop: {content}"
|
||||
)
|
||||
|
||||
def _stop_call(self):
|
||||
try:
|
||||
self.api.cancel_post_with_http_info() # (_request_timeout=request_timeout)
|
||||
except requests.exceptions.Timeout:
|
||||
content = traceback.format_exc()
|
||||
logger.error(
|
||||
f"Timeout error after {request_timeout} seconds on device {self._parent_name} "
|
||||
f"during stop: {content}"
|
||||
)
|
||||
except Exception:
|
||||
content = traceback.format_exc()
|
||||
logger.error(f"Error on device {self._parent_name} during stop: {content}")
|
||||
|
||||
thread = threading.Thread(
|
||||
target=_stop_call, daemon=True, args=(self,), name="stop_jungfraujoch_thread"
|
||||
)
|
||||
thread.start()
|
||||
|
||||
def wait_for_idle(self, timeout: int = 10, raise_on_timeout: bool = True) -> bool:
|
||||
"""
|
||||
Method to wait until the detector is in IDLE state. This is a blocking call with a
|
||||
timeout that can be specified. The additional parameter raise_on_timeout can be used to
|
||||
raise an exception on timeout instead of returning boolean True/False.
|
||||
def wait_for_idle(self, timeout: int = 10, request_timeout: float | None = None) -> bool:
|
||||
"""Wait for JungfrauJoch to be in Idle state. Blocking call with timeout.
|
||||
|
||||
Args:
|
||||
timeout (int): timeout in seconds
|
||||
raise_on_timeout (bool): If True, raises an exception on timeout. Default is True.
|
||||
Returns:
|
||||
bool: True if the detector is in IDLE state, False if timeout occurred
|
||||
"""
|
||||
if request_timeout is None:
|
||||
request_timeout = timeout
|
||||
try:
|
||||
self.api.wait_till_done_post(timeout=timeout, _request_timeout=timeout)
|
||||
self.api.wait_till_done_post(timeout=timeout, _request_timeout=request_timeout)
|
||||
except requests.exceptions.Timeout:
|
||||
raise TimeoutError(f"HTTP request timeout in wait_for_idle for {self._parent_name}")
|
||||
except Exception:
|
||||
content = traceback.format_exc()
|
||||
logger.info(
|
||||
f"Timeout after {timeout} seconds on device {self._parent_name} in wait_for_idle: {content}"
|
||||
)
|
||||
if raise_on_timeout:
|
||||
raise TimeoutError(
|
||||
f"Timeout after {timeout} seconds on device {self._parent_name} in wait_for_idle."
|
||||
)
|
||||
return False
|
||||
except Exception as exc:
|
||||
content = traceback.format_exc()
|
||||
logger.info(
|
||||
f"Error on device {self._parent_name} in wait_for_idle. Full traceback: {content}"
|
||||
)
|
||||
if raise_on_timeout:
|
||||
raise JungfrauJochClientError(
|
||||
f"Error on device {self._parent_name} in wait_for_idle: {content}"
|
||||
) from exc
|
||||
logger.debug(f"Waiting for device {self._parent_name} to become IDLE: {content}")
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -1,136 +1,22 @@
|
||||
"""
|
||||
Module for the JungfrauJoch preview ZMQ stream for the Eiger detector at cSAXS.
|
||||
The Preview client is implemented for the JungfrauJoch ZMQ PUB-SUB interface, and
|
||||
should be independent of the EIGER detector type.
|
||||
|
||||
The client connects to the ZMQ PUB-SUB preview stream and calls a user provided callback
|
||||
function with the decompressed messages received from the stream. The callback needs to be
|
||||
able to deal with the different message types sent by the JungfrauJoch server ("start",
|
||||
"image", "end") as described in the JungfrauJoch ZEROMQ preview stream documentation.
|
||||
(https://jungfraujoch.readthedocs.io/en/latest/ZEROMQ_STREAM.html#preview-stream).
|
||||
"""
|
||||
"""Module for the Eiger preview ZMQ stream."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
from typing import Callable
|
||||
|
||||
import cbor2
|
||||
import numpy as np
|
||||
import zmq
|
||||
from bec_lib.logger import bec_logger
|
||||
from dectris.compression import decompress
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
###############################
|
||||
###### CBOR TAG DECODERS ######
|
||||
###############################
|
||||
# Dectris specific CBOR tags and decoders for Jungfrau data
|
||||
# Reference:
|
||||
# https://github.com/dectris/documentation/blob/main/stream_v2/examples/client.py
|
||||
|
||||
|
||||
def decode_multi_dim_array(tag: cbor2.CBORTag, column_major: bool = False):
|
||||
"""Decode a multi-dimensional array from a CBOR tag."""
|
||||
dimensions, contents = tag.value
|
||||
if isinstance(contents, list):
|
||||
array = np.empty((len(contents),), dtype=object)
|
||||
array[:] = contents
|
||||
elif isinstance(contents, (np.ndarray, np.generic)):
|
||||
array = contents
|
||||
else:
|
||||
raise cbor2.CBORDecodeValueError("expected array or typed array")
|
||||
return array.reshape(dimensions, order="F" if column_major else "C")
|
||||
|
||||
|
||||
def decode_typed_array(tag: cbor2.CBORTag, dtype: str):
|
||||
"""Decode a typed array from a CBOR tag."""
|
||||
if not isinstance(tag.value, bytes):
|
||||
raise cbor2.CBORDecodeValueError("expected byte string in typed array")
|
||||
return np.frombuffer(tag.value, dtype=dtype)
|
||||
|
||||
|
||||
def decode_dectris_compression(tag: cbor2.CBORTag):
|
||||
"""Decode a Dectris compressed array from a CBOR tag."""
|
||||
algorithm, elem_size, encoded = tag.value
|
||||
return decompress(encoded, algorithm, elem_size=elem_size)
|
||||
|
||||
|
||||
#########################################
|
||||
#### Dectris CBOR TAG Extensions ########
|
||||
#########################################
|
||||
|
||||
# Mapping of various additional CBOR tags from Dectris to decoder functions
|
||||
tag_decoders = {
|
||||
40: lambda tag: decode_multi_dim_array(tag, column_major=False),
|
||||
64: lambda tag: decode_typed_array(tag, dtype="u1"),
|
||||
65: lambda tag: decode_typed_array(tag, dtype=">u2"),
|
||||
66: lambda tag: decode_typed_array(tag, dtype=">u4"),
|
||||
67: lambda tag: decode_typed_array(tag, dtype=">u8"),
|
||||
68: lambda tag: decode_typed_array(tag, dtype="u1"),
|
||||
69: lambda tag: decode_typed_array(tag, dtype="<u2"),
|
||||
70: lambda tag: decode_typed_array(tag, dtype="<u4"),
|
||||
71: lambda tag: decode_typed_array(tag, dtype="<u8"),
|
||||
72: lambda tag: decode_typed_array(tag, dtype="i1"),
|
||||
73: lambda tag: decode_typed_array(tag, dtype=">i2"),
|
||||
74: lambda tag: decode_typed_array(tag, dtype=">i4"),
|
||||
75: lambda tag: decode_typed_array(tag, dtype=">i8"),
|
||||
77: lambda tag: decode_typed_array(tag, dtype="<i2"),
|
||||
78: lambda tag: decode_typed_array(tag, dtype="<i4"),
|
||||
79: lambda tag: decode_typed_array(tag, dtype="<i8"),
|
||||
80: lambda tag: decode_typed_array(tag, dtype=">f2"),
|
||||
81: lambda tag: decode_typed_array(tag, dtype=">f4"),
|
||||
82: lambda tag: decode_typed_array(tag, dtype=">f8"),
|
||||
83: lambda tag: decode_typed_array(tag, dtype=">f16"),
|
||||
84: lambda tag: decode_typed_array(tag, dtype="<f2"),
|
||||
85: lambda tag: decode_typed_array(tag, dtype="<f4"),
|
||||
86: lambda tag: decode_typed_array(tag, dtype="<f8"),
|
||||
87: lambda tag: decode_typed_array(tag, dtype="<f16"),
|
||||
1040: lambda tag: decode_multi_dim_array(tag, column_major=True),
|
||||
56500: lambda tag: decode_dectris_compression(tag), # pylint: disable=unnecessary-lambda
|
||||
}
|
||||
|
||||
|
||||
def tag_hook(decoder, tag: int):
|
||||
"""
|
||||
Tag hook for the cbor2.loads method. Both arguments "decoder" and "tag" mus be present.
|
||||
We use the tag to choose the respective decoder from the tag_decoders registry if available.
|
||||
"""
|
||||
tag_decoder = tag_decoders.get(tag.tag)
|
||||
return tag_decoder(tag) if tag_decoder else tag
|
||||
|
||||
|
||||
######################
|
||||
#### ZMQ Settings ####
|
||||
######################
|
||||
|
||||
ZMQ_TOPIC_FILTER = b"" # Subscribe to all topics
|
||||
ZMQ_CONFLATE_SETTING = 1 # Keep only the most recent message
|
||||
ZMQ_RCVHWM_SETTING = 1 # Set high water mark to 1, this configures the max number of queue messages
|
||||
|
||||
|
||||
#################################
|
||||
#### Jungfrau Preview Client ####
|
||||
#################################
|
||||
ZMQ_TOPIC_FILTER = b""
|
||||
|
||||
|
||||
class JungfrauJochPreview:
|
||||
"""
|
||||
Preview client for the JungfrauJoch ZMQ preview stream. The client is started with
|
||||
a URL to receive the data from the JungfrauJoch PUB-SUB preview interface, and a
|
||||
callback function that is called with messages received from the preview stream.
|
||||
The callback needs to be able to deal with the different message types sent
|
||||
by the JungfrauJoch server ("start", "image", "end") as described in the
|
||||
JungfrauJoch ZEROMQ preview stream documentation. Messages are dictionary dumps.
|
||||
(https://jungfraujoch.readthedocs.io/en/latest/ZEROMQ_STREAM.html#preview-stream).
|
||||
|
||||
Args:
|
||||
url (str): ZMQ PUB-SUB preview stream URL.
|
||||
cb (Callable): Callback function called with messages received from the stream.
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["start", "stop"]
|
||||
|
||||
def __init__(self, url: str, cb: Callable):
|
||||
@@ -141,18 +27,16 @@ class JungfrauJochPreview:
|
||||
self._on_update_callback = cb
|
||||
|
||||
def connect(self):
|
||||
"""
|
||||
Connect to the JungfrauJoch PUB-SUB streaming interface. If the connection is refused
|
||||
it will reattempt a second time after a one second delay.
|
||||
"""Connect to the JungfrauJoch PUB-SUB streaming interface
|
||||
|
||||
JungfrauJoch may reject connection for a few seconds when it restarts,
|
||||
so if it fails, wait a bit and try to connect again.
|
||||
"""
|
||||
# pylint: disable=no-member
|
||||
|
||||
context = zmq.Context()
|
||||
self._socket = context.socket(zmq.SUB)
|
||||
self._socket.setsockopt(zmq.CONFLATE, ZMQ_CONFLATE_SETTING)
|
||||
self._socket.setsockopt(zmq.SUBSCRIBE, ZMQ_TOPIC_FILTER)
|
||||
self._socket.setsockopt(zmq.RCVHWM, ZMQ_RCVHWM_SETTING)
|
||||
|
||||
try:
|
||||
self._socket.connect(self.url)
|
||||
except ConnectionRefusedError:
|
||||
@@ -160,26 +44,17 @@ class JungfrauJochPreview:
|
||||
self._socket.connect(self.url)
|
||||
|
||||
def start(self):
|
||||
"""Start the ZMQ update loop in a background thread."""
|
||||
self._zmq_thread = threading.Thread(
|
||||
target=self._zmq_update_loop, daemon=True, name="JungfrauJoch_live_preview"
|
||||
)
|
||||
self._zmq_thread.start()
|
||||
|
||||
def stop(self):
|
||||
"""Stop the ZMQ update loop and wait for the thread to finish."""
|
||||
self._shutdown_event.set()
|
||||
if self._zmq_thread:
|
||||
self._zmq_thread.join(timeout=1.0)
|
||||
self._zmq_thread.join()
|
||||
|
||||
def _zmq_update_loop(self, poll_interval: float = 0.2):
|
||||
"""
|
||||
ZMQ update loop running in a background thread. The polling is throttled by
|
||||
the poll_interval parameter.
|
||||
|
||||
Args:
|
||||
poll_interval (float): Time in seconds to wait between polling attempts.
|
||||
"""
|
||||
def _zmq_update_loop(self):
|
||||
while not self._shutdown_event.is_set():
|
||||
if self._socket is None:
|
||||
self.connect()
|
||||
@@ -189,21 +64,18 @@ class JungfrauJochPreview:
|
||||
# Happens when ZMQ partially delivers the multipart message
|
||||
pass
|
||||
except zmq.error.Again:
|
||||
logger.debug(
|
||||
f"ZMQ Again exception, receive queue is empty for JFJ preview at {self.url}."
|
||||
)
|
||||
finally:
|
||||
# We throttle the polling to avoid heavy load on the device server
|
||||
time.sleep(poll_interval)
|
||||
# Happens when receive queue is empty
|
||||
time.sleep(0.1)
|
||||
|
||||
def _poll(self):
|
||||
"""
|
||||
Poll the ZMQ socket for new data. We are currently subscribing and unsubscribing
|
||||
for each poll loop to avoid receiving too many messages. Throttling of the update
|
||||
loop is handled in the _zmq_update_loop method.
|
||||
Poll the ZMQ socket for new data. It will throttle the data update and
|
||||
only subscribe to the topic for a single update. This is not very nice
|
||||
but it seems like there is currently no option to set the update rate on
|
||||
the backend.
|
||||
"""
|
||||
|
||||
if self._shutdown_event.is_set():
|
||||
if self._shutdown_event.wait(0.2):
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -218,19 +90,7 @@ class JungfrauJochPreview:
|
||||
# Unsubscribe from the topic
|
||||
self._socket.setsockopt(zmq.UNSUBSCRIBE, ZMQ_TOPIC_FILTER)
|
||||
|
||||
def _parse_data(self, bytes_list: list[bytes]):
|
||||
"""
|
||||
Parse the received ZMQ data from the JungfrauJoch preview stream.
|
||||
We will call the _on_update_callback with the decompressed messages as a dictionary.
|
||||
|
||||
The callback needs to be able to deal with the different message types sent
|
||||
by the JungfrauJoch server ("start", "image", "end") as described in the
|
||||
JungfrauJoch ZEROMQ preview stream documentation. Messages are dictionary dumps.
|
||||
(https://jungfraujoch.readthedocs.io/en/latest/ZEROMQ_STREAM.html#preview-stream).
|
||||
|
||||
Args:
|
||||
bytes_list (list[bytes]): List of byte messages received from ZMQ recv_multipart.
|
||||
"""
|
||||
for byte_msg in bytes_list:
|
||||
msg = cbor2.loads(byte_msg, tag_hook=tag_hook)
|
||||
self._on_update_callback(msg)
|
||||
def _parse_data(self, data):
|
||||
# TODO decode and parse the data
|
||||
# self._on_update_callback(data)
|
||||
pass
|
||||
|
||||
@@ -4,7 +4,6 @@ This module contains the base class for Galil controllers as well as the signals
|
||||
|
||||
import functools
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from bec_lib import bec_logger
|
||||
from ophyd.utils import ReadOnlyError
|
||||
@@ -348,7 +347,7 @@ class GalilSignalBase(SocketSignal):
|
||||
def __init__(self, signal_name, **kwargs):
|
||||
self.signal_name = signal_name
|
||||
super().__init__(**kwargs)
|
||||
self.controller = self.root.controller if hasattr(self.root, "controller") else None
|
||||
self.controller = self.parent.controller
|
||||
|
||||
|
||||
class GalilSignalRO(GalilSignalBase):
|
||||
|
||||
@@ -1,47 +1,13 @@
|
||||
"""
|
||||
Module for the Galil RIO (RIO-471xx) controller interface. The controller is a compact PLC
|
||||
with Ethernet. It has digital and analog I/O as well as counters and timers.
|
||||
|
||||
Link to the Galil RIO vendor page:
|
||||
https://www.galil.com/plcs/remote-io/rio-471xx
|
||||
|
||||
This module provides the GalilRIOController for communication with the RIO controller
|
||||
over TCP/IP. It also provides a device integration that interfaces to its
|
||||
8 analog channels, and 16 digital output channels. Some PLCs may have 24 digital output channels,
|
||||
which can be easily supported by changing the _NUM_DIGITAL_OUTPUT_CHANNELS variable.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Literal
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd import DynamicDeviceComponent as DDC
|
||||
from ophyd import Kind
|
||||
from ophyd.utils import ReadOnlyError
|
||||
from ophyd_devices import PSIDeviceBase
|
||||
from ophyd_devices.utils.controller import Controller, threadlocked
|
||||
from ophyd_devices.utils.socket import SocketIO
|
||||
from ophyd_devices.utils.socket import SocketSignal
|
||||
|
||||
from csaxs_bec.devices.omny.galil.galil_ophyd import (
|
||||
GalilCommunicationError,
|
||||
GalilSignalBase,
|
||||
retry_once,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
from bec_lib.devicemanager import ScanInfo
|
||||
from bec_server.device_server.devices.devicemanager import DeviceManagerDS
|
||||
|
||||
logger = bec_logger.logger
|
||||
from csaxs_bec.devices.omny.galil.galil_ophyd import GalilCommunicationError, retry_once
|
||||
|
||||
|
||||
class GalilRIOController(Controller):
|
||||
"""Controller Class for Galil RIO controller communication."""
|
||||
class GalilRIO(Controller):
|
||||
|
||||
@threadlocked
|
||||
def socket_put(self, val: str) -> None:
|
||||
"""Socker put method."""
|
||||
self.sock.put(f"{val}\r".encode())
|
||||
|
||||
@retry_once
|
||||
@@ -62,229 +28,8 @@ class GalilRIOController(Controller):
|
||||
)
|
||||
|
||||
|
||||
class GalilRIOAnalogSignalRO(GalilSignalBase):
|
||||
"""
|
||||
Signal for reading analog input channels of the Galil RIO controller. This signal is read-only, so
|
||||
the set method raises a ReadOnlyError. The get method retrieves the values of all analog
|
||||
channels in a single socket command. The readback values of all channels are updated based
|
||||
on the response, and subscriptions are run for all channels. Readings are cached as implemented
|
||||
in the SocketSignal class, so that multiple reads of the same channel within an update cycle do
|
||||
not result in multiple socket calls.
|
||||
|
||||
Args:
|
||||
signal_name (str): The name of the signal, e.g. "ch0", "ch1", ..., "ch7"
|
||||
channel (int): The channel number corresponding to the signal, e.g. 0 for "ch0", 1 for "ch1", ...
|
||||
parent (GalilRIO): The parent device instance that this signal belongs to.
|
||||
"""
|
||||
|
||||
_NUM_ANALOG_CHANNELS = 8
|
||||
|
||||
def __init__(self, signal_name: str, channel: int, parent: GalilRIO, **kwargs):
|
||||
super().__init__(signal_name=signal_name, parent=parent, **kwargs)
|
||||
self._channel = channel
|
||||
self._metadata["connected"] = False
|
||||
self._metadata["write_access"] = False
|
||||
|
||||
def _socket_set(self, val):
|
||||
"""Read-only signal, so set method raises an error."""
|
||||
raise ReadOnlyError(f"Signal {self.name} is read-only.")
|
||||
|
||||
def _socket_get(self) -> float:
|
||||
"""Get command for the readback signal"""
|
||||
cmd = "MG@" + ", @".join([f"AN[{ii}]" for ii in range(self._NUM_ANALOG_CHANNELS)])
|
||||
ret = self.controller.socket_put_and_receive(cmd)
|
||||
values = [float(val) for val in ret.strip().split(" ")]
|
||||
# Run updates for all channels. This also updates the _readback and metadata timestamp
|
||||
# value of this channel.
|
||||
self._update_all_channels(values)
|
||||
return self._readback
|
||||
|
||||
# pylint: disable=protected-access
|
||||
def _update_all_channels(self, values: list[float]) -> None:
|
||||
"""
|
||||
Method to receive a list of readback values for channels 0 to 7. Updates for each channel idx
|
||||
are applied to the corresponding GalilRIOAnalogSignalRO signal with matching attr_name "ch{idx}".
|
||||
|
||||
We also update the _last_readback attribute of each of the signals, to avoid multiple socket calls,
|
||||
but rather use the cached value of the combined reading for all channels.
|
||||
|
||||
Args:
|
||||
values (list[float]): List of new readback values for all channels, where the
|
||||
index corresponds to the channel number (0-7).
|
||||
"""
|
||||
updates: dict[str, tuple[float, float]] = {} # attr_name -> (new_val, old_val)
|
||||
# Update all readbacks first
|
||||
for walk in self.parent.walk_signals():
|
||||
if isinstance(walk.item, GalilRIOAnalogSignalRO):
|
||||
idx = int(walk.item.attr_name[-1])
|
||||
if 0 <= idx < len(values):
|
||||
old_val = walk.item._readback
|
||||
new_val = values[idx]
|
||||
walk.item._metadata["timestamp"] = self._last_readback
|
||||
walk.item._last_readback = self._last_readback
|
||||
walk.item._readback = new_val
|
||||
if (
|
||||
idx != self._channel
|
||||
): # Only run subscriptions on other channels, not on itself
|
||||
# as this is handled by the SocketSignal and we want to avoid running multiple
|
||||
# subscriptions for the same channel update
|
||||
updates[walk.item.attr_name] = (new_val, old_val)
|
||||
else:
|
||||
logger.warning(
|
||||
f"Received {len(values)} values but found channel index {idx} in signal {walk.item.name}. Skipping update for this signal."
|
||||
)
|
||||
|
||||
# Run subscriptions after all readbacks have been updated
|
||||
# on all channels except the one that triggered the update
|
||||
for walk in self.parent.walk_signals():
|
||||
if walk.item.attr_name in updates:
|
||||
new_val, old_val = updates[walk.item.attr_name]
|
||||
walk.item._run_subs(
|
||||
sub_type=walk.item.SUB_VALUE,
|
||||
old_value=old_val,
|
||||
value=new_val,
|
||||
timestamp=self._last_readback,
|
||||
)
|
||||
|
||||
|
||||
class GalilRIODigitalOutSignal(GalilSignalBase):
|
||||
"""Signal for controlling digital outputs of the Galil RIO controller."""
|
||||
|
||||
_NUM_DIGITAL_OUTPUT_CHANNELS = 16
|
||||
|
||||
def __init__(self, signal_name: str, channel: int, parent: GalilRIO, **kwargs):
|
||||
super().__init__(signal_name, parent=parent, **kwargs)
|
||||
self._channel = channel
|
||||
self._metadata["connected"] = False
|
||||
|
||||
def _socket_get(self) -> float:
|
||||
"""Get command for the readback signal"""
|
||||
cmd = f"MG@OUT[{self._channel}]"
|
||||
ret = self.controller.socket_put_and_receive(cmd)
|
||||
self._readback = float(ret.strip())
|
||||
return self._readback
|
||||
|
||||
def _socket_set(self, val: Literal[0, 1]) -> None:
|
||||
"""Set command for the digital output signal. Value should be 0 or 1."""
|
||||
|
||||
if val not in (0, 1):
|
||||
raise ValueError("Digital output value must be 0 or 1.")
|
||||
cmd = f"SB{self._channel}" if val == 1 else f"CB{self._channel}"
|
||||
self.controller.socket_put_confirmed(cmd)
|
||||
|
||||
|
||||
def _create_analog_channels(num_channels: int) -> dict[str, tuple]:
|
||||
"""
|
||||
Helper method to create a dictionary of analog channel definitions for the DynamicDeviceComponent.
|
||||
|
||||
Args:
|
||||
num_channels (int): The number of analog channels to create.
|
||||
"""
|
||||
an_channels = {}
|
||||
for i in range(0, num_channels):
|
||||
an_channels[f"ch{i}"] = (
|
||||
GalilRIOAnalogSignalRO,
|
||||
f"ch{i}",
|
||||
{"kind": Kind.normal, "notify_bec": True, "channel": i, "doc": f"Analog channel {i}."},
|
||||
)
|
||||
return an_channels
|
||||
|
||||
|
||||
def _create_digital_output_channels(num_channels: int) -> dict[str, tuple]:
|
||||
"""
|
||||
Helper method to create a dictionary of digital output channel definitions for the DynamicDeviceComponent.
|
||||
|
||||
Args:
|
||||
num_channels (int): The number of digital output channels to create.
|
||||
"""
|
||||
di_out_channels = {}
|
||||
for i in range(0, num_channels):
|
||||
di_out_channels[f"ch{i}"] = (
|
||||
GalilRIODigitalOutSignal,
|
||||
f"ch{i}",
|
||||
{
|
||||
"kind": Kind.config,
|
||||
"notify_bec": True,
|
||||
"channel": i,
|
||||
"doc": f"Digital output channel {i}.",
|
||||
},
|
||||
)
|
||||
return di_out_channels
|
||||
|
||||
|
||||
class GalilRIO(PSIDeviceBase):
|
||||
"""
|
||||
Galil RIO controller integration with 16 digital output channels and 8 analog input channels.
|
||||
The default port for the controller is 23.
|
||||
|
||||
Args:
|
||||
host (str): Hostname or IP address of the Galil RIO controller.
|
||||
port (int, optional): Port number for the TCP/IP connection. Defaults to 23.
|
||||
socket_cls (type[SocketIO], optional): Socket class to use for communication. Defaults to SocketIO.
|
||||
scan_info (ScanInfo, optional): ScanInfo object for the device.
|
||||
device_manager (DeviceManagerDS): The device manager instance that manages this device.
|
||||
**kwargs: Additional keyword arguments passed to the PSIDeviceBase constructor.
|
||||
"""
|
||||
|
||||
SUB_CONNECTION_CHANGE = "connection_change"
|
||||
|
||||
#############################
|
||||
### Analog input channels ###
|
||||
#############################
|
||||
|
||||
analog_in = DDC(_create_analog_channels(GalilRIOAnalogSignalRO._NUM_ANALOG_CHANNELS))
|
||||
digital_out = DDC(
|
||||
_create_digital_output_channels(GalilRIODigitalOutSignal._NUM_DIGITAL_OUTPUT_CHANNELS)
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
name: str,
|
||||
host: str,
|
||||
device_manager: DeviceManagerDS,
|
||||
port: int | None = None,
|
||||
socket_cls: type[SocketIO] = SocketIO,
|
||||
scan_info: ScanInfo | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
if port is None:
|
||||
port = 23 # Default port for Galil RIO controller
|
||||
self.controller = GalilRIOController(
|
||||
name=f"GalilRIOController_{name}",
|
||||
socket_cls=socket_cls,
|
||||
socket_host=host,
|
||||
socket_port=port,
|
||||
device_manager=device_manager,
|
||||
)
|
||||
self._readback_metadata: dict[str, float] = {"last_readback": 0.0}
|
||||
super().__init__(name=name, device_manager=device_manager, scan_info=scan_info, **kwargs)
|
||||
self.controller.subscribe(
|
||||
self._update_connection_state, event_type=self.SUB_CONNECTION_CHANGE
|
||||
)
|
||||
|
||||
# pylint: disable=arguments-differ
|
||||
def wait_for_connection(self, timeout: float = 30.0) -> None:
|
||||
"""Wait for the RIO controller to be connected within timeout period."""
|
||||
self.controller.on(timeout=timeout)
|
||||
|
||||
def destroy(self) -> None:
|
||||
"""Make sure to turn off the controller socket on destroy."""
|
||||
self.controller.off(update_config=False)
|
||||
return super().destroy()
|
||||
|
||||
# pylint: disable=protected-access
|
||||
def _update_connection_state(self, **kwargs):
|
||||
for walk in self.walk_signals():
|
||||
walk.item._metadata["connected"] = self.controller.connected
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
HOST_NAME = "129.129.122.14"
|
||||
from bec_server.device_server.tests.utils import DMMock
|
||||
|
||||
dm = DMMock()
|
||||
rio = GalilRIO(name="rio", host=HOST_NAME, device_manager=dm)
|
||||
rio.wait_for_connection(timeout=10)
|
||||
print("Connected:", rio.an_ch1.read())
|
||||
print("All channels:", rio.read())
|
||||
class GalilRIOSignalBase(SocketSignal):
|
||||
def __init__(self, signal_name, **kwargs):
|
||||
self.signal_name = signal_name
|
||||
super().__init__(**kwargs)
|
||||
self.rio_controller = self.parent.rio_controller
|
||||
|
||||
@@ -498,9 +498,6 @@ class RtFlomniController(Controller):
|
||||
)
|
||||
# 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)
|
||||
@@ -632,8 +629,6 @@ class RtFlomniMotor(Device, PositionerBase):
|
||||
SUB_CONNECTION_CHANGE = "connection_change"
|
||||
_default_sub = SUB_READBACK
|
||||
|
||||
connectionTimeout = 20
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
axis_Id,
|
||||
|
||||
@@ -1,87 +1,82 @@
|
||||
"""
|
||||
Fast Shutter control for OMNY setup. If started with a config file in which the device_manager
|
||||
has a 'fsh' device (cSAXSFastEpicsShutter), this device will be used as the shutter.
|
||||
Otherwise, the device will create a dummy shutter device that will log warnings when shutter
|
||||
methods are called, but will not raise exceptions.
|
||||
"""
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
import time
|
||||
import socket
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, Signal
|
||||
from ophyd_devices import PSIDeviceBase
|
||||
|
||||
logger = bec_logger.logger
|
||||
from ophyd import Device
|
||||
from ophyd import EpicsSignal
|
||||
|
||||
|
||||
class OMNYFastShutter(PSIDeviceBase, Device):
|
||||
class OMNYFastEpicsShutterError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _detect_host_pv():
|
||||
"""Detect host subnet and return appropriate PV name."""
|
||||
try:
|
||||
hostname = socket.gethostname()
|
||||
local_ip = socket.gethostbyname(hostname)
|
||||
if local_ip.startswith("129.129.122."):
|
||||
return "X12SA-ES1-TTL:OUT_01"
|
||||
else:
|
||||
return "XOMNYI-XEYE-DUMMYSHUTTER:0"
|
||||
except Exception as ex:
|
||||
print(f"Warning: could not detect IP subnet ({ex}), using dummy shutter.")
|
||||
return "XOMNYI-XEYE-DUMMYSHUTTER:0"
|
||||
|
||||
|
||||
class OMNYFastEpicsShutter(Device):
|
||||
"""
|
||||
Fast Shutter control for OMNY setup. If started with at the beamline, it will expose
|
||||
the shutter control methods (fshopen, fshclose, fshstatus, fshinfo) from the
|
||||
cSAXSFastEpicsShutter device. The device is identified by the 'fsh' name in the device manager.
|
||||
If the 'fsh' device is not found in the device manager, this device will create a dummy shutter
|
||||
and log warnings when shutter methods are called, but will not raise exceptions.
|
||||
Fast EPICS shutter with automatic PV selection based on host subnet.
|
||||
"""
|
||||
|
||||
USER_ACCESS = ["fshopen", "fshclose", "fshstatus", "fshinfo", "help", "fshstatus_readback"]
|
||||
USER_ACCESS = ["fshopen", "fshclose", "fshstatus", "fshinfo", "help"]
|
||||
SUB_VALUE = "value"
|
||||
_default_sub = SUB_VALUE
|
||||
|
||||
shutter = Cpt(Signal, name="shutter")
|
||||
# PV is detected dynamically at import time
|
||||
shutter = Cpt(EpicsSignal, name="shutter", read_pv=_detect_host_pv(), auto_monitor=True)
|
||||
|
||||
def __init__(self, prefix="", *, name, **kwargs):
|
||||
super().__init__(prefix, name=name, **kwargs)
|
||||
self.shutter.subscribe(self._emit_value)
|
||||
|
||||
def _emit_value(self, **kwargs):
|
||||
timestamp = kwargs.pop("timestamp", time.time())
|
||||
self.wait_for_connection()
|
||||
self._run_subs(sub_type=self.SUB_VALUE, timestamp=timestamp, obj=self)
|
||||
|
||||
# -----------------------------------------------------
|
||||
# User-facing shutter control functions
|
||||
# -----------------------------------------------------
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def _check_if_cSAXS_shutter_exists_in_config(self) -> bool:
|
||||
"""
|
||||
Check on the device manager if the shutter device exists.
|
||||
|
||||
Returns:
|
||||
bool: True if the 'fsh' device exists in the device manager, False otherwise
|
||||
"""
|
||||
if self.device_manager.devices.get("fsh", None) is None:
|
||||
logger.warning(f"Fast shutter device not found for {self.name}.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def fshopen(self):
|
||||
"""Open the fast shutter."""
|
||||
if self._check_if_cSAXS_shutter_exists_in_config():
|
||||
return self.device_manager.devices["fsh"].fshopen()
|
||||
else:
|
||||
self.shutter.put(1)
|
||||
try:
|
||||
self.shutter.put(1, wait=True)
|
||||
except Exception as ex:
|
||||
raise OMNYFastEpicsShutterError(f"Failed to open shutter: {ex}")
|
||||
|
||||
def fshclose(self):
|
||||
"""Close the fast shutter."""
|
||||
if self._check_if_cSAXS_shutter_exists_in_config():
|
||||
return self.device_manager.devices["fsh"].fshclose()
|
||||
else:
|
||||
self.shutter.put(0)
|
||||
try:
|
||||
self.shutter.put(0, wait=True)
|
||||
except Exception as ex:
|
||||
raise OMNYFastEpicsShutterError(f"Failed to close shutter: {ex}")
|
||||
|
||||
def fshstatus(self):
|
||||
"""Return the fast shutter status (0=closed, 1=open)."""
|
||||
if self._check_if_cSAXS_shutter_exists_in_config():
|
||||
return self.device_manager.devices["fsh"].fshstatus()
|
||||
else:
|
||||
try:
|
||||
return self.shutter.get()
|
||||
except Exception as ex:
|
||||
raise OMNYFastEpicsShutterError(f"Failed to read shutter status: {ex}")
|
||||
|
||||
def fshinfo(self):
|
||||
"""Print information about which EPICS PV channel is being used."""
|
||||
if self._check_if_cSAXS_shutter_exists_in_config():
|
||||
return self.device_manager.devices["fsh"].fshinfo()
|
||||
else:
|
||||
print("Using dummy fast shutter device. No EPICS channel is connected.")
|
||||
pvname = self.shutter.pvname
|
||||
print(f"Fast shutter connected to EPICS channel: {pvname}")
|
||||
return pvname
|
||||
|
||||
def help(self):
|
||||
"""Display available user methods."""
|
||||
print("Available methods:")
|
||||
for method in self.USER_ACCESS:
|
||||
print(f" - {method}")
|
||||
|
||||
def fshstatus_readback(self):
|
||||
"""Return the fast shutter status (0=closed, 1=open) from the readback signal."""
|
||||
if self._check_if_cSAXS_shutter_exists_in_config():
|
||||
return self.device_manager.devices["fsh"].fshstatus_readback()
|
||||
else:
|
||||
self.shutter.get()
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
"""Module to integrate the PandaBox for cSAXS measurements."""
|
||||
|
||||
import time
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd_devices import AsyncMultiSignal, StatusBase
|
||||
from ophyd_devices.devices.panda_box.panda_box import PandaBox, PandaState
|
||||
from pandablocks.responses import FrameData
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class PandaBoxCSAXS(PandaBox):
|
||||
|
||||
def on_init(self):
|
||||
super().on_init()
|
||||
self._acquisition_group = "burst"
|
||||
self._timeout_on_completed = 10
|
||||
|
||||
def on_stage(self):
|
||||
# TODO, adjust as seen fit.
|
||||
# Adjust the acquisition group based on scan parameters if needed
|
||||
if self.scan_info.msg.scan_type == "fly":
|
||||
self._acquisition_group = "fly"
|
||||
elif self.scan_info.msg.scan_type == "step":
|
||||
if self.scan_info.msg.scan_parameters["frames_per_trigger"] == 1:
|
||||
self._acquisition_group = "monitored"
|
||||
else:
|
||||
self._acquisition_group = "burst"
|
||||
|
||||
def on_complete(self):
|
||||
"""On complete is called after the scan is complete. We need to wait for the capture to complete before we can disarm the PandaBox."""
|
||||
|
||||
def _check_capture_complete():
|
||||
captured = 0
|
||||
start_time = time.monotonic()
|
||||
try:
|
||||
expected_points = int(self.scan_info.msg.num_points * self.scan_info.msg.scan_parameters.get("frames_per_trigger",1))
|
||||
while captured < expected_points:
|
||||
logger.info(
|
||||
f"Run with captured {captured} and expected points : {expected_points}."
|
||||
)
|
||||
ret = self.send_raw("*PCAP.CAPTURED?")
|
||||
captured = int(ret[0].split("=")[-1])
|
||||
time.sleep(0.01)
|
||||
if (time.monotonic() - start_time) > self._timeout_on_completed:
|
||||
raise TimeoutError(f"Pandabox {self.name} did not complete after {self._timeout_on_completed} with points captured {captured}/{expected_points}")
|
||||
finally:
|
||||
self._disarm()
|
||||
|
||||
_check_capture_complete()
|
||||
|
||||
# NOTE: This utility class allows to submit a blocking function to a thread and return a status object
|
||||
# that can be awaited for. This allows for asynchronous waiting for the capture to complete without blocking
|
||||
# the main duty cycle of the device server. The device server knows how to handle the status object (future)
|
||||
# and will wait for it to complete.
|
||||
# status = self.task_handler.submit_task(_check_capture_complete, run=True)
|
||||
|
||||
# status_panda_state = StatusBase(obj=self)
|
||||
# self.add_status_callback(
|
||||
# status, success=[PandaState.END, PandaState.DISARMED], failure=[PandaState.READY]
|
||||
# )
|
||||
# ret_status = status & status_panda_state
|
||||
# self.cancel_on_stop(ret_status)
|
||||
# return ret_status
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import time
|
||||
|
||||
panda = PandaBoxCSAXS(
|
||||
name="omny_panda",
|
||||
host="omny-panda.psi.ch",
|
||||
signal_alias={
|
||||
"FMC_IN.VAL2.Value": "alias",
|
||||
"FMC_IN.VAL1.Min": "alias2",
|
||||
"FMC_IN.VAL1.Max": "alias3",
|
||||
"FMC_IN.VAL1.Mean": "alias4",
|
||||
},
|
||||
)
|
||||
panda.on_connected()
|
||||
status = StatusBase(obj=panda)
|
||||
panda.add_status_callback(
|
||||
status=status, success=[PandaState.DISARMED], failure=[PandaState.READY]
|
||||
)
|
||||
panda.stop()
|
||||
status.wait(timeout=2)
|
||||
panda.unstage()
|
||||
logger.info(f"Panda connected")
|
||||
ret = panda.stage()
|
||||
logger.info(f"Panda staged")
|
||||
ret = panda.pre_scan()
|
||||
ret.wait(timeout=5)
|
||||
logger.info(f"Panda pre scan done")
|
||||
time.sleep(5)
|
||||
panda.stop()
|
||||
st = panda.complete()
|
||||
st.wait(timeout=5)
|
||||
logger.info(f"Measurement completed")
|
||||
panda.unstage()
|
||||
logger.info(f"Panda Unstaged")
|
||||
@@ -1,102 +0,0 @@
|
||||
"""Module to integrate the PandaBox for cSAXS measurements."""
|
||||
|
||||
import time
|
||||
|
||||
from bec_lib.logger import bec_logger
|
||||
from ophyd_devices import AsyncMultiSignal, StatusBase
|
||||
from ophyd_devices.devices.panda_box.panda_box import PandaBox, PandaState
|
||||
from pandablocks.responses import FrameData
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class PandaBoxOMNY(PandaBox):
|
||||
|
||||
def on_init(self):
|
||||
super().on_init()
|
||||
self._acquisition_group = "burst"
|
||||
self._timeout_on_completed = 10
|
||||
|
||||
def on_stage(self):
|
||||
|
||||
# TODO, adjust as seen fit.
|
||||
# Adjust the acquisition group based on scan parameters if needed
|
||||
if self.scan_info.msg.scan_type == "fly":
|
||||
self._acquisition_group = "fly"
|
||||
elif self.scan_info.msg.scan_type == "step":
|
||||
if self.scan_info.msg.scan_parameters["frames_per_trigger"] == 1:
|
||||
self._acquisition_group = "monitored"
|
||||
else:
|
||||
self._acquisition_group = "burst"
|
||||
|
||||
def on_complete(self):
|
||||
"""On complete is called after the scan is complete. We need to wait for the capture to complete before we can disarm the PandaBox."""
|
||||
|
||||
def _check_capture_complete():
|
||||
captured = 0
|
||||
start_time = time.monotonic()
|
||||
try:
|
||||
expected_points = int(self.scan_info.msg.num_points * self.scan_info.msg.scan_parameters.get("frames_per_trigger",1))
|
||||
while captured < expected_points:
|
||||
logger.info(
|
||||
f"Run with captured {captured} and expected points : {expected_points}."
|
||||
)
|
||||
ret = self.send_raw("*PCAP.CAPTURED?")
|
||||
captured = int(ret[0].split("=")[-1])
|
||||
time.sleep(0.01)
|
||||
if (time.monotonic() - start_time) > self._timeout_on_completed:
|
||||
raise TimeoutError(f"Pandabox {self.name} did not complete after {self._timeout_on_completed} with points captured {captured}/{expected_points}")
|
||||
finally:
|
||||
self._disarm()
|
||||
|
||||
_check_capture_complete()
|
||||
|
||||
# NOTE: This utility class allows to submit a blocking function to a thread and return a status object
|
||||
# that can be awaited for. This allows for asynchronous waiting for the capture to complete without blocking
|
||||
# the main duty cycle of the device server. The device server knows how to handle the status object (future)
|
||||
# and will wait for it to complete.
|
||||
# status = self.task_handler.submit_task(_check_capture_complete, run=True)
|
||||
|
||||
# status_panda_state = StatusBase(obj=self)
|
||||
# self.add_status_callback(
|
||||
# status, success=[PandaState.END, PandaState.DISARMED], failure=[PandaState.READY]
|
||||
# )
|
||||
# ret_status = status & status_panda_state
|
||||
# self.cancel_on_stop(ret_status)
|
||||
# return ret_status
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import time
|
||||
|
||||
panda = PandaBoxCSAXS(
|
||||
name="omny_panda",
|
||||
host="omny-panda.psi.ch",
|
||||
signal_alias={
|
||||
"FMC_IN.VAL2.Value": "alias",
|
||||
"FMC_IN.VAL1.Min": "alias2",
|
||||
"FMC_IN.VAL1.Max": "alias3",
|
||||
"FMC_IN.VAL1.Mean": "alias4",
|
||||
},
|
||||
)
|
||||
panda.on_connected()
|
||||
status = StatusBase(obj=panda)
|
||||
panda.add_status_callback(
|
||||
status=status, success=[PandaState.DISARMED], failure=[PandaState.READY]
|
||||
)
|
||||
panda.stop()
|
||||
status.wait(timeout=2)
|
||||
panda.unstage()
|
||||
logger.info(f"Panda connected")
|
||||
ret = panda.stage()
|
||||
logger.info(f"Panda staged")
|
||||
ret = panda.pre_scan()
|
||||
ret.wait(timeout=5)
|
||||
logger.info(f"Panda pre scan done")
|
||||
time.sleep(5)
|
||||
panda.stop()
|
||||
st = panda.complete()
|
||||
st.wait(timeout=5)
|
||||
logger.info(f"Measurement completed")
|
||||
panda.unstage()
|
||||
logger.info(f"Panda Unstaged")
|
||||
@@ -70,7 +70,7 @@ class SmaractSensors:
|
||||
|
||||
|
||||
class SmaractController(Controller):
|
||||
_axes_per_controller = 9
|
||||
_axes_per_controller = 6
|
||||
_initialized = False
|
||||
USER_ACCESS = [
|
||||
"socket_put_and_receive",
|
||||
|
||||
@@ -6,7 +6,6 @@ import numpy as np
|
||||
from bec_lib import bec_logger
|
||||
from ophyd import Component as Cpt
|
||||
from ophyd import Device, PositionerBase, Signal
|
||||
from ophyd.device import DEFAULT_CONNECTION_TIMEOUT
|
||||
from ophyd.status import wait as status_wait
|
||||
from ophyd.utils import LimitError, ReadOnlyError
|
||||
from ophyd_devices.utils.controller import threadlocked
|
||||
|
||||
@@ -210,7 +210,7 @@ class LamNIFermatScan(ScanBase, LamNIMixin):
|
||||
arg_input = {}
|
||||
arg_bundle_size = {"bundle": len(arg_input), "min": None, "max": None}
|
||||
|
||||
def __init__(self, *args, parameter: dict = None, frames_per_trigger:int=1, exp_time:float=0,**kwargs):
|
||||
def __init__(self, *args, parameter: dict = None, **kwargs):
|
||||
"""
|
||||
A LamNI scan following Fermat's spiral.
|
||||
|
||||
@@ -230,10 +230,10 @@ class LamNIFermatScan(ScanBase, LamNIMixin):
|
||||
|
||||
Examples:
|
||||
>>> scans.lamni_fermat_scan(fov_size=[20], step=0.5, exp_time=0.1)
|
||||
>>> scans.lamni_fermat_scan(fov_size=[20, 25], center_x=0.02, center_y=0, shift_x=0, shift_y=0, angle=0, step=0.5, fov_circular=0, exp_time=0.1, frames_per_trigger=1)
|
||||
>>> scans.lamni_fermat_scan(fov_size=[20, 25], center_x=0.02, center_y=0, shift_x=0, shift_y=0, angle=0, step=0.5, fov_circular=0, exp_time=0.1)
|
||||
"""
|
||||
|
||||
super().__init__(parameter=parameter, frames_per_trigger=frames_per_trigger, exp_time=exp_time,**kwargs)
|
||||
super().__init__(parameter=parameter, **kwargs)
|
||||
self.axis = []
|
||||
scan_kwargs = parameter.get("kwargs", {})
|
||||
self.fov_size = scan_kwargs.get("fov_size")
|
||||
@@ -482,7 +482,6 @@ class LamNIFermatScan(ScanBase, LamNIMixin):
|
||||
yield from self.open_scan()
|
||||
yield from self.stage()
|
||||
yield from self.run_baseline_reading()
|
||||
yield from self.pre_scan()
|
||||
yield from self.scan_core()
|
||||
yield from self.finalize()
|
||||
yield from self.unstage()
|
||||
|
||||
@@ -27,7 +27,6 @@ from bec_lib import bec_logger, messages
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_server.scan_server.errors import ScanAbortion
|
||||
from bec_server.scan_server.scans import SyncFlyScanBase
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import TRIGGERSOURCE
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
@@ -52,7 +51,6 @@ class FlomniFermatScan(SyncFlyScanBase):
|
||||
angle: float = None,
|
||||
corridor_size: float = 3,
|
||||
parameter: dict = None,
|
||||
frames_per_trigger:int=1,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
@@ -63,8 +61,7 @@ class FlomniFermatScan(SyncFlyScanBase):
|
||||
fovy(float) [um]: Fov in the piezo plane (i.e. piezo range). Max 100 um
|
||||
cenx(float) [um]: center position in x.
|
||||
ceny(float) [um]: center position in y.
|
||||
exp_time(float) [s]: exposure time per burst frame
|
||||
frames_per_trigger(int) : Number of burst frames per point
|
||||
exp_time(float) [s]: exposure time
|
||||
step(float) [um]: stepsize
|
||||
zshift(float) [um]: shift in z
|
||||
angle(float) [deg]: rotation angle (will rotate first)
|
||||
@@ -73,16 +70,17 @@ class FlomniFermatScan(SyncFlyScanBase):
|
||||
Returns:
|
||||
|
||||
Examples:
|
||||
>>> scans.flomni_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01, frames_per_trigger=1)
|
||||
>>> scans.flomni_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01)
|
||||
"""
|
||||
|
||||
super().__init__(parameter=parameter, exp_time=exp_time, frames_per_trigger=frames_per_trigger, **kwargs)
|
||||
super().__init__(parameter=parameter, **kwargs)
|
||||
self.show_live_table = False
|
||||
self.axis = []
|
||||
self.fovx = fovx
|
||||
self.fovy = fovy
|
||||
self.cenx = cenx
|
||||
self.ceny = ceny
|
||||
self.exp_time = exp_time
|
||||
self.step = step
|
||||
self.zshift = zshift
|
||||
self.angle = angle
|
||||
@@ -153,9 +151,6 @@ class FlomniFermatScan(SyncFlyScanBase):
|
||||
yield from self.stubs.send_rpc_and_wait("rty", "set", self.positions[0][1])
|
||||
|
||||
def _prepare_setup_part2(self):
|
||||
# Prepare DDG1 to use
|
||||
yield from self.stubs.send_rpc_and_wait("ddg1", "set_trigger", TRIGGERSOURCE.EXT_RISING_EDGE.value)
|
||||
|
||||
if self.flomni_rotation_status:
|
||||
self.flomni_rotation_status.wait()
|
||||
|
||||
@@ -312,10 +307,6 @@ class FlomniFermatScan(SyncFlyScanBase):
|
||||
|
||||
logger.warning("No positions found to return to start")
|
||||
|
||||
def cleanup(self):
|
||||
yield from self.stubs.send_rpc_and_wait("ddg1", "set_trigger", TRIGGERSOURCE.SINGLE_SHOT.value)
|
||||
yield from super().cleanup()
|
||||
|
||||
def run(self):
|
||||
self.initialize()
|
||||
yield from self.read_scan_motors()
|
||||
@@ -325,7 +316,6 @@ class FlomniFermatScan(SyncFlyScanBase):
|
||||
yield from self.stage()
|
||||
yield from self.run_baseline_reading()
|
||||
yield from self._prepare_setup_part2()
|
||||
yield from self.pre_scan()
|
||||
yield from self.scan_core()
|
||||
yield from self.finalize()
|
||||
yield from self.unstage()
|
||||
|
||||
@@ -51,7 +51,6 @@ class OMNYFermatScan(SyncFlyScanBase):
|
||||
angle: float = None,
|
||||
corridor_size: float = 3,
|
||||
parameter: dict = None,
|
||||
frames_per_trigger:int=1,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
@@ -63,7 +62,6 @@ class OMNYFermatScan(SyncFlyScanBase):
|
||||
cenx(float) [um]: center position in x.
|
||||
ceny(float) [um]: center position in y.
|
||||
exp_time(float) [s]: exposure time
|
||||
frames_per_trigger:int: Number of burst frames per trigger, defaults to 1.
|
||||
step(float) [um]: stepsize
|
||||
zshift(float) [um]: shift in z
|
||||
angle(float) [deg]: rotation angle (will rotate first)
|
||||
@@ -75,7 +73,7 @@ class OMNYFermatScan(SyncFlyScanBase):
|
||||
>>> scans.omny_fermat_scan(fovx=20, fovy=25, cenx=10, ceny=0, zshift=0, angle=0, step=2, exp_time=0.01)
|
||||
"""
|
||||
|
||||
super().__init__(parameter=parameter, exp_time=exp_time, frames_per_trigger=frames_per_trigger, **kwargs)
|
||||
super().__init__(parameter=parameter, **kwargs)
|
||||
self.axis = []
|
||||
self.fovx = fovx
|
||||
self.fovy = fovy
|
||||
@@ -301,7 +299,6 @@ class OMNYFermatScan(SyncFlyScanBase):
|
||||
yield from self.stage()
|
||||
yield from self.run_baseline_reading()
|
||||
yield from self._prepare_setup_part2()
|
||||
yield from self.pre_scan()
|
||||
yield from self.scan_core()
|
||||
yield from self.finalize()
|
||||
yield from self.unstage()
|
||||
|
||||
@@ -193,15 +193,14 @@ The basic scan function can be called by `scans.flomni_fermat_scan()` and offers
|
||||
| fovy (float) | Fov in the piezo plane (i.e. piezo range). Max 100 um |
|
||||
| cenx (float) | center position in x |
|
||||
| ceny (float) | center position in y |
|
||||
| exp_time (float) | exposure time per frame |
|
||||
| frames_per_trigger(int) | Number of burst frames per position |
|
||||
| exp_time (float) | exposure time |
|
||||
| step (float) | stepsize |
|
||||
| zshift (float) | shift in z |
|
||||
| angle (float) | rotation angle (will rotate first) |
|
||||
| corridor_size (float) | corridor size for the corridor optimization. Default 3 um |
|
||||
|
||||
Example:
|
||||
`scans.flomni_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01, frames_per_trigger=1)`
|
||||
`scans.flomni_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01)`
|
||||
|
||||
#### Overview of the alignment steps
|
||||
|
||||
|
||||
@@ -327,15 +327,14 @@ The basic scan function can be called by `scans.omny_fermat_scan()` and offers a
|
||||
| fovy (float) | Fov in the piezo plane (i.e. piezo range). Max 100 um |
|
||||
| cenx (float) | center position in x |
|
||||
| ceny (float) | center position in y |
|
||||
| exp_time (float) | exposure time per frame |
|
||||
| frames_per_trigger(int) | Number of burst frames per position |
|
||||
| exp_time (float) | exposure time |
|
||||
| step (float) | stepsize |
|
||||
| zshift (float) | shift in z |
|
||||
| angle (float) | rotation angle (will rotate first) |
|
||||
| corridor_size (float) | corridor size for the corridor optimization. Default 3 um |
|
||||
|
||||
Example:
|
||||
`scans.omny_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01, frames_per_trigger=1)`
|
||||
`scans.omny_fermat_scan(fovx=20, fovy=25, cenx=0.02, ceny=0, zshift=0, angle=0, step=0.5, exp_time=0.01)`
|
||||
|
||||
#### Overview of the alignment steps
|
||||
|
||||
|
||||
@@ -25,8 +25,6 @@ dependencies = [
|
||||
"bec_widgets",
|
||||
"zmq",
|
||||
"opencv-python",
|
||||
"dectris-compression", # for JFJ preview stream decompression
|
||||
"cbor2",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
||||
@@ -6,35 +6,9 @@ from unittest import mock
|
||||
import numpy as np
|
||||
import ophyd
|
||||
import pytest
|
||||
from bec_server.device_server.tests.utils import DMMock
|
||||
from ophyd_devices.tests.utils import patched_device
|
||||
from ophyd_devices.tests.utils import MockPV, patch_dual_pvs
|
||||
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs import DDG1, DDG2
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.ddg_1 import (
|
||||
DEFAULT_IO_CONFIG as DDG1_DEFAULT_IO_CONFIG,
|
||||
)
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.ddg_1 import (
|
||||
DEFAULT_READOUT_TIMES as DDG1_DEFAULT_READOUT_TIMES,
|
||||
)
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.ddg_1 import (
|
||||
DEFAULT_REFERENCES as DDG1_DEFAULT_REFERENCES,
|
||||
)
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.ddg_1 import (
|
||||
DEFAULT_TRIGGER_SOURCE as DDG1_DEFAULT_TRIGGER_SOURCE,
|
||||
)
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.ddg_1 import PROC_EVENT_MODE
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.ddg_2 import (
|
||||
DEFAULT_IO_CONFIG as DDG2_DEFAULT_IO_CONFIG,
|
||||
)
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.ddg_2 import (
|
||||
DEFAULT_READOUT_TIMES as DDG2_DEFAULT_READOUT_TIMES,
|
||||
)
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.ddg_2 import (
|
||||
DEFAULT_REFERENCES as DDG2_DEFAULT_REFERENCES,
|
||||
)
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.ddg_2 import (
|
||||
DEFAULT_TRIGGER_SOURCE as DDG2_DEFAULT_TRIGGER_SOURCE,
|
||||
)
|
||||
from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import (
|
||||
BURSTCONFIG,
|
||||
CHANNELREFERENCE,
|
||||
@@ -42,46 +16,68 @@ from csaxs_bec.devices.epics.delay_generator_csaxs.delay_generator_csaxs import
|
||||
TRIGGERSOURCE,
|
||||
DelayGeneratorCSAXS,
|
||||
)
|
||||
from csaxs_bec.devices.epics.mcs_card.mcs_card_csaxs import MCSCardCSAXS
|
||||
|
||||
############################
|
||||
### Test Delay Generator ###
|
||||
############################
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_ddg1() -> Generator[DDG1, DDG1, DDG1]:
|
||||
"""Fixture to mock the DDG1 device."""
|
||||
name = "ddg1"
|
||||
prefix = "test_ddg1:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = DDG1(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
yield dev
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_ddg2() -> Generator[DDG2, DDG2, DDG2]:
|
||||
"""Fixture to mock the DDG1 device."""
|
||||
name = "ddg2"
|
||||
prefix = "test_ddg2:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = DDG2(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
yield dev
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_ddg() -> Generator[DelayGeneratorCSAXS, DelayGeneratorCSAXS, DelayGeneratorCSAXS]:
|
||||
"""Fixture to mock the camera device."""
|
||||
with patched_device(
|
||||
DelayGeneratorCSAXS, name="ddg", prefix="test:", _mock_pv_initial_value=0
|
||||
) as dev:
|
||||
try:
|
||||
yield dev
|
||||
finally:
|
||||
dev.destroy()
|
||||
name = "ddg"
|
||||
prefix = "test:"
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
dev = DelayGeneratorCSAXS(name=name, prefix=prefix)
|
||||
patch_dual_pvs(dev)
|
||||
yield dev
|
||||
|
||||
|
||||
def test_ddg_init(mock_ddg: DelayGeneratorCSAXS):
|
||||
def test_ddg_init(mock_ddg):
|
||||
"""Test the proc event status method."""
|
||||
assert mock_ddg.name == "ddg"
|
||||
assert mock_ddg.prefix == "test:"
|
||||
|
||||
|
||||
def test_ddg_proc_event_status(mock_ddg: DelayGeneratorCSAXS):
|
||||
def test_ddg_proc_event_status(mock_ddg):
|
||||
"""Test the proc event status method."""
|
||||
mock_ddg.state.proc_status.put(0)
|
||||
mock_ddg.proc_event_status()
|
||||
assert mock_ddg.state.proc_status.get() == 1
|
||||
|
||||
|
||||
def test_ddg_set_trigger(mock_ddg: DelayGeneratorCSAXS):
|
||||
def test_ddg_set_trigger(mock_ddg):
|
||||
"""Test setting the trigger."""
|
||||
for trigger in TRIGGERSOURCE:
|
||||
mock_ddg.set_trigger(trigger)
|
||||
assert mock_ddg.trigger_source.get() == trigger.value
|
||||
|
||||
|
||||
def test_ddg_burst_enable(mock_ddg: DelayGeneratorCSAXS):
|
||||
def test_ddg_burst_enable(mock_ddg):
|
||||
"""Test enabling burst mode."""
|
||||
mock_ddg.burst_enable(count=100, delay=0.1, period=0.02, config=BURSTCONFIG.ALL_CYCLES)
|
||||
mock_ddg.burst_mode.get() == 1
|
||||
@@ -105,7 +101,7 @@ def test_ddg_burst_enable(mock_ddg: DelayGeneratorCSAXS):
|
||||
mock_ddg.burst_mode.get() == BURSTCONFIG.FIRST_CYCLE.value
|
||||
|
||||
|
||||
def test_ddg_wait_for_event_status(mock_ddg: DelayGeneratorCSAXS):
|
||||
def test_ddg_wait_for_event_status(mock_ddg):
|
||||
"""Test setting wait for event status."""
|
||||
mock_ddg: DelayGeneratorCSAXS
|
||||
mock_ddg.state.event_status._read_pv.mock_data = 0
|
||||
@@ -121,7 +117,7 @@ def test_ddg_wait_for_event_status(mock_ddg: DelayGeneratorCSAXS):
|
||||
# assert status.done is True
|
||||
|
||||
|
||||
def test_ddg_set_io_values(mock_ddg: DelayGeneratorCSAXS):
|
||||
def test_ddg_set_io_values(mock_ddg):
|
||||
"""Test setting IO values."""
|
||||
mock_ddg.set_io_values(channel="ab", amplitude=3, offset=2, polarity=1, mode="ttl")
|
||||
assert mock_ddg.ab.io.amplitude.get() == 3
|
||||
@@ -142,7 +138,7 @@ def test_ddg_set_io_values(mock_ddg: DelayGeneratorCSAXS):
|
||||
assert attr.nim_mode.get() == 1
|
||||
|
||||
|
||||
def test_ddg_set_delay_pairs(mock_ddg: DelayGeneratorCSAXS):
|
||||
def test_ddg_set_delay_pairs(mock_ddg):
|
||||
"""Test setting delay pairs."""
|
||||
mock_ddg.set_delay_pairs(channel="ab", delay=0.1, width=0.2)
|
||||
assert np.isclose(mock_ddg.ab.delay.get(), 0.1)
|
||||
@@ -160,449 +156,149 @@ def test_ddg_set_delay_pairs(mock_ddg: DelayGeneratorCSAXS):
|
||||
assert np.isclose(getattr(mock_ddg, channel).ch2.setpoint.get(), delay + 0.2)
|
||||
|
||||
|
||||
#########################
|
||||
### Test DDG1 Device ####
|
||||
#########################
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_mcs_csaxs() -> Generator[MCSCardCSAXS, None, None]:
|
||||
"""Fixture to mock the MCSCardCSAXS device."""
|
||||
dm = DMMock()
|
||||
with patched_device(
|
||||
MCSCardCSAXS,
|
||||
name="mcs",
|
||||
prefix="X12SA-MCS-CSAXS:",
|
||||
device_manager=dm,
|
||||
_mock_pv_initial_value=0,
|
||||
) as dev:
|
||||
dev.enabled = True
|
||||
dev.device_manager.devices["mcs"] = dev
|
||||
try:
|
||||
yield dev
|
||||
finally:
|
||||
dev.destroy()
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_ddg1(mock_mcs_csaxs: MCSCardCSAXS) -> Generator[DDG1, None, None]:
|
||||
"""Fixture to mock the DDG1 device."""
|
||||
# Add enabled to mock_mcs_csaxs
|
||||
dm_mock = mock_mcs_csaxs.device_manager
|
||||
with patched_device(
|
||||
DDG1, name="ddg1", prefix="test_ddg1:", device_manager=dm_mock, _mock_pv_initial_value=0
|
||||
) as dev:
|
||||
dev.enabled = True
|
||||
dev.device_manager.devices["ddg1"] = dev
|
||||
try:
|
||||
yield dev
|
||||
finally:
|
||||
dev.destroy()
|
||||
|
||||
|
||||
def test_ddg1_on_connected(mock_ddg1: DDG1):
|
||||
def test_ddg1_on_connected(mock_ddg1):
|
||||
"""Test the on_connected method of DDG1."""
|
||||
mock_ddg1.burst_mode.put(1) # Set burst mode to 1, if connected should reset it to 0
|
||||
mock_ddg1.burst_delay.put(5) # Set to non-zero, should reset to 0 on connected
|
||||
mock_ddg1.burst_count.put(10) # Set to non-default, should reset to 1 on connected
|
||||
with mock.patch.object(mock_ddg1, "set_io_values") as mock_set_io_values:
|
||||
mock_ddg1.on_connected()
|
||||
mock_ddg1.on_connected()
|
||||
# IO defaults
|
||||
assert mock_ddg1.burst_mode.get() == 0
|
||||
assert mock_ddg1.ab.io.amplitude.get() == 5.0
|
||||
assert mock_ddg1.cd.io.offset.get() == 0.0
|
||||
assert mock_ddg1.ef.io.polarity.get() == 1
|
||||
assert mock_ddg1.gh.io.ttl_mode.get() == 1
|
||||
|
||||
# Burst mode Defaults
|
||||
assert mock_ddg1.burst_mode.get() == 0
|
||||
assert mock_ddg1.burst_delay.get() == 0
|
||||
assert mock_ddg1.burst_count.get() == 1
|
||||
# reference defaults
|
||||
assert mock_ddg1.ab.ch1.reference.get() == 0 # CHANNELREFERENCE.T0.value
|
||||
assert mock_ddg1.ab.ch2.reference.get() == 1 # CHANNELREFERENCE.A.value
|
||||
assert mock_ddg1.cd.ch1.reference.get() == 0 # CHANNELREFERENCE.T0.value
|
||||
assert mock_ddg1.cd.ch2.reference.get() == 3 # CHANNELREFERENCE.C.value
|
||||
assert mock_ddg1.ef.ch1.reference.get() == 4 # CHANNELREFERENCE.D.value
|
||||
assert mock_ddg1.ef.ch2.reference.get() == 5 # CHANNELREFERENCE.E.value
|
||||
assert mock_ddg1.gh.ch1.reference.get() == 0 # CHANNELREFERENCE.T0.value
|
||||
assert mock_ddg1.gh.ch2.reference.get() == 7 # CHANNELREFERENCE.G.value
|
||||
|
||||
assert mock_set_io_values.call_count == len(DDG1_DEFAULT_IO_CONFIG)
|
||||
for ch, config in DDG1_DEFAULT_IO_CONFIG.items():
|
||||
assert mock.call(ch, **config) in mock_set_io_values.call_args_list
|
||||
|
||||
# Check reference values from DEFAULT_REFERENCES
|
||||
for ch, refs in DDG1_DEFAULT_REFERENCES:
|
||||
if ch == "A":
|
||||
sub_ch = mock_ddg1.ab.ch1
|
||||
elif ch == "B":
|
||||
sub_ch = mock_ddg1.ab.ch2
|
||||
elif ch == "C":
|
||||
sub_ch = mock_ddg1.cd.ch1
|
||||
elif ch == "D":
|
||||
sub_ch = mock_ddg1.cd.ch2
|
||||
elif ch == "E":
|
||||
sub_ch = mock_ddg1.ef.ch1
|
||||
elif ch == "F":
|
||||
sub_ch = mock_ddg1.ef.ch2
|
||||
elif ch == "G":
|
||||
sub_ch = mock_ddg1.gh.ch1
|
||||
elif ch == "H":
|
||||
sub_ch = mock_ddg1.gh.ch2
|
||||
assert sub_ch.reference.get() == refs.value
|
||||
|
||||
# Check Default trigger source
|
||||
assert mock_ddg1.trigger_source.get() == DDG1_DEFAULT_TRIGGER_SOURCE.value
|
||||
|
||||
# Check proc state mode
|
||||
assert mock_ddg1.state.proc_status_mode.get() == PROC_EVENT_MODE.EVENT.value
|
||||
|
||||
# Check the poll thread is started
|
||||
assert mock_ddg1._poll_thread.is_alive()
|
||||
assert not mock_ddg1._poll_thread_kill_event.is_set()
|
||||
assert not mock_ddg1._poll_thread_poll_loop_done.is_set()
|
||||
assert not mock_ddg1._poll_thread_run_event.is_set()
|
||||
# Default trigger source
|
||||
assert mock_ddg1.trigger_source.get() == 5 # TRIGGERSOURCE.SINGLE_SHOT.value
|
||||
|
||||
|
||||
def test_ddg1_prepare_mcs(mock_ddg1: DDG1, mock_mcs_csaxs: MCSCardCSAXS):
|
||||
"""Test the prepare_mcs method of DDG1."""
|
||||
mcs = mock_mcs_csaxs
|
||||
ddg = mock_ddg1
|
||||
# Simulate default state
|
||||
mcs.acquiring._read_pv.mock_data = 0 # not acquiring
|
||||
mcs.erase_start.put(0) # reset erase start
|
||||
|
||||
# Prepare MCS on trigger
|
||||
st = ddg._prepare_mcs_on_trigger(mcs)
|
||||
assert st.done is False
|
||||
assert st.success is False
|
||||
assert mcs.erase_start.get() == 1 # erase started
|
||||
|
||||
# Simulate acquiring started
|
||||
mcs.acquiring._read_pv.mock_data = 1 # acquiring
|
||||
st.wait(2)
|
||||
assert st.done is True
|
||||
assert st.success is True
|
||||
|
||||
|
||||
def test_ddg1_stage(mock_ddg1: DDG1):
|
||||
def test_ddg1_stage(mock_ddg1):
|
||||
"""Test the on_stage method of DDG1."""
|
||||
exp_time = 0.1
|
||||
frames_per_trigger = 10
|
||||
|
||||
mock_ddg1.burst_mode.put(0) # Non-default, should be reset on stage
|
||||
mock_ddg1.burst_delay.put(5) # Non-default, should be reset on stage
|
||||
mock_ddg1.burst_count.put(10) # Non-default, should be reset on stage
|
||||
|
||||
mock_ddg1.burst_mode.put(1)
|
||||
mock_ddg1.scan_info.msg.scan_parameters["exp_time"] = exp_time
|
||||
mock_ddg1.scan_info.msg.scan_parameters["frames_per_trigger"] = frames_per_trigger
|
||||
mock_ddg1.fast_shutter_control._read_pv.mock_data = 0 # Simulate shutter control
|
||||
|
||||
mock_ddg1.stage()
|
||||
|
||||
shutter_width = mock_ddg1._shutter_to_open_delay + exp_time * frames_per_trigger
|
||||
|
||||
assert np.isclose(mock_ddg1.burst_mode.get(), 1) # burst mode is enabled
|
||||
assert np.isclose(mock_ddg1.burst_delay.get(), 0)
|
||||
assert np.isclose(mock_ddg1.burst_period.get(), shutter_width)
|
||||
assert np.isclose(mock_ddg1.burst_period.get(), exp_time)
|
||||
|
||||
# Trigger DDG2 through EXT/EN
|
||||
|
||||
assert np.isclose(mock_ddg1.ab.delay.get(), 2e-3)
|
||||
assert np.isclose(mock_ddg1.ab.width.get(), 1e-6)
|
||||
# Shutter channel cd
|
||||
assert np.isclose(mock_ddg1.cd.delay.get(), 0)
|
||||
assert np.isclose(mock_ddg1.cd.width.get(), shutter_width)
|
||||
assert np.isclose(mock_ddg1.cd.width.get(), 2e-3 + exp_time * frames_per_trigger + 1e-3)
|
||||
# MCS channel ef or gate
|
||||
assert np.isclose(mock_ddg1.ef.delay.get(), 0)
|
||||
assert np.isclose(mock_ddg1.ef.width.get(), 1e-6)
|
||||
|
||||
assert mock_ddg1.staged == ophyd.Staged.yes
|
||||
mock_ddg1.unstage()
|
||||
|
||||
# Test if shutter is kept open..
|
||||
mock_ddg1.fast_shutter_control._read_pv.mock_data = 1 # Simulate shutter control is kept open
|
||||
# Test method
|
||||
mock_ddg1.keep_shutter_open_during_scan(True)
|
||||
shutter_width = mock_ddg1._shutter_to_open_delay + exp_time * frames_per_trigger
|
||||
assert np.isclose(
|
||||
shutter_width, exp_time * frames_per_trigger
|
||||
) # Shutter to open delay is not added as shutter is kept open
|
||||
# Simulate fly scan, so no extra trigger for MCS card.
|
||||
mock_ddg1.scan_info.msg.scan_type = "fly"
|
||||
mock_ddg1.stage()
|
||||
# Shutter channel cd
|
||||
assert np.isclose(mock_ddg1.cd.delay.get(), 0)
|
||||
assert np.isclose(mock_ddg1.cd.width.get(), shutter_width)
|
||||
# MCS channel ef or gate
|
||||
assert np.isclose(mock_ddg1.ef.delay.get(), 0)
|
||||
assert np.isclose(mock_ddg1.ef.width.get(), 0) # No triggering of MCS due to shutter fly scan
|
||||
|
||||
|
||||
def test_ddg1_on_trigger(mock_ddg1: DDG1):
|
||||
"""
|
||||
Test the on_trigger method of the DDG1.
|
||||
|
||||
We will test two scenarios:
|
||||
I. Trigger is prepared, and resolves successfully after END_OF_BURST is reached in event status register.
|
||||
II. Trigger is called while _poll_thread_loop_done is not yet finished from a previous trigger.
|
||||
This may be the case if polling is yet to finsish. The next on_trigger should terminate the previous
|
||||
polling, and work as expected. In addition, we will simulate that the mcs card is disabled, thus not prepared.
|
||||
"""
|
||||
ddg = mock_ddg1
|
||||
# Make sure DDG is setup in default state through on_connected
|
||||
ddg.on_connected()
|
||||
|
||||
# Check that poll thread is running and run event is not set
|
||||
assert ddg._poll_thread.is_alive()
|
||||
assert not ddg._poll_thread_run_event.is_set()
|
||||
assert not ddg._poll_thread_poll_loop_done.is_set()
|
||||
|
||||
# Set the status register bit
|
||||
ddg.state.event_status._read_pv.mock_data = STATUSBITS.ABORT_DELAY.value
|
||||
|
||||
#################################
|
||||
# Scenario I - normal operation #
|
||||
#################################
|
||||
with mock.patch.object(ddg, "_prepare_mcs_on_trigger") as mock_prepare_mcs:
|
||||
mock_prepare_mcs.return_value = ophyd.StatusBase(done=True, success=True)
|
||||
# MCS card is present and enabled, should call prepare_mcs_on_trigger
|
||||
# and the status should resolve once acuiring goes from 1 to 0.
|
||||
status = ddg.trigger()
|
||||
assert status.done is False
|
||||
mcs = ddg.device_manager.devices.get("mcs", None)
|
||||
assert mcs is not None
|
||||
mcs.acquiring._read_pv.mock_data = 1 # Simulate acquiring started
|
||||
assert status.done is False
|
||||
mcs.acquiring._read_pv.mock_data = 0 # Simulate acquiring stopped
|
||||
status.wait(timeout=1) # Wait for the status to be done
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
mock_prepare_mcs.assert_called_once()
|
||||
|
||||
# Now we disable the mcs card, and trigger again. This should not call prepare_mcs_on_trigger
|
||||
# and should fallback to polling the DDG for END_OF_BURST status bit.
|
||||
|
||||
# Disable mcs card
|
||||
mcs.enabled = False
|
||||
status = ddg.trigger()
|
||||
# Check that the poll thread run event is set
|
||||
# Careful in debugger, there is a timeout based on the exp_time + 5s default
|
||||
assert ddg._poll_thread_run_event.is_set()
|
||||
assert not ddg._poll_thread_poll_loop_done.is_set()
|
||||
def test_ddg1_trigger(mock_ddg1):
|
||||
"""Test the on_trigger method of DDG1."""
|
||||
mock_ddg1.state.event_status._read_pv.mock_data = STATUSBITS.NONE.value
|
||||
|
||||
with mock.patch.object(mock_ddg1, "device_manager") as mock_device_manager:
|
||||
# TODO add device manager DMMock, and properly test logic for mcs triggering.
|
||||
mock_get = mock_device_manager.devices.get = mock.Mock(return_value=None)
|
||||
status = mock_ddg1.trigger()
|
||||
assert mock_get.call_args == mock.call("mcs", None)
|
||||
assert status.done is False
|
||||
assert status.success is False
|
||||
assert ddg.trigger_shot.get() == 1
|
||||
|
||||
# Simulate that the event status bit reaches END_OF_BURST
|
||||
ddg.state.event_status._read_pv.mock_data = STATUSBITS.END_OF_BURST.value
|
||||
assert mock_ddg1.trigger_shot.get() == 1
|
||||
mock_ddg1.state.event_status._read_pv.mock_data = STATUSBITS.END_OF_BURST.value
|
||||
status.wait(timeout=1) # Wait for the status to be done
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
|
||||
# Should finish the poll loop
|
||||
ddg._poll_thread_poll_loop_done.wait(timeout=1)
|
||||
assert not ddg._poll_thread_run_event.is_set()
|
||||
|
||||
############################################
|
||||
# Scenario II - previous poll not finished #
|
||||
# MCS card disabled #
|
||||
############################################
|
||||
|
||||
# Set mcs card to enabled = False
|
||||
ddg.device_manager.devices["mcs"].enabled = False
|
||||
ddg.state.event_status._read_pv.mock_data = STATUSBITS.ABORT_DELAY.value
|
||||
ddg._start_polling()
|
||||
assert ddg._poll_thread_run_event.is_set()
|
||||
with mock.patch.object(ddg, "_prepare_mcs_on_trigger") as mock_prepare_mcs:
|
||||
status = ddg.trigger()
|
||||
mock_prepare_mcs.assert_not_called() # MCS is disabled, should not be called
|
||||
assert status.done is False
|
||||
assert status.success is False
|
||||
|
||||
# Resolve the status by simulating END_OF_BURST
|
||||
ddg.state.event_status._read_pv.mock_data = STATUSBITS.END_OF_BURST.value
|
||||
status.wait(timeout=1) # Wait for the status to be done
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
|
||||
# Wait for poll loop to finish
|
||||
ddg._poll_thread_poll_loop_done.wait(timeout=1)
|
||||
assert not ddg._poll_thread_run_event.is_set()
|
||||
def test_ddg1_stop(mock_ddg1):
|
||||
"""Test the on_stop method of DDG1."""
|
||||
mock_ddg1.burst_mode.put(1) # Enable burst mode
|
||||
mock_ddg1.stop()
|
||||
assert mock_ddg1.burst_mode.get() == 0 # Burst mode is disabled
|
||||
|
||||
|
||||
# def test_ddg1_trigger(mock_ddg1):
|
||||
# """Test the on_trigger method of DDG1."""
|
||||
# mock_ddg1.state.event_status._read_pv.mock_data = STATUSBITS.NONE.value
|
||||
def test_ddg2_on_connected(mock_ddg2):
|
||||
"""Test on connected method of DDG2."""
|
||||
mock_ddg2.on_connected()
|
||||
# IO defaults
|
||||
assert mock_ddg2.burst_mode.get() == 0
|
||||
assert mock_ddg2.ab.io.amplitude.get() == 5.0
|
||||
assert mock_ddg2.cd.io.offset.get() == 0.0
|
||||
assert mock_ddg2.ef.io.polarity.get() == 1
|
||||
assert mock_ddg2.gh.io.ttl_mode.get() == 1
|
||||
|
||||
# with mock.patch.object(mock_ddg1, "device_manager") as mock_device_manager:
|
||||
# # TODO add device manager DMMock, and properly test logic for mcs triggering.
|
||||
# mock_get = mock_device_manager.devices.get = mock.Mock(return_value=None)
|
||||
# status = mock_ddg1.trigger()
|
||||
# assert mock_get.call_args == mock.call("mcs", None)
|
||||
# assert status.done is False
|
||||
# assert status.success is False
|
||||
# assert mock_ddg1.trigger_shot.get() == 1
|
||||
# mock_ddg1.state.event_status._read_pv.mock_data = STATUSBITS.END_OF_BURST.value
|
||||
# status.wait(timeout=1) # Wait for the status to be done
|
||||
# assert status.done is True
|
||||
# assert status.success is True
|
||||
# reference defaults
|
||||
assert mock_ddg2.ab.ch1.reference.get() == 0 # CHANNELREFERENCE.T0.value
|
||||
assert mock_ddg2.ab.ch2.reference.get() == 1 # CHANNELREFERENCE.A.value
|
||||
assert mock_ddg2.cd.ch1.reference.get() == 0 # CHANNELREFERENCE.T0.value
|
||||
assert mock_ddg2.cd.ch2.reference.get() == 3 # CHANNELREFERENCE.C.value
|
||||
assert mock_ddg2.ef.ch1.reference.get() == 0 # CHANNELREFERENCE.T0.value
|
||||
assert mock_ddg2.ef.ch2.reference.get() == 5 # CHANNELREFERENCE.E.value
|
||||
assert mock_ddg2.gh.ch1.reference.get() == 0 # CHANNELREFERENCE.T0.value
|
||||
assert mock_ddg2.gh.ch2.reference.get() == 7 # CHANNELREFERENCE.G.value
|
||||
|
||||
# Default trigger source
|
||||
assert mock_ddg2.trigger_source.get() == 1 # TRIGGERSOURCE.EXT_RISING_EDGE.value
|
||||
|
||||
|
||||
# def test_ddg1_stop(mock_ddg1):
|
||||
# """Test the on_stop method of DDG1."""
|
||||
# mock_ddg1.burst_mode.put(1) # Enable burst mode
|
||||
# mock_ddg1.stop()
|
||||
# assert mock_ddg1.burst_mode.get() == 0 # Burst mode is disabled
|
||||
|
||||
|
||||
#########################
|
||||
### Test DDG2 Device ####
|
||||
#########################
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_ddg2(mock_mcs_csaxs: MCSCardCSAXS) -> Generator[DDG2, None, None]:
|
||||
"""Fixture to mock the DDG1 device."""
|
||||
# Add enabled to mock_mcs_csaxs
|
||||
dm_mock = mock_mcs_csaxs.device_manager
|
||||
with patched_device(
|
||||
DDG2, name="ddg2", prefix="test_ddg2:", device_manager=dm_mock, _mock_pv_initial_value=0
|
||||
) as dev:
|
||||
dev.enabled = True
|
||||
dev.device_manager.devices["ddg2"] = dev
|
||||
try:
|
||||
yield dev
|
||||
finally:
|
||||
dev.destroy()
|
||||
|
||||
|
||||
def test_ddg2_on_connected(mock_ddg2: DDG2):
|
||||
"""Test the on_connected method of DDG1."""
|
||||
mock_ddg2.burst_mode.put(1) # Set burst mode to 1, if connected should reset it to 0
|
||||
mock_ddg2.burst_delay.put(5) # Set to non-zero, should reset to 0 on connected
|
||||
mock_ddg2.burst_count.put(10) # Set to non-default, should reset to 1 on connected
|
||||
with mock.patch.object(mock_ddg2, "set_io_values") as mock_set_io_values:
|
||||
mock_ddg2.on_connected()
|
||||
# Burst mode Defaults
|
||||
assert mock_ddg2.burst_mode.get() == 0
|
||||
|
||||
assert mock_set_io_values.call_count == len(DDG2_DEFAULT_IO_CONFIG)
|
||||
for ch, config in DDG2_DEFAULT_IO_CONFIG.items():
|
||||
assert mock.call(ch, **config) in mock_set_io_values.call_args_list
|
||||
|
||||
# Check reference values from DEFAULT_REFERENCES
|
||||
for ch, refs in DDG2_DEFAULT_REFERENCES:
|
||||
if ch == "A":
|
||||
sub_ch = mock_ddg2.ab.ch1
|
||||
elif ch == "B":
|
||||
sub_ch = mock_ddg2.ab.ch2
|
||||
elif ch == "C":
|
||||
sub_ch = mock_ddg2.cd.ch1
|
||||
elif ch == "D":
|
||||
sub_ch = mock_ddg2.cd.ch2
|
||||
elif ch == "E":
|
||||
sub_ch = mock_ddg2.ef.ch1
|
||||
elif ch == "F":
|
||||
sub_ch = mock_ddg2.ef.ch2
|
||||
elif ch == "G":
|
||||
sub_ch = mock_ddg2.gh.ch1
|
||||
elif ch == "H":
|
||||
sub_ch = mock_ddg2.gh.ch2
|
||||
assert sub_ch.reference.get() == refs.value
|
||||
|
||||
# Check Default trigger source
|
||||
assert mock_ddg2.trigger_source.get() == DDG2_DEFAULT_TRIGGER_SOURCE.value
|
||||
|
||||
|
||||
def test_ddg2_on_stage(mock_ddg2: DDG2):
|
||||
"""
|
||||
Test the on_stage method of DDG2.
|
||||
|
||||
We will test two scenarios:
|
||||
I. Stage device with valid parameters.
|
||||
II. Stage device with invalid parameters (too short exp_time). Should raise ValueError.
|
||||
"""
|
||||
ddg = mock_ddg2
|
||||
def test_ddg2_stage(mock_ddg2):
|
||||
"""Test the on_stage method of DDG2."""
|
||||
exp_time = 0.1
|
||||
frames_per_trigger = 10
|
||||
ddg.on_connected()
|
||||
ddg.scan_info.msg.scan_parameters["exp_time"] = exp_time
|
||||
ddg.scan_info.msg.scan_parameters["frames_per_trigger"] = frames_per_trigger
|
||||
mock_ddg2.on_connected()
|
||||
|
||||
# Set non-default burst mode settings
|
||||
ddg.burst_mode.put(0)
|
||||
ddg.burst_delay.put(5)
|
||||
mock_ddg2.burst_mode.put(0)
|
||||
mock_ddg2.scan_info.msg.scan_parameters["exp_time"] = exp_time
|
||||
mock_ddg2.scan_info.msg.scan_parameters["frames_per_trigger"] = frames_per_trigger
|
||||
|
||||
# Stage device with valid parameters
|
||||
ddg.stage()
|
||||
assert ddg.staged == ophyd.Staged.yes
|
||||
assert ddg.burst_mode.get() == 1 # Burst mode is enabled
|
||||
assert ddg.burst_delay.get() == 0 # Burst delay is set to 0
|
||||
assert ddg.burst_count.get() == frames_per_trigger
|
||||
assert ddg.burst_period.get() == exp_time
|
||||
mock_ddg2.stage()
|
||||
|
||||
# Pulse width is exp_time - readout_time
|
||||
burst_pulse_width = exp_time - DDG2_DEFAULT_READOUT_TIMES["ab"]
|
||||
assert np.isclose(ddg.ab.delay.get(), 0)
|
||||
assert np.isclose(ddg.ab.width.get(), burst_pulse_width)
|
||||
assert np.isclose(mock_ddg2.burst_mode.get(), 1) # Burst mode is enabled
|
||||
assert np.isclose(mock_ddg2.ab.delay.get(), 0)
|
||||
assert np.isclose(mock_ddg2.ab.width.get(), exp_time - 2e-4) # DEFAULT_READOUT_TIMES["ab"])
|
||||
assert mock_ddg2.burst_count.get() == frames_per_trigger
|
||||
assert np.isclose(mock_ddg2.burst_delay.get(), 0)
|
||||
assert np.isclose(mock_ddg2.burst_period.get(), exp_time)
|
||||
|
||||
assert mock_ddg2.trigger_source.get() == TRIGGERSOURCE.EXT_RISING_EDGE.value
|
||||
|
||||
assert mock_ddg2.staged == ophyd.Staged.yes
|
||||
mock_ddg2.unstage() # Reset staged state for next test
|
||||
|
||||
# Unstage to reset
|
||||
ddg.unstage() # Reset staged state for next test
|
||||
exp_time_short = 2e-4 # too short exposure time
|
||||
with pytest.raises(ValueError):
|
||||
ddg.scan_info.msg.scan_parameters["exp_time"] = exp_time_short
|
||||
ddg.stage()
|
||||
mock_ddg2.scan_info.msg.scan_parameters["exp_time"] = 2e-4 # too short exposure time
|
||||
mock_ddg2.stage()
|
||||
|
||||
|
||||
def test_ddg2_on_trigger(mock_ddg2: DDG2):
|
||||
def test_ddg2_trigger(mock_ddg2):
|
||||
"""Test the on_trigger method of DDG2."""
|
||||
ddg = mock_ddg2
|
||||
ddg.on_connected()
|
||||
ddg.trigger_shot.put(0)
|
||||
status = ddg.trigger()
|
||||
assert ddg.trigger_shot.get() == 0 # Should not trigger DDG2 via soft trigger
|
||||
mock_ddg2.trigger_shot.put(0)
|
||||
status = mock_ddg2.trigger()
|
||||
assert mock_ddg2.trigger_shot.get() == 0 # Should not trigger DDG2 via soft trigger
|
||||
status.wait()
|
||||
assert status.done is True
|
||||
assert status.success is True
|
||||
|
||||
|
||||
def test_ddg2_on_stop(mock_ddg2: DDG2):
|
||||
def test_ddg2_stop(mock_ddg2):
|
||||
"""Test the on_stop method of DDG2."""
|
||||
ddg = mock_ddg2
|
||||
ddg.on_connected()
|
||||
ddg.burst_mode.put(1) # Enable burst mode
|
||||
ddg.stop()
|
||||
assert ddg.burst_mode.get() == 0 # Burst mode is disabled
|
||||
|
||||
|
||||
# def test_ddg2_stage(mock_ddg2):
|
||||
# """Test the on_stage method of DDG2."""
|
||||
# exp_time = 0.1
|
||||
# frames_per_trigger = 10
|
||||
# mock_ddg2.on_connected()
|
||||
|
||||
# mock_ddg2.burst_mode.put(0)
|
||||
# mock_ddg2.scan_info.msg.scan_parameters["exp_time"] = exp_time
|
||||
# mock_ddg2.scan_info.msg.scan_parameters["frames_per_trigger"] = frames_per_trigger
|
||||
|
||||
# mock_ddg2.stage()
|
||||
|
||||
# assert np.isclose(mock_ddg2.burst_mode.get(), 1) # Burst mode is enabled
|
||||
# assert np.isclose(mock_ddg2.ab.delay.get(), 0)
|
||||
# assert np.isclose(mock_ddg2.ab.width.get(), exp_time - 2e-4) # DEFAULT_READOUT_TIMES["ab"])
|
||||
# assert mock_ddg2.burst_count.get() == frames_per_trigger
|
||||
# assert np.isclose(mock_ddg2.burst_delay.get(), 0)
|
||||
# assert np.isclose(mock_ddg2.burst_period.get(), exp_time)
|
||||
|
||||
# assert mock_ddg2.trigger_source.get() == TRIGGERSOURCE.EXT_RISING_EDGE.value
|
||||
|
||||
# assert mock_ddg2.staged == ophyd.Staged.yes
|
||||
# mock_ddg2.unstage() # Reset staged state for next test
|
||||
|
||||
# with pytest.raises(ValueError):
|
||||
# mock_ddg2.scan_info.msg.scan_parameters["exp_time"] = 2e-4 # too short exposure time
|
||||
# mock_ddg2.stage()
|
||||
|
||||
|
||||
# def test_ddg2_trigger(mock_ddg2):
|
||||
# """Test the on_trigger method of DDG2."""
|
||||
# mock_ddg2.trigger_shot.put(0)
|
||||
# status = mock_ddg2.trigger()
|
||||
# assert mock_ddg2.trigger_shot.get() == 0 # Should not trigger DDG2 via soft trigger
|
||||
# status.wait()
|
||||
# assert status.done is True
|
||||
# assert status.success is True
|
||||
|
||||
|
||||
# def test_ddg2_stop(mock_ddg2):
|
||||
# """Test the on_stop method of DDG2."""
|
||||
# mock_ddg2.burst_mode.put(1) # Enable burst mode
|
||||
# mock_ddg2.stop()
|
||||
# assert mock_ddg2.burst_mode.get() == 0 # Burst mode is disabled
|
||||
mock_ddg2.burst_mode.put(1) # Enable burst mode
|
||||
mock_ddg2.stop()
|
||||
assert mock_ddg2.burst_mode.get() == 0 # Burst mode is disabled
|
||||
|
||||
@@ -5,7 +5,6 @@ from time import time
|
||||
from typing import TYPE_CHECKING, Generator
|
||||
from unittest import mock
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from bec_lib.messages import FileMessage, ScanStatusMessage
|
||||
from jfjoch_client.models.broker_status import BrokerStatus
|
||||
@@ -79,7 +78,7 @@ def detector_list(request) -> Generator[DetectorList, None, None]:
|
||||
),
|
||||
DetectorListElement(
|
||||
id=2,
|
||||
description="EIGER 9M",
|
||||
description="EIGER 8.5M (tmp)",
|
||||
serial_number="123456",
|
||||
base_ipv4_addr="192.168.0.1",
|
||||
udp_interface_count=1,
|
||||
@@ -104,11 +103,7 @@ def eiger_1_5m(mock_scan_info) -> Generator[Eiger1_5M, None, None]:
|
||||
name = "eiger_1_5m"
|
||||
dev = Eiger1_5M(name=name, beam_center=(256, 256), detector_distance=100.0)
|
||||
dev.scan_info.msg = mock_scan_info
|
||||
try:
|
||||
yield dev
|
||||
finally:
|
||||
if dev._destroyed is False:
|
||||
dev.destroy()
|
||||
yield dev
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@@ -118,19 +113,7 @@ def eiger_9m(mock_scan_info) -> Generator[Eiger9M, None, None]:
|
||||
name = "eiger_9m"
|
||||
dev = Eiger9M(name=name)
|
||||
dev.scan_info.msg = mock_scan_info
|
||||
try:
|
||||
yield dev
|
||||
finally:
|
||||
if dev._destroyed is False:
|
||||
dev.destroy()
|
||||
|
||||
|
||||
def test_eiger_wait_for_connection(eiger_1_5m, eiger_9m):
|
||||
"""Test the wait_for_connection metho is calling status_get on the JFJ API client."""
|
||||
for eiger in (eiger_1_5m, eiger_9m):
|
||||
with mock.patch.object(eiger.jfj_client.api, "status_get") as mock_status_get:
|
||||
eiger.wait_for_connection(timeout=1)
|
||||
mock_status_get.assert_called_once_with(_request_timeout=1)
|
||||
yield dev
|
||||
|
||||
|
||||
@pytest.mark.parametrize("detector_state", ["Idle", "Inactive"])
|
||||
@@ -158,7 +141,7 @@ def test_eiger_1_5m_on_connected(eiger_1_5m, detector_list, detector_state):
|
||||
else:
|
||||
eiger.on_connected()
|
||||
assert mock_set_det.call_args == mock.call(
|
||||
DetectorSettings(frame_time_us=500, timing=DetectorTiming.TRIGGER), timeout=5
|
||||
DetectorSettings(frame_time_us=500, timing=DetectorTiming.TRIGGER), timeout=10
|
||||
)
|
||||
assert mock_file_writer.call_args == mock.call(
|
||||
file_writer_settings=FileWriterSettings(
|
||||
@@ -196,7 +179,7 @@ def test_eiger_9m_on_connected(eiger_9m, detector_list, detector_state):
|
||||
else:
|
||||
eiger.on_connected()
|
||||
assert mock_set_det.call_args == mock.call(
|
||||
DetectorSettings(frame_time_us=500, timing=DetectorTiming.TRIGGER), timeout=5
|
||||
DetectorSettings(frame_time_us=500, timing=DetectorTiming.TRIGGER), timeout=10
|
||||
)
|
||||
assert mock_file_writer.call_args == mock.call(
|
||||
file_writer_settings=FileWriterSettings(
|
||||
@@ -233,39 +216,11 @@ def test_eiger_on_stop(eiger_1_5m):
|
||||
stop_event.wait(timeout=5) # Thread should be killed from task_handler
|
||||
|
||||
|
||||
def test_eiger_on_destroy(eiger_1_5m):
|
||||
"""Test the on_destroy logic of the Eiger detector. This is equivalent for 9M and 1_5M."""
|
||||
eiger = eiger_1_5m
|
||||
start_event = threading.Event()
|
||||
stop_event = threading.Event()
|
||||
|
||||
def tmp_task():
|
||||
start_event.set()
|
||||
try:
|
||||
while True:
|
||||
time.sleep(0.1)
|
||||
finally:
|
||||
stop_event.set()
|
||||
|
||||
eiger.task_handler.submit_task(tmp_task)
|
||||
start_event.wait(timeout=5)
|
||||
|
||||
with (
|
||||
mock.patch.object(eiger.jfj_preview_client, "stop") as mock_jfj_preview_client_stop,
|
||||
mock.patch.object(eiger.jfj_client, "stop") as mock_jfj_client_stop,
|
||||
):
|
||||
eiger.on_destroy()
|
||||
mock_jfj_preview_client_stop.assert_called_once()
|
||||
mock_jfj_client_stop.assert_called_once()
|
||||
stop_event.wait(timeout=5)
|
||||
|
||||
|
||||
@pytest.mark.timeout(25)
|
||||
@pytest.mark.parametrize("raise_timeout", [True, False])
|
||||
def test_eiger_on_complete(eiger_1_5m, raise_timeout):
|
||||
"""Test the on_complete logic of the Eiger detector. This is equivalent for 9M and 1_5M."""
|
||||
eiger = eiger_1_5m
|
||||
eiger._wait_for_on_complete = 1 # reduce wait time for testing
|
||||
|
||||
callback_completed_event = threading.Event()
|
||||
|
||||
@@ -275,7 +230,7 @@ def test_eiger_on_complete(eiger_1_5m, raise_timeout):
|
||||
|
||||
unblock_wait_for_idle = threading.Event()
|
||||
|
||||
def mock_wait_for_idle(timeout: float, raise_on_timeout: bool) -> bool:
|
||||
def mock_wait_for_idle(timeout: int, request_timeout: float):
|
||||
if unblock_wait_for_idle.wait(timeout):
|
||||
if raise_timeout:
|
||||
return False
|
||||
@@ -283,18 +238,11 @@ def test_eiger_on_complete(eiger_1_5m, raise_timeout):
|
||||
return False
|
||||
|
||||
with (
|
||||
mock.patch.object(
|
||||
eiger.jfj_client.api, "status_get", return_value=BrokerStatus(state="Idle")
|
||||
),
|
||||
mock.patch.object(eiger.jfj_client, "wait_for_idle", side_effect=mock_wait_for_idle),
|
||||
mock.patch.object(
|
||||
eiger.jfj_client.api,
|
||||
"statistics_data_collection_get",
|
||||
return_value=MeasurementStatistics(
|
||||
run_number=1,
|
||||
images_collected=eiger.scan_info.msg.num_points
|
||||
* eiger.scan_info.msg.scan_parameters["frames_per_trigger"],
|
||||
),
|
||||
return_value=MeasurementStatistics(run_number=1),
|
||||
),
|
||||
):
|
||||
status = eiger.complete()
|
||||
@@ -336,7 +284,7 @@ def test_eiger_file_event_callback(eiger_1_5m, tmp_path):
|
||||
assert file_msg.hinted_h5_entries == {"data": "entry/data/data"}
|
||||
|
||||
|
||||
def test_eiger_on_stage(eiger_1_5m):
|
||||
def test_eiger_on_sage(eiger_1_5m):
|
||||
"""Test the on_stage and on_unstage logic of the Eiger detector. This is equivalent for 9M and 1_5M."""
|
||||
eiger = eiger_1_5m
|
||||
scan_msg = eiger.scan_info.msg
|
||||
@@ -368,35 +316,3 @@ def test_eiger_on_stage(eiger_1_5m):
|
||||
)
|
||||
assert mock_start.call_args == mock.call(settings=data_settings)
|
||||
assert eiger.staged is Staged.yes
|
||||
|
||||
|
||||
def test_eiger_set_det_distance_test_beam_center(eiger_1_5m):
|
||||
"""Test the set_detector_distance and set_beam_center methods. Equivalent for 9M and 1_5M."""
|
||||
eiger = eiger_1_5m
|
||||
old_distance = eiger.detector_distance
|
||||
new_distance = old_distance + 100
|
||||
old_beam_center = eiger.beam_center
|
||||
new_beam_center = (old_beam_center[0] + 20, old_beam_center[1] + 50)
|
||||
eiger.set_detector_distance(new_distance)
|
||||
assert eiger.detector_distance == new_distance
|
||||
eiger.set_beam_center(x=new_beam_center[0], y=new_beam_center[1])
|
||||
assert eiger.beam_center == new_beam_center
|
||||
with pytest.raises(ValueError):
|
||||
eiger.set_beam_center(x=-10, y=100) # Cannot set negative beam center
|
||||
with pytest.raises(ValueError):
|
||||
eiger.detector_distance = -50 # Cannot set negative detector distance
|
||||
|
||||
|
||||
def test_eiger_preview_callback(eiger_1_5m):
|
||||
"""Preview callback test for the Eiger detector. This is equivalent for 9M and 1_5M."""
|
||||
eiger = eiger_1_5m
|
||||
# NOTE: I don't find models for the CBOR messages used by JFJ, currently using a dummay dict.
|
||||
# Please adjust once the proper model is found.
|
||||
for msg_type in ["start", "end", "image", "calibration", "metadata"]:
|
||||
msg = {"type": msg_type, "data": {"default": np.array([[1, 2], [3, 4]])}}
|
||||
with mock.patch.object(eiger.preview_image, "put") as mock_preview_put:
|
||||
eiger._preview_callback(msg)
|
||||
if msg_type == "image":
|
||||
mock_preview_put.assert_called_once_with(msg["data"]["default"])
|
||||
else:
|
||||
mock_preview_put.assert_not_called()
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
"""Module to test epics devices."""
|
||||
|
||||
import pytest
|
||||
from ophyd_devices.tests.utils import patched_device
|
||||
|
||||
from csaxs_bec.devices.epics.fast_shutter import cSAXSFastEpicsShutter
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fast_shutter_device():
|
||||
"""Fixture to create a patched cSAXSFastEpicsShutter device for testing."""
|
||||
with patched_device(cSAXSFastEpicsShutter, name="fsh", prefix="X12SA-ES1-TTL:") as device:
|
||||
yield device
|
||||
|
||||
|
||||
def test_fast_shutter_methods(fast_shutter_device):
|
||||
"""Test the user-facing methods of the cSAXSFastEpicsShutter device."""
|
||||
assert fast_shutter_device.name == "fsh", "Device name should be 'fsh'"
|
||||
assert fast_shutter_device.prefix == "X12SA-ES1-TTL:", "Device prefix is 'X12SA-ES1-TTL:'"
|
||||
# Test fshopen and fshclose
|
||||
fast_shutter_device.fshopen()
|
||||
assert fast_shutter_device.shutter.get() == 1, "Shutter should be open (1) after fshopen()"
|
||||
assert fast_shutter_device.fshstatus() == 1, "fshstatus should return 1 when shutter is open"
|
||||
|
||||
fast_shutter_device.fshclose()
|
||||
assert fast_shutter_device.shutter.get() == 0, "Shutter should be closed (0) after fshclose()"
|
||||
assert fast_shutter_device.fshstatus() == 0, "fshstatus should return 0 when shutter is closed"
|
||||
|
||||
# shutter_readback is connected to separate PV.
|
||||
fast_shutter_device.shutter_readback._read_pv.mock_data = 1 # Simulate readback showing open
|
||||
assert (
|
||||
fast_shutter_device.fshstatus_readback() == 1
|
||||
), "fshstatus_readback should return 1 when shutter readback shows open"
|
||||
fast_shutter_device.shutter_readback._read_pv.mock_data = 0 # Simulate readback showing closed
|
||||
assert (
|
||||
fast_shutter_device.fshstatus_readback() == 0
|
||||
), "fshstatus_readback should return 0 when shutter readback shows closed"
|
||||
@@ -1,99 +0,0 @@
|
||||
# pylint: skip-file
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from ophyd_devices.tests.utils import patched_device
|
||||
|
||||
from csaxs_bec.devices.epics.eps import EPS
|
||||
|
||||
ALL_PVS = [
|
||||
# ALARMS
|
||||
"X12SA-EPS-PLC:AlarmCnt_EPS",
|
||||
"ARS00-MIS-PLC-01:AlarmCnt_Frontends",
|
||||
# FRONTEND VALVES
|
||||
"X12SA-FE-VVPG-0000:PLC_OPEN",
|
||||
"X12SA-FE-VVPG-1010:PLC_OPEN",
|
||||
"X12SA-FE-VVFV-2010:PLC_OPEN",
|
||||
"X12SA-FE-VVPG-2010:PLC_OPEN",
|
||||
# Optics VALVES
|
||||
"X12SA-OP-VVPG-1010:PLC_OPEN",
|
||||
"X12SA-OP-VVPG-2010:PLC_OPEN",
|
||||
"X12SA-OP-VVPG-3010:PLC_OPEN",
|
||||
"X12SA-OP-VVPG-3020:PLC_OPEN",
|
||||
"X12SA-OP-VVPG-4010:PLC_OPEN",
|
||||
"X12SA-OP-VVPG-5010:PLC_OPEN",
|
||||
"X12SA-OP-VVPG-6010:PLC_OPEN",
|
||||
"X12SA-OP-VVPG-7010:PLC_OPEN",
|
||||
# Endstation VALVES
|
||||
"X12SA-ES-VVPG-1010:PLC_OPEN",
|
||||
# Frontend SHUTTERS
|
||||
"X12SA-FE-PSH1-EMLS-0010:OPEN",
|
||||
"X12SA-FE-STO1-EMLS-0010:OPEN",
|
||||
# Optics SHUTTERS
|
||||
"X12SA-OP-PSH1-EMLS-7010:OPEN",
|
||||
# DMM Monochromator
|
||||
"X12SA-OP-DMM-ETTC-3010:TEMP",
|
||||
"X12SA-OP-DMM-ETTC-3020:TEMP",
|
||||
"X12SA-OP-DMM-ETTC-3030:TEMP",
|
||||
"X12SA-OP-DMM-ETTC-3040:TEMP",
|
||||
"X12SA-OP-DMM-EMLS-3010:THRU",
|
||||
"X12SA-OP-DMM-EMLS-3020:IN",
|
||||
"X12SA-OP-DMM-EMLS-3030:THRU",
|
||||
"X12SA-OP-DMM-EMLS-3040:IN",
|
||||
"X12SA-OP-DMM-EMSW-3050:SWITCH",
|
||||
"X12SA-OP-DMM-EMSW-3060:SWITCH",
|
||||
"X12SA-OP-DMM-EMSW-3070:SWITCH",
|
||||
"X12SA-OP-DMM1:ENERGY-GET",
|
||||
"X12SA-OP-DMM1:POSITION",
|
||||
"X12SA-OP-DMM1:STRIPE",
|
||||
# CCM Monochromator
|
||||
"X12SA-OP-CCM-ETTC-4010:TEMP",
|
||||
"X12SA-OP-CCM-ETTC-4020:TEMP",
|
||||
"X12SA-OP-CCM-EMSW-4010:SWITCH",
|
||||
"X12SA-OP-CCM-EMSW-4020:SWITCH",
|
||||
"X12SA-OP-CCM-EMSW-4030:SWITCH",
|
||||
"X12SA-OP-CCM1:ENERGY-GET",
|
||||
"X12SA-OP-CCM1:POSITION",
|
||||
# Water Cooling
|
||||
"X12SA-OP-SL1-EFSW-2010:FLOW",
|
||||
"X12SA-OP-SL2-EFSW-2010:FLOW",
|
||||
"X12SA-OP-EB1-EFSW-5010:FLOW",
|
||||
"X12SA-OP-EB1-EFSW-5020:FLOW",
|
||||
"X12SA-OP-SL3-EFSW-5010:FLOW",
|
||||
"X12SA-OP-KB-EFSW-6010:FLOW",
|
||||
"X12SA-OP-PSH1-EFSW-7010:FLOW",
|
||||
"X12SA-ES-EB2-EFSW-1010:FLOW",
|
||||
"X12SA-OP-CS-ECVW-0010:PLC_OPEN",
|
||||
"X12SA-OP-CS-ECVW-0020:PLC_OPEN",
|
||||
# Request PVs
|
||||
"X12SA-EPS-PLC:ACKERR-REQUEST",
|
||||
"X12SA-OP-CS-ECVW:PLC_REQUEST",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def eps():
|
||||
dev_name = "EPS"
|
||||
with patched_device(EPS, name=dev_name) as eps:
|
||||
yield eps
|
||||
|
||||
|
||||
def test_eps_has_signals(eps):
|
||||
"""Test that all expected PVs are present in the eps device."""
|
||||
found_pvs = [walk.item._read_pv.pvname for walk in eps.walk_signals()]
|
||||
assert set(found_pvs) == set(
|
||||
ALL_PVS
|
||||
), f"Expected PVs {ALL_PVS} but found {set(ALL_PVS) - set(found_pvs)}"
|
||||
|
||||
|
||||
# pylint: disable=line-too-long
|
||||
expected_show_all_output = "\x1b[1mX12SA EPS status\x1b[0m\n\n\x1b[1mEPS Alarms\x1b[0m\n - X12SA EPS Alarm count 0\n - FrontEnd MIS Alarm count 0\n\n\x1b[1mValves Frontend\x1b[0m\n - FE-VVPG-0000 \x1b[91mCLOSED\x1b[0m\n - FE-VVPG-1010 \x1b[91mCLOSED\x1b[0m\n - FE-VVFV-2010 \x1b[91mCLOSED\x1b[0m\n - FE-VVPG-2010 \x1b[91mCLOSED\x1b[0m\n\n\x1b[1mValves Optics Hutch\x1b[0m\n - OP-VVPG-1010 \x1b[91mCLOSED\x1b[0m\n - OP-VVPG-2010 \x1b[91mCLOSED\x1b[0m\n - OP-VVPG-3010 \x1b[91mCLOSED\x1b[0m\n - OP-VVPG-3020 \x1b[91mCLOSED\x1b[0m\n - OP-VVPG-4010 \x1b[91mCLOSED\x1b[0m\n - OP-VVPG-5010 \x1b[91mCLOSED\x1b[0m\n - OP-VVPG-6010 \x1b[91mCLOSED\x1b[0m\n - OP-VVPG-7010 \x1b[91mCLOSED\x1b[0m\n\n\x1b[1mValves ES Hutch\x1b[0m\n - ES-VVPG-1010 \x1b[91mCLOSED\x1b[0m\n\n\x1b[1mShutters Frontend\x1b[0m\n - FE-PSH1-EMLS-0010 \x1b[91mCLOSED\x1b[0m\n - FE-STO1-EMLS-0010 \x1b[91mCLOSED\x1b[0m\n\n\x1b[1mShutters Endstation\x1b[0m\n - OP-PSH1-EMLS-7010 \x1b[91mCLOSED\x1b[0m\n\n\x1b[1mDMM Monochromator\x1b[0m\n - DMM Temp Surface 1 0.0\n - DMM Temp Surface 2 0.0\n - DMM Temp Shield 1 (disaster) 0.0\n - DMM Temp Shield 2 (disaster) 0.0\n - DMM Translation ThruPos \x1b[91mINACTIVE\x1b[0m\n - DMM Translation InPos \x1b[91mINACTIVE\x1b[0m\n - DMM Bragg ThruPos \x1b[91mINACTIVE\x1b[0m\n - DMM Bragg InPos \x1b[91mINACTIVE\x1b[0m\n - DMM Heater Fault XTAL 1 \x1b[92mOK\x1b[0m\n - DMM Heater Fault XTAL 2 \x1b[92mOK\x1b[0m\n - DMM Heater Fault Support 1 \x1b[92mOK\x1b[0m\n - DMM Energy 0.0000\n - DMM Position out of beam\n - DMM Stripe Stripe 1 W/B4C\n\n\x1b[1mCCM Monochromator\x1b[0m\n - CCM Temp Crystal 0.0\n - CCM Temp Shield (disaster) 0.0\n - CCM Heater Fault 1 \x1b[92mOK\x1b[0m\n - CCM Heater Fault 2 \x1b[92mOK\x1b[0m\n - CCM Heater Fault 3 \x1b[92mOK\x1b[0m\n - CCM Energy 0.0000\n - CCM Position out of beam\n\n\x1b[1mCooling Water\x1b[0m\n - OP-SL1-EFSW-2010 \x1b[91mFAIL\x1b[0m\n - OP-SL2-EFSW-2010 \x1b[91mFAIL\x1b[0m\n - OP-EB1-EFSW-5010 \x1b[91mFAIL\x1b[0m\n - OP-EB1-EFSW-5020 \x1b[91mFAIL\x1b[0m\n - OP-SL3-EFSW-5010 \x1b[91mFAIL\x1b[0m\n - OP-KB-EFSW-6010 \x1b[91mFAIL\x1b[0m\n - OP-PSH1-EFSW-7010 \x1b[91mFAIL\x1b[0m\n - ES-EB2-EFSW-1010 \x1b[91mFAIL\x1b[0m\n - OP-CS-ECVW-0010 \x1b[91mCLOSED\x1b[0m\n - OP-CS-ECVW-0020 \x1b[91mCLOSED\x1b[0m\n\n\x1b[96mHint:\x1b[0m Both water cooling valves are CLOSED.\nYou can open them using: \x1b[1mdev.x12saEPS.water_cooling_op()\x1b[0m\n"
|
||||
|
||||
|
||||
def test_eps_show_all(eps, capsys):
|
||||
"""Test that the show_all method outputs the expected status."""
|
||||
eps.show_all()
|
||||
output = capsys.readouterr().out
|
||||
assert (
|
||||
output == expected_show_all_output
|
||||
), f"Expected output does not match actual output.\nExpected:\n{expected_show_all_output}\nActual:\n{output}"
|
||||
@@ -2,7 +2,6 @@ from unittest import mock
|
||||
|
||||
import pytest
|
||||
from ophyd_devices.tests.utils import SocketMock
|
||||
from ophyd_devices.utils.socket import SocketSignal
|
||||
|
||||
from csaxs_bec.devices.omny.galil.fupr_ophyd import FuprGalilController, FuprGalilMotor
|
||||
|
||||
@@ -18,11 +17,6 @@ def fsamroy(dm_with_devices):
|
||||
socket_cls=SocketMock,
|
||||
device_manager=dm_with_devices,
|
||||
)
|
||||
for walk in fsamroy_motor.walk_signals():
|
||||
if isinstance(walk.item, SocketSignal):
|
||||
walk.item._readback_timeout = (
|
||||
0.0 # Set the readback timeout to 0 to avoid waiting during tests
|
||||
)
|
||||
fsamroy_motor.controller.on()
|
||||
assert isinstance(fsamroy_motor.controller, FuprGalilController)
|
||||
yield fsamroy_motor
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import copy
|
||||
import inspect
|
||||
from unittest import mock
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from bec_server.device_server.tests.utils import DMMock
|
||||
from ophyd_devices.tests.utils import SocketMock
|
||||
|
||||
from csaxs_bec.devices.npoint.npoint import NPointAxis, NPointController
|
||||
from csaxs_bec.devices.omny.galil.fgalil_ophyd import FlomniGalilController, FlomniGalilMotor
|
||||
from csaxs_bec.devices.omny.galil.fupr_ophyd import FuprGalilController, FuprGalilMotor
|
||||
from csaxs_bec.devices.omny.galil.galil_rio import (
|
||||
GalilRIO,
|
||||
GalilRIOAnalogSignalRO,
|
||||
GalilRIOController,
|
||||
)
|
||||
from csaxs_bec.devices.omny.galil.lgalil_ophyd import LamniGalilController, LamniGalilMotor
|
||||
from csaxs_bec.devices.omny.galil.ogalil_ophyd import OMNYGalilController, OMNYGalilMotor
|
||||
from csaxs_bec.devices.omny.galil.sgalil_ophyd import GalilController, SGalilMotor
|
||||
@@ -179,9 +173,9 @@ def test_find_reference(leyex, axis_nr, socket_put_messages, socket_get_messages
|
||||
assert leyex.controller.sock.buffer_put == socket_put_messages
|
||||
|
||||
|
||||
def test_wait_for_connection_called(dm_with_devices):
|
||||
def test_wait_for_connection_called():
|
||||
"""Test that wait_for_connection is called on all motors that have a socket controller."""
|
||||
dm = dm_with_devices
|
||||
dm = DMMock()
|
||||
testable_connections = [
|
||||
(NPointAxis, NPointController),
|
||||
(FlomniGalilMotor, FlomniGalilController),
|
||||
@@ -193,7 +187,6 @@ def test_wait_for_connection_called(dm_with_devices):
|
||||
(RtLamniMotor, RtLamniController),
|
||||
(RtOMNYMotor, RtOMNYController),
|
||||
(SmaractMotor, SmaractController),
|
||||
(GalilRIO, GalilRIOController),
|
||||
]
|
||||
for motor_cls, controller_cls in testable_connections:
|
||||
# Store values to restore later
|
||||
@@ -202,20 +195,14 @@ def test_wait_for_connection_called(dm_with_devices):
|
||||
controller_cls._reset_controller()
|
||||
controller_cls._axes_per_controller = 3
|
||||
|
||||
inspect_args = inspect.getfullargspec(motor_cls.__init__).args
|
||||
inspect_kwargs = inspect.getfullargspec(motor_cls.__init__).kwonlyargs
|
||||
if len(inspect_args) > 1:
|
||||
args = ("C",)
|
||||
else:
|
||||
args = ()
|
||||
kwargs = {
|
||||
"name": "test_motor",
|
||||
"host": "mpc2680.psi.ch",
|
||||
"port": 8081,
|
||||
"device_manager": dm,
|
||||
"socket_cls": SocketMock,
|
||||
}
|
||||
motor = motor_cls(*args, **kwargs)
|
||||
motor = motor_cls(
|
||||
"C",
|
||||
name="test_motor",
|
||||
host="mpc2680.psi.ch",
|
||||
port=8081,
|
||||
socket_cls=SocketMock,
|
||||
device_manager=dm,
|
||||
)
|
||||
with mock.patch.object(motor.controller, "on") as mock_on:
|
||||
|
||||
motor.wait_for_connection(timeout=5.0)
|
||||
@@ -232,172 +219,3 @@ def test_wait_for_connection_called(dm_with_devices):
|
||||
finally:
|
||||
controller_cls._reset_controller()
|
||||
controller_cls._axes_per_controller = ctrl_axis_backup
|
||||
|
||||
|
||||
########################
|
||||
#### Test Galil RIO ####
|
||||
########################
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def galil_rio(dm_with_devices):
|
||||
try:
|
||||
rio = GalilRIO(
|
||||
name="galil_rio",
|
||||
host="129.129.0.1",
|
||||
socket_cls=SocketMock,
|
||||
device_manager=dm_with_devices,
|
||||
)
|
||||
rio.wait_for_connection()
|
||||
yield rio
|
||||
finally:
|
||||
rio.destroy()
|
||||
|
||||
|
||||
def test_galil_rio_initialization(galil_rio):
|
||||
"""
|
||||
Test that the Galil RIO signal can establish a connection.
|
||||
"""
|
||||
assert galil_rio.controller.connected is True
|
||||
# All signals should be connected if the controller is connected
|
||||
for walk in galil_rio.walk_signals():
|
||||
signal = walk.item
|
||||
assert signal.connected is True
|
||||
|
||||
assert galil_rio.controller._socket_host == "129.129.0.1"
|
||||
assert galil_rio.controller._socket_port == 23 # Default port
|
||||
|
||||
|
||||
def test_galil_rio_signal_read(galil_rio):
|
||||
"""
|
||||
Test that the Galil RIO signal can read values correctly.
|
||||
"""
|
||||
###########
|
||||
## Test read of all channels
|
||||
###########
|
||||
|
||||
assert galil_rio.analog_in.ch0._readback_timeout == 0.1 # Default read timeout of 100ms
|
||||
# Mock the socket to return specific values
|
||||
analog_bufffer = b" 1.234 2.345 3.456 4.567 5.678 6.789 7.890 8.901\r\n"
|
||||
galil_rio.controller.sock.buffer_recv = [] # Clear any existing buffer
|
||||
galil_rio.controller.sock.buffer_recv.append(analog_bufffer)
|
||||
read_values = galil_rio.read()
|
||||
assert len(read_values) == 8 # 8 channels
|
||||
|
||||
expected_values = {
|
||||
galil_rio.analog_in.ch0.name: {"value": 1.234},
|
||||
galil_rio.analog_in.ch1.name: {"value": 2.345},
|
||||
galil_rio.analog_in.ch2.name: {"value": 3.456},
|
||||
galil_rio.analog_in.ch3.name: {"value": 4.567},
|
||||
galil_rio.analog_in.ch4.name: {"value": 5.678},
|
||||
galil_rio.analog_in.ch5.name: {"value": 6.789},
|
||||
galil_rio.analog_in.ch6.name: {"value": 7.890},
|
||||
galil_rio.analog_in.ch7.name: {"value": 8.901},
|
||||
}
|
||||
# All timestamps should be the same
|
||||
assert all(
|
||||
ret["timestamp"] == read_values[galil_rio.analog_in.ch0.name]["timestamp"]
|
||||
for signal_name, ret in read_values.items()
|
||||
)
|
||||
# Check values
|
||||
for signal_name, expected in expected_values.items():
|
||||
assert np.isclose(read_values[signal_name]["value"], expected["value"])
|
||||
assert "timestamp" in read_values[signal_name]
|
||||
|
||||
# Check communication command to socker
|
||||
assert galil_rio.controller.sock.buffer_put == [
|
||||
b"MG@AN[0], @AN[1], @AN[2], @AN[3], @AN[4], @AN[5], @AN[6], @AN[7]\r"
|
||||
]
|
||||
|
||||
###########
|
||||
## Test read of single channel with callback
|
||||
###########
|
||||
|
||||
# Add callback to update readback
|
||||
value_callback_buffer: list[tuple] = []
|
||||
|
||||
def value_callback(value, old_value, **kwargs):
|
||||
obj = kwargs.get("obj")
|
||||
galil = obj.parent.parent
|
||||
readback = galil.read()
|
||||
value_callback_buffer.append(readback)
|
||||
|
||||
galil_rio.analog_in.ch0.subscribe(value_callback, run=False)
|
||||
galil_rio.controller.sock.buffer_recv = [b" 2.5 2.6 2.7 2.8 2.9 3.0 3.1 3.2"]
|
||||
expected_values = [2.5, 2.6, 2.7, 2.8, 2.9, 3.0, 3.1, 3.2]
|
||||
|
||||
##################
|
||||
## Test cached readback
|
||||
##################
|
||||
|
||||
# Should have used the cached value
|
||||
for walk in galil_rio.walk_signals():
|
||||
walk.item._readback_timeout = 10 # Make sure cached read is used
|
||||
ret = galil_rio.analog_in.ch0.read()
|
||||
|
||||
# Should not trigger callback since value did not change
|
||||
assert np.isclose(ret[galil_rio.analog_in.ch0.name]["value"], 1.234)
|
||||
# Same timestamp as for another channel as this is cached read
|
||||
assert np.isclose(
|
||||
ret[galil_rio.analog_in.ch0.name]["timestamp"], galil_rio.analog_in.ch7.timestamp
|
||||
)
|
||||
assert len(value_callback_buffer) == 0
|
||||
|
||||
##################
|
||||
## Test unchached read from controller
|
||||
##################
|
||||
|
||||
# Now force a read from the controller
|
||||
galil_rio.analog_in.ch0._last_readback = 0 # Force read from controller
|
||||
ret = galil_rio.analog_in.ch0.read()
|
||||
|
||||
assert np.isclose(ret[galil_rio.analog_in.ch0.name]["value"], 2.5)
|
||||
|
||||
# Check callback invocation, but only 1 callback even with galil_rio.read() call in callback
|
||||
assert len(value_callback_buffer) == 1
|
||||
values = [value["value"] for value in value_callback_buffer[0].values()]
|
||||
assert np.isclose(values, expected_values).all()
|
||||
assert all(
|
||||
[
|
||||
value["timestamp"]
|
||||
== value_callback_buffer[0][galil_rio.analog_in.ch0.name]["timestamp"]
|
||||
for value in value_callback_buffer[0].values()
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_galil_rio_digital_out_signal(galil_rio):
|
||||
"""
|
||||
Test that the Galil RIO digital output signal can be set correctly.
|
||||
"""
|
||||
## Test Read from digital output channels
|
||||
buffer_receive = []
|
||||
excepted_put_buffer = []
|
||||
for ii in range(galil_rio.digital_out.ch0._NUM_DIGITAL_OUTPUT_CHANNELS):
|
||||
cmd = f"MG@OUT[{ii}]\r".encode()
|
||||
excepted_put_buffer.append(cmd)
|
||||
recv = " 1.000".encode()
|
||||
buffer_receive.append(recv)
|
||||
|
||||
galil_rio.controller.sock.buffer_recv = buffer_receive # Mock response for readback
|
||||
|
||||
digital_read = galil_rio.read_configuration() # Read to populate readback values
|
||||
|
||||
for walk in galil_rio.digital_out.walk_signals():
|
||||
assert np.isclose(digital_read[walk.item.name]["value"], 1.0)
|
||||
|
||||
assert galil_rio.controller.sock.buffer_put == excepted_put_buffer
|
||||
|
||||
# Test writing to digital output channels
|
||||
galil_rio.controller.sock.buffer_put = [] # Clear buffer put
|
||||
galil_rio.controller.sock.buffer_recv = [b":"] # Mock response for readback
|
||||
|
||||
# Set digital output channel 0 to high
|
||||
galil_rio.digital_out.ch0.put(1)
|
||||
assert galil_rio.controller.sock.buffer_put == [b"SB0\r"]
|
||||
|
||||
# Set digital output channel 0 to low
|
||||
galil_rio.controller.sock.buffer_put = [] # Clear buffer put
|
||||
galil_rio.controller.sock.buffer_recv = [b":"] # Mock response for readback
|
||||
galil_rio.digital_out.ch0.put(0)
|
||||
assert galil_rio.controller.sock.buffer_put == [b"CB0\r"]
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# pylint: skip-file
|
||||
import threading
|
||||
from copy import deepcopy
|
||||
from typing import Generator
|
||||
from unittest import mock
|
||||
|
||||
import numpy as np
|
||||
@@ -10,7 +8,6 @@ import pytest
|
||||
from bec_lib import messages
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_server.device_server.tests.utils import DMMock
|
||||
from ophyd_devices.interfaces.base_classes.psi_device_base import DeviceStoppedError
|
||||
from ophyd_devices.tests.utils import MockPV, patch_dual_pvs
|
||||
|
||||
from csaxs_bec.devices.epics.mcs_card.mcs_card import (
|
||||
@@ -24,7 +21,7 @@ from csaxs_bec.devices.epics.mcs_card.mcs_card import (
|
||||
READMODE,
|
||||
MCSCard,
|
||||
)
|
||||
from csaxs_bec.devices.epics.mcs_card.mcs_card_csaxs import MCSCardCSAXS
|
||||
from csaxs_bec.devices.epics.mcs_card.mcs_card_csaxs import READYTOREAD, MCSCardCSAXS
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@@ -49,237 +46,434 @@ def test_mcs_card(mock_mcs_card):
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mock_mcs_csaxs() -> Generator[MCSCardCSAXS, None, None]:
|
||||
def mock_mcs_csaxs():
|
||||
"""Fixture to mock the MCSCardCSAXS device."""
|
||||
name = "mcs_csaxs"
|
||||
prefix = "X12SA-MCS-CSAXS:"
|
||||
dm = DMMock()
|
||||
try:
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
mcs_card_csaxs = MCSCardCSAXS(name=name, prefix=prefix, device_manager=dm)
|
||||
patch_dual_pvs(mcs_card_csaxs)
|
||||
yield mcs_card_csaxs
|
||||
finally:
|
||||
mcs_card_csaxs.on_destroy()
|
||||
with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
mock_cl.get_pv = MockPV
|
||||
mock_cl.thread_class = threading.Thread
|
||||
mcs_card_csaxs = MCSCardCSAXS(name=name, prefix=prefix, device_manager=dm)
|
||||
patch_dual_pvs(mcs_card_csaxs)
|
||||
yield mcs_card_csaxs
|
||||
|
||||
|
||||
def test_mcs_card_csaxs(mock_mcs_csaxs: MCSCardCSAXS):
|
||||
def test_mcs_card_csaxs(mock_mcs_csaxs):
|
||||
"""Test the MCSCardCSAXS initialization."""
|
||||
assert mock_mcs_csaxs.name == "mcs_csaxs"
|
||||
assert mock_mcs_csaxs.prefix == "X12SA-MCS-CSAXS:"
|
||||
assert mock_mcs_csaxs._acquisition_group == "monitored"
|
||||
assert mock_mcs_csaxs._num_total_triggers == 0
|
||||
assert mock_mcs_csaxs._mcs_clock == 1e7
|
||||
assert mock_mcs_csaxs._pv_timeout == 2.0
|
||||
assert mock_mcs_csaxs._mca_counter_index == 0
|
||||
assert mock_mcs_csaxs._current_data_index == 0
|
||||
assert mock_mcs_csaxs._current_data == {}
|
||||
assert mock_mcs_csaxs.NUM_MCA_CHANNELS == 32
|
||||
assert mock_mcs_csaxs.counter_mapping == {
|
||||
"mcs_csaxs_counters_mca1": "current1",
|
||||
"mcs_csaxs_counters_mca2": "current2",
|
||||
"mcs_csaxs_counters_mca3": "current3",
|
||||
"mcs_csaxs_counters_mca4": "current4",
|
||||
"mcs_csaxs_counters_mca5": "count_time",
|
||||
}
|
||||
assert mock_mcs_csaxs._mcs_clock == 1e7 # 10 MHz
|
||||
|
||||
|
||||
def test_mcs_card_csaxs_on_connected(mock_mcs_csaxs: MCSCardCSAXS):
|
||||
def test_mcs_card_csaxs_on_connected(mock_mcs_csaxs):
|
||||
"""Test the on_connected method of MCSCardCSAXS."""
|
||||
mcs = mock_mcs_csaxs
|
||||
with (
|
||||
mock.patch.object(mcs.counters.mca1, "subscribe") as mock_mca_subscribe,
|
||||
mock.patch.object(mcs, "mcs_recovery") as mock_mcs_recovery,
|
||||
mock.patch.object(mcs._scan_done_thread, "start") as mock_scan_done_thread_start,
|
||||
):
|
||||
mcs.on_connected()
|
||||
# Stop called
|
||||
assert mcs.stop_all.get() == 1
|
||||
# Channel advance settings
|
||||
assert mcs.channel_advance.get() == CHANNELADVANCE.EXTERNAL
|
||||
assert mcs.channel1_source.get() == CHANNEL1SOURCE.EXTERNAL
|
||||
assert mcs.prescale.get() == 1
|
||||
#
|
||||
assert mcs.user_led.get() == 0
|
||||
# Only 5 channels are connected
|
||||
assert mcs.mux_output.get() == 5
|
||||
# input output settings
|
||||
assert mcs.input_mode.get() == INPUTMODE.MODE_3
|
||||
assert mcs.input_polarity.get() == POLARITY.NORMAL
|
||||
assert mcs.output_mode.get() == OUTPUTMODE.MODE_2
|
||||
assert mcs.output_polarity.get() == POLARITY.NORMAL
|
||||
assert mcs.count_on_start.get() == 0
|
||||
assert mcs.read_mode.get() == READMODE.PASSIVE
|
||||
assert mcs.acquire_mode.get() == ACQUIREMODE.MCS
|
||||
|
||||
with mock.patch.object(mcs.counters.mca1, "subscribe") as mock_mca_subscribe:
|
||||
mcs.on_connected()
|
||||
# Stop called
|
||||
assert mcs.stop_all.get() == 1
|
||||
# Channel advance settings
|
||||
assert mcs.channel_advance.get() == CHANNELADVANCE.EXTERNAL
|
||||
assert mcs.channel1_source.get() == CHANNEL1SOURCE.EXTERNAL
|
||||
assert mcs.prescale.get() == 1
|
||||
assert mcs.user_led.get() == 0
|
||||
|
||||
# Mux output
|
||||
assert mcs.mux_output.get() == mcs.NUM_MCA_CHANNELS
|
||||
|
||||
# input output settings
|
||||
assert mcs.input_mode.get() == INPUTMODE.MODE_3
|
||||
assert mcs.input_polarity.get() == POLARITY.NORMAL
|
||||
assert mcs.output_mode.get() == OUTPUTMODE.MODE_2
|
||||
assert mcs.output_polarity.get() == POLARITY.NORMAL
|
||||
assert mcs.count_on_start.get() == 0
|
||||
assert mcs.read_mode.get() == READMODE.PASSIVE
|
||||
assert mcs.acquire_mode.get() == ACQUIREMODE.MCS
|
||||
|
||||
# Check if subscriptions are setup correctly
|
||||
assert mock_mca_subscribe.call_args == mock.call(mcs._on_counter_update, run=False)
|
||||
# Check if recovery is called
|
||||
mock_mcs_recovery.assert_called_once_with(timeout=1)
|
||||
# Check if scan done thread is started
|
||||
mock_scan_done_thread_start.assert_called_once()
|
||||
|
||||
|
||||
def test_mcs_card_csaxs_stage(mock_mcs_csaxs: MCSCardCSAXS):
|
||||
def test_mcs_card_csaxs_stage(mock_mcs_csaxs):
|
||||
"""Test on stage method of MCSCardCSAXS"""
|
||||
mcs = mock_mcs_csaxs
|
||||
triggers = 5
|
||||
num_points = 10
|
||||
mcs.scan_info.msg.scan_parameters["frames_per_trigger"] = triggers
|
||||
mcs.scan_info.msg.num_points = num_points
|
||||
|
||||
# Simulate that the MCS card is still acquiring, and that current channel is !=0
|
||||
mcs.current_channel._read_pv.mock_data = 2 # Simulate that current channel is not zero
|
||||
mcs.erase_all.put(0) # Set erase_all to 0
|
||||
mcs._current_data = {"mca1": [1, 2, 3]} # Simulate existing data
|
||||
mcs._scan_done_callbacks = [lambda: None] # Simulate existing callbacks
|
||||
mcs._start_monitor_async_data_emission.set() # Simulate that monitoring is started
|
||||
mcs._omit_mca_callbacks.set() # Simulate that mca callbacks are omitted
|
||||
|
||||
mcs.erase_all.put(0)
|
||||
mcs.stage()
|
||||
# Check that card is staged
|
||||
assert mcs._staged == ophyd.Staged.yes
|
||||
|
||||
# Check that erase_all, stop_all, preset_real, num_use_all are set correctly
|
||||
assert mcs.erase_all.get() == 1 # Should be set to 1 as current_channel !=0
|
||||
assert mcs.erase_all.get() == 1
|
||||
assert mcs.preset_real.get() == 0
|
||||
assert mcs.num_use_all.get() == triggers
|
||||
|
||||
# Check that internal variables are reset
|
||||
assert mcs._num_total_triggers == triggers * num_points
|
||||
assert mcs._current_data == {}
|
||||
assert mcs._scan_done_callbacks == []
|
||||
assert mcs._current_data_index == 0
|
||||
|
||||
# Check that thread events are cleared properly
|
||||
assert not mcs._start_monitor_async_data_emission.is_set()
|
||||
assert not mcs._omit_mca_callbacks.is_set()
|
||||
|
||||
|
||||
def test_mcs_card_csaxs_unstage(mock_mcs_csaxs):
|
||||
"""Test unstage method of MCSCardCSAXS"""
|
||||
mcs = mock_mcs_csaxs
|
||||
mcs.stop_all.put(0)
|
||||
mcs.erase_all.put(0)
|
||||
mcs.ready_to_read.put(0)
|
||||
mcs.erase_all.put(1)
|
||||
mcs.unstage()
|
||||
assert mcs.stop_all.get() == 1
|
||||
assert mcs.erase_all.get() == 1
|
||||
assert mcs.ready_to_read.get() == READYTOREAD.DONE
|
||||
assert mcs.erase_all.get() == 0
|
||||
|
||||
|
||||
def test_mcs_card_csaxs_complete_and_stop(mock_mcs_csaxs: MCSCardCSAXS):
|
||||
"""
|
||||
Test complete method of MCSCarcCSAXS.
|
||||
|
||||
Two use cases:
|
||||
I. Acquisition is stopped externally
|
||||
II. Acquisition completes normally
|
||||
"""
|
||||
def test_mcs_card_csaxs_complete_and_stop(mock_mcs_csaxs):
|
||||
"""Test complete method of MCSCarcCSAXS"""
|
||||
mcs = mock_mcs_csaxs
|
||||
mcs.acquiring._read_pv.mock_data = ACQUIRING.ACQUIRING
|
||||
# Make sure that device on_connected has been called which starts the monitoring thread
|
||||
mcs.on_connected()
|
||||
|
||||
#######################
|
||||
# I. Use case where acquisition is stopped
|
||||
#######################
|
||||
|
||||
st = mcs.complete()
|
||||
assert st.done is False
|
||||
assert mcs._start_monitor_async_data_emission.is_set()
|
||||
|
||||
# Status should be cancelled by stop
|
||||
mcs.stop_all.put(0)
|
||||
mcs.ready_to_read.put(READYTOREAD.PROCESSING)
|
||||
mcs.stop()
|
||||
with pytest.raises(DeviceStoppedError):
|
||||
with pytest.raises(Exception):
|
||||
st.wait(timeout=3)
|
||||
|
||||
# Callback on status failure should stop monitoring
|
||||
mcs._start_monitor_async_data_emission.wait(2)
|
||||
assert not mcs._start_monitor_async_data_emission.is_set()
|
||||
|
||||
#######################
|
||||
# II. Use case where acquisition completes normally
|
||||
#######################
|
||||
|
||||
mcs._current_data_index = 0
|
||||
mcs.scan_info.msg.num_points = 10
|
||||
mcs.acquiring._read_pv.mock_data = ACQUIRING.ACQUIRING
|
||||
|
||||
st = mcs.complete()
|
||||
assert st.done is False
|
||||
assert mcs._start_monitor_async_data_emission.is_set()
|
||||
|
||||
mcs.acquiring._read_pv.mock_data = ACQUIRING.DONE
|
||||
|
||||
# This should now automatically complete the status
|
||||
mcs._current_data_index = 10
|
||||
st.wait(timeout=3)
|
||||
assert st.done is True
|
||||
assert st.success is True
|
||||
|
||||
# Clean up procedure should stop the async_data monitoring
|
||||
mcs._start_monitor_async_data_emission.wait(2)
|
||||
assert not mcs._start_monitor_async_data_emission.is_set()
|
||||
|
||||
|
||||
def test_mcs_recovery(mock_mcs_csaxs: MCSCardCSAXS):
|
||||
mcs = mock_mcs_csaxs
|
||||
# Simulate ongoing acquisition
|
||||
mcs.erase_all._read_pv.mock_data = 0
|
||||
mcs.stop_all._read_pv.mock_data = 0
|
||||
mcs.erase_start.put(0)
|
||||
mcs.mcs_recovery(timeout=0.1)
|
||||
assert mcs.erase_all.get() == 1
|
||||
assert st.success is False
|
||||
assert mcs.stop_all.get() == 1
|
||||
assert mcs.erase_start.get() == 1
|
||||
assert not mcs._omit_mca_callbacks.is_set()
|
||||
assert mcs.ready_to_read.get() == READYTOREAD.DONE
|
||||
|
||||
|
||||
def test_mcs_card_csaxs_on_counter_updated(mock_mcs_csaxs: MCSCardCSAXS):
|
||||
"""
|
||||
Test the on_counter_update method of MCSCardCSAXS.
|
||||
We will test 2 use cases:
|
||||
I. Suppressed callbacks
|
||||
II. Callback from 32 mca counters, should result in data being sent to BEC
|
||||
"""
|
||||
def test_mcs_card_csaxs_on_counter_updated(mock_mcs_csaxs):
|
||||
mcs = mock_mcs_csaxs
|
||||
|
||||
# I. Suppressed callbacks
|
||||
mcs._omit_mca_callbacks.set()
|
||||
# Called for mca1
|
||||
kwargs = {"obj": mcs.counters.mca1}
|
||||
mcs._on_counter_update(1, **kwargs)
|
||||
assert mcs._current_data == {}
|
||||
assert mcs.mcs.mca1.get() == 1
|
||||
assert mcs.bpm.current1.get() == 1
|
||||
assert mcs.counter_updated == [mcs.counters.mca1.name]
|
||||
# Called for mca2
|
||||
kwargs = {"obj": mcs.counters.mca2}
|
||||
mcs._on_counter_update(np.array([2, 4]), **kwargs)
|
||||
assert mcs.mcs.mca2.get() == [2, 4]
|
||||
assert np.isclose(mcs.bpm.current2.get(), 3)
|
||||
assert mcs.counter_updated == [mcs.counters.mca1.name, mcs.counters.mca2.name]
|
||||
# Called for mca3
|
||||
kwargs = {"obj": mcs.counters.mca3}
|
||||
mcs._on_counter_update(1000, **kwargs)
|
||||
assert mcs.mcs.mca3.get() == 1000
|
||||
assert mcs.bpm.current3.get() == 1000
|
||||
assert mcs.counter_updated == [
|
||||
mcs.counters.mca1.name,
|
||||
mcs.counters.mca2.name,
|
||||
mcs.counters.mca3.name,
|
||||
]
|
||||
# Called for mca4
|
||||
kwargs = {"obj": mcs.counters.mca4}
|
||||
mcs._on_counter_update(np.array([20, 40]), **kwargs)
|
||||
assert mcs.mcs.mca4.get() == [20, 40]
|
||||
assert np.isclose(mcs.bpm.current4.get(), 30)
|
||||
assert mcs.counter_updated == [
|
||||
mcs.counters.mca1.name,
|
||||
mcs.counters.mca2.name,
|
||||
mcs.counters.mca3.name,
|
||||
mcs.counters.mca4.name,
|
||||
]
|
||||
# Called for mca5
|
||||
assert mcs.ready_to_read.get() == 0
|
||||
kwargs = {"obj": mcs.counters.mca5}
|
||||
mcs._on_counter_update(np.array([10000, 10000]), **kwargs)
|
||||
assert np.isclose(mcs.bpm.count_time.get(), 10000 / 1e7)
|
||||
assert mcs.mcs.mca5.get() == [10000, 10000]
|
||||
|
||||
# II. Callback from 32 mca counters
|
||||
mcs._omit_mca_callbacks.clear()
|
||||
mcs._mca_counter_index = 0
|
||||
mcs._current_data_index = 0
|
||||
val = mcs.mca.get()
|
||||
|
||||
for ii in range(mcs.NUM_MCA_CHANNELS):
|
||||
counter = getattr(mcs.counters, f"mca{ii+1}")
|
||||
kwargs = {"obj": counter, "timestamp": 1.0}
|
||||
if ii % 2 == 1:
|
||||
value = np.array([ii, (ii + 1) * 2])
|
||||
else:
|
||||
value = ii
|
||||
mcs._on_counter_update(value, **kwargs)
|
||||
if ii < (mcs.NUM_MCA_CHANNELS - 1):
|
||||
assert mcs._current_data_index == 0
|
||||
assert mcs._mca_counter_index == ii + 1
|
||||
assert counter.attr_name in mcs._current_data
|
||||
assert (
|
||||
mcs._current_data[counter.attr_name]["value"] == value.tolist()
|
||||
if isinstance(value, np.ndarray)
|
||||
else [value]
|
||||
)
|
||||
buffer = deepcopy(mcs._current_data)
|
||||
assert mcs.mca.get() == val # Async mca signal should not change
|
||||
else:
|
||||
# On last counter, data should be sent to BEC, and internal variables reset
|
||||
buffer[counter.attr_name] = {
|
||||
"value": value.tolist() if isinstance(value, np.ndarray) else [value],
|
||||
"timestamp": 1.0,
|
||||
}
|
||||
assert mcs._mca_counter_index == 0
|
||||
assert mcs._current_data_index == 1
|
||||
assert mcs._current_data == {}
|
||||
# @pytest.fixture(scope="function")
|
||||
# def mock_det():
|
||||
# name = "mcs"
|
||||
# prefix = "X12SA-MCS:"
|
||||
# dm = DMMock()
|
||||
# with mock.patch.object(dm, "connector"):
|
||||
# with (
|
||||
# mock.patch(
|
||||
# "ophyd_devices.interfaces.base_classes.bec_device_base.FileWriter"
|
||||
# ) as filemixin,
|
||||
# mock.patch(
|
||||
# "ophyd_devices.interfaces.base_classes.psi_detector_base.PSIDetectorBase._update_service_config"
|
||||
# ) as mock_service_config,
|
||||
# ):
|
||||
# with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
# mock_cl.get_pv = MockPV
|
||||
# mock_cl.thread_class = threading.Thread
|
||||
# with mock.patch.object(MCScSAXS, "_init"):
|
||||
# det = MCScSAXS(name=name, prefix=prefix, device_manager=dm)
|
||||
# patch_dual_pvs(det)
|
||||
# det.TIMEOUT_FOR_SIGNALS = 0.1
|
||||
# yield det
|
||||
|
||||
# Check that the async mca signal is properly set
|
||||
assert isinstance(mcs.mca.get(), messages.DeviceMessage)
|
||||
assert len(mcs.mca.get().signals) == mcs.NUM_MCA_CHANNELS
|
||||
|
||||
# def test_init():
|
||||
# """Test the _init function:"""
|
||||
# name = "eiger"
|
||||
# prefix = "X12SA-ES-EIGER9M:"
|
||||
# dm = DMMock()
|
||||
# with mock.patch.object(dm, "connector"):
|
||||
# with (
|
||||
# mock.patch("ophyd_devices.interfaces.base_classes.bec_device_base.FileWriter"),
|
||||
# mock.patch(
|
||||
# "ophyd_devices.interfaces.base_classes.psi_detector_base.PSIDetectorBase._update_service_config"
|
||||
# ),
|
||||
# ):
|
||||
# with mock.patch.object(ophyd, "cl") as mock_cl:
|
||||
# mock_cl.get_pv = MockPV
|
||||
# with (
|
||||
# mock.patch(
|
||||
# "csaxs_bec.devices.epics.mcs_csaxs.MCSSetup.initialize_detector"
|
||||
# ) as mock_init_det,
|
||||
# mock.patch(
|
||||
# "csaxs_bec.devices.epics.mcs_csaxs.MCSSetup.initialize_detector_backend"
|
||||
# ) as mock_init_backend,
|
||||
# ):
|
||||
# MCScSAXS(name=name, prefix=prefix, device_manager=dm)
|
||||
# mock_init_det.assert_called_once()
|
||||
# mock_init_backend.assert_called_once()
|
||||
|
||||
|
||||
# @pytest.mark.parametrize(
|
||||
# "trigger_source, channel_advance, channel_source1, pv_channels",
|
||||
# [
|
||||
# (
|
||||
# 3,
|
||||
# 1,
|
||||
# 0,
|
||||
# {
|
||||
# "user_led": 0,
|
||||
# "mux_output": 5,
|
||||
# "input_pol": 0,
|
||||
# "output_pol": 1,
|
||||
# "count_on_start": 0,
|
||||
# "stop_all": 1,
|
||||
# },
|
||||
# )
|
||||
# ],
|
||||
# )
|
||||
# def test_initialize_detector(
|
||||
# mock_det, trigger_source, channel_advance, channel_source1, pv_channels
|
||||
# ):
|
||||
# """Test the _init function:
|
||||
|
||||
# This includes testing the functions:
|
||||
# - initialize_detector
|
||||
# - stop_det
|
||||
# - parent.set_trigger
|
||||
# --> Testing the filewriter is done in test_init_filewriter
|
||||
|
||||
# Validation upon setting the correct PVs
|
||||
|
||||
# """
|
||||
# mock_det.custom_prepare.initialize_detector() # call the method you want to test
|
||||
# assert mock_det.channel_advance.get() == channel_advance
|
||||
# assert mock_det.channel1_source.get() == channel_source1
|
||||
# assert mock_det.user_led.get() == pv_channels["user_led"]
|
||||
# assert mock_det.mux_output.get() == pv_channels["mux_output"]
|
||||
# assert mock_det.input_polarity.get() == pv_channels["input_pol"]
|
||||
# assert mock_det.output_polarity.get() == pv_channels["output_pol"]
|
||||
# assert mock_det.count_on_start.get() == pv_channels["count_on_start"]
|
||||
# assert mock_det.input_mode.get() == trigger_source
|
||||
|
||||
|
||||
# def test_trigger(mock_det):
|
||||
# """Test the trigger function:
|
||||
# Validate that trigger calls the custom_prepare.on_trigger() function
|
||||
# """
|
||||
# with mock.patch.object(mock_det.custom_prepare, "on_trigger") as mock_on_trigger:
|
||||
# mock_det.trigger()
|
||||
# mock_on_trigger.assert_called_once()
|
||||
|
||||
|
||||
# @pytest.mark.parametrize(
|
||||
# "value, num_lines, num_points, done", [(100, 5, 500, False), (500, 5, 500, True)]
|
||||
# )
|
||||
# def test_progress_update(mock_det, value, num_lines, num_points, done):
|
||||
# mock_det.num_lines.set(num_lines)
|
||||
# mock_det.scaninfo.num_points = num_points
|
||||
# calls = mock.call(sub_type="progress", value=value, max_value=num_points, done=done)
|
||||
# with mock.patch.object(mock_det, "_run_subs") as mock_run_subs:
|
||||
# mock_det.custom_prepare._progress_update(value=value)
|
||||
# mock_run_subs.assert_called_once()
|
||||
# assert mock_run_subs.call_args == calls
|
||||
|
||||
|
||||
# @pytest.mark.parametrize(
|
||||
# "values, expected_nothing",
|
||||
# [([[100, 120, 140], [200, 220, 240], [300, 320, 340]], False), ([100, 200, 300], True)],
|
||||
# )
|
||||
# def test_on_mca_data(mock_det, values, expected_nothing):
|
||||
# """Test the on_mca_data function:
|
||||
# Validate that on_mca_data calls the custom_prepare.on_mca_data() function
|
||||
# """
|
||||
# with mock.patch.object(mock_det.custom_prepare, "_send_data_to_bec") as mock_send_data:
|
||||
# mock_object = mock.MagicMock()
|
||||
# for ii, name in enumerate(mock_det.custom_prepare.mca_names):
|
||||
# mock_object.attr_name = name
|
||||
# mock_det.custom_prepare._on_mca_data(obj=mock_object, value=values[ii])
|
||||
# if not expected_nothing and ii < (len(values) - 1):
|
||||
# assert mock_det.custom_prepare.mca_data[name] == values[ii]
|
||||
|
||||
# if not expected_nothing:
|
||||
# mock_send_data.assert_called_once()
|
||||
# assert mock_det.custom_prepare.acquisition_done is True
|
||||
|
||||
|
||||
# @pytest.mark.parametrize(
|
||||
# "metadata, mca_data",
|
||||
# [
|
||||
# (
|
||||
# {"scan_id": 123},
|
||||
# {
|
||||
# "mca1": {"value": [100, 120, 140]},
|
||||
# "mca3": {"value": [200, 220, 240]},
|
||||
# "mca4": {"value": [300, 320, 340]},
|
||||
# },
|
||||
# )
|
||||
# ],
|
||||
# )
|
||||
# def test_send_data_to_bec(mock_det, metadata, mca_data):
|
||||
# mock_det.scaninfo.scan_msg = mock.MagicMock()
|
||||
# mock_det.scaninfo.scan_msg.metadata = metadata
|
||||
# mock_det.scaninfo.scan_id = metadata["scan_id"]
|
||||
# mock_det.custom_prepare.mca_data = mca_data
|
||||
# mock_det.custom_prepare._send_data_to_bec()
|
||||
# device_metadata = mock_det.scaninfo.scan_msg.metadata
|
||||
# metadata.update({"async_update": "append", "num_lines": mock_det.num_lines.get()})
|
||||
# data = messages.DeviceMessage(signals=dict(mca_data), metadata=device_metadata)
|
||||
# calls = mock.call(
|
||||
# topic=MessageEndpoints.device_async_readback(
|
||||
# scan_id=metadata["scan_id"], device=mock_det.name
|
||||
# ),
|
||||
# msg={"data": data},
|
||||
# expire=1800,
|
||||
# )
|
||||
|
||||
# assert mock_det.connector.xadd.call_args == calls
|
||||
|
||||
|
||||
# @pytest.mark.parametrize(
|
||||
# "scaninfo, triggersource, stopped, expected_exception",
|
||||
# [
|
||||
# (
|
||||
# {"num_points": 500, "frames_per_trigger": 1, "scan_type": "step"},
|
||||
# TriggerSource.MODE3,
|
||||
# False,
|
||||
# False,
|
||||
# ),
|
||||
# (
|
||||
# {"num_points": 500, "frames_per_trigger": 1, "scan_type": "fly"},
|
||||
# TriggerSource.MODE3,
|
||||
# False,
|
||||
# False,
|
||||
# ),
|
||||
# (
|
||||
# {"num_points": 5001, "frames_per_trigger": 2, "scan_type": "step"},
|
||||
# TriggerSource.MODE3,
|
||||
# False,
|
||||
# True,
|
||||
# ),
|
||||
# (
|
||||
# {"num_points": 500, "frames_per_trigger": 2, "scan_type": "random"},
|
||||
# TriggerSource.MODE3,
|
||||
# False,
|
||||
# True,
|
||||
# ),
|
||||
# ],
|
||||
# )
|
||||
# def test_stage(mock_det, scaninfo, triggersource, stopped, expected_exception):
|
||||
# mock_det.scaninfo.num_points = scaninfo["num_points"]
|
||||
# mock_det.scaninfo.frames_per_trigger = scaninfo["frames_per_trigger"]
|
||||
# mock_det.scaninfo.scan_type = scaninfo["scan_type"]
|
||||
# mock_det.stopped = stopped
|
||||
# with mock.patch.object(mock_det.custom_prepare, "prepare_detector_backend") as mock_prep_fw:
|
||||
# if expected_exception:
|
||||
# with pytest.raises(MCSError):
|
||||
# mock_det.stage()
|
||||
# mock_prep_fw.assert_called_once()
|
||||
# else:
|
||||
# mock_det.stage()
|
||||
# mock_prep_fw.assert_called_once()
|
||||
# # Check set_trigger
|
||||
# mock_det.input_mode.get() == triggersource
|
||||
# if scaninfo["scan_type"] == "step":
|
||||
# assert mock_det.num_use_all.get() == int(scaninfo["frames_per_trigger"]) * int(
|
||||
# scaninfo["num_points"]
|
||||
# )
|
||||
# elif scaninfo["scan_type"] == "fly":
|
||||
# assert mock_det.num_use_all.get() == int(scaninfo["num_points"])
|
||||
# mock_det.preset_real.get() == 0
|
||||
|
||||
# # # CHeck custom_prepare.arm_acquisition
|
||||
# # assert mock_det.custom_prepare.counter == 0
|
||||
# # assert mock_det.erase_start.get() == 1
|
||||
# # mock_prep_fw.assert_called_once()
|
||||
# # # Check _prep_det
|
||||
# # assert mock_det.cam.num_images.get() == int(
|
||||
# # scaninfo["num_points"] * scaninfo["frames_per_trigger"]
|
||||
# # )
|
||||
# # assert mock_det.cam.num_frames.get() == 1
|
||||
|
||||
# # mock_publish_file_location.assert_called_with(done=False)
|
||||
# # assert mock_det.cam.acquire.get() == 1
|
||||
|
||||
|
||||
# def test_prepare_detector_backend(mock_det):
|
||||
# mock_det.custom_prepare.prepare_detector_backend()
|
||||
# assert mock_det.erase_all.get() == 1
|
||||
# assert mock_det.read_mode.get() == ReadoutMode.EVENT
|
||||
|
||||
|
||||
# def test_complete(mock_det):
|
||||
# with (mock.patch.object(mock_det.custom_prepare, "finished") as mock_finished,):
|
||||
# mock_det.complete()
|
||||
# assert mock_finished.call_count == 1
|
||||
|
||||
|
||||
# def test_stop_detector_backend(mock_det):
|
||||
# mock_det.custom_prepare.stop_detector_backend()
|
||||
# assert mock_det.custom_prepare.acquisition_done is True
|
||||
|
||||
|
||||
# def test_stop(mock_det):
|
||||
# with (
|
||||
# mock.patch.object(mock_det.custom_prepare, "stop_detector") as mock_stop_det,
|
||||
# mock.patch.object(
|
||||
# mock_det.custom_prepare, "stop_detector_backend"
|
||||
# ) as mock_stop_detector_backend,
|
||||
# ):
|
||||
# mock_det.stop()
|
||||
# mock_stop_det.assert_called_once()
|
||||
# mock_stop_detector_backend.assert_called_once()
|
||||
# assert mock_det.stopped is True
|
||||
|
||||
|
||||
# @pytest.mark.parametrize(
|
||||
# "stopped, acquisition_done, acquiring_state, expected_exception",
|
||||
# [
|
||||
# (False, True, 0, False),
|
||||
# (False, False, 0, True),
|
||||
# (False, True, 1, True),
|
||||
# (True, True, 0, True),
|
||||
# ],
|
||||
# )
|
||||
# def test_finished(mock_det, stopped, acquisition_done, acquiring_state, expected_exception):
|
||||
# mock_det.custom_prepare.acquisition_done = acquisition_done
|
||||
# mock_det.acquiring._read_pv.mock_data = acquiring_state
|
||||
# mock_det.scaninfo.num_points = 500
|
||||
# mock_det.num_lines.put(500)
|
||||
# mock_det.current_channel._read_pv.mock_data = 1
|
||||
# mock_det.stopped = stopped
|
||||
|
||||
# if expected_exception:
|
||||
# with pytest.raises(MCSTimeoutError):
|
||||
# mock_det.timeout = 0.1
|
||||
# mock_det.custom_prepare.finished()
|
||||
# else:
|
||||
# mock_det.custom_prepare.finished()
|
||||
# if stopped:
|
||||
# assert mock_det.stopped is stopped
|
||||
|
||||
Reference in New Issue
Block a user