EEGPREP is a Python port of core EEGLAB preprocessing concepts, workflows, file names, data structures, GUI patterns, and user experience. Build features so EEGLAB users can predict where code lives and how APIs behave, while still using simple idiomatic Python when MATLAB style would make the code worse. Remember, while we are doing a porting project, EEGPrep must work well standalone and must be a delight to use for EEG Researchers.
Primary references:
- EEGLAB source: https://github.com/sccn/eeglab
- EEGLAB data structures: https://eeglab.org/tutorials/ConceptsGuide/Data_Structures.html
src/eegprep/functions/popfunc/: EEGLAB-stylepop_*user-facing wrappers andeeg_*functions that operate on EEG structures, such as ICA wrappers. Keep each pop function in apop_<name>.pymodule that mirrorsfunctions/popfunc/in EEGLAB.src/eegprep/functions/guifunc/: EEGLAB-style GUI helpers such asinputgui, dialog specs, and Qt rendering. Keep GUI infrastructure parallel tofunctions/guifunc/in EEGLAB.src/eegprep/functions/adminfunc/: EEGLAB-style administrative helpers such aseeg_checkset.py,eeg_options.py, the main GUI launcher, and the synchronizedeegprep-consoleworkspace.src/eegprep/functions/sigprocfunc/: EEGLAB-style low-level signal processing functions such asrunica.py,runamica.py,topoplot.py,epoch.py, andeegrej.py.src/eegprep/plugins/clean_rawdata/: Python ports of the EEGLAB clean_rawdata plugin, includingclean_*and ASR modules.src/eegprep/plugins/clean_rawdata/private/: ports of clean_rawdata private helpers such asfit_eeg_distribution,geometric_median, FIR helpers, covariance helpers, and spherical-spline interpolation.src/eegprep/plugins/ICLabel/: Python ports of the EEGLAB ICLabel plugin and bundlednetICL.mat.src/eegprep/plugins/firfilt/: Python ports of the EEGLAB firfilt plugin helpers.src/eegprep/functions/miscfunc/: EEGLAB-style miscellaneous helpers, including format conversion and numerical utilities.src/eegprep/functions/eegobj/: Python counterpart to EEGLAB'sfunctions/@eegobj/.src/eegprep/plugins/EEG_BIDS/: Python ports and workflow helpers for the EEGLAB EEG-BIDS plugin.src/eegprep/utils/: Python-only test/development support. Do not put EEGLAB-equivalent processing code here.src/eegprep/resources/: MATLAB option files, montages, help text, and package data.src/eegprep/resources/help/: EEGPrep-owned Markdown help resources for GUI Help buttons and EEGLAB-stylepophelptext. Add<function_name>.mdhere whenever new user-facing functionality needs GUI help.src/eegprep/eeglab/: vendored EEGLAB reference code for local development and parity work. Treat as reference input; do not edit unless explicitly updating the bundled reference. Do not make package runtime behavior depend on this directory existing.tests/matlab/: MATLAB parity scripts and MATLAB helper fixtures used by Python tests.scripts/*.m: MATLAB/Octave helper scripts that are not part of the normal unit-test tree.sample_data/: small checked-in EEG sample datasets, named to match EEGLAB'ssample_dataconvention.sample_notebooks/: exploratory/sample notebooks. Keep runnable examples in docs when they are user-facing.tools/: developer and parity tooling that is not installed as part of theeegpreppackage.tests/:unittesttests. Test files generally mirror source module names.docs/source/: Sphinx docs, examples, API pages..github/workflows/test.yml: CI test and pre-commit entry points.pre-commit.py: required lint/check script for this repo.
- Check whether a matching skill exists. Skills are task-focused playbooks in
.agents/skills/and are also accessible as.claude/skills/. Before starting any non-trivial task, scan the skill descriptions in your system prompt; if one matches, invoke it via the Skill tool instead of using ad-hoc commands. - Use
.agents/skills/eeglab-gui-visual-parity/SKILL.mdwhen building or iterating on EEGPrep GUI features, especiallypop_*dialogs that should match EEGLAB screenshots through the visual parity capture loop. - State assumptions before implementing. If the request has multiple plausible interpretations, present them.
- If something is unclear, stop and ask. Do not hide confusion in code.
- If a simpler approach exists, say so. Push back on speculative features, compatibility shims, or unnecessary abstractions.
- For multi-step work, state a short plan with verification for each step. Include code snippets when they clarify the intended change.
- Define verifiable success criteria. For example: bug fix means reproduce with a failing test, implement, then pass the test; feature means update behavior, tests, docs, and pre-commit.
- Use EEGLAB as a development and parity oracle, not as an EEGPrep runtime dependency. During development, compare against EEGLAB so EEGPrep features look, feel, and behave like EEGLAB for EEGLAB users; at runtime, EEGPrep must work standalone without an EEGLAB checkout.
- EEGLAB users must feel at home while using EEGPrep. For GUI-based features, match EEGLAB's alignment, arrangement, labels, buttons, text fields, control order, default values, enabled/disabled states, and workflow before introducing Python-only improvements.
- Keep naming and directory structure as close to EEGLAB as practical. Put
pop_*wrappers infunctions/popfunc, GUI helpers infunctions/guifunc, administrative functions infunctions/adminfunc, signal-processing functions infunctions/sigprocfunc, clean_rawdata ports inplugins/clean_rawdata, and ICLabel ports inplugins/ICLabel. - Before porting or changing behavior, inspect the matching MATLAB file under
src/eegprep/eeglab/functions/orsrc/eegprep/eeglab/plugins/. - Runtime code in the installed
eegpreppackage must work withoutsrc/eegprep/eeglab/present. Do not read from, import from, shell out to, or otherwise depend on the EEGLAB checkout in package runtime paths. - If EEGLAB-like text, examples, or Help content are needed at runtime, make them EEGPrep-owned packaged resources. For GUI Help and
pophelp, add Markdown files undersrc/eegprep/resources/help/<function_name>.md; missing help resources should fail clearly instead of falling back to the EEGLAB checkout or Python docstrings. - Preserve EEG dict semantics unless the user asks for a new abstraction. Core fields include
data,nbchan,pnts,trials,srate,xmin,xmax,times,chanlocs,event,urevent,epoch,history,icaact,icawinv,icasphere,icaweights, andicachansind. - Data is channel-major: continuous data is usually
(nbchan, pnts), epoched data is usually(nbchan, pnts, trials). - Be explicit about MATLAB/Python indexing boundaries. EEGLAB event latencies and many user-facing indices are 1-based; Python arrays are 0-based internally. Test first/last sample and boundary-event behavior.
- Prefer explicit inputs and return values. Do not introduce hidden global state; EEGLAB avoids globals for processing functions.
- Do not force MATLAB style blindly. If Python style and EEGLAB parity conflict, choose the simpler maintainable implementation and document the tradeoff in code, tests, or PR notes.
- EEGPrep's primary interactive workflow is mixed GUI plus
eegprep-console. Both share oneEEGPrepSession;EEG,ALLEEG,CURRENTSET,LASTCOM,ALLCOM,STUDY, andCURRENTSTUDYmust stay synchronized. - Treat GUI plus console flow as one user experience, not two separate clients. EEGLAB users commonly switch between menus/dialogs and command history/workspace inspection, so GUI actions, console commands, dataset selection, history, and visible state must stay seamless in both directions.
EEGPrepSession.CURRENTSETis a list of EEGLAB-facing 1-based dataset indices; expose it as0, scalarn, or list[n, ...]in the console. Useselected_dataset_indices()for read-only multi-dataset state.- GUI/menu actions must update datasets and history through
EEGPrepSessionhelpers such asstore_current,add_history, andnotify_changed; do not mutate GUI-only state that the console cannot see. - User-facing
pop_*functions should supportreturn_com=Trueand return EEGLAB-style history commands. The console wrappers rely on(EEG, com)results to auto-store bare calls likepop_reref(EEG, []). - When adding or changing a GUI-reachable function, test both interaction directions when relevant: GUI action then console inspection, and console command then GUI refresh/history.
- When GUI/console synchronization, history behavior, dataset selection, STUDY state, or registered menu actions change, update the Sphinx workflow docs so users understand how to move between the GUI and
eegprep-console, and update developer notes whenEEGPrepSessioncontracts change. - Menu placeholders must carry phase/exclusion metadata in
menu_placeholders.py; mark EEGBrowser/eegplot-style scrolling workflows with an explicit exclusion instead of treating them as ordinary TODOs.
- Make the smallest change that solves the request. Every changed line should trace to the task.
- Do not add features beyond what was asked. No speculative configurability, future-proofing, or error handling for impossible states.
- If a solution is 200 lines and could be 50, rewrite it.
- Touch only files you must. Do not refactor adjacent code, reformat unrelated blocks, or delete pre-existing dead code unless asked.
- Remove imports, variables, functions, and files that your change made unused. Do not clean up pre-existing dead code unless asked.
- Match existing local style, even where it is imperfect.
- All imports go at the top. No local imports except for circular dependency breaks or optional dependency guards. Do not use
TYPE_CHECKINGguards; fix cycles structurally, often with protocols or smaller modules. - Prefer top-level functions over classes when code does not mutate shared state. Avoid deep inheritance.
- Use early returns to reduce nesting.
- Prefer logging over
print, except in scripts and temporary debugging. - Resolve environment-dependent defaults once and fail fast on unknown inputs.
- No ad-hoc compatibility hacks such as
hasattr(obj, "old_attr"); update call sites consistently. - No backward compatibility by default. Replace old APIs and update all call sites. Add compatibility shims only when explicitly requested.
- Prefer small concrete helpers over indirection-heavy abstractions. Do not add abstractions for single-use code.
- Put magic strings and magic numbers in top-level constants when they are reused or semantically important.
- Before writing any utility, helper, or data structure, search for existing implementations and check the standard library, current dependencies, and appropriate third-party packages before adding new code or dependencies. If a suitable implementation exists, use it.
- Public APIs need concise docstrings. Use the style already present in nearby code; Google-style is preferred for new public functions.
- Skip docstrings for trivial private helpers with clear names.
- Any user-facing EEGPrep change must include matching Sphinx updates under
docs/source/in the same branch unless the change has no user-visible behavior. Update workflow/tutorial pages, examples, generated or hand-written API pages, troubleshooting notes, and migration notes where relevant. - Write docs from an EEGPrep standalone user point of view. Use EEGLAB comparisons when they clarify migration, parity, file compatibility, or indexing boundaries, but do not frame normal EEGPrep behavior as merely "EEGLAB-style."
- When adding a user-facing function or GUI dialog with a Help button, add or update the corresponding Markdown help resource in
src/eegprep/resources/help/, for examplesrc/eegprep/resources/help/pop_reref.md. - Keep comments for module/class behavior, subtle logic, or non-obvious boolean arguments. Do not restate code.
- Delete stale comments when you encounter them in touched code.
- Pytest is the default test runner. Existing tests may still use
unittest.TestCase; do not rewrite them unless the touched test benefits from pytest fixtures or parametrization. - Registered markers include
slow,matlab,octave,gui,visual, andparity. - Legacy
unittesttests are categorized by path/name intests/conftest.py; update that map when adding obvious slow, MATLAB, GUI, visual, or parity coverage. - Always fix tests you break.
- Run the narrowest relevant tests first, then broaden as risk requires:
- Single file:
uv run pytest tests/test_pop_select.py - Marker subset:
uv run pytest -m "not slow" - Full suite:
uv run pytest tests - No MATLAB locally:
EEGPREP_SKIP_MATLAB=1 uv run pytest tests
- Single file:
- Some parity tests require MATLAB Engine or Octave via
src/eegprep/functions/adminfunc/eeglabcompat.py; preserve skip behavior instead of weakening assertions. - Prefer integration-style tests that validate externally observable behavior on EEG dicts, BIDS outputs, files, or MATLAB parity results.
- For features reachable from
eegprep-console, add or updatetests/test_console_workspace.pycoverage when namespace sync, history, import aliases, or barepop_*auto-store behavior can be affected. - Search existing test files before creating new ones. Extend the closest existing test first.
- No mocks unless testing I/O boundaries such as network or filesystem. Test real behavior for numerical transforms.
- Do not write tautological tests that only assert types, constants, or implementation details.
- Never relax tolerances or hack around failures without proving the numerical expectation is wrong.
- Avoid duplicate fixtures; reuse
tests/fixtures.pyor local helpers already present in the relevant test file.
./pre-commit.pyis the required lint/check entry point.- Use
./pre-commit.pybefore committing to check staged files. - Use
./pre-commit.py --fixto fix only staged files. - Use
./pre-commit.py --changed-from origin/developfor PR-scope checks. - Use
./pre-commit.py --changed-from origin/develop --fixto fix only files changed against the base branch. - Avoid
./pre-commit.py --all-files --fixunless the user explicitly wants a repo-wide cleanup. - CI also runs
uv run --no-sync ruff check .,uv run --no-sync ruff format --check ., anduv run --no-sync ty check; run these locally after broad formatting, import, or typing-sensitive changes. - Pre-commit is separate from unit tests; run both when code changes.
- Python support starts at 3.10 per
pyproject.toml. uvis the default package and environment manager for development, CI, and agent workflows..python-versionsets the default development interpreter to Python 3.11.- Use
uv sync --group devafter cloning or when dependencies change. - Use
uv run python ...for Python commands so tests run inside the project environment. - Do not add dependencies for tiny helpers. If a dependency is justified, update
pyproject.toml, docs, and any CI/install notes.
- NEVER SAY "You're absolutely right!"
- Never credit yourself or AI tools in commits. No
Co-authored-byor generated-by trailers unless the user explicitly asks. - Keep commits scoped to one logical change with concise messages. Commit often.
- Add the
agent-generatedlabel only when a repository automation workflow creates the PR or issue. Do not add it when a human asks an agent to open or update a PR from an interactive session. - Agent comments on PRs/issues must begin with
🤖unless the exact text was explicitly approved by the user. - When creating a PR that includes GUI changes, attach side-by-side GUI comparison images as GitHub user attachments using
gh attach --comment; do not commit those images or visual parity artifacts to the repo. - When using
ghto inspect issues or PRs, prefer--json <fields>or explicit narrow flags such as--comments; avoid plaingh issue vieworgh pr view, which can fail on this repo because GitHub classic project fields are deprecated. - If you notice unrelated dead code or unrelated cleanup, mention it separately instead of changing it.
- Over-protective
try/exceptblocks and defensiveNonechecks for impossible states. - Boolean dispatch where separate clear functions would be simpler.
- Verbose or redundant docstrings.
__all__churn in__init__.pywithout a real API need.- Environment variables where explicit parameters belong.
- New classes for stateless behavior.
- Parallel implementations of existing helpers.
- Broad formatting-only diffs mixed with behavior changes.