THE AUDITORY MODELING TOOLBOX

Applies to version: 1.6.0

View the help

Go to function

PLOT_HEERINGA2024
plots the output of data_heeringa2024

Program code:

function plot_heeringa2024(type,varargin)
%PLOT_HEERINGA2024 plots the output of data_heeringa2024
%
%   Usage:
%     plot_heeringa2024('waveform', curvedata, curveresp, curvesettings); 
%     plot_heeringa2024('waveform', curvedata, curveresp, curvesettings, animalID);
%     plot_heeringa2024('psth', andata, unit, TestLevel); 
%
%   This function plots the waveform of AN responses or peri-stimulus
%   histogram (PSTH) from AN responses. For more details,  
%   see https://datadryad.org/stash/downloads/file_stream/3070542.
%
%   PLOT_HEERINGA2024('waveform', ...) plots four panels: the unfiltered first data trace, 
%   the interspike interval (ISI) histogram, the first 300 spike waveforms, and 
%   the waveform average with the 95 percent confidence intervals. 
%   The ISI gives an indication whether a single or a multi unit
%   was recorded from. The waveform plots reveal if the spikes have 
%   a prepotential, i.e., they were recorded from AVCN neurons, instead of 
%   the auditory nerve. Also the click latency shows whether a single
%   unit was derived from AN or AVCN. The input parameters are:
%
%     curvedata     Structure with spike times and the information about 
%                   randomization of the trials. See exp.data.BF.curvedata 
%                   in https://datadryad.org/stash/downloads/file_stream/3070542 
%                   for detailed information.
%
%     curveresp     Cell matrix with a size of n x m, where n is the number of 
%                   different tone frequencies and m is the number of repetitions. 
%                   Each cell contains the (raw analogue data) voltage trace (in mV) 
%                   of the recording of its respective trial. Corresponding frequencies
%                   and sampling rate needs to be provided in curvesettings. 
%
%     curvesettings Structure with metadata of the recording. See exp.data.BF.curvesettings
%                   in https://datadryad.org/stash/downloads/file_stream/3070542 
%                   for detailed information. 
%
%     animalID      Optional string (e.g., with the animal ID) displayed in the panels.
%
%   The plot of the waveform is used in Fig. 4 in data_heeringa2024.
%
%   PLOT_HEERINGA2024('psth',...) plots a peri-stimulus time histogram (PSTH) 
%   of recordings above the TestLevel. The input parameters are:
%
%     andata    Struct with the AN recordings, the output of data_heeringa2024('data', animalID). 
%               The content of the andata is described in https://datadryad.org/stash/downloads/file_stream/3070542.
%     
%     unit      Index of the unit in andata to be plotted.
%     
%     TestLevel Level (in dB) above threshold at which the PSTH will be plotted.
%
%   The plot of the PSTH is used in Fig. 5b in data_heeringa2024.
%
%   References:
%     A. N. Heeringa, C. Jüchter, R. Beutelmann, G. M. Klump, and C. Köppl.
%     Altered neural encoding of vowels in noise does not affect behavioral
%     vowel discrimination in gerbils with age-related hearing loss.
%     Frontiers in Neuroscience, 17, 2023.
%     
%     F. Steenken, H. Oetjen, R. Beutelmann, L. H. Carney, C. Koeppl, and
%     G. M. Klump. Neural processing and perception of schroeder-phase
%     harmonic tone complexes in the gerbil: Relating single-unit
%     neurophysiology to behavior. European Journal of Neuroscience,
%     56(3):4060--4085, 2022.
%     
%     A. Heeringa. Single-unit auditory nerve fibre responses of young-adult
%     and aging gerbils, 2024.
%     
%     A. N. Heeringa. Single-unit data for sensory neuroscience: Responses
%     from the auditory nerve of young-adult and aging gerbils. Scientific
%     Data, 11:411, Apr. 2024.
%     
%      heeringa2020 steenken2021 heeringa2022 heeringa2023b
%
%   See also: data_heeringa2024
%
%   Url: http://amtoolbox.org/amt-1.6.0/doc/plot/plot_heeringa2024.php


%   #StatusDoc: Submitted
%   #StatusCode: Submitted
%   #Verification: Qualified
%   #Author: Amarins Heeringa (2024): original implementation of checkAN.m and makePSTH.m on <doi.org/10.5061/dryad.qv9s4mwn4>
%   #Author: Piotr Majdak(2024): integration in the AMT

% 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.flags.type = {'waveform','psth'};
flags = ltfatarghelper({}, definput,{type});

if flags.do_waveform
  curvedata=varargin{1};
  curveresp=varargin{2};
  curvesettings=varargin{3};
  if nargin == 4
    local_plotAN(curvedata,curveresp,curvesettings);
  else
    animalID=varargin{4};
    local_plotAN(curvedata,curveresp,curvesettings,animalID);
  end
end
 
if flags.do_psth
  andata=varargin{1};
  unit=varargin{2};
  TestLevel=varargin{3};
  local_plotPSTH(andata,unit,TestLevel);
end

function local_plotAN(curvedata,curveresp,curvesettings, animalID)

% This script is based on checkAN.m from https://datadryad.org/stash/downloads/file_stream/3070542
%
% Original implementation by Amarins Heeringa, Koeppl Lab, July 2018
% Integration in the AMT by Piotr Majdak


Fs = curvesettings.Fs(1,1); 

T = length(curveresp{1,1})/Fs;
t = 1/Fs:1/Fs:T;
yspik = zeros(1,length(curvedata.spike_times{1,1})); 
yspik = yspik + max(curveresp{1,1})/10*1.1;

% plot the first datatrace and spikes
figure;
subplot(2,2,1);
plot(t*1000,curveresp{1,1}/10,'b')
hold on
scatter(curvedata.spike_times{1,1}*1000,yspik,[],'r','*');
xlabel('time (ms)');
ylabel('Amplitude (V)');
if exist('animalID','var')
  title([animalID newline 'First data trace, unfiltered']);
else
  title('First data trace, unfiltered');
end

% prepare variables to get single waveforms
[a,b,c] = size(curveresp);
ms1 = round(Fs*0.00125); % amount of datapoints for 1.25 ms
lwv = (ms1*2)+1; % total length waveform in datapoints
spks = nan(lwv,400000); % pre-allocate waveform matrix
cnt = 1;
isis = []; 

% get all the waveforms
for an = 1:a
    for bn = 1:b
        for cn = 1:c
            spT = curvedata.spike_times{an,bn,cn}; % spike times in s
            isis = [isis diff(spT)]; % get the interspike intervals
            spDP = round(spT*Fs); % spike times in data points
            resp = curveresp{an,bn,cn}; % get data trace
            for sp = 1:length(spT) % loop through spikes of that trial
                if spDP(sp)>ms1 && spDP(sp)+ms1<length(resp) % only include spikes not too close to the edges of the response
                    spks(:,cnt) = resp(spDP(sp)-ms1:spDP(sp)+ms1); % get the waveform
                    cnt = cnt+1;
                end
            end
        end
    end
end

% plot ISIs and info
subplot(2,2,2);
edges = 0:1:50;
histogram(isis*1000,edges,'FaceAlpha',0.8,'FaceColor',[11 154 51]/256);
xlabel('Interspike intervals (ms)');
ylabel('Count');
if exist('animalID','var')
  title([animalID newline 'Interspike interval histogram']);
else
  title('Interspike interval histogram');
end
yl=ylim;
text(23,yl(2)*0.9,sprintf('total ISIs = %d',length(isis)));
text(23,yl(2)*0.82,sprintf('ISIs < 1 ms = %d',sum(isis<0.001)));
text(23,yl(2)*0.74,sprintf('ISIs < 0.6 ms = %d',sum(isis<0.0006)));
text(23,yl(2)*0.66,sprintf('ISI median = %0.1f ms',median(isis)*1000));
text(23,yl(2)*0.58,sprintf('min ISI = %0.1f ms',min(isis)*1000));

spks=spks(:,1:cnt-1);
twv = linspace(-1.25,1.25,lwv);

% plot the first 300 waveforms
subplot(2,2,3);
plot(twv,spks(:,1)/10,'k','LineWidth',0.3);
hold on
if cnt>300
    n=300;
else
    n=cnt-1;
end
for i=2:n
    plot(twv,spks(:,i)/10,'k','LineWidth',0.3);
end
if exist('animalID','var')
  title([animalID newline 'First 300 spike waveforms']);
else
  title('First 300 spike waveforms');
end
xlabel('Time rel. spike peak (ms)');
ylabel('Amplitude (V)');
set(gca,'XTick',[-1 0 1],'XTickLabel',{'-1 ms','0','1 ms'});
xlim([-1.3 1.3]); yl=ylim;

% plot the average waveform
% mWV = mean(spks,2); 
% stdWV = std(spks,[],2);
mWV = median(spks,2)/10; 
ci1 = quantile(spks,0.025,2)/10;
ci2 = quantile(spks,0.975,2)/10;

subplot(2,2,4);
plot(twv,mWV,'r','LineWidth',2);
hold on
% plot(twv,mWV+2*stdWV,'r',twv,mWV-2*stdWV,'r','LineWidth',0.3)
plot(twv,ci1,'r',twv,ci2,'r','LineWidth',0.3);
patch([twv fliplr(twv)],[(ci2)' fliplr((ci1)')],'r','FaceAlpha',0.25,'EdgeColor','none');
text(-0.9,max(mWV),sprintf('n = %d',cnt));
xlabel('Time rel. spike peak (ms)');
ylabel('Amplitude (V)');
if exist('animalID','var')
  title([animalID newline 'Waveform median +/- 95% CI']);
else
  title('Waveform median +/- 95% CI');
end
set(gca,'XTick',[-1 0 1],'XTickLabel',{'-1 ms','0','1 ms'});
xlim([-1.3 1.3]); ylim(yl);



function local_plotPSTH(andata,i,TestLevel)
% This script is based on makePSTH.m from https://datadryad.org/stash/downloads/file_stream/3070542
%
% Original implementation by Amarins Heeringa
% Integration in the AMT by Piotr Majdak

figure;
binSize = 0.001; % set binsize for PSTH

if ~isempty(andata.data(i).BF)
    bf = andata.data(i).BF.analysis.bf;
else
    bf = [];
end
if ~isempty(andata.data(i).RLF)
    thr = andata.data(i).RLF.analysis.threshold;
else
    thr = [];
end

% plot PSTH of BF file as closest to BF as possible
if ~isempty(andata.data(i).BF)

    BF = andata.data(i).BF;
    BFdur = BF.curvesettings.tdt.AcqDuration; 
    BFdelay = BF.curvesettings.stim.Delay; 
    idx2 = find(abs(BF.curvedata.depvars_sort(:,1,1) - bf)==min(abs(BF.curvedata.depvars_sort(:,1,1) - bf)));
    if length(idx2) > 1
        idx2 = min(idx2);
    end
    reps = length(BF.curvedata.depvars_sort(1,:,1));
    spiksBF=[];
    for r = 1:reps
        spks = BF.curvedata.spike_times{idx2,r}; 
        spiksBF=[spiksBF spks];        
    end
    
    subplot(2,2,1);
    nbin = round(BFdur/binSize);
    [N,cent] = hist(spiksBF,nbin); N=(N/reps)/binSize;
    bar(cent,N,0.9,'k');
    xlabel('Time (s)'); ylabel('Rate (spikes/s)');
    xlim([0 BFdur]);
    
    level = unique(andata.data(i).BF.curvesettings.stimcache.ABI); level = level(end);
    title(sprintf('BF = %d Hz, PSTH at %d Hz\n Thr = %.1f dB, Level = %.1f dB',...
        bf, BF.curvedata.depvars_sort(idx2,1,1), thr, level));
else
    BF = [];
end

% plot PSTH of RLF file at TestLevel above threshold
if ~isempty(andata.data(i).RLF) && ~isnan(thr)

    ABI = andata.data(i).RLF;
    ABIdur = ABI.curvesettings.tdt.AcqDuration; 
    ABIdelay = ABI.curvesettings.stim.Delay; 
    lev = thr + TestLevel;
    idx2 = find(abs(ABI.curvedata.depvars_sort(:,1,1) - lev)==min(abs(ABI.curvedata.depvars_sort(:,1,1) - lev)));
    if length(idx2) > 1
        idx2 = max(idx2);
    end
    
    reps = length(ABI.curvedata.depvars_sort(1,:,1));
    spiksABI=[];
    for r = 1:reps
        spks = ABI.curvedata.spike_times{idx2,r}; 
        spiksABI=[spiksABI spks];        
    end
    
    subplot(2,2,2);
    nbin = round(ABIdur/binSize);
    [N,cent] = hist(spiksABI,nbin); N=(N/reps)/binSize;
    bar(cent,N,0.9,'k');
    xlabel('Time (s)'); ylabel('Rate (spikes/s)');
    xlim([0 ABIdur]);
    
    title(sprintf('BF = %d Hz, PSTH at %d Hz\n Thr = %.1f dB, Level = %.1f dB',...
        bf,andata.data(i).RLF.analysis.frequency,thr,ABI.curvedata.depvars_sort(idx2,1,1)));
else
    ABI = [];
end

% plot PSTH of PH file at TestLevel above threshold
if ~isempty(andata.data(i).PH) && ~isempty(thr)

    PH = andata.data(i).PH;
    PHdur = PH.curvesettings.tdt.AcqDuration; 
    PHdelay = PH.curvesettings.stim.Delay; 
    lev = thr + TestLevel;
    idx2 = find(abs(PH.curvedata.depvars_sort(:,1,1) - lev)==min(abs(PH.curvedata.depvars_sort(:,1,1) - lev)));
    if length(idx2) > 1
        idx2 = max(idx2);
    elseif isempty(idx2)
        idx2 = 1;
    end
    
    reps = length(PH.curvedata.depvars_sort(1,:,1));
    spiksPH=[];
    for r = 1:reps
        spks = PH.curvedata.spike_times{idx2,r}; 
        spiksPH=[spiksPH spks];        
    end
    
    subplot(2,2,3);
    nbin = round(PHdur/binSize);
    [N,cent] = hist(spiksPH,nbin); N=(N/reps)/binSize;
    bar(cent,N,0.9,'k');
    xlabel('Time (s)'); ylabel('Rate (spikes/s)');
    xlim([0 PHdur]);
    
    title(sprintf('BF = %d Hz, PSTH at %d Hz\n Thr = %.1f dB, Level = %.1f dB',...
        bf,andata.data(i).PH.analysis.frequency,thr,PH.curvedata.depvars_sort(idx2,1,1)));
else
    PH = [];

end

% Plot PSTH with as much as possible data combined
if isempty(ABI) && isempty(PH) || isempty(BF) && isempty(ABI) || isempty(BF) && isempty(PH)
    % do nothing, more than one file missing
    spiks = [];
elseif isempty(BF) || isempty(ABI) || isempty(PH)
    % only file missing, try to combine other two
    if isempty(BF)
        if ABIdelay == PHdelay && ABIdur == PHdur
            spiks = [spiksABI spiksPH];
            dur = ABIdur;
        else
            spiks = [];
        end
    elseif isempty(ABI)
        if BFdelay == PHdelay && BFdur == PHdur
            spiks = [spiksBF spiksPH];
            dur = BFdur;
        else
            spiks = [];
        end
    elseif isempty(PH)
        if BFdelay == ABIdelay && BFdur == ABIdur
            spiks = [spiksBF spiksABI];
            dur = BFdur;
        else
            spiks = [];
        end
    end
else
    % all files there, try to combine
    if ABIdelay == PHdelay && ABIdur == PHdur
        spiks = [spiksABI spiksPH];
        dur = ABIdur;
        if ABIdelay == BFdelay && ABIdur == BFdur
            spiks = [spiks spiksBF];
            dur = BFdur;
        end
    elseif PHdelay == BFdelay && PHdur == BFdur
        spiks = [spiksPH spiksBF];
        dur = BFdur;
    end
end

if ~isempty(spiks)
	subplot(2,2,4);
	nbin = round(dur/binSize);
	bar(cent,N,0.9,'k');
	[N,cent] = hist(spiks,nbin); N=(N/reps)/binSize;
	xlabel('Time (s)'); ylabel('Rate (spikes/s)');
	xlim([0 dur]);
	title('All data together');
end