diff --git a/+bc/+dependencies/SGLX_readMeta.m b/+bc/+dependencies/SGLX_readMeta.m index 84949028..af5e14a9 100644 --- a/+bc/+dependencies/SGLX_readMeta.m +++ b/+bc/+dependencies/SGLX_readMeta.m @@ -4,7 +4,7 @@ % Simple class of MATLAB functions deomonstrating % how to read and manipulate SpikeGLX meta and binary files. % When this file is included in the MATLAB path, the individual methods -% are called with SGLX_readMeta. +% are called with bc.dependencies.SGLX_readMeta. % % The most important method in the class is ReadMeta(). % The functions for reading binary data are not optimized for speed, @@ -34,13 +34,16 @@ % utility functions below. % function [meta] = ReadMeta(binName, path) - + if nargin < 2 + full_path_meta = binName; + else % Create the matching metafile name [~,name,~] = fileparts(binName); metaName = strcat(name, '.meta'); - + full_path_meta = fullfile(path, metaName); + end % Parse ini file into cell entries C{1}{i} = C{2}{i} - fid = fopen(fullfile(path, metaName), 'r'); + fid = fopen(full_path_meta, 'r'); % ------------------------------------------------------------- % Need 'BufSize' adjustment for MATLAB earlier than 2014 % C = textscan(fid, '%[^=] = %[^\r\n]', 'BufSize', 32768); @@ -101,7 +104,7 @@ % Get channel index of requested digital word dwReq switch meta.typeThis case 'imec' - [AP, LF, SY] = SGLX_readMeta.ChannelCountsIM(meta); + [AP, LF, SY] = bc.dependencies.SGLX_readMeta.ChannelCountsIM(meta); if SY == 0 fprintf('No imec sync channel saved\n'); digArray = []; @@ -110,7 +113,7 @@ digCh = AP + LF + dwReq; end case 'nidq' - [MN,MA,XA,DW] = SGLX_readMeta.ChannelCountsNI(meta); + [MN,MA,XA,DW] = bc.dependencies.SGLX_readMeta.ChannelCountsNI(meta); if dwReq > DW fprintf('Maximum digital word in file = %d\n', DW); digArray = []; @@ -119,7 +122,7 @@ digCh = MN + MA + XA + dwReq; end case 'obx' - [XA,DW,SY] = SGLX_readMeta.ChannelCountsOBX(meta); + [XA,DW,SY] = bc.dependencies.SGLX_readMeta.ChannelCountsOBX(meta); if dwReq > DW fprintf('Maximum digital word in file = %d\n', DW); digArray = []; @@ -317,7 +320,7 @@ APgain = APgain + 1; end end - fI2V = SGLX_readMeta.Int2Volts(meta); + fI2V = bc.dependencies.SGLX_readMeta.Int2Volts(meta); APChan0_to_uV = 1e6*fI2V/APgain(1); if size(LFgain) > 0 LFChan0_to_uV = 1e6*fI2V/LFgain(1); @@ -341,12 +344,12 @@ % function dataArray = GainCorrectNI(dataArray, chanList, meta) - [MN,MA] = SGLX_readMeta.ChannelCountsNI(meta); - fI2V = SGLX_readMeta.Int2Volts(meta); + [MN,MA] = bc.dependencies.SGLX_readMeta.ChannelCountsNI(meta); + fI2V = bc.dependencies.SGLX_readMeta.Int2Volts(meta); for i = 1:length(chanList) j = chanList(i); % index into timepoint - conv = fI2V / SGLX_readMeta.ChanGainNI(j, MN, MA, meta); + conv = fI2V / bc.dependencies.SGLX_readMeta.ChanGainNI(j, MN, MA, meta); dataArray(j,:) = dataArray(j,:) * conv; end end % GainCorrectNI @@ -365,7 +368,7 @@ % function dataArray = GainCorrectOBX(dataArray, chanList, meta) - fI2V = SGLX_readMeta.Int2Volts(meta); + fI2V = bc.dependencies.SGLX_readMeta.Int2Volts(meta); for i = 1:length(chanList) j = chanList(i); % index into timepoint @@ -388,13 +391,13 @@ function dataArray = GainCorrectIM(dataArray, chanList, meta) % Look up gain with acquired channel ID - chans = SGLX_readMeta.OriginalChans(meta); - [APgain,LFgain] = SGLX_readMeta.ChanGainsIM(meta); + chans = bc.dependencies.SGLX_readMeta.OriginalChans(meta); + [APgain,LFgain] = bc.dependencies.SGLX_readMeta.ChanGainsIM(meta); nAP = length(APgain); nNu = nAP * 2; % Common conversion factor - fI2V = SGLX_readMeta.Int2Volts(meta); + fI2V = bc.dependencies.SGLX_readMeta.Int2Volts(meta); for i = 1:length(chanList) j = chanList(i); % index into timepoint @@ -421,7 +424,7 @@ 'EndOfLine', ')' ); nBank = numel(C{1}) + 1; % bank0/shank0 is at time = 0 - srate = SGLX_readMeta.SampRate(meta); + srate = bc.dependencies.SGLX_readMeta.SampRate(meta); bankTimes = zeros([nBank,4], "double"); bankTimes(2:nBank,1) = double(C{1}); @@ -470,6 +473,7 @@ function writeMeta(meta, newPath) % streamStr, e.g. 'ap' or 'lf' % % + function [runName,gateStr,triggerStr,probeStr,streamStr] = parseFileName(fileName) % Remove extension, if present diff --git a/+bc/+load/readSpikeGLXMetaFile.m b/+bc/+load/readSpikeGLXMetaFile.m index 5f8f565c..f5046eda 100644 --- a/+bc/+load/readSpikeGLXMetaFile.m +++ b/+bc/+load/readSpikeGLXMetaFile.m @@ -13,98 +13,30 @@ % microvolts % -% read meta file -filetext = fileread(metaFile); +meta = bc.dependencies.SGLX_readMeta.ReadMeta(metaFile); -% try and get probe type from meta file (imDatPrb_type or imProbeOpt fields) -expr_scaling = 'imDatPrb_type=*'; -[~, startIndex] = regexp(filetext, expr_scaling); -if isempty(startIndex) % try second option: there are two different saving conventions - expr_scaling = 'imProbeOpt=*'; - [~, startIndex] = regexp(filetext, expr_scaling); -end -if isempty(startIndex) % if still no probe type information is found, use the param value - if strcmp(probeType, 'NaN') - error(['no probe type found in spikeGLX meta file and no param.probeType specified. ', ... - 'Edit the param.probeType value in bc_qualityParamValues.']) - end -else - endIndex = find(filetext(startIndex:end) == newline, 1, 'first') + startIndex - 2; - if isempty(endIndex) - endIndex = length(filetext); - end - probeType = filetext(startIndex+1:endIndex-1); -end - -% get channel map information -expr_chanMap = 'imRoFile='; -[~, startIndexChanMap] = regexp(filetext, expr_chanMap); -expr_afterChanMap = 'imSampRate'; -[~, endIndexChanMap] = regexp(filetext, expr_afterChanMap); -if isempty(startIndexChanMap) % new convention in new spike glx argh - expr_chanMap = 'imroFile='; - [~, startIndexChanMap] = regexp(filetext, expr_chanMap); - expr_afterChanMap = 'nDataDirs'; - [~, endIndexChanMap] = regexp(filetext, expr_afterChanMap); -end - -channelMapImro = filetext(startIndexChanMap+1:endIndexChanMap-2-length(expr_afterChanMap)); +% channelMapImro +channelMapImro = meta.imRoFile; if isempty(channelMapImro) % default was used if strcmp(probeType, '0') channelMapImro = 'NPtype21_bank0_ref0'; end end -% get bits_encoding -expr_imMax = 'imMaxInt='; -[~, startIndeximMax] = regexp(filetext, expr_imMax); -if isempty(startIndeximMax) % new convention in new spike glx argh - if ismember(probeType, {'1', '3', '0', '1020', '1030', '1100', '1120', '1121', '1122', '1123', '1200', '1300', '1110'}) %NP1, NP1-like - bits_encoding = 2^10; % 10-bit analog to digital - elseif ismember(probeType, {'21', '2003', '2004', '24', '2013', '2014', '2020'}) % NP2, NP2-like - bits_encoding = 2^14; % 14-bit analog to digital - else - error('unrecognized probe type. Check the imDatPrb_type value in your meta file and create a github issue / email us to add support for this probe type') - end -else - endIndex = find(filetext(startIndeximMax:end) == newline, 1, 'first') + startIndeximMax - 2; - if isempty(endIndex) - endIndex = length(filetext); - end - bits_encoding = str2num(filetext(startIndeximMax+1:endIndex-1)); -end +% probeType +probeType = meta.imDatPrb_type; -% get -expr_vMax = 'imAiRangeMax ='; -[~, startIndexvMax] = regexp(filetext, expr_vMax); -if isempty(startIndexvMax) % new convention in new spike glx argh - if ismember(probeType, {'1', '3', '0', '1020', '1030', '1100', '1120', '1121', '1122', '1123', '1200', '1300', '1110'}) %NP1, NP2-like - Vrange = 1.2e6; % from -0.6 to 0.6 V = 1.2 V = 1.2 e6 microvolts - elseif ismember(probeType, {'21', '2003', '2004', '24', '2013', '2014', '2020'}) % NP2, NP2-like - Vrange = 1e6; % from -0.5 to 0.5 V = 1 V = 1 e6 microvolts - else - error('unrecognized probe type. Check the imDatPrb_type value in your meta file and create a github issue / email us to add support for this probe type') - end -else - endIndex = find(filetext(startIndexvMax:end) == newline, 1, 'first') + startIndexvMax - 2; - if isempty(endIndex) - endIndex = length(filetext); - end - Vrange = 2 * str2num(filetext(startIndexvMax+1:endIndex-1)) * 1e6; -end +%% scaling factor +% gain +gain_allChannels = bc.dependencies.SGLX_readMeta.ChanGainsIM(meta); +allChannels_index = bc.dependencies.SGLX_readMeta.OriginalChans(meta); +thisChannel = find(allChannels_index == peakChan); +thisGain = gain_allChannels(thisChannel); -% gain - QQ read out from table, modify. this could be wrong in some cases. -% -if ismember(probeType, {'1', '3', '0', '1020', '1030', '1100', '1120', '1121', '1122', '1123', '1200', '1300', '1110'}) %NP1, NP2-like - gain = 500; % 10-bit analog to digital -elseif ismember(probeType, {'21', '2003', '2004', '24'}) % NP2, NP2-like - gain = 80; -elseif ismember(probeType, {'2013', '2014', '2020'}) % NP2, NP2-like - gain = 100; % 14-bit analog to digital -else - error('unrecognized probe type. Check the imDatPrb_type value in your meta file and create a github issue / email us to add support for this probe type') -end +% Vrange / bits_encoding +fI2V = bc.dependencies.SGLX_readMeta.Int2Volts(meta); % calculate scaling factor -scalingFactor = Vrange / bits_encoding / gain; +scalingFactor = fI2V / thisGain; + end \ No newline at end of file diff --git a/+bc/+qm/getRawAmplitude.asv b/+bc/+qm/getRawAmplitude.asv new file mode 100644 index 00000000..47786270 --- /dev/null +++ b/+bc/+qm/getRawAmplitude.asv @@ -0,0 +1,42 @@ +function rawAmplitude_uV = getRawAmplitude(rawWaveforms, peakChan, metaFile, probeType, gain_to_uV) +% JF, Get the amplitude of the mean raw waveform for a unit +% ------ +% Inputs +% ------ +% rawWaveforms: nTimePoints × 1 double vector of the mean raw waveform +% for one unit in mV +% metaFileDir: dir structure containing the location of the raw .meta or .oebin file. +% probeType: optional. only used if you are using spikeGLX *and* the meta +% file does not contain any probetype field (imDatPrb_type or imProbeOpt) +% ------ +% Outputs +% ------ +% rawAmplitude: raw amplitude in microVolts of the mean raw waveform for +% this unit + +% sanitize inputs +if nargin < 4 || isempty(probeType) + probeType = 'NaN'; +else + probeType = num2str(probeType); +end + +% get scaling factor +if strcmp(metaFile, 'NaN') == 0 + if contains(metaFile, 'oebin') + % open ephys format + scalingFactor = bc.load.readOEMetaFile(metaFile); % single sclaing factor per channel for now + else + % spikeGLX format + [scalingFactors_all, ~, ~] = bc.load.readSpikeGLXMetaFile(metaFile, probeType); + scalingFactor = scalingFactors_all(peakChan); + end +else + scalingFactor = gain_to_uV; +end + + % scale waveforms to get amplitude in microVolts + rawWaveforms = rawWaveforms .* scalingFactor; + rawAmplitude_uV = abs(max(rawWaveforms)) + abs(min(rawWaveforms)); + %rawAmplitude_mV = rawAmplitude_uV ./1000; +end diff --git a/+bc/+qm/getRawAmplitude.m b/+bc/+qm/getRawAmplitude.m index f593ea73..c6c79176 100644 --- a/+bc/+qm/getRawAmplitude.m +++ b/+bc/+qm/getRawAmplitude.m @@ -1,4 +1,4 @@ -function rawAmplitude_uV = getRawAmplitude(rawWaveforms, metaFile, probeType, gain_to_uV) +function rawAmplitude_uV = getRawAmplitude(rawWaveforms, peakChan, metaFile, probeType, gain_to_uV) % JF, Get the amplitude of the mean raw waveform for a unit % ------ % Inputs @@ -15,7 +15,7 @@ % this unit % sanitize inputs -if nargin < 3 || isempty(probeType) +if nargin < 4 || isempty(probeType) probeType = 'NaN'; else probeType = num2str(probeType); @@ -28,6 +28,7 @@ scalingFactor = bc.load.readOEMetaFile(metaFile); else % spikeGLX format + [scalingFactor, ~, ~] = bc.load.readSpikeGLXMetaFile(metaFile, probeType); end else diff --git a/+bc/+qm/runAllQualityMetrics.asv b/+bc/+qm/runAllQualityMetrics.asv index 880244b4..0d05aced 100644 --- a/+bc/+qm/runAllQualityMetrics.asv +++ b/+bc/+qm/runAllQualityMetrics.asv @@ -165,8 +165,8 @@ for iUnit = 1:size(uniqueTemplates, 1) %% amplitude if param.extractRaw - qMetric.rawAmplitude(iUnit) = bc.qm.getRawAmplitude(rawWaveformsFull(thisUnit, rawWaveformsPeakChan(thisUnit), :), ... - param.ephysMetaFile, param.probeType, param.gain_to_uV); + qMetric.rawAmplitude(iUnit) = bc.qm.getRawAmplitude(rawWaveformsFull(thisUnit, rawWaveformsPeakChan(thisUnit), :), ... + rawWaveformsPeakChan(thisUnit), param.ephysMetaFile, param.probeType, param.gain_to_uV); else qMetric.rawAmplitude(iUnit) = NaN; qMetric.signalToNoiseRatio(iUnit) = NaN; diff --git a/+bc/+qm/runAllQualityMetrics.m b/+bc/+qm/runAllQualityMetrics.m index 652514db..0e6c4f17 100644 --- a/+bc/+qm/runAllQualityMetrics.m +++ b/+bc/+qm/runAllQualityMetrics.m @@ -165,8 +165,8 @@ %% amplitude if param.extractRaw - qMetric.rawAmplitude(iUnit) = bc.qm.getRawAmplitude(rawWaveformsFull(thisUnit, rawWaveformsPeakChan(thisUnit), :), ... - param.ephysMetaFile, param.probeType, param.gain_to_uV); + qMetric.rawAmplitude(iUnit) = bc.qm.getRawAmplitude(rawWaveformsFull(thisUnit, :, :), ... + rawWaveformsPeakChan(thisUnit), param.ephysMetaFile, param.probeType, param.gain_to_uV); else qMetric.rawAmplitude(iUnit) = NaN; qMetric.signalToNoiseRatio(iUnit) = NaN; diff --git a/README.md b/README.md index b7cb1e88..a3b4e689 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Below is a flowchart of how bombcell evaluates and classifies each unit: Bombcell extracts relevant quality metrics to categorize units into four categories: single somatic units, multi-units, noise units and non-somatic units. -Take a look at the MATLAB live script [`gettingStarted`](https://github.com/Julie-Fabre/bombcell/blob/main/gettingStarted.mlx) or [`bombcell_pipeline`](https://github.com/Julie-Fabre/bombcell/blob/main/pipelines/bombcell_pipeline.m) to see an example workflow and play around with our small toy dataset. You can also take a look at the exercise we prepared for the 2024 Neuropixels course [here](https://github.com/BombCell/Neuropixels_course_2024). +Take a look at the MATLAB live script [`gettingStarted`](https://github.com/Julie-Fabre/bombcell/blob/main/gettingStarted.mlx) to see an example workflow and play around with our small toy dataset. You can also take a look at the exercise we prepared for the 2024 Neuropixels course [here](https://github.com/BombCell/Neuropixels_course_2024). #### Installation diff --git a/demos/demo_bombcell.mlx b/demos/demo_bombcell.mlx deleted file mode 100644 index b676957d..00000000 Binary files a/demos/demo_bombcell.mlx and /dev/null differ diff --git a/demos/gettingStarted_NPXcourse.mlx b/demos/gettingStarted_NPXcourse.mlx deleted file mode 100644 index 6566a73a..00000000 Binary files a/demos/gettingStarted_NPXcourse.mlx and /dev/null differ diff --git a/demos/gettingStarted_SWCdemo.mlx b/demos/gettingStarted_SWCdemo.mlx deleted file mode 100644 index 8eb0313a..00000000 Binary files a/demos/gettingStarted_SWCdemo.mlx and /dev/null differ diff --git a/pipelines/phyPlugins/colorSelectorPlugin.py b/demos/phyPlugins/colorSelectorPlugin.py similarity index 100% rename from pipelines/phyPlugins/colorSelectorPlugin.py rename to demos/phyPlugins/colorSelectorPlugin.py diff --git a/pipelines/phyPlugins/customizeSelectorStatsPlugin.py b/demos/phyPlugins/customizeSelectorStatsPlugin.py similarity index 100% rename from pipelines/phyPlugins/customizeSelectorStatsPlugin.py rename to demos/phyPlugins/customizeSelectorStatsPlugin.py diff --git a/pipelines/phyPlugins/phy_config.py b/demos/phyPlugins/phy_config.py similarity index 100% rename from pipelines/phyPlugins/phy_config.py rename to demos/phyPlugins/phy_config.py diff --git a/pipelines/phyPlugins/qMetricsPlugin.py b/demos/phyPlugins/qMetricsPlugin.py similarity index 100% rename from pipelines/phyPlugins/qMetricsPlugin.py rename to demos/phyPlugins/qMetricsPlugin.py diff --git a/gettingStarted.mlx b/gettingStarted.mlx index 79870b7a..50938dff 100644 Binary files a/gettingStarted.mlx and b/gettingStarted.mlx differ diff --git a/pipelines/bombcell_pipeline.asv b/pipelines/bombcell_pipeline.asv deleted file mode 100644 index da19c36b..00000000 --- a/pipelines/bombcell_pipeline.asv +++ /dev/null @@ -1,123 +0,0 @@ -%% ~~ Example bombcell pipeline ~~ -% Adjust the paths in the 'set paths' section and the parameters in bc_qualityParamValues -% This pipeline will: -% (1) load your ephys data, -% (2) decompress your raw data if it is in .cbin format -% (3) run bombcell on your data and save the output and -% (4) bring up summary plots and a GUI to flip through classified cells. -% The first time, this pipeline will be significantly slower (10-20' more) -% than after because it extracts raw waveforms. Subsequent times these -% pre-extracted waveforms are simply loaded in. -% We recommend running this pipeline on a few datasets and deciding on -% quality metric thresholds depending on the summary plots (histograms -% of the distributions of quality metrics for each unit) and GUI. - - -%% set paths - EDIT THESE. Currently contains links to small toy dataset. -currentPath = [matlab.desktop.editor.getActiveFilename, filesep, '..', filesep, '..']; -ephysKilosortPath = [currentPath, filsesep, 'demos', filesep, 'toy_data', filesep];% path to your kilosort output files -ephysRawDir = "NaN"; % dir() path to your raw .bin or .dat data. currently NaN because storing raw data on github is cumbersome. -ephysMetaDir = dir([currentPath, filsesep, 'demos', filesep, 'toy_data', filesep '*ap*.*meta']); % dir() path to your .meta or .oebin meta file -savePath = '/home/netshare/zaru/JF093/2023-03-06/ephys/kilosort2/site1/qMetrics'; % where you want to save the quality metrics -decompressDataLocal = '/media/julie/ExtraHD/decompressedData'; % where to save raw decompressed ephys data -gain_to_uV = NaN;%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 = '') -kilosortVersion = 2;% if using kilosort4, you need to change this value. Otherwise it does not matter. - -%% check MATLAB version -if exist('isMATLABReleaseOlderThan', 'file') == 0 % function introduced in MATLAB 2020b. - oldMATLAB = true; -else - oldMATLAB = isMATLABReleaseOlderThan("R2019a"); -end -if oldMATLAB - error('This MATLAB version is older than 2019a - download a more recent version before continuing') -end - -%% load data -[spikeTimes_samples, spikeTemplates, templateWaveforms, templateAmplitudes, pcFeatures, ... - pcFeatureIdx, channelPositions] = bc.load.loadEphysData(ephysKilosortPath); - -%% detect whether data is compressed, decompress locally if necessary -rawFile = bc.dcomp.manageDataCompression(ephysRawDir, decompressDataLocal); - -%% which quality metric parameters to extract and thresholds -param = bc.qm.qualityParamValues(ephysMetaDir, rawFile, ephysKilosortPath, gain_to_uV, kilosortVersion); %for unitmatch, run this: -% param = qualityParamValuesForUnitMatch(ephysMetaDir, rawFile, ephysKilosortPath, gain_to_uV) - -%% compute quality metrics -rerun = 0; -qMetricsExist = ~isempty(dir(fullfile(savePath, 'qMetric*.mat'))) || ~isempty(dir(fullfile(savePath, 'templates._bc_qMetrics.parquet'))); - -if qMetricsExist == 0 || rerun - [qMetric, unitType] = bc.qm.runAllQualityMetrics(param, spikeTimes_samples, spikeTemplates, ... - templateWaveforms, templateAmplitudes, pcFeatures, pcFeatureIdx, channelPositions, savePath); -else - [param, qMetric] = load.loadSavedMetrics(savePath); - unitType = bc.qm.getQualityUnitType(param, qMetric, savePath); -end - -%% view units + quality metrics in GUI -% load data for GUI -loadRawTraces = 0; % default: don't load in raw data (this makes the GUI significantly faster) -bc.load.loadMetricsForGUI; - -% GUI guide: -% left/right arrow: toggle between units -% g : go to next good unit -% m : go to next multi-unit -% n : go to next noise unit -% up/down arrow: toggle between time chunks in the raw data -% u: brings up a input dialog to enter the unit you want to go to - -% currently this GUI works best with a screen in portrait mode - we are -% working to get it to handle screens in landscape mode better. -unitQualityGuiHandle = bc.viz.unitQualityGUI(memMapData, ephysData, qMetric, forGUI, rawWaveforms, ... - param, probeLocation, unitType, loadRawTraces); - - -%% example: get the quality metrics for one unit -% this is an example to get the quality metric for the unit with the -% original kilosort and phy label of xx (0-indexed), which corresponds to -% the unit with qMetric.clusterID == xx + 1, and to -% qMetric.phy_clusterID == xx . This is *NOT NECESSARILY* the -% (xx + 1)th row of the structure qMetric - some of the clusters that kilosort -% outputs are empty, because they were dropped in the last stages of the -% algorithm. These empty clusters are not included in the qMetric structure -% there are two ways to do this: -% 1: -original_id_we_want_to_load = 0; -id_we_want_to_load_1_indexed = original_id_we_want_to_load + 1; -number_of_spikes_for_this_cluster = qMetric.nSpikes(qMetric.clusterID == id_we_want_to_load_1_indexed); -% or 2: -original_id_we_want_to_load = 0; -number_of_spikes_for_this_cluster = qMetric.nSpikes(qMetric.phy_clusterID == original_id_we_want_to_load); - - -%% example: get unit labels -% the output of `unitType = getQualityUnitType(param, qMetric);` gives -% the unitType in a number format. 1 indicates good units, 2 indicates mua units, 3 -% indicates non-somatic units and 0 indciates noise units (see below) - -goodUnits = unitType == 1; -muaUnits = unitType == 2; -noiseUnits = unitType == 0; -nonSomaticUnits = unitType == 3; - -% example: get all good units number of spikes -all_good_units_number_of_spikes = qMetric.nSpikes(goodUnits); - -% (for use with another language: output a .tsv file of labels. You can then simply load this) -label_table = table(unitType); -writetable(label_table,[savePath filesep 'templates._bc_unit_labels.tsv'],'FileType', 'text','Delimiter','\t'); - - -%% optional: additionally compute ephys properties for each unit and classify cell types -rerunEP = 0; -region = ''; % options include 'Striatum' and 'Cortex' -[ephysProperties, unitClassif] = bc.ep.runAllEphysProperties(ephysKilosortPath, savePath, rerunEP, region); - -% example: get good MSN units -goodMSNs = strcmp(unitClassif, 'MSN') & unitType == 1; - - diff --git a/pipelines/bombcell_pipeline.m b/pipelines/bombcell_pipeline.m deleted file mode 100644 index ec5e479e..00000000 --- a/pipelines/bombcell_pipeline.m +++ /dev/null @@ -1,129 +0,0 @@ -%% ~~ Example bombcell pipeline ~~ -% Adjust the paths in the 'set paths' section and the parameters in bc_qualityParamValues -% This pipeline will: -% (1) load your ephys data, -% (2) decompress your raw data if it is in .cbin format -% (3) run bombcell on your data and save the output and -% (4) bring up summary plots and a GUI to flip through classified cells. -% The first time, this pipeline will be significantly slower (10-20' more) -% than after because it extracts raw waveforms. Subsequent times these -% pre-extracted waveforms are simply loaded in. -% We recommend running this pipeline on a few datasets and deciding on -% quality metric thresholds depending on the summary plots (histograms -% of the distributions of quality metrics for each unit) and GUI. - - -%% set paths - EDIT THESE. Currently contains links to small toy dataset. -currentPath = [fileparts(matlab.desktop.editor.getActiveFilename), filesep, '..']; -ephysKilosortPath = [currentPath, filesep, 'toy_data'];% path to your kilosort output files -ephysRawDir = "NaN"; % dir() path to your raw .bin or .dat data. currently NaN because storing raw data on github is cumbersome. -% for testing: dir('/home/netshare/zaru/JF093/2023-03-06/ephys/site1/*ap*.*bin') -ephysMetaDir = dir([currentPath, filesep, 'demos', filesep, 'toy_data', filesep '*ap*.*meta']); % dir() path to your .meta or .oebin meta file -savePath = '/media/julie/ExtraHD/toy_data_qMetrics'; % where you want to save the quality metrics -decompressDataLocal = '/media/julie/ExtraHD/decompressedData'; % where to save raw decompressed ephys data -gain_to_uV = NaN;%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 = '') -kilosortVersion = 2;% if using kilosort4, you need to change this value. Otherwise it does not matter. - -%% check MATLAB version -if exist('isMATLABReleaseOlderThan', 'file') == 0 % function introduced in MATLAB 2020b. - oldMATLAB = true; -else - oldMATLAB = isMATLABReleaseOlderThan("R2019a"); -end -if oldMATLAB - error('This MATLAB version is older than 2019a - download a more recent version before continuing') -end - -%% load data -[spikeTimes_samples, spikeTemplates, templateWaveforms, templateAmplitudes, pcFeatures, ... - pcFeatureIdx, channelPositions] = bc.load.loadEphysData(ephysKilosortPath, savePath); - -%% detect whether data is compressed, decompress locally if necessary -rawFile = bc.dcomp.manageDataCompression(ephysRawDir, decompressDataLocal); - -%% which quality metric parameters to extract and thresholds -param = bc.qm.qualityParamValues(ephysMetaDir, rawFile, ephysKilosortPath, gain_to_uV, kilosortVersion); %for unitmatch, run this: -% param = qualityParamValuesForUnitMatch(ephysMetaDir, rawFile, ephysKilosortPath, gain_to_uV) - -% payparticular attention to param.nChannels -% param.nChannels must correspond to the total number of channels in your raw data, including any sync channels. For Neuropixels probes, this value should typically be either 384 or 385 channels. param.nSyncChannels must correspond to the number of sync channels you recorded. This value is typically 1 or 0. -param.nChannels = 385; -param.nSyncChannels = 1; - -%% compute quality metrics -rerun = 0; %whether to re-run (and save) quality metrics if they are already present -qMetricsExist = ~isempty(dir(fullfile(savePath, 'qMetric*.mat'))) || ~isempty(dir(fullfile(savePath, 'templates._bc_qMetrics.parquet'))); - -if qMetricsExist == 0 || rerun - [qMetric, unitType] = bc.qm.runAllQualityMetrics(param, spikeTimes_samples, spikeTemplates, ... - templateWaveforms, templateAmplitudes, pcFeatures, pcFeatureIdx, channelPositions, savePath); -else - [param, qMetric] = bc.load.loadSavedMetrics(savePath); - unitType = bc.qm.getQualityUnitType(param, qMetric, savePath); -end - -%% view units + quality metrics in GUI -% load data for GUI -loadRawTraces = 0; % default: don't load in raw data (this makes the GUI significantly faster) -bc.load.loadMetricsForGUI; - -% GUI guide: -% left/right arrow: toggle between units -% g : go to next good unit -% m : go to next multi-unit -% n : go to next noise unit -% up/down arrow: toggle between time chunks in the raw data -% u: brings up a input dialog to enter the unit you want to go to - -% currently this GUI works best with a screen in portrait mode - we are -% working to get it to handle screens in landscape mode better. -unitQualityGuiHandle = bc.viz.unitQualityGUI(memMapData, ephysData, qMetric, forGUI, rawWaveforms, ... - param, probeLocation, unitType, loadRawTraces); - - -%% example: get the quality metrics for one unit -% this is an example to get the quality metric for the unit with the -% original kilosort and phy label of xx (0-indexed), which corresponds to -% the unit with qMetric.clusterID == xx + 1, and to -% qMetric.phy_clusterID == xx . This is *NOT NECESSARILY* the -% (xx + 1)th row of the structure qMetric - some of the clusters that kilosort -% outputs are empty, because they were dropped in the last stages of the -% algorithm. These empty clusters are not included in the qMetric structure -% there are two ways to do this: -% 1: -original_id_we_want_to_load = 0; -id_we_want_to_load_1_indexed = original_id_we_want_to_load + 1; -number_of_spikes_for_this_cluster = qMetric.nSpikes(qMetric.clusterID == id_we_want_to_load_1_indexed); -% or 2: -original_id_we_want_to_load = 0; -number_of_spikes_for_this_cluster = qMetric.nSpikes(qMetric.phy_clusterID == original_id_we_want_to_load); - - -%% example: get unit labels -% the output of `unitType = getQualityUnitType(param, qMetric);` gives -% the unitType in a number format. 1 indicates good units, 2 indicates mua units, 3 -% indicates non-somatic units and 0 indciates noise units (see below) - -goodUnits = unitType == 1; -muaUnits = unitType == 2; -noiseUnits = unitType == 0; -nonSomaticUnits = unitType == 3; - -% example: get all good units number of spikes -all_good_units_number_of_spikes = qMetric.nSpikes(goodUnits); - -% (for use with another language: output a .tsv file of labels. You can then simply load this) -label_table = table(unitType); -writetable(label_table,[savePath filesep 'templates._bc_unit_labels.tsv'],'FileType', 'text','Delimiter','\t'); - - -%% optional: additionally compute ephys properties for each unit and classify cell types -rerunEP = 0; %whether to re-run (and save) ephys properties if they are already present -region = 'Cortex'; % options include 'Striatum' and 'Cortex' -[ephysProperties, unitClassif] = bc.ep.runAllEphysProperties(ephysKilosortPath, savePath, rerunEP, region); - -% example: get good MSN units -goodMSNs = strcmp(unitClassif, 'MSN') & unitType == 1; - -