flowchart LR
yaml["project.yaml<br/>(plugins + config)"] --> project["Project<br/>(pyPhases)"]
project --> phases["Phases<br/>(Python classes)"]
phases -->|registerData| exporter["Exporter <br/>(Pickle / Memmap / HDF5)"]
exporter -->|hash-keyed cache| disk[(disk)]
phases -->|getData| exporter
Getting Started with SleePyPhases
From installation to a trained sleep staging model
This page is the central entry point for SleePyPhases. It walks through a complete sleep staging project end-to-end, explains every part of project.yaml, and links to detail pages for advanced use cases.
Installation
Requirements: Python ≥ 3.9
pip install SleePyPhases pyPhasesRecordloaderSleepEDFInstalling SleePyPhases automatically installs the full stack:
| Package | Role |
|---|---|
pyPhases |
Core framework — Phase, Config, Project, Exporter |
phases |
CLI — phases create, phases run |
pyPhasesML |
ML extension — model management, training loop, evaluation |
SleepHarmonizer |
PSG channel and annotation harmonization |
SleePyPhases |
Multi-dataset sleep framework (this package) |
Dataset loaders are installed separately as needed, e.g.:
pip install pyPhasesRecordloaderSleepEDF # SleepEDF (PhysioNet)
pip install pyPhasesRecordloaderSHHS # SHHS
# see the Plugins & Loaders reference for the full listCreate a project
phases create myprojectAnswer the prompts (select sleep study, enter project name, create Init phase). The wizard generates a complete project skeleton:
myproject/
├── project.yaml ← central configuration file
└── myproject/
├── __init__.py
├── phases/
│ └── Init.py ← wires your custom classes into the framework
├── SignalPreprocessing.py ← subclass for custom signal preprocessing steps
├── PreManipulation.py ← subclass for whole-record manipulation before segmentation
├── DataManipulation.py ← subclass for per-segment and batch manipulation
└── models/
└── MyModel/
└── MyModel.py ← PyTorch model stub
As an alternative, you can use a programmatic approach or a Python notebook to create a project and run phases without using the CLI. See the “flat / notebook approach” section in Setup for details.
Project structure and core concepts
project.yaml is the single source of truth for an experiment. Every config value participates in cache hashing: change any value and all downstream data that depend on it is automatically invalidated and regenerated.
The project is defined in YAML.
How it works
→ pyPhases core concepts explains phases, config hashing, and exporters in detail.
Loading additional config files
A project can split its configuration across multiple YAML files. Use importAfter (or importBefore) inside any YAML file to merge additional configs on load, or pass a file at runtime with the -c flag:
# project.yaml
name: myproject
plugins: [...]
importAfter:
- configs/model.yaml # merged after this file
- configs/dataset.yamlphases run Training -c configs/shhs.yaml # merged at runtime, overrides baseIndividual keys can also be overridden via environment variables: PHASE_CONFIG_preprocessing_targetFrequency=64.
This pattern is commonly used to keep a single project.yaml and swap dataset or model configs without editing the base file.
plugins
plugins:
- pyPhasesRecordloaderSleepEDF # dataset loader
- pyPhasesRecordloader # base loader support
- SleepHarmonizer # channel harmonization
- pyPhasesML # ML training/eval infrastructure
- SleePyPhases # provides the built-in pipeline phasesSleePyPhases registers the following built-in phases that you can run directly:
| Phase | What it does |
|---|---|
LoadData |
Loads metadata and record IDs |
Extract |
Loads raw records, runs signal preprocessing, writes harmonized cache |
Training |
Model training loop — saves best checkpoint |
EvalReport |
Generates HTML/Markdown evaluation report |
Eval |
Intermediate phase that runs the evaluation on test split |
ThresholdOptimisation |
Intermediate phase that finds optimal per-class thresholds on the validation fold |
BuildDataset |
Intermediate phase that splits records into train/val/test folds |
For a complete walkthrough, only the Training phase, and the EvalReport phases need to be run. LI automatically resolves dependencies: for example, running the Training will trigger BuildDataset, which triggers Extract if their outputs are not cached. Only EvalReport does not allow a full training run, to prevent unintended training runs. This can be changed if Eval.allowTraining is set to True.
For transparency and understanding, we list the intermediate phases here, but they are not required to be run directly in the common case. LoadData and Extract do not need to be run separately, but are listed because they are often run separately. One of the first steps is often data exploration, where the metadata is analysed without requiring training. Often pipelines do not require a GPU when running Extract and can therefore be run separately with more optimised hardware.
phases (your custom phases)
On default the wizard generates an Init phase that wires your custom subclasses into the framework before any other phase runs:
# myproject/phases/Init.py
from pyPhases import Phase
from SleePyPhases import SignalPreprocessing as SP, PreManipulation as PM, DataManipulation as DM
from myproject.SignalPreprocessing import SignalPreprocessing
from myproject.PreManipulation import PreManipulation
from myproject.DataManipulation import DataManipulation
class Init(Phase):
def prepareConfig(self):
SP.setClass(SignalPreprocessing)
PM.setClass(PreManipulation)
DM.setClass(DataManipulation)SignalPreprocessing, PreManipulation, and DataManipulation are swappable classes inside SleePyPhases — the framework instantiates them by calling getInstance(), which returns your subclass instead of the base class once setClass has been called. This means you can override or extend any individual processing step without touching the rest of the pipeline. → Swappable Classes explains all swappable classes, what each one controls, and how to extend them.
You can add your own phases to the phases list in project.yaml and implement them as Python classes. They can be used for custom data manipulation and analysis, additional evaluation steps, or even self-supervised pretraining. For more details see “Phase” in pyPhases core concepts.
project.yaml — full walkthrough
Below is the annotated project.yaml from the Single-Page project. Every section is explained with links for more detailed configuration.
name: simplecnn
plugins:
- pyPhasesRecordloaderSleepEDF
- pyPhasesRecordloader
- SleepHarmonizer
- pyPhasesML
- SleePyPhases
phases:
- name: Init
config: {} sleepedf-path — dataset location
useLoader: sleepedf # which loader plugin to activate
sleepedf-path: /data/sleepedfuseLoader selects the active dataset plugin. The default path key is <loadername>-path and is consumed by the corresponding loader package.
→ Load Data lists all available loaders, channel loading vs. selection, multi-dataset training with datafold.datasets, and external evaluation with evalOn. → To add a new public dataset or configure a vendor device, see New Record Loader and Vendor Config.
dataversion — data splits
dataversion:
version: Debug-split # label for identification, part of cache hash
seed: null # random seed for shuffling, before splitting
folds: 5 # number of cross-validation folds
split:
trainval: "0:170" # record index range for train + validation
test: "170:197" # held-out test set→ For advanced split options (groupBy, recordIds, filterQuery, validationSplit and named splits), see Dataset Splits.
labelChannels — ground-truth channels
labelChannels:
- SleepStagesAASMDefines which annotation channels are extracted from the raw recordings and used as ground truth. The available harmonized label channels are:
| Channel | Description |
|---|---|
SleepStagesAASM |
AASM sleep staging (W / R / N1 / N2 / N3) |
SleepArousals |
Arousal events |
SleepArousalRera |
Distinguish between Arousal and RERA arousals |
RespEvents |
Respiratory events (obstructive, central, mixed, hypopnea) + RERA flow limitation |
SleepApnea |
Respiratory events (obstructive, central, mixed, hypopnea) |
SleepLegMovements |
Leg movement events |
SleepLegMovementsPLM |
PLM-filtered leg movements |
→ For multi-label studies add multiple entries, each label is added to the Y as channel.
→ To define your own harmonized label channels, see Adding Custom Label Channels (WIP).
classification — label and class names
classification:
name: sleep
labelNames:
- Sleep
classNames:
- [Wake, R, N1, N2, N3]labelNames and classNames give human-readable identifiers used in evaluation reports and metric tables. The number of classes for each label is defined by the length of the corresponding classNames list.
For multi-task models (predicting sleep stages, arousals, respiratory events, and leg movements simultaneously), see Classification Specification, which covers scorerTypes, predictionSignals, predictionFrequencies, and their relationship to clinical metrics.
preprocessing — signal preprocessing
preprocessing:
targetFrequency: 32 # Hz — all signals resampled to this rate
labelFrequency: 0.03333 # Hz — 1/30 for 30-second epoch labels
stepsPerType:
eeg: [resample] # method names on your SignalPreprocessing class
manipulationSteps:
- name: trimSPT # whole-record manipulation before saving
targetChannels:
- [EEG Fpz-Cz] # channels to keep in the output filestepsPerType maps a channel type (eeg, eog, emg, ppg, ecg, …) or a specific channel name (e.g. EEG Fpz-Cz) to an ordered list of steps. Each step is either a plain string (method name) or a dict with name plus any keyword arguments forwarded to the method — the same principle as data manipulation steps. Methods receive (self, signal, recordSignal, **kwargs) — see Preprocess Signals for full API documentation.
# myproject/SignalPreprocessing.py
from scipy.signal import resample
from SleePyPhases import SignalPreprocessing as SPPSignalPreprocessing
class SignalPreprocessing(SPPSignalPreprocessing):
def resample(self, signal, recordSignal):
factor = recordSignal.targetFrequency / signal.frequency
signal.signal = resample(signal.signal, int(len(signal.signal) * factor))
signal.frequency = recordSignal.targetFrequencytargetFrequency and labelFrequency are configuration constants — they do not automatically resample signals. Your preprocessing methods are responsible for the actual resampling (e.g. using recordSignal.targetFrequency).
manipulationSteps are whole-record operations applied after per-channel steps — before the data is written to the cache. Common uses include tailor recording to sleep scoring.
→ Preprocess Signals covers parameter passing and how to implement and configure seperate featureChannels and combineChannels.
recordWise — full recording vs. windowed segments
recordWise: true| Value | Behaviour |
|---|---|
true |
The dataset yields one full recording per sample. The model receives the entire night. Use with segmentManipulation to pad/trim to a fixed size. |
false |
The dataset yields fixed-length windows. Use with segmentLength. |
For windowed models, add:
recordWise: false
segmentLength: 3000 # window length in samplesMore details on windowing and segment generation in Manipulate Data.
segmentManipulation — per-segment steps
segmentManipulation:
- name: addBatchDimension # insert batch dimension
- name: fixedSize # pad/trim to fixed (X, Y) dimensions
sizeX: 806400
sizeY: 840Each step is a method on your DataManipulation subclass, called with (X, Y, **kwargs) where kwargs come from the YAML dict. Steps are applied in order before a segment enters a batch.
# myproject/DataManipulation.py
import numpy as np, torch
from SleePyPhases import DataManipulation as SPPDataManipulation
class DataManipulation(SPPDataManipulation):
def fixedSize(self, X, Y, sizeX, sizeY, fillValue=0, fillValueY=-1):
# pad or truncate X and Y to fixed dimensions
...
return X, Y
def toTensor(self, X, Y):
X = torch.tensor(np.stack(X), dtype=torch.float32)
Y = torch.tensor(np.stack(Y), dtype=torch.float32)
X = X.permute(0, 2, 1) # (batch, samples, channels) → (batch, channels, samples) as expected by the model
return X, Y→ Manipulate Data for more details.
batchManipulation — per-batch steps
batchManipulation:
- name: toTensorSame mechanism as segmentManipulation but applied to the assembled batch just before it is fed to the model.
modelPath / modelName — model class
modelPath: simplecnn/models # directory containing model subdirectories
modelName: SimpleCNN # must match subdirectory name and class name
inputShape: [806400, 1] # [samples, channels] — passed to define()The model class lives at {modelPath}/{modelName}/{modelName}.py and must subclass ModelTorchAdapter (PyTorch) or ModelTFAdapter (TensorFlow).
| Method | Purpose |
|---|---|
define() |
Build architecture, assign to self.model |
prepareX(X) |
Reshape/cast input batch before forward pass |
prepareY(Y) |
Reshape/cast ground truth for loss computation |
getLossFunction() |
Return loss function instance |
mapOutputForLoss(output) |
Reshape model output to match loss expectations |
prepareForScore(targets, pred) |
Reshape for metric computation |
Additional model.* config keys are passed as options to the model — useful for architecture hyperparameters:
model:
seq_len: 20
num_blocks: 1
fc_dropout: 0.1Access inside the model: self.getOption("seq_len").
Generated model stub
The wizard produces a ready-to-fill PyTorch model class:
# myproject/models/MyModel/MyModel.py
import torch.nn as nn
from pyPhasesML.adapter.ModelTorchAdapter import ModelTorchAdapter
class MyModel(ModelTorchAdapter):
def define(self):
"""Build your architecture here. self.inputShape is set from config."""
self.model = nn.Sequential(
# TODO: replace with your architecture
nn.Flatten(),
nn.Linear(self.inputShape[0] * self.inputShape[1], 5),
)
def prepareY(self, y, validation=False):
y = super().prepareY(y, validation)
return y.long().squeeze(-1)
def getLossFunction(self):
return nn.CrossEntropyLoss(ignore_index=self.ignoreClassIndex)
def mapOutputForLoss(self, output):
return output.permute(0, 2, 1)
def prepareForScore(self, targets, prediction):
return targets, prediction→ Define a Model covers the full interface and all overridable methods.
→ For advanced use cases — overloading the training loop, self-supervised pretraining, two-stage training — see Define a Model.
trainingParameter — training loop config
trainingParameter:
maxEpochs: 100
lr: 0.001
batchSize: 32
optimizer: adams # adams, sgd, rmsprop
stopAfterNotImproving: 10 # early stopping patience
shuffle: True # whether to shuffle training data each epoch
validationMetrics:
- kappa # metric used for model selectionAll keys are consumed by pyPhasesML’s training loop.
Advanced options:
| Key | Description |
|---|---|
cyclicLearningRate |
Enable CLR schedule |
findCyclicLearningRate |
Run LR range finder before training |
batchSizeAccumulation |
Gradient accumulation steps (effective batch = batchSize × batchSizeAccumulation) |
weightDecay |
L2 regularization |
gradientClip |
Max gradient norm |
classWeights |
Per-class loss weights |
shuffle / shuffleSeed |
Shuffle training data |
→ Train for all keys and cross-validation usage.
fixedThreshold — classification thresholds
fixedThreshold: [0.5]Per-class probability threshold applied when converting model output to class predictions. Set to null (or omit) to use the ThresholdOptimisation phase, which finds optimal thresholds on the validation fold:
phases run ThresholdOptimisation
phases run EvalFor multi-task models, one threshold per task:
fixedThreshold: null # run ThresholdOptimisation instead
optimizeThresholdFor: [Arousal, LM]
optimizeOn: validation
thresholdMetric: [kappa, f1, f1, f1]→ Evaluate covers threshold optimization in detail.
manipulationAfterPredict — post-prediction steps
manipulationAfterPredict:
- name: toNumpyDataManipulation steps applied to (prediction, ground_truth) after the model inference, before metrics are computed. Common uses:
| Step | Purpose |
|---|---|
toNumpy |
Convert PyTorch tensors to numpy arrays |
deleteIgnored |
Remove timesteps where ground truth is -1 (the ignoreClassIndex), restoring original recording length after padding |
transposeX |
Transpose prediction axes |
hotDecode |
Convert one-hot predictions to class indices |
toEvent |
Convert per-sample predictions to event spans for event-level evaluation |
Run the pipeline
# Run training
phases run Training
# Run for a specific cross-validation fold
phases run Training --fold 2
# Run evaluation
phases run EvalReport # this will not trigger training, unless its set in the configThe CLI resolves phase dependencies automatically: Training will trigger Extract and BuildDataset first if their outputs are not cached.
Next steps
More details on each config section and advanced use cases are covered in the linked pages below. - Load Data — dataset plugins, multi-dataset training - Preprocess Signals — signal preprocessing steps - Manipulate Data — per-segment and batch steps - Define a Model — model architecture and adapters - Train — training loop and cross-validation - Evaluate — metrics, thresholds, clinical metrics - Config Reference — all config keys in one table