Skip to content

Fix events.tsv onset off by one in bids_preproc - #315

Open
arnodelorme wants to merge 1 commit into
developfrom
fix/bids-preproc-event-onset
Open

Fix events.tsv onset off by one in bids_preproc#315
arnodelorme wants to merge 1 commit into
developfrom
fix/bids-preproc-event-onset

Conversation

@arnodelorme

Copy link
Copy Markdown
Collaborator

🤖

Problem

bids_preproc rewrote the derivative events.tsv with ev_time = EEG['times'][e['latency']] / 1000.0. Event latencies are 1-based samples (latency 1.0 = first sample, EEGLAB convention) while EEG['times'] is a 0-based array, so:

  • Every onset was one sample late: latency 1 mapped to times[1] = 7.8125 ms at 128 Hz instead of 0.
  • An event at the last sample (latency == pnts) raised IndexError and was dropped.
  • Any fractional latency (e.g. 129.00875 in sample_data/eeglab_data.set, or anything after resampling) raised IndexError and was dropped. On eeglab_data.set the old code kept 0 of 154 events.

The dropped events also masked a second failure: pop_loadset yields duration=None for empty MATLAB fields, so e.get('duration', 0.0) / srate would have raised TypeError once an event got past the onset lookup.

Fix

  • Onset is now (latency - 1) / srate, the same conversion already used by plugins/EEG_BIDS/pop_exportbids.py. No times[...] lookup, so no IndexError and no dropped events. Duration stays duration / srate (EEGLAB stores duration in samples); None is treated as 0.
  • The writer is extracted into a top-level _write_events_tsv(EEG, fpath) so it can be tested on real data without running the pipeline. grep -rn "times\[" src/eegprep/plugins/EEG_BIDS/ shows this was the only such lookup under EEG_BIDS.

Epoched data

bids_preproc writes events.tsv after the optional pop_epoch stage, so epoched datasets are exported too. pop_epoch produces 1-based latencies concatenated across epochs (... + 1 + pnts * epoch_index), so (latency - 1) / srate gives the onset relative to the start of the concatenated epoched array, consistent with pop_exportbids. No behaviour beyond that is introduced; the helper docstring states this.

Verification

EEGPREP_SKIP_MATLAB=1 python -m pytest tests/test_bids_preproc.py tests/test_bids_preproc_old.py \
    tests/test_bids_gen_derived_fpath.py tests/test_bids_load_frombids_helpers.py -q
# 8 passed, 2 skipped (MATLAB end-to-end), 3 subtests passed
ruff check / ruff format --check on the two changed files: clean

New TestWriteEventsTsv (2 tests, real sample_data/eeglab_data.set, no torch/MATLAB): all 154 events written with onset (latency-1)/srate (fractional latencies included, duration 0, type preserved), and latency 1 / latency pnts map to 0.0 s and (pnts-1)/srate.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Code review skipped: your organization's credit balance is too low for Code Review to run.

An organization admin can purchase credits or upgrade the plan in Plans & Billing.

Once credits are available, reopen this pull request to trigger a review.

@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown

Claude encountered an error after 0s —— View job


I'll analyze this and get back to you.

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