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.
@@ -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
+ [![Documentation](https://img.shields.io/badge/Documentation-black?style=for-the-badge&logo=readthedocs&logoColor=white)](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
+