From 9273e0b29e1490a36fd815dba40069b118667054 Mon Sep 17 00:00:00 2001 From: Lawrence Date: Tue, 9 Feb 2021 11:12:34 -0500 Subject: [PATCH] Update 'last updated' date in basicUsage.html --- tutorials/basicUsage.mlx | Bin 94803 -> 94806 bytes tutorials/html/basicUsage.html | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorials/basicUsage.mlx b/tutorials/basicUsage.mlx index 144fbf7bff6dc0d8d4a484e6722c190eb4e24eab..06762ed253d46a4a7aa99c44283aafa2ad5fe30d 100644 GIT binary patch delta 5911 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

Using NWB Data

last updated: July 30, 2019
In this tutorial, we demonstrate the reading and usage of the NWB file produced in the File Conversion Tutorial. The output is a near-reproduction of Figure 1e from the Li et al publication, showing raster and peristimulus time histogram (PSTH) plots for neural recordings from anterior lateral motor cortex (ALM). This figure illustrates the main finding of the publication, showing the robustness of motor planning behavior and neural dynamics following short unilateral network silencing via optogenetic inhibition.

Reading NWB Files

NWB files can be read in using the nwbRead() function. This function returns a nwbfile object which is the in-memory representation of the NWB file structure.
nwb = nwbRead('out\ANM255201_20141124.nwb');

Constrained Sets

Analyzed data in NWB is placed under the analysis property, which is a Constrained Set. A constrained set consists of an arbitrary amount of key-value pairs similar to Map containers in MATLAB or a dictionary in Python. However, constrained sets also have the ability to validate their own properties closer to how a typed Object would.
You can get/set values in constrained sets using their respective .get()/.set() methods and retrieve all Set properties using the keys() method, like in a containers.Map.
unit_names = keys(nwb.analysis);

Dynamic Tables

nwb.intervals_trials returns a unique type of table called a Dynamic Table. Dynamic tables inherit from the NWB type types.hdmf_common.DynamicTable and allow for a table-like interface in NWB. In the case below, we grab the special column start_time. Dynamic Tables allow adding your own vectors using their vectordata and vectorindex properties, which are Constrained Sets. All columns are represented by either a types.hdmf_common.VectorData or a types.hdmf_common.VectorIndex type.

Data Stubs

The data property of the column id in nwb.units is a types.untyped.DataStub. This object is a representation of a dataset that is not loaded in memory, and is what allows MatNWB to lazily load its file data. To load the data into memory, use the .load() method which extracts all data from the NWB file. Alternatively, you can index into the DataStub directly using conventional MATLAB syntax.

Jagged Arrays in Dynamic Tables

With the new addition of addRow and getRow to Dynamic Tables, the concept of jagged arrays can be worked around and no longer require full understanding outside of specific data format concerns or low-level nwb tool development. The below paragraph is retained in its entirety from its original form as purely informational.
All data in a Dynamic Table must be aligned by row and column, but not all data fits into this paradigm neatly. In order to represent variable amounts of data that is localised to each row and column, NWB uses a concept called Jagged Arrays. These arrays consist of two column types: the familiar types.core.VectorData, and the new types.core.VectorIndex. A Vector Index holds no data, instead holding a reference to another Vector Data and a vector of indices that align to the Dynamic Table dimensions. The indices represent the last index boundary in the Vector Data object for the Vector Index row. As an example, an index of three in the first row of the Vector Index column points to the first three values in the referenced Vector Data column. Subsequently, if the next index were a five, it would indicate the fourth and fifth elements in the referenced Vector Data column.
The jagged arrays serve to represent multiple trials and spike times associated to each unit by id. A convenient way to represent these in MATLAB is to use Map containers where each unit's data is indexed directly by its unit id. Below, we utilize getRow in order to build the same Map.
unit_ids = nwb.units.id.data.load(); % array of unit ids represented within this
% Initialize trials & times Map containers indexed by unit_ids
unit_trials = containers.Map('KeyType',class(unit_ids),'ValueType','any');
unit_times = containers.Map('KeyType',class(unit_ids),'ValueType','any');
last_idx = 0;
for i = 1:length(unit_ids)
unit_id = unit_ids(i);
row = nwb.units.getRow(unit_id, 'useId', true, 'columns', {'spike_times', 'trials'});
unit_trials(unit_id) = row{2};
unit_times(unit_id) = row{1};
end

Process Units

We now do the following for each Unit:
  • Filter out invalid trials
  • Separate datasets based on resulting mouse behavior (right/left licks).
  • Derive "sample", "delay", and "response" times for this analyzed neuron.
  • Compose a peristimulus time histogram from the data.
sorted_ids = sort(unit_ids);
Photostim = struct(...
'ind', true,... % mask into xs and ys for this photostim
'period', 'none',...
'duration', 0,... % in seconds
'ramp_offset', 0); % in seconds
% Initialize Map container of plotting data for each unit, stored as structure
Unit = containers.Map('KeyType',class(unit_ids),'ValueType','any');
unit_struct = struct(...
'id', [],...
'xs', [],...
'ys', [],...
'xlim', [-Inf Inf],...
'sample', 0,...
'delay', 0,...
'response', 0,...
'left_scatter', false,...
'right_scatter', false,...
'photostim', Photostim); % can have multiple photostim
for unit_id = unit_ids'
We first extract trial IDs from the Unit IDs.
unit_trial_id = unit_trials(unit_id);
Then filter out outliers from the Sample, Delay, and Response time points with which we derive a "good enough" estimate.
trial = nwb.intervals_trials.getRow(unit_trial_id, 'useId', true,...
'columns', {'PoleInTime', 'PoleOutTime', 'CueTime', 'GoodTrials'});
unit_sample = trial{1};
unit_delay = trial{2};
unit_response = trial{3};
unit_good_trials = trial{4};
% Subjective parameters
delay_threshold = 0.064;
response_threshold = 0.43;
expected_delay_offset = 1.3; % determined from figure 1a
expected_response_offset = 1.3;
expected_delay = unit_sample + expected_delay_offset;
expected_response = unit_delay + expected_response_offset;
good_delay = (unit_delay > expected_delay - delay_threshold) &...
(unit_delay < expected_delay + delay_threshold);
good_response = (unit_response > expected_response - response_threshold) &...
(unit_response < expected_response + response_threshold);
avg_sample = mean(unit_sample(good_delay & good_response));
avg_delay = mean(unit_delay(good_delay & good_response));
avg_response = mean(unit_response(good_delay & good_response));
Filter the rest of the data by "good" trials.
unit_good_trials = unit_good_trials & good_delay & good_response;
unit_trial_id = unit_trial_id(unit_good_trials);
unit_spike_time = unit_times(unit_id);
unit_spike_time = unit_spike_time(unit_good_trials);
Map the Trial-aligned data to Unit-aligned data.
trial = nwb.intervals_trials.getRow(unit_trial_id, 'useId', true,...
'columns', {'start_time', 'HitR', 'HitL', 'StimTrials', 'PhotostimulationType'});
unit_start_time = trial{1};
unit_lick_right = logical(trial{2});
unit_lick_left = logical(trial{3});
unit_is_photostim = logical(trial{4});
unit_stim_type = trial{5};
unit_no_stim = ~unit_is_photostim & 0 == unit_stim_type;
unit_sample_stim = unit_is_photostim & 1 == unit_stim_type;
unit_early_stim = unit_is_photostim & 2 == unit_stim_type;
unit_middle_stim = unit_is_photostim & 3 == unit_stim_type;
Compose Scatter Plots and the Peristimulus Time Histogram zeroed on the Response time.
xs = unit_spike_time - unit_start_time - avg_response;
ys = unit_trial_id;
curr_unit = unit_struct;
curr_unit.xs = xs;
curr_unit.ys = ys;
curr_unit.left_scatter = unit_lick_left;
curr_unit.right_scatter = unit_lick_right;
curr_unit.sample = avg_sample - avg_response;
curr_unit.delay = avg_delay - avg_response;
curr_unit.response = 0;
% Photostim periods
curr_unit.photostim.ind = unit_no_stim;
% Sample
if any(unit_sample_stim)
SampleStim = Photostim;
SampleStim.ind = unit_sample_stim;
SampleStim.period = 'Sample';
SampleStim.duration = 0.5;
SampleStim.ramp_offset = 0.1;
curr_unit.photostim(end+1) = SampleStim;
end
% Early Delay
if any(unit_early_stim)
early_stim_types = unique(unit_stim_type(unit_early_stim));
for i_early_types=1:length(early_stim_types)
early_type = early_stim_types(i_early_types);
EarlyStim = Photostim;
EarlyStim.period = 'Early Delay';
EarlyStim.ind = early_type == unit_stim_type & unit_early_stim;
if early_type == 2
EarlyStim.duration = 0.5;
EarlyStim.ramp_offset = 0.1;
else
EarlyStim.duration = 0.8;
EarlyStim.ramp_offset = 0.2;
end
curr_unit.photostim(end+1) = EarlyStim;
end
end
% Middle Delay
if any(unit_middle_stim)
MiddleStim = Photostim;
MiddleStim.ind = unit_middle_stim;
MiddleStim.period = 'Middle Delay';
MiddleStim.duration = 0.5;
MiddleStim.ramp_offset = 0.1;
curr_unit.photostim(end+1) = MiddleStim;
end
Unit(unit_id) = curr_unit;
end

Plot Example Neurons

neuron_labels = [2, 3]; % neuron labels from Figure 1e
neuron_ids = [11, 2]; % neuron unit IDs corresponding to the Fig 1e labels
num_conditions = 4; % photostim conditions: nostim, sample, early, middle if applicable
num_neurons = length(neuron_ids);
% Inititalize data structures for each summary plot of categorized neural spike data at specified stimulus condition
RasterPlot = struct(...
'xs', 0,...
'ys', 0);
ConditionPlot = struct(...
'label', '',...
'xlim', 0,...
'sample', 0,...
'delay', 0,...
'response', 0,...
'right_scatter', RasterPlot,...
'left_scatter', RasterPlot,...
'psth_bin_window', 0,...
'stim_type', '');
fig = figure;
% Plot neural spike data for each neuron and stimulus condition in a subplot array: num_neurons (rows) x num_conditions (columns)
for nn=1:num_neurons
Neuron = Unit(neuron_ids(nn));
% Initialize structure with neural + stimulus condition data
CurrPlot = ConditionPlot;
CurrPlot.xlim = [min(Neuron.xs) max(Neuron.xs)];
CurrPlot.sample = Neuron.sample;
CurrPlot.delay = Neuron.delay;
CurrPlot.response = Neuron.response;
% Plot each neuron/condition
plot_row = (nn - 1) * num_conditions;
for cc=1:num_conditions
ax = subplot(num_neurons, num_conditions, plot_row + cc, 'Parent', fig);
Stim = Neuron.photostim(cc);
CurrPlot.stim_type = Stim.period;
if strcmp(Stim.period, 'none')
CurrPlot.label = sprintf('Neuron %d', neuron_labels(nn));
CurrPlot.psth_bin_window = 9;
else
CurrPlot.label = Stim.period;
CurrPlot.psth_bin_window = 2;
end
stim_left_scatter_ind = Stim.ind & Neuron.left_scatter;
stim_left_scatter_trials = Neuron.ys(stim_left_scatter_ind);
CurrPlot.left_scatter.xs = Neuron.xs(stim_left_scatter_ind);
[~,CurrPlot.left_scatter.ys] = ismember(stim_left_scatter_trials,unique(stim_left_scatter_trials));
stim_right_scatter_ind = Stim.ind & Neuron.right_scatter;
stim_right_scatter_trials = Neuron.ys(stim_right_scatter_ind);
CurrPlot.right_scatter.xs = Neuron.xs(stim_right_scatter_ind);
[~,CurrPlot.right_scatter.ys] = ismember(stim_right_scatter_trials,unique(stim_right_scatter_trials));
plot_condition(ax, CurrPlot);
end
end

Helper Functions

PSTH helper function
function [psth_xs, psth_ys] = calculate_psth(xs, bin_window, bin_width)
[bin_counts, edges] = histcounts(xs, 'BinWidth', bin_width);
psth_xs = edges(1:end-1) + (bin_width / 2);
moving_avg_b = (1/bin_window) * ones(1,bin_window);
psth_ys = filter(moving_avg_b, 1, bin_counts);
end
Plotter function for each stimulus condition
function plot_condition(ax, ConditionPlot)
left_cdata = [1 0 0]; % red
right_cdata = [0 0 1]; % blue
hist_margin = 50;
scatter_margin = 10;
% Calculate PSTH values
% moving average over 200 ms as per figure 1e
hist_bin_width = 0.2 / ConditionPlot.psth_bin_window;
[left_psth_xs, left_psth_ys] =...
calculate_psth(ConditionPlot.left_scatter.xs, ConditionPlot.psth_bin_window, hist_bin_width);
[right_psth_xs, right_psth_ys] =...
calculate_psth(ConditionPlot.right_scatter.xs, ConditionPlot.psth_bin_window, hist_bin_width);
right_scatter_offset = min(ConditionPlot.right_scatter.ys);
right_scatter_height = max(ConditionPlot.right_scatter.ys) - right_scatter_offset;
left_scatter_offset = min(ConditionPlot.left_scatter.ys);
left_scatter_height = max(ConditionPlot.left_scatter.ys) - left_scatter_offset;
psth_height = max([left_psth_ys right_psth_ys]);
left_y_offset = hist_margin...
+ psth_height...
- left_scatter_offset;
right_y_offset = scatter_margin...
+ left_y_offset...
+ left_scatter_offset...
+ left_scatter_height...
- right_scatter_offset;
subplot_height = right_y_offset...
+ right_scatter_offset...
+ right_scatter_height;
hold(ax, 'on');
% PSTH
plot(ax, left_psth_xs, left_psth_ys, 'Color', left_cdata);
plot(ax, right_psth_xs, right_psth_ys, 'Color', right_cdata);
% Scatter Plot
scatter(ax,...
ConditionPlot.left_scatter.xs,...
left_y_offset + ConditionPlot.left_scatter.ys,...
'Marker', '.',...
'CData', left_cdata,...
'SizeData', 1);
scatter(ax,...
ConditionPlot.right_scatter.xs,...
right_y_offset + ConditionPlot.right_scatter.ys,...
'Marker', '.',...
'CData', right_cdata,...
'SizeData', 1);
% sample, delay, response lines
line(ax, repmat(ConditionPlot.sample, 1, 2), [0 subplot_height],...
'Color', 'k', 'LineStyle', '--');
line(ax, repmat(ConditionPlot.delay, 1, 2), [0 subplot_height],...
'Color', 'k', 'LineStyle', '--');
line(ax, repmat(ConditionPlot.response, 1, 2), [0 subplot_height],...
'Color', 'k', 'LineStyle', '--');
% blue bar for photoinhibition period
if ~strcmp(ConditionPlot.stim_type, 'none')
stim_height = subplot_height;
stim_width = 0.5; % seconds
% end time relative to 'go' cue as described in the paper.
switch ConditionPlot.stim_type
case 'Sample'
end_offset = 1.6;
case 'Early Delay'
end_offset = 0.8;
case 'Middle Delay'
end_offset = 0.3;
otherwise
error('Invalid photostim period `%s`', ConditionPlot.stim_type);
end
stim_offset = ConditionPlot.response - stim_width - end_offset;
patch_vertices = [...
stim_offset, 0;...
stim_offset, stim_height;...
stim_offset+stim_width, stim_height;...
stim_offset+stim_width, 0];
patch(ax,...
'Faces', 1:4,...
'Vertices', patch_vertices,...
'FaceColor', '#B3D3EC',... % light blue shading
'EdgeColor', 'none',...
'FaceAlpha', 0.8);
end
title(ax, ConditionPlot.label);
xlabel(ax, 'Time (Seconds)');
ylabel(ax, 'Spikes s^{-1}')
xticks(ax, [-2 0 2]);
yticks(ax, [0 max(10, round(psth_height, -1))]);
% legend(ax, [scatter_left_plot, scatter_right_plot], {'Left Lick', 'Right Lick'},...
% 'location', 'northwestoutside');
ax.TickDir = 'out';
ax.XLim = ConditionPlot.xlim;
ax.YLim = [0 subplot_height];
hold(ax, 'off');
end
+.S12 { color: rgb(64, 64, 64); padding: 10px 0px 6px 17px; background: rgb(255, 255, 255) none repeat scroll 0% 0% / auto padding-box border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 14px; overflow-x: hidden; line-height: 17.234px; }

Using NWB Data

last updated: February 9, 2021
In this tutorial, we demonstrate the reading and usage of the NWB file produced in the File Conversion Tutorial. The output is a near-reproduction of Figure 1e from the Li et al publication, showing raster and peristimulus time histogram (PSTH) plots for neural recordings from anterior lateral motor cortex (ALM). This figure illustrates the main finding of the publication, showing the robustness of motor planning behavior and neural dynamics following short unilateral network silencing via optogenetic inhibition.

Reading NWB Files

NWB files can be read in using the nwbRead() function. This function returns a nwbfile object which is the in-memory representation of the NWB file structure.
nwb = nwbRead('out\ANM255201_20141124.nwb');

Constrained Sets

Analyzed data in NWB is placed under the analysis property, which is a Constrained Set. A constrained set consists of an arbitrary amount of key-value pairs similar to Map containers in MATLAB or a dictionary in Python. However, constrained sets also have the ability to validate their own properties closer to how a typed Object would.
You can get/set values in constrained sets using their respective .get()/.set() methods and retrieve all Set properties using the keys() method, like in a containers.Map.
unit_names = keys(nwb.analysis);

Dynamic Tables

nwb.intervals_trials returns a unique type of table called a Dynamic Table. Dynamic tables inherit from the NWB type types.hdmf_common.DynamicTable and allow for a table-like interface in NWB. In the case below, we grab the special column start_time. Dynamic Tables allow adding your own vectors using their vectordata and vectorindex properties, which are Constrained Sets. All columns are represented by either a types.hdmf_common.VectorData or a types.hdmf_common.VectorIndex type.

Data Stubs

The data property of the column id in nwb.units is a types.untyped.DataStub. This object is a representation of a dataset that is not loaded in memory, and is what allows MatNWB to lazily load its file data. To load the data into memory, use the .load() method which extracts all data from the NWB file. Alternatively, you can index into the DataStub directly using conventional MATLAB syntax.

Jagged Arrays in Dynamic Tables

With the new addition of addRow and getRow to Dynamic Tables, the concept of jagged arrays can be worked around and no longer require full understanding outside of specific data format concerns or low-level nwb tool development. The below paragraph is retained in its entirety from its original form as purely informational.
All data in a Dynamic Table must be aligned by row and column, but not all data fits into this paradigm neatly. In order to represent variable amounts of data that is localised to each row and column, NWB uses a concept called Jagged Arrays. These arrays consist of two column types: the familiar types.core.VectorData, and the new types.core.VectorIndex. A Vector Index holds no data, instead holding a reference to another Vector Data and a vector of indices that align to the Dynamic Table dimensions. The indices represent the last index boundary in the Vector Data object for the Vector Index row. As an example, an index of three in the first row of the Vector Index column points to the first three values in the referenced Vector Data column. Subsequently, if the next index were a five, it would indicate the fourth and fifth elements in the referenced Vector Data column.
The jagged arrays serve to represent multiple trials and spike times associated to each unit by id. A convenient way to represent these in MATLAB is to use Map containers where each unit's data is indexed directly by its unit id. Below, we utilize getRow in order to build the same Map.
unit_ids = nwb.units.id.data.load(); % array of unit ids represented within this
% Initialize trials & times Map containers indexed by unit_ids
unit_trials = containers.Map('KeyType',class(unit_ids),'ValueType','any');
unit_times = containers.Map('KeyType',class(unit_ids),'ValueType','any');
last_idx = 0;
for i = 1:length(unit_ids)
unit_id = unit_ids(i);
row = nwb.units.getRow(unit_id, 'useId', true, 'columns', {'spike_times', 'trials'});
unit_trials(unit_id) = row{2};
unit_times(unit_id) = row{1};
end

Process Units

We now do the following for each Unit:
  • Filter out invalid trials
  • Separate datasets based on resulting mouse behavior (right/left licks).
  • Derive "sample", "delay", and "response" times for this analyzed neuron.
  • Compose a peristimulus time histogram from the data.
sorted_ids = sort(unit_ids);
Photostim = struct(...
'ind', true,... % mask into xs and ys for this photostim
'period', 'none',...
'duration', 0,... % in seconds
'ramp_offset', 0); % in seconds
% Initialize Map container of plotting data for each unit, stored as structure
Unit = containers.Map('KeyType',class(unit_ids),'ValueType','any');
unit_struct = struct(...
'id', [],...
'xs', [],...
'ys', [],...
'xlim', [-Inf Inf],...
'sample', 0,...
'delay', 0,...
'response', 0,...
'left_scatter', false,...
'right_scatter', false,...
'photostim', Photostim); % can have multiple photostim
for unit_id = unit_ids'
We first extract trial IDs from the Unit IDs.
unit_trial_id = unit_trials(unit_id);
Then filter out outliers from the Sample, Delay, and Response time points with which we derive a "good enough" estimate.
trial = nwb.intervals_trials.getRow(unit_trial_id, 'useId', true,...
'columns', {'PoleInTime', 'PoleOutTime', 'CueTime', 'GoodTrials'});
unit_sample = trial{1};
unit_delay = trial{2};
unit_response = trial{3};
unit_good_trials = trial{4};
% Subjective parameters
delay_threshold = 0.064;
response_threshold = 0.43;
expected_delay_offset = 1.3; % determined from figure 1a
expected_response_offset = 1.3;
expected_delay = unit_sample + expected_delay_offset;
expected_response = unit_delay + expected_response_offset;
good_delay = (unit_delay > expected_delay - delay_threshold) &...
(unit_delay < expected_delay + delay_threshold);
good_response = (unit_response > expected_response - response_threshold) &...
(unit_response < expected_response + response_threshold);
avg_sample = mean(unit_sample(good_delay & good_response));
avg_delay = mean(unit_delay(good_delay & good_response));
avg_response = mean(unit_response(good_delay & good_response));
Filter the rest of the data by "good" trials.
unit_good_trials = unit_good_trials & good_delay & good_response;
unit_trial_id = unit_trial_id(unit_good_trials);
unit_spike_time = unit_times(unit_id);
unit_spike_time = unit_spike_time(unit_good_trials);
Map the Trial-aligned data to Unit-aligned data.
trial = nwb.intervals_trials.getRow(unit_trial_id, 'useId', true,...
'columns', {'start_time', 'HitR', 'HitL', 'StimTrials', 'PhotostimulationType'});
unit_start_time = trial{1};
unit_lick_right = logical(trial{2});
unit_lick_left = logical(trial{3});
unit_is_photostim = logical(trial{4});
unit_stim_type = trial{5};
unit_no_stim = ~unit_is_photostim & 0 == unit_stim_type;
unit_sample_stim = unit_is_photostim & 1 == unit_stim_type;
unit_early_stim = unit_is_photostim & 2 == unit_stim_type;
unit_middle_stim = unit_is_photostim & 3 == unit_stim_type;
Compose Scatter Plots and the Peristimulus Time Histogram zeroed on the Response time.
xs = unit_spike_time - unit_start_time - avg_response;
ys = unit_trial_id;
curr_unit = unit_struct;
curr_unit.xs = xs;
curr_unit.ys = ys;
curr_unit.left_scatter = unit_lick_left;
curr_unit.right_scatter = unit_lick_right;
curr_unit.sample = avg_sample - avg_response;
curr_unit.delay = avg_delay - avg_response;
curr_unit.response = 0;
% Photostim periods
curr_unit.photostim.ind = unit_no_stim;
% Sample
if any(unit_sample_stim)
SampleStim = Photostim;
SampleStim.ind = unit_sample_stim;
SampleStim.period = 'Sample';
SampleStim.duration = 0.5;
SampleStim.ramp_offset = 0.1;
curr_unit.photostim(end+1) = SampleStim;
end
% Early Delay
if any(unit_early_stim)
early_stim_types = unique(unit_stim_type(unit_early_stim));
for i_early_types=1:length(early_stim_types)
early_type = early_stim_types(i_early_types);
EarlyStim = Photostim;
EarlyStim.period = 'Early Delay';
EarlyStim.ind = early_type == unit_stim_type & unit_early_stim;
if early_type == 2
EarlyStim.duration = 0.5;
EarlyStim.ramp_offset = 0.1;
else
EarlyStim.duration = 0.8;
EarlyStim.ramp_offset = 0.2;
end
curr_unit.photostim(end+1) = EarlyStim;
end
end
% Middle Delay
if any(unit_middle_stim)
MiddleStim = Photostim;
MiddleStim.ind = unit_middle_stim;
MiddleStim.period = 'Middle Delay';
MiddleStim.duration = 0.5;
MiddleStim.ramp_offset = 0.1;
curr_unit.photostim(end+1) = MiddleStim;
end
Unit(unit_id) = curr_unit;
end

Plot Example Neurons

neuron_labels = [2, 3]; % neuron labels from Figure 1e
neuron_ids = [11, 2]; % neuron unit IDs corresponding to the Fig 1e labels
num_conditions = 4; % photostim conditions: nostim, sample, early, middle if applicable
num_neurons = length(neuron_ids);
% Inititalize data structures for each summary plot of categorized neural spike data at specified stimulus condition
RasterPlot = struct(...
'xs', 0,...
'ys', 0);
ConditionPlot = struct(...
'label', '',...
'xlim', 0,...
'sample', 0,...
'delay', 0,...
'response', 0,...
'right_scatter', RasterPlot,...
'left_scatter', RasterPlot,...
'psth_bin_window', 0,...
'stim_type', '');
fig = figure;
% Plot neural spike data for each neuron and stimulus condition in a subplot array: num_neurons (rows) x num_conditions (columns)
for nn=1:num_neurons
Neuron = Unit(neuron_ids(nn));
% Initialize structure with neural + stimulus condition data
CurrPlot = ConditionPlot;
CurrPlot.xlim = [min(Neuron.xs) max(Neuron.xs)];
CurrPlot.sample = Neuron.sample;
CurrPlot.delay = Neuron.delay;
CurrPlot.response = Neuron.response;
% Plot each neuron/condition
plot_row = (nn - 1) * num_conditions;
for cc=1:num_conditions
ax = subplot(num_neurons, num_conditions, plot_row + cc, 'Parent', fig);
Stim = Neuron.photostim(cc);
CurrPlot.stim_type = Stim.period;
if strcmp(Stim.period, 'none')
CurrPlot.label = sprintf('Neuron %d', neuron_labels(nn));
CurrPlot.psth_bin_window = 9;
else
CurrPlot.label = Stim.period;
CurrPlot.psth_bin_window = 2;
end
stim_left_scatter_ind = Stim.ind & Neuron.left_scatter;
stim_left_scatter_trials = Neuron.ys(stim_left_scatter_ind);
CurrPlot.left_scatter.xs = Neuron.xs(stim_left_scatter_ind);
[~,CurrPlot.left_scatter.ys] = ismember(stim_left_scatter_trials,unique(stim_left_scatter_trials));
stim_right_scatter_ind = Stim.ind & Neuron.right_scatter;
stim_right_scatter_trials = Neuron.ys(stim_right_scatter_ind);
CurrPlot.right_scatter.xs = Neuron.xs(stim_right_scatter_ind);
[~,CurrPlot.right_scatter.ys] = ismember(stim_right_scatter_trials,unique(stim_right_scatter_trials));
plot_condition(ax, CurrPlot);
end
end

Helper Functions

PSTH helper function
function [psth_xs, psth_ys] = calculate_psth(xs, bin_window, bin_width)
[bin_counts, edges] = histcounts(xs, 'BinWidth', bin_width);
psth_xs = edges(1:end-1) + (bin_width / 2);
moving_avg_b = (1/bin_window) * ones(1,bin_window);
psth_ys = filter(moving_avg_b, 1, bin_counts);
end
Plotter function for each stimulus condition
function plot_condition(ax, ConditionPlot)
left_cdata = [1 0 0]; % red
right_cdata = [0 0 1]; % blue
hist_margin = 50;
scatter_margin = 10;
% Calculate PSTH values
% moving average over 200 ms as per figure 1e
hist_bin_width = 0.2 / ConditionPlot.psth_bin_window;
[left_psth_xs, left_psth_ys] =...
calculate_psth(ConditionPlot.left_scatter.xs, ConditionPlot.psth_bin_window, hist_bin_width);
[right_psth_xs, right_psth_ys] =...
calculate_psth(ConditionPlot.right_scatter.xs, ConditionPlot.psth_bin_window, hist_bin_width);
right_scatter_offset = min(ConditionPlot.right_scatter.ys);
right_scatter_height = max(ConditionPlot.right_scatter.ys) - right_scatter_offset;
left_scatter_offset = min(ConditionPlot.left_scatter.ys);
left_scatter_height = max(ConditionPlot.left_scatter.ys) - left_scatter_offset;
psth_height = max([left_psth_ys right_psth_ys]);
left_y_offset = hist_margin...
+ psth_height...
- left_scatter_offset;
right_y_offset = scatter_margin...
+ left_y_offset...
+ left_scatter_offset...
+ left_scatter_height...
- right_scatter_offset;
subplot_height = right_y_offset...
+ right_scatter_offset...
+ right_scatter_height;
hold(ax, 'on');
% PSTH
plot(ax, left_psth_xs, left_psth_ys, 'Color', left_cdata);
plot(ax, right_psth_xs, right_psth_ys, 'Color', right_cdata);
% Scatter Plot
scatter(ax,...
ConditionPlot.left_scatter.xs,...
left_y_offset + ConditionPlot.left_scatter.ys,...
'Marker', '.',...
'CData', left_cdata,...
'SizeData', 1);
scatter(ax,...
ConditionPlot.right_scatter.xs,...
right_y_offset + ConditionPlot.right_scatter.ys,...
'Marker', '.',...
'CData', right_cdata,...
'SizeData', 1);
% sample, delay, response lines
line(ax, repmat(ConditionPlot.sample, 1, 2), [0 subplot_height],...
'Color', 'k', 'LineStyle', '--');
line(ax, repmat(ConditionPlot.delay, 1, 2), [0 subplot_height],...
'Color', 'k', 'LineStyle', '--');
line(ax, repmat(ConditionPlot.response, 1, 2), [0 subplot_height],...
'Color', 'k', 'LineStyle', '--');
% blue bar for photoinhibition period
if ~strcmp(ConditionPlot.stim_type, 'none')
stim_height = subplot_height;
stim_width = 0.5; % seconds
% end time relative to 'go' cue as described in the paper.
switch ConditionPlot.stim_type
case 'Sample'
end_offset = 1.6;
case 'Early Delay'
end_offset = 0.8;
case 'Middle Delay'
end_offset = 0.3;
otherwise
error('Invalid photostim period `%s`', ConditionPlot.stim_type);
end
stim_offset = ConditionPlot.response - stim_width - end_offset;
patch_vertices = [...
stim_offset, 0;...
stim_offset, stim_height;...
stim_offset+stim_width, stim_height;...
stim_offset+stim_width, 0];
patch(ax,...
'Faces', 1:4,...
'Vertices', patch_vertices,...
'FaceColor', '#B3D3EC',... % light blue shading
'EdgeColor', 'none',...
'FaceAlpha', 0.8);
end
title(ax, ConditionPlot.label);
xlabel(ax, 'Time (Seconds)');
ylabel(ax, 'Spikes s^{-1}')
xticks(ax, [-2 0 2]);
yticks(ax, [0 max(10, round(psth_height, -1))]);
% legend(ax, [scatter_left_plot, scatter_right_plot], {'Left Lick', 'Right Lick'},...
% 'location', 'northwestoutside');
ax.TickDir = 'out';
ax.XLim = ConditionPlot.xlim;
ax.YLim = [0 subplot_height];
hold(ax, 'off');
end