batch2p 0.1.0__py3-none-any.whl
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.
- batch2p/__init__.py +1 -0
- batch2p/cli.py +427 -0
- batch2p/compute_F_sub.py +147 -0
- batch2p/extractors/__init__.py +17 -0
- batch2p/extractors/base.py +32 -0
- batch2p/extractors/suite2p.py +342 -0
- batch2p/extractors/suite3d.py +143 -0
- batch2p/gui.py +1513 -0
- batch2p/multi.py +176 -0
- batch2p-0.1.0.dist-info/METADATA +17 -0
- batch2p-0.1.0.dist-info/RECORD +13 -0
- batch2p-0.1.0.dist-info/WHEEL +4 -0
- batch2p-0.1.0.dist-info/entry_points.txt +5 -0
batch2p/multi.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""batch2p-multi — generate one data.json per directory from a template.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
batch2p-multi <template_data.json> <dir_list.txt> --params-file <params.json> [--output-dir <dir>]
|
|
6
|
+
|
|
7
|
+
The template data.json may contain {{ job_id }} and {{ root_dir }} placeholders,
|
|
8
|
+
which are substituted per-directory at generation time. The dir_list file must
|
|
9
|
+
contain one directory path per line (blank lines and lines starting with # are
|
|
10
|
+
ignored).
|
|
11
|
+
|
|
12
|
+
For each directory the script:
|
|
13
|
+
- collects all .tif/.tiff files and .b64 files (lexicographically sorted),
|
|
14
|
+
- substitutes {{ job_id }} and {{ root_dir }} in all string fields,
|
|
15
|
+
- writes <output_dir>/<job_id>_<n>_data.json (1-based index, zero-padded).
|
|
16
|
+
"""
|
|
17
|
+
import argparse
|
|
18
|
+
import json
|
|
19
|
+
import re
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _apply_template_vars(obj, variables: dict):
|
|
25
|
+
"""Recursively replace {{ var }} placeholders in string values.
|
|
26
|
+
|
|
27
|
+
Placeholders whose variable is absent or empty are left verbatim.
|
|
28
|
+
"""
|
|
29
|
+
if isinstance(obj, str):
|
|
30
|
+
def _replace(m):
|
|
31
|
+
val = variables.get(m.group(1).strip())
|
|
32
|
+
return val if val else m.group(0)
|
|
33
|
+
return re.sub(r'\{\{\s*(\w+)\s*\}\}', _replace, obj)
|
|
34
|
+
if isinstance(obj, dict):
|
|
35
|
+
return {k: _apply_template_vars(v, variables) for k, v in obj.items()}
|
|
36
|
+
if isinstance(obj, list):
|
|
37
|
+
return [_apply_template_vars(x, variables) for x in obj]
|
|
38
|
+
return obj
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _replace_literal(obj, old: str, new: str):
|
|
42
|
+
"""Recursively replace all occurrences of `old` with `new` in string values."""
|
|
43
|
+
if isinstance(obj, str):
|
|
44
|
+
return obj.replace(old, new)
|
|
45
|
+
if isinstance(obj, dict):
|
|
46
|
+
return {k: _replace_literal(v, old, new) for k, v in obj.items()}
|
|
47
|
+
if isinstance(obj, list):
|
|
48
|
+
return [_replace_literal(x, old, new) for x in obj]
|
|
49
|
+
return obj
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _collect_files(directory: Path, pattern: str) -> list[str]:
|
|
53
|
+
return sorted(str(p) for p in directory.glob(pattern))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def main():
|
|
57
|
+
parser = argparse.ArgumentParser(
|
|
58
|
+
description="Generate one data.json per directory from a template."
|
|
59
|
+
)
|
|
60
|
+
parser.add_argument("template", help="Template data.json with {{ }} placeholders.")
|
|
61
|
+
parser.add_argument("dir_list", help="Text file with one directory per line.")
|
|
62
|
+
parser.add_argument("--params-file", required=True,
|
|
63
|
+
help="Path to the params.json file (same for all outputs).")
|
|
64
|
+
parser.add_argument("--output-dir", default=".",
|
|
65
|
+
help="Directory where generated data.json files are written (default: cwd).")
|
|
66
|
+
parser.add_argument("--tif-regexp", default=None,
|
|
67
|
+
help="Only include .tif/.tiff files whose filename matches this regexp.")
|
|
68
|
+
parser.add_argument("--force-sync", action="store_true",
|
|
69
|
+
help="Require .b64 files to be present and match the number of tif files; "
|
|
70
|
+
"skip directories where this condition is not met.")
|
|
71
|
+
args = parser.parse_args()
|
|
72
|
+
|
|
73
|
+
template_path = Path(args.template)
|
|
74
|
+
dir_list_path = Path(args.dir_list)
|
|
75
|
+
params_file = str(Path(args.params_file).resolve())
|
|
76
|
+
out_dir = Path(args.output_dir)
|
|
77
|
+
|
|
78
|
+
tif_regexp = re.compile(args.tif_regexp) if args.tif_regexp else None
|
|
79
|
+
|
|
80
|
+
# Load template
|
|
81
|
+
if not template_path.exists():
|
|
82
|
+
sys.exit(f"Template not found: {template_path}")
|
|
83
|
+
with open(template_path) as f:
|
|
84
|
+
template = json.load(f)
|
|
85
|
+
|
|
86
|
+
# Load directory list
|
|
87
|
+
if not dir_list_path.exists():
|
|
88
|
+
sys.exit(f"Directory list not found: {dir_list_path}")
|
|
89
|
+
directories = []
|
|
90
|
+
with open(dir_list_path) as f:
|
|
91
|
+
for raw in f:
|
|
92
|
+
line = raw.strip()
|
|
93
|
+
if not line or line.startswith("#"):
|
|
94
|
+
continue
|
|
95
|
+
p = Path(line)
|
|
96
|
+
if not p.is_dir():
|
|
97
|
+
print(f"Warning: not a directory, skipping: {line}", file=sys.stderr)
|
|
98
|
+
continue
|
|
99
|
+
directories.append(p)
|
|
100
|
+
|
|
101
|
+
if not directories:
|
|
102
|
+
sys.exit("No valid directories found in the list.")
|
|
103
|
+
|
|
104
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
105
|
+
|
|
106
|
+
# Derive base job_id from the template (may itself contain a placeholder)
|
|
107
|
+
base_job_id = template.get("job_id", "run")
|
|
108
|
+
digits = len(str(len(directories)))
|
|
109
|
+
|
|
110
|
+
# If the template has a literal root_path, treat it as the prototype root: every
|
|
111
|
+
# occurrence of that path in any string field will be replaced with the current
|
|
112
|
+
# directory. This lets real data.json files (with no {{ }} placeholders) be used
|
|
113
|
+
# directly as templates without manual editing.
|
|
114
|
+
proto_root = template.get("root_path", "").rstrip("/")
|
|
115
|
+
|
|
116
|
+
generated = []
|
|
117
|
+
for idx, directory in enumerate(directories, start=1):
|
|
118
|
+
root_dir = str(directory.absolute())
|
|
119
|
+
|
|
120
|
+
# Collect files
|
|
121
|
+
tif_files = sorted(set(
|
|
122
|
+
_collect_files(directory, "*.tif") +
|
|
123
|
+
_collect_files(directory, "*.tiff")
|
|
124
|
+
))
|
|
125
|
+
if tif_regexp is not None:
|
|
126
|
+
tif_files = [p for p in tif_files if tif_regexp.search(Path(p).name)]
|
|
127
|
+
if not tif_files:
|
|
128
|
+
print(f"Warning: no tif files found, skipping: {directory}", file=sys.stderr)
|
|
129
|
+
continue
|
|
130
|
+
b64_files = _collect_files(directory, "*.b64")
|
|
131
|
+
if args.force_sync:
|
|
132
|
+
if not b64_files:
|
|
133
|
+
print(f"Warning: --force-sync: no b64 files found, skipping: {directory}", file=sys.stderr)
|
|
134
|
+
continue
|
|
135
|
+
if len(b64_files) != len(tif_files):
|
|
136
|
+
print(f"Warning: --force-sync: {len(b64_files)} b64 vs {len(tif_files)} tif, skipping: {directory}", file=sys.stderr)
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
# Substitute variables in the template (produces the per-dir job_id).
|
|
140
|
+
# Step 1: replace the prototype root_path literally in all string fields so
|
|
141
|
+
# that templates with real paths (no {{ }} placeholders) work correctly.
|
|
142
|
+
# Step 2: apply {{ }} placeholder substitution for explicit placeholders.
|
|
143
|
+
suffix = f"_{idx:0{digits}d}"
|
|
144
|
+
working = _replace_literal(template, proto_root, root_dir) if proto_root else template
|
|
145
|
+
resolved_base_job_id = _apply_template_vars(
|
|
146
|
+
_replace_literal(base_job_id, proto_root, root_dir) if proto_root else base_job_id,
|
|
147
|
+
{"root_dir": root_dir},
|
|
148
|
+
)
|
|
149
|
+
variables = {"job_id": resolved_base_job_id, "root_dir": root_dir}
|
|
150
|
+
data_dict = _apply_template_vars(working, variables)
|
|
151
|
+
|
|
152
|
+
# Resolve the job_id after substitution, then append numeric suffix
|
|
153
|
+
resolved_job_id = data_dict.get("job_id", resolved_base_job_id)
|
|
154
|
+
final_job_id = f"{resolved_job_id}{suffix}"
|
|
155
|
+
|
|
156
|
+
data_dict["job_id"] = final_job_id
|
|
157
|
+
data_dict["params_file"] = params_file
|
|
158
|
+
data_dict["root_path"] = root_dir
|
|
159
|
+
data_dict["data"] = tif_files
|
|
160
|
+
if b64_files:
|
|
161
|
+
data_dict["behavior_data"] = b64_files
|
|
162
|
+
elif "behavior_data" in data_dict:
|
|
163
|
+
del data_dict["behavior_data"]
|
|
164
|
+
|
|
165
|
+
out_path = out_dir / f"{final_job_id}_data.json"
|
|
166
|
+
with open(out_path, "w") as f:
|
|
167
|
+
json.dump(data_dict, f, indent=2)
|
|
168
|
+
|
|
169
|
+
generated.append(out_path)
|
|
170
|
+
print(f" [{idx:>{digits}}/{len(directories)}] {out_path.name} ({len(tif_files)} tif, {len(b64_files)} b64) {root_dir}")
|
|
171
|
+
|
|
172
|
+
print(f"\nGenerated {len(generated)} data.json file(s) in: {out_dir.resolve()}")
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
if __name__ == "__main__":
|
|
176
|
+
main()
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: batch2p
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Author: Francesco P. Battaglia
|
|
5
|
+
License-Expression: GPL-3.0-or-later
|
|
6
|
+
Classifier: Operating System :: OS Independent
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: numpy
|
|
10
|
+
Requires-Dist: pynapple
|
|
11
|
+
Requires-Dist: suite2p
|
|
12
|
+
Requires-Dist: tifffile
|
|
13
|
+
Requires-Dist: tifftrim
|
|
14
|
+
Requires-Dist: totalsync-2p
|
|
15
|
+
Requires-Dist: totalsync-utils
|
|
16
|
+
Provides-Extra: gui
|
|
17
|
+
Requires-Dist: pyqt5; extra == 'gui'
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
batch2p/__init__.py,sha256=L3g3zkH1gR1Igqp0olvgeWwpSxEXqC4qbTx8ldPQATg,56
|
|
2
|
+
batch2p/cli.py,sha256=PsHrTxl8fjyFmWapu4bMkH527jpVCxDLnmAV4uzFFjA,17731
|
|
3
|
+
batch2p/compute_F_sub.py,sha256=QEPU4rPtEwLBVyET-urM3Pqc1uEHiZcPWjq3wWaAcVE,5249
|
|
4
|
+
batch2p/gui.py,sha256=WRgFrDio47fHDrxj3nzBqPE_XGC3HWto_iHs2k-cH70,74149
|
|
5
|
+
batch2p/multi.py,sha256=KPfcDELcroNEJZ-b7cI8w6648Jr-_YmgdQ4H1gRpnTg,7387
|
|
6
|
+
batch2p/extractors/__init__.py,sha256=bYtbjPhfst7z6sodVsJ2T3XRPrsWfOmNVUyM216odZs,470
|
|
7
|
+
batch2p/extractors/base.py,sha256=l23PUW71DHv2k7vFc3rShE13lOuFxXtKN9e2Fal602c,1089
|
|
8
|
+
batch2p/extractors/suite2p.py,sha256=eVYmkicuTmSYLHFIgNpkmlAfG3R90TSdDTMOJ7KWE1c,13267
|
|
9
|
+
batch2p/extractors/suite3d.py,sha256=wsuRteMgYH3Yi-blEHtnTe8ny4oV7l1fKBINFYSKykc,5609
|
|
10
|
+
batch2p-0.1.0.dist-info/METADATA,sha256=FtmtoM9qBN3A6jhOAs56KYhHIMwf2mc9NmAvRY6VmWk,470
|
|
11
|
+
batch2p-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
12
|
+
batch2p-0.1.0.dist-info/entry_points.txt,sha256=lFNxgFg89kcBWBqakObR0uPdz80N3Z5VnJMcdpkEL8Y,161
|
|
13
|
+
batch2p-0.1.0.dist-info/RECORD,,
|