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
modules/extraction.py
ADDED
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""extraction
|
|
3
|
+
|
|
4
|
+
Module implementing the constrained nonnegative matrix factorization (CNMF)
|
|
5
|
+
step of the Analysis 2P pipeline.
|
|
6
|
+
|
|
7
|
+
This module was extracted from :mod:`Mesmerize.pipeline_mcorr_cnmf` so that the
|
|
8
|
+
CNMF logic can be reused independently of the full pipeline.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
import re
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
# Add project root to path
|
|
18
|
+
PROJECT_ROOT = Path(__file__).parent.parent
|
|
19
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
20
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
21
|
+
|
|
22
|
+
import json
|
|
23
|
+
import multiprocessing
|
|
24
|
+
import shutil
|
|
25
|
+
import threading
|
|
26
|
+
import time
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
import numpy as np
|
|
30
|
+
import mesmerize_core as mc
|
|
31
|
+
from scipy import io
|
|
32
|
+
|
|
33
|
+
from caiman.mmapping import load_memmap, prepare_shape
|
|
34
|
+
from caiman import save_memmap as save_memmap
|
|
35
|
+
|
|
36
|
+
from pipeline.utils.pipeline_utils import (
|
|
37
|
+
log_and_print,
|
|
38
|
+
clip_range,
|
|
39
|
+
cat_movies_to_mp4,
|
|
40
|
+
load_caiman_memmap,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"run_cnmf",
|
|
45
|
+
"save_processing_parameters",
|
|
46
|
+
"prepare_cnmf_object",
|
|
47
|
+
"copy_mean_intensity_template",
|
|
48
|
+
"export_cnmf_results",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _prepare_cnmf_movie(mcorr_movie_path: str | Path, export_path: str | Path) -> Path:
|
|
53
|
+
mcorr_movie_path = Path(mcorr_movie_path)
|
|
54
|
+
export_path = Path(export_path)
|
|
55
|
+
|
|
56
|
+
# Ensure export directory exists for saving memmaps
|
|
57
|
+
if not export_path.exists():
|
|
58
|
+
export_path.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
|
|
60
|
+
if mcorr_movie_path.suffix.lower() in {".h5", ".hdf5", ".tif", ".tiff"}:
|
|
61
|
+
|
|
62
|
+
# Save memmap directly to export_path/mcorr to avoid tmp directory issues
|
|
63
|
+
mcorr_movie_path = Path(
|
|
64
|
+
save_memmap(
|
|
65
|
+
[str(mcorr_movie_path)],
|
|
66
|
+
base_name=str(export_path / "mcorr"),
|
|
67
|
+
order="C",
|
|
68
|
+
dview=None
|
|
69
|
+
)
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
RelPath = mcorr_movie_path.relative_to(export_path)
|
|
74
|
+
except ValueError:
|
|
75
|
+
# Path is not relative to export path (e.g. it's in /tmp)
|
|
76
|
+
new_path = export_path / mcorr_movie_path.name
|
|
77
|
+
if new_path.exists():
|
|
78
|
+
if new_path.stat().st_size == mcorr_movie_path.stat().st_size:
|
|
79
|
+
mcorr_movie_path = new_path
|
|
80
|
+
else:
|
|
81
|
+
shutil.move(mcorr_movie_path, new_path)
|
|
82
|
+
mcorr_movie_path = new_path
|
|
83
|
+
else:
|
|
84
|
+
shutil.move(mcorr_movie_path, new_path)
|
|
85
|
+
mcorr_movie_path = new_path
|
|
86
|
+
log_and_print(f"Moved temporary memmap to {mcorr_movie_path}")
|
|
87
|
+
|
|
88
|
+
return mcorr_movie_path
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _mean_projection_from_memmap(
|
|
92
|
+
movie_path: Path,
|
|
93
|
+
chunk_size: int = 200,
|
|
94
|
+
max_frames: int | None = None,
|
|
95
|
+
) -> np.ndarray:
|
|
96
|
+
adapter = load_caiman_memmap(movie_path)
|
|
97
|
+
T, Ly, Lx = adapter.shape
|
|
98
|
+
if max_frames is not None:
|
|
99
|
+
T = min(T, max_frames)
|
|
100
|
+
running_sum = np.zeros((Ly, Lx), dtype=np.float64)
|
|
101
|
+
for start in range(0, T, chunk_size):
|
|
102
|
+
stop = min(T, start + chunk_size)
|
|
103
|
+
running_sum += adapter[start:stop].sum(axis=0, dtype=np.float64)
|
|
104
|
+
adapter.close()
|
|
105
|
+
return (running_sum / float(T)).astype(np.float32)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _mean_projection_from_tiff(
|
|
109
|
+
movie_path: Path,
|
|
110
|
+
max_frames: int | None = None,
|
|
111
|
+
) -> np.ndarray:
|
|
112
|
+
import tifffile
|
|
113
|
+
|
|
114
|
+
running_sum = None
|
|
115
|
+
count = 0
|
|
116
|
+
with tifffile.TiffFile(movie_path) as tif:
|
|
117
|
+
for page in tif.pages:
|
|
118
|
+
frame = page.asarray()
|
|
119
|
+
if running_sum is None:
|
|
120
|
+
running_sum = np.zeros_like(frame, dtype=np.float64)
|
|
121
|
+
running_sum += frame.astype(np.float64, copy=False)
|
|
122
|
+
count += 1
|
|
123
|
+
if max_frames is not None and count >= max_frames:
|
|
124
|
+
break
|
|
125
|
+
if running_sum is None or count == 0:
|
|
126
|
+
raise ValueError(f"No frames found in {movie_path}")
|
|
127
|
+
return (running_sum / float(count)).astype(np.float32)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _mean_projection_from_h5(
|
|
131
|
+
movie_path: Path,
|
|
132
|
+
max_frames: int | None = None,
|
|
133
|
+
) -> np.ndarray:
|
|
134
|
+
import h5py
|
|
135
|
+
|
|
136
|
+
with h5py.File(movie_path, "r") as f:
|
|
137
|
+
if "data" in f:
|
|
138
|
+
dset = f["data"]
|
|
139
|
+
else:
|
|
140
|
+
keys = [k for k in f.keys()]
|
|
141
|
+
if not keys:
|
|
142
|
+
raise ValueError(f"No datasets found in {movie_path}")
|
|
143
|
+
dset = f[keys[0]]
|
|
144
|
+
T = dset.shape[0]
|
|
145
|
+
if max_frames is not None:
|
|
146
|
+
T = min(T, max_frames)
|
|
147
|
+
running_sum = np.zeros(dset.shape[1:], dtype=np.float64)
|
|
148
|
+
for start in range(0, T, 200):
|
|
149
|
+
stop = min(T, start + 200)
|
|
150
|
+
running_sum += dset[start:stop].sum(axis=0, dtype=np.float64)
|
|
151
|
+
return (running_sum / float(T)).astype(np.float32)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _mean_projection_from_path(
|
|
155
|
+
movie_path: str | Path,
|
|
156
|
+
max_frames: int | None = None,
|
|
157
|
+
) -> np.ndarray:
|
|
158
|
+
if isinstance(movie_path, np.memmap):
|
|
159
|
+
if movie_path.ndim == 3:
|
|
160
|
+
return np.mean(movie_path, axis=0).astype(np.float32)
|
|
161
|
+
filename = getattr(movie_path, "filename", None)
|
|
162
|
+
if filename:
|
|
163
|
+
path = Path(filename)
|
|
164
|
+
if path.suffix.lower() in {".mmap", ".npy"}:
|
|
165
|
+
return _mean_projection_from_memmap(path, max_frames=max_frames)
|
|
166
|
+
raise TypeError("Unsupported memmap input for mean projection")
|
|
167
|
+
else:
|
|
168
|
+
movie_path = Path(movie_path)
|
|
169
|
+
suffix = movie_path.suffix.lower()
|
|
170
|
+
if suffix in {".mmap", ".npy"}:
|
|
171
|
+
return _mean_projection_from_memmap(movie_path, max_frames=max_frames)
|
|
172
|
+
if suffix in {".tif", ".tiff", ".btf"}:
|
|
173
|
+
return _mean_projection_from_tiff(movie_path, max_frames=max_frames)
|
|
174
|
+
if suffix in {".h5", ".hdf5", ".n5", ".zarr"}:
|
|
175
|
+
return _mean_projection_from_h5(movie_path, max_frames=max_frames)
|
|
176
|
+
|
|
177
|
+
import caiman as cm
|
|
178
|
+
movie = cm.load(str(movie_path))
|
|
179
|
+
return np.mean(movie, axis=0).astype(np.float32)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _coerce_matlab_value(value):
|
|
183
|
+
if value is None:
|
|
184
|
+
return np.array([])
|
|
185
|
+
return value
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def countdown(n: int) -> None:
|
|
189
|
+
"""Simple countdown timer used when prompting the user."""
|
|
190
|
+
while n > 0:
|
|
191
|
+
mins, secs = divmod(n, 60)
|
|
192
|
+
timer = f"{mins:02d}:{secs:02d}"
|
|
193
|
+
print(timer, end="\r")
|
|
194
|
+
time.sleep(1)
|
|
195
|
+
n -= 1
|
|
196
|
+
|
|
197
|
+
def create_components_movie(batch_path, export_path, mcorr_movie_path=None, cnmf_obj=None, index=-1, excerpt=None):
|
|
198
|
+
"""
|
|
199
|
+
Create a movie concatenating the components and the residuals.
|
|
200
|
+
"""
|
|
201
|
+
# if mcorr_movie is not a string, load the data frame from the batch
|
|
202
|
+
# if not isinstance(mcorr_movie, str):
|
|
203
|
+
if batch_path is not None:
|
|
204
|
+
df = mc.load_batch(batch_path)
|
|
205
|
+
# Load the motion corrected movie
|
|
206
|
+
# mcorr_movie = df.iloc[index].caiman.get_input_movie()
|
|
207
|
+
# Load the components (reconstructed movie with no background)
|
|
208
|
+
neural_activity_movie = df.iloc[index].cnmf.get_rcm()
|
|
209
|
+
# image_neural_activity = neural_activity_movie[0,:,:]
|
|
210
|
+
# Load the residuals
|
|
211
|
+
residuals_movie = df.iloc[index].cnmf.get_residuals()
|
|
212
|
+
# image_residuals = residuals_movie[0,:,:]
|
|
213
|
+
else:
|
|
214
|
+
mcorr_movie_path = _prepare_cnmf_movie(mcorr_movie_path, export_path)
|
|
215
|
+
mcorr_movie, dims, T = load_memmap(str(mcorr_movie_path))
|
|
216
|
+
mcorr_movie = np.reshape(mcorr_movie.T, [T] + list(dims), order='F')
|
|
217
|
+
mcorr_movie = mcorr_movie.transpose(0, 2, 1)
|
|
218
|
+
|
|
219
|
+
# # If cnmf_obj is not a CNMF object, load the data frame from the batch path
|
|
220
|
+
# if isinstance(cnmf_obj, str):
|
|
221
|
+
# # Load the data frame from the batch
|
|
222
|
+
# df = mc.load_batch(batch_path)
|
|
223
|
+
# # Load the CNMF results
|
|
224
|
+
# cnmf_obj = df.iloc[index].cnmf.get_output()
|
|
225
|
+
# # Keep only accepted components
|
|
226
|
+
# cnmf_obj.estimates.select_components(use_object=True)
|
|
227
|
+
# # Compute dF/F
|
|
228
|
+
# if cnmf_obj.estimates.F_dff is None:
|
|
229
|
+
# print('Calculating estimates.F_dff')
|
|
230
|
+
# cnmf_obj.estimates.detrend_df_f(quantileMin=8,
|
|
231
|
+
# frames_window=400,
|
|
232
|
+
# use_residuals=False)
|
|
233
|
+
|
|
234
|
+
# reconstruct denoised components movie
|
|
235
|
+
neural_activity = cnmf_obj.estimates.A @ cnmf_obj.estimates.C # A ⊗ C
|
|
236
|
+
# reshape neural activity to movie dimensions
|
|
237
|
+
dims = cnmf_obj.dims
|
|
238
|
+
T = cnmf_obj.estimates.C.shape[1]
|
|
239
|
+
neural_activity_movie = np.reshape(neural_activity.T, [T] + list(dims), order='F')
|
|
240
|
+
|
|
241
|
+
# If residuals_movie is not defined, reconstruct it
|
|
242
|
+
# if 'residuals_movie' not in locals():
|
|
243
|
+
|
|
244
|
+
# reconstruct background movie
|
|
245
|
+
background = cnmf_obj.estimates.b @ cnmf_obj.estimates.f # b ⊗ f
|
|
246
|
+
# reconstruct denoised movie
|
|
247
|
+
denoised_movie = neural_activity + background # AC + bf
|
|
248
|
+
# reshape denoised movie to movie dimensions
|
|
249
|
+
denoised_movie = np.reshape(denoised_movie.T, [T] + list(dims), order='F')
|
|
250
|
+
# reconstruct residuals movie
|
|
251
|
+
residuals_movie = mcorr_movie - denoised_movie # mcorr_movie - AC - bf
|
|
252
|
+
# turn into a movie object
|
|
253
|
+
# denoised_movie = cm.movie(denoised_movie).reshape(dims + (-1,), order='F').transpose([2, 0, 1])
|
|
254
|
+
|
|
255
|
+
# If excerpt requested, keep only the first x frames of the movie
|
|
256
|
+
if excerpt is not None:
|
|
257
|
+
neural_activity_movie = neural_activity_movie[:excerpt]
|
|
258
|
+
residuals_movie = residuals_movie[:excerpt]
|
|
259
|
+
|
|
260
|
+
# Data is originally uint16, but values may extend beyond range, and converted to float at this point.
|
|
261
|
+
# Clip the values to the uint16 range, and convert to uint16 data type
|
|
262
|
+
neural_activity_movie = clip_range(neural_activity_movie, 'uint16').astype('uint16')
|
|
263
|
+
residuals_movie = clip_range(residuals_movie, 'uint16').astype('uint16')
|
|
264
|
+
|
|
265
|
+
# # Convert to uint8 to reduce file size further
|
|
266
|
+
# neural_activity_movie = (neural_activity_movie / (2**16-1) * 255).astype('uint8')
|
|
267
|
+
# residuals_movie = (residuals_movie / (2**16-1) * 255).astype('uint8')
|
|
268
|
+
|
|
269
|
+
# Set the path of the mp4 movie
|
|
270
|
+
movie_path = Path.joinpath(export_path, f"compare_components_residuals.mp4")
|
|
271
|
+
|
|
272
|
+
# Concatenate the two movies horizontally
|
|
273
|
+
cat_movies_to_mp4(neural_activity_movie, residuals_movie, movie_path)
|
|
274
|
+
|
|
275
|
+
log_and_print(f"Saved components movie to {export_path}/components_movie.mp4")
|
|
276
|
+
|
|
277
|
+
def run_cnmf(
|
|
278
|
+
batch: Path,
|
|
279
|
+
index: int | None,
|
|
280
|
+
export_path: Path,
|
|
281
|
+
params_extraction: dict,
|
|
282
|
+
data_path,
|
|
283
|
+
input_movie_path: str | Path | None = None,
|
|
284
|
+
z_correlation=None,
|
|
285
|
+
z_motion_scaling_factors=None,
|
|
286
|
+
):
|
|
287
|
+
"""Run CNMF on a motion corrected movie and export results.
|
|
288
|
+
|
|
289
|
+
Parameters
|
|
290
|
+
----------
|
|
291
|
+
batch : Path
|
|
292
|
+
Path to the Mesmerize batch file produced by motion correction.
|
|
293
|
+
index : int | None
|
|
294
|
+
Index of the motion correction item within the batch. None if using input_movie_path.
|
|
295
|
+
export_path : Path
|
|
296
|
+
Directory where outputs should be written.
|
|
297
|
+
params_extraction : dict
|
|
298
|
+
Parameters for the CNMF algorithm.
|
|
299
|
+
data_path : str or Path or list
|
|
300
|
+
Original data location (used only for metadata in the saved parameters).
|
|
301
|
+
input_movie_path : str | Path, optional
|
|
302
|
+
Direct path to input movie if not using a batch item.
|
|
303
|
+
z_correlation : np.ndarray, optional
|
|
304
|
+
Pre-computed z correlation data.
|
|
305
|
+
z_motion_scaling_factors : np.ndarray, optional
|
|
306
|
+
Scaling factors from z motion subtraction (unused but kept for backwards
|
|
307
|
+
compatibility).
|
|
308
|
+
"""
|
|
309
|
+
# Set the parent raw data path before any batch operations
|
|
310
|
+
mc.set_parent_raw_data_path(Path(export_path))
|
|
311
|
+
|
|
312
|
+
# Load batch dataframe
|
|
313
|
+
df = mc.load_batch(batch)
|
|
314
|
+
|
|
315
|
+
if input_movie_path is not None:
|
|
316
|
+
mcorr_movie_path = Path(input_movie_path)
|
|
317
|
+
item_name = mcorr_movie_path.stem
|
|
318
|
+
elif index is not None:
|
|
319
|
+
mcorr_movie_path=df.iloc[index]
|
|
320
|
+
item_name = df.iloc[index]["item_name"]
|
|
321
|
+
else:
|
|
322
|
+
raise ValueError("Either index or input_movie_path must be provided.")
|
|
323
|
+
|
|
324
|
+
if str(mcorr_movie_path).endswith((".h5", ".hdf5", ".tif", ".tiff")):
|
|
325
|
+
mcorr_movie_path = _prepare_cnmf_movie(mcorr_movie_path, export_path)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# Add CNMF item using the motion corrected result
|
|
329
|
+
df.caiman.add_item(
|
|
330
|
+
algo="cnmf",
|
|
331
|
+
input_movie_path=mcorr_movie_path,
|
|
332
|
+
params=params_extraction,
|
|
333
|
+
item_name=item_name,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Handle existing multiprocessing children from previous runs
|
|
337
|
+
if len(multiprocessing.active_children()) > 0:
|
|
338
|
+
import select
|
|
339
|
+
|
|
340
|
+
print("There are active processes : ")
|
|
341
|
+
for p in multiprocessing.active_children():
|
|
342
|
+
print(p)
|
|
343
|
+
print("Do you want to kill them ? (y/n)")
|
|
344
|
+
|
|
345
|
+
t = threading.Thread(target=countdown, args=(300,))
|
|
346
|
+
t.start()
|
|
347
|
+
|
|
348
|
+
i, _, _ = select.select([sys.stdin], [], [], 300)
|
|
349
|
+
if i:
|
|
350
|
+
answer = sys.stdin.readline().strip()
|
|
351
|
+
else:
|
|
352
|
+
print("Timeout, killing the processes.")
|
|
353
|
+
answer = "y"
|
|
354
|
+
|
|
355
|
+
if answer == "y":
|
|
356
|
+
print("Killing processes")
|
|
357
|
+
for p in multiprocessing.active_children():
|
|
358
|
+
p.terminate()
|
|
359
|
+
p.join()
|
|
360
|
+
else:
|
|
361
|
+
print("CNMF may not run properly if a cluster is already running.")
|
|
362
|
+
|
|
363
|
+
# Run CNMF for items that haven't been processed yet
|
|
364
|
+
for _, row in df.iterrows():
|
|
365
|
+
if row.algo != "cnmf":
|
|
366
|
+
continue
|
|
367
|
+
if row["outputs"] is not None:
|
|
368
|
+
continue
|
|
369
|
+
|
|
370
|
+
log_and_print(
|
|
371
|
+
f"Running batch item {row.name}, id {row.uuid}, algo {row.algo}."
|
|
372
|
+
)
|
|
373
|
+
process = row.caiman.run()
|
|
374
|
+
|
|
375
|
+
if process.__class__.__name__ == "DummyProcess":
|
|
376
|
+
df = df.caiman.reload_from_disk()
|
|
377
|
+
|
|
378
|
+
log_and_print(f"Batch completed for CNMF. Results saved to {batch}.")
|
|
379
|
+
|
|
380
|
+
# Reload to ensure we have the latest results
|
|
381
|
+
df = df.caiman.reload_from_disk()
|
|
382
|
+
|
|
383
|
+
# Save parameters and export results
|
|
384
|
+
save_processing_parameters(df, export_path, data_path, params_extraction)
|
|
385
|
+
|
|
386
|
+
# Get CNMF object and prepare it for export
|
|
387
|
+
cnmf_obj = prepare_cnmf_object(df)
|
|
388
|
+
|
|
389
|
+
# Copy the mean intensity template if available
|
|
390
|
+
copy_mean_intensity_template(df, batch, export_path)
|
|
391
|
+
|
|
392
|
+
# Export results to MATLAB format
|
|
393
|
+
export_cnmf_results(df, cnmf_obj, export_path, z_correlation)
|
|
394
|
+
|
|
395
|
+
return cnmf_obj
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def save_processing_parameters(df, export_path, data_path, params_extraction):
|
|
399
|
+
"""Save the parameters used for motion correction and CNMF processing.
|
|
400
|
+
|
|
401
|
+
Parameters
|
|
402
|
+
----------
|
|
403
|
+
df : pandas.DataFrame
|
|
404
|
+
Mesmerize batch dataframe containing processing information.
|
|
405
|
+
export_path : Path
|
|
406
|
+
Directory where parameter file should be saved.
|
|
407
|
+
data_path : str, Path, or list
|
|
408
|
+
Original data location.
|
|
409
|
+
params_extraction : dict
|
|
410
|
+
CNMF parameters used for processing.
|
|
411
|
+
"""
|
|
412
|
+
params_path = export_path / "caiman_params.json"
|
|
413
|
+
|
|
414
|
+
if isinstance(data_path, list):
|
|
415
|
+
data_path = data_path[0]
|
|
416
|
+
|
|
417
|
+
# Use the first batch item to identify the original input movie if possible
|
|
418
|
+
# This preserves the logic to point to the raw data (input of mcorr) rather than the mcorr output
|
|
419
|
+
movie_name = str(df.iloc[0]["input_movie_path"])
|
|
420
|
+
|
|
421
|
+
caiman_params = {
|
|
422
|
+
"data_path": str(data_path),
|
|
423
|
+
"export_path": str(export_path),
|
|
424
|
+
"movie_name": movie_name,
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
# Attempt to retrieve motion correction parameters from the batch history
|
|
428
|
+
caiman_mcorr_params = {}
|
|
429
|
+
|
|
430
|
+
# Check if we have mcorr items in the batch
|
|
431
|
+
# We filter by algo to be safe
|
|
432
|
+
mcorr_items = df[df["algo"] == "mcorr"]
|
|
433
|
+
if not mcorr_items.empty:
|
|
434
|
+
# Use the first mcorr item found
|
|
435
|
+
caiman_mcorr_params = mcorr_items.iloc[0]["params"]["main"]
|
|
436
|
+
caiman_mcorr_params["timestamp_mcorr"] = mcorr_items.iloc[0]["ran_time"]
|
|
437
|
+
|
|
438
|
+
# Get CNMF parameters
|
|
439
|
+
# Merge passed params with what's in the dataframe to be sure
|
|
440
|
+
caiman_cnmf_params = params_extraction["main"]
|
|
441
|
+
if "params" in df.columns and "main" in df.iloc[-1]["params"]:
|
|
442
|
+
caiman_cnmf_params = caiman_cnmf_params | df.iloc[-1]["params"]["main"]
|
|
443
|
+
|
|
444
|
+
caiman_cnmf_params["timestamp_cnmf"] = df.iloc[-1]["ran_time"]
|
|
445
|
+
|
|
446
|
+
caiman_params = caiman_params | caiman_mcorr_params | caiman_cnmf_params
|
|
447
|
+
|
|
448
|
+
with open(params_path, "w") as f:
|
|
449
|
+
json.dump(caiman_params, f, indent=4)
|
|
450
|
+
|
|
451
|
+
log_and_print(f"Saved parameters to {params_path}.")
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def prepare_cnmf_object(df):
|
|
455
|
+
"""Prepare the CNMF object for export by selecting components and calculating dF/F.
|
|
456
|
+
|
|
457
|
+
Parameters
|
|
458
|
+
----------
|
|
459
|
+
df : pandas.DataFrame
|
|
460
|
+
Mesmerize batch dataframe containing processing information.
|
|
461
|
+
|
|
462
|
+
Returns
|
|
463
|
+
-------
|
|
464
|
+
cnmf_obj : caiman.source_extraction.cnmf.cnmf.CNMF
|
|
465
|
+
Prepared CNMF object with selected components and calculated F_dff.
|
|
466
|
+
"""
|
|
467
|
+
cnmf_obj = df.iloc[-1].cnmf.get_output()
|
|
468
|
+
|
|
469
|
+
# Select components
|
|
470
|
+
cnmf_obj.estimates.select_components(use_object=True)
|
|
471
|
+
|
|
472
|
+
# Calculate dF/F if not already calculated
|
|
473
|
+
if cnmf_obj.estimates.F_dff is None:
|
|
474
|
+
log_and_print("Calculating estimates.F_dff")
|
|
475
|
+
cnmf_obj.estimates.detrend_df_f(
|
|
476
|
+
quantileMin=8, frames_window=400, use_residuals=False
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
return cnmf_obj
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def copy_mean_intensity_template(df, batch, export_path):
|
|
483
|
+
"""Copy the mean intensity template file to the export directory.
|
|
484
|
+
|
|
485
|
+
Parameters
|
|
486
|
+
----------
|
|
487
|
+
df : pandas.DataFrame
|
|
488
|
+
Mesmerize batch dataframe containing processing information.
|
|
489
|
+
batch : Path
|
|
490
|
+
Path to the Mesmerize batch file.
|
|
491
|
+
export_path : Path
|
|
492
|
+
Directory where template file should be copied.
|
|
493
|
+
"""
|
|
494
|
+
cnmf_uuid = df[df["algo"] == "cnmf"]["uuid"].values[0]
|
|
495
|
+
mean_intensity_template_path = batch.parent / cnmf_uuid / f"{cnmf_uuid}_cn.npy"
|
|
496
|
+
|
|
497
|
+
if mean_intensity_template_path.exists():
|
|
498
|
+
shutil.copy(mean_intensity_template_path, export_path / "mean_intensity_template.npy")
|
|
499
|
+
log_and_print(f"Saved mean_intensity_template.npy to {export_path}.")
|
|
500
|
+
else:
|
|
501
|
+
log_and_print(
|
|
502
|
+
f"Could not find cn.npy file at {mean_intensity_template_path}.")
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def export_cnmf_results(df, cnmf_obj, export_path, z_correlation=None):
|
|
506
|
+
"""Export CNMF results to MATLAB format.
|
|
507
|
+
|
|
508
|
+
Parameters
|
|
509
|
+
----------
|
|
510
|
+
df : pandas.DataFrame
|
|
511
|
+
Mesmerize batch dataframe containing processing information.
|
|
512
|
+
cnmf_obj : caiman.source_extraction.cnmf.cnmf.CNMF
|
|
513
|
+
CNMF object containing processing results.
|
|
514
|
+
export_path : Path
|
|
515
|
+
Directory where results should be saved.
|
|
516
|
+
z_correlation : np.ndarray, optional
|
|
517
|
+
Z correlation data if available.
|
|
518
|
+
"""
|
|
519
|
+
# Get Z position information if available
|
|
520
|
+
if z_correlation is None:
|
|
521
|
+
zcorr_file = export_path / "z_correlation.npz"
|
|
522
|
+
if zcorr_file.exists():
|
|
523
|
+
z_correlation = np.load(zcorr_file)
|
|
524
|
+
zpos = z_correlation["zpos"]
|
|
525
|
+
else:
|
|
526
|
+
zpos = np.array([np.nan])
|
|
527
|
+
else:
|
|
528
|
+
# Assume z_correlation is a dict-like object with "zpos" key
|
|
529
|
+
zpos = z_correlation.get("zpos", np.array([np.nan]))
|
|
530
|
+
|
|
531
|
+
mcorr_rows = df[df["algo"] == "mcorr"]
|
|
532
|
+
if not mcorr_rows.empty:
|
|
533
|
+
mean_map = mcorr_rows.iloc[0].caiman.get_projection("mean")
|
|
534
|
+
else:
|
|
535
|
+
# Use the input movie of the CNMF item
|
|
536
|
+
movie_path = df.iloc[-1].caiman.get_input_movie()
|
|
537
|
+
mean_map = _mean_projection_from_path(movie_path)
|
|
538
|
+
|
|
539
|
+
# Export to MATLAB format
|
|
540
|
+
io.savemat(
|
|
541
|
+
export_path / "results_caiman.mat",
|
|
542
|
+
mdict={
|
|
543
|
+
"mean_map_motion_corrected": _coerce_matlab_value(mean_map),
|
|
544
|
+
"spatial_components": _coerce_matlab_value(cnmf_obj.estimates.A),
|
|
545
|
+
"temporal_components": _coerce_matlab_value(cnmf_obj.estimates.C),
|
|
546
|
+
"background_spatial_component": _coerce_matlab_value(cnmf_obj.estimates.b),
|
|
547
|
+
"background_temporal_component": _coerce_matlab_value(cnmf_obj.estimates.f),
|
|
548
|
+
"residuals": _coerce_matlab_value(cnmf_obj.estimates.R),
|
|
549
|
+
"df_wo_bckgrnd": _coerce_matlab_value(cnmf_obj.estimates.F_dff),
|
|
550
|
+
"deconv_spk": _coerce_matlab_value(cnmf_obj.estimates.S),
|
|
551
|
+
"SNR_comp": _coerce_matlab_value(cnmf_obj.estimates.SNR_comp),
|
|
552
|
+
"baseline": _coerce_matlab_value(np.array(cnmf_obj.estimates.bl, dtype=np.float32) if cnmf_obj.estimates.bl is not None else None),
|
|
553
|
+
"noise": _coerce_matlab_value(np.array(cnmf_obj.estimates.neurons_sn, dtype=np.float32) if cnmf_obj.estimates.neurons_sn is not None else None),
|
|
554
|
+
"zpos": zpos,
|
|
555
|
+
},
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
log_and_print(f"Saved results to {export_path}/results_caiman.mat.")
|
|
559
|
+
|