misleep 0.3.0__py3-none-any.whl

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 (94) hide show
  1. misleep/__init__.py +111 -0
  2. misleep/__main__.py +16 -0
  3. misleep/analysis/__init__.py +23 -0
  4. misleep/analysis/auto_stage.py +158 -0
  5. misleep/analysis/detection.py +198 -0
  6. misleep/analysis/features.py +131 -0
  7. misleep/analysis/models/P30_EEG_F_lightgbm.pkl +0 -0
  8. misleep/analysis/models/P30_EEG_P_lightgbm.pkl +0 -0
  9. misleep/analysis/models/__init__.py +0 -0
  10. misleep/analysis/models/ado_EEG_F_lightgbm.pkl +0 -0
  11. misleep/analysis/models/ado_EEG_P_lightgbm.pkl +0 -0
  12. misleep/analysis/models/adult_EEG_F_lightgbm.pkl +0 -0
  13. misleep/analysis/models/adult_EEG_P_lightgbm.pkl +0 -0
  14. misleep/analysis/transformer/__init__.py +46 -0
  15. misleep/analysis/transformer/checkpoints/CausalTransformer_best.pt +0 -0
  16. misleep/analysis/transformer/checkpoints/__init__.py +0 -0
  17. misleep/analysis/transformer/configs.py +52 -0
  18. misleep/analysis/transformer/data.py +362 -0
  19. misleep/analysis/transformer/inference.py +328 -0
  20. misleep/analysis/transformer/models.py +220 -0
  21. misleep/analysis/transformer/preprocessing.py +105 -0
  22. misleep/config/__init__.py +105 -0
  23. misleep/config/default_config.ini +38 -0
  24. misleep/data/__init__.py +13 -0
  25. misleep/data/annotation.py +114 -0
  26. misleep/data/midata.py +387 -0
  27. misleep/gui/__init__.py +23 -0
  28. misleep/gui/app.py +183 -0
  29. misleep/gui/config_dialog.py +429 -0
  30. misleep/gui/dialogs.py +1107 -0
  31. misleep/gui/event_filters.py +26 -0
  32. misleep/gui/main_window.py +2699 -0
  33. misleep/gui/qt_utils.py +277 -0
  34. misleep/gui/resources/__init__.py +9 -0
  35. misleep/gui/resources/entire_logo.png +0 -0
  36. misleep/gui/resources/logo.png +0 -0
  37. misleep/gui/resources/misleep.ico +0 -0
  38. misleep/gui/resources/misleep.qrc +7 -0
  39. misleep/gui/resources/misleep_rc.py +1324 -0
  40. misleep/gui/spec_window.py +187 -0
  41. misleep/gui/style.py +994 -0
  42. misleep/gui/uis/SWA_detect_dialog.ui +181 -0
  43. misleep/gui/uis/SWA_detect_dialog_ui.py +176 -0
  44. misleep/gui/uis/__init__.py +6 -0
  45. misleep/gui/uis/about.ui +88 -0
  46. misleep/gui/uis/about_ui.py +80 -0
  47. misleep/gui/uis/auto_stage_causalTransformer_dialog.ui +77 -0
  48. misleep/gui/uis/auto_stage_causalTransformer_dialog_ui.py +90 -0
  49. misleep/gui/uis/auto_stage_lightGBM_dialog.ui +124 -0
  50. misleep/gui/uis/auto_stage_lightGBM_dialog_ui.py +124 -0
  51. misleep/gui/uis/horizontal_line_dialog.ui +166 -0
  52. misleep/gui/uis/horizontal_line_dialog_ui.py +157 -0
  53. misleep/gui/uis/label_dialog.ui +65 -0
  54. misleep/gui/uis/label_dialog_ui.py +72 -0
  55. misleep/gui/uis/main_window.ui +871 -0
  56. misleep/gui/uis/main_window_ui.py +616 -0
  57. misleep/gui/uis/save_data_dialog.ui +95 -0
  58. misleep/gui/uis/save_data_dialog_ui.py +98 -0
  59. misleep/gui/uis/spec_window.ui +109 -0
  60. misleep/gui/uis/spec_window_ui.py +103 -0
  61. misleep/gui/uis/spindle_detect_dialog.ui +202 -0
  62. misleep/gui/uis/spindle_detect_dialog_ui.py +194 -0
  63. misleep/gui/uis/state_spectral_dialog.ui +240 -0
  64. misleep/gui/uis/state_spectral_dialog_ui.py +204 -0
  65. misleep/gui/uis/transfer_result_dialog.ui +104 -0
  66. misleep/gui/uis/transfer_result_dialog_ui.py +94 -0
  67. misleep/gui/workers.py +69 -0
  68. misleep/io/__init__.py +68 -0
  69. misleep/io/annotation.py +367 -0
  70. misleep/io/array.py +209 -0
  71. misleep/io/base.py +174 -0
  72. misleep/io/edf.py +109 -0
  73. misleep/io/mat.py +189 -0
  74. misleep/logger.py +79 -0
  75. misleep/preprocessing/__init__.py +23 -0
  76. misleep/preprocessing/artifacts.py +76 -0
  77. misleep/preprocessing/filtering.py +94 -0
  78. misleep/preprocessing/segment.py +59 -0
  79. misleep/preprocessing/spectral.py +169 -0
  80. misleep/utils/__init__.py +45 -0
  81. misleep/utils/annotation.py +130 -0
  82. misleep/utils/entropy.py +135 -0
  83. misleep/utils/misc.py +69 -0
  84. misleep/utils/time_utils.py +61 -0
  85. misleep/viz/__init__.py +12 -0
  86. misleep/viz/hypnogram.py +48 -0
  87. misleep/viz/signals.py +51 -0
  88. misleep/viz/spectral.py +98 -0
  89. misleep-0.3.0.dist-info/METADATA +249 -0
  90. misleep-0.3.0.dist-info/RECORD +94 -0
  91. misleep-0.3.0.dist-info/WHEEL +5 -0
  92. misleep-0.3.0.dist-info/entry_points.txt +15 -0
  93. misleep-0.3.0.dist-info/licenses/LICENSE +29 -0
  94. misleep-0.3.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,277 @@
1
+ # -*- coding: UTF-8 -*-
2
+ """GUI utility functions and Qt helper classes.
3
+
4
+ Pure helpers that the GUI uses for drawing spectra and formatting time;
5
+ the general-purpose helpers are re-exported from :mod:`misleep.utils` so
6
+ there is a single source of truth.
7
+ """
8
+
9
+ from PySide6.QtCore import QAbstractListModel, QModelIndex, Qt
10
+ from PySide6.QtGui import QIcon
11
+ from PySide6.QtWidgets import (
12
+ QSizePolicy,
13
+ QToolButton,
14
+ QVBoxLayout,
15
+ QWidget,
16
+ )
17
+
18
+
19
+ def app_icon() -> QIcon:
20
+ """Return the MiSleep application icon from the Qt resources.
21
+
22
+ The square ``misleep.ico`` is the primary icon (crisp at small title-bar
23
+ / taskbar sizes); ``logo.png`` is kept as the large-size fallback.
24
+ """
25
+ icon = QIcon()
26
+ icon.addFile(":/logo/misleep.ico")
27
+ icon.addFile(":/logo/logo.png")
28
+ if icon.isNull():
29
+ icon = QIcon(":/logo/logo.png")
30
+ return icon
31
+
32
+
33
+ class CollapsibleSection(QWidget):
34
+ """A titled, collapsible panel used in the right-hand sidebar.
35
+
36
+ One header row with a fold/unfold arrow plus a content widget that can
37
+ be hidden. Replaces the old draggable docks with a tidy, fixed panel.
38
+ """
39
+
40
+ def __init__(self, title, content=None, parent=None, collapsed=False):
41
+ super().__init__(parent)
42
+ self._title = title
43
+ self._content = None
44
+
45
+ layout = QVBoxLayout(self)
46
+ layout.setContentsMargins(0, 0, 0, 0)
47
+ layout.setSpacing(0)
48
+
49
+ self.header = QToolButton()
50
+ self.header.setObjectName("SidebarSectionHeader")
51
+ self.header.setCheckable(True)
52
+ self.header.setChecked(not collapsed)
53
+ self.header.setCursor(Qt.CursorShape.PointingHandCursor)
54
+ self.header.setSizePolicy(QSizePolicy.Policy.Expanding,
55
+ QSizePolicy.Policy.Fixed)
56
+ self.header.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
57
+ self.header.setToolTip("Click to expand / collapse")
58
+ self.header.clicked.connect(self._on_header_clicked)
59
+ layout.addWidget(self.header)
60
+
61
+ if content is not None:
62
+ self.set_content(content, collapsed=collapsed)
63
+ self._update_arrow()
64
+
65
+ # ------------------------------------------------------------------
66
+ # Public API
67
+ # ------------------------------------------------------------------
68
+ def set_content(self, widget, collapsed=None):
69
+ """Attach the content widget (re-parents it into this section)."""
70
+ if self._content is not None:
71
+ self._content.setParent(None)
72
+ self._content = widget
73
+ if widget is not None:
74
+ # Dock contents often retain a large layout size hint from their
75
+ # former floating-dock geometry. Ignore that horizontal hint so
76
+ # the unified sidebar can actually constrain every section to its
77
+ # viewport instead of silently clipping the rightmost controls.
78
+ widget.setMinimumWidth(0)
79
+ widget.setSizePolicy(QSizePolicy.Policy.Ignored,
80
+ QSizePolicy.Policy.Preferred)
81
+ self.layout().addWidget(widget)
82
+ if collapsed is not None:
83
+ self.header.setChecked(not collapsed)
84
+ widget.setVisible(not collapsed)
85
+ self._update_arrow()
86
+
87
+ def set_expanded(self, expanded):
88
+ """Programmatically expand (True) or collapse (False) the section."""
89
+ self.header.setChecked(expanded)
90
+ if self._content is not None:
91
+ self._content.setVisible(expanded)
92
+ self._update_arrow()
93
+
94
+ def is_expanded(self):
95
+ return self.header.isChecked()
96
+
97
+ @property
98
+ def title(self):
99
+ return self._title
100
+
101
+ # ------------------------------------------------------------------
102
+ # Internals
103
+ # ------------------------------------------------------------------
104
+ def _on_header_clicked(self):
105
+ if self._content is not None:
106
+ self._content.setVisible(self.header.isChecked())
107
+ self._update_arrow()
108
+
109
+ def _update_arrow(self):
110
+ self.header.setArrowType(
111
+ Qt.ArrowType.DownArrow if self.header.isChecked()
112
+ else Qt.ArrowType.RightArrow)
113
+ self.header.setText(self._title)
114
+
115
+
116
+ class ChannelListModel(QAbstractListModel):
117
+ """A simple editable, drag-reorderable string list model.
118
+
119
+ Unlike ``QStringListModel`` this implements ``moveRows``, so a
120
+ drag & drop emits ``rowsMoved`` (a genuine move) instead of being
121
+ performed as remove+insert -- which previously confused the rename
122
+ logic.
123
+ """
124
+
125
+ def __init__(self, channels=None, parent=None):
126
+ super().__init__(parent)
127
+ self._channels = list(channels or [])
128
+
129
+ def rowCount(self, parent=QModelIndex()):
130
+ return 0 if parent.isValid() else len(self._channels)
131
+
132
+ def data(self, index, role=Qt.ItemDataRole.DisplayRole):
133
+ if not index.isValid() or not (0 <= index.row() < len(self._channels)):
134
+ return None
135
+ if role in (Qt.ItemDataRole.DisplayRole, Qt.ItemDataRole.EditRole):
136
+ return self._channels[index.row()]
137
+ return None
138
+
139
+ def setData(self, index, value, role=Qt.ItemDataRole.EditRole):
140
+ if role == Qt.ItemDataRole.EditRole and index.isValid() \
141
+ and 0 <= index.row() < len(self._channels):
142
+ self._channels[index.row()] = str(value)
143
+ self.dataChanged.emit(index, index, [Qt.ItemDataRole.EditRole])
144
+ return True
145
+ return False
146
+
147
+ def flags(self, index):
148
+ if not index.isValid():
149
+ return Qt.ItemFlag.ItemIsDropEnabled
150
+ return (Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable
151
+ | Qt.ItemFlag.ItemIsEditable | Qt.ItemFlag.ItemIsDragEnabled)
152
+
153
+ def moveRows(self, sourceParent, sourceRow, count, destinationParent, destinationChild):
154
+ """Move a single row within the same list (drag & drop reorder)."""
155
+ if sourceParent.isValid() or destinationParent.isValid():
156
+ return False
157
+ if count != 1 or sourceRow == destinationChild:
158
+ return False
159
+ if not (0 <= sourceRow < len(self._channels)):
160
+ return False
161
+
162
+ dest = destinationChild
163
+ if sourceRow < dest:
164
+ dest -= 1
165
+ if dest < 0:
166
+ dest = 0
167
+ dest = min(dest, len(self._channels) - 1)
168
+
169
+ last = sourceRow
170
+ self.beginMoveRows(sourceParent, sourceRow, sourceRow, destinationParent, destinationChild)
171
+ item = self._channels.pop(sourceRow)
172
+ self._channels.insert(dest, item)
173
+ self.endMoveRows()
174
+ return True
175
+
176
+ def channels(self):
177
+ """Return a copy of the channel names in their current order."""
178
+ return list(self._channels)
179
+
180
+ def setChannels(self, channels):
181
+ """Replace the whole list (no move/insert signals)."""
182
+ self.beginResetModel()
183
+ self._channels = list(channels)
184
+ self.endResetModel()
185
+
186
+
187
+ def cal_draw_spectrum(data, sf, nperseg, freq_band=None, relative=None, nfft=None, gaussian_sigma=None):
188
+ """Calculate a power spectrum and return it together with a figure.
189
+
190
+ Parameters
191
+ ----------
192
+ data : ndarray
193
+ Signal to analyze.
194
+ sf : float
195
+ Sampling frequency.
196
+ nperseg : int
197
+ Window length (in samples) for the Welch FFT.
198
+ freq_band : list, optional
199
+ Frequency band ``[low, high]``. Defaults to ``[0.5, 30]``.
200
+ relative : bool, optional
201
+ Whether to normalize the PSD to a relative power.
202
+ nfft : int, optional
203
+ Number of FFT points.
204
+ gaussian_sigma : float, optional
205
+ Gaussian smoothing sigma.
206
+
207
+ Returns
208
+ -------
209
+ (spectrum, figure) : tuple
210
+ ``spectrum`` is an array of shape ``(2, n_freq)`` (frequencies and
211
+ powers); ``figure`` is the matplotlib figure.
212
+ """
213
+ # IMPORTANT: build the figure with matplotlib.figure.Figure directly
214
+ # (not pyplot). The old ``plt.close()`` here silently closed the main
215
+ # window's figures, which froze the signal/hypnogram panels after an
216
+ # export. A non-pyplot figure is invisible to pyplot bookkeeping.
217
+ import numpy as np
218
+ from matplotlib.figure import Figure
219
+ from scipy.ndimage import gaussian_filter1d
220
+ from scipy.signal import welch
221
+
222
+ if freq_band is None:
223
+ freq_band = [0.5, 30]
224
+ F, P = welch(data, sf, nperseg=nperseg, nfft=nfft, scaling="density")
225
+
226
+ F = np.array([round(each, 2) for each in F])
227
+ if gaussian_sigma is not None:
228
+ P = gaussian_filter1d(P, sigma=gaussian_sigma)
229
+
230
+ if freq_band is not None:
231
+ idx_band = np.logical_and(F >= freq_band[0], F <= freq_band[1])
232
+ F = F[idx_band]
233
+ P = P[idx_band]
234
+
235
+ if relative:
236
+ total = sum(P)
237
+ if total > 0:
238
+ P = [each / total for each in P]
239
+
240
+ major_ticks_top = np.linspace(0, freq_band[1] + 0.1, 10)
241
+
242
+ figure = Figure(figsize=(10, 7))
243
+ ax = figure.subplots(nrows=1, ncols=1)
244
+ figure.subplots_adjust(top=0.95, left=0.15, bottom=0.15, right=0.95)
245
+
246
+ ax.xaxis.set_ticks(major_ticks_top)
247
+ ax.grid(which="major", alpha=0.6)
248
+
249
+ ax.set_xlim(freq_band[0], freq_band[1] + 0.1)
250
+ ax.plot(F, P)
251
+ ax.set_xlabel("Frequency (Hz)")
252
+ ax.set_ylabel("Power spectral density (Power/Hz)")
253
+
254
+ return np.array([F, P]), figure
255
+
256
+
257
+ # Re-export shared helpers so existing imports of ``misleep.gui.utils`` keep working.
258
+ from misleep.utils.misc import ( # noqa: E402, F401
259
+ create_new_mianno,
260
+ identify_startend_color,
261
+ get_base_path,
262
+ downsample_by_most_frequent,
263
+ )
264
+ from misleep.utils.time_utils import second2time, transfer_time # noqa: E402, F401
265
+ from misleep.utils.annotation import insert_row, temp_loop4below_row # noqa: E402, F401
266
+
267
+ __all__ = [
268
+ "cal_draw_spectrum",
269
+ "create_new_mianno",
270
+ "identify_startend_color",
271
+ "get_base_path",
272
+ "downsample_by_most_frequent",
273
+ "second2time",
274
+ "transfer_time",
275
+ "insert_row",
276
+ "temp_loop4below_row",
277
+ ]
@@ -0,0 +1,9 @@
1
+ # -*- coding: UTF-8 -*-
2
+ """GUI resource module.
3
+
4
+ Importing this package registers the compiled Qt resources
5
+ (``misleep_rc``) so that ``:/logo/...`` paths used by the generated UI
6
+ modules resolve correctly.
7
+ """
8
+
9
+ from misleep.gui.resources import misleep_rc # noqa: F401
Binary file
Binary file
Binary file
@@ -0,0 +1,7 @@
1
+ <RCC>
2
+ <qresource prefix="logo">
3
+ <file>logo.png</file>
4
+ <file>entire_logo.png</file>
5
+ <file>misleep.ico</file>
6
+ </qresource>
7
+ </RCC>