misleep 0.2.2b0__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. {misleep-0.2.2b0 → misleep-0.2.3}/LICENSE +1 -1
  2. misleep-0.2.3/PKG-INFO +95 -0
  3. misleep-0.2.3/README.md +61 -0
  4. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/__main__.py +0 -1
  5. misleep-0.2.3/misleep/analysis/__init__.py +2 -0
  6. misleep-0.2.3/misleep/analysis/auto_stage.py +158 -0
  7. misleep-0.2.3/misleep/analysis/auto_stage_model/EEG_F_lightgbm_20241221.pkl +0 -0
  8. misleep-0.2.3/misleep/analysis/auto_stage_model/EEG_P_lightgbm_20241221.pkl +0 -0
  9. misleep-0.2.3/misleep/analysis/classification.py +0 -0
  10. misleep-0.2.3/misleep/analysis/detection.py +178 -0
  11. misleep-0.2.3/misleep/config.ini +13 -0
  12. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/about.py +0 -2
  13. misleep-0.2.3/misleep/gui/dialog.py +939 -0
  14. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/main_window.py +384 -86
  15. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/show.py +6 -2
  16. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/spec_window.py +27 -24
  17. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/thread.py +5 -5
  18. misleep-0.2.3/misleep/gui/uis/SWA_detect_dialog_ui.py +122 -0
  19. misleep-0.2.3/misleep/gui/uis/auto_stage_dialog_ui.py +69 -0
  20. misleep-0.2.3/misleep/gui/uis/horizontal_line_dialog_ui.py +109 -0
  21. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/uis/label_dialog_ui.py +3 -2
  22. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/uis/main_window_ui.py +158 -166
  23. misleep-0.2.3/misleep/gui/uis/spindle_detect_dialog_ui.py +134 -0
  24. misleep-0.2.3/misleep/gui/uis/state_spectral_dialog_ui.py +109 -0
  25. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/uis/transfer_result_dialog_ui.py +27 -39
  26. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/utils.py +71 -1
  27. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/io/annotation_io.py +41 -55
  28. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/io/signal_io.py +46 -11
  29. misleep-0.2.3/misleep/preprocessing/signals.py +68 -0
  30. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/preprocessing/spectral.py +25 -17
  31. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/utils/__init__.py +2 -1
  32. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/utils/annotation.py +10 -7
  33. misleep-0.2.3/misleep/utils/logger_handler.py +23 -0
  34. misleep-0.2.3/misleep.egg-info/PKG-INFO +95 -0
  35. {misleep-0.2.2b0 → misleep-0.2.3}/misleep.egg-info/SOURCES.txt +12 -0
  36. {misleep-0.2.2b0 → misleep-0.2.3}/misleep.egg-info/requires.txt +2 -0
  37. {misleep-0.2.2b0 → misleep-0.2.3}/setup.py +9 -6
  38. misleep-0.2.2b0/PKG-INFO +0 -96
  39. misleep-0.2.2b0/README.md +0 -64
  40. misleep-0.2.2b0/misleep/config.ini +0 -12
  41. misleep-0.2.2b0/misleep/gui/dialog.py +0 -217
  42. misleep-0.2.2b0/misleep/preprocessing/signals.py +0 -15
  43. misleep-0.2.2b0/misleep.egg-info/PKG-INFO +0 -96
  44. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/__init__.py +0 -0
  45. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/__init__.py +0 -0
  46. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/resources/__init__.py +0 -0
  47. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/resources/entire_logo.png +0 -0
  48. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/resources/logo.png +0 -0
  49. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/resources/misleep.py +0 -0
  50. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/resources/misleep.qrc +0 -0
  51. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/uis/__init__.py +0 -0
  52. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/uis/about_ui.py +0 -0
  53. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/uis/save_data_dialog_ui.py +0 -0
  54. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/gui/uis/spec_window_ui.py +0 -0
  55. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/io/__init__.py +0 -0
  56. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/io/base.py +0 -0
  57. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/preprocessing/__init__.py +0 -0
  58. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/preprocessing/channel.py +0 -0
  59. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/utils/signals.py +0 -0
  60. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/viz/__init__.py +0 -0
  61. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/viz/hypnogram.py +0 -0
  62. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/viz/signals.py +0 -0
  63. {misleep-0.2.2b0 → misleep-0.2.3}/misleep/viz/spectral.py +0 -0
  64. {misleep-0.2.2b0 → misleep-0.2.3}/misleep.egg-info/dependency_links.txt +0 -0
  65. {misleep-0.2.2b0 → misleep-0.2.3}/misleep.egg-info/top_level.txt +0 -0
  66. {misleep-0.2.2b0 → misleep-0.2.3}/setup.cfg +0 -0
  67. {misleep-0.2.2b0 → misleep-0.2.3}/test/test_annotation_io.py +0 -0
  68. {misleep-0.2.2b0 → misleep-0.2.3}/test/test_loadmat73.py +0 -0
  69. {misleep-0.2.2b0 → misleep-0.2.3}/test/test_midata.py +0 -0
  70. {misleep-0.2.2b0 → misleep-0.2.3}/test/test_show.py +0 -0
  71. {misleep-0.2.2b0 → misleep-0.2.3}/test/test_signal_io.py +0 -0
  72. {misleep-0.2.2b0 → misleep-0.2.3}/test/test_signals_viz.py +0 -0
  73. {misleep-0.2.2b0 → misleep-0.2.3}/test/test_spectral_viz.py +0 -0
@@ -1,6 +1,6 @@
1
1
  BSD 3-Clause License
2
2
 
3
- Copyright (c) 2018, Raphael Vallat
3
+ Copyright (c) 2023, Xueqiang Wang
4
4
  All rights reserved.
5
5
 
6
6
  Redistribution and use in source and binary forms, with or without
misleep-0.2.3/PKG-INFO ADDED
@@ -0,0 +1,95 @@
1
+ Metadata-Version: 2.1
2
+ Name: misleep
3
+ Version: 0.2.3
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
+ ![logo](resources/entire_logo.png)
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
+
@@ -0,0 +1,61 @@
1
+ # MiSleep
2
+ MiSleep is for EEG/EMG signal processing and visualization
3
+
4
+ ![logo](resources/entire_logo.png)
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
+
@@ -1,7 +1,6 @@
1
1
  import sys
2
2
  import os
3
3
  sys.path.append(os.getcwd()+'\misleep')
4
- print(os.getcwd())
5
4
 
6
5
  from misleep.gui.show import show
7
6
 
@@ -0,0 +1,2 @@
1
+
2
+ from .detection import *
@@ -0,0 +1,158 @@
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
+
10
+
11
+ def filter_power_line_noise(data, sf, noise_band='50-100-150'):
12
+ """Use 50-100-150 Hz bandstop filter to filter the power line noise
13
+ The noise band can be '50-100-150' or '60-120-180'
14
+ TODO: add the 60-120-180 band
15
+ """
16
+ filter_band = []
17
+ if noise_band == '50-100-150' and sf > 306:
18
+ filter_band = [[47, 53], [97, 103], [147, 153]]
19
+ elif noise_band == '50-100-150' and sf > 206:
20
+ filter_band = [[47, 53], [97, 103]]
21
+ elif sf > 106:
22
+ filter_band = [[47, 53]]
23
+ for each in filter_band:
24
+ data, _ = misleep.signal_filter(data, sf, btype='bandstop', low=each[0], high=each[1])
25
+
26
+ return data
27
+
28
+ def split_window_data(data, sf, state, window_length=20, stride_length=5):
29
+ """Split the data into several windows with the window length
30
+ window length is in seconds, so we need the sampling frequency (sf) to locate the data point,
31
+ stride length is the step
32
+ Also contain the state information within the data
33
+ """
34
+
35
+ # If the data is shorter than the window length, remove it
36
+ if data.shape[0]/sf < 20:
37
+ return []
38
+
39
+ window_data = []
40
+ # Make sure we have enough data point of the final segment
41
+ data_sec_length = floor(data.shape[0] / sf)
42
+ for i in range(0, data_sec_length-stride_length, stride_length):
43
+ window = data[int(i*sf):int((i+window_length)*sf)]
44
+ window_data.append([window, state])
45
+
46
+ return window_data
47
+
48
+ def delta_theta_ratio(data, sf):
49
+ """Calculate the delta/theta ratio from the original signal data
50
+ Note, here the data is 20 seconds, we only need the ratio from the 5 seconds beginning
51
+ """
52
+ freq, t, Sxx = misleep.spectrogram(data, sf, window=1)
53
+ band_second = np.where(t < 5)
54
+ psd = np.sum(np.array([each[band_second] for each in Sxx]), axis=1)
55
+ band_power = misleep.band_power(psd, freq, bands=[[0.5, 4, 'delta'], [5, 9, 'theta']])
56
+ return band_power['delta'] / band_power['theta']
57
+
58
+ def get_data_features(data, sf, data_format='EEG'):
59
+ """ Get data features, data format can be 'EEG' or 'EMG'
60
+ data: list of window signal data, [[signal_array(20s), signal_label], ...]
61
+ """
62
+
63
+ # Extract the time-domain and frequency domain features from window data, use a dataframe to store all the features
64
+ window_feature_df = pd.DataFrame()
65
+ window_feature_df['label'] = [each[1] for each in data]
66
+
67
+ # Time-domain features, both EEG and EMG
68
+ # 1. standard deviation
69
+ data_std = np.array([np.std(each[0][:int(5*sf)]) for each in data])
70
+ # Set those abnormal points to the upper quartile
71
+ data_std_upper_quantile = np.quantile(data_std, 0.95)
72
+ data_std = [each if each < data_std_upper_quantile else data_std_upper_quantile for each in data_std]
73
+ # Z-score the std features
74
+ window_feature_df[f'{data_format}_std_zscore'] = (data_std - np.mean(data_std)) / np.std(data_std)
75
+
76
+ # 2. Zero crossing rate for EEG and EMG
77
+ zerocross_rate = [antropy.num_zerocross(each[0][:int(5*sf)]) / (5*sf) for each in data]
78
+ window_feature_df[f'{data_format}_zerocross_rate'] = (zerocross_rate - np.mean(zerocross_rate)) / np.std(zerocross_rate)
79
+
80
+ # 3. Hjorth parameters -- Mobility and Complexity
81
+ hjorth = [antropy.hjorth_params(each[0][:int(5*sf)]) for each in data]
82
+ hjorth_M = [each[0] for each in hjorth]
83
+ hjorth_C = [each[1] for each in hjorth]
84
+ window_feature_df[f'{data_format}_Hjorth_M'] = (hjorth_M - np.mean(hjorth_M)) / np.std(hjorth_M)
85
+ window_feature_df[f'{data_format}_Hjorth_C'] = (hjorth_C - np.mean(hjorth_C)) / np.std(hjorth_C)
86
+
87
+ # 4. Permutation entropy
88
+ perm_entropy= [antropy.perm_entropy(each[0][:int(5*sf)]) for each in data]
89
+ window_feature_df[f'{data_format}_perm_entropy'] = (perm_entropy - np.mean(perm_entropy)) / np.std(perm_entropy)
90
+
91
+ # Some features only with EEG
92
+ if data_format.startswith('EEG'):
93
+ # 1. Skewness and kurtosis for EEG signal(s)
94
+ data_skewness = np.array([stats.skew(each[0][:int(5*sf)]) for each in data])
95
+ data_kurtosis = np.array([stats.kurtosis(each[0][:int(5*sf)]) for each in data])
96
+ window_feature_df[f'{data_format}_skewness_zscore'] = (data_skewness - np.mean(data_skewness)) / np.std(data_skewness)
97
+ window_feature_df[f'{data_format}_kurtosis_zscore'] = (data_kurtosis - np.mean(data_kurtosis)) / np.std(data_kurtosis)
98
+ # Frequency-domain features
99
+ # 1. delta/theta
100
+ delta_theta = [delta_theta_ratio(each[0], sf) for each in data]
101
+ window_feature_df[f'{data_format}_delta_theta'] = (delta_theta - np.mean(delta_theta)) / np.std(delta_theta)
102
+
103
+ return window_feature_df
104
+
105
+ def result_constraints(pred_label):
106
+ """
107
+ Once finished the prediction, add some constraints to make the result more smooth
108
+ 1. No REM after Wake, set to NREM
109
+ 2. All one epoch between two same state will be set to the around state
110
+ """
111
+
112
+ pred_label = copy.deepcopy(pred_label)
113
+ for idx in range(1, len(pred_label)-1):
114
+ label_ = pred_label[idx]
115
+ if label_ == 3 and pred_label[idx+1] == 2: # REM after Wake
116
+ pred_label[idx+1] = 1
117
+ if pred_label[idx-1] ==pred_label[idx+1]: # Same state previous and after
118
+ pred_label[idx] = pred_label[idx-1]
119
+
120
+ return pred_label
121
+
122
+
123
+ def auto_stage_gbm(EEG, EMG, sf, EEG_channel='F'):
124
+ """
125
+ Auto stage with lightgbm method
126
+
127
+ Parameters
128
+ ----------
129
+ EEG : array
130
+ EEG data for auto stage. For channel specify, see EEG_channel.
131
+ EMG : array
132
+ EMG data for auto stage.
133
+ sf : double
134
+ Sampling frequency of the EEG and EMG data, should be the same.
135
+ EEG_channel : string
136
+ Specify the channel of EEG, frontal or parietal. Options: {'F', 'P'}. Default is 'F' for frontal.
137
+
138
+ Return
139
+ ------
140
+ pred_label : list
141
+ Predicted labels for every second. May less than the data length for about 15 seconds.
142
+ """
143
+ EEG = split_window_data(EEG, sf, state=4) # All set the initial state '4'
144
+ EMG = split_window_data(EMG, sf, state=4)
145
+
146
+ window_feature_df = pd.DataFrame()
147
+ window_feature_df = pd.concat([get_data_features(EEG, sf, data_format='EEG'), get_data_features(EMG, sf, data_format='EMG')], axis=1)
148
+ window_feature_df = window_feature_df.filter(like='E')
149
+
150
+ if EEG_channel == 'F':
151
+ gbm_model = joblib.load(r'./misleep/analysis/auto_stage_model/EEG_F_lightgbm_20241221.pkl')
152
+ if EEG_channel == 'P':
153
+ gbm_model = joblib.load(r'./misleep/analysis/auto_stage_model/EEG_P_lightgbm_20241221.pkl')
154
+
155
+ pred_label = gbm_model.predict(window_feature_df, num_iteration=gbm_model.best_iteration_)
156
+ pred_label = result_constraints(pred_label)
157
+ pred_label = [item for each in pred_label for item in [each]*5]
158
+ 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.3
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}")