pykrait 0.3.3__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {pykrait-0.3.3 → pykrait-0.4.0}/.gitignore +5 -1
  2. {pykrait-0.3.3 → pykrait-0.4.0}/PKG-INFO +54 -44
  3. {pykrait-0.3.3 → pykrait-0.4.0}/README.md +35 -26
  4. {pykrait-0.3.3 → pykrait-0.4.0}/pyproject.toml +25 -28
  5. pykrait-0.4.0/src/pykrait/__init__.py +27 -0
  6. pykrait-0.4.0/src/pykrait/__main__.py +20 -0
  7. {pykrait-0.3.3 → pykrait-0.4.0}/src/pykrait/gui/app.py +19 -19
  8. pykrait-0.4.0/src/pykrait/gui/async_workers.py +397 -0
  9. {pykrait-0.3.3 → pykrait-0.4.0}/src/pykrait/gui/custom_widgets.py +35 -10
  10. pykrait-0.4.0/src/pykrait/gui/fileselection.py +753 -0
  11. {pykrait-0.3.3 → pykrait-0.4.0}/src/pykrait/gui/mainwindow.py +363 -310
  12. pykrait-0.4.0/src/pykrait/io/files.py +292 -0
  13. pykrait-0.4.0/src/pykrait/io/frame_cache.py +292 -0
  14. pykrait-0.4.0/src/pykrait/io/images.py +646 -0
  15. pykrait-0.4.0/src/pykrait/pipeline/pipeline.py +2043 -0
  16. pykrait-0.4.0/src/pykrait/preprocessing/motion_correction.py +752 -0
  17. pykrait-0.4.0/src/pykrait/preprocessing/segmentation.py +328 -0
  18. pykrait-0.4.0/src/pykrait/preprocessing/timeseries_extraction.py +315 -0
  19. {pykrait-0.3.3 → pykrait-0.4.0}/src/pykrait/trace_analysis/filtering.py +55 -0
  20. {pykrait-0.3.3 → pykrait-0.4.0}/src/pykrait/trace_analysis/oscillations.py +61 -28
  21. {pykrait-0.3.3 → pykrait-0.4.0}/src/pykrait/trace_analysis/peak_analysis.py +122 -38
  22. {pykrait-0.3.3 → pykrait-0.4.0}/src/pykrait/trace_analysis/synchronicity.py +110 -38
  23. {pykrait-0.3.3 → pykrait-0.4.0}/src/pykrait/visualization/image_overlays.py +116 -61
  24. pykrait-0.3.3/src/pykrait/__main__.py +0 -10
  25. pykrait-0.3.3/src/pykrait/gui/async_workers.py +0 -341
  26. pykrait-0.3.3/src/pykrait/gui/fileselection.py +0 -713
  27. pykrait-0.3.3/src/pykrait/io/files.py +0 -191
  28. pykrait-0.3.3/src/pykrait/io/images.py +0 -263
  29. pykrait-0.3.3/src/pykrait/pipeline/pipeline.py +0 -869
  30. pykrait-0.3.3/src/pykrait/preprocessing/segmentation.py +0 -201
  31. pykrait-0.3.3/src/pykrait/preprocessing/timeseries_extraction.py +0 -445
  32. pykrait-0.3.3/src/pykrait/visualization/__init__.py +0 -1
  33. {pykrait-0.3.3 → pykrait-0.4.0}/LICENSE +0 -0
  34. {pykrait-0.3.3/src/pykrait → pykrait-0.4.0/src/pykrait/gui}/__init__.py +0 -0
  35. {pykrait-0.3.3 → pykrait-0.4.0}/src/pykrait/gui/resources/logo.png +0 -0
  36. {pykrait-0.3.3 → pykrait-0.4.0}/src/pykrait/io/__init__.py +0 -0
  37. {pykrait-0.3.3/src/pykrait/gui → pykrait-0.4.0/src/pykrait/pipeline}/__init__.py +0 -0
  38. {pykrait-0.3.3/src/pykrait/pipeline → pykrait-0.4.0/src/pykrait/preprocessing}/__init__.py +0 -0
  39. {pykrait-0.3.3/src/pykrait/preprocessing → pykrait-0.4.0/src/pykrait/trace_analysis}/__init__.py +0 -0
  40. {pykrait-0.3.3/src/pykrait/trace_analysis → pykrait-0.4.0/src/pykrait/visualization}/__init__.py +0 -0
@@ -56,4 +56,8 @@ docs/Makefile
56
56
  *.pem
57
57
 
58
58
  # cellpose models
59
- cellpose_models/
59
+ cellpose_models/
60
+ notebooks/batch_notebook.ipynb
61
+
62
+ # local notebooks, may contain real data paths and executed outputs
63
+ notebooks/local_*.ipynb
@@ -1,36 +1,37 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: pykrait
3
- Version: 0.3.3
3
+ Version: 0.4.0
4
4
  Summary: Python Calcium Recording Analysis and Interpretation Toolbox
5
5
  Author-email: phaleanopsis <22663502+phaleanopsis@users.noreply.github.com>
6
6
  License: MIT
7
7
  License-File: LICENSE
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
8
12
  Requires-Python: >=3.11
9
13
  Requires-Dist: aicspylibczi>=3.3.1
10
- Requires-Dist: bioio-czi>=2.4.0
11
- Requires-Dist: bioio-imageio
12
- Requires-Dist: bioio-ome-tiff>=1.1.0
13
- Requires-Dist: bioio-tifffile>=1.3.0
14
- Requires-Dist: bioio>=3.2.0
15
- Requires-Dist: cellpose>=4.0.8
14
+ Requires-Dist: bioio-czi<5,>=2.4.0
15
+ Requires-Dist: bioio-imageio<2
16
+ Requires-Dist: bioio-ome-tiff<2,>=1.1.0
17
+ Requires-Dist: bioio-tifffile<2,>=1.3.0
18
+ Requires-Dist: bioio<4,>=3.2.0
19
+ Requires-Dist: cellpose<5,>=4.0.8
16
20
  Requires-Dist: dask>=2025.5.1
17
21
  Requires-Dist: fsspec>=2022.8.0
18
22
  Requires-Dist: imagecodecs>=2025.3.30
19
- Requires-Dist: lxml>=5.4.0
20
23
  Requires-Dist: matplotlib>=3.10.3
21
- Requires-Dist: numcodecs<0.16
24
+ Requires-Dist: numcodecs<0.16,>=0.15
22
25
  Requires-Dist: numpy>2
23
- Requires-Dist: ome-types>=0.5.3
24
- Requires-Dist: ome-zarr>=0.10.2
26
+ Requires-Dist: opencv-python-headless>=4.10.0.84
25
27
  Requires-Dist: pandas>=2.3.1
26
- Requires-Dist: pip>=25.1.1
27
- Requires-Dist: pydantic<2
28
- Requires-Dist: pylibczirw>=6.0.1
28
+ Requires-Dist: psutil>=6.0
29
+ Requires-Dist: pylibczirw>=6.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
29
30
  Requires-Dist: pyyaml>=6.0.2
30
- Requires-Dist: resource-backed-dask-array>=0.1.0
31
31
  Requires-Dist: scikit-image>=0.25.2
32
- Requires-Dist: scikit-learn>=1.7.2
33
32
  Requires-Dist: scipy>=1.15.3
33
+ Requires-Dist: tifffile>=2025.5.10
34
+ Requires-Dist: tqdm>=4.66
34
35
  Requires-Dist: xarray>=2025.12
35
36
  Requires-Dist: zarr>=2.15.0
36
37
  Requires-Dist: zstandard>=0.23.0
@@ -40,7 +41,7 @@ Requires-Dist: hatch>=1.14.1; extra == 'dev'
40
41
  Requires-Dist: ipywidgets>=8.1.7; extra == 'dev'
41
42
  Requires-Dist: myst-parser>=5.0.0; extra == 'dev'
42
43
  Requires-Dist: pydata-sphinx-theme>=0.16.1; extra == 'dev'
43
- Requires-Dist: pytest-cov; extra == 'dev'
44
+ Requires-Dist: pytest-cov>=6.0; extra == 'dev'
44
45
  Requires-Dist: pytest>=8.4.1; extra == 'dev'
45
46
  Requires-Dist: ruff>=0.11.13; extra == 'dev'
46
47
  Requires-Dist: sphinx-autoapi>=3.6.0; extra == 'dev'
@@ -53,16 +54,15 @@ Requires-Dist: pyside6>=6.9.1; extra == 'gui'
53
54
  Description-Content-Type: text/markdown
54
55
 
55
56
  <p align="center">
56
- <img src="docs/_static/logo.png" alt="pyKrait" width="200">
57
+ <img src="https://raw.githubusercontent.com/Winklerlab-HD/pykrait/main/docs/_static/logo.png" alt="pyKrait" width="200">
57
58
  </p>
58
59
 
59
- <h1 align="center">pyKrait</h1>
60
-
60
+ <h1 style="text-align: center;">pyKrait</h1>
61
61
 
62
62
  <p align="center">
63
63
  <a href="https://pypi.org/project/pykrait/"><img src="https://img.shields.io/pypi/v/pykrait" alt="PyPI"></a>
64
64
  <a href="https://pypi.org/project/pykrait/"><img src="https://img.shields.io/pypi/pyversions/pykrait" alt="Python versions"></a>
65
- <a href="https://github.com/&lt;your-org&gt;/pykrait/actions"><img src="https://img.shields.io/github/actions/workflow/status/&lt;your-org&gt;/pykrait/CI.yaml" alt="CI"></a>
65
+ <a href="https://github.com/Winklerlab-HD/pykrait/actions"><img src="https://img.shields.io/github/actions/workflow/status/Winklerlab-HD/pykrait/CI.yaml" alt="CI"></a>
66
66
  <a href="https://pykrait.readthedocs.io"><img src="https://img.shields.io/readthedocs/pykrait" alt="Docs"></a>
67
67
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License"></a>
68
68
  </p>
@@ -74,34 +74,34 @@ Description-Content-Type: text/markdown
74
74
 
75
75
 
76
76
  <p align="center">
77
- <img src="docs/_static/pyKrait_steps.png" alt="Analysis Steps of the pykrait package" width="600">
77
+ <img src="https://raw.githubusercontent.com/Winklerlab-HD/pykrait/main/docs/_static/pyKrait_steps.png" alt="Analysis Steps of the pykrait package" width="600">
78
78
  </p>
79
79
 
80
80
  ## Overview
81
-
82
81
  pyKrait (**py**thon **C**alcium **r**ecording **a**nalysis and **i**nterpretation **t**oolbox) automatically processes calcium-imaging videos (.czi, .tif, .tiff) and computes calcium activity, peak statistics, periodicity scores, and neighbour-aware synchronicity z-scores.
83
82
 
84
83
  ## Installation
85
84
 
86
85
  It is recommended to install pyKrait in a virtual environment using either venv or uv. The graphical user interface is optional, install it with `pip install "pykrait[gui]"`.
87
86
 
88
- ### Standard `venv`
87
+ ### Using `uv`
89
88
 
90
89
  ```bash
91
- python -m venv .venv
90
+ uv venv --python 3.12
92
91
  source .venv/bin/activate # Windows: .venv\Scripts\activate
93
- pip install --upgrade pip
94
- pip install pykrait
92
+ uv pip install 'pykrait[gui]'
95
93
  ```
96
94
 
97
- ### Using `uv`
95
+ ### Standard `venv`
98
96
 
99
97
  ```bash
100
- uv venv --python 3.11
98
+ python -m venv .venv
101
99
  source .venv/bin/activate # Windows: .venv\Scripts\activate
102
- uv pip install pykrait
100
+ pip install --upgrade pip
101
+ pip install "pykrait[gui]"
103
102
  ```
104
103
 
104
+
105
105
  ## Quickstart
106
106
 
107
107
  ### Launch the GUI
@@ -110,8 +110,11 @@ From inside the activated virtual environment:
110
110
  ```bash
111
111
  python -m pykrait
112
112
  ```
113
-
114
- The GUI walks you through video selection, segmentation, peak detection, periodicity, and synchronicity, and lets you save results to disk.
113
+ or
114
+ ```bash
115
+ uv run python -m pykrait
116
+ ```
117
+ The GUI then walks you through video selection, segmentation, peak detection, periodicity, and synchronicity, and lets you save results to disk.
115
118
 
116
119
  ### Batch process an entire folder
117
120
 
@@ -131,12 +134,14 @@ concat_analysis_files("/path/to/videos", filetype="output")
131
134
 
132
135
  This produces one `Analysis_<video>/` directory per video and an `analysis_output_overview.csv` at the root of the folder.
133
136
 
134
- ## Examples
137
+ To analyze videos in subfolders, pass `recursive=True`; to analyze only some of the acquisitions, pass `filter_string` / `exclude_string` (e.g. `filter_string="*_timeseries.czi"`, where a pattern with `*`, `?` or `[]` matches the whole file name and anything else matches as a substring).
138
+
139
+ ## Example Notebooks
135
140
 
136
141
  | Scenario | Notebook |
137
142
  |----------|----------|
138
- | Run a folder with custom parameters | [`notebooks/example_batch_with_custom_parameters.ipynb`](notebooks/example_batch_with_custom_parameters.ipynb) |
139
- | Re-run a folder reusing saved parameters and cached masks | [`notebooks/example_batch_reusing_parameters.ipynb`](notebooks/example_batch_reusing_parameters.ipynb) |
143
+ | Run a folder with custom parameters | [notebooks/example_batch_with_custom_parameters](https://github.com/Winklerlab-HD/pykrait/blob/main/notebooks/example_batch_with_custom_parameters.ipynb) |
144
+ | Re-run a folder reusing saved parameters and cached masks | [notebooks/example_batch_reusing_parameters](https://github.com/Winklerlab-HD/pykrait/blob/main/notebooks/example_batch_reusing_parameters.ipynb) |
140
145
  | Inspect or refine a single video interactively | Launch the GUI: `python -m pykrait` |
141
146
 
142
147
  ## How it works
@@ -144,19 +149,24 @@ This produces one `Analysis_<video>/` directory per video and an `analysis_outpu
144
149
  For each video, pyKrait runs the following stages:
145
150
 
146
151
  1. **Load** — lazy `Dask` array via `bioio` (`.czi`, `.tif`, `.tiff`).
147
- 2. **Project** — STD or SUM projection across time, with optional CLAHE.
148
- 3. **Segment** — Cellpose (`cpsam` by default, or a custom model path).
149
- 4. **Extract** — per-cell mean intensity per frame, computed lazily on Dask.
150
- 5. **Detrend** — Blackman-windowed sinc filter (cf. pyBOAT).
151
- 6. **Detect peaks** — `scipy.signal.find_peaks` with width / height / prominence thresholds.
152
- 7. **Periodicity** — STD and CoV of inter-peak intervals, scored against a shuffled-peaks null.
153
- 8. **Adjacency** — neighbour graph from segmented masks (kernel-based proximity).
154
- 9. **Synchronicity** — co-firing peaks within a time window and topological distance, z-scored against label-shuffled controls.
152
+ 2. **Correct motion** *(optional, off by default)* — align every frame to a template, either by a rigid translation or by a mesh-based / polynomial optical-flow warp.
153
+ 3. **Project** — STD or SUM projection across time, with optional CLAHE.
154
+ 4. **Segment** — Cellpose (`cpsam` by default, or a custom model path).
155
+ 5. **Extract** — per-cell mean intensity per frame, computed lazily on Dask.
156
+ 6. **Detrend** — Blackman-windowed sinc filter (cf. pyBOAT).
157
+ 7. **Detect peaks** — `scipy.signal.find_peaks` with width / height / prominence thresholds.
158
+ 8. **Periodicity** — STD and CoV of inter-peak intervals, scored against a shuffled-peaks null.
159
+ 9. **Adjacency** — neighbour graph from segmented masks (kernel-based proximity).
160
+ 10. **Synchronicity** — co-firing peaks within a time window and topological distance, z-scored against label-shuffled controls.
155
161
 
156
162
  ## Documentation
157
163
 
158
164
  Full API reference and tutorials: **<https://pykrait.readthedocs.io/en/latest/>**.
159
165
 
166
+ ## Citation
167
+
168
+ If you use pyKrait, please cite the [preprint](https://www.biorxiv.org/content/10.64898/2026.05.10.723715v1): Hebach, N. R., Olshausen, N., Schlag, J., Mayer, C. D., Henkenjohann, J., Kraft, T., Bang, S., Thommek, C., Nürnberg, C., Horsak, N. E., Hoffmann, D. C., Kourtesakis, A., Porzberg, N., Flores Valle, A., Linke, C. Z., Yang, Y., Azorín, D. D., Hausmann, D., Venkataramani, V., … Karreman, M. A. (2026). Collective cancer cell calcium activity drives brain metastasis. bioRxiv, 2026.05.10.723715. https://doi.org/10.64898/2026.05.10.723715
169
+
160
170
  ## Acknowledgements
161
171
 
162
172
  pyKrait builds on and heavily uses prior open-source work:
@@ -1,14 +1,13 @@
1
1
  <p align="center">
2
- <img src="docs/_static/logo.png" alt="pyKrait" width="200">
2
+ <img src="https://raw.githubusercontent.com/Winklerlab-HD/pykrait/main/docs/_static/logo.png" alt="pyKrait" width="200">
3
3
  </p>
4
4
 
5
- <h1 align="center">pyKrait</h1>
6
-
5
+ <h1 style="text-align: center;">pyKrait</h1>
7
6
 
8
7
  <p align="center">
9
8
  <a href="https://pypi.org/project/pykrait/"><img src="https://img.shields.io/pypi/v/pykrait" alt="PyPI"></a>
10
9
  <a href="https://pypi.org/project/pykrait/"><img src="https://img.shields.io/pypi/pyversions/pykrait" alt="Python versions"></a>
11
- <a href="https://github.com/&lt;your-org&gt;/pykrait/actions"><img src="https://img.shields.io/github/actions/workflow/status/&lt;your-org&gt;/pykrait/CI.yaml" alt="CI"></a>
10
+ <a href="https://github.com/Winklerlab-HD/pykrait/actions"><img src="https://img.shields.io/github/actions/workflow/status/Winklerlab-HD/pykrait/CI.yaml" alt="CI"></a>
12
11
  <a href="https://pykrait.readthedocs.io"><img src="https://img.shields.io/readthedocs/pykrait" alt="Docs"></a>
13
12
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="License"></a>
14
13
  </p>
@@ -20,34 +19,34 @@
20
19
 
21
20
 
22
21
  <p align="center">
23
- <img src="docs/_static/pyKrait_steps.png" alt="Analysis Steps of the pykrait package" width="600">
22
+ <img src="https://raw.githubusercontent.com/Winklerlab-HD/pykrait/main/docs/_static/pyKrait_steps.png" alt="Analysis Steps of the pykrait package" width="600">
24
23
  </p>
25
24
 
26
25
  ## Overview
27
-
28
26
  pyKrait (**py**thon **C**alcium **r**ecording **a**nalysis and **i**nterpretation **t**oolbox) automatically processes calcium-imaging videos (.czi, .tif, .tiff) and computes calcium activity, peak statistics, periodicity scores, and neighbour-aware synchronicity z-scores.
29
27
 
30
28
  ## Installation
31
29
 
32
30
  It is recommended to install pyKrait in a virtual environment using either venv or uv. The graphical user interface is optional, install it with `pip install "pykrait[gui]"`.
33
31
 
34
- ### Standard `venv`
32
+ ### Using `uv`
35
33
 
36
34
  ```bash
37
- python -m venv .venv
35
+ uv venv --python 3.12
38
36
  source .venv/bin/activate # Windows: .venv\Scripts\activate
39
- pip install --upgrade pip
40
- pip install pykrait
37
+ uv pip install 'pykrait[gui]'
41
38
  ```
42
39
 
43
- ### Using `uv`
40
+ ### Standard `venv`
44
41
 
45
42
  ```bash
46
- uv venv --python 3.11
43
+ python -m venv .venv
47
44
  source .venv/bin/activate # Windows: .venv\Scripts\activate
48
- uv pip install pykrait
45
+ pip install --upgrade pip
46
+ pip install "pykrait[gui]"
49
47
  ```
50
48
 
49
+
51
50
  ## Quickstart
52
51
 
53
52
  ### Launch the GUI
@@ -56,8 +55,11 @@ From inside the activated virtual environment:
56
55
  ```bash
57
56
  python -m pykrait
58
57
  ```
59
-
60
- The GUI walks you through video selection, segmentation, peak detection, periodicity, and synchronicity, and lets you save results to disk.
58
+ or
59
+ ```bash
60
+ uv run python -m pykrait
61
+ ```
62
+ The GUI then walks you through video selection, segmentation, peak detection, periodicity, and synchronicity, and lets you save results to disk.
61
63
 
62
64
  ### Batch process an entire folder
63
65
 
@@ -77,12 +79,14 @@ concat_analysis_files("/path/to/videos", filetype="output")
77
79
 
78
80
  This produces one `Analysis_<video>/` directory per video and an `analysis_output_overview.csv` at the root of the folder.
79
81
 
80
- ## Examples
82
+ To analyze videos in subfolders, pass `recursive=True`; to analyze only some of the acquisitions, pass `filter_string` / `exclude_string` (e.g. `filter_string="*_timeseries.czi"`, where a pattern with `*`, `?` or `[]` matches the whole file name and anything else matches as a substring).
83
+
84
+ ## Example Notebooks
81
85
 
82
86
  | Scenario | Notebook |
83
87
  |----------|----------|
84
- | Run a folder with custom parameters | [`notebooks/example_batch_with_custom_parameters.ipynb`](notebooks/example_batch_with_custom_parameters.ipynb) |
85
- | Re-run a folder reusing saved parameters and cached masks | [`notebooks/example_batch_reusing_parameters.ipynb`](notebooks/example_batch_reusing_parameters.ipynb) |
88
+ | Run a folder with custom parameters | [notebooks/example_batch_with_custom_parameters](https://github.com/Winklerlab-HD/pykrait/blob/main/notebooks/example_batch_with_custom_parameters.ipynb) |
89
+ | Re-run a folder reusing saved parameters and cached masks | [notebooks/example_batch_reusing_parameters](https://github.com/Winklerlab-HD/pykrait/blob/main/notebooks/example_batch_reusing_parameters.ipynb) |
86
90
  | Inspect or refine a single video interactively | Launch the GUI: `python -m pykrait` |
87
91
 
88
92
  ## How it works
@@ -90,19 +94,24 @@ This produces one `Analysis_<video>/` directory per video and an `analysis_outpu
90
94
  For each video, pyKrait runs the following stages:
91
95
 
92
96
  1. **Load** — lazy `Dask` array via `bioio` (`.czi`, `.tif`, `.tiff`).
93
- 2. **Project** — STD or SUM projection across time, with optional CLAHE.
94
- 3. **Segment** — Cellpose (`cpsam` by default, or a custom model path).
95
- 4. **Extract** — per-cell mean intensity per frame, computed lazily on Dask.
96
- 5. **Detrend** — Blackman-windowed sinc filter (cf. pyBOAT).
97
- 6. **Detect peaks** — `scipy.signal.find_peaks` with width / height / prominence thresholds.
98
- 7. **Periodicity** — STD and CoV of inter-peak intervals, scored against a shuffled-peaks null.
99
- 8. **Adjacency** — neighbour graph from segmented masks (kernel-based proximity).
100
- 9. **Synchronicity** — co-firing peaks within a time window and topological distance, z-scored against label-shuffled controls.
97
+ 2. **Correct motion** *(optional, off by default)* — align every frame to a template, either by a rigid translation or by a mesh-based / polynomial optical-flow warp.
98
+ 3. **Project** — STD or SUM projection across time, with optional CLAHE.
99
+ 4. **Segment** — Cellpose (`cpsam` by default, or a custom model path).
100
+ 5. **Extract** — per-cell mean intensity per frame, computed lazily on Dask.
101
+ 6. **Detrend** — Blackman-windowed sinc filter (cf. pyBOAT).
102
+ 7. **Detect peaks** — `scipy.signal.find_peaks` with width / height / prominence thresholds.
103
+ 8. **Periodicity** — STD and CoV of inter-peak intervals, scored against a shuffled-peaks null.
104
+ 9. **Adjacency** — neighbour graph from segmented masks (kernel-based proximity).
105
+ 10. **Synchronicity** — co-firing peaks within a time window and topological distance, z-scored against label-shuffled controls.
101
106
 
102
107
  ## Documentation
103
108
 
104
109
  Full API reference and tutorials: **<https://pykrait.readthedocs.io/en/latest/>**.
105
110
 
111
+ ## Citation
112
+
113
+ If you use pyKrait, please cite the [preprint](https://www.biorxiv.org/content/10.64898/2026.05.10.723715v1): Hebach, N. R., Olshausen, N., Schlag, J., Mayer, C. D., Henkenjohann, J., Kraft, T., Bang, S., Thommek, C., Nürnberg, C., Horsak, N. E., Hoffmann, D. C., Kourtesakis, A., Porzberg, N., Flores Valle, A., Linke, C. Z., Yang, Y., Azorín, D. D., Hausmann, D., Venkataramani, V., … Karreman, M. A. (2026). Collective cancer cell calcium activity drives brain metastasis. bioRxiv, 2026.05.10.723715. https://doi.org/10.64898/2026.05.10.723715
114
+
106
115
  ## Acknowledgements
107
116
 
108
117
  pyKrait builds on and heavily uses prior open-source work:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pykrait"
3
- version = "0.3.3"
3
+ version = "0.4.0"
4
4
  description = "Python Calcium Recording Analysis and Interpretation Toolbox"
5
5
  readme = { file = "README.md", content-type = "text/markdown" }
6
6
  license = { text = "MIT" }
@@ -8,37 +8,44 @@ authors = [
8
8
  { name = "phaleanopsis", email = "22663502+phaleanopsis@users.noreply.github.com" }
9
9
  ]
10
10
  requires-python = ">=3.11"
11
+ # bioio plugins and cellpose change their APIs between major versions (bioio-czi 4 removed
12
+ # use_aicspylibczi), so they are capped at the next untested major version
11
13
  dependencies = [
12
14
  "aicspylibczi>=3.3.1",
13
- "bioio>=3.2.0",
14
- "bioio-czi>=2.4.0",
15
- "bioio-ome-tiff>=1.1.0",
16
- "bioio-tifffile>=1.3.0",
17
- "bioio-imageio",
18
- "cellpose>=4.0.8",
15
+ "bioio>=3.2.0,<4",
16
+ "bioio-czi>=2.4.0,<5",
17
+ "bioio-ome-tiff>=1.1.0,<2",
18
+ "bioio-tifffile>=1.3.0,<2",
19
+ "bioio-imageio<2",
20
+ "cellpose>=4.0.8,<5",
19
21
  "dask>=2025.5.1",
20
22
  "fsspec>=2022.8.0",
21
23
  "imagecodecs>=2025.3.30",
22
- "lxml>=5.4.0",
23
24
  "matplotlib>=3.10.3",
24
- "numcodecs<0.16",
25
+ # zarr 2 imports numcodecs.blosc.cbuffer_sizes, which numcodecs 0.16 removed
26
+ "numcodecs>=0.15,<0.16",
25
27
  "numpy>2",
26
- "ome-types>=0.5.3",
27
- "ome-zarr>=0.10.2",
28
- "pip>=25.1.1",
29
- "pydantic<2",
28
+ "opencv-python-headless>=4.10.0.84",
30
29
  "pyyaml>=6.0.2",
31
- "resource-backed-dask-array>=0.1.0",
32
30
  "scikit-image>=0.25.2",
33
31
  "scipy>=1.15.3",
32
+ "tifffile>=2025.5.10",
33
+ "tqdm>=4.66",
34
34
  "xarray>=2025.12",
35
35
  "pandas>=2.3.1",
36
+ "psutil>=6.0",
37
+ # reads CZI files much faster than bioio; there is no wheel for Intel Macs, which
38
+ # fall back to bioio
39
+ "pylibCZIrw>=6.0; sys_platform != 'darwin' or platform_machine != 'x86_64'",
36
40
  "zstandard>=0.23.0",
37
- "scikit-learn>=1.7.2",
38
41
  "zarr>=2.15.0",
39
- "pylibczirw>=6.0.1",
40
42
  ]
41
-
43
+ classifiers = [
44
+ "Programming Language :: Python :: 3",
45
+ "Programming Language :: Python :: 3.11",
46
+ "Programming Language :: Python :: 3.12",
47
+ "Programming Language :: Python :: 3.13",
48
+ ]
42
49
  [project.optional-dependencies]
43
50
  gui = [
44
51
  "pyside6>=6.9.1",
@@ -55,7 +62,7 @@ dev = [
55
62
  "tomli>=2.2.1",
56
63
  "twine>=6.1.0",
57
64
  "pytest>=8.4.1",
58
- "pytest-cov",
65
+ "pytest-cov>=6.0",
59
66
  "myst-parser>=5.0.0",
60
67
  ]
61
68
 
@@ -95,17 +102,7 @@ source = ["pykrait"]
95
102
  show_missing = true
96
103
  skip_covered = true
97
104
 
98
- [tool.setuptools.package-data]
99
- "pykrait.gui.resources" = ["logo.png"]
100
-
101
105
  [tool.ruff]
102
106
  src = ["src"]
103
107
  line-length = 88
104
108
  target-version = "py311"
105
-
106
- classifiers = [
107
- "Programming Language :: Python :: 3",
108
- "Programming Language :: Python :: 3.11",
109
- "Programming Language :: Python :: 3.12",
110
- "Programming Language :: Python :: 3.13",
111
- ]
@@ -0,0 +1,27 @@
1
+ import logging
2
+ import sys
3
+
4
+
5
+ class _StdoutHandler(logging.StreamHandler):
6
+ """Writes to the current sys.stdout, which notebooks and tests replace after import."""
7
+
8
+ @property
9
+ def stream(self):
10
+ return sys.stdout
11
+
12
+ @stream.setter
13
+ def stream(self, value):
14
+ pass
15
+
16
+
17
+ # pykrait reports its progress through the "pykrait" logger. By default the messages are
18
+ # printed to stdout as before; silence them with
19
+ # logging.getLogger("pykrait").setLevel(logging.WARNING), or remove this handler to
20
+ # route them through your own logging configuration.
21
+ _logger = logging.getLogger(__name__)
22
+ _handler = _StdoutHandler()
23
+ _handler.setFormatter(logging.Formatter("%(message)s"))
24
+ _logger.addHandler(_handler)
25
+ if _logger.level == logging.NOTSET: # keep a level that was set before the import
26
+ _logger.setLevel(logging.INFO)
27
+ _logger.propagate = False
@@ -0,0 +1,20 @@
1
+ """
2
+ Copyright © 2026 Phaleanopsis
3
+ """
4
+
5
+ # src/pykrait/__main__.py
6
+ import sys
7
+
8
+ try:
9
+ from pykrait.gui.app import AppController
10
+ except ImportError as e:
11
+ if getattr(e, "name", None) and e.name.startswith("PySide6"):
12
+ sys.exit(
13
+ "The pyKrait GUI needs the optional GUI dependencies. "
14
+ 'Install them with: pip install "pykrait[gui]"'
15
+ )
16
+ raise
17
+
18
+ if __name__ == "__main__":
19
+ app_controller = AppController()
20
+ app_controller.run()
@@ -1,3 +1,4 @@
1
+ import logging
1
2
  import sys
2
3
  import os
3
4
  import warnings
@@ -8,15 +9,18 @@ from pykrait.gui.fileselection import FileSelectionWindow
8
9
  from pykrait.gui.mainwindow import MainWindow
9
10
  from pykrait.io.files import get_pykrait_version
10
11
 
12
+ logger = logging.getLogger(__name__)
13
+
14
+
11
15
  def warning_no_source_line(message, category, filename, lineno, file=None, line=None):
12
16
  return f"{filename}:{lineno}: {category.__name__}: {message}\n"
13
17
 
18
+
14
19
  class AppController:
15
20
  def __init__(self):
16
-
17
21
  # format warnings to not print the source line every time
18
22
  warnings.formatwarning = warning_no_source_line
19
-
23
+
20
24
  self.app = QApplication(sys.argv)
21
25
 
22
26
  # Loading icon from resources
@@ -25,7 +29,6 @@ class AppController:
25
29
  from importlib.resources import files
26
30
 
27
31
  icon_path = files("pykrait.gui.resources").joinpath("logo.png")
28
- os.path.exists(icon_path)
29
32
  except Exception:
30
33
  icon_path = os.path.abspath(
31
34
  os.path.join(
@@ -36,28 +39,21 @@ class AppController:
36
39
  "logo.png",
37
40
  )
38
41
  )
39
- os.path.exists(icon_path)
40
42
  if os.path.exists(icon_path):
41
- appIcon.addFile(str(icon_path), QSize(16, 16))
42
- appIcon.addFile(str(icon_path), QSize(24, 24))
43
- appIcon.addFile(str(icon_path), QSize(32, 32))
44
- appIcon.addFile(str(icon_path), QSize(48, 48))
45
- appIcon.addFile(str(icon_path), QSize(64, 64))
46
- appIcon.addFile(str(icon_path), QSize(256, 256))
43
+ for size in (16, 24, 32, 48, 64, 256):
44
+ appIcon.addFile(str(icon_path), QSize(size, size))
47
45
  self.app.setWindowIcon(appIcon)
48
46
  else:
49
- print(f"Warning: Icon file not found at {icon_path}")
47
+ logger.warning(f"Warning: Icon file not found at {icon_path}")
50
48
 
51
49
  # Set application name and version
52
50
  self.app.setApplicationName("pyKrait")
53
51
  version = get_pykrait_version()
54
52
  if version is not None:
55
53
  self.app.setApplicationVersion(version)
56
- self.file_selection = FileSelectionWindow()
57
- self.file_selection.analysis_complete.connect(self.on_analysis_complete)
58
- self.file_selection.show()
59
-
60
- #updating the style
54
+ self._open_file_selection()
55
+
56
+ # updating the style
61
57
  self.app.setStyle("Fusion")
62
58
  light_palette = self.app.style().standardPalette()
63
59
  self.app.setPalette(light_palette)
@@ -72,6 +68,7 @@ class AppController:
72
68
  # Launch main window, keep reference
73
69
  self.main_window = MainWindow(
74
70
  frames=results["frames"],
71
+ display_channel=results.get("intensity_channel", 0),
75
72
  mask=results["masks"],
76
73
  rois=results["rois"],
77
74
  mean_intensities=results["mean_intensities"],
@@ -81,6 +78,11 @@ class AppController:
81
78
  )
82
79
  self.main_window.show()
83
80
 
81
+ def _open_file_selection(self):
82
+ self.file_selection = FileSelectionWindow()
83
+ self.file_selection.analysis_complete.connect(self.on_analysis_complete)
84
+ self.file_selection.show()
85
+
84
86
  def run(self):
85
87
  sys.exit(self.app.exec())
86
88
 
@@ -89,6 +91,4 @@ class AppController:
89
91
  self.main_window.close()
90
92
  self.main_window = None
91
93
 
92
- self.file_selection = FileSelectionWindow()
93
- self.file_selection.analysis_complete.connect(self.on_analysis_complete)
94
- self.file_selection.show()
94
+ self._open_file_selection()