From d95e3817c63fc628649a1140997408afa3a60e6f Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Fri, 21 Nov 2025 14:03:28 +0000
Subject: [PATCH 1/9] Implement samples2times and times2samples for axon_abf to
handle gaps
---
+ndr/+reader/axon_abf.m | 32 +++++++++++++++++
tools/tests/test_axon_gaps.m | 70 ++++++++++++++++++++++++++++++++++++
2 files changed, 102 insertions(+)
create mode 100644 tools/tests/test_axon_gaps.m
diff --git a/+ndr/+reader/axon_abf.m b/+ndr/+reader/axon_abf.m
index 6b04b8d..97c7a2d 100755
--- a/+ndr/+reader/axon_abf.m
+++ b/+ndr/+reader/axon_abf.m
@@ -269,6 +269,38 @@
end;
end; % daqchannels2internalchannels
+ function t = samples2times(axon_abf_obj, channeltype, channel, epochstreams, epoch_select, s)
+ % SAMPLES2TIMES - convert sample numbers to time
+ %
+ % T = SAMPLES2TIMES(AXON_ABF_OBJ, CHANNELTYPE, CHANNEL, EPOCHSTREAMS, EPOCH_SELECT, S)
+ %
+ % Given sample numbers S, returns the time T of these samples.
+ %
+ % This function overrides the base class to handle gaps by reading the time channel.
+ %
+ t_all = axon_abf_obj.readchannels_epochsamples('time', 1, epochstreams, epoch_select, -inf, inf);
+ t_all = t_all(:);
+ s_all = (1:numel(t_all))';
+
+ t = interp1(s_all, t_all, s, 'linear', 'extrap');
+ end % samples2times
+
+ function s = times2samples(axon_abf_obj, channeltype, channel, epochstreams, epoch_select, t)
+ % TIMES2SAMPLES - convert time to sample numbers
+ %
+ % S = TIMES2SAMPLES(AXON_ABF_OBJ, CHANNELTYPE, CHANNEL, EPOCHSTREAMS, EPOCH_SELECT, T)
+ %
+ % Given sample times T, returns the sample numbers S of these samples.
+ %
+ % This function overrides the base class to handle gaps by reading the time channel.
+ %
+ t_all = axon_abf_obj.readchannels_epochsamples('time', 1, epochstreams, epoch_select, -inf, inf);
+ t_all = t_all(:);
+ s_all = (1:numel(t_all))';
+
+ s = interp1(t_all, s_all, t, 'linear', 'extrap');
+ s = round(s);
+ end % times2samples
end % methods
diff --git a/tools/tests/test_axon_gaps.m b/tools/tests/test_axon_gaps.m
new file mode 100644
index 0000000..ecdfc40
--- /dev/null
+++ b/tools/tests/test_axon_gaps.m
@@ -0,0 +1,70 @@
+function test_axon_gaps(filename)
+% TEST_AXON_GAPS - Test samples2times and times2samples with gaps
+%
+% TEST_AXON_GAPS(FILENAME)
+%
+% FILENAME should be an ABF file, preferably one with gaps (sweeps).
+%
+
+r = ndr.reader('abf');
+epoch_select = 1;
+
+% Read full time vector to establish ground truth
+disp('Reading full time vector...');
+t_all = r.readchannels_epochsamples('time', 1, {filename}, epoch_select, -inf, inf);
+t_all = t_all(:); % Ensure column
+s_all = (1:numel(t_all))';
+
+disp(['Total samples: ' num2str(numel(t_all))]);
+if ~isempty(t_all)
+ disp(['Time range: ' num2str(t_all(1)) ' to ' num2str(t_all(end))]);
+end
+
+if isempty(t_all)
+ error('No time data read!');
+end
+
+% Test samples2times with specific samples
+s_test = [1, floor(numel(t_all)/2), numel(t_all)];
+disp('Testing samples2times with integer samples...');
+t_out = r.samples2times('ai', 1, {filename}, epoch_select, s_test);
+
+for i=1:numel(s_test)
+ err = abs(t_out(i) - t_all(s_test(i)));
+ disp(['Sample ' num2str(s_test(i)) ': Time ' num2str(t_out(i)) ' (Error: ' num2str(err) ')']);
+ if err > 1e-5
+ warning('High error in samples2times!');
+ end
+end
+
+% Test interpolation in samples2times
+if numel(t_all) > 10
+ s_interp = 10.5;
+ t_interp = r.samples2times('ai', 1, {filename}, epoch_select, s_interp);
+ t_expected = interp1(s_all, t_all, s_interp, 'linear');
+ disp(['Interpolation test (s=' num2str(s_interp) '): ' num2str(t_interp) ' (Expected: ' num2str(t_expected) ')']);
+end
+
+% Test times2samples
+disp('Testing times2samples...');
+t_test = t_out; % Use times from previous test
+s_out = r.times2samples('ai', 1, {filename}, epoch_select, t_test);
+
+for i=1:numel(s_out)
+ err = abs(s_out(i) - s_test(i));
+ disp(['Time ' num2str(t_test(i)) ': Sample ' num2str(s_out(i)) ' (Error: ' num2str(err) ')']);
+ if err > 0.1
+ warning('High error in times2samples!');
+ end
+end
+
+% Test rounding in times2samples
+if numel(t_all) > 10
+ t_mid = (t_all(10) + t_all(11)) / 2;
+ s_mid = r.times2samples('ai', 1, {filename}, epoch_select, t_mid);
+ disp(['Rounding test (t=' num2str(t_mid) '): Sample ' num2str(s_mid)]);
+ % Should probably be 10 or 11
+end
+
+disp('Done.');
+end
From 2be83f266d254779330472c9ac02a1ebd6b06288 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 21 Nov 2025 14:06:03 +0000
Subject: [PATCH 2/9] 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 805a27b..c74a7b0 100644
--- a/.github/badges/code_issues.svg
+++ b/.github/badges/code_issues.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
From 5bf38394352bc327b4206ff3d481fdda364f881d Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Fri, 21 Nov 2025 14:18:38 +0000
Subject: [PATCH 3/9] Implement samples2times and times2samples for axon_abf
with unittest
---
.github/badges/code_issues.svg | 2 +-
.../+ndr/+unittest/+reader/MockAxonAbf.m | 39 +++++++++++
.../+ndr/+unittest/+reader/TestAxonAbf.m | 58 +++++++++++++++
tools/tests/test_axon_gaps.m | 70 -------------------
4 files changed, 98 insertions(+), 71 deletions(-)
create mode 100644 tools/tests/+ndr/+unittest/+reader/MockAxonAbf.m
create mode 100644 tools/tests/+ndr/+unittest/+reader/TestAxonAbf.m
delete mode 100644 tools/tests/test_axon_gaps.m
diff --git a/.github/badges/code_issues.svg b/.github/badges/code_issues.svg
index c74a7b0..805a27b 100644
--- a/.github/badges/code_issues.svg
+++ b/.github/badges/code_issues.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/tools/tests/+ndr/+unittest/+reader/MockAxonAbf.m b/tools/tests/+ndr/+unittest/+reader/MockAxonAbf.m
new file mode 100644
index 0000000..2625fe8
--- /dev/null
+++ b/tools/tests/+ndr/+unittest/+reader/MockAxonAbf.m
@@ -0,0 +1,39 @@
+classdef MockAxonAbf < ndr.reader.axon_abf
+ % MOCKAXONABF - Mock class for testing ndr.reader.axon_abf
+ %
+ % This class inherits from ndr.reader.axon_abf and overrides
+ % readchannels_epochsamples to return a predefined time vector.
+ % This allows testing samples2times and times2samples without a real file.
+
+ properties
+ TimeVector
+ end
+
+ methods
+ function obj = MockAxonAbf(t_vec)
+ % Constructor
+ % t_vec: Column vector of time points
+ obj.TimeVector = t_vec;
+ end
+
+ function data = readchannels_epochsamples(obj, channeltype, channel, epochstreams, epoch_select, s0, s1)
+ % Mock implementation returns TimeVector for 'time' channel
+
+ % Check channeltype
+ if iscell(channeltype)
+ ctype = channeltype{1};
+ else
+ ctype = channeltype;
+ end
+
+ if strcmp(ctype, 'time')
+ % Return the full time vector (ignoring s0, s1 as samples2times calls with -inf, inf)
+ % In a real scenario, we would slice, but for testing the gap logic,
+ % samples2times requests everything.
+ data = obj.TimeVector(:);
+ else
+ error('MockAxonAbf only supports reading time channel.');
+ end
+ end
+ end
+end
diff --git a/tools/tests/+ndr/+unittest/+reader/TestAxonAbf.m b/tools/tests/+ndr/+unittest/+reader/TestAxonAbf.m
new file mode 100644
index 0000000..bfea67f
--- /dev/null
+++ b/tools/tests/+ndr/+unittest/+reader/TestAxonAbf.m
@@ -0,0 +1,58 @@
+classdef TestAxonAbf < matlab.unittest.TestCase
+ % TESTAXONABF - Unit tests for ndr.reader.axon_abf
+ %
+ % Verifies the functionality of samples2times and times2samples
+ % using a mock reader to simulate gaps in recording.
+
+ methods (Test)
+ function testSamples2Times(testCase)
+ % Define a time vector with a gap (simulating concatenated sweeps)
+ % Sweep 1: 0, 0.1, 0.2
+ % Sweep 2: 0.4, 0.5 (Gap of 0.2 between 0.2 and 0.4, assuming dt=0.1)
+ t_vec = [0 0.1 0.2 0.4 0.5]';
+ reader = ndr.unittest.reader.MockAxonAbf(t_vec);
+
+ % Test exact samples
+ s = [1 2 3 4 5];
+ t = reader.samples2times('ai', 1, {}, 1, s);
+ testCase.verifyEqual(t, t_vec(s), 'AbsTol', 1e-9);
+
+ % Test interpolation within a sweep
+ s_interp = 1.5; % Between sample 1 (0.0) and 2 (0.1)
+ t_interp = reader.samples2times('ai', 1, {}, 1, s_interp);
+ expected = 0.05;
+ testCase.verifyEqual(t_interp, expected, 'AbsTol', 1e-9);
+
+ % Test interpolation across gap
+ % s=3 is t=0.2, s=4 is t=0.4
+ % s=3.5 should linearly interpolate to 0.3
+ t_gap = reader.samples2times('ai', 1, {}, 1, 3.5);
+ testCase.verifyEqual(t_gap, 0.3, 'AbsTol', 1e-9);
+ end
+
+ function testTimes2Samples(testCase)
+ t_vec = [0 0.1 0.2 0.4 0.5]';
+ reader = ndr.unittest.reader.MockAxonAbf(t_vec);
+
+ % Test exact times
+ t = [0 0.1 0.2 0.4 0.5];
+ s = reader.times2samples('ai', 1, {}, 1, t);
+ expected_s = [1 2 3 4 5];
+ testCase.verifyEqual(s, expected_s');
+
+ % Test interpolation & rounding
+ % t=0.05 -> s=1.5 -> round to 2
+ s_round = reader.times2samples('ai', 1, {}, 1, 0.05);
+ testCase.verifyEqual(s_round, 2);
+
+ % t=0.04 -> s=1.4 -> round to 1
+ s_round = reader.times2samples('ai', 1, {}, 1, 0.04);
+ testCase.verifyEqual(s_round, 1);
+
+ % Test across gap
+ % t=0.3 -> s=3.5 -> round to 4
+ s_gap = reader.times2samples('ai', 1, {}, 1, 0.3);
+ testCase.verifyEqual(s_gap, 4);
+ end
+ end
+end
diff --git a/tools/tests/test_axon_gaps.m b/tools/tests/test_axon_gaps.m
deleted file mode 100644
index ecdfc40..0000000
--- a/tools/tests/test_axon_gaps.m
+++ /dev/null
@@ -1,70 +0,0 @@
-function test_axon_gaps(filename)
-% TEST_AXON_GAPS - Test samples2times and times2samples with gaps
-%
-% TEST_AXON_GAPS(FILENAME)
-%
-% FILENAME should be an ABF file, preferably one with gaps (sweeps).
-%
-
-r = ndr.reader('abf');
-epoch_select = 1;
-
-% Read full time vector to establish ground truth
-disp('Reading full time vector...');
-t_all = r.readchannels_epochsamples('time', 1, {filename}, epoch_select, -inf, inf);
-t_all = t_all(:); % Ensure column
-s_all = (1:numel(t_all))';
-
-disp(['Total samples: ' num2str(numel(t_all))]);
-if ~isempty(t_all)
- disp(['Time range: ' num2str(t_all(1)) ' to ' num2str(t_all(end))]);
-end
-
-if isempty(t_all)
- error('No time data read!');
-end
-
-% Test samples2times with specific samples
-s_test = [1, floor(numel(t_all)/2), numel(t_all)];
-disp('Testing samples2times with integer samples...');
-t_out = r.samples2times('ai', 1, {filename}, epoch_select, s_test);
-
-for i=1:numel(s_test)
- err = abs(t_out(i) - t_all(s_test(i)));
- disp(['Sample ' num2str(s_test(i)) ': Time ' num2str(t_out(i)) ' (Error: ' num2str(err) ')']);
- if err > 1e-5
- warning('High error in samples2times!');
- end
-end
-
-% Test interpolation in samples2times
-if numel(t_all) > 10
- s_interp = 10.5;
- t_interp = r.samples2times('ai', 1, {filename}, epoch_select, s_interp);
- t_expected = interp1(s_all, t_all, s_interp, 'linear');
- disp(['Interpolation test (s=' num2str(s_interp) '): ' num2str(t_interp) ' (Expected: ' num2str(t_expected) ')']);
-end
-
-% Test times2samples
-disp('Testing times2samples...');
-t_test = t_out; % Use times from previous test
-s_out = r.times2samples('ai', 1, {filename}, epoch_select, t_test);
-
-for i=1:numel(s_out)
- err = abs(s_out(i) - s_test(i));
- disp(['Time ' num2str(t_test(i)) ': Sample ' num2str(s_out(i)) ' (Error: ' num2str(err) ')']);
- if err > 0.1
- warning('High error in times2samples!');
- end
-end
-
-% Test rounding in times2samples
-if numel(t_all) > 10
- t_mid = (t_all(10) + t_all(11)) / 2;
- s_mid = r.times2samples('ai', 1, {filename}, epoch_select, t_mid);
- disp(['Rounding test (t=' num2str(t_mid) '): Sample ' num2str(s_mid)]);
- % Should probably be 10 or 11
-end
-
-disp('Done.');
-end
From 02d7e24ce8911dca8bf9207a21bb9d8b70274b5d Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Fri, 21 Nov 2025 14:32:44 +0000
Subject: [PATCH 4/9] Implement samples2times and times2samples for axon_abf
with robust unittest
---
tools/tests/+ndr/+unittest/+reader/TestAxonAbf.m | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/tools/tests/+ndr/+unittest/+reader/TestAxonAbf.m b/tools/tests/+ndr/+unittest/+reader/TestAxonAbf.m
index bfea67f..63a4e00 100644
--- a/tools/tests/+ndr/+unittest/+reader/TestAxonAbf.m
+++ b/tools/tests/+ndr/+unittest/+reader/TestAxonAbf.m
@@ -15,7 +15,13 @@ function testSamples2Times(testCase)
% Test exact samples
s = [1 2 3 4 5];
t = reader.samples2times('ai', 1, {}, 1, s);
- testCase.verifyEqual(t, t_vec(s), 'AbsTol', 1e-9);
+
+ % Ensure expected has same shape as output/input (interp1 respects input shape)
+ expected_t = t_vec(s);
+ % Force shape match to be robust
+ expected_t = reshape(expected_t, size(s));
+
+ testCase.verifyEqual(t, expected_t, 'AbsTol', 1e-9);
% Test interpolation within a sweep
s_interp = 1.5; % Between sample 1 (0.0) and 2 (0.1)
@@ -38,7 +44,8 @@ function testTimes2Samples(testCase)
t = [0 0.1 0.2 0.4 0.5];
s = reader.times2samples('ai', 1, {}, 1, t);
expected_s = [1 2 3 4 5];
- testCase.verifyEqual(s, expected_s');
+ % Verify s matches expected_s (row vector)
+ testCase.verifyEqual(s, expected_s);
% Test interpolation & rounding
% t=0.05 -> s=1.5 -> round to 2
From 8e35777bf251b4e07d6240c132e2ea1c4f7f4da9 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 21 Nov 2025 14:35:13 +0000
Subject: [PATCH 5/9] Update GitHub badges
---
.github/badges/code_issues.svg | 2 +-
.github/badges/tests.svg | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/badges/code_issues.svg b/.github/badges/code_issues.svg
index 805a27b..c74a7b0 100644
--- a/.github/badges/code_issues.svg
+++ b/.github/badges/code_issues.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.github/badges/tests.svg b/.github/badges/tests.svg
index cf55c40..df1de8c 100644
--- a/.github/badges/tests.svg
+++ b/.github/badges/tests.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
From 9f9b18b91afbb8cfb04d78a0ed1cf23ec8c6e6a2 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Fri, 21 Nov 2025 14:43:37 +0000
Subject: [PATCH 6/9] Convert intan incomplete file test to unittest class
---
.github/badges/code_issues.svg | 2 +-
.github/badges/tests.svg | 2 +-
.../+format/+intan/TestIntanIncompleteFile.m | 120 ++++++++++++++++++
tools/tests/test_intan_incomplete_file.m | 116 -----------------
4 files changed, 122 insertions(+), 118 deletions(-)
create mode 100644 tools/tests/+ndr/+unittest/+format/+intan/TestIntanIncompleteFile.m
delete mode 100644 tools/tests/test_intan_incomplete_file.m
diff --git a/.github/badges/code_issues.svg b/.github/badges/code_issues.svg
index c74a7b0..805a27b 100644
--- a/.github/badges/code_issues.svg
+++ b/.github/badges/code_issues.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.github/badges/tests.svg b/.github/badges/tests.svg
index df1de8c..cf55c40 100644
--- a/.github/badges/tests.svg
+++ b/.github/badges/tests.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/tools/tests/+ndr/+unittest/+format/+intan/TestIntanIncompleteFile.m b/tools/tests/+ndr/+unittest/+format/+intan/TestIntanIncompleteFile.m
new file mode 100644
index 0000000..97d751a
--- /dev/null
+++ b/tools/tests/+ndr/+unittest/+format/+intan/TestIntanIncompleteFile.m
@@ -0,0 +1,120 @@
+classdef TestIntanIncompleteFile < matlab.unittest.TestCase
+
+ methods (Test)
+ function testReadIncompleteFile(testCase)
+ % testReadIncompleteFile - Test reading an Intan RHD file with incomplete data blocks
+ %
+ % This test creates a dummy .rhd file with a partial data block and checks if
+ % ndr.format.intan.read_Intan_RHD2000_datafile handles it gracefully (ignoring the partial block).
+
+ % Create a dummy header
+ sample_rate = 20000;
+ num_samples_per_data_block = 60;
+
+ header = struct();
+ header.fileinfo = struct(...
+ 'dirname', '.', ...
+ 'filename', 'test_incomplete_file',...
+ 'filesize', 0, ... % will be updated later
+ 'magic_number', hex2dec('c6912702'),...
+ 'data_file_main_version_number', 1,...
+ 'data_file_secondary_version_number', 2,...
+ 'eval_board_mode',0,...
+ 'reference_channel','',...
+ 'num_samples_per_data_block',num_samples_per_data_block,...
+ 'notes',struct('note1','','note2','','note3',''));
+ header.fileinfo.headersize = 100;
+
+ header.frequency_parameters = struct( ...
+ 'amplifier_sample_rate', sample_rate, ...
+ 'aux_input_sample_rate', sample_rate / 4, ...
+ 'supply_voltage_sample_rate', sample_rate / num_samples_per_data_block, ...
+ 'board_adc_sample_rate', sample_rate, ...
+ 'board_dig_in_sample_rate', sample_rate, ...
+ 'desired_dsp_cutoff_frequency', 0, ...
+ 'actual_dsp_cutoff_frequency', 0, ...
+ 'dsp_enabled', 0, ...
+ 'desired_lower_bandwidth', 0, ...
+ 'actual_lower_bandwidth', 0, ...
+ 'desired_upper_bandwidth', 0, ...
+ 'actual_upper_bandwidth', 0, ...
+ 'notch_filter_frequency', 0, ...
+ 'desired_impedance_test_frequency', 0, ...
+ 'actual_impedance_test_frequency', 0 );
+
+ % Define data structure for data channels.
+ channel_struct = struct( ...
+ 'native_channel_name', 'dummy', ...
+ 'custom_channel_name', 'dummy', ...
+ 'native_order', 0, ...
+ 'custom_order', 0, ...
+ 'board_stream', 0, ...
+ 'chip_channel', 0, ...
+ 'port_name', 'dummy', ...
+ 'port_prefix', 'dummy', ...
+ 'port_number', 0, ...
+ 'electrode_impedance_magnitude', 0, ...
+ 'electrode_impedance_phase', 0 );
+
+ empty_channel_struct = struct( ...
+ 'native_channel_name', {}, ...
+ 'custom_channel_name', {}, ...
+ 'native_order', {}, ...
+ 'custom_order', {}, ...
+ 'board_stream', {}, ...
+ 'chip_channel', {}, ...
+ 'port_name', {}, ...
+ 'port_prefix', {}, ...
+ 'port_number', {}, ...
+ 'electrode_impedance_magnitude', {}, ...
+ 'electrode_impedance_phase', {} );
+
+
+ header.amplifier_channels = channel_struct;
+ header.amplifier_channels.native_channel_name = 'A-000';
+ header.aux_input_channels = empty_channel_struct;
+ header.supply_voltage_channels = empty_channel_struct;
+ header.board_adc_channels = channel_struct;
+ header.board_adc_channels.native_channel_name = 'ANALOG-IN-00';
+ header.board_dig_in_channels = empty_channel_struct;
+ header.board_dig_out_channels = empty_channel_struct;
+ header.num_temp_sensor_channels = 0;
+
+
+ % Create a dummy file
+ filename = [tempname '.rhd'];
+
+ % Ensure cleanup on failure
+ cleanupObj = onCleanup(@() delete(filename));
+
+ fid = fopen(filename, 'w');
+ testCase.assertNotEqual(fid, -1, 'Could not open test file.');
+
+ fwrite(fid, zeros(1, header.fileinfo.headersize), 'uint8');
+
+ % Calculate bytes per block
+ % Intan_RHD2000_blockinfo requires filename, but mostly uses header logic or reads file if header incomplete?
+ % We'll assume it works as in the original script.
+ [~, bytes_per_block] = ndr.format.intan.Intan_RHD2000_blockinfo(filename, header);
+
+ % Write one and a half blocks of data
+ dummy_data = zeros(1, floor(bytes_per_block * 1.5), 'uint8');
+ fwrite(fid, dummy_data, 'uint8');
+
+ fclose(fid);
+
+ s = dir(filename);
+ header.fileinfo.filesize = s.bytes;
+
+ % 2. Call ndr.format.intan.read_Intan_RHD2000_datafile to read the file.
+ % We expect it to succeed without error.
+
+ [data,total_samples,total_time,blockinfo] = ndr.format.intan.read_Intan_RHD2000_datafile(filename, header, 'adc', 1, 0, inf);
+
+ % 3. Verify results
+ testCase.verifyEqual(total_samples, 60, 'total_samples should be 60 (1 complete block).');
+
+ % If total_samples is correct, we can assume it handled the incomplete block correctly.
+ end
+ end
+end
diff --git a/tools/tests/test_intan_incomplete_file.m b/tools/tests/test_intan_incomplete_file.m
deleted file mode 100644
index d6b9b96..0000000
--- a/tools/tests/test_intan_incomplete_file.m
+++ /dev/null
@@ -1,116 +0,0 @@
-% Test script for reading incomplete Intan RHD2000 files
-
-% 1. Create a dummy .rhd file with a partial data block.
-
-% Create a dummy header
-sample_rate = 20000;
-num_samples_per_data_block = 60;
-
-header = struct();
-header.fileinfo = struct(...
- 'dirname', '.', ...
- 'filename', 'test_incomplete_file',...
- 'filesize', 0, ... % will be updated later
- 'magic_number', hex2dec('c6912702'),...
- 'data_file_main_version_number', 1,...
- 'data_file_secondary_version_number', 2,...
- 'eval_board_mode',0,...
- 'reference_channel','',...
- 'num_samples_per_data_block',num_samples_per_data_block,...
- 'notes',struct('note1','','note2','','note3',''));
-header.fileinfo.headersize = 100;
-
-header.frequency_parameters = struct( ...
- 'amplifier_sample_rate', sample_rate, ...
- 'aux_input_sample_rate', sample_rate / 4, ...
- 'supply_voltage_sample_rate', sample_rate / num_samples_per_data_block, ...
- 'board_adc_sample_rate', sample_rate, ...
- 'board_dig_in_sample_rate', sample_rate, ...
- 'desired_dsp_cutoff_frequency', 0, ...
- 'actual_dsp_cutoff_frequency', 0, ...
- 'dsp_enabled', 0, ...
- 'desired_lower_bandwidth', 0, ...
- 'actual_lower_bandwidth', 0, ...
- 'desired_upper_bandwidth', 0, ...
- 'actual_upper_bandwidth', 0, ...
- 'notch_filter_frequency', 0, ...
- 'desired_impedance_test_frequency', 0, ...
- 'actual_impedance_test_frequency', 0 );
-
-% Define data structure for data channels.
-channel_struct = struct( ...
- 'native_channel_name', 'dummy', ...
- 'custom_channel_name', 'dummy', ...
- 'native_order', 0, ...
- 'custom_order', 0, ...
- 'board_stream', 0, ...
- 'chip_channel', 0, ...
- 'port_name', 'dummy', ...
- 'port_prefix', 'dummy', ...
- 'port_number', 0, ...
- 'electrode_impedance_magnitude', 0, ...
- 'electrode_impedance_phase', 0 );
-
-empty_channel_struct = struct( ...
- 'native_channel_name', {}, ...
- 'custom_channel_name', {}, ...
- 'native_order', {}, ...
- 'custom_order', {}, ...
- 'board_stream', {}, ...
- 'chip_channel', {}, ...
- 'port_name', {}, ...
- 'port_prefix', {}, ...
- 'port_number', {}, ...
- 'electrode_impedance_magnitude', {}, ...
- 'electrode_impedance_phase', {} );
-
-
-header.amplifier_channels = channel_struct;
-header.amplifier_channels.native_channel_name = 'A-000';
-header.aux_input_channels = empty_channel_struct;
-header.supply_voltage_channels = empty_channel_struct;
-header.board_adc_channels = channel_struct;
-header.board_adc_channels.native_channel_name = 'ANALOG-IN-00';
-header.board_dig_in_channels = empty_channel_struct;
-header.board_dig_out_channels = empty_channel_struct;
-header.num_temp_sensor_channels = 0;
-
-
-% Create a dummy file
-filename = 'test_incomplete_file.rhd';
-fid = fopen(filename, 'w');
-fwrite(fid, zeros(1, header.fileinfo.headersize), 'uint8');
-
-% Calculate bytes per block
-[~, bytes_per_block] = ndr.format.intan.Intan_RHD2000_blockinfo(filename, header);
-
-
-% Write one and a half blocks of data
-dummy_data = zeros(1, floor(bytes_per_block * 1.5), 'uint8');
-fwrite(fid, dummy_data, 'uint8');
-fclose(fid);
-
-s = dir(filename);
-header.fileinfo.filesize = s.bytes;
-
-% 2. Call ndr.format.intan.read_Intan_RHD2000_datafile to read the file.
-disp('Testing with incomplete file...');
-try
- [data,total_samples,total_time,blockinfo] = ndr.format.intan.read_Intan_RHD2000_datafile(filename, header, 'adc', 1, 0, inf);
-
- % 3. Verify that a warning is issued and that the function returns the correct number of complete data blocks without error.
- disp('Test passed: The function executed without error.');
-
- if total_samples == 60
- disp('Test passed: total_samples is correct.');
- else
- disp('Test failed: total_samples is incorrect.');
- end
-
-catch e
- disp('Test failed: An error occurred.');
- disp(e);
-end
-
-% clean up
-delete(filename);
From d34b9000e8526945f1b1cb48673f9444b174d32c Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 21 Nov 2025 14:46:10 +0000
Subject: [PATCH 7/9] Update GitHub badges
---
.github/badges/code_issues.svg | 2 +-
.github/badges/tests.svg | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/badges/code_issues.svg b/.github/badges/code_issues.svg
index 805a27b..c74a7b0 100644
--- a/.github/badges/code_issues.svg
+++ b/.github/badges/code_issues.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.github/badges/tests.svg b/.github/badges/tests.svg
index cf55c40..df1de8c 100644
--- a/.github/badges/tests.svg
+++ b/.github/badges/tests.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
From 3a9c3a4ca2e5aa452f4c683bc7e4cd39900ac471 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Fri, 21 Nov 2025 14:58:06 +0000
Subject: [PATCH 8/9] Enforce column output in axon_abf and fix unittest
vectors
---
+ndr/+reader/axon_abf.m | 8 +++++--
.github/badges/code_issues.svg | 2 +-
.github/badges/tests.svg | 2 +-
.../+ndr/+unittest/+reader/TestAxonAbf.m | 22 +++++++++----------
4 files changed, 18 insertions(+), 16 deletions(-)
diff --git a/+ndr/+reader/axon_abf.m b/+ndr/+reader/axon_abf.m
index 97c7a2d..46de852 100755
--- a/+ndr/+reader/axon_abf.m
+++ b/+ndr/+reader/axon_abf.m
@@ -173,6 +173,10 @@
% in abfread, the reader reads up to s1 -1 instead of s1
data = ndr.format.axon.read_abf(filename,header,channeltype{1},channel,T(1),T(2));
+ if numel(channel) == 1
+ data = data(:);
+ end
+
end % ndr.reader.axon_abf.readchannels_epochsamples
function sr = samplerate(axon_abf_obj, epochstreams, epoch_select, channeltype, channel)
@@ -278,7 +282,7 @@
%
% This function overrides the base class to handle gaps by reading the time channel.
%
- t_all = axon_abf_obj.readchannels_epochsamples('time', 1, epochstreams, epoch_select, -inf, inf);
+ t_all = axon_abf_obj.readchannels_epochsamples({'time'}, 1, epochstreams, epoch_select, -inf, inf);
t_all = t_all(:);
s_all = (1:numel(t_all))';
@@ -294,7 +298,7 @@
%
% This function overrides the base class to handle gaps by reading the time channel.
%
- t_all = axon_abf_obj.readchannels_epochsamples('time', 1, epochstreams, epoch_select, -inf, inf);
+ t_all = axon_abf_obj.readchannels_epochsamples({'time'}, 1, epochstreams, epoch_select, -inf, inf);
t_all = t_all(:);
s_all = (1:numel(t_all))';
diff --git a/.github/badges/code_issues.svg b/.github/badges/code_issues.svg
index c74a7b0..805a27b 100644
--- a/.github/badges/code_issues.svg
+++ b/.github/badges/code_issues.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.github/badges/tests.svg b/.github/badges/tests.svg
index df1de8c..cf55c40 100644
--- a/.github/badges/tests.svg
+++ b/.github/badges/tests.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/tools/tests/+ndr/+unittest/+reader/TestAxonAbf.m b/tools/tests/+ndr/+unittest/+reader/TestAxonAbf.m
index 63a4e00..323fb45 100644
--- a/tools/tests/+ndr/+unittest/+reader/TestAxonAbf.m
+++ b/tools/tests/+ndr/+unittest/+reader/TestAxonAbf.m
@@ -9,22 +9,20 @@ function testSamples2Times(testCase)
% Define a time vector with a gap (simulating concatenated sweeps)
% Sweep 1: 0, 0.1, 0.2
% Sweep 2: 0.4, 0.5 (Gap of 0.2 between 0.2 and 0.4, assuming dt=0.1)
- t_vec = [0 0.1 0.2 0.4 0.5]';
+ t_vec = [0; 0.1; 0.2; 0.4; 0.5];
reader = ndr.unittest.reader.MockAxonAbf(t_vec);
- % Test exact samples
- s = [1 2 3 4 5];
+ % Test exact samples - Use Column Vector for s
+ s = [1; 2; 3; 4; 5];
t = reader.samples2times('ai', 1, {}, 1, s);
- % Ensure expected has same shape as output/input (interp1 respects input shape)
+ % Expect column output
expected_t = t_vec(s);
- % Force shape match to be robust
- expected_t = reshape(expected_t, size(s));
testCase.verifyEqual(t, expected_t, 'AbsTol', 1e-9);
% Test interpolation within a sweep
- s_interp = 1.5; % Between sample 1 (0.0) and 2 (0.1)
+ s_interp = 1.5; % Scalar
t_interp = reader.samples2times('ai', 1, {}, 1, s_interp);
expected = 0.05;
testCase.verifyEqual(t_interp, expected, 'AbsTol', 1e-9);
@@ -37,14 +35,14 @@ function testSamples2Times(testCase)
end
function testTimes2Samples(testCase)
- t_vec = [0 0.1 0.2 0.4 0.5]';
+ t_vec = [0; 0.1; 0.2; 0.4; 0.5];
reader = ndr.unittest.reader.MockAxonAbf(t_vec);
- % Test exact times
- t = [0 0.1 0.2 0.4 0.5];
+ % Test exact times - Use Column Vector
+ t = [0; 0.1; 0.2; 0.4; 0.5];
s = reader.times2samples('ai', 1, {}, 1, t);
- expected_s = [1 2 3 4 5];
- % Verify s matches expected_s (row vector)
+ expected_s = [1; 2; 3; 4; 5];
+ % Verify s matches expected_s (column vector)
testCase.verifyEqual(s, expected_s);
% Test interpolation & rounding
From 42a35ae3a347c43a96ccc4e15b13341c325e8255 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 21 Nov 2025 15:01:05 +0000
Subject: [PATCH 9/9] Update GitHub badges
---
.github/badges/code_issues.svg | 2 +-
.github/badges/tests.svg | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/badges/code_issues.svg b/.github/badges/code_issues.svg
index 805a27b..40a2245 100644
--- a/.github/badges/code_issues.svg
+++ b/.github/badges/code_issues.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/.github/badges/tests.svg b/.github/badges/tests.svg
index cf55c40..df1de8c 100644
--- a/.github/badges/tests.svg
+++ b/.github/badges/tests.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file