lbm_suite2p_python 3.0.8__tar.gz → 3.1.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.
Files changed (32) hide show
  1. {lbm_suite2p_python-3.0.8/lbm_suite2p_python.egg-info → lbm_suite2p_python-3.1.1}/PKG-INFO +2 -2
  2. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/__init__.py +2 -0
  3. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/cli.py +2 -2
  4. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/db_settings.py +114 -26
  5. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/run_lsp.py +43 -22
  6. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/utils.py +10 -1
  7. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/volume.py +2 -2
  8. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/zplane.py +4950 -4950
  9. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1/lbm_suite2p_python.egg-info}/PKG-INFO +2 -2
  10. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python.egg-info/requires.txt +1 -1
  11. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/pyproject.toml +106 -106
  12. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/LICENSE.md +0 -0
  13. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/MANIFEST.in +0 -0
  14. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/README.md +0 -0
  15. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/__main__.py +0 -0
  16. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/_benchmarking.py +0 -0
  17. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/cellpose.py +0 -0
  18. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/conversion.py +0 -0
  19. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/default_ops.py +0 -0
  20. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/grid_search.py +0 -0
  21. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/gui.py +0 -0
  22. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/merging.py +0 -0
  23. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python/postprocessing.py +0 -0
  24. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python.egg-info/SOURCES.txt +0 -0
  25. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python.egg-info/dependency_links.txt +0 -0
  26. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python.egg-info/entry_points.txt +0 -0
  27. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/lbm_suite2p_python.egg-info/top_level.txt +0 -0
  28. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/setup.cfg +0 -0
  29. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/tests/test_frame_count_aliases.py +0 -0
  30. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/tests/test_pipeline_parameters.py +0 -0
  31. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.1}/tests/test_refactored_pipeline.py +0 -0
  32. {lbm_suite2p_python-3.0.8 → lbm_suite2p_python-3.1.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.0.8
3
+ Version: 3.1.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
@@ -11,7 +11,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
11
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>=3.0.7
14
+ Requires-Dist: mbo_utilities>=3.2.1
15
15
  Requires-Dist: suite2p>=1.0.0.1
16
16
  Requires-Dist: setuptools<81
17
17
  Provides-Extra: rastermap
@@ -70,6 +70,7 @@ from lbm_suite2p_python.postprocessing import (
70
70
  load_ops,
71
71
  load_planar_results,
72
72
  dff_rolling_percentile,
73
+ dff_median_filter,
73
74
  zscore_trace,
74
75
  baseline_percentile_dff,
75
76
  dff_shot_noise,
@@ -133,6 +134,7 @@ __all__ = [
133
134
  "load_ops",
134
135
  "load_planar_results",
135
136
  "dff_rolling_percentile",
137
+ "dff_median_filter",
136
138
  "zscore_trace",
137
139
  "baseline_percentile_dff",
138
140
  "dff_shot_noise",
@@ -208,9 +208,9 @@ Examples:
208
208
  help="smoothing window for dF/F"
209
209
  )
210
210
  dff.add_argument(
211
- "--norm-method", choices=["dff", "zscore"], default="dff",
211
+ "--norm-method", choices=["dff", "zscore"], default="zscore",
212
212
  dest="norm_method",
213
- help="normalization for norm_traces.npy (default: dff)"
213
+ help="normalization for norm_traces.npy (default: zscore)"
214
214
  )
215
215
  dff.add_argument(
216
216
  "--correct-neuropil", dest="correct_neuropil",
@@ -175,20 +175,121 @@ _FORK_TO_UPSTREAM_RENAMES: dict[str, str] = {
175
175
  "pretrained_model": "cellpose_model",
176
176
  }
177
177
 
178
- # fork's anatomical_only int (1-4) selects which image cellpose runs on.
179
- # upstream replaced this with a string in cellpose_settings.img. mapping:
180
- # 1 -> 'max_proj / meanImg' (log ratio)
181
- # 2 -> 'meanImg'
182
- # 3 -> enhanced_mean_img (REMOVED upstream — no equivalent string;
183
- # falls through to max_proj branch)
184
- # 4 -> 'max_proj' (anything not matching the two strings hits else: img=max_proj)
178
+ # fork's anatomical_only int selects which image cellpose runs on;
179
+ # upstream encodes this via cellpose_settings.img. 3 (enhanced mean) was
180
+ # removed from suite2p detection — reconcile_detection_keys maps it to 1.
185
181
  _ANATOMICAL_ONLY_TO_IMG: dict[int, str] = {
186
182
  1: "max_proj / meanImg",
187
183
  2: "meanImg",
188
184
  4: "max_proj",
189
185
  }
186
+
187
+ # Values suite2p actually accepts. Mirrors suite2p.parameters:
188
+ # detection.algorithm description -> ['sparsery', 'sourcery', 'cellpose'];
189
+ # cellpose_settings.img description -> ['max_proj / meanImg', 'meanImg',
190
+ # 'max_proj']. Hardcoded (the schema lists these only in a description
191
+ # string) and kept in sync with upstream.
192
+ VALID_DETECTION_ALGORITHMS = ("sparsery", "sourcery", "cellpose")
193
+ VALID_CELLPOSE_IMG = ("max_proj / meanImg", "meanImg", "max_proj")
194
+
195
+ # legacy / renamed cellpose image spellings -> current suite2p string. the
196
+ # old enhanced-mean image (meanImgE) was removed from suite2p detection, so
197
+ # it maps to suite2p's default; use spatial_hp_cp (highpass_spatial) > 0 to
198
+ # recover the sharpening.
199
+ _LEGACY_IMG_ALIASES = {
200
+ "meanimge": "max_proj / meanImg",
201
+ "enhanced_meanimg": "max_proj / meanImg",
202
+ "enhanced_mean_img": "max_proj / meanImg",
203
+ "mean_img": "meanImg",
204
+ "maximg": "max_proj",
205
+ "max_img": "max_proj",
206
+ "maxproj": "max_proj",
207
+ }
190
208
  _UPSTREAM_TO_FORK_RENAMES = {v: k for k, v in _FORK_TO_UPSTREAM_RENAMES.items()}
191
209
 
210
+
211
+ def reconcile_detection_keys(ops: dict | None) -> dict | None:
212
+ """Make the flat detection keys consistent and valid for suite2p.
213
+
214
+ The fork spells detection as `anatomical_only` (int) / `sparse_mode`
215
+ (bool); suite2p uses `algorithm` (str). Users mix both. This keeps them
216
+ mutually consistent and validates them against what suite2p accepts, so
217
+ either spelling engages identical code paths. Mutates and returns `ops`
218
+ (no-op for non-dicts); idempotent.
219
+
220
+ - `algorithm="cellpose"` with no `anatomical_only` -> `anatomical_only=1`
221
+ (suite2p's default cellpose image), so the fork's anatomical paths run.
222
+ - `anatomical_only>0` with no `algorithm` -> `algorithm="cellpose"`.
223
+ - `algorithm` in {sparsery, sourcery} -> `anatomical_only=0`, `sparse_mode`.
224
+ - legacy `anatomical_only=3` (enhanced mean, removed upstream) -> 1 + warn.
225
+ - unknown `algorithm` / `img` -> warn and fall back to a valid value.
226
+ """
227
+ import warnings
228
+
229
+ if not isinstance(ops, dict):
230
+ return ops
231
+
232
+ algo = ops.get("algorithm")
233
+ if isinstance(algo, str):
234
+ algo = algo.strip()
235
+ if algo in VALID_DETECTION_ALGORITHMS:
236
+ ops["algorithm"] = algo
237
+ else:
238
+ warnings.warn(
239
+ f"Unknown detection algorithm {ops['algorithm']!r}; suite2p "
240
+ f"accepts {VALID_DETECTION_ALGORITHMS}. Ignoring it (falling "
241
+ "back to anatomical_only / suite2p default).",
242
+ stacklevel=2,
243
+ )
244
+ ops.pop("algorithm", None)
245
+ algo = None
246
+ else:
247
+ algo = None
248
+
249
+ anat = ops.get("anatomical_only")
250
+ try:
251
+ anat_int = int(anat) if anat is not None else None
252
+ except (TypeError, ValueError):
253
+ anat_int = None
254
+
255
+ if anat_int == 3:
256
+ warnings.warn(
257
+ "anatomical_only=3 (enhanced mean image) was removed from suite2p "
258
+ "detection; using anatomical_only=1 (suite2p default image). Set "
259
+ "spatial_hp_cp>0 to sharpen the detection image.",
260
+ stacklevel=2,
261
+ )
262
+ anat_int = 1
263
+ ops["anatomical_only"] = 1
264
+
265
+ if algo == "cellpose":
266
+ if not anat_int or anat_int <= 0:
267
+ ops["anatomical_only"] = 1 # suite2p's default cellpose image
268
+ elif algo in ("sparsery", "sourcery"):
269
+ ops["anatomical_only"] = 0
270
+ ops.setdefault("sparse_mode", algo == "sparsery")
271
+ elif algo is None and anat_int and anat_int > 0:
272
+ ops["algorithm"] = "cellpose"
273
+
274
+ img = ops.get("img")
275
+ if isinstance(img, str) and img not in VALID_CELLPOSE_IMG:
276
+ mapped = _LEGACY_IMG_ALIASES.get(img.strip().lower())
277
+ if mapped:
278
+ warnings.warn(
279
+ f"cellpose image {img!r} is legacy/renamed; using {mapped!r}.",
280
+ stacklevel=2,
281
+ )
282
+ ops["img"] = mapped
283
+ else:
284
+ warnings.warn(
285
+ f"Unknown cellpose image {img!r}; suite2p accepts "
286
+ f"{VALID_CELLPOSE_IMG}. Ignoring it (using suite2p default).",
287
+ stacklevel=2,
288
+ )
289
+ ops.pop("img", None)
290
+
291
+ return ops
292
+
192
293
  # per-section flat-key disambiguation. Several upstream sections share
193
294
  # an upstream key. When flattened to ops, the second iteration overwrites
194
295
  # the first; on the reverse derivation, the surviving value gets fanned
@@ -286,8 +387,9 @@ def ops_to_db_settings(ops: dict) -> tuple[dict, dict]:
286
387
  settings: dict[str, Any] = {}
287
388
 
288
389
  # resolve fork→upstream renames into a temporary lookup that
289
- # doesn't mutate the caller's ops
290
- lookup = dict(ops)
390
+ # doesn't mutate the caller's ops. reconcile first so algorithm /
391
+ # anatomical_only / img are consistent and valid before translation.
392
+ lookup = reconcile_detection_keys(dict(ops))
291
393
  for fork_name, upstream_name in _FORK_TO_UPSTREAM_RENAMES.items():
292
394
  if fork_name in lookup and upstream_name not in lookup:
293
395
  lookup[upstream_name] = lookup[fork_name]
@@ -377,11 +479,9 @@ def ops_to_db_settings(ops: dict) -> tuple[dict, dict]:
377
479
  if key in lookup:
378
480
  cellpose[key] = lookup[key]
379
481
 
380
- # fork's anatomical_only int picks which image cellpose runs on.
381
- # upstream encodes this via cellpose_settings.img; without this
382
- # mapping anatomical_only=4 silently degrades to anatomical_only=1
383
- # (the upstream default 'max_proj / meanImg' = log ratio image)
384
- # because the translator only sets algorithm='cellpose' otherwise.
482
+ # fork's anatomical_only int picks which image cellpose runs on;
483
+ # upstream encodes this via cellpose_settings.img. anatomical_only was
484
+ # already validated/normalized (incl. legacy 3) by reconcile above.
385
485
  anat = lookup.get("anatomical_only")
386
486
  if anat and "img" not in cellpose:
387
487
  try:
@@ -390,18 +490,6 @@ def ops_to_db_settings(ops: dict) -> tuple[dict, dict]:
390
490
  anat_int = None
391
491
  if anat_int in _ANATOMICAL_ONLY_TO_IMG:
392
492
  cellpose["img"] = _ANATOMICAL_ONLY_TO_IMG[anat_int]
393
- elif anat_int == 3:
394
- # upstream removed enhanced_mean_img — closest fallback is
395
- # 'meanImg' (same source family, no median-ratio enhancement).
396
- # warn the caller so they know something changed.
397
- import warnings
398
- warnings.warn(
399
- "anatomical_only=3 (enhanced_mean_img) is no longer supported "
400
- "upstream; falling back to cellpose_settings.img='meanImg'. "
401
- "Use anatomical_only in {1, 2, 4} to avoid this fallback.",
402
- stacklevel=2,
403
- )
404
- cellpose["img"] = "max_proj"
405
493
 
406
494
  if cellpose:
407
495
  detection["cellpose_settings"] = cellpose
@@ -14,7 +14,7 @@ import copy
14
14
  import numpy as np
15
15
 
16
16
  from lbm_suite2p_python.default_ops import default_ops
17
- from lbm_suite2p_python.db_settings import save_ops_db_settings
17
+ from lbm_suite2p_python.db_settings import save_ops_db_settings, reconcile_detection_keys
18
18
  from lbm_suite2p_python.postprocessing import (
19
19
  ops_to_json,
20
20
  load_ops,
@@ -751,6 +751,7 @@ def _prepare_plane_ops(*, base_ops, plane_idx, num_planes, input_arr,
751
751
  """
752
752
  plane_num = plane_idx + 1
753
753
  current_ops = copy.deepcopy(load_ops(base_ops)) if base_ops else default_ops()
754
+ reconcile_detection_keys(current_ops)
754
755
  current_ops["plane"] = plane_num
755
756
  current_ops["num_zplanes"] = num_planes
756
757
 
@@ -796,7 +797,7 @@ def pipeline(
796
797
  dff_window_size: int = None,
797
798
  dff_percentile: int = 20,
798
799
  dff_smooth_window: int = None,
799
- norm_method: str = "dff",
800
+ norm_method: str = "zscore",
800
801
  correct_neuropil: bool = True,
801
802
  cell_filters: list = None,
802
803
  accept_all_cells: bool = False,
@@ -827,8 +828,17 @@ def pipeline(
827
828
  Output directory for results. If None, saves next to input file.
828
829
  Required when input_data is an array without filenames.
829
830
  ops : dict, optional
830
- Suite2p parameters to override defaults. If None, uses optimized
831
- defaults with metadata auto-populated from input.
831
+ Flat Suite2p parameter overrides (the fork's ops shape). If None,
832
+ uses suite2p defaults with metadata auto-populated from input.
833
+ Detection is selectable either way and kept consistent: suite2p's
834
+ ``"algorithm"`` ("cellpose" / "sparsery" / "sourcery") or the fork's
835
+ ``"anatomical_only"`` (0=functional, 1=max_proj/mean, 2=mean,
836
+ 4=max_proj). Setting one fills in the other; invalid or legacy
837
+ values (e.g. ``anatomical_only=3``) are warned and mapped to what
838
+ suite2p accepts.
839
+ db, settings : dict, optional
840
+ suite2p's nested ``db`` / ``settings`` dicts (the new upstream
841
+ shape). Flattened and merged into ``ops``; explicit ``ops`` keys win.
832
842
  planes : int or list, optional
833
843
  Which z-planes to process (1-indexed). None processes all planes.
834
844
  roi_mode : int, optional
@@ -862,7 +872,7 @@ def pipeline(
862
872
  dff_smooth_window : int, optional
863
873
  Temporal smoothing window for dF/F traces (frames).
864
874
  If None, auto-calculated. Set to 1 to disable.
865
- norm_method : str, default "dff"
875
+ norm_method : str, default "zscore"
866
876
  Normalization for the saved norm_traces.npy. "dff" uses the rolling
867
877
  percentile ΔF/F (dff_window_size / dff_percentile / dff_smooth_window
868
878
  apply); "zscore" uses per-ROI (F - mean) / std. Quality metrics
@@ -1232,7 +1242,7 @@ def run_volume(
1232
1242
  dff_window_size: int = None,
1233
1243
  dff_percentile: int = 20,
1234
1244
  dff_smooth_window: int = None,
1235
- norm_method: str = "dff",
1245
+ norm_method: str = "zscore",
1236
1246
  correct_neuropil: bool = True,
1237
1247
  accept_all_cells: bool = False,
1238
1248
  cell_filters: list = None,
@@ -1275,7 +1285,7 @@ def run_volume(
1275
1285
  List of frame indices to process.
1276
1286
  dff_window_size, dff_percentile, dff_smooth_window : optional
1277
1287
  dF/F calculation parameters (used when norm_method="dff").
1278
- norm_method : str, default "dff"
1288
+ norm_method : str, default "zscore"
1279
1289
  Normalization for norm_traces.npy: "dff" or "zscore". See pipeline().
1280
1290
  accept_all_cells : bool, default False
1281
1291
  Mark all ROIs as accepted.
@@ -1381,8 +1391,8 @@ def run_volume(
1381
1391
  else None
1382
1392
  )
1383
1393
  _volume_source_shape = (
1384
- tuple(input_arr.shape5d)
1385
- if input_arr is not None and hasattr(input_arr, "shape5d")
1394
+ tuple(input_arr._shape5d())
1395
+ if input_arr is not None and hasattr(input_arr, "_shape5d")
1386
1396
  else None
1387
1397
  )
1388
1398
 
@@ -1530,8 +1540,10 @@ def run_volume(
1530
1540
  )
1531
1541
 
1532
1542
  # Download the cellpose model once before fanning out; concurrent
1533
- # first-time downloads race on the shared cache file.
1534
- _prewarm_cellpose_model(ops)
1543
+ # first-time downloads race on the shared cache file. Use a prepared
1544
+ # per-plane ops (already reconciled) so algorithm="cellpose" without
1545
+ # anatomical_only still triggers the warm-up.
1546
+ _prewarm_cellpose_model(prepared[0][1] if prepared else ops)
1535
1547
 
1536
1548
  manager = mp.Manager()
1537
1549
  log_queue = manager.Queue()
@@ -2262,7 +2274,7 @@ def run_plane(
2262
2274
  dff_window_size: int = None,
2263
2275
  dff_percentile: int = 20,
2264
2276
  dff_smooth_window: int = None,
2265
- norm_method: str = "dff",
2277
+ norm_method: str = "zscore",
2266
2278
  correct_neuropil: bool = True,
2267
2279
  accept_all_cells: bool = False,
2268
2280
  cell_filters: list = None,
@@ -2287,7 +2299,11 @@ def run_plane(
2287
2299
  Root directory to save the results. A subdirectory will be created based on
2288
2300
  the input filename or `plane_name` parameter.
2289
2301
  ops : dict, str or Path, optional
2290
- Path to or dict of user‐supplied ops.npy.
2302
+ Path to or dict of user‐supplied ops (flat fork shape). Detection
2303
+ accepts suite2p's ``"algorithm"`` or the fork's ``"anatomical_only"``
2304
+ interchangeably (kept consistent; invalid/legacy values warned and
2305
+ mapped to suite2p-valid ones). Pass ``db`` / ``settings`` for the
2306
+ nested upstream shape.
2291
2307
  chan2_file : str, optional
2292
2308
  Path to structural / anatomical data used for registration.
2293
2309
  keep_raw : bool, default False
@@ -2304,7 +2320,7 @@ def run_plane(
2304
2320
  Percentile for baseline F0.
2305
2321
  dff_smooth_window : int, optional
2306
2322
  Smoothing window for dF/F. Default: auto-calculated.
2307
- norm_method : str, default "dff"
2323
+ norm_method : str, default "zscore"
2308
2324
  Normalization for norm_traces.npy: "dff" (rolling percentile ΔF/F)
2309
2325
  or "zscore" (per-ROI (F - mean) / std). Quality metrics always use
2310
2326
  ΔF/F regardless of this setting.
@@ -2413,6 +2429,9 @@ def run_plane(
2413
2429
  ops_user = load_ops(ops) if ops else {}
2414
2430
  if db is not None or settings is not None:
2415
2431
  ops_user = merge_db_settings_into_ops(ops_user, db, settings)
2432
+ # keep the flat (anatomical_only/sparse_mode) and suite2p (algorithm)
2433
+ # detection spellings consistent + valid before they merge into ops.
2434
+ reconcile_detection_keys(ops_user)
2416
2435
  ops = {**ops_default, **ops_user, "data_path": str(input_path.resolve())}
2417
2436
 
2418
2437
  # normalize kwargs
@@ -2735,7 +2754,7 @@ def run_plane(
2735
2754
  dict(file.metadata) if hasattr(file, "metadata") else None
2736
2755
  )
2737
2756
  _src_shape = (
2738
- tuple(file.shape5d) if hasattr(file, "shape5d") else None
2757
+ tuple(file._shape5d()) if hasattr(file, "_shape5d") else None
2739
2758
  )
2740
2759
  _apply_reactive_metadata(
2741
2760
  ops=ops,
@@ -2868,13 +2887,11 @@ def run_plane(
2868
2887
  show_progress=False,
2869
2888
  )
2870
2889
  ops["chan2_file"] = str((plane_dir / "data_chan2.bin").resolve())
2871
- # Use shape5d (the always-5D contract) so natural-rank arrays
2872
- # (e.g. a 4D TZYX TiffArray with C=1 squeezed) report the
2873
- # real T dimension. shape[0] picks Y on a natural 4D array
2874
- # and crashes/mis-counts on a 2D one — same class of bug
2875
- # mbo_utilities just fixed in `_imwrite_base`.
2876
- if hasattr(chan2_data, "shape5d"):
2877
- ops["nframes_chan2"] = int(chan2_data.shape5d[0])
2890
+ # Use _shape5d() (the always-5D contract) so natural-rank
2891
+ # arrays (BinArray, a 4D TZYX _ChannelView) still report the
2892
+ # real T dimension at index 0.
2893
+ if hasattr(chan2_data, "_shape5d"):
2894
+ ops["nframes_chan2"] = int(chan2_data._shape5d()[0])
2878
2895
  elif hasattr(chan2_data, "shape"):
2879
2896
  ops["nframes_chan2"] = int(chan2_data.shape[0])
2880
2897
  else:
@@ -2950,6 +2967,10 @@ def run_plane(
2950
2967
  save_ops_db_settings(ops_file, updated_ops)
2951
2968
 
2952
2969
  except Exception as e:
2970
+ if "no ROIs were found" in str(e):
2971
+ print(f"Warning: no ROIs found for {plane_dir.name}, skipping plane.")
2972
+ _cleanup_bin_files(plane_dir, keep_raw, keep_reg)
2973
+ return ops_file
2953
2974
  print(f"Error in run_plane_bin: {e}")
2954
2975
  traceback.print_exc()
2955
2976
  _cleanup_bin_files(plane_dir, keep_raw, keep_reg)
@@ -3,7 +3,14 @@ import numpy as np
3
3
  from pathlib import Path
4
4
 
5
5
 
6
- # Supported lazy array types from mbo_utilities
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
+
7
14
  _LAZY_ARRAY_TYPES = (
8
15
  "ScanImageArray",
9
16
  "LBMArray",
@@ -22,6 +29,8 @@ _LAZY_ARRAY_TYPES = (
22
29
 
23
30
  def _is_lazy_array(obj):
24
31
  """Check if obj is an mbo_utilities lazy array type."""
32
+ if _LazyArray is not None and isinstance(obj, _LazyArray):
33
+ return True
25
34
  return type(obj).__name__ in _LAZY_ARRAY_TYPES
26
35
 
27
36
 
@@ -1891,7 +1891,7 @@ def plot_volume_trace_figures(
1891
1891
  dff_percentile: int = 8,
1892
1892
  dff_window_size: int = None,
1893
1893
  dff_smooth_window: int = None,
1894
- norm_method: str = "dff",
1894
+ norm_method: str = "zscore",
1895
1895
  correct_neuropil: bool = True,
1896
1896
  ):
1897
1897
  """
@@ -1926,7 +1926,7 @@ def plot_volume_trace_figures(
1926
1926
  model exists, the rastermap heatmap is skipped.
1927
1927
  dff_percentile, dff_window_size, dff_smooth_window
1928
1928
  Forwarded to :func:`dff_rolling_percentile` (when norm_method="dff").
1929
- norm_method : str, default "dff"
1929
+ norm_method : str, default "zscore"
1930
1930
  Normalization for the norm-trace plots: "dff" or "zscore".
1931
1931
  """
1932
1932
  from types import SimpleNamespace