lbm_suite2p_python 3.0.0__tar.gz → 3.0.2__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 (35) hide show
  1. {lbm_suite2p_python-3.0.0/lbm_suite2p_python.egg-info → lbm_suite2p_python-3.0.2}/PKG-INFO +4 -7
  2. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/__init__.py +6 -0
  3. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/cellpose.py +2 -5
  4. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/cli.py +7 -5
  5. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/conversion.py +3 -3
  6. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/db_settings.py +118 -28
  7. lbm_suite2p_python-3.0.2/lbm_suite2p_python/default_ops.py +84 -0
  8. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/gui.py +0 -1
  9. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/postprocessing.py +235 -6
  10. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/run_lsp.py +357 -406
  11. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/volume.py +240 -21
  12. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/zplane.py +678 -304
  13. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2/lbm_suite2p_python.egg-info}/PKG-INFO +4 -7
  14. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python.egg-info/SOURCES.txt +0 -1
  15. lbm_suite2p_python-3.0.2/lbm_suite2p_python.egg-info/requires.txt +12 -0
  16. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/pyproject.toml +106 -112
  17. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/tests/test_frame_count_aliases.py +315 -317
  18. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/tests/test_pipeline_parameters.py +2 -2
  19. lbm_suite2p_python-3.0.0/lbm_suite2p_python/_padding_shim.py +0 -140
  20. lbm_suite2p_python-3.0.0/lbm_suite2p_python/default_ops.py +0 -222
  21. lbm_suite2p_python-3.0.0/lbm_suite2p_python.egg-info/requires.txt +0 -16
  22. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/LICENSE.md +0 -0
  23. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/MANIFEST.in +0 -0
  24. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/README.md +0 -0
  25. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/__main__.py +0 -0
  26. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/_benchmarking.py +0 -0
  27. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/grid_search.py +0 -0
  28. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/merging.py +0 -0
  29. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/utils.py +0 -0
  30. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python.egg-info/dependency_links.txt +0 -0
  31. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python.egg-info/entry_points.txt +0 -0
  32. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python.egg-info/top_level.txt +0 -0
  33. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/setup.cfg +0 -0
  34. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/tests/test_refactored_pipeline.py +0 -0
  35. {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/tests/test_run_volume.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lbm_suite2p_python
3
- Version: 3.0.0
4
- Summary: Light Beads Microscopy Pipeline using Suite2p
3
+ Version: 3.0.2
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
7
7
  Keywords: Pipeline,Numpy,Microscopy,ScanImage,Suite2p,tiff
@@ -11,18 +11,15 @@ 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>=2.7.7
14
+ Requires-Dist: mbo_utilities>=3.0.1
15
15
  Requires-Dist: suite2p>=1.0.0.1
16
16
  Requires-Dist: setuptools<81
17
17
  Provides-Extra: rastermap
18
18
  Requires-Dist: rastermap; extra == "rastermap"
19
19
  Provides-Extra: cellpose
20
20
  Requires-Dist: cellpose>=4.0.6; extra == "cellpose"
21
- Provides-Extra: torch
22
- Requires-Dist: torch>=2.7.0; extra == "torch"
23
- Requires-Dist: torchvision>=0.22.0; extra == "torch"
24
21
  Provides-Extra: all
25
- Requires-Dist: lbm_suite2p_python[cellpose,rastermap,torch]; extra == "all"
22
+ Requires-Dist: lbm_suite2p_python[cellpose,rastermap]; extra == "all"
26
23
  Dynamic: license-file
27
24
 
28
25
  <p align="center">
@@ -39,6 +39,8 @@ from lbm_suite2p_python.zplane import (
39
39
  plot_filtered_cells,
40
40
  plot_diameter_histogram,
41
41
  plot_projection,
42
+ plot_accepted_rejected_overlay,
43
+ plot_volume_accepted_rejected_overlay,
42
44
  )
43
45
 
44
46
  from lbm_suite2p_python.volume import (
@@ -46,6 +48,7 @@ from lbm_suite2p_python.volume import (
46
48
  plot_orthoslices,
47
49
  plot_3d_roi_map,
48
50
  plot_3d_rastermap_clusters,
51
+ plot_volume_trace_figures,
49
52
  plot_volume_signal,
50
53
  plot_volume_neuron_counts,
51
54
  consolidate_volume,
@@ -147,8 +150,11 @@ __all__ = [
147
150
  "plot_orthoslices",
148
151
  "plot_3d_roi_map",
149
152
  "plot_3d_rastermap_clusters",
153
+ "plot_volume_trace_figures",
150
154
  "plot_projection",
151
155
  "plot_volume_signal",
152
156
  "plot_volume_neuron_counts",
153
157
  "consolidate_volume",
158
+ "plot_accepted_rejected_overlay",
159
+ "plot_volume_accepted_rejected_overlay",
154
160
  ]
@@ -194,10 +194,8 @@ def _masks_to_stat(masks, img=None, compute_overlap=True):
194
194
 
195
195
  # Build pixel count map for overlap detection
196
196
  if compute_overlap and masks.ndim == 2:
197
- # Count how many ROIs claim each pixel (for overlap computation)
198
- # Since cellpose masks are non-overlapping by design, we check boundaries
199
- from scipy import ndimage
200
- # Dilate each mask slightly to find potential overlaps at boundaries
197
+ # cellpose masks are non-overlapping by construction; track per-pixel
198
+ # claim count so callers can detect dilated-boundary overlaps
201
199
  overlap_map = np.zeros(masks.shape, dtype=np.int32)
202
200
  for roi_id in range(1, n_rois + 1):
203
201
  roi_mask = masks == roi_id
@@ -1611,7 +1609,6 @@ def prepare_training_data(
1611
1609
  train_cellpose : Train model on prepared data
1612
1610
  save_gui_results : Save results in GUI-compatible format
1613
1611
  """
1614
- import shutil
1615
1612
  import tifffile
1616
1613
 
1617
1614
  output_dir = Path(output_dir)
@@ -23,13 +23,10 @@ Examples:
23
23
  """
24
24
 
25
25
  import argparse
26
- import json
27
26
  import sys
28
27
  from pathlib import Path
29
28
  from typing import Any
30
29
 
31
- import numpy as np
32
-
33
30
 
34
31
  def _snake_to_kebab(name: str) -> str:
35
32
  """convert snake_case to kebab-case for CLI args."""
@@ -95,7 +92,6 @@ def _get_ops_help() -> dict[str, str]:
95
92
  def build_parser() -> argparse.ArgumentParser:
96
93
  """build the argument parser with all pipeline and ops parameters."""
97
94
  from lbm_suite2p_python.default_ops import s2p_ops
98
- from lbm_suite2p_python import __version__
99
95
 
100
96
  parser = argparse.ArgumentParser(
101
97
  prog="lsp",
@@ -193,6 +189,11 @@ Examples:
193
189
  "--dff-smooth", type=int, dest="dff_smooth_window",
194
190
  help="smoothing window for dF/F"
195
191
  )
192
+ dff.add_argument(
193
+ "--correct-neuropil", dest="correct_neuropil",
194
+ action=argparse.BooleanOptionalAction, default=True,
195
+ help="subtract 0.7*Fneu before dF/F (default: on; use --no-correct-neuropil to disable)"
196
+ )
196
197
 
197
198
  # cell filter options
198
199
  filters = parser.add_argument_group("cell filter options")
@@ -491,7 +492,7 @@ def main():
491
492
 
492
493
  # run pipeline
493
494
  try:
494
- results = lsp.pipeline(
495
+ lsp.pipeline(
495
496
  input_data=input_path,
496
497
  save_path=output_path,
497
498
  ops=ops,
@@ -505,6 +506,7 @@ def main():
505
506
  dff_window_size=args.dff_window_size,
506
507
  dff_percentile=args.dff_percentile,
507
508
  dff_smooth_window=args.dff_smooth_window,
509
+ correct_neuropil=args.correct_neuropil,
508
510
  cell_filters=cell_filters,
509
511
  accept_all_cells=args.accept_all_cells,
510
512
  save_json=args.save_json,
@@ -20,6 +20,8 @@ from pathlib import Path
20
20
  import numpy as np
21
21
  from mbo_utilities.file_io import load_npy
22
22
 
23
+ from lbm_suite2p_python.db_settings import _ensure_diameter_array
24
+
23
25
 
24
26
  # file signatures for format detection
25
27
  SUITE2P_REQUIRED = ["stat.npy", "iscell.npy", "ops.npy"]
@@ -436,7 +438,7 @@ def cellpose_to_suite2p(
436
438
  meta_path = cellpose_dir / "cellpose_meta.npy"
437
439
  if meta_path.exists():
438
440
  cp_meta = np.load(meta_path, allow_pickle=True).item()
439
- ops["diameter"] = cp_meta.get("diameter", ops.get("diameter"))
441
+ ops["diameter"] = _ensure_diameter_array(cp_meta.get("diameter", ops.get("diameter")))
440
442
  ops["cellprob_threshold"] = cp_meta.get("cellprob_threshold")
441
443
  ops["flow_threshold"] = cp_meta.get("flow_threshold")
442
444
 
@@ -744,8 +746,6 @@ def compare_detections(path_a, path_b, iou_threshold=0.5):
744
746
  dict
745
747
  Comparison results with matched pairs and unique ROIs.
746
748
  """
747
- from scipy.ndimage import label
748
-
749
749
  path_a, path_b = Path(path_a), Path(path_b)
750
750
 
751
751
  # load masks from both
@@ -172,9 +172,48 @@ _FORK_TO_UPSTREAM_RENAMES: dict[str, str] = {
172
172
  "nbinned": "nbins",
173
173
  "high_pass": "highpass_time",
174
174
  "spatial_hp_cp": "highpass_spatial",
175
+ "pretrained_model": "cellpose_model",
176
+ }
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)
185
+ _ANATOMICAL_ONLY_TO_IMG: dict[int, str] = {
186
+ 1: "max_proj / meanImg",
187
+ 2: "meanImg",
188
+ 4: "max_proj",
175
189
  }
176
190
  _UPSTREAM_TO_FORK_RENAMES = {v: k for k, v in _FORK_TO_UPSTREAM_RENAMES.items()}
177
191
 
192
+ # per-section flat-key disambiguation. Several upstream sections share
193
+ # an upstream key. When flattened to ops, the second iteration clobbers
194
+ # the first; on the reverse derivation, the surviving value gets fanned
195
+ # back into BOTH sections — silently corrupting one of them.
196
+ # This map gives the colliding (section, upstream_key) pair its own
197
+ # distinct flat-ops key so the round-trip preserves both values.
198
+ #
199
+ # Discovered collisions (audit via _SETTINGS_SECTIONS + detection groups):
200
+ # "batch_size" — registration vs extraction.
201
+ # registration keeps the legacy flat name "batch_size"
202
+ # (matches the historical fork ops shape);
203
+ # extraction gets "extract_batch_size".
204
+ # "block_size" — registration vs detection (suite2p uses the same
205
+ # upstream key for the rigid-block geometry AND the
206
+ # pca-denoise / sparsery block size).
207
+ # registration keeps the legacy flat name "block_size";
208
+ # detection gets "det_block_size".
209
+ #
210
+ # Both rename targets match the field names that mbo's GUI dataclass
211
+ # already uses (`extract_batch_size`, `det_block_size_y/_x`).
212
+ _SECTION_FLAT_RENAMES: dict[tuple[str, str], str] = {
213
+ ("extraction", "batch_size"): "extract_batch_size",
214
+ ("detection", "block_size"): "det_block_size",
215
+ }
216
+
178
217
  # baseline values differ between fork and upstream:
179
218
  # fork dcnv.preprocess branches on "maximin" / "constant" / "constant_prctile"
180
219
  # upstream dcnv.preprocess branches on "maximin" / "constant" / "prctile"
@@ -189,17 +228,24 @@ _BASELINE_UPSTREAM_TO_FORK = {
189
228
  }
190
229
 
191
230
 
192
- def _ensure_diameter_list(value: Any) -> list[float]:
193
- """Upstream expects diameter as [dy, dx]. Fork may store a scalar int."""
231
+ def _ensure_diameter_array(value: Any) -> np.ndarray:
232
+ """Coerce diameter to an `np.ndarray` of shape (2,), mirroring suite2p
233
+ `pipeline_s2p.py:150-160`: scalar -> [d, d]; list/tuple -> array; size-1
234
+ array -> [d, d]; size>=2 ndarray passes through. None falls back to
235
+ [6., 6.] (fork-only — upstream doesn't accept None at this point).
236
+ """
194
237
  if value is None:
195
- return [12.0, 12.0]
238
+ return np.array([6.0, 6.0])
239
+ if not isinstance(value, (list, tuple, np.ndarray)):
240
+ return np.array([value, value])
196
241
  if isinstance(value, (list, tuple)):
197
242
  if len(value) == 0:
198
- return [12.0, 12.0]
199
- if len(value) == 1:
200
- return [float(value[0]), float(value[0])]
201
- return [float(value[0]), float(value[1])]
202
- return [float(value), float(value)]
243
+ return np.array([6.0, 6.0])
244
+ value = np.array(value)
245
+ if value.size == 1:
246
+ v = value.item()
247
+ return np.array([v, v])
248
+ return value
203
249
 
204
250
 
205
251
  def _ensure_do_registration_int(value: Any) -> int:
@@ -255,7 +301,7 @@ def ops_to_db_settings(ops: dict) -> tuple[dict, dict]:
255
301
  for key in _SETTINGS_TOP_LEVEL:
256
302
  if key in lookup:
257
303
  if key == "diameter":
258
- settings[key] = _ensure_diameter_list(lookup[key])
304
+ settings[key] = _ensure_diameter_array(lookup[key])
259
305
  else:
260
306
  settings[key] = lookup[key]
261
307
 
@@ -263,9 +309,15 @@ def ops_to_db_settings(ops: dict) -> tuple[dict, dict]:
263
309
  for section, keys in _SETTINGS_SECTIONS.items():
264
310
  bucket: dict[str, Any] = {}
265
311
  for key in keys:
266
- if key not in lookup:
312
+ # per-section flat-key disambiguation. e.g. extraction reads
313
+ # its batch_size from ops["extract_batch_size"] (not
314
+ # ops["batch_size"]) so registration's batch_size doesn't get
315
+ # spread into both sections during the flat→structured
316
+ # derivation. Mirror of the write side in db_settings_to_ops.
317
+ flat_key = _SECTION_FLAT_RENAMES.get((section, key), key)
318
+ if flat_key not in lookup:
267
319
  continue
268
- value = lookup[key]
320
+ value = lookup[flat_key]
269
321
  if section == "run" and key == "do_registration":
270
322
  value = _ensure_do_registration_int(value)
271
323
  if section == "registration" and key == "block_size" and isinstance(value, list):
@@ -295,11 +347,16 @@ def ops_to_db_settings(ops: dict) -> tuple[dict, dict]:
295
347
  for key in _DETECTION_TOP_KEYS:
296
348
  if key == "algorithm":
297
349
  continue
298
- if key in lookup:
299
- value = lookup[key]
300
- if key == "block_size" and isinstance(value, list):
301
- value = tuple(value)
302
- detection[key] = value
350
+ # per-section flat-key disambiguation. detection.block_size reads
351
+ # from ops["det_block_size"] (not ops["block_size"], which holds
352
+ # the registration block size). Mirror of the write side.
353
+ flat_key = _SECTION_FLAT_RENAMES.get(("detection", key), key)
354
+ if flat_key not in lookup:
355
+ continue
356
+ value = lookup[flat_key]
357
+ if key == "block_size" and isinstance(value, list):
358
+ value = tuple(value)
359
+ detection[key] = value
303
360
 
304
361
  sparsery: dict[str, Any] = {}
305
362
  for key in _DETECTION_SPARSERY_KEYS:
@@ -319,6 +376,33 @@ def ops_to_db_settings(ops: dict) -> tuple[dict, dict]:
319
376
  for key in _DETECTION_CELLPOSE_KEYS:
320
377
  if key in lookup:
321
378
  cellpose[key] = lookup[key]
379
+
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.
385
+ anat = lookup.get("anatomical_only")
386
+ if anat and "img" not in cellpose:
387
+ try:
388
+ anat_int = int(anat)
389
+ except (TypeError, ValueError):
390
+ anat_int = None
391
+ if anat_int in _ANATOMICAL_ONLY_TO_IMG:
392
+ 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
+
322
406
  if cellpose:
323
407
  detection["cellpose_settings"] = cellpose
324
408
 
@@ -348,16 +432,9 @@ def db_settings_to_ops(db: dict | None, settings: dict | None) -> dict:
348
432
  if key in settings:
349
433
  value = settings[key]
350
434
  if key == "diameter":
351
- # fork's run_plane_bin / cellpose path / rigid reg assume a
352
- # scalar diameter (e.g. `np.isnan(ops["diameter"])`), but
353
- # upstream stores it as [dy, dx]. Collapse to a single
354
- # value for fork consumers — use dy if equal, else the
355
- # mean rounded to 1 decimal.
356
- if isinstance(value, (list, tuple)) and len(value) >= 1:
357
- if len(value) >= 2 and float(value[0]) != float(value[1]):
358
- value = round((float(value[0]) + float(value[1])) / 2, 1)
359
- else:
360
- value = float(value[0])
435
+ # keep upstream's [dy, dx] np.ndarray shape fork consumers
436
+ # that need a scalar should use np.mean / [0] explicitly.
437
+ value = _ensure_diameter_array(value)
361
438
  ops[key] = value
362
439
 
363
440
  # flat sections
@@ -369,7 +446,14 @@ def db_settings_to_ops(db: dict | None, settings: dict | None) -> dict:
369
446
  value = section_dict[key]
370
447
  if section == "dcnv_preprocess" and key == "baseline":
371
448
  value = _BASELINE_UPSTREAM_TO_FORK.get(str(value), value)
372
- target_name = _UPSTREAM_TO_FORK_RENAMES.get(key, key)
449
+ # per-section disambiguation first (e.g. extraction.batch_size
450
+ # → extract_batch_size to avoid colliding with
451
+ # registration.batch_size). Fall back to the global
452
+ # upstream→fork rename map.
453
+ target_name = _SECTION_FLAT_RENAMES.get(
454
+ (section, key),
455
+ _UPSTREAM_TO_FORK_RENAMES.get(key, key),
456
+ )
373
457
  ops[target_name] = value
374
458
 
375
459
  # mirror align_by_chan2 back to align_by_chan for fork consumers
@@ -382,7 +466,13 @@ def db_settings_to_ops(db: dict | None, settings: dict | None) -> dict:
382
466
  for key in _DETECTION_TOP_KEYS:
383
467
  if key in detection:
384
468
  value = detection[key]
385
- target_name = _UPSTREAM_TO_FORK_RENAMES.get(key, key)
469
+ # per-section disambiguation first (e.g. detection.block_size
470
+ # → det_block_size to avoid colliding with
471
+ # registration.block_size).
472
+ target_name = _SECTION_FLAT_RENAMES.get(
473
+ ("detection", key),
474
+ _UPSTREAM_TO_FORK_RENAMES.get(key, key),
475
+ )
386
476
  ops[target_name] = value
387
477
 
388
478
  # reverse the algorithm → sparse_mode derivation for fork consumers
@@ -0,0 +1,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
+ This module now 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. There is now ONE source of truth for "what
14
+ default means": suite2p's parameter schema. Any LBM-specific tweaks
15
+ (diameter, tau, etc.) belong in user code, not in the default.
16
+ """
17
+
18
+ from mbo_utilities.metadata import get_param, get_voxel_size
19
+
20
+
21
+ def s2p_ops() -> dict:
22
+ """Suite2p's default ops in flat (fork-style) form.
23
+
24
+ Composed from `suite2p.default_settings()` + `suite2p.default_db()`
25
+ via `db_settings_to_ops`, so the rename map and per-section
26
+ flat-key disambiguation (e.g. extraction.batch_size →
27
+ extract_batch_size) are applied consistently.
28
+ """
29
+ from suite2p import default_settings, default_db
30
+ from lbm_suite2p_python.db_settings import db_settings_to_ops
31
+
32
+ return db_settings_to_ops(default_db(), default_settings())
33
+
34
+
35
+ def default_ops(metadata: dict | None = None, ops: dict | None = None) -> dict:
36
+ """Return default ops for the LBM Suite2p pipeline.
37
+
38
+ Parameters
39
+ ----------
40
+ metadata : dict, optional
41
+ Source-data metadata. When provided, `fs` and the (`dx`, `dy`)
42
+ voxel-size pair are pulled in from the metadata and overlaid
43
+ onto the suite2p defaults.
44
+ ops : dict, optional
45
+ A user-supplied ops dict to start from. When None, starts from
46
+ `s2p_ops()` (suite2p's defaults).
47
+
48
+ Returns
49
+ -------
50
+ dict
51
+ Flat ops dict ready to be passed to `pipeline()` / `run_plane()`.
52
+
53
+ Notes
54
+ -----
55
+ `nplanes=1` and `nchannels=1` are forced at the end. The lsp
56
+ pipeline always processes one plane at a time, so these stay fixed
57
+ regardless of caller input.
58
+
59
+ Examples
60
+ --------
61
+ >>> import lbm_suite2p_python as lsp
62
+ >>> ops = lsp.default_ops()
63
+ >>> lsp.run_plane(
64
+ ... ops=ops,
65
+ ... input_tiff="D:/demo/raw_data/raw_file_00001.tif",
66
+ ... save_path="D:/demo/results",
67
+ ... save_folder="v1",
68
+ ... )
69
+ """
70
+ if ops is None:
71
+ ops = s2p_ops()
72
+
73
+ if metadata is not None:
74
+ fs = get_param(metadata, "fs")
75
+ if fs is not None:
76
+ ops["fs"] = fs
77
+ voxel = get_voxel_size(metadata)
78
+ if voxel.dx != 1.0 or voxel.dy != 1.0:
79
+ ops["dx"] = voxel.dx
80
+ ops["dy"] = voxel.dy
81
+
82
+ ops["nplanes"] = 1
83
+ ops["nchannels"] = 1
84
+ return ops
@@ -9,7 +9,6 @@ Usage:
9
9
  """
10
10
 
11
11
  import argparse
12
- import sys
13
12
  import tempfile
14
13
  from pathlib import Path
15
14