# install cell skipped during CI (deps preinstalled into system Python)
⚠️ Restart runtime after install
The install may upgrade packages already loaded in the kernel. Go to Runtime → Restart session, then Run all cells below (skip this install cell on re-run).
IBL Widefield — Raw Data Tutorial¶
This tutorial shows how to access data from DANDI:001712 for the IBL widefield dataset.
Study Overview¶
This dataset contains widefield imaging data from mice, investigating how prior expectations and neural dynamics are modified in a mouse model of autism (CNTNALP2 knockout). The study explores sensory perception and decision-making tasks to understand the neural basis of altered prior utilization.
Contents¶
- Setup and Data Access
- Session and Subject Metadata
- Raw Imaging Data and Metadata
- Synchronization Signals
- Behavior
# Visualization
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
# Configure matplotlib
plt.rcParams['figure.figsize'] = (12, 6)
plt.rcParams['font.size'] = 10
2. Session and Subject Metadata ¶
from load_nwb_utils import *
dandiset_id = "001712"
subject_id = "FD-28" # Example subject
session_id = "81f90b18-e61c-4d32-bbce-3e0c5f33f06c" # EID for the session
# Choose data source (DANDI streaming or local)
USE_DANDI = True # Set to False to use local files
if USE_DANDI:
nwbfile, io = load_nwb_from_dandi(dandiset_id, subject_id, session_id, description="raw")
else:
# TODO Specify your local directory path
local_directory = f"E:/IBL-widefield-nwbfiles/full/"
nwbfile, io = load_nwb_local(local_directory, subject_id, session_id, description="raw")
print("=== SESSION INFORMATION ===")
print(f"Experiment description:\n {nwbfile.experiment_description}")
print(f"Session description:\n {nwbfile.session_description}")
print(f"Session start time:\n {nwbfile.session_start_time}")
=== SESSION INFORMATION === Experiment description: In dynamic environments, updating beliefs based on past experiences (priors) is essential for optimal decision-making. Prior utilization is often impaired in psychiatric disorders, affecting perception and behavior. We investigate how Neurexin1α (Nrxn1α) loss-of-function disrupts this process, providing insight into circuit deficits underlying sensorimotor dysfunction. While the synaptic role of Nrxn1α is well studied, its impact on network dynamics and decision-making behavior remain unclear. Using widefield calcium imaging, we assess cortex-wide activity in mice performing a two-choice task to probe how priors influence visually-guided decisions. This task requires the mouse to combine sensory evidence with the prior probability over the stimulus side. We find Nrxn1α KO mice underutilized priors and were slower to update choices based on feedback. During decision-making, cortex-wide cortical activity is both elevated and increasingly correlated in Nrxn1α KO mice, independent of task period. Moreover, a larger fraction of cortical variance was explained by movement variables, consistent with stronger coupling of cortical activity to motor signals and a bias toward movement-related dynamics. These findings suggest that core computations underlying decision-making, such as integrating past experience with current evidence, depend on intact synaptic mechanisms shaped by genes like Nrxn1α. Session description: The task protocol(s) performed in this experimental session: 1. Biased choice world — the standard IBL data-collection task for trained mice. A Gabor patch appears at ±35° azimuth and the mouse turns a wheel to bring it to the center. Correct responses earn a water reward (~1.5 µL); incorrect responses trigger white noise and a 2s timeout. Stimulus probability alternates between 80/20 and 20/80 blocks (starting with a 50/50 block), with block lengths drawn from a truncated exponential distribution (min 20, max 100 trials). Full contrast set: [1.0, 0.25, 0.125, 0.0625, 0.0]. Session start time: 2023-11-14 12:10:33.757227-08:00
print("\n=== SUBJECT INFORMATION ===")
print(f"ID: {nwbfile.subject.subject_id}")
print(f"DOB: {nwbfile.subject.date_of_birth}")
print(f"Strain: {nwbfile.subject.species}")
print(f"Genotype: {nwbfile.subject.genotype}")
print(f"Sex: {nwbfile.subject.sex}")
=== SUBJECT INFORMATION === ID: FD-28 DOB: 2022-12-08 00:00:00-08:00 Strain: Mus musculus Genotype: None Sex: F
3. Raw Imaging Data and Metadata ¶
OnePhotonSeries ¶
Raw widefield imaging data are stored as OnePhotonSeries objects in the NWB file's acquisition module. Each excitation wavelength is added to a separate OnePhotonSeries object.
Data shape
- Arrays are shaped
(time, num_rows, num_columns)— the first dimension is time (frame), the second and third are the height and width of the image.
Series names
- Calcium (470 nm):
OnePhotonSeriesCalcium - Isosbestic (405 nm):
OnePhotonSeriesIsosbestic
Access example
calcium = nwbfile.acquisition['OnePhotonSeriesCalcium']
first_frame = calcium.data[0] # lazy read of frame 0
calcium_imaging = nwbfile.acquisition["OnePhotonSeriesCalcium"]
print("=== RAW WIDEFIELD CALCIUM SIGNAL ===")
print(f"Name: {calcium_imaging.name}")
print(f"Description: {calcium_imaging.description}")
print(f"Data shape: {calcium_imaging.data.shape}")
print(f"Duration: {calcium_imaging.timestamps[-1] - calcium_imaging.timestamps[0]:.2f} seconds")
=== RAW WIDEFIELD CALCIUM SIGNAL === Name: OnePhotonSeriesCalcium Description: Widefield raw imaging under blue excitation at 470 nm (GCaMP signal). The dimensions are (time, height, width). Data shape: (124886, 540, 640) Duration: 3999.27 seconds
isosbestic_imaging = nwbfile.acquisition["OnePhotonSeriesIsosbestic"]
print("=== RAW WIDEFIELD ISOSBESTIC SIGNAL ===")
print(f"Name: {isosbestic_imaging.name}")
print(f"Description: {isosbestic_imaging.description}")
print(f"Data shape: {isosbestic_imaging.data.shape}")
print(f"Duration: {isosbestic_imaging.timestamps[-1] - isosbestic_imaging.timestamps[0]:.2f} seconds")
=== RAW WIDEFIELD ISOSBESTIC SIGNAL === Name: OnePhotonSeriesIsosbestic Description: Widefield raw imaging under violet excitation at 405 nm (isosbestic control). The dimensions are (time, height, width). Data shape: (124886, 540, 640) Duration: 3999.27 seconds
# Plot with matplotlib (example)
fig, axes = plt.subplots(1, 2, sharex=True, sharey=True, dpi=100)
axes[0].imshow(calcium_imaging.data[0, ...], cmap='gray')
axes[0].set_title('Calcium Imaging (470 nm) - First Frame')
axes[0].axis('off')
axes[1].imshow(isosbestic_imaging.data[0, ...], cmap='gray')
axes[1].set_title('Isosbestic Imaging (405 nm) - First Frame')
axes[1].axis('off')
plt.tight_layout()
plt.show()
Imaging Metadata ¶
print("=== IMAGING METADATA ===")
print("All imaging metadata are stored in the imaging module in imaging_planes:")
print("-" * 100)
for imaging_plane_name in nwbfile.imaging_planes:
imaging_plane = nwbfile.imaging_planes[imaging_plane_name]
print(f"Imaging Plane: {imaging_plane_name}")
print("-" * 100)
print(f" Description: {imaging_plane.description}")
print(f" Imaging Rate: {imaging_plane.imaging_rate} Hz")
print(f" Optical Channel: {imaging_plane.optical_channel[0].name}, {imaging_plane.optical_channel[0].description}")
print(f" Indicator: {imaging_plane.indicator}")
print(f" Excitation Wavelength: {imaging_plane.excitation_lambda} nm")
print(f" Emission Wavelength: {imaging_plane.optical_channel[0].emission_lambda} nm")
print(f" Location: {imaging_plane.location}")
print("-"*100)
=== IMAGING METADATA === All imaging metadata are stored in the imaging module in imaging_planes: ---------------------------------------------------------------------------------------------------- Imaging Plane: ImagingPlaneCalcium ---------------------------------------------------------------------------------------------------- Description: The imaging plane for calcium imaging from Blue light excitation. Imaging Rate: 15.0 Hz Optical Channel: OpticalChannel, GCaMP Ca2+ bound emission (calcium signal). (Thorlabs, cat. no. M470L4) Indicator: GCaMP6f Excitation Wavelength: 470.0 nm Emission Wavelength: 510.0 nm Location: Whole Brain ---------------------------------------------------------------------------------------------------- Imaging Plane: ImagingPlaneIsosbestic ---------------------------------------------------------------------------------------------------- Description: The imaging plane for calcium imaging from Violet light excitation. Imaging Rate: 15.0 Hz Optical Channel: OpticalChannel, GCaMP Ca2+ independent emission (isosbestic signal). (Thorlabs, cat. no. M405L4) Indicator: GCaMP6f Excitation Wavelength: 405.0 nm Emission Wavelength: 510.0 nm Location: Whole Brain ----------------------------------------------------------------------------------------------------
event_names = [name for name in nwbfile.acquisition if "Events" in name]
print("=== SYNCHRONIZATION EVENTS IN ACQUISITION ===")
print("-" * 100)
for name in event_names:
print(f" Name: {name}")
print(f" Description: {nwbfile.acquisition[name].description}")
=== SYNCHRONIZATION EVENTS IN ACQUISITION === ---------------------------------------------------------------------------------------------------- Name: EventsAudio Description: Auditory stimulus presentation events. Marks timing of audio stimulus delivery for auditory tasks or cue presentation. Label meanings: - audio_off: Audio stimulus off - audio_on: Audio stimulus on Name: EventsBodyCamera Description: Video frame acquisition times for the body camera. Each event marks when a video frame was captured by the camera, enabling temporal alignment of behavior videos with neural and task data. Label meanings: - exposure_end: Camera exposure end or frame readout complete - frame_start: Camera frame acquisition start (frame timestamp) Name: EventsFrame2ttl Description: Monitor refresh events detected by photodiode for visual stimulus timing. The Frame2TTL device uses a photodiode to detect changes in screen luminance, providing precise timing of when visual stimuli are displayed on the monitor. This is essential for accurate stimulus-response latency measurements. Label meanings: - screen_dark: Screen transitioned to dark (photodiode detected low luminance) - screen_bright: Screen transitioned to bright (photodiode detected high luminance) Name: EventsFrameTrigger Description: Widefield imaging frame trigger events. Each event marks the trigger signal that initiates acquisition of a widefield imaging frame, providing precise timing for aligning imaging data with behavioral and electrophysiological recordings. Label meanings: - frame_off: Frame trigger signal off - frame_on: Frame trigger signal on Name: EventsLeftCamera Description: Video frame acquisition times for the left-side camera. Each event marks when a video frame was captured by the camera, enabling temporal alignment of behavior videos with neural and task data. Label meanings: - exposure_end: Camera exposure end or frame readout complete - frame_start: Camera frame acquisition start (frame timestamp) Name: EventsRightCamera Description: Video frame acquisition times for the right-side camera. Each event marks when a video frame was captured by the camera, enabling temporal alignment of behavior videos with neural and task data. Label meanings: - exposure_end: Camera exposure end or frame readout complete - frame_start: Camera frame acquisition start (frame timestamp) Name: EventsRotaryEncoder0 Description: Rotary encoder pulses tracking wheel movement (quadrature phase A). Each pulse represents a discrete angular increment of the wheel rotation, used to measure behavioral responses and locomotion with high temporal precision. Label meanings: - phase_low: Encoder phase transition to LOW - phase_high: Encoder phase transition to HIGH Name: EventsRotaryEncoder1 Description: Rotary encoder pulses tracking wheel movement (quadrature phase B). Combined with rotary_encoder_0, this provides directional information for wheel rotation through quadrature encoding. Label meanings: - phase_low: Encoder phase transition to LOW - phase_high: Encoder phase transition to HIGH
from itertools import cycle
def squares(tscale, polarity, ax=None, yrange=[0, 1], **kwargs):
"""
Plot rising/falling fronts as a square wave.
"""
if ax is None:
ax = plt.gca()
isort = np.argsort(tscale)
tscale = tscale[isort]
polarity = polarity[isort]
f = np.tile(polarity, (2, 1))
t = np.concatenate((tscale, np.r_[tscale[1:], tscale[-1]])).reshape(2, f.shape[1])
ydata = f.T.ravel()
ydata = (ydata + 1) / 2 * (yrange[1] - yrange[0]) + yrange[0]
ax.plot(t.T.ravel(), ydata, **kwargs)
num_events = 11 # number of on-off events to plot
cmap = plt.get_cmap('tab20')
color_cycle = cycle([cmap(i) for i in range(cmap.N)])
for i, event_name in enumerate(event_names):
fig, ax = plt.subplots(figsize=(8, 2), dpi=200)
event = nwbfile.acquisition[event_name]
# take first num_events on/off pairs (2 * num_events samples)
times = event.timestamps[:num_events * 2]
data = event.data[:num_events * 2].astype(int)
# find change points (edges)
changes = np.where(np.diff(data) != 0)[0] + 1
if changes.size == 0:
continue
tscale = times[changes]
prev_vals = data[changes - 1]
next_vals = data[changes]
# +1 for 0->1 (rising), -1 for 1->0 (falling)
polarity = np.where(
(prev_vals == 0) & (next_vals == 1), 1,
np.where((prev_vals == 1) & (next_vals == 0), -1, 0)
)
valid = polarity != 0
tscale = tscale[valid]
polarity = polarity[valid]
color = next(color_cycle)
# plot as square wave
squares(tscale, polarity, ax=ax, yrange=[0, 1], color=color, linewidth=1.5)
# if i == 0:
# axes[i].set_title("Synchronization Events (acquisition)")
legend_patches = [mpatches.Patch(color=color, alpha=0.8, label=event_name)]
ax.legend(handles=legend_patches, bbox_to_anchor=(1.01, 1), loc='upper left')
ax.set_yticks([0, 1])
ax.set_yticklabels(['0', '1'])
ax.set_frame_on(False)
if i == len(event_names) - 1:
ax.set_xlabel("Time (s)")
# turn off grid
#ax.grid(False)
plt.tight_layout()
plt.show()
Analog Signals ¶
analog_signal_names = [name for name in nwbfile.acquisition if "TimeSeries" in name]
print("=== SYNCHRONIZATION SIGNALS IN ACQUISITION ===")
print("-" * 100)
for name in analog_signal_names:
print(f" Name: {name}")
print(f" Description: {nwbfile.acquisition[name].description}")
=== SYNCHRONIZATION SIGNALS IN ACQUISITION === ---------------------------------------------------------------------------------------------------- Name: TimeSeriesBpod Description: Analog signal from Bpod behavioral control system. This continuous voltage signal encodes behavioral state machine events and timestamps from the Bpod system, which controls stimulus presentation and reward delivery during tasks. The analog encoding allows precise temporal alignment between Bpod events and neural recordings.
signal_name = "TimeSeriesBpod"
analog_signal = nwbfile.acquisition[signal_name]
rate = analog_signal.rate
# desired time window (minutes)
start_min, end_min = 5, 6
start_sec, end_sec = start_min * 60, end_min * 60
# convert absolute seconds to sample indices (respecting starting_time)
start_idx = int((start_sec - analog_signal.starting_time) * rate)
end_idx = int((end_sec - analog_signal.starting_time) * rate)
# clamp to valid range
start_idx = max(0, start_idx)
end_idx = min(len(analog_signal.data), end_idx)
data_slice = analog_signal.data[start_idx:end_idx] * analog_signal.conversion
times = analog_signal.starting_time + np.arange(start_idx, end_idx) / rate
fig, ax = plt.subplots(dpi=200)
ax.plot(times, data_slice, label=signal_name)
ax.set_xlim(start_sec, end_sec)
ax.set_xlabel("Time (s)")
ax.set_ylabel("Voltage (V)")
ax.set_title("Analog Synchronization Signal")
ax.legend(bbox_to_anchor=(1.01, 1), loc='upper left')
ax.set_frame_on(False)
plt.show()
nwbfile.epochs
Raw Video Data ¶
print("=== RAW VIDEO ===\n")
image_series = []
for name, acq in nwbfile.acquisition.items():
if "Video" not in name:
continue
print(f"{name} - {acq.description}: ")
print(f" External file: {acq.external_file[:]}")
print("-" * 100)
image_series.append(name)
=== RAW VIDEO === VideoBodyCamera - Raw video from camera recording behavioral and task events.: External file: ['./sub-FD-28_ses-81f90b18-e61c-4d32-bbce-3e0c5f33f06c_ecephys+image\\sub-FD-28_ses-81f90b18-e61c-4d32-bbce-3e0c5f33f06c_VideoBodyCamera.mp4'] ---------------------------------------------------------------------------------------------------- VideoLeftCamera - Raw video from camera recording behavioral and task events.: External file: ['./sub-FD-28_ses-81f90b18-e61c-4d32-bbce-3e0c5f33f06c_ecephys+image\\sub-FD-28_ses-81f90b18-e61c-4d32-bbce-3e0c5f33f06c_VideoLeftCamera.mp4'] ---------------------------------------------------------------------------------------------------- VideoRightCamera - Raw video from camera recording behavioral and task events.: External file: ['./sub-FD-28_ses-81f90b18-e61c-4d32-bbce-3e0c5f33f06c_ecephys+image\\sub-FD-28_ses-81f90b18-e61c-4d32-bbce-3e0c5f33f06c_VideoRightCamera.mp4'] ----------------------------------------------------------------------------------------------------
nwbfile.acquisition["VideoBodyCamera"]
VideoBodyCamera (ImageSeries)
data
| Data type | uint8 |
|---|---|
| Shape | (0, 0, 0) |
| Array size | 0.00 bytes |
| Chunk shape | None |
| Compression | None |
| Compression opts | None |
| Uncompressed size (bytes) | 0 |
| Compressed size (bytes) | 0 |
| Compression ratio | undefined |
[]
timestamps
| Data type | float64 |
|---|---|
| Shape | (119964,) |
| Array size | 937.22 KiB |
| Chunk shape | (119964,) |
| Compression | gzip |
| Compression opts | 4 |
| Uncompressed size (bytes) | 959712 |
| Compressed size (bytes) | 482800 |
| Compression ratio | 1.987804473902237 |
external_file
| Data type | object |
|---|---|
| Shape | (1,) |
| Array size | 8.00 bytes |
| Chunk shape | None |
| Compression | None |
| Compression opts | None |
| Uncompressed size (bytes) | 8 |
| Compressed size (bytes) | 16 |
| Compression ratio | 0.5 |
['./sub-FD-28_ses-81f90b18-e61c-4d32-bbce-3e0c5f33f06c_ecephys+image\\sub-FD-28_ses-81f90b18-e61c-4d32-bbce-3e0c5f33f06c_VideoBodyCamera.mp4']
starting_frame
| Data type | int64 |
|---|---|
| Shape | (1,) |
| Array size | 8.00 bytes |
[0]