Accessing BCI Data#
This tutorial will go over how to load the BCI data and access its contents.
Import required packages#
The BCI data is packaged in NWB format with a Zarr backend. To access the data, use NWBZarrIO from hdmf-zarr. nwb2widget creates an interactive GUI with the NWB file, which is useful for exploring the file contents and looking at basic plots.
from hdmf_zarr import NWBZarrIO
from nwbwidgets import nwb2widget
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
Load the data#
Let’s load the data for one recording session using NWBZarrIO.
# Set filename
nwb_path = '/data/single-plane-ophys_731015_2025-01-10_18-06-31_processed_2025-08-03_20-39-09/single-plane-ophys_731015_2025-01-10_18-06-31_behavior_nwb'
# Assign file to an NWBZarrIO object
io = NWBZarrIO(nwb_path, 'r')
# Read the file
nwbfile = io.read()
nwb2widget is useful for exploring the NWB file structure and contents. The widget can also generate basic plots, like the neural activity timeseries traces.
nwb2widget(nwbfile)
Image Segmentation Table#
During processing, a segmentation algorithm (e.g. Suite2p or Cellpose) is applied to the raw fluorescence data to extract ROIs for detected neurons. The extracted ROIs are accessible in the form of image masks, a HxW sparse array with non-zero values where the ROI is masked out in the imaging plane. The detected ROIs are run through a soma/dendrite classifier to confirm if the ROI masks fit certain features of a soma or dendrite. The image masks and outputs of the soma/dendrite classifier are stored in the image_segmentation table in the processing container.
Column |
Description |
|---|---|
is_soma |
==1 if ROI classified as soma, ==0 if not |
soma_probability |
if >0.5 classified as soma |
is_dendrite |
==1 if ROI classified as dendrite, ==0 if not |
dendrite_probability |
if >0.5 classified as dendrite |
image_mask |
HxW sparse array defining image masks |
If you want to work with neural activity from somas, use only ROIs that pass the is_soma classification.
image_segmentation = nwbfile.processing["processed"].data_interfaces["image_segmentation"].plane_segmentations["roi_table"].to_dataframe()
image_segmentation.head()
| is_soma | soma_probability | is_dendrite | dendrite_probability | image_mask | |
|---|---|---|---|---|---|
| id | |||||
| 0 | 1 | 0.886588 | 0 | 0.000000e+00 | [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,... |
| 1 | 0 | 0.000000 | 0 | 8.818507e-05 | [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,... |
| 2 | 1 | 0.999333 | 0 | 0.000000e+00 | [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,... |
| 3 | 0 | 0.220683 | 0 | 0.000000e+00 | [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,... |
| 4 | 0 | 0.000644 | 0 | 5.960464e-08 | [[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,... |
Cell activity traces#
After the ROIs are extracted, the change in fluorescence over a baseline (dF/F) is calculated for each ROI. The dF/F is accessible as shown below.
The shape of dff is number of frames x number of rois.
dff = nwbfile.processing["processed"].data_interfaces["dff"].roi_response_series["dff"].data
print('dff shape (nframes, nrois):',np.shape(dff))
dff shape (nframes, nrois): (220344, 1214)
We can find the frame rate for these experiments:
frame_rate = nwbfile.imaging_planes["processed"].imaging_rate
print('Frame Rate:', frame_rate)
Frame Rate: 58.2634
Epoch Table#
The epoch table contains the start and stop times/frames for each stimulus epoch. You can use the epoch table with the dff array to pull and compare neural activity across different stimulus epochs.
Column |
Description |
|---|---|
stimulus_name |
descriptive name of the epoch |
start_frame |
epoch start(frames) |
stop_frame |
epoch end (frames) |
start_time |
epoch start (sec) |
stop_time |
epoch end (sec) |
epoch_table = nwbfile.intervals["epochs"].to_dataframe()
epoch_table
| stimulus_name | start_frame | stop_frame | start_time | stop_time | |
|---|---|---|---|---|---|
| id | |||||
| 0 | spont | 0 | 2399 | 0.000000 | 41.175077 |
| 1 | photostim | 2400 | 43793 | 41.192241 | 751.638250 |
| 2 | spont_01 | 43794 | 49519 | 751.655413 | 849.916071 |
| 3 | BCI | 49520 | 89755 | 849.933234 | 1540.503987 |
| 4 | photostim_post | 89756 | 220343 | 1540.521150 | 3781.842460 |
Session structure is variable!
The epoch order and structure is variable across sessions. Sometimes the spontaneous epoch occurs before photostimulation and sometimes there are repeats of the same epoch type. Check the epoch table before working with the session data.
Photostimulation Table#
During the “photostim” epochs, single neurons were optogenetically activated using 2p photostimulation to probe the functional connectivity in the network.
The PhotostimTrials table (stimulus>PhotostimTrials) contains information about the photostimulation trials.
Column |
Description |
|---|---|
start_time |
stimulus start (s) |
stop_time |
stimulus end (s) |
start_frame |
stimulus start (frame) |
stop_frame |
stimulus end (frame) |
tiff_file |
data source file name |
stimulus_name |
stimulus name |
laser_x |
x coordinate of stimulated neuron (pixels) |
laser_y |
y coordinate of stimulated neuron (pixels) |
power |
stimulus intensity (mW) |
duration |
trial duration (s) |
stimulus_function |
stimulus template |
group_index |
number identifier for stimulated neuron(s) |
closest_roi |
index in dff that corresponds to the photostimulated neuron |
photostim = nwbfile.stimulus["PhotostimTrials"].to_dataframe()
photostim.head()
| start_time | stop_time | start_frame | stop_frame | tiff_file | stimulus_name | laser_x | laser_y | power | duration | stimulus_function | group_index | closest_roi | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||
| 0 | 41.192241 | 42.307864 | 2400 | 2465 | before_exp_slm_00001.tif | photostim | 336.5 | 189.5 | 4 | 0.082 | scanimage.mroi.stimulusfunctions.logspiral | 27 | 631 |
| 1 | 42.325027 | 43.492141 | 2466 | 2534 | before_exp_slm_00002.tif | photostim | 53.5 | 60.5 | 4 | 0.082 | scanimage.mroi.stimulusfunctions.logspiral | 50 | 66 |
| 2 | 43.509304 | 44.624927 | 2535 | 2600 | before_exp_slm_00003.tif | photostim | 236.5 | 28.5 | 4 | 0.082 | scanimage.mroi.stimulusfunctions.logspiral | 6 | 99 |
| 3 | 44.642091 | 45.757714 | 2601 | 2666 | before_exp_slm_00004.tif | photostim | 370.5 | 24.5 | 4 | 0.082 | scanimage.mroi.stimulusfunctions.logspiral | 32 | 113 |
| 4 | 45.774878 | 46.890501 | 2667 | 2732 | before_exp_slm_00005.tif | photostim | 454.5 | 58.5 | 4 | 0.082 | scanimage.mroi.stimulusfunctions.logspiral | 31 | 1028 |
BCI Behavior Table#
During the “BCI” epochs, the mouse engaged in an optical brain-computer-interface task in which the activity of a single neuron (conditioned neuron) in the imaging plane is used to control the movement of a reward lickport towards the mouse’s face (zaber_steps_times). Once the lickport crosses a spatial threshold (threshold_crossing_times), a reward is delivered. The mouse has 10s to complete the trial before the lickport returns to its start position. After the reward is delivered, the mouse can consume the reward at any timepoint (reward_time).
*Important notes - zaber_steps_times that occurred after the threshold_crossing_time in the data were misreported due to a technical bug. After reaching the threshold, the lickport still tried to move but did not actually move. Additionally, the hit column unreliably reports the hit rate, so disregard this information.
Information about each BCI behavior trial can be found in the intervals > trials table.
Column |
Description |
|---|---|
start_time |
trial start (sec) |
stop_time |
trial end (sec) |
go_cue |
time of auditory go cue relative to start time (sec) |
hit* |
boolean of whether trial was hit |
lick_l |
lick times (sec) |
reward_time |
reward delivery time (sec) |
threshold_crossing_times |
time when reward port crossed position threshold (sec) |
zaber_steps_times |
times when lickport moved |
tiff_file |
data source file |
start_frame |
trial start (frame) |
stop_frame |
trial end (frame) |
conditioned_neuron_x |
coordinate for conditioned neuron (pixels) |
conditioned_neuron_y |
coordinate for conditioned neuron (pixels) |
closest_roi |
index in dff that corresponds to the conditioned neuron |
bci = nwbfile.stimulus["Trials"].to_dataframe()
bci.head()
| start_time | stop_time | go_cue | hit | lick_L | reward_time | threshold_crossing_times | zaber_step_times | tiff_file | start_frame | stop_frame | conditioned_neuron_x | conditioned_neuron_y | closest_roi | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | ||||||||||||||
| 0 | 849.933234 | 864.247538 | 0.2359 | True | [5.2544, 5.4253, 5.5753, 5.682, 5.793, 5.93950... | 5.2544 | 5.1198 | [3.6918, 4.5068, 4.7348, 4.8018, 4.8368, 4.868... | neuron46_00001.tif | 49520 | 50354 | 56.5 | 112.5 | 38 |
| 1 | 864.264701 | 878.046939 | 0.2359 | True | [6.526000000000001, 6.6604, 6.7766, 6.9938, 7.... | 6.5260 | 6.4260 | [0.5134000000000001, 0.6654, 1.1373, 1.4703000... | neuron46_00002.tif | 50355 | 51158 | 56.5 | 112.5 | 38 |
| 2 | 878.064102 | 884.672024 | 0.2359 | True | [1.0756, 1.1934, 1.3018, 1.4094, 1.5212, 1.641... | 1.0756 | 0.8314 | [0.2868, 0.3497, 0.4128, 0.4757, 0.5298, 0.572... | neuron46_00003.tif | 51159 | 51544 | 56.5 | 112.5 | 38 |
| 3 | 884.689187 | 889.786727 | 0.2359 | True | [1.7614, 1.9537, 2.0812, 2.3348, 2.4525, 2.563... | 1.9537 | 1.8503 | [0.7273000000000001, 0.8013, 0.862300000000000... | neuron46_00004.tif | 51545 | 51842 | 56.5 | 112.5 | 38 |
| 4 | 889.803891 | 894.935757 | 0.2359 | True | [1.8142, 1.9819, 2.142, 2.4023000000000003, 2.... | 1.9819 | 1.9437 | [0.2868, 0.3557, 0.4288, 0.4937, 0.7277, 0.817... | neuron46_00005.tif | 51843 | 52142 | 56.5 | 112.5 | 38 |