Contributing to EEGPrep#
We welcome contributions from the community! This guide will help you get started with contributing to EEGPrep.
Reporting Bugs#
Found a bug? Please report it on GitHub Issues.
Include:
EEGPrep version (
python -c "import eegprep; print(eegprep.__version__)")Python version and operating system
Minimal code that reproduces the problem
The full error message and traceback
Getting Started#
Fork and Clone#
Fork the repository on GitHub by clicking the “Fork” button
Clone your fork locally:
git clone https://github.com/YOUR_USERNAME/eegprep.git
cd eegprep
Add the upstream repository:
git remote add upstream https://github.com/sccn/eegprep.git
Create a new branch for your feature or bugfix:
git checkout -b feature/your-feature-name
Development Environment#
Project Task Tracking#
We use Beads (bd) to track project tasks, dependencies, and handoffs. It is a
development tool, not an EEGPrep runtime dependency. The local Dolt database runs
in embedded mode without a server. Public bug reports still belong in GitHub
Issues.
In a fresh clone, install Beads and run bd bootstrap from the repository root
to recover the shared task database, then bd ready to find available work. Follow the
repository setup guide
for installation, coding-agent hooks, and explicit task synchronization. A code
commit or git push does not synchronize the task database.
Virtual Environment Setup#
Create the uv-managed development environment:
uv python install 3.12
uv sync --group dev
Install Dependencies#
If you only need documentation dependencies, sync the docs extra:
uv sync --extra docs --group dev
uv sync installs:
The eegprep package in editable mode
Repo tooling dependencies
GUI and
eegprep-consoleruntime dependenciesDocumentation dependencies when
--extra docsis used
Code Style Guidelines#
PEP 8 Compliance#
We follow PEP 8 style guidelines. Key points:
Use 4 spaces for indentation (never tabs)
Maximum line length: 100 characters
Use descriptive variable and function names
Add spaces around operators:
x = 1, notx=1
Naming Conventions#
Functions and variables: Use lowercase with underscores (
snake_case)Classes: Use CapWords (
PascalCase)Constants: Use UPPERCASE with underscores (
CONSTANT_NAME)Private methods/attributes: Prefix with underscore (
_private_method)
Code Formatting#
Use the repository pre-commit entry point:
./pre-commit.py --fix
Testing Requirements#
Running Tests#
Run the full test suite:
uv run pytest tests
Run tests for a specific module:
uv run pytest tests/test_clean_artifacts.py
Run tests with verbose output:
uv run pytest -v tests/test_clean_artifacts.py
Reference Tests Against MATLAB#
Reference-contract tests use Python assertions with an explicitly selected
backend. Ordinary pytest runs deselect these tests; deselection is not
validation. Keep their inputs, workflows, and expectations faithful to the
current sccn/eeglab_tests suite even when EEGPrep lacks the corresponding
feature. Provenance decorators identify source scenarios, but do not prove
behavioral equivalence or MATLAB code coverage.
Use the reference revisions recorded in tests/eeglab_tests/__init__.py.
Initialize the suite’s EEGLAB submodule and its plugin submodules, download the
required Git LFS datasets, and install MATLAB Engine for the installed MATLAB
release into the development environment. MATLAB, plugins, and reference data
are development dependencies only; EEGPrep users do not need them.
Select a migrated test module and run it against the pinned EEGLAB checkout:
uv run --no-sync pytest path/to/test_module.py \
--eeglab-backend=matlab --eeglab-root=/path/to/eeglab_tests/eeglab
--no-sync preserves MATLAB Engine when it was installed separately from the
project dependencies. EEGPREP_EEGLAB_ROOT can supply the reference path in
place of --eeglab-root. Missing MATLAB, reference files, or required plugins
are failures in this lane, not successful skips. Selecting a provenance-tagged
test that still calls Python directly is a collection error, preventing it from
being reported as MATLAB validation.
Tests using original datasets obtain them through eeglab_suite_root. Set
--eeglab-suite-root=/path/to/eeglab_tests if this is not the parent of
--eeglab-root. The fixture verifies the suite and EEGLAB Git revisions before
using the data. Download the original Git LFS files rather than replacing them
with generated recordings. The MATLAB backend also checks its EEGLAB revision.
The eeglab_backend fixture calls the selected implementation by name, for
example eeglab_backend("eeg_point2lat", points, epochs, srate, limits).
Resolve functions through this fixture rather than importing EEGPrep functions
at collection time. The MATLAB lane uses independent MAT-file transport, not
EEGPrep’s dataset readers or writers. Preserve numeric types and array shapes;
make any MATLAB/Python indexing conversion explicit in the test.
Automated MATLAB figures default to invisible, and backend teardown closes
figures after each test. Plotting computations still execute; these runs do
not replace separate interactive or visual-parity checks. Explicit GUI calls
can override MATLAB’s visibility default: the original LIMO workflows call
eeglab/eeglab redraw and are marked gui and slow. Use
-m "not gui" when desktop windows must stay closed; those workflows then
remain unvalidated, rather than being counted as passes.
The same tests run in CI with --eeglab-backend=python and
-m "parity and not slow and not gui" against the pinned eeglab_tests
checkout, including its Git LFS data. The job has no continue-on-error,
so it fails on any new failure or strict XPASS. Maintainers should mark it
required in branch protection. A missing Python feature must fail honestly; it
is not a reason to change the reference expectation, skip a case, or weaken a
tolerance. Gaps that already fail on that lane are listed one test at a time
in tests/eeglab_python_backend_xfails.py and marked xfail(strict=True)
only for the Python backend. The reason names the gap, and raises names
the stable exception type, so a test that fails for a new reason is a failure.
A listed test that starts passing fails the job until its entry is removed.
GUI and slow contracts stay outside this lane. MATLAB-oracle unittest classes
such as TestPinvParity stay out as well: they need MATLAB, and this lane
deselects them instead of recording that absence as a Python gap.
Report source accounting, live MATLAB results, and unresolved blockers separately. Neither a source-count match nor a passing subset establishes full suite validation.
Writing Tests#
Write a test before the code it covers, never after. Add a test only when it
would catch a real bug that the EEGLAB-ported tests and the workflow tests on
sample_data would miss, for example a fixed regression, a 1-based latency
or boundary-event edge, or a numerical result checked against EEGLAB. Do not add
tests to raise coverage, and do not assert only types, shapes, or that a result
is not None.
For a bug fix, first reproduce the bug with a failing test in the closest existing test file:
from eegprep import pop_loadset, pop_select
def test_pop_select_keeps_final_sample():
EEG = pop_loadset("sample_data/eeglab_data.set")
last = EEG["xmax"]
out = pop_select(EEG, time=[last - 1, last])
assert out["data"][:, -1].tolist() == EEG["data"][:, -1].tolist()
Documentation Standards#
Docstring Format#
Use Google-style docstrings:
def preprocess_eeg(eeg, filter_type='bandpass', freq_range=(1, 50)):
"""Preprocess EEG data with filtering and artifact removal.
This function applies a series of preprocessing steps to clean
EEG data for further analysis.
Parameters
----------
eeg : EEGobj
The EEG object to preprocess.
filter_type : str, optional
Type of filter to apply. Options: 'bandpass', 'highpass', 'lowpass'.
Default is 'bandpass'.
freq_range : tuple, optional
Frequency range for filtering in Hz. Default is (1, 50).
Returns
-------
dict
The preprocessed EEG dictionary.
Raises
------
ValueError
If filter_type is not recognized.
TypeError
If eeg is not an EEG dictionary.
Examples
--------
>>> from eegprep import pop_eegfiltnew, pop_loadset
>>> EEG = pop_loadset("data.set")
>>> EEG, com = pop_eegfiltnew(EEG, locutoff=1, hicutoff=50, return_com=True)
Notes
-----
This function returns the updated EEG dictionary.
See Also
--------
clean_artifacts : Remove artifacts from EEG data
clean_flatlines : Remove flat-line channels
"""
# Implementation here
pass
Documentation Examples#
Include practical examples in docstrings:
def load_bids_dataset(bids_root, subject_id):
"""Load a BIDS-formatted EEG dataset.
Examples
--------
>>> import eegprep
>>> eeg = eegprep.load_bids_dataset('/data/bids_root', 'sub-001')
>>> print(eeg.nbchan) # Number of channels
64
"""
pass
Pull Request Process#
Before Submitting#
Update your branch with the latest upstream changes:
git fetch upstream
git rebase upstream/develop
Run tests locally:
uv run pytest tests
Check code style:
./pre-commit.py
Build documentation locally:
cd docs
make html
Submitting a Pull Request#
Push your branch to your fork:
git push origin feature/your-feature-name
Go to the GitHub repository and click “New Pull Request”
Fill in the PR template with:
Title: Clear, descriptive title
Description: What changes are made and why
Related Issues: Link to any related issues (e.g., “Fixes #123”)
Testing: Describe how you tested the changes
Documentation: Note any documentation updates
Ensure all CI checks pass
Wait for review and address feedback
PR Review Process#
At least one maintainer review is required
All CI checks must pass
Code coverage should not decrease
Documentation must be updated if needed
Commits should be clean and well-organized
Code of Conduct#
Respectful Collaboration#
We are committed to providing a welcoming and inclusive environment. All contributors must:
Be respectful and professional in all interactions
Welcome diverse perspectives and experiences
Provide constructive feedback
Report inappropriate behavior to the maintainers
Unacceptable Behavior#
The following behaviors are not tolerated:
Harassment, discrimination, or intimidation
Offensive comments or language
Unwelcome sexual attention or advances
Deliberate disruption of discussions
Publishing private information without consent
Reporting Issues#
If you experience or witness unacceptable behavior, please report it to the maintainers at:
Open an issue on GitHub (private if needed)
Contact the project maintainers directly
All reports will be handled confidentially and investigated promptly.
Getting Help#
Documentation: Check the User Guide and Functions
Examples: See Examples for practical examples
Issues: Search GitHub Issues
Discussions: Join our community discussions on GitHub
Thank you for contributing to EEGPrep!