diff --git a/calc_CellMetrics/loadOpenEphysDigital.m b/calc_CellMetrics/loadOpenEphysDigital.m index 88a850c..fbbc69a 100644 --- a/calc_CellMetrics/loadOpenEphysDigital.m +++ b/calc_CellMetrics/loadOpenEphysDigital.m @@ -1,29 +1,22 @@ -function openephysDig = loadOpenEphysDigital(session) +function openephysDig = loadOpenEphysDigital(session, varargin) % function to load digital inputs from Open Ephys and align the data properly % % INPUT % session: session struct +% Optional inputs: +% probeLetter: 'A' or 'B' for specific probe TTL data. If empty, tries ProbeA first, then legacy format % % OUTPUTS % intanDig.on: on-state changes for each channel [in seconds] % intanDig.off: off-state changes for each channel [in seconds] % -% By Peter Petersen +% By Peter Petersen, Mingze Dou % petersen.peter@gmail.com - -% Load TTL data -% Each path must contain either: -% Legacy format: -% 1. timestamps.npy -% 2. channel_states.npy -% 3. channels.npy -% 4. full_words.npy -% New format: -% 1. timestamps.npy -% 2. states.npy -% 3. sample_numbers.npy -% 4. full_words.npy +p = inputParser; +addParameter(p,'probeLetter','',@ischar); +parse(p,varargin{:}); +parameters = p.Results; TTL_paths = {}; epochs_startTime = []; @@ -31,27 +24,62 @@ % Determine file format and set paths for i = 1:numel(session.epochs) - % Try new format path first - newFormatPath = fullfile(session.epochs{i}.name,'events','Neuropix-PXI-100.ProbeA','TTL'); - legacyFormatPath = fullfile(session.epochs{i}.name,'events','Neuropix-PXI-100.0','TTL_1'); - - % Check which path exists and set accordingly - if exist(fullfile(session.general.basePath, newFormatPath), 'dir') - TTL_paths{i} = newFormatPath; - timestampPath = fullfile(session.general.basePath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.ProbeA','timestamps.npy'); - isNewFormat = true; %format flag + + if ~isempty(parameters.probeLetter) + % Specific probe requested + TTL_path = fullfile(session.epochs{i}.name,'events',['Neuropix-PXI-100.Probe' parameters.probeLetter],'TTL'); + disp(['Checking TTL path for epoch ' num2str(i) ': ' fullfile(session.general.basePath, TTL_path)]) + if exist(fullfile(session.general.basePath, TTL_path), 'dir') + TTL_paths{i} = TTL_path; + isNewFormat = true; + disp('Found TTL directory') + else + error(['TTL path not found for Probe' parameters.probeLetter ' in epoch ' num2str(i)]); + end else - TTL_paths{i} = legacyFormatPath; - timestampPath = fullfile(session.general.basePath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.1','timestamps.npy'); - isNewFormat = false; + % Try ProbeA format first, then legacy format + probeA_path = fullfile(session.epochs{i}.name,'events','Neuropix-PXI-100.ProbeA','TTL'); + legacy_path = fullfile(session.epochs{i}.name,'events','Neuropix-PXI-100.0','TTL_1'); + + if exist(fullfile(session.general.basePath, probeA_path), 'dir') + TTL_paths{i} = probeA_path; + isNewFormat = true; + elseif exist(fullfile(session.general.basePath, legacy_path), 'dir') + TTL_paths{i} = legacy_path; + isNewFormat = false; + else + error(['No valid TTL path found for epoch ' num2str(i)]); + end end - + + % Set timestamp path based on format + if isNewFormat + probeStr = parameters.probeLetter; + if isempty(probeStr) + probeStr = 'A'; + end + timestampPath = fullfile(session.general.basePath, session.epochs{i}.name, 'continuous', ... + ['Neuropix-PXI-100.Probe' probeStr], 'timestamps.npy'); + else + timestampPath = fullfile(session.general.basePath, session.epochs{i}.name, 'continuous', ... + 'Neuropix-PXI-100.1', 'timestamps.npy'); + end + + disp(['Checking timestamp path for epoch ' num2str(i) ': ' timestampPath]) + if exist(timestampPath, 'file') + temp = readNPY(timestampPath); + disp(['Found ' num2str(length(temp)) ' timestamps, first value: ' num2str(temp(1))]) + else + disp('Timestamp file not found') + end + epochs_startTime(i) = session.epochs{i}.startTime; - temp = readNPY(timestampPath); if isNewFormat - ephys_t0(i) = double(temp(1)); % timestamps in second according to new format of open ephys + ephys_t0(i) = double(temp(1)); + disp(['ephys_t0 for epoch ' num2str(i) ': ' num2str(ephys_t0(i))]) else - ephys_t0(i) = double(temp(1))/session.extracellular.sr; + ephys_t0(i) = double(temp(1))/session.extracellular.sr; + disp(['ephys_t0 for epoch ' num2str(i) ': ' num2str(ephys_t0(i))]) end end @@ -60,23 +88,31 @@ % Load and process first epoch data basePath = fullfile(session.general.basePath, TTL_paths{1}); -timestamps = readNPY(fullfile(basePath,'timestamps.npy')); +ttlPath = fullfile(basePath,'timestamps.npy'); +disp(['Checking TTL timestamps path: ' ttlPath]) +if exist(ttlPath, 'file') + timestamps = readNPY(ttlPath); + disp(['Found ' num2str(length(timestamps)) ' TTL timestamps']) +else + disp('TTL timestamp file not found') +end if isNewFormat - openephysDig.timestamps = epochs_startTime(1) + double(timestamps) - ephys_t0(1); % timestamps in second + openephysDig.timestamps = epochs_startTime(1) + double(timestamps) - ephys_t0(1); % timestamps in second else - openephysDig.timestamps = epochs_startTime(1) + double(timestamps)/session.extracellular.sr - ephys_t0(1); % Legacy format + openephysDig.timestamps = epochs_startTime(1) + double(timestamps)/session.extracellular.sr - ephys_t0(1); % legacy format end % Check which format to use for first epoch if exist(fullfile(basePath,'states.npy'), 'file') + % New format files openephysDig.channel_states = readNPY(fullfile(basePath,'states.npy')); openephysDig.channels = readNPY(fullfile(basePath,'sample_numbers.npy')); - openephysDig.full_words = readNPY(fullfile(basePath,'full_words.npy')); else + % Legacy format files openephysDig.channel_states = readNPY(fullfile(basePath,'channel_states.npy')); openephysDig.channels = readNPY(fullfile(basePath,'channels.npy')); - openephysDig.full_words = readNPY(fullfile(basePath,'full_words.npy')); end +openephysDig.full_words = readNPY(fullfile(basePath,'full_words.npy')); openephysDig.on{1} = double(openephysDig.timestamps(openephysDig.channel_states == 1)); openephysDig.off{1} = double(openephysDig.timestamps(openephysDig.channel_states == -1)); @@ -92,18 +128,20 @@ timestamps = readNPY(fullfile(basePath,'timestamps.npy')); if isNewFormat - timestamps = epochs_startTime(i) + double(timestamps) - ephys_t0(i); % timestamps in second + timestamps = epochs_startTime(i) + double(timestamps) - ephys_t0(i); % timestamps in second else - timestamps = epochs_startTime(i) + double(timestamps)/session.extracellular.sr - ephys_t0(i); % Legacy format + timestamps = epochs_startTime(i) + double(timestamps)/session.extracellular.sr - ephys_t0(i); % legacy format end openephysDig.timestamps = [openephysDig.timestamps; timestamps]; % Load states and channels based on format if exist(fullfile(basePath,'states.npy'), 'file') + % New format files channel_states = readNPY(fullfile(basePath,'states.npy')); openephysDig.channel_states = [openephysDig.channel_states; channel_states]; openephysDig.channels = [openephysDig.channels; readNPY(fullfile(basePath,'sample_numbers.npy'))]; else + % Legacy format files channel_states = readNPY(fullfile(basePath,'channel_states.npy')); openephysDig.channel_states = [openephysDig.channel_states; channel_states]; openephysDig.channels = [openephysDig.channels; readNPY(fullfile(basePath,'channels.npy'))]; @@ -134,4 +172,4 @@ % Saving data saveStruct(openephysDig,'digitalseries','session',session); -end +end \ No newline at end of file diff --git a/calc_CellMetrics/loadOpenEphysDigitalNidaq.m b/calc_CellMetrics/loadOpenEphysDigitalNidaq.m new file mode 100644 index 0000000..4c9d2b3 --- /dev/null +++ b/calc_CellMetrics/loadOpenEphysDigitalNidaq.m @@ -0,0 +1,189 @@ +function openephysDig = loadOpenEphysDigitalNidaq(session, varargin) +% loadOpenEphysDigitalNidaq - Load and process digital TTL signals from OpenEphys NI-DAQ Board +% +% This function processes TTL signals recorded by the NI-DAQ board in OpenEphys, handling +% multiple recording epochs and aligning timestamps to the probe's timeline. +% +% INPUTS: +% session - Session struct containing recording metadata and paths +% +% Optional Name-Value Pairs: +% 'channelNum' - Integer. Channel number on NI-DAQ board to process +% 0: Clock signal (1 Hz) +% 1: Camera trigger (120 Hz) +% Default: [] (processes all channels) +% 'probeLetter' - Character. Specifies which probe's timeline to use for alignment +% 'A': Use ProbeA timestamps (default) +% 'B': Use ProbeB timestamps +% +% OUTPUTS: +% openephysDig - Structure containing processed TTL data with fields: +% .timestamps - [Nx1] Vector of all event timestamps (in seconds) +% .states - [Nx1] Vector of binary states (0/1) for each timestamp +% .epochNum - [Nx1] Vector indicating the epoch number for each timestamp +% .on - {1x1} Cell containing timestamps of rising edges (0→1) +% .off - {1x1} Cell containing timestamps of falling edges (1→0) +% .diagnostics - Structure containing timing analysis and data quality info +% including timestamp duplicates and state conflicts +% +% NOTES: +% - Rising and falling edges are paired based on channel-specific transitions +% - Timestamps are aligned to probe recording start time +% - Function warns if rising edges lack corresponding falling edges +% - Empty epochs or missing files are handled gracefully with warnings +% - Duplicate timestamps are analyzed with their states for conflicts +% +% EXAMPLE: +% % Load camera trigger signals from channel 1 +% ttl = loadOpenEphysDigitalNidaq(session, 'channelNum', 1) +% +% See also: readNPY, saveStruct +% +% By Mingze Dou + +p = inputParser; +addParameter(p,'channelNum', [], @isnumeric); +addParameter(p,'probeLetter', 'A', @ischar); +parse(p,varargin{:}); +parameters = p.Results; + +% Check for existing digitalseries file +existingFile = fullfile(session.general.basePath, [session.general.name '.openephysDig.digitalseries.mat']); +if exist(existingFile, 'file') + load(existingFile, 'openephysDig'); + return +end + +% Initialize +ttlPaths = {}; +epochsStartTime = []; +ephysT0 = []; +validEpochs = []; +openephysDig.on = {[]}; +openephysDig.off = {[]}; +openephysDig.timestamps = []; +openephysDig.states = []; +openephysDig.epochNum = []; +openephysDig.diagnostics = struct(); + +% Find valid epochs +for i = 1:numel(session.epochs) + ttlPath = fullfile(session.epochs{i}.name, 'events', 'NI-DAQmx-116.PXIe-6341', 'TTL'); + fullWordsPath = fullfile(session.general.basePath, ttlPath, 'full_words.npy'); + + if exist(fullWordsPath, 'file') && (~isempty(parameters.channelNum) && ... + any(bitand(readNPY(fullWordsPath), 2^parameters.channelNum)) || isempty(parameters.channelNum)) + validEpochs = [validEpochs i]; + ttlPaths{end+1} = ttlPath; + + % Get probe timestamps + probeStr = parameters.probeLetter; + if ~isempty(parameters.probeLetter), probeStr = parameters.probeLetter; end + + timestampPath = fullfile(session.general.basePath, session.epochs{i}.name, 'continuous', ... + ['Neuropix-PXI-103.Probe' probeStr], 'timestamps.npy'); + + if ~exist(timestampPath, 'file') + error(['Timestamp file not found for Probe' probeStr ' in epoch ' num2str(i)]); + end + + timestampData = readNPY(timestampPath); + epochsStartTime(end+1) = session.epochs{i}.startTime; + ephysT0(end+1) = double(timestampData(1)); + end +end + +if isempty(validEpochs) + error('No valid epochs found with the specified channel'); +end + +% Process epochs +for i = 1:length(validEpochs) + currentEpoch = validEpochs(i); + basePath = fullfile(session.general.basePath, ttlPaths{i}); + timestamps = readNPY(fullfile(basePath,'timestamps.npy')); + fullWords = readNPY(fullfile(basePath,'full_words.npy')); + if isempty(timestamps) || isempty(fullWords) + warning('Empty data found in epoch %d', currentEpoch); + continue; + end + + % Align timestamps and get states + alignedTimestamps = epochsStartTime(i) + double(timestamps) - ephysT0(i); + channel_states = bitget(fullWords, parameters.channelNum + 1); + + % Find unique timestamps and duplicates + [uniqueTimestamps, uniqueIdx] = unique(alignedTimestamps); + uniqueStates = channel_states(uniqueIdx); + + % Analyze duplicates and their states + [~, ~, dupGroups] = unique(alignedTimestamps); % Get grouping for all timestamps + duplicateInfo = struct('timestamps', [], 'states', [], 'hasConflict', []); + + % Find timestamps that appear more than once + [dupCounts, dupValues] = hist(alignedTimestamps, unique(alignedTimestamps)); + duplicateTimestamps = dupValues(dupCounts > 1); + + if ~isempty(duplicateTimestamps) + for j = 1:length(duplicateTimestamps) + dupTime = duplicateTimestamps(j); + dupIndices = find(alignedTimestamps == dupTime); + dupStates = channel_states(dupIndices); + + % Store duplicate information + duplicateInfo.timestamps(j) = dupTime; + duplicateInfo.states{j} = dupStates; + duplicateInfo.hasConflict(j) = length(unique(dupStates)) > 1; + end + + % Store in diagnostics + openephysDig.diagnostics.epoch(i).duplicates = duplicateInfo; + end + + % Initialize arrays for this epoch + rising_edges = []; + falling_edges = []; + + % Process the state sequence + state_idx = 1; + while state_idx < length(uniqueStates) + % Find next rising edge + while state_idx < length(uniqueStates) && uniqueStates(state_idx) == 0 + state_idx = state_idx + 1; + end + + if state_idx < length(uniqueStates) + % Found a rising edge + rising_edge_idx = state_idx; + + % Look for the next falling edge for this specific channel + state_idx = state_idx + 1; + while state_idx < length(uniqueStates) && uniqueStates(state_idx) == 1 + state_idx = state_idx + 1; + end + + if state_idx < length(uniqueStates) + % Found the corresponding falling edge + falling_edge_idx = state_idx; + + % Store the edges + rising_edges = [rising_edges; rising_edge_idx]; + falling_edges = [falling_edges; falling_edge_idx]; + else + warning('Found rising edge without corresponding falling edge at timestamp %f in epoch %d', ... + uniqueTimestamps(rising_edge_idx), currentEpoch); + end + end + end + + % Store results using the correct timestamps + openephysDig.on{1} = [openephysDig.on{1}; uniqueTimestamps(rising_edges)]; + openephysDig.off{1} = [openephysDig.off{1}; uniqueTimestamps(falling_edges)]; + openephysDig.timestamps = [openephysDig.timestamps; uniqueTimestamps]; + openephysDig.states = [openephysDig.states; uniqueStates]; + openephysDig.epochNum = [openephysDig.epochNum; repmat(currentEpoch, length(uniqueTimestamps), 1)]; +end + +% Save +saveStruct(openephysDig,'digitalseries','session',session); +end \ No newline at end of file diff --git a/calc_CellMetrics/loadOpenEphysSettingsFile.m b/calc_CellMetrics/loadOpenEphysSettingsFile.m index 5934493..4240846 100644 --- a/calc_CellMetrics/loadOpenEphysSettingsFile.m +++ b/calc_CellMetrics/loadOpenEphysSettingsFile.m @@ -5,13 +5,13 @@ % https://open-ephys.github.io/gui-docs/User-Manual/Recording-data/Binary-format.html p = inputParser; -addParameter(p,'probe_letter','A',@ischar); +addParameter(p,'probeLetter','A',@ischar); % Parsing inputs parse(p,varargin{:}) parameters = p.Results; -switch parameters.probe_letter +switch parameters.probeLetter case 'A' stream_id = 1; case 'B' diff --git a/calc_CellMetrics/loadOptitrack.m b/calc_CellMetrics/loadOptitrack.m index edf908e..d1b4e13 100644 --- a/calc_CellMetrics/loadOptitrack.m +++ b/calc_CellMetrics/loadOptitrack.m @@ -118,10 +118,14 @@ % Estimating the speed of the rat animal_speed = [optitrack_temp.FrameRate*sqrt(sum(diff(position3D)'.^2)),0]; -animal_speed = nanconv(animal_speed,ones(1,10)/10,'edge'); -animal_acceleration = [0,diff(animal_speed)]; +animal_speed = nanconv(animal_speed,ones(1,10)/10,'edge'); % Original smoothing -% Adding output struct +% Additional smoothing with larger window +window_size = 80; +smoothed_speed = nanconv(animal_speed, ones(1,window_size)/window_size, 'edge'); +animal_acceleration = [0,optitrack_temp.FrameRate*diff(smoothed_speed)]; + +% Adding output struct optitrack_temp.position3D = position3D'; % Generating buzcode fields and output struct @@ -134,7 +138,8 @@ optitrack.position.units = 'centimeters'; optitrack.position.referenceFrame = 'global'; optitrack.position.coordinateSystem = 'cartesian'; -optitrack.speed = animal_speed; +optitrack.speed = animal_speed; % Original speed +optitrack.smoothedSpeed = smoothed_speed; % Added smoothed speed optitrack.acceleration = animal_acceleration; optitrack.orientation.x = optitrack_temp.Xr; optitrack.orientation.y = optitrack_temp.Yr; diff --git a/calc_CellMetrics/preprocessOpenEphysData.m b/calc_CellMetrics/preprocessOpenEphysData.m index f0e1694..70e5aa0 100644 --- a/calc_CellMetrics/preprocessOpenEphysData.m +++ b/calc_CellMetrics/preprocessOpenEphysData.m @@ -17,6 +17,7 @@ addParameter(p,'saveMat', true, @islogical); % Saves basename.session.mat file addParameter(p,'showGUI',false,@islogical); addParameter(p,'processData',true,@islogical); + addParameter(p,'probeLetter','A',@(x) ismember(x,{'A','B','C'})); parse(p,varargin{:}) parameters = p.Results; @@ -44,66 +45,62 @@ end end end + saveStruct(session); % 2. Imports extracellular metadata and Channel coordinates from the first structure.oebin file file1 = fullfile(session.general.basePath,session.epochs{1}.name,'structure.oebin'); - session = loadOpenEphysSettingsFile(file1,session); - - + session = loadOpenEphysSettingsFile(file1, session, 'probeLetter', parameters.probeLetter); + % 3. Epoch durations - [session,inputFiles] = calculateEpochDurations(session,basepath); - - % Shows session GUI if requested by user + [session,inputFiles] = calculateEpochDurations(session, basepath, parameters.probeLetter); + + % Shows session GUI if requested if parameters.showGUI session = gui_session(session); - - % Calculates epoch durations and inputFiles again in case the number of epochs has changed - [session,inputFiles] = calculateEpochDurations(session,basepath); + [session,inputFiles] = calculateEpochDurations(session, basepath, parameters.probeLetter); end - + % 4. Saving session struct if parameters.saveMat saveStruct(session); end - - + % 5. Merge dat files to single binary .dat file in basepath if parameters.processData - disp('Attempting to concatenate binary files with spiking data.') - outputFile = fullfile(basepath,[session.general.name,'.dat']); + outputFile = fullfile(basepath,[session.general.name, '.dat']); binaryMergeWrapper(inputFiles, outputFile) + end - % 6. Merge lfp files - inputFiles_lfp = {}; - for i = 1:numel(session.epochs) - if exist(fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.1','continuous.bin'),'file') - inputFiles_lfp{i} = fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.1','continuous.bin'); - elseif exist(fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.1','continuous.dat'),'file') - inputFiles_lfp{i} = fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.1','continuous.dat'); - else - inputFiles_lfp{i} = fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.ProbeA-LFP','continuous.dat'); - end + % 6. Merge lfp files + inputFiles_lfp = {}; + for i = 1:numel(session.epochs) + if exist(fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.1','continuous.bin'),'file') + inputFiles_lfp{i} = fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.1','continuous.bin'); + elseif exist(fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.1','continuous.dat'),'file') + inputFiles_lfp{i} = fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.1','continuous.dat'); + else + inputFiles_lfp{i} = fullfile(basepath,session.epochs{i}.name,'continuous',['Neuropix-PXI-100.Probe', parameters.probeLetter, '-LFP'],'continuous.dat'); end - outputFile_lfp = fullfile(basepath,[session.general.name,'.lfp']); + end + outputFile_lfp = fullfile(basepath,[session.general.name, '_Probe', parameters.probeLetter, '.lfp']); - disp('Attempting to concatenate binary LFP files.') - binaryMergeWrapper(inputFiles_lfp, outputFile_lfp) + disp('Attempting to concatenate binary LFP files.') + binaryMergeWrapper(inputFiles_lfp, outputFile_lfp) - % 7. Merge digital timeseries - openephysDig = loadOpenEphysDigital(session); + % 7. Merge digital timeseries + % openephysDig = loadOpenEphysDigital(session); - end +end - function subFolderNames = checkfolder(dir1, folderstring) - files = dir(dir1); - dirFlags = [files.isdir]; % Get a logical vector that tells which is a directory. - subFolders = files(dirFlags); % Extract only those that are directories. - subFolderNames = {subFolders(3:end).name}; % Get only the folder names into a cell array. Start at 3 to skip . and .. - subFolderNames = subFolderNames(contains(subFolderNames,folderstring)); - end +function subFolderNames = checkfolder(dir1, folderstring) +files = dir(dir1); +dirFlags = [files.isdir]; % Get a logical vector that tells which is a directory. +subFolders = files(dirFlags); % Extract only those that are directories. +subFolderNames = {subFolders(3:end).name}; % Get only the folder names into a cell array. Start at 3 to skip . and .. +subFolderNames = subFolderNames(contains(subFolderNames,folderstring)); end @@ -200,31 +197,42 @@ function binaryMergeWrapper(inputFiles, outputFile) totalSize = sum(individualSizes); end -function [session,inputFiles] = calculateEpochDurations(session,basepath) +function [session,inputFiles] = calculateEpochDurations(session, basepath, probeLetter) inputFiles = {}; startTime = 0; stopTime = 0; nSamples = 0; + for i = 1:numel(session.epochs) session.epochs{i}.startTime = startTime; - if exist(fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.0','continuous.bin'),'file') - inputFiles{i} = fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.0','continuous.bin'); - - elseif exist(fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.0','continuous.dat'),'file') - inputFiles{i} = fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.0','continuous.dat'); - elseif exist(fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.ProbeA','continuous.dat'),'file') - inputFiles{i} = fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.ProbeA','continuous.dat'); - - elseif exist(fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.ProbeA-AP','continuous.dat'),'file') - inputFiles{i} = fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.ProbeA-AP','continuous.dat'); - - else - error(['Epoch duration could not be estimated as raw data file does not exist: ', inputFiles{i}]); + % Define possible file paths + possiblePaths = { + fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.0','continuous.dat'), + fullfile(basepath,session.epochs{i}.name,'continuous','Neuropix-PXI-100.0','continuous.bin'), + fullfile(basepath,session.epochs{i}.name,'continuous',['Neuropix-PXI-100.Probe' probeLetter],'continuous.dat'), + fullfile(basepath,session.epochs{i}.name,'continuous',['Neuropix-PXI-100.Probe' probeLetter '-AP'],'continuous.dat'), + fullfile(basepath,session.epochs{i}.name,'continuous',['Neuropix-PXI-103.Probe' probeLetter],'continuous.dat'), + fullfile(basepath,session.epochs{i}.name,'continuous',['Neuropix-PXI-103.Probe' probeLetter '-AP'],'continuous.dat'), + }; + + % Check each possible path + fileFound = false; + for pathIdx = 1:length(possiblePaths) + if exist(possiblePaths{pathIdx}, 'file') + inputFiles{i} = possiblePaths{pathIdx}; + fileFound = true; + break; + end end + + if ~fileFound + error(['Epoch duration could not be estimated as raw data file does not exist for Probe' probeLetter ' in epoch ' num2str(i)]); + end + temp = dir(inputFiles{i}); duration = temp.bytes/session.extracellular.sr/session.extracellular.nChannels/2; - stopTime = startTime+duration; + stopTime = startTime + duration; session.epochs{i}.stopTime = stopTime; startTime = stopTime; nSamples = nSamples + temp.bytes/session.extracellular.nChannels/2;