misleep 0.3.0__tar.gz → 0.3.2__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.
Potentially problematic release.
This version of misleep might be problematic. Click here for more details.
- {misleep-0.3.0 → misleep-0.3.2}/CHANGELOG.md +68 -0
- {misleep-0.3.0/src/misleep.egg-info → misleep-0.3.2}/PKG-INFO +10 -12
- {misleep-0.3.0 → misleep-0.3.2}/README.md +4 -6
- {misleep-0.3.0 → misleep-0.3.2}/docs/api_reference.md +6 -3
- {misleep-0.3.0 → misleep-0.3.2}/docs/config_file.md +4 -4
- {misleep-0.3.0 → misleep-0.3.2}/docs/getting_started.md +8 -9
- misleep-0.3.2/docs/imgs/gui/main_light.png +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/docs/index.md +4 -0
- {misleep-0.3.0 → misleep-0.3.2}/pyproject.toml +14 -7
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/__init__.py +1 -1
- misleep-0.3.2/src/misleep/_compat.py +36 -0
- misleep-0.3.2/src/misleep/analysis/auto_stage.py +176 -0
- misleep-0.3.2/src/misleep/analysis/autostage/__init__.py +24 -0
- misleep-0.3.2/src/misleep/analysis/autostage/benchmark.py +186 -0
- misleep-0.3.2/src/misleep/analysis/autostage/features.py +368 -0
- misleep-0.3.2/src/misleep/analysis/autostage/hmm.py +110 -0
- misleep-0.3.2/src/misleep/analysis/autostage/model.py +27 -0
- misleep-0.3.2/src/misleep/analysis/autostage/postprocess.py +52 -0
- misleep-0.3.2/src/misleep/analysis/models/benchmark_models.pkl +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/analysis/transformer/__init__.py +3 -3
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/analysis/transformer/inference.py +3 -3
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/config/__init__.py +4 -3
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/config/default_config.ini +2 -2
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/data/midata.py +15 -5
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/app.py +26 -19
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/dialogs.py +73 -15
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/main_window.py +2834 -2699
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/style.py +20 -7
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/about.ui +3 -3
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/about_ui.py +2 -2
- misleep-0.3.2/src/misleep/gui/uis/auto_stage_lightGBM_dialog.ui +209 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/auto_stage_lightGBM_dialog_ui.py +104 -46
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/io/annotation.py +95 -18
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/io/base.py +2 -0
- {misleep-0.3.0 → misleep-0.3.2/src/misleep.egg-info}/PKG-INFO +10 -12
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep.egg-info/SOURCES.txt +8 -7
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep.egg-info/entry_points.txt +3 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep.egg-info/requires.txt +6 -2
- {misleep-0.3.0 → misleep-0.3.2}/tests/test_analysis.py +1 -1
- {misleep-0.3.0 → misleep-0.3.2}/tests/test_data.py +53 -0
- {misleep-0.3.0 → misleep-0.3.2}/tests/test_gui.py +1 -1
- {misleep-0.3.0 → misleep-0.3.2}/tests/test_io.py +30 -0
- misleep-0.3.0/docs/imgs/gui/main_dark.png +0 -0
- misleep-0.3.0/docs/imgs/gui/main_light.png +0 -0
- misleep-0.3.0/src/misleep/analysis/auto_stage.py +0 -158
- misleep-0.3.0/src/misleep/analysis/models/P30_EEG_F_lightgbm.pkl +0 -0
- misleep-0.3.0/src/misleep/analysis/models/P30_EEG_P_lightgbm.pkl +0 -0
- misleep-0.3.0/src/misleep/analysis/models/ado_EEG_F_lightgbm.pkl +0 -0
- misleep-0.3.0/src/misleep/analysis/models/ado_EEG_P_lightgbm.pkl +0 -0
- misleep-0.3.0/src/misleep/analysis/models/adult_EEG_F_lightgbm.pkl +0 -0
- misleep-0.3.0/src/misleep/analysis/models/adult_EEG_P_lightgbm.pkl +0 -0
- misleep-0.3.0/src/misleep/gui/uis/auto_stage_lightGBM_dialog.ui +0 -124
- {misleep-0.3.0 → misleep-0.3.2}/LICENSE +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/MANIFEST.in +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/docs/data_formats.md +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/docs/developer_guide.md +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/docs/imgs/entire_logo.png +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/docs/imgs/logo.png +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/docs/user_guide.md +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/examples/auto_stage_example.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/examples/basic_usage.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/examples/extend_io_example.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/setup.cfg +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/__main__.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/analysis/__init__.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/analysis/detection.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/analysis/features.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/analysis/models/__init__.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/analysis/transformer/checkpoints/CausalTransformer_best.pt +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/analysis/transformer/checkpoints/__init__.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/analysis/transformer/configs.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/analysis/transformer/data.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/analysis/transformer/models.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/analysis/transformer/preprocessing.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/data/__init__.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/data/annotation.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/__init__.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/config_dialog.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/event_filters.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/qt_utils.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/resources/__init__.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/resources/entire_logo.png +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/resources/logo.png +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/resources/misleep.ico +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/resources/misleep.qrc +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/resources/misleep_rc.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/spec_window.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/SWA_detect_dialog.ui +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/SWA_detect_dialog_ui.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/__init__.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/auto_stage_causalTransformer_dialog.ui +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/auto_stage_causalTransformer_dialog_ui.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/horizontal_line_dialog.ui +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/horizontal_line_dialog_ui.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/label_dialog.ui +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/label_dialog_ui.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/main_window.ui +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/main_window_ui.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/save_data_dialog.ui +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/save_data_dialog_ui.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/spec_window.ui +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/spec_window_ui.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/spindle_detect_dialog.ui +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/spindle_detect_dialog_ui.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/state_spectral_dialog.ui +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/state_spectral_dialog_ui.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/transfer_result_dialog.ui +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/uis/transfer_result_dialog_ui.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/gui/workers.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/io/__init__.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/io/array.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/io/edf.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/io/mat.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/logger.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/preprocessing/__init__.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/preprocessing/artifacts.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/preprocessing/filtering.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/preprocessing/segment.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/preprocessing/spectral.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/utils/__init__.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/utils/annotation.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/utils/entropy.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/utils/misc.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/utils/time_utils.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/viz/__init__.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/viz/hypnogram.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/viz/signals.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep/viz/spectral.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep.egg-info/dependency_links.txt +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/src/misleep.egg-info/top_level.txt +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/tests/test_preprocessing.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/tests/test_segment.py +0 -0
- {misleep-0.3.0 → misleep-0.3.2}/tests/test_viz_config.py +0 -0
|
@@ -5,6 +5,74 @@ All notable changes to MiSleep are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.2] — 2026-08-24
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Filtering broke on fractional sampling frequencies**: `MiData.add()` (used
|
|
13
|
+
by `MiData.filter()`) recomputed the duration with
|
|
14
|
+
`math.floor(len(signal) / sf)` from a signal that had already been
|
|
15
|
+
truncated to `int(duration * sf)` samples. When `sf` is a decimal (e.g.
|
|
16
|
+
`99.9927` Hz), that came out one second short, so filtering either raised a
|
|
17
|
+
spurious "new signal is shorter" error or produced a channel whose length /
|
|
18
|
+
duration did not match the original signal, which then failed the signal /
|
|
19
|
+
label consistency checks in the GUI. The check now compares sample counts
|
|
20
|
+
against `int(duration * sf)` directly, so filtering preserves the sample
|
|
21
|
+
count and duration for any positive `sf` while still rejecting genuinely
|
|
22
|
+
short signals.
|
|
23
|
+
|
|
24
|
+
## [0.3.1] — 2026-08-18
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- **Epoch / BIDS annotation import**: the CSV/TSV annotation loader now
|
|
29
|
+
understands `onset` / `duration` / `stage` (BIDS `events.tsv`),
|
|
30
|
+
headerless `[epoch index, epoch second, epoch label]` and
|
|
31
|
+
`[epoch second, epoch label]` tables (with or without a header row), on
|
|
32
|
+
top of the existing `start`/`end`/`state` and per-second layouts.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- **Faster startup**: the one-time warm-up (scipy imports, matplotlib font
|
|
37
|
+
cache) now runs in a background thread so the window appears instantly,
|
|
38
|
+
and the redundant `SetProcessDpiAwareness` calls were removed (Qt6
|
|
39
|
+
manages its own per-monitor DPI context - the old call produced an
|
|
40
|
+
"Access is denied" warning).
|
|
41
|
+
- **Hypnogram layout**: the initial hypnogram strip is shorter (drag the
|
|
42
|
+
splitter to resize), the figure now fills the whole strip even without
|
|
43
|
+
the confidence chart, and the axis margins were widened so the time
|
|
44
|
+
tick labels are always visible (no overflow below the panel).
|
|
45
|
+
- **Auto-staging defaults**: the low-confidence rate now defaults to
|
|
46
|
+
**0.8** and the HMM temperature to **0.1**. The HMM decoding is
|
|
47
|
+
confidence-based: the LightGBM probabilities drive the emissions and
|
|
48
|
+
the learned transition matrix only constrains state transitions.
|
|
49
|
+
|
|
50
|
+
- **LightGBM auto-staging rewritten on the benchmark models**
|
|
51
|
+
(`benchmark_models.pkl`, 6 channel-combo models). Every mouse age and
|
|
52
|
+
EEG site uses the same model; the dialog lets the user pick the EEG
|
|
53
|
+
channel, an optional EMG channel and an optional ACC channel (ACC
|
|
54
|
+
requires EMG), the EEG site, the age group, the HMM temperature and the
|
|
55
|
+
**low-confidence rate**. Predictions are decoded with a learned 3-state
|
|
56
|
+
HMM and cover the **whole recording** (the first window and the tail
|
|
57
|
+
take the nearest epoch). The per-epoch **confidence** is attached to the
|
|
58
|
+
annotation and drawn as a separate line chart below the hypnogram with a
|
|
59
|
+
dashed threshold line - static, so page flips stay fast and label edits
|
|
60
|
+
never recompute it. The *cover current* checkbox only fills the INIT
|
|
61
|
+
states, keeping already-scored labels.
|
|
62
|
+
|
|
63
|
+
- **Python support**: the package now supports **Python 3.8 through 3.14**
|
|
64
|
+
(`requires-python >= 3.8`); annotations and resource loading were made
|
|
65
|
+
3.8-compatible, and the GUI extra pins the last 3.8-compatible PySide6
|
|
66
|
+
line on older Pythons.
|
|
67
|
+
- **Dependencies**: the packaging metadata now reflects the complete set
|
|
68
|
+
of runtime prerequisites, and **PySide6 moved into the base
|
|
69
|
+
dependencies** - a plain `pip install misleep` installs the GUI and all
|
|
70
|
+
core packages automatically (no separate PySide6 install needed).
|
|
71
|
+
LightGBM / torch remain optional extras.
|
|
72
|
+
- **Documentation**: bright theme with a light header (readable
|
|
73
|
+
black-on-transparent logo), full-width MiSleep banner on the home page,
|
|
74
|
+
single GUI preview screenshot.
|
|
75
|
+
|
|
8
76
|
## [0.3.0] — 2026-08-18
|
|
9
77
|
|
|
10
78
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: misleep
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: MiSleep: Mice sleep EEG/EMG visualization, scoring, and analysis
|
|
5
5
|
Author-email: Xueqiang Wang <swang9194@gmail.com>
|
|
6
6
|
Maintainer-email: Xueqiang Wang <swang9194@gmail.com>
|
|
@@ -43,6 +43,7 @@ Classifier: Intended Audience :: Science/Research
|
|
|
43
43
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
44
44
|
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
45
45
|
Classifier: Programming Language :: Python :: 3
|
|
46
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
46
47
|
Classifier: Programming Language :: Python :: 3.9
|
|
47
48
|
Classifier: Programming Language :: Python :: 3.10
|
|
48
49
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -53,7 +54,7 @@ Classifier: License :: OSI Approved :: BSD License
|
|
|
53
54
|
Classifier: Operating System :: Microsoft :: Windows
|
|
54
55
|
Classifier: Operating System :: POSIX :: Linux
|
|
55
56
|
Classifier: Operating System :: MacOS
|
|
56
|
-
Requires-Python: >=3.
|
|
57
|
+
Requires-Python: >=3.8
|
|
57
58
|
Description-Content-Type: text/markdown
|
|
58
59
|
License-File: LICENSE
|
|
59
60
|
Requires-Dist: numpy>=1.20
|
|
@@ -65,19 +66,18 @@ Requires-Dist: mat73>=0.60
|
|
|
65
66
|
Requires-Dist: openpyxl>=3.0
|
|
66
67
|
Requires-Dist: joblib>=1.1
|
|
67
68
|
Requires-Dist: scikit-learn>=1.0
|
|
69
|
+
Requires-Dist: PySide6>=6.6; python_version >= "3.9"
|
|
70
|
+
Requires-Dist: PySide6<6.6,>=6.4; python_version < "3.9"
|
|
68
71
|
Provides-Extra: gui
|
|
69
|
-
Requires-Dist: PySide6>=6.6; extra == "gui"
|
|
70
72
|
Provides-Extra: analysis
|
|
71
73
|
Requires-Dist: lightgbm>=3.3; extra == "analysis"
|
|
72
74
|
Provides-Extra: transformer
|
|
73
75
|
Requires-Dist: torch>=2.0; extra == "transformer"
|
|
74
76
|
Provides-Extra: full
|
|
75
|
-
Requires-Dist: PySide6>=6.6; extra == "full"
|
|
76
77
|
Requires-Dist: lightgbm>=3.3; extra == "full"
|
|
77
78
|
Provides-Extra: dev
|
|
78
79
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
79
80
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
80
|
-
Dynamic: license-file
|
|
81
81
|
|
|
82
82
|
# MiSleep
|
|
83
83
|
|
|
@@ -122,19 +122,17 @@ The name *MiSleep* comes from "**Mi**ce **Sleep**" and sounds like
|
|
|
122
122
|
## Installation
|
|
123
123
|
|
|
124
124
|
```bash
|
|
125
|
-
#
|
|
125
|
+
# Base install - includes everything (data, I/O, preprocessing, analysis,
|
|
126
|
+
# visualization AND the PySide6 GUI)
|
|
126
127
|
pip install misleep
|
|
127
128
|
|
|
128
|
-
# With the
|
|
129
|
-
pip install "misleep[gui]"
|
|
130
|
-
|
|
131
|
-
# With everything, including the LightGBM auto-staging model
|
|
129
|
+
# With the LightGBM auto-staging model as well
|
|
132
130
|
pip install "misleep[full]"
|
|
133
131
|
|
|
134
132
|
# For development / contributing
|
|
135
133
|
git clone https://github.com/BryanWang0702/MiSleep.git
|
|
136
134
|
cd misleepv3
|
|
137
|
-
pip install -e ".[
|
|
135
|
+
pip install -e ".[analysis,dev]"
|
|
138
136
|
```
|
|
139
137
|
|
|
140
138
|
> **Note on PyTorch:** the causal-transformer auto-staging model requires
|
|
@@ -41,19 +41,17 @@ The name *MiSleep* comes from "**Mi**ce **Sleep**" and sounds like
|
|
|
41
41
|
## Installation
|
|
42
42
|
|
|
43
43
|
```bash
|
|
44
|
-
#
|
|
44
|
+
# Base install - includes everything (data, I/O, preprocessing, analysis,
|
|
45
|
+
# visualization AND the PySide6 GUI)
|
|
45
46
|
pip install misleep
|
|
46
47
|
|
|
47
|
-
# With the
|
|
48
|
-
pip install "misleep[gui]"
|
|
49
|
-
|
|
50
|
-
# With everything, including the LightGBM auto-staging model
|
|
48
|
+
# With the LightGBM auto-staging model as well
|
|
51
49
|
pip install "misleep[full]"
|
|
52
50
|
|
|
53
51
|
# For development / contributing
|
|
54
52
|
git clone https://github.com/BryanWang0702/MiSleep.git
|
|
55
53
|
cd misleepv3
|
|
56
|
-
pip install -e ".[
|
|
54
|
+
pip install -e ".[analysis,dev]"
|
|
57
55
|
```
|
|
58
56
|
|
|
59
57
|
> **Note on PyTorch:** the causal-transformer auto-staging model requires
|
|
@@ -119,12 +119,15 @@ Default state map: `{1: 'NREM', 2: 'REM', 3: 'Wake', 4: 'Init'}`.
|
|
|
119
119
|
|
|
120
120
|
### Automatic staging
|
|
121
121
|
|
|
122
|
-
* `auto_stage_gbm(EEG, EMG, label, sf, EEG_channel='F', mouse_age='adult'
|
|
123
|
-
→ list of per-second states
|
|
122
|
+
* `auto_stage_gbm(EEG, EMG=None, label, sf, EEG_channel='F', mouse_age='adult',
|
|
123
|
+
ACC=None, return_probs=False)` → list of per-second states (plus per-epoch
|
|
124
|
+
confidence when `return_probs=True`) — LightGBM auto staging with the
|
|
125
|
+
benchmark models (all ages use the same model; EMG and ACC are optional,
|
|
126
|
+
ACC requires EMG).
|
|
124
127
|
* `result_constraints(pred_prob)` → list — smooth/constrain raw model
|
|
125
128
|
probabilities into state labels.
|
|
126
129
|
* `model_path(mouse_age='adult', EEG_channel='F')` → Path — packaged
|
|
127
|
-
|
|
130
|
+
benchmark model path.
|
|
128
131
|
* `misleep.analysis.transformer.auto_stage_llm(EEG, EMG, label=None,
|
|
129
132
|
config=None)` → list — transformer auto staging (requires torch).
|
|
130
133
|
* `misleep.analysis.transformer.AutoStageConfig` — dataclass of
|
|
@@ -23,8 +23,8 @@ wipe your personal settings. Open the user file from the GUI via
|
|
|
23
23
|
|
|
24
24
|
| key | default | meaning |
|
|
25
25
|
|-----|---------|---------|
|
|
26
|
-
| `version` | `v0.3.
|
|
27
|
-
| `updatetime` | `
|
|
26
|
+
| `version` | `v0.3.2` | displayed in the About dialog |
|
|
27
|
+
| `updatetime` | `2026/08/24` | displayed in the About dialog |
|
|
28
28
|
| `marker` | `['half_signal', 'first REM', ...]` | marker label list (label picker) |
|
|
29
29
|
| `startend` | `['high_theta', 'REM', ...]` | start-end label list (label picker) |
|
|
30
30
|
| `statemap` | `{"1": "NREM", "2": "REM", "3": "Wake", "4": "INIT", ...}` | state code -> name mapping (JSON) |
|
|
@@ -48,8 +48,8 @@ wipe your personal settings. Open the user file from the GUI via
|
|
|
48
48
|
|
|
49
49
|
```ini
|
|
50
50
|
[gui]
|
|
51
|
-
version = v0.3.
|
|
52
|
-
updatetime =
|
|
51
|
+
version = v0.3.2
|
|
52
|
+
updatetime = 2026/08/24
|
|
53
53
|
marker = ['half_signal', 'first REM', 'WindEEG', 'W-R', 'maker']
|
|
54
54
|
startend = ['high_theta', 'REM', 'Wake', 'Spindle', 'SWA', 'start end label', 'start end label']
|
|
55
55
|
statemap = {"1": "NREM", "2": "REM", "3": "Wake", "4": "INIT", "5": "IS", "6": "MicroArousal"}
|
|
@@ -14,19 +14,20 @@
|
|
|
14
14
|
## Installation
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
# From PyPI
|
|
17
|
+
# From PyPI - the base install already includes the PySide6 GUI and all
|
|
18
|
+
# core dependencies
|
|
18
19
|
pip install misleep
|
|
19
20
|
|
|
20
|
-
#
|
|
21
|
-
pip install "misleep[gui]"
|
|
22
|
-
|
|
23
|
-
# Everything for a full experience (GUI + LightGBM auto staging)
|
|
21
|
+
# Everything for a full experience (adds LightGBM auto staging)
|
|
24
22
|
pip install "misleep[full]"
|
|
25
23
|
|
|
24
|
+
# Causal-transformer auto staging (PyTorch, not on all platforms)
|
|
25
|
+
pip install "misleep[transformer]"
|
|
26
|
+
|
|
26
27
|
# Development install from the repository
|
|
27
28
|
git clone https://github.com/BryanWang0702/MiSleep.git
|
|
28
29
|
cd misleepv3
|
|
29
|
-
pip install -e ".[
|
|
30
|
+
pip install -e ".[analysis,dev]"
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
> **Note for Apple Silicon (macOS):** `torch` and `lightgbm` ship official
|
|
@@ -56,9 +57,7 @@ on the right. A light and a dark theme are available
|
|
|
56
57
|
(`Ctrl+Shift+T` to toggle, or Settings → General → Theme /
|
|
57
58
|
Color scheme).
|
|
58
59
|
|
|
59
|
-

|
|
60
|
+

|
|
62
61
|
|
|
63
62
|
### Opening files from the command line
|
|
64
63
|
|
|
Binary file
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# MiSleep documentation
|
|
2
2
|
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="imgs/entire_logo.png" alt="MiSleep" width="80%"/>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
3
7
|
MiSleep is an open-source Python toolbox for **mice sleep EEG/EMG**
|
|
4
8
|
visualization, scoring, and analysis. This documentation covers both the
|
|
5
9
|
desktop GUI and the Python library.
|
|
@@ -4,10 +4,10 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "misleep"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.2"
|
|
8
8
|
description = "MiSleep: Mice sleep EEG/EMG visualization, scoring, and analysis"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
11
|
license = { file = "LICENSE" }
|
|
12
12
|
authors = [
|
|
13
13
|
{ name = "Xueqiang Wang", email = "swang9194@gmail.com" },
|
|
@@ -25,6 +25,7 @@ classifiers = [
|
|
|
25
25
|
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
26
26
|
"Topic :: Scientific/Engineering :: Medical Science Apps.",
|
|
27
27
|
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.8",
|
|
28
29
|
"Programming Language :: Python :: 3.9",
|
|
29
30
|
"Programming Language :: Python :: 3.10",
|
|
30
31
|
"Programming Language :: Python :: 3.11",
|
|
@@ -46,13 +47,16 @@ dependencies = [
|
|
|
46
47
|
"openpyxl>=3.0",
|
|
47
48
|
"joblib>=1.1",
|
|
48
49
|
"scikit-learn>=1.0",
|
|
50
|
+
# GUI toolkit - part of the base install so `pip install misleep` just
|
|
51
|
+
# works (PySide6 >= 6.6 needs Python 3.9+; 3.8 gets the 6.5 line)
|
|
52
|
+
"PySide6>=6.6; python_version >= '3.9'",
|
|
53
|
+
"PySide6>=6.4,<6.6; python_version < '3.9'",
|
|
49
54
|
]
|
|
50
55
|
|
|
51
56
|
[project.optional-dependencies]
|
|
52
|
-
# PySide6
|
|
53
|
-
gui
|
|
54
|
-
|
|
55
|
-
]
|
|
57
|
+
# The GUI (PySide6) is now part of the base dependencies; this empty extra
|
|
58
|
+
# is kept so existing `misleep[gui]` install commands keep working.
|
|
59
|
+
gui = []
|
|
56
60
|
# Automatic sleep staging with the LightGBM model
|
|
57
61
|
analysis = [
|
|
58
62
|
"lightgbm>=3.3",
|
|
@@ -63,7 +67,6 @@ transformer = [
|
|
|
63
67
|
]
|
|
64
68
|
# Everything needed for a full MiSleep experience
|
|
65
69
|
full = [
|
|
66
|
-
"PySide6>=6.6",
|
|
67
70
|
"lightgbm>=3.3",
|
|
68
71
|
]
|
|
69
72
|
# Tooling for developers / contributors
|
|
@@ -81,6 +84,10 @@ Documentation = "https://bryanwang.cn/MiSleep/"
|
|
|
81
84
|
[project.scripts]
|
|
82
85
|
misleep = "misleep.gui.app:main"
|
|
83
86
|
|
|
87
|
+
# Windowless launcher (no console window) - creates misleepw.exe on Windows
|
|
88
|
+
[project.gui-scripts]
|
|
89
|
+
misleepw = "misleep.gui.app:main"
|
|
90
|
+
|
|
84
91
|
[project.entry-points."misleep.signal_readers"]
|
|
85
92
|
mat = "misleep.io.mat:load_mat"
|
|
86
93
|
edf = "misleep.io.edf:load_edf"
|
|
@@ -22,7 +22,7 @@ works without PySide6; import ``misleep.gui`` explicitly when needed.
|
|
|
22
22
|
from importlib import import_module
|
|
23
23
|
|
|
24
24
|
__author__ = "Xueqiang Wang <swang9194@gmail.com>"
|
|
25
|
-
__version__ = "0.3.
|
|
25
|
+
__version__ = "0.3.2"
|
|
26
26
|
|
|
27
27
|
# Importing ``misleep`` used to import scipy, pandas, sklearn and matplotlib
|
|
28
28
|
# immediately. The GUI necessarily imports some of those later, but making
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
"""Compatibility helpers so MiSleep runs on Python 3.8 through 3.14.
|
|
3
|
+
|
|
4
|
+
Only packaging/resource helpers live here; there is no application logic.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def resource_dir(package: str) -> Path:
|
|
11
|
+
"""Return the filesystem directory of a package (3.8-compatible)."""
|
|
12
|
+
try:
|
|
13
|
+
from importlib.resources import files
|
|
14
|
+
|
|
15
|
+
return Path(str(files(package)))
|
|
16
|
+
except (ImportError, AttributeError): # pragma: no cover - Python 3.8
|
|
17
|
+
import importlib
|
|
18
|
+
|
|
19
|
+
return Path(str(importlib.import_module(package).__path__[0]))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def resource_path(package: str, name: str) -> Path:
|
|
23
|
+
"""Return the filesystem path of a package resource file.
|
|
24
|
+
|
|
25
|
+
``importlib.resources.files`` (Python 3.9+) is preferred; on Python
|
|
26
|
+
3.8 the older ``importlib.resources.path`` is used instead.
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
from importlib.resources import files
|
|
30
|
+
|
|
31
|
+
return Path(str(files(package).joinpath(name)))
|
|
32
|
+
except (ImportError, AttributeError): # pragma: no cover - Python 3.8
|
|
33
|
+
import importlib.resources
|
|
34
|
+
|
|
35
|
+
with importlib.resources.path(package, name) as path:
|
|
36
|
+
return Path(str(path))
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
"""Automatic sleep staging with the benchmark LightGBM models.
|
|
3
|
+
|
|
4
|
+
The packaged ``benchmark_models.pkl`` contains six LightGBM classifiers
|
|
5
|
+
(one per channel combo: EEG site x optional EMG x optional ACC), each
|
|
6
|
+
combined with a per-recording z-score normalisation, a learned 3-state HMM
|
|
7
|
+
and class priors. **Every mouse age and EEG site use the same model file**;
|
|
8
|
+
the age / site arguments are kept only for API compatibility.
|
|
9
|
+
|
|
10
|
+
The pipeline (adapted from the rodent-autostage benchmark):
|
|
11
|
+
|
|
12
|
+
1. feature extraction on 5 s epochs (STFT band powers + time-domain
|
|
13
|
+
features + EEG-EMG coherence, W = 10 s window at 1 s stride),
|
|
14
|
+
2. per-recording z-score normalisation,
|
|
15
|
+
3. LightGBM class probabilities,
|
|
16
|
+
4. HMM / Viterbi decoding with a softmax temperature,
|
|
17
|
+
5. physiologically constrained smoothing (Init -> Wake, REM never after
|
|
18
|
+
Wake, single-epoch flip removal),
|
|
19
|
+
6. per-second expansion of the per-epoch states.
|
|
20
|
+
|
|
21
|
+
Besides the per-second labels, the per-epoch probability of the predicted
|
|
22
|
+
state is returned (the *confidence* used by the GUI to mark low-confidence
|
|
23
|
+
regions in the hypnogram).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import copy
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
import numpy as np
|
|
32
|
+
|
|
33
|
+
from misleep._compat import resource_dir
|
|
34
|
+
from misleep.logger import logger
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _model_dir() -> Path:
|
|
38
|
+
"""Return the directory containing the packaged LightGBM models."""
|
|
39
|
+
return resource_dir("misleep.analysis.models")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def model_path(mouse_age="adult", EEG_channel="F") -> Path:
|
|
43
|
+
"""Return the path of the packaged benchmark models.
|
|
44
|
+
|
|
45
|
+
A single model file serves every mouse age and EEG site, so
|
|
46
|
+
``mouse_age`` and ``EEG_channel`` are accepted for API compatibility
|
|
47
|
+
only.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
mouse_age : str
|
|
52
|
+
Accepted for API compatibility (ignored).
|
|
53
|
+
EEG_channel : str
|
|
54
|
+
Accepted for API compatibility (ignored).
|
|
55
|
+
|
|
56
|
+
Returns
|
|
57
|
+
-------
|
|
58
|
+
Path
|
|
59
|
+
"""
|
|
60
|
+
return _model_dir() / "benchmark_models.pkl"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def result_constraints(pred_prob):
|
|
64
|
+
"""Post-process raw model probabilities into smooth state labels.
|
|
65
|
+
|
|
66
|
+
Applies the following constraints:
|
|
67
|
+
|
|
68
|
+
1. A REM probability below 0.15 is overridden.
|
|
69
|
+
2. REM directly after Wake is set to NREM.
|
|
70
|
+
3. State 4 (Init) is set to Wake.
|
|
71
|
+
4. A single epoch between two identical states takes that state.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
pred_prob : ndarray
|
|
76
|
+
Model output probabilities, shape ``(n_windows, n_classes)``.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
list of int
|
|
81
|
+
Per-window predicted state codes (1-indexed).
|
|
82
|
+
"""
|
|
83
|
+
pred_prob = copy.deepcopy(pred_prob)
|
|
84
|
+
pred_label = [each + 1 for each in np.argmax(pred_prob, axis=1)]
|
|
85
|
+
pred_label = [2 if each[1] > 0.15 else pred_label[idx]
|
|
86
|
+
for idx, each in enumerate(pred_prob)]
|
|
87
|
+
|
|
88
|
+
for idx in range(1, len(pred_label) - 1):
|
|
89
|
+
label_ = pred_label[idx]
|
|
90
|
+
|
|
91
|
+
if label_ == 4:
|
|
92
|
+
pred_label[idx] = 3
|
|
93
|
+
if label_ == 3 and pred_label[idx + 1] == 2: # REM after Wake
|
|
94
|
+
pred_label[idx + 1] = 1
|
|
95
|
+
if pred_label[idx - 1] == pred_label[idx + 1] and pred_label[idx] != 3:
|
|
96
|
+
pred_label[idx] = pred_label[idx - 1]
|
|
97
|
+
|
|
98
|
+
return pred_label
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def auto_stage_gbm(EEG, EMG, label, sf, EEG_channel="F", mouse_age="adult",
|
|
102
|
+
ACC=None, return_probs=False, temperature=0.1):
|
|
103
|
+
"""Auto-stage a recording with the benchmark LightGBM models.
|
|
104
|
+
|
|
105
|
+
All mouse ages use the same model; ``mouse_age`` is kept for API
|
|
106
|
+
compatibility. The channel combo (EEG site x EMG x ACC) selects which
|
|
107
|
+
of the six benchmark models is used - ACC requires EMG.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
EEG : ndarray
|
|
112
|
+
EEG signal.
|
|
113
|
+
EMG : ndarray or None
|
|
114
|
+
EMG signal (optional; pass ``None`` to run EEG-only).
|
|
115
|
+
label : list
|
|
116
|
+
Reference labels (kept for API compatibility; not used by the model).
|
|
117
|
+
sf : float
|
|
118
|
+
Sampling frequency of the signals.
|
|
119
|
+
EEG_channel : {'F', 'P'}
|
|
120
|
+
EEG electrode site (frontal or parietal). Default is ``'F'``.
|
|
121
|
+
mouse_age : str
|
|
122
|
+
Accepted for API compatibility (ignored - all ages use the same
|
|
123
|
+
model).
|
|
124
|
+
ACC : ndarray or None
|
|
125
|
+
ACC signal (optional; requires ``EMG``).
|
|
126
|
+
return_probs : bool
|
|
127
|
+
Also return the per-epoch confidence (probability of the predicted
|
|
128
|
+
state) as a numpy array.
|
|
129
|
+
temperature : float
|
|
130
|
+
HMM softmax temperature (lower sharpens the transition structure).
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
list of int, or (list of int, ndarray) when ``return_probs`` is True.
|
|
135
|
+
Per-second predicted state codes (1 = NREM, 2 = REM, 3 = Wake),
|
|
136
|
+
plus the per-epoch confidence when requested. The prediction may
|
|
137
|
+
be a few seconds shorter than the recording (window drop).
|
|
138
|
+
"""
|
|
139
|
+
from misleep.analysis.autostage import benchmark as _bm
|
|
140
|
+
|
|
141
|
+
if EEG is None or len(np.asarray(EEG)) / sf < _bm.W + _bm.EPOCH_S:
|
|
142
|
+
raise ValueError(
|
|
143
|
+
"Signal too short for auto staging (need at least "
|
|
144
|
+
f"{int(_bm.W + _bm.EPOCH_S)} s).")
|
|
145
|
+
if ACC is not None and EMG is None:
|
|
146
|
+
raise ValueError("ACC auto-staging requires an EMG channel as well.")
|
|
147
|
+
|
|
148
|
+
use_emg = EMG is not None
|
|
149
|
+
use_acc = ACC is not None
|
|
150
|
+
combo = _bm.model_combo(EEG_channel, use_emg, use_acc)
|
|
151
|
+
|
|
152
|
+
models = _bm.load_models()
|
|
153
|
+
if combo not in models:
|
|
154
|
+
# ACC without EMG is not part of the model set - fall back to the
|
|
155
|
+
# EEG+EMG (or EEG-only) model.
|
|
156
|
+
if use_acc and use_emg:
|
|
157
|
+
raise ValueError(
|
|
158
|
+
f"No model for combo '{combo}' in the packaged models.")
|
|
159
|
+
combo = _bm.model_combo(EEG_channel, use_emg, False)
|
|
160
|
+
|
|
161
|
+
sig_map = {"eeg": EEG, "emg": EMG, "acc": ACC}
|
|
162
|
+
res = _bm.predict_model(models[combo], sig_map, sf,
|
|
163
|
+
site=EEG_channel, temperature=temperature)
|
|
164
|
+
|
|
165
|
+
# Full-length per-second labels (every second gets a label; the first
|
|
166
|
+
# W seconds and the tail take the nearest epoch) + per-epoch confidence
|
|
167
|
+
# (one value per 5 s epoch - small data).
|
|
168
|
+
pred_second = [int(x) for x in res["label_sec"]]
|
|
169
|
+
conf_epoch = np.asarray(res["prob"], dtype=float)
|
|
170
|
+
logger.info(
|
|
171
|
+
"Auto staging finished (combo=%s, %d epochs, %d seconds)",
|
|
172
|
+
combo, len(res["label"]), len(pred_second))
|
|
173
|
+
|
|
174
|
+
if return_probs:
|
|
175
|
+
return pred_second, conf_epoch
|
|
176
|
+
return pred_second
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
"""Benchmark auto-staging pipeline for MiSleep (LightGBM + HMM).
|
|
3
|
+
|
|
4
|
+
Vendored and adapted from the rodent-autostage benchmark models; see
|
|
5
|
+
:mod:`misleep.analysis.autostage.benchmark` for the public API used by the
|
|
6
|
+
LightGBM auto-staging dialog.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from misleep.analysis.autostage.benchmark import ( # noqa: F401
|
|
10
|
+
EPOCH_S,
|
|
11
|
+
STRIDE,
|
|
12
|
+
W,
|
|
13
|
+
extract_recording,
|
|
14
|
+
load_models,
|
|
15
|
+
model_combo,
|
|
16
|
+
models_path,
|
|
17
|
+
predict_model,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"EPOCH_S", "STRIDE", "W",
|
|
22
|
+
"extract_recording", "load_models", "model_combo", "models_path",
|
|
23
|
+
"predict_model",
|
|
24
|
+
]
|