Skip to content

Commit

Permalink
duplicate spikes only if on same channels, and maxChannel bug fix
Browse files Browse the repository at this point in the history
  • Loading branch information
Julie-Fabre committed Oct 29, 2023
1 parent 6d74ae6 commit 5b011e8
Show file tree
Hide file tree
Showing 8 changed files with 453 additions and 87 deletions.
9 changes: 5 additions & 4 deletions bc_qualityMetrics_pipeline.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@


%% set paths - EDIT THESE
ephysKilosortPath = '/home/netshare/zaru/JF093/2023-03-06/ephys/kilosort2/site1/';% path to your kilosort output files
ephysRawDir = dir('/home/netshare/zaru/JF093/2023-03-06/ephys/site1/*ap*.*bin'); % path to yourraw .bin or .dat data
ephysMetaDir = dir('/home/netshare/zaru/JF093/2023-03-06/ephys/site1/*ap*.*meta'); % path to your .meta or .oebin meta file
savePath = '/media/julie/ExtraHD/JF093/qMetrics'; % where you want to save the quality metrics
% '/home/netshare/zaru/JF093/2023-03-06/ephys/kilosort2/site1
ephysKilosortPath = '/home/netshare/zaru/JF093/2023-03-08/ephys/pykilosort/site2/output';% path to your kilosort output files
ephysRawDir = dir('/home/netshare/zaru/JF093/2023-03-08/ephys/site2/*ap*.*bin'); % path to your raw .bin or .dat data
ephysMetaDir = dir('/home/netshare/zaru/JF093/2023-03-08/ephys/site2/*ap*.*meta'); % path to your .meta or .oebin meta file
savePath = '/media/julie/ExtraHD/JF093/2023-03-08/ephys/site2/qMetrics'; % where you want to save the quality metrics
decompressDataLocal = '/media/julie/ExtraHD/decompressedData'; % where to save raw decompressed ephys data
gain_to_uV = 0.195; % use this if you are not using spikeGLX or openEphys to record your data. You then must leave the ephysMetaDir
% empty(e.g. ephysMetaDir = '')
Expand Down
2 changes: 1 addition & 1 deletion qualityMetrics/bc_checkParameterFields.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
%% Default values for fields
% duplicate spikes
defaultValues.removeDuplicateSpikes = 0;
defaultValues.duplicateSpikeWindow_s = 0.000166;
defaultValues.duplicateSpikeWindow_s = 0.00005;
defaultValues.saveSpikes_withoutDuplicates = 1;
defaultValues.recomputeDuplicateSpikes = 0;

Expand Down
2 changes: 1 addition & 1 deletion qualityMetrics/bc_qualityParamValues.m
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

% duplicate spikes parameters
param.removeDuplicateSpikes = 1;
param.duplicateSpikeWindow_s = 0.000166; % in seconds
param.duplicateSpikeWindow_s = 0.0000025; % in seconds
param.saveSpikes_withoutDuplicates = 1;
param.recomputeDuplicateSpikes = 0;

Expand Down
147 changes: 147 additions & 0 deletions qualityMetrics/bc_removeDuplicateSpikes.asv
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
function [nonEmptyUnits, duplicateSpikes_idx, spikeTimes_samples, spikeTemplates, templateAmplitudes, ...
pcFeatures, rawWaveformsFull, rawWaveformsPeakChan, signalToNoiseRatio] = ...
bc_removeDuplicateSpikes(spikeTimes_samples, spikeTemplates, templateAmplitudes, ...
pcFeatures, rawWaveformsFull, rawWaveformsPeakChan, signalToNoiseRatio, ...
removeDuplicateSpikes_flag, ...
duplicateSpikeWindow_s, ephys_sample_rate, saveSpikes_withoutDuplicates_flag, savePath, recompute)
% JF, Remove any duplicate spikes
% Some spike sorters (including kilosort) can sometimes count spikes twice
% if for instance the residuals are re-fitted. see https://github.com/MouseLand/Kilosort/issues/29
% ------
% Inputs
% ------
%
% ------
% Outputs
% ------
%

if removeDuplicateSpikes_flag
% Check if we need to extract duplicate spikes
if recompute || isempty(dir([savePath, filesep, 'spikes._bc_duplicateSpikes.npy']))
% Parameters
duplicateSpikeWindow_samples = duplicateSpikeWindow_s * ephys_sample_rate;
batch_size = 10000;
overlap_size = 100;
numSpikes_full = length(spikeTimes_samples);

% initialize and re-allocate
duplicateSpikes_idx = false(1, numSpikes_full);

% check for duplicate spikes in batches
for start_idx = 1:batch_size - overlap_size:numSpikes_full
end_idx = min(start_idx+batch_size-1, numSpikes_full);
batch_spikeTimes_samples = spikeTimes_samples(start_idx:end_idx);
batch_spikeTemplates = spikeTemplates(start_idx:end_idx);
batch_templateAmplitudes = templateAmplitudes(start_idx:end_idx);

[~, ~, batch_removeIdx] = removeDuplicates(batch_spikeTimes_samples, ...
batch_spikeTemplates, batch_templateAmplitudes, duplicateSpikeWindow_samples);

duplicateSpikes_idx(start_idx:end_idx) = batch_removeIdx;

if end_idx == numSpikes_full
break;
end
end
% save data if required
if saveSpikes_withoutDuplicates_flag
writeNPY(duplicateSpikes_idx, [savePath, filesep, 'spikes._bc_duplicateSpikes.npy'])
end

else
duplicateSpikes_idx = readNPY([savePath, filesep, 'spikes._bc_duplicateSpikes.npy']);
end

% check if there are any empty units
unique_templates = unique(spikeTemplates);
nonEmptyUnits = unique(spikeTemplates(~duplicateSpikes_idx));
emptyUnits_idx = ~ismember(unique_templates, nonEmptyUnits);

% remove any empty units from ephys data
spikeTimes_samples = spikeTimes_samples(~duplicateSpikes_idx);
spikeTemplates = spikeTemplates(~duplicateSpikes_idx);
templateAmplitudes = templateAmplitudes(~duplicateSpikes_idx);
if ~isempty(pcFeatures)
pcFeatures = pcFeatures(~duplicateSpikes_idx, :, :);
end
if ~isempty(rawWaveformsFull)
rawWaveformsFull = rawWaveformsFull(~emptyUnits_idx, :, :);
rawWaveformsPeakChan = rawWaveformsPeakChan(~emptyUnits_idx);
end

if ~isempty(signalToNoiseRatio)
signalToNoiseRatio = signalToNoiseRatio(~emptyUnits_idx);
end

fprintf('\n Removed %.0f spike duplicates out of %.0f. \n', sum(duplicateSpikes_idx), length(duplicateSpikes_idx))

else
nonEmptyUnits = unique(spikeTemplates);
duplicateSpikes_idx = zeros(size(spikeTimes_samples, 1), 1);

end


function [spikeTimes_samples, spikeTemplates, removeIdx] = removeDuplicates(spikeTimes_samples, ...
spikeTemplates, templateAmplitudes, duplicateSpikeWindow_samples)
numSpikes = length(spikeTimes_samples);
removeIdx = false(1, numSpikes);

% Intra-unit duplicate removal
for i = 1:numSpikes
if removeIdx(i)
continue;
end

for j = i + 1:numSpikes
if removeIdx(j)
continue;
end

if spikeTemplates(i) == spikeTemplates(j)
if abs(spikeTimes_samples(i)-spikeTimes_samples(j)) <= duplicateSpikeWindow_samples
if templateAmplitudes(i) < templateAmplitudes(j)
spikeTimes_samples(i) = NaN;
removeIdx(i) = true;
break;
else
spikeTimes_samples(j) = NaN;
removeIdx(j) = true;
end
end
end
end
end


% Inter-unit duplicate removal
unitSpikeCounts = accumarray(spikeTemplates, 1);
for i = 1:length(spikeTimes_samples)
if removeIdx(i)
continue;
end

for j = i + 1:length(spikeTimes_samples)
if removeIdx(j)
continue;
end

if spikeTemplates(i) ~= spikeTemplates(j)
if abs(spikeTimes_samples(i)-spikeTimes_samples(j)) <= duplicateSpikeWindow_samples
if unitSpikeCounts(spikeTemplates(i)) < unitSpikeCounts(spikeTemplates(j))
spikeTimes_samples(i) = NaN;
removeIdx(i) = true;
break;
else
spikeTimes_samples(j) = NaN;
removeIdx(j) = true;
end
end
end
end
end
end


end
81 changes: 49 additions & 32 deletions qualityMetrics/bc_removeDuplicateSpikes.m
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
function [nonEmptyUnits, duplicateSpikes_idx, spikeTimes_samples, spikeTemplates, templateAmplitudes, ...
pcFeatures, rawWaveformsFull, rawWaveformsPeakChan, signalToNoiseRatio] = ...
pcFeatures, rawWaveformsFull, rawWaveformsPeakChan, signalToNoiseRatio, maxChannels] = ...
bc_removeDuplicateSpikes(spikeTimes_samples, spikeTemplates, templateAmplitudes, ...
pcFeatures, rawWaveformsFull, rawWaveformsPeakChan, signalToNoiseRatio,...
removeDuplicateSpikes_flag, ...
pcFeatures, rawWaveformsFull, rawWaveformsPeakChan, signalToNoiseRatio, ...
maxChannels, removeDuplicateSpikes_flag, ...
duplicateSpikeWindow_s, ephys_sample_rate, saveSpikes_withoutDuplicates_flag, savePath, recompute)
% JF, Remove any duplicate spikes
% Some spike sorters (including kilosort) can sometimes count spikes twice
Expand All @@ -28,14 +28,21 @@
% initialize and re-allocate
duplicateSpikes_idx = false(1, numSpikes_full);

% Rename the spike templates according to the remaining templates
good_templates_idx = unique(spikeTemplates);
new_spike_idx = nan(max(spikeTemplates), 1);
new_spike_idx(good_templates_idx) = 1:length(good_templates_idx);
spikeTemplates_flat = new_spike_idx(spikeTemplates);

% check for duplicate spikes in batches
for start_idx = 1:batch_size - overlap_size:numSpikes_full
end_idx = min(start_idx+batch_size-1, numSpikes_full);
batch_spikeTimes_samples = spikeTimes_samples(start_idx:end_idx);
batch_spikeTemplates = spikeTemplates(start_idx:end_idx);
batch_templateAmplitudes = templateAmplitudes(start_idx:end_idx);

[~, ~, batch_removeIdx] = removeDuplicates(batch_spikeTimes_samples, batch_spikeTemplates, batch_templateAmplitudes, duplicateSpikeWindow_samples);
[~, ~, batch_removeIdx] = removeDuplicates(batch_spikeTimes_samples, ...
batch_spikeTemplates, batch_templateAmplitudes, duplicateSpikeWindow_samples, spikeTemplates_flat);

duplicateSpikes_idx(start_idx:end_idx) = batch_removeIdx;

Expand All @@ -57,15 +64,18 @@
nonEmptyUnits = unique(spikeTemplates(~duplicateSpikes_idx));
emptyUnits_idx = ~ismember(unique_templates, nonEmptyUnits);

% remove any empty units from ephys data
% remove any empty units from ephys data
spikeTimes_samples = spikeTimes_samples(~duplicateSpikes_idx);
spikeTemplates = spikeTemplates(~duplicateSpikes_idx);
templateAmplitudes = templateAmplitudes(~duplicateSpikes_idx);
if ~isempty(pcFeatures)
pcFeatures = pcFeatures(~duplicateSpikes_idx, :, :);
end
rawWaveformsFull = rawWaveformsFull(~emptyUnits_idx, :, :);
rawWaveformsPeakChan = rawWaveformsPeakChan(~emptyUnits_idx);
if ~isempty(rawWaveformsFull)
rawWaveformsFull = rawWaveformsFull(~emptyUnits_idx, :, :);
rawWaveformsPeakChan = rawWaveformsPeakChan(~emptyUnits_idx);
end

if ~isempty(signalToNoiseRatio)
signalToNoiseRatio = signalToNoiseRatio(~emptyUnits_idx);
end
Expand All @@ -74,35 +84,39 @@

else
nonEmptyUnits = unique(spikeTemplates);
duplicateSpikes_idx = zeros(size(spikeTimes_samples,1),1);
duplicateSpikes_idx = zeros(size(spikeTimes_samples, 1), 1);

end


function [spikeTimes_samples, spikeTemplates, removeIdx] = removeDuplicates(spikeTimes_samples, spikeTemplates, templateAmplitudes, duplicateSpikeWindow_samples)
function [spikeTimes_samples, spikeTemplates, removeIdx] = removeDuplicates(spikeTimes_samples, ...
spikeTemplates, templateAmplitudes, duplicateSpikeWindow_samples, maxChannels, spikeTemplates_flat)
numSpikes = length(spikeTimes_samples);
removeIdx = false(1, numSpikes);

% Intra-unit duplicate removal
for i = 1:numSpikes
if removeIdx(i)
for iSpike1 = 1:numSpikes
if removeIdx(iSpike1)
continue;
end

for j = i + 1:numSpikes
if removeIdx(j)
for iSpike2 = iSpike1 + 1:numSpikes
if removeIdx(iSpike2)
continue;
end
if maxChannels(spikeTemplates_flat(iSpike2)) ~= maxChannels(spikeTemplates_flat(iSpike1)) % spikes are not on same channel
continue;
end

if spikeTemplates(i) == spikeTemplates(j)
if abs(spikeTimes_samples(i)-spikeTimes_samples(j)) <= duplicateSpikeWindow_samples
if templateAmplitudes(i) < templateAmplitudes(j)
spikeTimes_samples(i) = NaN;
removeIdx(i) = true;
if spikeTemplates(iSpike1) == spikeTemplates(iSpike2)
if abs(spikeTimes_samples(iSpike1)-spikeTimes_samples(iSpike2)) <= duplicateSpikeWindow_samples
if templateAmplitudes(iSpike1) < templateAmplitudes(iSpike2)
spikeTimes_samples(iSpike1) = NaN;
removeIdx(iSpike1) = true;
break;
else
spikeTimes_samples(j) = NaN;
removeIdx(j) = true;
spikeTimes_samples(iSpike2) = NaN;
removeIdx(iSpike2) = true;
end
end
end
Expand All @@ -112,25 +126,28 @@

% Inter-unit duplicate removal
unitSpikeCounts = accumarray(spikeTemplates, 1);
for i = 1:length(spikeTimes_samples)
if removeIdx(i)
for iSpike1 = 1:length(spikeTimes_samples)
if removeIdx(iSpike1)
continue;
end

for j = i + 1:length(spikeTimes_samples)
if removeIdx(j)
for iSpike2 = iSpike1 + 1:length(spikeTimes_samples)
if removeIdx(iSpike2)
continue;
end
if maxChannels(spikeTemplates_flat(iSpike2)) ~= maxChannels(spikeTemplates_flat(iSpike1)) % spikes are not on same channel
continue;
end

if spikeTemplates(i) ~= spikeTemplates(j)
if abs(spikeTimes_samples(i)-spikeTimes_samples(j)) <= duplicateSpikeWindow_samples
if unitSpikeCounts(spikeTemplates(i)) < unitSpikeCounts(spikeTemplates(j))
spikeTimes_samples(i) = NaN;
removeIdx(i) = true;
if spikeTemplates(iSpike1) ~= spikeTemplates(iSpike2)
if abs(spikeTimes_samples(iSpike1)-spikeTimes_samples(iSpike2)) <= duplicateSpikeWindow_samples
if unitSpikeCounts(spikeTemplates(iSpike1)) < unitSpikeCounts(spikeTemplates(iSpike2))
spikeTimes_samples(iSpike1) = NaN;
removeIdx(iSpike1) = true;
break;
else
spikeTimes_samples(j) = NaN;
removeIdx(j) = true;
spikeTimes_samples(iSpike2) = NaN;
removeIdx(iSpike2) = true;
end
end
end
Expand Down
Loading

0 comments on commit 5b011e8

Please sign in to comment.