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: object

Slot 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

__init__(timestamps, cycles, used, sat_fraction, fill_time, window)[source]
property n_tdc
class daplis_rtp.functions.tdc_occupancy.OccupancySummary(occupancy, tdcs)[source]

Bases: object

The 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

__init__(occupancy, tdcs)[source]
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:

Occupancy

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.