function [] = exp_felsheim2024(varargin)
%exp_felsheim2024 Reproduce figures from Felsheim and Dietz (2024)
%
% Usage: exp_felsheim2024(flag)
%
% EXP_FELSHEIM2024(flag) reproduces the figures from Felsheim and Dietz (2024).
%
% The following flags can be specified:
%
% 'fig5a' The I_{50} (current amplitude at 50% firing probability) for biphasic pulses with
% different phase durations is shown, normalized on the I_{60} of the pulse with
% a 100-mu s phase duration. The figure shows both the predictions by the aLIFP model
% and the data recorded by Shepherd and Javel (1999) from a single cat auditory
% nerve fiber.
%
% 'fig5b' The spike probability for different amplitdues for mono- and biphasic pulses
% with a phase duration 100 mu s and 0 mu s IPG are shown. The figure shows both the
% predictions by the aLIFP model and the data recorded by Shepherd and Javel (1999)
% from a single cat auditory nerve fiber.
%
% 'fig5cd' The spike latency (c) and the spike jitter (d) of monophasic pulses are shown.
% The data is shown togehter with the predictions of the aLIPF model and was
% collected by Miller et al. (1999), who used monophasic pulses with a duration
% of 40 mu s and without IPG to stimulate a single cat auditory nerve fiber.
%
% 'fig6ab' The mean I_50 (a) and relative spread (b) during the refractory period recorded
% with monophasic pulses with a length of 40 mu s in 34 cat nerve fibers
% (Miller et al., 2001) and a length of 100 mu s in 8 cat nerve fibers by
% (Dynes, 1996), together with the predictions made by the aLIFP.
%
% 'fig6cd' In (c), the data by Dynes (1996) for facilitation by a single sub-threshold
% pulse is shown, again, together with the predictions by both models and in (d),
% the same is shown for four and 40 sub-threshold masker. Dynes (1996) used
% monophasic pulses with a duration of 100 mu s.
%
% 'fig6e' The spike rate adaptation for different pulse rates and amplitudes, as measured
% by Zhang et al. (2007). Our model was fitted at two amplitudes for each of the
% pulse rates 250 pps, 1000 pps, and 5000 pps. The low amplitude was always
% 1.4 dB re I_{50}. For 250 pps and 1000 pps the high amplitude was 3.1 dB re I_{50}
% and for 5000 pps the high amplitude was 4.0 dB re I_{50}. Note the slightly higher
% second amplitude for 5000 pps, which was used according to the data. .
%
% 'fig7' The decrease in threshold due to facilitation is measured by Cartee et al. (2000)
% by dividing the I_{50} of two pseudo-monophasic pulses by the I_{50} of a single of
% these pulses. The data was collected by Cartee et al. (2000) using 38 cat nerve
% fibers and is compared to predictions of the aLIFP model.
%
% 'fig8' Facilitation and accommodation as calculated from spike trains by
% Heffer et al. (2010). Negative values indicate accommodation, while positive
% ones denote facilitation. The predictions from the aLIFP model fit the data
% nicely, except for 2000 pps at low levels, where the aLIFP model overestimates
% the facilitation.
%
% 'fig9' Mean firing probability over level for four 100 mu s pulse trains with different
% pulse rates. The x-axes give the amplitude in dB re I_{50} at 100 pps.
% The data were collected on a single cat nerve fiber by Javel (1990) and
% simulated using the aLIFP model.
%
% 'fig10' Data collected by Miller et al. (2011), who measured the impact of a low
% amplitude stimulus on a subsequent probe in 48 cat nerve fibers. The amplitudes
% were placed around the spike threshold for the masker, defined as the level
% where firing just started.
%
% 'fig11' The vector strength from Miller et al. (2008), Hartmann and Klinke (1990) and
% Dynes and Delgutte (1992) for different pulse rates measured in cats. As the
% were made both for duration of the IPG was not clear, the predictions an IPG
% of 0 mu s and 30 mu s. The predictions of the aLIFP model are not affected
% by the IPG and it fits the data for both pulse shapes up to 1600 pps.
%
% 'fig12' The spike rate and the vector strength of 100 Hz sinusoidally amplitude
% modulated and unmodulated pulse trains with a rate of 5000 pps were calculated.
% The predictions the aLIFP model were compared to data of 72 cat nerve fibers
% from Hu et al. (2010).
%
% 'figA1' The vector strength obtained from the predictions of the aLIFP model for different
% IPG durations and two amplitudes: 1 dB and 2 dB relative to single pulse I_{50}.
% The left plot shows the predictions for a 2500 pps stimulus and the right one
% for a 5000 pps stimulus, both with a duration of 0.3 s. For the 5000 pps
% stimulus the calculations could only reasonably be performed up to 50 mu s IPG,
% because for higher IPG values the gap between the pulses would have been
% shorter than the IPG.
%
% 'redo' Force recalculation of the data. Cached data are shown per default.
%
%
% Examples:
% ---------
%
% To display Fig. 5a use :
%
% exp_felsheim2024('fig5a');
%
% To display Fig. 5b use :
%
% exp_felsheim2024('fig5b');
%
% To display Fig. 5cd use :
%
% exp_felsheim2024('fig5cd');
%
% To display Fig. 6ab use :
%
% exp_felsheim2024('fig6ab');
%
% To display Fig. 6cd use :
%
% exp_felsheim2024('fig6cd');
%
% To display Fig. 6e use :
%
% exp_felsheim2024('fig6e');
%
% To display Fig. 7 use :
%
% exp_felsheim2024('fig7');
%
% To display Fig. 8 use :
%
% exp_felsheim2024('fig8');
%
% To display Fig. 9 use :
%
% exp_felsheim2024('fig9');
%
% To display Fig. 10 use :
%
% exp_felsheim2024('fig10');
%
% To display Fig. 11 use :
%
% exp_felsheim2024('fig11');
%
% To display Fig. 12 use :
%
% exp_felsheim2024('fig12');
%
%
% See also: felsheim2024 demo_felsheim2024 plot_felsheim2024
%
% References:
% R. C. Felsheim and M. Dietz. An adaptive leaky integrate and firing
% probability model of an electrically stimulated auditory nerve fiber.
% Trends in Heaaring, 2024. submitted.
%
%
% Url: http://amtoolbox.org/amt-1.6.0/doc/experiments/exp_felsheim2024.php
% #Author: Rebecca C. Felsheim (2024): Original implementation.
% #Author: Piotr Majdak (2024): Adaptations for AMT 1.6.
% #Requirements: M-Stats
% This file is licensed unter the GNU General Public License (GPL) either
% version 3 of the license, or any later version as published by the Free Software
% Foundation. Details of the GPLv3 can be found in the AMT directory "licences" and
% at <https://www.gnu.org/licenses/gpl-3.0.html>.
% You can redistribute this file and/or modify it under the terms of the GPLv3.
% This file is distributed without any warranty; without even the implied warranty
% of merchantability or fitness for a particular purpose.
definput.import={'amt_cache'};
definput.flags.type = {'missingflag','fig5a','fig5b','fig5cd', 'fig6ab', 'fig6cd', 'fig6e', ...
'fig7', 'fig8', 'fig9', 'fig10', 'fig11', 'fig12', 'figA1'}; % to check: fig11, fig12
definput.keyvals.FontSize = 9;
definput.keyvals.MarkerSize = 3;
flags = ltfatarghelper({'FontSize','MarkerSize'},definput,varargin);
if flags.do_missingflag
flagnames=[sprintf('%s, ',definput.flags.type{2:end-2}),...
sprintf('%s or %s',definput.flags.type{end-1},definput.flags.type{end})];
error('%s: You must specify one of the following flags: %s.',upper(mfilename),flagnames);
end
colors = [0 0.4470 0.7410; ... %"#0072BD"
0.8500 0.3250 0.0980; ... %[0.8500 0.3250 0.0980]
0.9290 0.6940 0.1250; ... %"#EDB120"
0.4940 0.1840 0.5560; ... %"#7E2F8E"
0.4660 0.6740 0.1880; ... %"#77AC30"
0.3010 0.7450 0.9330; ... %"#4DBEEE"
0.6350 0.0780 0.1840; ... %"#A2142F"
0 0 0; ... %"#000000"
0 0 1; ... %"#0000FF"
1 0 1]; %"#FF00FF"
%% Figure 5a: Shepherd and Javel (1999), stimulation threshold (I_50) over phase length
if flags.do_fig5a
measured_data = amt_load('felsheim2024', 'Shepherd_Javel_1999_fig5.mat');
measured_data = measured_data.data;
amplitudes = (0.05:0.01:1.5) * 1e-3;
phase_lengths = 50:10:200; % in microseconds
data_I50 = nan(5,1);
aLIFP_I50 = nan(size(phase_lengths));
for ind = 1:7
curr_x = measured_data(:, (ind * 2 - 1));
curr_data = measured_data(:, (ind * 2));
curr_x = 10 .^ (curr_x / 20) * 1e-3;
curr_data = curr_data(~isnan(curr_x));
curr_x = curr_x(~isnan(curr_x));
[m, ~] = local_fitgaussian(curr_x, curr_data);
data_I50(ind) = m;
end
for ind = 1:length(phase_lengths)
pulse = [zeros(10,1); -1 * ones(phase_lengths(ind), 1);0; ones(phase_lengths(ind),1); zeros(100,1)];
aLIFP_I50(ind) = local_getthreshold(pulse, amplitudes, 0.5);
end
figure("Name", "Figure 5a", "DefaultAxesFontsize", 13);
plot([60, 80, 100, 120, 140, 160, 180], data_I50 / data_I50(3), 'x', 'MarkerSize', 10, 'LineWidth',2)
hold on
[~,m] = min(abs(phase_lengths - 100)); % I_50 is shown relative to the I_50 at 100 microseonds phase length
plot(phase_lengths, aLIFP_I50 / aLIFP_I50(m), 'LineWidth', 2, 'color', [0 0.4470 0.7410]);
grid on;
ylabel("I_{50} / I_{50} at 100 \mus");
xlabel("Phase length (\mus)");
legend("data", "aLIFP")
end
%% Figure 5b: Shepherd and Javel (1999), spike probability for monophasic and biphasic pulses
if flags.do_fig5b
measured_data = amt_load('felsheim2024', 'Shepherd_Javel_1999_fig4.mat');
measured_data = measured_data.data;
pulse_monophasic = [zeros(10,1); -1 * ones(100, 1); zeros(100,1)];
pulse_biphasic = [zeros(10,1); -1* ones(100, 1); ones(100,1); zeros(100,1)];
x_monophasic = 10 .^ (measured_data(:,5) / 20) * 1e-3;
x_biphasic = 10 .^ (measured_data(:,7) / 20) * 1e-3;
data_monophasic = measured_data(~isnan(x_monophasic),6);
x_monophasic(isnan(x_monophasic)) = [];
data_biphasic = measured_data(~isnan(x_biphasic), 8);
x_biphasic(isnan(x_biphasic)) = [];
[monophasic_I50, ~] = local_fitgaussian(x_monophasic, data_monophasic);
x_monophasic = 20 * log10(x_monophasic / monophasic_I50);
x_biphasic = 20 * log10(x_biphasic / monophasic_I50);
% compute the spike probabilities for the bi- and monophasic pulse
amplitudes = 0.5:0.01:.8;
aLIFP_probabilities_monophasic = zeros(size(amplitudes));
aLIFP_probabilities_biphasic = zeros(size(amplitudes));
for a_ind = 1:length(amplitudes)
out_struct = felsheim2024(pulse_monophasic * amplitudes(a_ind) * 1e-3);
aLIFP_probabilities_monophasic(a_ind) = out_struct(1).total_probability;
out_struct = felsheim2024(pulse_biphasic * amplitudes(a_ind) * 1e-3);
aLIFP_probabilities_biphasic(a_ind) = out_struct(1).total_probability;
end
[monophasic_I50, ~] = local_fitgaussian(amplitudes, aLIFP_probabilities_monophasic);
amplitudes = 20 * log10(amplitudes / monophasic_I50);
% plot the results
figure("Name", "Figure 5b","DefaultAxesFontsize", 13);
plot(x_monophasic, data_monophasic, 'x', 'LineWidth',2, 'markersize', 10);
hold on;
plot(amplitudes, aLIFP_probabilities_monophasic, 'color', [0 0.4470 0.7410], 'LineWidth',2);
plot(x_biphasic, data_biphasic, 'x', 'color', [0.8500 0.3250 0.0980], 'LineWidth',2, 'markersize', 10)
plot(amplitudes, aLIFP_probabilities_biphasic, 'color', [0.8500 0.3250 0.0980], 'LineWidth',2);
xlim([-2,3])
ylim([0,1.01])
grid on
legend( "data monophasic", "aLIFP monophasic", "data biphasic", "aLIFP biphasic", 'location', 'southeast');
xlabel("Amplitude (dB re mono-phasic threshold)")
ylabel("Spiking probability P_s")
end
%% Figure 5c and d: Miller et al. (1999), Latency and Jitter in the response to a monophasic pulse
if flags.do_fig5cd
measured_data = amt_load('felsheim2024', 'Miller_1999_fig2.mat');
data = measured_data.data;
data_spiking_probability = measured_data.data_spiking_probability;
spiking_probability = data_spiking_probability(:,2) / 100;
data_amplitude = data(:,1);
[data_I50,~] = local_fitgaussian(data_amplitude, spiking_probability);
latency = data(:,2);
jitter = (data(:,3) -latency) * 2;
pulse = [zeros(100,1); -1 * ones(40,1); zeros(100,1)];
amplitudes = (1:0.01:1.4) * 1e-3;
modelled_latency = zeros(size(amplitudes));
modelled_jitter = zeros(size(amplitudes));
% run the aLIPF model
pulse_start = 101 / 1e6;
aLIFP_probabilties = zeros(size(amplitudes));
for a = 1:length(amplitudes)
out_struct = felsheim2024(pulse * amplitudes(a));
aLIFP_probabilties(a) = out_struct.total_probability;
modelled_latency(a) = out_struct(1).mu - pulse_start;
modelled_jitter(a) = out_struct(1).sigma;
end
[aLIFP_I50, ~] = local_fitgaussian(amplitudes, aLIFP_probabilties);
modelled_latency = modelled_latency * 1e3;
modelled_jitter = modelled_jitter * 1e6;
% plot the results
fig = figure("Name", "Figure 5c","DefaultAxesFontsize", 13);
fig.InnerPosition = [0 0 550, 450];
plot(data_amplitude / data_I50, latency, 'x', 'LineWidth',2, 'markersize', 10);
hold on
plot(amplitudes / aLIFP_I50, modelled_latency, 'LineWidth', 2, 'color', [0 0.4470 0.7410]);
legend("data", "aLIFP");
ylabel("Spike latency, \mu_{st}-t_{start} (ms)");
xlabel("Amplitude re I_{50}")
grid on;
xlim([0.89, 1.21])
ylim([0.5, 0.81])
fig2 = figure("Name", "Figure 5d","DefaultAxesFontsize", 13);
fig2.InnerPosition = [0 0 550, 450];
plot(data_amplitude / data_I50, jitter * 1e3, 'x', 'LineWidth',2, 'markersize', 10);
hold on
plot(amplitudes / aLIFP_I50, modelled_jitter, 'LineWidth', 2, 'color', [0 0.4470 0.7410]);
ylabel("Spike jitter, \sigma_{st} (\mus)");
legend("data", "aLIFP", 'location', 'southwest');
xlim([0.89, 1.21])
ylim([10, 135])
xlabel("Amplitude re I_{50}")
grid on;
end
%% Figure 6a and b: Miller et al. (2001) and Dynes (1996), change in stimulation threshold (I_50)
% and relative spread during the refractory period
if flags.do_fig6ab
measured_data = amt_load('felsheim2024', 'Miller_2001_fig7.mat');
measured_data = measured_data.data;
x = amt_load('felsheim2024', 'Dynes1996_refraction.mat');
dynes_threshold_change = 10.^(x.data_Dynes1996.thrDynes / 20);
dynes_ipi = x.data_Dynes1996.ipiDynes;
measured_ipi = measured_data(:,1);
measured_threshold_change = measured_data(:,2);
measured_relative_spread_change = measured_data(:,5);
unmasked_I50 = nan;
unmasked_relative_spread = nan;
masked_I50s = nan(size(measured_ipi));
masked_relative_spread = nan(size(measured_ipi));
amplitudes = [0, 0.05:0.05:20] * 1e-3;
masker_probe_intervals = [0, 300:100:2000, 2200:200:4000, 4500, 5000:1000:12000]';
phase_length = 100;
data = amt_cache('get', 'data_fig6ab', flags.cachemode);
if isempty(data)
% compute aLIPF predictions
for ind = 1:length(masker_probe_intervals)
lower_rs = nan;
upper_rs = nan;
curr_I50 = nan;
last_probability = 0;
all_probabilities = zeros(1,length(amplitudes));
for a_ind = 2:length(amplitudes)
if ind == 1
signal = [zeros(100, 1); -1 * ones(phase_length,1) * amplitudes(a_ind); zeros(100,1)];
else
signal = [zeros(100,1); -1 * ones(phase_length,1) * 3 * unmasked_I50; ...
zeros(masker_probe_intervals(ind) - 40, 1); -1 * ones(phase_length,1) * amplitudes(a_ind); ...
zeros(100,1)];
end
out_struct = felsheim2024(signal);
if out_struct(end).total_probability >= 0.16 && isnan(lower_rs)
m = (amplitudes(a_ind) - amplitudes(a_ind - 1)) / (out_struct(end).total_probability - last_probability);
lower_rs = m * (0.16 - last_probability) + amplitudes(a_ind - 1);
end
if out_struct(end).total_probability >= 0.5 && isnan(curr_I50)
m = (amplitudes(a_ind) - amplitudes(a_ind - 1)) / (out_struct(end).total_probability - last_probability);
curr_I50 = m * (0.5 - last_probability) + amplitudes(a_ind - 1);
end
if out_struct(end).total_probability >= 0.84 && isnan(upper_rs)
m = (amplitudes(a_ind) - amplitudes(a_ind - 1)) / (out_struct(end).total_probability - last_probability);
upper_rs = m * (0.84 - last_probability) + amplitudes(a_ind - 1);
break;
end
last_probability = out_struct(end).total_probability;
all_probabilities(a_ind) = last_probability;
end
curr_relative_spread = (upper_rs - lower_rs) /( 2 * curr_I50);
if ind == 1
unmasked_I50 = curr_I50;
unmasked_relative_spread = curr_relative_spread;
else
masked_I50s(ind - 1) = curr_I50 / unmasked_I50;
masked_relative_spread(ind - 1) = curr_relative_spread / unmasked_relative_spread;
end
end
data.masked_I50s = masked_I50s;
data.masked_relative_spread = masked_relative_spread;
amt_cache('set', 'data_fig6ab', data);
else
masked_I50s = data.masked_I50s;
masked_relative_spread = data.masked_relative_spread;
end
measured_ipi = measured_ipi / 1e3;
masker_probe_intervals = masker_probe_intervals / 1e3;
dynes_ipi = dynes_ipi / 1e3;
% plot the results
figure("Name", "Figure 6a","DefaultAxesFontsize", 13);
plot(measured_ipi, measured_threshold_change, 'x', 'MarkerSize',10, 'LineWidth',2);
hold on;
plot(dynes_ipi, dynes_threshold_change, 'o', 'color', [0 0.4470 0.7410], 'MarkerSize',10, 'LineWidth',2);
plot(masker_probe_intervals(2:end), masked_I50s, '-', 'LineWidth',2, 'color', [0 0.4470 0.7410]);
legend("Miller et al. (2001)", "Dynes (1996)", "aLIFP");
ylabel("I_{50} re Unmasked I_{50}");
xlabel("Masker-Pulse Interval (ms)")
grid on;
ylim([0.8,3])
xlim([0,12])
figure("Name", "Figure 6b","DefaultAxesFontsize", 13);
plot(measured_ipi, measured_relative_spread_change, 'x', 'MarkerSize',10, 'LineWidth',2);
hold on;
plot(masker_probe_intervals(2:end), masked_relative_spread, '-','LineWidth',2, 'Color', [0 0.4470 0.7410]);
legend("Miller et al. (2001)", "aLIFP");
ylabel("Relative spread re Unmasked relative spread");
xlabel("Masker-Pulse Interval (ms)")
grid on;
ylim([0.6,5])
xlim([0,12])
end
%% Figure 6c and d: Dynes (1996), Change in stimulation threshold (I_50) after stimulation with
% 1, 4, or 40 sub-threhsold masker (Facilitation)
if flags.do_fig6cd
measured_data = amt_load('felsheim2024', 'Dynes_1996_fig4-1.mat');
measured_data = measured_data.data;
phaseL = 100; % in microseconds
amplitudes = [0, 0.05:0.01:2] * 1e-3;
pulse = -1 * ones(phaseL,1);
single_pulse_I50 = local_getthreshold(pulse, amplitudes, 0.5);
masker_amplitudes = single_pulse_I50 * 0.89; % taken from the paper
masker = [1, 4, 40];
all_I50s = amt_cache('get', 'data_fig6cd', flags.cachemode);
if isempty(all_I50s)
all_I50s = cell(length(masker), 1);
for m_ind = 1:length(masker)
ipis = round(measured_data(:, (m_ind * 2) - 1) * 1e3);
ipis = ipis(~isnan(ipis));
curr_I50s = nan(size(ipis));
curr_maskers = [];
for tmp_ind = 1:masker(m_ind)
curr_maskers = [curr_maskers; zeros(1000 - phaseL,1); pulse * masker_amplitudes];
end
for ind = 1:length(ipis)
for a_ind = 2:length(amplitudes)
curr_signal = [curr_maskers; zeros(ipis(ind) - phaseL,1); pulse * amplitudes(a_ind) ;zeros(100,1)];
out_struct = felsheim2024(curr_signal);
if sum([out_struct(end).total_probability]) >= 0.5
m = (amplitudes(a_ind) - amplitudes(a_ind - 1)) / ...
(sum([out_struct(end).total_probability]) - last_probability);
curr_I50s(ind) = m * (0.5 - last_probability) + amplitudes(a_ind - 1);
curr_I50s(ind) = curr_I50s(ind) / single_pulse_I50;
break;
end
last_probability = [out_struct(end).total_probability];
curr_I50s(ind) = curr_I50s(ind) / single_pulse_I50;
end
end
all_I50s{m_ind} = curr_I50s;
end
amt_cache('set', 'data_fig6cd', all_I50s);
end
for m_ind = 1:length(masker)
ipis = round(measured_data(:, (m_ind * 2) - 1) * 1e3);
data = measured_data(:, m_ind * 2);
data = data(~isnan(ipis));
data = 10.^(data / 20);
ipis = ipis(~isnan(ipis));
curr_I50s = all_I50s{m_ind};
if masker(m_ind) == 1
fig = figure("Name", "Figure6c","DefaultAxesFontsize", 13);
elseif masker(m_ind) == 4
fig = figure("Name", "Figure6e","DefaultAxesFontsize", 13);
end
fig.OuterPosition = [0 0 600, 600];
hold on;
plot(ipis * 1e-3, data, 'x', 'MarkerSize',10, 'LineWidth',2, 'color', colors(m_ind,:));
plot(ipis * 1e-3, curr_I50s, '-', 'LineWidth', 2, 'color', colors(m_ind,:));
if masker(m_ind) == 1
legend("data, 1 masker", "aLIFP, 1 masker", "location", "southeast");
elseif masker(m_ind) == 40
legend("data, 4 masker", "aLIFP, 4 masker", "data, 40 masker", "aLIFP, 40 masker", "location", "southeast");
end
ylabel("I_{50} re Single Pulse I_{50}")
xlabel("Masker-Pulse Interval (ms)")
ylim([0.2, 1.3])
grid on
end
end
if flags.do_fig6e
data = amt_load('felsheim2024', 'Zhang_2007_fig2.mat');
data = data.data;
aLIFP_base_amplitude = 2.2e-3;
% these values have been fitted together with the data
lower_offset_dB = -1.7;
upper_offset_dB = 0.9;
pulse = [-1 * ones(40,1); ones(40,1)];
% calculate the stimulating amplitudes realtive to the fitted one
test_amplitudes = 1:0.05:2;
aLIFP_I50 = local_getthreshold(pulse, test_amplitudes * 1e-3, 0.5);
aLIFP_base_amplitude_dB = 20 * log10(aLIFP_base_amplitude / aLIFP_I50);
aLIFP_lower_amplitude = aLIFP_base_amplitude * 10.^(lower_offset_dB/20);
aLIFP_upper_amplitude = aLIFP_base_amplitude * 10.^(upper_offset_dB/20);
amplitudes = [aLIFP_lower_amplitude, aLIFP_lower_amplitude, aLIFP_lower_amplitude;...
aLIFP_base_amplitude, aLIFP_base_amplitude, aLIFP_upper_amplitude];
amplitudes_dB = [aLIFP_base_amplitude_dB + lower_offset_dB, aLIFP_base_amplitude_dB + lower_offset_dB, aLIFP_base_amplitude_dB + lower_offset_dB; ...
aLIFP_base_amplitude_dB, aLIFP_base_amplitude_dB, aLIFP_base_amplitude_dB + upper_offset_dB];
% indices to access the correct parts of the dataset, as not all data was used (do not change!)
data_inds = [4, 7, 9; 2, 5, 8];
x_bins = [0;4;12;24;36;48;100; 200; 300] * 1e-3;
length_x_bins = length(x_bins) - 1; % needed to allow for the parallel loop
pulse_rates = [250, 1000, 5000];
x_values = x_bins(2:end) - diff(x_bins)/2;
mean_spiking_probability = amt_cache('get', 'data_fig6e', flags.cachemode);
if isempty(mean_spiking_probability)
mean_spiking_probability = nan(length(pulse_rates), 2, length(x_bins)- 1);
% calculate the mean spiking probability
for p_ind = 1:length(pulse_rates)
stimulus = sig_pulsetrain(0.3, pulse_rates(p_ind), pulse);
for amp_ind = 1:2
[out_struct] = felsheim2024(stimulus * amplitudes(amp_ind,p_ind));
dist = felsheim2024_spikeprobability(out_struct, max(x_bins), 1e6, 'fast');
for ind = 1:length_x_bins
curr_dist = dist(round(x_bins(ind) * 1e6)+1:round(x_bins(ind + 1) * 1e6));
mean_spiking_probability(p_ind, amp_ind, ind) = sum(curr_dist) / (x_bins(ind + 1) - x_bins(ind));
end
end
end
amt_cache('set', 'data_fig6e', mean_spiking_probability);
end
%% plot the results
fig = figure("Name", "Figure6e", "DefaultAxesFontsize", 13);
fig.OuterPosition = [0 0 1200, 700];
ax=subplot(2,3,1); %t = tiledlayout(2,3, "padding", "none"); % use this when on Matlab 2019b or later
%t.TileIndexing = 'columnmajor'; % use this when on Matlab 2019b or later
for p_ind = 1:length(pulse_rates)
for amp_ind = 1:2
subplot(2,3,(p_ind-1)*2+amp_ind);%ax = nexttile;
hold on
plot(x_values, squeeze(mean_spiking_probability(p_ind, amp_ind, :)), 'o-', 'Linewidth', 2, 'Markersize', 8, "color", [0 0.4470 0.7410]);
plot(x_values, data(:, data_inds(amp_ind, p_ind)), 'xk', 'Linewidth', 2, 'markersize', 8);
xlabel("Time (s)")
ylabel("Spike rate (spike/s)");
title(sprintf("%d pps, %.1f dB re I_{50}", pulse_rates(p_ind), amplitudes_dB(amp_ind, p_ind)))
grid on
m = max([squeeze(mean_spiking_probability(p_ind, amp_ind, :));...
data(:, data_inds(amp_ind, p_ind))], [], 'all');
ylim([0, ceil(m / 100) * 100]);
end
end
leg = legend(ax, "aLIFP", "data", "orientation", "horizontal");
%leg.Layout.Tile = 'north'; % use this when on Matlab 2019b or later
end
%% Figure 7: Cartee et al. (2000), Facilitation with pseudo-monophasic pulses
if flags.do_fig7
x = amt_load('felsheim2024', 'Cartee2000.mat');
facilitation_Cartee2000 = x.facilitation_Cartee2000;
data_ipis = [100, 200, 300]; % in microseconds
ipis = 100:5:300;
phaseL = 50;
amplitudes = (0.01:0.01:2) * 1e-3;
I50s = nan(size(ipis));
for ind = 1:length(ipis)
pulse = [-1 * ones(phaseL,1);ones(ipis(ind)-phaseL,1)./((ipis(ind)-phaseL)/phaseL)];
double_pulse = [zeros(100,1);pulse;pulse;zeros(100,1)];
single_pulse_I50 = local_getthreshold(pulse, amplitudes, 0.5);
double_pulse_I50 = local_getthreshold(double_pulse, amplitudes, 0.5);
I50s(ind) = double_pulse_I50 / single_pulse_I50;
end
means(1) = mean(facilitation_Cartee2000.raw(1:15,2));
means(2) = mean(facilitation_Cartee2000.raw(16:30,2));
means(3) = mean(facilitation_Cartee2000.raw(31:43,2));
stds(1) = std(facilitation_Cartee2000.raw(1:15,2));
stds(2) = std(facilitation_Cartee2000.raw(16:30,2));
stds(3) = std(facilitation_Cartee2000.raw(31:43,2));
fig = figure("Name", "Figure7", "DefaultAxesFontsize", 13);
fig.OuterPosition = [0 0 600, 600];
errorbar(data_ipis, means, stds, 'xk', 'MarkerSize',10, 'LineWidth',2);
hold on;
plot(ipis, I50s, 'LineWidth', 2, "color", [0 0.4470 0.7410]);
legend("data", "aLIFP");
ylabel("I_{50} re Single pulse I_{50}")
xlabel("Masker-Pulse Interval (\mus)")
xlim([100, 300])
grid on
end
%% Figure 8, Heffer et al. (2010), Facilitation and accommodation with pulse trains
if flags.do_fig8
x = amt_load('felsheim2024', 'Heffer2010.mat');
data_Heffer2010 = x.data_Heffer2010;
signal_length = 2e-3;
stim_rates = [200, 1000, 2000, 5000];
% target probabilities to test
low_level_probabilities = (0.02:0.02:0.18);
medium_level_probabilities = (0.3:0.05:0.7);
high_level_probabilities = (0.75:0.025:0.95);
pulse = [-1 * ones(25,1); zeros(8,1); ones(25,1)];
amplitudes = (0.8:0.1:4) * 1e-3;
% get the corresponding amplitude values
low_levels = zeros(size(low_level_probabilities));
for ind = 1:length(low_level_probabilities)
low_levels(ind) = local_getthreshold(pulse, amplitudes, low_level_probabilities(ind));
end
medium_levels = zeros(size(medium_level_probabilities));
for ind = 1:length(medium_level_probabilities)
medium_levels(ind) = local_getthreshold(pulse, amplitudes, medium_level_probabilities(ind));
end
high_levels = zeros(size(high_level_probabilities));
for ind = 1:length(high_level_probabilities)
high_levels(ind) = local_getthreshold(pulse, amplitudes, high_level_probabilities(ind));
end
% calculate the model predictions
onset_probabilities_low = nan(length(stim_rates), length(low_levels));
predicted_probabilities_low = nan(length(stim_rates),length(low_levels));
onset_probabilities_medium = nan(length(stim_rates), length(medium_levels));
predicted_probabilities_medium = nan(length(stim_rates),length(medium_levels));
onset_probabilities_high = nan(length(stim_rates), length(high_levels));
predicted_probabilities_high = nan(length(stim_rates),length(high_levels));
for s_ind = 1:length(stim_rates)
[stimulus, num_pulses] = sig_pulsetrain(signal_length, stim_rates(s_ind), pulse);
for l_ind = 1:length(low_levels)
[out_struct] = felsheim2024(stimulus * low_levels(l_ind));
onset_probabilities_low(s_ind, l_ind) = min(1,sum([out_struct.total_probability]));
predicted_probabilities_low(s_ind, l_ind) = min(1,onset_probabilities_low(1, l_ind) * num_pulses);
end
for l_ind = 1:length(medium_levels)
[out_struct] = felsheim2024(stimulus * medium_levels(l_ind));
onset_probabilities_medium(s_ind, l_ind) = min(1,sum([out_struct.total_probability]));
predicted_probabilities_medium(s_ind, l_ind) = min(1,onset_probabilities_medium(1, l_ind) * num_pulses);
end
for l_ind = 1:length(high_levels)
[out_struct] = felsheim2024(stimulus * high_levels(l_ind));
onset_probabilities_high(s_ind, l_ind) = min(1,sum([out_struct.total_probability]));
predicted_probabilities_high(s_ind, l_ind) = min(1,onset_probabilities_high(1, l_ind) * num_pulses);
end
end
probability_change_low = onset_probabilities_low - predicted_probabilities_low;
probability_change_medium = onset_probabilities_medium - predicted_probabilities_medium;
probability_change_high = onset_probabilities_high - predicted_probabilities_high;
% plot the results
visual_off = 0.07;
fig = figure("Name", "Figure 8","DefaultAxesFontsize", 13);
fig.OuterPosition = [0 0 1500, 600];
subplot(1,3,1); %tiledlayout(1,3); % use this when on Matlab 2019b or later
%nexttile % use this when on Matlab 2019b or later
lower_error = data_Heffer2010(3).change(1:4,2) - data_Heffer2010(3).change(5:2:end,2);
upper_error = data_Heffer2010(3).change(6:2:end,2) - data_Heffer2010(3).change(1:4,2);
errorbar((1:4) - visual_off, data_Heffer2010(3).change(1:4,2), lower_error, upper_error , 'square', 'linewidth', 2, "color", "black");
hold on;
curr_lower = prctile(probability_change_high, 25, 2);
curr_upper = prctile(probability_change_high, 75, 2);
curr_mean = median(probability_change_high, 2);
errorbar((1:4) + visual_off, curr_mean, curr_mean - curr_lower, curr_upper - curr_mean, 'square', 'linewidth', 2, "color", [0 0.4470 0.7410]);
ylabel("Spike probability change");
xticks(1:4);
xticklabels(["200", "1000", "2000", "5000"]);
xlabel("Pulse rate (pps)");
title("High level");
ylim([-0.4,1]);
xlim([0.5, 4.5]);
yline(0);
subplot(1,3,2); % nexttile % use this when on Matlab 2019b or later
lower_error = data_Heffer2010(2).change(1:4,2) - data_Heffer2010(2).change(5:2:end,2);
upper_error = data_Heffer2010(2).change(6:2:end,2) - data_Heffer2010(2).change(1:4,2);
errorbar((1:4) - visual_off, data_Heffer2010(2).change(1:4,2), lower_error, upper_error , 'square', 'linewidth', 2, "color", "black");
hold on;
curr_lower = prctile(probability_change_medium, 25, 2);
curr_upper = prctile(probability_change_medium, 75, 2);
curr_mean = median(probability_change_medium, 2);
errorbar((1:4) + visual_off, curr_mean, curr_mean - curr_lower, curr_upper - curr_mean, 'square', 'linewidth', 2, "color", [0 0.4470 0.7410]);
ylabel("Spike probability change");
xticks(1:4);
xticklabels(["200", "1000", "2000", "5000"]);
xlabel("Pulse rate (pps)");
title("Medium level");
ylim([-0.4,1]);
xlim([0.5, 4.5]);
yline(0);
ax=subplot(1,3,3); %ax = nexttile; % use this when on Matlab 2019b or later
lower_error = data_Heffer2010(1).change(1:4,2) - data_Heffer2010(1).change(5:2:end,2);
upper_error = data_Heffer2010(1).change(6:2:end,2) - data_Heffer2010(1).change(1:4,2);
errorbar((1:4) - visual_off, data_Heffer2010(1).change(1:4,2), lower_error, upper_error , 'square', 'linewidth', 2, "color", "black");
hold on;
curr_lower = prctile(probability_change_low, 25, 2);
curr_upper = prctile(probability_change_low, 75, 2);
curr_mean = median(probability_change_low, 2);
errorbar((1:4) + visual_off, curr_mean, curr_mean - curr_lower, curr_upper - curr_mean, 'square', 'linewidth', 2, "color", [0 0.4470 0.7410]);
ylabel("Spike probability change");
xticks(1:4);
xticklabels(["200", "1000", "2000", "5000"]);
xlabel("Pulse rate (pps)");
title("Low level");
ylim([-0.4,1]);
xlim([0.5, 4.5]);
yline(0);
lg = legend(ax, "data", "aLIFP", 'numcolumns', 3);
%lg.Layout.Tile = "North"; % use this when on Matlab 2019b or later
end
%% Figure 9: Javel (1999), mean spike probability of pulse trains
if flags.do_fig9
x = amt_load('felsheim2024', 'Javel1990.mat');
data_Javel1990 = x.data_Javel1990;
pulse_rates = [100, 200, 400, 800];
pulse = [-1 * ones(50,1); zeros(0,1); ones(50,1)];
amplitudes = (1:0.1:3.2) * 1e-3;
a_length = length(amplitudes); % needed because of the parfor loop
mean_spiking_probability = amt_cache('get', 'data_fig9', flags.cachemode);
if isempty(mean_spiking_probability)
mean_spiking_probability = zeros(length(pulse_rates), length(amplitudes));
for p_ind = [1, 2, 3, 4]
stimulus = sig_pulsetrain(0.1, pulse_rates(p_ind), pulse);
for a_ind = 1:a_length
[out_struct] = felsheim2024(stimulus * amplitudes(a_ind));
mean_spiking_probability(p_ind, a_ind) = mean([out_struct.total_probability]);
end
end
amt_cache('set', 'data_fig9', mean_spiking_probability);
end
[I50_phaseL100, ~] = local_fitgaussian(amplitudes, mean_spiking_probability(1, :));
fig = figure("Name", "Figure 9","defaultaxesfontsize", 13);
fig.InnerPosition = [0 0 600, 550];
hold on;
for p_ind =[1,2,3,4]
if p_ind == 1
curr_data_amplitude = data_Javel1990.hundred(:,1);
curr_data = data_Javel1990.hundred(:,2);
elseif p_ind == 2
curr_data_amplitude = data_Javel1990.twoH(:,1);
curr_data = data_Javel1990.twoH(:,2);
elseif p_ind == 3
curr_data_amplitude = data_Javel1990.fourH(:,1);
curr_data = data_Javel1990.fourH(:,2);
else
curr_data_amplitude = data_Javel1990.eightH(:,1);
curr_data = data_Javel1990.eightH(:,2);
end
curr_data_amplitude = 10.^(curr_data_amplitude/20);
if p_ind == 1
[data_threshold,~] = local_fitgaussian(curr_data_amplitude, data_Javel1990.hundred(:,2));
end
plot(20 * log10(curr_data_amplitude / data_threshold), curr_data, 'x','color', colors(p_ind,:), 'MarkerSize',10, "LineWidth",2)
plot(20 * log10(amplitudes/I50_phaseL100), mean_spiking_probability(p_ind,:),'color', colors(p_ind,:), 'LineWidth', 1.5);
ylabel("Mean firing probability");
xlabel("Stimulus level (dB re 100 pps)")
xlim([-2,8])
grid on
end
legend("data 100 pps", "aLIFP 100 pps", ...
"data 200 pps", "aLIFP 200 pps", ...
"data 400 pps", "aLIFP 400 pps", ...
"data 800 pps", "aLIFP 800 pps", 'location', 'northoutside', ...
"orientation", "horizontal", 'NumColumns', 2, "fontsize", 13);
end
%% Figure 10: Miller et al (2011), accommodation with a 250 pps and 5000 pps masker
if flags.do_fig10
x = amt_load('felsheim2024', 'Miller_2011_fig2.mat');
accommodation_Miller2011 = x.accommodation_Miller2011;
masker_level = 0.9e-3;
probe_level = 1.1e-3; %1.05e-3;
test_levels_dB = -3:1:4; % in dB
test_levels = masker_level * 10 .^(test_levels_dB/20);
pulse = [-1 * ones(40,1); zeros(30,1); ones(40,1)];
data = amt_cache('get', 'data_fig10', flags.cachemode);
if isempty(data)
% probe definition
pps =100;
len = 0.250; % in seconds
probe = sig_pulsetrain(len, pps, pulse);
% 250 pps masker definition
pps = 250;
len = 0.2; % in seconds
[masker250, num_pulses250] = sig_pulsetrain(len, pps, pulse);
% 5000 pps masker definition
pps = 5000;
len = 0.2; % in seconds
[masker5000, num_pulses5000] = sig_pulsetrain(len, pps, pulse);
% no masker
[out_dist_probe] = felsheim2024(probe * probe_level);
unmasked_spike_probability = mean([out_dist_probe(:).total_probability]);
% compute response to 250pps masker
masker250_spike_probability = zeros(size(test_levels));
amt_disp(''); % start of volatile display
for l_ind = 1:length(test_levels)
amt_disp(['250-pps masker: Calculating for level ' num2str(test_levels(l_ind) * 1e3) ...
' (condition #' num2str(l_ind) ' of ' num2str(length(test_levels)) ')...'], 'volatile');
curr_stimulus = [masker250 * test_levels(l_ind); probe * probe_level];
out_dist = felsheim2024(curr_stimulus);
masker250_spike_probability(l_ind) = mean([out_dist(num_pulses250 + 1:end).total_probability]);
end
% compute response to 5000pps masker
masker5000_spike_probability = zeros(size(test_levels));
for l_ind = 1:length(test_levels)
amt_disp(['5000-pps masker: Calculating for level ' num2str(test_levels(l_ind) * 1e3) ...
' (condition #' num2str(l_ind) ' of ' num2str(length(test_levels)) ')...'], 'volatile');
curr_stimulus = [masker5000 * test_levels(l_ind); probe * probe_level];
out_dist = felsheim2024(curr_stimulus);
masker5000_spike_probability(l_ind) = mean([out_dist(num_pulses5000 + 1:end).total_probability]);
end
amt_disp(); %end of volatile display
masker5000_spike_probability = masker5000_spike_probability/unmasked_spike_probability;
masker250_spike_probability = masker250_spike_probability/unmasked_spike_probability;
data.masker5000_spike_probability = masker5000_spike_probability;
data.masker250_spike_probability = masker250_spike_probability;
amt_cache('set', 'data_fig10', data);
else
masker5000_spike_probability = data.masker5000_spike_probability;
masker250_spike_probability = data.masker250_spike_probability;
end
% plot the results
fig = figure("Name", "Figure10");
fig.Position = [0 0 1000, 400];
subplot(1,2,1); %tiledlayout(1,2, "Padding", "compact"); % use this when on Matlab 2019b or later
%nexttile % use this when on Matlab 2019b or later
scatter(accommodation_Miller2011.Masker250.Raw.MaskerLevel, accommodation_Miller2011.Masker250.Raw.Recovery, 'o', ...
'MarkerEdgeColor', [0.3555 0.5000 0.1797], 'MarkerEdgeAlpha', 0.5)
hold on
plot(accommodation_Miller2011.Masker250.Medians.MaskerLevel, accommodation_Miller2011.Masker250.Medians.Recovery, ...
"kx-", "MarkerSize", 8, "LineWidth",2);
plot(test_levels_dB, masker250_spike_probability, "o-", "color", [0 0.4470 0.7410], ...
"LineWidth", 2, "MarkerSize", 8 );
xlabel("Masker level (dB re thr)")
ylabel("Probe recorvery ratio");
title(sprintf("250 pps"))
grid on
ax=subplot(1,2,2); %ax = nexttile; % use this when on Matlab 2019b or later
scatter(accommodation_Miller2011.Masker5000.Raw.MaskerLevel, accommodation_Miller2011.Masker5000.Raw.Recovery, 'o', ...
'MarkerEdgeColor', [0.3555 0.5000 0.1797], 'MarkerEdgeAlpha', 0.5)
hold on;
plot(accommodation_Miller2011.Masker5000.Medians.MaskerLevel, accommodation_Miller2011.Masker5000.Medians.Recovery, ...
"kx-", "MarkerSize", 8, "LineWidth",2);
plot(test_levels_dB, masker5000_spike_probability, "o-", "color", [0 0.4470 0.7410], ...
"Linewidth", 2, "MarkerSize", 8 );
xlabel("Masker level (dB re thr)")
ylabel("Probe recorvery ratio");
title("5000 pps")
grid on
leg = legend(ax, "raw data", "median data", "aLIFP", "orientation", "horizontal");
%leg.Layout.Tile = 'north'; % use this when on Matlab 2019b or later
end
%% Figure 11: Miller et al. (2008), Hartmann and Klinke (1990), Dynes and Delgutte (1992), vector strength
if flags.do_fig11
x = amt_load('felsheim2024', 'vector_strength_data.mat');
temporal_coding_data = x.temporal_coding_data;
ipgs = [0, 30];
pulse_rates = [50, 100, 200, 400, 800, 1000, 1250, 1600, 2500, 5000];
vector_strength = amt_cache('get', 'data_fig11', flags.cachemode);
if isempty(vector_strength)
vector_strength = nan(length(ipgs), length(pulse_rates));
% obtain the aLIFP predictions
amt_disp(''); % start of volatile display
for i_ind = 1:length(ipgs)
pulse = [-1 * ones(40,1); zeros(ipgs(i_ind),1); ones(40,1)];
pulse_amplitude = local_getthreshold(pulse, (0.4:0.05:3)*1e-3, 0.9);
for p_ind = 1:length(pulse_rates)
amt_disp(['Calculating for IPG #' num2str(i_ind) ' of ' num2str(length(ipgs)) ...
' and pulse rate #' num2str(p_ind) ' of ' num2str(length(pulse_rates)) '...'], 'volatile');
pulse_train = sig_pulsetrain(0.3, pulse_rates(p_ind), pulse);
out_struct = felsheim2024(pulse_train * pulse_amplitude);
dist = felsheim2024_spikeprobability(out_struct, 0.3);
vector_strength(i_ind, p_ind) = local_vectorstrength(dist, pulse_rates(p_ind));
end
end
amt_disp(); %end of volatile display
amt_cache('set', 'data_fig11', vector_strength);
end
% plot the results
figure("Name", "Figure11");
legend_entries = "";
for i_ind = 1:length(ipgs)
semilogx(pulse_rates, vector_strength(i_ind,:),"o-", "Markersize", 8, 'LineWidth',2)
hold on;
legend_entries(i_ind) = sprintf("aLIFP %d \mus", ipgs(i_ind));
end
legend_entries(end + 1:end + 3) = ["Miller et al. 2008", "Hartmann and Klinke (1990)", "Dynes and Delgutte (1992)"];
errorbar([250, 1000, 5000], temporal_coding_data.Miller.avg, temporal_coding_data.Miller.std, 'xk', 'Markersize', 8, 'Linewidth', 1);
p = temporal_coding_data.HartmannKlinke.cis(:,1) - temporal_coding_data.HartmannKlinke.averages;
n = temporal_coding_data.HartmannKlinke.averages - temporal_coding_data.HartmannKlinke.cis(:,2);
errorbar(temporal_coding_data.HartmannKlinke.frqs, temporal_coding_data.HartmannKlinke.averages, ...
p, n, 'o', 'color', [0.1758 0.4922 0.1797], 'Markersize', 8, 'Linewidth', 1);
p = temporal_coding_data.DynesDelgutte.cis(:,1) - temporal_coding_data.DynesDelgutte.averages;
n = temporal_coding_data.DynesDelgutte.averages - temporal_coding_data.DynesDelgutte.cis(:,2);
errorbar(temporal_coding_data.DynesDelgutte.frq, temporal_coding_data.DynesDelgutte.averages, ...
p, n, 'd', 'color', [0.4922 0.1758 0.4883], 'Markersize', 8, 'Linewidth',1);
xticks([50, 100, 500, 1000, 5000]);
xlabel("Pulse rate (pps)");
ylabel("Vector Strength");
legend(legend_entries, "location", "southwest");
grid on;
ylim([0, 1.1]);
end
%% Figure 12: Hu et al. (2010), spike rate and vector strength for amplitude modulated stimuli
if flags.do_fig12
data = amt_load('felsheim2024', 'Hu_etal_2010_fig56.mat');
spike_rate_data = data.spike_rate_data;
vector_strength_data = data.vector_strength_data;
pulse = [0;-1 * ones(40,1); ones(40,1);0];
t = (1:ceil(0.4 * 1e6))' / 1e6;
modulation = (1 + 0.1 * sin(2 * pi * 100 * t ));
% amplitudes were found to match the mean spike rates in the rate groups:
% R1-80, R2-200, R3-350, R4-500];
amplitudes_modulated = [1.35, 1.6, 1.88 , 2.24] * 1e-3;
amplitudes_unmodulated = [1.43, 1.54, 1.72 , 1.95] * 1e-3;
fig = figure("Name", "Figure12","DefaultAxesFontSize", 13);
fig.Position = [0 0 800, 900];
ax=subplot(4,2,1); %ax = tiledlayout(4,2, "tilespacing", "tight", "padding", "tight"); % use this when on Matlab 2019b or later
time_steps = 0:0.05:0.4;
num_time_steps = length(time_steps) - 1;
x_values = time_steps(1:end - 1) + diff(time_steps)/2;
vector_strength = zeros([2, num_time_steps,length(amplitudes_modulated)]) ;
spike_rate = vector_strength;
data = amt_cache('get', 'data_fig12', flags.cachemode);
if isempty(data)
for modulated = [true, false]
if modulated
stimulus = [sig_pulsetrain(0.4, 5000, pulse, 1e6, modulation); zeros(100,1)];
amplitudes = amplitudes_modulated;
text = "Modulated";
m_ind = 1;
else
stimulus = [sig_pulsetrain(0.4, 5000, pulse); zeros(100, 1)];
amplitudes = amplitudes_unmodulated;
text = "Unmodulated";
m_ind = 2;
end
% compute the model response and obtain the vector strength and mean spike rate
amt_disp(''); % start of volatile display
parfor a_ind = 1:length(amplitudes)
amt_disp(['Calculating for ' text ' amplitude #' num2str(a_ind) ...
' of ' num2str(length(amplitudes)) '...'],'volatile');
[out_struct] = felsheim2024(stimulus * amplitudes(a_ind));
dist = felsheim2024_spikeprobability(out_struct, 0.41);
for t_ind = 1:(num_time_steps)
curr_dist = dist(round(time_steps(t_ind) * 1e6) + 1:round(time_steps(t_ind + 1) * 1e6));
spike_rate(m_ind, t_ind, a_ind) = sum(curr_dist) / 0.05;
if modulated
vs = local_vectorstrength(curr_dist, 100);
else
vs = local_vectorstrength(curr_dist, 5000);
end
vector_strength(m_ind, t_ind, a_ind) = vs;
end
end
amt_disp(); %end of volatile display
end
data.vector_strength = vector_strength;
data.spike_rate = spike_rate;
amt_cache('set', 'data_fig12', data);
else
vector_strength = data.vector_strength;
spike_rate = data.spike_rate;
end
for modulated = [true, false]
if modulated
off = 0;
color = [0 0.4470 0.7410];
m_ind = 1;
else
off = 1;
color = [0.8500 0.3250 0.0980];
m_ind = 2;
end
for a_ind = 1:length(amplitudes_unmodulated)
subplot(4,2,a_ind*2-1); %nexttile(a_ind * 2 - 1) % use this when on Matlab 2019b or later
plot(spike_rate_data(:,1), spike_rate_data(:,a_ind * 2 + off), "x", "MarkerSize", 8, "LineWidth", 2, "color", color);
hold on
plot(x_values * 1e3, spike_rate(m_ind,:,a_ind), "-o", "LineWidth", 2, "color", color);
ylabel("Spike rate (spike/s)")
xlabel("Time (ms)")
title(sprintf("Spike rate, R%d", a_ind))
m = max([spike_rate(:, a_ind); spike_rate_data(:,a_ind * 2 + off)], [],'all');
ylim([0,ceil(m/100)*100]);
grid on;
ax=subplot(4,2,a_ind*2); %ax = nexttile(a_ind * 2); % use this when on Matlab 2019b or later
plot(vector_strength_data(:,1), vector_strength_data(:, a_ind * 2 + off), "x", "MarkerSize", 8, "LineWidth", 2, "color", color);
hold on
plot(x_values * 1e3, vector_strength(m_ind,:, a_ind), "-o", "LineWidth", 2, "color", color)
ylabel("Vector Strength")
xlabel("Time (ms)")
title(sprintf("Vector Strength, R%d", a_ind))
ylim([0,1]);
grid on;
end
end
leg = legend(ax, "Data modulated", "aLIFP modulated", "Data unmodulated", "aLIFP unmodulated", "orientation", "horizontal");
%leg.Layout.Tile = 'north'; % use this when on Matlab 2019b or later
end
%% Figure A.1: vector strength for different inter-phase gap (IPG) values
if flags.do_figA1
pulse_rates = [2500, 5000];
amplitudes_dB = [1, 2];
duration = 0.3; % in seconds
num_amplitudes = length(amplitudes_dB); % needed for the parfor loop
vector_strength = amt_cache('get', 'data_figA1', flags.cachemode);
if isempty(vector_strength)
vector_strength = nan(2, length(amplitudes_dB), 7, 1);
for p_ind = 1:length(pulse_rates)
if pulse_rates(p_ind) == 5000
ipg = [0, 10, 20, 30, 40, 50];
else
ipg = [0, 10, 20, 30, 40, 50, 100];
end
parfor ipg_ind = 1:length(ipg)
pulse = [-1 * ones(40,1); zeros(ipg(ipg_ind),1); ones(40,1)];
aLIFP_I50 = local_getthreshold(pulse, (0.4:0.05:3)*1e-3, 0.5);
pulse_train = sig_pulsetrain(duration, pulse_rates(p_ind), pulse);
for a_ind = 1:num_amplitudes
curr_amplitude = aLIFP_I50 * 10.^(amplitudes_dB(a_ind)/20);
out_struct = felsheim2024(pulse_train * curr_amplitude);
dist = felsheim2024_spikeprobability(out_struct, duration);
vector_strength(p_ind, a_ind, ipg_ind) = local_vectorstrength(dist, pulse_rates(p_ind));
end
end
end
amt_cache('set', 'data_figA1', vector_strength);
end
fig = figure("Name", "Figure A.1", "DefaultAxesFontSize", 13);
fig.Position = [0 0 1100, 550];
subplot(1,2,1); %tiledlayout(1,2); % use this when on Matlab 2019b or later
%nexttile % use this when on Matlab 2019b or later
hold on;
alpha = [1, 0.6, 0.4, 0.2];
markers = ["o-", "--d", "-.x", ":s"];
ipg = [0, 10, 20, 30, 40, 50, 100];
labels = strings(length(amplitudes_dB),1);
for a_ind = 1:length(amplitudes_dB)
plot(ipg, squeeze(vector_strength(1, a_ind,:)), [markers(a_ind)], "LineWidth", 2, "color", [0, 0.4471, 0.7412, alpha(a_ind)])
labels(a_ind) = sprintf("aLIFP %g dB re I_{50}", amplitudes_dB(a_ind));
end
title("2500 pps")
ylabel("Vector Strength")
xlabel("IPG (\mus)")
ylim([0,1])
grid on
ax=subplot(1,2,2); %ax = nexttile; % use this when on Matlab 2019b or later
hold on;
for a_ind = 1:length(amplitudes_dB)
plot(ipg, squeeze(vector_strength(2, a_ind,:)), [markers(a_ind)], "LineWidth", 2, "color", [0, 0.4471, 0.7412, alpha(a_ind)])
end
title("5000 pps")
leg = legend(ax, labels, 'orientation', 'horizontal', "Location", "northoutside");
leg.Layout.Tile = 'north';
ylabel("Vector Strength")
xlabel("IPG (\mus)")
ylim([0,1])
grid on
end
function [vs] = local_vectorstrength(distribution, pulse_rate)
T = 1/pulse_rate * 1e6;
sample_step = 1;
time_points = 1:sample_step:length(distribution);
probabilities = distribution(time_points);
theta = 2 * pi * mod(time_points, T) / T;
x = cos(theta) .* probabilities;
y = sin(theta) .* probabilities;
vs = sqrt( sum(x).^2 + sum(y).^2) / sum(probabilities);
function [threshold] = local_getthreshold(signal, amplitudes, target_probability)
last_probability = nan;
probabilities = nan(size(amplitudes));
threshold = nan;
for a_ind = 1:length(amplitudes)
[out_dist] = felsheim2024(signal * amplitudes(a_ind));
curr_probability = sum([out_dist.total_probability]); % changed from mean
if curr_probability >= target_probability
if a_ind == 1
error("Too low amplitudes were given. Please start with a lower values.")
end
m = (amplitudes(a_ind) - amplitudes(a_ind - 1)) / (curr_probability - last_probability);
threshold = m * (target_probability - last_probability) + amplitudes(a_ind - 1);
break;
end
last_probability = curr_probability;
probabilities(a_ind) = curr_probability;
if curr_probability >= 1
break;
end
end
function [mean_out, std_out] = local_fitgaussian(amplitude, probabilities)
fit_function= @(params, data) normcdf(data, params(1), params(2));
probabilities = probabilities(~isnan(amplitude));
amplitude = amplitude(~isnan(amplitude));
options = optimset("Display", "off");
[out] = lsqcurvefit(fit_function, [median(amplitude), std(amplitude)], amplitude, probabilities, [], [], options);
mean_out = out(1);
std_out = out(2);