lbm_suite2p_python 3.2.0__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.2.0/lbm_suite2p_python.egg-info → lbm_suite2p_python-3.2.1}/PKG-INFO +1 -1
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/cli.py +11 -1
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/run_lsp.py +115 -36
- lbm_suite2p_python-3.2.1/lbm_suite2p_python/utils.py +229 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1/lbm_suite2p_python.egg-info}/PKG-INFO +1 -1
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/pyproject.toml +106 -106
- lbm_suite2p_python-3.2.0/lbm_suite2p_python/utils.py +0 -144
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/LICENSE.md +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/MANIFEST.in +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/README.md +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/__init__.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/__main__.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/_benchmarking.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/cellpose.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/conversion.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/db_settings.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/default_ops.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/grid_search.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/gui.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/merging.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/postprocessing.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/volume.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python/zplane.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/SOURCES.txt +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/dependency_links.txt +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/entry_points.txt +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/requires.txt +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/top_level.txt +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/setup.cfg +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/tests/test_frame_count_aliases.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/tests/test_pipeline_parameters.py +0 -0
- {lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/tests/test_refactored_pipeline.py +0 -0
- {lbm_suite2p_python-3.2.0 → 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.2.
|
|
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
|
|
@@ -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,
|
|
@@ -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,
|
|
@@ -793,7 +830,9 @@ def pipeline(
|
|
|
793
830
|
force_reg: bool = False,
|
|
794
831
|
force_detect: bool = False,
|
|
795
832
|
replot: bool = True,
|
|
833
|
+
timepoints: list | int | None = None,
|
|
796
834
|
num_timepoints: int = None,
|
|
835
|
+
num_zplanes: int = None,
|
|
797
836
|
frame_indices: list | None = None,
|
|
798
837
|
dff_window_size: int = None,
|
|
799
838
|
dff_percentile: int = 20,
|
|
@@ -859,16 +898,21 @@ def pipeline(
|
|
|
859
898
|
Regenerate per-plane figures. Set False to skip per-plane figure
|
|
860
899
|
regeneration (e.g. the volumetric aggregate over already-plotted
|
|
861
900
|
planes); suite2p and the volumetric plots are unaffected.
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
``frame_indices`` instead.
|
|
866
|
-
frame_indices : list[int], optional
|
|
867
|
-
Explicit 0-based frame indices to process. Supports stride
|
|
868
|
-
(e.g. ``list(range(0, 1574, 2))`` for every other frame).
|
|
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).
|
|
869
904
|
When provided, the implicit stride is used by `OutputMetadata`
|
|
870
905
|
to reactively scale `fs` (e.g. stride of 2 → `fs / 2` in the
|
|
871
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.
|
|
872
916
|
dff_window_size : int, optional
|
|
873
917
|
Window size for rolling percentile dF/F baseline (frames).
|
|
874
918
|
If None, auto-calculated as ~10 * tau * fs.
|
|
@@ -981,7 +1025,15 @@ def pipeline(
|
|
|
981
1025
|
DeprecationWarning,
|
|
982
1026
|
stacklevel=2,
|
|
983
1027
|
)
|
|
984
|
-
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))
|
|
985
1037
|
|
|
986
1038
|
# flatten (db, settings) into ops so downstream run_volume / run_plane
|
|
987
1039
|
# don't each need to forward the pair. explicit ops keys still win.
|
|
@@ -991,14 +1043,10 @@ def pipeline(
|
|
|
991
1043
|
|
|
992
1044
|
reader_kwargs = reader_kwargs or {}
|
|
993
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.
|
|
994
1048
|
if num_timepoints is not None:
|
|
995
|
-
writer_kwargs["
|
|
996
|
-
|
|
997
|
-
# 1-based frame numbers.
|
|
998
|
-
if frame_indices is not None:
|
|
999
|
-
writer_kwargs["frames"] = [int(i) + 1 for i in frame_indices]
|
|
1000
|
-
# don't double-pass num_frames; len(frame_indices) is implicit
|
|
1001
|
-
writer_kwargs.pop("num_frames", None)
|
|
1049
|
+
writer_kwargs["num_timepoints"] = num_timepoints
|
|
1002
1050
|
|
|
1003
1051
|
# Always load array to check dimensions and ensure downstream functions have the array shape
|
|
1004
1052
|
# If input is already array, this is fast. If path or list of paths, it loads lazy array.
|
|
@@ -1034,7 +1082,7 @@ def pipeline(
|
|
|
1034
1082
|
force_reg=force_reg,
|
|
1035
1083
|
force_detect=force_detect,
|
|
1036
1084
|
replot=replot,
|
|
1037
|
-
|
|
1085
|
+
timepoints=timepoints,
|
|
1038
1086
|
dff_window_size=dff_window_size,
|
|
1039
1087
|
dff_percentile=dff_percentile,
|
|
1040
1088
|
dff_smooth_window=dff_smooth_window,
|
|
@@ -1074,7 +1122,7 @@ def pipeline(
|
|
|
1074
1122
|
force_reg=force_reg,
|
|
1075
1123
|
force_detect=force_detect,
|
|
1076
1124
|
replot=replot,
|
|
1077
|
-
|
|
1125
|
+
timepoints=timepoints,
|
|
1078
1126
|
dff_window_size=dff_window_size,
|
|
1079
1127
|
dff_percentile=dff_percentile,
|
|
1080
1128
|
dff_smooth_window=dff_smooth_window,
|
|
@@ -1246,6 +1294,9 @@ def run_volume(
|
|
|
1246
1294
|
force_reg: bool = False,
|
|
1247
1295
|
force_detect: bool = False,
|
|
1248
1296
|
replot: bool = True,
|
|
1297
|
+
timepoints: list | int | None = None,
|
|
1298
|
+
num_timepoints: int = None,
|
|
1299
|
+
num_zplanes: int = None,
|
|
1249
1300
|
frame_indices: list | None = None,
|
|
1250
1301
|
dff_window_size: int = None,
|
|
1251
1302
|
dff_percentile: int = 20,
|
|
@@ -1337,6 +1388,17 @@ def run_volume(
|
|
|
1337
1388
|
_resolve_gpu_env()
|
|
1338
1389
|
_apply_thread_limits(threads_per_worker)
|
|
1339
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
|
+
|
|
1340
1402
|
# Handle input data
|
|
1341
1403
|
input_arr = None
|
|
1342
1404
|
input_paths = []
|
|
@@ -1430,7 +1492,7 @@ def run_volume(
|
|
|
1430
1492
|
force_reg=force_reg,
|
|
1431
1493
|
force_detect=force_detect,
|
|
1432
1494
|
replot=replot,
|
|
1433
|
-
|
|
1495
|
+
timepoints=timepoints,
|
|
1434
1496
|
dff_window_size=dff_window_size,
|
|
1435
1497
|
dff_percentile=dff_percentile,
|
|
1436
1498
|
dff_smooth_window=dff_smooth_window,
|
|
@@ -2283,6 +2345,8 @@ def run_plane(
|
|
|
2283
2345
|
force_reg: bool = False,
|
|
2284
2346
|
force_detect: bool = False,
|
|
2285
2347
|
replot: bool = True,
|
|
2348
|
+
timepoints: list | int | None = None,
|
|
2349
|
+
num_timepoints: int = None,
|
|
2286
2350
|
frame_indices: list | None = None,
|
|
2287
2351
|
dff_window_size: int = None,
|
|
2288
2352
|
dff_percentile: int = 20,
|
|
@@ -2353,13 +2417,18 @@ def run_plane(
|
|
|
2353
2417
|
Example: {"n_clusters": 50, "n_PCs": 64}.
|
|
2354
2418
|
save_json : bool, default False
|
|
2355
2419
|
Save ops as JSON.
|
|
2356
|
-
|
|
2357
|
-
Explicit
|
|
2358
|
-
(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).
|
|
2359
2423
|
When provided, the binary on disk contains exactly these
|
|
2360
|
-
|
|
2361
|
-
based on the implicit stride. Takes precedence over
|
|
2362
|
-
``
|
|
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.
|
|
2363
2432
|
plane_name : str, optional
|
|
2364
2433
|
Custom name for the plane subdirectory.
|
|
2365
2434
|
reader_kwargs : dict, optional
|
|
@@ -2379,6 +2448,14 @@ def run_plane(
|
|
|
2379
2448
|
|
|
2380
2449
|
_resolve_gpu_env()
|
|
2381
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
|
+
|
|
2382
2459
|
progress_callback = kwargs.pop("progress_callback", None)
|
|
2383
2460
|
|
|
2384
2461
|
if "debug" in kwargs:
|
|
@@ -2637,7 +2714,8 @@ def run_plane(
|
|
|
2637
2714
|
else:
|
|
2638
2715
|
# prefer the user-specified frame limit over raw array shape
|
|
2639
2716
|
nframes_hint = (
|
|
2640
|
-
writer_kwargs.get("
|
|
2717
|
+
writer_kwargs.get("num_timepoints")
|
|
2718
|
+
or writer_kwargs.get("num_frames")
|
|
2641
2719
|
or ops.get("nframes")
|
|
2642
2720
|
)
|
|
2643
2721
|
if not nframes_hint and input_arr is not None and hasattr(input_arr, "shape"):
|
|
@@ -2666,8 +2744,8 @@ def run_plane(
|
|
|
2666
2744
|
ops_file = plane_dir / "ops.npy"
|
|
2667
2745
|
|
|
2668
2746
|
# extract expected dims from input for cache validation
|
|
2669
|
-
# honors
|
|
2670
|
-
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")
|
|
2671
2749
|
exp_ly = exp_lx = None
|
|
2672
2750
|
if input_arr is not None and hasattr(input_arr, "shape"):
|
|
2673
2751
|
if exp_nframes is None:
|
|
@@ -2796,12 +2874,13 @@ def run_plane(
|
|
|
2796
2874
|
write_planes = [plane] if _get_num_planes(file) > 1 else None
|
|
2797
2875
|
|
|
2798
2876
|
write_kw = dict(writer_kwargs)
|
|
2799
|
-
# If the caller gave us explicit
|
|
2800
|
-
# `
|
|
2801
|
-
#
|
|
2802
|
-
#
|
|
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.
|
|
2803
2881
|
if frame_indices is not None:
|
|
2804
|
-
write_kw["
|
|
2882
|
+
write_kw["timepoints"] = [int(i) + 1 for i in frame_indices]
|
|
2883
|
+
write_kw.pop("num_timepoints", None)
|
|
2805
2884
|
write_kw.pop("num_frames", None)
|
|
2806
2885
|
|
|
2807
2886
|
imwrite(
|
|
@@ -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.2.
|
|
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,106 +1,106 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=61", "wheel"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "lbm_suite2p_python"
|
|
7
|
-
version = "3.2.
|
|
8
|
-
description = "Calcium Imaging Pipeline built with Suite2p, Cellpose and Rastermap"
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
license = "BSD-3-Clause"
|
|
11
|
-
requires-python = ">=3.12.7,<3.14"
|
|
12
|
-
keywords = ["Pipeline", "Numpy", "Microscopy", "ScanImage", "Suite2p", "tiff"]
|
|
13
|
-
urls = {homepage = "https://github.com/MillerBrainObservatory/LBM-Suite2p-Python"}
|
|
14
|
-
classifiers=[
|
|
15
|
-
"Development Status :: 3 - Alpha",
|
|
16
|
-
"Intended Audience :: Science/Research",
|
|
17
|
-
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
-
]
|
|
19
|
-
|
|
20
|
-
dependencies = [
|
|
21
|
-
"mbo_utilities>=3.2.1",
|
|
22
|
-
"suite2p>=1.0.0.1",
|
|
23
|
-
"setuptools<81",
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
[project.scripts]
|
|
27
|
-
lsp = "lbm_suite2p_python.__main__:main"
|
|
28
|
-
cellpose-gui = "lbm_suite2p_python.gui:main"
|
|
29
|
-
|
|
30
|
-
[project.optional-dependencies]
|
|
31
|
-
rastermap = [
|
|
32
|
-
"rastermap",
|
|
33
|
-
]
|
|
34
|
-
# Cellpose for anatomical cell detection (requires PyTorch)
|
|
35
|
-
cellpose = [
|
|
36
|
-
"cellpose>=4.0.6",
|
|
37
|
-
]
|
|
38
|
-
# All optional dependencies
|
|
39
|
-
all = [
|
|
40
|
-
"lbm_suite2p_python[rastermap,cellpose]",
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
-
[dependency-groups]
|
|
44
|
-
dev = [
|
|
45
|
-
"pytest>=8.0.0",
|
|
46
|
-
]
|
|
47
|
-
docs = [
|
|
48
|
-
"docutils>=0.21.2",
|
|
49
|
-
"myst-nb>=1.2.0",
|
|
50
|
-
"sphinx>=8.1.3",
|
|
51
|
-
"roman-numerals-py>=2.0.0,<3.0.0", # 3.x is broken
|
|
52
|
-
"sphinx-autodoc2>=0.5.0",
|
|
53
|
-
"sphinx-copybutton>=0.5.2",
|
|
54
|
-
"sphinx_togglebutton",
|
|
55
|
-
"sphinx-design>=0.6.1",
|
|
56
|
-
"sphinxcontrib-bibtex",
|
|
57
|
-
"sphinx-tippy",
|
|
58
|
-
"sphinx_book_theme",
|
|
59
|
-
"numpydoc",
|
|
60
|
-
"ipykernel",
|
|
61
|
-
"sphinxcontrib-images",
|
|
62
|
-
"sphinxcontrib-video",
|
|
63
|
-
"jupytext",
|
|
64
|
-
"scikit-image",
|
|
65
|
-
"scipy",
|
|
66
|
-
"pandas",
|
|
67
|
-
"suite2p",
|
|
68
|
-
]
|
|
69
|
-
|
|
70
|
-
# https://github.com/charliermarsh/ruff
|
|
71
|
-
[tool.ruff]
|
|
72
|
-
line-length = 88
|
|
73
|
-
src = ["lbm_suite2p_python"]
|
|
74
|
-
exclude = ["docs", "exclude", "demos", "scripts", "dev"]
|
|
75
|
-
|
|
76
|
-
[tool.ruff.lint]
|
|
77
|
-
pydocstyle = { convention = "numpy" }
|
|
78
|
-
select = ["ALL"]
|
|
79
|
-
ignore = [
|
|
80
|
-
"D401", # First line should be in imperative mood (remove to opt in)
|
|
81
|
-
"COM812", # Missing trailing comma (conflicts with ruff format)
|
|
82
|
-
"ISC001", # Import sorting (conflicts with ruff format)
|
|
83
|
-
"FIX002", # Fixable issue
|
|
84
|
-
"DOC201", # TODO enable in follow-up PR; no doc for return type.
|
|
85
|
-
"FBT", # TODO: enable in follow-up PR; require bool options to be keyword-only.
|
|
86
|
-
]
|
|
87
|
-
|
|
88
|
-
[tool.ruff.lint.per-file-ignores]
|
|
89
|
-
"docs/*.py" = ["D"]
|
|
90
|
-
|
|
91
|
-
[tool.setuptools.exclude-package-data]
|
|
92
|
-
"*" = ["data/*"]
|
|
93
|
-
|
|
94
|
-
[tool.setuptools.packages.find]
|
|
95
|
-
where = ["."]
|
|
96
|
-
include = ["lbm_suite2p_python*"]
|
|
97
|
-
|
|
98
|
-
[tool.coverage.report]
|
|
99
|
-
exclude_lines = [
|
|
100
|
-
"pragma: no cover",
|
|
101
|
-
"if TYPE_CHECKING:",
|
|
102
|
-
"@overload",
|
|
103
|
-
"except ImportError",
|
|
104
|
-
"\\.\\.\\.",
|
|
105
|
-
"raise NotImplementedError()"
|
|
106
|
-
]
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lbm_suite2p_python"
|
|
7
|
+
version = "3.2.1"
|
|
8
|
+
description = "Calcium Imaging Pipeline built with Suite2p, Cellpose and Rastermap"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "BSD-3-Clause"
|
|
11
|
+
requires-python = ">=3.12.7,<3.14"
|
|
12
|
+
keywords = ["Pipeline", "Numpy", "Microscopy", "ScanImage", "Suite2p", "tiff"]
|
|
13
|
+
urls = {homepage = "https://github.com/MillerBrainObservatory/LBM-Suite2p-Python"}
|
|
14
|
+
classifiers=[
|
|
15
|
+
"Development Status :: 3 - Alpha",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
dependencies = [
|
|
21
|
+
"mbo_utilities>=3.2.1",
|
|
22
|
+
"suite2p>=1.0.0.1",
|
|
23
|
+
"setuptools<81",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
lsp = "lbm_suite2p_python.__main__:main"
|
|
28
|
+
cellpose-gui = "lbm_suite2p_python.gui:main"
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
rastermap = [
|
|
32
|
+
"rastermap",
|
|
33
|
+
]
|
|
34
|
+
# Cellpose for anatomical cell detection (requires PyTorch)
|
|
35
|
+
cellpose = [
|
|
36
|
+
"cellpose>=4.0.6",
|
|
37
|
+
]
|
|
38
|
+
# All optional dependencies
|
|
39
|
+
all = [
|
|
40
|
+
"lbm_suite2p_python[rastermap,cellpose]",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[dependency-groups]
|
|
44
|
+
dev = [
|
|
45
|
+
"pytest>=8.0.0",
|
|
46
|
+
]
|
|
47
|
+
docs = [
|
|
48
|
+
"docutils>=0.21.2",
|
|
49
|
+
"myst-nb>=1.2.0",
|
|
50
|
+
"sphinx>=8.1.3",
|
|
51
|
+
"roman-numerals-py>=2.0.0,<3.0.0", # 3.x is broken
|
|
52
|
+
"sphinx-autodoc2>=0.5.0",
|
|
53
|
+
"sphinx-copybutton>=0.5.2",
|
|
54
|
+
"sphinx_togglebutton",
|
|
55
|
+
"sphinx-design>=0.6.1",
|
|
56
|
+
"sphinxcontrib-bibtex",
|
|
57
|
+
"sphinx-tippy",
|
|
58
|
+
"sphinx_book_theme",
|
|
59
|
+
"numpydoc",
|
|
60
|
+
"ipykernel",
|
|
61
|
+
"sphinxcontrib-images",
|
|
62
|
+
"sphinxcontrib-video",
|
|
63
|
+
"jupytext",
|
|
64
|
+
"scikit-image",
|
|
65
|
+
"scipy",
|
|
66
|
+
"pandas",
|
|
67
|
+
"suite2p",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
# https://github.com/charliermarsh/ruff
|
|
71
|
+
[tool.ruff]
|
|
72
|
+
line-length = 88
|
|
73
|
+
src = ["lbm_suite2p_python"]
|
|
74
|
+
exclude = ["docs", "exclude", "demos", "scripts", "dev"]
|
|
75
|
+
|
|
76
|
+
[tool.ruff.lint]
|
|
77
|
+
pydocstyle = { convention = "numpy" }
|
|
78
|
+
select = ["ALL"]
|
|
79
|
+
ignore = [
|
|
80
|
+
"D401", # First line should be in imperative mood (remove to opt in)
|
|
81
|
+
"COM812", # Missing trailing comma (conflicts with ruff format)
|
|
82
|
+
"ISC001", # Import sorting (conflicts with ruff format)
|
|
83
|
+
"FIX002", # Fixable issue
|
|
84
|
+
"DOC201", # TODO enable in follow-up PR; no doc for return type.
|
|
85
|
+
"FBT", # TODO: enable in follow-up PR; require bool options to be keyword-only.
|
|
86
|
+
]
|
|
87
|
+
|
|
88
|
+
[tool.ruff.lint.per-file-ignores]
|
|
89
|
+
"docs/*.py" = ["D"]
|
|
90
|
+
|
|
91
|
+
[tool.setuptools.exclude-package-data]
|
|
92
|
+
"*" = ["data/*"]
|
|
93
|
+
|
|
94
|
+
[tool.setuptools.packages.find]
|
|
95
|
+
where = ["."]
|
|
96
|
+
include = ["lbm_suite2p_python*"]
|
|
97
|
+
|
|
98
|
+
[tool.coverage.report]
|
|
99
|
+
exclude_lines = [
|
|
100
|
+
"pragma: no cover",
|
|
101
|
+
"if TYPE_CHECKING:",
|
|
102
|
+
"@overload",
|
|
103
|
+
"except ImportError",
|
|
104
|
+
"\\.\\.\\.",
|
|
105
|
+
"raise NotImplementedError()"
|
|
106
|
+
]
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{lbm_suite2p_python-3.2.0 → lbm_suite2p_python-3.2.1}/lbm_suite2p_python.egg-info/requires.txt
RENAMED
|
File without changes
|
{lbm_suite2p_python-3.2.0 → 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
|