misleep 0.2.5__tar.gz → 0.2.6__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.5 → misleep-0.2.6}/PKG-INFO +1 -1
- {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage.py +5 -5
- {misleep-0.2.5 → misleep-0.2.6}/misleep/config.ini +2 -2
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/dialog.py +46 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/main_window.py +3 -3
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/state_spectral_dialog_ui.py +18 -14
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/utils.py +2 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/utils/__init__.py +2 -1
- misleep-0.2.6/misleep/utils/self_antropy.py +386 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep.egg-info/PKG-INFO +1 -1
- {misleep-0.2.5 → misleep-0.2.6}/misleep.egg-info/SOURCES.txt +1 -0
- {misleep-0.2.5 → misleep-0.2.6}/setup.py +1 -1
- {misleep-0.2.5 → misleep-0.2.6}/LICENSE +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/README.md +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/__init__.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/__main__.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/__init__.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage_model/P30_EEG_F_lightgbm.pkl +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage_model/P30_EEG_P_lightgbm.pkl +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage_model/ado_EEG_F_lightgbm.pkl +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage_model/ado_EEG_P_lightgbm.pkl +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage_model/adult_EEG_F_lightgbm.pkl +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage_model/adult_EEG_P_lightgbm.pkl +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/detection.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/__init__.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/about.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/resources/__init__.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/resources/entire_logo.png +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/resources/logo.png +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/resources/misleep.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/resources/misleep.qrc +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/show.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/spec_window.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/thread.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/SWA_detect_dialog_ui.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/__init__.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/about_ui.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/auto_stage_dialog_ui.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/horizontal_line_dialog_ui.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/label_dialog_ui.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/main_window_ui.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/save_data_dialog_ui.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/spec_window_ui.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/spindle_detect_dialog_ui.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/transfer_result_dialog_ui.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/io/__init__.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/io/annotation_io.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/io/base.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/io/signal_io.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/preprocessing/__init__.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/preprocessing/channel.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/preprocessing/signals.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/preprocessing/spectral.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/utils/annotation.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/utils/logger_handler.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/utils/signals.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/viz/__init__.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/viz/hypnogram.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/viz/signals.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep/viz/spectral.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep.egg-info/dependency_links.txt +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep.egg-info/requires.txt +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/misleep.egg-info/top_level.txt +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/setup.cfg +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/test/test_annotation_io.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/test/test_loadmat73.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/test/test_midata.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/test/test_show.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/test/test_signal_io.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/test/test_signals_viz.py +0 -0
- {misleep-0.2.5 → misleep-0.2.6}/test/test_spectral_viz.py +0 -0
|
@@ -3,7 +3,7 @@ import numpy as np
|
|
|
3
3
|
from math import floor
|
|
4
4
|
import pandas as pd
|
|
5
5
|
from scipy import stats
|
|
6
|
-
import
|
|
6
|
+
from misleep.utils.self_antropy import num_zerocross, perm_entropy, hjorth_params
|
|
7
7
|
import joblib
|
|
8
8
|
import copy
|
|
9
9
|
import lightgbm
|
|
@@ -81,19 +81,19 @@ def get_data_features(data, sf, data_format='EEG'):
|
|
|
81
81
|
window_feature_df[f'{data_format}_std_zscore'] = self_zscore(data_std)
|
|
82
82
|
|
|
83
83
|
# 2. Zero crossing rate for EEG and EMG
|
|
84
|
-
zerocross_rate = [
|
|
84
|
+
zerocross_rate = [num_zerocross(each[0][:int(5*sf)]) / (5*sf) for each in data]
|
|
85
85
|
window_feature_df[f'{data_format}_zerocross_rate'] = (zerocross_rate - np.mean(zerocross_rate)) / np.std(zerocross_rate)
|
|
86
86
|
|
|
87
87
|
# 3. Hjorth parameters -- Mobility and Complexity
|
|
88
|
-
hjorth = [
|
|
88
|
+
hjorth = [hjorth_params(each[0][:int(5*sf)]) for each in data]
|
|
89
89
|
hjorth_M = [each[0] for each in hjorth]
|
|
90
90
|
hjorth_C = [each[1] for each in hjorth]
|
|
91
91
|
window_feature_df[f'{data_format}_Hjorth_M'] = self_zscore(hjorth_M)
|
|
92
92
|
window_feature_df[f'{data_format}_Hjorth_C'] = self_zscore(hjorth_C)
|
|
93
93
|
|
|
94
94
|
# 4. Permutation entropy
|
|
95
|
-
|
|
96
|
-
window_feature_df[f'{data_format}_perm_entropy'] = self_zscore(
|
|
95
|
+
perm_entropy_ = [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
97
|
|
|
98
98
|
# Some features only with EEG
|
|
99
99
|
if data_format.startswith('EEG'):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[gui]
|
|
2
|
-
version = v0.2.
|
|
3
|
-
updatetime = 2025/
|
|
2
|
+
version = v0.2.6
|
|
3
|
+
updatetime = 2025/3/24
|
|
4
4
|
marker = ['good', 'first REM', 'WindEEG', 'W-R ', 'maker']
|
|
5
5
|
startend = ['burst-supression', 'REM', 'Wake', 'Spindle', 'SWA', 'start end label', 'start end label']
|
|
6
6
|
statemap = {"1": "NREM", "2": "REM", "3": "Wake", "4": "INIT", "5": "IS", "6": "MicroArousal"}
|
|
@@ -396,6 +396,49 @@ class stateSpectral_dialog(QDialog, Ui_StateSpectralDialog):
|
|
|
396
396
|
4: 'Init'
|
|
397
397
|
}
|
|
398
398
|
|
|
399
|
+
# Check the hour segmentation checkbox, and calculate the spectrum for each hour, with the second
|
|
400
|
+
# if enabled
|
|
401
|
+
NREM_hour_spec = []
|
|
402
|
+
REM_hour_spec = []
|
|
403
|
+
Wake_hour_spec = []
|
|
404
|
+
if self.HourSegmentCheckBox.isChecked():
|
|
405
|
+
for sec in range(start_sec, end_sec, 3600):
|
|
406
|
+
sleep_state = mianno.sleep_state[sec:sec+3601]
|
|
407
|
+
sleep_state = lst2group([[idx, each] for idx, each in enumerate(sleep_state)])
|
|
408
|
+
|
|
409
|
+
# Merge 4 states' data
|
|
410
|
+
NREM_data = [channel_data[int((each[0]+sec)*sf): int((each[1]+sec)*sf)]
|
|
411
|
+
for each in sleep_state if each[2] == 1]
|
|
412
|
+
NREM_data = [element for sublist in NREM_data for element in sublist]
|
|
413
|
+
REM_data = [channel_data[int((each[0]+sec)*sf): int((each[1]+sec)*sf)]
|
|
414
|
+
for each in sleep_state if each[2] == 2]
|
|
415
|
+
REM_data = [element for sublist in REM_data for element in sublist]
|
|
416
|
+
Wake_data = [channel_data[int((each[0]+sec)*sf): int((each[1]+sec)*sf)]
|
|
417
|
+
for each in sleep_state if each[2] == 3]
|
|
418
|
+
Wake_data = [element for sublist in Wake_data for element in sublist]
|
|
419
|
+
Init_data = [channel_data[int((each[0]+sec)*sf): int((each[1]+sec)*sf)]
|
|
420
|
+
for each in sleep_state if each[2] == 4]
|
|
421
|
+
Init_data = [element for sublist in Init_data for element in sublist]
|
|
422
|
+
|
|
423
|
+
if self.RejectArtifactCheckBox.isChecked():
|
|
424
|
+
threshold = self.ArtThresholdSpinBox.value()
|
|
425
|
+
else:
|
|
426
|
+
threshold = 1.5
|
|
427
|
+
if self.RejectArtifactCheckBox.isChecked():
|
|
428
|
+
NREM_data = reject_artifact(NREM_data, sf=sf, threshold=threshold)
|
|
429
|
+
REM_data = reject_artifact(REM_data, sf=sf, threshold=threshold)
|
|
430
|
+
Wake_data = reject_artifact(Wake_data, sf=sf, threshold=threshold)
|
|
431
|
+
Init_data = reject_artifact(Init_data, sf=sf, threshold=threshold)
|
|
432
|
+
|
|
433
|
+
NREM_hour_spec.append(cal_draw_spectrum(data=NREM_data, sf=sf,
|
|
434
|
+
nperseg=nperseg, relative=relative)[0][1])
|
|
435
|
+
REM_hour_spec.append(cal_draw_spectrum(data=REM_data, sf=sf,
|
|
436
|
+
nperseg=nperseg, relative=relative)[0][1])
|
|
437
|
+
Wake_hour_spec.append(cal_draw_spectrum(data=Wake_data, sf=sf,
|
|
438
|
+
nperseg=nperseg, relative=relative)[0][1])
|
|
439
|
+
hour_spec = [NREM_hour_spec, REM_hour_spec, Wake_hour_spec]
|
|
440
|
+
|
|
441
|
+
|
|
399
442
|
fd = QFileDialog.getExistingDirectory(self,
|
|
400
443
|
"Select a folder to save 4 stages' data",
|
|
401
444
|
f"{config['gui']['openpath']}")
|
|
@@ -411,6 +454,9 @@ class stateSpectral_dialog(QDialog, Ui_StateSpectralDialog):
|
|
|
411
454
|
# Write to excel file
|
|
412
455
|
for idx, spec in enumerate([NREM_spec, REM_spec, Wake_spec]):
|
|
413
456
|
_df = pd.DataFrame(data=spec.T, columns=['frequency', 'power'])
|
|
457
|
+
if hour_spec[idx] != []:
|
|
458
|
+
# Add the hour spectrum to the dataframe
|
|
459
|
+
_df[[str(each) for each in range(1, len(hour_spec[idx])+1)]] = pd.DataFrame(hour_spec[idx]).T
|
|
414
460
|
_df.to_excel(excel_writer=writer, sheet_name=name_map[idx+1], index=False)
|
|
415
461
|
if len(Init_data) > sf*10:
|
|
416
462
|
Init_spec, Init_figure = cal_draw_spectrum(data=Init_data, sf=sf,
|
|
@@ -1332,11 +1332,11 @@ class main_window(QMainWindow, Ui_MiSleep):
|
|
|
1332
1332
|
|
|
1333
1333
|
def show_spec_window(self):
|
|
1334
1334
|
"""Show spectrum dialog, triggered by PlotSpecBt"""
|
|
1335
|
-
if self.SleepStateRadio.isChecked() and len(self.start_end) == 2 and self.start_end[1] - self.start_end[0] >=
|
|
1335
|
+
if self.SleepStateRadio.isChecked() and len(self.start_end) == 2 and self.start_end[1] - self.start_end[0] >= 5:
|
|
1336
1336
|
start_ = self.start_end[0]
|
|
1337
1337
|
end_ = self.start_end[1]
|
|
1338
1338
|
|
|
1339
|
-
elif self.StartEndRadio.isChecked() and len(self.start_end_ms) == 2 and self.start_end_ms[1] - self.start_end_ms[0] >=
|
|
1339
|
+
elif self.StartEndRadio.isChecked() and len(self.start_end_ms) == 2 and self.start_end_ms[1] - self.start_end_ms[0] >= 5:
|
|
1340
1340
|
start_ = self.start_end_ms[0]
|
|
1341
1341
|
end_ = self.start_end_ms[1]
|
|
1342
1342
|
|
|
@@ -1344,7 +1344,7 @@ class main_window(QMainWindow, Ui_MiSleep):
|
|
|
1344
1344
|
QMessageBox.about(
|
|
1345
1345
|
self,
|
|
1346
1346
|
"Error",
|
|
1347
|
-
"Please select a start-end larger than
|
|
1347
|
+
"Please select a start-end larger than 5 seconds.",
|
|
1348
1348
|
)
|
|
1349
1349
|
return
|
|
1350
1350
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Form implementation generated from reading ui file 'misleep/gui/uis/state_spectral_dialog.ui'
|
|
4
4
|
#
|
|
5
|
-
# Created by: PyQt5 UI code generator 5.15.
|
|
5
|
+
# Created by: PyQt5 UI code generator 5.15.9
|
|
6
6
|
#
|
|
7
7
|
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
|
|
8
8
|
# run again. Do not edit this file unless you know what you are doing.
|
|
@@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets
|
|
|
14
14
|
class Ui_StateSpectralDialog(object):
|
|
15
15
|
def setupUi(self, StateSpectralDialog):
|
|
16
16
|
StateSpectralDialog.setObjectName("StateSpectralDialog")
|
|
17
|
-
StateSpectralDialog.resize(
|
|
17
|
+
StateSpectralDialog.resize(279, 471)
|
|
18
18
|
icon = QtGui.QIcon()
|
|
19
19
|
icon.addPixmap(QtGui.QPixmap(":/logo/logo.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
|
20
20
|
StateSpectralDialog.setWindowIcon(icon)
|
|
@@ -73,18 +73,21 @@ class Ui_StateSpectralDialog(object):
|
|
|
73
73
|
self.groupBox_2.setObjectName("groupBox_2")
|
|
74
74
|
self.gridLayout_3 = QtWidgets.QGridLayout(self.groupBox_2)
|
|
75
75
|
self.gridLayout_3.setObjectName("gridLayout_3")
|
|
76
|
-
self.EndTimeEditor = QtWidgets.QDateTimeEdit(self.groupBox_2)
|
|
77
|
-
self.EndTimeEditor.setObjectName("EndTimeEditor")
|
|
78
|
-
self.gridLayout_3.addWidget(self.EndTimeEditor, 3, 0, 1, 1)
|
|
79
|
-
self.StartTimeEditor = QtWidgets.QDateTimeEdit(self.groupBox_2)
|
|
80
|
-
self.StartTimeEditor.setObjectName("StartTimeEditor")
|
|
81
|
-
self.gridLayout_3.addWidget(self.StartTimeEditor, 1, 0, 1, 1)
|
|
82
|
-
self.StartTimeCheckBox = QtWidgets.QCheckBox(self.groupBox_2)
|
|
83
|
-
self.StartTimeCheckBox.setObjectName("StartTimeCheckBox")
|
|
84
|
-
self.gridLayout_3.addWidget(self.StartTimeCheckBox, 0, 0, 1, 1)
|
|
85
76
|
self.EndTimeCheckBox = QtWidgets.QCheckBox(self.groupBox_2)
|
|
86
77
|
self.EndTimeCheckBox.setObjectName("EndTimeCheckBox")
|
|
87
78
|
self.gridLayout_3.addWidget(self.EndTimeCheckBox, 2, 0, 1, 1)
|
|
79
|
+
self.StartTimeCheckBox = QtWidgets.QCheckBox(self.groupBox_2)
|
|
80
|
+
self.StartTimeCheckBox.setObjectName("StartTimeCheckBox")
|
|
81
|
+
self.gridLayout_3.addWidget(self.StartTimeCheckBox, 0, 0, 1, 1)
|
|
82
|
+
self.StartTimeEditor = QtWidgets.QDateTimeEdit(self.groupBox_2)
|
|
83
|
+
self.StartTimeEditor.setObjectName("StartTimeEditor")
|
|
84
|
+
self.gridLayout_3.addWidget(self.StartTimeEditor, 1, 0, 1, 1)
|
|
85
|
+
self.EndTimeEditor = QtWidgets.QDateTimeEdit(self.groupBox_2)
|
|
86
|
+
self.EndTimeEditor.setObjectName("EndTimeEditor")
|
|
87
|
+
self.gridLayout_3.addWidget(self.EndTimeEditor, 3, 0, 1, 1)
|
|
88
|
+
self.HourSegmentCheckBox = QtWidgets.QCheckBox(self.groupBox_2)
|
|
89
|
+
self.HourSegmentCheckBox.setObjectName("HourSegmentCheckBox")
|
|
90
|
+
self.gridLayout_3.addWidget(self.HourSegmentCheckBox, 4, 0, 1, 1)
|
|
88
91
|
self.gridLayout_2.addWidget(self.groupBox_2, 0, 0, 1, 2)
|
|
89
92
|
|
|
90
93
|
self.retranslateUi(StateSpectralDialog)
|
|
@@ -102,8 +105,9 @@ class Ui_StateSpectralDialog(object):
|
|
|
102
105
|
self.MergeDataCheckBox.setText(_translate("StateSpectralDialog", "Merge data"))
|
|
103
106
|
self.CancelBt.setText(_translate("StateSpectralDialog", "Cancel"))
|
|
104
107
|
self.groupBox_2.setTitle(_translate("StateSpectralDialog", "Time options"))
|
|
105
|
-
self.EndTimeEditor.setDisplayFormat(_translate("StateSpectralDialog", "yyyy/MM/dd HH:mm:ss"))
|
|
106
|
-
self.StartTimeEditor.setDisplayFormat(_translate("StateSpectralDialog", "yyyy/MM/dd HH:mm:ss"))
|
|
107
|
-
self.StartTimeCheckBox.setText(_translate("StateSpectralDialog", "Start time"))
|
|
108
108
|
self.EndTimeCheckBox.setText(_translate("StateSpectralDialog", "Stop time"))
|
|
109
|
+
self.StartTimeCheckBox.setText(_translate("StateSpectralDialog", "Start time"))
|
|
110
|
+
self.StartTimeEditor.setDisplayFormat(_translate("StateSpectralDialog", "yyyy/MM/dd HH:mm:ss"))
|
|
111
|
+
self.EndTimeEditor.setDisplayFormat(_translate("StateSpectralDialog", "yyyy/MM/dd HH:mm:ss"))
|
|
112
|
+
self.HourSegmentCheckBox.setText(_translate("StateSpectralDialog", "Hour segmentation"))
|
|
109
113
|
from misleep.gui.resources import misleep
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
@Project: misleep
|
|
4
|
+
@File: signals.py
|
|
5
|
+
@Author: Xueqiang Wang
|
|
6
|
+
@Date: 2025/2/21
|
|
7
|
+
@Description: Self implemente some functions from the 'antropy' package,
|
|
8
|
+
because to import the antropy package cost a lot of time
|
|
9
|
+
I just copy the functions there
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from math import factorial
|
|
14
|
+
|
|
15
|
+
def num_zerocross(x, normalize=False, axis=-1):
|
|
16
|
+
"""Number of zero-crossings.
|
|
17
|
+
|
|
18
|
+
.. versionadded: 0.1.3
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
x : list or np.array
|
|
23
|
+
1D or N-D data.
|
|
24
|
+
normalize : bool
|
|
25
|
+
If True, divide by the number of samples to normalize the output
|
|
26
|
+
between 0 and 1. Otherwise, return the absolute number of zero
|
|
27
|
+
crossings.
|
|
28
|
+
axis : int
|
|
29
|
+
The axis along which to perform the computation. Default is -1 (last).
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
nzc : int or float
|
|
34
|
+
Number of zero-crossings.
|
|
35
|
+
|
|
36
|
+
Examples
|
|
37
|
+
--------
|
|
38
|
+
Simple examples
|
|
39
|
+
|
|
40
|
+
>>> import numpy as np
|
|
41
|
+
>>> import antropy as ant
|
|
42
|
+
>>> ant.num_zerocross([-1, 0, 1, 2, 3])
|
|
43
|
+
1
|
|
44
|
+
|
|
45
|
+
>>> ant.num_zerocross([0, 0, 2, -1, 0, 1, 0, 2])
|
|
46
|
+
2
|
|
47
|
+
|
|
48
|
+
Number of zero crossings of a pure sine
|
|
49
|
+
|
|
50
|
+
>>> import numpy as np
|
|
51
|
+
>>> import antropy as ant
|
|
52
|
+
>>> sf, f, dur = 100, 1, 4
|
|
53
|
+
>>> N = sf * dur # Total number of discrete samples
|
|
54
|
+
>>> t = np.arange(N) / sf # Time vector
|
|
55
|
+
>>> x = np.sin(2 * np.pi * f * t)
|
|
56
|
+
>>> ant.num_zerocross(x)
|
|
57
|
+
7
|
|
58
|
+
|
|
59
|
+
Random 2D data
|
|
60
|
+
|
|
61
|
+
>>> np.random.seed(42)
|
|
62
|
+
>>> x = np.random.normal(size=(4, 3000))
|
|
63
|
+
>>> ant.num_zerocross(x)
|
|
64
|
+
array([1499, 1528, 1547, 1457])
|
|
65
|
+
|
|
66
|
+
Same but normalized by the number of samples
|
|
67
|
+
|
|
68
|
+
>>> np.round(ant.num_zerocross(x, normalize=True), 4)
|
|
69
|
+
array([0.4997, 0.5093, 0.5157, 0.4857])
|
|
70
|
+
|
|
71
|
+
Fractional Gaussian noise with H = 0.5
|
|
72
|
+
|
|
73
|
+
>>> import stochastic.processes.noise as sn
|
|
74
|
+
>>> rng = np.random.default_rng(seed=42)
|
|
75
|
+
>>> x = sn.FractionalGaussianNoise(hurst=0.5, rng=rng).sample(10000)
|
|
76
|
+
>>> print(f"{ant.num_zerocross(x, normalize=True):.4f}")
|
|
77
|
+
0.4973
|
|
78
|
+
|
|
79
|
+
Fractional Gaussian noise with H = 0.9
|
|
80
|
+
|
|
81
|
+
>>> rng = np.random.default_rng(seed=42)
|
|
82
|
+
>>> x = sn.FractionalGaussianNoise(hurst=0.9, rng=rng).sample(10000)
|
|
83
|
+
>>> print(f"{ant.num_zerocross(x, normalize=True):.4f}")
|
|
84
|
+
0.2615
|
|
85
|
+
|
|
86
|
+
Fractional Gaussian noise with H = 0.1
|
|
87
|
+
|
|
88
|
+
>>> rng = np.random.default_rng(seed=42)
|
|
89
|
+
>>> x = sn.FractionalGaussianNoise(hurst=0.1, rng=rng).sample(10000)
|
|
90
|
+
>>> print(f"{ant.num_zerocross(x, normalize=True):.4f}")
|
|
91
|
+
0.6451
|
|
92
|
+
"""
|
|
93
|
+
x = np.asarray(x)
|
|
94
|
+
# https://stackoverflow.com/a/29674950/10581531
|
|
95
|
+
nzc = np.diff(np.signbit(x), axis=axis).sum(axis=axis)
|
|
96
|
+
if normalize:
|
|
97
|
+
nzc = nzc / x.shape[axis]
|
|
98
|
+
return nzc
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def hjorth_params(x, axis=-1):
|
|
102
|
+
"""Calculate Hjorth mobility and complexity on given axis.
|
|
103
|
+
|
|
104
|
+
.. versionadded: 0.1.3
|
|
105
|
+
|
|
106
|
+
Parameters
|
|
107
|
+
----------
|
|
108
|
+
x : list or np.array
|
|
109
|
+
1D or N-D data.
|
|
110
|
+
axis : int
|
|
111
|
+
The axis along which to perform the computation. Default is -1 (last).
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
mobility, complexity : float
|
|
116
|
+
Mobility and complexity parameters.
|
|
117
|
+
|
|
118
|
+
Notes
|
|
119
|
+
-----
|
|
120
|
+
Hjorth Parameters are indicators of statistical properties used in signal
|
|
121
|
+
processing in the time domain introduced by Bo Hjorth in 1970. The
|
|
122
|
+
parameters are activity, mobility, and complexity. EntroPy only returns the
|
|
123
|
+
mobility and complexity parameters, since activity is simply the variance
|
|
124
|
+
of :math:`x`, which can be computed easily with :py:func:`numpy.var`.
|
|
125
|
+
|
|
126
|
+
The **mobility** parameter represents the mean frequency or the proportion
|
|
127
|
+
of standard deviation of the power spectrum. This is defined as the square
|
|
128
|
+
root of variance of the first derivative of :math:`x` divided by the
|
|
129
|
+
variance of :math:`x`.
|
|
130
|
+
|
|
131
|
+
The **complexity** gives an estimate of the bandwidth of the signal, which
|
|
132
|
+
indicates the similarity of the shape of the signal to a pure sine wave
|
|
133
|
+
(where the value converges to 1). Complexity is defined as the ratio of
|
|
134
|
+
the mobility of the first derivative of :math:`x` to the mobility of
|
|
135
|
+
:math:`x`.
|
|
136
|
+
|
|
137
|
+
References
|
|
138
|
+
----------
|
|
139
|
+
- https://en.wikipedia.org/wiki/Hjorth_parameters
|
|
140
|
+
- https://doi.org/10.1016%2F0013-4694%2870%2990143-4
|
|
141
|
+
|
|
142
|
+
Examples
|
|
143
|
+
--------
|
|
144
|
+
Hjorth parameters of a pure sine
|
|
145
|
+
|
|
146
|
+
>>> import numpy as np
|
|
147
|
+
>>> import antropy as ant
|
|
148
|
+
>>> sf, f, dur = 100, 1, 4
|
|
149
|
+
>>> N = sf * dur # Total number of discrete samples
|
|
150
|
+
>>> t = np.arange(N) / sf # Time vector
|
|
151
|
+
>>> x = np.sin(2 * np.pi * f * t)
|
|
152
|
+
>>> np.round(ant.hjorth_params(x), 4)
|
|
153
|
+
array([0.0627, 1.005 ])
|
|
154
|
+
|
|
155
|
+
Random 2D data
|
|
156
|
+
|
|
157
|
+
>>> np.random.seed(42)
|
|
158
|
+
>>> x = np.random.normal(size=(4, 3000))
|
|
159
|
+
>>> mob, com = ant.hjorth_params(x)
|
|
160
|
+
>>> print(mob)
|
|
161
|
+
[1.42145064 1.4339572 1.42186993 1.40587512]
|
|
162
|
+
|
|
163
|
+
>>> print(com)
|
|
164
|
+
[1.21877527 1.21092261 1.217278 1.22623163]
|
|
165
|
+
|
|
166
|
+
Fractional Gaussian noise with H = 0.5
|
|
167
|
+
|
|
168
|
+
>>> import stochastic.processes.noise as sn
|
|
169
|
+
>>> rng = np.random.default_rng(seed=42)
|
|
170
|
+
>>> x = sn.FractionalGaussianNoise(hurst=0.5, rng=rng).sample(10000)
|
|
171
|
+
>>> np.round(ant.hjorth_params(x), 4)
|
|
172
|
+
array([1.4073, 1.2283])
|
|
173
|
+
|
|
174
|
+
Fractional Gaussian noise with H = 0.9
|
|
175
|
+
|
|
176
|
+
>>> rng = np.random.default_rng(seed=42)
|
|
177
|
+
>>> x = sn.FractionalGaussianNoise(hurst=0.9, rng=rng).sample(10000)
|
|
178
|
+
>>> np.round(ant.hjorth_params(x), 4)
|
|
179
|
+
array([0.8395, 1.9143])
|
|
180
|
+
|
|
181
|
+
Fractional Gaussian noise with H = 0.1
|
|
182
|
+
|
|
183
|
+
>>> rng = np.random.default_rng(seed=42)
|
|
184
|
+
>>> x = sn.FractionalGaussianNoise(hurst=0.1, rng=rng).sample(10000)
|
|
185
|
+
>>> np.round(ant.hjorth_params(x), 4)
|
|
186
|
+
array([1.6917, 1.0717])
|
|
187
|
+
"""
|
|
188
|
+
x = np.asarray(x)
|
|
189
|
+
# Calculate derivatives
|
|
190
|
+
dx = np.diff(x, axis=axis)
|
|
191
|
+
ddx = np.diff(dx, axis=axis)
|
|
192
|
+
# Calculate variance
|
|
193
|
+
x_var = np.var(x, axis=axis) # = activity
|
|
194
|
+
dx_var = np.var(dx, axis=axis)
|
|
195
|
+
ddx_var = np.var(ddx, axis=axis)
|
|
196
|
+
# Mobility and complexity
|
|
197
|
+
mob = np.sqrt(dx_var / x_var)
|
|
198
|
+
com = np.sqrt(ddx_var / dx_var) / mob
|
|
199
|
+
return mob, com
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def perm_entropy(x, order=3, delay=1, normalize=False):
|
|
203
|
+
"""Permutation Entropy.
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
x : list or np.array
|
|
208
|
+
One-dimensional time series of shape (n_times)
|
|
209
|
+
order : int
|
|
210
|
+
Order of permutation entropy. Default is 3.
|
|
211
|
+
delay : int, list, np.ndarray or range
|
|
212
|
+
Time delay (lag). Default is 1. If multiple values are passed
|
|
213
|
+
(e.g. [1, 2, 3]), AntroPy will calculate the average permutation
|
|
214
|
+
entropy across all these delays.
|
|
215
|
+
normalize : bool
|
|
216
|
+
If True, divide by log2(order!) to normalize the entropy between 0
|
|
217
|
+
and 1. Otherwise, return the permutation entropy in bit.
|
|
218
|
+
|
|
219
|
+
Returns
|
|
220
|
+
-------
|
|
221
|
+
pe : float
|
|
222
|
+
Permutation Entropy.
|
|
223
|
+
|
|
224
|
+
Notes
|
|
225
|
+
-----
|
|
226
|
+
The permutation entropy is a complexity measure for time-series first
|
|
227
|
+
introduced by Bandt and Pompe in 2002.
|
|
228
|
+
|
|
229
|
+
The permutation entropy of a signal :math:`x` is defined as:
|
|
230
|
+
|
|
231
|
+
.. math:: H = -\\sum p(\\pi)\\log_2(\\pi)
|
|
232
|
+
|
|
233
|
+
where the sum runs over all :math:`n!` permutations :math:`\\pi` of order
|
|
234
|
+
:math:`n`. This is the information contained in comparing :math:`n`
|
|
235
|
+
consecutive values of the time series. It is clear that
|
|
236
|
+
:math:`0 ≤ H (n) ≤ \\log_2(n!)` where the lower bound is attained for an
|
|
237
|
+
increasing or decreasing sequence of values, and the upper bound for a
|
|
238
|
+
completely random system where all :math:`n!` possible permutations appear
|
|
239
|
+
with the same probability.
|
|
240
|
+
|
|
241
|
+
The embedded matrix :math:`Y` is created by:
|
|
242
|
+
|
|
243
|
+
.. math::
|
|
244
|
+
y(i)=[x_i,x_{i+\\text{delay}}, ...,x_{i+(\\text{order}-1) *
|
|
245
|
+
\\text{delay}}]
|
|
246
|
+
|
|
247
|
+
.. math:: Y=[y(1),y(2),...,y(N-(\\text{order}-1))*\\text{delay})]^T
|
|
248
|
+
|
|
249
|
+
References
|
|
250
|
+
----------
|
|
251
|
+
Bandt, Christoph, and Bernd Pompe. "Permutation entropy: a
|
|
252
|
+
natural complexity measure for time series." Physical review letters
|
|
253
|
+
88.17 (2002): 174102.
|
|
254
|
+
|
|
255
|
+
Examples
|
|
256
|
+
--------
|
|
257
|
+
Permutation entropy with order 2
|
|
258
|
+
|
|
259
|
+
>>> import numpy as np
|
|
260
|
+
>>> import antropy as ant
|
|
261
|
+
>>> import stochastic.processes.noise as sn
|
|
262
|
+
>>> x = [4, 7, 9, 10, 6, 11, 3]
|
|
263
|
+
>>> # Return a value in bit between 0 and log2(factorial(order))
|
|
264
|
+
>>> print(f"{ant.perm_entropy(x, order=2):.4f}")
|
|
265
|
+
0.9183
|
|
266
|
+
|
|
267
|
+
Normalized permutation entropy with order 3
|
|
268
|
+
|
|
269
|
+
>>> # Return a value comprised between 0 and 1.
|
|
270
|
+
>>> print(f"{ant.perm_entropy(x, normalize=True):.4f}")
|
|
271
|
+
0.5888
|
|
272
|
+
|
|
273
|
+
Fractional Gaussian noise with H = 0.5, averaged across multiple delays
|
|
274
|
+
>>> rng = np.random.default_rng(seed=42)
|
|
275
|
+
>>> x = sn.FractionalGaussianNoise(hurst=0.5, rng=rng).sample(10000)
|
|
276
|
+
>>> print(f"{ant.perm_entropy(x, delay=[1, 2, 3], normalize=True):.4f}")
|
|
277
|
+
0.9999
|
|
278
|
+
|
|
279
|
+
Fractional Gaussian noise with H = 0.1, averaged across multiple delays
|
|
280
|
+
|
|
281
|
+
>>> rng = np.random.default_rng(seed=42)
|
|
282
|
+
>>> x = sn.FractionalGaussianNoise(hurst=0.1, rng=rng).sample(10000)
|
|
283
|
+
>>> print(f"{ant.perm_entropy(x, delay=[1, 2, 3], normalize=True):.4f}")
|
|
284
|
+
0.9986
|
|
285
|
+
|
|
286
|
+
Random
|
|
287
|
+
|
|
288
|
+
>>> rng = np.random.default_rng(seed=42)
|
|
289
|
+
>>> print(f"{ant.perm_entropy(rng.random(1000), normalize=True):.4f}")
|
|
290
|
+
0.9997
|
|
291
|
+
|
|
292
|
+
Pure sine wave
|
|
293
|
+
|
|
294
|
+
>>> x = np.sin(2 * np.pi * 1 * np.arange(3000) / 100)
|
|
295
|
+
>>> print(f"{ant.perm_entropy(x, normalize=True):.4f}")
|
|
296
|
+
0.4463
|
|
297
|
+
|
|
298
|
+
Linearly-increasing time-series
|
|
299
|
+
|
|
300
|
+
>>> x = np.arange(1000)
|
|
301
|
+
>>> print(f"{ant.perm_entropy(x, normalize=True):.4f}")
|
|
302
|
+
-0.0000
|
|
303
|
+
"""
|
|
304
|
+
# If multiple delay are passed, return the average across all d
|
|
305
|
+
if isinstance(delay, (list, np.ndarray, range)):
|
|
306
|
+
return np.mean([perm_entropy(x, order=order, delay=d, normalize=normalize) for d in delay])
|
|
307
|
+
x = np.array(x)
|
|
308
|
+
ran_order = range(order)
|
|
309
|
+
hashmult = np.power(order, ran_order)
|
|
310
|
+
assert delay > 0, "delay must be greater than zero."
|
|
311
|
+
# Embed x and sort the order of permutations
|
|
312
|
+
sorted_idx = _embed(x, order=order, delay=delay).argsort(kind="quicksort")
|
|
313
|
+
# Associate unique integer to each permutations
|
|
314
|
+
hashval = (np.multiply(sorted_idx, hashmult)).sum(1)
|
|
315
|
+
# Return the counts
|
|
316
|
+
_, c = np.unique(hashval, return_counts=True)
|
|
317
|
+
# Use np.true_divide for Python 2 compatibility
|
|
318
|
+
p = np.true_divide(c, c.sum())
|
|
319
|
+
pe = -_xlogx(p).sum()
|
|
320
|
+
if normalize:
|
|
321
|
+
pe /= np.log2(factorial(order))
|
|
322
|
+
return pe
|
|
323
|
+
|
|
324
|
+
def _embed(x, order=3, delay=1):
|
|
325
|
+
"""Time-delay embedding.
|
|
326
|
+
|
|
327
|
+
Parameters
|
|
328
|
+
----------
|
|
329
|
+
x : array_like
|
|
330
|
+
1D-array of shape (n_times) or 2D-array of shape (signal_indice, n_times)
|
|
331
|
+
order : int
|
|
332
|
+
Embedding dimension (order).
|
|
333
|
+
delay : int
|
|
334
|
+
Delay.
|
|
335
|
+
|
|
336
|
+
Returns
|
|
337
|
+
-------
|
|
338
|
+
embedded : array_like
|
|
339
|
+
Embedded time series, of shape (..., n_times - (order - 1) * delay, order)
|
|
340
|
+
"""
|
|
341
|
+
x = np.asarray(x)
|
|
342
|
+
N = x.shape[-1]
|
|
343
|
+
assert x.ndim in [1, 2], "Only 1D or 2D arrays are currently supported."
|
|
344
|
+
if order * delay > N:
|
|
345
|
+
raise ValueError("Error: order * delay should be lower than x.size")
|
|
346
|
+
if delay < 1:
|
|
347
|
+
raise ValueError("Delay has to be at least 1.")
|
|
348
|
+
if order < 2:
|
|
349
|
+
raise ValueError("Order has to be at least 2.")
|
|
350
|
+
|
|
351
|
+
if x.ndim == 1:
|
|
352
|
+
# 1D array (n_times)
|
|
353
|
+
Y = np.zeros((order, N - (order - 1) * delay))
|
|
354
|
+
for i in range(order):
|
|
355
|
+
Y[i] = x[(i * delay) : (i * delay + Y.shape[1])]
|
|
356
|
+
return Y.T
|
|
357
|
+
else:
|
|
358
|
+
# 2D array (signal_indice, n_times)
|
|
359
|
+
Y = []
|
|
360
|
+
# pre-defiend an empty list to store numpy.array (concatenate with a list is faster)
|
|
361
|
+
embed_signal_length = N - (order - 1) * delay
|
|
362
|
+
# define the new signal length
|
|
363
|
+
indice = [[(i * delay), (i * delay + embed_signal_length)] for i in range(order)]
|
|
364
|
+
# generate a list of slice indice on input signal
|
|
365
|
+
for i in range(order):
|
|
366
|
+
# loop with the order
|
|
367
|
+
temp = x[:, indice[i][0] : indice[i][1]].reshape(-1, embed_signal_length, 1)
|
|
368
|
+
# slicing the signal with the indice of each order (vectorized operation)
|
|
369
|
+
Y.append(temp)
|
|
370
|
+
# append the sliced signal to list
|
|
371
|
+
Y = np.concatenate(Y, axis=-1)
|
|
372
|
+
return Y
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _xlogx(x, base=2):
|
|
376
|
+
"""Returns x log_b x if x is positive, 0 if x == 0, and np.nan
|
|
377
|
+
otherwise. This handles the case when the power spectrum density
|
|
378
|
+
takes any zero value.
|
|
379
|
+
"""
|
|
380
|
+
x = np.asarray(x)
|
|
381
|
+
xlogx = np.zeros(x.shape)
|
|
382
|
+
xlogx[x < 0] = np.nan
|
|
383
|
+
valid = x > 0
|
|
384
|
+
xlogx[valid] = x[valid] * np.log(x[valid]) / np.log(base)
|
|
385
|
+
return xlogx
|
|
386
|
+
|
|
@@ -11,7 +11,7 @@ MAINTAINER_EMAIL = "swang@gmail.com"
|
|
|
11
11
|
URL = "https://github.com/BryanWang0702/MiSleep/"
|
|
12
12
|
LICENSE = "BSD (3-clause)"
|
|
13
13
|
DOWNLOAD_URL = "https://github.com/BryanWang0702/MiSleep/"
|
|
14
|
-
VERSION = "0.2.
|
|
14
|
+
VERSION = "0.2.6"
|
|
15
15
|
|
|
16
16
|
INSTALL_REQUIRES = [
|
|
17
17
|
"numpy>=1.18.1",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|