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.
Files changed (73) hide show
  1. {misleep-0.2.1b0 → misleep-0.2.2}/LICENSE +1 -1
  2. misleep-0.2.2/PKG-INFO +95 -0
  3. misleep-0.2.2/README.md +61 -0
  4. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/__main__.py +0 -1
  5. misleep-0.2.2/misleep/analysis/__init__.py +2 -0
  6. misleep-0.2.2/misleep/analysis/auto_stage.py +136 -0
  7. misleep-0.2.2/misleep/analysis/auto_stage_model/EEG_F_lightgbm_20241221.pkl +0 -0
  8. misleep-0.2.2/misleep/analysis/auto_stage_model/EEG_P_lightgbm_20241221.pkl +0 -0
  9. misleep-0.2.2/misleep/analysis/classification.py +0 -0
  10. misleep-0.2.2/misleep/analysis/detection.py +178 -0
  11. misleep-0.2.2/misleep/config.ini +13 -0
  12. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/about.py +0 -2
  13. misleep-0.2.2/misleep/gui/dialog.py +938 -0
  14. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/main_window.py +390 -87
  15. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/show.py +6 -2
  16. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/spec_window.py +27 -24
  17. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/thread.py +5 -5
  18. misleep-0.2.2/misleep/gui/uis/SWA_detect_dialog_ui.py +122 -0
  19. misleep-0.2.2/misleep/gui/uis/auto_stage_dialog_ui.py +69 -0
  20. misleep-0.2.2/misleep/gui/uis/horizontal_line_dialog_ui.py +109 -0
  21. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/label_dialog_ui.py +3 -2
  22. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/main_window_ui.py +161 -169
  23. misleep-0.2.2/misleep/gui/uis/spindle_detect_dialog_ui.py +134 -0
  24. misleep-0.2.2/misleep/gui/uis/state_spectral_dialog_ui.py +109 -0
  25. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/transfer_result_dialog_ui.py +28 -21
  26. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/utils.py +71 -1
  27. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/io/annotation_io.py +41 -55
  28. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/io/signal_io.py +53 -11
  29. misleep-0.2.2/misleep/preprocessing/signals.py +68 -0
  30. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/preprocessing/spectral.py +25 -17
  31. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/utils/__init__.py +2 -1
  32. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/utils/annotation.py +10 -7
  33. misleep-0.2.2/misleep/utils/logger_handler.py +23 -0
  34. misleep-0.2.2/misleep.egg-info/PKG-INFO +95 -0
  35. {misleep-0.2.1b0 → misleep-0.2.2}/misleep.egg-info/SOURCES.txt +12 -0
  36. {misleep-0.2.1b0 → misleep-0.2.2}/misleep.egg-info/requires.txt +2 -0
  37. {misleep-0.2.1b0 → misleep-0.2.2}/setup.py +9 -6
  38. misleep-0.2.1b0/PKG-INFO +0 -96
  39. misleep-0.2.1b0/README.md +0 -64
  40. misleep-0.2.1b0/misleep/config.ini +0 -12
  41. misleep-0.2.1b0/misleep/gui/dialog.py +0 -203
  42. misleep-0.2.1b0/misleep/preprocessing/signals.py +0 -15
  43. misleep-0.2.1b0/misleep.egg-info/PKG-INFO +0 -96
  44. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/__init__.py +0 -0
  45. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/__init__.py +0 -0
  46. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/resources/__init__.py +0 -0
  47. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/resources/entire_logo.png +0 -0
  48. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/resources/logo.png +0 -0
  49. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/resources/misleep.py +0 -0
  50. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/resources/misleep.qrc +0 -0
  51. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/__init__.py +0 -0
  52. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/about_ui.py +0 -0
  53. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/save_data_dialog_ui.py +0 -0
  54. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/gui/uis/spec_window_ui.py +0 -0
  55. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/io/__init__.py +0 -0
  56. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/io/base.py +0 -0
  57. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/preprocessing/__init__.py +0 -0
  58. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/preprocessing/channel.py +0 -0
  59. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/utils/signals.py +0 -0
  60. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/viz/__init__.py +0 -0
  61. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/viz/hypnogram.py +0 -0
  62. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/viz/signals.py +0 -0
  63. {misleep-0.2.1b0 → misleep-0.2.2}/misleep/viz/spectral.py +0 -0
  64. {misleep-0.2.1b0 → misleep-0.2.2}/misleep.egg-info/dependency_links.txt +0 -0
  65. {misleep-0.2.1b0 → misleep-0.2.2}/misleep.egg-info/top_level.txt +0 -0
  66. {misleep-0.2.1b0 → misleep-0.2.2}/setup.cfg +0 -0
  67. {misleep-0.2.1b0 → misleep-0.2.2}/test/test_annotation_io.py +0 -0
  68. {misleep-0.2.1b0 → misleep-0.2.2}/test/test_loadmat73.py +0 -0
  69. {misleep-0.2.1b0 → misleep-0.2.2}/test/test_midata.py +0 -0
  70. {misleep-0.2.1b0 → misleep-0.2.2}/test/test_show.py +0 -0
  71. {misleep-0.2.1b0 → misleep-0.2.2}/test/test_signal_io.py +0 -0
  72. {misleep-0.2.1b0 → misleep-0.2.2}/test/test_signals_viz.py +0 -0
  73. {misleep-0.2.1b0 → misleep-0.2.2}/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.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
+ ![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.
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.
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,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}")