daplis_rtp.functions.tdc_occupancy module
How much of the TDC slot memory an acquisition actually uses.
Each TDC of LinoSPAD2 holds ‘timestamps’ slots per acquisition cycle, shared by the four pixels wired to it (firmware 2212b: TDC t serves pixels 4t..4t+3; 2212s: pixels t, t+64, t+128, t+192). Once those slots are full the rest of the cycle is dropped on the floor, and nothing in the data file says so - a saturated file is exactly the same size as a healthy one, it just stops part way through every window. The damage is rate-dependent and time-correlated: the brighter of a pair of boards truncates earlier than the dimmer one, so a cross-board reduction of such a run silently compares different slices of time.
Hence this module. It counts, per TDC and per cycle, how many slots were used, and turns that into the two numbers worth looking at before a long acquisition is started:
the busiest TDC among the pixels actually being measured, as a fraction of its slots - “TDC 36 at 36 % of 150” - which says whether this measurement is truncated;
the worst TDC anywhere on the sensor, which says whether the board is near the edge at all.
Both are needed. A per-sensor average hides a single saturating TDC, and the sensor-wide maximum is usually a hot pixel nobody is measuring - on the B7d run of 2026.09.07, TDC 17 saturated in 848 of 1000 cycles while TDC 36 and TDC 39, which carried the pixels of interest, used 54 and 25 of their 150 slots.
The one case where a hot pixel does matter is when it shares a TDC with a pixel of interest: the four pixels compete for the same slots, so the hot one eats the memory the signal needs. That is why the TDCs of interest are taken as “every TDC holding at least one pixel of interest”, counting all of that TDC’s traffic - dropping the hot pixel would hide exactly the case that hurts.
Nothing here reads a file: the caller hands over the unpacked matrix it already has, so the check costs one comparison and a couple of small fancy-indexed reads on top of the unpacking that has happened anyway.
- class daplis_rtp.functions.tdc_occupancy.Occupancy(timestamps, cycles, used, sat_fraction, fill_time, window)[source]
Bases:
objectSlot use of every TDC, cycle by cycle.
- timestamps
Slots per TDC per acquisition cycle - the setting under test.
- Type:
int
- cycles
Acquisition cycles in the file.
- Type:
int
- used
Slots used, shape (n_tdc, cycles).
- Type:
ndarray
- median_used, max_used
Per TDC, over the cycles. The median is the headroom figure; the maximum is what the setting has to cover.
- Type:
ndarray
- sat_fraction
Per TDC, the fraction of cycles that used every slot - i.e. in which the rest of the window was thrown away.
- Type:
ndarray
- fill_time
Per TDC, the median time in picoseconds at which it filled up, over the cycles in which it did; NaN for TDCs that never did.
- Type:
ndarray
- window
Length of the timestamping window in picoseconds, taken as the latest timestamp seen in a cycle that did not fill up. NaN when every cycle of every TDC filled up, in which case the file holds no evidence of how long the window really was.
- Type:
float
- property n_tdc
- class daplis_rtp.functions.tdc_occupancy.OccupancySummary(occupancy, tdcs)[source]
Bases:
objectThe busiest TDC of a set, and what to do about it.
- tdc
The TDC picked out of the set: the one that saturates in most cycles, or, when none of them saturate, the one using most of its slots. None when the set is empty.
- Type:
int or None
- median_used, max_used, sat_fraction
That TDC’s figures, from ‘Occupancy’.
- Type:
float
- fraction
‘median_used / timestamps’ - the headroom figure to display.
- Type:
float
- state
‘ok’, ‘warn’ (running close to the limit) or ‘bad’ (losing data), by ‘HEADROOM_WARN_FRACTION’ and ‘SAT_WARN_FRACTION’.
- Type:
str
- suggested_timestamps
A setting that would hold the traffic measured here, with ‘HEADROOM_FACTOR’ to spare. None when the set saturated throughout, since the file then shows neither the real rate nor the length of the window.
- Type:
int or None
- n_tdc
How many TDCs the set holds - 64 means nothing was narrowed down and the figure is the sensor-wide one.
- Type:
int
- daplis_rtp.functions.tdc_occupancy.parse_pixel_spec(text: str, n_pixels: int = 256)[source]
Read a pixel list like ‘36, 40-43, 100’ into an index array.
Whitespace, commas and semicolons all separate entries; ‘a-b’ and ‘a:b’ are inclusive ranges. Returns an empty array for empty input.
- Raises:
ValueError – If an entry is neither a number nor a range, or lies outside 0..n_pixels-1.
- daplis_rtp.functions.tdc_occupancy.pixel_to_tdc_map(fw_ver: str, pix_add_fix: bool = False)[source]
Return the TDC index of every pixel, as a 256-long array.
Indexed by pixel number as plotted, which is not the pixel number the board reads out: with ‘pix_add_fix’ the sensor half is re-addressed for display exactly as ‘sen_pop’ does it, so the x-axis of the plot can be used directly to pick TDCs.
- Parameters:
fw_ver (str) – LinoSPAD2 firmware version, ‘2212b’ or ‘2212s’.
pix_add_fix (bool, optional) – Whether the pixel addressing of the sensor half is corrected for display, by default False.
- Returns:
‘tdc_of_pixel[plotted_pixel]’ -> TDC index.
- Return type:
ndarray
- Raises:
ValueError – Raised if the firmware version is not one of ‘FW_VERSIONS’.
- daplis_rtp.functions.tdc_occupancy.slot_occupancy(data, fw_ver: str, timestamps: int)[source]
Count the slots each TDC used in each acquisition cycle.
- Parameters:
data (ndarray) – The matrix returned by ‘unpack_bin’: (64, cycles * (timestamps + 1), 2), where the extra column per cycle holds the ‘-2’ end-of-cycle marker.
fw_ver (str) – LinoSPAD2 firmware version, ‘2212b’ or ‘2212s’. Only checked here - the slot bookkeeping is the same for both, since they differ in which four pixels share a TDC and not in how many.
timestamps (int) – Slots per TDC per cycle - the same value the data were unpacked with.
- Return type:
- Raises:
ValueError – If the firmware version is not one of ‘FW_VERSIONS’, or if ‘data’ does not divide into whole cycles of ‘timestamps’ slots, which means it was unpacked with a different setting than the one passed here.