optimouse 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.
- modules/__init__.py +21 -0
- modules/bruker_concat_tif.py +650 -0
- modules/compute_zcorr.py +2040 -0
- modules/extraction.py +559 -0
- modules/motion_correction.py +982 -0
- modules/utils/plot_zones.py +125 -0
- modules/utils/save_z_motion_args.py +93 -0
- optimouse-0.1.0.dist-info/METADATA +321 -0
- optimouse-0.1.0.dist-info/RECORD +24 -0
- optimouse-0.1.0.dist-info/WHEEL +5 -0
- optimouse-0.1.0.dist-info/licenses/LICENSE.md +21 -0
- optimouse-0.1.0.dist-info/top_level.txt +2 -0
- pipeline/__init__.py +0 -0
- pipeline/configs/config_C57_O1M2_10022023_run1run2.json +107 -0
- pipeline/configs/config_C57_O1M2_10022023_run1run2run3run4.json +114 -0
- pipeline/configs/config_template_cnmf.json +123 -0
- pipeline/configs/config_template_suite2p.json +102 -0
- pipeline/configs/config_test.json +114 -0
- pipeline/pipeline_cnmf.py +263 -0
- pipeline/pipeline_mcorr.py +299 -0
- pipeline/roi_zcorr.py +65 -0
- pipeline/utils/__init__.py +0 -0
- pipeline/utils/config_loader.py +92 -0
- pipeline/utils/pipeline_utils.py +537 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"""CNMF extraction pipeline step and CLI runner."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import datetime
|
|
7
|
+
import json
|
|
8
|
+
import logging
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
# Add project root to path
|
|
14
|
+
PROJECT_ROOT = Path(__file__).parent.parent
|
|
15
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
16
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
17
|
+
|
|
18
|
+
import mesmerize_core as mc
|
|
19
|
+
from modules import extraction
|
|
20
|
+
from pipeline.utils.pipeline_utils import (
|
|
21
|
+
log_and_print,
|
|
22
|
+
memory_manager,
|
|
23
|
+
cleanup_files,
|
|
24
|
+
find_latest_batch,
|
|
25
|
+
)
|
|
26
|
+
from pipeline.utils.config_loader import load_config
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def run_cnmf(
|
|
30
|
+
data_path,
|
|
31
|
+
parameters,
|
|
32
|
+
export_path=None,
|
|
33
|
+
mcorr_movie=None,
|
|
34
|
+
):
|
|
35
|
+
"""Run CNMF extraction only.
|
|
36
|
+
|
|
37
|
+
Parameters
|
|
38
|
+
----------
|
|
39
|
+
data_path : list | Path
|
|
40
|
+
Original data location for metadata logging.
|
|
41
|
+
parameters : dict
|
|
42
|
+
Dictionary containing at least ``params_extraction`` and ``export_path``.
|
|
43
|
+
export_path : str or Path, optional
|
|
44
|
+
Where to store the CNMF outputs.
|
|
45
|
+
mcorr_movie : str or Path, optional
|
|
46
|
+
Path to a motion-corrected movie to use when motion correction was
|
|
47
|
+
performed externally and no batch files are present.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
log_and_print("Starting CNMF extraction.")
|
|
51
|
+
if export_path is None:
|
|
52
|
+
export_path = Path(parameters["export_path"])
|
|
53
|
+
else:
|
|
54
|
+
export_path = Path(export_path)
|
|
55
|
+
export_path.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
|
|
57
|
+
batch_path = None
|
|
58
|
+
if mcorr_movie is None:
|
|
59
|
+
try:
|
|
60
|
+
batch_path = find_latest_batch(export_path)
|
|
61
|
+
except FileNotFoundError:
|
|
62
|
+
batch_path = None
|
|
63
|
+
if batch_path is None:
|
|
64
|
+
mcorr_movie = _detect_mcorr_movie(
|
|
65
|
+
export_path, parameters.get("params_mcorr", {})
|
|
66
|
+
)
|
|
67
|
+
if mcorr_movie is None:
|
|
68
|
+
raise FileNotFoundError(
|
|
69
|
+
f"No batch file or motion-corrected movie found in {export_path}."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if batch_path is None:
|
|
73
|
+
batch_path = export_path / f"batch_{datetime.datetime.now().strftime('%Y%m%d-%H%M%S')}.pickle"
|
|
74
|
+
mc.create_batch(batch_path)
|
|
75
|
+
log_and_print(f"Created new batch for CNMF at {batch_path}")
|
|
76
|
+
|
|
77
|
+
with memory_manager("cnmf"):
|
|
78
|
+
params_cnmf = parameters["params_extraction"]
|
|
79
|
+
if mcorr_movie is not None:
|
|
80
|
+
extraction.run_cnmf(
|
|
81
|
+
batch_path,
|
|
82
|
+
None,
|
|
83
|
+
export_path,
|
|
84
|
+
params_cnmf,
|
|
85
|
+
data_path,
|
|
86
|
+
input_movie_path=mcorr_movie,
|
|
87
|
+
)
|
|
88
|
+
else:
|
|
89
|
+
extraction.run_cnmf(batch_path, 0, export_path, params_cnmf, data_path)
|
|
90
|
+
|
|
91
|
+
extraction.create_components_movie(
|
|
92
|
+
batch_path=batch_path, export_path=export_path, excerpt=240
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
postproc_cleanup = parameters.get("params_extra", {}).get("cleanup", True)
|
|
96
|
+
if postproc_cleanup and batch_path is not None:
|
|
97
|
+
cleanup_files(batch_path, export_path, preserve_batch=False)
|
|
98
|
+
elif batch_path is not None:
|
|
99
|
+
log_and_print(
|
|
100
|
+
f"Keeping batch files associated to {batch_path}.", level="warning"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return export_path, batch_path
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
107
|
+
parser = argparse.ArgumentParser()
|
|
108
|
+
parser.add_argument("config_file", nargs="+", help="Input configuration JSON file")
|
|
109
|
+
parser.add_argument(
|
|
110
|
+
"--mcorr-movie",
|
|
111
|
+
dest="mcorr_output",
|
|
112
|
+
default=None,
|
|
113
|
+
help="Path to a motion corrected movie to use if motion correction was skipped",
|
|
114
|
+
)
|
|
115
|
+
parser.add_argument(
|
|
116
|
+
"--mcorr-output",
|
|
117
|
+
dest="mcorr_output",
|
|
118
|
+
default=None,
|
|
119
|
+
help="Path to a motion corrected movie to use if motion correction was skipped",
|
|
120
|
+
)
|
|
121
|
+
return parser.parse_args(argv)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _deepcopy_dict(data: dict) -> dict:
|
|
125
|
+
return json.loads(json.dumps(data))
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _detect_mcorr_movie(export_path: Path, params_mcorr: dict | None) -> Path | None:
|
|
129
|
+
export_path = Path(export_path)
|
|
130
|
+
params_mcorr = params_mcorr or {}
|
|
131
|
+
preferred = []
|
|
132
|
+
save_format = params_mcorr.get("save_mcorr_movie")
|
|
133
|
+
if save_format:
|
|
134
|
+
save_format = save_format.lower()
|
|
135
|
+
if save_format in {"tif", "tiff"}:
|
|
136
|
+
preferred.append("mcorr_movie.tiff")
|
|
137
|
+
elif save_format == "h5":
|
|
138
|
+
preferred.append("mcorr_movie.h5")
|
|
139
|
+
elif save_format == "bin":
|
|
140
|
+
preferred.append("mcorr_movie.bin")
|
|
141
|
+
elif save_format in {"memmap", "mmap"}:
|
|
142
|
+
preferred.append("*.mmap")
|
|
143
|
+
|
|
144
|
+
fallback = ["mcorr_movie.tiff", "mcorr_movie.h5", "mcorr_movie.bin", "*.mmap"]
|
|
145
|
+
for name in preferred + [n for n in fallback if n not in preferred]:
|
|
146
|
+
if "*" in name:
|
|
147
|
+
candidates = sorted(export_path.glob(name), key=lambda p: p.stat().st_mtime, reverse=True)
|
|
148
|
+
if candidates:
|
|
149
|
+
log_and_print(f"Using motion corrected movie: {candidates[0]}")
|
|
150
|
+
return candidates[0]
|
|
151
|
+
continue
|
|
152
|
+
candidate = export_path / name
|
|
153
|
+
if candidate.exists():
|
|
154
|
+
log_and_print(f"Using motion corrected movie: {candidate}")
|
|
155
|
+
return candidate
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _process_config(cfg_path: str, args: argparse.Namespace) -> None:
|
|
160
|
+
config = load_config(cfg_path)
|
|
161
|
+
|
|
162
|
+
paths_cfg = config.get("paths", {})
|
|
163
|
+
data_paths = paths_cfg.get("data_paths", [])
|
|
164
|
+
export_paths = paths_cfg.get("export_paths", [])
|
|
165
|
+
concatenation_groups = paths_cfg.get("concatenation_groups", None)
|
|
166
|
+
|
|
167
|
+
logging_cfg = config.get("logging", {})
|
|
168
|
+
if logging_cfg:
|
|
169
|
+
log_path = logging_cfg.get("log_path", os.path.dirname(export_paths[0]))
|
|
170
|
+
log_level = logging_cfg.get("log_level", "INFO")
|
|
171
|
+
else:
|
|
172
|
+
log_path = os.path.dirname(export_paths[0]) if export_paths else Path.cwd()
|
|
173
|
+
log_level = "INFO"
|
|
174
|
+
|
|
175
|
+
os.makedirs(log_path, exist_ok=True)
|
|
176
|
+
log_file = os.path.join(
|
|
177
|
+
log_path, datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".log"
|
|
178
|
+
)
|
|
179
|
+
print("\n")
|
|
180
|
+
print("Log file: " + log_file)
|
|
181
|
+
print("\n")
|
|
182
|
+
logging.basicConfig(
|
|
183
|
+
filename=log_file,
|
|
184
|
+
level=log_level,
|
|
185
|
+
format="%(asctime)s %(levelname)s: %(message)s",
|
|
186
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
187
|
+
)
|
|
188
|
+
logging.info("Starting batch CNMF extraction for data in %s", cfg_path)
|
|
189
|
+
|
|
190
|
+
if concatenation_groups:
|
|
191
|
+
grouped_paths = {}
|
|
192
|
+
for index, path in zip(concatenation_groups, data_paths):
|
|
193
|
+
grouped_paths.setdefault(index, []).append(path)
|
|
194
|
+
grouped_paths_list = list(grouped_paths.values())
|
|
195
|
+
else:
|
|
196
|
+
grouped_paths_list = [[p] for p in data_paths]
|
|
197
|
+
|
|
198
|
+
print("\n🔹 Runs will be processed as follows:")
|
|
199
|
+
for group_num, paths in enumerate(grouped_paths_list, start=1):
|
|
200
|
+
print(f" * Group {group_num}:")
|
|
201
|
+
for p in paths:
|
|
202
|
+
print(f" - {p}")
|
|
203
|
+
print("\n")
|
|
204
|
+
|
|
205
|
+
imaging_cfg = config.get("imaging", {})
|
|
206
|
+
params_extraction_cfg = _deepcopy_dict(config.get("params_extraction", {}))
|
|
207
|
+
extraction_main = params_extraction_cfg.setdefault("main", {})
|
|
208
|
+
if "fr" not in extraction_main and "fr" in imaging_cfg:
|
|
209
|
+
extraction_main["fr"] = imaging_cfg["fr"]
|
|
210
|
+
|
|
211
|
+
base_params = {
|
|
212
|
+
"params_extraction": params_extraction_cfg,
|
|
213
|
+
"params_extra": config.get("params_extra", {}),
|
|
214
|
+
"params_mcorr": config.get("params_mcorr", {}),
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
for i, data_group in enumerate(grouped_paths_list):
|
|
218
|
+
data_group_paths = [Path(p) for p in data_group]
|
|
219
|
+
print(f"\n🔹 Processing Group {i+1}:")
|
|
220
|
+
print(" Data path(s):", data_group_paths)
|
|
221
|
+
|
|
222
|
+
if i >= len(export_paths):
|
|
223
|
+
print(f"⚠️ Warning: Missing export path for Group {i+1}. Skipping.")
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
export_path = Path(export_paths[i])
|
|
227
|
+
print(" Export path:", export_path)
|
|
228
|
+
|
|
229
|
+
params = _deepcopy_dict(base_params)
|
|
230
|
+
params["export_path"] = str(export_path)
|
|
231
|
+
|
|
232
|
+
logging.info("Data path: %s", data_group_paths)
|
|
233
|
+
logging.info("Export path: %s", export_path)
|
|
234
|
+
logging.info("Parameters: %s", params)
|
|
235
|
+
|
|
236
|
+
params_path = export_path / "run_cnmf_arguments.json"
|
|
237
|
+
export_path.mkdir(parents=True, exist_ok=True)
|
|
238
|
+
save_params = {
|
|
239
|
+
"data_path": [str(p) for p in data_group_paths],
|
|
240
|
+
"parameters": params,
|
|
241
|
+
}
|
|
242
|
+
with open(params_path, "w") as fh:
|
|
243
|
+
json.dump(save_params, fh, indent=4)
|
|
244
|
+
|
|
245
|
+
_, batch_path = run_cnmf(
|
|
246
|
+
data_group_paths,
|
|
247
|
+
params,
|
|
248
|
+
mcorr_movie=args.mcorr_output,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
logging.info("Batch path: %s", batch_path)
|
|
252
|
+
|
|
253
|
+
logging.shutdown()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def main(argv: list[str] | None = None) -> None:
|
|
257
|
+
args = _parse_args(argv)
|
|
258
|
+
for cfg_path in args.config_file:
|
|
259
|
+
_process_config(cfg_path, args)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
if __name__ == "__main__":
|
|
263
|
+
main()
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""Motion correction pipeline step.
|
|
2
|
+
|
|
3
|
+
This module wraps the motion correction workflow.
|
|
4
|
+
It can be executed independently of ROI extraction.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python -m pipeline.pipeline_mcorr path/to/config.json
|
|
8
|
+
(equivalent to `python pipeline/pipeline_mcorr.py path/to/config.json`)
|
|
9
|
+
|
|
10
|
+
Input:
|
|
11
|
+
A configuration JSON file specifying data paths, export paths, and parameters.
|
|
12
|
+
See `pipeline/configs/` for examples.
|
|
13
|
+
|
|
14
|
+
Output:
|
|
15
|
+
Motion-corrected movies, and associated batch files if using the caiman pipeline (memmap format).
|
|
16
|
+
Files are saved in the specified export paths
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import argparse
|
|
23
|
+
import datetime
|
|
24
|
+
import json
|
|
25
|
+
import logging
|
|
26
|
+
import os
|
|
27
|
+
import sys
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' # Suppress TensorFlow warnings
|
|
31
|
+
os.environ['CUDA_VISIBLE_DEVICES'] = '0' # Use first GPU if available
|
|
32
|
+
os.environ['TF_FORCE_GPU_ALLOW_GROWTH'] = 'true' # Prevent TF from taking all GPU memory
|
|
33
|
+
os.environ['TF_ENABLE_ONEDNN_OPTS'] = '0' # Disable the Intel OneAPI Deep Neural Network Library optimizations
|
|
34
|
+
os.environ['TF_CPP_VMODULE'] = 'cuda_dnn=0,cuda_fft=0,cuda_blas=0' # Prevent TensorFlow from logging CUDA-related warnings
|
|
35
|
+
|
|
36
|
+
# Add project root to path
|
|
37
|
+
PROJECT_ROOT = Path(__file__).parent.parent
|
|
38
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
39
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
40
|
+
|
|
41
|
+
from modules import motion_correction as mcorr
|
|
42
|
+
from pipeline.utils.pipeline_utils import (
|
|
43
|
+
log_and_print,
|
|
44
|
+
memory_manager,
|
|
45
|
+
cleanup_files,
|
|
46
|
+
)
|
|
47
|
+
import pipeline.utils.pipeline_utils as _pipeline_utils
|
|
48
|
+
from pipeline.utils.config_loader import load_config
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _suppress_tensorflow_logging() -> None:
|
|
52
|
+
logging.getLogger('tensorflow').setLevel(logging.ERROR)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
_suppress_tensorflow_logging()
|
|
56
|
+
|
|
57
|
+
def run_mcorr(
|
|
58
|
+
data_path,
|
|
59
|
+
parameters,
|
|
60
|
+
regex_pattern="*_Ch2_*.ome.tif",
|
|
61
|
+
recompute=True,
|
|
62
|
+
output_format="memmap",
|
|
63
|
+
):
|
|
64
|
+
"""Run motion correction only.
|
|
65
|
+
|
|
66
|
+
Parameters
|
|
67
|
+
----------
|
|
68
|
+
data_path : list[Path] | Path
|
|
69
|
+
Input data paths.
|
|
70
|
+
parameters : dict
|
|
71
|
+
Dictionary containing at least ``params_mcorr`` and ``export_path``.
|
|
72
|
+
regex_pattern : str
|
|
73
|
+
Pattern to match input files.
|
|
74
|
+
recompute : bool
|
|
75
|
+
Recompute motion correction even if outputs exist.
|
|
76
|
+
output_format : {"memmap", "h5", "bin", False}
|
|
77
|
+
Format of the saved movie.
|
|
78
|
+
If not False, use the specified format for saving the motion-corrected movie.
|
|
79
|
+
Options are 'h5', 'memmap' (default), 'tiff' or 'bin'.
|
|
80
|
+
"""
|
|
81
|
+
log_and_print("Starting motion correction pipeline.")
|
|
82
|
+
log_and_print(f"pipeline_utils loaded from: {_pipeline_utils.__file__}")
|
|
83
|
+
export_path = Path(parameters["export_path"])
|
|
84
|
+
export_path.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
|
|
86
|
+
with memory_manager("motion_correction"):
|
|
87
|
+
mcorr_results = mcorr.run_motion_correction_workflow(
|
|
88
|
+
data_path=data_path,
|
|
89
|
+
export_path=export_path,
|
|
90
|
+
parameters=parameters,
|
|
91
|
+
regex_pattern=regex_pattern,
|
|
92
|
+
recompute=recompute,
|
|
93
|
+
create_movies=True,
|
|
94
|
+
output_format=output_format,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
batch_path = mcorr_results["batch_path"]
|
|
98
|
+
|
|
99
|
+
postproc_cleanup = parameters.get("params_extra", {}).get("cleanup", False)
|
|
100
|
+
|
|
101
|
+
preserve_batch = False
|
|
102
|
+
if output_format == "memmap":
|
|
103
|
+
log_and_print(
|
|
104
|
+
"Output format is 'memmap'. Configuring cleanup to preserve the memory-mapped file.",
|
|
105
|
+
level="warning"
|
|
106
|
+
)
|
|
107
|
+
preserve_batch = True
|
|
108
|
+
|
|
109
|
+
if postproc_cleanup:
|
|
110
|
+
import inspect
|
|
111
|
+
|
|
112
|
+
cleanup_params = inspect.signature(cleanup_files).parameters
|
|
113
|
+
if "preserve_batch" in cleanup_params:
|
|
114
|
+
cleanup_files(batch_path, export_path, preserve_batch=preserve_batch)
|
|
115
|
+
else:
|
|
116
|
+
if preserve_batch:
|
|
117
|
+
log_and_print(
|
|
118
|
+
"cleanup_files does not support preserve_batch; proceeding without it.",
|
|
119
|
+
level="warning",
|
|
120
|
+
)
|
|
121
|
+
cleanup_files(batch_path, export_path)
|
|
122
|
+
else:
|
|
123
|
+
log_and_print(
|
|
124
|
+
f"Keeping batch files associated to {batch_path}.", level="warning"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return export_path, batch_path
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
|
131
|
+
parser = argparse.ArgumentParser()
|
|
132
|
+
parser.add_argument("config_file", nargs="+", help="Input configuration JSON file")
|
|
133
|
+
parser.add_argument(
|
|
134
|
+
"-rp",
|
|
135
|
+
"--regex_pattern",
|
|
136
|
+
default="*_Ch2_*.ome.tif",
|
|
137
|
+
help="Regular expression pattern to match the data files",
|
|
138
|
+
)
|
|
139
|
+
parser.add_argument(
|
|
140
|
+
"-rc",
|
|
141
|
+
"--recompute",
|
|
142
|
+
action="store_false",
|
|
143
|
+
default=True,
|
|
144
|
+
help="Recompute the motion correction even if the batch folder exists",
|
|
145
|
+
)
|
|
146
|
+
parser.add_argument(
|
|
147
|
+
"--save-binary",
|
|
148
|
+
default=False,
|
|
149
|
+
help=(
|
|
150
|
+
"Save final movie as binary file, either 'bin', 'tiff', or 'h5'. "
|
|
151
|
+
"Default is False (a binary memmap is used temporarily and removed after cleanup)"
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
return parser.parse_args(argv)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _deepcopy_dict(data: dict) -> dict:
|
|
158
|
+
return json.loads(json.dumps(data))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _process_config(cfg_path: str, args: argparse.Namespace) -> None:
|
|
162
|
+
config = load_config(cfg_path)
|
|
163
|
+
|
|
164
|
+
paths_cfg = config.get("paths", {})
|
|
165
|
+
data_paths = paths_cfg.get("data_paths", [])
|
|
166
|
+
export_paths = paths_cfg.get("export_paths", [])
|
|
167
|
+
concatenation_groups = paths_cfg.get("concatenation_groups", None)
|
|
168
|
+
zstack_paths = paths_cfg.get("zstack_paths", None)
|
|
169
|
+
|
|
170
|
+
logging_cfg = config.get("logging", {})
|
|
171
|
+
if logging_cfg:
|
|
172
|
+
log_path = logging_cfg.get("log_path", os.path.dirname(export_paths[0]))
|
|
173
|
+
log_level = logging_cfg.get("log_level", "INFO")
|
|
174
|
+
else:
|
|
175
|
+
log_path = os.path.dirname(export_paths[0]) if export_paths else Path.cwd()
|
|
176
|
+
log_level = "INFO"
|
|
177
|
+
|
|
178
|
+
os.makedirs(log_path, exist_ok=True)
|
|
179
|
+
log_file = os.path.join(
|
|
180
|
+
log_path, datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".log"
|
|
181
|
+
)
|
|
182
|
+
print("\n")
|
|
183
|
+
print("Log file: " + log_file)
|
|
184
|
+
print("\n")
|
|
185
|
+
logging.basicConfig(
|
|
186
|
+
filename=log_file,
|
|
187
|
+
level=log_level,
|
|
188
|
+
format="%(asctime)s %(levelname)s: %(message)s",
|
|
189
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
190
|
+
)
|
|
191
|
+
logging.info("Starting batch motion correction for data in %s", cfg_path)
|
|
192
|
+
|
|
193
|
+
if concatenation_groups:
|
|
194
|
+
grouped_paths = {}
|
|
195
|
+
for index, path in zip(concatenation_groups, data_paths):
|
|
196
|
+
grouped_paths.setdefault(index, []).append(path)
|
|
197
|
+
grouped_paths_list = list(grouped_paths.values())
|
|
198
|
+
else:
|
|
199
|
+
grouped_paths_list = [[p] for p in data_paths]
|
|
200
|
+
|
|
201
|
+
print("\n🔹 Runs will be processed as follows:")
|
|
202
|
+
for group_num, paths in enumerate(grouped_paths_list, start=1):
|
|
203
|
+
print(f" * Group {group_num}:")
|
|
204
|
+
for p in paths:
|
|
205
|
+
print(f" - {p}")
|
|
206
|
+
print("\n")
|
|
207
|
+
|
|
208
|
+
base_params_mcorr = config.get("params_mcorr", {}).copy()
|
|
209
|
+
z_motion_correction = base_params_mcorr.pop("z_motion_correction", None)
|
|
210
|
+
base_params_mcorr.pop("method", None)
|
|
211
|
+
base_params_mcorr.pop("save_mcorr_movie", None)
|
|
212
|
+
base_params = {
|
|
213
|
+
"experimenter": config.get("experimenter", {}),
|
|
214
|
+
"subject": config.get("subject", {}),
|
|
215
|
+
"imaging": config.get("imaging", {}),
|
|
216
|
+
"params_mcorr": base_params_mcorr,
|
|
217
|
+
"params_extraction": config.get("params_extraction", {}),
|
|
218
|
+
"params_extra": config.get("params_extra", {}),
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for i, data_group in enumerate(grouped_paths_list):
|
|
222
|
+
data_group_paths = [Path(p) for p in data_group]
|
|
223
|
+
print(f"\n🔹 Processing Group {i+1}:")
|
|
224
|
+
print(" Data path(s):", data_group_paths)
|
|
225
|
+
|
|
226
|
+
if i >= len(export_paths):
|
|
227
|
+
print(f"⚠️ Warning: Missing export path for Group {i+1}. Skipping.")
|
|
228
|
+
continue
|
|
229
|
+
|
|
230
|
+
export_path = Path(export_paths[i])
|
|
231
|
+
print(" Export path:", export_path)
|
|
232
|
+
|
|
233
|
+
missing_paths = [p for p in data_group_paths if not p.exists()]
|
|
234
|
+
if missing_paths:
|
|
235
|
+
print(f"❌ Data paths not found: {missing_paths}. Skipping.")
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
params = _deepcopy_dict(base_params)
|
|
239
|
+
|
|
240
|
+
zstack_path = None
|
|
241
|
+
if zstack_paths is not None:
|
|
242
|
+
zstack_path = zstack_paths[i] if len(zstack_paths) > 1 else zstack_paths[0]
|
|
243
|
+
if not os.path.exists(zstack_path):
|
|
244
|
+
print(f"❌ Z-stack path not found: {zstack_path}. Check the path.")
|
|
245
|
+
continue
|
|
246
|
+
if z_motion_correction:
|
|
247
|
+
params["params_mcorr"]["z_motion_correction"] = z_motion_correction
|
|
248
|
+
params["zstack_path"] = str(zstack_path)
|
|
249
|
+
|
|
250
|
+
logging.info("Data path: %s", data_group_paths)
|
|
251
|
+
logging.info("Export path: %s", export_path)
|
|
252
|
+
logging.info("Parameters: %s", params)
|
|
253
|
+
|
|
254
|
+
params["export_path"] = str(export_path)
|
|
255
|
+
|
|
256
|
+
params_path = export_path / "run_mcorr_arguments.json"
|
|
257
|
+
export_path.mkdir(parents=True, exist_ok=True)
|
|
258
|
+
save_params = {
|
|
259
|
+
"data_path": [str(p) for p in data_group_paths],
|
|
260
|
+
"parameters": params,
|
|
261
|
+
"regex_pattern": args.regex_pattern,
|
|
262
|
+
"recompute": args.recompute,
|
|
263
|
+
}
|
|
264
|
+
with open(params_path, "w") as fh:
|
|
265
|
+
json.dump(save_params, fh, indent=4)
|
|
266
|
+
|
|
267
|
+
save_mcorr_movie = config.get("params_mcorr", {}).get("save_mcorr_movie", "memmap")
|
|
268
|
+
print(f" Save motion-corrected movie format from config: {save_mcorr_movie}")
|
|
269
|
+
if args.save_binary:
|
|
270
|
+
print(f"Overriding save_mcorr_movie with command line argument: {args.save_binary}")
|
|
271
|
+
save_mcorr_movie = args.save_binary
|
|
272
|
+
|
|
273
|
+
if save_mcorr_movie not in ['h5', 'memmap', 'bin', 'tiff']:
|
|
274
|
+
print(f"Invalid save_mcorr_movie format: {save_mcorr_movie}. Using 'memmap'")
|
|
275
|
+
save_mcorr_movie = 'memmap'
|
|
276
|
+
|
|
277
|
+
logging.info("Save motion-corrected movie as: %s", save_mcorr_movie)
|
|
278
|
+
|
|
279
|
+
_, batch_path = run_mcorr(
|
|
280
|
+
data_group_paths,
|
|
281
|
+
params,
|
|
282
|
+
regex_pattern=args.regex_pattern,
|
|
283
|
+
recompute=args.recompute,
|
|
284
|
+
output_format=save_mcorr_movie,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
logging.info("Batch path: %s", batch_path)
|
|
288
|
+
|
|
289
|
+
logging.shutdown()
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def main(argv: list[str] | None = None) -> None:
|
|
293
|
+
args = _parse_args(argv)
|
|
294
|
+
for cfg_path in args.config_file:
|
|
295
|
+
_process_config(cfg_path, args)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
if __name__ == "__main__":
|
|
299
|
+
main()
|
pipeline/roi_zcorr.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Batch execution of ROI z-motion correction."""
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import logging
|
|
6
|
+
import datetime
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
# Add project root to path
|
|
12
|
+
PROJECT_ROOT = Path(__file__).parent.parent
|
|
13
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
14
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
15
|
+
|
|
16
|
+
from modules.motion_correction import run_roi_zcorr
|
|
17
|
+
from pipeline.utils.config_loader import load_config
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
parser = argparse.ArgumentParser()
|
|
22
|
+
parser.add_argument("config_file", nargs="+", help="Input configuration JSON file")
|
|
23
|
+
args = parser.parse_args()
|
|
24
|
+
|
|
25
|
+
for cfg_path in args.config_file:
|
|
26
|
+
config = load_config(cfg_path)
|
|
27
|
+
|
|
28
|
+
paths_cfg = config.get("paths", {})
|
|
29
|
+
export_paths = paths_cfg.get("export_paths", [])
|
|
30
|
+
|
|
31
|
+
logging_cfg = config.get("logging", {})
|
|
32
|
+
if logging_cfg:
|
|
33
|
+
log_path = logging_cfg.get("log_path", os.path.dirname(export_paths[0]))
|
|
34
|
+
log_level = logging_cfg.get("log_level", "INFO")
|
|
35
|
+
else:
|
|
36
|
+
log_path = os.path.dirname(export_paths[0])
|
|
37
|
+
log_level = "INFO"
|
|
38
|
+
|
|
39
|
+
os.makedirs(log_path, exist_ok=True)
|
|
40
|
+
log_file = os.path.join(
|
|
41
|
+
log_path, datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ".log"
|
|
42
|
+
)
|
|
43
|
+
print("\n")
|
|
44
|
+
print("Log file: " + log_file)
|
|
45
|
+
print("\n")
|
|
46
|
+
logging.basicConfig(
|
|
47
|
+
filename=log_file,
|
|
48
|
+
level=log_level,
|
|
49
|
+
format="%(asctime)s %(levelname)s: %(message)s",
|
|
50
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
51
|
+
)
|
|
52
|
+
logging.info("Starting ROI z-motion correction for data in " + cfg_path)
|
|
53
|
+
|
|
54
|
+
params = {"params_mcorr": config.get("params_mcorr", {})}
|
|
55
|
+
|
|
56
|
+
for export_path in export_paths:
|
|
57
|
+
export_path = Path(export_path)
|
|
58
|
+
logging.info("Export path: " + str(export_path))
|
|
59
|
+
run_roi_zcorr(export_path, params)
|
|
60
|
+
|
|
61
|
+
logging.shutdown()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
main()
|
|
File without changes
|