Skip to content

Fix the TimeSeriesReference that add_interval computes for a rate-based TimeSeries - #2275

Open
aaronkanzer wants to merge 1 commit into
NeurodataWithoutBorders:devfrom
aaronkanzer:fix/add-interval-rate-index
Open

aaronkanzer wants to merge 1 commit into
NeurodataWithoutBorders:devfrom
aaronkanzer:fix/add-interval-rate-index

Conversation

@aaronkanzer

Copy link
Copy Markdown

TimeIntervals.add_interval (and so NWBFile.add_epoch) gives a different TimeSeriesReference for a TimeSeries with starting_time and rate than for the same samples stored as timestamps. For the rate case the reference can start one sample before start_time. If the interval starts before the series, it is stored as a missing reference. If the interval runs past the end, the reference raises IndexError when used.

import numpy as np
from pynwb import TimeSeries
from pynwb.epoch import TimeIntervals

ts_rate = TimeSeries(name="r", data=np.arange(100), unit="u", starting_time=1.0, rate=100.0)
ts_tst = TimeSeries(name="t", data=np.arange(100), unit="u", timestamps=ts_rate.get_timestamps())
for start, stop in [(1.255, 1.505), (0.5, 1.1), (1.9, 3.0)]:
    ti = TimeIntervals(name="x")
    ti.add_interval(start_time=start, stop_time=stop, timeseries=[ts_rate, ts_tst])
    print((start, stop), [(r.idx_start, r.count) for r in ti["timeseries"][0]])

Observed (pynwb 4.2.0 and dev), rate vs timestamps:

(1.255, 1.505) [(25, 25), (26, 25)]    # rate reference starts at t=1.25, before start_time
(0.5, 1.1)     [(None, None), (0, 10)] # negative idx_start, read back as a missing reference
(1.9, 3.0)     [(89, 111), (90, 10)]   # 0.9*100 = 89.999..., and count runs past the 100 samples

For the last case, ref.isvalid() raises IndexError: 'idx_start + count' out of range for timeseries 'r'.

Expected: both series give (26, 25), (0, 10) and (90, 10).

Cause: the rate branch of __calculate_idx_count uses int((t - starting_time) * rate). That truncates instead of finding the first sample at or after t, is sensitive to floating point error, and is never clipped to [0, num_samples]. The timestamps branch uses bisect_left, which has none of these problems.

Fix: compute the rate-based index the way bisect_left would on the sample times starting_time + i / rate (ceil with a one-step correction for rounding), clipped to the number of samples. Adds tests that compare the rate and timestamps results, including intervals that extend past either end.

@aaronkanzer
aaronkanzer force-pushed the fix/add-interval-rate-index branch from 281ad9e to fcabe50 Compare October 24, 2026 12:36

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant