Metadata-Version: 2.4
Name: aperion
Version: 0.1.0
Summary: Honest, leakage-safe resting-state EEG analysis for cross-hospital Parkinson's disease classification.
Project-URL: Homepage, https://github.com/nimitakhawat/Aperion
Project-URL: Repository, https://github.com/nimitakhawat/Aperion
Project-URL: Documentation, https://github.com/nimitakhawat/Aperion#readme
Project-URL: Issues, https://github.com/nimitakhawat/Aperion/issues
Project-URL: Changelog, https://github.com/nimitakhawat/Aperion/blob/main/CHANGELOG.md
Author-email: Nimit Akhawat <nimit@isecol.com>
Maintainer-email: Nimit Akhawat <nimit@isecol.com>
License: MIT License
        
        Copyright (c) 2026 Nimit Akhawat
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: aperiodic,cross-site,domain-adaptation,eeg,fooof,machine-learning,mne,neuroscience,parkinson
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Healthcare Industry
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
Requires-Python: >=3.11
Requires-Dist: fooof>=1.1
Requires-Dist: matplotlib>=3.7
Requires-Dist: mne>=1.6
Requires-Dist: numpy>=1.24
Requires-Dist: pandas>=2.0
Requires-Dist: scikit-learn>=1.3
Requires-Dist: scipy>=1.10
Provides-Extra: app
Requires-Dist: streamlit>=1.30; extra == 'app'
Provides-Extra: dev
Requires-Dist: black>=24.0; extra == 'dev'
Requires-Dist: pytest-cov>=4.0; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
Description-Content-Type: text/markdown

# aperion

Resting-state EEG analysis for neurodevelopmental disorder research. The goal is a
**simple, honest** pipeline that predicts **Parkinson's disease (PD)** vs **healthy
control (HC)** from resting-state EEG — and, crucially, that keeps working when tested on
a hospital it has never seen.

> **Status:** chunks one–three complete.
> 1. **Plumbing + honesty gate** — one dataset (ds002778), band-power features, logistic
>    regression, shuffled-label leakage control.
> 2. **Aperiodic method** — separate the 1/f background (offset, exponent) from the
>    periodic peaks (FOOOF), scored with a nested SelectKBest + shrinkage-LDA harness.
> 3. **Cross-hospital** — three hospitals (ds002778, ds003490, ds004584),
>    leave-one-dataset-out evaluation, and a harmonizer to remove site effects.

## Headline result (honest)

Trained on two hospitals, tested on an entirely unseen third (leave-one-dataset-out):

| Features | cross-hospital AUC (no harmonizer) | with harmonizer |
|---|--:|--:|
| band-power | 0.711 | 0.703 |
| aperiodic | 0.660 | **0.716** |

The harmonizer **rescues** the aperiodic method from behind to a **tie** with band-power
(not a decisive win). The strongest evidence is the **hospital-probe control**: after
harmonizing, the ability to predict *which hospital* a recording came from collapses
(aperiodic 0.72 → 0.24, below chance) **while disease AUC rises** — i.e. it removed
hospital effects, not disease signal. Benefit was heterogeneous (large on ds002778 /
ds004584, flat on ds003490).

## Project layout

```
NDDProject_EEG/
├── pyproject.toml          # build (hatchling) + deps + optional [app]/[dev] extras
├── src/aperion/            # the importable package (LEAN: core deps only)
│   ├── datasets/           # dataset loaders (+ multi-cohort loader)
│   ├── preprocessing/      # one identical cleaning pipeline -> epochs
│   ├── features/           # band-power + aperiodic (FOOOF) features
│   ├── harmonize/          # PerHospitalStandardizer (site harmonizer)
│   ├── evaluate/           # subject-grouped CV, LODO, honesty/probe controls
│   ├── utils/              # structural harmonization (channels, sample rate)
│   ├── viz/                # PSD / group-comparison plots
│   ├── pipeline_api.py     # raw -> subject-level features (single source of truth)
│   ├── model.py            # trainable/saveable AperionModel (aperiodic + harmonizer)
│   └── inference.py        # single-recording scoring for the app
├── app/                    # SEPARATE demo web app (Streamlit) — imports aperion only
│   ├── streamlit_app.py
│   └── assets/             # saved results.json, figures, demo recordings
├── benchmarks/             # build_phase_b_artifacts.py (trains model, saves results)
├── models/                 # saved model file (generated; gitignored)
├── examples/ · tests/ · docs/
```

## Install

A virtual environment (`.venv`) is expected in the project root.

```bash
python -m venv .venv
source .venv/bin/activate           # Windows: .\.venv\Scripts\Activate.ps1
pip install -e ".[dev]"             # core + dev tools (pytest/ruff/black)
```

Core install pulls only `mne, scikit-learn, numpy, scipy, pandas, matplotlib, fooof`.
It does **not** install streamlit — the web app is an opt-in extra (see below).

Verify:

```bash
python -c "import aperion; print(aperion.__version__)"   # -> 0.1.0
pytest
```

## Quickstart

The feature extractors and the harmonizer are standard scikit-learn transformers, so they
drop straight into a pipeline. This snippet is self-contained (toy spectra) and runs as-is:

```python
import numpy as np
from aperion import AperiodicPeriodicExtractor, BandPowerExtractor, PerHospitalStandardizer

freqs = np.arange(2.0, 40.0, 0.5)
ch_names = ["Fz", "Cz", "Pz", "Oz"]

# power spectra shaped (n_recordings, n_channels, n_freqs) -- here a toy 1/f example
base = (10.0 ** 1.0) / (freqs ** 1.3)
spectra = np.tile(base, (6, len(ch_names), 1))

# 1) two interchangeable feature families
#    (n_jobs=1 keeps FOOOF single-process — safest on Windows / inside scripts)
ap = AperiodicPeriodicExtractor(freqs, ch_names, n_jobs=1).fit_transform(spectra)  # 1/f + peaks
bp = BandPowerExtractor(freqs, ch_names).fit_transform(spectra)                    # relative band power

# 2) remove per-hospital effects (fit on training sites; held-out site uses its own stats)
cohorts = np.array(["H1"] * 3 + ["H2"] * 3)
harm = PerHospitalStandardizer().fit(ap, cohorts=cohorts)
ap_harmonized = harm.transform(ap, cohorts=cohorts)
print(ap.shape, bp.shape, ap_harmonized.shape)
```

Score a real recording with the saved model (after running the build step below):

```python
from aperion import load_model, load_recording, analyze_raw

model = load_model("models/aperion_model_v0.1.0.joblib")
raw = load_recording("data/raw/ds002778/sub-pd3/ses-off/eeg/sub-pd3_ses-off_task-rest_eeg.bdf")
result = analyze_raw(raw, model, cohort="ds002778")
print(f"P(Parkinson's) = {result['probability']:.2f} ± {result['uncertainty']:.2f}")
```

## Reproduce the benchmark

Verify the headline cross-hospital result from the raw data (fixed seeds; requires the three
datasets in `data/raw/`):

```bash
python benchmarks/reproduce_cross_hospital.py
```

It rebuilds features, runs leave-one-dataset-out ± harmonizer for both feature types and the
hospital-probe control, and checks the two headline effects (harmonizer rescue + probe
collapse) against expected values — printing `HEADLINE REPRODUCED` on success.

## Demo web app

The app is a **research demonstration** — *not a medical device and not a diagnosis*. It
lives in `app/` and only imports/calls `aperion` (no data or modelling logic of its own);
it loads a pre-trained model and pre-computed result files rather than recomputing science.

**1. Install the optional app dependencies** (adds streamlit):

```bash
pip install -e ".[app]"
```

**2. Build the model + result files once** (the app loads these; requires the datasets in
`data/raw/`):

```bash
python benchmarks/build_phase_b_artifacts.py
```

**3. Run the app locally:**

```bash
streamlit run app/streamlit_app.py
# open http://localhost:8501
```

- **Analyze** page: pick a bundled demo recording (or upload one); see its spectrum split
  into the 1/f background and the peaks, plus the model's Parkinson's probability with an
  uncertainty band and a medical disclaimer.
- **Results** page: the honest cross-hospital story (harmonizer rescue, hospital-probe
  control, matched-size domain shift, and limitations), loaded from saved files.

### Sharing it online (optional, not deployed here)

The simplest free option is **[Streamlit Community Cloud](https://streamlit.io/cloud)**:
push this repo to GitHub, then on Community Cloud point a new app at
`app/streamlit_app.py` and set the dependencies to `.[app]`. You would need to include (or
regenerate) the `models/` file and `app/assets/` since large data is gitignored. This repo
does **not** deploy anything automatically.

## Dependencies

- **Core:** `mne`, `scikit-learn`, `numpy`, `scipy`, `pandas`, `matplotlib`, `fooof`
- **`[app]`:** `streamlit`  ·  **`[dev]`:** `pytest`, `ruff`, `black`

## License

MIT — see [LICENSE](LICENSE).
