Napari is a fast, interactive, multi-dimensional image viewer for Python. It’s designed for browsing, annotating, and analyzing large multi-dimensional images including calcium imaging data, microscopy stacks, and other scientific imaging data.
Why Napari?
- Fast Rendering: Handles large image stacks smoothly
- N-Dimensional: Works with 2D, 3D, 4D+ data
- Interactive: Real-time manipulation and annotation
- Layer-Based: Overlay multiple data types
- Plugin Ecosystem: Extensible functionality
- Python Integration: Seamless workflow with NumPy/scikit-image
Basic Usage
Opening Images
import napari
import numpy as np
from skimage import data
# Start viewer
viewer = napari.Viewer()
# Add 2D image
image = data.camera()
viewer.add_image(image, name='camera')
# Add image stack (time series)
stack = np.random.rand(10, 512, 512) # (time, height, width)
viewer.add_image(stack, name='time series')
# Run viewer
napari.run()
Quick View
import napari
# Quick view (single line)
napari.view_image(image, name='My Image')
# Or with stack
napari.view_image(stack, name='Time Series')
Working with Calcium Imaging
Load and View Recording
import napari
import tifffile
# Load calcium imaging stack
stack = tifffile.imread('calcium_recording.tif')
# Shape: (frames, height, width)
# View with Napari
viewer = napari.view_image(
stack,
name='Ca²⁺ Recording',
colormap='green',
contrast_limits=[100, 400]
)
napari.run()
View with ROIs
import napari
import numpy as np
import tifffile
# Load data
stack = tifffile.imread('recording.tif')
# Load ROI masks
roi_masks = np.load('rois.npy') # Binary masks
# Create viewer
viewer = napari.Viewer()
# Add image
viewer.add_image(
stack,
name='Recording',
colormap='gray',
blending='additive'
)
# Add ROIs as labels
viewer.add_labels(
roi_masks,
name='ROIs',
opacity=0.5
)
napari.run()
Layer Types
Image Layer
# Grayscale
viewer.add_image(data, colormap='gray')
# With properties
viewer.add_image(
data,
name='My Image',
colormap='viridis',
contrast_limits=[0, 100],
blending='additive',
opacity=0.7
)
Labels Layer (Segmentation)
# Integer labels
labels = np.zeros((512, 512), dtype=int)
labels[100:150, 100:150] = 1 # ROI 1
labels[200:250, 200:250] = 2 # ROI 2
viewer.add_labels(labels, name='ROIs')
Points Layer
# Mark specific locations
points = np.array([
[100, 120],
[200, 85],
[150, 200]
])
viewer.add_points(
points,
name='Cell Centers',
size=10,
face_color='red'
)
Shapes Layer
# Draw regions
shapes_data = [
np.array([[100, 100], [100, 200], [200, 200], [200, 100]]), # Rectangle
]
viewer.add_shapes(
shapes_data,
shape_type='polygon',
edge_color='red',
face_color='transparent',
edge_width=2,
name='Annotations'
)
Interactive ROI Selection
Manual ROI Drawing
import napari
import tifffile
import numpy as np
# Load image
mean_img = tifffile.imread('mean_projection.tif')
# Create viewer
viewer = napari.Viewer()
viewer.add_image(mean_img, name='Mean Image', colormap='gray')
# Add shapes layer for drawing
shapes_layer = viewer.add_shapes(
name='ROIs',
shape_type='polygon',
edge_color='red',
face_color=[1, 0, 0, 0.3] # Semi-transparent red
)
print("Draw ROIs using the polygon tool")
print("Press 'P' to activate polygon tool")
print("Click to add points, double-click to close polygon")
napari.run()
# After closing, get drawn ROIs
roi_shapes = shapes_layer.data
print(f"Drew {len(roi_shapes)} ROIs")
Convert Shapes to Masks
from skimage import draw
# Get image dimensions
height, width = mean_img.shape
# Create mask for each ROI
masks = []
for roi in shapes_layer.data:
mask = np.zeros((height, width), dtype=bool)
# Get polygon coordinates
rr, cc = draw.polygon(roi[:, 0], roi[:, 1], shape=(height, width))
mask[rr, cc] = True
masks.append(mask)
# Stack masks
roi_masks = np.array(masks)
print(f"Created {len(masks)} masks")
# Save
np.save('manual_rois.npy', roi_masks)
Multi-Channel Imaging
View Multiple Channels
import napari
# Load channels
green_channel = tifffile.imread('green_channel.tif') # GCaMP
red_channel = tifffile.imread('red_channel.tif') # Anatomical
# Create viewer
viewer = napari.Viewer()
# Add channels with different colors
viewer.add_image(
green_channel,
name='GCaMP',
colormap='green',
blending='additive'
)
viewer.add_image(
red_channel,
name='Anatomical',
colormap='red',
blending='additive',
opacity=0.5
)
napari.run()
Time Series Navigation
Navigate Frames
# Load time series
stack = tifffile.imread('timeseries.tif') # (time, height, width)
viewer = napari.view_image(
stack,
name='Time Series',
colormap='viridis'
)
# Use slider to navigate time
# Keyboard shortcuts:
# - Left/Right arrows: previous/next frame
# - Alt+Left/Right: jump 10 frames
napari.run()
Visualization Techniques
Side-by-Side Comparison
import napari
viewer = napari.Viewer()
# Add grid arrangement
viewer.add_image(raw_data, name='Raw')
viewer.add_image(processed_data, name='Processed')
# Set grid mode
viewer.grid.enabled = True
viewer.grid.shape = (1, 2) # 1 row, 2 columns
napari.run()
Overlay Segmentation
import napari
from skimage import segmentation
# Segment image
labels = segmentation.watershed(...)
viewer = napari.Viewer()
# Original image
viewer.add_image(image, name='Original', colormap='gray')
# Overlay labels
viewer.add_labels(
labels,
name='Segmentation',
opacity=0.5
)
napari.run()
Programmatic Interaction
Update Layers
import napari
import numpy as np
viewer = napari.Viewer()
# Add initial image
image_layer = viewer.add_image(np.random.rand(512, 512))
# Update data
new_data = np.random.rand(512, 512)
image_layer.data = new_data
# Update properties
image_layer.colormap = 'viridis'
image_layer.contrast_limits = [0, 0.5]
Add Callbacks
@viewer.bind_key('s')
def save_screenshot(viewer):
"""Press 's' to save screenshot."""
screenshot = viewer.screenshot()
import matplotlib.pyplot as plt
plt.imsave('screenshot.png', screenshot)
print("Screenshot saved!")
Practical Workflows
Quality Check Pipeline
import napari
import tifffile
from pathlib import Path
def inspect_recordings(data_dir):
"""Visually inspect all recordings in directory."""
data_path = Path(data_dir)
viewer = napari.Viewer()
# Load all TIFF files
for tiff_file in sorted(data_path.glob('*.tif')):
stack = tifffile.imread(tiff_file)
viewer.add_image(stack, name=tiff_file.stem)
napari.run()
# Usage
inspect_recordings('data/recordings')
ROI Refinement
import napari
import numpy as np
# Load automated ROI detection results
auto_rois = np.load('auto_rois.npy')
mean_image = np.load('mean_image.npy')
viewer = napari.Viewer()
# Show mean image
viewer.add_image(mean_image, name='Mean', colormap='gray')
# Show automated ROIs
viewer.add_labels(auto_rois, name='Auto ROIs')
# Add layer for manual corrections
manual_layer = viewer.add_labels(
np.zeros_like(auto_rois),
name='Manual Corrections'
)
print("Edit ROIs:")
print("- Brush tool (1): paint new regions")
print("- Eraser tool (2): remove regions")
print("- Fill tool (3): fill regions")
napari.run()
# Save refined ROIs
refined_rois = manual_layer.data
np.save('refined_rois.npy', refined_rois)
Plugins
Install Plugins
# Install plugin manager
pip install napari[all]
# Install specific plugins
pip install napari-animation # Create animations
pip install napari-segment-blobs-and-things-with-membranes
Use Plugins
import napari
viewer = napari.Viewer()
viewer.add_image(data)
# Access plugins through menu
# Plugins → napari-animation → Wizard
Advanced Features
3D Rendering
# View 3D data
stack_3d = np.random.rand(50, 512, 512) # (z, y, x)
viewer = napari.view_image(
stack_3d,
name='3D Stack',
rendering='mip' # Maximum intensity projection
)
# Switch to 3D mode
viewer.dims.ndisplay = 3
napari.run()
Headless Mode (No GUI)
import napari
# Create viewer without showing
viewer = napari.Viewer(show=False)
# Add layers
viewer.add_image(data)
# Take screenshot
screenshot = viewer.screenshot()
# Close
viewer.close()
Keyboard Shortcuts
- Navigation: Arrow keys move through dimensions
- Zoom: Ctrl/Cmd + scroll
- Pan: Click and drag (or middle mouse)
- Toggle Layer: Click eye icon
- Full Screen: F
- Screenshot: Alt + S
- Grid View: Ctrl/Cmd + G
Installation
# Basic installation
pip install "napari[all]"
# Or with pixi
pixi add "napari[all]"
# Minimal (no Qt)
pip install napari
# With PyQt5
pip install "napari[pyqt5]"
Best Practices
- Start with default contrast limits, adjust as needed
- Use blending=‘additive’ for multi-channel images
- Save screenshots for figures and presentations
- Use labels layers for segmentation masks
- Leverage keyboard shortcuts for efficiency
- Use grid view for comparisons
- Install relevant plugins for specific tasks
Performance Tips
- Use appropriate data types (uint8, uint16 instead of float64)
- Downsample very large images
- Use multiscale for pyramidal viewing
- Close unused layers
- Limit number of visible layers
Resources
- Documentation: https://napari.org/
- Tutorials: https://napari.org/stable/tutorials/
- Plugin Directory: https://napari-hub.org/
- GitHub: https://github.com/napari/napari
Summary
Napari is essential for:
- Interactive exploration of multi-dimensional images
- Manual ROI selection and refinement
- Quality control of imaging data
- Visualization of analysis results
It bridges the gap between automated analysis and manual verification, making it invaluable for calcium imaging workflows.