Multi-Cell Manual ROI Selection in Napari
Authors
Setup
Import Libraries
import tifffile as tiff
import numpy as np
import matplotlib.pyplot as plt
import napariDownload Data
import owncloud
import os
if not os.path.exists('data'):
print('Creating directory for data')
os.mkdir('data')
if not os.path.exists('data/cell1.tif'):
oc = owncloud.Client.from_public_link('https://uni-bonn.sciebo.de/s/RRU6CghB2O1tCFs')
oc.get_file('/', 'data/cell1.tif');
if not os.path.exists('data/cell2.tif'):
oc = owncloud.Client.from_public_link('https://uni-bonn.sciebo.de/s/6V9seZuhhJpul1y')
oc.get_file('/', 'data/cell2.tif');
if not os.path.exists('data/cell3.tif'):
oc = owncloud.Client.from_public_link('https://uni-bonn.sciebo.de/s/NWfGwHzF0W34sRY')
oc.get_file('/', 'data/cell3.tif');
if not os.path.exists('data/multi_cell.tif'):
oc = owncloud.Client.from_public_link('https://uni-bonn.sciebo.de/s/FB21ECmeJgmfUqQ')
oc.get_file('/', 'data/multi_cell.tif');Creating directory for datamovie = tiff.imread('data/multi_cell.tif')
cell1_data = tiff.imread('data/cell1.tif')
cell2_data = tiff.imread('data/cell2.tif')
cell3_data = tiff.imread('data/cell3.tif')Understanding calcium imaging data, where changes in fluorescence intensity serve as proxies for neural activity, is a key skill in modern neuroscience. In this notebook we walk through the process of manually selecting regions of interest (ROIs) from calcium imaging recordings using Napari, a powerful tool for interactive image analysis. Step by step we learn how to load image stacks, label individual neurons, and extract fluorescence traces that represent their activity over time.
Section 1: Loading Movies and Images on Napari
In this section, we will learn how to load images and movies of calcium imaging data on Napari
| Code | Description |
|---|---|
napari.Viewer() |
Create a Napari viewer for visualizing multi-dimensional data, such as images or time series. |
viewer.close() |
Close the Napari viewer window. |
viewer.add_image(cell1_data, name='Cell 1') |
Add image data (cell1_data) to the Napari viewer with the name 'Cell 1'. |
cell1_data[:10] |
Access the first 10 frames (or data points) from cell1_data. |
cell1_data[100:110] |
Access frames 100 to 109 from cell1_data. |
Exercises
Example: Load and play frames of Cell 1. Close the viewer once you are done.
viewer = napari.Viewer()
viewer.add_image(cell1_data, name='Cell 1')viewer.close()Exercise: Load and play the first 100 frames of Cell 1. Close the viewer once you are done.
Solution
viewer = napari.Viewer()
viewer.add_image(cell1_data[:100], name='Cell 1')viewer.close()Exercise: Load and play 10 frames from 1000th frame of Cell 1. Close the viewer once you are done.
Solution
viewer = napari.Viewer()
viewer.add_image(cell1_data[1000:1010], name='Cell 1')viewer.close()Section 2: Labelling in napari
Labeling in Napari refers to the process of marking regions of interest (ROIs) directly on an image using a Labels layer. This is commonly used to identify structures such as individual neurons in calcium imaging data.
| Code | Description |
|---|---|
cell1_data.mean(axis=0) |
Compute the mean of cell1_data across frames (axis=0 corresponds to frames). |
cell1_data.max(axis=0) |
Compute the maximum value of cell1_data across frames (axis=0 corresponds to frames). |
cell1_data.std(axis=0) |
Compute the standard deviation of cell1_data across frames (axis=0 corresponds to frames). |
viewer.layers['roi'].data |
Access the data from the roi layer in the Napari viewer. |
mask = (rois == 1) |
Create a binary mask where the rois values equal 1. |
plt.contour(mask, colors='r') |
Plot the contour of the binary mask in red, highlighting the region of interest. |
Let us label single neurons in Napari using the brush tool.
Exercises
viewer = napari.Viewer()Example: Open mean projection of cell1
mean_proj = cell1_data.mean(axis=0)
viewer.add_image(mean_proj, name="Mean", colormap="gray")Exercise: Add max projection of Cell1 to an existing napari viewer
Solution
max_proj = cell1_data.max(axis=0)
viewer.add_image(max_proj, name="Max", colormap="gray")Exercise: Add std projection of Cell1 to an existing napari viewer
Solution
std_proj = cell1_data.std(axis=0)
viewer.add_image(std_proj, name="Std", colormap="gray")Create a cell mask for Cell 1
- Click on New Labels Layer and rename it as
roi. - Click the brush icon or press
Bto select the brush tool. - Carefully paint over the cell body of the selected neuron.
- OR: Use the paint bucket tool to fill enclosed regions.
We can access the layers from the jupyter notebook directly without having to first export it to another file.
Example: Access roi and plot it in the notebook.
roi = viewer.layers['roi'].data
plt.imshow(roi, cmap='Reds');Exercise: Add/remove few pixels in napari from the roi layer. Access roi and plot here.
Solution
roi = viewer.layers['roi'].data
plt.imshow(roi, cmap='Reds');Close viewer
viewer.close()Napari can handle multiple neurons in a single Labels layer by treating neurons painted in different colors as a different neuron. In a Labels layer, each integer value corresponds to a separate labeled region. For example, pixels with value 1 may represent one neuron, 2 another neuron, and so on. The background is typically labeled with 0.
The steps to identify multiple neurons in a single layer is below.
- Create a new labels layer.
- Select the brush tool: Press B to switch to the brush tool, or click the brush icon in the left toolbar.
- Set the brush size to small size:
- Paint a rough outline of a neuron.
- Carefully paint over the neuron body.
- Move to the next label index:
- Press M to increment the label index.
- This will automatically update the label color, so the next neuron will appear in a new color.
- Repeat steps 2–6 for each neuron:
- For each new neuron:
- Paint its contour.
- Use the fill tool to create a clean mask.
- Press M to move to the next label index.
- For each new neuron:
- Review all labeled ROIs: Zoom out and visually inspect the full image.
viewer = napari.Viewer()
viewer.add_image(movie.std(axis=0))Exercise: Label multiple neurons in the summary image added in napari in a layer named rois.
Solution
rois = viewer.layers['rois'].dataExample: Access the first neuron and plot it here.
mask = (rois == 1)
plt.imshow(movie.std(axis=0), cmap='gray')
plt.contour(mask, colors='r')Exercise: Access second neuron and plot it here.
Solution
mask = (rois == 2)
plt.imshow(movie.std(axis=0), cmap='gray')
plt.contour(mask, colors='r')Exercise: Access the zeroth position and plot it here.
Solution
mask = (rois == 0)
plt.imshow(movie.std(axis=0), cmap='gray')
plt.contour(mask, colors='r')Section 3: Isolating ROI Pixels
| Code | Description |
|---|---|
movie[:, mask].mean(axis=1) |
Compute the mean across frames for the pixels defined by the mask (axis=1 corresponds to frames). |
n_frames = movie.shape[0] |
Get the number of frames in the movie (first dimension of the shape). |
np.zeros((2, n_frames)) |
Create an array of zeros with shape (2, n_frames) to represent two rows of zeros, one for each frame. |
np.unique(rois) |
Get the unique values in the rois array, representing different regions of interest (ROIs). |
len(np.unique(rois)) |
Get the number of unique ROIs by counting the unique values in rois. |
To extract a trace for a given ROI, we must first identify which pixels it occupies in the field of view. This section focuses on converting labelled ROI data into binary masks—arrays that define the spatial footprint of each individual neuron. Using these masks, we can isolate and examine raw pixel values belonging to each ROI.
With this mask, we can easily extract the calcium trace from a specific neuron with
mask = rois == 1 # Create a boolean mask where pixels belonging to ROI 1 are True
movie[:, mask] # Select only the ROI pixels at each time point ([frames, num_pixels_in_roi])
.mean(axis=1) # Compute the average fluorescence across ROI pixels for each frame)Exercises
Example: Plot calcium trace from 1st neuron.
mask = rois == 1
cal_trace = movie[:, mask].mean(axis=1)
plt.plot(cal_trace)Exercise: Plot calcium trace from 2nd neuron.
Solution
mask = rois == 2
cal_trace = movie[:, mask].mean(axis=1)
plt.plot(cal_trace)Exercise: Plot calcium trace from 3rd neuron.
Solution
mask = rois == 3
cal_trace = movie[:, mask].mean(axis=1)
plt.plot(cal_trace)Section 4: For-Loops for Storing Multiple Calcium Traces
We usually have several neurons imaged simulataneously and they are all typically stored in a single matrix such that each row of that matrix represents calcium trace of a single neuron. In this section, we restructure the calcium traces extracted from our data into that format.
Exercises
Example: Store all frames for the first two neurons in single array.
n_frames = movie.shape[0]
traces = np.zeros((2, n_frames))
traces[0] = movie[:, rois == 1].mean(axis=1)
traces[1] = movie[:, rois == 2].mean(axis=1)
plt.plot(traces[0])
plt.plot(traces[1])Exercise: Store all frames for the first three neurons in single array.
Solution
n_frames = movie.shape[0]
traces = np.zeros((3, n_frames))
traces[0] = movie[:, rois == 1].mean(axis=1)
traces[1] = movie[:, rois == 2].mean(axis=1)
traces[2] = movie[:, rois == 3].mean(axis=1)
plt.plot(traces[0])
plt.plot(traces[1])
plt.plot(traces[2])Exercise: Store all frames for the first four neurons in single array.
Solution
n_frames = movie.shape[0]
traces = np.zeros((4, n_frames))
traces[0] = movie[:, rois == 1].mean(axis=1)
traces[1] = movie[:, rois == 2].mean(axis=1)
traces[2] = movie[:, rois == 3].mean(axis=1)
traces[3] = movie[:, rois == 4].mean(axis=1)
plt.plot(traces[0])
plt.plot(traces[1])
plt.plot(traces[2])
plt.plot(traces[3])It can be tedious to do this manually especially when we have many number of neurons. We can use for-loop to do the same for multiple rois by
traces = np.zeros((2, 2000)) # Create an empty array to store calcium traces for two ROIs (2 ROIs, 2000 frames)
for roi_num in range(1, 3): # Loop through each ROI (1 and 2)
mask = rois == roi_num # Create a boolean mask for the current ROI
trace = movie[:, mask].mean(axis=1) # average fluorescence for the current ROI across all pixels at each time point
traces[roi_num - 1] = trace # Store the calcium trace for the current ROI in the 'traces' arrayExample: Use for loop to fill first two rois in their respective locations in traces.
traces = np.zeros((2, 2000))
for roi_num in range(1, 3):
mask = rois == roi_num
trace = movie[:, mask].mean(axis=1)
traces[roi_num - 1] = trace
plt.plot(traces[0])
plt.plot(traces[1])Exercise: Use for loop to fill first three rois in their respective locations in traces.
Solution
traces = np.zeros((3, 2000))
for roi_num in range(1, 4):
mask = rois == roi_num
trace = movie[:, mask].mean(axis=1)
traces[roi_num - 1] = trace
plt.plot(traces[0])
plt.plot(traces[1])
plt.plot(traces[2])Exercise: Use for loop to fill all rois in their respective locations in traces.
Solution
np.unique(rois)roi_nums = len(np.unique(rois))
traces = np.zeros((roi_nums, 2000))
for roi_num in range(1, roi_nums):
mask = rois == roi_num
trace = movie[:, mask].mean(axis=1)
traces[roi_num - 1] = trace
for i in range(len(traces)):
plt.plot(traces[i], color='gray');Section 5: Interpreting and Visualizing Extracted Traces
This section introduces simple plotting tools to inspect both individual traces and full populations of neurons. Visualization helps verify the success of the extraction process, and also offers a first glimpse into the temporal structure of the data.
Exercises
Example: Plot location and trace of neuron 1.
roi_num = 1
summary_image = movie.std(axis=0)
mask = rois == roi_num
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].imshow(summary_image, cmap='gray')
axes[0].contour(mask, colors='red', linewidths=1)
axes[1].plot(traces[roi_num-1])
plt.tight_layout()Exercise: Plot location and trace of neuron 2.
Solution
roi_num = 2
summary_image = movie.std(axis=0)
mask = rois == roi_num
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].imshow(summary_image, cmap='gray')
axes[0].contour(mask, colors='red', linewidths=1)
axes[1].plot(traces[roi_num-1])
plt.tight_layout()Exercise: Plot location and trace of neuron 3.
Solution
roi_num = 3
summary_image = movie.std(axis=0)
mask = rois == roi_num
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].imshow(summary_image, cmap='gray')
axes[0].contour(mask, colors='red', linewidths=1)
axes[1].plot(traces[roi_num-1])
plt.tight_layout()Interactive plot
%matplotlib inline
from ipywidgets import interact, IntSlider
import ipywidgets as widgets
roi_nums = np.unique(rois)[1:]
summary_image = movie.std(axis=0)
def plot_neuron_trace(roi_idx):
mask = rois == roi_idx
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].imshow(summary_image, cmap='gray')
axes[0].contour(mask, colors='red', linewidths=1)
axes[0].set_title(f"ROI {roi_idx} Location")
axes[0].axis("off")
axes[1].plot(traces[roi_idx - 1])
axes[1].set_title(f"Fluorescence Trace (ROI {roi_idx})")
axes[1].set_xlabel("Frame")
axes[1].set_ylabel("Fluorescence")
plt.tight_layout()
plt.show()
interact(plot_neuron_trace, roi_idx=IntSlider(min=1, max=len(roi_nums), step=1, value=0));Section 6: Load Numpy Arrays as ROIs on Napari
- File -> Open File(s)
- Choose rois.npy file
- Right click on the new layer and choose
Convert to Labels