diff --git a/external/spikeglx/DemoReadSGLXData.m b/external/spikeglx/DemoReadSGLXData.m new file mode 100644 index 0000000000..10d261247c --- /dev/null +++ b/external/spikeglx/DemoReadSGLXData.m @@ -0,0 +1,45 @@ +% ============================================================= +% Simple demo program calling functions from the +% SGLX_readMeta class. +% +function DemoReadSGLXData() + +% Ask user for binary file +[binName,path] = uigetfile('*.bin', 'Select Binary File'); + +% Parse the corresponding metafile +meta = SGLX_readMeta.ReadMeta(binName, path); + +% Get first one second of data +nSamp = floor(1.0 * SGLX_readMeta.SampRate(meta)); +dataArray = SGLX_readMeta.ReadBin(0, nSamp, meta, binName, path); +dataType = 'A'; %set to 'A' for analog, 'D' for digital data + +% For an analog channel: gain correct saved channel ch (1-based for MATLAB). +ch = 1; + +% For a digital channel: read this digital word dw in the saved file +% (1-based). For imec data there is never more than one saved digital word. +dw = 1; + +% Read these lines in dw (0-based). +% For 3B2 imec data: the sync pulse is stored in line 6. +% May be 1 or more line indices. +dLineList = [0,1,6]; + +if dataType == 'A' + if strcmp(meta.typeThis, 'imec') + dataArray = SGLX_readMeta.GainCorrectIM(dataArray, [ch], meta); + else + dataArray = SGLX_readMeta.GainCorrectNI(dataArray, [ch], meta); + end + plot(1e6*dataArray(ch,:)); +else + digArray = SGLX_readMeta.ExtractDigital(dataArray, meta, dw, dLineList); + for i = 1:numel(dLineList) + plot(digArray(i,:)); + hold on + end + hold off +end +end % DemoReadSGLXData \ No newline at end of file diff --git a/external/spikeglx/SGLX_readMeta.m b/external/spikeglx/SGLX_readMeta.m new file mode 100644 index 0000000000..5e71f7c39a --- /dev/null +++ b/external/spikeglx/SGLX_readMeta.m @@ -0,0 +1,436 @@ +% ============================================================= +% 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. +% +% The most important method in the class is ReadMeta(). +% The functions for reading binary data are not optimized for speed, +% and are included as a useful starting point. +% +classdef SGLX_readMeta + + methods(Static) + + % ========================================================= + % Parse ini file returning a structure whose field names + % are the metadata left-hand-side tags, and whose right- + % hand-side values are MATLAB strings. We remove any + % leading '~' characters from tags because MATLAB uses + % '~' as an operator. + % + % If you're unfamiliar with structures, the benefit + % is that after calling this function you can refer + % to metafile items by name. For example: + % + % meta.fileCreateTime // file create date and time + % meta.nSavedChans // channels per timepoint + % + % All of the values are MATLAB strings, but you can + % obtain a numeric value using str2double(meta.nSavedChans). + % More complicated parsing of values is demonstrated in the + % utility functions below. + % + function [meta] = ReadMeta(binName, path) + + % Create the matching metafile name + [~,name,~] = fileparts(binName); + metaName = strcat(name, '.meta'); + + % Parse ini file into cell entries C{1}{i} = C{2}{i} + fid = fopen(fullfile(path, metaName), 'r'); + % ------------------------------------------------------------- + % Need 'BufSize' adjustment for MATLAB earlier than 2014 + % C = textscan(fid, '%[^=] = %[^\r\n]', 'BufSize', 32768); + C = textscan(fid, '%[^=] = %[^\r\n]'); + % ------------------------------------------------------------- + fclose(fid); + + % New empty struct + meta = struct(); + + % Convert each cell entry into a struct entry + for i = 1:length(C{1}) + tag = C{1}{i}; + if tag(1) == '~' + % remake tag excluding first character + tag = sprintf('%s', tag(2:end)); + end + meta.(tag) = C{2}{i}; + end + end % ReadMeta + + + % ========================================================= + % Read nSamp timepoints from the binary file, starting + % at timepoint offset samp0. The returned array has + % dimensions [nChan,nSamp]. Note that nSamp returned + % is the lesser of: {nSamp, timepoints available}. + % + % IMPORTANT: samp0 and nSamp must be integers. + % + function dataArray = ReadBin(samp0, nSamp, meta, binName, path) + + nChan = str2double(meta.nSavedChans); + + nFileSamp = str2double(meta.fileSizeBytes) / (2 * nChan); + samp0 = max(samp0, 0); + nSamp = min(nSamp, nFileSamp - samp0); + + sizeA = [nChan, nSamp]; + + fid = fopen(fullfile(path, binName), 'rb'); + fseek(fid, samp0 * 2 * nChan, 'bof'); + dataArray = fread(fid, sizeA, 'int16=>double'); + fclose(fid); + end % ReadBin + + + % ========================================================= + % Return an array [lines X timepoints] of uint8 values for + % a specified set of digital lines. + % + % - dwReq is the one-based index into the saved file of the + % 16-bit word that contains the digital lines of interest. + % - dLineList is a zero-based list of one or more lines/bits + % to scan from word dwReq. + % + function digArray = ExtractDigital(dataArray, meta, dwReq, dLineList) + % Get channel index of requested digital word dwReq + if strcmp(meta.typeThis, 'imec') + [AP, LF, SY] = SGLX_readMeta.ChannelCountsIM(meta); + if SY == 0 + fprintf('No imec sync channel saved\n'); + digArray = []; + return; + else + digCh = AP + LF + dwReq; + end + else + [MN,MA,XA,DW] = SGLX_readMeta.ChannelCountsNI(meta); + if dwReq > DW + fprintf('Maximum digital word in file = %d\n', DW); + digArray = []; + return; + else + digCh = MN + MA + XA + dwReq; + end + end + [~,nSamp] = size(dataArray); + digArray = zeros(numel(dLineList), nSamp, 'uint8'); + for i = 1:numel(dLineList) + digArray(i,:) = bitget(dataArray(digCh,:), dLineList(i)+1, 'int16'); + end + end % ExtractDigital + + + % ========================================================= + % Return sample rate as double. + % + function srate = SampRate(meta) + if strcmp(meta.typeThis, 'imec') + srate = str2double(meta.imSampRate); + else + srate = str2double(meta.niSampRate); + end + end % SampRate + + + % ========================================================= + % Return a multiplicative factor for converting 16-bit + % file data to voltage. This does not take gain into + % account. The full conversion with gain is: + % + % dataVolts = dataInt * fI2V / gain. + % + % Note that each channel may have its own gain. + % + function fI2V = Int2Volts(meta) + if strcmp(meta.typeThis, 'imec') + if isfield(meta,'imMaxInt') + maxInt = str2num(meta.imMaxInt); + else + maxInt = 512; + end + fI2V = str2double(meta.imAiRangeMax) / maxInt; + else + fI2V = str2double(meta.niAiRangeMax) / 32768; + end + end % Int2Volts + + + % ========================================================= + % Return array of original channel IDs. As an example, + % suppose we want the imec gain for the ith channel stored + % in the binary data. A gain array can be obtained using + % ChanGainsIM() but we need an original channel index to + % do the look-up. Because you can selectively save channels + % the ith channel in the file isn't necessarily the ith + % acquired channel, so use this function to convert from + % ith stored to original index. + % + % Note: In SpikeGLX channels are 0-based, but MATLAB uses + % 1-based indexing, so we add 1 to the original IDs here. + % + function chans = OriginalChans(meta) + if strcmp(meta.snsSaveChanSubset, 'all') + chans = (1:str2double(meta.nSavedChans)); + else + chans = str2num(meta.snsSaveChanSubset); + chans = chans + 1; + end + end % OriginalChans + + + % ========================================================= + % Return counts of each imec channel type that compose + % the timepoints stored in binary file. + % + function [AP,LF,SY] = ChannelCountsIM(meta) + M = str2num(meta.snsApLfSy); + AP = M(1); + LF = M(2); + SY = M(3); + end % ChannelCountsIM + + % ========================================================= + % Return counts of each nidq channel type that compose + % the timepoints stored in binary file. + % + function [MN,MA,XA,DW] = ChannelCountsNI(meta) + M = str2num(meta.snsMnMaXaDw); + MN = M(1); + MA = M(2); + XA = M(3); + DW = M(4); + end % ChannelCountsNI + + + % ========================================================= + % Return gain for ith channel stored in the nidq file. + % + % ichan is a saved channel index, rather than an original + % (acquired) index. + % + function gain = ChanGainNI(ichan, savedMN, savedMA, meta) + if ichan <= savedMN + gain = str2double(meta.niMNGain); + elseif ichan <= savedMN + savedMA + gain = str2double(meta.niMAGain); + else + gain = 1; + end + end % ChanGainNI + + + % ========================================================= + % Return gain arrays for imec channels. + % + % Index into these with original (acquired) channel IDs. + % + function [APgain,LFgain] = ChanGainsIM(meta) + % list of probe types with NP 1.0 imro format + np1_imro = [0,1020,1030,1200,1100,1120,1121,1122,1123,1300]; + % number of channels acquired + acqCountList = str2num(meta.acqApLfSy); + + APgain = zeros(acqCountList(1)); % default type = float64 + LFgain = zeros(acqCountList(2)); % empty array for 2.0 + + if isfield(meta,'imDatPrb_type') + probeType = str2double(meta.imDatPrb_type); + else + probeType = 0; + end + + if ismember(probeType, np1_imro) + % imro + probe allows setting gain independently for each channel + % 3A or 3B data? + % 3A metadata has field "typeEnabled" which was replaced + % with "typeImEnabled" and "typeNiEnabled" in 3B. + % The 3B imro table has an additional field for the + % high pass filter enabled/disabled + if isfield(meta,'typeEnabled') + % 3A data + C = textscan(meta.imroTbl, '(%*s %*s %*s %d %d', ... + 'EndOfLine', ')', 'HeaderLines', 1 ); + else + % 3B data + C = textscan(meta.imroTbl, '(%*s %*s %*s %d %d %*s', ... + 'EndOfLine', ')', 'HeaderLines', 1 ); + end + APgain = double(cell2mat(C(1))); + LFgain = double(cell2mat(C(2))); + else + % get gain from imChan0apGain, if present + if isfield(meta,'imChan0apGain') + APgain = APgain + str2num(meta.imChan0apGain); + if acqCountList(2) > 0 + LFgain = LFgain + str2num(meta.imChan0lfGain); + end + elseif (probeType == 1110) + % active UHD, for metadata lacking imChan0apGain, get gain from + % imro table header + currList = sscanf(meta.imroTbl, '(%d,%d,%d,%d,%d'); + APgain = APgain + currList(4); + LFgain = LFgain + currList(5); + elseif (probeType == 21) || (probeType == 24) + % development NP 2.0; APGain = 80 for all AP + % return 0 for LFgain (no LF channels) + APgain = APgain + 80; + elseif (probeType == 2013) + % commercial NP 2.0; APGain = 80 for all AP + APgain = APgain + 100; + else + fprintf('unknown gain, setting APgain to 1\n'); + APgain = APgain + 1; + end + end + end % ChanGainsIM + + + % ========================================================= + % Having acquired a block of raw nidq data using ReadBin(), + % convert values to gain-corrected voltages. The conversion + % is only applied to the saved-channel indices in chanList. + % Remember saved-channel indices are in range [1:nSavedChans]. + % The dimensions of the dataArray remain unchanged. ChanList + % examples: + % + % [1:MN] % all MN chans (MN from ChannelCountsNI) + % [2,6,20] % just these three channels + % + function dataArray = GainCorrectNI(dataArray, chanList, meta) + + [MN,MA] = SGLX_readMeta.ChannelCountsNI(meta); + fI2V = SGLX_readMeta.Int2Volts(meta); + + for i = 1:length(chanList) + j = chanList(i); % index into timepoint + conv = fI2V / SGLX_readMeta.ChanGainNI(j, MN, MA, meta); + dataArray(j,:) = dataArray(j,:) * conv; + end + end % GainCorrectNI + + + % ========================================================= + % Having acquired a block of raw imec data using ReadBin(), + % convert values to gain-corrected voltages. The conversion + % is only applied to the saved-channel indices in chanList. + % Remember saved-channel indices are in range [1:nSavedChans]. + % The dimensions of the dataArray remain unchanged. ChanList + % examples: + % + % [1:AP] % all AP chans (AP from ChannelCountsIM) + % [2,6,20] % just these three channels + % + function dataArray = GainCorrectIM(dataArray, chanList, meta) + + % Look up gain with acquired channel ID + chans = SGLX_readMeta.OriginalChans(meta); + [APgain,LFgain] = SGLX_readMeta.ChanGainsIM(meta); + nAP = length(APgain); + nNu = nAP * 2; + + % Common conversion factor + fI2V = SGLX_readMeta.Int2Volts(meta); + + for i = 1:length(chanList) + j = chanList(i); % index into timepoint + k = chans(j); % acquisition index + if k <= nAP + conv = fI2V / APgain(k); + elseif k <= nNu + conv = fI2V / LFgain(k - nAP); + else + continue; + end + dataArray(j,:) = dataArray(j,:) * conv; + end + end % GainCorrectIM + + % ========================================================= + % Return array of survey bank times + % + % + function bankTimes = svyBankTimes(meta) + + % Look up gain with acquired channel ID + C = textscan(meta.svySBTT, '(%d %d %d %d', ... + 'EndOfLine', ')' ); + + nBank = numel(C{1}) + 1; % bank0/shank0 is at time = 0 + srate = SGLX_readMeta.SampRate(meta); + + bankTimes = zeros([nBank,4], "double"); + bankTimes(2:nBank,1) = double(C{1}); + bankTimes(2:nBank,2) = double(C{2}); + bankTimes(2:nBank,3) = double(C{3})/srate; + bankTimes(2:nBank,4) = double(C{4})/srate; + + end % svyBankTimes + + % ========================================================= + % Write metadata file using values in meta structure + % + % + function writeMeta(meta, newPath) + + % Write out metadata file. Tag order matches order of addition to + % structure when read + fmeta = fopen( newPath, 'w'); + tildeTags{1} = 'muxTbl'; + tildeTags{2} = 'imroTbl'; + tildeTags{3} = 'snsChanMap'; + tildeTags{4} = 'snsGeomMap'; + tildeTags{5} = 'snsShankMap'; + + fn = fieldnames(meta); + for i = 1:numel(fieldnames(meta)) + currTag = fn{i}; + tagFound = find(strcmp(tildeTags, currTag)); + if isempty(tagFound) + currLine = sprintf('%s=%s',currTag,meta.(currTag)); + fprintf(fmeta,'%s\n',currLine); + else + currLine = sprintf('~%s=%s',currTag,meta.(currTag)); + fprintf(fmeta,'%s\n',currLine); + end + end + + end % writeMeta + + % =========================================================== + % parse SGLX imec filename with or without extension, return + % runName, + % gateStr, e.g. 'g0' + % triggerStr, e.g. 't0' or 'tcat' + % probeStr, e.g. 'imec0' + % streamStr, e.g. 'ap' or 'lf' + % + % + function [runName,gateStr,triggerStr,probeStr,streamStr] = parseFileName(fileName) + + % Remove extension, if present + if endsWith(fileName, '.bin') + fileName = fileName(1:length(fileName)-4); + elseif endsWith(fileName, '.meta') + fileName = fileName(1:length(fileName)-5); + end + + % Find periods and underscores + perPos = strfind(fileName,'.'); + usPos = strfind(fileName,'_'); + nPer = length(perPos); + nUS = length(usPos); + streamStr = fileName(perPos(nPer)+1:end); + probeStr = fileName(perPos(nPer-1)+1:perPos(nPer)-1); + triggerStr = fileName(usPos(nUS)+1:perPos(nPer-1)-1); + gateStr = fileName(usPos(nUS-1)+1:usPos(nUS)-1); + runName = fileName(1:usPos(nUS-1)-1); + + end % parseFileName + + end % SGLX_readMeta methods + +end % SGLX_readMeta classdef diff --git a/external/spikeglx/ks25_phy_toBinary.m b/external/spikeglx/ks25_phy_toBinary.m new file mode 100644 index 0000000000..222c7998bd --- /dev/null +++ b/external/spikeglx/ks25_phy_toBinary.m @@ -0,0 +1,260 @@ +function ks25_phy_toBinary( varargin ) + +% Read drift corrected data from ks25, unwhiten and rescale for input +% into C_Waves. Requires a full phy folder of results. + +% IMPORTANT NOTE: the preprocessed file only contains channels that KS2.5 +% uses for sorting. Channels with connected=0 in the chanMap file OR +% eliminated due to low spike count (ops.minfr_goodchannels > 0) will not +% be included. The information in the phy output is used to generate a new +% SGLX metadata file which includes the correct channels. + +% Another important note: The correct information to unwhiten and rescale +% the KS2.5 drift-corrected output is ONLY available in the rez2.mat output +% file. The whitening.mat.npy and whitening_mat_inv.npy are just identity +% matrices. Voltage values will be incorrectly scaled and the spatial +% fields are post-whitening if the rez2.mat file is unavailable. + +% Requires: +% SGLX_readMeta class: https://github.com/jenniferColonell/SpikeGLX_Datafile_Tools/ +% C_Waves: https://billkarsh.github.io/SpikeGLX/#post-processing-tools +% npy-matlab: https://github.com/kwikteam/npy-matlab/tree/master/npy-matlab +% + +% user parameters +modStr = 'ksprocUW'; % will be added to the run name of the output binary and metadata +bRunCWaves = 1; % whether to run C_Waves +exePath = 'C:\Users\colonellj\Documents\C_Waves-win'; % path to C_Waves directory on local machine + +if isempty(varargin ) + phyDir = uigetdir([],'select phy directory'); + [binName,binPath] = uigetfile({'*.bin';'*.dat'},'Select PROCESSED binary file'); + fprocFullPath = fullfile(binPath,binName); + [metaName, mPath] = uigetfile('*.meta','Select ORIGINAL metadata file'); + metaName = metaName(1:length(metaName)-5); % remove extension +else + % called from another function + inputCell = varargin(1); + phyDir = inputCell{1}; + inputCell = varargin(2); + fprocFullPath = inputCell{1}; + inputCell = varargin(3); + origMetaFullpath = inputCell{1}; + [mPath, metaName, ~] = fileparts(origMetaFullpath); +end + + +[baseName,gateStr,triggerStr,probeStr,~] = SGLX_readMeta.parseFileName(metaName); + +outName = sprintf( '%s_%s_%s_%s.%s', baseName, modStr, gateStr, triggerStr, probeStr); +binOutName = sprintf( '%s%s', outName, '.ap.bin'); +metaOutName = sprintf( '%s%s', outName, '.ap.meta'); +outFullPath = fullfile(phyDir, binOutName); + + +% KS2.5 makes a readable binary from its datashifted data. Rather +% than overlapping the batches, it reads in some extra points for filtering +% and then trims them back off. ops.ntbuff = ntb +% read NTbuff = NT + 3*ntb points +% for a standard batch (neither first nor last) read: +% --ntb points overlapping the last batch +% --NT points that belong to this batch +% --2*ntb more points; first set of ntb will be blended with next +% batch, 2nd ntb just filtering buffer +% After fitering, points ntb+1:ntb are blended with "dat_prev" which is +% NT+ntb+1:NT+2*ntb saved from the previous batch. +% Batch zero gets ntb zeros prepended to its data and blended with the +% initialized dat_prev (also zeros). +% After filtering, the data is whitened and then transposed back to NChan +% rows by NT columns to save. When these batches are read for sorting in +% learnTemplates, the data is transposed after reading. + +% read whitening matrix and chanMap from phy directory +chanMap = readNPY(fullfile(phyDir,'channel_map.npy')); % 0 based indicies of the channels in the output file +Nchan = numel(chanMap); + +if isfile(fullfile(phyDir,'rez2.mat')) + rez = load(fullfile(phyDir,'rez2.mat')); + Wrot = rez.rez.Wrot; +else + fprintf('Wrot unavailable, will use identity matrix\n'); + fprintf('Voltages will NOT be correct.\n') + Wrot = eye(Nchan); + % In the release version of KS2.5, the whitening matrix saved in + % whitening_mat.npy is just the identity matrix/rez.ops.scaleProc. + % whitening_mat_inv.npy is the inverse of whitening_mat. + % Result: whitening_mat.npy is closer to being the inverse of the + % whitening matrix than whitening_mat_inv.npy, but NEITHER is correct. + % To obtain correctly scaled, unwhitened waveforms from the drift + % corrected data, it is necessary to save the rez2.mat file in the + % script calling KS2.5. +end + + +% get number of data points +fp = dir(fprocFullPath); +procSizeBytes = fp.bytes; %double +totNT = procSizeBytes/(Nchan*2); %total number of time points +fprintf('Total time points in binary %d\n', totNT); +if totNT ~= floor(totNT) + fprintf('binary doesn not match phy output'); + exit; +end + +fid = fopen(fprocFullPath, 'r'); +fidW = fopen(outFullPath, 'w'); % open for writing processed data, transposed + +% NT is set here to the KS default, but the results are independent of the +% value of NT used by KS2.5. +% NOTE: this is not true for KS2.0. +NT = 65000; + +Nbatch = floor(totNT/NT); +batchstart = 0:NT:NT*Nbatch; % batches start at these timepoints +for ibatch = 1:Nbatch + offset = 2 * Nchan*batchstart(ibatch); % binary file offset in bytes + fseek(fid, offset, 'bof'); + dat = fread(fid, [Nchan NT], '*int16'); + dat = int16(single(dat')/Wrot); + fwrite(fidW, dat', 'int16'); % write transposed batch to binary, these chunks are in order +end +% get the end of the file +NTlast = totNT - Nbatch*NT; +offset = 2*Nchan*batchstart(Nbatch); +fseek(fid, offset, 'bof'); +dat = fread(fid, [Nchan NTlast], '*int16'); +dat = int16(single(dat')/Wrot); +fwrite(fidW, dat', 'int16'); +fclose(fid); +fclose(fidW); + +fp = dir(outFullPath); +newBytes = uint64(fp.bytes); + +% get binaary name and path from origMetaFullPath +origBinName = sprintf('%s.bin', metaName); +origMeta = SGLX_readMeta.ReadMeta(origBinName, mPath); + +newMeta = origMeta; +newMeta.fileName = sprintf('%s', outFullPath); +newMeta.fileSizeBytes = sprintf('%ld', newBytes); +newMeta.nSavedChans = sprintf('%d', Nchan); % read from phy channel_map.npy; +newMeta.snsApLfSy = sprintf('%d,0,0', Nchan); + +% map channels to original channels. This is required for correct +% intepretation of gains read from the imro table for NP 1.0-like probes + +origChans = SGLX_readMeta.OriginalChans(origMeta) - 1; % zero based indicies of channels in the original binary +origChansNew = origChans(chanMap+1); % zero based indicies of channels in the processed binary +newMeta.snsSaveChanSubset = build_sep_str(origChansNew,','); + +% snsChanMap, snsGeomMap and snsShankMap all include only the saved +% channels. Need to get the array of entries from the string, index to +% get the channels included in the output file, +mapTags = {'snsChanMap', 'snsGeomMap', 'snsShankMap'}; +for i = 1:numel(mapTags) + if isfield(origMeta,mapTags{i}) + currMap = origMeta.(mapTags{i}); + mapArr = split(currMap,')'); + mapArr(numel(mapArr)) = []; % removing last entry from final ')' + % mapArr is original Nchan + 1 long, first entry is the header + % each entry is '(' plus the string inside the parens. + % Entries in the new map are the header (entry 1) + chanMap entries + 2 + new_map_ind = zeros([Nchan+1,1]); + new_map_ind(1) = 1; % for header entry + new_map_ind(2:Nchan+1) = chanMap(1:Nchan) + 2; + mapArrNew = mapArr(new_map_ind); + if mapTags{i} == 'snsChanMap' + % chanMap header to match saved entries + mapArrNew(1) = {sprintf('(%d,0,0)', Nchan)}; + end + mapNewStr = build_sep_str(mapArrNew,')'); + newMeta.(mapTags{i}) = sprintf('%s)',mapNewStr); % adding final close paren + end +end + +SGLX_readMeta.writeMeta(newMeta, fullfile(phyDir,metaOutName) ); +fprintf( 'Output file has %d channels\n', Nchan ); + +if bRunCWaves + spkFilePath = fullfile(phyDir,'spike_times.npy'); + spkCluPath = fullfile(phyDir,'spike_clusters.npy'); + clusTablePath = fullfile(phyDir,'clus_Table.npy'); + if ~isfile(clusTablePath) + phy_to_clusTable(phyDir, Wrot); + end + call_CWaves( phyDir, outFullPath, spkFilePath, spkCluPath, clusTablePath, exePath ); +end + +end + +function sepStr = build_sep_str(m, sep) + % for a 1D array m and separator setp, build the string + ne = numel(m); + sepStr = ''; + switch class(m) + case 'cell' + for i = 1:ne-1 + % get the contents of the cell + cellElem = m(i); + cellStr = cellElem{1}; + sepStr = sprintf('%s%s%s',sepStr,cellStr,sep); + end + % last element + cellElem = m(ne); + cellStr = cellElem{1}; + sepStr = sprintf('%s%s',sepStr,cellStr); + otherwise + for i = 1:ne-1 + sepStr = sprintf('%s%g%s',sepStr,m(i),sep); + end + % last element + sepStr = sprintf('%s%g',sepStr,m(ne)); + end +end + +function phy_to_clusTable(phyDir, Wrot) +% buld clusTable for C_Waves from information in phy directory +% clus table is an npy file containing: +% 2-col table (uint32): {num_spike, pk-chan} + templates = readNPY(fullfile(phyDir,'templates.npy')); + templates_unwh = zeros(size(templates)); + [nUnit,~,~] = size(templates); + for i = 1:nUnit + templates_unwh(i,:,:) = squeeze(templates(i,:,:))/Wrot; + end + pp_all = squeeze(max(templates_unwh,[],2) - min(templates_unwh,[],2)); + [~,maxChan] = max(pp_all,[],2); + spikeClusters = readNPY(fullfile(phyDir,'spike_clusters.npy')); + [counts,labels] = groupcounts(spikeClusters); + clu_arr = zeros([nUnit,2],'uint32'); + clu_arr(labels+1,1) = uint32(counts); + clu_arr(:,2) = uint32(maxChan); + writeNPY(clu_arr, fullfile(phyDir,'clus_Table.npy')) + +end + +function call_CWaves( inputPath, binPath, spkFilePath, spkCluPath, clusTablePath, exePath ) + +% build command line to call CWaves + + args = sprintf("-spikeglx_bin=%s", binPath); + args = sprintf("%s -clus_table_npy=%s", args, clusTablePath); + args = sprintf("%s -clus_time_npy=%s", args, spkFilePath); + args = sprintf("%s -clus_lbl_npy=%s", args, spkCluPath); + args = sprintf("%s -dest=%s", args, inputPath); + args = sprintf("%s -prefix=ksproc -samples_per_spike=82 -pre_samples=20 -num_spikes=1000 -snr_radius=8 -snr_radius_um=140", args); + + cwaves_cmd = sprintf("%s %s", fullfile(exePath,'runit.bat'), args); + fprintf("%s\n", cwaves_cmd); + status = system(cwaves_cmd) + +% typical command line: +% -spikeglx_bin=\\dm11\apig\C_waves_test_data\SC024_092319_NP1.0_Midbrain_g0_tcat.imec0.ap.bin ^ +% -clus_table_npy=\\dm11\apig\C_waves_test_data\clus_Table.npy ^ +% -clus_time_npy=\\dm11\apig\C_waves_test_data\spike_times.npy ^ +% -clus_lbl_npy=\\dm11\apig\C_waves_test_data\spike_clusters.npy ^ +% -dest=\\dm11\apig\C_waves_test_data\out ^ +% -samples_per_spike=82 -pre_samples=20 -num_spikes=1000 -snr_radius=8 -snr_radius_um=140) +end + diff --git a/fileio/ft_filetype.m b/fileio/ft_filetype.m index 0509d1903e..06a417e65a 100644 --- a/fileio/ft_filetype.m +++ b/fileio/ft_filetype.m @@ -1629,6 +1629,14 @@ type = 'vtk'; manufacturer = 'ParaView'; content = 'geometrical meshes'; +elseif filetype_check_extension(filename, '.bin') && exist(fullfile(p, [f '.meta']), 'file') + type = 'spikeglx_bin'; + manufacturer = 'SpikeGLX'; + content = 'neuropixel data'; +elseif filetype_check_extension(filename, '.meta') && exist(fullfile(p, [f '.bin']), 'file') + type = 'spikeglx_bin'; + manufacturer = 'SpikeGLX'; + content = 'neuropixel data'; end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/fileio/ft_read_header.m b/fileio/ft_read_header.m index 4742d5ba5c..d5f549982a 100644 --- a/fileio/ft_read_header.m +++ b/fileio/ft_read_header.m @@ -59,30 +59,32 @@ % ANT - Advanced Neuro Technology, EEProbe (*.avr, *.eeg, *.cnt) % BCI2000 (*.dat) % Biosemi (*.bdf) +% Bitalino OpenSignals (*.txt) % BrainVision (*.eeg, *.seg, *.dat, *.vhdr, *.vmrk) % CED - Cambridge Electronic Design (*.smr) % EGI - Electrical Geodesics, Inc. (*.egis, *.ave, *.gave, *.ses, *.raw, *.sbin, *.mff) % GTec (*.mat, *.hdf5) +% GTec Unicorn (*.csv) % Generic data formats (*.edf, *.gdf) % Megis/BESA (*.avr, *.swf, *.besa) -% NeuroScan (*.eeg, *.cnt, *.avg) -% Nexstim (*.nxe) -% TMSi (*.Poly5) % Mega Neurone (directory) % Natus/Nicolet/Nervus (.e files) % Nihon Kohden (*.m00, *.EEG) -% Bitalino OpenSignals (*.txt) +% NeuroScan (*.eeg, *.cnt, *.avg) +% Nexstim (*.nxe) % OpenBCI (*.txt) +% TMSi (*.Poly5) % % The following spike and LFP dataformats are supported -% Neuralynx (*.ncs, *.nse, *.nts, *.nev, *.nrd, *.dma, *.log) -% Plextor (*.nex, *.plx, *.ddt) % CED - Cambridge Electronic Design (*.smr) % MPI - Max Planck Institute (*.dap) +% Neuralynx (*.ncs, *.nse, *.nts, *.nev, *.nrd, *.dma, *.log) +% Neurodata Without Borders (*.nwb) % Neurosim (neurosim_spikes, neurosim_signals, neurosim_ds) -% Windaq (*.wdq) % NeuroOmega (*.mat transformed from *.mpx) -% Neurodata Without Borders: Neurophysiology (*.nwb) +% Neuropixel data recorded with SpikeGLX (*.bin, *.meta) +% Plextor (*.nex, *.plx, *.ddt) +% Windaq (*.wdq) % % The following NIRS dataformats are supported % Artinis - Artinis Medical Systems B.V. (*.oxy3, *.oxy4, *.oxy5, *.oxyproj) diff --git a/fileio/private/bids_tsv.m b/fileio/private/bids_tsv.m index dd9a62d109..98c12c26f6 100644 --- a/fileio/private/bids_tsv.m +++ b/fileio/private/bids_tsv.m @@ -15,7 +15,7 @@ % See https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/06-physiological-and-other-continuous-recordings.html % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2019, Robert Oostenveld % diff --git a/fileio/private/biopac_acq.m b/fileio/private/biopac_acq.m index 3381eedc9a..21482a55b2 100644 --- a/fileio/private/biopac_acq.m +++ b/fileio/private/biopac_acq.m @@ -8,7 +8,7 @@ % evt = biopac_acq(filename, hdr); % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2018-2024 Robert Oostenveld % diff --git a/fileio/private/bucn_txt.m b/fileio/private/bucn_txt.m index 38f785c923..d290b399c7 100644 --- a/fileio/private/bucn_txt.m +++ b/fileio/private/bucn_txt.m @@ -9,7 +9,7 @@ % evt = bucn_txt(filename, hdr); % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT, READ_BUCN_NIRSHDR, READ_BUCN_NIRSDATA, READ_BUCN_NIRSEVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2022-2024, Robert Oostenveld % diff --git a/fileio/private/eegsynth_tsv.m b/fileio/private/eegsynth_tsv.m index 47b634bf88..45b7384f48 100644 --- a/fileio/private/eegsynth_tsv.m +++ b/fileio/private/eegsynth_tsv.m @@ -13,7 +13,7 @@ % See https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/05-task-events.html % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % FIXME it might be that the events are one sample off, but I cannot be bothered % checking that precisely at the moment of the initial implementation. diff --git a/fileio/private/events_tsv.m b/fileio/private/events_tsv.m index 1e595496fb..b8325c30a7 100644 --- a/fileio/private/events_tsv.m +++ b/fileio/private/events_tsv.m @@ -15,7 +15,7 @@ % See https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/05-task-events.html % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2018-2024, Robert Oostenveld % diff --git a/fileio/private/liberty_csv.m b/fileio/private/liberty_csv.m index 3b4234b2b6..1aa2ac8500 100644 --- a/fileio/private/liberty_csv.m +++ b/fileio/private/liberty_csv.m @@ -8,7 +8,7 @@ % evt = liberty_csv(filename, hdr); % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2021-2024, Robert Oostenveld % diff --git a/fileio/private/maus_textgrid.m b/fileio/private/maus_textgrid.m index 9c6a334fd3..ed8aa484a0 100644 --- a/fileio/private/maus_textgrid.m +++ b/fileio/private/maus_textgrid.m @@ -12,7 +12,7 @@ % corresponding wav file with the same filename in the same directory. % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2019-2024, Robert Oostenveld % diff --git a/fileio/private/motion_c3d.m b/fileio/private/motion_c3d.m index 8d7a33e9b4..6a99bb41c9 100644 --- a/fileio/private/motion_c3d.m +++ b/fileio/private/motion_c3d.m @@ -8,7 +8,7 @@ % evt = motion_c3d(filename, hdr); % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2019-2024, Robert Oostenveld % diff --git a/fileio/private/openbci_txt.m b/fileio/private/openbci_txt.m index 2eae069cc4..90eef933ae 100644 --- a/fileio/private/openbci_txt.m +++ b/fileio/private/openbci_txt.m @@ -8,7 +8,7 @@ % evt = openbci_txt(filename, hdr); % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2020-2024, Robert Oostenveld % diff --git a/fileio/private/openpose_keypoints.m b/fileio/private/openpose_keypoints.m index 881d161f09..8eb086a631 100644 --- a/fileio/private/openpose_keypoints.m +++ b/fileio/private/openpose_keypoints.m @@ -8,7 +8,7 @@ % evt = openpose_keypoints(filename, hdr); % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2021-2024, Robert Oostenveld % diff --git a/fileio/private/opensignals_txt.m b/fileio/private/opensignals_txt.m index cda373f265..8f7f53111b 100644 --- a/fileio/private/opensignals_txt.m +++ b/fileio/private/opensignals_txt.m @@ -8,7 +8,7 @@ % evt = opensignals_txt(filename, hdr); % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2019-2024, Robert Oostenveld % diff --git a/fileio/private/openvibe_mat.m b/fileio/private/openvibe_mat.m index 640c292e86..60e6f7052a 100644 --- a/fileio/private/openvibe_mat.m +++ b/fileio/private/openvibe_mat.m @@ -9,7 +9,7 @@ % evt = openvibe_mat(filename, hdr); % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2023-2024, Robert Oostenveld % diff --git a/fileio/private/opm_fil.m b/fileio/private/opm_fil.m index bd7865aff9..5a73764554 100644 --- a/fileio/private/opm_fil.m +++ b/fileio/private/opm_fil.m @@ -10,7 +10,7 @@ % See https://github.com/tierneytim/OPM for technical details. % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2020-2024, Robert Oostenveld % diff --git a/fileio/private/qualisys_tsv.m b/fileio/private/qualisys_tsv.m index 68b473e47a..11b7bce68f 100644 --- a/fileio/private/qualisys_tsv.m +++ b/fileio/private/qualisys_tsv.m @@ -8,7 +8,7 @@ % evt = qualysis_tsv(filename, hdr); % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2019-2024, Robert Oostenveld % diff --git a/fileio/private/sccn_xdf.m b/fileio/private/sccn_xdf.m index 0df20bcbc7..b39ceda33a 100644 --- a/fileio/private/sccn_xdf.m +++ b/fileio/private/sccn_xdf.m @@ -8,7 +8,7 @@ % evt = sccn_xdf(filename, hdr); % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT, XDF2FIELDTRIP -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2019-2024, Robert Oostenveld % diff --git a/fileio/private/sensys_csv.m b/fileio/private/sensys_csv.m index 900c88b4a1..69f2904ac6 100644 --- a/fileio/private/sensys_csv.m +++ b/fileio/private/sensys_csv.m @@ -10,7 +10,7 @@ % evt = sensys_csv(filename, hdr); % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2022-2024, Robert Oostenveld % diff --git a/fileio/private/snirf.m b/fileio/private/snirf.m index a1e126352a..4639d274d3 100644 --- a/fileio/private/snirf.m +++ b/fileio/private/snirf.m @@ -14,7 +14,7 @@ % must have the same sampling rate and be sampled at the same time. % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT, SNIRF2OPTO -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2020-2024, Robert Oostenveld % diff --git a/fileio/private/spikeglx_bin.m b/fileio/private/spikeglx_bin.m new file mode 100644 index 0000000000..e49e0f4150 --- /dev/null +++ b/fileio/private/spikeglx_bin.m @@ -0,0 +1,126 @@ +function varargout = spikeglx_bin(filename, hdr, begsample, endsample, chanindx) + +% SPIKEGLX_BIN reads Neuropixel data from SpikeGLX .bin files +% +% See https://github.com/jenniferColonell/SpikeGLX_Datafile_Tools +% +% Use as +% hdr = spikeglx_bin(filename); +% dat = spikeglx_bin(filename, hdr, begsample, endsample, chanindx); +% evt = spikeglx_bin(filename, hdr); +% +% See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX + +% Copyright (C) 2024, Robert Oostenveld +% +% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org +% for the documentation and details. +% +% FieldTrip is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% FieldTrip is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with FieldTrip. If not, see . +% +% $Id$ + +% ensure that the external toolbox is available +ft_hastoolbox('spikeglx', 1); + +needhdr = (nargin==1); +needevt = (nargin==2); +needdat = (nargin==5); + +% use the full filename including path to distinguish between similarly named files in different directories +[p, f, x] = fileparts(filename); + +if strcmp(x, '.meta') + x = '.bin'; +end + +if isempty(p) + % no path was specified + fullname = which(filename); +elseif startsWith(p, ['.' filesep]) + % a relative path was specified + fullname = fullfile(pwd, p(3:end), [f, x]); +else + fullname = filename; +end + + +if needhdr + [p, f, x] = fileparts(fullname); + meta = SGLX_readMeta.ReadMeta([f x], p); + chan = split(meta.snsChanMap, ')('); + + hdr = []; + hdr.nChans = str2double(meta.nSavedChans); + hdr.Fs = SGLX_readMeta.SampRate(meta); + hdr.nSamples = str2double(meta.fileSizeBytes)/(hdr.nChans*2); + hdr.nSamplesPre = 0; % continuous data + hdr.nTrials = 1; % continuous data + hdr.label = strtok(chan(2:end), ';'); % the first does not seem to be a channel + hdr.orig = meta; % keep the original details + + % return the header + varargout = {hdr}; + +elseif needdat + % the header has already been read and passed as input argument + meta = hdr.orig; + + % read the binary data + dat = SGLX_readMeta.ReadBin(begsample-1, endsample-begsample+1, meta, [f x], p); + + % apply the calibration + if strcmp(meta.typeThis, 'imec') + dat = SGLX_readMeta.GainCorrectIM(dat, 1:size(dat,1), meta); + else + dat = SGLX_readMeta.GainCorrectNI(dat, 1:size(dat,1), meta); + end + + % make the selection of channels + dat = dat(chanindx,:); + + % return the data + varargout = {dat}; + +elseif needevt + % the header has already been read and passed as input argument + meta = hdr.orig; + + % For a digital channel: read this digital word dw in the saved file + % (1-based). For imec data there is never more than one saved digital word. + dw = 1; + + ntrig = 8; + + dat = SGLX_readMeta.ReadBin(0, hdr.nSamples, meta, [f x], p); + dig = SGLX_readMeta.ExtractDigital(dat, meta, dw, 1:ntrig); + + event = []; + for i=1:ntrig + trig = single(dig(i,:)); + onset = find(diff([0 trig])>0); + offset = find(diff([trig 0])<0); + for j=1:numel(onset) + event(end+1).type = 'trigger'; + event(end ).value = i; + event(end ).sample = onset(j); + event(end ).duration = offset(j)-onset(j)+1; + event(end ).offset = 0; + end + end + + % return the events + varargout = {event}; +end \ No newline at end of file diff --git a/fileio/private/unicorn_csv.m b/fileio/private/unicorn_csv.m index a9e8248c2c..1f041f99c6 100644 --- a/fileio/private/unicorn_csv.m +++ b/fileio/private/unicorn_csv.m @@ -10,7 +10,7 @@ % evt = unicorn_csv(filename, hdr); % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2022-2024, Robert Oostenveld % diff --git a/fileio/private/xsens_mvnx.m b/fileio/private/xsens_mvnx.m index 5976049830..c55fa314ed 100644 --- a/fileio/private/xsens_mvnx.m +++ b/fileio/private/xsens_mvnx.m @@ -12,7 +12,7 @@ % evt = xsens_mvnx(filename, hdr); % % See also FT_FILETYPE, FT_READ_HEADER, FT_READ_DATA, FT_READ_EVENT -% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, UNICORN_CSV, XSENS_MVNX +% See also BIDS_TSV, BIOPAC_ACQ, BUCN_TXT, EEGSYNTH_TSV, EVENTS_TSV, LIBERTY_CSV, MAUS_TEXTGRID, MOTION_C3D, OPENBCI_TXT, OPENPOSE_KEYPOINTS, OPENSIGNALS_TXT, OPENVIBE_MAT, OPM_FIL, QUALISYS_TSV, SCCN_XDF, SENSYS_CSV, SNIRF, SPIKEGLX_BIN, UNICORN_CSV, XSENS_MVNX % Copyright (C) 2020-2024, Helena Cockx % diff --git a/private/dataset2files.m b/private/dataset2files.m deleted file mode 100644 index ac4ac7fc07..0000000000 --- a/private/dataset2files.m +++ /dev/null @@ -1,97 +0,0 @@ -function cfg = dataset2files(cfg) - -% Helper function that converts a dataset into headerfile and datafile -% if necessary. This is used in PREPROCESSING and DEFINETRIAL -% -% This function operates only on -% cfg.dataset -% cfg.datafile -% cfg.headerfile -% and returns the updated cfg. - -% Copyright (C) 2004, Robert Oostenveld -% -% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org -% for the documentation and details. -% -% FieldTrip is free software: you can redistribute it and/or modify -% it under the terms of the GNU General Public License as published by -% the Free Software Foundation, either version 3 of the License, or -% (at your option) any later version. -% -% FieldTrip is distributed in the hope that it will be useful, -% but WITHOUT ANY WARRANTY; without even the implied warranty of -% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -% GNU General Public License for more details. -% -% You should have received a copy of the GNU General Public License -% along with FieldTrip. If not, see . -% -% $Id$ - -% start with empty fields if thery are not present -if ~isfield(cfg, 'dataset') - cfg.dataset = []; -end -if ~isfield(cfg, 'datafile') - cfg.datafile = []; -end -if ~isfield(cfg, 'headerfile') - cfg.headerfile = []; -end - -if ~isempty(cfg.dataset) - if strcmp(cfg.dataset, 'gui') - d = uigetdir; - if d==0 - [f, p] = uigetfile; - if f==0 - ft_error('You should select a dataset file or directory'); - else - d = fullfile(p, f); - end - end - cfg.dataset = d; - end - - switch ft_filetype(cfg.dataset) - case 'ctf_ds' - % convert CTF dataset into filenames - [path, file, ext] = fileparts(cfg.dataset); - cfg.headerfile = fullfile(cfg.dataset, [file '.res4']); - cfg.datafile = fullfile(cfg.dataset, [file '.meg4']); - case 'brainvision_vhdr' - [path, file, ext] = fileparts(cfg.dataset); - cfg.headerfile = fullfile(path, [file '.vhdr']); - if exist(fullfile(path, [file '.eeg'])) - cfg.datafile = fullfile(path, [file '.eeg']); - elseif exist(fullfile(path, [file '.seg'])) - cfg.datafile = fullfile(path, [file '.seg']); - end - case 'brainvision_eeg' - [path, file, ext] = fileparts(cfg.dataset); - cfg.headerfile = fullfile(path, [file '.vhdr']); - cfg.datafile = fullfile(path, [file '.eeg']); - case 'brainvision_seg' - [path, file, ext] = fileparts(cfg.dataset); - cfg.headerfile = fullfile(path, [file '.vhdr']); - cfg.datafile = fullfile(path, [file '.seg']); - case {'physionet_dat', 'physionet_hea'} - [path, file, ext] = fileparts(cfg.dataset); - cfg.headerfile = fullfile(path, [file '.hea']); - cfg.datafile = fullfile(path, [file '.dat']); - otherwise - % convert dataset into filenames, assume that the header and data are the same - cfg.datafile = cfg.dataset; - cfg.headerfile = cfg.dataset; - end - -elseif ~isempty(cfg.datafile) && isempty(cfg.headerfile) - % assume that the datafile also contains the header - cfg.headerfile = cfg.datafile; - -elseif isempty(cfg.datafile) && ~isempty(cfg.headerfile) - % assume that the headerfile also contains the data - cfg.datafile = cfg.headerfile; -end - diff --git a/utilities/ft_hastoolbox.m b/utilities/ft_hastoolbox.m index 38f40caf96..88a63fb9ab 100644 --- a/utilities/ft_hastoolbox.m +++ b/utilities/ft_hastoolbox.m @@ -182,6 +182,7 @@ 'SON2' 'see http://www.kcl.ac.uk/depsta/biomedical/cfnr/lidierth.html, or contact Malcolm Lidierth' 'SPECEST' 'see http://www.fieldtriptoolbox.org' 'SPIKE' 'see http://www.fieldtriptoolbox.org' + 'SPIKEGLX' 'see https://github.com/jenniferColonell/SpikeGLX_Datafile_Tools' 'SPLINES' 'see http://www.mathworks.com/products/splines' 'SPM' 'see http://www.fil.ion.ucl.ac.uk/spm' 'SPM12' 'see http://www.fil.ion.ucl.ac.uk/spm' @@ -455,6 +456,8 @@ dependency = {'spm_opm_vslm'}; case 'READ_MED' dependency = {'read_MED', 'plot_MED'}; + case 'SKIPEGLX' + dependency = {'SGLX_readMeta'}; % the following are FieldTrip modules or toolboxes case 'FILEIO' diff --git a/utilities/private/dataset2files.m b/utilities/private/dataset2files.m index 25a7f701f6..c1f4afd646 100644 --- a/utilities/private/dataset2files.m +++ b/utilities/private/dataset2files.m @@ -189,6 +189,10 @@ headerfile = filename; datafile = filename; end + case {'spikeglx_bin'} + [p, f, x] = fileparts(filename); + headerfile = fullfile(p, [f '.meta']); + datafile = fullfile(p, [f '.bin']); otherwise % convert filename into filenames, assume that the header and data are the same datafile = filename;