calab 0.1.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.
calab-0.1.0/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ *.pyc
calab-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Daniel Aharoni
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
calab-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,173 @@
1
+ Metadata-Version: 2.4
2
+ Name: calab
3
+ Version: 0.1.0
4
+ Summary: CaLab: calcium imaging analysis tools — deconvolution and data preparation
5
+ Project-URL: Homepage, https://github.com/miniscope/CaLab
6
+ Project-URL: Repository, https://github.com/miniscope/CaLab
7
+ Project-URL: Issues, https://github.com/miniscope/CaLab/issues
8
+ Author: Daniel Aharoni
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: FISTA,calcium-imaging,deconvolution,neuroscience,spike-inference
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
21
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
22
+ Classifier: Typing :: Typed
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: numpy>=1.24
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7.0; extra == 'dev'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # CaLab Python
30
+
31
+ Calcium imaging analysis tools — deconvolution and data preparation. Python companion package for the [CaLab](https://github.com/miniscope/CaLab) tools.
32
+
33
+ ## Installation
34
+
35
+ ```bash
36
+ pip install calab
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ```python
42
+ import calab
43
+
44
+ # Build a calcium kernel
45
+ kernel = calab.build_kernel(tau_rise=0.02, tau_decay=0.4, fs=30.0)
46
+
47
+ # Get AR(2) coefficients
48
+ g1, g2, d, r = calab.tau_to_ar2(tau_rise=0.02, tau_decay=0.4, fs=30.0)
49
+
50
+ # Compute Lipschitz constant for FISTA step size
51
+ L = calab.compute_lipschitz(kernel)
52
+ ```
53
+
54
+ ## Deconvolution
55
+
56
+ Run FISTA deconvolution matching the CaTune web app's Rust solver exactly
57
+ (baseline estimation + lambda scaling by kernel DC gain):
58
+
59
+ ```python
60
+ import numpy as np
61
+ import calab
62
+
63
+ # Load your calcium traces (n_cells x n_timepoints)
64
+ traces = np.load("my_traces.npy")
65
+
66
+ # Basic: returns non-negative activity array
67
+ activity = calab.run_deconvolution(traces, fs=30.0, tau_r=0.02, tau_d=0.4, lam=0.01)
68
+
69
+ # Full: returns activity, baseline, reconvolution, iterations, converged
70
+ result = calab.run_deconvolution_full(traces, fs=30.0, tau_r=0.02, tau_d=0.4, lam=0.01)
71
+ print(f"Baseline: {result.baseline}, Converged: {result.converged}")
72
+ ```
73
+
74
+ > **Note:** The deconvolved output represents scaled neural activity, not discrete
75
+ > spikes or firing rates. The signal is scaled by an unknown constant (indicator
76
+ > expression level, optical path, etc.), so absolute values should not be
77
+ > interpreted as spike counts.
78
+
79
+ ## Bandpass Filter
80
+
81
+ Apply the same FFT bandpass filter used in the CaTune web app:
82
+
83
+ ```python
84
+ filtered = calab.bandpass_filter(trace, tau_rise=0.02, tau_decay=0.4, fs=100.0)
85
+ ```
86
+
87
+ ## Using CaTune Export JSON
88
+
89
+ Load parameters from a CaTune export JSON and run deconvolution:
90
+
91
+ ```python
92
+ import calab
93
+
94
+ # Load export params
95
+ params = calab.load_export_params("catune-params-2025-01-15.json")
96
+ # -> {'tau_rise': 0.02, 'tau_decay': 0.4, 'lambda_': 0.01, 'fs': 30.0, 'filter_enabled': False}
97
+
98
+ # One-step pipeline: loads params, optionally filters, and deconvolves
99
+ activity = calab.deconvolve_from_export(traces, "catune-params-2025-01-15.json")
100
+ ```
101
+
102
+ ## Saving Data for CaTune
103
+
104
+ ```python
105
+ import calab
106
+
107
+ calab.save_for_tuning(traces, fs=30.0, path="my_recording")
108
+ # Creates my_recording.npy + my_recording_metadata.json
109
+ # Load into CaTune browser tool via the .npy file
110
+ ```
111
+
112
+ ## Converting from CaImAn / Minian
113
+
114
+ CaLab works with raw calcium traces extracted by any pipeline. Use
115
+ `save_for_tuning()` to convert extracted traces into CaTune-compatible
116
+ format. No additional dependencies are required -- users extract arrays
117
+ with their existing pipeline tools.
118
+
119
+ ### CaImAn
120
+
121
+ ```python
122
+ import h5py
123
+ import calab
124
+
125
+ with h5py.File("caiman_results.hdf5", "r") as f:
126
+ traces = f["estimates/C"][:] # shape: (n_cells, n_timepoints)
127
+ fs = float(f["params/data/fr"][()])
128
+
129
+ calab.save_for_tuning(traces, fs, "my_recording")
130
+ # -> my_recording.npy + my_recording_metadata.json, ready for CaTune
131
+ ```
132
+
133
+ ### Minian
134
+
135
+ ```python
136
+ import zarr
137
+ import calab
138
+
139
+ store = zarr.open("minian_output", mode="r")
140
+ traces = store["C"][:] # shape: (n_cells, n_frames)
141
+ fs = 30.0 # user must know their frame rate
142
+
143
+ calab.save_for_tuning(traces, fs, "my_recording")
144
+ ```
145
+
146
+ ### Then deconvolve
147
+
148
+ After tuning parameters in CaTune's browser interface, export your settings
149
+ and apply them in Python:
150
+
151
+ ```python
152
+ import numpy as np
153
+ import calab
154
+
155
+ traces = np.load("my_recording.npy")
156
+ activity = calab.deconvolve_from_export(traces, "catune-params.json")
157
+ # activity is non-negative deconvolved neural activity (scaled by unknown constant)
158
+ ```
159
+
160
+ ## API Reference
161
+
162
+ | Function | Description |
163
+ | ------------------------------------------------------- | ----------------------------------------- |
164
+ | `build_kernel(tau_rise, tau_decay, fs)` | Build double-exponential calcium kernel |
165
+ | `tau_to_ar2(tau_rise, tau_decay, fs)` | Derive AR(2) coefficients from tau values |
166
+ | `compute_lipschitz(kernel)` | Lipschitz constant for FISTA step size |
167
+ | `run_deconvolution(traces, fs, tau_r, tau_d, lam)` | FISTA deconvolution, returns activity |
168
+ | `run_deconvolution_full(traces, fs, tau_r, tau_d, lam)` | Full result with baseline, reconvolution |
169
+ | `bandpass_filter(trace, tau_rise, tau_decay, fs)` | FFT bandpass filter from kernel params |
170
+ | `save_for_tuning(traces, fs, path)` | Save traces for CaTune browser tool |
171
+ | `load_tuning_data(path)` | Load traces saved by save_for_tuning |
172
+ | `load_export_params(path)` | Load params from CaTune export JSON |
173
+ | `deconvolve_from_export(traces, params_path)` | Full pipeline: load params + deconvolve |
calab-0.1.0/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # CaLab Python
2
+
3
+ Calcium imaging analysis tools — deconvolution and data preparation. Python companion package for the [CaLab](https://github.com/miniscope/CaLab) tools.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install calab
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```python
14
+ import calab
15
+
16
+ # Build a calcium kernel
17
+ kernel = calab.build_kernel(tau_rise=0.02, tau_decay=0.4, fs=30.0)
18
+
19
+ # Get AR(2) coefficients
20
+ g1, g2, d, r = calab.tau_to_ar2(tau_rise=0.02, tau_decay=0.4, fs=30.0)
21
+
22
+ # Compute Lipschitz constant for FISTA step size
23
+ L = calab.compute_lipschitz(kernel)
24
+ ```
25
+
26
+ ## Deconvolution
27
+
28
+ Run FISTA deconvolution matching the CaTune web app's Rust solver exactly
29
+ (baseline estimation + lambda scaling by kernel DC gain):
30
+
31
+ ```python
32
+ import numpy as np
33
+ import calab
34
+
35
+ # Load your calcium traces (n_cells x n_timepoints)
36
+ traces = np.load("my_traces.npy")
37
+
38
+ # Basic: returns non-negative activity array
39
+ activity = calab.run_deconvolution(traces, fs=30.0, tau_r=0.02, tau_d=0.4, lam=0.01)
40
+
41
+ # Full: returns activity, baseline, reconvolution, iterations, converged
42
+ result = calab.run_deconvolution_full(traces, fs=30.0, tau_r=0.02, tau_d=0.4, lam=0.01)
43
+ print(f"Baseline: {result.baseline}, Converged: {result.converged}")
44
+ ```
45
+
46
+ > **Note:** The deconvolved output represents scaled neural activity, not discrete
47
+ > spikes or firing rates. The signal is scaled by an unknown constant (indicator
48
+ > expression level, optical path, etc.), so absolute values should not be
49
+ > interpreted as spike counts.
50
+
51
+ ## Bandpass Filter
52
+
53
+ Apply the same FFT bandpass filter used in the CaTune web app:
54
+
55
+ ```python
56
+ filtered = calab.bandpass_filter(trace, tau_rise=0.02, tau_decay=0.4, fs=100.0)
57
+ ```
58
+
59
+ ## Using CaTune Export JSON
60
+
61
+ Load parameters from a CaTune export JSON and run deconvolution:
62
+
63
+ ```python
64
+ import calab
65
+
66
+ # Load export params
67
+ params = calab.load_export_params("catune-params-2025-01-15.json")
68
+ # -> {'tau_rise': 0.02, 'tau_decay': 0.4, 'lambda_': 0.01, 'fs': 30.0, 'filter_enabled': False}
69
+
70
+ # One-step pipeline: loads params, optionally filters, and deconvolves
71
+ activity = calab.deconvolve_from_export(traces, "catune-params-2025-01-15.json")
72
+ ```
73
+
74
+ ## Saving Data for CaTune
75
+
76
+ ```python
77
+ import calab
78
+
79
+ calab.save_for_tuning(traces, fs=30.0, path="my_recording")
80
+ # Creates my_recording.npy + my_recording_metadata.json
81
+ # Load into CaTune browser tool via the .npy file
82
+ ```
83
+
84
+ ## Converting from CaImAn / Minian
85
+
86
+ CaLab works with raw calcium traces extracted by any pipeline. Use
87
+ `save_for_tuning()` to convert extracted traces into CaTune-compatible
88
+ format. No additional dependencies are required -- users extract arrays
89
+ with their existing pipeline tools.
90
+
91
+ ### CaImAn
92
+
93
+ ```python
94
+ import h5py
95
+ import calab
96
+
97
+ with h5py.File("caiman_results.hdf5", "r") as f:
98
+ traces = f["estimates/C"][:] # shape: (n_cells, n_timepoints)
99
+ fs = float(f["params/data/fr"][()])
100
+
101
+ calab.save_for_tuning(traces, fs, "my_recording")
102
+ # -> my_recording.npy + my_recording_metadata.json, ready for CaTune
103
+ ```
104
+
105
+ ### Minian
106
+
107
+ ```python
108
+ import zarr
109
+ import calab
110
+
111
+ store = zarr.open("minian_output", mode="r")
112
+ traces = store["C"][:] # shape: (n_cells, n_frames)
113
+ fs = 30.0 # user must know their frame rate
114
+
115
+ calab.save_for_tuning(traces, fs, "my_recording")
116
+ ```
117
+
118
+ ### Then deconvolve
119
+
120
+ After tuning parameters in CaTune's browser interface, export your settings
121
+ and apply them in Python:
122
+
123
+ ```python
124
+ import numpy as np
125
+ import calab
126
+
127
+ traces = np.load("my_recording.npy")
128
+ activity = calab.deconvolve_from_export(traces, "catune-params.json")
129
+ # activity is non-negative deconvolved neural activity (scaled by unknown constant)
130
+ ```
131
+
132
+ ## API Reference
133
+
134
+ | Function | Description |
135
+ | ------------------------------------------------------- | ----------------------------------------- |
136
+ | `build_kernel(tau_rise, tau_decay, fs)` | Build double-exponential calcium kernel |
137
+ | `tau_to_ar2(tau_rise, tau_decay, fs)` | Derive AR(2) coefficients from tau values |
138
+ | `compute_lipschitz(kernel)` | Lipschitz constant for FISTA step size |
139
+ | `run_deconvolution(traces, fs, tau_r, tau_d, lam)` | FISTA deconvolution, returns activity |
140
+ | `run_deconvolution_full(traces, fs, tau_r, tau_d, lam)` | Full result with baseline, reconvolution |
141
+ | `bandpass_filter(trace, tau_rise, tau_decay, fs)` | FFT bandpass filter from kernel params |
142
+ | `save_for_tuning(traces, fs, path)` | Save traces for CaTune browser tool |
143
+ | `load_tuning_data(path)` | Load traces saved by save_for_tuning |
144
+ | `load_export_params(path)` | Load params from CaTune export JSON |
145
+ | `deconvolve_from_export(traces, params_path)` | Full pipeline: load params + deconvolve |
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "calab"
7
+ version = "0.1.0"
8
+ description = "CaLab: calcium imaging analysis tools — deconvolution and data preparation"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Daniel Aharoni" }]
14
+ keywords = ["calcium-imaging", "deconvolution", "neuroscience", "FISTA", "spike-inference"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Science/Research",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
25
+ "Topic :: Scientific/Engineering :: Medical Science Apps.",
26
+ "Typing :: Typed",
27
+ ]
28
+ dependencies = ["numpy>=1.24"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/miniscope/CaLab"
32
+ Repository = "https://github.com/miniscope/CaLab"
33
+ Issues = "https://github.com/miniscope/CaLab/issues"
34
+
35
+ [project.optional-dependencies]
36
+ dev = ["pytest>=7.0"]
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["src/calab"]
40
+
41
+ [tool.pytest.ini_options]
42
+ testpaths = ["tests"]
@@ -0,0 +1,21 @@
1
+ """CaLab: calcium imaging analysis tools — deconvolution and data preparation."""
2
+
3
+ from ._kernel import build_kernel, compute_lipschitz, tau_to_ar2
4
+ from ._fista import run_deconvolution, run_deconvolution_full, DeconvolutionResult
5
+ from ._filter import bandpass_filter
6
+ from ._io import load_tuning_data, save_for_tuning, load_export_params, deconvolve_from_export
7
+
8
+ __version__ = "0.2.0"
9
+ __all__ = [
10
+ "build_kernel",
11
+ "tau_to_ar2",
12
+ "compute_lipschitz",
13
+ "run_deconvolution",
14
+ "run_deconvolution_full",
15
+ "DeconvolutionResult",
16
+ "bandpass_filter",
17
+ "save_for_tuning",
18
+ "load_tuning_data",
19
+ "load_export_params",
20
+ "deconvolve_from_export",
21
+ ]
@@ -0,0 +1,103 @@
1
+ """Bandpass filter -- port of wasm/catune-solver/src/filter.rs.
2
+
3
+ Pure numpy FFT-based bandpass filter derived from kernel time constants.
4
+ Uses cosine-tapered gain curve matching Rust's implementation exactly.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import numpy as np
10
+
11
+ # Margin factors for deriving bandpass cutoffs from kernel time constants.
12
+ # HP cutoff = 1/(2*pi*tau_decay*M_HP), LP cutoff = M_LP/(2*pi*tau_rise).
13
+ # HP uses 16x to preserve the slow calcium decay tail.
14
+ # LP uses 4x for tighter noise rejection above the kernel's rise band.
15
+ _MARGIN_FACTOR_HP = 16.0
16
+ _MARGIN_FACTOR_LP = 4.0
17
+
18
+
19
+ def bandpass_filter(
20
+ trace: np.ndarray,
21
+ tau_rise: float,
22
+ tau_decay: float,
23
+ fs: float,
24
+ ) -> np.ndarray:
25
+ """Apply FFT bandpass filter derived from kernel time constants.
26
+
27
+ Cutoffs are computed from time constants (matching filter.rs:62-90):
28
+ - f_hp = 1 / (2*pi * tau_decay * 16)
29
+ - f_lp = min(4 / (2*pi * tau_rise), fs/2)
30
+
31
+ The gain curve uses cosine tapers at transitions (matching filter.rs:130-162).
32
+
33
+ Parameters
34
+ ----------
35
+ trace : np.ndarray
36
+ 1-D calcium trace to filter.
37
+ tau_rise : float
38
+ Rise time constant in seconds.
39
+ tau_decay : float
40
+ Decay time constant in seconds.
41
+ fs : float
42
+ Sampling rate in Hz.
43
+
44
+ Returns
45
+ -------
46
+ np.ndarray
47
+ Filtered trace. Returns input unchanged if filter would be invalid
48
+ (f_hp >= f_lp) or trace is too short (< 8 samples).
49
+ """
50
+ n = len(trace)
51
+ if n < 8:
52
+ return trace.copy()
53
+
54
+ nyquist = fs / 2.0
55
+
56
+ # Compute cutoffs (matching filter.rs:62-90)
57
+ f_hp = 1.0 / (2.0 * np.pi * tau_decay * _MARGIN_FACTOR_HP)
58
+ f_lp = _MARGIN_FACTOR_LP / (2.0 * np.pi * tau_rise)
59
+
60
+ # Clamp LP to Nyquist
61
+ if f_lp > nyquist:
62
+ f_lp = nyquist
63
+
64
+ # Invalid if HP >= LP
65
+ if f_hp >= f_lp:
66
+ return trace.copy()
67
+
68
+ # Build cosine-tapered gain curve (matching filter.rs:130-162)
69
+ spectrum_len = n // 2 + 1
70
+ df = fs / n
71
+ freqs = np.arange(spectrum_len) * df
72
+
73
+ # Taper widths: 50% of respective cutoff frequency
74
+ w_hp = f_hp * 0.5
75
+ w_lp = f_lp * 0.5
76
+
77
+ gain = np.zeros(spectrum_len)
78
+ for i in range(spectrum_len):
79
+ f = freqs[i]
80
+ if f < f_hp - w_hp:
81
+ # Stopband (below high-pass)
82
+ gain[i] = 0.0
83
+ elif f < f_hp + w_hp:
84
+ # HP transition (cosine taper 0 -> 1)
85
+ t = (f - (f_hp - w_hp)) / (2.0 * w_hp)
86
+ gain[i] = 0.5 * (1.0 - np.cos(np.pi * t))
87
+ elif f < f_lp - w_lp:
88
+ # Passband
89
+ gain[i] = 1.0
90
+ elif f < f_lp + w_lp:
91
+ # LP transition (cosine taper 1 -> 0)
92
+ t = (f - (f_lp - w_lp)) / (2.0 * w_lp)
93
+ gain[i] = 0.5 * (1.0 + np.cos(np.pi * t))
94
+ else:
95
+ # Stopband (above low-pass)
96
+ gain[i] = 0.0
97
+
98
+ # Apply via rfft -> multiply -> irfft
99
+ spectrum = np.fft.rfft(trace)
100
+ spectrum *= gain
101
+ filtered = np.fft.irfft(spectrum, n=n)
102
+
103
+ return filtered