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.
Files changed (71) hide show
  1. {misleep-0.2.5 → misleep-0.2.6}/PKG-INFO +1 -1
  2. {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage.py +5 -5
  3. {misleep-0.2.5 → misleep-0.2.6}/misleep/config.ini +2 -2
  4. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/dialog.py +46 -0
  5. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/main_window.py +3 -3
  6. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/state_spectral_dialog_ui.py +18 -14
  7. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/utils.py +2 -0
  8. {misleep-0.2.5 → misleep-0.2.6}/misleep/utils/__init__.py +2 -1
  9. misleep-0.2.6/misleep/utils/self_antropy.py +386 -0
  10. {misleep-0.2.5 → misleep-0.2.6}/misleep.egg-info/PKG-INFO +1 -1
  11. {misleep-0.2.5 → misleep-0.2.6}/misleep.egg-info/SOURCES.txt +1 -0
  12. {misleep-0.2.5 → misleep-0.2.6}/setup.py +1 -1
  13. {misleep-0.2.5 → misleep-0.2.6}/LICENSE +0 -0
  14. {misleep-0.2.5 → misleep-0.2.6}/README.md +0 -0
  15. {misleep-0.2.5 → misleep-0.2.6}/misleep/__init__.py +0 -0
  16. {misleep-0.2.5 → misleep-0.2.6}/misleep/__main__.py +0 -0
  17. {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/__init__.py +0 -0
  18. {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage_model/P30_EEG_F_lightgbm.pkl +0 -0
  19. {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage_model/P30_EEG_P_lightgbm.pkl +0 -0
  20. {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage_model/ado_EEG_F_lightgbm.pkl +0 -0
  21. {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage_model/ado_EEG_P_lightgbm.pkl +0 -0
  22. {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage_model/adult_EEG_F_lightgbm.pkl +0 -0
  23. {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/auto_stage_model/adult_EEG_P_lightgbm.pkl +0 -0
  24. {misleep-0.2.5 → misleep-0.2.6}/misleep/analysis/detection.py +0 -0
  25. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/__init__.py +0 -0
  26. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/about.py +0 -0
  27. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/resources/__init__.py +0 -0
  28. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/resources/entire_logo.png +0 -0
  29. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/resources/logo.png +0 -0
  30. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/resources/misleep.py +0 -0
  31. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/resources/misleep.qrc +0 -0
  32. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/show.py +0 -0
  33. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/spec_window.py +0 -0
  34. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/thread.py +0 -0
  35. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/SWA_detect_dialog_ui.py +0 -0
  36. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/__init__.py +0 -0
  37. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/about_ui.py +0 -0
  38. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/auto_stage_dialog_ui.py +0 -0
  39. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/horizontal_line_dialog_ui.py +0 -0
  40. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/label_dialog_ui.py +0 -0
  41. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/main_window_ui.py +0 -0
  42. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/save_data_dialog_ui.py +0 -0
  43. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/spec_window_ui.py +0 -0
  44. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/spindle_detect_dialog_ui.py +0 -0
  45. {misleep-0.2.5 → misleep-0.2.6}/misleep/gui/uis/transfer_result_dialog_ui.py +0 -0
  46. {misleep-0.2.5 → misleep-0.2.6}/misleep/io/__init__.py +0 -0
  47. {misleep-0.2.5 → misleep-0.2.6}/misleep/io/annotation_io.py +0 -0
  48. {misleep-0.2.5 → misleep-0.2.6}/misleep/io/base.py +0 -0
  49. {misleep-0.2.5 → misleep-0.2.6}/misleep/io/signal_io.py +0 -0
  50. {misleep-0.2.5 → misleep-0.2.6}/misleep/preprocessing/__init__.py +0 -0
  51. {misleep-0.2.5 → misleep-0.2.6}/misleep/preprocessing/channel.py +0 -0
  52. {misleep-0.2.5 → misleep-0.2.6}/misleep/preprocessing/signals.py +0 -0
  53. {misleep-0.2.5 → misleep-0.2.6}/misleep/preprocessing/spectral.py +0 -0
  54. {misleep-0.2.5 → misleep-0.2.6}/misleep/utils/annotation.py +0 -0
  55. {misleep-0.2.5 → misleep-0.2.6}/misleep/utils/logger_handler.py +0 -0
  56. {misleep-0.2.5 → misleep-0.2.6}/misleep/utils/signals.py +0 -0
  57. {misleep-0.2.5 → misleep-0.2.6}/misleep/viz/__init__.py +0 -0
  58. {misleep-0.2.5 → misleep-0.2.6}/misleep/viz/hypnogram.py +0 -0
  59. {misleep-0.2.5 → misleep-0.2.6}/misleep/viz/signals.py +0 -0
  60. {misleep-0.2.5 → misleep-0.2.6}/misleep/viz/spectral.py +0 -0
  61. {misleep-0.2.5 → misleep-0.2.6}/misleep.egg-info/dependency_links.txt +0 -0
  62. {misleep-0.2.5 → misleep-0.2.6}/misleep.egg-info/requires.txt +0 -0
  63. {misleep-0.2.5 → misleep-0.2.6}/misleep.egg-info/top_level.txt +0 -0
  64. {misleep-0.2.5 → misleep-0.2.6}/setup.cfg +0 -0
  65. {misleep-0.2.5 → misleep-0.2.6}/test/test_annotation_io.py +0 -0
  66. {misleep-0.2.5 → misleep-0.2.6}/test/test_loadmat73.py +0 -0
  67. {misleep-0.2.5 → misleep-0.2.6}/test/test_midata.py +0 -0
  68. {misleep-0.2.5 → misleep-0.2.6}/test/test_show.py +0 -0
  69. {misleep-0.2.5 → misleep-0.2.6}/test/test_signal_io.py +0 -0
  70. {misleep-0.2.5 → misleep-0.2.6}/test/test_signals_viz.py +0 -0
  71. {misleep-0.2.5 → misleep-0.2.6}/test/test_spectral_viz.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: misleep
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: MiSleep: Mice Sleep EEG/EMG visualization, scoring and analysis.
5
5
  Home-page: https://github.com/BryanWang0702/MiSleep/
6
6
  Download-URL: https://github.com/BryanWang0702/MiSleep/
@@ -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 antropy
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 = [antropy.num_zerocross(each[0][:int(5*sf)]) / (5*sf) for each in data]
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 = [antropy.hjorth_params(each[0][:int(5*sf)]) for each in data]
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
- perm_entropy= [antropy.perm_entropy(each[0][:int(5*sf)]) for each in data]
96
- window_feature_df[f'{data_format}_perm_entropy'] = self_zscore(perm_entropy)
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.5
3
- updatetime = 2025/1/17
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] >= 10:
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] >= 10:
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 10 seconds.",
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.10
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(261, 424)
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
@@ -143,6 +143,8 @@ def cal_draw_spectrum(data, sf, nperseg, freq_band=None, relative=None):
143
143
  import numpy as np
144
144
  import matplotlib.pyplot as plt
145
145
 
146
+ plt.close()
147
+
146
148
  if freq_band is None:
147
149
  freq_band = [0.5, 30]
148
150
  F, P = welch(data, sf, nperseg=nperseg)
@@ -9,4 +9,5 @@
9
9
 
10
10
  from .signals import *
11
11
  from .annotation import *
12
- from .logger_handler import *
12
+ from .logger_handler import *
13
+ from .self_antropy import *
@@ -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
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: misleep
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: MiSleep: Mice Sleep EEG/EMG visualization, scoring and analysis.
5
5
  Home-page: https://github.com/BryanWang0702/MiSleep/
6
6
  Download-URL: https://github.com/BryanWang0702/MiSleep/
@@ -54,6 +54,7 @@ misleep/preprocessing/spectral.py
54
54
  misleep/utils/__init__.py
55
55
  misleep/utils/annotation.py
56
56
  misleep/utils/logger_handler.py
57
+ misleep/utils/self_antropy.py
57
58
  misleep/utils/signals.py
58
59
  misleep/viz/__init__.py
59
60
  misleep/viz/hypnogram.py
@@ -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.5"
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