A high-performance Python desktop diagnostic application for characterizing Single-Photon Avalanche Diode (SPAD) detector afterpulsing and recovery dynamics using the cronologic xHPTDC8-PCIe time-to-digital converter.
-
Direct C-DLL Integration via
ctypes: Zero-overhead streaming interface to the native cronologic xHPTDC8 driver (xhptdc8_read_hits). -
Real-Time Dual-Plot Dashboard (PySide6 / pyqtgraph):
-
Live Count Rate Timeline: Smooth 30 Hz scrolling 3-minute window showing measured raw count rate (
$R_{\text{obs}}$ , cyan) and dead-time-corrected count rate ($R_{\text{corr}}$ , amber dashed). - Moving-Average Inter-Arrival Histogram: 10-second rolling buffer updated at 2 Hz to capture transient pulse statistics with minimal compute load.
-
Live Count Rate Timeline: Smooth 30 Hz scrolling 3-minute window showing measured raw count rate (
-
Physical Parameter Estimation & Modeling:
-
Data-Driven Hold-Off Estimation: Automated detection of the SPAD quenching & rearming cutoff edge (
$t_{\text{holdoff}}$ ). -
Poisson Arrival Distribution Overlay: Compares measured counts against theoretical Poisson arrival statistics:
$$p_{\text{random}}(t) = \lambda \cdot e^{-\lambda (t - t_{\text{holdoff}})}$$ -
Afterpulsing Probability (
$P_{\text{after}}$ ): Quantifies excess avalanche events relative to primary trigger events:$$P_{\text{after}} = \frac{N_{\text{excess}}}{N_{\text{total}} - N_{\text{excess}}} = \frac{N_{\text{excess}}}{N_{\text{primary}}}$$ -
Photon Detection Efficiency (PDE): Real-time PDE calculation based on the true primary photon rate
$R_{\text{primary}} = \frac{R_{\text{corr}}}{1 + P_{\text{after}}}$ :$$\text{PDE} = \frac{R_{\text{primary}}}{R_{\text{expected}}} \cdot 100,% = \frac{R_{\text{corr}}}{(1 + P_{\text{after}}) \cdot R_{\text{expected}}} \cdot 100,%$$
-
Data-Driven Hold-Off Estimation: Automated detection of the SPAD quenching & rearming cutoff edge (
-
Live Hardware Reconfiguration: Interactive GUI threshold slider adjusting the input comparator voltage (
$-1.32,\text{V}$ to$+1.18,\text{V}$ ) on the fly viaxhptdc8_configure(). - Dummy Driver Support: Full simulated development and CI testing mode using the official dummy driver from xhptdc8_babel.
SPAD detectors exhibit characteristic recovery kinetics after detecting an avalanche event:
Avalanche
Detection
│
▼
┌─────────────┐
│ Quenching │ Active / passive circuit quenching
└──────┬──────┘
▼
┌─────────────┐
│ Hold-Off │ Dead time (~10 µs): No events observable (zero-count baseline)
└──────┬──────┘
▼
┌─────────────┐
│ Rearming │ Detector bias restored; high probability of trapped carrier release
└──────┬──────┘
▼
┌───────────────────────┐
│ Afterpulsing Cluster │ ──► Visible as excess peak in inter-arrival histogram
└───────────────────────┘
-
Dead-Time Correction: Corrects the observed count rate for the estimated hold-off dead time:
$$R_{\text{corr}} = \frac{R_{\text{obs}}}{1 - R_{\text{obs}} \cdot t_{\text{holdoff}}}$$ -
Excess Afterpulsing Ratio & Primary Event Normalization: Evaluates excess counts in the afterpulse window (with trap lifetime extrapolation correction) normalized by primary events:
$$N_{\text{excess}} = \max(0, N_{\text{measured}}(A) - N_{\text{random}}(A)) \cdot c_{\text{extrapolation}}$$ $$P_{\text{after}} = \frac{N_{\text{excess}}}{N_{\text{total}} - N_{\text{excess}}} = \frac{N_{\text{excess}}}{N_{\text{primary}}}$$ -
Primary Photon Rate & PDE:
$$R_{\text{primary}} = \frac{R_{\text{corr}}}{1 + P_{\text{after}}}$$ $$\text{PDE} = \frac{R_{\text{primary}}}{R_{\text{expected}}} \cdot 100,%$$
By default, the acquisition pipeline sets the xHPTDC8 card to:
- Operating Mode: Continuous Mode (
grouping.enabled = false) - Active Channel: Channel 0 (Channel A) only, rising edge polarity (
channel[0].rising = true) - Trigger Threshold:
+0.40 Vdefault (adjustable from-1.32 Vto+1.18 V) - Unused Channels: TDC channels 1–7 and all ADC channels disabled to conserve DMA bandwidth
┌───────────────────────────────────────────────────────────┐
│ xHPTDC8 PCIe Hardware / Dummy C-DLL │
└─────────────────────────────┬─────────────────────────────┘
│ C-ABI: TDCHit structs (16 bytes)
▼
┌───────────────────────────────────────────────────────────┐
│ Hardware Adapter (src/driver/xhptdc8_adapter.py) │
│ • ctypes bindings for C API │
│ • Pre-allocated C memory buffers │
└─────────────────────────────┬─────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────┐
│ Acquisition Worker (src/workers/acquisition.py) │
│ • QThread non-blocking buffer reader │
│ • Channel filtering & 1D int64 relative time diffs │
└─────────────────────────────┬─────────────────────────────┘
│ Qt Signal (relative_time_ps chunks)
▼
┌───────────────────────────────────────────────────────────┐
│ Analysis Engine (src/analysis/engine.py) │
│ • 20-slot ring buffer for 10 s rolling histogram │
│ • Hold-off onset estimator │
│ • Poisson random curve model & excess integration │
└─────────────────────────────┬─────────────────────────────┘
│ Aggregated plot data & metrics
▼
┌───────────────────────────────────────────────────────────┐
│ Desktop UI (src/gui/main_window.py) │
│ • PySide6 application frame │
│ • pyqtgraph high-speed continuous scrolling plots │
└───────────────────────────────────────────────────────────┘
- Python 3.14 or newer
- Windows 10/11 x64 (compatible with the 64-bit cronologic C-DLL)
- cronologic xHPTDC8 driver package or dummy DLLs from xhptdc8_babel
-
Clone the repository:
git clone https://github.com/cronologic-de/xhptdc8-spad-afterpulsing.git cd xhptdc8-spad-afterpulsing
-
Set up a virtual environment:
py -3.14 -m venv .venv .\.venv\Scripts\Activate.ps1 python -m pip install --upgrade pip python -m pip install numpy PySide6 pyqtgraph matplotlib
-
Configure Driver DLL Path: Place
xhptdc8_driver_64.dllandxhptdc8_util.dllunderlib/dummy/or specify your system DLL path inconfig.json/ environment variables.
Important: Always execute scripts from the project root directory to ensure relative paths for DLL binaries and modules resolve correctly.
By default, the application runs in development/simulation mode using the 64-bit dummy DLL:
python main.py--hardware: Forces usage of the real physical PCIe hardware driver DLL (bin/xhptdc8_driver_64.dll).python main.py --hardware--dummy: Forces usage of the mock/simulation driver (lib/dummy/xhptdc8_driver_64.dll, default behavior).python main.py --dummy--dll <path>: Specifies a custom path to a specificxhptdc8_driver_64.dllbinary.python main.py --dll "C:\path\to\xhptdc8_driver_64.dll"
python -m unittest discover -s tests| Script | Description | Command |
|---|---|---|
scripts/test_dummy_rate.py |
Validates DLL loading and checks channel 0 hit rates. | python scripts/test_dummy_rate.py |
scripts/generate_screenshots.py |
Runs simulated physics data and saves UI screenshots to docs/screenshots/. |
python scripts/generate_screenshots.py |
scripts/plot_evaluation_chart.py |
Generates a publication-quality Matplotlib evaluation figure. | python scripts/plot_evaluation_chart.py |
tests/test_periodic_rates.py |
Sweeps frequency inputs (1 MHz to 100 Hz) to verify rate metrics. | python -m unittest tests/test_periodic_rates.py |
- Hardware & Products: cronologic xHPTDC8-PCIe Product Page
- C/C++ Driver Bindings & Dummy Drivers: cronologic-de/xhptdc8_babel on GitHub
- Company Homepage: cronologic GmbH & Co. KG
The architecture, source code, test suites, and documentation in this repository were created predominantly using Artificial Intelligence coding agents.
Copyright (c) 2026 cronologic GmbH & Co. KG
cronologic GmbH & Co. KG
Jahnstraße 49
60318 Frankfurt am Main, Germany
Phone: +49 (0) 69 173 20 256-0
Email: support@cronologic.de
Web: www.cronologic.de
- Commercial Register: HRA 42869, Amtsgericht Frankfurt am Main
- General Partner (Komplementärin): cronologic Verwaltungs GmbH (HRB 87151, Amtsgericht Frankfurt am Main)
- Managing Directors (Vertreten durch): Kolja Sulimma, Dr. Till Jahnke
- VAT Identification Number (USt-IdNr.): DE 235184378
This project is licensed under the MIT License.
MIT License
Copyright (c) 2026 cronologic GmbH & Co. KG
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS, COPYRIGHT HOLDERS, OR CRONOLOGIC GMBH & CO. KG BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, ALL LIABILITY IS EXCLUDED.
All legal relationships and disputes arising out of or in connection with this project, repository, software, or documentation shall be governed exclusively by the laws of the Federal Republic of Germany, excluding the United Nations Convention on Contracts for the International Sale of Goods (CISG) and conflict-of-law rules.
The exclusive place of jurisdiction for all disputes shall be Frankfurt am Main, Germany.

