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.
- {lbm_suite2p_python-3.0.0/lbm_suite2p_python.egg-info → lbm_suite2p_python-3.0.2}/PKG-INFO +4 -7
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/__init__.py +6 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/cellpose.py +2 -5
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/cli.py +7 -5
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/conversion.py +3 -3
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/db_settings.py +118 -28
- lbm_suite2p_python-3.0.2/lbm_suite2p_python/default_ops.py +84 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/gui.py +0 -1
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/postprocessing.py +235 -6
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/run_lsp.py +357 -406
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/volume.py +240 -21
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/zplane.py +678 -304
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2/lbm_suite2p_python.egg-info}/PKG-INFO +4 -7
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python.egg-info/SOURCES.txt +0 -1
- lbm_suite2p_python-3.0.2/lbm_suite2p_python.egg-info/requires.txt +12 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/pyproject.toml +106 -112
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/tests/test_frame_count_aliases.py +315 -317
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/tests/test_pipeline_parameters.py +2 -2
- lbm_suite2p_python-3.0.0/lbm_suite2p_python/_padding_shim.py +0 -140
- lbm_suite2p_python-3.0.0/lbm_suite2p_python/default_ops.py +0 -222
- lbm_suite2p_python-3.0.0/lbm_suite2p_python.egg-info/requires.txt +0 -16
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/LICENSE.md +0 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/MANIFEST.in +0 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/README.md +0 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/__main__.py +0 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/_benchmarking.py +0 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/grid_search.py +0 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/merging.py +0 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python/utils.py +0 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python.egg-info/dependency_links.txt +0 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python.egg-info/entry_points.txt +0 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/lbm_suite2p_python.egg-info/top_level.txt +0 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/setup.cfg +0 -0
- {lbm_suite2p_python-3.0.0 → lbm_suite2p_python-3.0.2}/tests/test_refactored_pipeline.py +0 -0
- {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.
|
|
4
|
-
Summary:
|
|
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>=
|
|
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
|
|
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
|
-
#
|
|
198
|
-
#
|
|
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
|
-
|
|
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
|
|
193
|
-
"""
|
|
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 [
|
|
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 [
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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] =
|
|
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
|
-
|
|
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[
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
#
|
|
352
|
-
# scalar
|
|
353
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|