diff --git a/GUITools/EventExplorer/EEhelpers/EventVewPlot.m b/GUITools/EventExplorer/EEhelpers/EventVewPlot.m index 6730fed9..a400c998 100755 --- a/GUITools/EventExplorer/EEhelpers/EventVewPlot.m +++ b/GUITools/EventExplorer/EEhelpers/EventVewPlot.m @@ -26,7 +26,7 @@ hold(FO.viewwin,'off') %Plot The LFP ywinrange = bz_MultiLFPPlot(FO.data.lfp,'timewin',thiseventwin,'spikes',FO.data.spikes,... - 'axhandle',FO.viewwin,'scaleLFP',FO.scaleLFP) + 'axhandle',FO.viewwin,'scaleLFP',FO.scaleLFP); hold on %Plot the events diff --git a/analysis/lfp/bz_Comodulogram.m b/analysis/CrossFrequencyCoupling/bz_Comodulogram.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/bz_Comodulogram.m rename to analysis/CrossFrequencyCoupling/bz_Comodulogram.m diff --git a/analysis/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m b/analysis/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m new file mode 100644 index 00000000..5052a9e3 --- /dev/null +++ b/analysis/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m @@ -0,0 +1,131 @@ +function [phaseamplitude] = bz_PhaseAmplitudeDist(lfp,phaserange,amprange,varargin) +% [phaseamplitudemap,ampfreqs,phasecenters] = bz_PhaseAmplitudeDist(lfp,phaserange,amprange) +%This function calculates the mean amplitude of multiple higher frequency bands at +%a the phase for a single lower frequency band signal +% +%%INPUT +% lfp a buzcode structure with fields lfp.data, +% lfp.timestamps +% lfp.samplingRate +% phaserange [min max] frequency to filter for the single phase signal +% amprange [min max] frequency range for wavelets for the power +% signal +% optional list of property-value pairs (see table below) +% +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% not implemented yet.... 'nfreqs' for amplitude signal +% 'ncyc' for wavelet parameters +% 'interval' interval on which to calculate +% other filter parameters +% 'phasesignal' option for other signal to be +% used as phase information +% ========================================================================= +% +%OUTPUT +% phaseamplitude phase by frequency matrix. value is mean amplitude +% of that frequency at that phase +% (relative to the mean over the entire signal) +% ampfreqs amplitude frequencies +% phasecenters phase bins +% +% +%Dependencies +% bz_Filter +% bz_WaveSpec +% +%Last Updated: 31/10/2019 (EFO) +%DLevenstein + +% +% +%DLevenstein 2016 +%Updated by Eliezyer de Oliveira (EFO)/2019 + +%% parsing inputs + +if ~bz_isLFP(lfp) + error('Not following the lfp structure required, see documentation') +end + + +p = inputParser; +addParameter(p,'filterType','fir1',@ischar); +addParameter(p,'filterOrder',4,@isnumeric); +addParameter(p,'numBins',50,@isnumeric); +addParameter(p,'intervals',[0 inf],@isnumeric); +addParameter(p,'nfreqs',100,@isnumeric); +addParameter(p,'ncyc',7,@isnumeric); +addParameter(p,'makePlot',true,@islogical); + +parse(p,varargin{:}); +makePlot = p.Results.makePlot; +filterType = p.Results.filterType; +filterOrder = p.Results.filterOrder; +numBins = p.Results.numBins; +nfreqs = p.Results.nfreqs; +ncyc = p.Results.ncyc; +intervals = p.Results.intervals; + +%% Deal with input types +% sf_LFP = lfp.samplingRate; + + +%% Filter LFP for the phase +filtered_phase = bz_Filter(lfp,'passband',phaserange,'filter',filterType,'order',filterOrder,'intervals',intervals); + +%% Get LFP, Phase in intervals +%edgebuffer = 1; %s +%edgebuffer_si = edgebuffer.*sf_LFP; +%edgebuffer = edgebuffer.*[1 1]; +% LFP_int = IsolateEpochs2(LFP.data,int,edgebuffer,sf_LFP); +% LFPphase_int = IsolateEpochs2(filtered.phase,int,edgebuffer,sf_LFP); + +%% Wavelet Transform LFP in intervals +wavespec_amp = bz_WaveSpec(lfp,'frange',amprange,'nfreqs',nfreqs,'ncyc',ncyc,'intervals',intervals); +%[ampfreqs,~,spec_int] +%spec_int = cellfun(@(X) abs(X),spec_int,'UniformOutput',false); +wavespec_amp.data = log10(abs(wavespec_amp.data)); %log scaled for normality +wavespec_amp.mean = mean(wavespec_amp.data,1); +%Remove Buffers (this should be done via 'intervals' in bz_WaveSpec +%spec_int = cellfun(@(X) X(:,edgebuffer_si:end-edgebuffer_si),spec_int,'UniformOutput',false); +%LFPphase_int = cellfun(@(X) X(edgebuffer_si:end-edgebuffer_si),LFPphase_int,'UniformOutput',false); + +%% Bin phase and power +phasebins = linspace(-pi,pi,numBins+1); +phasecenters = phasebins(1:end-1)+(diff(phasebins)/2); + +[~,~,phaseall] = histcounts(filtered_phase.phase,phasebins); + +phaseamplitudemap = zeros(numBins,nfreqs); +for bb = 1:numBins + phaseamplitudemap(bb,:) = mean(wavespec_amp.data(phaseall==bb,:),1)./wavespec_amp.mean; +end + +ampfreqs = wavespec_amp.freqs; + +phaseamplitude.map = phaseamplitudemap; +phaseamplitude.phasecenters = phasecenters; +phaseamplitude.amp_freq = ampfreqs; +phaseamplitude.phase_range = num2str(mean(phaserange)); +phaseamplitude.params.filterType = filterType; +phaseamplitude.params.filterOrder = filterOrder; + +%% Plot +if makePlot + figure + imagesc(phaseamplitude.phasecenters,log2(phaseamplitude.amp_freq),((phaseamplitude.map))') + hold on + imagesc(phaseamplitude.phasecenters+2*pi,log2(phaseamplitude.amp_freq),((phaseamplitude.map))') + plot(linspace(-pi,3*pi),cos(linspace(-pi,3*pi))+log2(phaseamplitude.amp_freq(end/2)),'k') + xlabel(['Phase (',num2str(phaseamplitude.phase_range),' Hz)']); + ylabel('Amplitude Frequency (Hz)') + LogScale('y',2) + xlim([phaseamplitude.phasecenters(1) phaseamplitude.phasecenters(end)+2*pi]); + colorbar + axis xy +end + +end + diff --git a/analysis/RankOrder/bz_RankOrder.m b/analysis/RankOrder/bz_RankOrder.m new file mode 100644 index 00000000..ec01c476 --- /dev/null +++ b/analysis/RankOrder/bz_RankOrder.m @@ -0,0 +1,496 @@ +function [rankStats] = bz_RankOrder(varargin) +% [rankStats] = RankOrder() +% Get rank order of spikes inside events from previously calculated +% bz_getSpikesRank structure. The 'corrEvents' output field structure shows +% rank correlation of each event with the selected 'templateType', and the +% 'pvalEvents' field states how significantly different from chance is that +% correlation, so presumably those events with low 'pvalEvents' will have a +% rank order that repeats over time. In the case that there is more than +% one rank sequence, the output field structure 'rankClusters' will number +% the events equally if they have similar sequences. +% +% INPUTS +% +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% 'basepath' full path where session is located (default pwd) +% E.g. /mnt/Data/buddy140_060813_reo/buddy140_060813_reo +% 'spkEventTimes' Field to be used is: +% .EventRel: 2xN cell matrix. In the first row, relative +% times of spikes for that particular event across all units. +% In the second row, the UID associated to the above spike +% 'templateType' String that can be: +% - 'Pairwise': (default) it computes the rank correlation +% of each event pairwise with each other event. +% - 'MeanRank': it computes the rank correlation +% of each event against the mean rank +% of the rest of the events. +% - 'Peak': searchs for the bz_findPlaceFieldsTemplate +% output, the X.placeFieldTemplate.mat, loads it +% and takes the 'Peak' field, a 1 x (# conditions) +% cell array. Within each cell there is a (# units) x 3 +% matrix, which has bins corresponding to the firing +% map peak for each unit (NaN for units without place +% field); second column has the unit ID; third column +% has the position of the unit in the UID vector. The +% rest of the cells contain this similar matrix for the +% other conditions. +% - 'CenterofMass': searchs for the bz_findPlaceFieldsTemplate +% output, the X.placeFieldTemplate.mat, loads it +% and takes the 'CenterofMass', a 1 x (# conditions) +% cell array. Within each cell there is a (# units) x 3 +% matrix, which has bins corresponding to the firing +% map peak for each unit (NaN for units without place +% field); second column has the unit ID; third column +% has the position of the unit in the UID vector. +% The rest of the cells contain this similar matrix +% for the other conditions. +% 'eventIDs' A (#events)-length array of 0s and 1s that indicates +% which event belongs to one of two different groups +% of events. By default all events will belong to a +% single group, and the rank correlation of each event +% will be performed against the rest of the events. +% However, if an 'eventIDs' array establishes two +% different groups of events (e.g. spontaneous vs induced +% ripples), then the rank correlation of events in +% one group will be performed against the rank of the +% other group. +% 'timeSpike' A string, to determine what time reference of spike: +% 1. 'first': (default) takes into account the first time +% the unit fires +% 2. 'mean': takes the mean of the spike time +% 'minUnits' Minimum number of units parcitipating in each event. +% Events with less than 'minUnits' are not considered +% for correlation. +% 'normalize' Work with normalized ranks, logical (default: true) +% So instead of ranking 1, 4, 5, 2, 3, it will be 0, +% 0.8, 1.0, 0.2, 0.4. +% 'numRep' Number of permutation test repetitions +% to compute statistical significance. In each +% permutation test, rank of firing units on each +% event is randomly permuted (default: 1000) +% 'pvalTest' pval value under which to test the total statistical +% significance of sequence consistency (default: 0.05) +% 'minCorr' minimum correlation value to search for different +% rank clusters (that can be interpreted as different +% sequences), clusters within which rank is highly +% correlated (default: 0.8) +% 'doPlot' Make plots, logical (default: true) +% 'saveMat' Saves file, logical (default: true) +% +% ========================================================================= +% +% +% OUTPUTS +% +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% 'rankStats' structure with the following subfields: +% .corrMean (#conditions x 1) Mean of rank correlations +% of all events. If there are several conditions +% each row is the mean correlation of events +% against the template given by each condition. +% .corrStd (#conditions x 1) Standard deviation of rank +% of all events. If there are several conditions +% each row is the mean correlation of events +% against the template given by each condition. +% .corrEvents (#conditions x #events matrix) Rank correlation +% of each event. Same as with 'corrMean' happens +% for each row. +% .corrMeanShuff (numRep x #conditions matrix) Mean of rank +% correlations of each event for all permutation +% tests. Same as with 'corrEvents' happens for +% each column. +% .corrEventsShuff (numRep x #events x #conditions matrix) Rank +% correlations of each event for all permutation +% tests. Same as with 'corrEvents' happens for +% the third dimension. +% .pvalEvents (#conditions x 1) P-value per event. If there +% are several conditions each row is the p-value +% of the correlation between each event rank +% against the external template given by each +% condition. +% .pvalTotal binomial test over 'pvalEvents' +% .rankUnits (#units x #events matrix) Rank of units for +% each event. This is the matrix that has been +% used to compute the correlation. +% E.g.: A [ 0.0 0.4 0.6 nan 0.6 +% B 0.2 0.2 nan nan nan +% C 0.4 0.0 nan nan nan +% D 0.6 0.6 1.0 nan ... 1.0 +% E 0.8 0.8 0.3 nan 0.3 +% F 1.0 1.0 nan nan nan ] +% .rankClusters (1 x #events array) Different sequences are +% searched using an agglomerative hierarchical +% cluster tree. This array shows which of the +% different found sequences has each event. +% E.g.: [ 1 1 2 nan ... 2 ] +% where 1 is sequence A B C D E F +% 2 is sequence E A D +% +% +% See also bz_getSpikesRank, bz_findPlaceFieldsTemplate +% +% +% +% Andrea Navas-Olive, 2019 + + +%% Parse inputs +p = inputParser; +addParameter(p,'basepath',pwd,@isstr); +addParameter(p,'spkEventTimes',{},@isstruct); +addParameter(p,'templateType','MeanRank',@isstr); +addParameter(p,'eventIDs',1,@isnumeric); +addParameter(p,'timeSpike','first',@isstr); +addParameter(p,'minUnits',5,@isnumeric); +addParameter(p,'normalize',true,@islogical); +addParameter(p,'numRep',1000,@isnumeric); +addParameter(p,'pvalTest',0.05,@isnumeric); +addParameter(p,'minCorr',0.05,@isnumeric); +addParameter(p,'doPlot', true, @islogical); +addParameter(p,'saveMat', true, @islogical); + +parse(p,varargin{:}); +basepath = p.Results.basepath; +spkEventTimes = p.Results.spkEventTimes; +templateType = p.Results.templateType; +eventIDs = p.Results.eventIDs; +normalize = p.Results.normalize; +timeSpike = p.Results.timeSpike; +minUnits = p.Results.minUnits; +numRep = p.Results.numRep; +pvalTest = p.Results.pvalTest; +minCorr = p.Results.minCorr; +doPlot = p.Results.doPlot; +saveMat = p.Results.saveMat; + +% Get session info +basename = bz_BasenameFromBasepath(basepath); +load([basepath filesep basename '.sessionInfo.mat']); +% Load default spkEventTimes +if isempty(spkEventTimes) + spkEventTimes = load([basepath filesep basename '.spkEventTimes.mat']); + spkEventTimes = spkEventTimes.spkEventTimes; +end +% Relative times of spikes for each particular event across all units. +evtTimes = spkEventTimes.EventRel; + +% If template type is 'Peak' or 'CenterofMass' load 'placeFieldTemplate' +if strcmp(templateType,'Peak') || strcmp(templateType,'CenterofMass') + % External template + templateExt = load([basepath filesep basename '.placeFieldTemplate.mat']); + templateExt = templateExt.placeFieldTemplate; + templateExt = templateExt.(templateType); +else + templateExt = []; +end + + +%% Rank matrix +% Create (#events) x (#units) matrix with position of spikes in event. It +% considers just the first spikes of each unit +rankUnits = nan*ones(size(spkEventTimes.UnitEventRel)); +for event = 1:length(evtTimes) + % Take into account just first spike + if strcmp(timeSpike,'first') + units = unique(evtTimes{event}(3,:),'stable'); + elseif strcmp(timeSpike,'mean') + means = []; + for jj = unique(evtTimes{event}(3,:)) + means = [means [mean(evtTimes{event}(1,evtTimes{event}(3,:)==jj)); jj]]; + end + if ~isempty(means) + means = sortrows(means')'; + units = means(2,:); + else + units = []; + end + else + warning('The variable "timeSpike" is invalid'); + end + nUnits = length(units); + % Set event as nan if it has no enough units + if nUnits < minUnits + rankUnits(:,event) = nan; + % Rank units + else + rankUnits(units,event) = 1:nUnits; + % If normalize, set ranks between 0 and 1 + if normalize + rankUnits(units,event) = rankUnits(units,event) / nUnits; + end + end +end + +%% Compute correlation + +% Compute mean and standard deviation of rank correlation, and correlation +% event by event +[corrMean, corrStd, corrEvents] = compute_rank_correlation(templateType, rankUnits, evtTimes, templateExt, eventIDs); + +% Permutation test: random shuffling the ranks of the units that +% participated in each event and carrying out the entire procedure, +% culminating in the computation of the mean rank correlation. This is +% repeated 'numRep' times and the observed mean rank correlation is +% compared with the distribution of the randomly permuted mean rank +% correlations +nCond = size(corrEvents,1); +corrMeanShuff = zeros(numRep,nCond); +corrEventsShuff = zeros(numRep,length(evtTimes),nCond); +parfor irep = 1:numRep + % Suffle each column of rankUnits + rankUnitsSuffled = shuffle_by_column(rankUnits,normalize); + % Rank correlation with suffled rankUnits + [tmpcorr, ~, tmpcorrTemplate] = compute_rank_correlation(templateType, rankUnitsSuffled, evtTimes, templateExt, eventIDs); + corrMeanShuff(irep,:) = tmpcorr'; + corrEventsShuff(irep,:,:) = tmpcorrTemplate'; +end + +% For statistical significance of sequence consistency, get percentage of +% times that the correlation of a certain event has been higher that the +% correlation of that same event in the permutation test +pvalEvents = zeros(nCond,length(corrEvents)); +parfor event = 1:length(corrEvents) + pvalEvents(:,event) = sum( corrEvents(:,event) > squeeze(corrEventsShuff(:,event,:))',2 ) / numRep; +end +% Transform it in a p-value +pvalEvents = 1 - pvalEvents; +% Compute a general p-value, measuring how probable is to get such +% pvalEvents distribution +pvalTotal = binom_test(pvalEvents,pvalTest); + + +%% Find different sequences + +if any(ismember(templateType,{'Pairwise','MeanRank'})) + % Events with statistically significant rank sequences + statEvents = find(pvalEvents<=0.05); + + try + % Perform a pairwise correlation + corrMat = corr(rankUnits(:,statEvents), 'rows', 'pairwise'); + % Clean correlation matrix (remove nans) + corrMat(isnan(corrMat)) = 0; + % Take intro account correlation above 'minCorr' + corrMat(corrMat <= minCorr) = 0; + + % Perform an agglomerative hierarchical cluster tree + tree = linkage(corrMat,'complete','correlation'); + % Compute a cutoff (to select the hierarchical level to perform the clusterization) + treeNonan = tree(~isnan(tree(:,3)),:); + cutoff = median([treeNonan(end-2,3) treeNonan(end-1,3)]); + % Cluster + groups = cluster(tree,'cutoff',cutoff,'criterion','distance'); + + % Save sequences + rankClusters = zeros(size(pvalEvents)); + rankClusters(statEvents) = groups; + catch + rankClusters = zeros(size(pvalEvents)); + end +else + rankClusters = []; +end + + +%% Save and plot + +% Save statistics +if saveMat + rankStats.corrMean = corrMean; + rankStats.corrStd = corrStd; + rankStats.corrEvents = corrEvents; + rankStats.corrMeanShuff = corrMeanShuff; + rankStats.corrEventsShuff = corrEventsShuff; + rankStats.pvalEvents = pvalEvents; + rankStats.pvalTotal = pvalTotal; + rankStats.rankUnits = rankUnits; + rankStats.rankClusters = rankClusters; + save([basepath filesep basename '.rankStats.mat'],'rankStats'); +end +% Plot statistics +if doPlot + + % Plot results of permutation test + figure + hold on + [yhist,xhist] = hist(corrEventsShuff,[-1:0.05:1]); + fill([xhist; flip(xhist)],[mean(yhist,2)+std(yhist')';mean(yhist,2)-std(yhist')']/max(mean(yhist,2)),1,'facecolor',[.7,.9,1],'linestyle','none','DisplayName','') + plot(xhist,mean(yhist,2)/max(mean(yhist,2)),'b','linewidth',2,'DisplayName','Shuffled data') + [yhist,xhist] = hist(corrEvents,[-1:0.05:1]); + plot(xhist,yhist/max(yhist),'k','linewidth',2,'DisplayName','Real data') + legend() + xlabel('Correlation values') + ylabel('Distribution') + + if exist('groups') + % Plot hierarchical tree + figure(), + dendrogram(tree,'ColorThreshold',cutoff) + xlabel('Events') + + % Plot rank clusters + figure('pos',[100,300,1000,400]), hold on + for group = unique(groups)' + idxsGroup = groups==group; + cycsRank = rankUnits(:,idxsGroup); + meanUnitPos = []; + for unit = find(sum(isnan(cycsRank),2) <= 0.5*sum(idxsGroup))' + meanUnitPos = [meanUnitPos [unit; nanmedian(cycsRank(unit,:))]]; + end + if ~isempty(meanUnitPos) + meanUnitPos = sortrows(meanUnitPos',2)'; + % Plot in order + subplot(1,max(groups),group), hold on + k = -1; + for unit = meanUnitPos(1,:) + color = 'k'; + plot(nanmedian(rankUnits(unit,idxsGroup)), k,'.k','markersize',30) + errorbar(nanmedian(rankUnits(unit,idxsGroup)),k,0,0,nanstd(rankUnits(unit,idxsGroup)),nanstd(rankUnits(unit,idxsGroup)),'color',color) + plot(rankUnits(unit,idxsGroup), k + 0.1*randn(1,sum(idxsGroup)),'.') + k = k-1; + end + set(gca,'ytick',[k+1:-1],'yticklabel',meanUnitPos(1,end:-1:1)); + xlabel('Rank position') + ylabel('Units') + end + end + end + +end + + +end + + + + +% RANK CORRELATION... +function [corrMean, corrStd, corrEvents] = compute_rank_correlation(templateType, rankUnits, evtTimes, templateExt, eventIDs) + + + % ... WITHOUT EXTERNAL TEMPLATE + + % Template method: a template for each specific ripple or theta event is + % constructed based on the averaged rank of all units over all other events + % (i.e., excluding that specific event). Then the rank correlation between + % each specific event and its template was computed, and averaged over all + % events + if strcmp(templateType,'MeanRank') + corrEvents = zeros(size(evtTimes)); + % - If there are no two groups of events + if length(unique(eventIDs))==1 + parfor event = 1:length(evtTimes) + % Order of units in this event + rankThisEvent = rankUnits(:,event); + % Mean order of units along the rest of the events (template) + rankTemplate = nanmean(rankUnits(:,[1:event-1,event+1:end]),2); + % Correlation between these previous variables + corrEvents(event) = corr(rankThisEvent, rankTemplate, 'rows', 'complete'); + end + % - If there are two groups of events, test this event against + % the meank rank of the other group + else + parfor event = 1:length(evtTimes) + % Order of units in this event + rankThisEvent = rankUnits(:,event); + % Mean order of units along the rest of the events (template) + rankTemplate = nanmean(rankUnits(:,eventIDs==(1-eventIDs(event))),2); + % Correlation between these previous variables + corrEvents(event) = corr(rankThisEvent, rankTemplate, 'rows', 'complete'); + end + end + % Mean and standard deviation of rank correlation + corrMean = nanmean(corrEvents); + corrStd = nanstd(corrEvents); + + % Pairwise method: + elseif strcmp(templateType,'Pairwise') + corrEvents = corr(rankUnits, 'rows', 'pairwise'); + corrEvents(eye(size(corrEvents))==1) = nan; + % Mean and standard deviation of rank correlation... + % - If there are no two groups of events + if length(unique(eventIDs))==1 + corrEvents = nanmean(corrEvents); + % - If there are two groups of events, test this event against + % the meank rank of the other group + else + corrEventsMean = zeros(size(evtTimes)); + parfor event = 1:length(evtTimes) + corrEventsMean(event) = nanmean(corrEvents(event,eventIDs==(1-eventIDs(event)))); + end + corrEvents = corrEventsMean; + end + % Mean and standard deviation of rank correlation + corrMean = nanmean(corrEvents); + corrStd = nanstd(corrEvents); + end + + + % ... WITH EXTERNAL TEMPLATE + % Template method: external template + if ismember(templateType,{'Peak','CenterofMass'}) + % Initialize + corrEvents = zeros(length(templateExt),size(evtTimes,2)); + corrMean = zeros(length(templateExt),1); + corrStd = zeros(length(templateExt),1); + for iCond = 1:length(templateExt) + parfor event = 1:size(evtTimes,2) + % Order of units in this event + rankThisEvent = rankUnits(:,event); + % External template (we need to transform it to an array + % similar to rankThisEvent: a (# units) x 1 array, where in + % the position of each unit the rank is written + rankTemplate = nan*ones(size(rankThisEvent)); + rankTemplate(templateExt{iCond}(:,3)) = templateExt{iCond}(:,1); + % Normalize + if max(max(rankUnits))==1 + rankTemplate = (rankTemplate - nanmin(templateExt{iCond}(:,1)))/ (nanmax(templateExt{iCond}(:,1))- nanmin(templateExt{iCond}(:,1))); + end + % Correlation between these previous variables + corrEvents(iCond,event) = corr(rankThisEvent, rankTemplate, 'rows', 'complete'); + end + end + % Mean and standard deviation of rank correlation + corrMean = nanmean(corrEvents,2); + corrStd = nanstd(corrEvents,[],2); + end +end + +% SUFFLE RANK IN EACH EVENT +function A = shuffle_by_column(A,normalize) + % Nan temporal vector + tmpNan = nan * ones(size(A,1),1); + % For each event... + parfor ii = 1:size(A,2) + % Find units participating + rankAi = find(~isnan(A(:,ii))); + % Shuffle ranks + tmp = tmpNan; + tmp(rankAi) = randperm(length(rankAi)); + if normalize + tmp = tmp / length(rankAi); + end + % Update matrix + A(:,ii) = tmp; + end +end + +% BINOMIAL TEST +function p = binom_test(ps,alpha) +% GLOBAL_P=BINOM_TEST(PS,ALPHA) where PS are the p-values of independent tests +% and ALPHA is 0.05 per default. + if nargin<2 || isempty(alpha) + alpha=.05; + end + ps(isnan(ps)) = []; + % Number of significant pvals + k = sum(ps<=alpha); + % Total number of pvals + n = numel(ps); + % Sum of: (alpha^q) * (1-alpha)^(n-q) * (n!/(q! (n-q)!)), q from k to n + p = nansum( feval( @(q) arrayfun( @(k) nchoosek(n,k), q) * ( alpha.^q.*(1-alpha).^(n-q) )', k:n )); +end diff --git a/analysis/RankOrder/bz_getSpikesRank.m b/analysis/RankOrder/bz_getSpikesRank.m new file mode 100644 index 00000000..2a0ce2c8 --- /dev/null +++ b/analysis/RankOrder/bz_getSpikesRank.m @@ -0,0 +1,168 @@ +function [spkEventTimes] = bz_getSpikesRank(varargin) +% [SpkEventTimes] = bz_getSpikesRank() +% Saves spike times of different units in different ways: +% 1. Absolute and relative time of spikes by unit and by event +% 2. Absolute and relative time of spikes by unit +% 3. Absolute and relative time of spikes by event +% +% INPUTS +% +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% 'basepath' full path where session is located (default pwd) +% e.g. /mnt/Data/buddy140_060813_reo/buddy140_060813_reo +% 'events' It can be either the following options: +% 1. Buzcode structure for specific events (ripples, UDStates, ...) +% By default it will load ripples (output from bz_DetectSWR). +% Specifically, if not provided, it loads this event +% structure from 'basepath' (if provided), or from current +% folder (if not). Its internal structure must have the +% following field: +% .timestamps: Nx2 matrix with starting and ending times +% (in segs) of each event. +% 2. A Nx2 matrix with starting and ending times (in segs) +% of each event, just like the .timestamps field. +% (N: number of events) +% 'spikes' buzcode event structure (from bz_GetSpikes). +% If not provided, it loads it from 'basepath' (if provided), +% or from current folder (if not) +% 'UIDs' A Mx1 boolean matrix with 1s for units to be considered +% and 0s for units to be discarded. +% (M: number of units) +% 'padding' extra time after event end to still search for spikes. +% (default is 0.05 seg) +% 'saveMat' Saves file, logical (default: true) +% +% ========================================================================= +% +% OUTPUTS +% +% spkEventTimes structure with the followin fields: +% +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% .UnitEventAbs MxN cell matrix. In each cell, absolute times of +% spikes for that particular unit and event +% .UnitEventRel MxN cell matrix. In each cell, relative times of +% spikes (relative to the starting time of events) for +% that particular unit and event +% .UnitAbs 1xM cell matrix. In each cell, absolute times of +% spikes for that particular unit across all events +% .UnitRel 1xM cell matrix. In each cell, relative times of +% spikes for that particular unit across all events +% .EventAbs 3xN cell matrix. In the first row, absolute times of +% spikes for that particular event across all units. In +% the second row, the UID associated to the above spike. +% In third row, the position within the UID vector of +% the above spike. +% .EventRel 3xN cell matrix. In the first row, relative times of +% spikes for that particular event across all units. In +% the second row, the UID associated to the above spike. +% In third row, the position within the UID vector of +% the above spike. +% +% ========================================================================= +% +% See also bz_RankOrder +% +% +% +% Antonio FR, 2017 +% Convert to buzcode format: Andrea Navas-Olive, 2019 + +% Parse inputs +p = inputParser; +addParameter(p,'basepath',pwd,@isstr); +addParameter(p,'events',[], @(x) isnumeric(x) || isstruct(x)); +addParameter(p,'spikes',{},@isstruct); +addParameter(p,'UIDs',[],@islogical); +addParameter(p,'padding',0.05,@isnumeric); +addParameter(p,'saveMat', true, @islogical); + +parse(p,varargin{:}); +basepath = p.Results.basepath; +events = p.Results.events; +spikes = p.Results.spikes; +UIDs = p.Results.UIDs; +padding = p.Results.padding; +saveMat = p.Results.saveMat; + +% Get session info +basename = bz_BasenameFromBasepath(basepath); +load([basepath filesep basename '.sessionInfo.mat']); +SR = sessionInfo.rates.wideband; + +% Default events, UIDs and spikes +if isempty(spikes) + spikes = load([basepath filesep basename '.spikes.cellinfo.mat']); + spikes = spikes.spikes; +end +if isempty(UIDs) + UIDs = ones(size(spikes.UID)); +end +if isempty(events) + events = load([basepath filesep basename '.ripples.events.mat']); + events = events.ripples; +end +% Starting and ending timestamps +if isnumeric(events) + timestamps = events; +elseif isstruct(events) + timestamps = events.timestamps; +else + warning('Events must be either a Nx2 vector or a bz event structure!'); +end + +%% Get spikes for each unit and each event + +% We will save spike times of different units in different ways: +spkEventTimes = {}; +% 1. Absolute and relative time of spikes by unit and by event +for unit = find(UIDs)' + for event = 1:length(timestamps) + % Start and end of event + tini = timestamps(event,1); + tend = timestamps(event,2) + padding; + % Spikes of this unit within this event interval + tsUnitEvent = spikes.times{unit}; + tsUnitEvent = tsUnitEvent(tsUnitEvent>=tini & tsUnitEvent<=tend); + % Absolute time of spikes by unit and by event + spkEventTimes.UnitEventAbs{unit,event} = tsUnitEvent'; + % Relative time of spikes by unit and by event to event start + spkEventTimes.UnitEventRel{unit,event} = tsUnitEvent' - tini; + end +end + +% 2. Absolute and relative time of spikes by unit +for unit = find(UIDs)' + spkEventTimes.UnitAbs{unit} = cell2mat(spkEventTimes.UnitEventAbs(unit,:)); + spkEventTimes.UnitRel{unit} = cell2mat(spkEventTimes.UnitEventRel(unit,:)); +end + +% 3. Absolute and relative time of spikes by event +for event = 1:length(timestamps) + spkEventTimes.EventAbs{event} = []; + spkEventTimes.EventRel{event} = []; + for unit = find(UIDs)' + spkEventTimes.EventAbs{event} = [ spkEventTimes.EventAbs{event}, ... + [cell2mat(spkEventTimes.UnitEventAbs(unit,event)); ... + cell2mat(spkEventTimes.UnitEventAbs(unit,event))*0+spikes.UID(unit); ... + cell2mat(spkEventTimes.UnitEventAbs(unit,event))*0+unit] ]; + spkEventTimes.EventRel{event} = [ spkEventTimes.EventRel{event}, ... + [cell2mat(spkEventTimes.UnitEventRel(unit,event)); ... + cell2mat(spkEventTimes.UnitEventRel(unit,event))*0+spikes.UID(unit); ... + cell2mat(spkEventTimes.UnitEventAbs(unit,event))*0+unit] ]; + end + spkEventTimes.EventAbs{event} = sortrows(spkEventTimes.EventAbs{event}')'; + spkEventTimes.EventRel{event} = sortrows(spkEventTimes.EventRel{event}')'; +end + +% Save +if saveMat + save([basepath filesep basename '.spkEventTimes.mat'],'spkEventTimes'); +end + + +end diff --git a/analysis/SharpWaveRipples/bz_DetectSWR.m b/analysis/SharpWaveRipples/bz_DetectSWR.m new file mode 100644 index 00000000..be0f02c4 --- /dev/null +++ b/analysis/SharpWaveRipples/bz_DetectSWR.m @@ -0,0 +1,1482 @@ +function [ripples] = bz_DetectSWR(Channels, varargin) +% bz_DetectSWR Detect Sharp Wave Ripple in epochs of continuous data. This +% code is designed for linear probes, with at least one shank +% that simultaneously records across the pyramidal cell layer +% i.e. the superficial contacts record the ripple and at +% least the deepest contact records the Sharp Wave (see the +% .png files within the bz_DetectSWR for examples). +% |o | +% | o| <-- superficial channels record ripple +% |o | +% | o| +% |o | +% | o| +% |o | +% | o| <-- deep channels record Sharp Wave +% \ / +% \ / +% . +% Before running this reocord inspect your .lfp or .lfp file +% to find that shank which best shows the sharp wave/ripple +% profile across the shank e.g. Channels = 25:32. +% +% USE CASES +% 1) uses default parameters +% detected_swr = bz_DetectSWR( Filebase, Channels, Epochs ) +% +% 2) supply parameters as a structure with fields an values +% detected_swr = bz_DetectSWR( ..., options ) +% +% 3) supply parameters as Property/Value pairs +% detected_swr = bz_DetectSWR( ..., 'swBP', [2 50], 'WinSize', 300, etc. ) +% Filtering routines adapted from 'detect_hfos' by Eran Stark +% +% NOTE: -see MEMORY CHECK for how this routine handles potential memory +% overflows associated with reading in large field potential files +% a.k.a how I try to avoid running over into swap files +% +% -If you would like to see how the detector performs against a hand +% labeled .evt file you created: name the .evt file Filebase.rip.evt +% with events 'start' and 'stop'. +% +% -To improve detection, statistical thresholds are set using a local +% window around each putative detection. This means global changes in +% SWR and ripple magnitude aren't documented. Though, in exchange, this +% routine is robust to non-stationarities in the data. +% +% author: John D. Long II, PhD contact: jlong29@gmail.com +% Convert to buzcode format: Andrea Navas-Olive, 2019 +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% INPUTS AND FREE PARAMETERS %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% Channels: an array of two channel IDs following LoadBinary format (base 1) +% First RIPPLE channel, and second the SHARP WAVE channel. +% Detection in based on these order, please be careful +% E.g. [1 11] +% +% All following inputs are optional: +% +% basepath: full path where session is located (default pwd) +% e.g. /mnt/Data/buddy140_060813_reo/buddy140_060813_reo +% +% Epochs: a list of time stamps [start stop] in seconds demarcating +% epochs in which to detect SWR. If this argument is empty, +% the entire .lfp/.lfp file will be used. +% +% saveMat: logical (default=false) to save in buzcode format +% +% forceDetect true or false to force detection (avoid load previous +% detection, default false) +% +% swBP: [low high], passband for filtering sharp wave activity +% +% ripBP: [low high] passband for filtering around ripple activity +% +% per_thresswD: a threshold placed upon the sharp wave difference magnitude +% of the candidate SWR cluster determined via k-means. +% per_thresRip: a threshold placed upon ripple power based upon the non-SWR +% cluster determined via k-means. +% +% WinSize: window size in milliseconds for non-overlapping detections +% +% Ns_chk: sets a window [-Ns_chk, Ns_chk] in seconds around a +% candidate SWR detection for estimating local statistics. +% +% thresSDswD: [low high], high is a threshold in standard deviations upon +% the detected maximum sharp wave difference magnitude, based +% upon the local distribution. low is the cutoff for the +% feature around the detected peak for determining the +% duration of the event, also based upon the local +% distribution. +% +% thresSDrip: [low high], high is a threshold in standard deviations upon +% the detected maximum ripple magnitude, based +% upon the local distribution. low is the cutoff for the +% feature around the detected peak for determining the +% duration of the event, also based upon the local +% distribution. +% minIsi: a threshold setting the minimum time between detections in +% seconds. +% +% minDurSW: a threshold setting the minimum duration of a localized +% sharp-wave event. +% +% maxDurSW: a threshold setting the maximum duration of a localized +% sharp-wave event. +% +% minDurRP: a threshold setting the minimum duration of a localized +% ripple event associated with a sharp-wave. +% +% EVENTFILE: boolean, a flag that triggers whether or not to write out +% an event file corresponding to the detections (for +% inspection in neuroscope). +% +% FIGS: boolean, a flag that allows the user to output a figure +% detailing the final clustering of the features and SWR +% +% TRAINING: boolean, a flag for processing labeled training data +% supplied by the user. +% +% DEBUG: boolean, a flag that triggers the program to output an +% additional set of figures and information. +% +% MAXGIGS: the maximum number of GIGABYTES this +% +% noPrompts true/false disable any user prompts (default: true) +% +%%%%%%%%%%%%%%% +%%% OUTPUTS %%% +%%%%%%%%%%%%%%% +% detected_swr: structure with fields +% +% .timestamps : Nx2, Start and end timestamps of each SWR +% .peaks : Nx1, trough nearest to peak power of ripple timestamp +% of each SWR +% .peakNormedPower : Peak power values of each SWR +% .detectorName : Name of this function +% .stdev : Standard deviation used to threshold ripple power +% .SwMax : Sharp-wave magnitude [zscore "local empirical percentile"] +% .RipMax : Ripple magnitude [zscore "local empirical percentile"] +% .detectorinfo : a structure with fields corresponding to all the +% parameters used in the analysis +% +% OPTIONAL +% event file: an event file formatted as filename.R01.evt that is +% compatible with neuroscope for inspecting detections +% [peak start stop] +% +% The analysis also writes out log file (.txt) containing the code used. +% +% DEPENDENCIES: all the major routines are contained within the associated +% private folder. All should need to do is add the bz_DetectSWR +% folder to your path. One of the training processes runs +% linear discriminant analysis on the joint and marginal +% spaces of the features. There is a statistics toolbox +% dependency, R2013a, which I believe requires different +% commands in later versions. I can update this if this +% routine becomes widely used. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% SET DEFAULT FREE PARAMETERS %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +p = inputParser; +addParameter(p, 'basepath', pwd, @isstr); +addParameter(p, 'Epochs', [], @isnumeric); +addParameter(p, 'saveMat', true, @islogical); +addParameter(p, 'forceDetect', false, @islogical); +addParameter(p, 'swBP', [ 2 50 ], @isnumeric); +addParameter(p, 'ripBP', [ 80 250 ], @isnumeric); +addParameter(p, 'per_thresswD', 10, @isnumeric); % percentile +addParameter(p, 'per_thresRip', 50, @isnumeric); % percentile +addParameter(p, 'WinSize', 200, @isnumeric); % ms +addParameter(p, 'Ns_chk', 5, @isnumeric); % seconds +addParameter(p, 'thresSDswD', [0.5 2.5], @isnumeric); % standard deviations +addParameter(p, 'thresSDrip', [0.5 2.5], @isnumeric); % standard deviations +addParameter(p, 'minIsi', 0.10, @isnumeric); % seconds +addParameter(p, 'minDurSW', 0.02, @isnumeric); % seconds +addParameter(p, 'maxDurSW', 0.5, @isnumeric); % seconds +addParameter(p, 'minDurRP', 0.025, @isnumeric); % seconds +addParameter(p, 'EVENTFILE', true, @islogical); +addParameter(p, 'FIGS', false, @islogical); +addParameter(p, 'TRAINING', false, @islogical); +addParameter(p, 'DEBUG', false, @islogical); +addParameter(p, 'MAXGIGS', 16, @isnumeric); % GigaBytes +addParameter(p, 'noPrompts', true, @islogical); + +parse(p,varargin{:}); + +basepath = p.Results.basepath; +Epochs = p.Results.Epochs; +saveMat = p.Results.saveMat; +forceDetect = p.Results.forceDetect; +swBP = p.Results.swBP; +ripBP = p.Results.ripBP; +per_thresswD = p.Results.per_thresswD; +per_thresRip = p.Results.per_thresRip; +WinSize = p.Results.WinSize; +Ns_chk = p.Results.Ns_chk; +thresSDswD = p.Results.thresSDswD; +thresSDrip = p.Results.thresSDrip; +minIsi = p.Results.minIsi; +minDurSW = p.Results.minDurSW; +maxDurSW = p.Results.maxDurSW; +minDurRP = p.Results.minDurRP; +EVENTFILE = p.Results.EVENTFILE; +FIGS = p.Results.FIGS; +TRAINING = p.Results.TRAINING; +DEBUG = p.Results.DEBUG; +MAXGIGS = p.Results.MAXGIGS; +noPrompts = p.Results.noPrompts; + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% IF RIPPLES ALREADY FOUND %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +sessionInfo = bz_getSessionInfo(basepath, 'noPrompts', noPrompts); +if exist([sessionInfo.FileName '.ripples.events.mat'],'file') && ~forceDetect + disp('Ripples already detected! Loading file...'); + load([sessionInfo.FileName '.ripples.events.mat']); + return +end + + + +%%%%%%%%%%%%%%%%%%%% +%%% INPUT CHECKS %%% +%%%%%%%%%%%%%%%%%%%% +mfname = mfilename; +% 1) fileabase +% user might have input an absolute or relative path +Filebase = bz_BasenameFromBasepath(basepath); +[pathname, filename, extname] = fileparts(Filebase); +if isempty(pathname) + pathname = pwd; +end + +% check whether user provided a file extension +if isempty(extname) + filechk = dir([Filebase '.xml']); + if isempty(filechk) + error(['%s: incompatible format for 1st argument (Filebase)\n', ... + '\tIf user supplied a relative path, the .xml and all\n', ... + '\trelevant files must be in the current directory.\n'],mfname); + end +else + filechk = dir(Filebase); + if isempty(filechk) + error(['%s: incompatible format for 1st argument (Filebase)\n', ... + '\tIf user supplied a relative path, the .xml and all\n', ... + '\trelevant files must be in the current directory.\n'],mfname); + end +end +Filebase = [pathname filesep filename]; + +% Read in xml of file parameters +fprintf(1, 'Loading XML file...\n'); +par = LoadXml( [Filebase '.xml'] ); +% pull parameters from .xml file +SR = par.lfpSampleRate; % lfp sampling rate +nChan = par.nChannels; % number of channels in the recording + +% 2) Channels +if ~all(Channels > 0 & Channels <= nChan) % corrected by AFR + error(['%s: incompatible input for 2nd argument (Channels)\n', ... + '\tAccording to the .xml file, the user did not supply\n', ... + '\ta valid list of channels.\n'],mfname); +end +Nchan = length(Channels); + +% Access lfp looking for an .lfp file first and a .lfp file second +if ~isempty(dir([Filebase '.lfp'])) + lfp_file = [Filebase '.lfp']; + lfp_info = dir(lfp_file); +elseif ~isempty(dir([Filebase '.lfp'])) + lfp_file = [Filebase '.lfp']; + lfp_info = dir(lfp_file); +else + error(['%s: Field potential file could not be found\n', ... + '\tThere is neither a .lfp nor a .lfp file matching the\n', ... + '\tbase name of the directory supplied by the user.\n'],mfname); +end + +% 3) Epochs +if nargin < 3 || isempty(Epochs) + % use the whole recording if no restrictions supplied by user + fprintf( 1, '%s: Using Default Epochs:\n', mfname ); + fprintf( 1, '\tNo Epochs supplied by user. Using the whole recording.\n'); + Epochs = [1/SR lfp_info.bytes/(2*nChan*SR)]; +elseif ~all(Epochs(:,1) < Epochs(:,2)) || ... + ~all(Epochs(:) > 0 & Epochs(:) < lfp_info.bytes/(2*nChan*SR)) || ... + size(Epochs,2) > 2 + error(['%s: Epochs are not formatted properly:\n', ... + '\tEpochs must be formatted as a list of [start stop] times in seconds.\n'], mfname); +end +Nepochs = size(Epochs,1); + + +%%%%%%%%%%%%%%%%%%%% +%%% MEMORY CHECK %%% +%%%%%%%%%%%%%%%%%%%% +% This routine requires 4*NumberOfChannelsRequested + 2 GigaBytes. +% This estimate includes memory used to copy the inputs into functions (my +% kingdom for a pointer!). +if isfield(par,'nBits') + ByteConversion = 64/par.nBits; % (double in Bytes)/(nBits -> Bytes) +else + ByteConversion = 4; +end +BytesPerChan = (lfp_info.bytes/nChan)*ByteConversion; +GigsRequested = (4*BytesPerChan*Nchan + 2*BytesPerChan)/(1024^3); +if GigsRequested > MAXGIGS + error(['%s: Too much memory requested\n', ... + '\tThe user requested %4.1f GigaBytes and the maximum\n', ... + '\tcurrently allowed is set by the variable MAXGIGS at %4.1f GigaBytes.\n', ... + '\tIf your system can handle more memory, increase the value of MAXGIGS.\n'],mfname,GigsRequested,MAXGIGS); +end + +%%%%%%%%%%%%%%%%%%%% + +if TRAINING + % Access hand labeled ripple events and convert to seconds + fprintf( 1, 'Loading Labeled Event File for Training...\n'); + + training_rip = [Filebase '.rip.evt']; + if isempty(dir(training_rip)) + error(['%s: Training Event file could not be found\n', ... + '\tThere is not an event file "Filebase.rip.evt" in \n', ... + '\tthe directory supplied by the user.\n'],mfname); + end + ripfile = fopen(training_rip,'r'); + + % count the number of lines in the file and rewind + count = 0; + while ischar(fgetl(ripfile)) + count = count + 1; + end + frewind(ripfile); + + % Access ripple events and convert to seconds [start, stop] + if mod(count,2) + fclose(ripfile); + error('Check your event file: you should have start/stop pairs, but you have an odd number of lines'); + end + + % Parse event file to pull out hand labeled start/stop event pairs + rippleTs = zeros(count/2,2); + count = 1; + while 1 + tline = fgetl(ripfile); + if ~ischar(tline), break, end + + if strfind(tline,'start') + + ws = regexp(tline,'\s'); + rippleTs(round(count/2),1) = str2double(tline(1:ws-1))/1000; + + elseif strfind(tline,'stop') + + ws = regexp(tline,'\s'); + rippleTs(round(count/2),2) = str2double(tline(1:ws-1))/1000; + end + count = count + 1; + end + fclose(ripfile); + + % For each ripple, I will select a segment of lfp data of the same length, + % that does not include a ripple. I will use these paired samples for + % comparison. + nonrippleTs = zeros(size(rippleTs)); + counter = 0; + + % for each trial, find the labeled ripples contained + fprintf( 1, 'Parsing non-ripple paired samples...\n'); + for ii = 1:Nepochs + + ripples = find(rippleTs(:,1) > Epochs(ii,1) & rippleTs(:,1) < Epochs(ii,2)); + if isempty(ripples) + continue + end + + % for each ripple within this trial, select a corresponding nonripple + % segment, checking to make sure it doesn't overlap with any ripples + % within that trial. + Trip = rippleTs(ripples,:); + Nrip = length(ripples); + nonripTmp = zeros(Nrip,2); + nonripTmp(:,1) = (Epochs(ii,2)-Epochs(ii,1))*rand(Nrip,1) + Epochs(ii,1); + nonripTmp(:,2) = nonripTmp(:,1) + diff(Trip,[],2); + + % Check nonripple segments for overlap with ripple segments and each + % other + flags = true(Nrip,1); + inds = 1:Nrip; + while any(flags) + for jj = inds + if flags(jj) + + % Concatenate all nonripples and all ripple times, + % excluding the one under inspection. + if Nrip > 1 + temp = nonripTmp; + temp(jj,:) = []; % exclude current nonripple + Temp = zeros(2*Nrip - 1,2); + Temp(1:Nrip - 1,:) = temp; + Temp(Nrip:end,:) = Trip; + else + Temp = Trip; + end + + % Check for overlap between current "nonripple" and ripples + % and other "nonripples" + if isempty(find(nonripTmp(jj,1) > Temp(:,1) & nonripTmp(jj,1) < Temp(:,2),1)) && ... + isempty(find(nonripTmp(jj,2) > Temp(:,1) & nonripTmp(jj,2) < Temp(:,2),1)) + + % log nonripple + nonrippleTs(counter+inds(jj),:) = nonripTmp(jj,:); + flags(jj) = 0; + + else + + % Resample + nonripTmp(jj,1) = (Epochs(ii,2)-Epochs(ii,1))*rand + Epochs(ii,1); + nonripTmp(jj,2) = nonripTmp(jj,1) + diff(Trip(jj,:)); + + % set all flags to true to double check that this + % resample didn't result in overlap + flags = true(Nrip,1); + end + end + end + end + % increment counter + counter = counter + Nrip; + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% ESTIMATE LFP FEATURES: START %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% As a consession to memory usage, I interleave loading of field potential +% variables, and extracting segments in the case of training data. + +% load lfp file +fprintf( 1, 'Loading LFP file...\n'); +lfp = LoadBinary(lfp_file,'frequency',SR,'nChannels',nChan,... + 'channels',Channels); + +% filter for sharp wave using Eran's routines and compute SW diff feature +hSw1 = makegausslpfir( swBP(1), SR, 6 ); +hSw2 = makegausslpfir( swBP(2), SR, 6 ); + +lfpLow = firfilt( lfp, hSw2 ); % lowpass filter +eegLo = firfilt( lfpLow, hSw1 ); % highpass filter +% lfpLow = lfpLow -.lfpLo; % difference of Gaussians +% clear .lfpLo %DL: not sure what this '.' was doing here... +lfpLow = lfpLow - eegLo; % difference of Gaussians +clear eegLo + + +swDiff = lfpLow(:,1) - lfpLow(:,end); + +if TRAINING + % Extract ripple and paired non-ripple segments from lfp + fprintf( 1,'Training: Extracting Paired Segments of lfp, lfpLow, and swDiff...\n'); + + % convert timestamps to samples + rippleSamp = floor(rippleTs*SR); + nonrippleSamp = floor(nonrippleTs*SR); + + % cut out ripple and paired non-ripple segments + pairs0 = cell(counter,2); + pairs1 = cell(counter,2); + pairs2 = cell(counter,2); + + for ii = 1:counter + pairs0{ii,1} = lfp(rippleSamp(ii,1):rippleSamp(ii,2),:); + pairs0{ii,2} = lfp(nonrippleSamp(ii,1):nonrippleSamp(ii,2),:); + + pairs1{ii,1} = lfpLow(rippleSamp(ii,1):rippleSamp(ii,2),:); + pairs1{ii,2} = lfpLow(nonrippleSamp(ii,1):nonrippleSamp(ii,2),:); + + pairs2{ii,1} = swDiff(rippleSamp(ii,1):rippleSamp(ii,2),1); + pairs2{ii,2} = swDiff(nonrippleSamp(ii,1):nonrippleSamp(ii,2),1); + + end +end +clear lfpLow + +% I made 2 changes to Eran's defaults for ripple processing: +% 1) I subtract the across channel mean from each channel prior to +% filtering +% 2) After subtracting, filtering, envelope, powerwin, and take the maximum +% across all channels at each time point as the ripple power. + +% filter and process ripples, using Eran's defaults (diff of Gaussians) +hRip1 = makegausslpfir( ripBP( 1 ), SR, 6 ); +hRip2 = makegausslpfir( ripBP( 2 ), SR, 6 ); + +% subtract mean before filtering +lfpM = lfp - int16(repmat(mean(lfp,2),[1 Nchan])); +%clear lfp + +rip = firfilt( lfpM, hRip2 ); % highpass filter +clear lfpM +eegLo = firfilt( rip, hRip1 ); % lowpass filter +% rip = rip -.lfpLo; % difference of gaussians +% clear.lfpLo +rip = rip - eegLo; % difference of gaussians +clear eegLo + +ripWindow = pi / mean( ripBP ); +powerWin = makegausslpfir( 1 / ripWindow, SR, 6 ); + +rip = abs(rip); +ripPower0 = firfilt( rip, powerWin ); +ripPower0 = max(ripPower0,[],2); +clear rip + +if TRAINING + % Extract ripple and paired non-ripple segments from lfp + fprintf( 1,'Training: Extracting Paired Segments of ripple Power...\n'); + + % cut out ripple and paired non-ripple segments + pairs3 = cell(counter,2); + + for ii = 1:counter + + pairs3{ii,1} = ripPower0(rippleSamp(ii,1):rippleSamp(ii,2),1); + pairs3{ii,2} = ripPower0(nonrippleSamp(ii,1):nonrippleSamp(ii,2),1); + + end + + if DEBUG + % Inspect paired segements + figure('Position',get(0,'ScreenSize')) + % Examine pairs, including low pass filtered data + for ii = 1:counter + set(gcf,'Name',sprintf('SWR Pair %d',ii)) + for jj = 1:Nchan + + h1 = subplot(Nchan + 2, 2, 2*jj - 1); ... + plot(pairs0{ii,1}(:,jj)), hold on + plot(pairs1{ii,1}(:,jj),'r'), hold off + ylims1 = get(h1,'ylim'); + + h2 = subplot(Nchan + 2, 2, 2*jj); ... + plot(pairs0{ii,2}(:,jj)), hold on + plot(pairs1{ii,2}(:,jj),'r'), hold off + ylims2 = get(h2,'ylim'); + + if jj == 1 + set(get(h1,'title'),'string','Labeled SWR') + set(get(h2,'title'),'string','Paired Non-SWR sample') + end + set(h1,'ylim',[min([ylims1(1) ylims2(1)]) max([ylims1(2) ylims2(2)])]) + set(h2,'ylim',[min([ylims1(1) ylims2(1)]) max([ylims1(2) ylims2(2)])]) + end + + h3 = subplot(Nchan + 2, 2, 2*Nchan + 1); + plot(pairs2{ii,1},'b'), hold off + ylims3 = get(h3,'ylim'); + ylabel('SW Diff') + h4 = subplot(Nchan + 2, 2, 2*Nchan + 2); + plot(pairs2{ii,2},'r'), hold off + ylims4 = get(h4,'ylim'); + h5 = subplot(Nchan + 2, 2, 2*(Nchan + 1) + 1); + plot(pairs3{ii,1},'b'), hold off + ylims5 = get(h5,'ylim'); + ylabel('Ripple Power') + h6 = subplot(Nchan + 2, 2, 2*(Nchan + 1) + 2); + plot(pairs3{ii,2},'r'), hold off + ylims6 = get(h6,'ylim'); + + set(h3,'ylim',[min([ylims3(1) ylims4(1)]) max([ylims3(2) ylims4(2)])]) + set(h4,'ylim',[min([ylims3(1) ylims4(1)]) max([ylims3(2) ylims4(2)])]) + set(h5,'ylim',[min([ylims5(1) ylims6(1)]) max([ylims5(2) ylims6(2)])]) + set(h6,'ylim',[min([ylims5(1) ylims6(1)]) max([ylims5(2) ylims6(2)])]) + + drawnow + pause(.1) + end + end +end + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% ESTIMATE LFP FEATURES: END %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +if TRAINING + + fprintf(1,'Estimating Performance on Training Data...\n'); + + % Assess how informative these features are for detection + % as a first pass, I will take the max of each feature with each paired + % sample window + swDiffMax = zeros(counter,2); + ripPowerMax = zeros(counter,2); + + for ii = 1:counter + swDiffMax(ii,1) = max(pairs2{ii,1}); + swDiffMax(ii,2) = max(pairs2{ii,2}); + + ripPowerMax(ii,1) = max(pairs3{ii,1}); + ripPowerMax(ii,2) = max(pairs3{ii,2}); + end + + % Calculate marginal histograms + [n1a, ctr1a] = hist(swDiffMax(:,1),40); + [n1b, ctr1b] = hist(swDiffMax(:,2),40); + [n2a, ctr2a] = hist(ripPowerMax(:,1),40); + [n2b, ctr2b] = hist(ripPowerMax(:,2),40); + + % run a basic k-means clustering and evaluate performance + [idx,Ctrs] = kmeans([swDiffMax(:) ripPowerMax(:)],2); + + labels = [ones(counter,1);2*ones(counter,1)]; + scores = logical(idx == labels); + + % let's run a linear discriminant analysis on the joint and marginal spaces + % (requires the statistics toolbox, R2013a, I believe the commands change + % in later versions) + FeaturesAll = [swDiffMax(:) ripPowerMax(:)]; + cls1 = ClassificationDiscriminant.fit(FeaturesAll,labels); % joint + cls2 = ClassificationDiscriminant.fit(FeaturesAll(:,1),labels); % swDiff + cls3 = ClassificationDiscriminant.fit(FeaturesAll(:,2),labels); % rip Power + % access performance on training data + perf0 = 100 - ((resubLoss(cls1)*cls1.NObservations)/cls1.NObservations)*100; + perf1 = 100 - ((resubLoss(cls2)*cls2.NObservations)/cls2.NObservations)*100; + perf2 = 100 - ((resubLoss(cls3)*cls3.NObservations)/cls3.NObservations)*100; + + % For the unsupervised case, I'll set thresholds for ripple power + % and sharp wave difference magnitude according to thresholds upon the + % empirical distributions. This requires I map the probabilities of one + % cluster to that of the other. + x = ripPowerMax(:,1); + [Fi,xi] = ecdf(x); + n = length(x); + xj = xi(2:end); + Fj = (Fi(1:end-1)+Fi(2:end))/2; + xj = [xj(1)-Fj(1)*(xj(2)-xj(1))/((Fj(2)-Fj(1))); + xj; + xj(n)+(1-Fj(n))*((xj(n)-xj(n-1))/(Fj(n)-Fj(n-1)))]; + Fj = [0; Fj; 1]; + + if DEBUG + figure('Name','Empirical Cumulative Probability Distribution') + stairs(xi,Fi,'r'); + hold on + plot(xj,Fj,'b-'); + hold off + end + + % Map the Nth percentile in the non-SWR cluster to the Pth in the SWR + % cluster. + per_thres = 50; + per_corr = 100*interp1(xj,Fj,prctile(ripPowerMax(:,2),per_thres)); + + % Main Figure in the case of hand labeled training data + % close all + figure('Color','w'), + + string = sprintf(['Sharp Wave Difference vs. Ripple Power\n',... + 'Mean subtraction before filter\n', ... + 'Max at each time point is taken for ripple power']); + + % Joint space of SWR difference and ripple power + h1 = subplot(2,2,2); + plot(swDiffMax(:,1), ripPowerMax(:,1),'.b'), hold on + plot(swDiffMax(:,2), ripPowerMax(:,2),'.r'), hold on + axis([min(swDiffMax(:)) max(swDiffMax(:)) 0 max(ripPowerMax(:))]) + % LDA coefficients + K1 = cls1.Coeffs(1,2).Const; + L1 = cls1.Coeffs(1,2).Linear; + % LDA function + f1 = @(x1,x2) K1 + L1(1)*x1 + L1(2)*x2; + % Percentile threshold + line(get(h1,'xlim'), repmat(prctile(ripPowerMax(:,1),per_corr), [1 2]), ... + 'linestyle','--','color','k') + l0 = ezplot(h1,f1,[get(h1,'xlim') get(h1,'ylim')]); + set(l0, 'Color','g','linewidth',2) + title(h1,string,'fontsize',12) + legend('SWR','~SWR','Location','NorthEastOutside') + xlabel('Sharp Wave Diff','fontsize',12) + ylabel('Ripple Power','fontsize',12) + set(h1,'fontsize',12) + % Marginal Space of Sharp Wave Difference + h2 = subplot(2,2,4); + bar(ctr1a,-n1a,1,'b'), hold on, bar(ctr1b,-n1b,1,'r'), ... + axis([min(swDiffMax(:)) max(swDiffMax(:)) -max([n1a n1b])*1.1 0]), + % LDA coefficients + K2 = cls2.Coeffs(1,2).Const; + L2 = cls2.Coeffs(1,2).Linear; + % LDA function + f2 = @(x1,x2) K2 + L2(1)*x1; + l1 = ezplot(h2,f2,[get(h2,'xlim') get(h2,'ylim')]); + set(l1, 'Color','g','linewidth',2) + title('') + xlabel('') + ylabel('') + axis('off'), + % Marginal Space of Ripple Power + h3 = subplot(2,2,1); + barh(ctr2a,-n2a,1,'b'), hold on, barh(ctr2b,-n2b,1,'r'), ... + axis([-max([n2a n2b])*1.1 0 min(ripPowerMax(:)) max(ripPowerMax(:))]) + % LDA coefficients + K3 = cls3.Coeffs(1,2).Const; + L3 = cls3.Coeffs(1,2).Linear; + % LDA function + f3 = @(x2,x1) K3 + L3(1)*x1; % note the flip in x and y + l2 = ezplot(h3,f3,[get(h3,'xlim') get(h3,'ylim')]); + set(l2, 'Color','g','linewidth',2) + title('') + xlabel('') + ylabel('') + axis('off'), + set(h1,'Position',[0.25 0.35 0.55 0.55]); + set(h2,'Position',[.25 .1 .55 .15]); + set(h3,'Position',[.05 .35 .15 .55]); + + string = sprintf(['K-means Clustering:\n', ... + ' Center1: X = %7.2f, Y = %7.2f\n', ... + ' Center2: X = %7.2f, Y = %7.2f\n', ... + ' Performance = %4.1f percent\n\n', ... + 'LDA, joint space:\n',... + ' Performance = %4.1f percent\n\n', ... + 'LDA, 1D SW Diff:\n',... + ' Performance = %4.1f percent\n\n', ... + 'LDA, 1D Ripple Power:\n',... + ' Performance = %4.1f percent\n\n', ... + '%dth percentile of ~SWR corresponds to\n%3.1fth percentile of SWR'], ... + Ctrs(1,1),Ctrs(1,2),Ctrs(2,1),Ctrs(2,2), ... + (sum(scores)/numel(scores))*100,perf0,perf1,perf2,per_thres,per_corr); + + annotation('textbox',[0.81 0.70 .1 .1],'String',string, ... + 'edgecolor','none','fontsize',12) + +end + +%%%%%%%%%%%%%%%%%%%%%%%%% +%%% DETECT SWR EVENTS %%% +%%%%%%%%%%%%%%%%%%%%%%%%% +% I take the max of the sharp waves in non-overlapping windows. Once a +% maximum in the sharp wave is detected, the maximum ripple power around +% that detected is logged. + +% Specifically, maximum detection is with reference to the Sharp Wave. If a +% 3 point maximum is detected for the sharp wave feature, then a maximum +% for the ripple power is also logged for that detection within a window +% around sharp wave detection. + +% This first pass is permissive: it is just detecting maxima without +% regards for magnitude + +WinSize = floor((WinSize*SR)/1000); %#ok % window in samples +HalfWinSize = floor(WinSize/2); % for logging ripple power around SW + +% for each trial, block features into windows +% first pass, just count blocks for pre-allocating memory +Nblocks = 0; +for ii = 1:Nepochs + epoch_inds = floor(Epochs(ii,:)*SR); + blocks = epoch_inds(1):WinSize:epoch_inds(2); + Nblocks = Nblocks + numel(blocks)-1; +end + +% Second pass populates values +swDiffAll = zeros(Nblocks,1); +ripPowerAll = zeros(Nblocks,1); + +% In samples at SR +featureTs = zeros(Nblocks,1); + +MaxCounter = 0; +for ii = 1:Nepochs + epoch_inds = floor(Epochs(ii,:)*SR); + blocks = epoch_inds(1):WinSize:epoch_inds(2); + + for jj = 1:length(blocks)-1 + %Find the maximum sharp-wave power in the block + [tempswDiff,ind0] = max(swDiff(blocks(jj):blocks(jj+1)-1,1)); + + % Transform from sample within block to sample within record + maxSample = blocks(jj) + ind0 - 1; + + % If the maximum is detected on the edges of the window, run a 3 + % point max check over the boundaries to resolve edge effect + if (ind0 == 1 || ind0 == WinSize) + + % Check boundaries and then check for 3 point max + if (maxSample == 1) || (maxSample == size(swDiff,1)) + % Don't use the boundaries of the recording + continue + else + % Check for 3 point maximum + [~, maxchk] = max([swDiff(maxSample - 1,1) ... + swDiff(maxSample,1) ... + swDiff(maxSample + 1,1)]); + if abs(2 - maxchk) > 0 + % This is not a maximum + continue + end + end + end + % Log features + MaxCounter = MaxCounter + 1; + featureTs(MaxCounter) = maxSample; + swDiffAll(MaxCounter) = tempswDiff; + %ripPowerAll(MaxCounter) = max(ripPower0(maxSample-HalfWinSize:maxSample+HalfWinSize,1)); + %added by aza to detect very long + if (maxSample-HalfWinSize)<1 + ripPowerAll(MaxCounter) = max(ripPower0(1:maxSample+HalfWinSize,1)); + else + %aza + try + ripPowerAll(MaxCounter) = max(ripPower0(maxSample-HalfWinSize:maxSample+HalfWinSize,1)); + catch + ripPowerAll(MaxCounter) = max(ripPower0(maxSample-10:maxSample+10,1)); + end + end +end +featureTs = featureTs(1:MaxCounter); +swDiffAll = swDiffAll(1:MaxCounter); +ripPowerAll = ripPowerAll(1:MaxCounter); + +% run a basic k-means clustering to partition SWR from ~SWR +[idx,Ctrs] = kmeans([swDiffAll(:) ripPowerAll(:)],2); + +% we know there will be far fewer SWR than not, so let's assign idx1 to +% the cluster with the fewest members i.e. SWR +if sum(idx == 1) > sum(idx == 2) + idx1 = logical(idx == 1); + idx2 = logical(idx == 2); + % reverse labels + temp = idx1; + idx1 = idx2; + idx2 = temp; + clear temp +else + idx1 = logical(idx == 1); + idx2 = logical(idx == 2); +end + +% For the unsupervised case, I'll set thresholds for ripple power +% and sharp wave difference magnitude according to thresholds upon the +% empirical distributions. + +if TRAINING + fprintf(1,'Estimating Precision and Recall surfaces for features...'); + % Construct Precision/Recall surfaces + thresL_swDs = 0:5:100; + thresL_Rips = 0:5:100; + + % Calculate precision/recall values if labeled data is supplied + % precision: true_positive / (true_positive + false_positive) + % recall: true_positive / (true_positive + false_negative) + Precision = zeros([length(thresL_swDs) length(thresL_Rips)]); + Recall = zeros([length(thresL_swDs) length(thresL_Rips)]); + + for jj = 1:length(thresL_swDs) + for kk = 1:length(thresL_Rips) + + per_thresswDt = thresL_swDs(jj); + per_thresRipt = thresL_Rips(kk); + + % Sharp Wave Difference: set a lower cutoff in terms of percentiles + thresL_swD = prctile(swDiffAll(idx1),per_thresswDt); + + % Map the Nth percentile in the non-SWR cluster to the Pth in the SWR + % cluster. + thresL_Rip = prctile(ripPowerAll(idx2,1),per_thresRipt); + + % retain SWR candidates according to clustering and thresholds + SWRind = idx1 & swDiffAll > thresL_swD & ripPowerAll > thresL_Rip; + + % convert featureTs to seconds + featureTsS = featureTs*1/SR; + SWRs = featureTsS(SWRind); + + TP = 0; % true positive + FP = 0; % false positive + + hits = false(size(rippleTs,1),1); + for ii = 1:length(SWRs) + + chk = SWRs(ii) > rippleTs(:,1) & SWRs(ii) < rippleTs(:,2); + + if sum(chk) < 1 + FP = FP + 1; + else + % exclude duplicates + if any(hits & chk) + continue + end + TP = TP + 1; + hits = hits | chk; + end + + end + + FN = sum(~hits); % false negatives + + Precision(jj,kk) = TP / (TP + FP); + Recall(jj,kk) = TP / (TP + FN); + + end + end + + figure, + subplot(121),imagesc(Precision), + xlabel('thresL rip'),ylabel('thresL swD'),title('Precision'), + set(gca,'ytick',1:2:21) + set(gca,'yticklabel',0:10:100) + set(gca,'xtick',1:2:21) + set(gca,'xticklabel',0:10:100) + colorbar + axis square + + subplot(122),imagesc(Recall), + xlabel('thresL rip'),ylabel('thresL swD'),title('Recall'), + set(gca,'ytick',1:2:21) + set(gca,'yticklabel',0:10:100) + set(gca,'xtick',1:2:21) + set(gca,'xticklabel',0:10:100) + colorbar + axis square +end + +%%%%%%%%%%%%%%%%%%%%%%%%% +%%% LOCALIZING EVENTS %%% +%%%%%%%%%%%%%%%%%%%%%%%%% +% Localize events and winnow detections using the following criteria: +fprintf('Localizing and Screening Detections ...\n'); +% 1) detected sharp wave difference magnitude must be > thresSDswD(2) +% 2) detected ripple power must be > thresSDrip(2) +% 3) detections can't be within a minimum inter-event threshold +% 4) both the detected sharp-wave and ripple power increase mubt persistent + % for a minimum duration. + minDurSWs = floor(minDurSW*SR); % convert to samples + minDurRPs = floor(minDurRP*SR); +% 5) The sharp-wave can not exceed a maximum duration + maxDurSWs = floor(maxDurSW*SR); + +% Sharp Wave Difference: set a lower cutoff in terms of percentiles +thresL_swD = prctile(swDiffAll(idx1),per_thresswD); + +% Set the value in the the non-SWR cluster at the Nth percentile as the +% lower threshold for ripple detection +thresL_Rip = prctile(ripPowerAll(idx2,1),per_thresRip); + +% retain SWR candidates according to clustering and thresholds +SWRind = idx1 & swDiffAll > thresL_swD & ripPowerAll > thresL_Rip; +SWRid = find(SWRind); + +% Access time-stamps of SWR features +SWRs = featureTs(SWRind); +Nswr = length(SWRs); + +% Access the subset of detections in samples to check for localization +bound = Ns_chk*SR; % convert to samples + +% Exclude candidate SWRs that don't have a +/- bounds around them. Of +% course, if you make bounds huge, or have a short recording, this will be +% a problem +exclusions = (SWRs - bound < 1) | (SWRs + bound > size(swDiff,1)); +SWRs(exclusions) = []; +SWRind(exclusions) = []; +SWRid(exclusions) = []; + +fprintf(1,'Number of candidate SWRs excluded due to boundary constraints: %d\n',Nswr - length(SWRs)); +Nswr = length(SWRs); +timeline = -bound:bound; +SWR_diff = [0; diff(SWRs*(1/SR))]; % in seconds + +% localize events +SWR_valid.Ts = zeros(size(SWRs,1),3); % peak, start, stop in samples +SWR_valid.SwMax = zeros(size(SWRs,1),2); % z-score & percentile +SWR_valid.RipMax = zeros(size(SWRs,1),2); % z-score & percentile + +valid = 0; +SWRindF = SWRind; + +if DEBUG + figure +end + +for ii = 1:Nswr + + if DEBUG + set(gcf,'Name',sprintf('Detection # %d out of %d',ii,Nswr)) + end + + % threshold feature distributions to localize about detection + + % 1) detected sharp wave difference magnitude must be > thresSD locally + swDiff_chk = swDiff(SWRs(ii)-bound:SWRs(ii)+bound); + MswDiff = median(swDiff_chk); + SDswDiff = std(swDiff_chk); + + if swDiff_chk(bound+1) < MswDiff + thresSDswD(2)*SDswDiff + if DEBUG + disp('Sharp Wave Difference magnitude too small locally') + end + SWRindF(SWRid(ii)) = false; + continue + end + + % 2) detected ripple power must be > thresSD locally + ripPow_chk = ripPower0(SWRs(ii)-bound:SWRs(ii)+bound); + MripPow = median(ripPow_chk); + SDripPow = std(ripPow_chk); + + if ripPowerAll(SWRid(ii)) < MripPow + thresSDrip(2)*SDripPow + if DEBUG + disp('Ripple Power too low locally') + end + SWRindF(SWRid(ii)) = false; + continue + end + + % 3) detections can't be within a minimum inter-event interval thresh + if ii < Nswr + if SWR_diff(ii) < minIsi + if DEBUG + disp('IEI is too small') + end + SWRindF(SWRid(ii)) = false; + continue + end + end + + % Estimate duration of detected Sharp-Wave event + startSWR = find(swDiff_chk(1:bound + 1) < MswDiff + thresSDswD(1)*SDswDiff,1,'last'); + stopSWR = find(swDiff_chk(bound + 1: end) < MswDiff + thresSDswD(1)*SDswDiff,1,'first'); + durSWR = (bound + 1 - startSWR) + stopSWR; + + % Estimate duration of detected ripple power increase event + % find index of maximum ripple power increase around Sharp-Wave + [~,maxRpInd] = max(ripPow_chk(bound+1-HalfWinSize:bound+1+HalfWinSize)); + startRP = find(ripPow_chk(1:bound - HalfWinSize + maxRpInd) ... + < MripPow + thresSDrip(1)*SDripPow,1,'last'); + stopRP = find(ripPow_chk(bound - HalfWinSize + maxRpInd: end) ... + < MripPow + thresSDrip(1)*SDripPow,1,'first'); + durRP = (bound - HalfWinSize + maxRpInd - startRP) + stopRP; + + % 4) events associated with detections must exceed a minimum duration + + if durSWR < minDurSWs + if DEBUG + disp('Sharp-Wave Event Duration is too short.') + end + SWRindF(SWRid(ii)) = false; + continue + end + + if durRP < minDurRPs + if DEBUG + disp('Ripple Event Duration is too short.') + end + SWRindF(SWRid(ii)) = false; + continue + end + + % 5) sharp-wave must not exceed a maximum duration + if durSWR > maxDurSWs + if DEBUG + disp('Sharp-Wave Event Duration is too long.') + end + SWRindF(SWRid(ii)) = false; + continue + end + + % We have a valid detection + valid = valid + 1; + SWR_valid.Ts(valid,:) = [SWRs(ii) (SWRs(ii)-(bound+1-startSWR)) (SWRs(ii)+stopSWR-1)]; + + swMax = swDiffAll(SWRid(ii)); + ripMax = ripPowerAll(SWRid(ii)); + + SWR_valid.SwMax(valid,:) = ... + [(swMax - MswDiff)/SDswDiff sum(swDiff_chk < swMax)/(2*bound + 1)]; + + SWR_valid.RipMax(valid,:) = ... + [(ripMax - MripPow)/SDripPow sum(ripPow_chk < ripMax)/(2*bound + 1)]; + + if DEBUG + h1 = subplot(2,3,[1 2]); + plot(timeline,swDiff_chk), axis tight, hold on + plot([timeline(startSWR) timeline(bound+stopSWR)],... + [swDiff_chk(startSWR) swDiff_chk(bound+stopSWR)],'og') + line([0 0],get(gca,'ylim'), ... + 'linestyle','--','color','k') + line(get(gca,'xlim'),repmat(MswDiff,[1 2]), ... + 'linestyle','--','color','k') + line(get(gca,'xlim'),repmat(MswDiff+thresSDswD(2)*SDswDiff,[1 2]),... + 'linestyle','--','color','r') + line(get(gca,'xlim'),repmat(MswDiff-thresSDswD(2)*SDswDiff,[1 2]), ... + 'linestyle','--','color','r') + xlabel('Samples'), + ylabel('A.U.'), + title(sprintf(['Sharp Wave Difference Magnitude\n',... + 'Sharp Wave Duration:%5.1f'],durSWR*(1000/SR))), hold off + subplot(2,3,3), hist(swDiff_chk,100), ... + line(repmat(MswDiff+thresSDswD(2)*SDswDiff, [1 2]),get(gca,'ylim'), ... + 'linestyle','--','color','k') + line(repmat(swDiff_chk(bound+1), [1 2]),get(gca,'ylim'), ... + 'linestyle','--','color','r') + xlabel('A.U.'), + ylabel('Counts') + title(sprintf(['Histogram of Sharp Wave Difference Magnitude\n', ... + '%3.1f SD threshold (black) and detected value (red)'],thresSDswD(2))) + hold off + + h2 = subplot(2,3,[4 5]); + plot(timeline,ripPow_chk), axis tight, hold on + plot([timeline(startSWR) timeline(bound+stopSWR)],... + [ripPow_chk(startSWR) ripPow_chk(bound+stopSWR)],'og') + line([0 0],get(gca,'ylim'), ... + 'linestyle','--','color','k') + line(get(gca,'xlim'),repmat(MripPow,[1 2]), ... + 'linestyle','--','color','k') + line(get(gca,'xlim'),repmat(MripPow+thresSDrip(2)*SDripPow,[1 2]), ... + 'linestyle','--','color','r') + line(get(gca,'xlim'),repmat(MripPow-thresSDrip(2)*SDripPow,[1 2]), ... + 'linestyle','--','color','r') + xlabel('Samples'),ylabel('A.U.'),title('Rip Pow'), hold off + subplot(2,3,6), hist(ripPow_chk,100), ... + line(repmat(MripPow+thresSDrip(2)*SDripPow, [1 2]),get(gca,'ylim'), ... + 'linestyle','--','color','k') + line(repmat(ripPow_chk(bound+1), [1 2]),get(gca,'ylim'), ... + 'linestyle','--','color','r') + xlabel('A.U.'), + ylabel('Counts') + title(sprintf(['Histogram of Ripple Power\n', ... + '%3.1f SD threshold (black) and detected value (red)'],thresSDrip(2))) + hold off + + linkaxes([h1 h2],'x') + +% keyboard + pause(.1) + + clf + end + +end +SWR_valid.Ts = SWR_valid.Ts(1:valid,:); +SWR_valid.SwMax = SWR_valid.SwMax(1:valid,:); +SWR_valid.RipMax = SWR_valid.RipMax(1:valid,:); + +if FIGS + % Evaluate fixed point Precision/Recall performance + figure('Color','w'), + + % marginal histograms + [n1, ctr1] = hist(swDiffAll,1000); + [n2, ctr2] = hist(ripPowerAll,1000); + + h1 = subplot(2,2,2); + plot(swDiffAll(idx1), ripPowerAll(idx1),'.r'), hold on + plot(swDiffAll(idx2), ripPowerAll(idx2),'.b'), + plot(swDiffAll(SWRindF),ripPowerAll(SWRindF),'og','markerfacecolor','none') + axis([min(swDiffAll(:)) max(swDiffAll(:)) min(ripPowerAll(:)) max(ripPowerAll(:))]) + line(get(h1,'xlim'), repmat(thresL_Rip,[1 2]),'linestyle','--','color','k') + line(repmat(thresL_swD,[1 2]),get(h1,'ylim'),'linestyle','--','color','k') + title('Sharp Wave Difference vs. Ripple Power','fontsize',12) + xlabel('Sharp Wave Diff','fontsize',12) + ylabel('Ripple Power','fontsize',12) + h2 = subplot(2,2,4); + bar(ctr1,-n1,1), + axis([min(swDiffAll(:)) max(swDiffAll(:)) -max(n1)*1.1 0]), + axis('off'), + h3 = subplot(2,2,1); + barh(ctr2,-n2,1), + axis([-max(n2)*1.1 0 min(ripPowerAll(:)) max(ripPowerAll(:))]) + axis('off'), + set(h1,'Position',[0.25 0.35 0.55 0.55],'fontsize',12); + set(h2,'Position',[.25 .1 .55 .15],'fontsize',12); + set(h3,'Position',[.05 .35 .15 .55],'fontsize',12); + + string = sprintf(['K-means Clustering:\n\n',... + ' Center1: X = %7.2f, Y = %7.2f\n', ... + ' Center2: X = %7.2f, Y = %7.2f\n\n', ... + 'Number of Detected SWR:%5d\n'],... + Ctrs(1,1),Ctrs(1,2),Ctrs(2,1),Ctrs(2,2), valid); + annotation('textbox',[0.81 0.70 .1 .1],'String',string, ... + 'edgecolor','none','fontsize',12) +end + + + + + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% TIME MINIMUM TROUGH %%% +%%%%%%%%%%%%%%%%%%%%%%%%%%% + +% Filter pyramidal lfp in ripple band and get the envelope +lfpfilt = bz_Filter(lfp(:,1),'passband',ripBP,'filter','fir1'); +signEnv = 2*lfpfilt.*lfpfilt; +envelope = sgolayfilt(signEnv,4,101); +envelope = abs(sqrt(envelope)); +% Compute timestap of the trough nearest to the envelope maximum +% That timestamp will be the middle timestamp of the ripple output +TsTrough = SWR_valid.Ts(:,1)*0; +parfor irip = 1:size(SWR_valid.Ts,1) + idxs = SWR_valid.Ts(irip,2):SWR_valid.Ts(irip,3); + [~, imax] = max(envelope(idxs)); + imax = imax + idxs(1)-1; + idxsmax = round([imax-0.01*SR:imax+0.01*SR]); + [~, imin] = min(lfp(idxsmax)); + imin = imin+idxsmax(1)-1; + TsTrough(irip) = imin; +% plot((idxs-imin)/SR, lfp(idxs,1), 'k', 'linewidth', 2) +% hold on +% plot((idxs-imin)/SR, lfpfilt(idxs)) +% plot((idxs-imin)/SR, 1500*signEnv(idxs)/max(signEnv(idxs))) +% plot(0/SR, lfp(imin), '*') +% hold off +% xlim([-0.05 0.05]) +% pause +end + +% Keep ripples whose computed troughs are inside the start and end timestamps +idxsIn = find(TsTrough>SWR_valid.Ts(:,2) & TsTrough= 0.001, 1, 'first' ) - 1, find( cwin <= 0.999, 1, 'last' ) + 1 ] +% 3 SD are usually enough + + + + + + +% FIRFILT FIR filtering with zero phase distortion. +% +% matrix columns are filtered; +% output is a column vector / matrix. +% 19-Jul-02 ES +% 27-jan-03 zero phase lag + +function Y = firfilt(x,W) + +if all(size(W)>=2), error('window must be a vector'), end +if numel(x)==max(size(x)), x=x(:); end + +C = length(W); +if C > size( x, 1 ) + Y = NaN * ones( size( x ) ); + return +end +D = ceil(C/2) - 1; +Y = filter(W,1,[flipud(x(1:C,:)); x; flipud(x(end-C+1:end,:))]); +clear x +Y = Y(1+C+D:end-C+D,:); + +return +% EOF + + + + + + + +function logAnalysisFile(AnalysisFileName, writePath) +% The purpose of this utility is to provide a record of analyses run by +% writing out to a log file the state of the code that was used to analyze +% the data. It writes the log file into the current directory if only a +% filename is provided. An absolute path is required to write out to an +% arbitrary location. +% +% This utility is designed to be placed within an .m file used for +% analysis. +% +% dependencies: mfilename +% +% author: John D. Long II, PhD contact: jlong29@gmail.com +% +%%%%%%%%%%%%%% +%%% INPUTS %%% +%%%%%%%%%%%%%% +% AnalysisFileName: the output of mfilename('fullname') called from within +% the .m file +% writePath (optional): a user specified path for writing the log file. +% +%%%%%%%%%%%%%%% +%%% OUTPUTS %%% +%%%%%%%%%%%%%%% +% A .log file entitled mfilename-date.log written to the location +% +% Example usage: +% logAnalysisFile(mfilename('fullpath') or +% logAnalysisFile(mfilename('fullpath','~/mnt/Analysis/') + +% input check +if nargin < 2 || isempty(writePath) + writePath = []; +end + +% assumes output from mfilename +info = dir([AnalysisFileName '.m']); +[~,filename] = fileparts([AnalysisFileName '.m']); + +% Check for log file and create or update as required +if ~isempty(writePath) + % check if writePath is a valid directory + if exist(writePath,'dir') + % check if writePath ends in filesep + if ~strcmp(writePath(end),filesep) + writePath = [writePath filesep]; + end + % check for pre-existing log file in writePath + loginfo = dir(sprintf('%s%s.log',writePath,filename)); + if isempty(loginfo) + fprintf(1,'First Use Of Analysis File: Writing Log.\n'); + fid1 = fopen(sprintf('%s%s.log',writePath,filename),'w+'); + else + % let's check if we need to update this file + fid1 = fopen(sprintf('%s%s.log',writePath,filename),'r'); + tline = fgetl(fid1); + if strcmp(tline,info.date) + fprintf(1,'Analysis File Up To Date.\n'); + return + else + fprintf(1,'Analysis File Changed: Writing Log.\n'); + fclose(fid1); + fid1 = fopen(sprintf('%s%s.log',writePath,filename),'w+'); + end + end + else + + fprintf(1,'InputWarning: Log File Written out to launch directory.\n'); + + % check for pre-existing log file current directory + loginfo = dir(sprintf('%s.log',filename)); + if isempty(loginfo) + fprintf(1,'First Use Of Analysis File: Writing Log.\n'); + fid1 = fopen(sprintf('%s.log',filename),'w+'); + else + % let's check if we need to update this file + fid1 = fopen(sprintf('%s.log',filename),'r'); + tline = fgetl(fid1); + if strcmp(tline,info.date) + fprintf(1,'Analysis File Up To Date.\n'); + return + else + fprintf(1,'Analysis File Changed: Writing Log.\n'); + fclose(fid1); + fid1 = fopen(sprintf('%s.log',filename),'w+'); + end + end + end +else + % check for pre-existing log file current directory + loginfo = dir(sprintf('%s.log',filename)); + if isempty(loginfo) + fprintf(1,'First Use Of Analysis File: Writing Log.\n'); + fid1 = fopen(sprintf('%s.log',filename),'w+'); + else + % let's check if we need to update this file + fid1 = fopen(sprintf('%s.log',filename),'r'); + tline = fgetl(fid1); + if strcmp(tline,info.date) + fprintf(1,'Analysis File Up To Date.\n'); + else + fprintf(1,'Analysis File Changed: Writing Log.\n'); + fclose(fid1); + fid1 = fopen(sprintf('%s.log',filename),'w+'); + end + end +end +% Open the analysis file to be read +fid2 = fopen(sprintf('%s',[AnalysisFileName '.m']),'r'); +fprintf(fid1,'%s\n',info.date); + +while ~feof(fid2) + try + tline = fgetl(fid2); + fprintf(fid1,'%s\n',tline); + catch + keyboard + end +end +% Close all files +fclose(fid1); +fclose(fid2); \ No newline at end of file diff --git a/detectors/detectEvents/bz_FindRipples.m b/analysis/SharpWaveRipples/bz_FindRipples.m similarity index 97% rename from detectors/detectEvents/bz_FindRipples.m rename to analysis/SharpWaveRipples/bz_FindRipples.m index 2680bec8..a56dcd00 100755 --- a/detectors/detectEvents/bz_FindRipples.m +++ b/analysis/SharpWaveRipples/bz_FindRipples.m @@ -14,15 +14,15 @@ % from a previous call. The estimated EMG can be used as an additional % exclusion criteria % -% INPUTS +% INPUTS - note these are NOT name-value pairs... just raw values % lfp unfiltered LFP (one channel) to use % timestamps timestamps to match filtered variable -% optional list of property-value pairs (see table below) +% optional list of property-value pairs (see tables below) % % OR % % basepath path to a single session to run findRipples on -% channel Ripple channel to use for detection +% channel Ripple channel to use for detection (0-indexed, a la neuroscope) % % ========================================================================= % Properties Values @@ -378,6 +378,16 @@ if isfield(detectorinfo.detectionparms,'timestamps') detectorinfo.detectionparms = rmfield(detectorinfo.detectionparms,'timestamps'); end +if isfield(p.Results,'channel') + detectorinfo.detectionchannel = p.Results.channel; +end +if ~isempty(noise) + detectorinfo.noisechannel = noise; +else + detectorinfo.noisechannel = nan; +end + + %Put it into the ripples structure ripples.detectorinfo = detectorinfo; diff --git a/analysis/lfp/SharpWaveRipples/bz_GetBestRippleChan.m b/analysis/SharpWaveRipples/bz_GetBestRippleChan.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/SharpWaveRipples/bz_GetBestRippleChan.m rename to analysis/SharpWaveRipples/bz_GetBestRippleChan.m diff --git a/analysis/lfp/SharpWaveRipples/bz_PlotRippleStats.m b/analysis/SharpWaveRipples/bz_PlotRippleStats.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/SharpWaveRipples/bz_PlotRippleStats.m rename to analysis/SharpWaveRipples/bz_PlotRippleStats.m diff --git a/analysis/lfp/SharpWaveRipples/bz_RippleStats.m b/analysis/SharpWaveRipples/bz_RippleStats.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/SharpWaveRipples/bz_RippleStats.m rename to analysis/SharpWaveRipples/bz_RippleStats.m diff --git a/analysis/SharpWaveRipples/bz_getRipSpikes.m b/analysis/SharpWaveRipples/bz_getRipSpikes.m new file mode 100644 index 00000000..2f7bd65d --- /dev/null +++ b/analysis/SharpWaveRipples/bz_getRipSpikes.m @@ -0,0 +1,162 @@ +function [spkEventTimes] = bz_getRipSpikes(varargin) +% +% [SpkEventTimes] = bz_getRipSpikes(varargin) +% Saves spike times of all units inside given events in different ways: +% 1. Absolute and relative time of spikes by unit and by event +% 2. Absolute and relative time of spikes by unit +% 3. Absolute and relative time of spikes by ripple +% +% INPUTS +% +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% 'basepath' full path where session is located (default pwd) +% 'events' It can be either the following options: +% 1. Buzcode structure for specific events (ripples, UDStates, ...) +% By default it will load ripples (output from bz_DetectSWR). +% Specifically, if not provided, it loads this ripple +% structure from 'basepath' (if provided), or from current +% folder (if not). The input structure must have the +% following field: +% .timestamps: Nx2 matrix with starting and ending times +% (in sec) of each event. +% 2. A Nx2 matrix with starting and ending times (in sec) +% of each event, just like the .timestamps field. +% (N: number of events) +% 'spikes' buzcode ripple structure (from bz_GetSpikes). +% If not provided, it loads it from 'basepath' (if provided), +% or from current folder (if not) +% 'UIDs' A Mx1 boolean matrix with 1s for units to be considered +% and 0s for units to be discarded.(M: number of units) +% 'padding' additional time after ripple end to still search for spikes. +% (default is 0.05 sec) +% 'saveMat' Saves file, logical (default: true) +% +% ========================================================================= +% +% OUTPUTS +% +% spkEventTimes structure with the followin fields: +% +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% .UnitEventAbs MxN cell matrix. In each cell, absolute times of +% spikes for that particular unit and event +% .UnitEventRel MxN cell matrix. In each cell, relative times of +% spikes (relative to the starting time of events) for +% that particular unit and event +% .UnitAbs 1xM cell matrix. In each cell, absolute times of +% spikes for that particular unit across all events +% .UnitRel 1xM cell matrix. In each cell, relative times of +% spikes for that particular unit across all events +% .EventAbs 2xN cell matrix. In the first row, absolute times of +% spikes for that particular event across all units. In +% the second row, the UID associated to the above spike +% .EventRel 2xN cell matrix. In the first row, relative times of +% spikes for that particular event across all units. In +% the second row, the UID associated to the above spike +% +% ========================================================================= +% +% Antonio FR, 2017 +% Converted to buzcode format: Andrea Navas-Olive, 2019 + +% Parse inputs +p = inputParser; +addParameter(p,'basepath',pwd,@isstr); +addParameter(p,'events',[], @(x) isnumeric(x) || isstruct(x)); +addParameter(p,'spikes',{},@isstruct); +addParameter(p,'UIDs',[],@islogical); +addParameter(p,'padding',0.05,@isnumeric); +addParameter(p,'saveMat', true, @islogical); + +parse(p,varargin{:}); +basepath = p.Results.basepath; +events = p.Results.events; +spikes = p.Results.spikes; +UIDs = p.Results.UIDs; +padding = p.Results.padding; +saveMat = p.Results.saveMat; + +% Get session info +basename = bz_BasenameFromBasepath(basepath); +load([basepath filesep basename '.sessionInfo.mat']); + +% Default events, UIDs and spikes +if isempty(spikes) + spikes = load([basepath filesep basename '.spikes.cellinfo.mat']); + spikes = spikes.spikes; +end +if isempty(UIDs) + UIDs = ones(size(spikes.UID)); +end +if isempty(events) + events = load([basepath filesep basename '.ripples.events.mat']); + events = events.ripples; +end +% Starting and ending timestamps +if isnumeric(events) + timestamps = events; +elseif isstruct(events) + timestamps = events.timestamps; +else + warning('Events must be either a Nx2 vector or a bz event structure!'); +end + +%% Get spikes for each unit and each ripple + +% We will save spike times of different units in different ways: +spkEventTimes = {}; +% 1. Absolute and relative time of spikes by unit and by ripple +for unit = 1:length(spikes.UID) + if UIDs(unit) + for event = 1:length(timestamps) + % Start and end of ripple + tini = timestamps(event,1); + tend = timestamps(event,2) + padding; + % Spikes of this unit within this ripple interval + tsUnitEvent = spikes.times{unit}; + tsUnitEvent = tsUnitEvent(tsUnitEvent>=tini & tsUnitEvent<=tend); + % Absolute time of spikes by unit and by ripple + spkEventTimes.UnitEventAbs{unit,event} = tsUnitEvent'; + % Relative time of spikes by unit and by ripple to ripple start + spkEventTimes.UnitEventRel{unit,event} = tsUnitEvent' - tini; + end + end +end + +% 2. Absolute and relative time of spikes by unit +for unit = 1:length(spikes.UID) + if UIDs(unit) + spkEventTimes.UnitAbs{unit} = cell2mat(spkEventTimes.UnitEventAbs(unit,:)); + spkEventTimes.UnitRel{unit} = cell2mat(spkEventTimes.UnitEventRel(unit,:)); + end +end + +% 3. Absolute and relative time of spikes by ripple +for event = 1:length(timestamps) + spkEventTimes.EventAbs{event} = []; + spkEventTimes.EventRel{event} = []; + for unit = 1:length(spikes.UID) + if UIDs(unit) + spkEventTimes.EventAbs{event} = [ spkEventTimes.EventAbs{event}, ... + [cell2mat(spkEventTimes.UnitEventAbs(unit,event)); ... + cell2mat(spkEventTimes.UnitEventAbs(unit,event))*0+spikes.UID(unit)] ]; + spkEventTimes.EventRel{event} = [ spkEventTimes.EventRel{event}, ... + [cell2mat(spkEventTimes.UnitEventRel(unit,event)); ... + cell2mat(spkEventTimes.UnitEventRel(unit,event))*0+spikes.UID(unit)] ]; + end + end + spkEventTimes.EventAbs{event} = sortrows(spkEventTimes.EventAbs{event}')'; + spkEventTimes.EventRel{event} = sortrows(spkEventTimes.EventRel{event}')'; +end + +% Save +if saveMat + save(fullfile(basepath, [basename '.spkEventTimes.mat'],'spkEventTimes')); +end + + +end diff --git a/detectors/detectEvents/detect_swr/ReadMe.txt b/analysis/SharpWaveRipples/detect_swr/ReadMe.txt old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/ReadMe.txt rename to analysis/SharpWaveRipples/detect_swr/ReadMe.txt diff --git a/detectors/detectEvents/detect_swr/SWR_between_trials.png b/analysis/SharpWaveRipples/detect_swr/SWR_between_trials.png old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/SWR_between_trials.png rename to analysis/SharpWaveRipples/detect_swr/SWR_between_trials.png diff --git a/detectors/detectEvents/detect_swr/SWR_during_behavior.png b/analysis/SharpWaveRipples/detect_swr/SWR_during_behavior.png old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/SWR_during_behavior.png rename to analysis/SharpWaveRipples/detect_swr/SWR_during_behavior.png diff --git a/detectors/detectEvents/detect_swr/detect_swr.m b/analysis/SharpWaveRipples/detect_swr/detect_swr.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/detect_swr.m rename to analysis/SharpWaveRipples/detect_swr/detect_swr.m diff --git a/detectors/detectEvents/detect_swr/private/LoadBinary.m b/analysis/SharpWaveRipples/detect_swr/private/LoadBinary.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/private/LoadBinary.m rename to analysis/SharpWaveRipples/detect_swr/private/LoadBinary.m diff --git a/detectors/detectEvents/detect_swr/private/LoadXml.m b/analysis/SharpWaveRipples/detect_swr/private/LoadXml.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/private/LoadXml.m rename to analysis/SharpWaveRipples/detect_swr/private/LoadXml.m diff --git a/detectors/detectEvents/detect_swr/private/firfilt.m b/analysis/SharpWaveRipples/detect_swr/private/firfilt.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/private/firfilt.m rename to analysis/SharpWaveRipples/detect_swr/private/firfilt.m diff --git a/detectors/detectEvents/detect_swr/private/license.txt b/analysis/SharpWaveRipples/detect_swr/private/license.txt old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/private/license.txt rename to analysis/SharpWaveRipples/detect_swr/private/license.txt diff --git a/detectors/detectEvents/detect_swr/private/logAnalysisFile.m b/analysis/SharpWaveRipples/detect_swr/private/logAnalysisFile.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/private/logAnalysisFile.m rename to analysis/SharpWaveRipples/detect_swr/private/logAnalysisFile.m diff --git a/detectors/detectEvents/detect_swr/private/makefir.m b/analysis/SharpWaveRipples/detect_swr/private/makefir.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/private/makefir.m rename to analysis/SharpWaveRipples/detect_swr/private/makefir.m diff --git a/detectors/detectEvents/detect_swr/private/makegausslpfir.m b/analysis/SharpWaveRipples/detect_swr/private/makegausslpfir.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/private/makegausslpfir.m rename to analysis/SharpWaveRipples/detect_swr/private/makegausslpfir.m diff --git a/detectors/detectEvents/detect_swr/private/parseArgs.m b/analysis/SharpWaveRipples/detect_swr/private/parseArgs.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/private/parseArgs.m rename to analysis/SharpWaveRipples/detect_swr/private/parseArgs.m diff --git a/detectors/detectEvents/detect_swr/private/suprathresh.m b/analysis/SharpWaveRipples/detect_swr/private/suprathresh.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/private/suprathresh.m rename to analysis/SharpWaveRipples/detect_swr/private/suprathresh.m diff --git a/detectors/detectEvents/detect_swr/private/v2struct.m b/analysis/SharpWaveRipples/detect_swr/private/v2struct.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/private/v2struct.m rename to analysis/SharpWaveRipples/detect_swr/private/v2struct.m diff --git a/detectors/detectEvents/detect_swr/private/v2structDemo1.m b/analysis/SharpWaveRipples/detect_swr/private/v2structDemo1.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/private/v2structDemo1.m rename to analysis/SharpWaveRipples/detect_swr/private/v2structDemo1.m diff --git a/detectors/detectEvents/detect_swr/private/v2structDemo2.m b/analysis/SharpWaveRipples/detect_swr/private/v2structDemo2.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/private/v2structDemo2.m rename to analysis/SharpWaveRipples/detect_swr/private/v2structDemo2.m diff --git a/detectors/detectEvents/detect_swr/private/xmltools.m b/analysis/SharpWaveRipples/detect_swr/private/xmltools.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/detect_swr/private/xmltools.m rename to analysis/SharpWaveRipples/detect_swr/private/xmltools.m diff --git a/analysis/lfp/SpectralAnalyses/MorletWavelet.m b/analysis/SpectralAnalyses/MorletWavelet.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/SpectralAnalyses/MorletWavelet.m rename to analysis/SpectralAnalyses/MorletWavelet.m diff --git a/analysis/lfp/SpectralAnalyses/bz_MTCoherogram.m b/analysis/SpectralAnalyses/bz_MTCoherogram.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/SpectralAnalyses/bz_MTCoherogram.m rename to analysis/SpectralAnalyses/bz_MTCoherogram.m diff --git a/analysis/lfp/SpectralAnalyses/bz_WaveSpec.m b/analysis/SpectralAnalyses/bz_WaveSpec.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/SpectralAnalyses/bz_WaveSpec.m rename to analysis/SpectralAnalyses/bz_WaveSpec.m diff --git a/analysis/lfp/CurrentSourceDensity/blank.file b/analysis/assemblies/blank.file old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/CurrentSourceDensity/blank.file rename to analysis/assemblies/blank.file diff --git a/analysis/spikes/assemblies/bz_peerPrediction.m b/analysis/assemblies/bz_peerPrediction.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/assemblies/bz_peerPrediction.m rename to analysis/assemblies/bz_peerPrediction.m diff --git a/analysis/spikes/cellTypeClassification/BrendonClassificationFromStark2013/ClusterPointsBoundaryOutBW.m b/analysis/cellTypeClassification/BrendonClassificationFromStark2013/ClusterPointsBoundaryOutBW.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/cellTypeClassification/BrendonClassificationFromStark2013/ClusterPointsBoundaryOutBW.m rename to analysis/cellTypeClassification/BrendonClassificationFromStark2013/ClusterPointsBoundaryOutBW.m diff --git a/analysis/spikes/cellTypeClassification/BrendonClassificationFromStark2013/DefaultArgs.m b/analysis/cellTypeClassification/BrendonClassificationFromStark2013/DefaultArgs.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/cellTypeClassification/BrendonClassificationFromStark2013/DefaultArgs.m rename to analysis/cellTypeClassification/BrendonClassificationFromStark2013/DefaultArgs.m diff --git a/analysis/spikes/cellTypeClassification/BrendonClassificationFromStark2013/PointInput.m b/analysis/cellTypeClassification/BrendonClassificationFromStark2013/PointInput.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/cellTypeClassification/BrendonClassificationFromStark2013/PointInput.m rename to analysis/cellTypeClassification/BrendonClassificationFromStark2013/PointInput.m diff --git a/analysis/spikes/cellTypeClassification/BrendonClassificationFromStark2013/ReadMe.txt b/analysis/cellTypeClassification/BrendonClassificationFromStark2013/ReadMe.txt old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/cellTypeClassification/BrendonClassificationFromStark2013/ReadMe.txt rename to analysis/cellTypeClassification/BrendonClassificationFromStark2013/ReadMe.txt diff --git a/analysis/spikes/cellTypeClassification/BrendonClassificationFromStark2013/bz_CellClassification.m b/analysis/cellTypeClassification/BrendonClassificationFromStark2013/bz_CellClassification.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/cellTypeClassification/BrendonClassificationFromStark2013/bz_CellClassification.m rename to analysis/cellTypeClassification/BrendonClassificationFromStark2013/bz_CellClassification.m diff --git a/analysis/spikes/cellTypeClassification/BrendonClassificationFromStark2013/getWavelet.m b/analysis/cellTypeClassification/BrendonClassificationFromStark2013/getWavelet.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/cellTypeClassification/BrendonClassificationFromStark2013/getWavelet.m rename to analysis/cellTypeClassification/BrendonClassificationFromStark2013/getWavelet.m diff --git a/analysis/spikes/bz_CellMetricsSimple.m b/analysis/cellTypeClassification/bz_CellMetricsSimple.m similarity index 100% rename from analysis/spikes/bz_CellMetricsSimple.m rename to analysis/cellTypeClassification/bz_CellMetricsSimple.m diff --git a/analysis/lfp/CrossFrequencyCoupling/PhaseAmpCouplingByAmp.m b/analysis/lfp/CrossFrequencyCoupling/PhaseAmpCouplingByAmp.m deleted file mode 100755 index 5e277367..00000000 --- a/analysis/lfp/CrossFrequencyCoupling/PhaseAmpCouplingByAmp.m +++ /dev/null @@ -1,91 +0,0 @@ -function [ ampbins,phasebins,sig2powerskew,sig2prefangle,phaseamphist ] = PhaseAmpCouplingByAmp( sig1phase,sig1amp,sig2amp,numbins ) -%PhaseAmpCouplingByAmp(sig1phase,sig1amp,sig2amp,ampbins,numphasebins) -%calculates phase amplitude coupling between the phase of signal 1 and the -%amplitude of signal 2, with respect to the amplitude of signal 1. -% -%INPUTS -% sig1phase -% sig1amp -% sig2amp -% ampbins -% numbins -% Detailed explanation goes here -% -%OUTPUTS -% -% -%DLevenstein Fall 2016 -%THIS FUNCTION NEEDS DOCUMENTATION, I/O IMPROVEMENTS -%% - -phasebins = linspace(-pi,pi,numbins+1); -phasebins=phasebins(1:end-1)+diff(phasebins(1:2)); -ampbins = linspace(-2.5,2.5,numbins); -numampbins = length(ampbins); - -%% -sig1amp = zscore(sig1amp); -sig2amp = zscore(sig2amp); - -%% -sig1binpower = interp1(ampbins,ampbins,sig1amp,'nearest'); -sig1binphase = interp1(phasebins,phasebins,sig1phase,'nearest'); - - -powerhist = zeros(numampbins,numbins); -sig2prefangle = zeros(numampbins,1); -sig2powerskew = zeros(numampbins,1); -for bb = 1:numampbins - for bbb = 1:numbins - ampwintimes = sig2amp(sig1binpower==ampbins(bb) & sig1binphase==phasebins(bbb)); - phaseamphist(bb,bbb) = mean(ampwintimes); - end - sig2powerskew(bb) = mean(sig2amp(sig1binpower==ampbins(bb)).*exp(1i.*sig1phase(sig1binpower==ampbins(bb)))); - sig2prefangle(bb) = angle(sig2powerskew(bb)); - sig2powerskew(bb) = abs(sig2powerskew(bb)); -end - -%% Figure - -rwbcolormap = makeColorMap([0 0 0.8],[1 1 1],[0.8 0 0]); -plotx = linspace(-pi,3*pi,100); -figure -subplot(2,2,1) -hold on - imagesc(phasebins,ampbins,phaseamphist) - imagesc(phasebins+2*pi,ampbins,phaseamphist) - plot(sig2prefangle,ampbins,'.k') - plot(sig2prefangle+2*pi,ampbins,'.k') - plot(plotx,cos(plotx),'k') - colormap(gca,rwbcolormap) - axis xy - axis tight - ColorbarWithAxis([-0.5 0.5],['Mean Amp.']) - caxis([-0.5 0.5]) - % xlim([-pi 3*pi]);ylim(ampbins([1 end])) - xlabel('Signal 1 Phase');ylabel('Signal 1 Amp (Z)') -subplot(4,2,2) - plot(ampbins,sig2powerskew,'k','LineWidth',1) - xlabel('Signal 1 Amp. (Z)'); - ylabel('Phase-Amp. Modulation (mrl)') - axis tight -subplot(4,2,6) - histogram(sig1amp,ampbins) - xlabel('Signal 1 Amp. (Z)'); - ylabel('Occupancy') - axis tight - title('Signal1/2 Amp. Distributions') - -subplot(4,2,8) - histogram(sig2amp,ampbins) - xlabel('Signal 2 Amp. (Z)'); - ylabel('Occupancy') - axis tight - -subplot(2,2,3) - gasphist = hist3([sig1amp,sig2amp],{ampbins,ampbins}); - imagesc(ampbins,ampbins,gasphist) - axis xy - xlabel('Signal 1 Power');ylabel('Signal 2 Power') -end - diff --git a/analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m b/analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m new file mode 100644 index 00000000..6f32a874 --- /dev/null +++ b/analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m @@ -0,0 +1,199 @@ +function [comodulogram] = bz_CFCPhaseAmp(lfp,phaserange,amprange,varargin) +% [comodulogram] = bz_CFCPhaseAmp(lfp,phaserange,amprange,flagPlot) +% +% +%This function calculates the modulation index of phase-amplitude between +%phase range (lower frequencies) to amplitude range (higher frequencies). +%It can really take a long time if you do very small steps of frequency, +%due to wavelet/filtering processing each frequency at a time. +% +%%INPUT +% lfp a buzcode structure with fields lfp.data, +% lfp.timestamps +% lfp.samplingRate +% lfp.channels +% +% phaserange [min:steps:max] array of frequencies to filter for the +% phase signal +% amprange [min:steps:max] array of frequencies range for wavelets +% for the power signal +% optional list of property-value pairs (see table below) +% +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% phaseCh channel to compute phase. If empty take first channel +% ampChans channels to compute amplitude. If empty take first channel +% method ['wavelet'(default)|'hilbert']. Method to extract power +% of specific bands, using wavelet (default) or hilbert +% +% makePlot default true +% filterType default 'fir1'. Method of filtering for the phase +% bands, in case of method = 'hilbert' it also defines the filter of +% bands in the amplitude range. +% filterOrder default 4. Order of the filter used for the phase +% bands, in case of method = 'hilbert' it also defines the filter order of +% bands in the amplitude range. +% numBins default 50. Number of bins to calculate the +% Amplitude/Phase distribution. +% ========================================================================= +% +%OUTPUT +% comodulogram.comod phase-frequency x amplitude-frequency x +% ch comodulogram matrix in modulation index units +% comodulogram.phase_bincenters phase bin centers +% comodulogram.amp_bincenters amp bin centers +% comodulogram.params.method method used to extract the amplitude +% comodulogram.params.filter filter type +% comodulogram.params.filterOrder filter order +% +%Dependencies +% bz_Filter +% bz_WaveSpec +% +% Eliezyer de Oliveira 2018 +% AntonioFR, 2/2019 +% +% +% Last Update: 31/10/2019 + +%TO DO: [] adapt cell/matrix lfp inputs to buzcode lfp format + + +%% Parse inputs +if ~bz_isLFP(lfp) + error('Not following the lfp structure required, see documentation') +end + +p = inputParser; +addParameter(p,'phaseCh',lfp.channels(1),@isnumeric); +addParameter(p,'ampCh',lfp.channels(1),@isnumeric); +addParameter(p,'samplingRate',1250,@isnumeric); +addParameter(p,'makePlot',true,@islogical); +addParameter(p,'filterType','fir1',@ischar); +addParameter(p,'filterOrder',4,@isnumeric); +addParameter(p,'numBins',50,@isnumeric); +addParameter(p,'method','wavelet',@ischar); + +parse(p,varargin{:}); +phaseCh = p.Results.phaseCh; +ampChans = p.Results.ampCh; +samplingRate = p.Results.samplingRate; +makePlot = p.Results.makePlot; +filterType = p.Results.filterType; +filterOrder = p.Results.filterOrder; +numBins = p.Results.numBins; +method = p.Results.method; + +%% this if structure needs to be adjusted to turn the cell/matrix lfp inputs to lfp format +% for now i'll leave it commented (EFO) + + +%lfp input +% if isstruct(lfp) +% data = lfp.data; +% timestamps = lfp.timestamps; +% samplingRate = lfp.samplingRate; +% elseif iscell(lfp) %for multiple trials %% the following elseifs need to adapt the code to a lfp format of buzcode (EFO) +% celllengths = cellfun(@length,lfp); +% data = vertcat(lfp{:}); +% elseif isnumeric(lfp) +% data = lfp; +% timestamps = [1:length(lfp)]'./samplingRate; +% end + +%% Filter LFP for phase +for bnd = 1:length(phaserange)-1 + filtered_phase(bnd) = bz_Filter(lfp,'passband',phaserange(bnd:bnd+1),'filter',filterType,'order',filterOrder,'channels',phaseCh); +end + +%% getting +switch(method) + + case 'wavelet' + %% Wavelet Transform LFP in intervals + for ch = 1:length(ampChans) + comod = zeros(length(amprange)-1,length(filtered_phase),length(ampChans)); + + for apr = 1:length(amprange)-1 + wavespec_amp = bz_WaveSpec(lfp,'frange',[amprange(apr:apr+1)],'nfreqs',1,'chanID',ampChans(ch)); + + + amplitude_data = abs(wavespec_amp.data); + %% Bin phase and power + + phasebins = linspace(-pi,pi,numBins+1); + + for idx = 1:length(filtered_phase) + [~,~,phaseall] = histcounts(filtered_phase(idx).phase,phasebins); + + phaseAmp = zeros(numBins,1); + for bb = 1:numBins + phaseAmp(bb) = mean(amplitude_data(phaseall==bb),1); + end + + phaseAmp = phaseAmp./sum(phaseAmp,1); + comod(apr,idx,ch) = sum(phaseAmp.*log(phaseAmp./(ones(numBins,size(phaseAmp,2))/numBins)))/log(numBins); + end + + end + % ampfreqs = wavespec_amp.freqs; + clear lfpCh + end + + case 'hilbert' + %% Power by hilbert transform in filtered lfp + for ch = 1:length(ampChans) + comod = zeros(length(amprange)-1,length(filtered_phase),length(ampChans)); + + for apr = 1:length(amprange)-1 + lfp_amp = bz_Filter(lfp,'passband',[amprange(apr:apr+1)],'filter',filterType,'order',filterOrder,'channels',ampChans(ch)); + + amplitude_data = lfp_amp.amp; + %% Bin phase and power + + phasebins = linspace(-pi,pi,numBins+1); + + for idx = 1:length(filtered_phase) + [~,~,phaseall] = histcounts(filtered_phase(idx).phase,phasebins); + + phaseAmp = zeros(numBins,1); + for bb = 1:numBins + phaseAmp(bb) = mean(amplitude_data(phaseall==bb),1); + end + + phaseAmp = phaseAmp./sum(phaseAmp,1); + comod(apr,idx,ch) = sum(phaseAmp.*log(phaseAmp./(ones(numBins,size(phaseAmp,2))/numBins)))/log(numBins); + end + + end + + end +end + +comodulogram.comod = comod; +comodulogram.phase_bincenters = phaserange(1:end-1)+(diff(phaserange)/2); +comodulogram.amp_bincenters = amprange(1:end-1)+(diff(amprange)/2); +comodulogram.params.method = method; +comodulogram.params.filter = filterType; +comodulogram.params.filterOrder = filterOrder; + +%% Plot +if makePlot + + figure + for ch = 1:length(ampChans) + subplot(1,length(ampChans),ch); + contourf(comodulogram.phase_bincenters,comodulogram.amp_bincenters,abs(comodulogram.comod(:,:,ch)),20,'LineColor','none'); + y = colorbar ('SouthOutside'); colormap jet; + xlabel(y,'CFC strength') + ylabel('Amplitude Frequency (Hz)') + xlabel('Phase Frequency (Hz)') + %title(signals) + if ch > 1 + set(gca,'YTick',[]); + end + end + +end + diff --git a/analysis/lfp/CrossFrequencyCoupling/bz_Comodulogram.m b/analysis/lfp/CrossFrequencyCoupling/bz_Comodulogram.m new file mode 100755 index 00000000..edfc000b --- /dev/null +++ b/analysis/lfp/CrossFrequencyCoupling/bz_Comodulogram.m @@ -0,0 +1,207 @@ +function [ comod ] = bz_Comodulogram(lfp,specparms,figparms) +%[ comod ] = bz_Comodulogram(lfp,specparms,figparms) calculates the +%comodulogram (i.e. power-power correlation) for an lfp file. +% +% +%INPUT +% LFP LFP structure in buzcode format: +% a structure with fields lfp.data, lfp.timestamps, lfp.refCh +% (optional). If only one lfp channel is provided, that is unsed +% for corr computation. If two, pairwise correlation is +% performed. If one refCh and n data channels are provided +% the correlation of the refCh against all data channels is +% performed. + +% specparms structure of parameters for the spectrogram +% .frange [min max] frequency +% .nfreqs number of frequencies +% .space spacing between freqs, 'lin' or 'log' +% .fvector predefined vector of frequencies +% .specnorm normalization for spectral power, +% options: 'mean','logmean','log' (default: log) +% .numvarbins number of bins for your external variable +% .varnorm normalization for the variable, +% options: 'percentile', 'none' (default: 'none') +% .type 'wavelet' or 'FFT' (default: 'wavelet') +% -if type: 'wavelet'- +% .ncyc number of wavelet cycles (recommended: ~5) +% -if type: 'FFT'- +% .winsize (s) +% .overlap +% figparms (optional) parameters for the figure +% .plotname nametag for the saved figure +% .figfolder folder to save the figure in +% .baseName baseName of the recording for the figure +% +% DLevenstein 2017 +% Modified by Antonio FR, 7/18/18 + +%TO DO + +%% Parse the inputs + +%Parameters +parms = inputParser; +addParameter(parms,'frange',[1 128],@isnumeric); +addParameter(parms,'nfreqs',100,@isnumeric); +addParameter(parms,'ncyc',5,@isnumeric); +addParameter(parms,'space','log'); +addParameter(parms,'samplingRate',[]); +addParameter(parms,'showprogress',false,@islogical); +addParameter(parms,'saveMat',false); +addParameter(parms,'fvector',[]); +addParameter(parms,'specnorm','log'); +addParameter(parms,'type','wavelet'); +addParameter(parms,'winsize',1,@isnumeric); +addParameter(parms,'overlap',0.5,@isnumeric); + + +parse(parms,specparms) +specparms.frange = parms.Results.frange; +specparms.nfreqs = parms.Results.nfreqs; +specparms.ncyc = parms.Results.ncyc; +specparms.space = parms.Results.space; +specparms.samplingRate = parms.Results.samplingRate; +specparms.showprogress = parms.Results.showprogress; +specparms.saveMat = parms.Results.saveMat; +specparms.fvector = parms.Results.fvector; +specparms.specnorm = parms.Results.specnorm; +specparms.type = parms.Results.type; +specparms.winsize = parms.Results.winsize; +specparms.overlap = parms.Results.overlap; + +%lfp input +if isstruct(lfp) + data = lfp.data; + timestamps = lfp.timestamps; + samplingRate = lfp.samplingRate; + if isfield(lfp,'refCh') + refCh = lfp.refCh; + else + refCh = []; + end +elseif isempty(lfp) + wavespec = lfp; + return +elseif iscell(lfp) %for multiple trials + celllengths = cellfun(@length,lfp); + data = vertcat(lfp{:}); +elseif isnumeric(lfp) + data = lfp; + timestamps = [1:length(lfp)]'./samplingRate; +end + +si = 1./samplingRate; + + +%% Calculate the spectrogram - FFT or WVLT + +switch specparms.type + + case 'wavelet' + %Calcualte the Wavelet Transform + if size(lfp.data,2) == 1 + [wavespec] = bz_WaveSpec(single(data),... + 'frange',specparms.frange,'nfreqs',specparms.nfreqs,'ncyc',specparms.ncyc,... + 'samplingRate',lfp.samplingRate,'space',specparms.space,'fvector',specparms.fvector); + spec = wavespec.data'; + elseif size(lfp.data,2) > 1 + for i = 1:size(lfp.data,2) + [wavespec] = bz_WaveSpec(single(data(:,i)),... + 'frange',specparms.frange,'nfreqs',specparms.nfreqs,'ncyc',specparms.ncyc,... + 'samplingRate',lfp.samplingRate,'space',specparms.space,'fvector',specparms.fvector); + spec{i} = wavespec.data'; + end + end + if ~isempty(refCh) + [wavespec] = bz_WaveSpec(single(refCh),... + 'frange',specparms.frange,'nfreqs',specparms.nfreqs,'ncyc',specparms.ncyc,... + 'samplingRate',lfp.samplingRate,'space',specparms.space,'fvector',specparms.fvector); + specRef = wavespec.data'; + else + specRef = []; + end + spectimestamps = timestamps; %Wavelet timestamp are same as LFP + comod.freqs = wavespec.freqs; + + case 'FFT' + %Calculate the frequences to use + if ~isempty(specparms.fvector) + comod.freqs = fvector; + else + switch specparms.space + case 'log' + comod.freqs = logspace(log10(specparms.frange(1)),... + log10(specparms.frange(2)),specparms.nfreqs); + case 'lin' + comod.freqs = linspace(specparms.frange(1),... + specparms.frange(2),specparms.nfreqs); + end + end + %Calculate the FFT spectrogram parameters - covert from s to sf + winsize = specparms.winsize*samplingRate; + noverlap = specparms.noverlap*samplingRate; + %Calculate the FFT spectrogram + if size(lfp.data,2) == 1 + [spec,~,spectimestamps] = spectrogram(single(data),... + winsize,noverlap,comod.freqs,samplingRate); + elseif size(lfp.data,2) == 2 + for i = 1:2 + [specT,~,spectimestamps] = spectrogram(single(data(:,i)),... + winsize,noverlap,comod.freqs,samplingRate); + spec{i} = specT; + end + end + if ~isempty(refCh) + [specRef,~,spectimestamps] = spectrogram(single(refCh),... + winsize,noverlap,comod.freqs,samplingRate); + else + specRef = []; + end + spectimestamps = spectimestamps'+timestamps(1); %account for any time offset +end + + +%% Calculate the power-power correlations +if isempty(refCh) + if size(lfp.data,2) == 1 + spec = log10(abs(spec)); %Log-transform power + comod.corrs = corr(spec','type','spearman'); + elseif size(lfp.data,2) == 2 + for i = 1:2 + spec{i} = log10(abs(spec{i})); %Log-transform power + end + comod.corrs = corr(spec{1}',spec{2}','type','spearman'); + end +elseif exist('refCh') + specRef = log10(abs(specRef)); %Log-transform power + if size(lfp.data,2) == 1 + spec = log10(abs(spec)); %Log-transform power + comod.corrs = corr(specRef',spec','type','spearman'); + elseif size(lfp.data,2) > 1 + for i = 1:size(lfp.data,2) + spec{i} = log10(abs(spec{i})); %Log-transform power + comod.corrs{i} = corr(specRef',spec{i}','type','spearman'); + end + end +end + + +%% Plot +% needs fix for multiple channels + +if exist('figparms','var') && isempty(refCh) %This whole figure thing can be better. +corrcolor= [makeColorMap([1 1 1],[0 0 0.8],[0 0 0]);... + makeColorMap([0 0 0],[0.8 0 0],[1 1 1])]; +figure +colormap(corrcolor) + imagesc(log2(comod.freqs),log2(comod.freqs),comod.corrs) + colorbar + ColorbarWithAxis([-0.4 0.4],'Power-Power Correlation (rho)') + LogScale('xy',2) + xlabel('f (Hz)');ylabel('f (Hz)') + +NiceSave(['Comodulogram',figparms.plotname],figparms.figfolder,figparms.baseName) + +end + diff --git a/analysis/lfp/CrossFrequencyCoupling/bz_CrossFreqMod.m b/analysis/lfp/CrossFrequencyCoupling/bz_CrossFreqMod.m deleted file mode 100644 index edc68afc..00000000 --- a/analysis/lfp/CrossFrequencyCoupling/bz_CrossFreqMod.m +++ /dev/null @@ -1,123 +0,0 @@ -function [comod] = bz_CrossFreqMod(lfp,phaserange,amprange,varargin) -% [comod] = bz_CrossFreqMod(lfp,phaserange,amprange,flagPlot) -% -% -%This function calculates the modulation index of phase-amplitude between -%phaserange (lower frequencies) to amplitude range (higher frequencies). -%It can really take a long time if you do very small steps of frequency, -%due to wavelet processing each frequency at a time. -% -%%INPUT -% lfp a buzcode structure with fields lfp.data, -% lfp.timestamps -% lfp.samplingRate -% -lfp can also be a [t x 1] timeseries signal. in which -% case you need to input 'samplingRate' -% phaserange [min:steps:max] array of frequencies to filter for the -% phase signal -% amprange [min:stepsmax] array of frequencies range for wavelets -% for the power signal -% optional list of property-value pairs (see table below) -% -% ========================================================================= -% Properties Values -% ------------------------------------------------------------------------- -% phaseCh channel to compute phase. If empty take first channel -% ampChans channels to compute amplitude. If empty take first channel -% makePlot default true -% ========================================================================= -% -%OUTPUT -% comod phase-frequency versus amplitude-frequency -% comodulogram matrix in modulation index units -% -%Dependencies -% bz_Filter -% bz_WaveSpec -% -% AntonioFR, 2/2019 - -%% Parse inputs - -p = inputParser; -addParameter(p,'phaseCh',lfp.channels(1),@isnumeric); -addParameter(p,'ampChans',lfp.channels(1),@isnumeric); -addParameter(p,'samplingRate',1250,@isnumeric); -addParameter(p,'makePlot',true,@islogical); - -parse(p,varargin{:}); -phaseCh = p.Results.phaseCh; -ampChans = p.Results.ampChans; -samplingRate = p.Results.samplingRate; -makePlot = p.Results.makePlot; - -%lfp input -if isstruct(lfp) - data = lfp.data; - timestamps = lfp.timestamps; - samplingRate = lfp.samplingRate; -elseif iscell(lfp) %for multiple trials - celllengths = cellfun(@length,lfp); - data = vertcat(lfp{:}); -elseif isnumeric(lfp) - data = lfp; - timestamps = [1:length(lfp)]'./samplingRate; -end - -%% Filter LFP for phase -nfreqs = length(amprange); - -for bnd = 1:length(phaserange)-1 - filtered_phase(bnd,:) = bz_Filter(lfp,'passband',phaserange(bnd:bnd+1),'filter','fir1','channels',phaseCh); -end - -%% Wavelet Transform LFP in intervals -for ch = 1:length(ampChans) - comod = zeros(length(amprange)-1,length(filtered_phase),length(ampChans)); - - lfpCh = lfp; % this should be remove when bz_WaveSpec can take 'channels' input - lfpCh.data = lfp.data(:,find(lfp.channels == ampChans(ch))); - - for apr = 1:length(amprange)-1 - wavespec_amp = bz_WaveSpec(lfp,'frange',[amprange(apr) amprange(apr+1)],'nfreqs',1); - - wavespec_amp.data = abs(wavespec_amp.data); - %% Bin phase and power - numbins = 50; - phasebins = linspace(-pi,pi,numbins+1); - phasecenters = phasebins(1:end-1)+(phasebins(2)-phasebins(1)); - - for idx = 1:length(filtered_phase) - [phasedist,~,phaseall] = histcounts(filtered_phase(idx).phase,phasebins); - - phaseAmp = zeros(numbins,1); - for bb = 1:numbins - phaseAmp(bb) = mean(wavespec_amp.data(phaseall==bb),1); - end - - phaseAmp = phaseAmp./sum(phaseAmp,1); - comod(apr,idx,ch) = sum(phaseAmp.*log(phaseAmp./(ones(numbins,size(phaseAmp,2))/numbins)))/log(numbins); - end - - end - ampfreqs = wavespec_amp.freqs; - clear lfpCh -end - - -%% Plot -if makePlot - - figure - for ch = 1:length(ampChans) - subplot(1,length(ampChans),ch); - contourf(phaserange(2:end),amprange(2:end),abs(comod(:,:,ch)),20,'LineColor','none'); - colorbar ('SouthOutside'); colormap jet; - %title(signals) - if ch > 1 - set(gca,'YTick',[]); - end - end - -end - diff --git a/analysis/lfp/CrossFrequencyCoupling/bz_ModIndex.m b/analysis/lfp/CrossFrequencyCoupling/bz_ModIndex.m deleted file mode 100755 index 72c425c4..00000000 --- a/analysis/lfp/CrossFrequencyCoupling/bz_ModIndex.m +++ /dev/null @@ -1,93 +0,0 @@ -function [comod] = bz_ModIndex(lfp,phaserange,amprange,flagPlot) -% [comod] = bz_ModIndex(lfp,phaserange,amprange,flagPlot) -%This function calculates the modulation index of phase-amplitude between -%phaserange (lower frequencies) to amplitude range (higher frequencies). -%It can really take a long time if you do very small steps of frequency, -%due to wavelet processing each frequency at a time. -% -%%INPUT -% lfp a buzcode structure with fields lfp.data, -% lfp.timestamps -% lfp.samplingRate -% phaserange [min:steps:max] array of frequencies to filter for the -% phase signal -% amprange [min:stepsmax] array of frequencies range for wavelets -% for the power signal -% optional list of property-value pairs (see table below) -% -% ========================================================================= -% Properties Values -% ------------------------------------------------------------------------- -% not implemented yet.... 'nfreqs' for amplitude signal -% 'ncyc' for wavelet parameters -% 'interval' interval on which to calculate -% other filter parameters -% 'phasesignal' option for other signal to be -% used as phase information -% ========================================================================= -% -%OUTPUT -% comod Modulation index matrix between phase frequency and -% amplitude -% -%Dependencies -% bz_Filter -% bz_WaveSpec -% -% Implemented by Eliezyer de Oliveira, 2018 -% Last Update: 08/06/2018 -% -% I couldn't found a better/faster way to do this processing, I could run -% all the frequencies at once but if the recording is too long it might not -% have enough memory to hold all the data. - - - - -nfreqs = length(amprange); - -%% Filter LFP for phase -parfor bnd = 1:length(phaserange)-1 - filtered_phase(bnd,:) = bz_Filter(lfp,'passband',phaserange(bnd:bnd+1),'filter','fir1'); -end - -%% Wavelet Transform LFP in intervals -comod = zeros(length(amprange)-1,length(filtered_phase)); -for apr = 1:length(amprange)-1 - wavespec_amp = bz_WaveSpec(lfp,'frange',[amprange(apr) amprange(apr+1)],'nfreqs',1); - - wavespec_amp.data = abs(wavespec_amp.data); - %% Bin phase and power - numbins = 50; - phasebins = linspace(-pi,pi,numbins+1); - phasecenters = phasebins(1:end-1)+(phasebins(2)-phasebins(1)); - - for idx = 1:length(filtered_phase) - [phasedist,~,phaseall] = histcounts(filtered_phase(idx).phase,phasebins); - - phaseAmp = zeros(numbins,1); - for bb = 1:numbins - phaseAmp(bb) = mean(wavespec_amp.data(phaseall==bb),1); - end - - phaseAmp = phaseAmp./sum(phaseAmp,1); - comod(apr,idx) = sum(phaseAmp.*log(phaseAmp./(ones(numbins,size(phaseAmp,2))/numbins)))/log(numbins); - end - -end - -ampfreqs = wavespec_amp.freqs; -%% Plot -if flagPlot - figure - imagesc(phaserange,log2(ampfreqs),comod); - colormap jet - hold on - xlabel('Frequency phase'); - ylabel('Frequency amplitude') - LogScale('y',2) - colorbar - axis xy - -end - diff --git a/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmpCouplingByAmp.m b/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmpCouplingByAmp.m new file mode 100755 index 00000000..d2065088 --- /dev/null +++ b/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmpCouplingByAmp.m @@ -0,0 +1,186 @@ +function [comod ] = bz_PhaseAmpCouplingByAmp(lfp,sig1band,sig2band,varargin) +% [comod] = bz_PhaseAmpCouplingByAmp(lfp,sig1band,sig2band,varargin) +%calculates phase amplitude coupling between the phase of signal 1 and the +%amplitude of signal 2, with respect to the amplitude of signal 1. +% +% +%%INPUT +% lfp a buzcode structure with fields lfp.data, +% lfp.timestamps +% lfp.samplingRate +% lfp.channels +% +% sig1band [min max] - band of the reference signal (Hz) +% sig2band [min max] - band of the tested signal (Hz) +% optional list of property-value pairs (see table below) +% +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% phaseCh channel to compute phase. If empty takes first channel +% ampChans channels to compute amplitude. If empty takes first channel +% method ['hilbert'(default)|'wavelet']. Method to extract power +% of both signals, using wavelet or hilbert (default) +% +% makePlot default true +% filterType default 'fir1'. Method of filtering for the phase +% bands, in case of method = 'hilbert' it also defines the filter of +% bands in the amplitude range. +% filterOrder default 4. Order of the filter used for the phase +% bands, in case of method = 'hilbert' it also defines the filter order of +% bands in the amplitude range. +% ampNumBins default 50. Number of bins to calculate the +% amplitude distribution. +% phaseNumBins default 50. Number of bins to calculate the phase +% distribution. +% ========================================================================= +% +% OUTPUT +% comod.ampbins = ampbins; +% comod.phasebins = phasebins; +% comod.sig2powerskew = sig2powerskew; +% comod.sig2prefangle = sig2prefangle; +% comod.phaseamphist = phaseamphist; +% comod.params Collection of parameters used to calculate this comod +% +%Dependencies +% bz_Filter +% bz_WaveSpec +% +% +%DLevenstein Fall 2016 +%Improved documentation and I/O. Added extraction of signal features - Eliezyer de Oliveira 2019 +% +%% + + +if ~bz_isLFP(lfp) + error('Not following the lfp structure required, see documentation') +end + +p = inputParser; +addParameter(p,'ampCh',lfp.channels(1),@isnumeric); +addParameter(p,'phaseCh',lfp.channels(1),@isnumeric); +addParameter(p,'filterType','fir1',@ischar); +addParameter(p,'filterOrder',4,@isnumeric); +addParameter(p,'ampNumBins',50,@isnumeric); +addParameter(p,'phaseNumBins',50,@isnumeric); +addParameter(p,'intervals',[0 inf],@isnumeric); +addParameter(p,'makePlot',true,@islogical); +addParameter(p,'method','hilbert',@ischar); + +parse(p,varargin{:}); +ampCh = p.Results.ampCh; +phaseCh = p.Results.phaseCh; +makePlot = p.Results.makePlot; +filterType = p.Results.filterType; +filterOrder = p.Results.filterOrder; +ampNumBins = p.Results.ampNumBins; +phaseNumBins = p.Results.phaseNumBins; +intervals = p.Results.intervals; +method = p.Results.method; + + +%% +phasebins = linspace(-pi,pi,phaseNumBins+1); +phasebins=phasebins(1:end-1)+diff(phasebins(1:2)); +ampbins = linspace(-2.5,2.5,ampNumBins); +% ampNumBins = length(ampbins); + +%% +sig1 = bz_Filter(lfp,'passband',sig1band,'filter',filterType,'order',filterOrder,'channels',phaseCh); +sig1phase = sig1.phase; +switch method + case 'wavelet' + sig1 = bz_Wavelet(lfp,'frange',sig1band,'nfreqs',1,'chanID',phaseCh); + sig1amp = abs(sig1.data); + + sig2 = bz_Wavelet(lfp,'frange',sig1band,'nfreqs',1,'chanID',ampCh); + sig2amp = abs(sig2.data); + case 'hilbert' + sig1 = bz_Filter(lfp,'passband',sig1band,'filter',filterType,'order',filterOrder,'channels',phaseCh); + sig1amp = sig1.amp; + + sig2 = bz_Filter(lfp,'passband',sig2band,'filter',filterType,'order',filterOrder,'channels',ampCh); + sig2amp = sig2.amp; + +end + +sig1amp = zscore(sig1amp); +sig2amp = zscore(sig2amp); + +%% +sig1binpower = interp1(ampbins,ampbins,sig1amp,'nearest'); +sig1binphase = interp1(phasebins,phasebins,sig1phase,'nearest'); + + +powerhist = zeros(ampNumBins,ampNumBins); +sig2prefangle = zeros(ampNumBins,1); +sig2powerskew = zeros(ampNumBins,1); +for bb = 1:ampNumBins + for bbb = 1:phaseNumBins + ampwintimes = sig2amp(sig1binpower==ampbins(bb) & sig1binphase==phasebins(bbb)); + phaseamphist(bb,bbb) = mean(ampwintimes); + end + sig2powerskew(bb) = mean(sig2amp(sig1binpower==ampbins(bb)).*exp(1i.*sig1phase(sig1binpower==ampbins(bb)))); + sig2prefangle(bb) = angle(sig2powerskew(bb)); + sig2powerskew(bb) = abs(sig2powerskew(bb)); +end + + +comod.ampbins = ampbins; +comod.phasebins = phasebins; +comod.sig2powerskew = sig2powerskew; +comod.sig2prefangle = sig2prefangle; +comod.phaseamphist = phaseamphist; +comod.params.sig1band = sig1band; +comod.params.sig2band = sig2band; +comod.params.method = method; +comod.params.filterType = filterType; +comod.params.filterOrder = filterOrder; +%% Figure + +if makePlot + rwbcolormap = makeColorMap([0 0 0.8],[1 1 1],[0.8 0 0]); + plotx = linspace(-pi,3*pi,100); + figure + subplot(2,2,1) + hold on + imagesc(phasebins,ampbins,phaseamphist) + imagesc(phasebins+2*pi,ampbins,phaseamphist) + plot(sig2prefangle,ampbins,'.k') + plot(sig2prefangle+2*pi,ampbins,'.k') + plot(plotx,cos(plotx),'k') + colormap(gca,rwbcolormap) + axis xy + axis tight + ColorbarWithAxis([-0.5 0.5],['Mean Amp.']) + caxis([-0.5 0.5]) + % xlim([-pi 3*pi]);ylim(ampbins([1 end])) + xlabel('Signal 1 Phase');ylabel('Signal 1 Amp (Z)') + subplot(4,2,2) + plot(ampbins,sig2powerskew,'k','LineWidth',1) + xlabel('Signal 1 Amp. (Z)'); + ylabel('Phase-Amp. Modulation (mrl)') + axis tight + subplot(4,2,6) + histogram(sig1amp,ampbins) + xlabel('Signal 1 Amp. (Z)'); + ylabel('Occupancy') + axis tight + title('Signal1/2 Amp. Distributions') + + subplot(4,2,8) + histogram(sig2amp,ampbins) + xlabel('Signal 2 Amp. (Z)'); + ylabel('Occupancy') + axis tight + + subplot(2,2,3) + gasphist = hist3([sig1amp,sig2amp],{ampbins,ampbins}); + imagesc(ampbins,ampbins,gasphist) + axis xy + xlabel('Signal 1 Power');ylabel('Signal 2 Power') +end +end + diff --git a/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m b/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m deleted file mode 100755 index 48e10ca7..00000000 --- a/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m +++ /dev/null @@ -1,101 +0,0 @@ -function [phaseamplitudemap,ampfreqs,phasecenters] = bz_PhaseAmplitudeDist(lfp,phaserange,amprange) -% [phaseamplitudemap,ampfreqs,phasecenters] = bz_PhaseAmplitudeDist(lfp,phaserange,amprange) -%This function calculates the mean amplitude of higher frequency bands at -%a the phase for a given lower frequency band signal -% -%%INPUT -% lfp a buzcode structure with fields lfp.data, -% lfp.timestamps -% lfp.samplingRate -% phaserange [min max] frequency to filter for the phase signal -% amprange [min max] frequency range for wavelets for the power -% signal -% optional list of property-value pairs (see table below) -% -% ========================================================================= -% Properties Values -% ------------------------------------------------------------------------- -% not implemented yet.... 'nfreqs' for amplitude signal -% 'ncyc' for wavelet parameters -% 'interval' interval on which to calculate -% other filter parameters -% 'phasesignal' option for other signal to be -% used as phase information -% ========================================================================= -% -%OUTPUT -% phaseamplitudemap phase by frequency matrix. value is mean amplitude -% of that frequency at that phase -% (relative to the mean over the entire signal) -% ampfreqs amplitude frequencies -% phasecenters phase bins -% -% -%Dependencies -% bz_Filter -% bz_WaveSpec -% -%Last Updated: 10/9/15 -%DLevenstein - -% -% -%DLevenstein 2016 -%TO DO: intervals support -%% DEV (these should be varagins) -nfreqs = 100; -ncyc = 7; - -%% Deal with input types -sf_LFP = lfp.samplingRate; - - -%% Filter LFP for the phase -filtered_phase = bz_Filter(lfp,'passband',phaserange,'filter','fir1'); - -%% Get LFP, Phase in intervals -%edgebuffer = 1; %s -%edgebuffer_si = edgebuffer.*sf_LFP; -%edgebuffer = edgebuffer.*[1 1]; -% LFP_int = IsolateEpochs2(LFP.data,int,edgebuffer,sf_LFP); -% LFPphase_int = IsolateEpochs2(filtered.phase,int,edgebuffer,sf_LFP); - -%% Wavelet Transform LFP in intervals -wavespec_amp = bz_WaveSpec(lfp,'frange',amprange); -%[ampfreqs,~,spec_int] -%spec_int = cellfun(@(X) abs(X),spec_int,'UniformOutput',false); -wavespec_amp.data = log10(abs(wavespec_amp.data)); %log scaled for normality -wavespec_amp.mean = mean(wavespec_amp.data,1); -%Remove Buffers (this should be done via 'intervals' in bz_WaveSpec -%spec_int = cellfun(@(X) X(:,edgebuffer_si:end-edgebuffer_si),spec_int,'UniformOutput',false); -%LFPphase_int = cellfun(@(X) X(edgebuffer_si:end-edgebuffer_si),LFPphase_int,'UniformOutput',false); - -%% Bin phase and power -numbins = 50; -phasebins = linspace(-pi,pi,numbins+1); -phasecenters = phasebins(1:end-1)+(phasebins(2)-phasebins(1)); - -[phasedist,~,phaseall] = histcounts(filtered_phase.phase,phasebins); - - -phaseamplitudemap = zeros(numbins,nfreqs); -for bb = 1:numbins - phaseamplitudemap(bb,:) = mean(wavespec_amp.data(phaseall==bb,:),1)./wavespec_amp.mean; -end - -ampfreqs = wavespec_amp.freqs; -%% Plot -figure - imagesc(phasecenters,log2(ampfreqs),((phaseamplitudemap))') - hold on - imagesc(phasecenters+2*pi,log2(ampfreqs),((phaseamplitudemap))') - plot(linspace(-pi,3*pi),cos(linspace(-pi,3*pi))+log2(ampfreqs(end/2)),'k') - xlabel(['Phase (',num2str(phaserange),'Hz)']); - ylabel('Frequency (Hz') - LogScale('y',2) - xlim([phasecenters(1) phasecenters(end)+2*pi]); - colorbar - axis xy - -end - diff --git a/analysis/lfp/CurrentSourceDensity/bz_CSD.m b/analysis/lfp_general/bz_CSD.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/CurrentSourceDensity/bz_CSD.m rename to analysis/lfp_general/bz_CSD.m diff --git a/analysis/lfp/bz_DownsampleLFP.m b/analysis/lfp_general/bz_DownsampleLFP.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/bz_DownsampleLFP.m rename to analysis/lfp_general/bz_DownsampleLFP.m diff --git a/analysis/lfp/bz_Filter.m b/analysis/lfp_general/bz_Filter.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/bz_Filter.m rename to analysis/lfp_general/bz_Filter.m diff --git a/analysis/lfp/bz_GradDescCluster.m b/analysis/lfp_general/bz_GradDescCluster.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/bz_GradDescCluster.m rename to analysis/lfp_general/bz_GradDescCluster.m diff --git a/analysis/lfp/bz_LFPPowerDist.m b/analysis/lfp_general/bz_LFPPowerDist.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/bz_LFPPowerDist.m rename to analysis/lfp_general/bz_LFPPowerDist.m diff --git a/analysis/lfp/bz_LFPSpecToExternalVar.m b/analysis/lfp_general/bz_LFPSpecToExternalVar.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/bz_LFPSpecToExternalVar.m rename to analysis/lfp_general/bz_LFPSpecToExternalVar.m diff --git a/analysis/lfp/bz_PowerSpectrumSlope.m b/analysis/lfp_general/bz_PowerSpectrumSlope.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/bz_PowerSpectrumSlope.m rename to analysis/lfp_general/bz_PowerSpectrumSlope.m diff --git a/analysis/lfp/CurrentSourceDensity/bz_eventCSD.m b/analysis/lfp_general/bz_eventCSD.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/CurrentSourceDensity/bz_eventCSD.m rename to analysis/lfp_general/bz_eventCSD.m diff --git a/analysis/spikes/correlation/CCG.m b/analysis/monosynapticPairs/CCG.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/correlation/CCG.m rename to analysis/monosynapticPairs/CCG.m diff --git a/analysis/spikes/correlation/CCGHeart.c b/analysis/monosynapticPairs/CCGHeart.c old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/correlation/CCGHeart.c rename to analysis/monosynapticPairs/CCGHeart.c diff --git a/analysis/spikes/functionalConnectionIdentification/ProbSynMat.mat b/analysis/monosynapticPairs/ProbSynMat.mat old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/functionalConnectionIdentification/ProbSynMat.mat rename to analysis/monosynapticPairs/ProbSynMat.mat diff --git a/analysis/lfp/LFPConnectivity/blank.file b/analysis/monosynapticPairs/blank.file old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/LFPConnectivity/blank.file rename to analysis/monosynapticPairs/blank.file diff --git a/analysis/spikes/functionalConnectionIdentification/bz_GetMonoSynapticallyConnected.m b/analysis/monosynapticPairs/bz_GetMonoSynapticallyConnected.m old mode 100755 new mode 100644 similarity index 62% rename from analysis/spikes/functionalConnectionIdentification/bz_GetMonoSynapticallyConnected.m rename to analysis/monosynapticPairs/bz_GetMonoSynapticallyConnected.m index e5104cfb..5824f84a --- a/analysis/spikes/functionalConnectionIdentification/bz_GetMonoSynapticallyConnected.m +++ b/analysis/monosynapticPairs/bz_GetMonoSynapticallyConnected.m @@ -33,6 +33,10 @@ %%% tight_subplot, mtit (from matlabcentral) %%% bz_cch_conv, bz_PlotMonoSyn, bz_MonoSynConvClick %%% +%%% saveMat: logical (default = false) to save as buzcode-style mono_res.cellinfo file +%%% +%%% plot: logical (default = true) +%%% %%% OUTPUT %%% mono_res.alpha = p-value %%% mono_res.ccgR = 3D CCG (time x ref x target; @@ -48,14 +52,48 @@ %%% mono_res.FalsePositive = FalsePositive rate from English et al., 2017; %%% mono_res.TruePositive = TruePositive rate from English et al., 2017; -%Written by Sam McKenzie 2017 +% Written by Sam McKenzie 2017 +% Modified by Roman Huszar 2019 + +saveMat = false; +% Look specifically for saveMat input type among inputs +charInds = find( cellfun(@ischar, varargin) ); +flag = cellfun(@(x) strcmp(x, 'saveMat'), varargin(charInds)); +if ~isempty( charInds(flag) ) + saveMat = varargin{charInds(flag)+1}; + if ~islogical(saveMat) + error('Incorrect value for property ''saveMat'''); + end + varargin(charInds(flag):charInds(flag)+1) = []; +end -%load data +% Load data spikes = bz_GetSpikes('basepath',basepath); -%get shank clu -spikeIDs = [spikes.shankID(spikes.spindices(:,2))' spikes.cluID(spikes.spindices(:,2))' spikes.spindices(:,2)]; +% Get shank clu +try + spikeIDs = [spikes.shankID(spikes.spindices(:,2))' spikes.cluID(spikes.spindices(:,2))' spikes.spindices(:,2)]; +catch +% For datasets where length(spikes.UID) ~= max(spikes.UID) + inds = nan(length(spikes.spindices), 1); + for kp = 1:length(spikes.spindices) + inds(kp) = find(spikes.UID == spikes.spindices(kp,2)); + end + spikeIDs = [spikes.shankID(inds)' spikes.cluID(inds)' inds]; + +end -%call main script +% Call detection script - here is where the heavy lifting happens mono_res = bz_MonoSynConvClick (spikeIDs,spikes.spindices(:,1),varargin); +basename = bz_BasenameFromBasepath(basepath); +mono_res.UID = spikes.UID; +mono_res.sessionName = basename; + +% Save +if saveMat + outpath = fullfile(basepath, [basename '.mono_res.cellinfo.mat']); + save(outpath, 'mono_res') +end + + end \ No newline at end of file diff --git a/analysis/monosynapticPairs/bz_GetSynapsembles.m b/analysis/monosynapticPairs/bz_GetSynapsembles.m new file mode 100644 index 00000000..7771791a --- /dev/null +++ b/analysis/monosynapticPairs/bz_GetSynapsembles.m @@ -0,0 +1,227 @@ +function bz_GetSynapsembles(varargin) +% bz_GetSynapsembles - identify E->I synaptic assemblies +% +% USAGE +% +% bz_GetSynapsembles(varargin) +% +% INPUTS +% +% basepath -path to recording (where .dat/.clu/etc files are) +% cellTypeSource -file format for loading cell types +% (default='SM' ; allowed formats {'buzcode', 'PP', 'SM'}) +% fitCoupling -logical (default=false) to fit the long term +% coupling; if coupling has already been fit, this will +% refit and overwrite existing files +% saveMat -logical (default=false) to save in buzcode format +% forcePlasticity -logical (default=false) run bz_GetSynapsembles.m for +% extracting fluctuations in spike transmission only +% (up to bz_fitPoissPlasticity.m) +% +% OUTPUTS +% +% 1) Find/load connected pairs - find significant high frequency, delayed +% synchrony (English, McKenzie et al., Neuron 2017) +% 2) Compute/load the temporal fluctuations in synaptic coupling for each pair +% while regressing out slow changes in post-synaptic rates +% 3) Identify synapses that covary together - synapsembles +% PCA then ICA (method described in Lopes-dos-Santos et al., 2013) +% +% NOTES +% - the function is written to support file formats in datasets that have +% looked at monosynaptic connections +% - for now, the function allows non-buzcode formats for some of the +% necessary inputs (e.g., cell types) +% +% Roman Huszar & Sam McKenzie, August 2019 + +%% STEP 0 PROCESS INPUTS + +% Parse / save user inputs +p = inputParser; + +addParameter(p, 'cellTypeSource', 'buzcode', @ischar) +addParameter(p, 'basepath',pwd,@isstr); +addParameter(p, 'fitCoupling',false,@islogical); +addParameter(p, 'plotFlag',false,@islogical); +addParameter(p, 'forcePlasticity',false,@islogical) + +parse(p,varargin{:}) + +cellTypeSource = p.Results.cellTypeSource; +basepath = p.Results.basepath; +fitCoupling = p.Results.fitCoupling; +plotFlag = p.Results.plotFlag; +forcePlasticity = p.Results.forcePlasticity; + +% If 'monosyn_plast' folder does not exist or is empty, fit long term coupling +glmfits_datadir = fullfile(basepath,'monosyn_plast'); +if ~exist(glmfits_datadir, 'dir') || ( exist(glmfits_datadir, 'dir') && 2 == length(dir(glmfits_datadir)) ) + fitCoupling = true; +end + +basename = bz_BasenameFromBasepath(basepath); + +% Get cell types +if strcmp(cellTypeSource, 'buzcode') % buzcode celltype formatting + try + load(fullfile(basepath,[basename '.CellClass.cellinfo.mat'])) + catch + error('basename.CellClass.cellinfo.mat could not be found in basepath...') + end + pyr = find( CellClass.pE ); + int = find( CellClass.pI ); +elseif strcmp(cellTypeSource, 'SM') % Sam McKenzie's celltype formatting + try + v = load(fullfile(basepath,[basename '_CellParams.mat'])); + catch + error('basename_CellParams.mat could not be found in basepath...') + end + pyr = find( [v.CellParams.cellType] == 1 ); + int = find( [v.CellParams.cellType] > 1 ); +elseif strcmp(cellTypeSource, 'PP') % Peter Petersen's celltype formatting + try + load(fullfile(basepath, [basename '.cell_metrics.cellinfo.mat'])) + catch + error('basename.cell_metrics.cellinfo.mat could not be found in basepath...') + end + pyr = find( cellfun( @(x) strcmp(x, 'Pyramidal Cell'), cell_metrics.putativeCellType ) ); + int = find( cellfun( @(x) ~isempty(regexp(x, 'Interneuron', 'once')), cell_metrics.putativeCellType ) ); +else + error('Incorrect value for property ''cellTypeSource'' '); +end + +%% STEP 1 FIND CONNECTED PAIRS (PYR-->INT) +% English, McKenzie et al., 2017 + +if fitCoupling + +% Find monosynaptically connected pairs +mono_path = [basepath filesep basename '.mono_res.cellinfo.mat']; +if strcmp(cellTypeSource, 'SM') + mono_res = v.mono_res; +elseif exist(mono_path, 'file') + load(mono_path); +else + fprintf('Detecting monosynaptic connections...\n') + mono_res = bz_GetMonoSynapticallyConnected(basepath,'plot',false, 'saveMat', false); +end + +% Only take connections between pyr --> int, other significant hits are +% either high freq. interneuron synchrony likely due to common input or +% spike sorting errors where spikes that are part of a burst a shared +% across units +if ~isfield(mono_res, 'pyr2int') + kp = ismember(mono_res.sig_con(:,1),pyr) & ismember(mono_res.sig_con(:,2),int); + mono_res.pyr2int = mono_res.sig_con(kp,:); + % Store mono_res.cellinfo file that includes + save( fullfile(basepath, [basename '.mono_res.cellinfo.mat']), 'mono_res') +end + +end + +%% 2) ESTIMATE FLUCTUATIONS IN EXCESS SYNCHRONY OVER TIME + +if fitCoupling + +% Fit Poisson GLM to account for long term coupling +for ii = 1:size(mono_res.pyr2int,1) + + bz_fitPoissPlasticity(basepath, mono_res.pyr2int(ii,1), mono_res.pyr2int(ii,2)) + +end + +end + +% Return if we only want to extract time-varying spike transmission +if forcePlasticity + return +end + +%% 3) DETECT SYNAPSEMBLES + +% Load all data from step 2 +fils= dir(glmfits_datadir); +fils = {fils.name}; +kp = cellfun(@any,regexp(fils',[basename '_p'])); +fils = fils(kp); + +% Load all spike transmission timeseries +load(fullfile(glmfits_datadir,fils{1}), 'ts_lastelement', 'down_dt','dt') +ts = 0:dt:ts_lastelement; ts_down = ts(1:round(down_dt/dt):end); +spktrans_t = nan(length(fils), length(ts_down)); +pairID = nan(length(fils), 2); + +fprintf('Loading spike transmission timeseries... \n') +for ii = 1:length(fils) + + v = load(fullfile(glmfits_datadir,fils{ii}), 'ltc_t', 'preID', 'postID'); + spktrans_t(ii,:) = v.ltc_t; + pairID(ii,:) = [v.preID v.postID]; + +end + +% Get rid of pairs with bad fits (nans / infs) +pairID( any(isnan(spktrans_t),2) | any(isinf(spktrans_t),2) , : ) = []; +spktrans_t( any(isnan(spktrans_t),2) | any(isinf(spktrans_t),2) , : ) = []; + +% Parameters for assembly detection +opts.threshold.method = 'MarcenkoPastur'; +opts.Patterns.method = 'ICA'; +opts.Patterns.number_of_iterations = 500; + +fprintf('Detecting synapsembles (PCA/ICA)... \n') +assemblyTemplates = assembly_patterns(spktrans_t,opts); + +% Project each datapoint in pre / post stim epoch onto each assembly +% template to get assembly expression strength over time +assemblyExpressions = assembly_activity(assemblyTemplates, spktrans_t); + +% Save output of synapsemble analysis +fprintf('Saving synapsembles... \n') +save( fullfile( glmfits_datadir, 'synapsembles.mat' ), 'assemblyTemplates', 'assemblyExpressions', '-v7.3') + +%% 4) PLOT + +if plotFlag + +close all + +figure +set(gcf,'Position',[ 1000 833 1109 505 ]) + +% Find sorting that maximally decorelates synapsemble weigths +D = pdist(assemblyTemplates,'correlation'); +Z = linkage(D,'average'); +leafOrder = optimalleaforder(Z,D); + +% Find which pairs best correlate with which synapsemble +wc = corr(zscore([assemblyExpressions;spktrans_t(leafOrder,:)])'); +nAssemblies = size(assemblyExpressions,1); +wc = wc(nAssemblies+1:end,1:nAssemblies); +[~,b] = max(wc); +[~,b] = sort(b); +aw = assemblyExpressions(b,:); + +% Plot spike transmission timeseries +subplot(2,1,1) +imagesc(ts_down / 60,[],zscore(spktrans_t(leafOrder,:),[],2),[-5 5]) +colorbar +xlabel('Time (min)', 'fontsize', 14) +ylabel('PYR-INT pair', 'fontsize', 14) + +% Plot synapsemble expression strength timeseries +subplot(2,1,2) +imagesc(ts_down / 60,[],zscore(aw,[],2),[-5 5]) +xlabel('Time (min)', 'fontsize', 14) +ylabel('Synapsemble', 'fontsize', 14) +colormap(gca,bluewhitered) +colorbar + +set(gca,'ydir','normal') + +end + +%% + +end diff --git a/analysis/spikes/functionalConnectionIdentification/bz_MonoSynConvClick.m b/analysis/monosynapticPairs/bz_MonoSynConvClick.m old mode 100755 new mode 100644 similarity index 99% rename from analysis/spikes/functionalConnectionIdentification/bz_MonoSynConvClick.m rename to analysis/monosynapticPairs/bz_MonoSynConvClick.m index ea15811a..d1689ce5 --- a/analysis/spikes/functionalConnectionIdentification/bz_MonoSynConvClick.m +++ b/analysis/monosynapticPairs/bz_MonoSynConvClick.m @@ -57,7 +57,7 @@ fil = which('bz_MonoSynConvClick'); -sl = regexp(fil,'/'); +sl = regexp(fil,filesep); fil = fil(1:sl(end)); foundMat = false; if exist([fil 'ProbSynMat.mat'],'file')==2 @@ -267,7 +267,7 @@ end %check which is bigger - if (any(sigud(prebins)) && sigpre) + if (any(sigud(postbins)) && sigpre) %test if causal is bigger than anti causal diff --git a/analysis/spikes/functionalConnectionIdentification/bz_PlotMonoSyn.m b/analysis/monosynapticPairs/bz_PlotMonoSyn.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/functionalConnectionIdentification/bz_PlotMonoSyn.m rename to analysis/monosynapticPairs/bz_PlotMonoSyn.m diff --git a/analysis/spikes/functionalConnectionIdentification/bz_cch_conv.m b/analysis/monosynapticPairs/bz_cch_conv.m old mode 100755 new mode 100644 similarity index 97% rename from analysis/spikes/functionalConnectionIdentification/bz_cch_conv.m rename to analysis/monosynapticPairs/bz_cch_conv.m index 9f5eb51b..98053035 --- a/analysis/spikes/functionalConnectionIdentification/bz_cch_conv.m +++ b/analysis/monosynapticPairs/bz_cch_conv.m @@ -1,143 +1,143 @@ -% bz_CCH_CONV predictor and p-values for CCH using convolution -% -% CALL [ PVALS, PRED, QVALS ] = CCH_CONV( CCH, W ) -% -% GETS CCH vector (a single CCH) or matrix (CCHs in columns) -% has to be non-negative integers (counts) -% W window width [samples] {5} -% has to be non-negative integer no larger than the CCH length -% RETURNS PVALS p-values (bin-wise) for exceeding chance -% PRED predictor(expected values) -% QVALS p-values (bin-wise) for below chance -% -% ADVICE for minimal run-time, collect multiple CCHs in -% the columns of CCH and call this routine once -% -% ADVANCE CALL [ PVALS, PRED ] = CCH_CONV( CCH, W, WINTYPE, HF ) -% -% ADVANCED ARGUMENTS WINTYPE window type; -% {'gaussian'} - with SD of W/2; has optimal statistical properties -% 'rect' - of W samples; equivalent to jittering one spike train by a rectangular window of width W -% 'triang' - of ~2W samples; equivalent to jittering both trains by a rectangular window of width W -% HF hollowed fraction; default value depends on window type; -% gaussian: 0.6 -% rectangular: 0.42 -% triangular: 0.63 -% -% REFERENCE Stark and Abeles JNM 2009 - -% 12-aug-09 ES - -% revisions -% 11-jan-12 added qvals for deficient counts. to get global significance -% (including correction for multiple comparisons), check crossing of alpha -% divided by the number of bins tested - -function [ pvals, pred, qvals ] = bz_cch_conv( CCH, W, WINTYPE, HF, CALCP ) - -% 1. CHECK ARGUMENTS -nargs = nargin; -CCH = double(CCH); -if nargs < 1, error( 'missing argument CCH' ), end -[ m n ] = size( CCH ); -if m * n <= 1, error( 'improper argument CCH' ), end -if m == 1 - CCH = CCH'; - nsamps = n; - ncchs = 1; -else - nsamps = m; - ncchs = n; -end -nlags = ( nsamps - 1 ) / 2; -if ( sum( sum( CCH - round( CCH ) ) ) ) || ( sum( sum( CCH < 0 ) ) > 0 ) -% error( 'improper argument CCH (must contain non-negative integers)' ) -end - -if nargs < 2 || isempty( W ), W = 5; end -if W ~= round( W ) || W < 1 - error( 'W must be non-negative interger' ) -end - -if nargs < 3 || isempty( WINTYPE ), WINTYPE = 'gaussian'; end -WINTYPE = lower( WINTYPE ); - -if nargs < 4 || isempty( HF ), - switch WINTYPE - case { 'gauss', 'gaussian' }, HF = 0.6; - case 'rect', HF = 0.42; - case 'triang', HF = 0.63; - end -else - if HF < 0 || HF > 1 - error( 'HF not in range (0-1)' ) - end -end - -if nargs < 5 || isempty( CALCP ), - CALCP = 1; -end - -% 2. PREPARE THE CONVOLUTION WINDOW -switch WINTYPE - case { 'gauss', 'gaussian' } - SDG = W / 2; - if round( SDG ) == SDG % even W - win = local_gausskernel( SDG, 6 * SDG + 1 ); - cidx = SDG * 3 + 1; - else - win = local_gausskernel( SDG, 6 * SDG + 2 ); - cidx = SDG * 3 + 1.5; - end - case 'rect' - if W / 2 == floor( W / 2 ) % even - win = ones( 1, W + 1 ); - cidx = W / 2 + 1; - else - win = ones( 1, W ); - cidx = ceil( W / 2 ); - end - case 'triang' - if W / 2 == floor( W / 2 ) % even - win = triang( 2 * W + 1 ); - cidx = W + 1; - else - win = triang( 2 * W - 1 ); - cidx = W; - end - otherwise - error( 'un-supported window type' ) -end -win( cidx ) = win( cidx ) * ( 1 - HF ); -win = win / sum( win ); -if nsamps < ( 1.5 * length( win ) ) - error( 'CCH-W mismatch (CCHs should be in columns; otherwise reduce W or elongate CCH)' ) -end - -% 3. COMPUTE A PREDICTOR BY CONVOLVING THE CCH WITH THE WINDOW: -pred = local_firfilt( CCH, win ); - -% 4. COMPUTE P-VALUE BASED ON A POISSON DISTRIBUTION WITH A CONTINUITY CORRECTION: -if CALCP -% pvals = 1 - poisscdf( CCH - 1, pred ) - poisspdf( CCH, pred ) .* rand( nsamps, ncchs ); % excess - pvals = 1 - poisscdf( CCH - 1, pred ) - poisspdf( CCH, pred ) * 0.5; % excess, deterministic -else - pvals = NaN; -end -qvals = 1 - pvals; % deficient - -return - -% LOCAL FUNCTIONS -function Y = local_firfilt( x, W ) % zero-phase lag low-pass filtering of x's columns with the FIR W -C = length( W ); -D = ceil( C / 2 ) - 1; -x = double(x); -Y = filter( W, 1, [ flipud( x( 1 : C, : ) ); x; flipud( x( end - C + 1 : end, : ) ) ] ); -Y = Y( 1 + C + D : end - C + D, : ); -return - -function K = local_gausskernel( sigmaX, N ) % 1D Gaussian kernel K with N samples and SD sigmaX -x = -( N - 1 ) / 2 : ( N - 1 ) / 2; -K = 1 / ( 2 * pi * sigmaX ) * exp( -( x.^2 / 2 / sigmaX^2 ) ); -return +% bz_CCH_CONV predictor and p-values for CCH using convolution +% +% CALL [ PVALS, PRED, QVALS ] = CCH_CONV( CCH, W ) +% +% GETS CCH vector (a single CCH) or matrix (CCHs in columns) +% has to be non-negative integers (counts) +% W window width [samples] {5} +% has to be non-negative integer no larger than the CCH length +% RETURNS PVALS p-values (bin-wise) for exceeding chance +% PRED predictor(expected values) +% QVALS p-values (bin-wise) for below chance +% +% ADVICE for minimal run-time, collect multiple CCHs in +% the columns of CCH and call this routine once +% +% ADVANCE CALL [ PVALS, PRED ] = CCH_CONV( CCH, W, WINTYPE, HF ) +% +% ADVANCED ARGUMENTS WINTYPE window type; +% {'gaussian'} - with SD of W/2; has optimal statistical properties +% 'rect' - of W samples; equivalent to jittering one spike train by a rectangular window of width W +% 'triang' - of ~2W samples; equivalent to jittering both trains by a rectangular window of width W +% HF hollowed fraction; default value depends on window type; +% gaussian: 0.6 +% rectangular: 0.42 +% triangular: 0.63 +% +% REFERENCE Stark and Abeles JNM 2009 + +% 12-aug-09 ES + +% revisions +% 11-jan-12 added qvals for deficient counts. to get global significance +% (including correction for multiple comparisons), check crossing of alpha +% divided by the number of bins tested + +function [ pvals, pred, qvals ] = bz_cch_conv( CCH, W, WINTYPE, HF, CALCP ) + +% 1. CHECK ARGUMENTS +nargs = nargin; +CCH = double(CCH); +if nargs < 1, error( 'missing argument CCH' ), end +[ m n ] = size( CCH ); +if m * n <= 1, error( 'improper argument CCH' ), end +if m == 1 + CCH = CCH'; + nsamps = n; + ncchs = 1; +else + nsamps = m; + ncchs = n; +end +nlags = ( nsamps - 1 ) / 2; +if ( sum( sum( CCH - round( CCH ) ) ) ) || ( sum( sum( CCH < 0 ) ) > 0 ) +% error( 'improper argument CCH (must contain non-negative integers)' ) +end + +if nargs < 2 || isempty( W ), W = 5; end +if W ~= round( W ) || W < 1 + error( 'W must be non-negative interger' ) +end + +if nargs < 3 || isempty( WINTYPE ), WINTYPE = 'gaussian'; end +WINTYPE = lower( WINTYPE ); + +if nargs < 4 || isempty( HF ), + switch WINTYPE + case { 'gauss', 'gaussian' }, HF = 0.6; + case 'rect', HF = 0.42; + case 'triang', HF = 0.63; + end +else + if HF < 0 || HF > 1 + error( 'HF not in range (0-1)' ) + end +end + +if nargs < 5 || isempty( CALCP ), + CALCP = 1; +end + +% 2. PREPARE THE CONVOLUTION WINDOW +switch WINTYPE + case { 'gauss', 'gaussian' } + SDG = W / 2; + if round( SDG ) == SDG % even W + win = local_gausskernel( SDG, 6 * SDG + 1 ); + cidx = SDG * 3 + 1; + else + win = local_gausskernel( SDG, 6 * SDG + 2 ); + cidx = SDG * 3 + 1.5; + end + case 'rect' + if W / 2 == floor( W / 2 ) % even + win = ones( 1, W + 1 ); + cidx = W / 2 + 1; + else + win = ones( 1, W ); + cidx = ceil( W / 2 ); + end + case 'triang' + if W / 2 == floor( W / 2 ) % even + win = triang( 2 * W + 1 ); + cidx = W + 1; + else + win = triang( 2 * W - 1 ); + cidx = W; + end + otherwise + error( 'un-supported window type' ) +end +win( cidx ) = win( cidx ) * ( 1 - HF ); +win = win / sum( win ); +if nsamps < ( 1.5 * length( win ) ) + error( 'CCH-W mismatch (CCHs should be in columns; otherwise reduce W or elongate CCH)' ) +end + +% 3. COMPUTE A PREDICTOR BY CONVOLVING THE CCH WITH THE WINDOW: +pred = local_firfilt( CCH, win ); + +% 4. COMPUTE P-VALUE BASED ON A POISSON DISTRIBUTION WITH A CONTINUITY CORRECTION: +if CALCP +% pvals = 1 - poisscdf( CCH - 1, pred ) - poisspdf( CCH, pred ) .* rand( nsamps, ncchs ); % excess + pvals = 1 - poisscdf( CCH - 1, pred ) - poisspdf( CCH, pred ) * 0.5; % excess, deterministic +else + pvals = NaN; +end +qvals = 1 - pvals; % deficient + +return + +% LOCAL FUNCTIONS +function Y = local_firfilt( x, W ) % zero-phase lag low-pass filtering of x's columns with the FIR W +C = length( W ); +D = ceil( C / 2 ) - 1; +x = double(x); +Y = filter( W, 1, [ flipud( x( 1 : C, : ) ); x; flipud( x( end - C + 1 : end, : ) ) ] ); +Y = Y( 1 + C + D : end - C + D, : ); +return + +function K = local_gausskernel( sigmaX, N ) % 1D Gaussian kernel K with N samples and SD sigmaX +x = -( N - 1 ) / 2 : ( N - 1 ) / 2; +K = 1 / ( 2 * pi * sigmaX ) * exp( -( x.^2 / 2 / sigmaX^2 ) ); +return diff --git a/analysis/monosynapticPairs/bz_fitPoissPlasticity.m b/analysis/monosynapticPairs/bz_fitPoissPlasticity.m new file mode 100644 index 00000000..df37aee9 --- /dev/null +++ b/analysis/monosynapticPairs/bz_fitPoissPlasticity.m @@ -0,0 +1,294 @@ +function bz_fitPoissPlasticity(basepath, preID,postID,varargin) +% This function takes a pair of neurons and fits a Poisson GLM to account +% for long term monosynaptic coupling. This framework exploits a bin-by-bin baseline +% offset that reflects the postsynaptic spiking binned at a timescale +% slower than the fine timescale spike-spike coupling. +% +% INPUT +% basepath = directory where at least one of two files are located (1 requires) a buzcode +% file [basename '.spikes.cellinfo.mat'] and (2 optional) a file +% with a struct 'stim' with field 'ts' which gives a cell array +% of stimulation times per location ([start stop]) +% preID = index of presynaptic neuron, e.g. spikes.times{preID} +% postID = index of presynaptic neuron, e.g. spikes.times{postID} +% +% OUTPUT +% resaves inputs: 'basepath','preID', 'postID' +% synParamsT = spk trans prob basis weights +% test_nll = negative log likelihood on test set during cross validation +% timescales = basis separation for spike trans to vary over (must be +% enough spikes for an estimate +% ltc_t = spike transmission timeseries +% +% 'basepath', 'preID', 'postID', 'pre', 'post', 'synParamsT', 'test_nll', 'timescales', 'down_dt', 'ts_lastelement', ... +% 'pre_binned_inds', 'post_binned_inds', 'ltc_t', 'lambda','argmax', 'delta', 'binsize', 'dt', 'fitting_duration' +% +% NOTES +% - this function does not verify whether the passed neuron IDs are +% monosynaptically connected +% +% Roman Huszar & Sam McKenzie, August 2019 + +%% STEP 0 PROCESS INPUTS + +tic + +% Parse / save user inputs +p = inputParser; + +addParameter(p, 'outpath', fullfile(basepath,'monosyn_plast'), @isstr) +addParameter(p, 'splitsize', 100, @isnumeric); +addParameter(p, 'delta', 0.015, @isnumeric); +addParameter(p, 'binsize', 0.015, @isnumeric); +addParameter(p, 'num_cv_iter', 10, @isnumeric); +addParameter(p, 'cv_fit_iter', 50, @isnumeric); +addParameter(p, 'timescales', [400 600 800 1000], @isvector); +addParameter(p, 'fit_iter', 1000, @isnumeric); +addParameter(p, 'dt', 0.0008, @isnumeric); +addParameter(p, 'down_dt', 0.1, @isnumeric); + +parse(p,varargin{:}) + +outpath = p.Results.outpath; +splitsize = p.Results.splitsize; % for crossvalidating +delta = p.Results.delta; % ( bz_SpktToSpkmat parameter ) +binsize = p.Results.binsize; % ( bz_SpktToSpkmat parameter ) +num_cv_iter = p.Results.num_cv_iter; % number of crossvalidations +cv_fit_iter = p.Results.cv_fit_iter; % number of crosssvalidation fit iterations +timescales = p.Results.timescales; % basis timescales +fit_iter = p.Results.fit_iter; % number of fit iterations +dt = p.Results.dt; % binsize for spike train binning +down_dt = p.Results.down_dt; % for downsampling excess synchrony estimates + +% Create path to output +if ~exist(outpath, 'dir') + mkdir(outpath) +end + +basename = bz_BasenameFromBasepath(basepath); +pairID = ['_p' num2str(preID) '_' num2str(postID)]; +outfile = fullfile(outpath,[basename pairID]); +% Give user feedback about what is being fit +fprintf('%s, %d to %d \n', basepath, preID, postID) + +%% STEP 1 PROCESS SPIKES + +% Load spikes +spikes = bz_GetSpikes('basepath', basepath); + +% Pull out the full spike trains +post = spikes.times{postID}; +pre = spikes.times{preID}; + +% Bin spikes +latest_spk = max(cellfun(@max, spikes.times)); +ts = [0:dt:latest_spk]'; +post_binned = [histcounts(post, [ts ; ts(end)+dt])]'; +post_binned(post_binned > 1) = 1; +post = find(post_binned)*dt; +pre_binned = [histcounts(pre, [ts ; ts(end)+dt])]'; +pre_binned(pre_binned > 1) = 1; +pre = find(pre_binned)*dt; + +%% STEP 2 GENERATE COUPLING COVARIATE + +% The latency to postsynaptic spike is estimated from the full CCG - we +% take the short-latency positive lag bin with the largest number of spikes +spk_times = [pre ; post]; +groups = [ones(size(pre)) ; 2*ones(size(post))]; + +[spk_times,b] = sort(spk_times); +groups = groups(b); +[ccg,t] = CCG(spk_times,groups, 'binsize', dt, 'duration', 0.2); +ccg = ccg(:,1,2); +ccg([1:126 135:end]) = 0; % Ignore counts outside the relevant window +[ ~,ind ] = max(ccg); +argmax = t(ind) / dt; +% Generate the coupling covariate - an indicator function that is 1 at the +% monosynaptic transmission time lag following each presynaptic spike +pre_spk_inds = find(pre_binned); +couple_indicator = zeros(size(post_binned)); +couple_indicator(pre_spk_inds+argmax) = 1; +if length(couple_indicator) > length(post_binned) + couple_indicator(length(post_binned)+1:end) = []; +end +% Keep track of the presynaptic spike indices associated with bins where +% the coupling indicator is 1 +couple_indicator_inds = couple_indicator; +couple_indicator_inds(couple_indicator_inds == 1) = 1:sum(couple_indicator); + +%% STEP 3 PREPARE ALL THE REGRESSORS + +% Generate even/odd splits indicator for crossvalidation +% We take every other 100 second chunk of data for training the model. +% The remaining data chunks are used for testing. +step = round(splitsize/dt); +splits_indicator = zeros(size(post_binned)); +for ii = 0:ceil(length(splits_indicator)/step)-1 + if mod(ii,2) == 0 + if (ii+1)*step <= length(splits_indicator) + splits_indicator(ii*step+1:(ii+1)*step) = 1; + else + splits_indicator(ii*step+1:end) = 1; + end + else + continue; + end + +end +splits_indicator = logical(splits_indicator); + +% Bin the postsynaptic spike train to get the slow rate +post_rate = bz_SpktToSpkmat({post}, 'binsize', binsize, 'dt', delta); +% Interpolate at the dt resolution to get an estimate of the coarse +% baseline at every time step +coarsened_rate = interp1(post_rate.timestamps, post_rate.data / post_rate.binsize, ts,'linear','extrap'); +coarsened_rate(coarsened_rate <= 0) = 1e-6; +% We take the log of the coarsened rate, so that when it's passed through an +% exponential in the GLM, you get 'coarsened_rate' as your predicted Poisson rate +X_cPost = log(double(coarsened_rate)); +clear smoothed_rate post_rate + +% Preparing options for constrained mininimization +opts.dt = dt; +options = optimoptions('fmincon',... + 'Algorithm','interior-point',... + 'OptimalityTolerance',1e-5,... + 'StepTolerance',1e-8,... + 'ConstraintTolerance',1e-5,... + 'SpecifyObjectiveGradient', true); + + +%% STEP 4 CROSS-VALIDATION +% Find the best spline basis timescale + +fprintf('CV to find the right timescale...\n'); +iter = 1; +% Store the test log likelihood for each timescale iteration +test_nll = {}; +for timescale = timescales + + fprintf('Timescale %d\n', timescale); + hyper_params_tmp.XTimeSpan = timescale; + % Obtain basis - center them on presynaptic spike times + X_couple = calculate_X({pre},hyper_params_tmp); + if size(X_couple,1) > sum(couple_indicator) + X_couple(sum(couple_indicator)+1:end,:) = []; + end + % Prepare training data regressors + coupling_bins_train = couple_indicator & splits_indicator; % Coupling bins within training set + opts.rate = X_cPost(coupling_bins_train,:); + X_odd_temp = X_couple( couple_indicator_inds(coupling_bins_train),: ); + nparams = size(X_odd_temp,2); + + % Handle for the cost function + costfun = @(x) nll_poissPlasticity(x,X_odd_temp,post_binned(coupling_bins_train),opts); + clear X_odd_temp coupling_bins_train + + % For each timescale, we fit model and compute the test likelihood + for kk = 1:num_cv_iter + + % Set the number of iterations for initialization + options.MaxIterations = cv_fit_iter; + options.Display = 'off'; + + % Initialize parameters + bta_init = rand(nparams, 1); + % Constrained optimization problem - we enforce nonnegative basis parameters + [synParamsT,~,~,~] = fmincon(costfun, bta_init, [],[],[],[],... + zeros(1,nparams),... + inf(1,nparams),... + [],options); + + % Get negative log likelihood of the test set + coupling_bins_test = couple_indicator & ~splits_indicator; % Coupling bins within indicator + opts.rate = X_cPost(coupling_bins_test,:); + X_even_temp = X_couple( couple_indicator_inds(coupling_bins_test),: ); + [ f_test,~,~ ] = nll_poissPlasticity(synParamsT,X_even_temp,post_binned(coupling_bins_test),opts); + clear X_even_temp coupling_bins_test + + % Store test likelihood for given this iteration + test_nll{ iter }(kk) = f_test; + + end + + iter = iter+1; +end +clear splits_indicator + +%% STEP 5 FIT GLM + +fprintf('Fit full model...\n'); +[~,arg] = min(cellfun(@mean, test_nll)); +timescale = timescales(arg); + +hyper_params_tmp.XTimeSpan = timescale; +% Obtain basis +X_couple = calculate_X({pre},hyper_params_tmp); +if size(X_couple,1) > sum(couple_indicator) + X_couple(sum(couple_indicator)+1:end,:) = []; +end + +% Generate regressor matrix for all data +coupling_bins = logical(couple_indicator); +opts.rate = X_cPost(coupling_bins); +nparams = size(X_couple,2); + +% Handle for the cost function +costfun = @(x) nll_poissPlasticity(x,X_couple,post_binned(coupling_bins),opts); + +% Update some of the options +options.MaxIterations = fit_iter; +options.Display = 'iter-detailed'; + +% Initialize +bta_init = rand(nparams, 1); +% Optimize +[synParamsT,~,~,~] = fmincon(costfun, bta_init, [],[],[],[],... + zeros(1,nparams),... + inf(1,nparams),... + [],options); + +% Get dt-resolution coupling term +cc = couple_indicator; cc(cc == 1) = X_couple*synParamsT; + +% Compute excess synchrony - timeseries measure of spike transmission in units of Hz +lambda_full = exp(X_cPost + cc); +lambda_baseline = exp(X_cPost); +lambda_diff = lambda_full - lambda_baseline; + +% Also store lambda as P(post = 1 | parameters) +lambda = lambda_full .* dt; + +% Prepare kernel +gauss_window = 180; % 3 minute size window (in seconds) +gauss_SD = 120; % 2 minute standard deviation +grid = -gauss_window:dt:gauss_window; +gk = Gauss(grid, 0, gauss_SD); gk = gk(:); + +gk = gausskernel(180/dt, 120/dt); +gk = gk ./ dt; + +% Convolve excess synchrony and presynaptic spike train. +ldiff_conv = conv2fft(lambda_diff,gk,'same'); +pre_binned_conv = conv2fft(pre_binned,gk,'same'); +% Normalize to get excess synchrony per presynaptic spike +ltc_t = ldiff_conv ./ pre_binned_conv; +% Downsample +ltc_t = ltc_t(1:round(down_dt/dt):end); + +%% STEP 6 SAVE + +% We store the bare minimum in order to be able to post-hoc reconstruct all +% of the variables that were used to fit the model. +ts_lastelement = ts(end); +pre_binned_inds = find(pre_binned); +post_binned_inds = find(post_binned); + +fitting_duration = toc; + +save(outfile, 'basepath', 'preID', 'postID', 'pre', 'post', 'synParamsT', 'test_nll', 'timescales', 'down_dt', 'ts_lastelement', ... + 'pre_binned_inds', 'post_binned_inds', 'ltc_t', 'lambda','argmax', 'delta', 'binsize', 'dt', 'fitting_duration', '-v7.3') + + +end diff --git a/analysis/monosynapticPairs/utils/calculate_X.m b/analysis/monosynapticPairs/utils/calculate_X.m new file mode 100644 index 00000000..0d355da0 --- /dev/null +++ b/analysis/monosynapticPairs/utils/calculate_X.m @@ -0,0 +1,29 @@ +function X = calculate_X(Tlist,hyper_params) +% Calculate covariate bases at the time of presynaptic spike. +% +% INPUT +% Tlist = pre (Tlist{1}) and post (Tlist{2}) synaptic spike times (s) +% hyper_params.XTimeSpan = number of seconds with which to divide session +% +% OUTPUT +% X = covariate matrix in basis space (rows = number of presyn spikes, +% columns = spline bases) +% +% Author: Abed Ghanbari + + +% Spline basis over time +mint = min(cellfun(@min,Tlist)); +maxt = max(cellfun(@max,Tlist)); +% Normalize presynaptic spike times by range of spike times in this pre/post pair +tt = (Tlist{1}-mint)/(maxt-mint); +if ~isfield(hyper_params,'nonstationary_nsplines') + hyper_params.nonstationary_nsplines = ceil( (maxt-mint)/hyper_params.XTimeSpan ); +end + +% Splines are centered on presynaptic spike times - every time hyper_params.XTimeSpan worth +% of spikes happens, you put down a spline. The splines are centered where +% they have their peak +Xfr = getCubicBSplineBasis(tt,hyper_params.nonstationary_nsplines,0); +X = Xfr(:,2:end); + diff --git a/analysis/monosynapticPairs/utils/getCubicBSplineBasis.m b/analysis/monosynapticPairs/utils/getCubicBSplineBasis.m new file mode 100644 index 00000000..7e8cfebb --- /dev/null +++ b/analysis/monosynapticPairs/utils/getCubicBSplineBasis.m @@ -0,0 +1,44 @@ +function b = getCubicBSplineBasis(x,nknots,isCirc) +% Calculates set of splines with n evenly spaces knots to describe +% fluctuations in x +% Author: Abed Ghanbari + +% input +% x = timestamps of neuron normalized by range +% nknots = number of knots required to evens sample time range +% isCirc = wrap time around ? + + +%output = beta weights for the nknots + +% nknots = 3; +% x = linspace(0,1,256); +% isCirc=false; +% x = linspace(0,2*pi,256); +% isCirc=true; + +if ~isCirc & nknots>1 + nknots=nknots-1; % for consistency across circ/non-circ + weights = ones(nknots+1,1); + knots = linspace(-2/nknots,1+2/nknots,nknots+5); %define know positions + s = fastBSpline(knots,weights); + b = s.getBasis(x); +else + knots = linspace(-2*2*pi/nknots,2*pi+2*2*pi/nknots,nknots+5); + weights = ones(nknots+1,1); + s = fastBSpline(knots,weights); + b = zeros(length(x),nknots+1); + for k=-4:4 + xk = x+k*2*pi; + b = b+s.getBasis(xk); + end + b = b(:,1:end-1); +end +% plot(x,b) +% length(knots)-length(weights)-1 +% axis tight +% imagesc(b) + +if nknots>1 + b = [b(:,1)*0+1 b]; +end \ No newline at end of file diff --git a/analysis/monosynapticPairs/utils/nll_poissPlasticity.m b/analysis/monosynapticPairs/utils/nll_poissPlasticity.m new file mode 100644 index 00000000..706bf641 --- /dev/null +++ b/analysis/monosynapticPairs/utils/nll_poissPlasticity.m @@ -0,0 +1,21 @@ +function [f,df,lambda] = nll_poissPlasticity(bta,X,Yy,opts) +% The log of the binned slow rate (opts.rate) is used as a regressor, with the +% weight fixed to be 1. Only the basis parameters affect the likelihood and its +% gradient. + +% Coupling component +cc = X*bta; +% Predicted rate in bins of size dt (can be interpreted as a probability) +lambda = exp(opts.rate + cc) .* opts.dt; +% Negative log likelihood +f = double(nansum(lambda) - nansum(Yy.*log(lambda))); + +% Gradient +df = [ nansum(X.*lambda) - sum(X(logical(Yy),:)) ]'; + +if any(~isfinite(df)) + f = Inf; +end + + +end \ No newline at end of file diff --git a/analysis/spikes/BasicWaveformMetrics/blank.file b/analysis/placeFields/blank.file old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/BasicWaveformMetrics/blank.file rename to analysis/placeFields/blank.file diff --git a/analysis/placeFields/bz_findPlaceFields1D.m b/analysis/placeFields/bz_findPlaceFields1D.m new file mode 100644 index 00000000..d18a51bf --- /dev/null +++ b/analysis/placeFields/bz_findPlaceFields1D.m @@ -0,0 +1,255 @@ +function [placeFieldStats] = bz_findPlaceFields1D(firingMaps,varargin) +% [stats] = bz_findPlaceFields1D(firingMaps) +% Find place fields from 1D firing maps. Reads the output of bz_firingMapAvg + +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% 'threshold' values above threshold*peak belong to the field +% (default = 0.2) +% 'minSize' fields smaller than this percentage of the maze size +% are considered spurious and ignored (default = 0.05) +% 'maxSize' fields larger than this percentage of the maze size +% are considered noise and ignored (default = 0.50) +% 'maxSize' fields with maximum Firing Rate closer to the edges less +% than this percentage of the maze size are ignored +% (default = 0.0) +% are considered noise and ignored (default = 0.50) +% 'minPeak' peaks smaller than this size are considered spurious +% and ignored (default = 1 Hz) +% 'minPeak2nd' for secondary place fields, peaks smaller than this +% percentage of maximum Firing Rate along the maze are +% considered spurious and ignored (default 0.60) +% 'verbose' display processing information (default = 'off') +% ========================================================================= + +% Antonio FR, 10/2019 + +%%%%%%%%%%%%%% WORK IN PROGRESS + +% Parse inputs +p=inputParser; +addParameter(p,'threshold',0.2,@isnumeric); +addParameter(p,'minSize',0.05,@isnumeric); +addParameter(p,'maxSize',0.50,@isnumeric); +addParameter(p,'minPeak',2,@isnumeric); +addParameter(p,'minPeak2nd',0.6,@isnumeric); +addParameter(p,'sepEdge',0.0,@isnumeric); +addParameter(p,'verbose','off',@isstr); +addParameter(p,'saveMat', true, @islogical); + +parse(p,varargin{:}); +sizeMaze = length(firingMaps.rateMaps{1}{1}); +threshold = p.Results.threshold; +minSize = p.Results.minSize * sizeMaze; +maxSize = p.Results.maxSize * sizeMaze; +sepEdge = p.Results.sepEdge * sizeMaze; +minPeak = p.Results.minPeak; +minPeak2nd = p.Results.minPeak2nd; +verbose = p.Results.verbose; +saveMat = p.Results.saveMat; + + +% Find place fields +for unit = 1:length(firingMaps.rateMaps) + for c = 1:length(firingMaps.rateMaps{1}) + % Default values + mapStats{unit,1}{c}.x = NaN; + mapStats{unit,1}{c}.field = []; + mapStats{unit,1}{c}.size = 0; + mapStats{unit,1}{c}.peak = 0; + mapStats{unit,1}{c}.mean = 0; + mapStats{unit,1}{c}.fieldX = [NaN NaN]; + mapStats{unit,1}{c}.specificity = 0; + mapStats{unit,1}{c}.m = nan; + mapStats{unit,1}{c}.r = nan; + mapStats{unit,1}{c}.mode = nan; + mapStats{unit,1}{c}.k = nan; + + % Determine the field as the connex area around the peak where the value or rate is > threshold*peak + % There can be two or more fields + z = firingMaps.rateMaps{unit}{c}; + x = 1:length( firingMaps.rateMaps{1}{1}); + + % Maximum FR along maze + maxFR = max(max(z)); + + % If there is no firing rate, go to next unit + if maxFR == 0, + mapStats{unit,1}{c}.field = logical(zeros(size(z))); + continue; + end + + nBinsX = max([1 length(x)]); % minimum number of bins is 1 + circX = 0; circY = 0; + % Each time we find a field, we will remove it from the map; make a copy first + % Try to find more fields until no remaining bin exceeds min value + i=1; + while true, + % Are there any candidate (unvisited) peaks left? + [peak,idx] = max(z(:)); + % If separation from edges is less than sepEdge, go to next unit + if (idx < sepEdge) | (idx > sizeMaze-sepEdge) + break; + end + % If FR peak of 1st PF is less than minPeak, go to next unit + % If FR peak of 2nd PF is less than minPeak2nd of maximum FR, + % go to next unit + if peak < ((i==1)*minPeak + (i>1)*maxFR*minPeak2nd) + break; + end + % Determine coordinates of largest candidate peak + [y,x] = ind2sub(size(z),idx); + % Find field (using min threshold for inclusion) + field1 = FindFieldHelper(z,x,y,peak*threshold,circX,circY); + size1 = sum(field1(:)); + % Does this field include two coalescent subfields? + % To answer this question, we simply re-run the same field-searching procedure on the field + % we then either keep the original field or choose the subfield if the latter is less than + % 1/2 the size of the former + m = peak*threshold; + field2 = FindFieldHelper(z-m,x,y,(peak-m)*threshold,circX,circY); + size2 = sum(field2(:)); + if size2< 1/2*size1, + field = field2; + tc = ' ';sc = '*'; % for debugging messages + else + field = field1; + tc = '*';sc = ' '; % for debugging messages + end + + % If rate map between place fields doesn't go below threshold, + % discard new place field + good2ndPF = true; + if i>1 + field0ini = find(diff(isnan(z))==1); + field0end = find(diff(isnan(z))==-1); + field1ini = find(diff(field)==1); + field1end = find(diff(field)==-1); + [~,idxBetwFields] = min([abs(field1ini-field0end),abs(field0ini-field1end)]); + if idxBetwFields == 1 + if ~any(z(field1end:field0ini) minSize) && (fieldSize < maxSize) && good2ndPF + mapStats{unit,1}{c}.field(:,i) = field; + mapStats{unit,1}{c}.size(i) = fieldSize; + mapStats{unit,1}{c}.peak(i) = peak; + mapStats{unit,1}{c}.mean(i) = mean(z(field)); + idx = find(field & z == peak); + [mapStats{unit,1}{c}.y(i),mapStats{unit,1}{c}.x(i)] = ind2sub(size(z),idx(1)); + [x,y] = FieldBoundaries(field,circX,circY); + [mapStats{unit,1}{c}.fieldX(i,:),mapStats{unit,1}{c}.fieldY(i,:)] = FieldBoundaries(field,circX,circY); + end + i = i + 1; + + % Mark field bins as visited + z(field) = NaN; + if all(isnan(z)), break; end + end + end +end + + + +% ================= +% WRITE OUTPUT +% ================= + +placeFieldStats = {}; + +% inherit required fields from spikes cellinfo struct +placeFieldStats.UID = firingMaps.UID; +placeFieldStats.sessionName = firingMaps.sessionName; +try +placeFieldStats.region = firingMaps.region; +catch + %warning('spikes.region is missing') +end + +placeFieldStats.params.sizeMaze = sizeMaze; +placeFieldStats.params.threshold = threshold; +placeFieldStats.params.minSize = minSize; +placeFieldStats.params.maxSize = maxSize; +placeFieldStats.params.sepEdge = sepEdge; +placeFieldStats.params.minPeak = minPeak; +placeFieldStats.params.minPeak2nd = minPeak2nd; +placeFieldStats.params.verbose = verbose; +placeFieldStats.params.saveMat = saveMat; + +if saveMat + save([placeFieldStats.sessionName '.placeFields.cellinfo.mat'],'placeFieldStats'); +end + + + + +% ========== +% PLOT +% ========== +for unit = 1:length(firingMaps.rateMaps) + figure; + for c = 1:length(firingMaps.rateMaps{1}) + subplot(2,2,c) + plot(firingMaps.rateMaps{unit}{c},'k') + if sum(firingMaps.rateMaps{unit}{c})>0 + hold on + for ii = 1:size(mapStats{unit}{c}.field,2) + plot(find(mapStats{unit}{c}.field(:,ii)),firingMaps.rateMaps{unit}{c}(mapStats{unit}{c}.field(:,ii)==1),'linewidth',2) + plot([1 1]*mapStats{unit}{c}.x(ii),[0 firingMaps.rateMaps{unit}{c}(mapStats{unit}{c}.x(ii)==1)],'--k') + end + end + if c==1 || c==3, ylabel('FR(Hz)'); end + if c>2, xlabel('Track (cm)'); end + if c==1, title([' Cell ' num2str(unit)]);; end + %ylim([0,12]) + end + saveas(gcf,[pwd '\newPCs\cell_' num2str(unit) '.png'],'png'); + close all; +end + +end + + +%% +% ------------------------------- Helper functions ------------------------------- + +% Field boundaries (circumscribed rectangle) + +function [x,y] = FieldBoundaries(field,circX,circY) + +% Find boundaries +x = find(any(field,1)); +if isempty(x), + x = [NaN NaN]; +else + x = [x(1) x(end)]; +end +y = find(any(field,2)); +if isempty(y), + y = [NaN NaN]; +else + y = [y(1) y(end)]; +end + +% The above works in almost all cases; it fails however for circular coordinates if the field extends +% around an edge, e.g. for angles between 350° and 30° + +if circX && x(1) == 1 && x(2) == size(field,2), + xx = find(~all(field,1)); + if ~isempty(xx), + x = [xx(end) xx(1)]; + end +end +if circY && y(1) == 1 && y(2) == size(field,1), + yy = find(~all(field,2)); + if ~isempty(yy), + y = [yy(end) yy(1)]; + end +end +end diff --git a/analysis/placeFields/bz_findPlaceFieldsTemplate.m b/analysis/placeFields/bz_findPlaceFieldsTemplate.m new file mode 100644 index 00000000..79b495d7 --- /dev/null +++ b/analysis/placeFields/bz_findPlaceFieldsTemplate.m @@ -0,0 +1,151 @@ +function [placeFieldTemplate] = bz_findPlaceFieldsTemplate(varargin) +% [placeFieldTemplate] = bz_findPlaceFields1D(firingMaps) +% Creates a template with all cells acording to the arragement of their +% place fields in a 1D maze. +% +% INPUTS +% +% placeFieldStats cellinfo structure from bz_findPlaceFields1D with the +% +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% +% 'placeFieldStats' cellinfo structure from bz_findPlaceFields1D with +% the following fields +% .UID unit ids +% .sessionName name of session +% .params parameters +% .mapStats Statistics of the Firing Map +% .x Bin position of maximum firing rate (center +% of place field) +% 'firingMaps' cellinfo struct with the following fields +% .rateMaps gaussian filtered rates +% .rateMaps_unsmooth raw rate data +% .rateMaps_box box filtered rates +% .countMaps raw spike count data +% .occuMaps position occupancy data +% ouput structure from bz_firingMapAvg. If not provided, +% it loads it from 'basepath' or current folder +% 'UIDs' A Mx1 boolean matrix with 1s for units to be considered +% and 0s for units to be discarded.(M: number of units) +% 'basepath' full path where session is located (default pwd) +% e.g. /mnt/Data/buddy140_060813_reo/buddy140_060813_reo +% 'saveMat' Saves file, logical (default: true) +% ========================================================================= +% +% OUTPUTS +% +% placeFieldTemplate structure with the following fields: +% +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% +% .Peak 1 x (# conditions) cell array. +% Within each cell there is a (# units) x 3 matrix. +% First column has the bin corresponding to the firing +% map peak for each unit (NaN for units without place +% field). Second column has the unit ID. Third column +% has the position of the unit in the UID vector. The +% third dimension contains this similar matrix for the +% other conditions. +% .CenterofMass 1 x (# conditions) cell array. +% Within each cell there is a (# units) x 3 matrix. +% First column has the center of mass of the firing +% rate map for each unit (NaN for units without place +% field). Second column has the unit ID. Third column +% has the position of the unit in the UID vector. The +% third dimension contains this similar matrix for the +% other conditions. +% ========================================================================= +% +% Andrea Navas-Olive, Antonio FR, 10/2019 + +% Parse inputs +p=inputParser; +addParameter(p,'basepath',pwd,@isstr); +addParameter(p,'firingMapsAvg',{},@isstruct); +addParameter(p,'placeFieldStats',{},@isstruct); +addParameter(p,'saveMat', true, @islogical); +addParameter(p,'UIDs',[],@islogical); + +parse(p,varargin{:}); +basepath = p.Results.basepath; +firingMaps = p.Results.firingMapsAvg; +placeFieldStats = p.Results.placeFieldStats; +UIDs = p.Results.UIDs; + +% Get session info +basename = bz_BasenameFromBasepath(basepath); +load([basepath filesep basename '.sessionInfo.mat']); +% Default firingMapsAvg +if isempty(firingMaps) + firingMaps = load([basepath filesep basename '.firingMapsAvg.cellinfo.mat']); + firingMaps = firingMaps.firingMaps; +end +% Default firingMapsAvg +if isempty(placeFieldStats) + placeFieldStats = load([basepath filesep basename '.placeFields.cellinfo.mat']); + placeFieldStats = placeFieldStats.placeFieldStats; +end +saveMat = p.Results.saveMat; + +%% Find place field template + +% Number of Units +nUnits = length(placeFieldStats.mapStats); +% Number of conditions +nCond = length(placeFieldStats.mapStats{1}); + +% Default UIDs +if isempty(UIDs) + UIDs = ones(nUnits); +end + +% Template by center of biggest place field +for c = 1:nCond + for unit = 1:nUnits + if UIDs(unit) + templatePF{c}(unit,1) = placeFieldStats.mapStats{unit}{c}.x(1); + templatePF{c}(unit,2) = placeFieldStats.UID(unit); + templatePF{c}(unit,3) = unit; + end + end +end +% Sort units by place field, condition-independent +for c = 1:nCond + templatePF{c} = sortrows(templatePF{c}); + templatePF{c}(isnan(templatePF{c}),:) = []; +end + +% Template by center of Center of Mass +for c = 1:nCond + for unit = 1:nUnits + if UIDs(unit) + x = 1:length( firingMaps.rateMaps{1}{1}); + rate = firingMaps.rateMaps{unit}{c}; + COM = sum(rate.*x) / sum(rate); + templateCOM{c}(unit,1) = COM; + templateCOM{c}(unit,2) = placeFieldStats.UID(unit); + templateCOM{c}(unit,3) = unit; + end + end +end +% Sort units by Center of Mass, condition-independent +for c = 1:nCond + templateCOM{c} = sortrows(templateCOM{c}); +end + +% Structure +placeFieldTemplate.Peak = templatePF; +placeFieldTemplate.CenterofMass = templateCOM; +% Save +if saveMat + save([basepath filesep basename '.placeFieldTemplate.mat'], 'placeFieldTemplate'); +end + + + +end + diff --git a/analysis/spikes/bz_firingMap1D.m b/analysis/placeFields/bz_firingMap1D.m old mode 100755 new mode 100644 similarity index 97% rename from analysis/spikes/bz_firingMap1D.m rename to analysis/placeFields/bz_firingMap1D.m index f80fbfcb..5a7031f8 --- a/analysis/spikes/bz_firingMap1D.m +++ b/analysis/placeFields/bz_firingMap1D.m @@ -1,130 +1,130 @@ -function [firingMaps] = bz_firingMap1D(varargin) -% USAGE -% [firingMaps] = bz_firingMap1D(spikes,behavior,lfp,tau) -% -% INPUTS -% -% spikes - buzcode format .cellinfo. struct with the following fields -% .times -% -% behavior - buzcode format behavior struct -% -% -% tau - desired smoothing window -% -% saveMat - logical (default: false) that saves firingMaps file -% -% -% OUTPUT -% -% firingMaps - cellinfo struct with the following fields -% .rateMaps gaussian filtered rates -% .rateMaps_unsmooth raw rate data -% .rateMaps_box box filtered rates -% .countMaps raw spike count data -% .occuMaps position occupancy data -% -% written by david tingley, 2017 - - -% parse inputs -p=inputParser; -addRequired(p,'spikes',@isstruct); -addRequired(p,'behavior',@isstruct); -addRequired(p,'tau',@isnumeric); -addParameter(p,'saveMat',false,@islogical); -parse(p,varargin{:}); - -spikes = p.Results.spikes; -behavior = p.Results.behavior; -tau = p.Results.tau; -saveMat = p.Results.saveMat; - -% start processing -for tt =1:length(unique(behavior.events.trialConditions)) - trials = find(behavior.events.trialConditions==tt); - countMap{tt} = zeros(length(spikes.times),length(trials),length(behavior.events.map{tt}.x)); - occuMap{tt} = zeros(length(trials),length(behavior.events.map{tt}.x)); - if ~isempty(behavior.events.map{tt}) - - for i =1:length(spikes.times) - for t=1:length(trials) - if ~isempty(spikes.times{i}) - start = behavior.events.trialIntervals(trials(t),1); - stop = behavior.events.trialIntervals(trials(t),2); - f = find(spikes.times{i}>start); - ff = find(spikes.times{i}start); + ff = find(spikes.times{i} optional list of property-value pairs (see table below) +% +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% 'smooth' smoothing size in bins (0 = no smoothing, default = 2) +% 'nBins' number of bins (default = 50) +% 'speedThresh' speed threshold to compute firing rate +% 'minTime' minimum time spent in each bin (in s, default = 0) +% 'mode' interpolate' to interpolate missing points (< minTime), +% or 'discard' to discard them (default) +% 'maxDistance' maximal distance for interpolation (default = 5) +% 'maxGap' z values recorded during time gaps between successive (x,y) +% samples exceeding this threshold (e.g. undetects) will not +% be interpolated; also, such long gaps in (x,y) sampling +% will be clipped to 'maxGap' to compute the occupancy map +% (default = 0.100 s) +% 'orderKalmanVel' order of Kalman Velocity Filter (default 2) +% saveMat - logical (default: false) that saves firingMaps file +% CellInspector - logical (default: false) that creates an otuput +% compatible with CellInspector + +% +% +% OUTPUT +% +% firingMaps - cellinfo struct with the following fields +% .rateMaps gaussian filtered rates +% .rateMaps_unsmooth raw rate data +% .rateMaps_box box filtered rates +% .countMaps raw spike count data +% .occuMaps position occupancy data +% +% Antonio FR, 10/2019 + +%% parse inputs +p=inputParser; +addParameter(p,'smooth',2,@isnumeric); +addParameter(p,'speedThresh',0.1,@isnumeric); +addParameter(p,'nBins',50,@isnumeric); +addParameter(p,'maxGap',0.1,@isnumeric); +addParameter(p,'minTime',0,@isnumeric); +addParameter(p,'saveMat',false,@islogical); +addParameter(p,'CellInspector',false,@islogical); +addParameter(p,'mode','discard',@isstr); +addParameter(p,'maxDistance',5,@isnumeric); +addParameter(p,'orderKalmanVel',2,@isnumeric); + +parse(p,varargin{:}); +smooth = p.Results.smooth; +speedThresh = p.Results.speedThresh; +nBins = p.Results.nBins; +maxGap = p.Results.maxGap; +minTime = p.Results.minTime; +saveMat = p.Results.saveMat; +CellInspector = p.Results.CellInspector; +mode = p.Results.mode; +maxDistance = p.Results.maxDistance; +order = p.Results.orderKalmanVel; + +% number of conditions + if iscell(positions) + conditions = length(positions); + elseif isvector(positions) + conditions = 1; + end + %%% TODO: conditions label + +%% Calculate + +% Erase positions below speed threshold +for iCond = 1:size(positions,2) + % Compute speed + post = positions{iCond}(:,1); + % - 1D + if size(positions{iCond},2)==2 + posx = positions{iCond}(:,2); + [~,~,~,vx,vy,~,~] = KalmanVel(posx,posx*0,post,order); + elseif size(positions{iCond},2)==3 + posx = positions{iCond}(:,2); + posy = positions{iCond}(:,3); + [~,~,~,vx,vy,~,~] = KalmanVel(posx,posy,post,order); + else + warning('This is not a linear nor a 2D space!'); + end + % Absolute speed + v = sqrt(vx.^2+vy.^2); + + % Compute timestamps where speed is under threshold + positions{iCond}(v 0 - error('Do Not Approach the Infitnite'); -end - -% if sum(sum(isnan(Pr))) > 0 -% error('What is ''not a nubmer''?'); -% end +function [Pr, prMax] = placeBayes(Cr, rateMap, binLength) +%function out = placeBayes1(Cr, rateMap, binLength) +% Inputs: +% Cr = [nTemporalBin X nCell] matrix of binned firing rates +% rateMap = [nSpatialBin X nCell] firing rate 'template' +% binLength = scalar of the duration of the bins in 'Cr' +% Outputs: +% Pr = [nTemporalBin X nSpatialBins] matrix of posterior probabilities +% prMax = the spatial bin with higher spatial probabilities for each +% temporalBin in Cr (note, ties go to the lower numbered bins as +% consistent with the behavior of the second output of built in function +% 'max') + + +Cr = Cr*binLength; +rateMap = rateMap'; +term2 = exp((-1)*binLength*sum(rateMap')); +mp = 1/size(rateMap, 2); +Pr = []; + +c = repmat(Cr, [1, 1, size(rateMap, 1)]); +b = repmat(rateMap', [1, 1, size(c, 1)]); +b = shiftdim(b, 2); + + +u = mp*prod(b.^c, 2); +u = squeeze(u); +Pr = u.*repmat(term2, size(u, 1), 1); +Pr = Pr./repmat(sum(Pr, 2), 1, size(Pr, 2)); + +[~, m] = max(Pr'); +prMax = m'; + +if sum(sum(isinf(Pr))) > 0 + error('Do Not Approach the Infitnite'); +end + +% if sum(sum(isnan(Pr))) > 0 +% error('What is ''not a nubmer''?'); +% end diff --git a/analysis/lfp_spikes/PhaseModulation/CircularMean.m_deprecate b/analysis/spikeLFPcoupling/CircularMean.m_deprecate old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp_spikes/PhaseModulation/CircularMean.m_deprecate rename to analysis/spikeLFPcoupling/CircularMean.m_deprecate diff --git a/analysis/lfp_spikes/PhaseModulation/PhaseModulation.m b/analysis/spikeLFPcoupling/PhaseModulation.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp_spikes/PhaseModulation/PhaseModulation.m rename to analysis/spikeLFPcoupling/PhaseModulation.m diff --git a/analysis/lfp_spikes/bz_GenSpikeLFPCoupling.m b/analysis/spikeLFPcoupling/bz_GenSpikeLFPCoupling.m similarity index 100% rename from analysis/lfp_spikes/bz_GenSpikeLFPCoupling.m rename to analysis/spikeLFPcoupling/bz_GenSpikeLFPCoupling.m diff --git a/analysis/lfp_spikes/PhaseModulation/bz_PhaseModulation.m b/analysis/spikeLFPcoupling/bz_PhaseModulation.m old mode 100755 new mode 100644 similarity index 65% rename from analysis/lfp_spikes/PhaseModulation/bz_PhaseModulation.m rename to analysis/spikeLFPcoupling/bz_PhaseModulation.m index 5fbdc136..9d7263d2 --- a/analysis/lfp_spikes/PhaseModulation/bz_PhaseModulation.m +++ b/analysis/spikeLFPcoupling/bz_PhaseModulation.m @@ -1,7 +1,7 @@ - function [PhaseLockingData] = bz_PhaseModulation(varargin) +function [PhaseLockingData] = bz_PhaseModulation(varargin) % USAGE %[PhaseLockingData] = bz_PhaseModulation(varargin) -% +% % INPUTS % spikes -spike time cellinfo struct % @@ -9,16 +9,16 @@ % % passband -frequency range for phase modulation [lowHz highHz] form % -% intervals -(optional) may specify timespans over which to calculate +% intervals -(optional) may specify timespans over which to calculate % phase modulation. Formats accepted: tstoolbox intervalSet % or a 2column matrix of [starts stops] in seconds % % samplingRate -specifies lfp sampling frequency default=1250 % -% method -method selection for how to generate phase, +% method -method selection for how to generate phase, % possibilties are: 'hilbert' (default) or 'wavelet' % -% powerThresh -integer power threshold to use as cut off, +% powerThresh -integer power threshold to use as cut off, % measured in standard deviations (default = 2) % % plotting -logical if you want to plot, false if not, default=true @@ -41,7 +41,7 @@ % phasestats.p p-value for Rayleigh test % phasestats.r mean resultant length % -% +% % Calculates distribution of spikes over various phases from a specified % cycle of an lfp vector. Phase 0 means peak of lfp wave. % @@ -69,7 +69,7 @@ intervals = p.Results.intervals; % interval(s) over which to calculate samplingRate = p.Results.samplingRate; % sampling rate of continuous signal (LFP) -method = p.Results.method; +method = p.Results.method; plotting = p.Results.plotting; numBins = p.Results.numBins; powerThresh = p.Results.powerThresh; @@ -78,6 +78,7 @@ %% Get phase for every time point in LFP switch lower(method) case ('hilbert') + [b a] = butter(3,[passband(1)/(samplingRate/2) passband(2)/(samplingRate/2)],'bandpass'); % order 3 % [b a] = cheby2(4,20,passband/(samplingRate/2)); filt = FiltFiltM(b,a,double(lfp.data(:,1))); @@ -86,52 +87,77 @@ lfpphase = mod(angle(hilb),2*pi); clear fil case ('wavelet')% Use Wavelet transform to calulate the signal phases -% nvoice = 12; -% freqlist= 2.^(log2(passband(1)):1/nvoice:log2(passband(2))); -% error('awt_freqlist, where did this come from?') -% wt = awt_freqlist(double(lfp.data(:,1)), samplingRate, freqlist); -% amp = (real(wt).^2 + imag(wt).^2).^.5; -% phase = atan2(imag(wt),real(wt)); -% [~,mIdx] = max(amp'); %get index with max power for each timepiont -% for i = 1:size(wt,1) -% lfpphase(i) = phase(i,mIdx(i)); -% end -% lfpphase = mod(lfpphase,2*pi); + % nvoice = 12; + % freqlist= 2.^(log2(passband(1)):1/nvoice:log2(passband(2))); + % error('awt_freqlist, where did this come from?') + % wt = awt_freqlist(double(lfp.data(:,1)), samplingRate, freqlist); + % amp = (real(wt).^2 + imag(wt).^2).^.5; + % phase = atan2(imag(wt),real(wt)); + % [~,mIdx] = max(amp'); %get index with max power for each timepiont + % for i = 1:size(wt,1) + % lfpphase(i) = phase(i,mIdx(i)); + % end + % lfpphase = mod(lfpphase,2*pi); [wave,f,t,coh,wphases,raw,coi,scale,priod,scalef]=getWavelet(double(lfp.data(:,1)),samplingRate,passband(1),passband(2),8,0); [~,mIdx]=max(wave);%get index max power for each timepiont pIdx=mIdx'+[0;size(f,2).*cumsum(ones(size(t,1)-1,1))];%converting to indices that will pick off single maxamp index from each of the freq-based phases at eacht timepoint - lfpphases=wphases(pIdx);%get phase of max amplitude wave at each timepoint - lfpphases = mod(lfpphases,2*pi);%covert to 0-2pi rather than -pi:pi -% % case ('peaks') + lfpphase=wphases(pIdx);%get phase of max amplitude wave at each timepoint + lfpphase = mod(lfpphase,2*pi);%covert to 0-2pi rather than -pi:pi + power = rms(abs(wave))'; + % % case ('peaks') % not yet coded % filter, smooth, diff = 0, diffdiff = negative end %% update intervals to remove sub-threshold power periods -disp('finding intervals below power threshold...') -thresh = mean(power) + std(power)*powerThresh; -minWidth = (samplingRate./passband(2)) * 2; % set the minimum width to two cycles - -below=find(power0; - ends=find(diff(below)~=1); - ends(end+1)=length(below); - ends=sort(ends); - lengths=diff(ends); - stops=below(ends)./samplingRate; - starts=lengths./samplingRate; - starts = [1; starts]; - below_thresh(:,2)=stops; - below_thresh(:,1)=stops-starts; -else - below_thresh=[]; +if (lower(method) == 'hilbert') + disp('finding intervals below power threshold...') + thresh = mean(power) + std(power)*powerThresh; + minWidth = (samplingRate./passband(2)) * 2; % set the minimum width to two cycles + + below=find(power0; + ends=find(diff(below)~=1); + ends(end+1)=length(below); + ends=sort(ends); + lengths=diff(ends); + stops=below(ends)./samplingRate; + starts=lengths./samplingRate; + starts = [1; starts]; + below_thresh(:,2)=stops; + below_thresh(:,1)=stops-starts; + else + below_thresh=[]; + end + % now merge interval sets from input and power threshold + intervals = SubtractIntervals(intervals,below_thresh); % subtract out low power intervals +elseif (lower(method) == 'wavelet') + disp('finding intervals below power threshold...') + thresh = mean(power) + std(power)*powerThresh; + minWidth = (samplingRate./passband(2)) * 2; % set the minimum width to two cycles + + below=find(power0; + ends=find(diff(below)~=1); + ends(end+1)=length(below); + ends=sort(ends); + lengths=diff(ends); + stops=below(ends)./samplingRate; + starts=lengths./samplingRate; + starts = [1; starts]; + below_thresh(:,2)=stops; + below_thresh(:,1)=stops-starts; + else + below_thresh=[]; + end + % now merge interval sets from input and power threshold + intervals = SubtractIntervals(intervals,below_thresh); % subtract out low power intervals end - -% now merge interval sets from input and power threshold -intervals = SubtractIntervals(intervals,below_thresh); % subtract out low power intervals - +minWidth = (samplingRate./passband(2)) * 2; intervals = intervals(diff(intervals')>minWidth./samplingRate,:); % only keep min width epochs @@ -144,8 +170,8 @@ bools = InIntervals(spikes.times{a},intervals); s =spikes.times{a}(bools); -% s = spikes{a}; - if isempty(s) + % s = spikes{a}; + if isempty(s) phasedistros(:,a) = zeros(numBins,1); phasestats.m(a) = nan; phasestats.r(a) = nan; @@ -156,6 +182,7 @@ else spkphases{a} = lfpphase(ceil(s*samplingRate)); + % cum_spkphases = vertcat(cum_spkphases, spkphases{a}); @@ -166,21 +193,21 @@ phasestats.k(a) = ps.k; phasestats.p(a) = ps.p; phasestats.mode(a) = ps.mode; - - %% plotting + + %% plotting if plotting if ~exist('PhaseModulationFig','dir') mkdir('PhaseModulationFig'); end h(end+1) = figure; - hax = subplot(1,2,1); + hax = subplot(1,2,1); rose(spkphases{a}) title(hax,['Cell #' num2str(a) '. Rayleigh p = ' num2str(phasestats.p(a)) '.']) - - hax = subplot(1,2,2); + + hax = subplot(1,2,2); bar(phasebins*180/pi,phasedistros(:,a)) xlim([0 360]) - set(hax,'XTick',[0 90 180 270 360]) + set(hax,'XTick',[0 90 180 270 360]) hold on; plot([0:360],cos(pi/180*[0:360])*0.05*max(phasedistros(:,a))+0.95*max(phasedistros(:,a)),'color',[.7 .7 .7]) set(h(end),'name',['PhaseModPlotsForCell' num2str(a)]); @@ -193,17 +220,17 @@ % if length(cum_spkphases) > 10 % [cpd,phasebins,cps]=CircularDistribution(cum_spkphases,'nBins',180); % cRp = cps.p; -% +% % if plotting % h(end+1) = figure; -% hax = subplot(1,2,1); +% hax = subplot(1,2,1); % rose(cum_spkphases) % title(hax,['All Spikes/Cells Accumulated. Rayleigh p = ' num2str(cps.p) '.']) -% -% hax = subplot(1,2,2); +% +% hax = subplot(1,2,2); % bar(phasebins*180/pi,cpd) % xlim([0 360]) -% set(hax,'XTick',[0 90 180 270 360]) +% set(hax,'XTick',[0 90 180 270 360]) % hold on; % plot([0:360],cos(pi/180*[0:360])*0.05*max(cpd)+0.95*max(cpd),'color',[.7 .7 .7]) % set(h(end),'name',['PhaseModPlotsForAllCells']); @@ -216,14 +243,14 @@ passband,powerThresh,channels); PhaseLockingData = v2struct(phasedistros,phasebins,... - phasestats,spkphases,... - detectorName, detectorParams); + phasestats,spkphases,... + detectorName, detectorParams); PhaseLockingData.region = spikes.region; PhaseLockingData.UID = spikes.UID; PhaseLockingData.sessionName = spikes.sessionName; if saveMat - save([lfp.Filename(1:end-4) '.PhaseLockingData.cellinfo.mat'],'PhaseLockingData'); + save([lfp.Filename(1:end-4) '.PhaseLockingData.cellinfo.mat'],'PhaseLockingData'); end end diff --git a/analysis/lfp_spikes/bz_PowerPhaseRatemap.m b/analysis/spikeLFPcoupling/bz_PowerPhaseRatemap.m similarity index 100% rename from analysis/lfp_spikes/bz_PowerPhaseRatemap.m rename to analysis/spikeLFPcoupling/bz_PowerPhaseRatemap.m diff --git a/analysis/spikes/assemblies/blank.file b/analysis/spikes/assemblies/blank.file deleted file mode 100755 index e69de29b..00000000 diff --git a/analysis/spikes/binnedMatrix/blank.file b/analysis/spikes/binnedMatrix/blank.file deleted file mode 100755 index e69de29b..00000000 diff --git a/analysis/spikes/functionalConnectionIdentification/blank.file b/analysis/spikes/functionalConnectionIdentification/blank.file deleted file mode 100755 index e69de29b..00000000 diff --git a/analysis/spikes/placeFields/blank.file b/analysis/spikes/placeFields/blank.file deleted file mode 100755 index e69de29b..00000000 diff --git a/analysis/spikes/Contents.m b/analysis/spikes_general/Contents.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/Contents.m rename to analysis/spikes_general/Contents.m diff --git a/analysis/spikes/GetSNR.m b/analysis/spikes_general/GetSNR.m old mode 100755 new mode 100644 similarity index 97% rename from analysis/spikes/GetSNR.m rename to analysis/spikes_general/GetSNR.m index b3a82661..70d0c2df --- a/analysis/spikes/GetSNR.m +++ b/analysis/spikes_general/GetSNR.m @@ -1,57 +1,57 @@ -% it would be nice to write something like this for the new file format....? - -% function SNR = GetSNR(iClust,varargin) - -% % GetSNR(iClust) -% % -% % INPUTS -% % iClust -% % -% % OUTPUTS -% % -% % SNR - signal to noise ratio of each channel for the cluster -% % Calculated for each channel as the value of the peak (or valley if -% % the cell is inverted) minus the value of the first waveform sample -% % divided by the standard deviation of the first sample. Calculation -% % based on a poster ncst saw at SFN 2003. -% % -% % If no outputs are requested, displays the maximum SNR of all channels -% % -% % TO USE WITH MCLUST, put this in the MClust/ClusterOptions folder - -% % ADR 2003 -% % -% % Status: PROMOTED (Release version) -% % See documentation for copyright (owned by original authors) and warranties (none!). -% % This code released as part of MClust 3.0. -% % Version control M3.0. -% % Extensively modified by ADR to accomodate new ClusterOptions methodology - -% global MClust_Clusters MClust_FeatureData MClust_TTdn MClust_TTfn MClust_ChannelValidity MClust_TTData MClust_FeatureTimestamps - -% ClustTT = []; -% NoiseTT = []; - -% Extract_varargin; - -% if isempty(ClustTT) || isempty(NoiseTT) - -% [f MClust_Clusters{iClust}] = FindInCluster(MClust_Clusters{iClust}, MClust_FeatureData); - -% ClustTT = ExtractCluster(MClust_TTData,f); -% NoiseTT = ExtractCluster(MClust_TTData,1:length(MClust_FeatureTimestamps)); -% end - -% mWV = AverageWaveform(ClustTT); -% [Noise_mWV Noise_sWV] = AverageWaveform(NoiseTT); -% [V maxPeak] = max(abs(mWV)'); - -% Peak_vals = mWV(sub2ind(size(mWV),1:4,maxPeak))'; -% Peak_value = abs(Peak_vals) - sign(Peak_vals).*Noise_mWV(:,1); -% Noise_std = Noise_sWV(:,1); - -% SNR = Peak_value./Noise_std; - -% if nargout == 0 -% disp(sprintf(' Cluster %2.0f Signal-To-Noise ratio = %5.1f',iClust,max(SNR))); +% it would be nice to write something like this for the new file format....? + +% function SNR = GetSNR(iClust,varargin) + +% % GetSNR(iClust) +% % +% % INPUTS +% % iClust +% % +% % OUTPUTS +% % +% % SNR - signal to noise ratio of each channel for the cluster +% % Calculated for each channel as the value of the peak (or valley if +% % the cell is inverted) minus the value of the first waveform sample +% % divided by the standard deviation of the first sample. Calculation +% % based on a poster ncst saw at SFN 2003. +% % +% % If no outputs are requested, displays the maximum SNR of all channels +% % +% % TO USE WITH MCLUST, put this in the MClust/ClusterOptions folder + +% % ADR 2003 +% % +% % Status: PROMOTED (Release version) +% % See documentation for copyright (owned by original authors) and warranties (none!). +% % This code released as part of MClust 3.0. +% % Version control M3.0. +% % Extensively modified by ADR to accomodate new ClusterOptions methodology + +% global MClust_Clusters MClust_FeatureData MClust_TTdn MClust_TTfn MClust_ChannelValidity MClust_TTData MClust_FeatureTimestamps + +% ClustTT = []; +% NoiseTT = []; + +% Extract_varargin; + +% if isempty(ClustTT) || isempty(NoiseTT) + +% [f MClust_Clusters{iClust}] = FindInCluster(MClust_Clusters{iClust}, MClust_FeatureData); + +% ClustTT = ExtractCluster(MClust_TTData,f); +% NoiseTT = ExtractCluster(MClust_TTData,1:length(MClust_FeatureTimestamps)); +% end + +% mWV = AverageWaveform(ClustTT); +% [Noise_mWV Noise_sWV] = AverageWaveform(NoiseTT); +% [V maxPeak] = max(abs(mWV)'); + +% Peak_vals = mWV(sub2ind(size(mWV),1:4,maxPeak))'; +% Peak_value = abs(Peak_vals) - sign(Peak_vals).*Noise_mWV(:,1); +% Noise_std = Noise_sWV(:,1); + +% SNR = Peak_value./Noise_std; + +% if nargout == 0 +% disp(sprintf(' Cluster %2.0f Signal-To-Noise ratio = %5.1f',iClust,max(SNR))); % end \ No newline at end of file diff --git a/analysis/spikes/IsolationDistance.m b/analysis/spikes_general/IsolationDistance.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/IsolationDistance.m rename to analysis/spikes_general/IsolationDistance.m diff --git a/analysis/spikes/L_Ratio.m b/analysis/spikes_general/L_Ratio.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/L_Ratio.m rename to analysis/spikes_general/L_Ratio.m diff --git a/analysis/spikes/burst_cls_kmeans.m b/analysis/spikes_general/burst_cls_kmeans.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/burst_cls_kmeans.m rename to analysis/spikes_general/burst_cls_kmeans.m diff --git a/detectors/detectEvents/bz_FindPopBursts.m b/analysis/spikes_general/bz_FindPopBursts.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/bz_FindPopBursts.m rename to analysis/spikes_general/bz_FindPopBursts.m diff --git a/analysis/spikes/bz_ISIStats.m b/analysis/spikes_general/bz_ISIStats.m similarity index 100% rename from analysis/spikes/bz_ISIStats.m rename to analysis/spikes_general/bz_ISIStats.m diff --git a/analysis/spikes/bz_JitterSpiketimes.m b/analysis/spikes_general/bz_JitterSpiketimes.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/bz_JitterSpiketimes.m rename to analysis/spikes_general/bz_JitterSpiketimes.m diff --git a/analysis/spikes/bz_SpktToSpkmat.m b/analysis/spikes_general/bz_SpktToSpkmat.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/bz_SpktToSpkmat.m rename to analysis/spikes_general/bz_SpktToSpkmat.m diff --git a/analysis/spikes/bz_compareReplay.m b/analysis/spikes_general/bz_compareReplay.m similarity index 100% rename from analysis/spikes/bz_compareReplay.m rename to analysis/spikes_general/bz_compareReplay.m diff --git a/analysis/spikes/bz_olypherInfo.m b/analysis/spikes_general/bz_olypherInfo.m old mode 100755 new mode 100644 similarity index 96% rename from analysis/spikes/bz_olypherInfo.m rename to analysis/spikes_general/bz_olypherInfo.m index 43829f8f..1d78cf23 --- a/analysis/spikes/bz_olypherInfo.m +++ b/analysis/spikes_general/bz_olypherInfo.m @@ -1,87 +1,87 @@ -function [track_info,pos_info_val] = bz_olypherInfo(data,round_to,smoothing) -% USAGE -% [track_info,pos_info_val] = bz_olypherInfo(data,round_to,smoothing) -% -% INPUTS -% data - matrix (M x N x D) where M is the number of cells to analyze, -% N is the number of trials for each cell, and D is the number -% of time bins -% round_to - integer value that data is discritized to. A value of -% 2 means all data will be rounded to nearest 2 (i.e. -% 2,4,8...) -% smoothing - 0 no smoothing, else smooth with N bins -% OUTPUTS -% track_info - matrix (M x D-2) of information scores across all -% trials or behavior windows (N) -% pos_info_val - matrix (M x N x D) of all information values -% that are calculated -% -% this function calculates the information carried in the firing rate of single -% neurons per spatial/temporal bin -% -% Written by David Tingley -% UCSD Cognitive Neuroscience -% 1/15/12 - -%% TODO -% - convert to varargin with input parser -% - add 'exclude' input to remove 0's from info calculation -% - - -M = size(data,1); -N = size(data,2); -D = size(data,3); - -if M == 0 - M = 1 -end -if N == 0 - N = 1 -end -if D == 0 - D = 1 -end -pos_info_val = zeros(M,N,D); -a = N*D; - -%% Rounding -if smoothing ~= 0 - for i = 1 : M - for k = 1:N - data(i,k,:) = smooth(squeeze(data(i,k,:)),smoothing).*smoothing; - end - end -end -data = round(data./round_to)*round_to; - -%% Info Analysis - -for i = 1 : M - for x = 1 : D - for k = 1:N - - q = data(i,k,x); - - pKx = ((length(find(data(i,:,x) == q)))/N); - - pK = ((length(find(data(i,:,:) == q)))/a); - - if pK == 0 || pKx == 0 || pKx < pK - pos_info_val(i,k,x) = pos_info_val(i,k,x); - else - pos_info_val(i,k,x) = pos_info_val(i,k,x) + (pKx*log2(pKx/pK)); - end - - end - end - -end - -for i = 1:M -track_info(i,:) = sum(pos_info_val(i,:,2:end-1),2); -end - - - - - +function [track_info,pos_info_val] = bz_olypherInfo(data,round_to,smoothing) +% USAGE +% [track_info,pos_info_val] = bz_olypherInfo(data,round_to,smoothing) +% +% INPUTS +% data - matrix (M x N x D) where M is the number of cells to analyze, +% N is the number of trials for each cell, and D is the number +% of time bins +% round_to - integer value that data is discritized to. A value of +% 2 means all data will be rounded to nearest 2 (i.e. +% 2,4,8...) +% smoothing - 0 no smoothing, else smooth with N bins +% OUTPUTS +% track_info - matrix (M x D-2) of information scores across all +% trials or behavior windows (N) +% pos_info_val - matrix (M x N x D) of all information values +% that are calculated +% +% this function calculates the information carried in the firing rate of single +% neurons per spatial/temporal bin +% +% Written by David Tingley +% UCSD Cognitive Neuroscience +% 1/15/12 + +%% TODO +% - convert to varargin with input parser +% - add 'exclude' input to remove 0's from info calculation +% - + +M = size(data,1); +N = size(data,2); +D = size(data,3); + +if M == 0 + M = 1 +end +if N == 0 + N = 1 +end +if D == 0 + D = 1 +end +pos_info_val = zeros(M,N,D); +a = N*D; + +%% Rounding +if smoothing ~= 0 + for i = 1 : M + for k = 1:N + data(i,k,:) = smooth(squeeze(data(i,k,:)),smoothing).*smoothing; + end + end +end +data = round(data./round_to)*round_to; + +%% Info Analysis + +for i = 1 : M + for x = 1 : D + for k = 1:N + + q = data(i,k,x); + + pKx = ((length(find(data(i,:,x) == q)))/N); + + pK = ((length(find(data(i,:,:) == q)))/a); + + if pK == 0 || pKx == 0 || pKx < pK + pos_info_val(i,k,x) = pos_info_val(i,k,x); + else + pos_info_val(i,k,x) = pos_info_val(i,k,x) + (pKx*log2(pKx/pK)); + end + + end + end + +end + +for i = 1:M +track_info(i,:) = sum(pos_info_val(i,:,2:end-1),2); +end + + + + + diff --git a/analysis/spikes/bz_phaseMap1D.m b/analysis/spikes_general/bz_phaseMap1D.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/bz_phaseMap1D.m rename to analysis/spikes_general/bz_phaseMap1D.m diff --git a/analysis/spikes_general/calc_PSTH.m b/analysis/spikes_general/calc_PSTH.m new file mode 100644 index 00000000..8ee6f51f --- /dev/null +++ b/analysis/spikes_general/calc_PSTH.m @@ -0,0 +1,121 @@ +function PSTH = calc_PSTH(event,spikes,varargin) +% This is a generalized way for creating a PSTH for units for various events +% +% INPUTS +% event : event times formatted according to the Cell Explorer's convention +% spikes : spikes formatted according to the Cell Explorer's convention +% +% OUTPUT +% psth +% +% Dependencies: CCG + +% By Peter Petersen +% petersen.peter@gmail.com +% Last edited 11-08-2019 + +p = inputParser; + +addParameter(p,'binCount',200,@isnumeric); % how many bins (for half the window) +addParameter(p,'alignment','onset',@ischar); % alignment of time ['onset','center','peaks','offset'] +addParameter(p,'binDistribution',[0.25,0.5,0.25],@isnumeric); % How the bins should be distributed around the events, pre, during, post. Must sum to 1 +addParameter(p,'duration',0,@isnumeric); % duration of PSTH (for half the window - used in CCG) [in seconds] +addParameter(p,'smoothing',0,@isnumeric); % any gaussian smoothing to apply? units of bins. +addParameter(p,'percentile',99,@isnumeric); % if events does not have the same length, the event duration can be determined from percentile of the distribution of events +addParameter(p,'eventName','',@ischar); +addParameter(p,'plots',true,@islogical); + +parse(p,varargin{:}) + +binCount = p.Results.binCount; +alignment = p.Results.alignment; +binDistribution = p.Results.binDistribution; +duration = p.Results.duration; +smoothing = p.Results.smoothing; +percentile = p.Results.percentile; +eventName = p.Results.eventName; +plots = p.Results.plots; + +% If no duration is given, an optimal duration is determined +if duration == 0 + durations = diff(event.timestamps'); + stim_duration = prctile(sort(durations),percentile); + duration = min(max(round(stim_duration*1000),50)/1000,30); +end + +binSize = max(round(duration/binCount*1000),1)/1000; % minimum binsize is 0.5ms. + +% Determine event alignment +switch alignment + case 'onset' + event_times = event.timestamps(:,1); + padding = binDistribution(1)/binDistribution(2)*stim_duration; + binsToKeep = ceil((padding+duration/2)/binSize):(duration+padding)*2/binSize; + case 'center' + event_times = mean(event.timestamps); + padding = 0; + binsToKeep = 1:duration*2/binSize; + case 'offset' + event_times = event.timestamps(:,2); + padding = binDistribution(3)/binDistribution(2)*stim_duration; + binsToKeep = 1:(duration+padding)*2/binSize-ceil((padding+duration/2)/binSize); + case 'peaks' + event_times = event.peaks; + padding = 0; + binsToKeep = 1:duration*2/binSize; +end + +disp([' ', num2str(length(event_times)), ' events, duration set to: ', num2str(duration), ' sec, aligned to ', alignment]) + +% Determining the bins interval for metrics +binsPre = 1:floor(binDistribution(1)*length(binsToKeep)); +binsEvents = floor(binDistribution(1)*length(binsToKeep))+1:floor((binDistribution(1)+binDistribution(2))*length(binsToKeep)); +binsPost = floor((binDistribution(1)+binDistribution(2))*length(binsToKeep))+1:length(binsToKeep); + +% Calculating PSTH +spike_times = spikes.spindices(:,1); +spike_cluster_index = spikes.spindices(:,2); +[spike_times,index] = sort([spike_times;event_times(:)]); +spike_cluster_index = [spike_cluster_index;zeros(length(event_times),1)]; +spike_cluster_index = spike_cluster_index(index); +[~, ~, spike_cluster_index] = unique(spike_cluster_index); +[ccg,time] = CCG(spike_times,spike_cluster_index,'binSize',binSize,'duration',(duration+padding)*2); + +time = time(binsToKeep+1); +PSTH_out = flip(ccg(:,2:end,1),1); +% PSTH_out = ccg(:,2:end,1); +PSTH_out = PSTH_out(binsToKeep+1,:)./length(event_times)/binSize; + +modulationIndex = mean(PSTH_out(binsEvents,:))./mean(PSTH_out(binsPre,:)); +modulationSignificanceLevel = []; +for i = 1:size(PSTH_out,2) + [~,p_kstest2] = kstest2(PSTH_out(binsEvents,i),PSTH_out(binsPre,i)); + modulationSignificanceLevel(i) = p_kstest2; +end + +if smoothing>0 + PSTH_out = nanconv(PSTH_out,gausswin(smoothing)/sum(gausswin(smoothing)),'edge'); +end + +[~,modulationPeakResponseTime] = max(PSTH_out); +modulationPeakResponseTime = time(modulationPeakResponseTime); + +PSTH.responsecurve = PSTH_out; +PSTH.time = time; +PSTH.alignment = alignment; + +PSTH.modulationIndex = modulationIndex; +PSTH.modulationPeakResponseTime = modulationPeakResponseTime'; +PSTH.modulationSignificanceLevel = modulationSignificanceLevel; + +if plots + figure, plot(time,PSTH_out), title(eventName), xlabel('Time') + [~,index2] = sort(modulationIndex,'descend'); + [~,index3] = sort(modulationPeakResponseTime); + + figure, + subplot(2,2,1), histogram(modulationIndex,40), title('modulationIndex'), xlabel('Ratio'), ylabel(eventName) + subplot(2,2,2), histogram(modulationPeakResponseTime,40), title('modulationPeakResponseTime'), xlabel('Time') + subplot(2,2,3), imagesc(time,[1:size(PSTH_out,2)],zscore(PSTH_out(:,index2))'), title(['Sorting: modulationIndex']), xlabel('Time'), ylabel('Units') + subplot(2,2,4), imagesc(time,[1:size(PSTH_out,2)],zscore(PSTH_out(:,index3))'), title(['Sorting: modulationPeakResponseTime']), xlabel('Time') +end diff --git a/compileBuzcode.m b/compileBuzcode.m index b988acc5..ff45f47b 100755 --- a/compileBuzcode.m +++ b/compileBuzcode.m @@ -33,9 +33,18 @@ mex -O xml_findstr.c cd(['..' filesep '..' filesep '..' filesep '..']) -cd(['analysis' filesep 'spikes' filesep 'correlation' filesep '']) +cd(['analysis' filesep 'monosynapticPairs' filesep ]) mex -O CCGHeart.c -cd(['..' filesep '..' filesep '..']) +cd(['..' filesep '..']) + +cd(['externalPackages' filesep 'fastBSpline']) +CompileMexFiles +cd(['..' filesep '..']) + +cd(['externalPackages' filesep 'CONVNFFT']) +convnfft_install +cd(['..' filesep '..']) + % below is incomplete % below adds buzcode to the matlab path and saves diff --git a/externalPackages/CONVNFFT/conv2fft.m b/externalPackages/CONVNFFT/conv2fft.m new file mode 100644 index 00000000..b3b85c70 --- /dev/null +++ b/externalPackages/CONVNFFT/conv2fft.m @@ -0,0 +1,40 @@ +function C = conv2fft(varargin) +% C = conv2fft(A, B) +% C = conv2fft(H1, H2, A) +% +% C = CONV2FFT(A, B) performs the 2-D convolution of matrices A and B. +% If [ma,na] = size(A), [mb,nb] = size(B), and [mc,nc] = size(C), then +% mc = max([ma+mb-1,ma,mb]) and nc = max([na+nb-1,na,nb]). +% +% C = CONV2FFT(H1, H2, A) first convolves each column of A with the vector +% H1 and then convolves each row of the result with the vector H2. If +% n1 = length(H1), n2 = length(H2), and [mc,nc] = size(C) then +% mc = max([ma+n1-1,ma,n1]) and nc = max([na+n2-1,na,n2]). +% CONV2(H1, H2, A) is equivalent to CONV2FFT(H1(:)*H2(:).', A) up to +% round-off. +% +% C = CONV2FFT(..., SHAPE) returns a subsection of the 2-D +% convolution with size specified by SHAPE: +% 'full' - (default) returns the full 2-D convolution, +% 'same' - returns the central part of the convolution +% that is the same size as A. +% 'valid' - returns only those parts of the convolution +% that are computed without the zero-padded edges. +% size(C) = max([ma-max(0,mb-1),na-max(0,nb-1)],0). +% +% See also CONV2, CONVN, CONVNFFT +% +% Author: Bruno Luong +% History: +% Original: 21-April-2014 + +if length(varargin)>=3 && isnumeric(varargin{3}) + [H1, H2, A] = deal(varargin{1:3}); + varargin = varargin(4:end); + C = convnfft(H1(:), A, varargin{:}); + C = convnfft(H2(:).', C, varargin{:}); +else + C = convnfft(varargin{:}); +end + +end % conv2fft diff --git a/externalPackages/CONVNFFT/convnfft.m b/externalPackages/CONVNFFT/convnfft.m new file mode 100644 index 00000000..981f9327 --- /dev/null +++ b/externalPackages/CONVNFFT/convnfft.m @@ -0,0 +1,219 @@ +function A = convnfft(A, B, shape, dims, options) +% CONVNFFT FFT-BASED N-dimensional convolution. +% C = CONVNFFT(A, B) performs the N-dimensional convolution of +% matrices A and B. If nak = size(A,k) and nbk = size(B,k), then +% size(C,k) = max([nak+nbk-1,nak,nbk]); +% +% C = CONVNFFT(A, B, SHAPE) controls the size of the answer C: +% 'full' - (default) returns the full N-D convolution +% 'same' - returns the central part of the convolution that +% is the same size as A. +% 'valid' - returns only the part of the result that can be +% computed without assuming zero-padded arrays. +% size(C,k) = max([nak-max(0,nbk-1)],0). +% +% C = CONVNFFT(..., SHAPE, DIMS) with DIMS is vector of dimensions where +% the convolution will be carried out. By default DIMS is +% [1:max(ndims(A),ndims(B))] (all dimensions). A and B must have the +% same lengths on other dimensions. +% C = CONVNFFT(..., SHAPE, DIMS, GPU) +% GPU is boolean flag, see next +% +% C = CONVNFFT(..., SHAPE, DIMS, OPTIONS) +% +% OPTIONS is structure with following optional fields +% - 'GPU', boolean. If GPU is TRUE Jacket/GPU FFT engine will be used +% By default GPU is FALSE. +% - 'Power2Flag', boolean. If it is TRUE, use FFT with length rounded +% to the next power-two. It is faster but requires more memory. +% Default value is TRUE. +% +% Class support for inputs A,B: +% float: double, single +% +% METHOD: CONVNFFT uses Fourier transform (FT) convolution theorem, i.e. +% FT of the convolution is equal to the product of the FTs of the +% input functions. +% In 1-D, the complexity is O((na+nb)*log(na+nb)), where na/nb are +% respectively the lengths of A and B. +% +% Usage recommendation: +% In 1D, this function is faster than CONV for nA, nB > 1000. +% In 2D, this function is faster than CONV2 for nA, nB > 20. +% In 3D, this function is faster than CONVN for nA, nB > 5. +% +% See also conv, conv2, convn. +% +% Author: Bruno Luong +% History: +% Original: 21-Jun-2009 +% 23-Jun-2009: correct bug when ndims(A) return zeros + return +end + +power2flag = getoption(options, 'Power2Flag', true); +if power2flag + % faster FFT if the dimension is power of 2 + lfftfun = @(l) 2^nextpow2(l); +else + % slower, but smaller temporary arrays + lfftfun = @(l) l; +end + +if GPU % GPU/Jacket FFT + if strcmp(classA,'single') + A = gsingle(A); + else + A = gdouble(A); + end + if strcmp(classB,'single') + B = gsingle(B); + else + B = gdouble(B); + end + % Do the FFT + subs(1:ndims(A)) = {':'}; + for dim=dims + m = size(A,dim); + n = size(B,dim); + % compute the FFT length + l = lfftfun(m+n-1); + % We need to swap dimensions because GPU FFT works along the + % first dimension + if dim~=1 % do the work when only required + swap = 1:nd; + swap([1 dim]) = swap([dim 1]); + A = permute(A, swap); + B = permute(B, swap); + end + A = fft(A,l); + B = fft(B,l); + subs{dim} = ifun(m,n); + end +else % Matlab FFT + % Do the FFT + subs(1:ndims(A)) = {':'}; + for dim=dims + m = size(A,dim); + n = size(B,dim); + % compute the FFT length + l = lfftfun(m+n-1); + A = fft(A,l,dim); + B = fft(B,l,dim); + subs{dim} = ifun(m,n); + end +end + +if GPU + A = A.*B; + clear B +else + % inplace product to save 1/3 of the memory + inplaceprod(A,B); +end + +% Back to the non-Fourier space +if GPU % GPU/Jacket FFT + for dim=dims(end:-1:1) % reverse loop + A = ifft(A,[]); + % Swap back the dimensions + if dim~=1 % do the work when only required + swap = 1:nd; + swap([1 dim]) = swap([dim 1]); + A = permute(A, swap); + end + end +else % Matlab IFFT + for dim=dims + A = ifft(A,[],dim); + end +end + +% Truncate the results +if ABreal + % Make sure the result is real + A = real(A(subs{:})); +else + A = A(subs{:}); +end + +% GPU/Jacket +if GPU + % Cast the type back + if strcmp(class(A),'gsingle') + A = single(A); + else + A = double(A); + end +end + +end % convnfft + + +%% Get defaut option +function value = getoption(options, name, defaultvalue) +% function value = getoption(options, name, defaultvalue) + value = defaultvalue; + fields = fieldnames(options); + found = strcmpi(name,fields); + if any(found) + i = find(found,1,'first'); + if ~isempty(options.(fields{i})) + value = options.(fields{i}); + end + end +end diff --git a/externalPackages/CONVNFFT/convnfft_install.m b/externalPackages/CONVNFFT/convnfft_install.m new file mode 100644 index 00000000..5d59ebd3 --- /dev/null +++ b/externalPackages/CONVNFFT/convnfft_install.m @@ -0,0 +1,23 @@ +function convnfft_install +% function convnfft_install +% Installation by building the C-mex file needed for convnfft +% +% Author: Bruno Luong +% History +% Original: 16/Sept/2009 + +arch=computer('arch'); +mexopts = {'-O' '-v' ['-' arch]}; + +if ~verLessThan('MATLAB','9.4') + R2018a_mexopts = {'-R2018a'}; +else + % 64-bit platform + if ~isempty(strfind(computer(),'64')) + mexopts(end+1) = {'-largeArrayDims'}; + end + R2018a_mexopts = {}; +end + +% invoke MEX compilation tool +mex(mexopts{:},R2018a_mexopts{:},'inplaceprod.c'); \ No newline at end of file diff --git a/externalPackages/CONVNFFT/inplaceprod.c b/externalPackages/CONVNFFT/inplaceprod.c new file mode 100644 index 00000000..ff651292 --- /dev/null +++ b/externalPackages/CONVNFFT/inplaceprod.c @@ -0,0 +1,187 @@ +/************************************************************************* + * MATLAB MEX ROUTINE inplaceprod.c + * + * > inplaceprod(A,B) + * performs inplace (i.e., without allocate array) complex array product + * > A(:) = A(:).*B(:); + * + * User must make sure A is not shared by other array. + * B must have the same size as A, no check will be carried out + * + * A, B must be of class double or single (mixing is allowed) + * + * >> mex -O inplaceprod.c + * For more recent MATLAB (R2018a) use + * >> mex -O -R2018a inplaceprod.c + * + * Author: Bruno Luong + * History + * Original: 16/Sept/2009 + ************************************************************************/ + +#include "mex.h" +#include "matrix.h" + +#define A (mxArray*)(prhs[0]) +#define B prhs[1] + +#if MX_HAS_INTERLEAVED_COMPLEX +#define REC_SHIFT 2 +#define ENGINE_RACB(Ar, Ai, Br, Bi, temp, Atype, Btype) { \ +mexErrMsgTxt("INPLACEPROD: A must be complex."); \ +} +#else +#define REC_SHIFT 1 +#define ENGINE_RACB(Ar, Ai, Br, Bi, temp, Atype, Btype) { \ +Ai = (Atype*)mxMalloc(n*sizeof(Atype)); \ +mxSetImagData(A, (void*)Ai); \ +if (Ai==NULL) \ + mexErrMsgTxt("INPLACEPROD: cannot allocate memory."); \ +for (i=0; i +#include +#include +#include +#include +#include + +/* Translate matlab types to C */ +#define uint64 unsigned long int +#define int64 long int +#define uint32 unsigned int +#define int32 int +#define uint16 unsigned short +#define int16 short +#define uint8 unsigned char +#define int8 char +#define single float + +#include "mexmetypecheck.c" + +/* Your extra includes and function definitions here */ + + +double bin0(const double *knots,int ii,int n,double x) +{ + return x < knots[ii+1] && x >= knots[ii]; +} + +double bin1(const double *knots,int ii,int n,double x) +{ + double delta = 1e-8; + if (x= knots[ii]) + return (x-knots[ii])/(delta + knots[ii+1]-knots[ii]); + else if(x= knots[ii+1]) + return (knots[ii+2] - x)/(delta + knots[ii+2]-knots[ii+1]); +} + +double bin2(const double *knots,int ii,int n,double x) +{ + double delta = 1e-8; + double m = (x= knots[ii+1]); + if(m) + { + return (x-knots[ii])/(knots[ii+2]-knots[ii]+delta)*((knots[ii+2] - x)/(delta + knots[ii+2]-knots[ii+1])) + + (knots[ii+3] - x)/(knots[ii+3]-knots[ii+1]+delta)*(x-knots[ii+1])/(delta + knots[ii+2]-knots[ii+1]); + } + else if(x= knots[ii]) + { + return (x-knots[ii])*(x-knots[ii])/((knots[ii+2]-knots[ii]+delta)*(delta + knots[ii+1]-knots[ii])); + } + else if(x= knots[ii+2]) + { + return (knots[ii+3] - x)/(knots[ii+3]-knots[ii+1]+delta)*(knots[ii+3] - x)/(delta + knots[ii+3]-knots[ii+2]); + } + return 0; + /* + return (x-knots[ii])/(knots[ii+2]-knots[ii]+delta)*( + (x-knots[ii])/(delta + knots[ii+1]-knots[ii])* + + (knots[ii+2] - x)/(delta + knots[ii+2]-knots[ii+1])*m) + + (knots[ii+3] - x)/(knots[ii+3]-knots[ii+1]+delta)*( + (x-knots[ii+1])/(delta + knots[ii+2]-knots[ii+1])*m + + (knots[ii+3] - x)/(delta + knots[ii+3]-knots[ii+2])*(x= knots[ii+2])); + **/ +} + + +double bin(const double *knots,int ii,int n,double x) +{ + double delta = 1e-8; + if(n == 0) + { + return bin0(knots,ii,n,x); + } + else if(n == 1) + { + return bin1(knots,ii,n,x); + } + else if(n == 2) + { + return bin2(knots,ii,n,x); + } + else + { + return (x-knots[ii])/(knots[ii+n]-knots[ii]+delta)*bin(knots,ii,n-1,x) + + (knots[ii+n+1] - x)/(knots[ii+n+1]-knots[ii+1]+delta)*bin(knots,ii+1,n-1,x); + } +} + +void mexFunction( int nlhs, mxArray *plhs[], + int nrhs, const mxArray *prhs[] ) +{ + +/*Input output boilerplate*/ + if(nlhs != 1 || nrhs != 6) + mexErrMsgTxt("Function must be called with 6 arguments and has 1 return values"); + + const mxArray *x_ptr = prhs[0]; + mexmetypecheck(x_ptr,mxDOUBLE_CLASS,"Argument x (#1) is expected to be of type double"); + const mwSize x_m = mxGetM(x_ptr); + const mwSize x_n = mxGetN(x_ptr); + const mwSize x_length = x_m == 1 ? x_n : x_m; + const mwSize x_numel = mxGetNumberOfElements(x_ptr); + const int x_ndims = mxGetNumberOfDimensions(x_ptr); + const mwSize *x_size = mxGetDimensions(x_ptr); + const double *x = (double *) mxGetData(x_ptr); + const mxArray *firstknot_ptr = prhs[1]; + mexmetypecheck(firstknot_ptr,mxDOUBLE_CLASS,"Argument firstknot (#2) is expected to be of type double"); + const mwSize firstknot_m = mxGetM(firstknot_ptr); + const mwSize firstknot_n = mxGetN(firstknot_ptr); + const mwSize firstknot_length = firstknot_m == 1 ? firstknot_n : firstknot_m; + const mwSize firstknot_numel = mxGetNumberOfElements(firstknot_ptr); + const int firstknot_ndims = mxGetNumberOfDimensions(firstknot_ptr); + const mwSize *firstknot_size = mxGetDimensions(firstknot_ptr); + const double *firstknot = (double *) mxGetData(firstknot_ptr); + const mxArray *lastknot_ptr = prhs[2]; + mexmetypecheck(lastknot_ptr,mxDOUBLE_CLASS,"Argument lastknot (#3) is expected to be of type double"); + const mwSize lastknot_m = mxGetM(lastknot_ptr); + const mwSize lastknot_n = mxGetN(lastknot_ptr); + const mwSize lastknot_length = lastknot_m == 1 ? lastknot_n : lastknot_m; + const mwSize lastknot_numel = mxGetNumberOfElements(lastknot_ptr); + const int lastknot_ndims = mxGetNumberOfDimensions(lastknot_ptr); + const mwSize *lastknot_size = mxGetDimensions(lastknot_ptr); + const double *lastknot = (double *) mxGetData(lastknot_ptr); + const mxArray *knots_ptr = prhs[3]; + mexmetypecheck(knots_ptr,mxDOUBLE_CLASS,"Argument knots (#4) is expected to be of type double"); + const mwSize knots_m = mxGetM(knots_ptr); + const mwSize knots_n = mxGetN(knots_ptr); + const mwSize knots_length = knots_m == 1 ? knots_n : knots_m; + const mwSize knots_numel = mxGetNumberOfElements(knots_ptr); + const int knots_ndims = mxGetNumberOfDimensions(knots_ptr); + const mwSize *knots_size = mxGetDimensions(knots_ptr); + const double *knots = (double *) mxGetData(knots_ptr); + const mxArray *weights_ptr = prhs[4]; + mexmetypecheck(weights_ptr,mxDOUBLE_CLASS,"Argument weights (#5) is expected to be of type double"); + const mwSize weights_m = mxGetM(weights_ptr); + const mwSize weights_n = mxGetN(weights_ptr); + const mwSize weights_length = weights_m == 1 ? weights_n : weights_m; + const mwSize weights_numel = mxGetNumberOfElements(weights_ptr); + const int weights_ndims = mxGetNumberOfDimensions(weights_ptr); + const mwSize *weights_size = mxGetDimensions(weights_ptr); + const double *weights = (double *) mxGetData(weights_ptr); + const mxArray *order_ptr = prhs[5]; + mexmetypecheck(order_ptr,mxDOUBLE_CLASS,"Argument order (#6) is expected to be of type double"); + if(mxGetNumberOfElements(order_ptr) != 1) + mexErrMsgTxt("Argument order (#6) must be scalar"); + const double order = (double) mxGetScalar(order_ptr); + + + mwSize Sx_dims[] = {x_length,1}; + plhs[0] = mxCreateNumericArray(2,Sx_dims,mxDOUBLE_CLASS,mxREAL); + mxArray **Sx_ptr = &plhs[0]; + double *Sx = (double *) mxGetData(*Sx_ptr); + + + +/*Actual function*/ +#include "evalBspline.snip.c" + + +} + diff --git a/externalPackages/fastBSpline/evalBin.c b/externalPackages/fastBSpline/evalBin.c new file mode 100644 index 00000000..9c153d13 --- /dev/null +++ b/externalPackages/fastBSpline/evalBin.c @@ -0,0 +1,162 @@ +/* C file autogenerated by mexme.m */ +#include +#include +#include +#include +#include +#include + +/* Translate matlab types to C */ +#define uint64 unsigned long int +#define int64 long int +#define uint32 unsigned int +#define int32 int +#define uint16 unsigned short +#define int16 short +#define uint8 unsigned char +#define int8 char +#define single float + +#include "mexmetypecheck.c" + +/* Your extra includes and function definitions here */ + + +double bin0(const double *knots,int ii,int n,double x) +{ + return x < knots[ii+1] && x >= knots[ii]; +} + +double bin1(const double *knots,int ii,int n,double x) +{ + double delta = 1e-8; + if (x= knots[ii]) + return (x-knots[ii])/(delta + knots[ii+1]-knots[ii]); + else if(x= knots[ii+1]) + return (knots[ii+2] - x)/(delta + knots[ii+2]-knots[ii+1]); +} + +double bin2(const double *knots,int ii,int n,double x) +{ + double delta = 1e-8; + double m = (x= knots[ii+1]); + if(m) + { + return (x-knots[ii])/(knots[ii+2]-knots[ii]+delta)*((knots[ii+2] - x)/(delta + knots[ii+2]-knots[ii+1])) + + (knots[ii+3] - x)/(knots[ii+3]-knots[ii+1]+delta)*(x-knots[ii+1])/(delta + knots[ii+2]-knots[ii+1]); + } + else if(x= knots[ii]) + { + return (x-knots[ii])*(x-knots[ii])/((knots[ii+2]-knots[ii]+delta)*(delta + knots[ii+1]-knots[ii])); + } + else if(x= knots[ii+2]) + { + return (knots[ii+3] - x)/(knots[ii+3]-knots[ii+1]+delta)*(knots[ii+3] - x)/(delta + knots[ii+3]-knots[ii+2]); + } + return 0; + /* + return (x-knots[ii])/(knots[ii+2]-knots[ii]+delta)*( + (x-knots[ii])/(delta + knots[ii+1]-knots[ii])* + + (knots[ii+2] - x)/(delta + knots[ii+2]-knots[ii+1])*m) + + (knots[ii+3] - x)/(knots[ii+3]-knots[ii+1]+delta)*( + (x-knots[ii+1])/(delta + knots[ii+2]-knots[ii+1])*m + + (knots[ii+3] - x)/(delta + knots[ii+3]-knots[ii+2])*(x= knots[ii+2])); + **/ +} + + +double bin(const double *knots,int ii,int n,double x) +{ + double delta = 1e-8; + if(n == 0) + { + return bin0(knots,ii,n,x); + } + else if(n == 1) + { + return bin1(knots,ii,n,x); + } + else if(n == 2) + { + return bin2(knots,ii,n,x); + } + else + { + return (x-knots[ii])/(knots[ii+n]-knots[ii]+delta)*bin(knots,ii,n-1,x) + + (knots[ii+n+1] - x)/(knots[ii+n+1]-knots[ii+1]+delta)*bin(knots,ii+1,n-1,x); + } +} + +void mexFunction( int nlhs, mxArray *plhs[], + int nrhs, const mxArray *prhs[] ) +{ + +/*Input output boilerplate*/ + if(nlhs != 1 || nrhs != 6) + mexErrMsgTxt("Function must be called with 6 arguments and has 1 return values"); + + const mxArray *x_ptr = prhs[0]; + mexmetypecheck(x_ptr,mxDOUBLE_CLASS,"Argument x (#1) is expected to be of type double"); + const mwSize x_m = mxGetM(x_ptr); + const mwSize x_n = mxGetN(x_ptr); + const mwSize x_length = x_m == 1 ? x_n : x_m; + const mwSize x_numel = mxGetNumberOfElements(x_ptr); + const int x_ndims = mxGetNumberOfDimensions(x_ptr); + const mwSize *x_size = mxGetDimensions(x_ptr); + const double *x = (double *) mxGetData(x_ptr); + const mxArray *firstknot_ptr = prhs[1]; + mexmetypecheck(firstknot_ptr,mxDOUBLE_CLASS,"Argument firstknot (#2) is expected to be of type double"); + const mwSize firstknot_m = mxGetM(firstknot_ptr); + const mwSize firstknot_n = mxGetN(firstknot_ptr); + const mwSize firstknot_length = firstknot_m == 1 ? firstknot_n : firstknot_m; + const mwSize firstknot_numel = mxGetNumberOfElements(firstknot_ptr); + const int firstknot_ndims = mxGetNumberOfDimensions(firstknot_ptr); + const mwSize *firstknot_size = mxGetDimensions(firstknot_ptr); + const double *firstknot = (double *) mxGetData(firstknot_ptr); + const mxArray *lastknot_ptr = prhs[2]; + mexmetypecheck(lastknot_ptr,mxDOUBLE_CLASS,"Argument lastknot (#3) is expected to be of type double"); + const mwSize lastknot_m = mxGetM(lastknot_ptr); + const mwSize lastknot_n = mxGetN(lastknot_ptr); + const mwSize lastknot_length = lastknot_m == 1 ? lastknot_n : lastknot_m; + const mwSize lastknot_numel = mxGetNumberOfElements(lastknot_ptr); + const int lastknot_ndims = mxGetNumberOfDimensions(lastknot_ptr); + const mwSize *lastknot_size = mxGetDimensions(lastknot_ptr); + const double *lastknot = (double *) mxGetData(lastknot_ptr); + const mxArray *knots_ptr = prhs[3]; + mexmetypecheck(knots_ptr,mxDOUBLE_CLASS,"Argument knots (#4) is expected to be of type double"); + const mwSize knots_m = mxGetM(knots_ptr); + const mwSize knots_n = mxGetN(knots_ptr); + const mwSize knots_length = knots_m == 1 ? knots_n : knots_m; + const mwSize knots_numel = mxGetNumberOfElements(knots_ptr); + const int knots_ndims = mxGetNumberOfDimensions(knots_ptr); + const mwSize *knots_size = mxGetDimensions(knots_ptr); + const double *knots = (double *) mxGetData(knots_ptr); + const mxArray *weights_ptr = prhs[4]; + mexmetypecheck(weights_ptr,mxDOUBLE_CLASS,"Argument weights (#5) is expected to be of type double"); + const mwSize weights_m = mxGetM(weights_ptr); + const mwSize weights_n = mxGetN(weights_ptr); + const mwSize weights_length = weights_m == 1 ? weights_n : weights_m; + const mwSize weights_numel = mxGetNumberOfElements(weights_ptr); + const int weights_ndims = mxGetNumberOfDimensions(weights_ptr); + const mwSize *weights_size = mxGetDimensions(weights_ptr); + const double *weights = (double *) mxGetData(weights_ptr); + const mxArray *order_ptr = prhs[5]; + mexmetypecheck(order_ptr,mxDOUBLE_CLASS,"Argument order (#6) is expected to be of type double"); + if(mxGetNumberOfElements(order_ptr) != 1) + mexErrMsgTxt("Argument order (#6) must be scalar"); + const double order = (double) mxGetScalar(order_ptr); + + + mwSize Sx_dims[] = {x_numel,weights_length}; + plhs[0] = mxCreateNumericArray(2,Sx_dims,mxDOUBLE_CLASS,mxREAL); + mxArray **Sx_ptr = &plhs[0]; + double *Sx = (double *) mxGetData(*Sx_ptr); + + + +/*Actual function*/ +#include "evalBin.snip.c" + + +} + diff --git a/externalPackages/fastBSpline/evalBin.snip.c b/externalPackages/fastBSpline/evalBin.snip.c new file mode 100644 index 00000000..d756f058 --- /dev/null +++ b/externalPackages/fastBSpline/evalBin.snip.c @@ -0,0 +1,10 @@ + +int orderint = (int) order; +for(int jj = 0; jj < x_length; jj++) +{ + for(int ii = firstknot[jj]; ii < lastknot[jj]; ii++) + { + Sx[jj + (ii-1)*x_m] = bin(&(knots[-1]),ii,orderint,x[jj]); + } +} + diff --git a/externalPackages/fastBSpline/evalBinTimesY.c b/externalPackages/fastBSpline/evalBinTimesY.c new file mode 100644 index 00000000..c4aa8423 --- /dev/null +++ b/externalPackages/fastBSpline/evalBinTimesY.c @@ -0,0 +1,171 @@ +/* C file autogenerated by mexme.m */ +#include +#include +#include +#include +#include +#include + +/* Translate matlab types to C */ +#define uint64 unsigned long int +#define int64 long int +#define uint32 unsigned int +#define int32 int +#define uint16 unsigned short +#define int16 short +#define uint8 unsigned char +#define int8 char +#define single float + +#include "mexmetypecheck.c" + +/* Your extra includes and function definitions here */ + + +double bin0(const double *knots,int ii,int n,double x) +{ + return x < knots[ii+1] && x >= knots[ii]; +} + +double bin1(const double *knots,int ii,int n,double x) +{ + double delta = 1e-8; + if (x= knots[ii]) + return (x-knots[ii])/(delta + knots[ii+1]-knots[ii]); + else if(x= knots[ii+1]) + return (knots[ii+2] - x)/(delta + knots[ii+2]-knots[ii+1]); +} + +double bin2(const double *knots,int ii,int n,double x) +{ + double delta = 1e-8; + double m = (x= knots[ii+1]); + if(m) + { + return (x-knots[ii])/(knots[ii+2]-knots[ii]+delta)*((knots[ii+2] - x)/(delta + knots[ii+2]-knots[ii+1])) + + (knots[ii+3] - x)/(knots[ii+3]-knots[ii+1]+delta)*(x-knots[ii+1])/(delta + knots[ii+2]-knots[ii+1]); + } + else if(x= knots[ii]) + { + return (x-knots[ii])*(x-knots[ii])/((knots[ii+2]-knots[ii]+delta)*(delta + knots[ii+1]-knots[ii])); + } + else if(x= knots[ii+2]) + { + return (knots[ii+3] - x)/(knots[ii+3]-knots[ii+1]+delta)*(knots[ii+3] - x)/(delta + knots[ii+3]-knots[ii+2]); + } + return 0; + /* + return (x-knots[ii])/(knots[ii+2]-knots[ii]+delta)*( + (x-knots[ii])/(delta + knots[ii+1]-knots[ii])* + + (knots[ii+2] - x)/(delta + knots[ii+2]-knots[ii+1])*m) + + (knots[ii+3] - x)/(knots[ii+3]-knots[ii+1]+delta)*( + (x-knots[ii+1])/(delta + knots[ii+2]-knots[ii+1])*m + + (knots[ii+3] - x)/(delta + knots[ii+3]-knots[ii+2])*(x= knots[ii+2])); + **/ +} + + +double bin(const double *knots,int ii,int n,double x) +{ + double delta = 1e-8; + if(n == 0) + { + return bin0(knots,ii,n,x); + } + else if(n == 1) + { + return bin1(knots,ii,n,x); + } + else if(n == 2) + { + return bin2(knots,ii,n,x); + } + else + { + return (x-knots[ii])/(knots[ii+n]-knots[ii]+delta)*bin(knots,ii,n-1,x) + + (knots[ii+n+1] - x)/(knots[ii+n+1]-knots[ii+1]+delta)*bin(knots,ii+1,n-1,x); + } +} + +void mexFunction( int nlhs, mxArray *plhs[], + int nrhs, const mxArray *prhs[] ) +{ + +/*Input output boilerplate*/ + if(nlhs != 1 || nrhs != 7) + mexErrMsgTxt("Function must be called with 7 arguments and has 1 return values"); + + const mxArray *x_ptr = prhs[0]; + mexmetypecheck(x_ptr,mxDOUBLE_CLASS,"Argument x (#1) is expected to be of type double"); + const mwSize x_m = mxGetM(x_ptr); + const mwSize x_n = mxGetN(x_ptr); + const mwSize x_length = x_m == 1 ? x_n : x_m; + const mwSize x_numel = mxGetNumberOfElements(x_ptr); + const int x_ndims = mxGetNumberOfDimensions(x_ptr); + const mwSize *x_size = mxGetDimensions(x_ptr); + const double *x = (double *) mxGetData(x_ptr); + const mxArray *firstknot_ptr = prhs[1]; + mexmetypecheck(firstknot_ptr,mxDOUBLE_CLASS,"Argument firstknot (#2) is expected to be of type double"); + const mwSize firstknot_m = mxGetM(firstknot_ptr); + const mwSize firstknot_n = mxGetN(firstknot_ptr); + const mwSize firstknot_length = firstknot_m == 1 ? firstknot_n : firstknot_m; + const mwSize firstknot_numel = mxGetNumberOfElements(firstknot_ptr); + const int firstknot_ndims = mxGetNumberOfDimensions(firstknot_ptr); + const mwSize *firstknot_size = mxGetDimensions(firstknot_ptr); + const double *firstknot = (double *) mxGetData(firstknot_ptr); + const mxArray *lastknot_ptr = prhs[2]; + mexmetypecheck(lastknot_ptr,mxDOUBLE_CLASS,"Argument lastknot (#3) is expected to be of type double"); + const mwSize lastknot_m = mxGetM(lastknot_ptr); + const mwSize lastknot_n = mxGetN(lastknot_ptr); + const mwSize lastknot_length = lastknot_m == 1 ? lastknot_n : lastknot_m; + const mwSize lastknot_numel = mxGetNumberOfElements(lastknot_ptr); + const int lastknot_ndims = mxGetNumberOfDimensions(lastknot_ptr); + const mwSize *lastknot_size = mxGetDimensions(lastknot_ptr); + const double *lastknot = (double *) mxGetData(lastknot_ptr); + const mxArray *knots_ptr = prhs[3]; + mexmetypecheck(knots_ptr,mxDOUBLE_CLASS,"Argument knots (#4) is expected to be of type double"); + const mwSize knots_m = mxGetM(knots_ptr); + const mwSize knots_n = mxGetN(knots_ptr); + const mwSize knots_length = knots_m == 1 ? knots_n : knots_m; + const mwSize knots_numel = mxGetNumberOfElements(knots_ptr); + const int knots_ndims = mxGetNumberOfDimensions(knots_ptr); + const mwSize *knots_size = mxGetDimensions(knots_ptr); + const double *knots = (double *) mxGetData(knots_ptr); + const mxArray *weights_ptr = prhs[4]; + mexmetypecheck(weights_ptr,mxDOUBLE_CLASS,"Argument weights (#5) is expected to be of type double"); + const mwSize weights_m = mxGetM(weights_ptr); + const mwSize weights_n = mxGetN(weights_ptr); + const mwSize weights_length = weights_m == 1 ? weights_n : weights_m; + const mwSize weights_numel = mxGetNumberOfElements(weights_ptr); + const int weights_ndims = mxGetNumberOfDimensions(weights_ptr); + const mwSize *weights_size = mxGetDimensions(weights_ptr); + const double *weights = (double *) mxGetData(weights_ptr); + const mxArray *order_ptr = prhs[5]; + mexmetypecheck(order_ptr,mxDOUBLE_CLASS,"Argument order (#6) is expected to be of type double"); + if(mxGetNumberOfElements(order_ptr) != 1) + mexErrMsgTxt("Argument order (#6) must be scalar"); + const double order = (double) mxGetScalar(order_ptr); + const mxArray *y_ptr = prhs[6]; + mexmetypecheck(y_ptr,mxDOUBLE_CLASS,"Argument y (#7) is expected to be of type double"); + const mwSize y_m = mxGetM(y_ptr); + const mwSize y_n = mxGetN(y_ptr); + const mwSize y_length = y_m == 1 ? y_n : y_m; + const mwSize y_numel = mxGetNumberOfElements(y_ptr); + const int y_ndims = mxGetNumberOfDimensions(y_ptr); + const mwSize *y_size = mxGetDimensions(y_ptr); + const double *y = (double *) mxGetData(y_ptr); + + + mwSize d_dims[] = {weights_length,1}; + plhs[0] = mxCreateNumericArray(2,d_dims,mxDOUBLE_CLASS,mxREAL); + mxArray **d_ptr = &plhs[0]; + double *d = (double *) mxGetData(*d_ptr); + + + +/*Actual function*/ +#include "evalBinTimesY.snip.c" + + +} + diff --git a/externalPackages/fastBSpline/evalBinTimesY.snip.c b/externalPackages/fastBSpline/evalBinTimesY.snip.c new file mode 100644 index 00000000..cb402ea3 --- /dev/null +++ b/externalPackages/fastBSpline/evalBinTimesY.snip.c @@ -0,0 +1,11 @@ + +int orderint = (int) order; +for(int jj = 0; jj < x_length; jj++) +{ + double y0 = y[jj]; + for(int ii = firstknot[jj]; ii < lastknot[jj]; ii++) + { + d[ii-1] += y0*bin(&(knots[-1]),ii,orderint,x[jj]); + } +} + diff --git a/externalPackages/fastBSpline/evalBspline.snip.c b/externalPackages/fastBSpline/evalBspline.snip.c new file mode 100644 index 00000000..766089a1 --- /dev/null +++ b/externalPackages/fastBSpline/evalBspline.snip.c @@ -0,0 +1,13 @@ + +int orderint = (int) order; + +for(int jj = 0; jj < x_length; jj++) +{ + double s = 0; + for(int ii = firstknot[jj]; ii < lastknot[jj]; ii++) + { + s += weights[ii-1]*bin(&(knots[-1]),ii,orderint,x[jj]); + } + Sx[jj] = s; +} + diff --git a/externalPackages/fastBSpline/fastBSpline.m b/externalPackages/fastBSpline/fastBSpline.m new file mode 100644 index 00000000..6250ea0d --- /dev/null +++ b/externalPackages/fastBSpline/fastBSpline.m @@ -0,0 +1,304 @@ +classdef fastBSpline + %fastBSpline - A fast, lightweight class that implements + %non-uniform B splines of any order + % + %Matlab's spline functions are very general. This generality comes at + %the price of speed. For large-scale applications, including model + %fitting where some components of the model are defined in terms of + %splines, such as generalized additive models, a faster solution is + %desirable. + % + %The fastBSpline class implements a lightweight set of B-spline + %features, including evaluation, differentiation, and parameter fitting. + %The hard work is done by C code, resulting in up to 10x acceleration + %for evaluating splines and up to 50x acceleration when evaluating + %of spline derivatives. + % + %Nevertheless, fastBSplines are manipulated using an intuitive, high- + %level object-oriented interface, thus allowing C-level performance + %without the messiness. Use CompileMexFiles to compile the required + %files. If mex files are not available, evaluation will be done in .m + %code, so you may still use the code if you can't use a compiler for your + %platform. + % + %B splines are defined in terms of basis functions: + % + % y(x) = sum_i B_i(x,knots)*weights_i + % + %B (the basis) is defined in terms of knots, a non-decreasing sequence + %of values. Each basis function is a piecewise polynomial of order + %length(knots)-length(weights)-1. The most commonly used B-spline is + %the cubic B-spline. In that case there are 4 more knots than there + %are weights. Another commonly used B-spline is the linear B-spline, + %whose basis function are shaped like tents, and whose application + %results in piecewise linear interpolation. + % + %The class offers two static functions to fit the weights of a spline: + %lsqspline and pspline. It includes facilities for computing the basis + %B and the derivatives of the spline at all points. + % + %Constructor: + % + %sp = fastBSpline(knots,weights); + % + %Example use: + % + %%Fit a noisy measurement with a smoothness-penalized spline (p-spline) + % x = (0:.5:10)'; + % y = sin(x*pi*.41-.9)+randn(size(x))*.2; + % knots = [0,0,0,0:.5:10,10,10,10]; + %%Notice there are as many knots as observations + % + %%Because there are so many knots, this is an exact interpolant + % sp1 = fastBSpline.lsqspline(knots,3,x,y); + %%Fit penalized on the smoothness of the spline + % sp2 = fastBSpline.pspline(knots,3,x,y,.7); + % + % clf; + % rg = -2:.005:12; + % plot(x,y,'o',rg,sp1.evalAt(rg),rg,sp2.evalAt(rg)); + % legend('measured','interpolant','smoothed'); + % + %fastBSpline properties: + % outOfRange - Determines how the spline is extrapolated outside the + % range of the knots + % knots - The knots of the spline (read only) + % weights - The weights of the spline (read only) + % + %fastBSpline Methods: + % fastBSpline - Construct a B spline from weights at the knots + % lsqspline - Construct a least-squares spline from noisy measurements + % pspline - Construct a smoothness-penalized spline from noisy + % measurements + % evalAt - Evaluate a spline at the given points + % getBasis - Get the values of the underlying basis at the given points + % Btimesy - Evaluate the product getBasis(x)'*y + % dx - Returns another fastBSpline object which computes the derivative of + % the original spline + % + %Disclaimer: fastBSpline is not meant to replace Matlab's spline functions; + % it does not include any code from the Mathworks + properties + %knots - The vector of knots. + knots = []; + + %weights - The vector of weights + weights = []; + + %If outOfRange = fastBSpline.ZERO, outside the knots the spline = 0 + % outOfRange = fastBSpline.CONSTANT, for x > knots(end), sp(x) = + % x(knots(end)), and similarly for x < knots(1) + outOfRange = fastBSpline.ZERO; + + %usemex - Whether use mex acceleration is used + usemex = false; + end + + methods + function this = fastBSpline(knots,weights) + %sp = fastBSpline(knots,weights) + % + %Class constructor for fastBSpline + %The order of the spline is = length(knots)-length(weights) - 1 + this.knots = knots; + this.weights = weights; + %Check that the required C files are compiled + if exist('evalBSpline','file') == 3 && exist('evalBin','file') == 3 + this.usemex = true; + else + warning('fastBSpline:nomex','Mex files are not available, this will be a bit slower. Try running CompileMexFiles.'); + end + end + + function s = dx(this) + %spp = sp.dx + %Given sp a spline, spp is another fastBSpline object such that + %spp(x) is the derivative of sp evaluated at x + this.weights = [0;this.weights;0]; + this.knots = this.knots([1,1:end,end]'); + wp = protect(this.order*diff(this.weights) ./ ... + (this.knots(this.order + (2:length(this.weights)))-this.knots((2:length(this.weights))))); + s = fastBSpline(this.knots(2:end-1),wp); + end + + function Sx = evalAt(this,x) + %Sx = sp.evalAt(x) - Evaluate the spline at x + %x can be a vector or N-D matrix. size(Sx) == size(x) + %This function is potentially accelerated through mex + sz = size(x); + x = x(:); + + order = this.order; + if this.outOfRange == fastBSpline.CONSTANT + x = max(min(x,this.knots(end)-1e-10),this.knots(1)+1e-10); + end + + if this.usemex + [~,thebins] = histc(x,this.knots); + firstknot = max(min(thebins-order,length(this.weights)),1); + lastknot = min(thebins+1,length(this.weights)+1); + + Sx = evalBSpline(x,firstknot,lastknot,this.knots,this.weights,order); + else + %Fallback + Sx = zeros(prod(sz),1); + for ii = 1:length(this.knots)-order-1 + v = x > this.knots(ii) & x <= this.knots(ii+order+1); + Sx(v) = Sx(v) + this.weights(ii)*bin(this.knots,ii,order,x(v)); + end + end + + Sx = reshape(Sx,sz); + end + + function Sx = getBasis(this,x) + %B = sp.getBasis(x) - Gets the basis B sampled at points x + %size(B) == [numel(x),length(sp.weights)] + x = x(:); + if this.outOfRange == fastBSpline.CONSTANT + x = max(min(x,this.knots(end)-1e-10),this.knots(1)+1e-10); + end + order = this.order; + if this.usemex + [~,thebins] = histc(x,this.knots); + firstknot = max(min(thebins-order,length(this.weights)),1); + lastknot = min(thebins+1,length(this.weights)+1); + + Sx = evalBin(x,firstknot,lastknot,this.knots,this.weights,order); + else + %Fallback + Sx = zeros(numel(x),length(this.weights)); + for ii = 1:length(this.knots)-this.order-1 + Sx(:,ii) = bin(this.knots,ii,this.order,x); + end + end + end + + function d = Btimesy(this,x,y) + %sp.Btimesy(x,y) - Computes the product sp.getBasis(x)'*y. + % + %This product is frequently needed in fitting models; for + %example, if E is an error function, r is the output of the + %spline evaluated at x, and y = dE/dr, then the derivative + %dE/dw is given by sp.Btimesy(x,y) by the chain rule. + % + %This product can be computed efficiently in C without actually + %forming the B matrix in memory. + + %Sanity check + x = x(:); + y = y(:); + if length(x) ~= length(y) + error('length(x) and length(y) must be equal'); + end + if this.usemex + [~,thebins] = histc(x,this.knots); + firstknot = max(min(thebins-this.order,length(this.weights)),1); + lastknot = min(thebins+1,length(this.weights)+1); + + d = evalBinTimesY(x,firstknot,lastknot,this.knots,this.weights,this.order,y); + else + %Fallback + d = this.getBasis(x)'*y; + end + end + + function x = order(this) + %x = sp.order - Returns the order of the spline sp + x = length(this.knots)-length(this.weights)-1; + end + + function [this] = set.knots(this,k) + %Set knots + this.knots = k(:); + if ~issorted(this.knots) + error('Knots must be non-decreasing'); + end + this.checkNum(); + end + + function [this] = set.weights(this,w) + %Set weights + this.weights = w(:); + this.checkNum(); + end + end + + methods(Access=protected) + function [] = checkNum(this) + %Check whether number of weights is consistent with number of knots + if length(this.knots) <= length(this.weights) + error('length(knots) must be > than length(weights)'); + end + end + end + + methods(Static) + function sp = lsqspline(knots,order,xi,yi) + %BSpline.lsqspline(knots,order,xi,yi) + %Fit the weights of the spline with given knots and order based + %on a least-squares fit of the data yi corresponding to xi. This + %function is static. + xi = xi(:); + yi = yi(:); + sp = fastBSpline(knots,ones(length(knots)-order-1,1)); + sp.outOfRange = fastBSpline.CONSTANT; + B = sp.getBasis(xi); + w = B\yi; + sp = fastBSpline(knots,w); + sp.outOfRange = fastBSpline.CONSTANT; + end + + function sp = pspline(knots,order,xi,yi,lambda) + %BSpline.pspline(knots,order,xi,yi,lambda) + %Like lsqspline but with a smoothness penalty of strength lambda + %on the weights of the spline. This function is static. + xi = xi(:); + yi = yi(:); + sp = fastBSpline(knots,ones(length(knots)-order-1,1)); + sp.outOfRange = fastBSpline.CONSTANT; + B = sp.getBasis(xi); + e = ones(size(B,2),1); + D = spdiags([-e 2*e -e], -1:1, size(B,2), size(B,2)); + w = [B;lambda*D]\[yi;zeros(size(B,2),1)]; + sp = fastBSpline(knots,w); + sp.outOfRange = fastBSpline.CONSTANT; + end + end + + properties(Constant,Hidden) + ZERO = 0; + CONSTANT = 1; + end +end + +function x = protect(x) + %Define 0/0 == 0 + x(isnan(x) | abs(x) == Inf) = 0; +end + +function y = bin(knots,i,n,x) + %bin - evaluate basis functions at given points. This is a fallback + %when C files are not available + delta = 1e-10; + if n == 0 + y = x= knots(i); + elseif n == 1 + %Removes one degree of recursion + y = (x-knots(i))/(delta + knots(i+1)-knots(i)).*(x= knots(i)) + ... + (knots(i+2) - x)/(delta + knots(i+2)-knots(i+1)).*(x= knots(i+1)); + elseif n == 2 + + m = (x= knots(i+1)); + y = (x-knots(i))/(knots(i+2)-knots(i)+delta).*( ... + (x-knots(i))/(delta + knots(i+1)-knots(i)).*(x= knots(i)) + ... + (knots(i+2) - x)/(delta + knots(i+2)-knots(i+1)).*m) + ... + ... + (knots(i+3) - x)/(knots(i+3)-knots(i+1)+delta).*(... + (x-knots(i+1))/(delta + knots(i+2)-knots(i+1)).*m + ... + (knots(i+3) - x)/(delta + knots(i+3)-knots(i+2)).*(x= knots(i+2))); + else + y = (x-knots(i))/(knots(i+n)-knots(i)+delta).*bin(knots,i,n-1,x) + ... + (knots(i+n+1) - x)/(knots(i+n+1)-knots(i+1)+delta).*bin(knots,i+1,n-1,x); + end +end diff --git a/externalPackages/fastBSpline/license.txt b/externalPackages/fastBSpline/license.txt new file mode 100644 index 00000000..daa3b37b --- /dev/null +++ b/externalPackages/fastBSpline/license.txt @@ -0,0 +1,24 @@ +Copyright (c) 2011, Patrick Mineault +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/externalPackages/fastBSpline/mexmetypecheck.c b/externalPackages/fastBSpline/mexmetypecheck.c new file mode 100644 index 00000000..c8ccf308 --- /dev/null +++ b/externalPackages/fastBSpline/mexmetypecheck.c @@ -0,0 +1,46 @@ +void mexmetypecheck(const mxArray *arr,int expected,char *errorMsg) +{ + int thetype = mxGetClassID(arr); + char passed = 0; + if(expected == mxINT8_CLASS || + expected == mxINT16_CLASS || + expected == mxINT32_CLASS || + expected == mxINT64_CLASS) + { + if(thetype == mxINT8_CLASS || + thetype == mxINT16_CLASS || + thetype == mxINT32_CLASS || + thetype == mxINT64_CLASS) + { + if(thetype <= expected) + passed = 1; + } + } + else if(expected == mxUINT8_CLASS || + expected == mxUINT16_CLASS || + expected == mxUINT32_CLASS || + expected == mxUINT64_CLASS) + { + if(thetype == mxUINT8_CLASS || + thetype == mxUINT16_CLASS || + thetype == mxUINT32_CLASS || + thetype == mxUINT64_CLASS) + { + if(thetype <= expected) + passed = 1; + } + } + else if(expected == mxSINGLE_CLASS || expected == mxDOUBLE_CLASS) + { + passed = 1; + if(thetype == mxDOUBLE_CLASS && expected == mxSINGLE_CLASS) + { + passed = 0; + } + } + + if(!passed) + { + mexErrMsgTxt(errorMsg); + } +} diff --git a/externalPackages/read_Intan_RHD2000_file.m b/externalPackages/read_Intan_RHD2000_file.m index 4bef0088..e558d623 100755 --- a/externalPackages/read_Intan_RHD2000_file.m +++ b/externalPackages/read_Intan_RHD2000_file.m @@ -1,5 +1,4 @@ -function read_Intan_RHD2000_file - +function intaninfo = read_Intan_RHD2000_file(varargin) % read_Intan_RHD2000_file % % Version 2.01, 11 October 2017 @@ -16,9 +15,36 @@ % >> whos % >> amplifier_channels(1) % >> plot(t_amplifier, amplifier_data(1,:)) - -[file, path, filterindex] = ... +% +% INPUT: +% returnStruct - logical (default=false) to return a structure instead +% of moving variables to the workspace (which is horrible) +% filepath - char (default=[]), give path to an info.rhd file +% +% Updated by Roman Huszar, 2019 +% Added functionality to return a struct with all the extracted info instead of +% saving variables to the base workspace, which is the default. +% Also added option to load from pre-identified info.rhd file when optional input +% is provided. + +% Parse user input +p = inputParser; +addParameter(p,'returnStruct',false,@islogical); +addParameter(p,'filepath',[],@isstr); +parse(p,varargin{:}) + +% Store user input +returnStruct = p.Results.returnStruct; +filepath = p.Results.filepath; + +% Get path to info.rhd file +if isempty(filepath) + [file, path, filterindex] = ... uigetfile('*.rhd', 'Select an RHD2000 Data File', 'MultiSelect', 'off'); +else + file = 'info.rhd'; + path = [filepath, filesep]; +end if (file == 0) return; @@ -457,77 +483,145 @@ end -% Move variables to base workspace. +% Output struct +intaninfo = struct(); -% new for version 2.01: move filename info to base workspace -filename = file; -move_to_base_workspace(filename); -move_to_base_workspace(path); +% Move variables to base workspace +if ~returnStruct -move_to_base_workspace(notes); -move_to_base_workspace(frequency_parameters); -if (data_file_main_version_number > 1) - move_to_base_workspace(reference_channel); -end + filename = file; + move_to_base_workspace(filename); + move_to_base_workspace(path); -if (num_amplifier_channels > 0) - move_to_base_workspace(amplifier_channels); - if (data_present) - move_to_base_workspace(amplifier_data); - move_to_base_workspace(t_amplifier); + move_to_base_workspace(notes); + move_to_base_workspace(frequency_parameters); + if (data_file_main_version_number > 1) + move_to_base_workspace(reference_channel); end - move_to_base_workspace(spike_triggers); -end -if (num_aux_input_channels > 0) - move_to_base_workspace(aux_input_channels); - if (data_present) - move_to_base_workspace(aux_input_data); - move_to_base_workspace(t_aux_input); + + if (num_amplifier_channels > 0) + move_to_base_workspace(amplifier_channels); + if (data_present) + move_to_base_workspace(amplifier_data); + move_to_base_workspace(t_amplifier); + end + move_to_base_workspace(spike_triggers); end -end -if (num_supply_voltage_channels > 0) - move_to_base_workspace(supply_voltage_channels); - if (data_present) - move_to_base_workspace(supply_voltage_data); - move_to_base_workspace(t_supply_voltage); + if (num_aux_input_channels > 0) + move_to_base_workspace(aux_input_channels); + if (data_present) + move_to_base_workspace(aux_input_data); + move_to_base_workspace(t_aux_input); + end end -end -if (num_board_adc_channels > 0) - move_to_base_workspace(board_adc_channels); - if (data_present) - move_to_base_workspace(board_adc_data); - move_to_base_workspace(t_board_adc); + if (num_supply_voltage_channels > 0) + move_to_base_workspace(supply_voltage_channels); + if (data_present) + move_to_base_workspace(supply_voltage_data); + move_to_base_workspace(t_supply_voltage); + end end -end -if (num_board_dig_in_channels > 0) - move_to_base_workspace(board_dig_in_channels); - if (data_present) - move_to_base_workspace(board_dig_in_data); - move_to_base_workspace(t_dig); + if (num_board_adc_channels > 0) + move_to_base_workspace(board_adc_channels); + if (data_present) + move_to_base_workspace(board_adc_data); + move_to_base_workspace(t_board_adc); + end end -end -if (num_board_dig_out_channels > 0) - move_to_base_workspace(board_dig_out_channels); - if (data_present) - move_to_base_workspace(board_dig_out_data); - move_to_base_workspace(t_dig); + if (num_board_dig_in_channels > 0) + move_to_base_workspace(board_dig_in_channels); + if (data_present) + move_to_base_workspace(board_dig_in_data); + move_to_base_workspace(t_dig); + end end -end -if (num_temp_sensor_channels > 0) + if (num_board_dig_out_channels > 0) + move_to_base_workspace(board_dig_out_channels); + if (data_present) + move_to_base_workspace(board_dig_out_data); + move_to_base_workspace(t_dig); + end + end + if (num_temp_sensor_channels > 0) + if (data_present) + move_to_base_workspace(temp_sensor_data); + move_to_base_workspace(t_temp_sensor); + end + end + + fprintf(1, 'Done! Elapsed time: %0.1f seconds\n', toc); if (data_present) - move_to_base_workspace(temp_sensor_data); - move_to_base_workspace(t_temp_sensor); + fprintf(1, 'Extracted data are now available in the MATLAB workspace.\n'); + else + fprintf(1, 'Extracted waveform information is now available in the MATLAB workspace.\n'); end -end + fprintf(1, 'Type ''whos'' to see variables.\n'); + fprintf(1, '\n'); -fprintf(1, 'Done! Elapsed time: %0.1f seconds\n', toc); -if (data_present) - fprintf(1, 'Extracted data are now available in the MATLAB workspace.\n'); +% Return a structure all the relevant information else - fprintf(1, 'Extracted waveform information is now available in the MATLAB workspace.\n'); + + intaninfo.filepath = filename; + intaninfo.notes = notes; + intaninfo.frequency_parameters = frequency_parameters; + + if (data_file_main_version_number > 1) + intaninfo.reference_channel = reference_channel; + end + + if (num_amplifier_channels > 0) + intaninfo.amplifier_channels = amplifier_channels; + if (data_present) + intaninfo.amplifier_data = amplifier_data; + intaninfo.t_amplifier = t_amplifier; + end + intaninfo.spike_triggers = spike_triggers; + end + if (num_aux_input_channels > 0) + intaninfo.aux_input_channels = aux_input_channels; + if (data_present) + intaninfo.aux_input_data = aux_input_data; + intaninfo.t_aux_input = t_aux_input; + end + end + if (num_supply_voltage_channels > 0) + intaninfo.supply_voltage_channels = supply_voltage_channels; + if (data_present) + intaninfo.supply_voltage_data = supply_voltage_data; + intaninfo.t_supply_voltage = t_supply_voltage; + end + end + if (num_board_adc_channels > 0) + intaninfo.board_adc_channels = board_adc_channels; + if (data_present) + intaninfo.board_adc_data = board_adc_data; + intaninfo.t_board_adc = t_board_adc; + end + end + if (num_board_dig_in_channels > 0) + intaninfo.board_dig_in_channels = board_dig_in_channels; + if (data_present) + intaninfo.board_dig_in_data = board_dig_in_data; + intaninfo.t_dig = t_dig; + end + end + if (num_board_dig_out_channels > 0) + intaninfo.board_dig_out_channels = board_dig_out_channels; + if (data_present) + intaninfo.board_dig_out_data = board_dig_out_data; + end + end + if (num_temp_sensor_channels > 0) + if (data_present) + intaninfo.temp_sensor_data = temp_sensor_data; + intaninfo.t_temp_sensor = t_temp_sensor; + end + end + + fprintf(1, 'Done! Elapsed time: %0.1f seconds\n', toc); + end -fprintf(1, 'Type ''whos'' to see variables.\n'); -fprintf(1, '\n'); + return diff --git a/io/bz_GetSpikes.m b/io/bz_GetSpikes.m index 0b7b2903..cb4b93dc 100755 --- a/io/bz_GetSpikes.m +++ b/io/bz_GetSpikes.m @@ -21,6 +21,12 @@ % clu/res/fet to spikes.cellinfo.mat file % saveMat -logical (default=false) to save in buzcode format % noPrompts -logical (default=false) to supress any user prompts +% verbose -logical (default=false) +% keepCluWave -logical (default=false) to keep waveform from +% previous bz_getSpikes functions (before 2019). It only +% affects clu inputs. +% sortingMethod - [], 'kilosort' or 'clu'. If [], tries to detect a +% kilosort folder or clu files. % % OUTPUTS % @@ -36,6 +42,7 @@ % .rawWaveform -average waveform on maxWaveformCh (from raw .dat) % .cluID -cluster ID, NOT UNIQUE ACROSS SHANKS % .numcells -number of cells/UIDs +% .filtWaveform -average filtered waveform on maxWaveformCh % % NOTES % @@ -63,6 +70,10 @@ % % % written by David Tingley, 2017 +% added Phy loading by Manu Valero, 2019 (previos bz_LoadPhy) + +% TO DO: Get waveforms by an independent function (ie getWaveform) that +% generates a waveform.cellinfo.mat file with all channels waves. %% Deal With Inputs spikeGroupsValidation = @(x) assert(isnumeric(x) || strcmp(x,'all'),... 'spikeGroups must be numeric or "all"'); @@ -74,9 +85,12 @@ addParameter(p,'basepath',pwd,@isstr); addParameter(p,'getWaveforms',true) addParameter(p,'forceReload',false,@islogical); -addParameter(p,'saveMat',false,@islogical); +addParameter(p,'saveMat',true,@islogical); addParameter(p,'noPrompts',false,@islogical); addParameter(p,'onlyLoad',[]); +addParameter(p,'verbose',true,@islogical); +addParameter(p,'keepCluWave',false,@islogical); +addParameter(p,'sortingMethod',[],@isstr); parse(p,varargin{:}) @@ -89,12 +103,13 @@ saveMat = p.Results.saveMat; noPrompts = p.Results.noPrompts; onlyLoad = p.Results.onlyLoad; - +verbose = p.Results.verbose; +keepCluWave = p.Results.keepCluWave; +sortingMethod = p.Results.sortingMethod; [sessionInfo] = bz_getSessionInfo(basepath, 'noPrompts', noPrompts); baseName = bz_BasenameFromBasepath(basepath); - spikes.samplingRate = sessionInfo.rates.wideband; nChannels = sessionInfo.nChannels; @@ -134,157 +149,233 @@ if strcmp(savebutton,'Yes'); saveMat = true; end end -disp('loading spikes from clu/res/spk files..') -% find res/clu/fet/spk files here -cluFiles = dir([basepath filesep '*.clu*']); -resFiles = dir([basepath filesep '*.res.*']); -if any(getWaveforms) - spkFiles = dir([basepath filesep '*.spk*']); -end - - -% remove *temp*, *autosave*, and *.clu.str files/directories -tempFiles = zeros(length(cluFiles),1); -for i = 1:length(cluFiles) - dummy = strsplit(cluFiles(i).name, '.'); % Check whether the component after the last dot is a number or not. If not, exclude the file/dir. - if ~isempty(findstr('temp',cluFiles(i).name)) | ~isempty(findstr('autosave',cluFiles(i).name)) | ... - isempty(str2num(dummy{length(dummy)})) | find(contains(dummy, 'clu')) ~= length(dummy)-1 | ... - ~strcmp(dummy{1},baseName) - tempFiles(i) = 1; + % find res/clu/fet/spk files or kilosort folder here... + cluFiles = dir([basepath filesep '*.clu*']); + resFiles = dir([basepath filesep '*.res.*']); + if any(getWaveforms) + spkFiles = dir([basepath filesep '*.spk*']); end -end -cluFiles(tempFiles==1)=[]; -tempFiles = zeros(length(resFiles),1); -for i = 1:length(resFiles) - dummy = strsplit(resFiles(i).name, '.'); - if ~isempty(findstr('temp',resFiles(i).name)) | ~isempty(findstr('autosave',resFiles(i).name)) | ... - ~strcmp(dummy{1},baseName) - tempFiles(i) = 1; + kilosort_path = dir([basepath filesep '*kilosort*']); + +if strcmpi(sortingMethod,'clu') || ~isempty(cluFiles) % LOADING FROM CLU/ RES + disp('loading spikes from clu/res/spk files..') + + % remove *temp*, *autosave*, and *.clu.str files/directories + tempFiles = zeros(length(cluFiles),1); + for i = 1:length(cluFiles) + dummy = strsplit(cluFiles(i).name, '.'); % Check whether the component after the last dot is a number or not. If not, exclude the file/dir. + if ~isempty(findstr('temp',cluFiles(i).name)) | ~isempty(findstr('autosave',cluFiles(i).name)) | ... + isempty(str2num(dummy{length(dummy)})) | find(contains(dummy, 'clu')) ~= length(dummy)-1 | ... + ~strcmp(dummy{1},baseName) + tempFiles(i) = 1; + end end -end -if any(getWaveforms) - resFiles(tempFiles==1)=[]; - tempFiles = zeros(length(spkFiles),1); - for i = 1:length(spkFiles) - dummy = strsplit(spkFiles(i).name, '.'); - if ~isempty(findstr('temp',spkFiles(i).name)) | ~isempty(findstr('autosave',spkFiles(i).name)) | ... - ~strcmp(dummy{1},baseName) + cluFiles(tempFiles==1)=[]; + tempFiles = zeros(length(resFiles),1); + for i = 1:length(resFiles) + dummy = strsplit(resFiles(i).name, '.'); + if ~isempty(findstr('temp',resFiles(i).name)) | ~isempty(findstr('autosave',resFiles(i).name)) | ... + ~strcmp(dummy{1},baseName) tempFiles(i) = 1; end end - spkFiles(tempFiles==1)=[]; -end + if any(getWaveforms) + resFiles(tempFiles==1)=[]; + tempFiles = zeros(length(spkFiles),1); + for i = 1:length(spkFiles) + dummy = strsplit(spkFiles(i).name, '.'); + if ~isempty(findstr('temp',spkFiles(i).name)) | ~isempty(findstr('autosave',spkFiles(i).name)) | ... + ~strcmp(dummy{1},baseName) + tempFiles(i) = 1; + end + end + spkFiles(tempFiles==1)=[]; + end -if isempty(cluFiles) - disp('no clu files found...') - spikes = []; - return -end + if isempty(cluFiles) + disp('no clu files found...') + spikes = []; + return + end + + % ensures we load in sequential order (forces compatibility with FMAT + % ordering) + for i = 1:length(cluFiles) + temp = strsplit(cluFiles(i).name,'.'); + shanks(i) = str2num(temp{length(temp)}); + end + [shanks ind] = sort(shanks); + cluFiles = cluFiles(ind); %Bug here if there are any files x.clu.x that are not your desired clus + resFiles = resFiles(ind); + if any(getWaveforms) + spkFiles = spkFiles(ind); + end + % check if there are matching #'s of files + if length(cluFiles) ~= length(resFiles) & length(cluFiles) ~= length(spkFiles) + error('found an incorrect number of res/clu/spk files...') + end -% ensures we load in sequential order (forces compatibility with FMAT -% ordering) -for i = 1:length(cluFiles) - temp = strsplit(cluFiles(i).name,'.'); - shanks(i) = str2num(temp{length(temp)}); -end -[shanks ind] = sort(shanks); -cluFiles = cluFiles(ind); %Bug here if there are any files x.clu.x that are not your desired clus -resFiles = resFiles(ind); -if any(getWaveforms) - spkFiles = spkFiles(ind); -end + % use the .clu files to get spike ID's and generate UID and spikeGroup + % use the .res files to get spike times + count = 1; -% check if there are matching #'s of files -if length(cluFiles) ~= length(resFiles) & length(cluFiles) ~= length(spkFiles) - error('found an incorrect number of res/clu/spk files...') -end + if isempty(sessionInfo.spikeGroups.groups) + sessionInfo.spikeGroups = sessionInfo.AnatGrps; + end + for i=1:length(cluFiles) + disp(['working on ' cluFiles(i).name]) + + temp = strsplit(cluFiles(i).name,'.'); + shankID = str2num(temp{length(temp)}); %shankID is the spikegroup number + clu = load(fullfile(basepath,cluFiles(i).name)); + clu = clu(2:end); % toss the first sample to match res/spk files + res = load(fullfile(basepath,resFiles(i).name)); + spkGrpChans = sessionInfo.spikeGroups.groups{shankID}; % we'll eventually want to replace these two lines + + if any(getWaveforms) && sum(clu)>0 %bug fix if no clusters + nSamples = sessionInfo.spikeGroups.nSamples(shankID); + % load waveforms + chansPerSpikeGrp = length(sessionInfo.spikeGroups.groups{shankID}); + fid = fopen(fullfile(basepath,spkFiles(i).name),'r'); + wav = fread(fid,[1 inf],'int16=>int16'); + try %bug in some spk files... wrong number of samples? + wav = reshape(wav,chansPerSpikeGrp,nSamples,[]); + catch + if strcmp(getWaveforms,'force') + wav = nan(chansPerSpikeGrp,nSamples,length(clu)); + display([spkFiles(i).name,' error.']) + else + error(['something is wrong with ',spkFiles(i).name,... + ' Use ''getWaveforms'', false to skip waveforms or ',... + '''getWaveforms'', ''force'' to write nans on bad shanks.']) + end + end + wav = permute(wav,[3 1 2]); + end -% use the .clu files to get spike ID's and generate UID and spikeGroup -% use the .res files to get spike times -count = 1; + cells = unique(clu); + % remove MUA and NOISE clusters... + cells(cells==0) = []; + cells(cells==1) = []; % consider adding MUA as another input argument...? + + for c = 1:length(cells) + spikes.UID(count) = count; % this only works if all shanks are loaded... how do we optimize this? + ind = find(clu == cells(c)); + spikes.times{count} = res(ind) ./ spikes.samplingRate; + spikes.shankID(count) = shankID; + spikes.cluID(count) = cells(c); + + %Waveforms + if any(getWaveforms) + wvforms = squeeze(mean(wav(ind,:,:)))-mean(mean(mean(wav(ind,:,:)))); % mean subtract to account for slower (theta) trends + if prod(size(wvforms))==length(wvforms)%in single-channel groups wvforms will squeeze too much and will have amplitude on D1 rather than D2 + wvforms = wvforms';%fix here + end + for t = 1:size(wvforms,1) + [a(t) b(t)] = max(abs(wvforms(t,:))); + end + [aa bb] = max(a,[],2); + spikes.rawWaveform{count} = wvforms(bb,:); + spikes.maxWaveformCh(count) = spkGrpChans(bb); + %Regions (needs waveform peak) + if isfield(sessionInfo,'region') %if there is regions field in your metadata + spikes.region{count} = sessionInfo.region{find(spkGrpChans(bb)==sessionInfo.channels)}; + elseif isfield(sessionInfo,'Units') %if no regions, but unit region from xml via Loadparamteres + %Find the xml Unit that matches group/cluster + unitnum = cellfun(@(X,Y) X==spikes.shankID(count) && Y==spikes.cluID(count),... + {sessionInfo.Units(:).spikegroup},{sessionInfo.Units(:).cluster}); + if sum(unitnum) == 0 + display(['xml Missing Unit - spikegroup: ',... + num2str(spikes.shankID(count)),' cluster: ',... + num2str(spikes.cluID(count))]) + spikes.region{count} = 'missingxml'; + else %possible future bug: two xml units with same group/clu... + spikes.region{count} = sessionInfo.Units(unitnum).structure; + end + end + clear a aa b bb + end -if isempty(sessionInfo.spikeGroups.groups) - sessionInfo.spikeGroups = sessionInfo.AnatGrps; -end -for i=1:length(cluFiles) - disp(['working on ' cluFiles(i).name]) + count = count + 1; + end + end + spikes.sessionName = sessionInfo.FileName; - temp = strsplit(cluFiles(i).name,'.'); - shankID = str2num(temp{length(temp)}); %shankID is the spikegroup number - clu = load(fullfile(basepath,cluFiles(i).name)); - clu = clu(2:end); % toss the first sample to match res/spk files - res = load(fullfile(basepath,resFiles(i).name)); - spkGrpChans = sessionInfo.spikeGroups.groups{shankID}; % we'll eventually want to replace these two lines +elseif strcmpi(sortingMethod, 'kilosort') || ~isempty(kilosort_path) % LOADING FROM KILOSORT - if any(getWaveforms) && sum(clu)>0 %bug fix if no clusters - nSamples = sessionInfo.spikeGroups.nSamples(shankID); - % load waveforms - chansPerSpikeGrp = length(sessionInfo.spikeGroups.groups{shankID}); - fid = fopen(fullfile(basepath,spkFiles(i).name),'r'); - wav = fread(fid,[1 inf],'int16=>int16'); - try %bug in some spk files... wrong number of samples? - wav = reshape(wav,chansPerSpikeGrp,nSamples,[]); - catch - if strcmp(getWaveforms,'force') - wav = nan(chansPerSpikeGrp,nSamples,length(clu)); - display([spkFiles(i).name,' error.']) - else - error(['something is wrong with ',spkFiles(i).name,... - ' Use ''getWaveforms'', false to skip waveforms or ',... - '''getWaveforms'', ''force'' to write nans on bad shanks.']) - end - end - wav = permute(wav,[3 1 2]); + disp('loading spikes from Kilosort/Phy format...') + fs = spikes.samplingRate; + spike_cluster_index = readNPY(fullfile(kilosort_path.name, 'spike_clusters.npy')); + spike_times = readNPY(fullfile(kilosort_path.name, 'spike_times.npy')); + cluster_group = tdfread(fullfile(kilosort_path.name,'cluster_group.tsv')); + try + shanks = readNPY(fullfile(kilosort_path.name, 'shanks.npy')); % done + catch + shanks = ones(size(cluster_group.cluster_id)); + warning('No shanks.npy file, assuming single shank!'); end - cells = unique(clu); - % remove MUA and NOISE clusters... - cells(cells==0) = []; - cells(cells==1) = []; % consider adding MUA as another input argument...? + spikes.sessionName = sessionInfo.FileName; + jj = 1; + for ii = 1:length(cluster_group.group) + if strcmpi(strtrim(cluster_group.group(ii,:)),'good') + ids = find(spike_cluster_index == cluster_group.cluster_id(ii)); % cluster id + spikes.UID_kilosort(jj) = cluster_group.cluster_id(ii); + spikes.UID(jj) = jj; + spikes.times{jj} = double(spike_times(ids))/fs; % cluster time + spikes.ts{jj} = double(spike_times(ids)); % cluster time + cluster_id = find(cluster_group.cluster_id == spikes.UID_kilosort(jj)); + spikes.shankID(jj) = shanks(cluster_id); + jj = jj + 1; + end + end - for c = 1:length(cells) - spikes.UID(count) = count; % this only works if all shanks are loaded... how do we optimize this? - ind = find(clu == cells(c)); - spikes.times{count} = res(ind) ./ spikes.samplingRate; - spikes.shankID(count) = shankID; - spikes.cluID(count) = cells(c); - - %Waveforms - if any(getWaveforms) - wvforms = squeeze(mean(wav(ind,:,:)))-mean(mean(mean(wav(ind,:,:)))); % mean subtract to account for slower (theta) trends - if prod(size(wvforms))==length(wvforms)%in single-channel groups wvforms will squeeze too much and will have amplitude on D1 rather than D2 - wvforms = wvforms';%fix here - end - for t = 1:size(wvforms,1) - [a(t) b(t)] = max(abs(wvforms(t,:))); - end - [aa bb] = max(a,[],2); - spikes.rawWaveform{count} = wvforms(bb,:); - spikes.maxWaveformCh(count) = spkGrpChans(bb); - %Regions (needs waveform peak) - if isfield(sessionInfo,'region') %if there is regions field in your metadata - spikes.region{count} = sessionInfo.region{find(spkGrpChans(bb)==sessionInfo.channels)}; - elseif isfield(sessionInfo,'Units') %if no regions, but unit region from xml via Loadparamteres - %Find the xml Unit that matches group/cluster - unitnum = cellfun(@(X,Y) X==spikes.shankID(count) && Y==spikes.cluID(count),... - {sessionInfo.Units(:).spikegroup},{sessionInfo.Units(:).cluster}); - if sum(unitnum) == 0 - display(['xml Missing Unit - spikegroup: ',... - num2str(spikes.shankID(count)),' cluster: ',... - num2str(spikes.cluID(count))]) - spikes.region{count} = 'missingxml'; - else %possible future bug: two xml units with same group/clu... - spikes.region{count} = sessionInfo.Units(unitnum).structure; - end - end - clear a aa b bb - end - - count = count + 1; + if ~isfield(spikes,'region') && isfield(spikes,'maxWaveformCh') && isfield(sessionInfo,'region') + for cc = 1:length(spikes.times) + spikes.region{cc} = [sessionInfo.region{find(spikes.maxWaveformCh(cc)==sessionInfo.channels)} '']; + end end +else + error('Unit format not recognized...'); end +% get waveforms +if getWaveforms || ~keepCluWave + nPull = 1000; % number of spikes to pull out + wfWin = 0.008; % Larger size of waveform windows for filterning + filtFreq = 500; + hpFilt = designfilt('highpassiir','FilterOrder',3, 'PassbandFrequency',filtFreq,'PassbandRipple',0.1, 'SampleRate',fs); + wfWin = round((wfWin * fs)/2); + for ii = 1 : size(spikes.times,2) + spkTmp = spikes.ts{ii}; + if length(spkTmp) > nPull + spkTmp = spkTmp(randperm(length(spkTmp))); + spkTmp = spkTmp(1:nPull); + end + wf = []; + for jj = 1 : length(spkTmp) + if verbose + fprintf(' ** %3.i/%3.i for cluster %3.i/%3.i \n',jj, length(spkTmp), ii, size(spikes.times,2)); + end + wf = cat(3,wf,bz_LoadBinary([sessionInfo.session.name '.dat'],'offset',spkTmp(jj) - (wfWin),... + 'samples',(wfWin * 2)+1,'frequency',sessionInfo.rates.wideband,'nChannels',sessionInfo.nChannels)); + end + wf = mean(wf,3); + if isfield(sessionInfo,'badchannels') + wf(:,ismember(sessionInfo.channels,sessionInfo.badchannels))=0; + end + for jj = 1 : size(wf,2) + wfF(:,jj) = filtfilt(hpFilt,wf(:,jj) - mean(wf(:,jj))); + end + [~, maxCh] = max(abs(wfF(wfWin,:))); + rawWaveform = detrend(wf(:,maxCh) - mean(wf(:,maxCh))); + filtWaveform = wfF(:,maxCh) - mean(wfF(:,maxCh)); + spikes.rawWaveform{ii} = rawWaveform(wfWin-(0.002*fs):wfWin+(0.002*fs)); % keep only +- 1ms of waveform + spikes.filtWaveform{ii} = filtWaveform(wfWin-(0.002*fs):wfWin+(0.002*fs)); + spikes.maxWaveformCh(ii) = sessionInfo.channels(maxCh); + end +end if ~isempty(onlyLoad) toRemove = true(size(spikes.UID)); @@ -299,15 +390,12 @@ spikes = removeCells(toRemove,spikes,getWaveforms); end - -spikes.sessionName = sessionInfo.FileName; -end - %% save to buzcode format (before exclusions) if saveMat save(cellinfofile,'spikes') end +end %% EXCLUSIONS %% @@ -355,27 +443,29 @@ spikes = []; end - - end - %% function spikes = removeCells(toRemove,spikes,getWaveforms) %Function to remove cells from the structure. toRemove is the INDEX of %the UID in spikes.UID - spikes.UID(toRemove) = []; spikes.times(toRemove) = []; spikes.region(toRemove) = []; - spikes.cluID(toRemove) = []; spikes.shankID(toRemove) = []; + if isfield(spikes,'cluID') + spikes.cluID(toRemove) = []; + elseif isfield(spikes,'UID_kilosort') + spikes.UID_kilosort(toRemove) = []; + end if any(getWaveforms) spikes.rawWaveform(toRemove) = []; spikes.maxWaveformCh(toRemove) = []; + if isfield(spikes,'filtWaveform') + spikes.filtWaveform(toRemove) = []; + end end - end diff --git a/io/bz_LFPfromDat.m b/io/bz_LFPfromDat.m index 6048ef4d..00a687a2 100755 --- a/io/bz_LFPfromDat.m +++ b/io/bz_LFPfromDat.m @@ -29,6 +29,8 @@ function bz_LFPfromDat(basepath,varargin) % 'lopass' (default: 450) low pass filter frequency % 'noPrompts' (default: true) prevents prompts about % saving/adding metadata +% 'useGPU' (default: false) whether or not to use GPU to speed +% processing (might not want to if limited GPU) % % %OUTPUT @@ -48,25 +50,39 @@ function bz_LFPfromDat(basepath,varargin) end basename = bz_BasenameFromBasepath(basepath); +GPUStatusUserDefined = 0; +for a = 1:length(varargin) + if strcmp('useGPU',varargin{a}) + GPUStatusUserDefined = 1; + end +end + defaultoutFS = 1250; %used later p = inputParser; addParameter(p,'noPrompts',true,@islogical); addParameter(p,'outFs',[],@isnumeric); addParameter(p,'lopass',450,@isnumeric); +addParameter(p,'useGPU',false,@islogical); parse(p,varargin{:}) noPrompts = p.Results.noPrompts; outFs = p.Results.outFs; lopass = p.Results.lopass; +useGPU = p.Results.useGPU; import iosr.dsp.* -useGPU = false; -try - if gpuDeviceCount>0 - useGPU = true; +% useGPU = false;%now setting default above +if GPUStatusUserDefined && ~useGPU + 1; %if user specified not to use GPU, don't use it +else% otherwise, see if it's available to use + try + if gpuDeviceCount>0 + useGPU = true; + end end end + sizeInBytes = 2; % %% files check diff --git a/io/bz_getIntanAccel.m b/io/bz_getIntanAccel.m new file mode 100644 index 00000000..f32064ca --- /dev/null +++ b/io/bz_getIntanAccel.m @@ -0,0 +1,207 @@ +function accel = bz_getIntanAccel(varargin) +% bz_getIntanAccel - Get accelerometer data from Intan-generated auxiliary.dat file +% +% USAGE +% +% acc = bz_getIntanAccel(varargin) +% +% INPUTS +% +% basepath -path to recording (where .dat/.clu/etc files are) +% lowpass -numeric value to low-pass filter acceleration data +% samplingRate -numeric value to control final sampling rate of acceleration data +% forceReload -logical (default=false) to force loading from auxiliary.dat file +% saveMat -logical (default=false) to save in buzcode format +% noPrompts -logical (default=false) to supress any user prompts +% +% OUTPUTS +% +% acc - behavior struct with the following fields +% .timestamps -array of timestamps that match the data subfields (in seconds) +% .acceleration -data substruct with x, y, z acceleration and overall magnitude of acceleration +% .samplingRate -sampling rate of data +% .lowpass -value used to low-pass filter raw data +% .units -unit of measurement of acceleration (volts, see the note below) +% .behaviorinfo -information substruct +% +% NOTES: +% +% Extract accelerometer data from auxiliary.dat file - when doing this, downsample +% to the rate of LFP acquisition, and low-pass filter the resulting signals +% (parameter 'lowpass'). Use acceleration along x, y, and z axes to compute +% magnitude of acceleration (i.e., the norm of a 3D vector). This can be used +% as a proxy for whether the animal is moving or not. In a final step, average +% subintervals of acceleration values to get a desired number of values per +% second (parameter 'samplingRate'). Use info.rhd Intan data file to verify +% number of active auxiliary.dat files - this step depends on a modified +% version of 'read_Intan_RHD2000_file.m' (Intan MATLAB script) +% +% The Intan accelerometer stores voltage values that reflect instantaneous +% acceleration along the x, y, and z axes. On average, a value of 0.34 V maps +% to 1g acceleration. For details, see the following link: +% http://intantech.com/files/Intan_RHD2000_accelerometer_calibration.pdf +% +% Written by Roman Huszar, 2019 + +%% Process and check user input + +% Parse user input +p = inputParser; +addParameter(p,'basepath',pwd,@isstr); +addParameter(p,'lowpass', 1, @isnumeric); +addParameter(p,'samplingRate', 1, @isnumeric); +addParameter(p,'forceReload',false,@islogical); +addParameter(p,'saveMat',false,@islogical); +addParameter(p,'noPrompts',false,@islogical); +parse(p,varargin{:}) + +% Store user input +basepath = p.Results.basepath; +lowpass = p.Results.lowpass; +samplingRate = p.Results.samplingRate; +forceReload = p.Results.forceReload; +saveMat = p.Results.saveMat; +noPrompts = p.Results.noPrompts; + +% Session information +basename = bz_BasenameFromBasepath(basepath); +aux_input_path = fullfile(basepath, [basename '_auxiliary.dat']); +accel_output_path = fullfile(basepath, [basename '.accel.behavior.mat']); +sessionInfo = bz_getSessionInfo(basepath, 'noPrompts', noPrompts); +fs_wide = sessionInfo.rates.wideband; +fs_lfp = sessionInfo.lfpSampleRate; + +% If auxiliary file not find, crash with grace +if 2 ~= exist(aux_input_path, 'file') + error('auxiliary.dat file not found in basepath - cannot extract accelerometer') +end + +% If file exists, return its contents +if 2 == exist(accel_output_path, 'file') && ~forceReload + disp('Loading acceleration data from acceler.behavior file..') + load(accel_output_path) %#ok + return +end + +% Ask user about saving data +if ~noPrompts && ~saveMat + savebutton = questdlg(['Would you like to store acceleration ',... + 'data in .accel.behavior.mat? ']); + if strcmp(savebutton,'Yes'); saveMat = true; end +end + +% Check validity of target sampling rate +if samplingRate > fs_lfp + error('samplingRate must be smaller than lfp sampling rate, which is %d.\nType ''help bz_getIntanAccel'' for more.', fs_lfp) +end + +% Get the auxiliary channels that are active +basepath_contents = dir(basepath); +dirs_in_basepath = {basepath_contents([basepath_contents.isdir]).name}; +% Find the first 'info.rhd' file. This assumes that sessions with more dat files +% (i.e., due to Intan crash) have identical info.rhd parameter settings... +% IMPORTANT NOTE - we assume info.rhd lives either in basepath directory, +% or in one of its subdirectories (we don't check sub-subdirectories!!) +found_flag = false; +for ii = 1:length(dirs_in_basepath) % First two directories are '.' and '..' + inforhd_path = fullfile(basepath, dirs_in_basepath{ii}); + if 2 == exist(fullfile(inforhd_path,'info.rhd'), 'file'); found_flag = true; break; end % Exit when first info.rhd file found +end + +% Read the Intan info file +if found_flag + try + intaninfo = read_Intan_RHD2000_file('returnStruct', true, 'filepath', inforhd_path); + n_active_channels = length(intaninfo.aux_input_channels); + active_channel_ids = {intaninfo.aux_input_channels.native_channel_name}; + is_x_on = any( cellfun(@(x) ~isempty(x), regexp(active_channel_ids, 'AUX1')) ); + is_y_on = any( cellfun(@(x) ~isempty(x), regexp(active_channel_ids, 'AUX2')) ); + is_z_on = any( cellfun(@(x) ~isempty(x), regexp(active_channel_ids, 'AUX3')) ); + catch + warning('found info.rhd, but it is unreadable. Cannot confirm number of active channels in auxiliary.dat. Default is set to 3 - can cause problems!') + n_active_channels = 3; + is_x_on = true; is_y_on = true; is_z_on = true; + end +else + warning('found no info.rhd file in basepath subdirectories. Cannot confirm number of active channels in auxiliary.dat. Default is set to 3 - can cause problems!') + n_active_channels = 3; + is_x_on = true; is_y_on = true; is_z_on = true; +end + +% Get IDs of the specific active auxiliary channels +if n_active_channels < 3 + warning('auxiliary.dat file contains fewer than the typical 3 channels, motion detection may be less accurate'); +end + +%% Process auxiliary dat file + +% Load auxiliary data file - downsample to rate of LFP while doing so +disp('Loading auxiliary.dat binary file...') +aux_data = bz_LoadBinary(aux_input_path, 'nchannels', n_active_channels, 'channels', 1:n_active_channels, 'frequency', fs_wide, 'precision', 'uint16', 'downsample', round(fs_wide / fs_lfp)); +aux_timestamps = [0 : 1/fs_lfp : (length(aux_data)-1)/fs_lfp]'; + +% Compute acceleration vector +% (Take into account older versions of MATLAB) +try + meeg = vecnorm(double(aux_data),2,2); +catch + meeg = vecnorm(double(aux_data),2); +end + +% Low pass filter the signal (default = 1 Hz) +% NOTE: this assumes that finer timescale data are not needed +Wn_theta = [0.1/(fs_lfp/2) lowpass/(fs_lfp/2)]; +[btheta, atheta] = butter(2, Wn_theta); +meeg_filt = filtfilt(btheta, atheta, meeg); +meeg_filt = abs(meeg_filt); +aux_data_filt = filtfilt(btheta, atheta, double(aux_data)); + +% Average values in prespecified interval to get desired number of data +% points per second (samplingRate) +dat_per_samp = round(fs_lfp / samplingRate); +% Motion proxy +motion = mean(reshape(meeg_filt(1:(length(meeg_filt) - mod(length(meeg_filt), dat_per_samp))), dat_per_samp, []), 1); +% Each axis separately +ii = 1; +if is_x_on + x = mean(reshape(aux_data_filt(1:(length(aux_data_filt) - mod(length(aux_data_filt), dat_per_samp)), ii), dat_per_samp, []), 1); ii = ii + 1; +else + x = []; +end +if is_y_on + y = mean(reshape(aux_data_filt(1:(length(aux_data_filt) - mod(length(aux_data_filt), dat_per_samp)), ii), dat_per_samp, []), 1); ii = ii + 1; +else + y = []; +end +if is_z_on + z = mean(reshape(aux_data_filt(1:(length(aux_data_filt) - mod(length(aux_data_filt), dat_per_samp)), 3), dat_per_samp, []), 1); +else + z = []; +end + +% Generate a buzcode behavior struct to hold the accelerometer information +accel = struct(); +accel.timestamps = aux_timestamps(1:dat_per_samp:end-dat_per_samp); +accel.acceleration.x = x; +accel.acceleration.y = y; +accel.acceleration.z = z; +accel.acceleration.motion = motion; +accel.acceleration.activeChannels = n_active_channels; +accel.samplingRate = samplingRate; +accel.lowpass = lowpass; +accel.units = 'V'; +accel.behaviorinfo.description = 'accelerometer from Intan auxiliary dat file'; +accel.behaviorinfo.acquisitionsystem = 'Intan'; +accel.behaviorinfo.processingfunction = 'bz_getIntanAccel.m'; + +% Save struct if prompted by user +if saveMat + save(accel_output_path, 'accel') +end + + +end + + + + diff --git a/preprocessing/bz_DatFileMetadata.m b/preprocessing/bz_DatFileMetadata.m index f3be47fd..ad838cef 100755 --- a/preprocessing/bz_DatFileMetadata.m +++ b/preprocessing/bz_DatFileMetadata.m @@ -107,7 +107,7 @@ try [amplifier_channels, notes, aux_input_channels, spike_triggers,... board_dig_in_channels, supply_voltage_channels, frequency_parameters ] =... - read_Intan_RHD2000_file(fullfile(basepath,d(didx).name),'info.rhd');%function from Intan + read_Intan_RHD2000_file_In(fullfile(basepath,d(didx).name),'info.rhd');%function from Intan DatsMetadata.Recordings.IntanRHDInfoRaw{thisidx} = v2struct(amplifier_channels, notes, aux_input_channels, spike_triggers,... board_dig_in_channels, supply_voltage_channels, frequency_parameters); NAmpChannels(thisidx) = length(amplifier_channels); @@ -257,3 +257,622 @@ end save(fullfile(basepath,[basename '_DatsMetadata.mat']),'DatsMetadata') + + +%% +function [amplifier_channels, notes, aux_input_channels, spike_triggers,... +board_dig_in_channels, supply_voltage_channels, frequency_parameters ] =... +read_Intan_RHD2000_file_In(path,file) + +% read_Intan_RHD2000_file +% +% Version 1.3, 10 December 2013 +% +% Reads Intan Technologies RHD2000 data file generated by evaluation board +% GUI. Data are parsed and placed into variables that appear in the base +% MATLAB workspace. Therefore, it is recommended to execute a 'clear' +% command before running this program to clear all other variables from the +% base workspace. +% +% INPUTS +% path and file are the path and filename for the rhd to be read. +% +% Example: +% >> clear +% >> read_Intan_RHD200_file +% >> whos +% >> amplifier_channels(1) +% >> plot(t_amplifier, amplifier_data(1,:)) + +if ~exist('path','var') + [file, path, filterindex] = ... + uigetfile('*.rhd', 'Select an RHD2000 Data File', 'MultiSelect', 'off'); +else + if ~strcmp('.rhd',file(end-3:end)) + file = strcat(file,'.rhd'); + end +end + +% Read most recent file automatically. +%path = 'C:\Users\Reid\Documents\RHD2132\testing\'; +%d = dir([path '*.rhd']); +%file = d(end).name; + +tic; +filename = fullfile(path,file); +fid = fopen(filename, 'r'); + +s = dir(filename); +filesize = s.bytes; + +% Check 'magic number' at beginning of file to make sure this is an Intan +% Technologies RHD2000 data file. +magic_number = fread(fid, 1, 'uint32'); +if magic_number ~= hex2dec('c6912702') + error('Unrecognized file type.'); +end + +% Read version number. +data_file_main_version_number = fread(fid, 1, 'int16'); +data_file_secondary_version_number = fread(fid, 1, 'int16'); + +% fprintf(1, '\n'); +% fprintf(1, 'Reading Intan Technologies RHD2000 Data File, Version %d.%d\n', ... +% data_file_main_version_number, data_file_secondary_version_number); +% fprintf(1, '\n'); + +% Read information of sampling rate and amplifier frequency settings. +sample_rate = fread(fid, 1, 'single'); +dsp_enabled = fread(fid, 1, 'int16'); +actual_dsp_cutoff_frequency = fread(fid, 1, 'single'); +actual_lower_bandwidth = fread(fid, 1, 'single'); +actual_upper_bandwidth = fread(fid, 1, 'single'); + +desired_dsp_cutoff_frequency = fread(fid, 1, 'single'); +desired_lower_bandwidth = fread(fid, 1, 'single'); +desired_upper_bandwidth = fread(fid, 1, 'single'); + +% This tells us if a software 50/60 Hz notch filter was enabled during +% the data acquisition. +notch_filter_mode = fread(fid, 1, 'int16'); +notch_filter_frequency = 0; +if (notch_filter_mode == 1) + notch_filter_frequency = 50; +elseif (notch_filter_mode == 2) + notch_filter_frequency = 60; +end + +desired_impedance_test_frequency = fread(fid, 1, 'single'); +actual_impedance_test_frequency = fread(fid, 1, 'single'); + +% Place notes in data strucure +notes = struct( ... + 'note1', fread_QString_In(fid), ... + 'note2', fread_QString_In(fid), ... + 'note3', fread_QString_In(fid) ); + +% If data file is from GUI v1.1 or later, see if temperature sensor data +% was saved. +num_temp_sensor_channels = 0; +if ((data_file_main_version_number == 1 & data_file_secondary_version_number >= 1) ... + | (data_file_main_version_number > 1)) + num_temp_sensor_channels = fread(fid, 1, 'int16'); +end + +% If data file is from GUI v1.3 or later, load eval board mode. +eval_board_mode = 0; +if ((data_file_main_version_number == 1 & data_file_secondary_version_number >= 3) ... + | (data_file_main_version_number > 1)) + eval_board_mode = fread(fid, 1, 'int16'); +end + +% Place frequency-related information in data structure. +frequency_parameters = struct( ... + 'amplifier_sample_rate', sample_rate, ... + 'aux_input_sample_rate', sample_rate / 4, ... + 'supply_voltage_sample_rate', sample_rate / 60, ... + 'board_adc_sample_rate', sample_rate, ... + 'board_dig_in_sample_rate', sample_rate, ... + 'desired_dsp_cutoff_frequency', desired_dsp_cutoff_frequency, ... + 'actual_dsp_cutoff_frequency', actual_dsp_cutoff_frequency, ... + 'dsp_enabled', dsp_enabled, ... + 'desired_lower_bandwidth', desired_lower_bandwidth, ... + 'actual_lower_bandwidth', actual_lower_bandwidth, ... + 'desired_upper_bandwidth', desired_upper_bandwidth, ... + 'actual_upper_bandwidth', actual_upper_bandwidth, ... + 'notch_filter_frequency', notch_filter_frequency, ... + 'desired_impedance_test_frequency', desired_impedance_test_frequency, ... + 'actual_impedance_test_frequency', actual_impedance_test_frequency ); + +% Define data structure for spike trigger settings. +spike_trigger_struct = struct( ... + 'voltage_trigger_mode', {}, ... + 'voltage_threshold', {}, ... + 'digital_trigger_channel', {}, ... + 'digital_edge_polarity', {} ); + +new_trigger_channel = struct(spike_trigger_struct); +spike_triggers = struct(spike_trigger_struct); + +% Define data structure for data channels. +channel_struct = struct( ... + 'native_channel_name', {}, ... + 'custom_channel_name', {}, ... + 'native_order', {}, ... + 'custom_order', {}, ... + 'board_stream', {}, ... + 'chip_channel', {}, ... + 'port_name', {}, ... + 'port_prefix', {}, ... + 'port_number', {}, ... + 'electrode_impedance_magnitude', {}, ... + 'electrode_impedance_phase', {} ); + +new_channel = struct(channel_struct); + +% Create structure arrays for each type of data channel. +amplifier_channels = struct(channel_struct); +aux_input_channels = struct(channel_struct); +supply_voltage_channels = struct(channel_struct); +board_adc_channels = struct(channel_struct); +board_dig_in_channels = struct(channel_struct); +board_dig_out_channels = struct(channel_struct); + +amplifier_index = 1; +aux_input_index = 1; +supply_voltage_index = 1; +board_adc_index = 1; +board_dig_in_index = 1; +board_dig_out_index = 1; + +% Read signal summary from data file header. + +number_of_signal_groups = fread(fid, 1, 'int16'); + +for signal_group = 1:number_of_signal_groups + signal_group_name = fread_QString_In(fid); + signal_group_prefix = fread_QString_In(fid); + signal_group_enabled = fread(fid, 1, 'int16'); + signal_group_num_channels = fread(fid, 1, 'int16'); + signal_group_num_amp_channels = fread(fid, 1, 'int16'); + + if (signal_group_num_channels > 0 && signal_group_enabled > 0) + new_channel(1).port_name = signal_group_name; + new_channel(1).port_prefix = signal_group_prefix; + new_channel(1).port_number = signal_group; + for signal_channel = 1:signal_group_num_channels + new_channel(1).native_channel_name = fread_QString_In(fid); + new_channel(1).custom_channel_name = fread_QString_In(fid); + new_channel(1).native_order = fread(fid, 1, 'int16'); + new_channel(1).custom_order = fread(fid, 1, 'int16'); + signal_type = fread(fid, 1, 'int16'); + channel_enabled = fread(fid, 1, 'int16'); + new_channel(1).chip_channel = fread(fid, 1, 'int16'); + new_channel(1).board_stream = fread(fid, 1, 'int16'); + new_trigger_channel(1).voltage_trigger_mode = fread(fid, 1, 'int16'); + new_trigger_channel(1).voltage_threshold = fread(fid, 1, 'int16'); + new_trigger_channel(1).digital_trigger_channel = fread(fid, 1, 'int16'); + new_trigger_channel(1).digital_edge_polarity = fread(fid, 1, 'int16'); + new_channel(1).electrode_impedance_magnitude = fread(fid, 1, 'single'); + new_channel(1).electrode_impedance_phase = fread(fid, 1, 'single'); + + if (channel_enabled) + switch (signal_type) + case 0 + amplifier_channels(amplifier_index) = new_channel; + spike_triggers(amplifier_index) = new_trigger_channel; + amplifier_index = amplifier_index + 1; + case 1 + aux_input_channels(aux_input_index) = new_channel; + aux_input_index = aux_input_index + 1; + case 2 + supply_voltage_channels(supply_voltage_index) = new_channel; + supply_voltage_index = supply_voltage_index + 1; + case 3 + board_adc_channels(board_adc_index) = new_channel; + board_adc_index = board_adc_index + 1; + case 4 + board_dig_in_channels(board_dig_in_index) = new_channel; + board_dig_in_index = board_dig_in_index + 1; + case 5 + board_dig_out_channels(board_dig_out_index) = new_channel; + board_dig_out_index = board_dig_out_index + 1; + otherwise + error('Unknown channel type'); + end + end + + end + end +end + +% Summarize contents of data file. +num_amplifier_channels = amplifier_index - 1; +num_aux_input_channels = aux_input_index - 1; +num_supply_voltage_channels = supply_voltage_index - 1; +num_board_adc_channels = board_adc_index - 1; +num_board_dig_in_channels = board_dig_in_index - 1; +num_board_dig_out_channels = board_dig_out_index - 1; + +% fprintf(1, 'Found %d amplifier channel%s.\n', ... +% num_amplifier_channels, plural(num_amplifier_channels)); +% fprintf(1, 'Found %d auxiliary input channel%s.\n', ... +% num_aux_input_channels, plural(num_aux_input_channels)); +% fprintf(1, 'Found %d supply voltage channel%s.\n', ... +% num_supply_voltage_channels, plural(num_supply_voltage_channels)); +% fprintf(1, 'Found %d board ADC channel%s.\n', ... +% num_board_adc_channels, plural(num_board_adc_channels)); +% fprintf(1, 'Found %d board digital input channel%s.\n', ... +% num_board_dig_in_channels, plural(num_board_dig_in_channels)); +% fprintf(1, 'Found %d board digital output channel%s.\n', ... +% num_board_dig_out_channels, plural(num_board_dig_out_channels)); +% fprintf(1, 'Found %d temperature sensors channel%s.\n', ... +% num_temp_sensor_channels, plural(num_temp_sensor_channels)); +% fprintf(1, '\n'); + +% Determine how many samples the data file contains. + +% Each data block contains 60 amplifier samples. +bytes_per_block = 60 * 4; % timestamp data +bytes_per_block = bytes_per_block + 60 * 2 * num_amplifier_channels; +% Auxiliary inputs are sampled 4x slower than amplifiers +bytes_per_block = bytes_per_block + 15 * 2 * num_aux_input_channels; +% Supply voltage is sampled 60x slower than amplifiers +bytes_per_block = bytes_per_block + 1 * 2 * num_supply_voltage_channels; +% Board analog inputs are sampled at same rate as amplifiers +bytes_per_block = bytes_per_block + 60 * 2 * num_board_adc_channels; +% Board digital inputs are sampled at same rate as amplifiers +if (num_board_dig_in_channels > 0) + bytes_per_block = bytes_per_block + 60 * 2; +end +% Board digital outputs are sampled at same rate as amplifiers +if (num_board_dig_out_channels > 0) + bytes_per_block = bytes_per_block + 60 * 2; +end +% Temp sensor is sampled 60x slower than amplifiers +if (num_temp_sensor_channels > 0) + bytes_per_block = bytes_per_block + 1 * 2 * num_temp_sensor_channels; +end + +% How many data blocks remain in this file? +data_present = 0; +bytes_remaining = filesize - ftell(fid); +if (bytes_remaining > 0) + data_present = 1; +end + +num_data_blocks = bytes_remaining / bytes_per_block; + +num_amplifier_samples = 60 * num_data_blocks; +num_aux_input_samples = 15 * num_data_blocks; +num_supply_voltage_samples = 1 * num_data_blocks; +num_board_adc_samples = 60 * num_data_blocks; +num_board_dig_in_samples = 60 * num_data_blocks; +num_board_dig_out_samples = 60 * num_data_blocks; + +record_time = num_amplifier_samples / sample_rate; + +% if (data_present) +% fprintf(1, 'File contains %0.3f seconds of data. Amplifiers were sampled at %0.2f kS/s.\n', ... +% record_time, sample_rate / 1000); +% fprintf(1, '\n'); +% else +% fprintf(1, 'Header file contains no data. Amplifiers were sampled at %0.2f kS/s.\n', ... +% sample_rate / 1000); +% fprintf(1, '\n'); +% end + +if (data_present) + + % Pre-allocate memory for data. + fprintf(1, 'Allocating memory for data...\n'); + + t_amplifier = zeros(1, num_amplifier_samples); + + amplifier_data = zeros(num_amplifier_channels, num_amplifier_samples); + aux_input_data = zeros(num_aux_input_channels, num_aux_input_samples); + supply_voltage_data = zeros(num_supply_voltage_channels, num_supply_voltage_samples); + temp_sensor_data = zeros(num_temp_sensor_channels, num_supply_voltage_samples); + board_adc_data = zeros(num_board_adc_channels, num_board_adc_samples); + board_dig_in_data = zeros(num_board_dig_in_channels, num_board_dig_in_samples); + board_dig_in_raw = zeros(1, num_board_dig_in_samples); + board_dig_out_data = zeros(num_board_dig_out_channels, num_board_dig_out_samples); + board_dig_out_raw = zeros(1, num_board_dig_out_samples); + + % Read sampled data from file. + fprintf(1, 'Reading data from file...\n'); + + amplifier_index = 1; + aux_input_index = 1; + supply_voltage_index = 1; + board_adc_index = 1; + board_dig_in_index = 1; + board_dig_out_index = 1; + + print_increment = 10; + percent_done = print_increment; + for i=1:num_data_blocks + % In version 1.2, we moved from saving timestamps as unsigned + % integeters to signed integers to accomidate negative (adjusted) + % timestamps for pretrigger data. + if ((data_file_main_version_number == 1 && data_file_secondary_version_number >= 2) ... + || (data_file_main_version_number > 1)) + t_amplifier(amplifier_index:(amplifier_index+59)) = fread(fid, 60, 'int32'); + else + t_amplifier(amplifier_index:(amplifier_index+59)) = fread(fid, 60, 'uint32'); + end + if (num_amplifier_channels > 0) + amplifier_data(:, amplifier_index:(amplifier_index+59)) = fread(fid, [60, num_amplifier_channels], 'uint16')'; + end + if (num_aux_input_channels > 0) + aux_input_data(:, aux_input_index:(aux_input_index+14)) = fread(fid, [15, num_aux_input_channels], 'uint16')'; + end + if (num_supply_voltage_channels > 0) + supply_voltage_data(:, supply_voltage_index) = fread(fid, [1, num_supply_voltage_channels], 'uint16')'; + end + if (num_temp_sensor_channels > 0) + temp_sensor_data(:, supply_voltage_index) = fread(fid, [1, num_temp_sensor_channels], 'int16')'; + end + if (num_board_adc_channels > 0) + board_adc_data(:, board_adc_index:(board_adc_index+59)) = fread(fid, [60, num_board_adc_channels], 'uint16')'; + end + if (num_board_dig_in_channels > 0) + board_dig_in_raw(board_dig_in_index:(board_dig_in_index+59)) = fread(fid, 60, 'uint16'); + end + if (num_board_dig_out_channels > 0) + board_dig_out_raw(board_dig_out_index:(board_dig_out_index+59)) = fread(fid, 60, 'uint16'); + end + + amplifier_index = amplifier_index + 60; + aux_input_index = aux_input_index + 15; + supply_voltage_index = supply_voltage_index + 1; + board_adc_index = board_adc_index + 60; + board_dig_in_index = board_dig_in_index + 60; + board_dig_out_index = board_dig_out_index + 60; + + fraction_done = 100 * (i / num_data_blocks); + if (fraction_done >= percent_done) + fprintf(1, '%d%% done...\n', percent_done); + percent_done = percent_done + print_increment; + end + end + + % Make sure we have read exactly the right amount of data. + bytes_remaining = filesize - ftell(fid); + if (bytes_remaining ~= 0) + %error('Error: End of file not reached.'); + end + +end + +% Close data file. +fclose(fid); + +if (data_present) + + fprintf(1, 'Parsing data...\n'); + + % Extract digital input channels to separate variables. + for i=1:num_board_dig_in_channels + mask = 2^(board_dig_in_channels(i).native_order) * ones(size(board_dig_in_raw)); + board_dig_in_data(i, :) = (bitand(board_dig_in_raw, mask) > 0); + end + for i=1:num_board_dig_out_channels + mask = 2^(board_dig_out_channels(i).native_order) * ones(size(board_dig_out_raw)); + board_dig_out_data(i, :) = (bitand(board_dig_out_raw, mask) > 0); + end + + % Scale voltage levels appropriately. + amplifier_data = 0.195 * (amplifier_data - 32768); % units = microvolts + aux_input_data = 37.4e-6 * aux_input_data; % units = volts + supply_voltage_data = 74.8e-6 * supply_voltage_data; % units = volts + if (eval_board_mode == 1) + board_adc_data = 152.59e-6 * (board_adc_data - 32768); % units = volts + else + board_adc_data = 50.354e-6 * board_adc_data; % units = volts + end + temp_sensor_data = temp_sensor_data / 100; % units = deg C + + % Check for gaps in timestamps. + num_gaps = sum(diff(t_amplifier) ~= 1); + if (num_gaps == 0) + fprintf(1, 'No missing timestamps in data.\n'); + else + fprintf(1, 'Warning: %d gaps in timestamp data found. Time scale will not be uniform!\n', ... + num_gaps); + end + + % Scale time steps (units = seconds). + t_amplifier = t_amplifier / sample_rate; + t_aux_input = t_amplifier(1:4:end); + t_supply_voltage = t_amplifier(1:60:end); + t_board_adc = t_amplifier; + t_dig = t_amplifier; + t_temp_sensor = t_supply_voltage; + + % If the software notch filter was selected during the recording, apply the + % same notch filter to amplifier data here. + if (notch_filter_frequency > 0) + fprintf(1, 'Applying notch filter...\n'); + + print_increment = 10; + percent_done = print_increment; + for i=1:num_amplifier_channels + amplifier_data(i,:) = ... + notch_filter_In(amplifier_data(i,:), sample_rate, notch_filter_frequency, 10); + + fraction_done = 100 * (i / num_amplifier_channels); + if (fraction_done >= percent_done) + fprintf(1, '%d%% done...\n', percent_done); + percent_done = percent_done + print_increment; + end + + end + end + +end + +% Move variables to base workspace. + +move_to_base_workspace_In(notes); +move_to_base_workspace_In(frequency_parameters); + +if (num_amplifier_channels > 0) + move_to_base_workspace_In(amplifier_channels); + if (data_present) + move_to_base_workspace_In(amplifier_data); + move_to_base_workspace_In(t_amplifier); + end + move_to_base_workspace_In(spike_triggers); +end +if (num_aux_input_channels > 0) + move_to_base_workspace_In(aux_input_channels); + if (data_present) + move_to_base_workspace_In(aux_input_data); + move_to_base_workspace_In(t_aux_input); + end +end +if (num_supply_voltage_channels > 0) + move_to_base_workspace_In(supply_voltage_channels); + if (data_present) + move_to_base_workspace_In(supply_voltage_data); + move_to_base_workspace_In(t_supply_voltage); + end +end +if (num_board_adc_channels > 0) + move_to_base_workspace_In(board_adc_channels); + if (data_present) + move_to_base_workspace_In(board_adc_data); + move_to_base_workspace_In(t_board_adc); + end +end +if (num_board_dig_in_channels > 0) + move_to_base_workspace_In(board_dig_in_channels); + if (data_present) + move_to_base_workspace_In(board_dig_in_data); + move_to_base_workspace_In(t_dig); + end +end +if (num_board_dig_out_channels > 0) + move_to_base_workspace_In(board_dig_out_channels); + if (data_present) + move_to_base_workspace_In(board_dig_out_data); + move_to_base_workspace_In(t_dig); + end +end +if (num_temp_sensor_channels > 0) + if (data_present) + move_to_base_workspace_In(temp_sensor_data); + move_to_base_workspace_In(t_temp_sensor); + end +end + +% fprintf(1, 'Done! Elapsed time: %0.1f seconds\n', toc); +% if (data_present) +% fprintf(1, 'Extracted data are now available in the MATLAB workspace.\n'); +% else +% fprintf(1, 'Extracted waveform information is now available in the MATLAB workspace.\n'); +% end +% fprintf(1, 'Type ''whos'' to see variables.\n'); +% fprintf(1, '\n'); + +return + + +function a = fread_QString_In(fid) + +% a = read_QString(fid) +% +% Read Qt style QString. The first 32-bit unsigned number indicates +% the length of the string (in bytes). If this number equals 0xFFFFFFFF, +% the string is null. + +a = ''; +length = fread(fid, 1, 'uint32'); +if length == hex2num('ffffffff') + return; +end +% convert length from bytes to 16-bit Unicode words +length = length / 2; + +for i=1:length + a(i) = fread(fid, 1, 'uint16'); +end + +return + + +function s = plural_In(n) + +% s = plural(n) +% +% Utility function to optionally plurailze words based on the value +% of n. + +if (n == 1) + s = ''; +else + s = 's'; +end + +return + + +function out = notch_filter_In(in, fSample, fNotch, Bandwidth) + +% out = notch_filter(in, fSample, fNotch, Bandwidth) +% +% Implements a notch filter (e.g., for 50 or 60 Hz) on vector 'in'. +% fSample = sample rate of data (in Hz or Samples/sec) +% fNotch = filter notch frequency (in Hz) +% Bandwidth = notch 3-dB bandwidth (in Hz). A bandwidth of 10 Hz is +% recommended for 50 or 60 Hz notch filters; narrower bandwidths lead to +% poor time-domain properties with an extended ringing response to +% transient disturbances. +% +% Example: If neural data was sampled at 30 kSamples/sec +% and you wish to implement a 60 Hz notch filter: +% +% out = notch_filter(in, 30000, 60, 10); + +tstep = 1/fSample; +Fc = fNotch*tstep; + +L = length(in); + +% Calculate IIR filter parameters +d = exp(-2*pi*(Bandwidth/2)*tstep); +b = (1 + d*d)*cos(2*pi*Fc); +a0 = 1; +a1 = -b; +a2 = d*d; +a = (1 + d*d)/2; +b0 = 1; +b1 = -2*cos(2*pi*Fc); +b2 = 1; + +out = zeros(size(in)); +out(1) = in(1); +out(2) = in(2); +% (If filtering a continuous data stream, change out(1) and out(2) to the +% previous final two values of out.) + +% Run filter +for i=3:L + out(i) = (a*b2*in(i-2) + a*b1*in(i-1) + a*b0*in(i) - a2*out(i-2) - a1*out(i-1))/a0; +end + +return + + +function move_to_base_workspace_In(variable) + +% move_to_base_workspace(variable) +% +% Move variable from function workspace to base MATLAB workspace so +% user will have access to it after the program ends. + +variable_name = inputname(1); +assignin('base', variable_name, variable); + +return; \ No newline at end of file diff --git a/utilities/fileConversions/ConvertPhyKilo2Neurosuite.m b/utilities/fileConversions/ConvertPhyKilo2Neurosuite.m index ebba0988..6d5e9a0f 100755 --- a/utilities/fileConversions/ConvertPhyKilo2Neurosuite.m +++ b/utilities/fileConversions/ConvertPhyKilo2Neurosuite.m @@ -1,22 +1,40 @@ function ConvertPhyKilo2Neurosuite(basepath,basename) % Converts Phy kilosort output files into klusters-compatible -% fet,res,clu,spk files. Works on a single shank of a recording, assumes a -% 16bit .dat, .xml file is present in "basepath" (home folder) and -% that they are named basename.dat and basename.xml. -% It assumes that your directory contains: -% spike_clusters.npy, spikes_times.npy, cluster_group.tsv, pcfeatures.npy, -% templates.npy, cluster_ids.npy and shanks.npy (these last two are -% generated by a phy plugin -Export Shanks- found in the lab wiki) +% fet,res,clu,spk files. assumes a 16bit .dat, .xml file is present in +% "basepath" (home folder) and that they are named basename.dat and +% basename.xml. It assumes that your directory is structured as follow: +% +% ### +% . +% +-- _folder(basename) +% | +-- basename.dat +% | +-- basename.xml +% | +-- _Kilosort_date_time +% | | +-- spike_clusters.npy +% | | +-- spike_times.npy +% | | +-- cluster_group.tsv +% | | +-- pc_features.npy +% | | +-- templates.npy +% | | +-- rez.mat +% | | +-- cluster_ids.npy +% | | +-- shanks.npy +% +% ### +% +% cluster_ids.npy and shanks.npy are generated by a phy plugin +% -Export Shanks- found in the lab wiki) % % Inputs: % basepath - directory path to the main recording folder with .dat and .xml -% as well as shank folders made by makeProbeMapKlusta2.m (default is -% current directory matlab is pointed to) +% as well as kilosort folder generated by Kilosortwrapper % basename - shared file name of .dat and .xml (default is last part of % current directory path, ie most immediate folder name) % +% % Eliezyer de Oliveira, 2018 +% +% - reviewed in 03/2019 if ~exist('basepath','var') @@ -25,8 +43,20 @@ function ConvertPhyKilo2Neurosuite(basepath,basename) end savepath = basepath; +%finding the last Kilosort folder in order +auxDir = dir; +auxKSD = find([auxDir.isdir]); +for i = auxKSD + if strfind(auxDir(i).name,'Kilosort') + KSdir = auxDir(i).name; + end +end + +%loading phy files +cd(KSdir); + if ~exist('rez','var'); - load(fullfile(basepath,'rez.mat')) + load(fullfile(basepath,KSdir,'rez.mat')) end Nchan = rez.ops.Nchan; @@ -38,7 +68,7 @@ function ConvertPhyKilo2Neurosuite(basepath,basename) % xcoords = ones(Nchan, 1); % ycoords = (1:Nchan)'; -par = LoadParameters(fullfile([basepath],[basename '.xml'])); +par = LoadParameters(fullfile([basepath],[basename '.xml'])); %this is load differently the xml file than before (EFO 3/3/2019) totalch = par.nChannels; sbefore = 16;%samples before/after for spike extraction @@ -95,11 +125,12 @@ function ConvertPhyKilo2Neurosuite(basepath,basename) pcFeatures = pcFeatures(auxiliarC,:,:); % pcFeatureInds = uint32(readNPY('pc_feature_ind.npy'))'; templates = readNPY('templates.npy'); +shanks = readNPY('shanks.npy'); +cluShank = readNPY('cluster_ids.npy'); +cd(basepath) mkdir(fullfile(savepath,'PhyClus')) %% assigning cluster ids to shanks -shanks = readNPY('shanks.npy'); -cluShank = readNPY('cluster_ids.npy'); auxC = unique(clu); templateshankassignments = zeros(size(auxC)); @@ -215,7 +246,7 @@ function ConvertPhyKilo2Neurosuite(basepath,basename) tforig = pct(i,:,:);%the subset of spikes with this clu ide tfnew = tforig; %will overwrite - ii = tdx(:,16);%handling nan cases where the template channel used was not in the shank + ii = tdx(tc,:);%handling nan cases where the template channel used was not in the shank gixs = ~isnan(ii);%good vs bad channels... those shank channels that were vs were not found in template pc channels bixs = isnan(ii); g = ii(gixs); @@ -223,7 +254,7 @@ function ConvertPhyKilo2Neurosuite(basepath,basename) tfnew(:,:,gixs) = tforig(:,:,gixs);%replace ok elements tfnew(:,:,bixs) = 0;%zero out channels that are not on this shank try - fets(i,:,:) = tfnew(:,:,1:size(pcFeatures,3)); + fets(i,:,1:length(par.spikeGroups.groups{groupidx})) = tfnew(:,:,1:length(par.spikeGroups.groups{groupidx})); catch keyboard end diff --git a/visualization/colormaps/bluewhitered.m b/visualization/colormaps/bluewhitered.m new file mode 100644 index 00000000..2f59c4a2 --- /dev/null +++ b/visualization/colormaps/bluewhitered.m @@ -0,0 +1,122 @@ +function newmap = bluewhitered(m) +%BLUEWHITERED Blue, white, and red color map. +% BLUEWHITERED(M) returns an M-by-3 matrix containing a blue to white +% to red colormap, with white corresponding to the CAXIS value closest +% to zero. This colormap is most useful for images and surface plots +% with positive and negative values. BLUEWHITERED, by itself, is the +% same length as the current colormap. +% +% Examples: +% ------------------------------ +% figure +% imagesc(peaks(250)); +% colormap(bluewhitered(256)), colorbar +% +% figure +% imagesc(peaks(250), [0 8]) +% colormap(bluewhitered), colorbar +% +% figure +% imagesc(peaks(250), [-6 0]) +% colormap(bluewhitered), colorbar +% +% figure +% surf(peaks) +% colormap(bluewhitered) +% axis tight +% +% See also HSV, HOT, COOL, BONE, COPPER, PINK, FLAG, +% COLORMAP, RGBPLOT. + + +if nargin < 1 + m = size(get(gcf,'colormap'),1); +end + + +bottom = [0 0 0.5]; +botmiddle = [0 0.5 1]; +middle = [1 1 1]; +topmiddle = [1 0 0]; +top = [0.5 0 0]; + +% Find middle +lims = get(gca, 'CLim'); + +% Find ratio of negative to positive +if (lims(1) < 0) & (lims(2) > 0) + % It has both negative and positive + % Find ratio of negative to positive + ratio = abs(lims(1)) / (abs(lims(1)) + lims(2)); + neglen = round(m*ratio); + poslen = m - neglen; + + % Just negative + new = [bottom; botmiddle; middle]; + len = length(new); + oldsteps = linspace(0, 1, len); + newsteps = linspace(0, 1, neglen); + newmap1 = zeros(neglen, 3); + + for i=1:3 + % Interpolate over RGB spaces of colormap + newmap1(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); + end + + % Just positive + new = [middle; topmiddle; top]; + len = length(new); + oldsteps = linspace(0, 1, len); + newsteps = linspace(0, 1, poslen); + newmap = zeros(poslen, 3); + + for i=1:3 + % Interpolate over RGB spaces of colormap + newmap(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); + end + + % And put 'em together + newmap = [newmap1; newmap]; + +elseif lims(1) >= 0 + % Just positive + new = [middle; topmiddle; top]; + len = length(new); + oldsteps = linspace(0, 1, len); + newsteps = linspace(0, 1, m); + newmap = zeros(m, 3); + + for i=1:3 + % Interpolate over RGB spaces of colormap + newmap(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); + end + +else + % Just negative + new = [bottom; botmiddle; middle]; + len = length(new); + oldsteps = linspace(0, 1, len); + newsteps = linspace(0, 1, m); + newmap = zeros(m, 3); + + for i=1:3 + % Interpolate over RGB spaces of colormap + newmap(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); + end + +end +% +% m = 64; +% new = [bottom; botmiddle; middle; topmiddle; top]; +% % x = 1:m; +% +% oldsteps = linspace(0, 1, 5); +% newsteps = linspace(0, 1, m); +% newmap = zeros(m, 3); +% +% for i=1:3 +% % Interpolate over RGB spaces of colormap +% newmap(:,i) = min(max(interp1(oldsteps, new(:,i), newsteps)', 0), 1); +% end +% +% % set(gcf, 'colormap', newmap), colorbar \ No newline at end of file