lbm_suite2p_python 3.1.1__tar.gz → 3.2.1__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.
- {lbm_suite2p_python-3.1.1/lbm_suite2p_python.egg-info → lbm_suite2p_python-3.2.1}/PKG-INFO +1 -1
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/cellpose.py +6 -6
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/cli.py +11 -1
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/conversion.py +2 -2
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/default_ops.py +96 -84
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/run_lsp.py +161 -51
- lbm_suite2p_python-3.2.1/lbm_suite2p_python/utils.py +229 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1/lbm_suite2p_python.egg-info}/PKG-INFO +1 -1
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/pyproject.toml +1 -1
- lbm_suite2p_python-3.1.1/lbm_suite2p_python/utils.py +0 -144
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/LICENSE.md +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/MANIFEST.in +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/README.md +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/__init__.py +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/__main__.py +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/_benchmarking.py +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/db_settings.py +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/grid_search.py +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/gui.py +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/merging.py +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/postprocessing.py +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/volume.py +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/zplane.py +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/SOURCES.txt +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/dependency_links.txt +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/entry_points.txt +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/requires.txt +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/top_level.txt +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/setup.cfg +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/tests/test_frame_count_aliases.py +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/tests/test_pipeline_parameters.py +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/tests/test_refactored_pipeline.py +0 -0
- {lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/tests/test_run_volume.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lbm_suite2p_python
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.1
|
|
4
4
|
Summary: Calcium Imaging Pipeline built with Suite2p, Cellpose and Rastermap
|
|
5
5
|
License-Expression: BSD-3-Clause
|
|
6
6
|
Project-URL: homepage, https://github.com/MillerBrainObservatory/LBM-Suite2p-Python
|
|
@@ -390,7 +390,7 @@ def cellpose(
|
|
|
390
390
|
# cellpose eval parameters
|
|
391
391
|
diameter: float = None,
|
|
392
392
|
flow_threshold: float = 0.0,
|
|
393
|
-
cellprob_threshold: float = -
|
|
393
|
+
cellprob_threshold: float = -4.0,
|
|
394
394
|
min_size: int = 2,
|
|
395
395
|
max_size: int = None,
|
|
396
396
|
max_size_fraction: float = None,
|
|
@@ -445,9 +445,9 @@ def cellpose(
|
|
|
445
445
|
Use GPU if available.
|
|
446
446
|
diameter : float, optional
|
|
447
447
|
Expected cell diameter in pixels. If None, Cellpose auto-estimates.
|
|
448
|
-
flow_threshold : float, default 0.
|
|
449
|
-
Maximum allowed error of flows for each mask.
|
|
450
|
-
cellprob_threshold : float, default
|
|
448
|
+
flow_threshold : float, default 0.0
|
|
449
|
+
Maximum allowed error of flows for each mask (0 = flow check off).
|
|
450
|
+
cellprob_threshold : float, default -4.0
|
|
451
451
|
Probability threshold for cell detection. Lower = more cells.
|
|
452
452
|
min_size : int, default 2
|
|
453
453
|
Minimum number of pixels per mask.
|
|
@@ -932,8 +932,8 @@ def save_gui_results(
|
|
|
932
932
|
flows: tuple = None,
|
|
933
933
|
styles: np.ndarray = None,
|
|
934
934
|
diameter: float = None,
|
|
935
|
-
cellprob_threshold: float =
|
|
936
|
-
flow_threshold: float = 0.
|
|
935
|
+
cellprob_threshold: float = -4.0,
|
|
936
|
+
flow_threshold: float = 0.0,
|
|
937
937
|
name: str = None,
|
|
938
938
|
) -> Path:
|
|
939
939
|
"""
|
|
@@ -134,13 +134,21 @@ Examples:
|
|
|
134
134
|
"--planes", nargs="*", type=int, dest="planes",
|
|
135
135
|
help="z-planes to process (1-indexed, e.g., --planes 1 2 3)"
|
|
136
136
|
)
|
|
137
|
+
pipeline.add_argument(
|
|
138
|
+
"--timepoints", nargs="*", type=int, dest="timepoints",
|
|
139
|
+
help="timepoints to process (1-indexed, e.g., --timepoints 1 50 100)"
|
|
140
|
+
)
|
|
137
141
|
pipeline.add_argument(
|
|
138
142
|
"--roi-mode", "--roi", type=int, dest="roi_mode",
|
|
139
143
|
help="ROI mode: None=stitch, 0=split all, N=specific ROI"
|
|
140
144
|
)
|
|
141
145
|
pipeline.add_argument(
|
|
142
146
|
"--num-timepoints", "--frames", type=int, dest="num_timepoints",
|
|
143
|
-
help="number of
|
|
147
|
+
help="number of timepoints to process (first N, for quick testing)"
|
|
148
|
+
)
|
|
149
|
+
pipeline.add_argument(
|
|
150
|
+
"--num-zplanes", type=int, dest="num_zplanes",
|
|
151
|
+
help="number of z-planes to process (first N)"
|
|
144
152
|
)
|
|
145
153
|
pipeline.add_argument(
|
|
146
154
|
"--overwrite", action="store_true",
|
|
@@ -566,8 +574,10 @@ def main():
|
|
|
566
574
|
save_path=output_path,
|
|
567
575
|
ops=ops,
|
|
568
576
|
planes=args.planes,
|
|
577
|
+
timepoints=args.timepoints,
|
|
569
578
|
roi_mode=args.roi_mode,
|
|
570
579
|
num_timepoints=args.num_timepoints,
|
|
580
|
+
num_zplanes=args.num_zplanes,
|
|
571
581
|
keep_reg=args.keep_reg,
|
|
572
582
|
keep_raw=args.keep_raw,
|
|
573
583
|
force_reg=args.force_reg or args.overwrite,
|
|
@@ -622,8 +622,8 @@ def export_for_gui(suite2p_dir, output_path=None, name=None):
|
|
|
622
622
|
"filename": str(proj_path),
|
|
623
623
|
"flows": None,
|
|
624
624
|
"est_diam": ops.get("diameter"),
|
|
625
|
-
"cellprob_threshold": ops.get("cellprob_threshold",
|
|
626
|
-
"flow_threshold": ops.get("flow_threshold", 0.
|
|
625
|
+
"cellprob_threshold": ops.get("cellprob_threshold", -4.0),
|
|
626
|
+
"flow_threshold": ops.get("flow_threshold", 0.0),
|
|
627
627
|
}
|
|
628
628
|
|
|
629
629
|
seg_file = output_dir / f"{name}_seg.npy"
|
|
@@ -1,84 +1,96 @@
|
|
|
1
|
-
"""Default ops for the LBM pipeline.
|
|
2
|
-
|
|
3
|
-
Historically this module hard-coded a flat ops dict whose values
|
|
4
|
-
diverged from suite2p's own defaults in a handful of fields
|
|
5
|
-
(`batch_size=500`, `chan2_thres=0.65`, `tau=1.3`, `diameter=4`, etc.).
|
|
6
|
-
That divergence made the round-trip through `settings.npy` confusing —
|
|
7
|
-
on-disk values that matched the lsp default were flagged as "modified"
|
|
8
|
-
against suite2p's schema, and vice versa.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
`suite2p.default_settings()` + `suite2p.default_db()`), flattened to the
|
|
12
|
-
fork's flat-ops shape via the same `db_settings_to_ops` translation
|
|
13
|
-
that the rest of lsp uses.
|
|
14
|
-
|
|
15
|
-
(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
""
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
1
|
+
"""Default ops for the LBM pipeline.
|
|
2
|
+
|
|
3
|
+
Historically this module hard-coded a flat ops dict whose values
|
|
4
|
+
diverged from suite2p's own defaults in a handful of fields
|
|
5
|
+
(`batch_size=500`, `chan2_thres=0.65`, `tau=1.3`, `diameter=4`, etc.).
|
|
6
|
+
That divergence made the round-trip through `settings.npy` confusing —
|
|
7
|
+
on-disk values that matched the lsp default were flagged as "modified"
|
|
8
|
+
against suite2p's schema, and vice versa.
|
|
9
|
+
|
|
10
|
+
`s2p_ops()` exposes exactly suite2p's defaults (the values from
|
|
11
|
+
`suite2p.default_settings()` + `suite2p.default_db()`), flattened to the
|
|
12
|
+
fork's flat-ops shape via the same `db_settings_to_ops` translation
|
|
13
|
+
that the rest of lsp uses. `default_ops()` then applies a small set of
|
|
14
|
+
LBM detection defaults (`_LBM_DETECTION_DEFAULTS`) on top, so a notebook,
|
|
15
|
+
a bare `pipeline()` / `run_plane()` call, and the GUI all start from the
|
|
16
|
+
same place. An explicit caller-supplied `ops=` is respected, not overlaid.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from mbo_utilities.metadata import get_param, get_voxel_size
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# LBM detection defaults that intentionally differ from suite2p's schema.
|
|
23
|
+
# default_ops() applies these on top of the suite2p mirror so notebook,
|
|
24
|
+
# function-call, and GUI runs share one set of defaults.
|
|
25
|
+
_LBM_DETECTION_DEFAULTS = {
|
|
26
|
+
"do_regmetrics": False, # PC reg-quality metrics: ~40s/plane on >=1500 frames
|
|
27
|
+
"cellprob_threshold": -4.0, # more permissive than suite2p's 0.0 for LBM data
|
|
28
|
+
"flow_threshold": 0.0, # flow check disabled
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def s2p_ops() -> dict:
|
|
33
|
+
"""Suite2p's default ops in flat (fork-style) form.
|
|
34
|
+
|
|
35
|
+
Composed from `suite2p.default_settings()` + `suite2p.default_db()`
|
|
36
|
+
via `db_settings_to_ops`, so the rename map and per-section
|
|
37
|
+
flat-key disambiguation (e.g. extraction.batch_size →
|
|
38
|
+
extract_batch_size) are applied consistently.
|
|
39
|
+
"""
|
|
40
|
+
from suite2p import default_settings, default_db
|
|
41
|
+
from lbm_suite2p_python.db_settings import db_settings_to_ops
|
|
42
|
+
|
|
43
|
+
return db_settings_to_ops(default_db(), default_settings())
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def default_ops(metadata: dict | None = None, ops: dict | None = None) -> dict:
|
|
47
|
+
"""Return default ops for the LBM Suite2p pipeline.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
metadata : dict, optional
|
|
52
|
+
Source-data metadata. When provided, `fs` and the (`dx`, `dy`)
|
|
53
|
+
voxel-size pair are pulled in from the metadata and overlaid
|
|
54
|
+
onto the suite2p defaults.
|
|
55
|
+
ops : dict, optional
|
|
56
|
+
A user-supplied ops dict to start from. When None, starts from
|
|
57
|
+
`s2p_ops()` (suite2p's defaults).
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
dict
|
|
62
|
+
Flat ops dict ready to be passed to `pipeline()` / `run_plane()`.
|
|
63
|
+
|
|
64
|
+
Notes
|
|
65
|
+
-----
|
|
66
|
+
`nplanes=1` and `nchannels=1` are forced at the end. The lsp
|
|
67
|
+
pipeline always processes one plane at a time, so these stay fixed
|
|
68
|
+
regardless of caller input.
|
|
69
|
+
|
|
70
|
+
Examples
|
|
71
|
+
--------
|
|
72
|
+
>>> import lbm_suite2p_python as lsp
|
|
73
|
+
>>> ops = lsp.default_ops()
|
|
74
|
+
>>> lsp.run_plane(
|
|
75
|
+
... ops=ops,
|
|
76
|
+
... input_tiff="D:/demo/raw_data/raw_file_00001.tif",
|
|
77
|
+
... save_path="D:/demo/results",
|
|
78
|
+
... save_folder="v1",
|
|
79
|
+
... )
|
|
80
|
+
"""
|
|
81
|
+
if ops is None:
|
|
82
|
+
ops = s2p_ops()
|
|
83
|
+
ops.update(_LBM_DETECTION_DEFAULTS)
|
|
84
|
+
|
|
85
|
+
if metadata is not None:
|
|
86
|
+
fs = get_param(metadata, "fs")
|
|
87
|
+
if fs is not None:
|
|
88
|
+
ops["fs"] = fs
|
|
89
|
+
voxel = get_voxel_size(metadata)
|
|
90
|
+
if voxel.dx != 1.0 or voxel.dy != 1.0:
|
|
91
|
+
ops["dx"] = voxel.dx
|
|
92
|
+
ops["dy"] = voxel.dy
|
|
93
|
+
|
|
94
|
+
ops["nplanes"] = 1
|
|
95
|
+
ops["nchannels"] = 1
|
|
96
|
+
return ops
|
|
@@ -446,14 +446,15 @@ def _is_valid_torch_checkpoint(path) -> bool:
|
|
|
446
446
|
def _prewarm_cellpose_model(ops) -> None:
|
|
447
447
|
"""Download the cellpose model once, in the parent, before workers fan out.
|
|
448
448
|
|
|
449
|
-
cellpose's
|
|
450
|
-
|
|
449
|
+
cellpose's model cache downloads to a temp file then renames with no
|
|
450
|
+
cross-process lock. Multiple workers hitting an empty cache at once
|
|
451
451
|
race: one wins the rename, the rest fail (Windows WinError 32/183) or read a
|
|
452
452
|
half-written file (PytorchStreamReader miniz error). Warming here serializes
|
|
453
453
|
the download so workers only ever read a complete file. A corrupt leftover
|
|
454
454
|
from a prior failed run is removed and re-downloaded.
|
|
455
455
|
"""
|
|
456
|
-
if not (ops.get("roidetect", True)
|
|
456
|
+
if not (ops.get("roidetect", True)
|
|
457
|
+
and (ops.get("anatomical_only", 0) > 0 or ops.get("algorithm") == "cellpose")):
|
|
457
458
|
return
|
|
458
459
|
try:
|
|
459
460
|
from cellpose import models as cp_models
|
|
@@ -467,7 +468,11 @@ def _prewarm_cellpose_model(ops) -> None:
|
|
|
467
468
|
except OSError:
|
|
468
469
|
pass
|
|
469
470
|
try:
|
|
470
|
-
|
|
471
|
+
# cellpose 4.x renamed cache_CPSAM_model_path() -> cache_model_path(backbone)
|
|
472
|
+
if hasattr(cp_models, "cache_model_path"):
|
|
473
|
+
cp_models.cache_model_path("cpsam")
|
|
474
|
+
else:
|
|
475
|
+
cp_models.cache_CPSAM_model_path()
|
|
471
476
|
except Exception as exc:
|
|
472
477
|
print(
|
|
473
478
|
f"Warning: could not pre-download cellpose model ({exc}); "
|
|
@@ -780,6 +785,38 @@ def _prepare_plane_ops(*, base_ops, plane_idx, num_planes, input_arr,
|
|
|
780
785
|
return current_ops
|
|
781
786
|
|
|
782
787
|
|
|
788
|
+
def _resolve_timepoints(timepoints=None, frames=None, frame_indices=None):
|
|
789
|
+
"""Resolve the canonical 1-based ``timepoints`` selection.
|
|
790
|
+
|
|
791
|
+
``frames`` (1-based) and ``frame_indices`` (0-based) are deprecated
|
|
792
|
+
aliases and emit a DeprecationWarning. Returns a 1-based list, or
|
|
793
|
+
None for all timepoints.
|
|
794
|
+
"""
|
|
795
|
+
import warnings
|
|
796
|
+
|
|
797
|
+
if frames is not None:
|
|
798
|
+
warnings.warn(
|
|
799
|
+
"'frames' is deprecated, use 'timepoints' (1-based)",
|
|
800
|
+
DeprecationWarning,
|
|
801
|
+
stacklevel=3,
|
|
802
|
+
)
|
|
803
|
+
if timepoints is None:
|
|
804
|
+
timepoints = frames
|
|
805
|
+
if frame_indices is not None:
|
|
806
|
+
warnings.warn(
|
|
807
|
+
"'frame_indices' is deprecated, use 'timepoints' (1-based)",
|
|
808
|
+
DeprecationWarning,
|
|
809
|
+
stacklevel=3,
|
|
810
|
+
)
|
|
811
|
+
if timepoints is None:
|
|
812
|
+
timepoints = [int(i) + 1 for i in frame_indices]
|
|
813
|
+
if timepoints is None:
|
|
814
|
+
return None
|
|
815
|
+
if isinstance(timepoints, (int, np.integer)):
|
|
816
|
+
return [int(timepoints)]
|
|
817
|
+
return [int(t) for t in timepoints]
|
|
818
|
+
|
|
819
|
+
|
|
783
820
|
def pipeline(
|
|
784
821
|
input_data,
|
|
785
822
|
save_path: str | Path = None,
|
|
@@ -792,7 +829,10 @@ def pipeline(
|
|
|
792
829
|
keep_raw: bool = False,
|
|
793
830
|
force_reg: bool = False,
|
|
794
831
|
force_detect: bool = False,
|
|
832
|
+
replot: bool = True,
|
|
833
|
+
timepoints: list | int | None = None,
|
|
795
834
|
num_timepoints: int = None,
|
|
835
|
+
num_zplanes: int = None,
|
|
796
836
|
frame_indices: list | None = None,
|
|
797
837
|
dff_window_size: int = None,
|
|
798
838
|
dff_percentile: int = 20,
|
|
@@ -854,16 +894,25 @@ def pipeline(
|
|
|
854
894
|
Force re-registration even if already complete.
|
|
855
895
|
force_detect : bool, default False
|
|
856
896
|
Force ROI detection even if stat.npy exists.
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
Explicit
|
|
863
|
-
(e.g. ``list(range(
|
|
897
|
+
replot : bool, default True
|
|
898
|
+
Regenerate per-plane figures. Set False to skip per-plane figure
|
|
899
|
+
regeneration (e.g. the volumetric aggregate over already-plotted
|
|
900
|
+
planes); suite2p and the volumetric plots are unaffected.
|
|
901
|
+
timepoints : list[int] or int, optional
|
|
902
|
+
Explicit 1-based timepoints to process. Supports stride
|
|
903
|
+
(e.g. ``list(range(1, 1575, 2))`` for every other timepoint).
|
|
864
904
|
When provided, the implicit stride is used by `OutputMetadata`
|
|
865
905
|
to reactively scale `fs` (e.g. stride of 2 → `fs / 2` in the
|
|
866
906
|
output ops.npy). Takes precedence over ``num_timepoints``.
|
|
907
|
+
num_timepoints : int, optional
|
|
908
|
+
Limit processing to first N timepoints (truncation only). For an
|
|
909
|
+
explicit set or a strided selection, use ``timepoints`` instead.
|
|
910
|
+
num_zplanes : int, optional
|
|
911
|
+
Limit processing to the first N z-planes. Shortcut for
|
|
912
|
+
``planes=[1..N]``; ignored when ``planes`` is given.
|
|
913
|
+
frame_indices : list[int], optional
|
|
914
|
+
Deprecated alias for ``timepoints`` (0-based). Emits a
|
|
915
|
+
DeprecationWarning.
|
|
867
916
|
dff_window_size : int, optional
|
|
868
917
|
Window size for rolling percentile dF/F baseline (frames).
|
|
869
918
|
If None, auto-calculated as ~10 * tau * fs.
|
|
@@ -976,7 +1025,15 @@ def pipeline(
|
|
|
976
1025
|
DeprecationWarning,
|
|
977
1026
|
stacklevel=2,
|
|
978
1027
|
)
|
|
979
|
-
num_timepoints
|
|
1028
|
+
if num_timepoints is None:
|
|
1029
|
+
num_timepoints = num_frames
|
|
1030
|
+
|
|
1031
|
+
# canonical 1-based timepoint selection (frames/frame_indices deprecated).
|
|
1032
|
+
timepoints = _resolve_timepoints(timepoints, kwargs.pop("frames", None), frame_indices)
|
|
1033
|
+
frame_indices = None
|
|
1034
|
+
# num_zplanes is a count shortcut for planes=[1..N].
|
|
1035
|
+
if num_zplanes is not None and planes is None:
|
|
1036
|
+
planes = list(range(1, int(num_zplanes) + 1))
|
|
980
1037
|
|
|
981
1038
|
# flatten (db, settings) into ops so downstream run_volume / run_plane
|
|
982
1039
|
# don't each need to forward the pair. explicit ops keys still win.
|
|
@@ -986,14 +1043,10 @@ def pipeline(
|
|
|
986
1043
|
|
|
987
1044
|
reader_kwargs = reader_kwargs or {}
|
|
988
1045
|
writer_kwargs = writer_kwargs or {}
|
|
1046
|
+
# num_timepoints truncation reaches the writer; an explicit `timepoints`
|
|
1047
|
+
# selection is forwarded as a param and rebuilt by run_plane.
|
|
989
1048
|
if num_timepoints is not None:
|
|
990
|
-
writer_kwargs["
|
|
991
|
-
|
|
992
|
-
# 1-based frame numbers.
|
|
993
|
-
if frame_indices is not None:
|
|
994
|
-
writer_kwargs["frames"] = [int(i) + 1 for i in frame_indices]
|
|
995
|
-
# don't double-pass num_frames; len(frame_indices) is implicit
|
|
996
|
-
writer_kwargs.pop("num_frames", None)
|
|
1049
|
+
writer_kwargs["num_timepoints"] = num_timepoints
|
|
997
1050
|
|
|
998
1051
|
# Always load array to check dimensions and ensure downstream functions have the array shape
|
|
999
1052
|
# If input is already array, this is fast. If path or list of paths, it loads lazy array.
|
|
@@ -1028,7 +1081,8 @@ def pipeline(
|
|
|
1028
1081
|
keep_raw=keep_raw,
|
|
1029
1082
|
force_reg=force_reg,
|
|
1030
1083
|
force_detect=force_detect,
|
|
1031
|
-
|
|
1084
|
+
replot=replot,
|
|
1085
|
+
timepoints=timepoints,
|
|
1032
1086
|
dff_window_size=dff_window_size,
|
|
1033
1087
|
dff_percentile=dff_percentile,
|
|
1034
1088
|
dff_smooth_window=dff_smooth_window,
|
|
@@ -1067,7 +1121,8 @@ def pipeline(
|
|
|
1067
1121
|
keep_raw=keep_raw,
|
|
1068
1122
|
force_reg=force_reg,
|
|
1069
1123
|
force_detect=force_detect,
|
|
1070
|
-
|
|
1124
|
+
replot=replot,
|
|
1125
|
+
timepoints=timepoints,
|
|
1071
1126
|
dff_window_size=dff_window_size,
|
|
1072
1127
|
dff_percentile=dff_percentile,
|
|
1073
1128
|
dff_smooth_window=dff_smooth_window,
|
|
@@ -1238,6 +1293,10 @@ def run_volume(
|
|
|
1238
1293
|
keep_raw: bool = False,
|
|
1239
1294
|
force_reg: bool = False,
|
|
1240
1295
|
force_detect: bool = False,
|
|
1296
|
+
replot: bool = True,
|
|
1297
|
+
timepoints: list | int | None = None,
|
|
1298
|
+
num_timepoints: int = None,
|
|
1299
|
+
num_zplanes: int = None,
|
|
1241
1300
|
frame_indices: list | None = None,
|
|
1242
1301
|
dff_window_size: int = None,
|
|
1243
1302
|
dff_percentile: int = 20,
|
|
@@ -1281,6 +1340,9 @@ def run_volume(
|
|
|
1281
1340
|
Force re-registration.
|
|
1282
1341
|
force_detect : bool, default False
|
|
1283
1342
|
Force detection.
|
|
1343
|
+
replot : bool, default True
|
|
1344
|
+
Regenerate per-plane figures (passed to run_plane). Set False to skip
|
|
1345
|
+
per-plane figure regeneration during a volumetric aggregate.
|
|
1284
1346
|
frame_indices : list, default None
|
|
1285
1347
|
List of frame indices to process.
|
|
1286
1348
|
dff_window_size, dff_percentile, dff_smooth_window : optional
|
|
@@ -1326,6 +1388,17 @@ def run_volume(
|
|
|
1326
1388
|
_resolve_gpu_env()
|
|
1327
1389
|
_apply_thread_limits(threads_per_worker)
|
|
1328
1390
|
|
|
1391
|
+
# canonical 1-based timepoints (frames/frame_indices deprecated); keep a
|
|
1392
|
+
# 0-based frame_indices for this function's reactive-metadata plumbing,
|
|
1393
|
+
# and forward `timepoints` to run_plane.
|
|
1394
|
+
timepoints = _resolve_timepoints(timepoints, kwargs.pop("frames", None), frame_indices)
|
|
1395
|
+
frame_indices = [int(t) - 1 for t in timepoints] if timepoints is not None else None
|
|
1396
|
+
if num_zplanes is not None and planes is None:
|
|
1397
|
+
planes = list(range(1, int(num_zplanes) + 1))
|
|
1398
|
+
writer_kwargs = dict(writer_kwargs or {})
|
|
1399
|
+
if num_timepoints is not None:
|
|
1400
|
+
writer_kwargs.setdefault("num_timepoints", num_timepoints)
|
|
1401
|
+
|
|
1329
1402
|
# Handle input data
|
|
1330
1403
|
input_arr = None
|
|
1331
1404
|
input_paths = []
|
|
@@ -1418,7 +1491,8 @@ def run_volume(
|
|
|
1418
1491
|
keep_raw=keep_raw,
|
|
1419
1492
|
force_reg=force_reg,
|
|
1420
1493
|
force_detect=force_detect,
|
|
1421
|
-
|
|
1494
|
+
replot=replot,
|
|
1495
|
+
timepoints=timepoints,
|
|
1422
1496
|
dff_window_size=dff_window_size,
|
|
1423
1497
|
dff_percentile=dff_percentile,
|
|
1424
1498
|
dff_smooth_window=dff_smooth_window,
|
|
@@ -2270,6 +2344,9 @@ def run_plane(
|
|
|
2270
2344
|
keep_reg: bool = True,
|
|
2271
2345
|
force_reg: bool = False,
|
|
2272
2346
|
force_detect: bool = False,
|
|
2347
|
+
replot: bool = True,
|
|
2348
|
+
timepoints: list | int | None = None,
|
|
2349
|
+
num_timepoints: int = None,
|
|
2273
2350
|
frame_indices: list | None = None,
|
|
2274
2351
|
dff_window_size: int = None,
|
|
2275
2352
|
dff_percentile: int = 20,
|
|
@@ -2314,6 +2391,9 @@ def run_plane(
|
|
|
2314
2391
|
If True, force a new registration.
|
|
2315
2392
|
force_detect : bool, default False
|
|
2316
2393
|
If True, force ROI detection.
|
|
2394
|
+
replot : bool, default True
|
|
2395
|
+
Generate per-plane figures. Set False to skip figure generation
|
|
2396
|
+
(keeps ROI stats; only the figures are skipped).
|
|
2317
2397
|
dff_window_size : int, optional
|
|
2318
2398
|
Frames for rolling percentile baseline. Default: auto-calculated (~10*tau*fs).
|
|
2319
2399
|
dff_percentile : int, default 20
|
|
@@ -2337,13 +2417,18 @@ def run_plane(
|
|
|
2337
2417
|
Example: {"n_clusters": 50, "n_PCs": 64}.
|
|
2338
2418
|
save_json : bool, default False
|
|
2339
2419
|
Save ops as JSON.
|
|
2340
|
-
|
|
2341
|
-
Explicit
|
|
2342
|
-
(e.g. ``list(range(
|
|
2420
|
+
timepoints : list[int] or int, optional
|
|
2421
|
+
Explicit 1-based timepoints. Supports stride
|
|
2422
|
+
(e.g. ``list(range(1, 1575, 2))`` for every other timepoint).
|
|
2343
2423
|
When provided, the binary on disk contains exactly these
|
|
2344
|
-
|
|
2345
|
-
based on the implicit stride. Takes precedence over
|
|
2346
|
-
``
|
|
2424
|
+
timepoints, and `OutputMetadata` reactively scales `fs` in ops.npy
|
|
2425
|
+
based on the implicit stride. Takes precedence over
|
|
2426
|
+
``num_timepoints``.
|
|
2427
|
+
num_timepoints : int, optional
|
|
2428
|
+
Limit processing to first N timepoints (truncation only).
|
|
2429
|
+
frame_indices : list[int], optional
|
|
2430
|
+
Deprecated alias for ``timepoints`` (0-based). Emits a
|
|
2431
|
+
DeprecationWarning.
|
|
2347
2432
|
plane_name : str, optional
|
|
2348
2433
|
Custom name for the plane subdirectory.
|
|
2349
2434
|
reader_kwargs : dict, optional
|
|
@@ -2363,6 +2448,14 @@ def run_plane(
|
|
|
2363
2448
|
|
|
2364
2449
|
_resolve_gpu_env()
|
|
2365
2450
|
|
|
2451
|
+
# canonical 1-based timepoints (frames/frame_indices deprecated); convert to
|
|
2452
|
+
# the 0-based frame_indices this function consumes internally.
|
|
2453
|
+
timepoints = _resolve_timepoints(timepoints, kwargs.pop("frames", None), frame_indices)
|
|
2454
|
+
frame_indices = [int(t) - 1 for t in timepoints] if timepoints is not None else None
|
|
2455
|
+
writer_kwargs = dict(writer_kwargs or {})
|
|
2456
|
+
if num_timepoints is not None:
|
|
2457
|
+
writer_kwargs.setdefault("num_timepoints", num_timepoints)
|
|
2458
|
+
|
|
2366
2459
|
progress_callback = kwargs.pop("progress_callback", None)
|
|
2367
2460
|
|
|
2368
2461
|
if "debug" in kwargs:
|
|
@@ -2621,7 +2714,8 @@ def run_plane(
|
|
|
2621
2714
|
else:
|
|
2622
2715
|
# prefer the user-specified frame limit over raw array shape
|
|
2623
2716
|
nframes_hint = (
|
|
2624
|
-
writer_kwargs.get("
|
|
2717
|
+
writer_kwargs.get("num_timepoints")
|
|
2718
|
+
or writer_kwargs.get("num_frames")
|
|
2625
2719
|
or ops.get("nframes")
|
|
2626
2720
|
)
|
|
2627
2721
|
if not nframes_hint and input_arr is not None and hasattr(input_arr, "shape"):
|
|
@@ -2650,8 +2744,8 @@ def run_plane(
|
|
|
2650
2744
|
ops_file = plane_dir / "ops.npy"
|
|
2651
2745
|
|
|
2652
2746
|
# extract expected dims from input for cache validation
|
|
2653
|
-
# honors
|
|
2654
|
-
exp_nframes = writer_kwargs.get("num_frames")
|
|
2747
|
+
# honors num_timepoints truncation if user requested fewer frames
|
|
2748
|
+
exp_nframes = writer_kwargs.get("num_timepoints") or writer_kwargs.get("num_frames")
|
|
2655
2749
|
exp_ly = exp_lx = None
|
|
2656
2750
|
if input_arr is not None and hasattr(input_arr, "shape"):
|
|
2657
2751
|
if exp_nframes is None:
|
|
@@ -2780,12 +2874,13 @@ def run_plane(
|
|
|
2780
2874
|
write_planes = [plane] if _get_num_planes(file) > 1 else None
|
|
2781
2875
|
|
|
2782
2876
|
write_kw = dict(writer_kwargs)
|
|
2783
|
-
# If the caller gave us explicit
|
|
2784
|
-
# `
|
|
2785
|
-
#
|
|
2786
|
-
#
|
|
2877
|
+
# If the caller gave us explicit timepoints, pass them as
|
|
2878
|
+
# `timepoints=` (1-based) to imwrite. This wins over any stale
|
|
2879
|
+
# truncation count in writer_kwargs — strided semantics require an
|
|
2880
|
+
# explicit index list, not a count.
|
|
2787
2881
|
if frame_indices is not None:
|
|
2788
|
-
write_kw["
|
|
2882
|
+
write_kw["timepoints"] = [int(i) + 1 for i in frame_indices]
|
|
2883
|
+
write_kw.pop("num_timepoints", None)
|
|
2789
2884
|
write_kw.pop("num_frames", None)
|
|
2790
2885
|
|
|
2791
2886
|
imwrite(
|
|
@@ -3143,7 +3238,7 @@ def run_plane(
|
|
|
3143
3238
|
except Exception as _e:
|
|
3144
3239
|
print(f" Warning: persisting post-processing kwargs failed: {_e}")
|
|
3145
3240
|
|
|
3146
|
-
# 3b. ROI statistics
|
|
3241
|
+
# 3b. ROI statistics (cheap; feeds the volumetric aggregate, so always run).
|
|
3147
3242
|
try:
|
|
3148
3243
|
from lbm_suite2p_python.postprocessing import compute_roi_stats
|
|
3149
3244
|
|
|
@@ -3152,20 +3247,35 @@ def run_plane(
|
|
|
3152
3247
|
except Exception as e:
|
|
3153
3248
|
print(f" Warning: ROI stats computation failed: {e}")
|
|
3154
3249
|
|
|
3155
|
-
# 4.
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3250
|
+
# 4. Per-plane figures. Timed as the "plots" step (recorded in
|
|
3251
|
+
# processing_history). Skipped when replot=False — e.g. the volumetric
|
|
3252
|
+
# aggregate re-running already-plotted planes, where regenerating the
|
|
3253
|
+
# per-plane figures is the dominant redundant cost.
|
|
3254
|
+
if replot:
|
|
3255
|
+
plot_start = time.time()
|
|
3256
|
+
try:
|
|
3257
|
+
plot_zplane_figures(
|
|
3258
|
+
plane_dir,
|
|
3259
|
+
dff_percentile=dff_percentile,
|
|
3260
|
+
dff_window_size=dff_window_size,
|
|
3261
|
+
dff_smooth_window=dff_smooth_window,
|
|
3262
|
+
norm_method=norm_method,
|
|
3263
|
+
correct_neuropil=correct_neuropil,
|
|
3264
|
+
run_rastermap=rastermap_kwargs is not None,
|
|
3265
|
+
rastermap_kwargs=rastermap_kwargs,
|
|
3266
|
+
)
|
|
3267
|
+
except Exception as e:
|
|
3268
|
+
print(f" Warning: Plot generation failed: {e}")
|
|
3269
|
+
|
|
3270
|
+
if ops_file.exists():
|
|
3271
|
+
try:
|
|
3272
|
+
_t_ops = load_ops(ops_file)
|
|
3273
|
+
_add_processing_step(_t_ops, "plots", duration_seconds=time.time() - plot_start)
|
|
3274
|
+
save_ops_db_settings(ops_file, _t_ops)
|
|
3275
|
+
except Exception as _e:
|
|
3276
|
+
print(f" Warning: recording plot timing failed: {_e}")
|
|
3277
|
+
else:
|
|
3278
|
+
print(" replot=False: keeping existing per-plane figures")
|
|
3169
3279
|
|
|
3170
3280
|
if save_json:
|
|
3171
3281
|
ops_to_json(ops_file)
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# mbo_utilities >= 4.0 exposes a single LazyArray base; isinstance covers
|
|
7
|
+
# every built-in and any third-party plugin. The class-name tuple is the
|
|
8
|
+
# fallback for mbo_utilities < 4.0, which has no shared base.
|
|
9
|
+
try:
|
|
10
|
+
from mbo_utilities import LazyArray as _LazyArray
|
|
11
|
+
except ImportError: # mbo_utilities < 4.0
|
|
12
|
+
_LazyArray = None
|
|
13
|
+
|
|
14
|
+
_LAZY_ARRAY_TYPES = (
|
|
15
|
+
"ScanImageArray",
|
|
16
|
+
"LBMArray",
|
|
17
|
+
"PiezoArray",
|
|
18
|
+
"SinglePlaneArray",
|
|
19
|
+
"Suite2pArray",
|
|
20
|
+
"MBOTiffArray",
|
|
21
|
+
"MboRawArray",
|
|
22
|
+
"TiffArray",
|
|
23
|
+
"ZarrArray",
|
|
24
|
+
"H5Array",
|
|
25
|
+
"NumpyArray",
|
|
26
|
+
"BinArray",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _is_lazy_array(obj):
|
|
31
|
+
"""Check if obj is an mbo_utilities lazy array type."""
|
|
32
|
+
if _LazyArray is not None and isinstance(obj, _LazyArray):
|
|
33
|
+
return True
|
|
34
|
+
return type(obj).__name__ in _LAZY_ARRAY_TYPES
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _get_num_planes(arr):
|
|
38
|
+
"""
|
|
39
|
+
Get number of z-planes from a lazy array.
|
|
40
|
+
|
|
41
|
+
mbo_utilities arrays are always 5D TCZYX, so Z is at shape[2].
|
|
42
|
+
Falls back to legacy 4D TZYX (Z at shape[1]) and other heuristics
|
|
43
|
+
for non-mbo arrays.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
arr : array-like
|
|
48
|
+
Input array, typically from mbo_utilities.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
int
|
|
53
|
+
Number of z-planes (1 if no Z dimension).
|
|
54
|
+
"""
|
|
55
|
+
# mbo_utilities Shape5DMixin
|
|
56
|
+
if hasattr(arr, "nz"):
|
|
57
|
+
return arr.nz
|
|
58
|
+
if hasattr(arr, "num_planes") and arr.num_planes is not None:
|
|
59
|
+
return arr.num_planes
|
|
60
|
+
shape = arr.shape
|
|
61
|
+
if len(shape) == 5:
|
|
62
|
+
return shape[2] # 5D TCZYX
|
|
63
|
+
if len(shape) == 4:
|
|
64
|
+
return shape[1] # legacy 4D TZYX
|
|
65
|
+
return 1
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _resize_masks_fit_crop(mask, target_shape):
|
|
69
|
+
"""Centers a mask within the target shape, cropping if too large or padding if too small."""
|
|
70
|
+
sy, sx = mask.shape
|
|
71
|
+
ty, tx = target_shape
|
|
72
|
+
|
|
73
|
+
# If mask is larger, crop it
|
|
74
|
+
if sy > ty or sx > tx:
|
|
75
|
+
start_y = (sy - ty) // 2
|
|
76
|
+
start_x = (sx - tx) // 2
|
|
77
|
+
return mask[start_y : start_y + ty, start_x : start_x + tx]
|
|
78
|
+
|
|
79
|
+
# If mask is smaller, pad it
|
|
80
|
+
resized_mask = np.zeros(target_shape, dtype=mask.dtype)
|
|
81
|
+
start_y = (ty - sy) // 2
|
|
82
|
+
start_x = (tx - sx) // 2
|
|
83
|
+
resized_mask[start_y : start_y + sy, start_x : start_x + sx] = mask
|
|
84
|
+
return resized_mask
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_common_path(ops_files: list | tuple):
|
|
88
|
+
"""
|
|
89
|
+
Find the common parent path of all files.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
ops_files : list or tuple
|
|
94
|
+
List of file paths.
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
Path
|
|
99
|
+
Common parent directory of all files.
|
|
100
|
+
"""
|
|
101
|
+
if not isinstance(ops_files, (list, tuple)):
|
|
102
|
+
ops_files = [ops_files]
|
|
103
|
+
if len(ops_files) == 1:
|
|
104
|
+
path = Path(ops_files[0]).parent
|
|
105
|
+
while (
|
|
106
|
+
path.exists() and len(list(path.iterdir())) <= 1
|
|
107
|
+
): # Traverse up if only one item exists
|
|
108
|
+
path = path.parent
|
|
109
|
+
return path
|
|
110
|
+
else:
|
|
111
|
+
return Path(os.path.commonpath(ops_files))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def estimate_peak_memory(ops, Ly, Lx, n_frames, device="cuda", workers=1):
|
|
115
|
+
"""
|
|
116
|
+
Estimate peak memory for one Suite2p plane from its parameters.
|
|
117
|
+
|
|
118
|
+
Registration and detection run sequentially in suite2p's pipeline, so
|
|
119
|
+
the peak for a plane is ``max(registration, detection)``, not the sum.
|
|
120
|
+
The two stages load different pools:
|
|
121
|
+
|
|
122
|
+
- Registration compute runs on ``device``. When ``device`` is cuda the
|
|
123
|
+
per-batch float32/FFT buffers live in VRAM, scaled by
|
|
124
|
+
``batch_size * Ly * Lx``. The reference-image correlation
|
|
125
|
+
(``pick_initial_reference``) and the binary read stay on host.
|
|
126
|
+
- Detection's binned movie is a host numpy array, and the default
|
|
127
|
+
``sparsery`` / ``sourcery`` detectors run on CPU. ``device`` is only
|
|
128
|
+
used by the cellpose path, which sees the 2D meanImg / max_proj, not
|
|
129
|
+
the movie. So the binned movie never enters VRAM.
|
|
130
|
+
|
|
131
|
+
Host RAM therefore peaks during detection (binned movie plus the
|
|
132
|
+
high-pass / sparsery copies, ~2.5x); VRAM peaks during registration
|
|
133
|
+
(or cellpose inference, when enabled). Neither VRAM term scales with
|
|
134
|
+
``n_frames``; host detection plateaus once ``n_frames // bin_size``
|
|
135
|
+
exceeds ``nbins``.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
ops : dict
|
|
140
|
+
Flat Suite2p ops. Reads ``nimg_init``, ``batch_size`` (registration
|
|
141
|
+
batch), ``nbins``, ``bin_size``, ``tau``, ``fs``, ``nchannels``, and
|
|
142
|
+
``anatomical_only``. Missing keys fall back to suite2p defaults.
|
|
143
|
+
Ly, Lx : int
|
|
144
|
+
Frame height and width in pixels. The detection crop (yrange/xrange)
|
|
145
|
+
is unknown before registration, so full ``Ly``/``Lx`` are used as an
|
|
146
|
+
upper bound.
|
|
147
|
+
n_frames : int
|
|
148
|
+
Number of frames in the plane.
|
|
149
|
+
device : str, optional (default "cuda")
|
|
150
|
+
Torch device. VRAM terms are reported only when this starts with
|
|
151
|
+
"cuda".
|
|
152
|
+
workers : int, optional (default 1)
|
|
153
|
+
Concurrent plane workers. Per-plane peaks are multiplied by this for
|
|
154
|
+
the ``*_total`` fields.
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
dict
|
|
159
|
+
Bytes for ``host_per_plane``, ``vram_per_plane``, ``host_total``,
|
|
160
|
+
``vram_total``. VRAM fields are 0 when ``device`` is not cuda.
|
|
161
|
+
|
|
162
|
+
Notes
|
|
163
|
+
-----
|
|
164
|
+
The 2.5x detection multiplier and the cpsam VRAM constant are rough; the
|
|
165
|
+
real values depend on data and hardware. Calibrate with
|
|
166
|
+
``torch.cuda.max_memory_allocated()`` and an RSS sample around the
|
|
167
|
+
registration / detection calls for tight bounds.
|
|
168
|
+
"""
|
|
169
|
+
cuda = str(device).startswith("cuda")
|
|
170
|
+
|
|
171
|
+
nimg_init = min(int(ops.get("nimg_init", 400)), n_frames)
|
|
172
|
+
reg_host = nimg_init * Ly * Lx * 10 # int16 frames + float64 ref corr (CPU)
|
|
173
|
+
|
|
174
|
+
nbins = int(ops.get("nbins", 5000))
|
|
175
|
+
bin_size = ops.get("bin_size") or max(
|
|
176
|
+
1, n_frames // nbins, round(ops.get("tau", 1.0) * ops.get("fs", 10.0))
|
|
177
|
+
)
|
|
178
|
+
nbinned = min(nbins, n_frames // bin_size)
|
|
179
|
+
detect_host = int(2.5 * nbinned * Ly * Lx * 4) # binned movie + hp/sparsery copies (CPU)
|
|
180
|
+
|
|
181
|
+
host_peak = max(reg_host, detect_host)
|
|
182
|
+
|
|
183
|
+
vram_peak = 0
|
|
184
|
+
if cuda:
|
|
185
|
+
reg_batch = int(ops.get("batch_size", 100))
|
|
186
|
+
nchan = int(ops.get("nchannels", 1))
|
|
187
|
+
reg_vram = nchan * 8 * reg_batch * Ly * Lx * 4 # ~8 float32/FFT buffers per batch
|
|
188
|
+
cpsam_vram = 1_500_000_000 if ops.get("anatomical_only", 0) else 0 # cpsam weights + activations
|
|
189
|
+
vram_peak = max(reg_vram, cpsam_vram)
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
"host_per_plane": host_peak,
|
|
193
|
+
"vram_per_plane": vram_peak,
|
|
194
|
+
"host_total": host_peak * max(1, workers),
|
|
195
|
+
"vram_total": vram_peak * max(1, workers),
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def bin1d(X, bin_size, axis=0):
|
|
200
|
+
"""
|
|
201
|
+
Mean bin over `axis` of `X` with bin `bin_size`.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
X : np.ndarray
|
|
206
|
+
Input array to be binned.
|
|
207
|
+
bin_size : int
|
|
208
|
+
Size of the bin. If <=0, no binning is performed.
|
|
209
|
+
axis : int, optional
|
|
210
|
+
Axis along which to bin. Default is 0.
|
|
211
|
+
|
|
212
|
+
Returns
|
|
213
|
+
-------
|
|
214
|
+
np.ndarray
|
|
215
|
+
Binned array with reduced size along the specified axis.
|
|
216
|
+
"""
|
|
217
|
+
if bin_size > 0:
|
|
218
|
+
size = list(X.shape)
|
|
219
|
+
Xb = X.swapaxes(0, axis)
|
|
220
|
+
size_new = Xb.shape
|
|
221
|
+
Xb = (
|
|
222
|
+
Xb[: size[axis] // bin_size * bin_size]
|
|
223
|
+
.reshape((size[axis] // bin_size, bin_size, *size_new[1:]))
|
|
224
|
+
.mean(axis=1)
|
|
225
|
+
)
|
|
226
|
+
Xb = Xb.swapaxes(axis, 0)
|
|
227
|
+
return Xb
|
|
228
|
+
else:
|
|
229
|
+
return X
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lbm_suite2p_python
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.1
|
|
4
4
|
Summary: Calcium Imaging Pipeline built with Suite2p, Cellpose and Rastermap
|
|
5
5
|
License-Expression: BSD-3-Clause
|
|
6
6
|
Project-URL: homepage, https://github.com/MillerBrainObservatory/LBM-Suite2p-Python
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import numpy as np
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
# mbo_utilities >= 4.0 exposes a single LazyArray base; isinstance covers
|
|
7
|
-
# every built-in and any third-party plugin. The class-name tuple is the
|
|
8
|
-
# fallback for mbo_utilities < 4.0, which has no shared base.
|
|
9
|
-
try:
|
|
10
|
-
from mbo_utilities import LazyArray as _LazyArray
|
|
11
|
-
except ImportError: # mbo_utilities < 4.0
|
|
12
|
-
_LazyArray = None
|
|
13
|
-
|
|
14
|
-
_LAZY_ARRAY_TYPES = (
|
|
15
|
-
"ScanImageArray",
|
|
16
|
-
"LBMArray",
|
|
17
|
-
"PiezoArray",
|
|
18
|
-
"SinglePlaneArray",
|
|
19
|
-
"Suite2pArray",
|
|
20
|
-
"MBOTiffArray",
|
|
21
|
-
"MboRawArray",
|
|
22
|
-
"TiffArray",
|
|
23
|
-
"ZarrArray",
|
|
24
|
-
"H5Array",
|
|
25
|
-
"NumpyArray",
|
|
26
|
-
"BinArray",
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _is_lazy_array(obj):
|
|
31
|
-
"""Check if obj is an mbo_utilities lazy array type."""
|
|
32
|
-
if _LazyArray is not None and isinstance(obj, _LazyArray):
|
|
33
|
-
return True
|
|
34
|
-
return type(obj).__name__ in _LAZY_ARRAY_TYPES
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _get_num_planes(arr):
|
|
38
|
-
"""
|
|
39
|
-
Get number of z-planes from a lazy array.
|
|
40
|
-
|
|
41
|
-
mbo_utilities arrays are always 5D TCZYX, so Z is at shape[2].
|
|
42
|
-
Falls back to legacy 4D TZYX (Z at shape[1]) and other heuristics
|
|
43
|
-
for non-mbo arrays.
|
|
44
|
-
|
|
45
|
-
Parameters
|
|
46
|
-
----------
|
|
47
|
-
arr : array-like
|
|
48
|
-
Input array, typically from mbo_utilities.
|
|
49
|
-
|
|
50
|
-
Returns
|
|
51
|
-
-------
|
|
52
|
-
int
|
|
53
|
-
Number of z-planes (1 if no Z dimension).
|
|
54
|
-
"""
|
|
55
|
-
# mbo_utilities Shape5DMixin
|
|
56
|
-
if hasattr(arr, "nz"):
|
|
57
|
-
return arr.nz
|
|
58
|
-
if hasattr(arr, "num_planes") and arr.num_planes is not None:
|
|
59
|
-
return arr.num_planes
|
|
60
|
-
shape = arr.shape
|
|
61
|
-
if len(shape) == 5:
|
|
62
|
-
return shape[2] # 5D TCZYX
|
|
63
|
-
if len(shape) == 4:
|
|
64
|
-
return shape[1] # legacy 4D TZYX
|
|
65
|
-
return 1
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _resize_masks_fit_crop(mask, target_shape):
|
|
69
|
-
"""Centers a mask within the target shape, cropping if too large or padding if too small."""
|
|
70
|
-
sy, sx = mask.shape
|
|
71
|
-
ty, tx = target_shape
|
|
72
|
-
|
|
73
|
-
# If mask is larger, crop it
|
|
74
|
-
if sy > ty or sx > tx:
|
|
75
|
-
start_y = (sy - ty) // 2
|
|
76
|
-
start_x = (sx - tx) // 2
|
|
77
|
-
return mask[start_y : start_y + ty, start_x : start_x + tx]
|
|
78
|
-
|
|
79
|
-
# If mask is smaller, pad it
|
|
80
|
-
resized_mask = np.zeros(target_shape, dtype=mask.dtype)
|
|
81
|
-
start_y = (ty - sy) // 2
|
|
82
|
-
start_x = (tx - sx) // 2
|
|
83
|
-
resized_mask[start_y : start_y + sy, start_x : start_x + sx] = mask
|
|
84
|
-
return resized_mask
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
def get_common_path(ops_files: list | tuple):
|
|
88
|
-
"""
|
|
89
|
-
Find the common parent path of all files.
|
|
90
|
-
|
|
91
|
-
Parameters
|
|
92
|
-
----------
|
|
93
|
-
ops_files : list or tuple
|
|
94
|
-
List of file paths.
|
|
95
|
-
|
|
96
|
-
Returns
|
|
97
|
-
-------
|
|
98
|
-
Path
|
|
99
|
-
Common parent directory of all files.
|
|
100
|
-
"""
|
|
101
|
-
if not isinstance(ops_files, (list, tuple)):
|
|
102
|
-
ops_files = [ops_files]
|
|
103
|
-
if len(ops_files) == 1:
|
|
104
|
-
path = Path(ops_files[0]).parent
|
|
105
|
-
while (
|
|
106
|
-
path.exists() and len(list(path.iterdir())) <= 1
|
|
107
|
-
): # Traverse up if only one item exists
|
|
108
|
-
path = path.parent
|
|
109
|
-
return path
|
|
110
|
-
else:
|
|
111
|
-
return Path(os.path.commonpath(ops_files))
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def bin1d(X, bin_size, axis=0):
|
|
115
|
-
"""
|
|
116
|
-
Mean bin over `axis` of `X` with bin `bin_size`.
|
|
117
|
-
|
|
118
|
-
Parameters
|
|
119
|
-
----------
|
|
120
|
-
X : np.ndarray
|
|
121
|
-
Input array to be binned.
|
|
122
|
-
bin_size : int
|
|
123
|
-
Size of the bin. If <=0, no binning is performed.
|
|
124
|
-
axis : int, optional
|
|
125
|
-
Axis along which to bin. Default is 0.
|
|
126
|
-
|
|
127
|
-
Returns
|
|
128
|
-
-------
|
|
129
|
-
np.ndarray
|
|
130
|
-
Binned array with reduced size along the specified axis.
|
|
131
|
-
"""
|
|
132
|
-
if bin_size > 0:
|
|
133
|
-
size = list(X.shape)
|
|
134
|
-
Xb = X.swapaxes(0, axis)
|
|
135
|
-
size_new = Xb.shape
|
|
136
|
-
Xb = (
|
|
137
|
-
Xb[: size[axis] // bin_size * bin_size]
|
|
138
|
-
.reshape((size[axis] // bin_size, bin_size, *size_new[1:]))
|
|
139
|
-
.mean(axis=1)
|
|
140
|
-
)
|
|
141
|
-
Xb = Xb.swapaxes(axis, 0)
|
|
142
|
-
return Xb
|
|
143
|
-
else:
|
|
144
|
-
return X
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{lbm_suite2p_python-3.1.1 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|