Files
John Henry BealeandClaude Opus 5.5 2d9bbdac9d replace runtime print statements with logging
- 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>
2026-10-16 11:56:49 +02:00

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")