docs: add readme for timepix

This commit is contained in:
2026-05-29 16:43:34 +02:00
parent 824cbcdb13
commit 110bdf1d83
+141
View File
@@ -0,0 +1,141 @@
# Timepix detector integration
This module integrates the ASI Serval `Timepix` detector into the SuperXAS BEC device layer. It combines EPICS/areaDetector controls for camera and image handling with the TimePixFly backend (`TimepixFlyBackend`) for raw-data processing and XES histogram generation.
## Main classes
The main integration class is `Timepix` in `timepix.py`. It combines the EPICS/areaDetector camera interface with BEC lifecycle hooks and BEC-facing signals.
The TimePixFly support lives in `timepix_fly_client/`:
- `timepix_fly_backend.py` defines `TimepixFlyBackend`, the lifecycle adapter used by `Timepix`.
- `timepix_fly_client.py` contains the REST/WebSocket client.
- `timepix_fly_interface.py` contains the Pydantic models for TimePixFly configuration and raw-data messages.
### Device configuration
This is an example device configuration for the `Timepix` device.
```yaml
timepix:
readoutPriority: async
description: ASI Serval Timepix Detector
deviceClass: superxas_bec.devices.timepix.timepix.Timepix
deviceConfig:
prefix: "X10DA-ES-TPX1:"
backend_rest_url: "P6-0008.psi.ch:8452"
hostname: "x10da-bec-001.psi.ch"
enable_xes: false
onFailure: retry
enabled: true
readOnly: false
softwareTrigger: true
```
The `deviceConfig` parameter `enable_xes` controls whether TimePixFly XES processing is active when the device is loaded. It can also be changed at runtime with `set_enable_xes(True)` or `set_enable_xes(False)`.
If HDF5 file writing is not possible, for example because the IOC cannot access the target mount, it can be disabled from the CLI with `dev.timepix.hdf.enable.put(0)`.
Currently, the `Timepix` device has the following BEC signals:
- `xes_data`: full XES data with time ROI bins and energy points.
- `xes_spectra`: XES data integrated over energy points.
- `xes_energy_1` and `xes_energy_2`: two grouped energy windows used when the
pixel map contains eight energy points. They represent the sum of energy points `0:4` and `4:8`, respectively.
- `tds_period`, `total_periods`, and `total_events`: timing and event counters
reported by TimePixFly.
- `preview`: 2D camera preview from the image plugin.
- `static_spectra`: 1D spectrum derived from received image arrays.
Users can set the pixel map through the `set_pixel_map()` method or the `set_pixel_map_from_json_file()` method, which accepts a file path to a JSON file matching the `PixelMap` schema defined in `timepix_fly_interface.py`. A default pixel map is created one energy point per detector module. The `set_enable_xes(True)` enables users to de/activate the XES data easily from the CLI.
## Scan lifecycle
### Initialization and connection
`on_init()` builds the default pixel map. No EPICS signals are connected yet, so detector PV defaults are not written here. In
`wait_for_connection()` we wait for all EPICS signals to connect and the connect the TimePixFly backend, and setup the raw-data callback in Redis.
Once connected, we set default values on signals in `on_connected()`:
- enables or disables TDC/raw-stream settings according to `enable_xes`;
- sets internal trigger mode, `HDMI1_1` trigger source, and timed exposure mode;
- resets the camera array counter;
- sets image mode to multiple and prepares the HDF5 plugin for stream writing;
- registers `msg_buffer_callback()` with the TimePixFly backend; This callback parses the XES data from Redis and updates the BEC-facing XES signals.
- starts preview polling and subscribes to image updates for `static_spectra`.
### Stage
`on_stage()` translates the scan parameters into detector settings:
- `exp_time` becomes the camera acquire period.
- `frames_per_trigger` becomes the camera burst size.
- `num_points * frames_per_trigger` becomes the expected HDF5 image count.
The camera acquire time is set to `exp_time - MIN_DETECTOR_READOUT_TIME`. Staging fails if the exposure is not longer than the detector readout time.
Please note that if `burst_at_each_point` is > 1, we run multiple full acquisitions for each trigger. This is needed in the current TimePixFly and ASI Serval Timepix integration for some reason. If this changes, this can be adapted.
If XES is enabled, `on_stage()` also configures TimePixFly:
- `TRoiStep` and `TRoiN` come from `troistep` and `troin`.
- `output_uri` is a Redis URI containing the BEC Redis host, port, raw-data topic, and scan id. Scan ID is stored in the metadata for each message, but currently not further used. One could safeguard against misconfigurations by checking the scan_id in the msg callback.
- `save_interval` is set for approximately 5 Hz XES updates.
### Pre-scan
During `on_pre_scan()`, we make sure that the camera is idle before starting the acquisition.
### Trigger
For every trigger, we software trigger the acquisition of the `Timepix` detector with a full cycle:
```text
DONE -> ACQUIRING -> DONE
```
Otherwise, the currently assumptions between TimePixFly and ASI Serval Timepix integration would be violated.
#### State management during a single trigger call
The `TimePixFly` backend undergoes a full lifecycle for each trigger. Therefore, we must properly manage and keep track of its transitions. The initial state is `config`, in which the backend must be at the beginning of each trigger. We then move to `await_connection` while the camera is still IDLE. Then, the camera needs to be started with `cam.acquire.put(1)`. The camera will connect to the `TimePixFly` backend on the raw-data socket, which initiates a state transition to `collect`. Once the acquisition is done, and the camera closes the socket, the backend transitions back to `config` after sending out the last `EndFrame` message. During `collect`, the backend sends out XES data messages to Redis at the specified `save_interval`. The backend status resolves when it transitions back to `config` after the acquisition is done. In the meantime, we track the transition of the detector control interface to cycle through
```text
DONE -> ACQUIRING -> DONE
```
This describes a full trigger cycle. Therefore, efficient data collection should rather use `frames_per_trigger > 1` instead of multiple software trigger cycle via `burst_at_each_point > 1`. This reduces the overhead due to the logic described above.
Please note, if at any point the state of the `TimePixFly` backend transitions to `error`, we will automatically raise an exception with the error message and stop the scan. The error will be forwarded to BEC.
### Complete
As this is called once the full scan is done, `on_complete()` waits for the camera to become `DONE`. If HDF5 is enabled, it also waits for capture to stop, file writing to finish, the write status to remain successful, and the expected image count to be captured. If XES is enabled, it waits for TimePixFly to be back in `config`. We also check that there is no error state in the writer, which can for example happen if there are file permission issues for the writer path.
### Stop and destroy
`on_stop()` stops camera acquisition, stops HDF5 capture, and asks the backend to stop any running collection when XES is enabled.
`on_destroy()` stops acquisition and capture, stops preview polling, and destroys backend resources.
## XES data callbacks
The idea behind the integration is that the `TimePixFlyBackend` class allows high-level classes to register callbacks to received the `StartFrame`, `XesData`, and `EndFrame` messages from `TimePixFly`. They can then parse the data from these messages as they see fit. The mechanism works through registering a callback function to the backend, which gets called with all the relevant data once an `EndFrame` message is received. This allows the backend to buffer all the incoming messages for one acquisition and then call the callback once all data is available.
In the current implementation, the `msg_buffer_callback()` builds the BEC-facing XES arrays:
1. It reads `NumEnergyPoints`, `TRoiN`, `save_interval`, and the collected `XesData` frames.
2. It sums `TDSpectra` values into a `(TRoiN, NumEnergyPoints)` array.
3. It publishes `tds_period`, `total_periods`, `total_events`, `xes_data`, and `xes_spectra`.
4. If there are eight energy points, it publishes two grouped spectra: energy points `0:4` and `4:8`.
If the signals change, they have to be adapted in this callback method. We currently have for example that if there are eight energy points, we publish two grouped spectra. The grouping here is the left and right half of the detector. This was written for the default pixel map, but it will obviously not true for an arbitrary pixel map with eight energy points.
### Image data callbacks
Two image-derived signals are updated from the areaDetector image plugin:
- The preview polling thread watches `image.unique_id`, reads `image.array_data`, reshapes it using the plugin array size, and publishes the 2D `preview`.
- The `image.unique_id` subscription computes a 1D `static_spectra` signal by summing the reshaped image along one axis.
## Operational notes
- `MIN_DETECTOR_READOUT_TIME` is `2.1e-3` seconds. Scan exposure times must be larger than this value.
- `enable_xes` controls the TDC/raw-stream settings and whether the TimePixFly backend participates in stage, trigger, complete, and stop.
- Image writing is independent of XES processing and is controlled through the HDF5 plugin enable signal or `set_enable_image_writing()`.
- The current default Redis topic for TimePixFly raw data is `public/timepix_fly_backend/raw_data`.
- The backend REST URL and BEC host name must be reachable from both the BEC server and the TimePixFly service.