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 @@ -code issuescode issues12701270 \ No newline at end of file +code issuescode issues12741274 \ 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 @@ -code issuescode issues12741274 \ No newline at end of file +code issuescode issues12701270 \ 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 @@ -code issuescode issues12701270 \ No newline at end of file +code issuescode issues12741274 \ 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 @@ -teststests34 passed34 passed \ No newline at end of file +teststests36 passed36 passed \ 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 @@ -code issuescode issues12741274 \ No newline at end of file +code issuescode issues12701270 \ 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 @@ -teststests36 passed36 passed \ No newline at end of file +teststests34 passed34 passed \ 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 @@ -code issuescode issues12701270 \ No newline at end of file +code issuescode issues12741274 \ 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 @@ -teststests34 passed34 passed \ No newline at end of file +teststests36 passed36 passed \ 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 @@ -code issuescode issues12741274 \ No newline at end of file +code issuescode issues12701270 \ 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 @@ -teststests36 passed36 passed \ No newline at end of file +teststests34 passed34 passed \ 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 @@ -code issuescode issues12701270 \ No newline at end of file +code issuescode issues12751275 \ 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 @@ -teststests34 passed34 passed \ No newline at end of file +teststests36 passed36 passed \ No newline at end of file