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#

  1. Fork the repository on GitHub by clicking the “Fork” button

  2. Clone your fork locally:

git clone https://github.com/YOUR_USERNAME/eegprep.git
cd eegprep
  1. Add the upstream repository:

git remote add upstream https://github.com/sccn/eegprep.git
  1. 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-console runtime dependencies

  • Documentation dependencies when --extra docs is 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, not x=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#

  1. Update your branch with the latest upstream changes:

git fetch upstream
git rebase upstream/develop
  1. Run tests locally:

uv run pytest tests
  1. Check code style:

./pre-commit.py
  1. Build documentation locally:

cd docs
make html

Submitting a Pull Request#

  1. Push your branch to your fork:

git push origin feature/your-feature-name
  1. Go to the GitHub repository and click “New Pull Request”

  2. 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

  3. Ensure all CI checks pass

  4. 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#

Thank you for contributing to EEGPrep!