lbm_suite2p_python 2.5.4__tar.gz → 2.5.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. {lbm_suite2p_python-2.5.4/lbm_suite2p_python.egg-info → lbm_suite2p_python-2.5.7}/PKG-INFO +6 -6
  2. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/cellpose.py +16 -11
  3. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/grid_search.py +24 -15
  4. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/postprocessing.py +12 -6
  5. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/run_lsp.py +842 -261
  6. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/utils.py +12 -5
  7. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/zplane.py +249 -258
  8. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7/lbm_suite2p_python.egg-info}/PKG-INFO +6 -6
  9. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python.egg-info/SOURCES.txt +1 -0
  10. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python.egg-info/requires.txt +4 -5
  11. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/pyproject.toml +6 -22
  12. lbm_suite2p_python-2.5.7/tests/test_frame_count_aliases.py +317 -0
  13. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/LICENSE.md +0 -0
  14. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/MANIFEST.in +0 -0
  15. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/README.md +0 -0
  16. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/__init__.py +0 -0
  17. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/__main__.py +0 -0
  18. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/_benchmarking.py +0 -0
  19. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/cli.py +0 -0
  20. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/conversion.py +0 -0
  21. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/default_ops.py +0 -0
  22. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/gui.py +0 -0
  23. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/merging.py +0 -0
  24. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python/volume.py +0 -0
  25. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python.egg-info/dependency_links.txt +0 -0
  26. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python.egg-info/entry_points.txt +0 -0
  27. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/lbm_suite2p_python.egg-info/top_level.txt +0 -0
  28. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/setup.cfg +0 -0
  29. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/tests/test_pipeline_parameters.py +0 -0
  30. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/tests/test_refactored_pipeline.py +0 -0
  31. {lbm_suite2p_python-2.5.4 → lbm_suite2p_python-2.5.7}/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: 2.5.4
3
+ Version: 2.5.7
4
4
  Summary: Light Beads Microscopy Pipeline using Suite2p
5
5
  License-Expression: BSD-3-Clause
6
6
  Project-URL: homepage, https://github.com/MillerBrainObservatory/LBM-Suite2p-Python
@@ -8,12 +8,12 @@ Keywords: Pipeline,Numpy,Microscopy,ScanImage,Suite2p,tiff
8
8
  Classifier: Development Status :: 3 - Alpha
9
9
  Classifier: Intended Audience :: Science/Research
10
10
  Classifier: Programming Language :: Python :: 3 :: Only
11
- Requires-Python: <3.12.10,>=3.12.7
11
+ Requires-Python: <3.14,>=3.12.7
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE.md
14
- Requires-Dist: mbo_utilities>=2.6.0
15
- Provides-Extra: suite2p
16
- Requires-Dist: suite2p_mbo>=2.0.1; extra == "suite2p"
14
+ Requires-Dist: mbo_utilities>=2.6.3
15
+ Requires-Dist: suite2p_mbo>=2.0.1
16
+ Requires-Dist: setuptools<81
17
17
  Provides-Extra: rastermap
18
18
  Requires-Dist: rastermap; extra == "rastermap"
19
19
  Provides-Extra: cellpose
@@ -22,7 +22,7 @@ Provides-Extra: torch
22
22
  Requires-Dist: torch>=2.7.0; extra == "torch"
23
23
  Requires-Dist: torchvision>=0.22.0; extra == "torch"
24
24
  Provides-Extra: all
25
- Requires-Dist: lbm_suite2p_python[cellpose,rastermap,suite2p,torch]; extra == "all"
25
+ Requires-Dist: lbm_suite2p_python[cellpose,rastermap,torch]; extra == "all"
26
26
  Dynamic: license-file
27
27
 
28
28
  <p align="center">
@@ -37,9 +37,10 @@ def _compute_projection(
37
37
  Parameters
38
38
  ----------
39
39
  arr : array-like
40
- Input array (T, Y, X) for 3D or (T, Z, Y, X) for 4D.
40
+ Input array. mbo_utilities arrays are 5D TCZYX.
41
+ Legacy 4D TZYX and 3D TYX inputs are also supported.
41
42
  plane_idx : int, optional
42
- For 4D arrays, which z-plane to extract (0-indexed).
43
+ For volumetric arrays, which z-plane to extract (0-indexed).
43
44
  If None, uses all planes for 3D segmentation.
44
45
  method : str
45
46
  Projection method: 'max', 'mean', 'std', or 'percentile'.
@@ -53,19 +54,23 @@ def _compute_projection(
53
54
  """
54
55
  ndim = len(arr.shape)
55
56
 
56
- if ndim == 4:
57
- # (T, Z, Y, X)
57
+ if ndim == 5:
58
+ # 5D TCZYX: use channel 0
59
+ if plane_idx is not None:
60
+ data = arr[:, 0, plane_idx, :, :] # (T, Y, X)
61
+ else:
62
+ data = arr[:, 0, :, :, :] # (T, Z, Y, X)
63
+ elif ndim == 4:
64
+ # legacy 4D TZYX
58
65
  if plane_idx is not None:
59
- # extract single plane -> (T, Y, X)
60
66
  data = arr[:, plane_idx, :, :]
61
67
  else:
62
- # keep all planes -> (T, Z, Y, X)
63
68
  data = arr[:]
64
69
  elif ndim == 3:
65
- # (T, Y, X)
70
+ # legacy 3D TYX
66
71
  data = arr[:]
67
72
  else:
68
- raise ValueError(f"Expected 3D or 4D array, got {ndim}D")
73
+ raise ValueError(f"Expected 3D, 4D, or 5D array, got {ndim}D")
69
74
 
70
75
  # convert to numpy if lazy
71
76
  if hasattr(data, "compute"):
@@ -577,18 +582,18 @@ def cellpose(
577
582
 
578
583
  # get array info
579
584
  shape = arr.shape
580
- ndim = len(shape)
581
585
  num_planes = _get_num_planes(arr)
582
586
  num_frames = shape[0]
587
+ is_volumetric = num_planes > 1
583
588
 
584
589
  print(f"\nDataset info:")
585
590
  print(f" Shape: {shape}")
586
591
  print(f" Frames: {num_frames}")
587
592
  print(f" Planes: {num_planes}")
588
- print(f" Data type: {'4D volumetric' if ndim == 4 else '3D planar'}")
593
+ print(f" Data type: {'volumetric' if is_volumetric else 'planar'}")
589
594
 
590
595
  # normalize planes to 0-indexed list
591
- if ndim == 4:
596
+ if is_volumetric:
592
597
  planes_to_process = _normalize_planes(planes, num_planes)
593
598
  else:
594
599
  planes_to_process = [None] # single plane data
@@ -246,12 +246,15 @@ def grid_search(
246
246
  print(f"Processing plane {plane_num}")
247
247
  print(f"{'='*60}")
248
248
 
249
- # get plane dimensions
250
- if num_planes > 1:
251
- sample_frame = arr[0, plane_idx]
249
+ # get plane spatial dimensions
250
+ # mbo_utilities arrays are 5D TCZYX; legacy 4D TZYX and 3D TYX still supported
251
+ ndim = len(arr.shape)
252
+ if ndim == 5:
253
+ plane_Ly, plane_Lx = arr.shape[3], arr.shape[4]
254
+ elif ndim == 4:
255
+ plane_Ly, plane_Lx = arr.shape[2], arr.shape[3]
252
256
  else:
253
- sample_frame = arr[0] if arr.ndim == 3 else arr[0, 0]
254
- plane_Ly, plane_Lx = sample_frame.shape[-2], sample_frame.shape[-1]
257
+ plane_Ly, plane_Lx = arr.shape[-2], arr.shape[-1]
255
258
 
256
259
  # create plane subdirectory
257
260
  plane_tag = f"plane{plane_num:02d}" if len(planes_to_process) > 1 else ""
@@ -300,18 +303,24 @@ def grid_search(
300
303
  else:
301
304
  print(f"\nUsing existing base binary: {base_bin_file}")
302
305
 
303
- # run registration once on base
306
+ # run registration once on base (or skip if user disabled it)
304
307
  base_reg_file = base_dir / "data.bin"
308
+ user_wants_reg = ops.get("do_registration", 1) not in (0, False)
309
+
305
310
  if not base_reg_file.exists() or force_reg:
306
- print(f"Running registration on base...")
307
- reg_ops = copy.deepcopy(default_ops())
308
- if base_ops_file.exists():
309
- reg_ops.update(load_ops(base_ops_file))
310
- reg_ops["do_registration"] = 1
311
- reg_ops["roidetect"] = 0
312
- np.save(base_ops_file, reg_ops)
313
- run_plane_bin(base_ops_file)
314
- print(f"Registration complete.")
311
+ if user_wants_reg:
312
+ print(f"Running registration on base...")
313
+ reg_ops = copy.deepcopy(default_ops())
314
+ if base_ops_file.exists():
315
+ reg_ops.update(load_ops(base_ops_file))
316
+ reg_ops["do_registration"] = 1
317
+ reg_ops["roidetect"] = 0
318
+ np.save(base_ops_file, reg_ops)
319
+ run_plane_bin(base_ops_file)
320
+ print(f"Registration complete.")
321
+ else:
322
+ print(f"Registration disabled, using raw binary as base.")
323
+ shutil.copy2(base_bin_file, base_reg_file)
315
324
  else:
316
325
  print(f"Using existing registered binary: {base_reg_file}")
317
326
 
@@ -77,8 +77,12 @@ def _save_filtered_iscell(plane_dir, iscell_filtered, iscell_original=None):
77
77
  iscell_path = plane_dir / "iscell.npy"
78
78
 
79
79
  # Load original to preserve probabilities
80
- if iscell_original is None and iscell_path.exists():
81
- iscell_original = np.load(iscell_path, allow_pickle=True)
80
+ if iscell_original is None:
81
+ backup_path = plane_dir / "iscell_suite2p.npy"
82
+ if backup_path.exists():
83
+ iscell_original = np.load(backup_path, allow_pickle=True)
84
+ elif iscell_path.exists():
85
+ iscell_original = np.load(iscell_path, allow_pickle=True)
82
86
 
83
87
  if iscell_original is not None and iscell_original.ndim == 2:
84
88
  # Preserve probability column
@@ -611,7 +615,7 @@ def apply_filters(
611
615
 
612
616
  # Save final result if requested
613
617
  if save and plane_dir is not None:
614
- _save_filtered_iscell(plane_dir, iscell_current)
618
+ _save_filtered_iscell(plane_dir, iscell_current, iscell_original=iscell_orig)
615
619
 
616
620
  n_total = total_removed.sum()
617
621
  n_orig = iscell_orig.sum()
@@ -1031,13 +1035,14 @@ def compute_trace_quality_score(
1031
1035
 
1032
1036
  n_neurons = F.shape[0]
1033
1037
 
1034
- # Neuropil correction
1038
+ # neuropil correction and rectification
1035
1039
  if Fneu is not None:
1036
1040
  F_corr = F - 0.7 * Fneu
1037
1041
  else:
1038
1042
  F_corr = F
1043
+ F_corr = np.maximum(F_corr, 0)
1039
1044
 
1040
- # Compute baseline and dF/F
1045
+ # compute baseline and dF/F
1041
1046
  baseline = np.percentile(F_corr, 20, axis=1, keepdims=True)
1042
1047
  baseline = np.maximum(baseline, 1e-6)
1043
1048
  dff = (F_corr - baseline) / baseline
@@ -1203,8 +1208,9 @@ def compute_roi_stats(plane_dir, fs=None):
1203
1208
 
1204
1209
  n_rois = F.shape[0]
1205
1210
 
1206
- # neuropil correction and dF/F
1211
+ # neuropil correction, rectify negatives, and dF/F
1207
1212
  F_corr = F - 0.7 * Fneu
1213
+ F_corr = np.maximum(F_corr, 0)
1208
1214
  baseline = np.percentile(F_corr, 20, axis=1, keepdims=True)
1209
1215
  baseline = np.maximum(baseline, 1e-6)
1210
1216
  dff = (F_corr - baseline) / baseline