From 3348bd5a5094843eeec2995ef19fc01f8005ff5c Mon Sep 17 00:00:00 2001 From: Eliezyer Date: Wed, 13 Mar 2019 16:30:15 -0400 Subject: [PATCH 01/32] Updating the function to be more general Now it does the conversion without need of moving files from folders. --- .../ConvertPhyKilo2Neurosuite.m | 61 ++++++++++++++----- 1 file changed, 46 insertions(+), 15 deletions(-) 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 From 830a47654e532b2dc54364b5e4f9b2569ac2879b Mon Sep 17 00:00:00 2001 From: Eliezyer Date: Thu, 23 May 2019 22:10:30 -0400 Subject: [PATCH 02/32] Update bz_PhaseModulation update on bz_PhaseModulation to work properly when wavelet method is selected --- analysis/lfp_spikes/PhaseModulation/bz_PhaseModulation.m | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/analysis/lfp_spikes/PhaseModulation/bz_PhaseModulation.m b/analysis/lfp_spikes/PhaseModulation/bz_PhaseModulation.m index 3bac0fe5..abf10cca 100755 --- a/analysis/lfp_spikes/PhaseModulation/bz_PhaseModulation.m +++ b/analysis/lfp_spikes/PhaseModulation/bz_PhaseModulation.m @@ -101,14 +101,15 @@ [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 + 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 % % case ('peaks') % not yet coded % filter, smooth, diff = 0, diffdiff = negative end %% update intervals to remove sub-threshold power periods +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 @@ -132,7 +133,8 @@ % now merge interval sets from input and power threshold intervals = SubtractIntervals(intervals,below_thresh); % subtract out low power intervals - +end +minWidth = (samplingRate./passband(2)) * 2; intervals = intervals(diff(intervals')>minWidth./samplingRate,:); % only keep min width epochs From 8ccf8fecbacac9f5352bc355ee530d70560ded7e Mon Sep 17 00:00:00 2001 From: Eliezyer Date: Wed, 21 Aug 2019 22:48:19 -0400 Subject: [PATCH 03/32] Update bz_PhaseModulation.m Adding the same process to power threshold in wavelet as in hilbert --- .../PhaseModulation/bz_PhaseModulation.m | 149 ++++++++++-------- 1 file changed, 86 insertions(+), 63 deletions(-) diff --git a/analysis/lfp_spikes/PhaseModulation/bz_PhaseModulation.m b/analysis/lfp_spikes/PhaseModulation/bz_PhaseModulation.m index abf10cca..35e6db50 100755 --- a/analysis/lfp_spikes/PhaseModulation/bz_PhaseModulation.m +++ b/analysis/lfp_spikes/PhaseModulation/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; @@ -80,59 +80,82 @@ switch lower(method) case ('hilbert') [b a] = butter(4,[passband(1)/(samplingRate/2) passband(2)/(samplingRate/2)],'bandpass'); -% [b a] = cheby2(4,20,passband/(samplingRate/2)); + % [b a] = cheby2(4,20,passband/(samplingRate/2)); filt = FiltFiltM(b,a,double(lfp.data(:,1))); power = fastrms(filt,ceil(samplingRate./passband(1))); % approximate power is frequency band hilb = hilbert(filt); 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 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 -% % case ('peaks') + power = rms(abs(wave))'; + % % case ('peaks') % not yet coded % filter, smooth, diff = 0, diffdiff = negative end %% update intervals to remove sub-threshold power periods 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 + 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 minWidth = (samplingRate./passband(2)) * 2; intervals = intervals(diff(intervals')>minWidth./samplingRate,:); % only keep min width epochs @@ -147,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; @@ -158,31 +181,31 @@ spkphases{a} = nan; else spkphases{a} = lfpphase(ceil(s*samplingRate)); - -% cum_spkphases = vertcat(cum_spkphases, spkphases{a}); - - %% Gather binned counts and stats (incl Rayleigh Test) + + % cum_spkphases = vertcat(cum_spkphases, spkphases{a}); + + %% Gather binned counts and stats (incl Rayleigh Test) [phasedistros(:,a),phasebins,ps]=CircularDistribution(spkphases{a},'nBins',numBins); phasestats.m(a) = mod(ps.m,2*pi); phasestats.r(a) = ps.r; 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)]); @@ -195,17 +218,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']); @@ -218,14 +241,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 From 32db1671395b597597eeaa45fbafa4e7a463f3ae Mon Sep 17 00:00:00 2001 From: AntonioFR Date: Mon, 28 Oct 2019 20:10:27 -0400 Subject: [PATCH 04/32] new place field function 1 --- .../spikes/placeFields/bz_findPlaceFields1D.m | 43 +++++++++++++++++++ .../bz_processConvertOptitrack2Behav.m | 2 +- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 analysis/spikes/placeFields/bz_findPlaceFields1D.m diff --git a/analysis/spikes/placeFields/bz_findPlaceFields1D.m b/analysis/spikes/placeFields/bz_findPlaceFields1D.m new file mode 100644 index 00000000..507a8ce8 --- /dev/null +++ b/analysis/spikes/placeFields/bz_findPlaceFields1D.m @@ -0,0 +1,43 @@ +function [mapStats] = bz_findPlaceFields1D(firingMaps) +% [stats] = bz_findPlaceFields1D(firingMaps) +% Find place fields from 1D firing maps + +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% 'smooth' smoothing size in bins (0 = no smoothing, default = 2) +% 'nBins' number of bins (default = 50) +% 'minTime' minimum time spent in each bin (in s, default = 0) +% '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) +% 'type' 'linear' for linear data, 'circular' for angular data +% (default 'linear') +% 'threshold' values above threshold*peak belong to the field +% (default = 0.2) +% 'minSize' fields smaller than this size are considered spurious +% and ignored (default = 10) +% 'minPeak' peaks smaller than this size are considered spurious +% and ignored (default = 1) +% 'verbose' display processing information (default = 'off') +% ========================================================================= + +%% Parse arguments + + +%% Calculate +for unit = 1:length(spikes.times) + for c = 1:conditions + mapStats{unit}{c} = MapStats(firingMaps.rateMaps{unit}{c}); + end +end + + +%% Write output + + + +end + diff --git a/preprocessing/positionTracking/optitrack/bz_processConvertOptitrack2Behav.m b/preprocessing/positionTracking/optitrack/bz_processConvertOptitrack2Behav.m index c8302d92..bb15fe71 100755 --- a/preprocessing/positionTracking/optitrack/bz_processConvertOptitrack2Behav.m +++ b/preprocessing/positionTracking/optitrack/bz_processConvertOptitrack2Behav.m @@ -139,7 +139,7 @@ % The system sometimes (rarely) keeps on recording a few frames after software stopped % recording. So we skip the last frames of the TTL -if length(frameT) Date: Mon, 28 Oct 2019 20:11:50 -0400 Subject: [PATCH 05/32] Revert "new place field function 1" This reverts commit 32db1671395b597597eeaa45fbafa4e7a463f3ae. --- .../spikes/placeFields/bz_findPlaceFields1D.m | 43 ------------------- .../bz_processConvertOptitrack2Behav.m | 2 +- 2 files changed, 1 insertion(+), 44 deletions(-) delete mode 100644 analysis/spikes/placeFields/bz_findPlaceFields1D.m diff --git a/analysis/spikes/placeFields/bz_findPlaceFields1D.m b/analysis/spikes/placeFields/bz_findPlaceFields1D.m deleted file mode 100644 index 507a8ce8..00000000 --- a/analysis/spikes/placeFields/bz_findPlaceFields1D.m +++ /dev/null @@ -1,43 +0,0 @@ -function [mapStats] = bz_findPlaceFields1D(firingMaps) -% [stats] = bz_findPlaceFields1D(firingMaps) -% Find place fields from 1D firing maps - -% ========================================================================= -% Properties Values -% ------------------------------------------------------------------------- -% 'smooth' smoothing size in bins (0 = no smoothing, default = 2) -% 'nBins' number of bins (default = 50) -% 'minTime' minimum time spent in each bin (in s, default = 0) -% '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) -% 'type' 'linear' for linear data, 'circular' for angular data -% (default 'linear') -% 'threshold' values above threshold*peak belong to the field -% (default = 0.2) -% 'minSize' fields smaller than this size are considered spurious -% and ignored (default = 10) -% 'minPeak' peaks smaller than this size are considered spurious -% and ignored (default = 1) -% 'verbose' display processing information (default = 'off') -% ========================================================================= - -%% Parse arguments - - -%% Calculate -for unit = 1:length(spikes.times) - for c = 1:conditions - mapStats{unit}{c} = MapStats(firingMaps.rateMaps{unit}{c}); - end -end - - -%% Write output - - - -end - diff --git a/preprocessing/positionTracking/optitrack/bz_processConvertOptitrack2Behav.m b/preprocessing/positionTracking/optitrack/bz_processConvertOptitrack2Behav.m index bb15fe71..c8302d92 100755 --- a/preprocessing/positionTracking/optitrack/bz_processConvertOptitrack2Behav.m +++ b/preprocessing/positionTracking/optitrack/bz_processConvertOptitrack2Behav.m @@ -139,7 +139,7 @@ % The system sometimes (rarely) keeps on recording a few frames after software stopped % recording. So we skip the last frames of the TTL -if length(frameT) Date: Mon, 28 Oct 2019 20:13:20 -0400 Subject: [PATCH 06/32] new place field function 1 --- analysis/spikes/placeFields/bz_firingMapAvg.m | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 analysis/spikes/placeFields/bz_firingMapAvg.m diff --git a/analysis/spikes/placeFields/bz_firingMapAvg.m b/analysis/spikes/placeFields/bz_firingMapAvg.m new file mode 100644 index 00000000..b750b3fa --- /dev/null +++ b/analysis/spikes/placeFields/bz_firingMapAvg.m @@ -0,0 +1,105 @@ +function [firingMaps] = bz_firingMapAvg(positions,spikes,varargin) + +% USAGE +% [firingMaps] = bz_firingMapAvg(positions,spikes,varargin) +% Calculates averaged firing map for a set of linear postions +% +% INPUTS +% +% spikes - buzcode format .cellinfo. struct with the following fields +% .times +% positions - [t x y ] or [t x] position matrix or +% cell with several of these matrices (for different conditions) +% or +% behavior - buzcode format behavior struct - NOT YET IMPLEMENTED +% 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) +% 'minTime' minimum time spent in each bin (in s, default = 0) +% '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) +% 'type' 'linear' for linear data, 'circular' for angular data +% (default 'linear') +% 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 +% +% Antonio FR, 10/2019 + +%% parse inputs +p=inputParser; +addParameter(p,'smooth',2,@isnumeric); +addParameter(p,'nBins',50,@isnumeric); +addParameter(p,'maxGap',0.1,@isnumeric); +addParameter(p,'minTime',0,@isnumeric); +addParameter(p,'type','linear',@isstr); +addParameter(p,'saveMat',false,@islogical); + +parse(p,varargin{:}); +smooth = p.Results.smooth; +nBins = p.Results.nBins; +maxGap = p.Results.maxGap; +minTime = p.Results.minTime; +type = p.Results.type; +saveMat = p.Results.saveMat; + +% number of conditions + if iscell(positions) + conditions = length(positions); + elseif isvector(positions) + conditions = 1; + end + %%% TODO: conditions label + +%% Calculate +for unit = 1:length(spikes.times) + for c = 1:conditions + map{unit}{c} = Map(positions{c},spikes.times{unit},... + 'smooth',smooth,'nBins',nBins,'maxGap',maxGap,'minTime',minTime); + end +end +%%% TODO: pass rest of inputs to Map + +%% restructure into cell info data type + +% inherit required fields from spikes cellinfo struct +firingMaps.UID = spikes.UID; +firingMaps.sessionName = spikes.sessionName; +try +firingMaps.region = spikes.region; +catch + %warning('spikes.region is missing') +end + +firingMaps.params.smooth = smooth; +firingMaps.params.nBins = nBins; +firingMaps.params.maxGap = maxGap; + +for unit = 1:length(spikes.times) + for c = 1:conditions + firingMaps.rateMaps{unit,1}{c} = map{unit}{c}.z; + firingMaps.countMaps{unit,1}{c} = map{unit}{c}.count; + firingMaps.occupancy{unit,1}{c} = map{unit}{c}.time; + end +end + +if saveMat + save([firingMaps.sessionName '.firingMapsAvg.cellinfo.mat'],'firingMaps'); +end + +end From 8b0e855d1deb8193efae16225314f067894981bb Mon Sep 17 00:00:00 2001 From: valegarman Date: Tue, 29 Oct 2019 12:34:23 -0400 Subject: [PATCH 07/32] Update bz_GetSpikes.m --- io/bz_GetSpikes.m | 356 ++++++++++++++++++++++++++++------------------ 1 file changed, 216 insertions(+), 140 deletions(-) diff --git a/io/bz_GetSpikes.m b/io/bz_GetSpikes.m index 0b7b2903..f506632e 100755 --- a/io/bz_GetSpikes.m +++ b/io/bz_GetSpikes.m @@ -21,6 +21,7 @@ % 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) % % OUTPUTS % @@ -36,6 +37,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 +65,7 @@ % % % written by David Tingley, 2017 +% added compatibility with Phy by Manu Valero, 2019 (previos bz_LoadPhy) %% Deal With Inputs spikeGroupsValidation = @(x) assert(isnumeric(x) || strcmp(x,'all'),... 'spikeGroups must be numeric or "all"'); @@ -74,9 +77,10 @@ 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); parse(p,varargin{:}) @@ -89,12 +93,12 @@ saveMat = p.Results.saveMat; noPrompts = p.Results.noPrompts; onlyLoad = p.Results.onlyLoad; +verbose = p.Results.verbose; [sessionInfo] = bz_getSessionInfo(basepath, 'noPrompts', noPrompts); baseName = bz_BasenameFromBasepath(basepath); - spikes.samplingRate = sessionInfo.rates.wideband; nChannels = sessionInfo.nChannels; @@ -134,155 +138,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 ~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 -% 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 -% 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 + % use the .clu files to get spike ID's and generate UID and spikeGroup + % use the .res files to get spike times + count = 1; -% use the .clu files to get spike ID's and generate UID and spikeGroup -% use the .res files to get spike times -count = 1; + 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 -if isempty(sessionInfo.spikeGroups.groups) - sessionInfo.spikeGroups = sessionInfo.AnatGrps; -end -for i=1:length(cluFiles) - disp(['working on ' cluFiles(i).name]) + 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 + + 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 ~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); + % spikes.amplitudes{jj} = double(spike_amplitudes(ids)); + 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; + % get waveforms + if getWaveforms + 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 - end - clear a aa b bb - end - - count = count + 1; + 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 ~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 @@ -299,16 +381,11 @@ spikes = removeCells(toRemove,spikes,getWaveforms); end - -spikes.sessionName = sessionInfo.FileName; -end - %% save to buzcode format (before exclusions) if saveMat save(cellinfofile,'spikes') end - %% EXCLUSIONS %% %filter by spikeGroups input @@ -355,10 +432,9 @@ spikes = []; end - - end +end %% function spikes = removeCells(toRemove,spikes,getWaveforms) From d8a984df4ffcf83d83062f15dfcce69b9d5addc6 Mon Sep 17 00:00:00 2001 From: valegarman Date: Tue, 29 Oct 2019 12:45:18 -0400 Subject: [PATCH 08/32] Revert "Update bz_GetSpikes.m" This reverts commit 8b0e855d1deb8193efae16225314f067894981bb. --- io/bz_GetSpikes.m | 356 ++++++++++++++++++---------------------------- 1 file changed, 140 insertions(+), 216 deletions(-) diff --git a/io/bz_GetSpikes.m b/io/bz_GetSpikes.m index f506632e..0b7b2903 100755 --- a/io/bz_GetSpikes.m +++ b/io/bz_GetSpikes.m @@ -21,7 +21,6 @@ % 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) % % OUTPUTS % @@ -37,7 +36,6 @@ % .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 % @@ -65,7 +63,6 @@ % % % written by David Tingley, 2017 -% added compatibility with Phy by Manu Valero, 2019 (previos bz_LoadPhy) %% Deal With Inputs spikeGroupsValidation = @(x) assert(isnumeric(x) || strcmp(x,'all'),... 'spikeGroups must be numeric or "all"'); @@ -77,10 +74,9 @@ addParameter(p,'basepath',pwd,@isstr); addParameter(p,'getWaveforms',true) addParameter(p,'forceReload',false,@islogical); -addParameter(p,'saveMat',true,@islogical); +addParameter(p,'saveMat',false,@islogical); addParameter(p,'noPrompts',false,@islogical); addParameter(p,'onlyLoad',[]); -addParameter(p,'verbose',true,@islogical); parse(p,varargin{:}) @@ -93,12 +89,12 @@ saveMat = p.Results.saveMat; noPrompts = p.Results.noPrompts; onlyLoad = p.Results.onlyLoad; -verbose = p.Results.verbose; [sessionInfo] = bz_getSessionInfo(basepath, 'noPrompts', noPrompts); baseName = bz_BasenameFromBasepath(basepath); + spikes.samplingRate = sessionInfo.rates.wideband; nChannels = sessionInfo.nChannels; @@ -138,233 +134,155 @@ if strcmp(savebutton,'Yes'); saveMat = true; end end - % 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 - kilosort_path = dir([basepath filesep '*kilosort*']); +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 -if ~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 + +% 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 - 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 +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 - 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 +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 - spkFiles(tempFiles==1)=[]; - end - - if isempty(cluFiles) - disp('no clu files found...') - spikes = []; - return end + spkFiles(tempFiles==1)=[]; +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 +if isempty(cluFiles) + disp('no clu files found...') + spikes = []; + return +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 - % use the .clu files to get spike ID's and generate UID and spikeGroup - % use the .res files to get spike times - count = 1; +% 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 - 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 +% 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 - 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 +% use the .clu files to get spike ID's and generate UID and spikeGroup +% use the .res files to get spike times +count = 1; - count = count + 1; - end - end - spikes.sessionName = sessionInfo.FileName; - -elseif ~isempty(kilosort_path) % LOADING FROM KILOSORT - - 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 +if isempty(sessionInfo.spikeGroups.groups) + sessionInfo.spikeGroups = sessionInfo.AnatGrps; +end +for i=1:length(cluFiles) + disp(['working on ' cluFiles(i).name]) - 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); - % spikes.amplitudes{jj} = double(spike_amplitudes(ids)); - jj = jj + 1; - end - end + 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 - % get waveforms - if getWaveforms - 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; + 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 - 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 + wav = permute(wav,[3 1 2]); end - 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 + 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 + + count = count + 1; end -else - error('Unit format not recognized...'); end @@ -381,11 +299,16 @@ spikes = removeCells(toRemove,spikes,getWaveforms); end + +spikes.sessionName = sessionInfo.FileName; +end + %% save to buzcode format (before exclusions) if saveMat save(cellinfofile,'spikes') end + %% EXCLUSIONS %% %filter by spikeGroups input @@ -432,10 +355,11 @@ spikes = []; end -end + end + %% function spikes = removeCells(toRemove,spikes,getWaveforms) %Function to remove cells from the structure. toRemove is the INDEX of From 0aea1a552d025c44345dc88e5557a13a7c9010ba Mon Sep 17 00:00:00 2001 From: valegarman Date: Tue, 29 Oct 2019 15:12:25 -0400 Subject: [PATCH 09/32] new detecct swr --- detectors/detectEvents/bz_DetectSWR.m | 1482 +++++++++++++++++++++++++ 1 file changed, 1482 insertions(+) create mode 100644 detectors/detectEvents/bz_DetectSWR.m diff --git a/detectors/detectEvents/bz_DetectSWR.m b/detectors/detectEvents/bz_DetectSWR.m new file mode 100644 index 00000000..be0f02c4 --- /dev/null +++ b/detectors/detectEvents/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 From ce59a962d4152efe99f5bb266267d0fc48a5f7bb Mon Sep 17 00:00:00 2001 From: AntonioFR Date: Tue, 29 Oct 2019 15:32:35 -0400 Subject: [PATCH 10/32] Update bz_firingMapAvg.m --- analysis/spikes/placeFields/bz_firingMapAvg.m | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/analysis/spikes/placeFields/bz_firingMapAvg.m b/analysis/spikes/placeFields/bz_firingMapAvg.m index b750b3fa..66f7f986 100644 --- a/analysis/spikes/placeFields/bz_firingMapAvg.m +++ b/analysis/spikes/placeFields/bz_firingMapAvg.m @@ -2,7 +2,7 @@ % USAGE % [firingMaps] = bz_firingMapAvg(positions,spikes,varargin) -% Calculates averaged firing map for a set of linear postions +% Calculates averaged firing maps for each cell in 1D or 2D enviroments % % INPUTS % @@ -20,6 +20,9 @@ % 'smooth' smoothing size in bins (0 = no smoothing, default = 2) % 'nBins' number of bins (default = 50) % '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 @@ -28,6 +31,9 @@ % 'type' 'linear' for linear data, 'circular' for angular data % (default 'linear') % saveMat - logical (default: false) that saves firingMaps file +% CellInspector - logical (default: false) that creates an otuput +% compatible with CellInspector + % % % OUTPUT @@ -49,6 +55,9 @@ addParameter(p,'minTime',0,@isnumeric); addParameter(p,'type','linear',@isstr); addParameter(p,'saveMat',false,@islogical); +addParameter(p,'CellInspector',false,@islogical); +addParameter(p,'mode','discard',@isstr); +addParameter(p,'maxDistance',5,@isnumeric); parse(p,varargin{:}); smooth = p.Results.smooth; @@ -57,6 +66,9 @@ minTime = p.Results.minTime; type = p.Results.type; saveMat = p.Results.saveMat; +CellInspector = p.Results.CellInspector; +mode = p.Results.mode; +maxDistance = p.Results.maxDistance; % number of conditions if iscell(positions) @@ -67,13 +79,18 @@ %%% TODO: conditions label %% Calculate + +% speed threshold + + + +% get firign rate maps for unit = 1:length(spikes.times) for c = 1:conditions - map{unit}{c} = Map(positions{c},spikes.times{unit},... - 'smooth',smooth,'nBins',nBins,'maxGap',maxGap,'minTime',minTime); + map{unit}{c} = Map(positions{c},spikes.times{unit},'smooth',smooth,'minTime',minTime,... + 'nBins',nBins,'maxGap',maxGap,'mode',mode,'type',type,'maxDistance',maxDistance); end end -%%% TODO: pass rest of inputs to Map %% restructure into cell info data type @@ -102,4 +119,21 @@ save([firingMaps.sessionName '.firingMapsAvg.cellinfo.mat'],'firingMaps'); end +% output for cell inspector - only works for 1D +if CellInspector + for unit = 1:length(spikes.times) + for c = 1:conditions + firingRateMap.map{unit}(:,c) = map{unit}{c}.z; + end + end + firingRateMap.x_bins = 1:1:lenght(map{1}{1}.z); + firingRateMap.state_labels = nan(1,conditions); + + save([firingMaps.sessionName '.firingRateMap.mat'],'firingMaps'); + +end + +%% Add option to plot + + end From b9f6e5441a08b03e40e171943766032aa0c3c0db Mon Sep 17 00:00:00 2001 From: valegarman Date: Tue, 29 Oct 2019 16:45:58 -0400 Subject: [PATCH 11/32] Update bz_GetSpikes.m --- io/bz_GetSpikes.m | 372 ++++++++++++++++++++++++++++------------------ 1 file changed, 227 insertions(+), 145 deletions(-) diff --git a/io/bz_GetSpikes.m b/io/bz_GetSpikes.m index 0b7b2903..0aa420b0 100755 --- a/io/bz_GetSpikes.m +++ b/io/bz_GetSpikes.m @@ -21,6 +21,9 @@ % 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). % % OUTPUTS % @@ -36,6 +39,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 +67,7 @@ % % % written by David Tingley, 2017 +% added Phy loading by Manu Valero, 2019 (previos bz_LoadPhy) %% Deal With Inputs spikeGroupsValidation = @(x) assert(isnumeric(x) || strcmp(x,'all'),... 'spikeGroups must be numeric or "all"'); @@ -74,9 +79,11 @@ 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); parse(p,varargin{:}) @@ -89,12 +96,12 @@ saveMat = p.Results.saveMat; noPrompts = p.Results.noPrompts; onlyLoad = p.Results.onlyLoad; - +verbose = p.Results.verbose; +keepCluWave = p.Results.keepCluWave; [sessionInfo] = bz_getSessionInfo(basepath, 'noPrompts', noPrompts); baseName = bz_BasenameFromBasepath(basepath); - spikes.samplingRate = sessionInfo.rates.wideband; nChannels = sessionInfo.nChannels; @@ -134,157 +141,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 ~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 -% 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 -% 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 + % use the .clu files to get spike ID's and generate UID and spikeGroup + % use the .res files to get spike times + count = 1; -% use the .clu files to get spike ID's and generate UID and spikeGroup -% use the .res files to get spike times -count = 1; + 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 -if isempty(sessionInfo.spikeGroups.groups) - sessionInfo.spikeGroups = sessionInfo.AnatGrps; -end -for i=1:length(cluFiles) - disp(['working on ' cluFiles(i).name]) + 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 + + 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 ~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 +382,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 +435,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 From 00fcdf5f2022fd43aa8e228fe2a63b31613e20eb Mon Sep 17 00:00:00 2001 From: valegarman Date: Tue, 29 Oct 2019 17:09:52 -0400 Subject: [PATCH 12/32] Update bz_firingMapAvg.m Addition of speed threshold --- analysis/spikes/placeFields/bz_firingMapAvg.m | 86 ++++++++++--------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/analysis/spikes/placeFields/bz_firingMapAvg.m b/analysis/spikes/placeFields/bz_firingMapAvg.m index 66f7f986..d1d0ada7 100644 --- a/analysis/spikes/placeFields/bz_firingMapAvg.m +++ b/analysis/spikes/placeFields/bz_firingMapAvg.m @@ -2,7 +2,7 @@ % USAGE % [firingMaps] = bz_firingMapAvg(positions,spikes,varargin) -% Calculates averaged firing maps for each cell in 1D or 2D enviroments +% Calculates averaged firing map for a set of linear postions % % INPUTS % @@ -17,22 +17,22 @@ % ========================================================================= % Properties Values % ------------------------------------------------------------------------- -% 'smooth' smoothing size in bins (0 = no smoothing, default = 2) -% 'nBins' number of bins (default = 50) -% '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) -% 'type' 'linear' for linear data, 'circular' for angular data -% (default 'linear') -% saveMat - logical (default: false) that saves firingMaps file -% CellInspector - logical (default: false) that creates an otuput -% compatible with CellInspector +% '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 % % @@ -50,25 +50,27 @@ %% 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,'type','linear',@isstr); 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; -type = p.Results.type; 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) @@ -80,17 +82,37 @@ %% Calculate -% speed threshold - +% 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 Date: Tue, 29 Oct 2019 17:17:20 -0400 Subject: [PATCH 13/32] Update bz_GetSpikes.m --- io/bz_GetSpikes.m | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/io/bz_GetSpikes.m b/io/bz_GetSpikes.m index 0aa420b0..cb4b93dc 100755 --- a/io/bz_GetSpikes.m +++ b/io/bz_GetSpikes.m @@ -23,7 +23,10 @@ % 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). +% 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 % @@ -68,6 +71,9 @@ % % 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"'); @@ -84,6 +90,7 @@ addParameter(p,'onlyLoad',[]); addParameter(p,'verbose',true,@islogical); addParameter(p,'keepCluWave',false,@islogical); +addParameter(p,'sortingMethod',[],@isstr); parse(p,varargin{:}) @@ -98,6 +105,7 @@ 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); @@ -149,7 +157,7 @@ end kilosort_path = dir([basepath filesep '*kilosort*']); -if ~isempty(cluFiles) % LOADING FROM CLU/ RES +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 @@ -294,7 +302,7 @@ end spikes.sessionName = sessionInfo.FileName; -elseif ~isempty(kilosort_path) % LOADING FROM KILOSORT +elseif strcmpi(sortingMethod, 'kilosort') || ~isempty(kilosort_path) % LOADING FROM KILOSORT disp('loading spikes from Kilosort/Phy format...') fs = spikes.samplingRate; From dd5327c64451e45e9a287ec1ef47201c5b60f60c Mon Sep 17 00:00:00 2001 From: AntonioFR Date: Tue, 29 Oct 2019 18:01:22 -0400 Subject: [PATCH 14/32] temporary find place fields work in progress --- .../spikes/placeFields/bz_findPlaceFields1D.m | 220 ++++++++++++++++++ .../FMAToolbox/Analyses/FindFieldHelper.m | 10 + 2 files changed, 230 insertions(+) create mode 100644 analysis/spikes/placeFields/bz_findPlaceFields1D.m create mode 100644 externalPackages/FMAToolbox/Analyses/FindFieldHelper.m diff --git a/analysis/spikes/placeFields/bz_findPlaceFields1D.m b/analysis/spikes/placeFields/bz_findPlaceFields1D.m new file mode 100644 index 00000000..003eaf0d --- /dev/null +++ b/analysis/spikes/placeFields/bz_findPlaceFields1D.m @@ -0,0 +1,220 @@ +function [mapStats] = 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 size are considered spurious +% and ignored (default = 10) +% 'maxSize' fields larger than this size are considered spurious +% and ignored (default = 50) +% 'minPeak' peaks smaller than this size are considered spurious +% and ignored (default = 1) +% 'verbose' display processing information (default = 'off') +% ========================================================================= + +%% Parse inputs +p=inputParser; +addParameter(p,'threshold',0.2,@isnumeric); +addParameter(p,'minSize',10,@isnumeric); +addParameter(p,'minPeak',1,@isnumeric); +addParameter(p,'verbose','off',@isstr); + +parse(p,varargin{:}); +threshold = p.Results.threshold; +minSize = p.Results.minSize; +minPeak = p.Results.minPeak; +verbose = p.Results.verbose; + + +%% 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:1:length( firingMaps.rateMaps{1}{1}); + + if max(max(z)) == 0, + mapStats{unit,1}{c}.field = logical(zeros(size(z))); + return; + 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 peak < minPeak, 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 + % Display debugging info +% if verbose, +% disp([int2zstr(i,2) ') peak ' num2str(peak) ' @ (' int2str(x) ',' int2str(y) ')']); +% disp([' ' tc ' field size ' int2str(size1)]); +% disp([' ' sc ' subfield size ' int2str(size2)]); +% disp(' '); +% figure; +% if nDims == 1, +% plot(z);hold on; +% PlotIntervals(ToIntervals(field1),'rectangles'); +% PlotIntervals(ToIntervals(field2),'bars'); +% ylabel(tc); +% else +% subplot(3,1,1);imagesc(z);xlabel('Data'); +% subplot(3,1,2);imagesc(field1);clim([0 1]);xlabel('Field'); +% subplot(3,1,3);imagesc(field2);clim([0 1]);ylabel(tc);xlabel('Subfield'); +% end +% end + fieldSize = sum(field(:)); + % Keep this field if its size is sufficient + if fieldSize > minSize + 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); + i = i + 1; + end + % Mark field bins as visited + z(field) = NaN; + if all(isnan(z)), break; end + end + + end +end + +%% Refine place fields + %%% this is not working yet +secondaryPF = 0.6; %secondary PF should have peak FR hihger than secondayPF*peakFR of primary PF + +for unit = 1:length(firingMaps.rateMaps) + for c = 1:length(firingMaps.rateMaps{1}) + if mapStats{unit}{c}.peak ~= 0 + for k=1:length(stats{unit}{1}.peak) % for each PF of this cell + if mapStats{unit}{c}.peak(k)>mapStats{unit}{c}.peak(1)*secondaryPF % destroy small secondary PFs + peakLoc(k)=firingMaps.rateMaps{unit}{c}.x(mapStats{unit}{c}.x(k)); + scatter(peakLoc(k),mapStats{unit}{c}.peak(k),'MarkerFaceColor','r','LineWidth',1); + fieldIni(k)=firingMaps.rateMaps{unit}{c}.x(mapStats{unit}{c}.fieldX(k,1)); + fieldFin(k)=firingMaps.rateMaps{unit}{c}.x(mapStats{unit}{c}.fieldX(k,2)); + x1=fieldIni(k);y1=get(gca,'ylim');hold on; plot([x1 x1],y1,'-.k','LineWidth',1); + x2=fieldFin(k);y1=get(gca,'ylim');hold on; plot([x2 x2],y1,'-.k','LineWidth',1); + else + mapStats{unit}{c}.x(k) = NaN; mapStats{unit}{c}.y(k) = NaN; + mapStats{unit}{c}.field(k) = 0; mapStats{unit}{c}.size(k) = 0; + mapStats{unit}{c}.peak(k) = 0; mapStats{unit}{c}.mean(k) = 0; + mapStats{unit}{c}.fieldX(k,1:2) = [NaN,NaN]; mapStats{unit}{c}.fieldY(k,1:2) = [NaN,NaN]; + end + end + for k=1:length(mapStats{unit}{c}.peak)% destroy too large PF (CHANGE for recalculate!!) + if mapStats{unit}{c}.peak(k)~=0 && ... + firingMaps.rateMaps{unit}{c}.x(stats{i}{1}.fieldX(k,2))-firingMaps.rateMaps{unit}{c}.x(mapStats{unit}{c}.fieldX(k,1)) > 0.5 + mapStats{unit}{c}.x(k) = NaN; mapStats{unit}{c}.y(k) = NaN; + mapStats{unit}{c}.field(k) = 0; mapStats{unit}{c}.size(k) = 0; + mapStats{unit}{c}.peak(k) = 0; mapStats{unit}{c}.mean(k) = 0; + mapStats{unit}{c}.fieldX(k,1:2) = [NaN,NaN]; mapStats{unit}{c}.fieldY(k,1:2) = [NaN,NaN]; + end + end + clear temp peakLoc fieldIni fieldFin x1 x2 y1 y2 k + end + end +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}); + ylabel('FR(Hz)');xlabel('track (cm)');hold on; + + suptitle([basename ' cell' num2str(i)]); + saveas(gcf,[pwd '\newPCs\cell_' num2str(i) '.png'],'png'); close all; + + + end +end + + %% Write output + + +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/externalPackages/FMAToolbox/Analyses/FindFieldHelper.m b/externalPackages/FMAToolbox/Analyses/FindFieldHelper.m new file mode 100644 index 00000000..9e56ea7d --- /dev/null +++ b/externalPackages/FMAToolbox/Analyses/FindFieldHelper.m @@ -0,0 +1,10 @@ +function [field] = FindFieldHelper(map,x,y,threshold,circX,circY) +%[field] = FindFieldHelper(map,x,y,threshold,circX,circY) + +% Helper function to pass arguments to FindField in order to avoid moving +% around compiled function + + field = FindField(map,x,y,threshold,circX,circY); + +end + From aeef521cff63f09e560c496d7fd145ae5d3fc9c2 Mon Sep 17 00:00:00 2001 From: AntonioFR Date: Tue, 29 Oct 2019 18:03:41 -0400 Subject: [PATCH 15/32] Update bz_findPlaceFields1D.m --- analysis/spikes/placeFields/bz_findPlaceFields1D.m | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/analysis/spikes/placeFields/bz_findPlaceFields1D.m b/analysis/spikes/placeFields/bz_findPlaceFields1D.m index 003eaf0d..1c7e1ba9 100644 --- a/analysis/spikes/placeFields/bz_findPlaceFields1D.m +++ b/analysis/spikes/placeFields/bz_findPlaceFields1D.m @@ -16,6 +16,10 @@ % 'verbose' display processing information (default = 'off') % ========================================================================= +% Antonio FR, 10/2019 + +%%%%%%%%%%%%%% WORK IN PROGRESS + %% Parse inputs p=inputParser; addParameter(p,'threshold',0.2,@isnumeric); From 63460566b52ac6de0fff46b4e224a6fde92886c9 Mon Sep 17 00:00:00 2001 From: valegarman Date: Wed, 30 Oct 2019 15:57:55 -0400 Subject: [PATCH 16/32] Update bz_findPlaceFields1D.m Tune some parameters to search place fields, and do it buzcode compatible --- .../spikes/placeFields/bz_findPlaceFields1D.m | 309 ++++++++++-------- 1 file changed, 170 insertions(+), 139 deletions(-) diff --git a/analysis/spikes/placeFields/bz_findPlaceFields1D.m b/analysis/spikes/placeFields/bz_findPlaceFields1D.m index 1c7e1ba9..d18a51bf 100644 --- a/analysis/spikes/placeFields/bz_findPlaceFields1D.m +++ b/analysis/spikes/placeFields/bz_findPlaceFields1D.m @@ -1,4 +1,4 @@ -function [mapStats] = bz_findPlaceFields1D(firingMaps,varargin) +function [placeFieldStats] = bz_findPlaceFields1D(firingMaps,varargin) % [stats] = bz_findPlaceFields1D(firingMaps) % Find place fields from 1D firing maps. Reads the output of bz_firingMapAvg @@ -7,12 +7,19 @@ % ------------------------------------------------------------------------- % 'threshold' values above threshold*peak belong to the field % (default = 0.2) -% 'minSize' fields smaller than this size are considered spurious -% and ignored (default = 10) -% 'maxSize' fields larger than this size are considered spurious -% and ignored (default = 50) +% '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) +% 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') % ========================================================================= @@ -20,167 +27,191 @@ %%%%%%%%%%%%%% WORK IN PROGRESS -%% Parse inputs +% Parse inputs p=inputParser; addParameter(p,'threshold',0.2,@isnumeric); -addParameter(p,'minSize',10,@isnumeric); -addParameter(p,'minPeak',1,@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; +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) +% 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:1:length( firingMaps.rateMaps{1}{1}); - - if max(max(z)) == 0, - mapStats{unit,1}{c}.field = logical(zeros(size(z))); - return; - end + 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)); - 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 peak < minPeak, 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 + % If there is no firing rate, go to next unit + if maxFR == 0, + mapStats{unit,1}{c}.field = logical(zeros(size(z))); + continue; end - % Display debugging info -% if verbose, -% disp([int2zstr(i,2) ') peak ' num2str(peak) ' @ (' int2str(x) ',' int2str(y) ')']); -% disp([' ' tc ' field size ' int2str(size1)]); -% disp([' ' sc ' subfield size ' int2str(size2)]); -% disp(' '); -% figure; -% if nDims == 1, -% plot(z);hold on; -% PlotIntervals(ToIntervals(field1),'rectangles'); -% PlotIntervals(ToIntervals(field2),'bars'); -% ylabel(tc); -% else -% subplot(3,1,1);imagesc(z);xlabel('Data'); -% subplot(3,1,2);imagesc(field1);clim([0 1]);xlabel('Field'); -% subplot(3,1,3);imagesc(field2);clim([0 1]);ylabel(tc);xlabel('Subfield'); -% end -% end - fieldSize = sum(field(:)); - % Keep this field if its size is sufficient - if fieldSize > minSize - 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); + + 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 - % Mark field bins as visited - z(field) = NaN; - if all(isnan(z)), break; end - end - end end -%% Refine place fields - %%% this is not working yet -secondaryPF = 0.6; %secondary PF should have peak FR hihger than secondayPF*peakFR of primary PF + -for unit = 1:length(firingMaps.rateMaps) - for c = 1:length(firingMaps.rateMaps{1}) - if mapStats{unit}{c}.peak ~= 0 - for k=1:length(stats{unit}{1}.peak) % for each PF of this cell - if mapStats{unit}{c}.peak(k)>mapStats{unit}{c}.peak(1)*secondaryPF % destroy small secondary PFs - peakLoc(k)=firingMaps.rateMaps{unit}{c}.x(mapStats{unit}{c}.x(k)); - scatter(peakLoc(k),mapStats{unit}{c}.peak(k),'MarkerFaceColor','r','LineWidth',1); - fieldIni(k)=firingMaps.rateMaps{unit}{c}.x(mapStats{unit}{c}.fieldX(k,1)); - fieldFin(k)=firingMaps.rateMaps{unit}{c}.x(mapStats{unit}{c}.fieldX(k,2)); - x1=fieldIni(k);y1=get(gca,'ylim');hold on; plot([x1 x1],y1,'-.k','LineWidth',1); - x2=fieldFin(k);y1=get(gca,'ylim');hold on; plot([x2 x2],y1,'-.k','LineWidth',1); - else - mapStats{unit}{c}.x(k) = NaN; mapStats{unit}{c}.y(k) = NaN; - mapStats{unit}{c}.field(k) = 0; mapStats{unit}{c}.size(k) = 0; - mapStats{unit}{c}.peak(k) = 0; mapStats{unit}{c}.mean(k) = 0; - mapStats{unit}{c}.fieldX(k,1:2) = [NaN,NaN]; mapStats{unit}{c}.fieldY(k,1:2) = [NaN,NaN]; - end - end - for k=1:length(mapStats{unit}{c}.peak)% destroy too large PF (CHANGE for recalculate!!) - if mapStats{unit}{c}.peak(k)~=0 && ... - firingMaps.rateMaps{unit}{c}.x(stats{i}{1}.fieldX(k,2))-firingMaps.rateMaps{unit}{c}.x(mapStats{unit}{c}.fieldX(k,1)) > 0.5 - mapStats{unit}{c}.x(k) = NaN; mapStats{unit}{c}.y(k) = NaN; - mapStats{unit}{c}.field(k) = 0; mapStats{unit}{c}.size(k) = 0; - mapStats{unit}{c}.peak(k) = 0; mapStats{unit}{c}.mean(k) = 0; - mapStats{unit}{c}.fieldX(k,1:2) = [NaN,NaN]; mapStats{unit}{c}.fieldY(k,1:2) = [NaN,NaN]; - end - end - clear temp peakLoc fieldIni fieldFin x1 x2 y1 y2 k - 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 +% ========== +% PLOT +% ========== for unit = 1:length(firingMaps.rateMaps) figure; for c = 1:length(firingMaps.rateMaps{1}) - subplot(2,2,c);plot(firingMaps.rateMaps{unit}{c}); - ylabel('FR(Hz)');xlabel('track (cm)');hold on; - - suptitle([basename ' cell' num2str(i)]); - saveas(gcf,[pwd '\newPCs\cell_' num2str(i) '.png'],'png'); close all; - - + 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 - - %% Write output - end From 81c97297638fa6c57db448468e335c4754a4bbd0 Mon Sep 17 00:00:00 2001 From: AntonioFR Date: Thu, 31 Oct 2019 16:39:33 -0400 Subject: [PATCH 17/32] new function bz_getRipSpikes to get spikes inside ripples or other buzcode events --- .../lfp/SharpWaveRipples/bz_getRipSpikes.m | 162 +++++++++++ .../spikes/{ => placeFields}/bz_firingMap1D.m | 260 +++++++++--------- 2 files changed, 292 insertions(+), 130 deletions(-) create mode 100644 analysis/lfp/SharpWaveRipples/bz_getRipSpikes.m rename analysis/spikes/{ => placeFields}/bz_firingMap1D.m (97%) mode change 100755 => 100644 diff --git a/analysis/lfp/SharpWaveRipples/bz_getRipSpikes.m b/analysis/lfp/SharpWaveRipples/bz_getRipSpikes.m new file mode 100644 index 00000000..2f7bd65d --- /dev/null +++ b/analysis/lfp/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/analysis/spikes/bz_firingMap1D.m b/analysis/spikes/placeFields/bz_firingMap1D.m old mode 100755 new mode 100644 similarity index 97% rename from analysis/spikes/bz_firingMap1D.m rename to analysis/spikes/placeFields/bz_firingMap1D.m index f80fbfcb..5a7031f8 --- a/analysis/spikes/bz_firingMap1D.m +++ b/analysis/spikes/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} Date: Thu, 31 Oct 2019 16:46:09 -0400 Subject: [PATCH 18/32] Deleting old CFC/MI function, updating bz_CrossFreqMod to have a hilbert method (keeping wavelet), changing the name of the function to bz_CFCPhaseAmp so we can better identify what the function does. Also giving more freedom for the user to change parameters for the function from the input. Some other updates necessary is on TO DO list --- .../CrossFrequencyCoupling/bz_CFCPhaseAmp.m | 191 ++++++++++++++++++ .../CrossFrequencyCoupling/bz_CrossFreqMod.m | 123 ----------- .../lfp/CrossFrequencyCoupling/bz_ModIndex.m | 93 --------- 3 files changed, 191 insertions(+), 216 deletions(-) create mode 100644 analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m delete mode 100644 analysis/lfp/CrossFrequencyCoupling/bz_CrossFreqMod.m delete mode 100755 analysis/lfp/CrossFrequencyCoupling/bz_ModIndex.m diff --git a/analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m b/analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m new file mode 100644 index 00000000..8c2fcd6c --- /dev/null +++ b/analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m @@ -0,0 +1,191 @@ +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 +% comod phase-frequency versus amplitude-frequency +% comodulogram matrix in modulation index units +% +%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 + +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); +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.ampChans; +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_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 - From 7555dfb3833f459d5f300179ca434c3afe871f4f Mon Sep 17 00:00:00 2001 From: eliezyer Date: Thu, 31 Oct 2019 16:54:34 -0400 Subject: [PATCH 19/32] updating the help of bz_CFC_PhaseAmp --- analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m b/analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m index 8c2fcd6c..d816f4f9 100644 --- a/analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m +++ b/analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m @@ -39,8 +39,13 @@ % ========================================================================= % %OUTPUT -% comod phase-frequency versus amplitude-frequency -% comodulogram matrix in modulation index units +% 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 From 840dfda3db7ba46e7185da3f2efbeed717ffec22 Mon Sep 17 00:00:00 2001 From: eliezyer Date: Thu, 31 Oct 2019 17:18:56 -0400 Subject: [PATCH 20/32] updating bz_PhaseAmplitudeDist, I've increased the freedom for the user and transformed the output into a structure. --- .../bz_PhaseAmplitudeDist.m | 73 ++++++++++++------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m b/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m index 48e10ca7..f62646ea 100755 --- a/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m +++ b/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m @@ -1,4 +1,4 @@ -function [phaseamplitudemap,ampfreqs,phasecenters] = bz_PhaseAmplitudeDist(lfp,phaserange,amprange) +function [phaseamplitudemap] = bz_PhaseAmplitudeDist(lfp,phaserange,amprange,varargin) % [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 @@ -35,23 +35,38 @@ % bz_Filter % bz_WaveSpec % -%Last Updated: 10/9/15 +%Last Updated: 31/10/2019 (EFO) %DLevenstein % % %DLevenstein 2016 +%Updated by Eliezyer de Oliviera (EFO)/2019 %TO DO: intervals support -%% DEV (these should be varagins) -nfreqs = 100; -ncyc = 7; +%% parsing inputs +p = inputParser; +addParameter(p,'filterType','fir1',@ischar); +addParameter(p,'filterOrder',4,@isnumeric); +addParameter(p,'numBins',50,@isnumeric); +addParameter(p,'intervals',[0 inf],@isvector); +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; %% Deal with input types -sf_LFP = lfp.samplingRate; +% sf_LFP = lfp.samplingRate; %% Filter LFP for the phase -filtered_phase = bz_Filter(lfp,'passband',phaserange,'filter','fir1'); +filtered_phase = bz_Filter(lfp,'passband',phaserange,'filter',filterType,'order',filterOrder); %% Get LFP, Phase in intervals %edgebuffer = 1; %s @@ -71,31 +86,39 @@ %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); +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 = 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 -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 +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('Frequency (Hz') + LogScale('y',2) + xlim([phaseamplitude.phasecenters(1) phaseamplitude.phasecenters(end)+2*pi]); + colorbar + axis xy +end end From df1c5c3ed310ac24baf33328b51486e30fd23b2e Mon Sep 17 00:00:00 2001 From: eliezyer Date: Thu, 31 Oct 2019 17:32:33 -0400 Subject: [PATCH 21/32] last updates of the day on bz_PhaseAmplitudeDist --- analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m b/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m index f62646ea..95160076 100755 --- a/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m +++ b/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m @@ -76,7 +76,7 @@ % LFPphase_int = IsolateEpochs2(filtered.phase,int,edgebuffer,sf_LFP); %% Wavelet Transform LFP in intervals -wavespec_amp = bz_WaveSpec(lfp,'frange',amprange); +wavespec_amp = bz_WaveSpec(lfp,'frange',amprange,'nfreqs',nfreqs,'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 From f51b5f108aef7fd93cf80ad038e0980bdca657fb Mon Sep 17 00:00:00 2001 From: AntonioFR Date: Thu, 31 Oct 2019 18:25:04 -0400 Subject: [PATCH 22/32] major folder reorganization and some new functions --- .../PhaseAmpCouplingByAmp.m | 0 .../bz_Comodulogram.m | 0 .../CrossFrequencyCoupling/bz_CrossFreqMod.m | 0 .../CrossFrequencyCoupling/bz_ModIndex.m | 0 .../bz_PhaseAmplitudeDist.m | 0 .../SharpWaveRipples}/bz_DetectSWR.m | 0 .../SharpWaveRipples}/bz_FindRipples.m | 0 .../SharpWaveRipples/bz_GetBestRippleChan.m | 0 .../SharpWaveRipples/bz_PlotRippleStats.m | 0 .../SharpWaveRipples/bz_RippleStats.m | 0 .../SharpWaveRipples/bz_getRipSpikes.m | 0 .../SharpWaveRipples}/detect_swr/ReadMe.txt | 0 .../detect_swr/SWR_between_trials.png | Bin .../detect_swr/SWR_during_behavior.png | Bin .../SharpWaveRipples}/detect_swr/detect_swr.m | 0 .../detect_swr/private/LoadBinary.m | 0 .../detect_swr/private/LoadXml.m | 0 .../detect_swr/private/firfilt.m | 0 .../detect_swr/private/license.txt | 0 .../detect_swr/private/logAnalysisFile.m | 0 .../detect_swr/private/makefir.m | 0 .../detect_swr/private/makegausslpfir.m | 0 .../detect_swr/private/parseArgs.m | 0 .../detect_swr/private/suprathresh.m | 0 .../detect_swr/private/v2struct.m | 0 .../detect_swr/private/v2structDemo1.m | 0 .../detect_swr/private/v2structDemo2.m | 0 .../detect_swr/private/xmltools.m | 0 .../SpectralAnalyses/MorletWavelet.m | 0 .../SpectralAnalyses/bz_MTCoherogram.m | 0 .../{lfp => }/SpectralAnalyses/bz_WaveSpec.m | 0 .../blank.file | 0 .../assemblies/bz_peerPrediction.m | 0 .../ClusterPointsBoundaryOutBW.m | 0 .../DefaultArgs.m | 0 .../PointInput.m | 0 .../ReadMe.txt | 0 .../bz_CellClassification.m | 0 .../getWavelet.m | 0 .../bz_CellMetricsSimple.m | 0 .../bz_CSD.m | 0 .../{lfp => lfp_general}/bz_DownsampleLFP.m | 0 analysis/{lfp => lfp_general}/bz_Filter.m | 0 .../{lfp => lfp_general}/bz_GradDescCluster.m | 0 .../{lfp => lfp_general}/bz_LFPPowerDist.m | 0 .../bz_LFPSpecToExternalVar.m | 0 .../bz_PowerSpectrumSlope.m | 0 .../bz_eventCSD.m | 0 .../correlation => monosynapticPairs}/CCG.m | 0 .../CCGHeart.c | 0 .../ProbSynMat.mat | Bin .../blank.file | 0 .../bz_GetMonoSynapticallyConnected.m | 0 .../bz_MonoSynConvClick.m | 0 .../bz_PlotMonoSyn.m | 0 .../bz_cch_conv.m | 286 +++++++++--------- .../blank.file | 0 .../placeFields/bz_findPlaceFields1D.m | 0 .../placeFields/bz_findPlaceFieldsTemplate.m | 151 +++++++++ .../{spikes => }/placeFields/bz_firingMap1D.m | 0 .../placeFields/bz_firingMapAvg.m | 0 .../placeFields/bz_getPlaceFields.m | 0 .../placeFields/bz_getPlaceFields1D.m | 0 .../bz_positionDecodingBayesian.m | 0 .../positionDecoding/bz_positionDecodingGLM.m | 0 .../bz_positionDecodingGLM_gauss.m | 0 .../bz_positionDecodingMaxCorr.m | 0 .../bz_positionDecodingThetaSeq.m | 0 .../positionDecoding/placeBayes.m | 80 ++--- .../CircularMean.m_deprecate | 0 .../PhaseModulation.m | 0 .../bz_GenSpikeLFPCoupling.m | 0 .../bz_PhaseModulation.m | 0 .../bz_PowerPhaseRatemap.m | 0 analysis/spikes/assemblies/blank.file | 0 analysis/spikes/binnedMatrix/blank.file | 0 .../blank.file | 0 analysis/spikes/placeFields/blank.file | 0 .../{spikes => spikes_general}/Contents.m | 0 analysis/{spikes => spikes_general}/GetSNR.m | 112 +++---- .../IsolationDistance.m | 0 analysis/{spikes => spikes_general}/L_Ratio.m | 0 .../burst_cls_kmeans.m | 0 .../spikes_general}/bz_FindPopBursts.m | 0 .../{spikes => spikes_general}/bz_ISIStats.m | 0 .../bz_JitterSpiketimes.m | 0 .../bz_SpktToSpkmat.m | 0 .../bz_compareReplay.m | 0 .../bz_olypherInfo.m | 174 +++++------ .../bz_phaseMap1D.m | 0 analysis/spikes_general/calc_PSTH.m | 121 ++++++++ 91 files changed, 598 insertions(+), 326 deletions(-) rename analysis/{lfp => }/CrossFrequencyCoupling/PhaseAmpCouplingByAmp.m (100%) mode change 100755 => 100644 rename analysis/{lfp => CrossFrequencyCoupling}/bz_Comodulogram.m (100%) mode change 100755 => 100644 rename analysis/{lfp => }/CrossFrequencyCoupling/bz_CrossFreqMod.m (100%) rename analysis/{lfp => }/CrossFrequencyCoupling/bz_ModIndex.m (100%) mode change 100755 => 100644 rename analysis/{lfp => }/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/bz_DetectSWR.m (100%) rename {detectors/detectEvents => analysis/SharpWaveRipples}/bz_FindRipples.m (100%) mode change 100755 => 100644 rename analysis/{lfp => }/SharpWaveRipples/bz_GetBestRippleChan.m (100%) mode change 100755 => 100644 rename analysis/{lfp => }/SharpWaveRipples/bz_PlotRippleStats.m (100%) mode change 100755 => 100644 rename analysis/{lfp => }/SharpWaveRipples/bz_RippleStats.m (100%) mode change 100755 => 100644 rename analysis/{lfp => }/SharpWaveRipples/bz_getRipSpikes.m (100%) rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/ReadMe.txt (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/SWR_between_trials.png (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/SWR_during_behavior.png (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/detect_swr.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/private/LoadBinary.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/private/LoadXml.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/private/firfilt.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/private/license.txt (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/private/logAnalysisFile.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/private/makefir.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/private/makegausslpfir.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/private/parseArgs.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/private/suprathresh.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/private/v2struct.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/private/v2structDemo1.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/private/v2structDemo2.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/SharpWaveRipples}/detect_swr/private/xmltools.m (100%) mode change 100755 => 100644 rename analysis/{lfp => }/SpectralAnalyses/MorletWavelet.m (100%) mode change 100755 => 100644 rename analysis/{lfp => }/SpectralAnalyses/bz_MTCoherogram.m (100%) mode change 100755 => 100644 rename analysis/{lfp => }/SpectralAnalyses/bz_WaveSpec.m (100%) mode change 100755 => 100644 rename analysis/{lfp/CurrentSourceDensity => assemblies}/blank.file (100%) mode change 100755 => 100644 rename analysis/{spikes => }/assemblies/bz_peerPrediction.m (100%) mode change 100755 => 100644 rename analysis/{spikes => }/cellTypeClassification/BrendonClassificationFromStark2013/ClusterPointsBoundaryOutBW.m (100%) mode change 100755 => 100644 rename analysis/{spikes => }/cellTypeClassification/BrendonClassificationFromStark2013/DefaultArgs.m (100%) mode change 100755 => 100644 rename analysis/{spikes => }/cellTypeClassification/BrendonClassificationFromStark2013/PointInput.m (100%) mode change 100755 => 100644 rename analysis/{spikes => }/cellTypeClassification/BrendonClassificationFromStark2013/ReadMe.txt (100%) mode change 100755 => 100644 rename analysis/{spikes => }/cellTypeClassification/BrendonClassificationFromStark2013/bz_CellClassification.m (100%) mode change 100755 => 100644 rename analysis/{spikes => }/cellTypeClassification/BrendonClassificationFromStark2013/getWavelet.m (100%) mode change 100755 => 100644 rename analysis/{spikes => cellTypeClassification}/bz_CellMetricsSimple.m (100%) rename analysis/{lfp/CurrentSourceDensity => lfp_general}/bz_CSD.m (100%) mode change 100755 => 100644 rename analysis/{lfp => lfp_general}/bz_DownsampleLFP.m (100%) mode change 100755 => 100644 rename analysis/{lfp => lfp_general}/bz_Filter.m (100%) mode change 100755 => 100644 rename analysis/{lfp => lfp_general}/bz_GradDescCluster.m (100%) mode change 100755 => 100644 rename analysis/{lfp => lfp_general}/bz_LFPPowerDist.m (100%) mode change 100755 => 100644 rename analysis/{lfp => lfp_general}/bz_LFPSpecToExternalVar.m (100%) mode change 100755 => 100644 rename analysis/{lfp => lfp_general}/bz_PowerSpectrumSlope.m (100%) mode change 100755 => 100644 rename analysis/{lfp/CurrentSourceDensity => lfp_general}/bz_eventCSD.m (100%) mode change 100755 => 100644 rename analysis/{spikes/correlation => monosynapticPairs}/CCG.m (100%) mode change 100755 => 100644 rename analysis/{spikes/correlation => monosynapticPairs}/CCGHeart.c (100%) mode change 100755 => 100644 rename analysis/{spikes/functionalConnectionIdentification => monosynapticPairs}/ProbSynMat.mat (100%) mode change 100755 => 100644 rename analysis/{lfp/LFPConnectivity => monosynapticPairs}/blank.file (100%) mode change 100755 => 100644 rename analysis/{spikes/functionalConnectionIdentification => monosynapticPairs}/bz_GetMonoSynapticallyConnected.m (100%) mode change 100755 => 100644 rename analysis/{spikes/functionalConnectionIdentification => monosynapticPairs}/bz_MonoSynConvClick.m (100%) mode change 100755 => 100644 rename analysis/{spikes/functionalConnectionIdentification => monosynapticPairs}/bz_PlotMonoSyn.m (100%) mode change 100755 => 100644 rename analysis/{spikes/functionalConnectionIdentification => monosynapticPairs}/bz_cch_conv.m (97%) mode change 100755 => 100644 rename analysis/{spikes/BasicWaveformMetrics => placeFields}/blank.file (100%) mode change 100755 => 100644 rename analysis/{spikes => }/placeFields/bz_findPlaceFields1D.m (100%) create mode 100644 analysis/placeFields/bz_findPlaceFieldsTemplate.m rename analysis/{spikes => }/placeFields/bz_firingMap1D.m (100%) rename analysis/{spikes => }/placeFields/bz_firingMapAvg.m (100%) rename analysis/{spikes => }/placeFields/bz_getPlaceFields.m (100%) mode change 100755 => 100644 rename analysis/{spikes => }/placeFields/bz_getPlaceFields1D.m (100%) mode change 100755 => 100644 rename analysis/{spikes => }/positionDecoding/bz_positionDecodingBayesian.m (100%) mode change 100755 => 100644 rename analysis/{spikes => }/positionDecoding/bz_positionDecodingGLM.m (100%) mode change 100755 => 100644 rename analysis/{spikes => }/positionDecoding/bz_positionDecodingGLM_gauss.m (100%) mode change 100755 => 100644 rename analysis/{spikes => }/positionDecoding/bz_positionDecodingMaxCorr.m (100%) mode change 100755 => 100644 rename analysis/{spikes => }/positionDecoding/bz_positionDecodingThetaSeq.m (100%) mode change 100755 => 100644 rename analysis/{spikes => }/positionDecoding/placeBayes.m (96%) mode change 100755 => 100644 rename analysis/{lfp_spikes/PhaseModulation => spikeLFPcoupling}/CircularMean.m_deprecate (100%) mode change 100755 => 100644 rename analysis/{lfp_spikes/PhaseModulation => spikeLFPcoupling}/PhaseModulation.m (100%) mode change 100755 => 100644 rename analysis/{lfp_spikes => spikeLFPcoupling}/bz_GenSpikeLFPCoupling.m (100%) rename analysis/{lfp_spikes/PhaseModulation => spikeLFPcoupling}/bz_PhaseModulation.m (100%) mode change 100755 => 100644 rename analysis/{lfp_spikes => spikeLFPcoupling}/bz_PowerPhaseRatemap.m (100%) delete mode 100755 analysis/spikes/assemblies/blank.file delete mode 100755 analysis/spikes/binnedMatrix/blank.file delete mode 100755 analysis/spikes/functionalConnectionIdentification/blank.file delete mode 100755 analysis/spikes/placeFields/blank.file rename analysis/{spikes => spikes_general}/Contents.m (100%) mode change 100755 => 100644 rename analysis/{spikes => spikes_general}/GetSNR.m (97%) mode change 100755 => 100644 rename analysis/{spikes => spikes_general}/IsolationDistance.m (100%) mode change 100755 => 100644 rename analysis/{spikes => spikes_general}/L_Ratio.m (100%) mode change 100755 => 100644 rename analysis/{spikes => spikes_general}/burst_cls_kmeans.m (100%) mode change 100755 => 100644 rename {detectors/detectEvents => analysis/spikes_general}/bz_FindPopBursts.m (100%) mode change 100755 => 100644 rename analysis/{spikes => spikes_general}/bz_ISIStats.m (100%) rename analysis/{spikes => spikes_general}/bz_JitterSpiketimes.m (100%) mode change 100755 => 100644 rename analysis/{spikes => spikes_general}/bz_SpktToSpkmat.m (100%) mode change 100755 => 100644 rename analysis/{spikes => spikes_general}/bz_compareReplay.m (100%) rename analysis/{spikes => spikes_general}/bz_olypherInfo.m (96%) mode change 100755 => 100644 rename analysis/{spikes => spikes_general}/bz_phaseMap1D.m (100%) mode change 100755 => 100644 create mode 100644 analysis/spikes_general/calc_PSTH.m diff --git a/analysis/lfp/CrossFrequencyCoupling/PhaseAmpCouplingByAmp.m b/analysis/CrossFrequencyCoupling/PhaseAmpCouplingByAmp.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/CrossFrequencyCoupling/PhaseAmpCouplingByAmp.m rename to analysis/CrossFrequencyCoupling/PhaseAmpCouplingByAmp.m 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/lfp/CrossFrequencyCoupling/bz_CrossFreqMod.m b/analysis/CrossFrequencyCoupling/bz_CrossFreqMod.m similarity index 100% rename from analysis/lfp/CrossFrequencyCoupling/bz_CrossFreqMod.m rename to analysis/CrossFrequencyCoupling/bz_CrossFreqMod.m diff --git a/analysis/lfp/CrossFrequencyCoupling/bz_ModIndex.m b/analysis/CrossFrequencyCoupling/bz_ModIndex.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/CrossFrequencyCoupling/bz_ModIndex.m rename to analysis/CrossFrequencyCoupling/bz_ModIndex.m diff --git a/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m b/analysis/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m rename to analysis/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m diff --git a/detectors/detectEvents/bz_DetectSWR.m b/analysis/SharpWaveRipples/bz_DetectSWR.m similarity index 100% rename from detectors/detectEvents/bz_DetectSWR.m rename to analysis/SharpWaveRipples/bz_DetectSWR.m diff --git a/detectors/detectEvents/bz_FindRipples.m b/analysis/SharpWaveRipples/bz_FindRipples.m old mode 100755 new mode 100644 similarity index 100% rename from detectors/detectEvents/bz_FindRipples.m rename to analysis/SharpWaveRipples/bz_FindRipples.m 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/lfp/SharpWaveRipples/bz_getRipSpikes.m b/analysis/SharpWaveRipples/bz_getRipSpikes.m similarity index 100% rename from analysis/lfp/SharpWaveRipples/bz_getRipSpikes.m rename to analysis/SharpWaveRipples/bz_getRipSpikes.m 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/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 100% rename from analysis/spikes/functionalConnectionIdentification/bz_GetMonoSynapticallyConnected.m rename to analysis/monosynapticPairs/bz_GetMonoSynapticallyConnected.m diff --git a/analysis/spikes/functionalConnectionIdentification/bz_MonoSynConvClick.m b/analysis/monosynapticPairs/bz_MonoSynConvClick.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/functionalConnectionIdentification/bz_MonoSynConvClick.m rename to analysis/monosynapticPairs/bz_MonoSynConvClick.m 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/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/spikes/placeFields/bz_findPlaceFields1D.m b/analysis/placeFields/bz_findPlaceFields1D.m similarity index 100% rename from analysis/spikes/placeFields/bz_findPlaceFields1D.m rename to analysis/placeFields/bz_findPlaceFields1D.m 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/placeFields/bz_firingMap1D.m b/analysis/placeFields/bz_firingMap1D.m similarity index 100% rename from analysis/spikes/placeFields/bz_firingMap1D.m rename to analysis/placeFields/bz_firingMap1D.m diff --git a/analysis/spikes/placeFields/bz_firingMapAvg.m b/analysis/placeFields/bz_firingMapAvg.m similarity index 100% rename from analysis/spikes/placeFields/bz_firingMapAvg.m rename to analysis/placeFields/bz_firingMapAvg.m diff --git a/analysis/spikes/placeFields/bz_getPlaceFields.m b/analysis/placeFields/bz_getPlaceFields.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/placeFields/bz_getPlaceFields.m rename to analysis/placeFields/bz_getPlaceFields.m diff --git a/analysis/spikes/placeFields/bz_getPlaceFields1D.m b/analysis/placeFields/bz_getPlaceFields1D.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/placeFields/bz_getPlaceFields1D.m rename to analysis/placeFields/bz_getPlaceFields1D.m diff --git a/analysis/spikes/positionDecoding/bz_positionDecodingBayesian.m b/analysis/positionDecoding/bz_positionDecodingBayesian.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/positionDecoding/bz_positionDecodingBayesian.m rename to analysis/positionDecoding/bz_positionDecodingBayesian.m diff --git a/analysis/spikes/positionDecoding/bz_positionDecodingGLM.m b/analysis/positionDecoding/bz_positionDecodingGLM.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/positionDecoding/bz_positionDecodingGLM.m rename to analysis/positionDecoding/bz_positionDecodingGLM.m diff --git a/analysis/spikes/positionDecoding/bz_positionDecodingGLM_gauss.m b/analysis/positionDecoding/bz_positionDecodingGLM_gauss.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/positionDecoding/bz_positionDecodingGLM_gauss.m rename to analysis/positionDecoding/bz_positionDecodingGLM_gauss.m diff --git a/analysis/spikes/positionDecoding/bz_positionDecodingMaxCorr.m b/analysis/positionDecoding/bz_positionDecodingMaxCorr.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/positionDecoding/bz_positionDecodingMaxCorr.m rename to analysis/positionDecoding/bz_positionDecodingMaxCorr.m diff --git a/analysis/spikes/positionDecoding/bz_positionDecodingThetaSeq.m b/analysis/positionDecoding/bz_positionDecodingThetaSeq.m old mode 100755 new mode 100644 similarity index 100% rename from analysis/spikes/positionDecoding/bz_positionDecodingThetaSeq.m rename to analysis/positionDecoding/bz_positionDecodingThetaSeq.m diff --git a/analysis/spikes/positionDecoding/placeBayes.m b/analysis/positionDecoding/placeBayes.m old mode 100755 new mode 100644 similarity index 96% rename from analysis/spikes/positionDecoding/placeBayes.m rename to analysis/positionDecoding/placeBayes.m index e0db8336..d3ab6e05 --- a/analysis/spikes/positionDecoding/placeBayes.m +++ b/analysis/positionDecoding/placeBayes.m @@ -1,40 +1,40 @@ -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 +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 100% rename from analysis/lfp_spikes/PhaseModulation/bz_PhaseModulation.m rename to analysis/spikeLFPcoupling/bz_PhaseModulation.m 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 From 0dd055ab0c7025a161663b73b656ebdea1b3a403 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 1 Nov 2019 12:34:26 -0400 Subject: [PATCH 23/32] Made intan read be subfunction of bz_DatFileMetadata.m Made intan read be subfunction of bz_DatFileMetadata.m --- preprocessing/bz_DatFileMetadata.m | 621 ++++++++++++++++++++++++++++- 1 file changed, 620 insertions(+), 1 deletion(-) 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 From 3ab46c83cfa9ccbc3623ddd57436c80798cbf7e0 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 1 Nov 2019 17:16:42 -0400 Subject: [PATCH 24/32] bz_LFPfromDat.m: Gave option to specify no GPU use bz_LFPfromDat.m: Gave option to specify no GPU use --- io/bz_LFPfromDat.m | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) 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 From 62a449ef66fba77beb27c35c081fff210a265f9f Mon Sep 17 00:00:00 2001 From: rhuszar Date: Sat, 2 Nov 2019 15:39:38 -0400 Subject: [PATCH 25/32] update monosyn detection, add synapsemble code --- .../bz_GetMonoSynapticallyConnected.m | 48 ++- .../monosynapticPairs/bz_GetSynapsembles.m | 227 +++++++++++++ .../monosynapticPairs/bz_MonoSynConvClick.m | 4 +- .../monosynapticPairs/bz_fitPoissPlasticity.m | 294 +++++++++++++++++ .../monosynapticPairs/utils/calculate_X.m | 29 ++ .../utils/getCubicBSplineBasis.m | 44 +++ .../utils/nll_poissPlasticity.m | 21 ++ compileBuzcode.m | 13 +- externalPackages/CONVNFFT/conv2fft.m | 40 +++ externalPackages/CONVNFFT/convnfft.m | 219 +++++++++++++ externalPackages/CONVNFFT/convnfft_install.m | 23 ++ externalPackages/CONVNFFT/inplaceprod.c | 187 +++++++++++ .../fastBSpline/CompileMexFiles.m | 6 + externalPackages/fastBSpline/TryBSpline.m | 151 +++++++++ externalPackages/fastBSpline/evalBSpline.c | 162 ++++++++++ externalPackages/fastBSpline/evalBin.c | 162 ++++++++++ externalPackages/fastBSpline/evalBin.snip.c | 10 + externalPackages/fastBSpline/evalBinTimesY.c | 171 ++++++++++ .../fastBSpline/evalBinTimesY.snip.c | 11 + .../fastBSpline/evalBspline.snip.c | 13 + externalPackages/fastBSpline/fastBSpline.m | 304 ++++++++++++++++++ externalPackages/fastBSpline/license.txt | 24 ++ externalPackages/fastBSpline/mexmetypecheck.c | 46 +++ visualization/colormaps/bluewhitered.m | 122 +++++++ 24 files changed, 2322 insertions(+), 9 deletions(-) create mode 100644 analysis/monosynapticPairs/bz_GetSynapsembles.m create mode 100644 analysis/monosynapticPairs/bz_fitPoissPlasticity.m create mode 100644 analysis/monosynapticPairs/utils/calculate_X.m create mode 100644 analysis/monosynapticPairs/utils/getCubicBSplineBasis.m create mode 100644 analysis/monosynapticPairs/utils/nll_poissPlasticity.m create mode 100644 externalPackages/CONVNFFT/conv2fft.m create mode 100644 externalPackages/CONVNFFT/convnfft.m create mode 100644 externalPackages/CONVNFFT/convnfft_install.m create mode 100644 externalPackages/CONVNFFT/inplaceprod.c create mode 100644 externalPackages/fastBSpline/CompileMexFiles.m create mode 100644 externalPackages/fastBSpline/TryBSpline.m create mode 100644 externalPackages/fastBSpline/evalBSpline.c create mode 100644 externalPackages/fastBSpline/evalBin.c create mode 100644 externalPackages/fastBSpline/evalBin.snip.c create mode 100644 externalPackages/fastBSpline/evalBinTimesY.c create mode 100644 externalPackages/fastBSpline/evalBinTimesY.snip.c create mode 100644 externalPackages/fastBSpline/evalBspline.snip.c create mode 100644 externalPackages/fastBSpline/fastBSpline.m create mode 100644 externalPackages/fastBSpline/license.txt create mode 100644 externalPackages/fastBSpline/mexmetypecheck.c create mode 100644 visualization/colormaps/bluewhitered.m diff --git a/analysis/monosynapticPairs/bz_GetMonoSynapticallyConnected.m b/analysis/monosynapticPairs/bz_GetMonoSynapticallyConnected.m index e5104cfb..5824f84a 100644 --- a/analysis/monosynapticPairs/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/monosynapticPairs/bz_MonoSynConvClick.m b/analysis/monosynapticPairs/bz_MonoSynConvClick.m index ea15811a..d1689ce5 100644 --- a/analysis/monosynapticPairs/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/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..ffc66973 --- /dev/null +++ b/analysis/monosynapticPairs/utils/nll_poissPlasticity.m @@ -0,0 +1,21 @@ +function [f,df,lambda] = nll_rate(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/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/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 From 1506cd42c8f7269890e10030c0ba0dcba436aa1c Mon Sep 17 00:00:00 2001 From: rhuszar Date: Sun, 3 Nov 2019 15:38:33 -0500 Subject: [PATCH 26/32] add function to extract accelerometer data --- .../utils/nll_poissPlasticity.m | 2 +- externalPackages/read_Intan_RHD2000_file.m | 216 +++++++++++++----- io/bz_getIntanAccel.m | 207 +++++++++++++++++ 3 files changed, 363 insertions(+), 62 deletions(-) create mode 100644 io/bz_getIntanAccel.m diff --git a/analysis/monosynapticPairs/utils/nll_poissPlasticity.m b/analysis/monosynapticPairs/utils/nll_poissPlasticity.m index ffc66973..706bf641 100644 --- a/analysis/monosynapticPairs/utils/nll_poissPlasticity.m +++ b/analysis/monosynapticPairs/utils/nll_poissPlasticity.m @@ -1,4 +1,4 @@ -function [f,df,lambda] = nll_rate(bta,X,Yy,opts) +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. 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_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 + + + + From ad88eff4afb4dcc53ea0680fc482f8a9168b6e6a Mon Sep 17 00:00:00 2001 From: eliezyer Date: Sun, 3 Nov 2019 23:13:30 -0500 Subject: [PATCH 27/32] updates on CFC functions --- .../PhaseAmpCouplingByAmp.m | 91 --------- .../CrossFrequencyCoupling/bz_CFCPhaseAmp.m | 7 +- .../bz_Comodulogram.m | 0 .../bz_PhaseAmpCouplingByAmp.m | 186 ++++++++++++++++++ .../bz_PhaseAmplitudeDist.m | 31 +-- 5 files changed, 210 insertions(+), 105 deletions(-) delete mode 100755 analysis/lfp/CrossFrequencyCoupling/PhaseAmpCouplingByAmp.m rename analysis/lfp/{ => CrossFrequencyCoupling}/bz_Comodulogram.m (100%) create mode 100755 analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmpCouplingByAmp.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 index d816f4f9..6f32a874 100644 --- a/analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m +++ b/analysis/lfp/CrossFrequencyCoupling/bz_CFCPhaseAmp.m @@ -61,10 +61,13 @@ %% 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,'ampChans',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); @@ -74,7 +77,7 @@ parse(p,varargin{:}); phaseCh = p.Results.phaseCh; -ampChans = p.Results.ampChans; +ampChans = p.Results.ampCh; samplingRate = p.Results.samplingRate; makePlot = p.Results.makePlot; filterType = p.Results.filterType; diff --git a/analysis/lfp/bz_Comodulogram.m b/analysis/lfp/CrossFrequencyCoupling/bz_Comodulogram.m similarity index 100% rename from analysis/lfp/bz_Comodulogram.m rename to analysis/lfp/CrossFrequencyCoupling/bz_Comodulogram.m 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 index 95160076..5052a9e3 100755 --- a/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m +++ b/analysis/lfp/CrossFrequencyCoupling/bz_PhaseAmplitudeDist.m @@ -1,13 +1,13 @@ -function [phaseamplitudemap] = bz_PhaseAmplitudeDist(lfp,phaserange,amprange,varargin) +function [phaseamplitude] = bz_PhaseAmplitudeDist(lfp,phaserange,amprange,varargin) % [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 +%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 phase signal +% 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) @@ -24,7 +24,7 @@ % ========================================================================= % %OUTPUT -% phaseamplitudemap phase by frequency matrix. value is mean amplitude +% 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 @@ -41,14 +41,20 @@ % % %DLevenstein 2016 -%Updated by Eliezyer de Oliviera (EFO)/2019 -%TO DO: intervals support +%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],@isvector); +addParameter(p,'intervals',[0 inf],@isnumeric); addParameter(p,'nfreqs',100,@isnumeric); addParameter(p,'ncyc',7,@isnumeric); addParameter(p,'makePlot',true,@islogical); @@ -60,13 +66,14 @@ 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); +filtered_phase = bz_Filter(lfp,'passband',phaserange,'filter',filterType,'order',filterOrder,'intervals',intervals); %% Get LFP, Phase in intervals %edgebuffer = 1; %s @@ -76,7 +83,7 @@ % LFPphase_int = IsolateEpochs2(filtered.phase,int,edgebuffer,sf_LFP); %% Wavelet Transform LFP in intervals -wavespec_amp = bz_WaveSpec(lfp,'frange',amprange,'nfreqs',nfreqs,'intervals',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 @@ -112,8 +119,8 @@ 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('Frequency (Hz') + xlabel(['Phase (',num2str(phaseamplitude.phase_range),' Hz)']); + ylabel('Amplitude Frequency (Hz)') LogScale('y',2) xlim([phaseamplitude.phasecenters(1) phaseamplitude.phasecenters(end)+2*pi]); colorbar From 2506791400ad70eaaaa39810c2a495bf9a57e715 Mon Sep 17 00:00:00 2001 From: Brendon Watson Date: Fri, 8 Nov 2019 22:22:28 -0500 Subject: [PATCH 28/32] Tweaks to findripple param saving and comments & EventViewPlot semicolon --- GUITools/EventExplorer/EEhelpers/EventVewPlot.m | 2 +- analysis/SharpWaveRipples/bz_FindRipples.m | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) mode change 100644 => 100755 analysis/SharpWaveRipples/bz_FindRipples.m 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/SharpWaveRipples/bz_FindRipples.m b/analysis/SharpWaveRipples/bz_FindRipples.m old mode 100644 new mode 100755 index 2680bec8..a56dcd00 --- a/analysis/SharpWaveRipples/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; From c5b91638964554882bc7c0c90764b5b87e422524 Mon Sep 17 00:00:00 2001 From: acnavasolive Date: Thu, 14 Nov 2019 10:04:52 +0100 Subject: [PATCH 29/32] First draft of Rank Order analysis Uploading new folder buzcode/analysis/RankOrder/ Also, test committing. --- analysis/RankOrder/bz_getEventRank.m | 410 +++++++++++++++++++++++++ analysis/RankOrder/bz_getEventSpikes.m | 164 ++++++++++ 2 files changed, 574 insertions(+) create mode 100644 analysis/RankOrder/bz_getEventRank.m create mode 100644 analysis/RankOrder/bz_getEventSpikes.m diff --git a/analysis/RankOrder/bz_getEventRank.m b/analysis/RankOrder/bz_getEventRank.m new file mode 100644 index 00000000..50aa4f90 --- /dev/null +++ b/analysis/RankOrder/bz_getEventRank.m @@ -0,0 +1,410 @@ +function [rankStats] = bz_getEventRank(varargin) +% [rankStats] = getEventRank() +% Get rank order of spikes inside events from previously calculated +% bz_getEventSpikes structure +% +% 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 (# units) x 3 x (# conditions) +% 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 +% third dimension contains this similar matrix for the +% other conditions. +% - 'CenterofMass': searchs for the bz_findPlaceFieldsTemplate +% output, the X.placeFieldTemplate.mat, loads it +% and takes the 'CenterofMass' field, a (# units) x +% 3 x (# conditions) 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 third dimension contains this similar matrix +% for the other conditions. +% '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. +% '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 rank +% clusters, clusters within which rank is highly +% correlated (default: 0.8) +% 'doPlot' Make plot, logical (default: true) +% 'saveMat' Saves file, logical (default: true) +% +% ========================================================================= +% +% +% INPUTS +% +% ========================================================================= +% Properties Values +% ------------------------------------------------------------------------- +% 'rankStats' structure with the following subfields: +% .corrMean (double) Mean of rank correlations of all events +% .corrStd (double) Standard deviation of rank correlations +% .corrEvents (1xN matrix) Rank correlations of each event. +% N: number of events +% .corrMeanShuff (numRep x 1 matrix) Mean of rank correlations +% of each event for all permutation tests +% .corrEventsShuff (numRep x N matrix) Rank correlations +% of each event for all permutation tests +% .pvalEvents pvalue per event +% .pvalTotal binomial test over 'pvalEvents' +% +% +% +% Andrea Navas-Olive, 2019 + +% TODO: +% - Have an option to test two group events +% - Test With an external template + +%% Parse inputs +p = inputParser; +addParameter(p,'basepath',pwd,@isstr); +addParameter(p,'spkEventTimes',{},@isstruct); +addParameter(p,'templateType','MeanRank',@isstr); +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; +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); + +% 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 +corrMeanShuff = zeros(numRep,1); +corrEventsShuff = zeros(numRep,length(evtTimes)); +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); + 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(1,length(corrEvents)); +parfor event = 1:length(corrEvents) + pvalEvents(event) = sum( corrEvents(event) > corrEventsShuff(:,event) ) / 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 + +% Events with statistically significant rank sequences +statEvents = find(pvalEvents<=0.05); + +if length(statEvents)>2 + % 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; +else + rankClusters = zeros(size(pvalEvents)); +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('tree') + % 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, spkPos, evtTimes, templateExt) + + + % ... 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)); + parfor event = 1:length(evtTimes) + % Order of units in this event + rankThisEvent = spkPos(:,event); + % Mean order of units alon g the rest of the events (template) + rankTemplate = nanmean(spkPos(:,[1:event-1,event+1:end]),2); + % Correlation between these previous variables + corrEvents(event) = corr(rankThisEvent, rankTemplate, 'rows', 'complete'); + end + % Mean and standard deviation of rank correlation + corrMean = nanmean(corrEvents); + corrStd = nanstd(corrEvents); + % Pairwise method: + elseif strcmp(templateType,'Pairwise') + corrEvents = corr(spkPos, 'rows', 'pairwise'); + corrEvents(find(eye(size(corrEvents)))) = nan; + corrEvents = nanmean(corrEvents); + % 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(size(evtTimes,2),length(templateExt)); + corrMean = zeros(1,length(templateExt)); + corrStd = zeros(1,length(templateExt)); + for iCond = 1:length(templateExt) + parfor event = 1:size(evtTimes,2) + % Order of units in this event + rankThisEvent = spkPos(:,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)) = [1:length(templateExt{iCond}(:,3))]; + % Normalize + if normalize + rankTemplate = rankTemplate / length(templateExt{iCond}(:,3)); + end + % Correlation between these previous variables + corrEvents(event,iCond) = corr(rankThisEvent, rankTemplate, 'rows', 'complete'); + end + end + % Mean and standard deviation of rank correlation + corrMean(iCond) = nanmean(corrEvents); + corrStd(iCond) = nanstd(corrEvents); + 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_getEventSpikes.m b/analysis/RankOrder/bz_getEventSpikes.m new file mode 100644 index 00000000..b15705ee --- /dev/null +++ b/analysis/RankOrder/bz_getEventSpikes.m @@ -0,0 +1,164 @@ +function [spkEventTimes] = bz_getEventSpikes(varargin) +% [SpkEventTimes] = bz_getEventSpikes() +% 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. +% +% ========================================================================= +% +% 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 From cc269b35cc500f6833cd0f98e3cf4a7f188c4a51 Mon Sep 17 00:00:00 2001 From: acnavasolive Date: Fri, 15 Nov 2019 17:31:02 +0100 Subject: [PATCH 30/32] Update bz_RankOrder Inside analysis/RankOrder folder, main changes: - Rename file from bz_getEventRank to bz_RankOrder - Rename file from bz_getEventSpikes to bz_getSpikesRank - Now there is the option to test rank order from one group of events against another group (e.g. spontaneous vs induced ripples), through the 'eventIDs' input. --- .../{bz_getEventRank.m => bz_RankOrder.m} | 138 +++++++++++++----- ...bz_getEventSpikes.m => bz_getSpikesRank.m} | 8 +- 2 files changed, 108 insertions(+), 38 deletions(-) rename analysis/RankOrder/{bz_getEventRank.m => bz_RankOrder.m} (71%) rename analysis/RankOrder/{bz_getEventSpikes.m => bz_getSpikesRank.m} (96%) diff --git a/analysis/RankOrder/bz_getEventRank.m b/analysis/RankOrder/bz_RankOrder.m similarity index 71% rename from analysis/RankOrder/bz_getEventRank.m rename to analysis/RankOrder/bz_RankOrder.m index 50aa4f90..3ac5facf 100644 --- a/analysis/RankOrder/bz_getEventRank.m +++ b/analysis/RankOrder/bz_RankOrder.m @@ -1,7 +1,13 @@ -function [rankStats] = bz_getEventRank(varargin) -% [rankStats] = getEventRank() +function [rankStats] = bz_RankOrder(varargin) +% [rankStats] = RankOrder() % Get rank order of spikes inside events from previously calculated -% bz_getEventSpikes structure +% 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 % @@ -9,7 +15,7 @@ % Properties Values % ------------------------------------------------------------------------- % 'basepath' full path where session is located (default pwd) -% e.g. /mnt/Data/buddy140_060813_reo/buddy140_060813_reo +% 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. @@ -38,11 +44,23 @@ % third column has the position of the unit in the UID % vector. The third dimension contains 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. +% '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. @@ -52,16 +70,17 @@ % 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 rank -% clusters, clusters within which rank is highly +% '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 plot, logical (default: true) +% 'doPlot' Make plots, logical (default: true) % 'saveMat' Saves file, logical (default: true) % % ========================================================================= % % -% INPUTS +% OUTPUTS % % ========================================================================= % Properties Values @@ -69,21 +88,38 @@ % 'rankStats' structure with the following subfields: % .corrMean (double) Mean of rank correlations of all events % .corrStd (double) Standard deviation of rank correlations -% .corrEvents (1xN matrix) Rank correlations of each event. -% N: number of events +% .corrEvents (1x#events matrix) Rank correlations of each event. % .corrMeanShuff (numRep x 1 matrix) Mean of rank correlations % of each event for all permutation tests -% .corrEventsShuff (numRep x N matrix) Rank correlations +% .corrEventsShuff (numRep x #events matrix) Rank correlations % of each event for all permutation tests % .pvalEvents pvalue per event % .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 +% +% % % Andrea Navas-Olive, 2019 % TODO: -% - Have an option to test two group events % - Test With an external template %% Parse inputs @@ -91,6 +127,7 @@ 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); @@ -104,6 +141,7 @@ 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; @@ -175,7 +213,7 @@ % Compute mean and standard deviation of rank correlation, and correlation % event by event -[corrMean, corrStd, corrEvents] = compute_rank_correlation(templateType, rankUnits, evtTimes, templateExt); +[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, @@ -189,7 +227,7 @@ % 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); + [tmpcorr, ~, tmpcorrTemplate] = compute_rank_correlation(templateType, rankUnitsSuffled, evtTimes, templateExt, eventIDs); corrMeanShuff(irep) = tmpcorr; corrEventsShuff(irep,:) = tmpcorrTemplate; end @@ -213,7 +251,7 @@ % Events with statistically significant rank sequences statEvents = find(pvalEvents<=0.05); -if length(statEvents)>2 +try % Perform a pairwise correlation corrMat = corr(rankUnits(:,statEvents), 'rows', 'pairwise'); % Clean correlation matrix (remove nans) @@ -232,7 +270,7 @@ % Save sequences rankClusters = zeros(size(pvalEvents)); rankClusters(statEvents) = groups; -else +catch rankClusters = zeros(size(pvalEvents)); end @@ -268,7 +306,7 @@ xlabel('Correlation values') ylabel('Distribution') - if exist('tree') + if exist('groups') % Plot hierarchical tree figure(), dendrogram(tree,'ColorThreshold',cutoff) @@ -311,33 +349,61 @@ % RANK CORRELATION... -function [corrMean, corrStd, corrEvents] = compute_rank_correlation(templateType, spkPos, evtTimes, templateExt) +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 + % 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)); - parfor event = 1:length(evtTimes) - % Order of units in this event - rankThisEvent = spkPos(:,event); - % Mean order of units alon g the rest of the events (template) - rankTemplate = nanmean(spkPos(:,[1:event-1,event+1:end]),2); - % Correlation between these previous variables - corrEvents(event) = corr(rankThisEvent, rankTemplate, 'rows', 'complete'); + % - 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(spkPos, 'rows', 'pairwise'); - corrEvents(find(eye(size(corrEvents)))) = nan; - corrEvents = nanmean(corrEvents); + 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); @@ -354,7 +420,7 @@ for iCond = 1:length(templateExt) parfor event = 1:size(evtTimes,2) % Order of units in this event - rankThisEvent = spkPos(:,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 diff --git a/analysis/RankOrder/bz_getEventSpikes.m b/analysis/RankOrder/bz_getSpikesRank.m similarity index 96% rename from analysis/RankOrder/bz_getEventSpikes.m rename to analysis/RankOrder/bz_getSpikesRank.m index b15705ee..2a0ce2c8 100644 --- a/analysis/RankOrder/bz_getEventSpikes.m +++ b/analysis/RankOrder/bz_getSpikesRank.m @@ -1,5 +1,5 @@ -function [spkEventTimes] = bz_getEventSpikes(varargin) -% [SpkEventTimes] = bz_getEventSpikes() +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 @@ -65,6 +65,10 @@ % % ========================================================================= % +% See also bz_RankOrder +% +% +% % Antonio FR, 2017 % Convert to buzcode format: Andrea Navas-Olive, 2019 From fcc61940f57bc443bda4852e5a77c6b176433859 Mon Sep 17 00:00:00 2001 From: acnavasolive Date: Mon, 18 Nov 2019 18:40:35 +0100 Subject: [PATCH 31/32] Updating bz_RankOrder: correlation against external template The last TODO of this function. Now the following fields from rankStats structure: - corrMean - corrStd - corrEvents - corrpvalEvents have an extradimension for each condition in X.placeFieldTemplate.mat, if the 'templateType' is either 'Peak' or 'CenterofMass' --- analysis/RankOrder/bz_RankOrder.m | 104 ++++++++++++++++-------------- 1 file changed, 55 insertions(+), 49 deletions(-) diff --git a/analysis/RankOrder/bz_RankOrder.m b/analysis/RankOrder/bz_RankOrder.m index 3ac5facf..afd30102 100644 --- a/analysis/RankOrder/bz_RankOrder.m +++ b/analysis/RankOrder/bz_RankOrder.m @@ -28,21 +28,23 @@ % 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 (# units) x 3 x (# conditions) +% 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 -% third dimension contains this similar matrix for 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' field, a (# units) x -% 3 x (# conditions) 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 third dimension contains this similar matrix +% 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 @@ -113,7 +115,7 @@ % 2 is sequence E A D % % -% See also bz_getSpikesRank +% See also bz_getSpikesRank, bz_findPlaceFieldsTemplate % % % @@ -221,23 +223,24 @@ % repeated 'numRep' times and the observed mean rank correlation is % compared with the distribution of the randomly permuted mean rank % correlations -corrMeanShuff = zeros(numRep,1); -corrEventsShuff = zeros(numRep,length(evtTimes)); +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; + 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(1,length(corrEvents)); +pvalEvents = zeros(nCond,length(corrEvents)); parfor event = 1:length(corrEvents) - pvalEvents(event) = sum( corrEvents(event) > corrEventsShuff(:,event) ) / numRep; + pvalEvents(:,event) = sum( corrEvents(:,event) > squeeze(corrEventsShuff(:,event,:))',2 ) / numRep; end % Transform it in a p-value pvalEvents = 1 - pvalEvents; @@ -248,34 +251,37 @@ %% Find different sequences -% 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)); +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 @@ -414,9 +420,9 @@ % Template method: external template if ismember(templateType,{'Peak','CenterofMass'}) % Initialize - corrEvents = zeros(size(evtTimes,2),length(templateExt)); - corrMean = zeros(1,length(templateExt)); - corrStd = zeros(1,length(templateExt)); + 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 @@ -425,18 +431,18 @@ % 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)) = [1:length(templateExt{iCond}(:,3))]; + rankTemplate(templateExt{iCond}(:,3)) = templateExt{iCond}(:,1); % Normalize - if normalize - rankTemplate = rankTemplate / length(templateExt{iCond}(:,3)); + 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(event,iCond) = corr(rankThisEvent, rankTemplate, 'rows', 'complete'); + corrEvents(iCond,event) = corr(rankThisEvent, rankTemplate, 'rows', 'complete'); end end % Mean and standard deviation of rank correlation - corrMean(iCond) = nanmean(corrEvents); - corrStd(iCond) = nanstd(corrEvents); + corrMean = nanmean(corrEvents,2); + corrStd = nanstd(corrEvents,[],2); end end From edf58feae889b1f62ecc6d2d415cdba49f92c473 Mon Sep 17 00:00:00 2001 From: acnavasolive Date: Mon, 18 Nov 2019 18:55:28 +0100 Subject: [PATCH 32/32] Update bz_RankOrder documentation --- analysis/RankOrder/bz_RankOrder.m | 34 ++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/analysis/RankOrder/bz_RankOrder.m b/analysis/RankOrder/bz_RankOrder.m index afd30102..ec01c476 100644 --- a/analysis/RankOrder/bz_RankOrder.m +++ b/analysis/RankOrder/bz_RankOrder.m @@ -88,14 +88,30 @@ % Properties Values % ------------------------------------------------------------------------- % 'rankStats' structure with the following subfields: -% .corrMean (double) Mean of rank correlations of all events -% .corrStd (double) Standard deviation of rank correlations -% .corrEvents (1x#events matrix) Rank correlations of each event. -% .corrMeanShuff (numRep x 1 matrix) Mean of rank correlations -% of each event for all permutation tests -% .corrEventsShuff (numRep x #events matrix) Rank correlations -% of each event for all permutation tests -% .pvalEvents pvalue per event +% .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 @@ -121,8 +137,6 @@ % % Andrea Navas-Olive, 2019 -% TODO: -% - Test With an external template %% Parse inputs p = inputParser;