Animation Overlays¶
This notebook demonstrates the new overlay system for visualizing animal behavior alongside spatial fields:
- Position Overlays - Trajectories with decaying trails
- Bodypart Overlays - Pose tracking with skeleton rendering
- Head Direction Overlays - Orientation arrows
- Multi-Animal Support - Track multiple animals simultaneously
- Regions - Highlight spatial regions of interest
- Temporal Alignment - Sync overlays at different sampling rates
- Backend Comparison - Same data across all backends
Estimated time: 20-25 minutes
Prerequisites: 16_field_animation.ipynb
Learning Objectives¶
By the end of this notebook, you will be able to:
- Overlay trajectories on animated spatial fields
- Visualize pose tracking data with skeletons
- Display head direction as dynamic arrows
- Track multiple animals in the same animation
- Highlight regions of interest with transparency
- Align overlays at different sampling rates using
frame_times - Choose the right backend for overlay visualization
Prerequisites¶
Optional dependencies (install as needed):
# For Napari backend (recommended for overlays)
pip install 'napari[all]>=0.4.18'
# For video export
# macOS: brew install ffmpeg
# Ubuntu: sudo apt install ffmpeg
Note: HTML backend supports position and region overlays only (no pose or head direction).
from pathlib import Path
import numpy as np
from shapely.geometry import Point
from neurospatial import Environment
from neurospatial.animation import (
BodypartOverlay,
HeadDirectionOverlay,
PositionOverlay,
Skeleton,
)
from neurospatial.animation.backends.video_backend import check_ffmpeg_available
# Set random seed for reproducibility
np.random.seed(42)
# Output directory
output_dir = Path.cwd()
print(f"Output directory: {output_dir}")
Output directory: /Users/edeno/Documents/GitHub/neurospatial/examples
Setup: Create Environment and Simulate Data¶
We'll create a circular arena and simulate:
- A place field that tracks with the animal
- Animal trajectory exploring the arena
- Head direction as the animal moves
- Pose data (nose, body center, tail base) for skeleton visualization
print("Creating circular arena environment...")
# Circular arena (50 cm radius)
center = Point(50, 50)
radius = 50.0
circle = center.buffer(radius)
env = Environment.from_polygon(polygon=circle, bin_size=2.5, name="CircularArena")
env.units = "cm"
env.frame = "open_field"
# Add region of interest (reward zone in upper-right quadrant)
reward_zone = Point(65, 65)
env.regions.add("reward", point=reward_zone)
print(f"Environment: {env.n_bins} bins, {env.n_dims}D")
print(f"Regions: {list(env.regions.keys())}")
Creating circular arena environment... Environment: 1264 bins, 2D Regions: ['reward']
print("\nSimulating animal trajectory...")
n_frames = 50 # 50 time points
t = np.linspace(0, 4 * np.pi, n_frames) # 2 revolutions
# Spiral trajectory from center outward
r = np.linspace(5, 40, n_frames) # Radius increases
theta = t + np.random.randn(n_frames) * 0.1 # Angle with noise
# Convert to Cartesian (center at 50, 50)
trajectory = np.column_stack([50 + r * np.cos(theta), 50 + r * np.sin(theta)])
# Head direction (tangent to spiral)
head_angles = theta + np.pi / 2 # Perpendicular to radius
print(f"Trajectory: {n_frames} frames")
print(f" Position range: [{trajectory.min():.1f}, {trajectory.max():.1f}] cm")
Simulating animal trajectory... Trajectory: 50 frames Position range: [13.9, 89.4] cm
print("\nSimulating pose data (nose, body, tail)...")
# Pose: 3 keypoints with skeleton
body_length = 10.0 # cm
# Nose: ahead of body center
nose_offset = body_length * 0.5
nose_x = trajectory[:, 0] + nose_offset * np.cos(head_angles)
nose_y = trajectory[:, 1] + nose_offset * np.sin(head_angles)
# Body center: trajectory position
body_x = trajectory[:, 0]
body_y = trajectory[:, 1]
# Tail: behind body center
tail_offset = body_length * 0.5
tail_x = trajectory[:, 0] - tail_offset * np.cos(head_angles)
tail_y = trajectory[:, 1] - tail_offset * np.sin(head_angles)
# Pose dictionary
pose_data = {
"nose": np.column_stack([nose_x, nose_y]),
"body": np.column_stack([body_x, body_y]),
"tail": np.column_stack([tail_x, tail_y]),
}
# Skeleton: defines bodypart nodes and edge connections
skeleton = Skeleton(
name="mouse_simple",
nodes=("nose", "body", "tail"),
edges=(("tail", "body"), ("body", "nose")),
edge_color="white",
edge_width=2.0,
)
print("Pose: 3 keypoints (nose, body, tail)")
print(f"Skeleton: {skeleton.n_edges} edges")
print(" Node colors and edge styling defined in Skeleton object")
Simulating pose data (nose, body, tail)... Pose: 3 keypoints (nose, body, tail) Skeleton: 2 edges Node colors and edge styling defined in Skeleton object
print("\nSimulating place field that tracks with animal...")
# Place field centered on animal position at each frame
fields = []
for i in range(n_frames):
# Find bin closest to animal position
pos = trajectory[i : i + 1] # Shape (1, 2)
center_bin = env.bin_at(pos)[0]
# Gaussian field around animal
distances = env.distance_to([center_bin])
sigma = 12.0 # cm
field = np.exp(-(distances**2) / (2 * sigma**2))
# Add noise
field = field + np.random.randn(env.n_bins) * 0.1
field = np.maximum(field, 0)
fields.append(field)
fields = np.array(fields)
print(f"Fields: {fields.shape} (frames x bins)")
# Create frame_times (required for animate_fields)
# Using 10 Hz frame rate (100ms per frame)
frame_times = np.arange(n_frames) / 10.0 # seconds
print(f"Frame times: {frame_times[0]:.2f}s to {frame_times[-1]:.2f}s (10 Hz)")
Simulating place field that tracks with animal...
Fields: (50, 1264) (frames x bins) Frame times: 0.00s to 4.90s (10 Hz)
Example 1: Position Overlay with Trail¶
Overlay the animal's trajectory on the animated field with a decaying trail showing recent positions.
Key features:
trail_length=10shows last 10 frames- Trail fades from current (opaque) to past (transparent)
- Current position rendered as a marker
# Create position overlay with trail
position_overlay = PositionOverlay(
positions=trajectory,
color="red",
size=12.0,
trail_length=10, # Show last 10 frames as trail
)
print("Example 1: Position Overlay with Trail")
print(f" Trajectory: {trajectory.shape[0]} frames")
print(" Trail length: 10 frames (decaying alpha)")
print(" Color: red, Size: 12.0")
try:
import napari
from IPython import get_ipython
print("\nLaunching Napari viewer...")
viewer = env.animate_fields(
fields,
overlays=[position_overlay],
frame_times=frame_times,
backend="napari",
speed=1.0,
title="Position Overlay with Trail",
)
print("✓ Napari viewer opened")
print(" Watch the red trail follow the animal")
if get_ipython() is None:
napari.run()
except ImportError:
print("⊗ Napari not available. Install with: pip install 'napari[all]>=0.4.18'")
Example 1: Position Overlay with Trail Trajectory: 50 frames Trail length: 10 frames (decaying alpha) Color: red, Size: 12.0 Launching Napari viewer...
/Users/edeno/Documents/GitHub/neurospatial/src/neurospatial/animation/core.py:436: UserWarning: render_napari received unknown keyword arguments that will be ignored: bitrate, codec, colorbar_label, contrast_limits, dpi, dry_run, image_format, max_html_frames, n_workers, overlay_trajectory, show_colorbar. These may be parameters intended for other backends. return render_napari(
✓ Napari viewer opened Watch the red trail follow the animal
Example 2: Pose Tracking with Skeleton¶
Overlay full pose data (nose, body, tail) with skeleton connecting the keypoints.
Key features:
datais a dict mapping bodypart names to trajectoriesskeletondefines edges between bodypartscolorscan customize per-bodypart colors- Skeleton rendered with specified color and width
# Create bodypart overlay with skeleton
# Note: Skeleton styling (edge_color, edge_width) comes from the Skeleton object
bodypart_overlay = BodypartOverlay(
data=pose_data,
skeleton=skeleton, # Skeleton object with nodes, edges, and styling
colors={"nose": "yellow", "body": "red", "tail": "blue"},
)
print("Example 2: Pose Tracking with Skeleton")
print(f" Bodyparts: {list(pose_data.keys())}")
print(f" Skeleton edges: {skeleton.edges}")
print(
f" Skeleton styling: edge_color={skeleton.edge_color}, edge_width={skeleton.edge_width}"
)
print(" Node colors: nose=yellow, body=red, tail=blue")
try:
import napari
from IPython import get_ipython
print("\nLaunching Napari viewer...")
viewer = env.animate_fields(
fields,
overlays=[bodypart_overlay],
frame_times=frame_times,
backend="napari",
speed=1.0,
title="Pose Tracking with Skeleton",
)
print("✓ Napari viewer opened")
print(" Watch the skeleton follow the animal pose")
if get_ipython() is None:
napari.run()
except ImportError:
print("⊗ Napari not available. Install with: pip install 'napari[all]>=0.4.18'")
Example 2: Pose Tracking with Skeleton
Bodyparts: ['nose', 'body', 'tail']
Skeleton edges: (('body', 'tail'), ('body', 'nose'))
Skeleton styling: edge_color=white, edge_width=2.0
Node colors: nose=yellow, body=red, tail=blue
Launching Napari viewer...
/Users/edeno/Documents/GitHub/neurospatial/src/neurospatial/animation/core.py:436: UserWarning: render_napari received unknown keyword arguments that will be ignored: bitrate, codec, colorbar_label, contrast_limits, dpi, dry_run, image_format, max_html_frames, n_workers, overlay_trajectory, show_colorbar. These may be parameters intended for other backends. return render_napari(
✓ Napari viewer opened Watch the skeleton follow the animal pose
Example 3: Head Direction Visualization¶
Overlay head direction as dynamic arrows pointing in the direction of travel.
Key features:
datacan be angles (radians) or unit vectors- Arrows rendered with specified color and length
- Arrow origin is at the animal's position
# Create head direction overlay (angles in radians)
head_direction_overlay = HeadDirectionOverlay(
headings=head_angles,
color="yellow",
length=15.0, # Arrow length in cm
)
print("Example 3: Head Direction Visualization")
print(f" Head angles: {head_angles.shape[0]} frames")
print(" Arrow color: yellow, Length: 15.0 cm")
try:
import napari
from IPython import get_ipython
print("\nLaunching Napari viewer...")
# Combine position + head direction overlays
viewer = env.animate_fields(
fields,
overlays=[position_overlay, head_direction_overlay],
frame_times=frame_times,
backend="napari",
speed=1.0,
title="Position + Head Direction",
)
print("✓ Napari viewer opened")
print(" Watch the yellow arrow show heading direction")
if get_ipython() is None:
napari.run()
except ImportError:
print("⊗ Napari not available. Install with: pip install 'napari[all]>=0.4.18'")
Example 3: Head Direction Visualization Head angles: 50 frames Arrow color: yellow, Length: 15.0 cm Launching Napari viewer...
/Users/edeno/Documents/GitHub/neurospatial/src/neurospatial/animation/core.py:436: UserWarning: render_napari received unknown keyword arguments that will be ignored: bitrate, codec, colorbar_label, contrast_limits, dpi, dry_run, image_format, max_html_frames, n_workers, overlay_trajectory, show_colorbar. These may be parameters intended for other backends. return render_napari(
✓ Napari viewer opened Watch the yellow arrow show heading direction
Example 4: Multi-Animal Tracking¶
Track multiple animals simultaneously by providing multiple overlay instances.
Key features:
- Pass a list of overlays for each animal
- Each overlay automatically gets a suffix (e.g., "Position_1", "Position_2")
- All animals rendered in the same animation with different colors
print("Example 4: Multi-Animal Tracking")
print("\nSimulating second animal...")
# Second animal with offset trajectory
trajectory_2 = trajectory + np.array([10, -10]) # Offset spatially
trajectory_2 = np.clip(trajectory_2, 5, 95) # Keep in bounds
# Create overlays for both animals
animal1_overlay = PositionOverlay(
positions=trajectory, color="red", size=12.0, trail_length=8
)
animal2_overlay = PositionOverlay(
positions=trajectory_2, color="blue", size=12.0, trail_length=8
)
print(" Animal 1: red")
print(" Animal 2: blue (offset trajectory)")
try:
import napari
from IPython import get_ipython
print("\nLaunching Napari viewer...")
viewer = env.animate_fields(
fields,
overlays=[animal1_overlay, animal2_overlay], # Multiple overlays
frame_times=frame_times,
backend="napari",
speed=1.0,
title="Multi-Animal Tracking",
)
print("✓ Napari viewer opened")
print(" Watch both animals explore simultaneously")
if get_ipython() is None:
napari.run()
except ImportError:
print("⊗ Napari not available. Install with: pip install 'napari[all]>=0.4.18'")
Example 4: Multi-Animal Tracking Simulating second animal... Animal 1: red Animal 2: blue (offset trajectory) Launching Napari viewer...
/Users/edeno/Documents/GitHub/neurospatial/src/neurospatial/animation/core.py:436: UserWarning: render_napari received unknown keyword arguments that will be ignored: bitrate, codec, colorbar_label, contrast_limits, dpi, dry_run, image_format, max_html_frames, n_workers, overlay_trajectory, show_colorbar. These may be parameters intended for other backends. return render_napari(
✓ Napari viewer opened Watch both animals explore simultaneously
Example 5: Regions Overlay with Spatial Fields¶
Highlight spatial regions of interest (e.g., reward zones) alongside overlays.
Key features:
show_regions=Truedisplays all defined regionsshow_regions=["reward"]displays specific regions onlyregion_alpha=0.3controls transparency- Regions rendered as colored polygons/points
print("Example 5: Regions Overlay")
print(f" Showing region: {list(env.regions.keys())}")
print(" Region alpha: 0.3 (30% transparent)")
try:
import napari
from IPython import get_ipython
print("\nLaunching Napari viewer...")
viewer = env.animate_fields(
fields,
overlays=[position_overlay],
show_regions=True, # Show all regions
region_alpha=0.3, # 30% transparent
frame_times=frame_times,
backend="napari",
speed=1.0,
title="Position + Reward Region",
)
print("✓ Napari viewer opened")
print(" Watch the animal approach the reward region")
if get_ipython() is None:
napari.run()
except ImportError:
print("⊗ Napari not available. Install with: pip install 'napari[all]>=0.4.18'")
Example 5: Regions Overlay Showing region: ['reward'] Region alpha: 0.3 (30% transparent) Launching Napari viewer...
/Users/edeno/Documents/GitHub/neurospatial/src/neurospatial/animation/core.py:436: UserWarning: render_napari received unknown keyword arguments that will be ignored: bitrate, codec, colorbar_label, contrast_limits, dpi, dry_run, image_format, max_html_frames, n_workers, overlay_trajectory, show_colorbar. These may be parameters intended for other backends. return render_napari(
✓ Napari viewer opened Watch the animal approach the reward region
Example 6: Mixed-Rate Temporal Alignment¶
Align overlays sampled at different rates using temporal timestamps.
Key features:
- Overlay
timesparameter specifies timestamps for each frame frame_timesparameter specifies field frame timestamps- Linear interpolation automatically aligns overlay to field frames
- Works even when overlay and fields have different sampling rates
Example: Position tracked at 120 Hz, fields computed at 10 Hz
print("Example 6: Mixed-Rate Temporal Alignment")
print("\nSimulating high-frequency position tracking...")
# High-frequency position tracking (120 Hz)
duration = 5.0 # seconds
fps_high = 120 # Hz
n_samples_high = int(duration * fps_high) # 600 samples
# Generate high-frequency trajectory
t_high = np.linspace(0, duration, n_samples_high)
theta_high = t_high * 2 * np.pi + np.random.randn(n_samples_high) * 0.05
r_high = 20 + 15 * np.sin(t_high * 3)
trajectory_high_freq = np.column_stack(
[50 + r_high * np.cos(theta_high), 50 + r_high * np.sin(theta_high)]
)
timestamps_high = t_high
print(f" Position tracking: {n_samples_high} samples at {fps_high} Hz")
# Low-frequency fields (10 Hz)
fps_low = 10 # Hz
n_frames_low = int(duration * fps_low) # 50 frames
frame_times = np.linspace(0, duration, n_frames_low)
print(f" Field computation: {n_frames_low} frames at {fps_low} Hz")
# Compute fields at low frequency
fields_low_freq = []
for t in frame_times:
# Find closest high-freq position
idx = np.argmin(np.abs(timestamps_high - t))
pos = trajectory_high_freq[idx : idx + 1]
center_bin = env.bin_at(pos)[0]
distances = env.distance_to([center_bin])
field = np.exp(-(distances**2) / (2 * 12.0**2))
field = field + np.random.randn(env.n_bins) * 0.1
field = np.maximum(field, 0)
fields_low_freq.append(field)
fields_low_freq = np.array(fields_low_freq)
# Create overlay with timestamps
position_overlay_timed = PositionOverlay(
positions=trajectory_high_freq,
times=timestamps_high, # 120 Hz timestamps
color="red",
size=10.0,
trail_length=15,
)
print("\n✓ Overlay will be interpolated to match field frame times")
print(" (Linear interpolation: 120 Hz → 10 Hz)")
try:
import napari
from IPython import get_ipython
print("\nLaunching Napari viewer...")
viewer = env.animate_fields(
fields_low_freq,
overlays=[position_overlay_timed],
frame_times=frame_times, # Explicit field timestamps
backend="napari",
speed=1.0,
title="Mixed-Rate Alignment (120 Hz → 10 Hz)",
)
print("✓ Napari viewer opened")
print(" Position automatically aligned to field frames")
if get_ipython() is None:
napari.run()
except ImportError:
print("⊗ Napari not available. Install with: pip install 'napari[all]>=0.4.18'")
Example 6: Mixed-Rate Temporal Alignment Simulating high-frequency position tracking... Position tracking: 600 samples at 120 Hz Field computation: 50 frames at 10 Hz
✓ Overlay will be interpolated to match field frame times (Linear interpolation: 120 Hz → 10 Hz) Launching Napari viewer...
/Users/edeno/Documents/GitHub/neurospatial/src/neurospatial/animation/core.py:436: UserWarning: render_napari received unknown keyword arguments that will be ignored: bitrate, codec, colorbar_label, contrast_limits, dpi, dry_run, image_format, max_html_frames, n_workers, overlay_trajectory, show_colorbar. These may be parameters intended for other backends. return render_napari(
✓ Napari viewer opened Position automatically aligned to field frames
Example 7: Backend Comparison¶
Compare overlay rendering across all backends with the same data.
Backend capabilities:
| Backend | Position | Bodypart | HeadDirection | Regions |
|---|---|---|---|---|
| Napari | ✓ | ✓ | ✓ | ✓ |
| Video | ✓ | ✓ | ✓ | ✓ |
| HTML | ✓ | ✗ | ✗ | ✓ |
| Widget | ✓ | ✓ | ✓ | ✓ |
Note: HTML backend warns when given unsupported overlay types.
print("Example 7a: Napari Backend (Full Support)")
try:
import napari
from IPython import get_ipython
# All overlay types supported
viewer = env.animate_fields(
fields,
overlays=[position_overlay, bodypart_overlay, head_direction_overlay],
show_regions=True,
frame_times=frame_times,
backend="napari",
speed=1.0,
title="Napari: All Overlays",
)
print("✓ Napari: Position + Pose + Head Direction + Regions")
if get_ipython() is None:
napari.run()
except ImportError:
print("⊗ Napari not available")
Example 7a: Napari Backend (Full Support)
/Users/edeno/Documents/GitHub/neurospatial/src/neurospatial/animation/core.py:436: UserWarning: render_napari received unknown keyword arguments that will be ignored: bitrate, codec, colorbar_label, contrast_limits, dpi, dry_run, image_format, max_html_frames, n_workers, overlay_trajectory, show_colorbar. These may be parameters intended for other backends. return render_napari(
✓ Napari: Position + Pose + Head Direction + Regions
print("Example 7b: Video Backend (Full Support)")
if check_ffmpeg_available():
# All overlay types supported
output_path = env.animate_fields(
fields,
overlays=[position_overlay, bodypart_overlay, head_direction_overlay],
show_regions=True,
frame_times=frame_times,
backend="video",
save_path=output_dir / "17_all_overlays.mp4",
speed=1.0,
n_workers=1,
)
print(f"✓ Video: Saved to {output_path}")
else:
print("⊗ ffmpeg not available for video export")
Example 7b: Video Backend (Full Support)
Rendering 50 frames using 4 workers... Estimated time: ~6 seconds
Workers: 0%| | 0/4 [00:00<?, ?it/s]
Workers: 25%|██▌ | 1/4 [00:03<00:11, 3.85s/it]
Workers: 100%|██████████| 4/4 [00:03<00:00, 1.03it/s]
Encoding video...
✓ Video saved to /Users/edeno/Documents/GitHub/neurospatial/examples/17_all_overlays.mp4 ✓ Video: Saved to /Users/edeno/Documents/GitHub/neurospatial/examples/17_all_overlays.mp4
print("Example 7c: HTML Backend (Position + Regions Only)")
print(" WARNING: HTML backend does NOT support bodypart or head direction overlays")
print(" (Warnings will be emitted if provided)\n")
# HTML: Only position and regions supported
html_path = env.animate_fields(
fields,
overlays=[position_overlay], # Only position overlay
show_regions=True,
frame_times=frame_times,
backend="html",
save_path=output_dir / "17_position_only.html",
speed=1.0,
)
print(f"✓ HTML: Saved to {html_path}")
print(" (Position + Regions rendered; pose/head direction not supported)")
Example 7c: HTML Backend (Position + Regions Only) WARNING: HTML backend does NOT support bodypart or head direction overlays (Warnings will be emitted if provided) Rendering 50 frames to PNG...
Encoding frames: 0%| | 0/50 [00:00<?, ?it/s]
Encoding frames: 6%|▌ | 3/50 [00:00<00:01, 28.52it/s]
Encoding frames: 14%|█▍ | 7/50 [00:00<00:01, 32.46it/s]
Encoding frames: 22%|██▏ | 11/50 [00:00<00:01, 33.92it/s]
Encoding frames: 30%|███ | 15/50 [00:00<00:01, 34.96it/s]
Encoding frames: 38%|███▊ | 19/50 [00:00<00:00, 34.94it/s]
Encoding frames: 46%|████▌ | 23/50 [00:00<00:00, 35.48it/s]
Encoding frames: 54%|█████▍ | 27/50 [00:00<00:00, 35.83it/s]
Encoding frames: 62%|██████▏ | 31/50 [00:00<00:00, 36.04it/s]
Encoding frames: 70%|███████ | 35/50 [00:00<00:00, 36.03it/s]
Encoding frames: 78%|███████▊ | 39/50 [00:01<00:00, 35.83it/s]
Encoding frames: 86%|████████▌ | 43/50 [00:01<00:00, 35.50it/s]
Encoding frames: 94%|█████████▍| 47/50 [00:01<00:00, 35.24it/s]
Encoding frames: 100%|██████████| 50/50 [00:01<00:00, 35.06it/s]
✓ HTML saved to /Users/edeno/Documents/GitHub/neurospatial/examples/17_position_only.html (1.6 MB) ✓ HTML: Saved to /Users/edeno/Documents/GitHub/neurospatial/examples/17_position_only.html (Position + Regions rendered; pose/head direction not supported)
print("Example 7d: Widget Backend (Full Support)")
try:
from IPython import get_ipython
if get_ipython() is not None:
# All overlay types supported
widget = env.animate_fields(
fields,
overlays=[position_overlay, bodypart_overlay, head_direction_overlay],
show_regions=True,
frame_times=frame_times,
backend="widget",
speed=1.0,
)
print("✓ Widget: Position + Pose + Head Direction + Regions")
else:
print("⊗ Not in Jupyter notebook")
except ImportError:
print("⊗ IPython/ipywidgets not available")
Example 7d: Widget Backend (Full Support) Pre-rendering 50 frames for widget...
✓ Widget: Position + Pose + Head Direction + Regions
Key Takeaways¶
Overlay Types¶
PositionOverlay: Trajectories with decaying trails
data: (n_frames, n_dims) arraytrail_length: Number of past frames to showcolor,size: Marker appearance
BodypartOverlay: Pose tracking with skeletons
data: Dict mapping bodypart names to (n_frames, n_dims) arraysskeleton: Skeleton object defining nodes, edges, and stylingcolors: Per-bodypart colors (or use skeleton.node_colors)
HeadDirectionOverlay: Orientation arrows
data: (n_frames,) angles in radians OR (n_frames, n_dims) unit vectorscolor,length: Arrow appearance
Temporal Alignment¶
- Add
timesparameter to overlay for timestamps - Add
frame_timesparameter toanimate_fields()for field timestamps - Linear interpolation automatically aligns overlay to field frames
- Works even when overlay and fields have different sampling rates
Backend Capabilities¶
- Napari: Full support (all overlay types + regions)
- Video: Full support (all overlay types + regions)
- HTML: Partial support (position + regions only, warns for others)
- Widget: Full support (all overlay types + regions)
Multi-Animal Support¶
- Pass multiple overlay instances in a list
- Each overlay automatically gets a suffix (e.g., "Position_1", "Position_2")
- Use different colors to distinguish animals
Common Patterns¶
# Simple trajectory overlay
from neurospatial import PositionOverlay
overlay = PositionOverlay(positions=trajectory, color="red", trail_length=10)
env.animate_fields(fields, frame_times=frame_times, overlays=[overlay], backend="napari")
# Pose with skeleton
from neurospatial import BodypartOverlay
from neurospatial.animation import Skeleton
skeleton = Skeleton(
name="mouse",
nodes=("nose", "body", "tail"),
edges=(("tail", "body"), ("body", "nose")),
edge_color="white",
edge_width=2.0,
)
overlay = BodypartOverlay(
data={"nose": nose_traj, "body": body_traj, "tail": tail_traj},
skeleton=skeleton,
)
env.animate_fields(fields, frame_times=frame_times, overlays=[overlay], backend="napari")
# Mixed-rate alignment
overlay = PositionOverlay(positions=trajectory_120hz, times=times_120hz)
env.animate_fields(
fields_10hz,
overlays=[overlay],
frame_times=times_10hz, # Automatic interpolation
backend="napari"
)
# Multi-animal
env.animate_fields(
fields,
frame_times=frame_times,
overlays=[overlay_animal1, overlay_animal2],
backend="napari"
)
# Show regions
env.animate_fields(
fields,
frame_times=frame_times,
overlays=[overlay],
show_regions=True,
region_alpha=0.3,
backend="napari"
)
Performance Tips¶
- Video export: Use
n_workers > 1for parallel rendering - Large datasets: Use Napari for exploration, subsample for video
- HTML file size: Limit frames (default max 500) or use video backend
- Parallel rendering: Call
env.clear_cache()before video export withn_workers > 1
Next Steps¶
- Apply overlays to your own behavioral tracking data
- Combine multiple overlay types for rich visualizations
- Export publication-quality videos with overlays
- Use temporal alignment for multi-modal data (tracking + neural recordings)
For more details, see:
docs/animation_overlays.md- Complete overlay documentationexamples/16_field_animation.ipynb- Animation backends without overlays