lbm_caiman_python 0.2.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.
- lbm_caiman_python/__init__.py +63 -0
- lbm_caiman_python/__main__.py +302 -0
- lbm_caiman_python/_version.py +8 -0
- lbm_caiman_python/batch.py +188 -0
- lbm_caiman_python/collation.py +125 -0
- lbm_caiman_python/default_ops.py +92 -0
- lbm_caiman_python/gui/__init__.py +3 -0
- lbm_caiman_python/gui/_store_model.py +170 -0
- lbm_caiman_python/gui/rungui.py +13 -0
- lbm_caiman_python/gui/widgets.py +114 -0
- lbm_caiman_python/helpers.py +262 -0
- lbm_caiman_python/postprocessing.py +319 -0
- lbm_caiman_python/run_lcp.py +1059 -0
- lbm_caiman_python/stdout.py +3 -0
- lbm_caiman_python/summary.py +569 -0
- lbm_caiman_python/util/__init__.py +87 -0
- lbm_caiman_python/util/exceptions.py +6 -0
- lbm_caiman_python/util/quality.py +366 -0
- lbm_caiman_python/util/signal.py +17 -0
- lbm_caiman_python/util/transform.py +208 -0
- lbm_caiman_python/visualize.py +522 -0
- lbm_caiman_python-0.2.0.dist-info/METADATA +161 -0
- lbm_caiman_python-0.2.0.dist-info/RECORD +27 -0
- lbm_caiman_python-0.2.0.dist-info/WHEEL +5 -0
- lbm_caiman_python-0.2.0.dist-info/entry_points.txt +2 -0
- lbm_caiman_python-0.2.0.dist-info/licenses/LICENSE.md +38 -0
- lbm_caiman_python-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
from typing import Any as ArrayLike
|
|
2
|
+
|
|
3
|
+
import matplotlib as mpl
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from matplotlib import pyplot as plt, patches as patches, patheffects as path_effects
|
|
8
|
+
import fastplotlib as fpl
|
|
9
|
+
|
|
10
|
+
from lbm_caiman_python.util.signal import smooth_data
|
|
11
|
+
|
|
12
|
+
def export_contours_with_params(row, save_path):
|
|
13
|
+
params = row.params
|
|
14
|
+
corr = row.caiman.get_corr_image()
|
|
15
|
+
contours = row.cnmf.get_contours("good", swap_dim=False)[0]
|
|
16
|
+
contours_bad = row.cnmf.get_contours("bad", swap_dim=False)[0]
|
|
17
|
+
|
|
18
|
+
table_data = params["main"]
|
|
19
|
+
df_table = pd.DataFrame(list(table_data.items()), columns=["Parameter", "Value"])
|
|
20
|
+
|
|
21
|
+
fig, axes = plt.subplots(1, 2, figsize=(14, 7))
|
|
22
|
+
axes[0].imshow(corr, cmap='gray')
|
|
23
|
+
for contour in contours:
|
|
24
|
+
axes[0].plot(contour[:, 0], contour[:, 1], color='cyan', linewidth=1)
|
|
25
|
+
for contour in contours_bad:
|
|
26
|
+
axes[0].plot(contour[:, 0], contour[:, 1], color='red', linewidth=0.2)
|
|
27
|
+
|
|
28
|
+
axes[0].set_title(f'Accepted ({len(contours)}) and Rejected ({len(contours_bad)}) Neurons')
|
|
29
|
+
axes[0].axis('off')
|
|
30
|
+
axes[1].axis('tight')
|
|
31
|
+
axes[1].axis('off')
|
|
32
|
+
|
|
33
|
+
table = axes[1].table(cellText=df_table.values,
|
|
34
|
+
colLabels=df_table.columns,
|
|
35
|
+
loc='center',
|
|
36
|
+
cellLoc='center',
|
|
37
|
+
colWidths=[0.4, 0.6])
|
|
38
|
+
|
|
39
|
+
table.auto_set_font_size(False)
|
|
40
|
+
table.set_fontsize(10)
|
|
41
|
+
table.auto_set_column_width([0, 1])
|
|
42
|
+
plt.tight_layout()
|
|
43
|
+
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def plot_contours(df, plot_index, histogram_widget=False):
|
|
47
|
+
"""
|
|
48
|
+
Plot the contours of the accepted and rejected components.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
df : DataFrame
|
|
53
|
+
DataFrame containing the CNMF pandas extension.
|
|
54
|
+
plot_index : int
|
|
55
|
+
Index of the DataFrame to plot.
|
|
56
|
+
histogram_widget : bool, optional
|
|
57
|
+
Flag to display the vmin/vmax histogram controller.
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
fpl.ImageWidget
|
|
62
|
+
Widget with 2 subplots containing the contours of the accepted and rejected components.
|
|
63
|
+
"""
|
|
64
|
+
model = df.iloc[plot_index].cnmf.get_output()
|
|
65
|
+
print(f"Accepted: {len(model.estimates.idx_components)} | Rejected: {len(model.estimates.idx_components_bad)}")
|
|
66
|
+
contours_g = df.iloc[plot_index].cnmf.get_contours("good", swap_dim=False)
|
|
67
|
+
contours_b = df.iloc[plot_index].cnmf.get_contours("bad", swap_dim=False)
|
|
68
|
+
mcorr_movie = df.iloc[plot_index].caiman.get_input_movie()
|
|
69
|
+
|
|
70
|
+
image_widget = fpl.ImageWidget(
|
|
71
|
+
data=[mcorr_movie, mcorr_movie],
|
|
72
|
+
names=['Accepted', 'Rejected'],
|
|
73
|
+
window_funcs={'t': (np.mean, 3)},
|
|
74
|
+
figure_kwargs={'size': (1200, 600)},
|
|
75
|
+
histogram_widget=histogram_widget
|
|
76
|
+
figure_kwargs={'size': (1200, 600)}
|
|
77
|
+
)
|
|
78
|
+
for subplot in image_widget.figure:
|
|
79
|
+
if subplot.name == 'Accepted':
|
|
80
|
+
subplot.add_line_collection(
|
|
81
|
+
contours_g[0],
|
|
82
|
+
name="contours"
|
|
83
|
+
)
|
|
84
|
+
elif subplot.name == 'Rejected':
|
|
85
|
+
subplot.add_line_collection(
|
|
86
|
+
contours_b[0],
|
|
87
|
+
name="contours"
|
|
88
|
+
)
|
|
89
|
+
return image_widget
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def export_contours_with_params(row, save_path):
|
|
93
|
+
params = row.params
|
|
94
|
+
corr = row.caiman.get_corr_image()
|
|
95
|
+
contours = row.cnmf.get_contours("good", swap_dim=False)[0]
|
|
96
|
+
contours_bad = row.cnmf.get_contours("bad", swap_dim=False)[0]
|
|
97
|
+
|
|
98
|
+
table_data = params["main"]
|
|
99
|
+
df_table = pd.DataFrame(list(table_data.items()), columns=["Parameter", "Value"])
|
|
100
|
+
|
|
101
|
+
fig, axes = plt.subplots(1, 2, figsize=(14, 7))
|
|
102
|
+
axes[0].imshow(corr, cmap='gray')
|
|
103
|
+
for contour in contours:
|
|
104
|
+
axes[0].plot(contour[:, 0], contour[:, 1], color='cyan', linewidth=1)
|
|
105
|
+
for contour in contours_bad:
|
|
106
|
+
axes[0].plot(contour[:, 0], contour[:, 1], color='red', linewidth=0.2)
|
|
107
|
+
|
|
108
|
+
axes[0].set_title(f'Accepted ({len(contours)}) and Rejected ({len(contours_bad)}) Neurons')
|
|
109
|
+
axes[0].axis('off')
|
|
110
|
+
axes[1].axis('tight')
|
|
111
|
+
axes[1].axis('off')
|
|
112
|
+
|
|
113
|
+
table = axes[1].table(cellText=df_table.values,
|
|
114
|
+
colLabels=df_table.columns,
|
|
115
|
+
loc='center',
|
|
116
|
+
cellLoc='center',
|
|
117
|
+
colWidths=[0.4, 0.6])
|
|
118
|
+
|
|
119
|
+
table.auto_set_font_size(False)
|
|
120
|
+
table.set_fontsize(10)
|
|
121
|
+
table.auto_set_column_width([0, 1])
|
|
122
|
+
plt.tight_layout()
|
|
123
|
+
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def plot_contours(df, plot_index, histogram_widget=False):
|
|
127
|
+
"""
|
|
128
|
+
Plot the contours of the accepted and rejected components.
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
df : DataFrame
|
|
133
|
+
DataFrame containing the CNMF pandas extension.
|
|
134
|
+
plot_index : int
|
|
135
|
+
Index of the DataFrame to plot.
|
|
136
|
+
histogram_widget : bool, optional
|
|
137
|
+
Flag to display the vmin/vmax histogram controller.
|
|
138
|
+
|
|
139
|
+
Returns
|
|
140
|
+
-------
|
|
141
|
+
fpl.ImageWidget
|
|
142
|
+
Widget with 2 subplots containing the contours of the accepted and rejected components.
|
|
143
|
+
"""
|
|
144
|
+
model = df.iloc[plot_index].cnmf.get_output()
|
|
145
|
+
print(f"Accepted: {len(model.estimates.idx_components)} | Rejected: {len(model.estimates.idx_components_bad)}")
|
|
146
|
+
contours_g = df.iloc[plot_index].cnmf.get_contours("good", swap_dim=False)
|
|
147
|
+
contours_b = df.iloc[plot_index].cnmf.get_contours("bad", swap_dim=False)
|
|
148
|
+
mcorr_movie = df.iloc[plot_index].caiman.get_input_movie()
|
|
149
|
+
|
|
150
|
+
image_widget = fpl.ImageWidget(
|
|
151
|
+
data=[mcorr_movie, mcorr_movie],
|
|
152
|
+
names=['Accepted', 'Rejected'],
|
|
153
|
+
window_funcs={'t': (np.mean, 3)},
|
|
154
|
+
figure_kwargs={'size': (1200, 600)},
|
|
155
|
+
histogram_widget=histogram_widget
|
|
156
|
+
)
|
|
157
|
+
for subplot in image_widget.figure:
|
|
158
|
+
if subplot.name == 'Accepted':
|
|
159
|
+
subplot.add_line_collection(
|
|
160
|
+
contours_g[0],
|
|
161
|
+
name="contours"
|
|
162
|
+
)
|
|
163
|
+
elif subplot.name == 'Rejected':
|
|
164
|
+
subplot.add_line_collection(
|
|
165
|
+
contours_b[0],
|
|
166
|
+
name="contours"
|
|
167
|
+
)
|
|
168
|
+
return image_widget
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def plot_with_scalebars(image: ArrayLike, pixel_resolution: float):
|
|
172
|
+
"""
|
|
173
|
+
Plot a 2D image with scale bars of 5, 10, and 20 microns.
|
|
174
|
+
|
|
175
|
+
Parameters
|
|
176
|
+
----------
|
|
177
|
+
image : ndarray
|
|
178
|
+
A 2D NumPy array representing the image to be plotted.
|
|
179
|
+
pixel_resolution : float
|
|
180
|
+
The resolution of the image in microns per pixel.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
None
|
|
185
|
+
"""
|
|
186
|
+
scale_bar_sizes = [5, 10, 20] # Sizes of scale bars in microns
|
|
187
|
+
|
|
188
|
+
# Calculate the size of scale bars in pixels for each bar size
|
|
189
|
+
scale_bar_lengths = [int(size / pixel_resolution) for size in scale_bar_sizes]
|
|
190
|
+
|
|
191
|
+
# Create subplots to display each version of the image
|
|
192
|
+
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
|
|
193
|
+
|
|
194
|
+
for ax, scale_length, size in zip(axes, scale_bar_lengths, scale_bar_sizes):
|
|
195
|
+
ax.imshow(image, cmap='gray')
|
|
196
|
+
|
|
197
|
+
# Determine image dimensions for dynamic placement of scale bar
|
|
198
|
+
image_height, image_width = image.shape
|
|
199
|
+
|
|
200
|
+
# Scale bar thickness is 1% of the image height, but at least 2px thick
|
|
201
|
+
bar_thickness = max(2, int(0.01 * image_height)) # Thinner bar than before
|
|
202
|
+
|
|
203
|
+
# Center the scale bar horizontally and vertically
|
|
204
|
+
bar_x = (image_width // 2) - (scale_length // 2) # Centered horizontally
|
|
205
|
+
bar_y = (image_height // 2) - (bar_thickness // 2) # Centered vertically
|
|
206
|
+
|
|
207
|
+
# Draw the scale bar
|
|
208
|
+
ax.add_patch(patches.Rectangle((bar_x, bar_y), scale_length, bar_thickness,
|
|
209
|
+
color='white', edgecolor='black', linewidth=1))
|
|
210
|
+
|
|
211
|
+
# Add annotation for the scale bar (below the bar)
|
|
212
|
+
font_size = max(10, int(0.03 * image_height)) # Font size relative to image size
|
|
213
|
+
text = ax.text(bar_x + scale_length / 2, bar_y + bar_thickness + font_size + 5,
|
|
214
|
+
f'{size} μm', color='white', ha='center', va='top',
|
|
215
|
+
fontsize=font_size, fontweight='bold')
|
|
216
|
+
|
|
217
|
+
# Apply a stroke effect to the text for better contrast
|
|
218
|
+
text.set_path_effects([
|
|
219
|
+
path_effects.Stroke(linewidth=2, foreground='black'),
|
|
220
|
+
path_effects.Normal()
|
|
221
|
+
])
|
|
222
|
+
|
|
223
|
+
# Remove axis for a clean image
|
|
224
|
+
ax.axis('off')
|
|
225
|
+
|
|
226
|
+
# Adjust layout for better visualization
|
|
227
|
+
plt.tight_layout()
|
|
228
|
+
plt.show()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def plot_optical_flows(input_df: pd.DataFrame, max_columns=4, save_path=None):
|
|
232
|
+
"""
|
|
233
|
+
Plots the dense optical flow images from a DataFrame containing metrics information.
|
|
234
|
+
|
|
235
|
+
Parameters
|
|
236
|
+
----------
|
|
237
|
+
input_df : DataFrame
|
|
238
|
+
DataFrame containing 'flows', 'batch_index', 'mean_corr', 'mean_norm', 'crispness', and other related columns.
|
|
239
|
+
Typically, use the output of `create_metrics_df` to get the input DataFrame.
|
|
240
|
+
max_columns : int, optional
|
|
241
|
+
Maximum number of columns to display in the plot. Default is 4.
|
|
242
|
+
|
|
243
|
+
Examples
|
|
244
|
+
--------
|
|
245
|
+
>>> import lbm_caiman_python as lcp
|
|
246
|
+
>>> import lbm_mc as mc
|
|
247
|
+
>>> batch_df = mc.load_batch('path/to/batch.pickle')
|
|
248
|
+
>>> metrics_files = lbm_caiman_python.summary.compute_mcorr_metrics_batch(batch_df)
|
|
249
|
+
>>> metrics_df = lbm_caiman_python.summary._create_df_from_metric_files(metrics_files)
|
|
250
|
+
>>> lcp.plot_optical_flows(metrics_df, max_columns=2)
|
|
251
|
+
"""
|
|
252
|
+
num_graphs = len(input_df)
|
|
253
|
+
num_rows = int(np.ceil(num_graphs / max_columns))
|
|
254
|
+
|
|
255
|
+
fig, axes = plt.subplots(num_rows, max_columns, figsize=(20, 5 * num_rows))
|
|
256
|
+
axes = axes.flatten()
|
|
257
|
+
|
|
258
|
+
flow_images = []
|
|
259
|
+
|
|
260
|
+
highest_corr_batch = input_df.loc[input_df['mean_corr'].idxmax()]['batch_index']
|
|
261
|
+
highest_crisp_batch = input_df.loc[input_df['crispness'].idxmax()]['batch_index']
|
|
262
|
+
lowest_norm_batch = input_df.loc[input_df['mean_norm'].idxmin()]['batch_index']
|
|
263
|
+
|
|
264
|
+
for i, (index, row) in enumerate(input_df.iterrows()):
|
|
265
|
+
# Avoid indexing beyond available axes if there are more df than plots
|
|
266
|
+
if i >= len(axes):
|
|
267
|
+
break
|
|
268
|
+
ax = axes[i]
|
|
269
|
+
|
|
270
|
+
batch_idx = row['batch_index']
|
|
271
|
+
metric_path = row['metric_path']
|
|
272
|
+
with np.load(metric_path) as f:
|
|
273
|
+
flows = f['flows']
|
|
274
|
+
flow_img = np.mean(np.sqrt(flows[:, :, :, 0] ** 2 + flows[:, :, :, 1] ** 2), axis=0)
|
|
275
|
+
del flows # free up expensive array
|
|
276
|
+
flow_images.append(flow_img)
|
|
277
|
+
|
|
278
|
+
ax.imshow(flow_img, vmin=0, vmax=0.3, cmap='viridis')
|
|
279
|
+
|
|
280
|
+
title_parts = []
|
|
281
|
+
|
|
282
|
+
# Title Part 1: Item and Batch Index
|
|
283
|
+
if batch_idx == -1:
|
|
284
|
+
item_title = "Raw Data"
|
|
285
|
+
else:
|
|
286
|
+
item_title = f'Batch Index: {batch_idx}'
|
|
287
|
+
|
|
288
|
+
if batch_idx == highest_corr_batch:
|
|
289
|
+
item_title = f'Batch Index: {batch_idx} **(Highest Correlation)**'
|
|
290
|
+
title_parts.append(item_title)
|
|
291
|
+
|
|
292
|
+
mean_norm = row['mean_norm']
|
|
293
|
+
norm_title = f'ROF: {mean_norm:.2f}'
|
|
294
|
+
if batch_idx == lowest_norm_batch:
|
|
295
|
+
norm_title = f'ROF: **{mean_norm:.2f}** (Lowest Norm)'
|
|
296
|
+
title_parts.append(norm_title)
|
|
297
|
+
|
|
298
|
+
smoothness = row['crispness']
|
|
299
|
+
crisp_title = f'Crispness: {smoothness:.2f}'
|
|
300
|
+
if batch_idx == highest_crisp_batch:
|
|
301
|
+
crisp_title = f'Crispness: **{smoothness:.2f}** (Highest Crispness)'
|
|
302
|
+
title_parts.append(crisp_title)
|
|
303
|
+
|
|
304
|
+
title = '\n'.join(title_parts)
|
|
305
|
+
|
|
306
|
+
ax.set_title(
|
|
307
|
+
title,
|
|
308
|
+
fontsize=14,
|
|
309
|
+
fontweight='bold',
|
|
310
|
+
color='black',
|
|
311
|
+
loc='center'
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
ax.axis('off')
|
|
315
|
+
|
|
316
|
+
# Turn off unused axes
|
|
317
|
+
for i in range(len(input_df), len(axes)):
|
|
318
|
+
axes[i].axis('off')
|
|
319
|
+
|
|
320
|
+
cbar_ax = fig.add_axes((0.92, 0.2, 0.02, 0.6))
|
|
321
|
+
norm = mpl.colors.Normalize(vmin=0, vmax=0.3)
|
|
322
|
+
sm = mpl.cm.ScalarMappable(cmap='viridis', norm=norm)
|
|
323
|
+
sm.set_array([])
|
|
324
|
+
cbar = fig.colorbar(sm, cax=cbar_ax)
|
|
325
|
+
|
|
326
|
+
cbar.set_label('Flow Magnitude', fontsize=16, fontweight='bold')
|
|
327
|
+
plt.tight_layout(rect=(0, 0, 0.9, 1))
|
|
328
|
+
plt.show()
|
|
329
|
+
|
|
330
|
+
if save_path:
|
|
331
|
+
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
|
332
|
+
print(f"Residual flows saved to {save_path}")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def plot_residual_flows(results, num_batches=3, smooth=True, winsize=5, save_path=None):
|
|
336
|
+
"""
|
|
337
|
+
Plot the top `num_batches` residual optical flows across batches.
|
|
338
|
+
|
|
339
|
+
Parameters
|
|
340
|
+
----------
|
|
341
|
+
results : DataFrame
|
|
342
|
+
DataFrame containing 'uuid' and 'batch_index' columns.
|
|
343
|
+
num_batches : int, optional
|
|
344
|
+
Number of "best" batches to plot. Default is 3.
|
|
345
|
+
smooth : bool, optional
|
|
346
|
+
Whether to smooth the residual flows using a moving average. Default is True.
|
|
347
|
+
winsize : int, optional
|
|
348
|
+
The window size for smoothing the data. Default is 5.
|
|
349
|
+
|
|
350
|
+
Examples
|
|
351
|
+
--------
|
|
352
|
+
>>> import lbm_caiman_python as lcp
|
|
353
|
+
>>> import lbm_mc as mc
|
|
354
|
+
>>> batch_df = mc.load_batch('path/to/batch.pickle')
|
|
355
|
+
>>> metrics_files = lcp.compute_mcorr_metrics_batch(batch_df)
|
|
356
|
+
>>> metrics_df = lcp._create_df_from_metric_files(metrics_files)
|
|
357
|
+
>>> lcp.plot_residual_flows(metrics_df, num_batches=6, smooth=True, winsize=8)
|
|
358
|
+
"""
|
|
359
|
+
# Sort and filter for top batches by mean_norm, lower is better
|
|
360
|
+
results_sorted = results.sort_values(by='mean_norm')
|
|
361
|
+
top_uuids = results_sorted['uuid'].values[:num_batches]
|
|
362
|
+
results_filtered = results[results['uuid'].isin(top_uuids)]
|
|
363
|
+
|
|
364
|
+
# Identify raw data UUID
|
|
365
|
+
raw_uuid = results.loc[results['uuid'].str.contains('raw', case=False, na=False), 'uuid'].values[0]
|
|
366
|
+
best_uuid = top_uuids[0] # Best (lowest) value
|
|
367
|
+
|
|
368
|
+
fig, ax = plt.subplots(figsize=(20, 10))
|
|
369
|
+
|
|
370
|
+
colors = plt.cm.Set1(np.linspace(0, 1, num_batches)) # Standout colors for other batches
|
|
371
|
+
plotted_uuids = set() # Track plotted UUIDs to avoid duplicates
|
|
372
|
+
|
|
373
|
+
if raw_uuid in results['uuid'].values:
|
|
374
|
+
row = results.loc[results['uuid'] == raw_uuid].iloc[0]
|
|
375
|
+
metric_path = row['metric_path']
|
|
376
|
+
|
|
377
|
+
with np.load(metric_path) as metric:
|
|
378
|
+
flows = metric['flows']
|
|
379
|
+
|
|
380
|
+
residual_flows = [np.linalg.norm(flows[i] - flows[i - 1], axis=2).mean() for i in range(1, len(flows))]
|
|
381
|
+
|
|
382
|
+
if smooth:
|
|
383
|
+
residual_flows = smooth_data(residual_flows, window_size=winsize)
|
|
384
|
+
|
|
385
|
+
if raw_uuid == best_uuid:
|
|
386
|
+
ax.plot(residual_flows, color='blue', linestyle='dotted', linewidth=2.5,
|
|
387
|
+
label=f'Best (Raw)')
|
|
388
|
+
else:
|
|
389
|
+
ax.plot(residual_flows, color='red', linestyle='dotted', linewidth=2.5,
|
|
390
|
+
label=f'Raw Data')
|
|
391
|
+
|
|
392
|
+
plotted_uuids.add(raw_uuid) # Add raw UUID to avoid double plotting
|
|
393
|
+
|
|
394
|
+
for i, row in results_filtered.iterrows():
|
|
395
|
+
file_uuid = row['uuid']
|
|
396
|
+
batch_idx = row['batch_index']
|
|
397
|
+
|
|
398
|
+
if file_uuid in plotted_uuids:
|
|
399
|
+
continue
|
|
400
|
+
|
|
401
|
+
metric_path = row['metric_path']
|
|
402
|
+
|
|
403
|
+
with np.load(metric_path) as metric:
|
|
404
|
+
flows = metric['flows']
|
|
405
|
+
|
|
406
|
+
residual_flows = [np.linalg.norm(flows[i] - flows[i - 1], axis=2).mean() for i in range(1, len(flows))]
|
|
407
|
+
|
|
408
|
+
if smooth:
|
|
409
|
+
residual_flows = smooth_data(residual_flows, window_size=winsize)
|
|
410
|
+
|
|
411
|
+
if file_uuid == best_uuid:
|
|
412
|
+
ax.plot(residual_flows, color='blue', linestyle='solid', linewidth=2.5,
|
|
413
|
+
label=f'Best Value | Batch Row Index: {batch_idx}')
|
|
414
|
+
else:
|
|
415
|
+
color_idx = list(top_uuids).index(file_uuid) if file_uuid in top_uuids else len(plotted_uuids) - 1
|
|
416
|
+
ax.plot(residual_flows, color=colors[color_idx], linestyle='solid', linewidth=1.5,
|
|
417
|
+
label=f'Batch Row Index: {batch_idx}')
|
|
418
|
+
|
|
419
|
+
plotted_uuids.add(file_uuid)
|
|
420
|
+
|
|
421
|
+
ax.set_xlabel('Frames (downsampled)', fontsize=12, fontweight='bold')
|
|
422
|
+
|
|
423
|
+
# Make X tick labels bold
|
|
424
|
+
ax.set_xticklabels([int(x) for x in ax.get_xticks()], fontweight='bold')
|
|
425
|
+
|
|
426
|
+
# Make Y tick labels bold
|
|
427
|
+
ax.set_yticklabels(np.round(ax.get_yticks(), 2), fontweight='bold')
|
|
428
|
+
|
|
429
|
+
ax.set_ylabel('Residual Optical Flow (ROF)', fontsize=12, fontweight='bold')
|
|
430
|
+
ax.set_title(f'Batches with Lowest Residual Optical Flow', fontsize=16, fontweight='bold')
|
|
431
|
+
ax.legend(loc='best', fontsize=12, title='Figure Key', title_fontsize=12, prop={'weight': 'bold'})
|
|
432
|
+
plt.tight_layout()
|
|
433
|
+
plt.show()
|
|
434
|
+
if save_path:
|
|
435
|
+
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
|
436
|
+
print(f"Residual flows saved to {save_path}")
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def plot_correlations(results, num_batches=3, smooth=True, winsize=5, save_path=None):
|
|
440
|
+
"""
|
|
441
|
+
Plot the top `num_batches` batches with the highest correlation coefficients.
|
|
442
|
+
|
|
443
|
+
Parameters
|
|
444
|
+
----------
|
|
445
|
+
results : DataFrame
|
|
446
|
+
DataFrame containing 'uuid' and 'batch_index' columns.
|
|
447
|
+
num_batches : int, optional
|
|
448
|
+
Number of "best" batches to plot. Default is 3.
|
|
449
|
+
smooth : bool, optional
|
|
450
|
+
Whether to smooth the correlation data using a moving average. Default is True.
|
|
451
|
+
winsize : int, optional
|
|
452
|
+
The window size for smoothing the data. Default is 5.
|
|
453
|
+
"""
|
|
454
|
+
results_sorted = results.sort_values(by='mean_corr', ascending=False)
|
|
455
|
+
top_uuids = results_sorted['uuid'].values[:num_batches]
|
|
456
|
+
results_filtered = results[results['uuid'].isin(top_uuids)]
|
|
457
|
+
|
|
458
|
+
raw_uuid = results.loc[results['uuid'].str.contains('raw', case=False, na=False), 'uuid'].values[0]
|
|
459
|
+
best_uuid = top_uuids[0]
|
|
460
|
+
|
|
461
|
+
fig, ax = plt.subplots(figsize=(20, 10))
|
|
462
|
+
|
|
463
|
+
colors = plt.cm.Set1(np.linspace(0, 1, num_batches))
|
|
464
|
+
plotted_uuids = set()
|
|
465
|
+
|
|
466
|
+
if raw_uuid in results['uuid'].values:
|
|
467
|
+
row = results.loc[results['uuid'] == raw_uuid].iloc[0]
|
|
468
|
+
metric_path = row['metric_path']
|
|
469
|
+
|
|
470
|
+
with np.load(metric_path) as metric:
|
|
471
|
+
correlations = metric['correlations']
|
|
472
|
+
|
|
473
|
+
if smooth:
|
|
474
|
+
correlations = smooth_data(correlations, window_size=winsize)
|
|
475
|
+
|
|
476
|
+
if raw_uuid == best_uuid:
|
|
477
|
+
ax.plot(correlations, color='blue', linestyle='dotted', linewidth=2.5,
|
|
478
|
+
label=f'Best (Raw)')
|
|
479
|
+
else:
|
|
480
|
+
ax.plot(correlations, color='red', linestyle='dotted', linewidth=2.5,
|
|
481
|
+
label=f'Raw Data')
|
|
482
|
+
|
|
483
|
+
plotted_uuids.add(raw_uuid)
|
|
484
|
+
|
|
485
|
+
for i, row in results_filtered.iterrows():
|
|
486
|
+
file_uuid = row['uuid']
|
|
487
|
+
batch_idx = row['batch_index']
|
|
488
|
+
|
|
489
|
+
if file_uuid in plotted_uuids:
|
|
490
|
+
continue
|
|
491
|
+
|
|
492
|
+
metric_path = row['metric_path']
|
|
493
|
+
|
|
494
|
+
with np.load(metric_path) as metric:
|
|
495
|
+
correlations = metric['correlations']
|
|
496
|
+
|
|
497
|
+
if smooth:
|
|
498
|
+
correlations = smooth_data(correlations, window_size=winsize)
|
|
499
|
+
|
|
500
|
+
if file_uuid == best_uuid:
|
|
501
|
+
ax.plot(correlations, color='blue', linestyle='solid', linewidth=2.5,
|
|
502
|
+
label=f'Best Value | Batch Row Index: {batch_idx}')
|
|
503
|
+
else:
|
|
504
|
+
color_idx = list(top_uuids).index(file_uuid) if file_uuid in top_uuids else len(plotted_uuids) - 1
|
|
505
|
+
ax.plot(correlations, color=colors[color_idx], linestyle='solid', linewidth=1.5,
|
|
506
|
+
label=f'Batch Row Index: {batch_idx}')
|
|
507
|
+
|
|
508
|
+
plotted_uuids.add(file_uuid)
|
|
509
|
+
|
|
510
|
+
ax.set_xlabel('Frame Index (Downsampled)', fontsize=12, fontweight='bold')
|
|
511
|
+
|
|
512
|
+
ax.set_xticklabels([int(x) for x in ax.get_xticks()], fontweight='bold')
|
|
513
|
+
ax.set_yticklabels(np.round(ax.get_yticks(), 2), fontweight='bold')
|
|
514
|
+
ax.set_ylabel('Correlation Coefficient (r)', fontsize=12, fontweight='bold')
|
|
515
|
+
ax.set_title(f'Batches with Highest Correlation', fontsize=16, fontweight='bold')
|
|
516
|
+
ax.legend(loc='best', fontsize=12, title='Figure Key', title_fontsize=12, prop={'weight': 'bold'})
|
|
517
|
+
plt.tight_layout()
|
|
518
|
+
plt.show()
|
|
519
|
+
|
|
520
|
+
if save_path:
|
|
521
|
+
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
|
522
|
+
print(f"Residual flows saved to {save_path}")
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lbm_caiman_python
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Light Beads Microscopy Pipeline using CaImAn
|
|
5
|
+
License-Expression: BSD-3-Clause
|
|
6
|
+
Project-URL: Homepage, https://github.com/MillerBrainObservatory/LBM-CaImAn-Python
|
|
7
|
+
Project-URL: Documentation, https://millerbrainobservatory.github.io/LBM-CaImAn-Python
|
|
8
|
+
Project-URL: Repository, https://github.com/MillerBrainObservatory/LBM-CaImAn-Python
|
|
9
|
+
Project-URL: Issues, https://github.com/MillerBrainObservatory/LBM-CaImAn-Python/issues
|
|
10
|
+
Keywords: Pipeline,Numpy,Microscopy,ScanImage,CaImAn,tiff,calcium imaging
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Requires-Python: <3.12.10,>=3.12.7
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE.md
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: scipy>=1.10
|
|
19
|
+
Requires-Dist: scikit-learn
|
|
20
|
+
Requires-Dist: scikit-image
|
|
21
|
+
Requires-Dist: joblib
|
|
22
|
+
Requires-Dist: tqdm
|
|
23
|
+
Requires-Dist: matplotlib
|
|
24
|
+
Requires-Dist: tifffile
|
|
25
|
+
Requires-Dist: h5py
|
|
26
|
+
Requires-Dist: zarr
|
|
27
|
+
Requires-Dist: dask
|
|
28
|
+
Requires-Dist: pandas
|
|
29
|
+
Requires-Dist: mbo_utilities>=3.1.0
|
|
30
|
+
Requires-Dist: lbm_suite2p_python
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# LBM-CaImAn-Python
|
|
34
|
+
|
|
35
|
+
[**Installation**](https://github.com/MillerBrainObservatory/LBM-CaImAn-Python#installation) | [**Notebooks**](https://github.com/MillerBrainObservatory/LBM-CaImAn-Python/tree/master/demos/notebooks)
|
|
36
|
+
|
|
37
|
+
[](https://millerbrainobservatory.github.io/LBM-CaImAn-Python/)
|
|
38
|
+
|
|
39
|
+
Python implementation of the Light Beads Microscopy (LBM) computational pipeline. The documentation has examples of the rendered notebooks.
|
|
40
|
+
|
|
41
|
+
For the `MATLAB` implementation, see [here](https://github.com/MillerBrainObservatory/LBM-CaImAn-MATLAB/)
|
|
42
|
+
|
|
43
|
+
## Pipeline Steps:
|
|
44
|
+
|
|
45
|
+
1. Image Assembly
|
|
46
|
+
- Extract raw `tiffs` to planar timeseries
|
|
47
|
+
2. Motion Correction
|
|
48
|
+
- Rigid/Non-rigid registration
|
|
49
|
+
3. Segmentation
|
|
50
|
+
- Iterative CNMF segmentation
|
|
51
|
+
- Deconvolution
|
|
52
|
+
- Refine neuron selection
|
|
53
|
+
4. Collation
|
|
54
|
+
- Collate images and metadata into a single volume
|
|
55
|
+
- Lateral offset correction (between z-planes. WIP)
|
|
56
|
+
|
|
57
|
+
## Requirements
|
|
58
|
+
|
|
59
|
+
- caiman
|
|
60
|
+
- numpy
|
|
61
|
+
- scipy
|
|
62
|
+
- fastplotlib
|
|
63
|
+
|
|
64
|
+
:exclamation: **Note:** This package makes heavy use of fastplotlib for visualizations.
|
|
65
|
+
|
|
66
|
+
fastplotlib runs on [Jupyter Lab](https://jupyterlab.readthedocs.io/en/stable/getting_started/installation.html),
|
|
67
|
+
but is not guarenteed to work with Jupyter Notebook or Visual Studio Code notebook environments.
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
Install [pixi](https://pixi.sh) (`pip install pixi` or see https://pixi.sh for other methods), then:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git clone https://github.com/MillerBrainObservatory/LBM-CaImAn-Python.git
|
|
75
|
+
cd LBM-CaImAn-Python
|
|
76
|
+
pixi install
|
|
77
|
+
pixi run setup-caiman
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
This installs CaImAn from conda-forge along with all dependencies and the project itself in editable mode.
|
|
81
|
+
|
|
82
|
+
To verify:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pixi run python -c "import lbm_caiman_python as lcp; print(lcp.__version__)"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
:exclamation: **Hardware requirements** The large CNMF visualizations with contours etc. usually require either a dedicated GPU or integrated GPU with access to at least 1GB of VRAM.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Troubleshooting
|
|
93
|
+
|
|
94
|
+
### Error during pip install: OSError: [Errno 2] No such file or directory
|
|
95
|
+
|
|
96
|
+
If you recieve an error during pip installation with the hint:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
|
|
100
|
+
HINT: This error might have occurred since this system does not have Windows Long Path support enabled. You can find
|
|
101
|
+
information on how to enable this at https://pip.pypa.io/warnings/enable-long-paths
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
In Windows Powershell, as Administrator:
|
|
106
|
+
|
|
107
|
+
`New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\FileSystem" -Name "LongPathsEnabled" -Value 1 -PropertyType DWORD -Force`
|
|
108
|
+
|
|
109
|
+
Or:
|
|
110
|
+
|
|
111
|
+
- Open Group Policy Editor (Press Windows Key and type gpedit.msc and hit Enter key.
|
|
112
|
+
|
|
113
|
+
- Navigate to the following directory:
|
|
114
|
+
|
|
115
|
+
`Local Computer Policy > Computer Configuration > Administrative Templates > System > Filesystem > NTFS.`
|
|
116
|
+
|
|
117
|
+
- Click Enable NTFS long paths option and enable it.
|
|
118
|
+
|
|
119
|
+
### Conda Slow / Stalling
|
|
120
|
+
|
|
121
|
+
if conda is behaving slow, clean the conda installation and update `conda-forge`:
|
|
122
|
+
|
|
123
|
+
``` bash
|
|
124
|
+
|
|
125
|
+
conda clean -a
|
|
126
|
+
|
|
127
|
+
conda update -c conda-forge --all
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### virtualenv Troubleshooting
|
|
132
|
+
|
|
133
|
+
#### Error During `pip install .` (CaImAn) on Linux
|
|
134
|
+
If you encounter errors during the installation of `CaImAn`, install the necessary development tools:
|
|
135
|
+
```bash
|
|
136
|
+
sudo apt-get install python3-dev
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Don't forget to press enter a few times if conda is taking a long time.
|
|
140
|
+
|
|
141
|
+
### Recommended Conda Distribution
|
|
142
|
+
|
|
143
|
+
The recommended conda installer is
|
|
144
|
+
|
|
145
|
+
This is a community-driven `conda`/`mamba` installer with pre-configured packages specific to [conda-forge](https://conda-forge.org/).
|
|
146
|
+
|
|
147
|
+
This helps avoid `conda-channel` conflicts and avoids any issues with the Anaconda TOS.
|
|
148
|
+
|
|
149
|
+
You can install the installer from a unix command line:
|
|
150
|
+
|
|
151
|
+
``` bash
|
|
152
|
+
curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh"
|
|
153
|
+
bash Miniforge3-$(uname)-$(uname -m).sh
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Or download the installer for your operating system [here](https://github.com/conda-forge/miniforge/releases).
|
|
157
|
+
|
|
158
|
+
### Graphics Driver Issues
|
|
159
|
+
|
|
160
|
+
If you are attempting to use fastplotlib and receive errors about graphics drivers, see the [fastplotlib driver documentation](https://github.com/fastplotlib/fastplotlib?tab=readme-ov-file#gpu-drivers-and-requirements).
|
|
161
|
+
|