From 2a0a0543f3fdff4b70b70a5def2005a4208cf7af Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 20:20:34 +0000 Subject: [PATCH 1/3] Fix bug in ndr.reader.base.samples2times passing scalar instead of interval ndr.time.fun.samples2times and times2samples require a 2-element vector [t0 t1] as the second argument to correctly handle cases where the input contains Inf (which maps to t1). The previous implementation in ndr.reader.base was incorrectly passing only the start time (t0) as a scalar. This commit fixes ndr.reader.base.samples2times and ndr.reader.base.times2samples to pass the full t0t1{1} vector. --- +ndr/+reader/base.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/+ndr/+reader/base.m b/+ndr/+reader/base.m index d4875ec..1a00411 100644 --- a/+ndr/+reader/base.m +++ b/+ndr/+reader/base.m @@ -258,7 +258,7 @@ error(['Do not know how to handle different sampling rates across channels.']); end; t0t1 = ndr_reader_base_obj.t0_t1(epochstreams, epoch_select); - t = ndr.time.fun.samples2times(s, t0t1{1}(1), sr_unique); + t = ndr.time.fun.samples2times(s, t0t1{1}, sr_unique); end; % samples2times() function s = times2samples(ndr_reader_base_obj, channeltype, channel, epochstreams, epoch_select, t) @@ -279,7 +279,7 @@ error(['Do not know how to handle different sampling rates across channels.']); end; t0t1 = ndr_reader_base_obj.t0_t1(epochstreams, epoch_select); - s = ndr.time.fun.times2samples(t, t0t1{1}(1), sr_unique); + s = ndr.time.fun.times2samples(t, t0t1{1}, sr_unique); end; % times2samples() end; % methods From 0ccf709acf468d326a24d45810080b3b63d6c1ac Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 20:38:12 +0000 Subject: [PATCH 2/3] Fix scalar t0 argument in samples2times and add Intan RHD unit test - Modify ndr.reader.base.samples2times and times2samples to pass the full t0t1 interval vector (start and end times) instead of just the scalar start time. This is required for ndr.time.fun functions to correctly handle Inf values (end of recording). - Add new unit test tools/tests/+ndr/+unittest/+reader/TestIntanRhd.m. This test uses the existing example.rhd data to verify: 1. Correct functionality of samples2times and times2samples with scalar, vector, and Inf inputs. 2. Basic functionality of readchannels_epochsamples. 3. Proper initialization of the ndr.reader.intan_rhd reader. --- .../+ndr/+unittest/+reader/TestIntanRhd.m | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tools/tests/+ndr/+unittest/+reader/TestIntanRhd.m diff --git a/tools/tests/+ndr/+unittest/+reader/TestIntanRhd.m b/tools/tests/+ndr/+unittest/+reader/TestIntanRhd.m new file mode 100644 index 0000000..08dc06c --- /dev/null +++ b/tools/tests/+ndr/+unittest/+reader/TestIntanRhd.m @@ -0,0 +1,86 @@ +classdef TestIntanRhd < matlab.unittest.TestCase + % TESTINTANRHD - Unit tests for ndr.reader.intan_rhd + + methods (Test) + function testSamples2Times(testCase) + % 1. Setup + reader = ndr.reader.intan_rhd(); + ndr_path = ndr.fun.ndrpath(); + rhd_file = fullfile(ndr_path, 'example_data', 'example.rhd'); + + epochstreams = {rhd_file}; + epoch_select = 1; + channeltype = 'ai'; + channel = 1; + + % 2. Get expected T0 and T1 from the reader itself + % This ensures we are testing the internal consistency of the reader + % and specifically the fix in ndr.reader.base.samples2times/times2samples + t0t1 = reader.t0_t1(epochstreams, epoch_select); + t0 = t0t1{1}(1); + t1 = t0t1{1}(2); + sr = reader.samplerate(epochstreams, epoch_select, channeltype, channel); + + % 3. Test samples2times + % Test regular sample + s = [1, 100, 1000]; + t = reader.samples2times(channeltype, channel, epochstreams, epoch_select, s); + + expected_t = (s - 1) / sr + t0; + testCase.verifyEqual(t, expected_t, 'AbsTol', 1e-9); + + % Test Inf (should be t1) + % This triggers the code path: t(g) = t0_t1(2); + % If the fix is not applied (passing scalar instead of vector), this would fail or error. + s_inf = Inf; + t_inf = reader.samples2times(channeltype, channel, epochstreams, epoch_select, s_inf); + testCase.verifyEqual(t_inf, t1, 'AbsTol', 1e-9); + + % 4. Test times2samples + % Test regular time + t_in = [t0, t0 + 0.1]; + s_out = reader.times2samples(channeltype, channel, epochstreams, epoch_select, t_in); + + expected_s = 1 + round((t_in - t0) * sr); + testCase.verifyEqual(s_out, expected_s, 'AbsTol', 1e-9); + + % Test Inf time + % In times2samples.m: s(g) = 1+sr*diff(t0_t1); + t_inf_in = Inf; + s_inf_out = reader.times2samples(channeltype, channel, epochstreams, epoch_select, t_inf_in); + + expected_s_inf = 1 + sr * (t1 - t0); + testCase.verifyEqual(s_inf_out, expected_s_inf, 'AbsTol', 1e-9); + + end + + function testReadChannels(testCase) + % Verify that reading channels works and matches ndr.format.intan output logic + reader = ndr.reader.intan_rhd(); + ndr_path = ndr.fun.ndrpath(); + rhd_file = fullfile(ndr_path, 'example_data', 'example.rhd'); + + epochstreams = {rhd_file}; + epoch_select = 1; + channeltype = 'ai'; + channel = 1; + + % Read a small chunk + s0 = 1; + s1 = 100; + + data = reader.readchannels_epochsamples(channeltype, channel, epochstreams, epoch_select, s0, s1); + + % Verify dimensions + testCase.verifyEqual(size(data, 1), s1 - s0 + 1); + testCase.verifyEqual(size(data, 2), 1); + + % We can also compare against direct format read if needed, but the above confirms basic reader function + % and the primary goal is covering samples2times fix. + + % Verify getting header + header = ndr.format.intan.read_Intan_RHD2000_header(rhd_file); + testCase.verifyNotEmpty(header); + end + end +end From b880b8c367cae7d081ab05085950b6d9d4dd4250 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 22 Nov 2025 20:40:41 +0000 Subject: [PATCH 3/3] Update GitHub badges --- .github/badges/tests.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/badges/tests.svg b/.github/badges/tests.svg index df1de8c..9285519 100644 --- a/.github/badges/tests.svg +++ b/.github/badges/tests.svg @@ -1 +1 @@ -teststests36 passed36 passed \ No newline at end of file +teststests38 passed38 passed \ No newline at end of file