Metadata-Version: 2.4
Name: dapkel
Version: 0.3.0
Summary: DAPKEL: Data Analysis Package for KELpie
Author-email: Sergei Kulkov <sergei.kulkov23@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/rngKomorebi/dapkel
Project-URL: Documentation, https://rngkomorebi.github.io/dapkel/
Project-URL: Repository, https://github.com/rngKomorebi/dapkel
Project-URL: Issues, https://github.com/rngKomorebi/dapkel/issues
Project-URL: Changelog, https://github.com/rngKomorebi/dapkel/blob/main/CHANGELOG.md
Keywords: SPAD,single-photon,photon counting,TDC,Kelpie,coincidences,dark count rate,cross-talk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Operating System :: OS Independent
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy>=1.24
Requires-Dist: matplotlib>=3.8
Requires-Dist: komorebi_mpl>=0.0.5
Requires-Dist: pandas>=2.0
Requires-Dist: pyarrow>=14
Requires-Dist: scipy>=1.10
Requires-Dist: tqdm>=4.60
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=5; extra == "dev"
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
Requires-Dist: mkdocstrings[python]>=0.24; extra == "docs"
Dynamic: license-file

# Data Analysis Package for KELpie (DAPKEL)

[![Tests](https://github.com/rngKomorebi/dapkel/actions/workflows/tests.yml/badge.svg)](https://github.com/rngKomorebi/dapkel/actions/workflows/tests.yml)
[![Docs](https://github.com/rngKomorebi/dapkel/actions/workflows/docs.yml/badge.svg)](https://rngkomorebi.github.io/dapkel/)
[![PyPI - Version](https://img.shields.io/pypi/v/dapkel)](https://pypi.org/project/dapkel/)
[![PyPI - License](https://img.shields.io/pypi/l/dapkel)](LICENSE)

Unpacking and analysis of the binary data written by the Kelpie SPAD camera:
dark count rate, optical cross-talk, hitmaps, TDC calibration and photon
coincidences.

**Documentation: <https://rngkomorebi.github.io/dapkel/>**

## The detector

The Kelpie detector was developed at EPFL by Dr. Tommaso Milanese. It features
a 64x64 Single-Photon Avalanche Diode (SPAD) sensor built from 2x2 macropixels.
It is fully reprogrammable, with high PDE across the whole visible spectrum
peaking at 780 nm, 40 ps (rms) jitter, low dark count rate and reasonable
cross-talk.

This package was derived from the original MATLAB functions written by
Dr. Milanese for offline unpacking and analysis of the detector's data.

## Installation

A fresh virtual environment is recommended
([how](https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/)).

```bash
pip install dapkel
```

To work on the package itself, clone the repo and install it in editable mode
with the test and documentation extras:

```bash
git clone https://github.com/rngKomorebi/dapkel.git
cd dapkel
pip install -e ".[dev,docs]"
```

`requirements.txt` lists the runtime dependencies alone, for the
`pip install -r requirements.txt` path; `pyproject.toml` is the source of truth.

## How it works

Every analysis is the same pipeline with a different reduction in the middle:

```
folder of .bin  ->  discover files  ->  fold frames into a sensor map
                ->  scale to physical units  ->  save
                ->  load  ->  plot
```

| stage | what it does | on disk |
|---|---|---|
| **0. raw** | the camera writes `.bin`; dapkel decodes it | `*.bin` (read-only) |
| **1. process** | fold frames into an array, scale to units, save | `processed/` |
| **2. plot** | load the saved array, render figures | `results/<kind>/` |

The rule that keeps this honest: **`processed/` holds data, `results/` holds
pictures, and stage 2 never re-unpacks `.bin` files.** Retrying a colormap
costs a load, not a re-read of the raw data.

## Quick start

> On a fresh acquisition, run the data-quality check first. A TDC that never
> stopped produces data that looks completely normal to every other analysis in
> this package.

```python
from dapkel.functions import data_quality, dcr_analysis

# 0. Did the TDC actually record timing? Codes should spread over the whole
#    range for the clock in use (~1397 codes at 10 MHz, ~6985 at 2 MHz),
#    not collapse onto a handful of values.
data_quality.plot_time_code_histogram(
    "path/to/data", pixel=(16, 16), nframes=10_000
)

# 1 + 2. Compute the full-sensor DCR map and save both figures.
#        'open_shutter_time_s' is
#    the photon-sensitive part of each frame, in SECONDS.
dcr_analysis.collect_and_plot_dcr_64(
    "path/to/data", nframes=1000, open_shutter_time_s=100e-9
)
```

Stages 1 and 2 are also separable, so a saved map can be re-plotted without
re-reading the raw files:

```python
from dapkel.core import store
from dapkel.functions import dcr_analysis

dcr_analysis.compute_and_save_dcr_64(
    "path/to/data", 1000, open_shutter_time_s=100e-9
)

dcr, meta = store.load_map("path/to/data", kind="dcr", tag="64")
fig = dcr_analysis.plot_heatmap(dcr)
```

Each driver can also skip straight to stage 2 from what is already in
`processed/`: `dcr_analysis` and `crosstalk_analysis` take `from_saved=True`,
`hitmap_analysis` takes `npy_path=`, `delta_t` takes `feather_path=`, and TDC
lookup tables come back through `tdc_calibration.load_lut`.

## What's in the package

`dapkel.functions` holds one module per measurement. Each declares its public
surface in `__all__` - read that list to see what you can call.

| module | what it measures |
|---|---|
| [`unpack`](https://rngkomorebi.github.io/dapkel/guide/unpack/) | decodes the raw `.bin` files; the shared foundation |
| [`data_quality`](https://rngkomorebi.github.io/dapkel/guide/data_quality/) | is this acquisition usable at all? |
| [`dcr_analysis`](https://rngkomorebi.github.io/dapkel/guide/dcr/) | per-pixel dark count rate, in cps |
| [`crosstalk_analysis`](https://rngkomorebi.github.io/dapkel/guide/crosstalk/) | optical cross-talk, per direction and combined |
| [`hitmap_analysis`](https://rngkomorebi.github.io/dapkel/guide/hitmap/) | occupancy and photon rate |
| [`tdc_calibration`](https://rngkomorebi.github.io/dapkel/guide/tdc_calibration/) | per-pixel TDC LUTs from the code density test |
| [`calc_diff`, `delta_t`](https://rngkomorebi.github.io/dapkel/guide/coincidences/) | timestamp differences, coincidences and jitter |
| [`background_subtraction`](https://rngkomorebi.github.io/dapkel/guide/background_subtraction/) | removes the accidental background by shifting frames |

Anything shared between two analyses lives in
[`dapkel.core`](https://rngkomorebi.github.io/dapkel/api/core/) rather than
being copied - a test fails if a function name is defined in two analysis
modules.

A standalone app for starting acquisitions and plotting the hitmap in real
time is at [dapkel-rtp](https://github.com/rngKomorebi/dapkel-rtp).

## Plot styling

Importing `dapkel` applies its matplotlib house style through `komorebi_mpl`,
so plots look consistent out of the box. Your choice always wins - the plotting
functions never touch `rcParams` themselves, so whatever style is active when
they draw is what you get:

```python
import dapkel                     # applies the default style on import
import komorebi_mpl

komorebi_mpl.use("night_wave")    # or any registered style, or "default"
```

## Development

```bash
pytest              # test suite, including the API-surface guards
ruff check .        # lint
mkdocs serve        # preview the documentation at localhost:8000
```

### Releasing

There is no version number to bump anywhere: `setuptools_scm` derives it from
the git tag, so **creating the tag is the version bump**.

The changelog is edited by hand, and it has to be edited *before* the tag
exists - the workflow reads `CHANGELOG.md` as it was at the tagged commit, so
notes still sitting under `[Unreleased]` cannot ship.

1. In `CHANGELOG.md`, rename the `## [Unreleased]` heading, leaving an empty
   `[Unreleased]` above it for the next cycle:

   ```markdown
   ## [Unreleased]

   ## [0.2.0] - 2026-08-02
   ```

   and update the link definitions at the foot of the file:

   ```markdown
   [Unreleased]: https://github.com/rngKomorebi/dapkel/compare/v0.2.0...HEAD
   [0.2.0]: https://github.com/rngKomorebi/dapkel/compare/v0.1.1...v0.2.0
   ```

2. Merge that into `main`.
3. On GitHub, *Releases -> Draft a new release*, create the tag `v0.2.0`
   **there** - it must point at the commit from step 2 - and publish.

Publishing runs `publish.yml`, which validates the tag, refuses to ship a
version with no changelog entry, runs the tests, checks the built version
matches the tag, uploads to PyPI, and rewrites the release body from the
changelog.

If the changelog entry is missing, the run fails at the first job, so nothing
is built and nothing reaches PyPI - no version number is lost. Fix the
changelog, push, then **delete the tag as well as the release** before
retrying: deleting a release leaves its tag behind, and re-creating one with
the same name silently reuses that tag and its old commit. PyPI versions
themselves cannot be reused, so a release that *does* upload is final.

### Contributing

`main` is the release branch: tested, and what PyPI is cut from. Work on a
feature branch and open a pull request against it.
[`TODO.md`](TODO.md) is the list of open questions — what is known to need
deciding and what would settle it. Start there if you are looking for
something to pick up, and add to it rather than leaving a decision undocumented. Contributions are welcome;
please follow [PEP 8](https://peps.python.org/pep-0008/) and
[PEP 257](https://peps.python.org/pep-0257/). If you are adding a new
measurement, start from
[Adding an analysis](https://rngkomorebi.github.io/dapkel/adding_an_analysis/) -
it describes the contract the tests enforce.

## License and contact

MIT - see [LICENSE](LICENSE). To get in touch, write to
sergei.kulkov23@gmail.com.
