Dark count rate (DCR)¶
The DCR map is the per-pixel dark-count rate in counts per second: accumulated dark counts divided by the time the sensor was actually collecting.
Counting mode vs timestamping mode¶
What "a count" is depends on the acquisition program, so mode is not
cosmetic — it decides which field of the unpacked data is read.
mode="count"(default) —S*C/ORCdata. Each pixel has a real 9-bit photon counter per frame, and the map is its sum.mode="timestamp"—S*T/ORTdata. There is no counter: the chip timestamps the first hit instead, andunpackreturns the low bits of the coarse timestamp in thephoton_countsslot. The count is therefore the number of frames carrying a valid timestamp,(ts > 0).sum(axis=2)— the same definitionhitmap_analysisuses.
Summing the counter field on *T data inflates the DCR ~40-50×
Those bits are coarse >> 1, so a fired pixel contributes a few hundred
instead of 1, and the result still looks like a plausible DCR map. On real
24 V S0T data the median came out at 247 kcps — nearly twice the 125 kcps
that one-hit-per-8-µs-frame physically allows. mode is cross-checked
against the last letter of tag, so tag="S0T" with mode="count" is a
ValueError, not a wrong number.
Saturation and pileup_correct¶
Timestamping records at most one count per pixel per frame, so the raw rate
is really an occupancy and it saturates at 1 / open_shutter_time_s — 125 kcps
at an 8 µs shutter. It under-reports well before the ceiling: at 50 % occupancy
it reads ~30 % low.
pileup_correct=True inverts the censoring under Poisson statistics, since a
frame fires with probability p = 1 - exp(-r·w):
It is off by default — the raw occupancy rate is what the sensor measured — and the drivers print a warning naming the ceiling whenever the hottest pixel gets within 5 % of it. Counting mode has no such ceiling and ignores the flag.
Live time is not frame time¶
The normalisation uses the live time per frame, which depends on the
firmware — see core.timing.resolve_live_time.
short_window(current firmware, the default) — each ~9 µs frame is mostly readout and only the user-setopen_shutter_time_s(e.g.200e-9s) is photon-sensitive. You must passopen_shutter_time_s;compute_dcr_32raises without it rather than guessing.full_window(legacy) — the whole frame is live, so the per-frame time comes fromresolve_frame_time: explicitexp_time_s+ 9 µs overhead, elseframe_rate_cnt.txt, else the 9 µs free-running fallback.
Under the legacy firmware the exposure argument passed to Kelpie_v2.exe maps
to an actual frame period with 9 µs of overhead: 0 → 9 µs, 10 µs → 19 µs.
Quadrants and the full sensor¶
compute_dcr_32 handles one SPAD quadrant tag — S0C...S3C in counting
mode, S0T...S3T in timestamping mode.
compute_dcr_64 unpacks all four and interleaves them onto the full (64, 64)
sensor — each tag reads one micropixel per 2×2 macropixel, so the quadrant maps
interleave rather than tile.
The SPAD indices run clockwise (S0 S1 / S3 S2), not row-major; the layout
is documented under cross-talk
and enforced by tests/test_spad_geometry.py.
Quadrant tags are matched with a leading underscore
DCR globs *_S0C*.bin, not *S0C*.bin, so a tag cannot match a filename
that merely happens to contain those characters. This is the
require_separator=True argument to
core.io.find_bin_files.
Pixel masking¶
Hot/warm pixel masking is not implemented yet. When per-board mask files
become available, masked pixels will read as NaN — which is why every
statistic in core.plots is already NaN-aware. Plain
np.median / np.percentile propagate NaN, and a single masked pixel would
otherwise turn vmax into NaN and flatten the entire heatmap to one colour
with no error raised.
API¶
dcr_analysis ¶
Dark count rate (DCR) maps, per quadrant and full-sensor.
DCR is accumulated dark counts divided by the time the sensor was actually
collecting. That live time is NOT the frame period: under the current
short_window firmware only the user-set open_shutter_time_s of each
~9 us frame is photon-sensitive, so open_shutter_time_s is required.
What counts as "a count" depends on the acquisition program, exactly as in 'hitmap_analysis':
* ``mode="count"`` (``S*C`` / ``ORC``) - the per-pixel photon counter is
summed over frames.
* ``mode="timestamp"`` (``S*T`` / ``ORT``) - the counter field does **not**
exist; 'unpack' returns the low bits of the coarse timestamp in its place
(see the note in 'unpack'). Summing it gives a number tens of times too
large. Here the count is the number of frames carrying a valid timestamp,
``(ts > 0).sum(axis=2)``.
In timestamp mode only the first dark count of a frame is recorded, so the
occupancy saturates at one per open_shutter_time_s (125 kcps at an 8 µs
window) and under-reports below it. pileup_correct=True inverts that
censoring under Poisson statistics; see 'compute_dcr_32'.
See docs/guide/dcr.md.
compute_dcr_32 ¶
compute_dcr_32(folder: str, nframes: int, exp_time_s: float | None = None, tag: str = '', *, mode: str = 'count', pileup_correct: bool = False, firmware_version: str = 'short_window', open_shutter_time_s: float | None = 5e-07) -> ndarray
Unpack the binary data and compute the (32, 32) DCR map.
Normalised by the live time per frame, which depends on the firmware
(see 'core.timing.resolve_live_time'): 'short_window' counts only
open_shutter_time_s as live, 'full_window' the whole frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
folder
|
str
|
Path to the folder with the '.bin' data files. |
required |
nframes
|
int
|
Number of frames stored in each '.bin' file. |
required |
exp_time_s
|
float | None
|
Legacy exposure time in seconds ( |
None
|
tag
|
str
|
SPAD quadrant tag - 'S0C'...'S3C' in counting mode, 'S0T'...'S3T' (or 'ORT') in timestamping mode. The default is "", which matches every '.bin' file in the folder. |
''
|
mode
|
str
|
|
'count'
|
pileup_correct
|
bool
|
|
False
|
firmware_version
|
str
|
|
'short_window'
|
open_shutter_time_s
|
float | None
|
Photon-sensitive window per frame, in seconds (required for
|
5e-07
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The (32, 32) dark count rate map, in counts per second. |
Raises:
| Type | Description |
|---|---|
ValueError
|
Raised when |
Source code in src/dapkel/functions/dcr_analysis.py
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 | |
compute_dcr_64 ¶
compute_dcr_64(folder: str, nframes: int, exp_time_s: float | None = None, *, mode: str = 'count', pileup_correct: bool = False, firmware_version: str = 'short_window', open_shutter_time_s: float | None = 5e-07) -> ndarray
Unpack the binary data and compute the (64, 64) DCR map.
Interleaves the four quadrant maps onto the full sensor. Normalised by the live time per frame; see 'compute_dcr_32'.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
folder
|
str
|
Path to the folder with the '.bin' data files. |
required |
nframes
|
int
|
Number of frames stored in each '.bin' file. |
required |
exp_time_s
|
float | None
|
Legacy exposure time in seconds ( |
None
|
mode
|
str
|
|
'count'
|
pileup_correct
|
bool
|
|
False
|
firmware_version
|
str
|
|
'short_window'
|
open_shutter_time_s
|
float | None
|
Photon-sensitive window per frame, in seconds (required for
|
5e-07
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The (64, 64) dark count rate map, in counts per second. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
Raised when no '.bin' files for any of the four SPAD tags are found in the folder. |
ValueError
|
Raised when |
Source code in src/dapkel/functions/dcr_analysis.py
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 | |
plot_heatmap ¶
Plot a dark count rate map as a 2D heatmap.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dcr
|
ndarray
|
Dark count rate map, e.g. from 'compute_dcr_32'/'compute_dcr_64'. |
required |
cmap
|
str | None
|
Matplotlib colormap name. The default is None — use the active
style's |
None
|
Returns:
| Type | Description |
|---|---|
Figure
|
The generated figure. |
Source code in src/dapkel/functions/dcr_analysis.py
plot_distribution ¶
Plot the sorted per-pixel dark count rate distribution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dcr
|
ndarray
|
Dark count rate map, e.g. from 'compute_dcr_32'/'compute_dcr_64'. |
required |
Returns:
| Type | Description |
|---|---|
Figure
|
The generated figure. |
Source code in src/dapkel/functions/dcr_analysis.py
compute_and_save_dcr_32 ¶
compute_and_save_dcr_32(path: str, nframes: int, exp_time_s: float | None = None, tag: str = '', *, mode: str = 'count', pileup_correct: bool = False, firmware_version: str = 'short_window', open_shutter_time_s: float | None = 5e-07) -> str
Compute the (32, 32) DCR map and save it under processed/.
The stage-1 half of the DCR analysis: unpack once, write the array plus a
'.meta.json' sidecar, and return the path. 'collect_and_plot_dcr_32' can
then replot it with from_saved=True without re-unpacking.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Path to the folder with the '.bin' data files. |
required |
nframes
|
int
|
Number of frames stored in each '.bin' file. |
required |
exp_time_s
|
float | None
|
Value passed to Kelpie_v2.exe, in seconds. The default is None. |
None
|
tag
|
str
|
SPAD quadrant tag ('S0C'...'S3C', or 'S0T'...'S3T'/'ORT' in timestamping mode). The default is "". |
''
|
mode
|
str
|
|
'count'
|
pileup_correct
|
bool
|
|
False
|
firmware_version
|
str
|
|
'short_window'
|
open_shutter_time_s
|
float | None
|
Photon-sensitive window per frame, in seconds. The default is
|
5e-07
|
Returns:
| Type | Description |
|---|---|
str
|
Path to the saved '.npy'. |
Source code in src/dapkel/functions/dcr_analysis.py
compute_and_save_dcr_64 ¶
compute_and_save_dcr_64(path: str, nframes: int, exp_time_s: float | None = None, *, mode: str = 'count', pileup_correct: bool = False, firmware_version: str = 'short_window', open_shutter_time_s: float | None = 5e-07) -> str
Assemble the (64, 64) full-sensor DCR map and save it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Path to the folder with the four SPAD quadrants' '.bin' files. |
required |
nframes
|
int
|
Number of frames stored in each '.bin' file. |
required |
exp_time_s
|
float | None
|
Value passed to Kelpie_v2.exe, in seconds. The default is None. |
None
|
mode
|
str
|
|
'count'
|
pileup_correct
|
bool
|
|
False
|
firmware_version
|
str
|
|
'short_window'
|
open_shutter_time_s
|
float | None
|
Photon-sensitive window per frame, in seconds. The default is
|
5e-07
|
Returns:
| Type | Description |
|---|---|
str
|
Path to the saved '.npy'. |
Source code in src/dapkel/functions/dcr_analysis.py
collect_and_plot_dcr_32 ¶
collect_and_plot_dcr_32(path: str, nframes: int, exp_time_s: float | None = None, tag: str = '', daughterboard_number: str | None = None, motherboard_number: str | None = None, cmap: str | None = None, *, mode: str = 'count', pileup_correct: bool = False, firmware_version: str = 'short_window', open_shutter_time_s: float | None = 5e-07, from_saved: bool = False) -> ndarray
Unpack, compute, and plot the (32, 32) DCR map for a SPAD tag.
Computes (and saves) the map, then writes both the heatmap and the
sorted distribution to results/dcr.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Path to the folder with the '.bin' data files. |
required |
nframes
|
int
|
Number of frames stored in each '.bin' file. |
required |
exp_time_s
|
float | None
|
Value passed to Kelpie_v2.exe, in seconds. See 'compute_dcr_32' for details. The default is None. |
None
|
tag
|
str
|
SPAD quadrant tag ('S0C'...'S3C', or 'S0T'...'S3T'/'ORT' in timestamping mode). The default is "", which matches every '.bin' file in the folder. |
''
|
daughterboard_number
|
str | None
|
Camera daughterboard number, used to look up the hot/warm pixel mask. Mask support is not implemented yet. The default is None. |
None
|
motherboard_number
|
str | None
|
Camera motherboard number, used together with 'daughterboard_number' to look up the hot/warm pixel mask. Mask support is not implemented yet. The default is None. |
None
|
cmap
|
str | None
|
Matplotlib colormap name for the heatmap. The default is None —
use the active style's |
None
|
mode
|
str
|
|
'count'
|
pileup_correct
|
bool
|
|
False
|
firmware_version
|
str
|
|
'short_window'
|
open_shutter_time_s
|
float | None
|
Photon-sensitive window per frame, in seconds (required for
|
5e-07
|
from_saved
|
bool
|
Load the map saved by 'compute_and_save_dcr_32' instead of re-unpacking the '.bin' files - use this to retry a colormap. The default is False, which computes and then saves the array. |
False
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The (32, 32) dark count rate map, in counts per second. |
Source code in src/dapkel/functions/dcr_analysis.py
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 | |
collect_and_plot_dcr_64 ¶
collect_and_plot_dcr_64(path: str, nframes: int, exp_time_s: float | None = None, daughterboard_number: str | None = None, motherboard_number: str | None = None, cmap: str | None = None, *, mode: str = 'count', pileup_correct: bool = False, firmware_version: str = 'short_window', open_shutter_time_s: float | None = 5e-07, from_saved: bool = False) -> ndarray
Unpack, compute, and plot the (64, 64) full sensor DCR map.
Computes (and saves) the map, then writes both the heatmap and the
sorted distribution to results/dcr.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
str
|
Path to the folder with the '.bin' data files. |
required |
nframes
|
int
|
Number of frames stored in each '.bin' file. |
required |
exp_time_s
|
float | None
|
Value passed to Kelpie_v2.exe, in seconds. See 'compute_dcr_64' for details. The default is None. |
None
|
daughterboard_number
|
str | None
|
Camera daughterboard number, used to look up the hot/warm pixel mask. Mask support is not implemented yet. The default is None. |
None
|
motherboard_number
|
str | None
|
Camera motherboard number, used together with 'daughterboard_number' to look up the hot/warm pixel mask. Mask support is not implemented yet. The default is None. |
None
|
cmap
|
str | None
|
Matplotlib colormap name for the heatmap. The default is None —
use the active style's |
None
|
mode
|
str
|
|
'count'
|
pileup_correct
|
bool
|
|
False
|
firmware_version
|
str
|
|
'short_window'
|
open_shutter_time_s
|
float | None
|
Photon-sensitive window per frame, in seconds (required for
|
5e-07
|
from_saved
|
bool
|
Load the map saved by 'compute_and_save_dcr_64' instead of re-unpacking the '.bin' files. The default is False, which computes and then saves the array. |
False
|
Returns:
| Type | Description |
|---|---|
ndarray
|
The (64, 64) dark count rate map, in counts per second. |
Source code in src/dapkel/functions/dcr_analysis.py
707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 | |