From cdf66080e4b25f1c5b8719b37461158d3394936f Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Apr 2026 10:56:37 +0000 Subject: [PATCH 1/5] Accept any .meta file in neuropixelsGLX reader, not just .ap.meta filenamefromepochfiles was hardcoded to match *.ap.meta, rejecting .nidq.meta and .lf.meta files. Now matches any *.meta file. https://claude.ai/code/session_01GQqT3LvvCKZ7tm7TCphrL1 --- +ndr/+reader/neuropixelsGLX.m | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/+ndr/+reader/neuropixelsGLX.m b/+ndr/+reader/neuropixelsGLX.m index 7db7c73..56ce368 100644 --- a/+ndr/+reader/neuropixelsGLX.m +++ b/+ndr/+reader/neuropixelsGLX.m @@ -1,9 +1,9 @@ classdef neuropixelsGLX < ndr.reader.base -%NDR.READER.NEUROPIXELSGLX - Reader class for Neuropixels SpikeGLX AP-band data. +%NDR.READER.NEUROPIXELSGLX - Reader class for SpikeGLX data (AP, LF, NIDQ). % -% This class reads action-potential band data from Neuropixels probes -% acquired with the SpikeGLX software. Each instance handles one probe's -% AP stream (one .ap.bin / .ap.meta file pair per epoch). +% This class reads data from Neuropixels probes and NI-DAQ devices +% acquired with the SpikeGLX software. Each instance handles one stream +% (one .bin / .meta file pair per epoch). % % SpikeGLX saves Neuropixels data as flat interleaved int16 binary files % with companion .meta text files. The binary files have no header. @@ -215,15 +215,16 @@ % METAFILE = FILENAMEFROMEPOCHFILES(OBJ, FILENAME_ARRAY) % % Searches the cell array FILENAME_ARRAY for a file matching - % the pattern *.ap.meta. Returns the full path. Errors if - % zero or more than one match is found. + % the pattern *.meta (e.g., .ap.meta, .lf.meta, .nidq.meta). + % Returns the full path. Errors if zero or more than one match + % is found. % % See also: ndr.reader.base metafile = ''; count = 0; for i = 1:numel(filename_array) - if endsWith(filename_array{i}, '.ap.meta', 'IgnoreCase', true) + if endsWith(filename_array{i}, '.meta', 'IgnoreCase', true) metafile = filename_array{i}; count = count + 1; end @@ -231,10 +232,10 @@ if count == 0 error('ndr:reader:neuropixelsGLX:NoMetaFile', ... - 'No .ap.meta file found in the epoch file list.'); + 'No .meta file found in the epoch file list.'); elseif count > 1 error('ndr:reader:neuropixelsGLX:MultipleMetaFiles', ... - 'Multiple .ap.meta files found. Each epoch should have exactly one.'); + 'Multiple .meta files found. Each epoch should have exactly one.'); end end From e3dce33d541a5ed09237c1ced0f488540820f8b4 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Apr 2026 18:43:50 +0000 Subject: [PATCH 2/5] Find companion .meta file by matching .bin filename When multiple .meta files are present in the epoch (e.g., for cross-device synchronization), derive the correct .meta file from the .bin file's base name instead of erroring on multiple matches. https://claude.ai/code/session_01GQqT3LvvCKZ7tm7TCphrL1 --- +ndr/+reader/neuropixelsGLX.m | 52 ++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/+ndr/+reader/neuropixelsGLX.m b/+ndr/+reader/neuropixelsGLX.m index 56ce368..272f959 100644 --- a/+ndr/+reader/neuropixelsGLX.m +++ b/+ndr/+reader/neuropixelsGLX.m @@ -210,32 +210,52 @@ end function metafile = filenamefromepochfiles(obj, filename_array) - %FILENAMEFROMEPOCHFILES Identify the .ap.meta file from epoch file list. + %FILENAMEFROMEPOCHFILES Identify the companion .meta file from epoch file list. % % METAFILE = FILENAMEFROMEPOCHFILES(OBJ, FILENAME_ARRAY) % - % Searches the cell array FILENAME_ARRAY for a file matching - % the pattern *.meta (e.g., .ap.meta, .lf.meta, .nidq.meta). - % Returns the full path. Errors if zero or more than one match - % is found. + % Finds the .meta file that is the companion to the .bin file + % in the epoch file list. The .meta file must have the same + % base name as the .bin file (e.g., run.nidq.bin -> run.nidq.meta). + % This allows other .meta files to be present in the epoch for + % synchronization purposes. % % See also: ndr.reader.base - metafile = ''; - count = 0; + % First, find the .bin file + binfile = ''; for i = 1:numel(filename_array) - if endsWith(filename_array{i}, '.meta', 'IgnoreCase', true) - metafile = filename_array{i}; - count = count + 1; + if endsWith(filename_array{i}, '.bin', 'IgnoreCase', true) + binfile = filename_array{i}; + break; end end - if count == 0 - error('ndr:reader:neuropixelsGLX:NoMetaFile', ... - 'No .meta file found in the epoch file list.'); - elseif count > 1 - error('ndr:reader:neuropixelsGLX:MultipleMetaFiles', ... - 'Multiple .meta files found. Each epoch should have exactly one.'); + if ~isempty(binfile) + % Derive the expected .meta filename from the .bin filename + metafile = [binfile(1:end-3) 'meta']; + % Verify it exists in the file list or on disk + if ~any(strcmp(filename_array, metafile)) && ~isfile(metafile) + error('ndr:reader:neuropixelsGLX:NoMetaFile', ... + 'No companion .meta file found for %s.', binfile); + end + else + % No .bin file; fall back to finding a single .meta file + metafile = ''; + count = 0; + for i = 1:numel(filename_array) + if endsWith(filename_array{i}, '.meta', 'IgnoreCase', true) + metafile = filename_array{i}; + count = count + 1; + end + end + if count == 0 + error('ndr:reader:neuropixelsGLX:NoMetaFile', ... + 'No .meta file found in the epoch file list.'); + elseif count > 1 + error('ndr:reader:neuropixelsGLX:MultipleMetaFiles', ... + 'Multiple .meta files found and no .bin file to disambiguate.'); + end end end From 8cc74ae5ad3d07742333b04c93a57c17ca960b74 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Apr 2026 18:51:43 +0000 Subject: [PATCH 3/5] Handle cell array channeltype in neuropixelsGLX reader methods readchannels_epochsamples and underlying_datatype crashed with "SWITCH expression must be a scalar or a character vector" when channeltype was passed as a cell array from the NDI call chain. Extract the first element when channeltype is a cell, matching the convention used by axon_abf and bjg readers. https://claude.ai/code/session_01GQqT3LvvCKZ7tm7TCphrL1 --- +ndr/+reader/neuropixelsGLX.m | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/+ndr/+reader/neuropixelsGLX.m b/+ndr/+reader/neuropixelsGLX.m index 272f959..8ee699f 100644 --- a/+ndr/+reader/neuropixelsGLX.m +++ b/+ndr/+reader/neuropixelsGLX.m @@ -122,12 +122,18 @@ % [DATATYPE, P, DATASIZE] = UNDERLYING_DATATYPE(OBJ, EPOCHSTREAMS, % EPOCH_SELECT, CHANNELTYPE, CHANNEL) % + % CHANNELTYPE may be a char vector or a cell array of char + % vectors. If a cell array, all entries must be the same type. + % % For analog_in channels: int16, [0 1], 16 bits. % For time channels: double (computed), [0 1], 64 bits. % For digital_in channels: int16 (sync word), [0 1], 16 bits. % % See also: ndr.reader.base/underlying_datatype + if iscell(channeltype) + channeltype = channeltype{1}; + end switch lower(channeltype) case {'analog_in', 'ai'} datatype = 'int16'; @@ -156,12 +162,19 @@ % Reads data between sample S0 and S1 (inclusive, 1-based). % Returns an (S1-S0+1) x numel(CHANNEL) matrix. % + % CHANNELTYPE may be a char vector or a cell array of char + % vectors. If a cell array, all entries must be the same type. + % % For 'analog_in': returns int16 neural data. % For 'time': returns double time stamps in seconds. % For 'digital_in': returns int16 sync word values. % % See also: ndr.format.neuropixelsGLX.read + if iscell(channeltype) + channeltype = channeltype{1}; + end + metafile = obj.filenamefromepochfiles(epochstreams); info = ndr.format.neuropixelsGLX.header(metafile); binfile = [metafile(1:end-4) 'bin']; From 57e3dbc235a17e8b1ca9cb79a039b308893312c9 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Apr 2026 18:59:30 +0000 Subject: [PATCH 4/5] Remove dead code that read entire file before re-reading by sample range The analog_in case called ndr.format.neuropixelsGLX.read with (-Inf, Inf) reading the entire file into memory, then immediately discarded the result and re-read using read_samples with the actual s0:s1 range. Removed the redundant full-file read. https://claude.ai/code/session_01GQqT3LvvCKZ7tm7TCphrL1 --- +ndr/+reader/neuropixelsGLX.m | 8 -------- 1 file changed, 8 deletions(-) diff --git a/+ndr/+reader/neuropixelsGLX.m b/+ndr/+reader/neuropixelsGLX.m index 8ee699f..dfef9c5 100644 --- a/+ndr/+reader/neuropixelsGLX.m +++ b/+ndr/+reader/neuropixelsGLX.m @@ -186,14 +186,6 @@ data = ndr.time.fun.samples2times((s0:s1)', t0t1{1}, info.sample_rate); case {'analog_in', 'ai'} - % Read neural channels (1-based channel numbers map to - % file columns 1:n_neural_chans) - [data, ~] = ndr.format.neuropixelsGLX.read(binfile, -Inf, Inf, ... - 'numChans', info.n_saved_chans, ... - 'SR', info.sample_rate, ... - 'channels', channel); - % The read function returns the full file; we need to - % subset by sample range. Instead, use binarymatrix directly. data = read_samples(binfile, info, uint32(channel), s0, s1); case {'digital_in', 'di'} From e45865da0c9e9055b937018d1ad4366babad6dc1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:01:58 +0000 Subject: [PATCH 5/5] Update GitHub badges --- .github/badges/code_issues.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/badges/code_issues.svg b/.github/badges/code_issues.svg index 8bc43d4..7a8387c 100644 --- a/.github/badges/code_issues.svg +++ b/.github/badges/code_issues.svg @@ -1 +1 @@ -code issuescode issues12871287 \ No newline at end of file +code issuescode issues12861286 \ No newline at end of file