accusleepy 0.10.0__tar.gz → 0.10.1__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 (49) hide show
  1. {accusleepy-0.10.0 → accusleepy-0.10.1}/PKG-INFO +5 -9
  2. {accusleepy-0.10.0 → accusleepy-0.10.1}/README.md +2 -4
  3. accusleepy-0.10.1/accusleepy/__init__.py +1 -0
  4. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/__main__.py +2 -0
  5. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/bouts.py +2 -0
  6. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/brain_state_set.py +2 -0
  7. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/classification.py +2 -0
  8. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/constants.py +2 -0
  9. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/fileio.py +2 -2
  10. accusleepy-0.10.1/accusleepy/gui/__init__.py +1 -0
  11. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/images/primary_window.png +0 -0
  12. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/main.py +4 -2
  13. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/manual_scoring.py +12 -7
  14. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/mplwidget.py +2 -1
  15. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/recording_manager.py +24 -14
  16. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/models.py +2 -0
  17. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/multitaper.py +3 -60
  18. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/signal_processing.py +2 -0
  19. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/temperature_scaling.py +2 -0
  20. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/validation.py +2 -0
  21. {accusleepy-0.10.0 → accusleepy-0.10.1}/pyproject.toml +4 -5
  22. accusleepy-0.10.0/accusleepy/__init__.py +0 -0
  23. accusleepy-0.10.0/accusleepy/gui/__init__.py +0 -0
  24. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/config.json +0 -0
  25. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/dialogs.py +0 -0
  26. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/icons/brightness_down.png +0 -0
  27. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/icons/brightness_up.png +0 -0
  28. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/icons/double_down_arrow.png +0 -0
  29. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/icons/double_up_arrow.png +0 -0
  30. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/icons/down_arrow.png +0 -0
  31. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/icons/home.png +0 -0
  32. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/icons/question.png +0 -0
  33. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/icons/save.png +0 -0
  34. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/icons/up_arrow.png +0 -0
  35. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/icons/zoom_in.png +0 -0
  36. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/icons/zoom_out.png +0 -0
  37. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/images/viewer_window.png +0 -0
  38. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/images/viewer_window_annotated.png +0 -0
  39. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/primary_window.py +0 -0
  40. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/primary_window.ui +0 -0
  41. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/resources.qrc +0 -0
  42. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/resources_rc.py +0 -0
  43. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/settings_widget.py +0 -0
  44. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/text/dev_guide.md +0 -0
  45. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/text/main_guide.md +0 -0
  46. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/text/manual_scoring_guide.md +0 -0
  47. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/viewer_window.py +0 -0
  48. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/gui/viewer_window.ui +0 -0
  49. {accusleepy-0.10.0 → accusleepy-0.10.1}/accusleepy/services.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: accusleepy
3
- Version: 0.10.0
3
+ Version: 0.10.1
4
4
  Summary: Python implementation of AccuSleep
5
5
  License: GPL-3.0-only
6
6
  Author: Zeke Barger
@@ -11,14 +11,12 @@ Classifier: Programming Language :: Python :: 3
11
11
  Classifier: Programming Language :: Python :: 3.11
12
12
  Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
- Requires-Dist: fastparquet (>=2024.11.0,<2025.0.0)
15
- Requires-Dist: joblib (>=1.4.2,<2.0.0)
16
14
  Requires-Dist: matplotlib (>=3.10.1,<4.0.0)
17
15
  Requires-Dist: numpy (>=2.2.4,<3.0.0)
18
16
  Requires-Dist: pandas (>=2.2.3,<3.0.0)
19
17
  Requires-Dist: pillow (>=11.1.0,<12.0.0)
20
- Requires-Dist: pre-commit (>=4.2.0,<5.0.0)
21
- Requires-Dist: pyside6 (>=6.9.0,<6.9.3)
18
+ Requires-Dist: pyarrow (>=23.0.0,<24.0.0)
19
+ Requires-Dist: pyside6 (>=6.10.1,<7.0.0)
22
20
  Requires-Dist: scipy (>=1.15.2,<2.0.0)
23
21
  Requires-Dist: torch (>=2.8.0,<3.0.0)
24
22
  Requires-Dist: torchvision (>=0.23.0,<1.0.0)
@@ -79,10 +77,8 @@ please consult the [developer guide](accusleepy/gui/text/dev_guide.md).
79
77
 
80
78
  ## Changelog
81
79
 
82
- - 0.10.0: Improved zoom behavior
83
- - 0.8.1-0.9.3: Improved error handling and code quality
84
- - 0.8.0: More configurable settings, visual improvements
85
- - 0.7.1-0.7.3: Bugfixes, code cleanup
80
+ - 0.10.0-0.10.1: Improved zoom behavior, updated dependencies
81
+ - 0.7.1-0.9.3: Bugfixes, code cleanup, additional config settings
86
82
  - 0.7.0: More settings can be configured in the UI
87
83
  - 0.6.0: Confidence scores can now be displayed and saved. Retraining your models is recommended
88
84
  since the new calibration feature will make the confidence scores more accurate.
@@ -52,10 +52,8 @@ please consult the [developer guide](accusleepy/gui/text/dev_guide.md).
52
52
 
53
53
  ## Changelog
54
54
 
55
- - 0.10.0: Improved zoom behavior
56
- - 0.8.1-0.9.3: Improved error handling and code quality
57
- - 0.8.0: More configurable settings, visual improvements
58
- - 0.7.1-0.7.3: Bugfixes, code cleanup
55
+ - 0.10.0-0.10.1: Improved zoom behavior, updated dependencies
56
+ - 0.7.1-0.9.3: Bugfixes, code cleanup, additional config settings
59
57
  - 0.7.0: More settings can be configured in the UI
60
58
  - 0.6.0: Confidence scores can now be displayed and saved. Retraining your models is recommended
61
59
  since the new calibration feature will make the confidence scores more accurate.
@@ -0,0 +1 @@
1
+ """AccuSleePy: Python implementation of AccuSleep for automated sleep scoring."""
@@ -1,3 +1,5 @@
1
+ """Entry point for running AccuSleePy as a module (python -m accusleepy)."""
2
+
1
3
  from accusleepy.gui.main import run_primary_window
2
4
 
3
5
  if __name__ == "__main__":
@@ -1,3 +1,5 @@
1
+ """Brain state bout length enforcement."""
2
+
1
3
  import re
2
4
  from dataclasses import dataclass
3
5
  from operator import attrgetter
@@ -1,3 +1,5 @@
1
+ """Define and organize brain states."""
2
+
1
3
  from dataclasses import dataclass
2
4
 
3
5
  import numpy as np
@@ -1,3 +1,5 @@
1
+ """Model training and brain state classification."""
2
+
1
3
  import os
2
4
 
3
5
  import numpy as np
@@ -1,3 +1,5 @@
1
+ """Application-wide constants and default settings."""
2
+
1
3
  import numpy as np
2
4
 
3
5
  # probably don't change these unless you really need to
@@ -1,3 +1,5 @@
1
+ """File I/O for recordings, labels, calibration data, and configuration."""
2
+
1
3
  import json
2
4
  import os
3
5
  from dataclasses import dataclass
@@ -5,7 +7,6 @@ from importlib.metadata import version, PackageNotFoundError
5
7
 
6
8
  import numpy as np
7
9
  import pandas as pd
8
- from PySide6.QtWidgets import QListWidgetItem
9
10
 
10
11
  from accusleepy.brain_state_set import BRAIN_STATES_KEY, BrainState, BrainStateSet
11
12
  import accusleepy.constants as c
@@ -55,7 +56,6 @@ class Recording:
55
56
  label_file: str = "" # path to label file
56
57
  calibration_file: str = "" # path to calibration file
57
58
  sampling_rate: int | float = 0.0 # sampling rate, in Hz
58
- widget: QListWidgetItem = None # list item widget shown in the GUI
59
59
 
60
60
 
61
61
  def load_calibration_file(filename: str) -> tuple[np.ndarray, np.ndarray]:
@@ -0,0 +1 @@
1
+ """Graphical user interface components for AccuSleePy."""
@@ -1,5 +1,7 @@
1
- # AccuSleePy main window
2
- # Icon source: Arkinasi, https://www.flaticon.com/authors/arkinasi
1
+ """AccuSleePy main window.
2
+
3
+ Icon source: Arkinasi, https://www.flaticon.com/authors/arkinasi
4
+ """
3
5
 
4
6
  import logging
5
7
  import os
@@ -1,8 +1,9 @@
1
- # AccuSleePy manual scoring GUI
2
- # Icon sources:
3
- # Arkinasi, https://www.flaticon.com/authors/arkinasi
4
- # kendis lasman, https://www.flaticon.com/packs/ui-79
1
+ """AccuSleePy manual scoring GUI.
5
2
 
3
+ Icon sources:
4
+ Arkinasi, https://www.flaticon.com/authors/arkinasi
5
+ kendis lasman, https://www.flaticon.com/packs/ui-79
6
+ """
6
7
 
7
8
  import copy
8
9
  import os
@@ -85,8 +86,8 @@ BRIGHTER_SCALE_FACTOR = 0.96
85
86
  DIMMER_SCALE_FACTOR = 1.07
86
87
  # zoom factor for upper plots - larger values = bigger changes
87
88
  ZOOM_FACTOR = 0.1
88
- # interval in seconds between zoom events triggered by scrolling
89
- ZOOM_DELAY = 0.05
89
+ # rate limit for zoom events triggered by scrolling
90
+ MAX_SCROLL_EVENTS_PER_SEC = 24
90
91
 
91
92
 
92
93
  @dataclass
@@ -983,11 +984,15 @@ class ManualScoringWindow(QDialog):
983
984
  return
984
985
 
985
986
  self.now_zooming = True
987
+ start_time = time.time()
986
988
  if event.button == "up":
987
989
  self.zoom_x(direction=ZOOM_IN)
988
990
  else:
989
991
  self.zoom_x(direction=ZOOM_OUT)
990
- time.sleep(ZOOM_DELAY)
992
+ end_time = time.time()
993
+ time_elapsed = end_time - start_time
994
+ if time_elapsed < 1 / MAX_SCROLL_EVENTS_PER_SEC:
995
+ time.sleep(1 / MAX_SCROLL_EVENTS_PER_SEC - time_elapsed)
991
996
  self.now_zooming = False
992
997
 
993
998
 
@@ -1,4 +1,5 @@
1
- # Widget with a matplotlib FigureCanvas for manual scoring
1
+ """Matplotlib FigureCanvas widget for manual scoring."""
2
+
2
3
  from collections.abc import Callable
3
4
 
4
5
  import matplotlib.ticker as mticker
@@ -1,11 +1,20 @@
1
1
  """Recording list manager"""
2
2
 
3
+ from dataclasses import dataclass
4
+
3
5
  from PySide6.QtCore import QObject
4
6
  from PySide6.QtWidgets import QListWidget, QListWidgetItem
5
7
 
6
8
  from accusleepy.fileio import Recording, load_recording_list, save_recording_list
7
9
 
8
10
 
11
+ @dataclass
12
+ class RecordingListItem(Recording):
13
+ """A Recording with an associated QListWidget item for the GUI"""
14
+
15
+ widget: QListWidgetItem = None
16
+
17
+
9
18
  class RecordingListManager(QObject):
10
19
  """Manages the list of recordings and the associated QListWidget"""
11
20
 
@@ -14,19 +23,19 @@ class RecordingListManager(QObject):
14
23
  self._widget = list_widget
15
24
 
16
25
  # Create initial empty recording (there is always at least one)
17
- first_recording = Recording(
26
+ first_recording = RecordingListItem(
18
27
  widget=QListWidgetItem("Recording 1", self._widget),
19
28
  )
20
- self._recordings: list[Recording] = [first_recording]
29
+ self._recordings: list[RecordingListItem] = [first_recording]
21
30
  self._widget.addItem(first_recording.widget)
22
31
  self._widget.setCurrentRow(0)
23
32
 
24
33
  @property
25
- def current(self) -> Recording:
34
+ def current(self) -> RecordingListItem:
26
35
  """The currently selected recording"""
27
36
  return self._recordings[self._widget.currentRow()]
28
37
 
29
- def add(self, sampling_rate: int | float) -> Recording:
38
+ def add(self, sampling_rate: int | float) -> RecordingListItem:
30
39
  """Add a new recording to the list
31
40
 
32
41
  :param sampling_rate: sampling rate for the new recording
@@ -35,7 +44,7 @@ class RecordingListManager(QObject):
35
44
  new_name = max(r.name for r in self._recordings) + 1
36
45
 
37
46
  # Create recording with widget
38
- recording = Recording(
47
+ recording = RecordingListItem(
39
48
  name=new_name,
40
49
  sampling_rate=sampling_rate,
41
50
  widget=QListWidgetItem(f"Recording {new_name}", self._widget),
@@ -63,7 +72,7 @@ class RecordingListManager(QObject):
63
72
  else:
64
73
  # Reset the single recording to defaults
65
74
  recording_name = self._recordings[0].name
66
- self._recordings[0] = Recording(widget=self._recordings[0].widget)
75
+ self._recordings[0] = RecordingListItem(widget=self._recordings[0].widget)
67
76
  self._recordings[0].widget.setText(f"Recording {self._recordings[0].name}")
68
77
  return f"cleared Recording {recording_name}"
69
78
 
@@ -85,14 +94,15 @@ class RecordingListManager(QObject):
85
94
  try:
86
95
  self._widget.clear()
87
96
 
88
- # Load recordings
89
- self._recordings = load_recording_list(filename)
90
-
91
- # Create widgets for each recording
92
- for recording in self._recordings:
93
- recording.widget = QListWidgetItem(
94
- f"Recording {recording.name}", self._widget
97
+ # Load recordings and create widgets
98
+ self._recordings = [
99
+ RecordingListItem(
100
+ **r.__dict__,
101
+ widget=QListWidgetItem(f"Recording {r.name}", self._widget),
95
102
  )
103
+ for r in load_recording_list(filename)
104
+ ]
105
+ for recording in self._recordings:
96
106
  self._widget.addItem(recording.widget)
97
107
  finally:
98
108
  self._widget.blockSignals(False)
@@ -106,5 +116,5 @@ class RecordingListManager(QObject):
106
116
  def __len__(self):
107
117
  return len(self._recordings)
108
118
 
109
- def __getitem__(self, index: int) -> Recording:
119
+ def __getitem__(self, index: int) -> RecordingListItem:
110
120
  return self._recordings[index]
@@ -1,3 +1,5 @@
1
+ """Neural network model definitions for sleep stage classification."""
2
+
1
3
  from torch import device, flatten, nn
2
4
  from torch import load as torch_load
3
5
  from torch import save as torch_save
@@ -14,10 +14,6 @@ import timeit
14
14
  import warnings
15
15
 
16
16
  import numpy as np
17
- from joblib import Parallel, cpu_count, delayed
18
-
19
- # from scipy.signal import detrend # unused by AccuSleePy
20
- # from scipy.signal.windows import dpss # lazily loaded later
21
17
 
22
18
 
23
19
  # MULTITAPER SPECTROGRAM #
@@ -206,19 +202,9 @@ def spectrogram(
206
202
  wt,
207
203
  )
208
204
 
209
- if multiprocess: # use multiprocessing
210
- n_jobs = max(cpu_count() - 1, 1) if n_jobs is None else n_jobs
211
- mt_spectrogram = np.vstack(
212
- Parallel(n_jobs=n_jobs)(
213
- delayed(calc_mts_segment)(data_segments[num_window, :], *mts_params)
214
- for num_window in range(num_windows)
215
- )
216
- )
217
-
218
- else: # if no multiprocessing, compute normally
219
- mt_spectrogram = np.apply_along_axis(
220
- calc_mts_segment, 1, data_segments, *mts_params
221
- )
205
+ mt_spectrogram = np.apply_along_axis(
206
+ calc_mts_segment, 1, data_segments, *mts_params
207
+ )
222
208
 
223
209
  # Compute one-sided PSD spectrum
224
210
  mt_spectrogram = mt_spectrogram.T
@@ -251,37 +237,6 @@ def spectrogram(
251
237
  if np.all(mt_spectrogram.flatten() == 0):
252
238
  print("\n Data was all zeros, no output")
253
239
 
254
- # # Plot multitaper spectrogram
255
- # if plot_on:
256
- # # convert from power to dB
257
- # spect_data = nanpow2db(mt_spectrogram)
258
- #
259
- # # Set x and y axes
260
- # dx = stimes[1] - stimes[0]
261
- # dy = sfreqs[1] - sfreqs[0]
262
- # extent = [stimes[0] - dx, stimes[-1] + dx, sfreqs[-1] + dy, sfreqs[0] - dy]
263
- #
264
- # # Plot spectrogram
265
- # if ax is None:
266
- # fig, ax = plt.subplots()
267
- # else:
268
- # fig = ax.get_figure()
269
- # im = ax.imshow(spect_data, extent=extent, aspect="auto")
270
- # fig.colorbar(im, ax=ax, label="PSD (dB)", shrink=0.8)
271
- # ax.set_xlabel("Time (HH:MM:SS)")
272
- # ax.set_ylabel("Frequency (Hz)")
273
- # im.set_cmap(plt.cm.get_cmap("cet_rainbow4"))
274
- # ax.invert_yaxis()
275
- #
276
- # # Scale colormap
277
- # if clim_scale:
278
- # clim = np.percentile(spect_data, [5, 98]) # from 5th percentile to 98th
279
- # im.set_clim(clim) # actually change colorbar scale
280
- #
281
- # fig.show()
282
- # if return_fig:
283
- # return mt_spectrogram, stimes, sfreqs, (fig, ax)
284
-
285
240
  return mt_spectrogram, stimes, sfreqs
286
241
 
287
242
 
@@ -568,18 +523,6 @@ def nanpow2db(y):
568
523
  return ydB
569
524
 
570
525
 
571
- # Helper #
572
- def is_outlier(data):
573
- smad = 1.4826 * np.median(
574
- abs(data - np.median(data))
575
- ) # scaled median absolute deviation
576
- outlier_mask = (
577
- abs(data - np.median(data)) > 3 * smad
578
- ) # outliers are more than 3 smads away from median
579
- outlier_mask = outlier_mask | np.isnan(data) | np.isinf(data)
580
- return outlier_mask
581
-
582
-
583
526
  # CALCULATE MULTITAPER SPECTRUM ON SINGLE SEGMENT
584
527
  def calc_mts_segment(
585
528
  data_segment,
@@ -1,3 +1,5 @@
1
+ """EEG/EMG signal processing, mixture z-scoring, and training image generation."""
2
+
1
3
  import logging
2
4
  import os
3
5
  import warnings
@@ -1,3 +1,5 @@
1
+ """Temperature scaling for model confidence calibration."""
2
+
1
3
  import logging
2
4
 
3
5
  import numpy as np
@@ -1,3 +1,5 @@
1
+ """Validation utilities for brain state labels and recordings."""
2
+
1
3
  import numpy as np
2
4
 
3
5
  from accusleepy.brain_state_set import BrainStateSet
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "accusleepy"
3
- version = "0.10.0"
3
+ version = "0.10.1"
4
4
  description = "Python implementation of AccuSleep"
5
5
  authors = [
6
6
  {name = "Zeke Barger",email = "zekebarger@gmail.com"}
@@ -15,15 +15,14 @@ dependencies = [
15
15
  "torchvision (>=0.23.0,<1.0.0)",
16
16
  "scipy (>=1.15.2,<2.0.0)",
17
17
  "matplotlib (>=3.10.1,<4.0.0)",
18
- "joblib (>=1.4.2,<2.0.0)",
19
18
  "pillow (>=11.1.0,<12.0.0)",
20
- "fastparquet (>=2024.11.0,<2025.0.0)",
21
- "pre-commit (>=4.2.0,<5.0.0)",
22
19
  "tqdm (>=4.67.1,<5.0.0)",
23
- "pyside6 (>=6.9.0, <6.9.3)",
20
+ "pyside6 (>=6.10.1,<7.0.0)",
21
+ "pyarrow (>=23.0.0,<24.0.0)",
24
22
  ]
25
23
 
26
24
  [tool.poetry.group.dev.dependencies]
25
+ pre-commit = ">=4.2.0,<5.0.0"
27
26
  pytest = ">=8.3.5,<9.0.0"
28
27
  pytest-qt = ">=4.4.0,<5.0.0"
29
28
  ruff = ">=0.11.2,<0.12.0"
File without changes
File without changes