Streaming and pre-processing time-resolved TimePix3 detector data
This package provides a pipeline for streaming, pre-processing, and visualizing data from Amsterdam Scientific Instruments (ASI) TimePix3 detectors. It implements time-resolved spectroscopy binning with TDC (Time-to-Digital Converter) triggering and supports both real-time visualization and ZMQ publishing for downstream analysis.
It also ships a PySide6 acquisition UI (tpx-ui) that drives the full pipeline — beam alignment, acquisition control, live heatmaps/spectra, clustering parameter sweeps, unified logs, and a session timeline (see Acquisition UI).
- Python 3.9+
- Linux/Ubuntu recommended (input handling uses
select.select()which is Unix-specific) - TimePix3 detector with ASI live-cli software
Clone the repository and install dependencies:
git clone https://github.com/als-computing/splash_timepix.git
cd splash_timepix
python3 -m venv .venv
source .venv/bin/activate
pip install -e .[dev]To use the PySide6 acquisition UI, also install the optional ui extra:
pip install -e ".[ui]"On any Linux machine that will receive high-rate detector UDP traffic (for example the Serval or acquisition PC), configure kernel UDP socket buffer limits before your first live acquisition. Serval expects a large receive buffer; the default kernel ceiling can silently cap it and increase the risk of packet loss under burst traffic.
Follow the step-by-step sysctl / sysctl.d instructions in UDP receive buffers (first-time Linux setup).
On a fresh Linux/GNOME machine the UI launches fine via tpx-ui, but the
window will appear in the dock with a generic gear icon and a main.py
tooltip until the desktop integration is installed. The repository ships a
script that creates the icon, an installed .desktop launcher, and a
clickable shortcut on the Desktop, all derived from the current clone path:
./scripts/install_desktop_integration.shRe-running is safe (every output is overwritten). To remove:
./scripts/install_desktop_integration.sh --uninstallWhat it sets up:
| Path | Purpose |
|---|---|
~/.local/share/icons/hicolor/256x256/apps/splash_timepix.png |
Themed icon for the dock and Activities. |
~/.local/share/applications/splash_timepix.desktop |
Launcher GNOME indexes; StartupWMClass=splash_timepix pairs the running window to this entry. |
~/Desktop/splash_timepix.desktop |
Clickable shortcut on the user's Desktop. |
After running it once, log out and back in (or restart the dock extension) so GNOME picks up the new launcher. On Wayland-GNOME the dock will then show the bundled icon instead of the gear.
numpy- Array operationsh5py- HDF5 I/O (centroider outputs)opencv-python- Real-time visualizationpyzmq- ZMQ publishingmsgpack- Serialization for ZMQtyper- CLI interfacepsutil- System monitoringrequests- HTTP client (Serval status)pydantic- Message schema validation
UI extra (pip install -e ".[ui]"): PySide6, pyqtgraph.
- All tool and code style configurations are in
pyproject.toml - Pre-commit hooks are configured in
.pre-commit-config.yaml - To enable pre-commit hooks (recommended):
pre-commit installAdditional markdown guides in the repository:
- Socket server — threading, ring buffer, and parser callback behavior
- Sample start message — example ZMQ start message (wire format and fields)
- UDP receive buffers (first-time Linux setup) — raise
net.core.rmem_*for Serval/streaming hosts (sysctl.ddrop-in) - Testing guide — manual and automated testing (simulator, ZMQ subscribers, ArroyoXPS integration).
- Centroider tutorial — scientist's guide to the Centroider tab (clustering parameter sweeps on
.tpx3files).
The system consists of five main components:
Multi-threaded TCP server that receives 12-byte TimePix3 packets and parses them with NumPy (parser):
- Thread 1: Socket listener (reads TCP chunks, batches complete packets into raw byte buffers)
- Thread 2: Vectorized parser (
parse_batch) → callback receives aBatchParseResult(arrays per packet type)
See info/SOCKET_SERVER_README.md for details.
Main application implementing time-resolved binning:
- Receives packets via callback from socket server
- Bins pixel events into 3D arrays (x, y, time) based on TDC triggers
- Flushes accumulated 3D arrays to processing queue
- Provides statistics display and user commands
- Publishes a heartbeat with queue depths and server state on a separate ZMQ port (default 5658) so the UI can monitor pipeline health
- Supports an
--alignmentmode: TDCs are ignored and wall-clock-gated 2D X/Y histograms are emitted at 1–30 Hz (used by the UI's Alignment tab)
Two alternative workers for consuming 3D arrays:
Plotting Worker (--plot flag):
- Real-time OpenCV visualization
- Displays 2D heatmap (x vs time, summed over y)
- Shows statistics overlay
- Interactive (press 'q' to close)
ZMQ Worker (default):
- Publishes arrays via ZMQ PUB socket
- Uses msgpack serialization
- Multi-part messages (metadata + array bytes)
- Supports multiple subscribers
- Publishes start/stop control messages for acquisition lifecycle tracking
Simulated TimePix3 data source for testing:
- Generates realistic packet streams
- Configurable pixel count rate and TDC frequency
- Interactive CLI for control
PySide6 desktop application (src/splash_timepix/ui/) that orchestrates Serval, the streaming server, and live-cli, and visualizes the ZMQ stream. Tabs:
- Alignment (default) — live 2D X/Y heatmap at 1–30 Hz for beam alignment, with grayscale LUT, binarize/log/integrate display modes, target overlay, and a rolling cps strip chart. Includes a local simulator mode that needs no hardware.
- Operator — acquisition control (Start / Preview / Simulator / Replay), live current-flush and running-average heatmaps, ROI cursors with energy-calibrated spectra, pipeline queue monitoring, and saving of averaged data (PNG, CSV, energy/time axes, JSON metadata) under a shared per-scan slug.
- Logs — unified system/server/Serval log viewer; all logs are also written to
<repo>/logs/for post-mortem debugging. - Timeline — chronological overview of the session's runs and log events in one place.
- Centroider — clustering parameter sweeps (eps-s × eps-t) on
.tpx3files via the bundledtpx3dump, with side-by-side x-histogram comparison. See the Centroider tutorial.
Launch with:
tpx-uiWidget preferences (acquisition settings, alignment display options) persist across sessions. Only one UI instance can run at a time (a lock with a takeover prompt guards against double-starts).
The package installs three console entry points (see pyproject.toml):
| Command | Equivalent | Purpose |
|---|---|---|
tpx-stream |
python -m splash_timepix.app |
Streaming server |
tpx-ui |
python -m splash_timepix.ui.main |
Acquisition UI |
tpx-sim |
python -m splash_timepix.simulator_cli |
Simulated data source |
Terminal 1 - Start server:
tpx-stream
# OR via
python -m splash_timepix.appTerminal 2 - Subscribe to published data:
# Use (or modify) the example
python -m splash_timepix.example_zmq_sub
# OR connect with your applicationTerminal 3 - Start detector:
./ASI/live-cliTerminal 1 - Start server with visualization:
tpx-stream --plotTerminal 2 - Start data source:
# Using real detector
./ASI/live-cli
# OR replaying from file
./ASI/live-cli --source-files path/to/recording.tpx3
# OR using the simulator
python -m splash_timepix.simulator_cliTerminal 1 - Start server with verbose output
tpx-stream --verboseTerminal 2
# (I) Low Count Rates (Simulator)
python -m splash_timepix.simulator_cli
cps 3
tdc 1
start 60
# OR (II) High Count Rates [<100kcps] (Simulator)
python -m splash_timepix.simulator_cli
cps 100000
tdc 0.1
start 60
# OR (III) Using Replay From File (live-cli)
./ASI/live-cli --source-files path/to/recording.tpx3python -m splash_timepix.app [OPTIONS]Flags:
--plot: Enable real-time plotting (default: ZMQ publishing)--verbose: Show detailed logs and packet samples (default: warnings only)
Server Options:
--host STR: Host for server to bind to (default: "localhost")--port INT: Socket server port matching live-cli client port (default: 9090)--buffer-size INT: Internal message queue size (default: 1000)--callback-batch-size INT: Number of parsed packets to batch per callback (default: 10000)--zmq-port INT: ZMQ publishing port (default: 5657)--heartbeat-port INT: ZMQ heartbeat/status port (default: 5658)--exit-on-disconnect: Stop the server when the client disconnects (default: keep running)
Time-Resolved Binning Options:
--tdc-ch INT: TDC channel to use - 0=both, 1=ch1, 2=ch2 (default: 0)--tdc-edge STR: TDC edge to trigger on - "rising" or "falling" (default: rising)--tdc-frequency FLOAT: Expected TDC trigger frequency in Hz (default: 100.0)--t-delta-ns FLOAT: Time bin width in nanoseconds (default: -1 = auto; bin width is derived from--n-bins)--n-bins INT: Number of time bins per TDC cycle, used when--t-delta-nsis not given (default: 350)--flush-interval FLOAT: Time between 3D x, y, t array flushes in seconds (default: 1.0)--collapse-y: Sum over the Y axis and emit 2D (x, t) arrays instead of 3D
Alignment Mode:
--alignment: Ignore TDCs and emit wall-clock-gated 2D X/Y histograms (used by the UI's Alignment tab)--alignment-rate-hz FLOAT: Flush rate for alignment mode, 1–30 Hz (default: 30.0)
Display Options:
--stats-update-time INT: Stats refresh interval in seconds (default: 1)
After starting the test source, use these commands:
cps <value>- Set pixel count rate (events/second)tdc <value>- Set TDC frequency (Hz)count <y/n>- Enable/disable packet counting statisticsbatch <seconds>- Wire-level burst batching interval (0 disables; sends per-packet)start <duration>- Start streaming for duration (seconds)stop- Stop streaming dataquit- Exit
The system publishes three types of messages via ZMQ. For a concrete start payload example, see info/SAMPLE_START_MESSAGE.md.
Published when data acquisition begins (first data arrives):
{
'msg_type': 'start',
'scan_name': 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f23456789', # full UUID4
'tdc_frequency_hz': 10.0,
'detector_size_x': 256,
'detector_size_y': 256,
'n_bins': 350,
't_delta_ns': 285714.29,
'mode': 'timing', # 'timing' (default) or 'alignment'
# ... other configuration parameters
}Published for each data flush:
Part 1: Metadata (msgpack)
{
'msg_type': 'event',
'shape': (256, 256, 350), # Array dimensions
'dtype': 'uint32', # Numpy dtype
'timestamp': 1699999999.123, # Unix timestamp
'flush_number': 1, # Sequential flush number
'cycles_in_flush': 10, # TDC cycles in this flush
'total_cycles': 10, # Cumulative cycle count
# ... other metadata
}Part 2: Array Data (raw bytes)
- Raw numpy array bytes
- Reconstruct with
np.frombuffer(bytes, dtype).reshape(shape)
Published when data acquisition ends (client disconnects or server shuts down):
{
'msg_type': 'stop',
'scan_name': 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f23456789', # same UUID as start
'total_flushes': 9,
'total_cycles': 99,
'total_packets': 50000,
'acquisition_duration_s': 28.91,
# ... statistics
}See also example_zmq_sub.py for a complete example that handles all message types.
import zmq
import msgpack
import numpy as np
context = zmq.Context()
socket = context.socket(zmq.SUB)
socket.connect("tcp://localhost:5657")
socket.setsockopt(zmq.SUBSCRIBE, b"")
while True:
# Receive first part (metadata)
metadata_bytes = socket.recv()
metadata = msgpack.unpackb(metadata_bytes)
msg_type = metadata.get('msg_type')
if msg_type == 'start':
print(f"Acquisition started: {metadata['scan_name']}")
elif msg_type == 'stop':
print(f"Acquisition stopped: {metadata['scan_name']}")
elif msg_type == 'event' or msg_type is None:
# Event message - receive array data
array_bytes = socket.recv()
array = np.frombuffer(array_bytes, dtype=metadata['dtype']).reshape(metadata['shape'])
print(f"Received flush #{metadata.get('flush_number')}: {array.shape}")For a more structured approach, use SplashTimePixZMQListener (similar to ArroyoXPS):
See also example_listener.py
from splash_timepix.listener import SplashTimePixZMQListener
from splash_timepix.schemas import TimePixStart, TimePixEvent, TimePixStop
def my_operator(message):
if isinstance(message, TimePixStart):
# Initialize processing
print(f"Start: {message.scan_name}")
elif isinstance(message, TimePixEvent):
# Process data array
print(f"Event: flush #{message.flush_number}, shape={message.array.shape}")
elif isinstance(message, TimePixStop):
# Finalize processing
print(f"Stop: {message.scan_name}, {message.total_flushes} flushes")
listener = SplashTimePixZMQListener(
zmq_address="tcp://localhost:5657",
operator=my_operator
)
listener.start() # Blocks until stoppedThe application displays real-time statistics:
- Server uptime
- Physical memory usage (RSS)
- Total packet count and rate
- Unknown packet count
- Message queue: Raw byte batches (socket reader → vectorized parser)
- Callback: One
BatchParseResultper batch (pixel/TDC/control arrays), not a list of packet objects - x, y, t array queue: 3D arrays (callback → worker)
- Single array size: Memory per 3D array
- Duration since last reset
- Packet count and rate for current session
- Reset with
rkey
- Last 10 valid packets
- Recent error messages
The application bins pixel events into 3D arrays based on TDC triggers:
- TDC trigger arrives → sets time zero (
t_zero) - Pixel events are binned relative to
t_zero:- Calculate time bin:
bin = (pixel_time - t_zero) / t_delta - Increment:
array[x, y, bin] += 1
- Calculate time bin:
- After flush_interval time, the array is flushed to the worker
- Worker either plots or publishes the array
Key parameters:
tdc_frequency: Inverse of total time window to capture per TDC trigger (t_cycle = 1 / tdc_frequency)t_delta: Width of each time bin (temporal resolution)n_bins: Number of time bins per cycle
You specify either the bin width or the bin count; the other is derived:
- With
--t-delta-nsset:n_bins = ceil(t_cycle / t_delta) - Without it (default):
t_delta = t_cycle / n_bins(with--n-bins, default 350)
Pixels outside the time window or before the first TDC are discarded and counted for diagnostics.
- Generates Poisson-distributed pixel events
- Realistic TDC pulses with configurable frequency
- Useful for testing and development
Real-time streaming from TimePix3:
./ASI/live-cliReplay recorded .tpx3 files:
./ASI/live-cli --source-files path/to/file.tpx3For step-by-step testing (server, simulator, ZMQ subscribers, optional ArroyoXPS), see info/TESTING_GUIDE.md.
# Run all tests
pytest
# Run start/stop message tests
pytest tests/test_start_stop_messages.py -v
# Run quick manual test (requires server running)
python tests/test_start_stop_quick.pypre-commit run --all-filesData Source → Socket Server → Callback (Binning) → Processing Queue → Worker
↓ ↓ ↓
Parser (NumPy batches) 3D Array (x,y,t) Plot or Publish
↓
ZMQ PUB (start/event/stop)
↓
SplashTimePixZMQListener
↓
Operator (your processing)
Threading:
- Socket Listener Thread (I/O bound)
- Data Processor Thread (CPU bound)
- Plotting/ZMQ Worker Thread (output bound)
- Input Listener Thread (user commands)
See info/SOCKET_SERVER_README.md for server details. The start/stop wire format is documented in info/SAMPLE_START_MESSAGE.md, and the schema definitions live in src/splash_timepix/schemas.py.
- Check TDC channel matches your trigger source (
--tdc-ch) - Check TDC edge setting (
--tdc-edge) - Verify you're receiving TDC packets (use
--verbose)
- Increase
t_delta(fewer time bins, smaller 3D array size) - Reduce
flush_interval(flush more frequently) - Increase xyt_queue consumption rate
- Increase
--buffer-size - Reduce callback processing time
- Increase
--callback-batch-size - Check if worker thread is keeping up
- Press ENTER after Ctrl+C to clear buffered warnings
- These are harmless and occur during daemon thread cleanup
When using the test simulator, you may see warnings like:
WARNING - Pixel outside time window: t_relative=... ps
This is expected behavior. The simulator uses real-time scheduling while packets use detector timestamps, which can drift slightly. These warnings indicate the application is correctly identifying and discarding events that fall outside the configured time window. In production with real detector hardware, similar edge cases may occur due to timing jitter, and the application handles them correctly.
Detector Interface for Streaming, Control, and Open-source integration (DISCO) Copyright (c) 2026, The Regents of the University of California, through Lawrence Berkeley National Laboratory (subject to receipt of any required approvals from the U.S. Dept. of Energy). All rights reserved.
If you have questions about your rights to use or distribute this software, please contact Berkeley Lab's Intellectual Property Office at IPO@lbl.gov.
NOTICE. This Software was developed under funding from the U.S. Department of Energy and the U.S. Government consequently retains certain rights. As such, the U.S. Government has been granted for itself and others acting on its behalf a paid-up, nonexclusive, irrevocable, worldwide license in the Software to reproduce, distribute copies to the public, prepare derivative works, and perform publicly and display publicly, and to permit others to do so.
This software is distributed under a BSD-style license (with an added Enhancements paragraph). See LICENSE.txt for the full terms.
Note: The third-party binaries under
ASI/(e.g. Serval,live-cli,tpx3dump) are provided by Amsterdam Scientific Instruments and are covered by their own respective licenses, not the DISCO license above.