misleep 0.2.3b0__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.
- {misleep-0.2.3b0 → misleep-0.2.4}/LICENSE +1 -1
- misleep-0.2.4/PKG-INFO +95 -0
- misleep-0.2.4/README.md +61 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/__main__.py +0 -1
- misleep-0.2.4/misleep/analysis/__init__.py +2 -0
- misleep-0.2.4/misleep/analysis/auto_stage.py +180 -0
- misleep-0.2.4/misleep/analysis/auto_stage_model/P30_EEG_F_lightgbm.pkl +0 -0
- misleep-0.2.4/misleep/analysis/auto_stage_model/P30_EEG_P_lightgbm.pkl +0 -0
- misleep-0.2.4/misleep/analysis/auto_stage_model/ado_EEG_F_lightgbm.pkl +0 -0
- misleep-0.2.4/misleep/analysis/auto_stage_model/ado_EEG_P_lightgbm.pkl +0 -0
- misleep-0.2.4/misleep/analysis/auto_stage_model/adult_EEG_F_lightgbm.pkl +0 -0
- misleep-0.2.4/misleep/analysis/auto_stage_model/adult_EEG_P_lightgbm.pkl +0 -0
- misleep-0.2.4/misleep/analysis/detection.py +178 -0
- misleep-0.2.4/misleep/config.ini +12 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/about.py +0 -2
- misleep-0.2.4/misleep/gui/dialog.py +945 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/main_window.py +393 -90
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/show.py +6 -2
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/spec_window.py +27 -24
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/thread.py +5 -5
- misleep-0.2.4/misleep/gui/uis/SWA_detect_dialog_ui.py +122 -0
- misleep-0.2.4/misleep/gui/uis/auto_stage_dialog_ui.py +86 -0
- misleep-0.2.4/misleep/gui/uis/horizontal_line_dialog_ui.py +109 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/uis/label_dialog_ui.py +3 -2
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/uis/main_window_ui.py +158 -166
- misleep-0.2.4/misleep/gui/uis/spindle_detect_dialog_ui.py +134 -0
- misleep-0.2.4/misleep/gui/uis/state_spectral_dialog_ui.py +109 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/uis/transfer_result_dialog_ui.py +27 -39
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/utils.py +71 -1
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/io/annotation_io.py +45 -59
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/io/signal_io.py +46 -11
- misleep-0.2.4/misleep/preprocessing/signals.py +68 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/preprocessing/spectral.py +25 -17
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/utils/__init__.py +2 -1
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/utils/annotation.py +10 -7
- misleep-0.2.4/misleep/utils/logger_handler.py +23 -0
- misleep-0.2.4/misleep.egg-info/PKG-INFO +95 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep.egg-info/SOURCES.txt +15 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep.egg-info/requires.txt +2 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/setup.py +13 -6
- misleep-0.2.3b0/PKG-INFO +0 -101
- misleep-0.2.3b0/README.md +0 -69
- misleep-0.2.3b0/misleep/config.ini +0 -12
- misleep-0.2.3b0/misleep/gui/dialog.py +0 -220
- misleep-0.2.3b0/misleep/preprocessing/signals.py +0 -15
- misleep-0.2.3b0/misleep.egg-info/PKG-INFO +0 -101
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/__init__.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/__init__.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/resources/__init__.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/resources/entire_logo.png +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/resources/logo.png +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/resources/misleep.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/resources/misleep.qrc +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/uis/__init__.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/uis/about_ui.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/uis/save_data_dialog_ui.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/gui/uis/spec_window_ui.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/io/__init__.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/io/base.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/preprocessing/__init__.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/preprocessing/channel.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/utils/signals.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/viz/__init__.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/viz/hypnogram.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/viz/signals.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep/viz/spectral.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep.egg-info/dependency_links.txt +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/misleep.egg-info/top_level.txt +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/setup.cfg +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/test/test_annotation_io.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/test/test_loadmat73.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/test/test_midata.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/test/test_show.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/test/test_signal_io.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/test/test_signals_viz.py +0 -0
- {misleep-0.2.3b0 → misleep-0.2.4}/test/test_spectral_viz.py +0 -0
misleep-0.2.4/PKG-INFO
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: misleep
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: MiSleep: Mice Sleep EEG/EMG visualization, scoring and analysis.
|
|
5
|
+
Home-page: https://github.com/BryanWang0702/MiSleep/
|
|
6
|
+
Download-URL: https://github.com/BryanWang0702/MiSleep/
|
|
7
|
+
Author: Xueqiang Wang
|
|
8
|
+
Author-email: swang@gmail.com
|
|
9
|
+
Maintainer: Xueqiang Wang
|
|
10
|
+
Maintainer-email: swang@gmail.com
|
|
11
|
+
License: BSD (3-clause)
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
18
|
+
Classifier: Operating System :: POSIX
|
|
19
|
+
Classifier: Operating System :: Unix
|
|
20
|
+
Classifier: Operating System :: MacOS
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: numpy>=1.18.1
|
|
24
|
+
Requires-Dist: matplotlib
|
|
25
|
+
Requires-Dist: scipy
|
|
26
|
+
Requires-Dist: pyedflib
|
|
27
|
+
Requires-Dist: hdf5storage
|
|
28
|
+
Requires-Dist: pyqt5
|
|
29
|
+
Requires-Dist: mat73
|
|
30
|
+
Requires-Dist: pandas
|
|
31
|
+
Requires-Dist: openpyxl
|
|
32
|
+
Requires-Dist: antropy
|
|
33
|
+
Requires-Dist: lightgbm
|
|
34
|
+
|
|
35
|
+
# MiSleep
|
|
36
|
+
MiSleep is for EEG/EMG signal processing and visualization
|
|
37
|
+
|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
The name 'MiSleep' is from '**Mi**ce **Sleep**' and sounds like '**my sleep**'.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Get start
|
|
45
|
+
```shell
|
|
46
|
+
pip install misleep
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Find the directory where you installed misleep, run
|
|
50
|
+
```shell
|
|
51
|
+
python -m misleep
|
|
52
|
+
```
|
|
53
|
+
If you use the miniconda or anaconda, the path will be like `D:/miniconda3/envs/misleep/Lib/site-packages`.
|
|
54
|
+
|
|
55
|
+
See [https://bryanwang.cn/MiSleep/](https://bryanwang.cn/MiSleep/) for a simple documentation.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Some features
|
|
60
|
+
1. Free self-define data structure
|
|
61
|
+
|
|
62
|
+
You can organize your data with matlab structure like this:
|
|
63
|
+
```matlab
|
|
64
|
+
data.EEG = AN_ARRAY_OF_EEG_DATA;
|
|
65
|
+
data.EMG_DIFF = AN_ARRAY_OF_EMG_DIFFERENTIAL_DATA;
|
|
66
|
+
% Channel name must be the same with you defined above
|
|
67
|
+
data.channels = {'EEG' 'EMG_DIFF'};
|
|
68
|
+
% Sampling frequency for each channel of data
|
|
69
|
+
data.sf = {256 256};
|
|
70
|
+
% Acquisition time of your data
|
|
71
|
+
data.time = {'20240409-18:00:00'};
|
|
72
|
+
```
|
|
73
|
+
Or if your data format is `.edf`, misleep will also support well.
|
|
74
|
+
|
|
75
|
+
2. Event Detection
|
|
76
|
+
|
|
77
|
+
For sleep spindle and sleep slow-wave activities detection, you can check the tools menu for event detection. The auto stage will coming soon.
|
|
78
|
+
|
|
79
|
+
3. Self-define `config.ini`
|
|
80
|
+
|
|
81
|
+
There is a config.ini in the root directory of MiSleep source package, multiple parameters can be self define there, check [config.ini](https://bryanwang.cn/MiSleep/#config-file) for detail.
|
|
82
|
+
|
|
83
|
+
4. Auto stage
|
|
84
|
+
|
|
85
|
+
Finally, now we have the auto stage function! Check it in the tool. The overall test accuracy of NREM and Wake is higher than 90%, and for REM, the accuracy is higher than 80%. Currently the model's result is rather fragmented in the state transition segments, we will add some constraints to fix this.
|
|
86
|
+
|
|
87
|
+
**Future**: Open for suggestions :).
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Cite this work
|
|
92
|
+
|
|
93
|
+
If you use this software, please cite it as below.
|
|
94
|
+
Xueqiang Wang. (2024). BryanWang0702/MiSleep. Zenodo. https://doi.org/10.5281/zenodo.14511905
|
|
95
|
+
|
misleep-0.2.4/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# MiSleep
|
|
2
|
+
MiSleep is for EEG/EMG signal processing and visualization
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+
|
|
6
|
+
The name 'MiSleep' is from '**Mi**ce **Sleep**' and sounds like '**my sleep**'.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Get start
|
|
11
|
+
```shell
|
|
12
|
+
pip install misleep
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Find the directory where you installed misleep, run
|
|
16
|
+
```shell
|
|
17
|
+
python -m misleep
|
|
18
|
+
```
|
|
19
|
+
If you use the miniconda or anaconda, the path will be like `D:/miniconda3/envs/misleep/Lib/site-packages`.
|
|
20
|
+
|
|
21
|
+
See [https://bryanwang.cn/MiSleep/](https://bryanwang.cn/MiSleep/) for a simple documentation.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Some features
|
|
26
|
+
1. Free self-define data structure
|
|
27
|
+
|
|
28
|
+
You can organize your data with matlab structure like this:
|
|
29
|
+
```matlab
|
|
30
|
+
data.EEG = AN_ARRAY_OF_EEG_DATA;
|
|
31
|
+
data.EMG_DIFF = AN_ARRAY_OF_EMG_DIFFERENTIAL_DATA;
|
|
32
|
+
% Channel name must be the same with you defined above
|
|
33
|
+
data.channels = {'EEG' 'EMG_DIFF'};
|
|
34
|
+
% Sampling frequency for each channel of data
|
|
35
|
+
data.sf = {256 256};
|
|
36
|
+
% Acquisition time of your data
|
|
37
|
+
data.time = {'20240409-18:00:00'};
|
|
38
|
+
```
|
|
39
|
+
Or if your data format is `.edf`, misleep will also support well.
|
|
40
|
+
|
|
41
|
+
2. Event Detection
|
|
42
|
+
|
|
43
|
+
For sleep spindle and sleep slow-wave activities detection, you can check the tools menu for event detection. The auto stage will coming soon.
|
|
44
|
+
|
|
45
|
+
3. Self-define `config.ini`
|
|
46
|
+
|
|
47
|
+
There is a config.ini in the root directory of MiSleep source package, multiple parameters can be self define there, check [config.ini](https://bryanwang.cn/MiSleep/#config-file) for detail.
|
|
48
|
+
|
|
49
|
+
4. Auto stage
|
|
50
|
+
|
|
51
|
+
Finally, now we have the auto stage function! Check it in the tool. The overall test accuracy of NREM and Wake is higher than 90%, and for REM, the accuracy is higher than 80%. Currently the model's result is rather fragmented in the state transition segments, we will add some constraints to fix this.
|
|
52
|
+
|
|
53
|
+
**Future**: Open for suggestions :).
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Cite this work
|
|
58
|
+
|
|
59
|
+
If you use this software, please cite it as below.
|
|
60
|
+
Xueqiang Wang. (2024). BryanWang0702/MiSleep. Zenodo. https://doi.org/10.5281/zenodo.14511905
|
|
61
|
+
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import misleep
|
|
2
|
+
import numpy as np
|
|
3
|
+
from math import floor
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from scipy import stats
|
|
6
|
+
import antropy
|
|
7
|
+
import joblib
|
|
8
|
+
import copy
|
|
9
|
+
import lightgbm
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def filter_power_line_noise(data, sf, noise_band='50-100-150'):
|
|
13
|
+
"""Use 50-100-150 Hz bandstop filter to filter the power line noise
|
|
14
|
+
The noise band can be '50-100-150' or '60-120-180'
|
|
15
|
+
TODO: add the 60-120-180 band
|
|
16
|
+
"""
|
|
17
|
+
filter_band = []
|
|
18
|
+
if noise_band == '50-100-150' and sf > 306:
|
|
19
|
+
filter_band = [[47, 53], [97, 103], [147, 153]]
|
|
20
|
+
elif noise_band == '50-100-150' and sf > 206:
|
|
21
|
+
filter_band = [[47, 53], [97, 103]]
|
|
22
|
+
elif sf > 106:
|
|
23
|
+
filter_band = [[47, 53]]
|
|
24
|
+
for each in filter_band:
|
|
25
|
+
data, _ = misleep.signal_filter(data, sf, btype='bandstop', low=each[0], high=each[1])
|
|
26
|
+
|
|
27
|
+
return data
|
|
28
|
+
|
|
29
|
+
def split_window_data(data, sf, state, window_length=20, stride_length=5):
|
|
30
|
+
"""Split the data into several windows with the window length
|
|
31
|
+
window length is in seconds, so we need the sampling frequency (sf) to locate the data point,
|
|
32
|
+
stride length is the step
|
|
33
|
+
Also contain the state information within the data
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
# If the data is shorter than the window length, remove it
|
|
37
|
+
if data.shape[0]/sf < window_length:
|
|
38
|
+
return []
|
|
39
|
+
|
|
40
|
+
window_data = []
|
|
41
|
+
# Make sure we have enough data point of the final segment
|
|
42
|
+
data_sec_length = floor(data.shape[0] / sf)
|
|
43
|
+
for i in range(0, data_sec_length-stride_length, stride_length):
|
|
44
|
+
window = data[int(i*sf):int((i+window_length)*sf)]
|
|
45
|
+
window_data.append([window, state])
|
|
46
|
+
|
|
47
|
+
return window_data
|
|
48
|
+
|
|
49
|
+
def delta_theta_ratio_theta(data, sf):
|
|
50
|
+
"""Calculate the delta/theta ratio from the original signal data
|
|
51
|
+
Note, here the data is 20 seconds, we only need the ratio from the 5 seconds beginning
|
|
52
|
+
"""
|
|
53
|
+
freq, t, Sxx = misleep.spectrogram(data, sf, window=1)
|
|
54
|
+
band_second = np.where(t < 5)
|
|
55
|
+
psd = np.sum(np.array([each[band_second] for each in Sxx]), axis=1)
|
|
56
|
+
band_power = misleep.band_power(psd, freq, bands=[[0.5, 4, 'delta'], [5, 9, 'theta']], relative=True)
|
|
57
|
+
return band_power['delta'] / band_power['theta'], band_power['theta']
|
|
58
|
+
|
|
59
|
+
def self_zscore(feature, quantile=0.95):
|
|
60
|
+
"""Zscore the extracted features from the signal data, to exclude the abnormal signal, use 0.95 quantile
|
|
61
|
+
Return the quantile zscore feature data
|
|
62
|
+
"""
|
|
63
|
+
upper_quantile = np.quantile(feature, quantile)
|
|
64
|
+
feature = [each if each < upper_quantile else upper_quantile for each in feature]
|
|
65
|
+
return (feature - np.mean(feature)) / np.std(feature)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_data_features(data, sf, data_format='EEG'):
|
|
69
|
+
""" Get data features, data format can be 'EEG' or 'EMG'
|
|
70
|
+
data: list of window signal data, [[signal_array(20s), signal_label], ...]
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
# Extract the time-domain and frequency domain features from window data, use a dataframe to store all the features
|
|
74
|
+
window_feature_df = pd.DataFrame()
|
|
75
|
+
window_feature_df['label'] = [each[1] for each in data]
|
|
76
|
+
|
|
77
|
+
# Time-domain features, both EEG and EMG
|
|
78
|
+
# 1. standard deviation
|
|
79
|
+
data_std = np.array([np.std(each[0][:int(5*sf)]) for each in data])
|
|
80
|
+
# Z-score the std features
|
|
81
|
+
window_feature_df[f'{data_format}_std_zscore'] = self_zscore(data_std)
|
|
82
|
+
|
|
83
|
+
# 2. Zero crossing rate for EEG and EMG
|
|
84
|
+
zerocross_rate = [antropy.num_zerocross(each[0][:int(5*sf)]) / (5*sf) for each in data]
|
|
85
|
+
window_feature_df[f'{data_format}_zerocross_rate'] = (zerocross_rate - np.mean(zerocross_rate)) / np.std(zerocross_rate)
|
|
86
|
+
|
|
87
|
+
# 3. Hjorth parameters -- Mobility and Complexity
|
|
88
|
+
hjorth = [antropy.hjorth_params(each[0][:int(5*sf)]) for each in data]
|
|
89
|
+
hjorth_M = [each[0] for each in hjorth]
|
|
90
|
+
hjorth_C = [each[1] for each in hjorth]
|
|
91
|
+
window_feature_df[f'{data_format}_Hjorth_M'] = self_zscore(hjorth_M)
|
|
92
|
+
window_feature_df[f'{data_format}_Hjorth_C'] = self_zscore(hjorth_C)
|
|
93
|
+
|
|
94
|
+
# 4. Permutation entropy
|
|
95
|
+
perm_entropy= [antropy.perm_entropy(each[0][:int(5*sf)]) for each in data]
|
|
96
|
+
window_feature_df[f'{data_format}_perm_entropy'] = self_zscore(perm_entropy)
|
|
97
|
+
|
|
98
|
+
# Some features only with EEG
|
|
99
|
+
if data_format.startswith('EEG'):
|
|
100
|
+
# 1. Skewness and kurtosis for EEG signal(s)
|
|
101
|
+
data_skewness = np.array([stats.skew(each[0][:int(5*sf)]) for each in data])
|
|
102
|
+
data_kurtosis = np.array([stats.kurtosis(each[0][:int(5*sf)]) for each in data])
|
|
103
|
+
window_feature_df[f'{data_format}_skewness_zscore'] = self_zscore(data_skewness)
|
|
104
|
+
window_feature_df[f'{data_format}_kurtosis_zscore'] = self_zscore(data_kurtosis)
|
|
105
|
+
# Frequency-domain features
|
|
106
|
+
# 1. delta/theta
|
|
107
|
+
delta_theta = [delta_theta_ratio_theta(each[0], sf) for each in data]
|
|
108
|
+
delta_theta_ratio = [each[0] for each in delta_theta]
|
|
109
|
+
theta = [each[1] for each in delta_theta]
|
|
110
|
+
window_feature_df[f'{data_format}_delta_theta_ratio'] = self_zscore(delta_theta_ratio)
|
|
111
|
+
window_feature_df[f'{data_format}_theta'] = self_zscore(theta)
|
|
112
|
+
|
|
113
|
+
return window_feature_df
|
|
114
|
+
|
|
115
|
+
def result_constraints(pred_prob):
|
|
116
|
+
"""
|
|
117
|
+
Once finished the prediction, add some constraints to make the result more smooth
|
|
118
|
+
1. No REM after Wake, set to NREM
|
|
119
|
+
2. All one epoch between two same state will be set to the around state
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
Once finished the prediction, add some constraints to make the result more smooth
|
|
124
|
+
1. REM threshold is lower, set to 0.15
|
|
125
|
+
2. No REM after Wake, set to NREM
|
|
126
|
+
3. All one epoch between two same state will be set to the around state
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
pred_prob = copy.deepcopy(pred_prob)
|
|
130
|
+
pred_label = [each+1 for each in np.argmax(pred_prob, axis=1)]
|
|
131
|
+
pred_label = [2 if each[1] > 0.1 else pred_label[idx] for idx, each in enumerate(pred_prob)] # REM threshold
|
|
132
|
+
|
|
133
|
+
# for idx in range(1, len(pred_label)-1):
|
|
134
|
+
# label_ = pred_label[idx]
|
|
135
|
+
|
|
136
|
+
# if label_ == 3 and pred_label[idx+1] == 2: # REM after Wake
|
|
137
|
+
# pred_label[idx+1] = 1
|
|
138
|
+
# if pred_label[idx-1] ==pred_label[idx+1]: # Same state previous and after
|
|
139
|
+
# pred_label[idx] = pred_label[idx-1]
|
|
140
|
+
|
|
141
|
+
return pred_label
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def auto_stage_gbm(EEG, EMG, label, sf, EEG_channel='F', mouse_age='adult'):
|
|
145
|
+
"""
|
|
146
|
+
Auto stage with lightgbm method
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
EEG : array
|
|
151
|
+
EEG data for auto stage. For channel specify, see EEG_channel.
|
|
152
|
+
EMG : array
|
|
153
|
+
EMG data for auto stage.
|
|
154
|
+
sf : double
|
|
155
|
+
Sampling frequency of the EEG and EMG data, should be the same.
|
|
156
|
+
EEG_channel : string
|
|
157
|
+
Specify the channel of EEG, frontal or parietal. Options: {'F', 'P'}. Default is 'F' for frontal.
|
|
158
|
+
mouse_age : string
|
|
159
|
+
Specify which age model to use. {'adult', 'ado', 'P30'}, default is 'adult'.
|
|
160
|
+
Here the adult is > P56, ado is P30~P56, P30 is less than P30.
|
|
161
|
+
|
|
162
|
+
Return
|
|
163
|
+
------
|
|
164
|
+
pred_label : list
|
|
165
|
+
Predicted labels for every second. May less than the data length for about 15 seconds.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
EEG = split_window_data(EEG, sf, state=4) # All set the initial state '4'
|
|
169
|
+
EMG = split_window_data(EMG, sf, state=4)
|
|
170
|
+
|
|
171
|
+
window_feature_df = pd.DataFrame()
|
|
172
|
+
window_feature_df = pd.concat([get_data_features(EEG, sf, data_format='EEG'), get_data_features(EMG, sf, data_format='EMG')], axis=1)
|
|
173
|
+
window_feature_df = window_feature_df.filter(like='E')
|
|
174
|
+
|
|
175
|
+
gbm_model = joblib.load(f'./misleep/analysis/auto_stage_model/{mouse_age}_EEG_{EEG_channel}_lightgbm.pkl')
|
|
176
|
+
|
|
177
|
+
pred_prob = gbm_model.predict_proba(window_feature_df, num_iteration=gbm_model.best_iteration_)
|
|
178
|
+
pred_label = result_constraints(pred_prob)
|
|
179
|
+
pred_label = [item for each in pred_label for item in [each]*5]
|
|
180
|
+
return pred_label
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from misleep.utils.signals import signal_filter
|
|
2
|
+
from misleep.preprocessing.spectral import spectrogram
|
|
3
|
+
from misleep.utils.annotation import lst2group
|
|
4
|
+
from scipy.signal import find_peaks
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
def SWA_detection(signal, sf, freq_band=[0.5, 4], amp_threshold=(75, ), df=False, start_time_sec=0):
|
|
9
|
+
"""Slow wave activity detection
|
|
10
|
+
Detect form trough to peaks with relative amplitude
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
signal : ndarray
|
|
15
|
+
Signal to detect Slow wave activity
|
|
16
|
+
sf : int or float
|
|
17
|
+
Sampling frequency of signal
|
|
18
|
+
freq_band : List
|
|
19
|
+
Frequency band of slow wave activity
|
|
20
|
+
amp_threshold : float
|
|
21
|
+
minimum and maximum absolute amplitude
|
|
22
|
+
df : bool, optional
|
|
23
|
+
Whether return as a dataframe, when analyze with code, this will be useful
|
|
24
|
+
start_time : float, optional
|
|
25
|
+
When use gui, this will be useful to locate each state's analysis
|
|
26
|
+
|
|
27
|
+
TODO: add relative methods
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
# filter
|
|
31
|
+
band_data, _ = signal_filter(signal, sf, btype='bandpass',
|
|
32
|
+
low=freq_band[0], high=freq_band[1])
|
|
33
|
+
|
|
34
|
+
# Find peaks and zerocrossing
|
|
35
|
+
pos_peak_idx, _ = find_peaks(band_data, amp_threshold)
|
|
36
|
+
neg_peak_idx, _ = find_peaks(-1*band_data, amp_threshold)
|
|
37
|
+
zero_crossing = np.where(np.diff(np.signbit(band_data), axis=0))[0]
|
|
38
|
+
|
|
39
|
+
# Find zero -> neg_peak -> zero -> pos_peak -> zero
|
|
40
|
+
negative_peaks_hold = []
|
|
41
|
+
positive_peaks_hold = []
|
|
42
|
+
zero_crossing_hold = []
|
|
43
|
+
for neg_idx in neg_peak_idx:
|
|
44
|
+
# find the zero cross after the current negative peak
|
|
45
|
+
for zero_idx in zero_crossing:
|
|
46
|
+
if zero_idx > neg_idx:
|
|
47
|
+
# find the positive peak after the zero cross
|
|
48
|
+
for pos_idx in pos_peak_idx:
|
|
49
|
+
if pos_idx > zero_idx and zero_idx not in zero_crossing_hold:
|
|
50
|
+
# no zero cross between pos and zero, neg and zero
|
|
51
|
+
if True not in (band_data[zero_idx+1: pos_idx] <= 0) and \
|
|
52
|
+
True not in (band_data[neg_idx: zero_idx] >= 0):
|
|
53
|
+
negative_peaks_hold.append(neg_idx)
|
|
54
|
+
positive_peaks_hold.append(pos_idx)
|
|
55
|
+
zero_crossing_hold.append(zero_idx)
|
|
56
|
+
|
|
57
|
+
break
|
|
58
|
+
break
|
|
59
|
+
|
|
60
|
+
if negative_peaks_hold == []:
|
|
61
|
+
return None
|
|
62
|
+
# find the zero before negative peak and after positive peak
|
|
63
|
+
# see np.searchsorted(a, v) insert v's element into a, find the index which will make the a's order preserve
|
|
64
|
+
start_zero_cross_hold = zero_crossing[:-1][np.diff(
|
|
65
|
+
np.searchsorted(negative_peaks_hold, zero_crossing)).astype(bool)]
|
|
66
|
+
# start_zero_cross_hold = [start_zero_cross_hold, band_data[start_zero_cross_hold]]
|
|
67
|
+
|
|
68
|
+
if zero_crossing[-1] < positive_peaks_hold[-1]:
|
|
69
|
+
zero_crossing = np.append(zero_crossing, positive_peaks_hold[-1] + 1)
|
|
70
|
+
end_zero_cross_hold = zero_crossing[np.searchsorted(zero_crossing, positive_peaks_hold)]
|
|
71
|
+
# end_zero_cross_hold = [end_zero_cross_hold, band_data[end_zero_cross_hold]]
|
|
72
|
+
|
|
73
|
+
df_lst = []
|
|
74
|
+
for idx, start_zero in enumerate(start_zero_cross_hold):
|
|
75
|
+
start_time = start_zero/sf + start_time_sec
|
|
76
|
+
end_time = end_zero_cross_hold[idx]/sf + start_time_sec
|
|
77
|
+
total_duration = end_time - start_time
|
|
78
|
+
frequency = 1/total_duration
|
|
79
|
+
if frequency > freq_band[1] or frequency < freq_band[0]:
|
|
80
|
+
continue
|
|
81
|
+
|
|
82
|
+
middle_cross_time = zero_crossing_hold[idx] / sf + start_time_sec
|
|
83
|
+
|
|
84
|
+
time_pos_peak = positive_peaks_hold[idx] / sf + start_time_sec
|
|
85
|
+
val_pos_peak = band_data[positive_peaks_hold[idx]]
|
|
86
|
+
time_neg_peak = negative_peaks_hold[idx] / sf + start_time_sec
|
|
87
|
+
val_neg_peak = band_data[negative_peaks_hold[idx]]
|
|
88
|
+
|
|
89
|
+
peak_to_peak = val_pos_peak - val_neg_peak
|
|
90
|
+
|
|
91
|
+
slope = peak_to_peak / (time_pos_peak - time_neg_peak)
|
|
92
|
+
|
|
93
|
+
df_lst.append([start_time, time_neg_peak, middle_cross_time, time_pos_peak,
|
|
94
|
+
end_time, total_duration, val_neg_peak, val_pos_peak,
|
|
95
|
+
peak_to_peak, slope, frequency])
|
|
96
|
+
|
|
97
|
+
if df:
|
|
98
|
+
return pd.DataFrame(df_lst, columns=['StartTime', 'NegTime', 'MiddleTime',
|
|
99
|
+
'PosTime', 'EndTime', 'Duration', 'NegPeak',
|
|
100
|
+
'PosPeak', 'PTP', 'Slope', 'Frequency'])
|
|
101
|
+
else:
|
|
102
|
+
return df_lst
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def spindle_detection(signal, sf, freq_band=[10, 15], start_time_sec=0,
|
|
106
|
+
std_thresh=None, duration_thresh=None):
|
|
107
|
+
"""Spindle detection"""
|
|
108
|
+
|
|
109
|
+
f, t, Sxx = spectrogram(signal, sf, band=freq_band,
|
|
110
|
+
step=0.2, window=2, norm=False)
|
|
111
|
+
|
|
112
|
+
# Get squared spectrum
|
|
113
|
+
Sxx = np.sum(Sxx, axis=0)
|
|
114
|
+
Sxx_squared = Sxx ** 2
|
|
115
|
+
|
|
116
|
+
# Use z score to find the real value for threshold
|
|
117
|
+
Sxx = Sxx_squared
|
|
118
|
+
Sxx_mean = np.mean(Sxx)
|
|
119
|
+
Sxx_std = np.std(Sxx)
|
|
120
|
+
spindle_threshold = std_thresh*Sxx_std + Sxx_mean
|
|
121
|
+
duration_threshold = duration_thresh*Sxx_std + Sxx_mean
|
|
122
|
+
|
|
123
|
+
Sxx_peaks_idx, _ = find_peaks(Sxx, (spindle_threshold))
|
|
124
|
+
|
|
125
|
+
# Fund duration
|
|
126
|
+
duration_group = lst2group([[idx, each] for idx, each in enumerate(Sxx > duration_threshold)])
|
|
127
|
+
start_time = []
|
|
128
|
+
end_time = []
|
|
129
|
+
for each in duration_group:
|
|
130
|
+
if each[0] != 0 and each[2]:
|
|
131
|
+
if each[1] < len(t) and each[2]:
|
|
132
|
+
start_time.append(t[each[0]])
|
|
133
|
+
end_time.append(t[each[1]])
|
|
134
|
+
|
|
135
|
+
start_time = np.array(start_time)
|
|
136
|
+
end_time = np.array(end_time)
|
|
137
|
+
|
|
138
|
+
if Sxx_peaks_idx.shape == (0, ):
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
# # find the minimum peak for duration
|
|
142
|
+
# minimum_peaks_idx = find_peaks(-1*Sxx)[0]
|
|
143
|
+
|
|
144
|
+
# # find the minimum peak around the Sxx peak
|
|
145
|
+
# if minimum_peaks_idx[-1] < Sxx_peaks_idx[-1]:
|
|
146
|
+
# minimum_peaks_idx = np.append(minimum_peaks_idx, Sxx_peaks_idx[-1]+1)
|
|
147
|
+
|
|
148
|
+
# end_minimun_sorted_idx = np.searchsorted(minimum_peaks_idx, Sxx_peaks_idx)
|
|
149
|
+
|
|
150
|
+
# end_minimum_peak_idx = minimum_peaks_idx[end_minimun_sorted_idx]
|
|
151
|
+
|
|
152
|
+
# if end_minimun_sorted_idx[0] == 0:
|
|
153
|
+
# end_minimun_sorted_idx = end_minimun_sorted_idx[1:]
|
|
154
|
+
# start_minimum_peak_idx = minimum_peaks_idx[end_minimun_sorted_idx-1]
|
|
155
|
+
# start_minimum_peak_idx = np.append(0, start_minimum_peak_idx)
|
|
156
|
+
|
|
157
|
+
# else:
|
|
158
|
+
# start_minimum_peak_idx = minimum_peaks_idx[end_minimun_sorted_idx-1]
|
|
159
|
+
|
|
160
|
+
# get time index based on peak index
|
|
161
|
+
start_time = start_time + start_time_sec
|
|
162
|
+
end_time = end_time + start_time_sec
|
|
163
|
+
|
|
164
|
+
if start_time.shape != end_time.shape:
|
|
165
|
+
return None
|
|
166
|
+
if start_time.shape == (0, ):
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
return [[each, end_time[idx]] for
|
|
170
|
+
idx, each in enumerate(start_time) if
|
|
171
|
+
end_time[idx] - each >= 0.5]
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def artifact_detection(signal):
|
|
175
|
+
"""Artifact detection"""
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[gui]
|
|
2
|
+
version = v0.2.4
|
|
3
|
+
updatetime = 2025/1/16
|
|
4
|
+
marker = ['good', 'first REM', 'WindEEG', 'W-R ', 'maker']
|
|
5
|
+
startend = ['burst-supression', 'REM', 'Wake', 'Spindle', 'SWA', 'start end label', 'start end label']
|
|
6
|
+
statemap = {"1": "NREM", "2": "REM", "3": "Wake", "4": "INIT", "5": "IS", "6": "MicroArousal"}
|
|
7
|
+
statecolor = {"1": "orange", "2": "skyblue", "3": "red", "4": "white", "5": "green", "6": "pink"}
|
|
8
|
+
startendcolor = {"NREM": "orange", "REM": "skyblue", "Wake": "red"}
|
|
9
|
+
statecolorbgalpha = 0.1
|
|
10
|
+
markerlinecolor = "red"
|
|
11
|
+
startendlinecolor = "blue"
|
|
12
|
+
openpath = E:
|
|
@@ -19,8 +19,6 @@ class about_dialog(QDialog, Ui_AboutDialog):
|
|
|
19
19
|
"""
|
|
20
20
|
super().__init__(parent)
|
|
21
21
|
|
|
22
|
-
# Enable high dpi devices
|
|
23
|
-
QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
|
|
24
22
|
self.setupUi(self)
|
|
25
23
|
if version:
|
|
26
24
|
self.VersionLabel.setText(f"Version: {version}")
|