Skip to content

Add M1-A dataset from FALCON (Karpowicz et al. 2024) - #81

Open
felipe-parodi wants to merge 2 commits into
mainfrom
feat/falcon-m1-pipeline
Open

Add M1-A dataset from FALCON (Karpowicz et al. 2024)#81
felipe-parodi wants to merge 2 commits into
mainfrom
feat/falcon-m1-pipeline

Conversation

@felipe-parodi

@felipe-parodi felipe-parodi commented Feb 16, 2026

Copy link
Copy Markdown

Add pipeline for FALCON M1 dataset (Karpowicz et al. 2024).

  • Monkey L reach-to-grasp task, 64-channel Utah array (motor cortex) +
    16-channel EMG.
  • Held-in / held-out / minival split per FALCON benchmark convention.
  • Optional spike binning via --bin-size-ms (default: raw spike
    times).
  • DANDI: 000941

// continued from Nov '25 brainathon

Summary by CodeRabbit

  • New Features
    • FALCON M1 2024 dataset pipeline for processing DANDI NWB files
    • Extracts EMG and neural spike data with optional configurable binning
    • Generates HDF5 output files with comprehensive session and device metadata
    • Automatically handles held-in, held-out, and minival data splits
    • Supports train, validation, and test domain assignment

FALCON M1 dataset (Karpowicz et al. 2024): Monkey L reach-to-grasp task.
64-channel Utah array in motor cortex with 16-channel EMG recordings.
Held-in/held-out/minival split per FALCON benchmark.

DANDI:000941
Copilot AI review requested due to automatic review settings February 16, 2026 00:57
@coderabbitai

coderabbitai Bot commented Feb 16, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@felipe-parodi has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 19 minutes and 1 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Introduces a new FALCON M1 2024 data pipeline that downloads and processes DANDI NWB files, extracting neural spike and EMG data with optional spike binning, metadata enrichment, and HDF5 serialization according to dataset-specific split rules.

Changes

Cohort / File(s) Summary
FALCON M1 2024 Pipeline
brainsets_pipelines/falcon_m1_2024/pipeline.py
Adds complete end-to-end pipeline: Pipeline class with manifest generation from NWB asset lists (extracting session dates and split types), download functionality, and processing flow that extracts EMG and spike data, applies optional temporal binning, constructs metadata (brainset, subject, session, device descriptions), assigns train/valid/test splits, and serializes to HDF5. Includes seven helper functions for split classification, EMG extraction, trial/evaluation mask conversion, spike binning with reference alignment, and split assignment.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A falcon pipeline takes flight so bright,
With NWB files processed just right,
Spikes are binned and EMG flows free,
Splits aligned for all to see! 🧠✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding the FALCON M1-A dataset pipeline. It matches the primary objective of the PR and accurately reflects the file additions summarized in the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/falcon-m1-pipeline

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@brainsets_pipelines/falcon_m1_2024/pipeline.py`:
- Around line 384-390: The end timestamps pushed into eval_mask_ends are
inconsistent: mid-session you append the first non-eval sample timestamp
(`eval_mask_ends.append(timestamp)`), but at session end you append
`emg.timestamps[-1]`; decide whether Interval expects end-exclusive
(one-past-last) or end-inclusive (last-sample) and make both branches match that
convention. If Interval is end-exclusive, change the session-end branch to
append a one-past-last timestamp (e.g., compute sample interval from
`emg.timestamps` and append `emg.timestamps[-1] + dt`); if Interval is
end-inclusive, change the mid-session branch to append the last-eval sample
timestamp (e.g., `previous_timestamp` or `emg.timestamps[idx-1]`). Update the
code paths that build `eval_mask_ends`, referencing the variables
`in_eval_period`, `eval_mask_ends`, `timestamp`, and `emg.timestamps` (and
`Interval` semantics) so both transitions use the same end-of-interval
convention.
- Around line 373-399: The eval_mask/EMG timestamps loop can silently truncate
when lengths differ; update the logic that builds eval_intervals (the loop over
eval_mask and emg.timestamps) to first validate len(eval_mask) ==
len(emg.timestamps) and raise a clear error if not, or use zip(..., strict=True)
to force a mismatch exception (requires Python 3.10+); also remove the unused
enumerate and i variable so the loop simply iterates over paired values. Ensure
the error message references eval_mask and emg.timestamps so callers can
diagnose the length mismatch before constructing the Interval objects.
- Around line 189-247: The NWBHDF5IO file handle is not protected against
exceptions causing a resource leak; wrap the IO usage in a context manager by
replacing the manual open/close with a with NWBHDF5IO(fpath, "r") as io: block,
move the nwbfile = io.read() and all subsequent code that accesses nwbfile
(calls to extract_emg_data, extract_spikes_from_nwbfile, bin_spikes,
extract_eval_mask, extract_trials, and any use of spikes/units) inside that with
block, and remove the explicit io.close() call so the file is always closed even
on errors.
- Around line 421-448: The current bin_edges are built from reference_timestamps
which yields non-uniform widths; instead generate uniform edges using bin_size_s
so each bin has width bin_size_s. Replace the construction of bin_edges
(currently using reference_timestamps / bin_end_timestamps) with a uniform
sequence starting at (reference_timestamps[0] - bin_size_s) and stepping by
bin_size_s for n_bins+1 edges (so bin_edges = start + np.arange(n_bins+1) *
bin_size_s). Keep the histogram loop over units (spike_times / spike_units) but
use the new uniform bin_edges, and compute bin_timestamps as the midpoints of
those uniform edges so the returned binned_counts and bin_timestamps align with
RegularTimeSeries(sampling_rate=1/bin_size_s).
🧹 Nitpick comments (1)
brainsets_pipelines/falcon_m1_2024/pipeline.py (1)

309-316: emg_timestamps is silently overwritten each iteration — assumes all muscles share identical timestamps.

If any muscle channel has different timestamps, the mismatch would go undetected and the resulting IrregularTimeSeries would pair incorrect timestamps with stacked data. Consider adding a validation or extracting timestamps only once:

Suggested defensive check
     emg_data = []
     emg_timestamps = None
     for muscle in muscles:
         ts_data = emg_container.get_timeseries(muscle)
         emg_data.append(ts_data.data[:])
-        emg_timestamps = ts_data.timestamps[:]
+        if emg_timestamps is None:
+            emg_timestamps = ts_data.timestamps[:]
+        else:
+            assert np.array_equal(emg_timestamps, ts_data.timestamps[:]), (
+                f"Timestamp mismatch for muscle {muscle}"
+            )

Comment thread brainsets_pipelines/falcon_m1_2024/pipeline.py Outdated
Comment thread brainsets_pipelines/falcon_m1_2024/pipeline.py
Comment thread brainsets_pipelines/falcon_m1_2024/pipeline.py Outdated
Comment thread brainsets_pipelines/falcon_m1_2024/pipeline.py

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a new pipeline for processing the FALCON M1 dataset (Karpowicz et al. 2024) from DANDI archive 000941. The dataset contains neural recordings from a 64-channel Utah array in motor cortex of a macaque monkey performing a reach-to-grasp task, along with 16-channel EMG recordings.

Changes:

  • New pipeline implementation for FALCON M1 dataset with support for held-in/held-out/minival split convention
  • Optional spike binning feature via --bin-size-ms argument (defaults to raw spike times)
  • EMG extraction and evaluation mask processing for FALCON benchmark compatibility

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

for muscle in muscles:
ts_data = emg_container.get_timeseries(muscle)
emg_data.append(ts_data.data[:])
emg_timestamps = ts_data.timestamps[:]

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

In the extract_emg_data function, emg_timestamps is overwritten in each loop iteration. This assumes all muscle time series have identical timestamps. If they differ, only the last muscle's timestamps will be used, which could cause a mismatch with the stacked data. Consider verifying that all timestamps are identical or using the first muscle's timestamps explicitly.

Suggested change
emg_timestamps = ts_data.timestamps[:]
ts_timestamps = ts_data.timestamps[:]
if emg_timestamps is None:
emg_timestamps = ts_timestamps
else:
if not np.array_equal(ts_timestamps, emg_timestamps):
raise ValueError(
"EMG timestamps are not identical across muscles; "
"cannot construct a consistent IrregularTimeSeries."
)

Copilot uses AI. Check for mistakes.
eval_mask_ends = []
in_eval_period = False

for i, (is_eval, timestamp) in enumerate(zip(eval_mask, emg.timestamps)):

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

The enumerate index variable 'i' is not used. Consider using 'for is_eval, timestamp in zip(eval_mask, emg.timestamps):' instead for cleaner code.

Suggested change
for i, (is_eval, timestamp) in enumerate(zip(eval_mask, emg.timestamps)):
for is_eval, timestamp in zip(eval_mask, emg.timestamps):

Copilot uses AI. Check for mistakes.
Comment on lines +445 to +446
# Bin center timestamps
bin_timestamps = bin_end_timestamps - (bin_size_s / 2.0)

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

The bin_spikes function assumes that reference_timestamps are evenly spaced when calculating bin centers (line 446). However, emg.timestamps is from an IrregularTimeSeries (line 228), which may have irregularly spaced timestamps. This could lead to inaccurate bin center calculations. Consider either: 1) verifying that EMG timestamps are regularly spaced, or 2) calculating bin centers directly from bin_edges as (bin_edges[:-1] + bin_edges[1:]) / 2.

Suggested change
# Bin center timestamps
bin_timestamps = bin_end_timestamps - (bin_size_s / 2.0)
# Bin center timestamps computed from bin edges to avoid assuming regular spacing
bin_timestamps = (bin_edges[:-1] + bin_edges[1:]) / 2.0

Copilot uses AI. Check for mistakes.
Comment on lines +193 to +197
subject = SubjectDescription(
id="monkey_l",
species=Species.MACACA_MULATTA,
sex=Sex.UNKNOWN,
)

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

Subject metadata is hardcoded instead of being extracted from the NWB file. Consider using extract_subject_from_nwb (from brainsets.utils.dandi_utils) which is available and used in other DANDI-based pipelines (e.g., pei_pandarinath_nlb_2021). This would make the code more maintainable and consistent with other pipelines.

Copilot uses AI. Check for mistakes.
eval_mask_ends = []
in_eval_period = False

for i, (is_eval, timestamp) in enumerate(zip(eval_mask, emg.timestamps)):

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

The function zips eval_mask and emg.timestamps without verifying they have the same length. If these arrays have different lengths, the shorter one will silently truncate the iteration, potentially missing evaluation periods. Consider adding a length check or assertion to ensure data integrity.

Copilot uses AI. Check for mistakes.
Comment on lines +478 to +482
else:
# Unknown split, default to train
data.set_train_domain(valid_trials)
data.set_valid_domain(empty_interval)
data.set_test_domain(empty_interval)

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

When split_type is "unknown" (line 294), the assign_falcon_split function silently assigns it to the training set without any warning or logging. This could mask configuration issues or unexpected filenames. Consider adding a warning log when encountering unknown split types to help with debugging.

Copilot uses AI. Check for mistakes.

Reference:
Karpowicz et al. (2024). FALCON: Few-shot Adaptive Learning of
neural COdecodersN. https://dandiarchive.org/dandiset/000941

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

The reference has a typo in the acronym expansion. It should be "COdecoders" not "COdecodersN".

Suggested change
neural COdecodersN. https://dandiarchive.org/dandiset/000941
neural COdecoders. https://dandiarchive.org/dandiset/000941

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,482 @@
# /// brainset-pipeline
# python-version = "3.11"
# dependencies = ["dandi>=0.71.3"]

Copilot AI Feb 16, 2026

Copy link

Choose a reason for hiding this comment

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

The dependency specification uses a range operator (>=) which is inconsistent with other pipelines in the codebase that use exact version pinning (==). For reproducibility, consider changing "dandi>=0.71.3" to "dandi==0.71.3".

Suggested change
# dependencies = ["dandi>=0.71.3"]
# dependencies = ["dandi==0.71.3"]

Copilot uses AI. Check for mistakes.
- Use NWBHDF5IO context manager for safe resource cleanup
- Validate eval_mask / EMG timestamp length match
- Make eval interval end timestamps consistently end-exclusive
- Generate uniform bin edges in bin_spikes
- Validate EMG muscle channels share identical timestamps
@codecov

codecov Bot commented Feb 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@milosobral milosobral added the new brainset Adding a new dataset to the supported brainsets list label Apr 20, 2026
@AlexandreAndr
AlexandreAndr self-requested a review May 18, 2026 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new brainset Adding a new dataset to the supported brainsets list

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants