psgscoring 0.2.2__tar.gz → 0.2.4__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.
- psgscoring-0.2.4/LICENSE +28 -0
- psgscoring-0.2.4/PKG-INFO +113 -0
- psgscoring-0.2.4/README.md +74 -0
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring/__init__.py +6 -4
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring/classify.py +29 -0
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring/constants.py +8 -0
- psgscoring-0.2.4/psgscoring/ecg_effort.py +390 -0
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring/pipeline.py +4 -0
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring/respiratory.py +299 -94
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring/signal_quality.py +1 -1
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring/spo2.py +14 -3
- psgscoring-0.2.4/psgscoring/tests/conftest.py +6 -0
- {psgscoring-0.2.2 → psgscoring-0.2.4/psgscoring}/tests/test_psgscoring.py +0 -2
- psgscoring-0.2.4/psgscoring.egg-info/PKG-INFO +113 -0
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring.egg-info/SOURCES.txt +3 -3
- psgscoring-0.2.4/psgscoring.egg-info/requires.txt +11 -0
- psgscoring-0.2.4/pyproject.toml +60 -0
- psgscoring-0.2.2/LICENSE +0 -28
- psgscoring-0.2.2/PKG-INFO +0 -308
- psgscoring-0.2.2/README.md +0 -272
- psgscoring-0.2.2/psgscoring/_arousal.py +0 -891
- psgscoring-0.2.2/psgscoring/_core.py +0 -2404
- psgscoring-0.2.2/psgscoring.egg-info/PKG-INFO +0 -308
- psgscoring-0.2.2/psgscoring.egg-info/requires.txt +0 -12
- psgscoring-0.2.2/pyproject.toml +0 -48
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring/ancillary.py +0 -0
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring/breath.py +0 -0
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring/plm.py +0 -0
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring/signal.py +0 -0
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring/utils.py +0 -0
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring.egg-info/dependency_links.txt +0 -0
- {psgscoring-0.2.2 → psgscoring-0.2.4}/psgscoring.egg-info/top_level.txt +0 -0
- {psgscoring-0.2.2 → psgscoring-0.2.4}/setup.cfg +0 -0
psgscoring-0.2.4/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2026, Bart Rombaut, Briek Rombaut, Cedric Rombaut
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: psgscoring
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: Open-source AASM 2.6-compliant respiratory scoring for polysomnography
|
|
5
|
+
Author: Briek Rombaut, Cedric Rombaut
|
|
6
|
+
Author-email: Bart Rombaut <bart.rombaut@azorg.be>
|
|
7
|
+
License: BSD-3-Clause
|
|
8
|
+
Project-URL: Homepage, https://github.com/bartromb/psgscoring
|
|
9
|
+
Project-URL: Documentation, https://github.com/bartromb/psgscoring#readme
|
|
10
|
+
Project-URL: Repository, https://github.com/bartromb/psgscoring
|
|
11
|
+
Project-URL: Issues, https://github.com/bartromb/psgscoring/issues
|
|
12
|
+
Project-URL: Changelog, https://github.com/bartromb/psgscoring/blob/main/CHANGES.md
|
|
13
|
+
Keywords: sleep,polysomnography,PSG,apnea,hypopnea,AHI,AASM,respiratory,scoring,sleep-staging,YASA,EDF,clinical,neuroscience
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Intended Audience :: Healthcare Industry
|
|
17
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: numpy>=1.22
|
|
30
|
+
Requires-Dist: scipy>=1.9
|
|
31
|
+
Requires-Dist: mne>=1.3
|
|
32
|
+
Provides-Extra: full
|
|
33
|
+
Requires-Dist: lightgbm>=3.3; extra == "full"
|
|
34
|
+
Requires-Dist: yasa>=0.6.4; extra == "full"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# psgscoring
|
|
41
|
+
|
|
42
|
+
**Open-source AASM 2.6-compliant respiratory scoring for polysomnography.**
|
|
43
|
+
|
|
44
|
+
`psgscoring` is a standalone Python library that implements the full AASM 2.6 respiratory scoring algorithm for polysomnographic (PSG) recordings. It is the scoring engine behind [YASAFlaskified](https://github.com/bartromb/YASAFlaskified) and is designed to work with [YASA](https://github.com/raphaelvallat/yasa) and [MNE-Python](https://mne.tools/).
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- **Apnea detection**: ≥90% flow reduction on oronasal thermistor, ≥10 s
|
|
49
|
+
- **Hypopnea detection**: ≥30% flow reduction on nasal pressure, ≥10 s, with ≥3% desaturation or arousal
|
|
50
|
+
- **Apnea type classification**: 7-rule obstructive/central/mixed classification with Hilbert phase-angle analysis
|
|
51
|
+
- **ECG-derived effort** (v0.2.4): Transformed ECG (TECG) method (Berry et al., JCSM 2019) + spectral classifier for improved central vs. obstructive differentiation
|
|
52
|
+
- **Six over-counting corrections**: post-apnea baseline inflation, SpO₂ cross-contamination, Cheyne-Stokes flagging, borderline classification, artefact-flank exclusion, local baseline validation
|
|
53
|
+
- **Six under-counting corrections**: peak-based hypopnea detection, SpO₂ de-blocking, extended nadir window, flow smoothing, position auto-mapping, configurable scoring profiles
|
|
54
|
+
- **SpO₂ analysis**: ODI at 3% and 4% thresholds, baseline (P90), T90
|
|
55
|
+
- **PLM scoring**: AASM 2.6 + WASM criteria
|
|
56
|
+
- **RERA and RDI**: flow limitation (flattening index) and FRI-RERA detection
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install psgscoring
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
With optional dependencies (LightGBM confidence calibration):
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install psgscoring[full]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Quick start
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import mne
|
|
74
|
+
from psgscoring import run_pneumo_analysis
|
|
75
|
+
|
|
76
|
+
raw = mne.io.read_raw_edf("recording.edf", preload=True)
|
|
77
|
+
hypno = ["W", "N1", "N2", "N2", "N3", ...] # 30-s epoch labels
|
|
78
|
+
|
|
79
|
+
results = run_pneumo_analysis(raw, hypno)
|
|
80
|
+
|
|
81
|
+
ahi = results["respiratory"]["summary"]["ahi_total"]
|
|
82
|
+
print(f"AHI: {ahi}")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Scoring profiles
|
|
86
|
+
|
|
87
|
+
Three configurable profiles balance sensitivity vs. specificity:
|
|
88
|
+
|
|
89
|
+
| Profile | Use case | Hypopnea threshold |
|
|
90
|
+
|---------|----------|-------------------|
|
|
91
|
+
| `strict` | Minimise false positives | 40% flow reduction |
|
|
92
|
+
| `standard` | AASM 2.6 default | 30% flow reduction |
|
|
93
|
+
| `sensitive` | Maximise detection | 25% flow reduction |
|
|
94
|
+
|
|
95
|
+
## References
|
|
96
|
+
|
|
97
|
+
- Berry RB et al. The AASM Manual for the Scoring of Sleep and Associated Events, Version 2.6. AASM, 2020.
|
|
98
|
+
- Berry RB et al. Use of a Transformed ECG Signal to Detect Respiratory Effort During Apnea. *J Clin Sleep Med.* 2019;15(11):1653–1660.
|
|
99
|
+
- Vallat R, Walker MP. An open-source, high-performance tool for automated sleep staging. *eLife.* 2021;10:e70092.
|
|
100
|
+
|
|
101
|
+
## Disclaimer
|
|
102
|
+
|
|
103
|
+
`psgscoring` is **research software**, not a cleared medical device. All computed indices are research-grade estimates that must be reviewed by a qualified clinician. See [DISCLAIMER.md](https://github.com/bartromb/psgscoring/blob/main/DISCLAIMER.md).
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
BSD-3-Clause. See [LICENSE](LICENSE).
|
|
108
|
+
|
|
109
|
+
## Citation
|
|
110
|
+
|
|
111
|
+
If you use `psgscoring` in your research, please cite:
|
|
112
|
+
|
|
113
|
+
> Rombaut B, Rombaut B, Rombaut C, Vallat R. YASAFlaskified: an open-source platform for automated PSG analysis with systematic over-counting and under-counting corrections. *In preparation*, 2026.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# psgscoring
|
|
2
|
+
|
|
3
|
+
**Open-source AASM 2.6-compliant respiratory scoring for polysomnography.**
|
|
4
|
+
|
|
5
|
+
`psgscoring` is a standalone Python library that implements the full AASM 2.6 respiratory scoring algorithm for polysomnographic (PSG) recordings. It is the scoring engine behind [YASAFlaskified](https://github.com/bartromb/YASAFlaskified) and is designed to work with [YASA](https://github.com/raphaelvallat/yasa) and [MNE-Python](https://mne.tools/).
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Apnea detection**: ≥90% flow reduction on oronasal thermistor, ≥10 s
|
|
10
|
+
- **Hypopnea detection**: ≥30% flow reduction on nasal pressure, ≥10 s, with ≥3% desaturation or arousal
|
|
11
|
+
- **Apnea type classification**: 7-rule obstructive/central/mixed classification with Hilbert phase-angle analysis
|
|
12
|
+
- **ECG-derived effort** (v0.2.4): Transformed ECG (TECG) method (Berry et al., JCSM 2019) + spectral classifier for improved central vs. obstructive differentiation
|
|
13
|
+
- **Six over-counting corrections**: post-apnea baseline inflation, SpO₂ cross-contamination, Cheyne-Stokes flagging, borderline classification, artefact-flank exclusion, local baseline validation
|
|
14
|
+
- **Six under-counting corrections**: peak-based hypopnea detection, SpO₂ de-blocking, extended nadir window, flow smoothing, position auto-mapping, configurable scoring profiles
|
|
15
|
+
- **SpO₂ analysis**: ODI at 3% and 4% thresholds, baseline (P90), T90
|
|
16
|
+
- **PLM scoring**: AASM 2.6 + WASM criteria
|
|
17
|
+
- **RERA and RDI**: flow limitation (flattening index) and FRI-RERA detection
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install psgscoring
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
With optional dependencies (LightGBM confidence calibration):
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install psgscoring[full]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import mne
|
|
35
|
+
from psgscoring import run_pneumo_analysis
|
|
36
|
+
|
|
37
|
+
raw = mne.io.read_raw_edf("recording.edf", preload=True)
|
|
38
|
+
hypno = ["W", "N1", "N2", "N2", "N3", ...] # 30-s epoch labels
|
|
39
|
+
|
|
40
|
+
results = run_pneumo_analysis(raw, hypno)
|
|
41
|
+
|
|
42
|
+
ahi = results["respiratory"]["summary"]["ahi_total"]
|
|
43
|
+
print(f"AHI: {ahi}")
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Scoring profiles
|
|
47
|
+
|
|
48
|
+
Three configurable profiles balance sensitivity vs. specificity:
|
|
49
|
+
|
|
50
|
+
| Profile | Use case | Hypopnea threshold |
|
|
51
|
+
|---------|----------|-------------------|
|
|
52
|
+
| `strict` | Minimise false positives | 40% flow reduction |
|
|
53
|
+
| `standard` | AASM 2.6 default | 30% flow reduction |
|
|
54
|
+
| `sensitive` | Maximise detection | 25% flow reduction |
|
|
55
|
+
|
|
56
|
+
## References
|
|
57
|
+
|
|
58
|
+
- Berry RB et al. The AASM Manual for the Scoring of Sleep and Associated Events, Version 2.6. AASM, 2020.
|
|
59
|
+
- Berry RB et al. Use of a Transformed ECG Signal to Detect Respiratory Effort During Apnea. *J Clin Sleep Med.* 2019;15(11):1653–1660.
|
|
60
|
+
- Vallat R, Walker MP. An open-source, high-performance tool for automated sleep staging. *eLife.* 2021;10:e70092.
|
|
61
|
+
|
|
62
|
+
## Disclaimer
|
|
63
|
+
|
|
64
|
+
`psgscoring` is **research software**, not a cleared medical device. All computed indices are research-grade estimates that must be reviewed by a qualified clinician. See [DISCLAIMER.md](https://github.com/bartromb/psgscoring/blob/main/DISCLAIMER.md).
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
BSD-3-Clause. See [LICENSE](LICENSE).
|
|
69
|
+
|
|
70
|
+
## Citation
|
|
71
|
+
|
|
72
|
+
If you use `psgscoring` in your research, please cite:
|
|
73
|
+
|
|
74
|
+
> Rombaut B, Rombaut B, Rombaut C, Vallat R. YASAFlaskified: an open-source platform for automated PSG analysis with systematic over-counting and under-counting corrections. *In preparation*, 2026.
|
|
@@ -20,6 +20,7 @@ spo2 – SpO2 coupling (Rule 1A) and full SpO2 analysis
|
|
|
20
20
|
plm – PLM detection (AASM 2.6)
|
|
21
21
|
ancillary – position, heart rate, snore, Cheyne-Stokes
|
|
22
22
|
respiratory – apnea/hypopnea detection, Rule 1B, summary statistics
|
|
23
|
+
ecg_effort – ECG-derived effort (TECG, spectral classifier) for central/obstructive differentiation
|
|
23
24
|
pipeline – MNE-facing master function (run_pneumo_analysis)
|
|
24
25
|
"""
|
|
25
26
|
|
|
@@ -49,6 +50,7 @@ from .breath import (
|
|
|
49
50
|
detect_breath_events,
|
|
50
51
|
)
|
|
51
52
|
from .classify import classify_apnea_type
|
|
53
|
+
from .ecg_effort import ecg_effort_assessment, compute_tecg
|
|
52
54
|
from .spo2 import analyze_spo2, detect_desaturations, get_desaturation
|
|
53
55
|
from .plm import analyze_plm
|
|
54
56
|
from .ancillary import (
|
|
@@ -65,9 +67,8 @@ from .utils import (
|
|
|
65
67
|
is_nrem, is_rem, is_sleep,
|
|
66
68
|
safe_r,
|
|
67
69
|
)
|
|
68
|
-
from .signal_quality import assess_signal_quality
|
|
69
70
|
|
|
70
|
-
__version__ = "0.2.
|
|
71
|
+
__version__ = "0.2.4"
|
|
71
72
|
__all__ = [
|
|
72
73
|
# Master
|
|
73
74
|
"run_pneumo_analysis",
|
|
@@ -92,6 +93,9 @@ __all__ = [
|
|
|
92
93
|
"detect_breath_events",
|
|
93
94
|
# Classify
|
|
94
95
|
"classify_apnea_type",
|
|
96
|
+
# ECG effort
|
|
97
|
+
"ecg_effort_assessment",
|
|
98
|
+
"compute_tecg",
|
|
95
99
|
# SpO2
|
|
96
100
|
"analyze_spo2",
|
|
97
101
|
"detect_desaturations",
|
|
@@ -103,8 +107,6 @@ __all__ = [
|
|
|
103
107
|
"analyze_heart_rate",
|
|
104
108
|
"analyze_snore",
|
|
105
109
|
"detect_cheyne_stokes",
|
|
106
|
-
# Signal quality (v0.2.2)
|
|
107
|
-
"assess_signal_quality",
|
|
108
110
|
# Utils
|
|
109
111
|
"detect_channels",
|
|
110
112
|
"channel_map_from_user",
|
|
@@ -112,6 +112,7 @@ def classify_apnea_type(
|
|
|
112
112
|
abdomen_raw: np.ndarray | None,
|
|
113
113
|
effort_baseline: float,
|
|
114
114
|
sf: float,
|
|
115
|
+
ecg_assessment: dict | None = None,
|
|
115
116
|
) -> tuple[str, float, dict]:
|
|
116
117
|
"""
|
|
117
118
|
Classify an apnea event as ``"obstructive"``, ``"central"``, or
|
|
@@ -126,9 +127,19 @@ def classify_apnea_type(
|
|
|
126
127
|
3. **Mixed pattern** – absent effort first half, present second half
|
|
127
128
|
4. **Effort clearly present** → obstructive
|
|
128
129
|
5. **Truly flat** – no raw movement, low envelope → central
|
|
130
|
+
5b. **ECG-derived reclassification** (v0.8.23) – TECG + spectral
|
|
131
|
+
analysis overrides RIP-based obstructive if cardiac artefact only.
|
|
129
132
|
6. **Borderline default** → obstructive (low confidence)
|
|
130
133
|
If LightGBM model available, confidence is calibrated by model.
|
|
131
134
|
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
ecg_assessment : dict, optional
|
|
138
|
+
Output of ``ecg_effort.ecg_effort_assessment()``.
|
|
139
|
+
If provided and ``reclassify_as_central`` is True, events that
|
|
140
|
+
would otherwise be classified as obstructive (rules 4, 6) are
|
|
141
|
+
reclassified as central.
|
|
142
|
+
|
|
132
143
|
Returns
|
|
133
144
|
-------
|
|
134
145
|
(type_str, confidence_0_to_1, detail_dict)
|
|
@@ -239,6 +250,24 @@ def classify_apnea_type(
|
|
|
239
250
|
)
|
|
240
251
|
return "central", safe_r(_conf(conf, 5), 2), detail
|
|
241
252
|
|
|
253
|
+
# ── Rule 5b (v0.8.23): ECG-derived reclassification ──────────────────
|
|
254
|
+
# If TECG shows no inspiratory bursts AND spectral analysis shows
|
|
255
|
+
# cardiac dominance, reclassify borderline/effort-present as central.
|
|
256
|
+
if ecg_assessment is not None and ecg_assessment.get("reclassify_as_central"):
|
|
257
|
+
detail["ecg_assessment"] = {
|
|
258
|
+
k: v for k, v in ecg_assessment.items()
|
|
259
|
+
if k not in ("tecg_detail", "spectral_detail")
|
|
260
|
+
}
|
|
261
|
+
# Only reclassify if effort is ambiguous (ratio < clear threshold)
|
|
262
|
+
if effort_ratio < EFFORT_PRESENT_RATIO * 1.5:
|
|
263
|
+
conf = 0.75
|
|
264
|
+
if ecg_assessment.get("ecg_effort_present") is False:
|
|
265
|
+
conf = 0.85 # both TECG and spectral agree
|
|
266
|
+
detail["decision_reason"] = (
|
|
267
|
+
f"ecg_reclassified_central_effort={safe_r(effort_ratio,3)}"
|
|
268
|
+
)
|
|
269
|
+
return "central", safe_r(_conf(conf, 5), 2), detail
|
|
270
|
+
|
|
242
271
|
# ── Rule 6: Borderline default ────────────────────────────────────────
|
|
243
272
|
detail["decision_reason"] = (
|
|
244
273
|
f"borderline_default_var={safe_r(raw_var_ratio,3)}_effort={safe_r(effort_ratio,3)}"
|
|
@@ -16,6 +16,8 @@ HYPOPNEA_SMOOTH_S = 3.0 # v0.8.12: smooth flow before thresholding (seco
|
|
|
16
16
|
# Mimics human visual averaging; reduces false negatives
|
|
17
17
|
APNEA_MIN_DUR_S = 10.0 # seconds
|
|
18
18
|
HYPOPNEA_MIN_DUR_S = 10.0 # seconds
|
|
19
|
+
APNEA_MAX_DUR_S = 90.0 # v0.8.22: events >90s are split at partial recovery
|
|
20
|
+
HYPOPNEA_MAX_DUR_S = 60.0 # v0.8.22: events >60s are split at partial recovery
|
|
19
21
|
DESATURATION_DROP_PCT = 3.0 # >= 3% SpO2 drop required for Rule 1A
|
|
20
22
|
EFFORT_ABSENT_RATIO = 0.20 # < 20% of baseline effort -> absent
|
|
21
23
|
EFFORT_PRESENT_RATIO = 0.40 # > 40% of baseline effort -> present
|
|
@@ -45,6 +47,8 @@ SCORING_PROFILES: dict[str, dict] = {
|
|
|
45
47
|
"HYPOPNEA_SMOOTH_S": 0.0, # no smoothing
|
|
46
48
|
"CROSS_CONTAM_WINDOW_S": 15.0,
|
|
47
49
|
"USE_PEAK_DETECTION": False, # envelope only
|
|
50
|
+
"HYPOPNEA_MAX_DUR_S": 60.0, # v0.8.22
|
|
51
|
+
"APNEA_MAX_DUR_S": 90.0, # v0.8.22
|
|
48
52
|
},
|
|
49
53
|
"standard": {
|
|
50
54
|
"label": "Standard (AASM 2.6)",
|
|
@@ -54,6 +58,8 @@ SCORING_PROFILES: dict[str, dict] = {
|
|
|
54
58
|
"HYPOPNEA_SMOOTH_S": 3.0,
|
|
55
59
|
"CROSS_CONTAM_WINDOW_S": 15.0,
|
|
56
60
|
"USE_PEAK_DETECTION": True, # peak + envelope
|
|
61
|
+
"HYPOPNEA_MAX_DUR_S": 60.0, # v0.8.22
|
|
62
|
+
"APNEA_MAX_DUR_S": 90.0, # v0.8.22
|
|
57
63
|
},
|
|
58
64
|
"sensitive": {
|
|
59
65
|
"label": "Sensitive (RPSGT-like)",
|
|
@@ -63,6 +69,8 @@ SCORING_PROFILES: dict[str, dict] = {
|
|
|
63
69
|
"HYPOPNEA_SMOOTH_S": 5.0, # more smoothing
|
|
64
70
|
"CROSS_CONTAM_WINDOW_S": 0.0, # no cross-contam check
|
|
65
71
|
"USE_PEAK_DETECTION": True,
|
|
72
|
+
"HYPOPNEA_MAX_DUR_S": 90.0, # v0.8.22: meer tolerant
|
|
73
|
+
"APNEA_MAX_DUR_S": 120.0, # v0.8.22
|
|
66
74
|
},
|
|
67
75
|
}
|
|
68
76
|
|