somnus-eeg 1.0.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. somnus_eeg-1.0.5/LICENSE +29 -0
  2. somnus_eeg-1.0.5/MANIFEST.in +2 -0
  3. somnus_eeg-1.0.5/PKG-INFO +179 -0
  4. somnus_eeg-1.0.5/README.md +144 -0
  5. somnus_eeg-1.0.5/pyproject.toml +73 -0
  6. somnus_eeg-1.0.5/setup.cfg +4 -0
  7. somnus_eeg-1.0.5/src/somnus/__init__.py +19 -0
  8. somnus_eeg-1.0.5/src/somnus/data/__init__.py +1 -0
  9. somnus_eeg-1.0.5/src/somnus/data/datasets.py +467 -0
  10. somnus_eeg-1.0.5/src/somnus/features/__init__.py +446 -0
  11. somnus_eeg-1.0.5/src/somnus/gui/__init__.py +2 -0
  12. somnus_eeg-1.0.5/src/somnus/gui/__main__.py +5 -0
  13. somnus_eeg-1.0.5/src/somnus/gui/app.py +1731 -0
  14. somnus_eeg-1.0.5/src/somnus/gui/core.py +744 -0
  15. somnus_eeg-1.0.5/src/somnus/models/__init__.py +5 -0
  16. somnus_eeg-1.0.5/src/somnus/models/model_somnus_1.0.json +551 -0
  17. somnus_eeg-1.0.5/src/somnus/predict.py +250 -0
  18. somnus_eeg-1.0.5/src/somnus/scorer/__init__.py +5 -0
  19. somnus_eeg-1.0.5/src/somnus/scorer/__main__.py +782 -0
  20. somnus_eeg-1.0.5/src/somnus/scorer/signal_utils.py +31 -0
  21. somnus_eeg-1.0.5/src/somnus/scorer/ui_rendering.py +419 -0
  22. somnus_eeg-1.0.5/src/somnus/scorer/video_handler.py +164 -0
  23. somnus_eeg-1.0.5/src/somnus/train/__init__.py +1 -0
  24. somnus_eeg-1.0.5/src/somnus/train/finetune.py +332 -0
  25. somnus_eeg-1.0.5/src/somnus_eeg.egg-info/PKG-INFO +179 -0
  26. somnus_eeg-1.0.5/src/somnus_eeg.egg-info/SOURCES.txt +28 -0
  27. somnus_eeg-1.0.5/src/somnus_eeg.egg-info/dependency_links.txt +1 -0
  28. somnus_eeg-1.0.5/src/somnus_eeg.egg-info/entry_points.txt +2 -0
  29. somnus_eeg-1.0.5/src/somnus_eeg.egg-info/requires.txt +17 -0
  30. somnus_eeg-1.0.5/src/somnus_eeg.egg-info/top_level.txt +1 -0
@@ -0,0 +1,29 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Somnus' Developers.
4
+ All rights reserved.
5
+
6
+ Redistribution and use in source and binary forms, with or without
7
+ modification, are permitted provided that the following conditions are met:
8
+
9
+ 1. Redistributions of source code must retain the above copyright notice, this
10
+ list of conditions and the following disclaimer.
11
+
12
+ 2. Redistributions in binary form must reproduce the above copyright notice,
13
+ this list of conditions and the following disclaimer in the documentation
14
+ and/or other materials provided with the distribution.
15
+
16
+ 3. Neither the name of the copyright holder nor the names of its
17
+ contributors may be used to endorse or promote products derived from
18
+ this software without specific prior written permission.
19
+
20
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,2 @@
1
+ # The released model weights ship inside the package.
2
+ recursive-include src/somnus/models *.json
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.4
2
+ Name: somnus-eeg
3
+ Version: 1.0.5
4
+ Summary: Sleep-state scoring (Wake/NREM/REM) for mouse EEG/EMG(+video): trained model, batch scorer, review GUI, and anchored fine-tuning
5
+ Author-email: Joshua Brenner <joshua.brenner@hhmi.org>, Matthew Caudill <mscaudill@gmail.com>
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/joshbrenner/somnus
8
+ Project-URL: Issues, https://github.com/joshbrenner/somnus/issues
9
+ Keywords: sleep,eeg,emg,polysomnography,sleep-scoring,mouse,hmm,machine-learning
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
15
+ Requires-Python: >=3.12
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy>=2.0
19
+ Requires-Dist: pandas>=2.2
20
+ Requires-Dist: scipy>=1.14
21
+ Requires-Dist: mne>=1.8
22
+ Requires-Dist: matplotlib>=3.9
23
+ Requires-Dist: openseize>=1.3
24
+ Requires-Dist: PySide6>=6.7
25
+ Requires-Dist: opencv-python>=4.10
26
+ Requires-Dist: av>=13.0
27
+ Requires-Dist: tables>=3.10
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest; extra == "dev"
30
+ Requires-Dist: build; extra == "dev"
31
+ Requires-Dist: twine; extra == "dev"
32
+ Requires-Dist: bumpver; extra == "dev"
33
+ Requires-Dist: check-manifest; extra == "dev"
34
+ Dynamic: license-file
35
+
36
+ <h1 align="center">
37
+ <img src="https://github.com/joshbrenner/somnus/raw/main/imgs/logo.png"
38
+ style="width:500px;height:auto;" alt="Somnus"/>
39
+ </h1>
40
+
41
+ ### Sleep-state scoring for mouse EEG/EMG (+ video)
42
+
43
+ Somnus assigns **Wake / NREM / REM** to every 4-second epoch of a mouse
44
+ polysomnography recording, using EEG, EMG, and — when available —
45
+ video-derived locomotion. It includes a trained logistic model, a batch scorer,
46
+ a review interface for correcting the model's output, and a fine-tuning step that
47
+ adapts the model to your dataset.
48
+
49
+ The design goal is not maximum accuracy on one rig. It is a system that **runs
50
+ on whatever data you have**, and that can be readily adapted to disease models
51
+ with degraded sleep architecture where heavier commercial scorers may fail altogether.
52
+
53
+ Features are computed in frequency tiers gated by each recording's measured bandwidth.
54
+ The temporal structure of sleep comes from an HMM/Viterbi model with a "transition resistance"
55
+ knob which allows you to adjust the frequency of state transitions. This can also be retrained on
56
+ data from your model organism.
57
+
58
+ ## Install
59
+
60
+ Install somnus in a virtual environment using conda, with a python version > 3.12:
61
+ ```bash
62
+ conda create -n somnus-eeg python=3.13 -y
63
+ conda activate somnus-eeg
64
+ pip install somnus-eeg
65
+ ```
66
+ ## The desktop GUI
67
+
68
+ To start the GUI, use:
69
+
70
+ ```bash
71
+ somnus-gui
72
+ ```
73
+
74
+ The GUI includes a five-tab workflow: **Project** (point it at a folder of recordings) →
75
+ **Score** (batch scoring with smoothing controls) → **Review & Relabel**
76
+ (whole-recording hypnogram with a confidence trace, plus a signal/video scorer
77
+ pre-loaded with the model's labels, so you correct rather than score from
78
+ scratch) → **Fine-tune** (adapt the model to your corrections) → **Evaluate**
79
+ (compare against the base model; export sleep-architecture statistics).
80
+
81
+ Two notes: **source data is never written to**, and
82
+ **the model never trains on its own output** — every epoch records where its
83
+ label came from, and only manually sourced labels can become training targets.
84
+
85
+ ## Alternative -- Score a recording in Python
86
+
87
+ ```python
88
+ from somnus import load_model
89
+ from somnus.predict import predict
90
+ from somnus.data.datasets import featurize
91
+
92
+ art = load_model() # the packaged v1.0 model
93
+ df = featurize({"recording": "myrec", "edf": "path/to/myrec.edf",
94
+ "dataset": "user", "group": "user", "subject": "m1",
95
+ "scored": None, "pkl": None},
96
+ eeg_chan=[1, 2, 3], emg_chan=4)
97
+ labels, proba = predict(art, df) # per-epoch states + probabilities
98
+ ```
99
+
100
+ `eeg_chan` and `emg_chan` set which channels are which, by number (counted
101
+ from 1) or by name. When several EEG channels are named, the best snr is used.
102
+
103
+ `predict(..., stickiness=...)` controls the temporal decode: `0` disables
104
+ smoothing, `1` uses the transition matrix as estimated, `>1` enforces longer
105
+ bouts.
106
+
107
+ ## Video tracking (optional)
108
+
109
+ If a recording has video with the animal's position tracked, Somnus adds a
110
+ locomotion feature. Pass the tracking file as `"pkl"` in the `featurize()`
111
+ entry, or put it beside the EDF and the GUI will find it.
112
+
113
+ One row per video frame, in frame order. Accepted formats:
114
+
115
+ | File | Contents |
116
+ |---|---|
117
+ | `.csv` | A DeepLabCut export, or any table with `x` and `y` columns |
118
+ | `.h5` | A DeepLabCut export (needs `pip install tables`) |
119
+ | `.pkl` | An `(n_frames, 2)` array of x/y, or a dict with a `coordinates` key |
120
+
121
+ A DeepLabCut file usually tracks many bodyparts. Somnus uses the one named
122
+ **`mouse_center`**; if the file tracks exactly one bodypart, it uses that.
123
+ Otherwise it stops rather than guess which point represents the animal — export
124
+ a `mouse_center` bodypart, or hand it a plain two-column `x,y` table instead.
125
+
126
+ **Filter low-confidence points yourself.** Somnus does not drop them, so a badly
127
+ tracked frame reads as real movement. Replace rejected points with `NaN` before
128
+ passing the file.
129
+
130
+ **Frame times are strongly suggested.** Somnus looks for a
131
+ `*_timestamps.npy` beside the tracking file with exactly one timestamp per row.
132
+ Cameras drop frames, so assuming a constant frame rate can misplace positions by
133
+ minutes; if no timestamps file is found, Somnus requires confirmation.
134
+
135
+ ## Fine-tuning
136
+
137
+ The performance loss on labeled epochs is minimized with a penalty that
138
+ pulls the weights toward the existing model. We use a parameter λ,
139
+ where "keep the shipped model" (λ→∞), and "train on my data
140
+ alone" (λ→0). The default λ value is chosen by cross-validation on your recordings,
141
+ so fine-tune versus retrain is decided by evidence.
142
+
143
+ ## Performance
144
+
145
+ Tested on held-out mice from a public multi-lab validation corpus —
146
+ 74 subjects, ~1.52 million labeled epochs — after training on 61,164 epochs
147
+ from six labs:
148
+
149
+ | metric | value |
150
+ |---|---|
151
+ | accuracy | 0.914 |
152
+ | balanced accuracy | 0.858 |
153
+ | Cohen's κ | 0.845 |
154
+
155
+ On in-house 5 kHz recordings (leave-one-recording-out): accuracy 0.975.
156
+
157
+ ## Limitations to keep in mind
158
+
159
+ - **REM scoring criteria differ between labs** so a general model necessarily compromises;
160
+ (F1 REM 0.728 vs >0.90 for Wake/NREM). Fine-tuning to your own data is the intended remedy!
161
+ - **Every recording must contain both sleep and wake.** The model depends on
162
+ z-scoring within each recording to accept a wide range of recording
163
+ conditions, so on a recording containing only one sleep/wake state — for
164
+ example a short session where the animal never sleeps — it fails completely.
165
+
166
+ A full write-up of the evaluation, caveats, and design rationale will
167
+ accompany the forthcoming preprint.
168
+
169
+ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for the module reference.
170
+
171
+ ## License
172
+
173
+ BSD 3-Clause. The trained model was fitted in part on a publicly available
174
+ multi-lab mouse polysomnography dataset. PSD estimation uses
175
+ [openseize](https://github.com/mscaudill/openseize).
176
+
177
+ ## Citation
178
+
179
+ A preprint is in preparation; until then, please cite this repository.
@@ -0,0 +1,144 @@
1
+ <h1 align="center">
2
+ <img src="https://github.com/joshbrenner/somnus/raw/main/imgs/logo.png"
3
+ style="width:500px;height:auto;" alt="Somnus"/>
4
+ </h1>
5
+
6
+ ### Sleep-state scoring for mouse EEG/EMG (+ video)
7
+
8
+ Somnus assigns **Wake / NREM / REM** to every 4-second epoch of a mouse
9
+ polysomnography recording, using EEG, EMG, and — when available —
10
+ video-derived locomotion. It includes a trained logistic model, a batch scorer,
11
+ a review interface for correcting the model's output, and a fine-tuning step that
12
+ adapts the model to your dataset.
13
+
14
+ The design goal is not maximum accuracy on one rig. It is a system that **runs
15
+ on whatever data you have**, and that can be readily adapted to disease models
16
+ with degraded sleep architecture where heavier commercial scorers may fail altogether.
17
+
18
+ Features are computed in frequency tiers gated by each recording's measured bandwidth.
19
+ The temporal structure of sleep comes from an HMM/Viterbi model with a "transition resistance"
20
+ knob which allows you to adjust the frequency of state transitions. This can also be retrained on
21
+ data from your model organism.
22
+
23
+ ## Install
24
+
25
+ Install somnus in a virtual environment using conda, with a python version > 3.12:
26
+ ```bash
27
+ conda create -n somnus-eeg python=3.13 -y
28
+ conda activate somnus-eeg
29
+ pip install somnus-eeg
30
+ ```
31
+ ## The desktop GUI
32
+
33
+ To start the GUI, use:
34
+
35
+ ```bash
36
+ somnus-gui
37
+ ```
38
+
39
+ The GUI includes a five-tab workflow: **Project** (point it at a folder of recordings) →
40
+ **Score** (batch scoring with smoothing controls) → **Review & Relabel**
41
+ (whole-recording hypnogram with a confidence trace, plus a signal/video scorer
42
+ pre-loaded with the model's labels, so you correct rather than score from
43
+ scratch) → **Fine-tune** (adapt the model to your corrections) → **Evaluate**
44
+ (compare against the base model; export sleep-architecture statistics).
45
+
46
+ Two notes: **source data is never written to**, and
47
+ **the model never trains on its own output** — every epoch records where its
48
+ label came from, and only manually sourced labels can become training targets.
49
+
50
+ ## Alternative -- Score a recording in Python
51
+
52
+ ```python
53
+ from somnus import load_model
54
+ from somnus.predict import predict
55
+ from somnus.data.datasets import featurize
56
+
57
+ art = load_model() # the packaged v1.0 model
58
+ df = featurize({"recording": "myrec", "edf": "path/to/myrec.edf",
59
+ "dataset": "user", "group": "user", "subject": "m1",
60
+ "scored": None, "pkl": None},
61
+ eeg_chan=[1, 2, 3], emg_chan=4)
62
+ labels, proba = predict(art, df) # per-epoch states + probabilities
63
+ ```
64
+
65
+ `eeg_chan` and `emg_chan` set which channels are which, by number (counted
66
+ from 1) or by name. When several EEG channels are named, the best snr is used.
67
+
68
+ `predict(..., stickiness=...)` controls the temporal decode: `0` disables
69
+ smoothing, `1` uses the transition matrix as estimated, `>1` enforces longer
70
+ bouts.
71
+
72
+ ## Video tracking (optional)
73
+
74
+ If a recording has video with the animal's position tracked, Somnus adds a
75
+ locomotion feature. Pass the tracking file as `"pkl"` in the `featurize()`
76
+ entry, or put it beside the EDF and the GUI will find it.
77
+
78
+ One row per video frame, in frame order. Accepted formats:
79
+
80
+ | File | Contents |
81
+ |---|---|
82
+ | `.csv` | A DeepLabCut export, or any table with `x` and `y` columns |
83
+ | `.h5` | A DeepLabCut export (needs `pip install tables`) |
84
+ | `.pkl` | An `(n_frames, 2)` array of x/y, or a dict with a `coordinates` key |
85
+
86
+ A DeepLabCut file usually tracks many bodyparts. Somnus uses the one named
87
+ **`mouse_center`**; if the file tracks exactly one bodypart, it uses that.
88
+ Otherwise it stops rather than guess which point represents the animal — export
89
+ a `mouse_center` bodypart, or hand it a plain two-column `x,y` table instead.
90
+
91
+ **Filter low-confidence points yourself.** Somnus does not drop them, so a badly
92
+ tracked frame reads as real movement. Replace rejected points with `NaN` before
93
+ passing the file.
94
+
95
+ **Frame times are strongly suggested.** Somnus looks for a
96
+ `*_timestamps.npy` beside the tracking file with exactly one timestamp per row.
97
+ Cameras drop frames, so assuming a constant frame rate can misplace positions by
98
+ minutes; if no timestamps file is found, Somnus requires confirmation.
99
+
100
+ ## Fine-tuning
101
+
102
+ The performance loss on labeled epochs is minimized with a penalty that
103
+ pulls the weights toward the existing model. We use a parameter λ,
104
+ where "keep the shipped model" (λ→∞), and "train on my data
105
+ alone" (λ→0). The default λ value is chosen by cross-validation on your recordings,
106
+ so fine-tune versus retrain is decided by evidence.
107
+
108
+ ## Performance
109
+
110
+ Tested on held-out mice from a public multi-lab validation corpus —
111
+ 74 subjects, ~1.52 million labeled epochs — after training on 61,164 epochs
112
+ from six labs:
113
+
114
+ | metric | value |
115
+ |---|---|
116
+ | accuracy | 0.914 |
117
+ | balanced accuracy | 0.858 |
118
+ | Cohen's κ | 0.845 |
119
+
120
+ On in-house 5 kHz recordings (leave-one-recording-out): accuracy 0.975.
121
+
122
+ ## Limitations to keep in mind
123
+
124
+ - **REM scoring criteria differ between labs** so a general model necessarily compromises;
125
+ (F1 REM 0.728 vs >0.90 for Wake/NREM). Fine-tuning to your own data is the intended remedy!
126
+ - **Every recording must contain both sleep and wake.** The model depends on
127
+ z-scoring within each recording to accept a wide range of recording
128
+ conditions, so on a recording containing only one sleep/wake state — for
129
+ example a short session where the animal never sleeps — it fails completely.
130
+
131
+ A full write-up of the evaluation, caveats, and design rationale will
132
+ accompany the forthcoming preprint.
133
+
134
+ See [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) for the module reference.
135
+
136
+ ## License
137
+
138
+ BSD 3-Clause. The trained model was fitted in part on a publicly available
139
+ multi-lab mouse polysomnography dataset. PSD estimation uses
140
+ [openseize](https://github.com/mscaudill/openseize).
141
+
142
+ ## Citation
143
+
144
+ A preprint is in preparation; until then, please cite this repository.
@@ -0,0 +1,73 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "somnus-eeg"
7
+ version = "1.0.5"
8
+ description = "Sleep-state scoring (Wake/NREM/REM) for mouse EEG/EMG(+video): trained model, batch scorer, review GUI, and anchored fine-tuning"
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "Joshua Brenner", email = "joshua.brenner@hhmi.org" },
12
+ { name = "Matthew Caudill", email = "mscaudill@gmail.com" },
13
+ ]
14
+ license = "BSD-3-Clause"
15
+ license-files = ["LICENSE"]
16
+ classifiers = [
17
+ "Development Status :: 5 - Production/Stable",
18
+ "Intended Audience :: Science/Research",
19
+ "Programming Language :: Python",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Scientific/Engineering :: Bio-Informatics",
22
+ ]
23
+ keywords = [
24
+ "sleep", "eeg", "emg", "polysomnography", "sleep-scoring",
25
+ "mouse", "hmm", "machine-learning",
26
+ ]
27
+ # openseize 1.3 requires 3.12+, and the feature pipeline requires openseize.
28
+ requires-python = ">=3.12"
29
+ # Floors are set a little below the versions the release was developed and
30
+ # verified against (see docs/DEVELOPMENT.md for the exact environment).
31
+ dependencies = [
32
+ "numpy >= 2.0",
33
+ "pandas >= 2.2",
34
+ "scipy >= 1.14",
35
+ "mne >= 1.8",
36
+ "matplotlib >= 3.9",
37
+ "openseize >= 1.3",
38
+ "PySide6 >= 6.7",
39
+ "opencv-python >= 4.10",
40
+ "av >= 13.0",
41
+ "tables >= 3.10",
42
+ ]
43
+
44
+ [project.optional-dependencies]
45
+ dev = ["pytest", "build", "twine", "bumpver", "check-manifest"]
46
+
47
+ [project.urls]
48
+ Homepage = "https://github.com/joshbrenner/somnus"
49
+ Issues = "https://github.com/joshbrenner/somnus/issues"
50
+
51
+ [project.gui-scripts]
52
+ somnus-gui = "somnus.gui.app:main"
53
+
54
+ [tool.setuptools.packages.find]
55
+ where = ["src"]
56
+
57
+ [tool.setuptools.package-data]
58
+ "somnus.models" = ["*.json"]
59
+
60
+ [tool.bumpver]
61
+ current_version = "1.0.5"
62
+ version_pattern = "MAJOR.MINOR.PATCH"
63
+ commit_message = "Bump version {old_version} -> {new_version}"
64
+ commit = true
65
+ tag = true
66
+ push = false
67
+
68
+ [tool.bumpver.file_patterns]
69
+ "pyproject.toml" = [
70
+ 'current_version = "{version}"',
71
+ 'version = "{version}"',
72
+ ]
73
+ "src/somnus/__init__.py" = ['__version__ = "{version}"']
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,19 @@
1
+ """Somnus — sleep-state scoring (Wake / NREM / REM) for mouse EEG/EMG(+video).
2
+
3
+ Quickstart::
4
+
5
+ from somnus import load_model
6
+ from somnus.predict import predict
7
+ art = load_model() # the packaged v1.0 model
8
+ labels, proba = predict(art, feature_df) # features from somnus.data.datasets.featurize()
9
+
10
+ The desktop application: ``somnus-gui`` (or ``python -m somnus.gui``).
11
+ """
12
+ __version__ = "1.0.5"
13
+
14
+
15
+ def __getattr__(name):
16
+ if name == "load_model":
17
+ from somnus.predict import load_model
18
+ return load_model
19
+ raise AttributeError(f"module 'somnus' has no attribute {name!r}")
@@ -0,0 +1 @@
1
+ """Per-recording featurization: EDF (+ optional scoring, video) -> epoch table."""