- all print calls outside the __main__ test blocks are now log calls:
info for run information (CTA configuration summary on one line, run
number, CTA start pid), warning/error for aborts and failures, debug for
dumps (object/transform info, autofocus measurements, logger tree)
- psi_device: remove the import-time logging.basicConfig(level=DEBUG) and
print('past logging'); the standalone test configures logging itself
- startup import timings are logged at info level
- ctadaq added to the -l log level list
Prints from PBTools/PBSwissMX, slic and the JFJ client are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
193 lines
6.2 KiB
Python
193 lines
6.2 KiB
Python
#!/usr/bin/env python
|
|
# *-----------------------------------------------------------------------*
|
|
# | |
|
|
# | Copyright (c) 2024 by Paul Scherrer Institute (http://www.psi.ch) |
|
|
# | |
|
|
# | Author xxx (xxx.xxx@psi.ch) |
|
|
# *-----------------------------------------------------------------------*
|
|
'''
|
|
slic interface class for CTAAcquisition
|
|
based on xxx code snipplets
|
|
'''
|
|
|
|
import logging
|
|
import numpy as np
|
|
from tqdm import tqdm
|
|
from time import time
|
|
from time import sleep
|
|
import sys, os, threading
|
|
|
|
sys.path.insert(0, os.path.expanduser('/sf/cristallina/applications/slic/slic-package'))
|
|
|
|
from slic.core.acquisition import SFAcquisition
|
|
from slic.core.acquisition.sfacquisition import BSChannels, transpose_dicts, print_response
|
|
from slic.core.task import DAQTask
|
|
|
|
_log=logging.getLogger(__name__)
|
|
|
|
|
|
class CTAAcquisition(SFAcquisition):
|
|
|
|
def __init__(self, cta, *args, n_block_size=None, **kwargs):
|
|
self.cta = cta
|
|
self.n_block_size = n_block_size
|
|
self.wait_time=60.0
|
|
self._abort_evt=threading.Event()
|
|
self._abort_pid=None
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def abort(self, stop_pid=None):
|
|
'''stop the CTA sequence and end the data retrieval at stop_pid (None: retrieve nothing more)'''
|
|
self._abort_pid=stop_pid
|
|
self._abort_evt.set()
|
|
self.cta.stop()
|
|
|
|
def acquire(self, filename, data_base_dir=None, detectors=None, channels=None, pvs=None, scan_info=None, n_block_size=None, n_pulses=100, n_repeat=1, is_scan_step=False, wait=True, **kwargs):
|
|
|
|
if n_repeat != 1:
|
|
|
|
raise NotImplementedError("Repetitions are not implemented") #TODO
|
|
|
|
if not is_scan_step:
|
|
|
|
run_number = self.client.next_run()
|
|
_log.info(f"Advanced run number to {run_number}.")
|
|
|
|
else:
|
|
|
|
run_number = self.client.run_number
|
|
_log.info(f"Continuing run number {run_number}.")
|
|
# if not filename or filename == "/dev/null":
|
|
# print("Skipping retrieval since no filename was given.")
|
|
# return
|
|
|
|
if detectors is None:
|
|
|
|
_log.debug("No detectors specified, using default detector list.")
|
|
detectors = self.default_detectors
|
|
|
|
if pvs is None:
|
|
|
|
_log.debug("No PVs specified, using default PV list.")
|
|
pvs = self.default_pvs
|
|
|
|
if channels is None:
|
|
|
|
_log.debug("No channels specified, using default channel list.")
|
|
channels = self.default_channels
|
|
n_block_size = n_block_size or self.n_block_size
|
|
#print("block size:", n_block_size)
|
|
|
|
bschs = BSChannels(*channels)
|
|
bschs.check()
|
|
client = self.client
|
|
#print(kwargs)
|
|
client.set_config(n_pulses, filename, detectors=detectors, channels=channels, pvs=pvs, scan_info=scan_info, **kwargs)
|
|
#print('set client')
|
|
self._abort_evt.clear()
|
|
self._abort_pid=None
|
|
abort_evt=self._abort_evt
|
|
|
|
def _acquire():
|
|
|
|
filenames=None
|
|
try:
|
|
if abort_evt.is_set():
|
|
return filenames
|
|
self.cta.stop()
|
|
self.cta.start()
|
|
if abort_evt.is_set(): # abort arrived while starting: make sure the sequence is stopped
|
|
self.cta.stop()
|
|
abort_evt.wait(10) ###? (returns early on abort)
|
|
time_start = time()
|
|
start_pid = self.cta.get_start_pid()
|
|
_log.info(f"CTA start pid: {start_pid}")
|
|
stop_pid = start_pid + n_pulses
|
|
#print("CTA stop pid:", stop_pid)
|
|
pids = np.arange(start_pid, stop_pid)
|
|
pids_blocks = split(pids, n_block_size)
|
|
#print(pids_blocks)
|
|
res = {}
|
|
for pb in tqdm(pids_blocks):
|
|
#print(pb)
|
|
if filename:
|
|
aborted = abort_evt.wait(len(pb) / 100 + 0.2) # wait for the pulses of this block, returns early on abort
|
|
if aborted:
|
|
# the retrieval can lag behind the pulses: continue with the following blocks, each cut at the
|
|
# abort pulse id, until a block is empty
|
|
stop_pid = self._abort_pid
|
|
pb = pb[pb <= stop_pid] if stop_pid is not None else pb[:0]
|
|
_log.warning(f"CTA acquisition aborted at pulse id {stop_pid}: retrieve {len(pb)} pulses of this block")
|
|
if len(pb) == 0:
|
|
break
|
|
sleep(0.2)
|
|
res = self.retrieve(filename, pb, run_number=run_number, scan_info=scan_info, **kwargs) # error here
|
|
res = transpose_dicts(res)
|
|
filenames = res.pop("filenames")
|
|
#print(filenames)
|
|
|
|
else:
|
|
res = {}
|
|
#print(res)
|
|
print_response(res)
|
|
# possibly optional, because cta should be done!
|
|
|
|
while self.cta.is_running() and not abort_evt.is_set():
|
|
abort_evt.wait(self.wait_time)
|
|
delta_time = time() - time_start
|
|
_log.info(f"Waiting since {delta_time:.0f} seconds for CTA sequence to finish")
|
|
|
|
return filenames
|
|
|
|
except Exception as e:
|
|
_log.error(f"CTA acquisition: {e}")
|
|
|
|
def stopper():
|
|
client.stop()
|
|
self.abort()
|
|
|
|
task = DAQTask(_acquire, stopper=stopper, filename=filename, hold=False)
|
|
self.current_task = task
|
|
if wait:
|
|
try:
|
|
task.wait()
|
|
except KeyboardInterrupt:
|
|
_log.warning("Stopped current DAQ task")
|
|
|
|
return task
|
|
|
|
def split(a, block_size):
|
|
if block_size is None:
|
|
return [a]
|
|
|
|
length = len(a)
|
|
if isinstance(block_size, (list, tuple, np.ndarray)):
|
|
if sum(block_size) != length:
|
|
raise ValueError(
|
|
f"Sum of block sizes {sum(block_size)} does not match PID length {length}."
|
|
)
|
|
|
|
blocks = []
|
|
start = 0
|
|
for size in block_size:
|
|
if size <= 0:
|
|
raise ValueError(f"Block size values must be positive, got {size}")
|
|
blocks.append(a[start:start + size])
|
|
start += size
|
|
return blocks
|
|
|
|
indices = np.arange(block_size, length, block_size) # must not start at 0, otherwise the first entry is an empty array
|
|
|
|
return np.array_split(a, indices)
|
|
|
|
if __name__ == "__main__":
|
|
from slic.devices.timing.events import CTASequencer
|
|
|
|
cta = CTASequencer("SAT-CCTA-ESC")
|
|
daq = CTAAcquisition(cta, "cristallina", "p19150", default_channels=["SAR-CVME-TIFALL6:EvtSet"], append_user_tag_to_data_dir=True)
|
|
|
|
cta.cfg.repetitions = n_pulses # etc. etc.
|
|
daq.acquire("test")
|
|
|
|
|