misleep 0.2.1b0__tar.gz → 0.2.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.
- {misleep-0.2.1b0 → misleep-0.2.2}/LICENSE +1 -1
- misleep-0.2.2/PKG-INFO +95 -0
- misleep-0.2.2/README.md +61 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/__main__.py +0 -1
- misleep-0.2.2/misleep/analysis/__init__.py +2 -0
- misleep-0.2.2/misleep/analysis/auto_stage.py +136 -0
- misleep-0.2.2/misleep/analysis/auto_stage_model/EEG_F_lightgbm_20241221.pkl +0 -0
- misleep-0.2.2/misleep/analysis/auto_stage_model/EEG_P_lightgbm_20241221.pkl +0 -0
- misleep-0.2.2/misleep/analysis/classification.py +0 -0
- misleep-0.2.2/misleep/analysis/detection.py +178 -0
- misleep-0.2.2/misleep/config.ini +13 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/about.py +0 -2
- misleep-0.2.2/misleep/gui/dialog.py +938 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/main_window.py +390 -87
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/show.py +6 -2
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/spec_window.py +27 -24
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/thread.py +5 -5
- misleep-0.2.2/misleep/gui/uis/SWA_detect_dialog_ui.py +122 -0
- misleep-0.2.2/misleep/gui/uis/auto_stage_dialog_ui.py +69 -0
- misleep-0.2.2/misleep/gui/uis/horizontal_line_dialog_ui.py +109 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/label_dialog_ui.py +3 -2
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/main_window_ui.py +161 -169
- misleep-0.2.2/misleep/gui/uis/spindle_detect_dialog_ui.py +134 -0
- misleep-0.2.2/misleep/gui/uis/state_spectral_dialog_ui.py +109 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/transfer_result_dialog_ui.py +28 -21
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/utils.py +71 -1
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/io/annotation_io.py +41 -55
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/io/signal_io.py +53 -11
- misleep-0.2.2/misleep/preprocessing/signals.py +68 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/preprocessing/spectral.py +25 -17
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/utils/__init__.py +2 -1
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/utils/annotation.py +10 -7
- misleep-0.2.2/misleep/utils/logger_handler.py +23 -0
- misleep-0.2.2/misleep.egg-info/PKG-INFO +95 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep.egg-info/SOURCES.txt +12 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep.egg-info/requires.txt +2 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/setup.py +9 -6
- misleep-0.2.1b0/PKG-INFO +0 -96
- misleep-0.2.1b0/README.md +0 -64
- misleep-0.2.1b0/misleep/config.ini +0 -12
- misleep-0.2.1b0/misleep/gui/dialog.py +0 -203
- misleep-0.2.1b0/misleep/preprocessing/signals.py +0 -15
- misleep-0.2.1b0/misleep.egg-info/PKG-INFO +0 -96
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/__init__.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/__init__.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/resources/__init__.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/resources/entire_logo.png +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/resources/logo.png +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/resources/misleep.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/resources/misleep.qrc +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/__init__.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/about_ui.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/save_data_dialog_ui.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/spec_window_ui.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/io/__init__.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/io/base.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/preprocessing/__init__.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/preprocessing/channel.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/utils/signals.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/viz/__init__.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/viz/hypnogram.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/viz/signals.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep/viz/spectral.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep.egg-info/dependency_links.txt +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/misleep.egg-info/top_level.txt +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/setup.cfg +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/test/test_annotation_io.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/test/test_loadmat73.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/test/test_midata.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/test/test_show.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/test/test_signal_io.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/test/test_signals_viz.py +0 -0
- {misleep-0.2.1b0 → misleep-0.2.2}/test/test_spectral_viz.py +0 -0
misleep-0.2.2/PKG-INFO
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: misleep
|
|
3
|
+
Version: 0.2.2
|
|
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.
|
|
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.2/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.
|
|
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,136 @@
|
|
|
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
|
+
|
|
9
|
+
|
|
10
|
+
def filter_power_line_noise(data, sf, noise_band='50-100-150'):
|
|
11
|
+
"""Use 50-100-150 Hz bandstop filter to filter the power line noise
|
|
12
|
+
The noise band can be '50-100-150' or '60-120-180'
|
|
13
|
+
TODO: add the 60-120-180 band
|
|
14
|
+
"""
|
|
15
|
+
filter_band = []
|
|
16
|
+
if noise_band == '50-100-150' and sf > 306:
|
|
17
|
+
filter_band = [[47, 53], [97, 103], [147, 153]]
|
|
18
|
+
elif noise_band == '50-100-150' and sf > 206:
|
|
19
|
+
filter_band = [[47, 53], [97, 103]]
|
|
20
|
+
elif sf > 106:
|
|
21
|
+
filter_band = [[47, 53]]
|
|
22
|
+
for each in filter_band:
|
|
23
|
+
data, _ = misleep.signal_filter(data, sf, btype='bandstop', low=each[0], high=each[1])
|
|
24
|
+
|
|
25
|
+
return data
|
|
26
|
+
|
|
27
|
+
def split_window_data(data, sf, state, window_length=20, stride_length=5):
|
|
28
|
+
"""Split the data into several windows with the window length
|
|
29
|
+
window length is in seconds, so we need the sampling frequency (sf) to locate the data point,
|
|
30
|
+
stride length is the step
|
|
31
|
+
Also contain the state information within the data
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# If the data is shorter than the window length, remove it
|
|
35
|
+
if data.shape[0]/sf < 20:
|
|
36
|
+
return []
|
|
37
|
+
|
|
38
|
+
window_data = []
|
|
39
|
+
for i in range(0, len(data) - int(window_length*sf) + 1, int(stride_length*sf)):
|
|
40
|
+
window = data[i:i + int(window_length*sf)]
|
|
41
|
+
window_data.append([window, state])
|
|
42
|
+
|
|
43
|
+
return window_data
|
|
44
|
+
|
|
45
|
+
def delta_theta_ratio(data, sf):
|
|
46
|
+
"""Calculate the delta/theta ratio from the original signal data
|
|
47
|
+
Note, here the data is 20 seconds, we only need the ratio from the 5 seconds beginning
|
|
48
|
+
"""
|
|
49
|
+
freq, t, Sxx = misleep.spectrogram(data, sf, window=1)
|
|
50
|
+
band_second = np.where(t < 5)
|
|
51
|
+
psd = np.sum(np.array([each[band_second] for each in Sxx]), axis=1)
|
|
52
|
+
band_power = misleep.band_power(psd, freq, bands=[[0.5, 4, 'delta'], [5, 9, 'theta']])
|
|
53
|
+
return band_power['delta'] / band_power['theta']
|
|
54
|
+
|
|
55
|
+
def get_data_features(data, sf, data_format='EEG'):
|
|
56
|
+
""" Get data features, data format can be 'EEG' or 'EMG'
|
|
57
|
+
data: list of window signal data, [[signal_array(20s), signal_label], ...]
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
# Extract the time-domain and frequency domain features from window data, use a dataframe to store all the features
|
|
61
|
+
window_feature_df = pd.DataFrame()
|
|
62
|
+
window_feature_df['label'] = [each[1] for each in data]
|
|
63
|
+
|
|
64
|
+
# Time-domain features, both EEG and EMG
|
|
65
|
+
# 1. standard deviation
|
|
66
|
+
data_std = np.array([np.std(each[0][:int(5*sf)]) for each in data])
|
|
67
|
+
# Set those abnormal points to the upper quartile
|
|
68
|
+
data_std_upper_quantile = np.quantile(data_std, 0.95)
|
|
69
|
+
data_std = [each if each < data_std_upper_quantile else data_std_upper_quantile for each in data_std]
|
|
70
|
+
# Z-score the std features
|
|
71
|
+
window_feature_df[f'{data_format}_std_zscore'] = (data_std - np.mean(data_std)) / np.std(data_std)
|
|
72
|
+
|
|
73
|
+
# 2. Zero crossing rate for EEG and EMG
|
|
74
|
+
zerocross_rate = [antropy.num_zerocross(each[0][:int(5*sf)]) / (5*sf) for each in data]
|
|
75
|
+
window_feature_df[f'{data_format}_zerocross_rate'] = (zerocross_rate - np.mean(zerocross_rate)) / np.std(zerocross_rate)
|
|
76
|
+
|
|
77
|
+
# 3. Hjorth parameters -- Mobility and Complexity
|
|
78
|
+
hjorth = [antropy.hjorth_params(each[0][:int(5*sf)]) for each in data]
|
|
79
|
+
hjorth_M = [each[0] for each in hjorth]
|
|
80
|
+
hjorth_C = [each[1] for each in hjorth]
|
|
81
|
+
window_feature_df[f'{data_format}_Hjorth_M'] = (hjorth_M - np.mean(hjorth_M)) / np.std(hjorth_M)
|
|
82
|
+
window_feature_df[f'{data_format}_Hjorth_C'] = (hjorth_C - np.mean(hjorth_C)) / np.std(hjorth_C)
|
|
83
|
+
|
|
84
|
+
# 4. Permutation entropy
|
|
85
|
+
perm_entropy= [antropy.perm_entropy(each[0][:int(5*sf)]) for each in data]
|
|
86
|
+
window_feature_df[f'{data_format}_perm_entropy'] = (perm_entropy - np.mean(perm_entropy)) / np.std(perm_entropy)
|
|
87
|
+
|
|
88
|
+
# Some features only with EEG
|
|
89
|
+
if data_format.startswith('EEG'):
|
|
90
|
+
# 1. Skewness and kurtosis for EEG signal(s)
|
|
91
|
+
data_skewness = np.array([stats.skew(each[0][:int(5*sf)]) for each in data])
|
|
92
|
+
data_kurtosis = np.array([stats.kurtosis(each[0][:int(5*sf)]) for each in data])
|
|
93
|
+
window_feature_df[f'{data_format}_skewness_zscore'] = (data_skewness - np.mean(data_skewness)) / np.std(data_skewness)
|
|
94
|
+
window_feature_df[f'{data_format}_kurtosis_zscore'] = (data_kurtosis - np.mean(data_kurtosis)) / np.std(data_kurtosis)
|
|
95
|
+
# Frequency-domain features
|
|
96
|
+
# 1. delta/theta
|
|
97
|
+
delta_theta = [delta_theta_ratio(each[0], sf) for each in data]
|
|
98
|
+
window_feature_df[f'{data_format}_delta_theta'] = (delta_theta - np.mean(delta_theta)) / np.std(delta_theta)
|
|
99
|
+
|
|
100
|
+
return window_feature_df
|
|
101
|
+
|
|
102
|
+
def auto_stage_gbm(EEG, EMG, sf, EEG_channel='F'):
|
|
103
|
+
"""
|
|
104
|
+
Auto stage with lightgbm method
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
EEG : array
|
|
109
|
+
EEG data for auto stage. For channel specify, see EEG_channel.
|
|
110
|
+
EMG : array
|
|
111
|
+
EMG data for auto stage.
|
|
112
|
+
sf : double
|
|
113
|
+
Sampling frequency of the EEG and EMG data, should be the same.
|
|
114
|
+
EEG_channel : string
|
|
115
|
+
Specify the channel of EEG, frontal or parietal. Options: {'F', 'P'}. Default is 'F' for frontal.
|
|
116
|
+
|
|
117
|
+
Return
|
|
118
|
+
------
|
|
119
|
+
pred_label : list
|
|
120
|
+
Predicted labels for every second. May less than the data length for about 15 seconds.
|
|
121
|
+
"""
|
|
122
|
+
EEG = split_window_data(EEG, sf, state=4) # All set the initial state '4'
|
|
123
|
+
EMG = split_window_data(EMG, sf, state=4)
|
|
124
|
+
|
|
125
|
+
window_feature_df = pd.DataFrame()
|
|
126
|
+
window_feature_df = pd.concat([get_data_features(EEG, sf, data_format='EEG'), get_data_features(EMG, sf, data_format='EMG')], axis=1)
|
|
127
|
+
window_feature_df = window_feature_df.filter(like='E')
|
|
128
|
+
|
|
129
|
+
if EEG_channel == 'F':
|
|
130
|
+
gbm_model = joblib.load(r'./misleep/analysis/auto_stage_model/EEG_F_lightgbm_20241221.pkl')
|
|
131
|
+
if EEG_channel == 'P':
|
|
132
|
+
gbm_model = joblib.load(r'./misleep/analysis/auto_stage_model/EEG_P_lightgbm_20241221.pkl')
|
|
133
|
+
|
|
134
|
+
pred_label = gbm_model.predict(window_feature_df, num_iteration=gbm_model.best_iteration_)
|
|
135
|
+
pred_label = [item for each in pred_label for item in [each]*5]
|
|
136
|
+
return pred_label
|
|
File without changes
|
|
@@ -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,13 @@
|
|
|
1
|
+
[gui]
|
|
2
|
+
version = v0.2.2
|
|
3
|
+
updatetime = 2024/12/23
|
|
4
|
+
marker = ['long REM', '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:
|
|
13
|
+
|
|
@@ -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}")
|