accusleepy 0.9.2__tar.gz → 0.10.0__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 (48) hide show
  1. {accusleepy-0.9.2 → accusleepy-0.10.0}/PKG-INFO +3 -3
  2. {accusleepy-0.9.2 → accusleepy-0.10.0}/README.md +2 -1
  3. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/config.json +2 -1
  4. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/constants.py +2 -0
  5. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/fileio.py +15 -12
  6. accusleepy-0.10.0/accusleepy/gui/images/primary_window.png +0 -0
  7. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/main.py +1 -5
  8. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/manual_scoring.py +96 -38
  9. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/primary_window.py +85 -85
  10. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/primary_window.ui +95 -107
  11. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/settings_widget.py +12 -0
  12. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/text/main_guide.md +15 -12
  13. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/text/manual_scoring_guide.md +1 -0
  14. {accusleepy-0.9.2 → accusleepy-0.10.0}/pyproject.toml +8 -2
  15. accusleepy-0.9.2/accusleepy/gui/images/primary_window.png +0 -0
  16. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/__init__.py +0 -0
  17. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/__main__.py +0 -0
  18. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/bouts.py +0 -0
  19. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/brain_state_set.py +0 -0
  20. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/classification.py +0 -0
  21. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/__init__.py +0 -0
  22. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/dialogs.py +0 -0
  23. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/icons/brightness_down.png +0 -0
  24. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/icons/brightness_up.png +0 -0
  25. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/icons/double_down_arrow.png +0 -0
  26. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/icons/double_up_arrow.png +0 -0
  27. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/icons/down_arrow.png +0 -0
  28. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/icons/home.png +0 -0
  29. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/icons/question.png +0 -0
  30. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/icons/save.png +0 -0
  31. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/icons/up_arrow.png +0 -0
  32. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/icons/zoom_in.png +0 -0
  33. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/icons/zoom_out.png +0 -0
  34. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/images/viewer_window.png +0 -0
  35. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/images/viewer_window_annotated.png +0 -0
  36. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/mplwidget.py +0 -0
  37. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/recording_manager.py +0 -0
  38. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/resources.qrc +0 -0
  39. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/resources_rc.py +0 -0
  40. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/text/dev_guide.md +0 -0
  41. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/viewer_window.py +0 -0
  42. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/gui/viewer_window.ui +0 -0
  43. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/models.py +0 -0
  44. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/multitaper.py +0 -0
  45. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/services.py +0 -0
  46. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/signal_processing.py +0 -0
  47. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/temperature_scaling.py +0 -0
  48. {accusleepy-0.9.2 → accusleepy-0.10.0}/accusleepy/validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: accusleepy
3
- Version: 0.9.2
3
+ Version: 0.10.0
4
4
  Summary: Python implementation of AccuSleep
5
5
  License: GPL-3.0-only
6
6
  Author: Zeke Barger
@@ -20,7 +20,6 @@ Requires-Dist: pillow (>=11.1.0,<12.0.0)
20
20
  Requires-Dist: pre-commit (>=4.2.0,<5.0.0)
21
21
  Requires-Dist: pyside6 (>=6.9.0,<6.9.3)
22
22
  Requires-Dist: scipy (>=1.15.2,<2.0.0)
23
- Requires-Dist: toml (>=0.10.2,<0.11.0)
24
23
  Requires-Dist: torch (>=2.8.0,<3.0.0)
25
24
  Requires-Dist: torchvision (>=0.23.0,<1.0.0)
26
25
  Requires-Dist: tqdm (>=4.67.1,<5.0.0)
@@ -80,7 +79,8 @@ please consult the [developer guide](accusleepy/gui/text/dev_guide.md).
80
79
 
81
80
  ## Changelog
82
81
 
83
- - 0.8.1-0.9.2: Improved error handling and code quality
82
+ - 0.10.0: Improved zoom behavior
83
+ - 0.8.1-0.9.3: Improved error handling and code quality
84
84
  - 0.8.0: More configurable settings, visual improvements
85
85
  - 0.7.1-0.7.3: Bugfixes, code cleanup
86
86
  - 0.7.0: More settings can be configured in the UI
@@ -52,7 +52,8 @@ please consult the [developer guide](accusleepy/gui/text/dev_guide.md).
52
52
 
53
53
  ## Changelog
54
54
 
55
- - 0.8.1-0.9.2: Improved error handling and code quality
55
+ - 0.10.0: Improved zoom behavior
56
+ - 0.8.1-0.9.3: Improved error handling and code quality
56
57
  - 0.8.0: More configurable settings, visual improvements
57
58
  - 0.7.1-0.7.3: Bugfixes, code cleanup
58
59
  - 0.7.0: More settings can be configured in the UI
@@ -35,5 +35,6 @@
35
35
  "training_epochs": 6
36
36
  },
37
37
  "epochs_to_show": 5,
38
- "autoscroll_state": false
38
+ "autoscroll_state": false,
39
+ "delete_training_images": true
39
40
  }
@@ -72,6 +72,7 @@ EMG_FILTER_KEY = "emg_filter"
72
72
  HYPERPARAMETERS_KEY = "hyperparameters"
73
73
  EPOCHS_TO_SHOW_KEY = "epochs_to_show"
74
74
  AUTOSCROLL_KEY = "autoscroll_state"
75
+ DELETE_TRAINING_IMAGES_KEY = "delete_training_images"
75
76
 
76
77
  # default values
77
78
  # default UI settings
@@ -87,6 +88,7 @@ DEFAULT_BATCH_SIZE = 64
87
88
  DEFAULT_LEARNING_RATE = 1e-3
88
89
  DEFAULT_MOMENTUM = 0.9
89
90
  DEFAULT_TRAINING_EPOCHS = 6
91
+ DEFAULT_DELETE_TRAINING_IMAGES_STATE = True
90
92
  # default manual scoring settings
91
93
  DEFAULT_EPOCHS_TO_SHOW = 5
92
94
  DEFAULT_AUTOSCROLL_STATE = False
@@ -1,11 +1,11 @@
1
1
  import json
2
2
  import os
3
3
  from dataclasses import dataclass
4
+ from importlib.metadata import version, PackageNotFoundError
4
5
 
5
6
  import numpy as np
6
7
  import pandas as pd
7
8
  from PySide6.QtWidgets import QListWidgetItem
8
- import toml
9
9
 
10
10
  from accusleepy.brain_state_set import BRAIN_STATES_KEY, BrainState, BrainStateSet
11
11
  import accusleepy.constants as c
@@ -43,6 +43,7 @@ class AccuSleePyConfig:
43
43
  hyperparameters: Hyperparameters
44
44
  epochs_to_show: int
45
45
  autoscroll_state: bool
46
+ delete_training_images: bool
46
47
 
47
48
 
48
49
  @dataclass
@@ -139,7 +140,8 @@ def load_config() -> AccuSleePyConfig:
139
140
  EMG filter parameters,
140
141
  model training hyperparameters,
141
142
  default epochs to show for manual scoring,
142
- default autoscroll state for manual scoring
143
+ default autoscroll state for manual scoring,
144
+ setting to delete training images automatically
143
145
  """
144
146
  with open(
145
147
  os.path.join(os.path.dirname(os.path.abspath(__file__)), c.CONFIG_FILE), "r"
@@ -183,6 +185,9 @@ def load_config() -> AccuSleePyConfig:
183
185
  ),
184
186
  epochs_to_show=data.get(c.EPOCHS_TO_SHOW_KEY, c.DEFAULT_EPOCHS_TO_SHOW),
185
187
  autoscroll_state=data.get(c.AUTOSCROLL_KEY, c.DEFAULT_AUTOSCROLL_STATE),
188
+ delete_training_images=data.get(
189
+ c.DELETE_TRAINING_IMAGES_KEY, c.DEFAULT_DELETE_TRAINING_IMAGES_STATE
190
+ ),
186
191
  )
187
192
 
188
193
 
@@ -196,6 +201,7 @@ def save_config(
196
201
  hyperparameters: Hyperparameters,
197
202
  epochs_to_show: int,
198
203
  autoscroll_state: bool,
204
+ delete_training_images: bool,
199
205
  ) -> None:
200
206
  """Save configuration of brain state options to json file
201
207
 
@@ -210,6 +216,8 @@ def save_config(
210
216
  :param hyperparameters: model training hyperparameters
211
217
  :param epochs_to_show: default epochs to show for manual scoring,
212
218
  :param autoscroll_state: default autoscroll state for manual scoring
219
+ :param delete_training_images: whether to automatically delete images
220
+ created for model training once training is complete
213
221
  """
214
222
  output_dict = brain_state_set.to_output_dict()
215
223
  output_dict.update({c.DEFAULT_EPOCH_LENGTH_KEY: default_epoch_length})
@@ -220,6 +228,7 @@ def save_config(
220
228
  output_dict.update({c.HYPERPARAMETERS_KEY: hyperparameters.__dict__})
221
229
  output_dict.update({c.EPOCHS_TO_SHOW_KEY: epochs_to_show})
222
230
  output_dict.update({c.AUTOSCROLL_KEY: autoscroll_state})
231
+ output_dict.update({c.DELETE_TRAINING_IMAGES_KEY: delete_training_images})
223
232
  with open(
224
233
  os.path.join(os.path.dirname(os.path.abspath(__file__)), c.CONFIG_FILE), "w"
225
234
  ) as f:
@@ -267,13 +276,7 @@ def get_version() -> str:
267
276
 
268
277
  :return: AccuSleePy package version
269
278
  """
270
- version = ""
271
- toml_file = os.path.join(
272
- os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
273
- "pyproject.toml",
274
- )
275
- if os.path.isfile(toml_file):
276
- toml_data = toml.load(toml_file)
277
- if "project" in toml_data and "version" in toml_data["project"]:
278
- version = toml_data["project"]["version"]
279
- return version
279
+ try:
280
+ return version("accusleepy")
281
+ except PackageNotFoundError:
282
+ return ""
@@ -76,7 +76,6 @@ class TrainingSettings:
76
76
  """Settings for training a new model"""
77
77
 
78
78
  epochs_per_img: int = 9
79
- delete_images: bool = True
80
79
  model_type: str = DEFAULT_MODEL_TYPE
81
80
  calibrate: bool = True
82
81
 
@@ -176,9 +175,6 @@ class AccuSleepWindow(QMainWindow):
176
175
  self.ui.image_number_input.valueChanged.connect(
177
176
  lambda v: setattr(self.training, "epochs_per_img", v)
178
177
  )
179
- self.ui.delete_image_box.stateChanged.connect(
180
- lambda v: setattr(self.training, "delete_images", bool(v))
181
- )
182
178
  self.ui.calibrate_checkbox.stateChanged.connect(
183
179
  self.update_training_calibration
184
180
  )
@@ -304,7 +300,7 @@ class AccuSleepWindow(QMainWindow):
304
300
  emg_filter=self.config.emg_filter,
305
301
  hyperparameters=self.config.hyperparameters,
306
302
  model_filename=model_filename,
307
- delete_images=self.training.delete_images,
303
+ delete_images=self.config.delete_training_images,
308
304
  )
309
305
 
310
306
  # Display results
@@ -6,6 +6,7 @@
6
6
 
7
7
  import copy
8
8
  import os
9
+ import time
9
10
  from dataclasses import dataclass
10
11
  from functools import partial
11
12
  from types import SimpleNamespace
@@ -82,9 +83,10 @@ UNDO_LIMIT = 1000
82
83
  # brightness scaling factors for the spectrogram
83
84
  BRIGHTER_SCALE_FACTOR = 0.96
84
85
  DIMMER_SCALE_FACTOR = 1.07
85
- # zoom factor for upper plots
86
- ZOOM_IN_FACTOR = 0.45
87
- ZOOM_OUT_FACTOR = 1.017
86
+ # zoom factor for upper plots - larger values = bigger changes
87
+ ZOOM_FACTOR = 0.1
88
+ # interval in seconds between zoom events triggered by scrolling
89
+ ZOOM_DELAY = 0.05
88
90
 
89
91
 
90
92
  @dataclass
@@ -352,8 +354,10 @@ class ManualScoringWindow(QDialog):
352
354
  )
353
355
  keypress_redo.activated.connect(self.redo)
354
356
 
355
- # user input: clicks
357
+ # user input: mouse events
356
358
  self.ui.upperfigure.canvas.mpl_connect("button_press_event", self.click_to_jump)
359
+ self.ui.upperfigure.canvas.mpl_connect("scroll_event", self.scroll_zoom)
360
+ self.now_zooming = False # impose timeout on zoom events
357
361
 
358
362
  # user input: buttons
359
363
  self.ui.savebutton.clicked.connect(self.save)
@@ -483,7 +487,7 @@ class ManualScoringWindow(QDialog):
483
487
 
484
488
  def closeEvent(self, event: QCloseEvent) -> None:
485
489
  """Check if there are unsaved changes before closing"""
486
- if not all(self.labels == self.last_saved_labels):
490
+ if not np.array_equal(self.labels, self.last_saved_labels):
487
491
  result = QMessageBox.question(
488
492
  self,
489
493
  "Unsaved changes",
@@ -668,6 +672,8 @@ class ManualScoringWindow(QDialog):
668
672
  self.label_display_options,
669
673
  )
670
674
  self.update_figures()
675
+ # upper plot x limits might need to change
676
+ self.zoom_x(direction=None)
671
677
 
672
678
  def update_signal_offset(self, signal: str, direction: str) -> None:
673
679
  """Shift EEG or EMG up or down
@@ -713,39 +719,19 @@ class ManualScoringWindow(QDialog):
713
719
  (self.upper_left_epoch, self.upper_right_epoch + 1)
714
720
  )
715
721
 
716
- def zoom_x(self, direction: str) -> None:
722
+ def zoom_x(self, direction: str | None) -> None:
717
723
  """Change upper figure x-axis zoom level
718
724
 
719
- :param direction: in, out, or reset
725
+ :param direction: in, out, reset, or None
720
726
  """
721
- epochs_shown = self.upper_right_epoch - self.upper_left_epoch + 1
722
- if direction == ZOOM_IN:
723
- self.upper_left_epoch = max(
724
- [
725
- self.upper_left_epoch,
726
- round(self.epoch - ZOOM_IN_FACTOR * epochs_shown),
727
- ]
728
- )
729
-
730
- self.upper_right_epoch = min(
731
- [
732
- self.upper_right_epoch,
733
- round(self.epoch + ZOOM_IN_FACTOR * epochs_shown),
734
- ]
735
- )
736
-
737
- elif direction == ZOOM_OUT:
738
- self.upper_left_epoch = max(
739
- [0, round(self.epoch - ZOOM_OUT_FACTOR * epochs_shown)]
740
- )
741
-
742
- self.upper_right_epoch = min(
743
- [self.n_epochs - 1, round(self.epoch + ZOOM_OUT_FACTOR * epochs_shown)]
744
- )
745
-
746
- else: # reset
747
- self.upper_left_epoch = 0
748
- self.upper_right_epoch = self.n_epochs - 1
727
+ self.upper_left_epoch, self.upper_right_epoch = find_new_x_limits(
728
+ direction=direction,
729
+ left_epoch=self.upper_left_epoch,
730
+ right_epoch=self.upper_right_epoch,
731
+ min_n_shown=self.epochs_to_show,
732
+ total_epochs=self.n_epochs,
733
+ selected_epoch=self.epoch,
734
+ )
749
735
  self.adjust_upper_figure_x_limits()
750
736
  self.ui.upperfigure.canvas.draw()
751
737
 
@@ -806,8 +792,11 @@ class ManualScoringWindow(QDialog):
806
792
  # update upper plot if needed
807
793
  upper_epochs_shown = self.upper_right_epoch - self.upper_left_epoch + 1
808
794
  if (
809
- self.epoch
810
- > self.upper_left_epoch + (1 - SCROLL_BOUNDARY) * upper_epochs_shown
795
+ (
796
+ self.epoch
797
+ > self.upper_left_epoch + (1 - SCROLL_BOUNDARY) * upper_epochs_shown
798
+ or self.epoch + (self.epochs_to_show - 1) / 2 > self.upper_right_epoch
799
+ )
811
800
  and self.upper_right_epoch < (self.n_epochs - 1)
812
801
  and direction == DIRECTION_RIGHT
813
802
  ):
@@ -815,7 +804,11 @@ class ManualScoringWindow(QDialog):
815
804
  self.upper_right_epoch += 1
816
805
  self.adjust_upper_figure_x_limits()
817
806
  elif (
818
- self.epoch < self.upper_left_epoch + SCROLL_BOUNDARY * upper_epochs_shown
807
+ (
808
+ self.epoch
809
+ < self.upper_left_epoch + SCROLL_BOUNDARY * upper_epochs_shown
810
+ or self.epoch - (self.epochs_to_show - 1) / 2 < self.upper_left_epoch
811
+ )
819
812
  and self.upper_left_epoch > 0
820
813
  and direction == DIRECTION_LEFT
821
814
  ):
@@ -984,6 +977,19 @@ class ManualScoringWindow(QDialog):
984
977
 
985
978
  self.update_figures()
986
979
 
980
+ def scroll_zoom(self, event) -> None:
981
+ """Zoom on mouse scroll events"""
982
+ if self.now_zooming:
983
+ return
984
+
985
+ self.now_zooming = True
986
+ if event.button == "up":
987
+ self.zoom_x(direction=ZOOM_IN)
988
+ else:
989
+ self.zoom_x(direction=ZOOM_OUT)
990
+ time.sleep(ZOOM_DELAY)
991
+ self.now_zooming = False
992
+
987
993
 
988
994
  def convert_labels(labels: np.array, style: str) -> np.array:
989
995
  """Convert labels between "display" and "digit" formats
@@ -1095,3 +1101,55 @@ def transform_eeg_emg(eeg: np.array, emg: np.array) -> (np.array, np.array):
1095
1101
  eeg = eeg / np.percentile(eeg, 95) / 2.2
1096
1102
  emg = emg / np.percentile(emg, 95) / 2.2
1097
1103
  return eeg, emg
1104
+
1105
+
1106
+ def find_new_x_limits(
1107
+ direction: str | None,
1108
+ left_epoch: int,
1109
+ right_epoch: int,
1110
+ total_epochs: int,
1111
+ min_n_shown: int,
1112
+ selected_epoch: int,
1113
+ ) -> (int, int):
1114
+ """Calculate new plot x limits to allow zooming
1115
+
1116
+ :param direction: in, out, reset, or None
1117
+ :param left_epoch: index of current leftmost epoch
1118
+ :param right_epoch: index of current rightmost epoch
1119
+ :param total_epochs: total number of epochs in the recording
1120
+ :param min_n_shown: minimum number of epochs to display
1121
+ :param selected_epoch: currently selected epoch
1122
+ """
1123
+ # number of epochs currently displayed in the upper plots
1124
+ current_n_shown = right_epoch - left_epoch + 1
1125
+ if direction == ZOOM_IN:
1126
+ # can't display fewer than the number of epochs in the lower plot
1127
+ new_n_shown = max([min_n_shown, round(current_n_shown * (1 - ZOOM_FACTOR))])
1128
+ elif direction == ZOOM_OUT:
1129
+ # can't display more than the total number of epochs
1130
+ new_n_shown = min([total_epochs, round(current_n_shown / (1 - ZOOM_FACTOR))])
1131
+ elif direction == ZOOM_RESET:
1132
+ left_epoch = 0
1133
+ right_epoch = total_epochs - 1
1134
+ return left_epoch, right_epoch
1135
+ else: # just recalculating if min_n_shown has changed
1136
+ new_n_shown = int(
1137
+ np.clip(current_n_shown, a_min=min_n_shown, a_max=total_epochs)
1138
+ )
1139
+
1140
+ # count epochs to show on either side of the selected epoch
1141
+ epochs_on_left_side = int(np.ceil((new_n_shown - 1) / 2))
1142
+ epochs_on_right_side = new_n_shown - epochs_on_left_side - 1
1143
+ if selected_epoch - epochs_on_left_side < 0:
1144
+ # can't go further left than 0
1145
+ left_epoch = 0
1146
+ right_epoch = new_n_shown - 1
1147
+ elif selected_epoch + epochs_on_right_side >= total_epochs:
1148
+ # can't go further right than the total number of epochs
1149
+ left_epoch = total_epochs - new_n_shown
1150
+ right_epoch = total_epochs - 1
1151
+ else:
1152
+ left_epoch = selected_epoch - epochs_on_left_side
1153
+ right_epoch = selected_epoch + epochs_on_right_side
1154
+
1155
+ return left_epoch, right_epoch
@@ -757,7 +757,7 @@ class Ui_PrimaryWindow(object):
757
757
  self.top_training_layout.setSpacing(10)
758
758
  self.top_training_layout.setObjectName("top_training_layout")
759
759
  self.horizontalLayout_5 = QHBoxLayout()
760
- self.horizontalLayout_5.setSpacing(5)
760
+ self.horizontalLayout_5.setSpacing(3)
761
761
  self.horizontalLayout_5.setObjectName("horizontalLayout_5")
762
762
  self.label = QLabel(self.model_training_tab)
763
763
  self.label.setObjectName("label")
@@ -786,21 +786,48 @@ class Ui_PrimaryWindow(object):
786
786
 
787
787
  self.top_training_layout.addItem(self.horizontalSpacer)
788
788
 
789
- self.delete_image_box = QCheckBox(self.model_training_tab)
790
- self.delete_image_box.setObjectName("delete_image_box")
789
+ self.calibrate_checkbox = QCheckBox(self.model_training_tab)
790
+ self.calibrate_checkbox.setObjectName("calibrate_checkbox")
791
791
  sizePolicy1.setHeightForWidth(
792
- self.delete_image_box.sizePolicy().hasHeightForWidth()
792
+ self.calibrate_checkbox.sizePolicy().hasHeightForWidth()
793
793
  )
794
- self.delete_image_box.setSizePolicy(sizePolicy1)
795
- self.delete_image_box.setChecked(True)
794
+ self.calibrate_checkbox.setSizePolicy(sizePolicy1)
795
+ self.calibrate_checkbox.setChecked(True)
796
+
797
+ self.top_training_layout.addWidget(self.calibrate_checkbox)
798
+
799
+ self.horizontalLayout_84 = QHBoxLayout()
800
+ self.horizontalLayout_84.setSpacing(3)
801
+ self.horizontalLayout_84.setObjectName("horizontalLayout_84")
802
+ self.horizontalLayout_84.setContentsMargins(10, -1, -1, -1)
803
+ self.calibrate_label = QLabel(self.model_training_tab)
804
+ self.calibrate_label.setObjectName("calibrate_label")
805
+ sizePolicy1.setHeightForWidth(
806
+ self.calibrate_label.sizePolicy().hasHeightForWidth()
807
+ )
808
+ self.calibrate_label.setSizePolicy(sizePolicy1)
809
+
810
+ self.horizontalLayout_84.addWidget(self.calibrate_label)
811
+
812
+ self.calibration_spinbox = QSpinBox(self.model_training_tab)
813
+ self.calibration_spinbox.setObjectName("calibration_spinbox")
814
+ sizePolicy1.setHeightForWidth(
815
+ self.calibration_spinbox.sizePolicy().hasHeightForWidth()
816
+ )
817
+ self.calibration_spinbox.setSizePolicy(sizePolicy1)
818
+ self.calibration_spinbox.setMinimum(10)
819
+ self.calibration_spinbox.setMaximum(50)
820
+ self.calibration_spinbox.setValue(15)
821
+
822
+ self.horizontalLayout_84.addWidget(self.calibration_spinbox)
796
823
 
797
- self.top_training_layout.addWidget(self.delete_image_box)
824
+ self.top_training_layout.addLayout(self.horizontalLayout_84)
798
825
 
799
- self.horizontalSpacer_6 = QSpacerItem(
826
+ self.horizontalSpacer_3 = QSpacerItem(
800
827
  10, 5, QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Minimum
801
828
  )
802
829
 
803
- self.top_training_layout.addItem(self.horizontalSpacer_6)
830
+ self.top_training_layout.addItem(self.horizontalSpacer_3)
804
831
 
805
832
  self.horizontalLayout_6 = QHBoxLayout()
806
833
  self.horizontalLayout_6.setObjectName("horizontalLayout_6")
@@ -836,18 +863,12 @@ class Ui_PrimaryWindow(object):
836
863
 
837
864
  self.top_training_layout.addLayout(self.horizontalLayout_6)
838
865
 
839
- self.horizontalSpacer_3 = QSpacerItem(
840
- 10, 5, QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Minimum
841
- )
842
-
843
- self.top_training_layout.addItem(self.horizontalSpacer_3)
844
-
845
- self.top_training_layout.setStretch(0, 2)
866
+ self.top_training_layout.setStretch(0, 5)
846
867
  self.top_training_layout.setStretch(1, 1)
847
- self.top_training_layout.setStretch(2, 2)
848
- self.top_training_layout.setStretch(3, 1)
849
- self.top_training_layout.setStretch(4, 3)
850
- self.top_training_layout.setStretch(5, 1)
868
+ self.top_training_layout.setStretch(2, 3)
869
+ self.top_training_layout.setStretch(3, 4)
870
+ self.top_training_layout.setStretch(4, 1)
871
+ self.top_training_layout.setStretch(5, 5)
851
872
 
852
873
  self.model_training_layout.addLayout(self.top_training_layout, 0, 0, 1, 1)
853
874
 
@@ -855,10 +876,10 @@ class Ui_PrimaryWindow(object):
855
876
  self.bottom_training_layout.setObjectName("bottom_training_layout")
856
877
  self.train_model_button = QPushButton(self.model_training_tab)
857
878
  self.train_model_button.setObjectName("train_model_button")
858
- sizePolicy3.setHeightForWidth(
879
+ sizePolicy1.setHeightForWidth(
859
880
  self.train_model_button.sizePolicy().hasHeightForWidth()
860
881
  )
861
- self.train_model_button.setSizePolicy(sizePolicy3)
882
+ self.train_model_button.setSizePolicy(sizePolicy1)
862
883
 
863
884
  self.bottom_training_layout.addWidget(self.train_model_button)
864
885
 
@@ -868,56 +889,8 @@ class Ui_PrimaryWindow(object):
868
889
 
869
890
  self.bottom_training_layout.addItem(self.horizontalSpacer_8)
870
891
 
871
- self.calibrate_checkbox = QCheckBox(self.model_training_tab)
872
- self.calibrate_checkbox.setObjectName("calibrate_checkbox")
873
- sizePolicy1.setHeightForWidth(
874
- self.calibrate_checkbox.sizePolicy().hasHeightForWidth()
875
- )
876
- self.calibrate_checkbox.setSizePolicy(sizePolicy1)
877
- self.calibrate_checkbox.setChecked(True)
878
-
879
- self.bottom_training_layout.addWidget(self.calibrate_checkbox)
880
-
881
- self.horizontalSpacer_7 = QSpacerItem(
882
- 10, 10, QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Minimum
883
- )
884
-
885
- self.bottom_training_layout.addItem(self.horizontalSpacer_7)
886
-
887
- self.calibrate_label = QLabel(self.model_training_tab)
888
- self.calibrate_label.setObjectName("calibrate_label")
889
- sizePolicy1.setHeightForWidth(
890
- self.calibrate_label.sizePolicy().hasHeightForWidth()
891
- )
892
- self.calibrate_label.setSizePolicy(sizePolicy1)
893
-
894
- self.bottom_training_layout.addWidget(self.calibrate_label)
895
-
896
- self.calibration_spinbox = QSpinBox(self.model_training_tab)
897
- self.calibration_spinbox.setObjectName("calibration_spinbox")
898
- sizePolicy1.setHeightForWidth(
899
- self.calibration_spinbox.sizePolicy().hasHeightForWidth()
900
- )
901
- self.calibration_spinbox.setSizePolicy(sizePolicy1)
902
- self.calibration_spinbox.setMinimum(10)
903
- self.calibration_spinbox.setMaximum(50)
904
- self.calibration_spinbox.setValue(15)
905
-
906
- self.bottom_training_layout.addWidget(self.calibration_spinbox)
907
-
908
- self.horizontalSpacer_78 = QSpacerItem(
909
- 10, 10, QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Minimum
910
- )
911
-
912
- self.bottom_training_layout.addItem(self.horizontalSpacer_78)
913
-
914
- self.bottom_training_layout.setStretch(0, 6)
892
+ self.bottom_training_layout.setStretch(0, 5)
915
893
  self.bottom_training_layout.setStretch(1, 5)
916
- self.bottom_training_layout.setStretch(2, 3)
917
- self.bottom_training_layout.setStretch(3, 1)
918
- self.bottom_training_layout.setStretch(4, 3)
919
- self.bottom_training_layout.setStretch(5, 1)
920
- self.bottom_training_layout.setStretch(6, 1)
921
894
 
922
895
  self.model_training_layout.addLayout(self.bottom_training_layout, 1, 0, 1, 1)
923
896
 
@@ -2749,6 +2722,30 @@ class Ui_PrimaryWindow(object):
2749
2722
 
2750
2723
  self.verticalLayout_14.addLayout(self.horizontalLayout_71)
2751
2724
 
2725
+ self.horizontalLayout_79 = QHBoxLayout()
2726
+ self.horizontalLayout_79.setObjectName("horizontalLayout_79")
2727
+ self.horizontalLayout_85 = QHBoxLayout()
2728
+ self.horizontalLayout_85.setObjectName("horizontalLayout_85")
2729
+ self.delete_image_box = QCheckBox(self.hyperparameter_page)
2730
+ self.delete_image_box.setObjectName("delete_image_box")
2731
+ sizePolicy1.setHeightForWidth(
2732
+ self.delete_image_box.sizePolicy().hasHeightForWidth()
2733
+ )
2734
+ self.delete_image_box.setSizePolicy(sizePolicy1)
2735
+ self.delete_image_box.setChecked(True)
2736
+
2737
+ self.horizontalLayout_85.addWidget(self.delete_image_box)
2738
+
2739
+ self.horizontalLayout_79.addLayout(self.horizontalLayout_85)
2740
+
2741
+ self.horizontalSpacer_93 = QSpacerItem(
2742
+ 40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum
2743
+ )
2744
+
2745
+ self.horizontalLayout_79.addItem(self.horizontalSpacer_93)
2746
+
2747
+ self.verticalLayout_14.addLayout(self.horizontalLayout_79)
2748
+
2752
2749
  self.horizontalLayout_74 = QHBoxLayout()
2753
2750
  self.horizontalLayout_74.setObjectName("horizontalLayout_74")
2754
2751
  self.horizontalSpacer_86 = QSpacerItem(
@@ -3033,11 +3030,16 @@ class Ui_PrimaryWindow(object):
3033
3030
  self.label.setText(
3034
3031
  QCoreApplication.translate("PrimaryWindow", "Epochs per image:", None)
3035
3032
  )
3036
- self.delete_image_box.setText(
3037
- QCoreApplication.translate(
3038
- "PrimaryWindow", "Delete images after training", None
3039
- )
3033
+ self.calibrate_checkbox.setText(
3034
+ QCoreApplication.translate("PrimaryWindow", "Calibrate model", None)
3040
3035
  )
3036
+ self.calibrate_label.setText(
3037
+ QCoreApplication.translate("PrimaryWindow", "Calibration set size:", None)
3038
+ )
3039
+ self.calibration_spinbox.setSuffix(
3040
+ QCoreApplication.translate("PrimaryWindow", "%", None)
3041
+ )
3042
+ self.calibration_spinbox.setPrefix("")
3041
3043
  self.label_2.setText(
3042
3044
  QCoreApplication.translate("PrimaryWindow", "Model type:", None)
3043
3045
  )
@@ -3059,15 +3061,6 @@ class Ui_PrimaryWindow(object):
3059
3061
  "PrimaryWindow", "Train classification model", None
3060
3062
  )
3061
3063
  )
3062
- self.calibrate_checkbox.setText(
3063
- QCoreApplication.translate("PrimaryWindow", "Calibrate model", None)
3064
- )
3065
- self.calibrate_label.setText(
3066
- QCoreApplication.translate("PrimaryWindow", "Calibration set size:", None)
3067
- )
3068
- self.calibration_spinbox.setSuffix(
3069
- QCoreApplication.translate("PrimaryWindow", "%", None)
3070
- )
3071
3064
  self.lower_tab_widget.setTabText(
3072
3065
  self.lower_tab_widget.indexOf(self.model_training_tab),
3073
3066
  QCoreApplication.translate("PrimaryWindow", "Model training", None),
@@ -3296,6 +3289,11 @@ class Ui_PrimaryWindow(object):
3296
3289
  self.training_epochs_label.setText(
3297
3290
  QCoreApplication.translate("PrimaryWindow", "Epochs:", None)
3298
3291
  )
3292
+ self.delete_image_box.setText(
3293
+ QCoreApplication.translate(
3294
+ "PrimaryWindow", "Delete images after training", None
3295
+ )
3296
+ )
3299
3297
  self.reset_hyperparams_button.setText(
3300
3298
  QCoreApplication.translate("PrimaryWindow", "reset to defaults", None)
3301
3299
  )
@@ -3306,7 +3304,9 @@ class Ui_PrimaryWindow(object):
3306
3304
  "- Batch size: number of examples in each training iteration\n"
3307
3305
  "- Learning rate: step size for adjusting model weights\n"
3308
3306
  "- Momentum: amount of past gradients to use in the current update. Typically between 0.6 and 0.99\n"
3309
- "- Epochs: number of passes over the training data",
3307
+ "- Epochs: number of passes over the training data\n"
3308
+ "\n"
3309
+ "You can also choose whether to delete images created by the training process once it's finished. This is recommended unless you are debugging the training code.",
3310
3310
  None,
3311
3311
  )
3312
3312
  )
@@ -1239,14 +1239,14 @@ color: rgb(244, 195, 68);</string>
1239
1239
  <number>10</number>
1240
1240
  </property>
1241
1241
  <item row="0" column="0">
1242
- <layout class="QHBoxLayout" name="top_training_layout" stretch="2,1,2,1,3,1">
1242
+ <layout class="QHBoxLayout" name="top_training_layout" stretch="5,1,3,4,1,5">
1243
1243
  <property name="spacing">
1244
1244
  <number>10</number>
1245
1245
  </property>
1246
1246
  <item>
1247
1247
  <layout class="QHBoxLayout" name="horizontalLayout_5">
1248
1248
  <property name="spacing">
1249
- <number>5</number>
1249
+ <number>3</number>
1250
1250
  </property>
1251
1251
  <item>
1252
1252
  <widget class="QLabel" name="label">
@@ -1299,7 +1299,7 @@ color: rgb(244, 195, 68);</string>
1299
1299
  </spacer>
1300
1300
  </item>
1301
1301
  <item>
1302
- <widget class="QCheckBox" name="delete_image_box">
1302
+ <widget class="QCheckBox" name="calibrate_checkbox">
1303
1303
  <property name="sizePolicy">
1304
1304
  <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
1305
1305
  <horstretch>0</horstretch>
@@ -1307,7 +1307,7 @@ color: rgb(244, 195, 68);</string>
1307
1307
  </sizepolicy>
1308
1308
  </property>
1309
1309
  <property name="text">
1310
- <string>Delete images after training</string>
1310
+ <string>Calibrate model</string>
1311
1311
  </property>
1312
1312
  <property name="checked">
1313
1313
  <bool>true</bool>
@@ -1315,7 +1315,55 @@ color: rgb(244, 195, 68);</string>
1315
1315
  </widget>
1316
1316
  </item>
1317
1317
  <item>
1318
- <spacer name="horizontalSpacer_6">
1318
+ <layout class="QHBoxLayout" name="horizontalLayout_84">
1319
+ <property name="spacing">
1320
+ <number>3</number>
1321
+ </property>
1322
+ <property name="leftMargin">
1323
+ <number>10</number>
1324
+ </property>
1325
+ <item>
1326
+ <widget class="QLabel" name="calibrate_label">
1327
+ <property name="sizePolicy">
1328
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
1329
+ <horstretch>0</horstretch>
1330
+ <verstretch>0</verstretch>
1331
+ </sizepolicy>
1332
+ </property>
1333
+ <property name="text">
1334
+ <string>Calibration set size:</string>
1335
+ </property>
1336
+ </widget>
1337
+ </item>
1338
+ <item>
1339
+ <widget class="QSpinBox" name="calibration_spinbox">
1340
+ <property name="sizePolicy">
1341
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
1342
+ <horstretch>0</horstretch>
1343
+ <verstretch>0</verstretch>
1344
+ </sizepolicy>
1345
+ </property>
1346
+ <property name="suffix">
1347
+ <string>%</string>
1348
+ </property>
1349
+ <property name="prefix">
1350
+ <string/>
1351
+ </property>
1352
+ <property name="minimum">
1353
+ <number>10</number>
1354
+ </property>
1355
+ <property name="maximum">
1356
+ <number>50</number>
1357
+ </property>
1358
+ <property name="value">
1359
+ <number>15</number>
1360
+ </property>
1361
+ </widget>
1362
+ </item>
1363
+ </layout>
1364
+ </item>
1365
+ <item>
1366
+ <spacer name="horizontalSpacer_3">
1319
1367
  <property name="orientation">
1320
1368
  <enum>Qt::Orientation::Horizontal</enum>
1321
1369
  </property>
@@ -1376,30 +1424,14 @@ color: rgb(244, 195, 68);</string>
1376
1424
  </item>
1377
1425
  </layout>
1378
1426
  </item>
1379
- <item>
1380
- <spacer name="horizontalSpacer_3">
1381
- <property name="orientation">
1382
- <enum>Qt::Orientation::Horizontal</enum>
1383
- </property>
1384
- <property name="sizeType">
1385
- <enum>QSizePolicy::Policy::Preferred</enum>
1386
- </property>
1387
- <property name="sizeHint" stdset="0">
1388
- <size>
1389
- <width>10</width>
1390
- <height>5</height>
1391
- </size>
1392
- </property>
1393
- </spacer>
1394
- </item>
1395
1427
  </layout>
1396
1428
  </item>
1397
1429
  <item row="1" column="0">
1398
- <layout class="QHBoxLayout" name="bottom_training_layout" stretch="6,5,3,1,3,1,1">
1430
+ <layout class="QHBoxLayout" name="bottom_training_layout" stretch="5,5">
1399
1431
  <item>
1400
1432
  <widget class="QPushButton" name="train_model_button">
1401
1433
  <property name="sizePolicy">
1402
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
1434
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
1403
1435
  <horstretch>0</horstretch>
1404
1436
  <verstretch>0</verstretch>
1405
1437
  </sizepolicy>
@@ -1428,89 +1460,6 @@ color: rgb(244, 195, 68);</string>
1428
1460
  </property>
1429
1461
  </spacer>
1430
1462
  </item>
1431
- <item>
1432
- <widget class="QCheckBox" name="calibrate_checkbox">
1433
- <property name="sizePolicy">
1434
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
1435
- <horstretch>0</horstretch>
1436
- <verstretch>0</verstretch>
1437
- </sizepolicy>
1438
- </property>
1439
- <property name="text">
1440
- <string>Calibrate model</string>
1441
- </property>
1442
- <property name="checked">
1443
- <bool>true</bool>
1444
- </property>
1445
- </widget>
1446
- </item>
1447
- <item>
1448
- <spacer name="horizontalSpacer_7">
1449
- <property name="orientation">
1450
- <enum>Qt::Orientation::Horizontal</enum>
1451
- </property>
1452
- <property name="sizeType">
1453
- <enum>QSizePolicy::Policy::Preferred</enum>
1454
- </property>
1455
- <property name="sizeHint" stdset="0">
1456
- <size>
1457
- <width>10</width>
1458
- <height>10</height>
1459
- </size>
1460
- </property>
1461
- </spacer>
1462
- </item>
1463
- <item>
1464
- <widget class="QLabel" name="calibrate_label">
1465
- <property name="sizePolicy">
1466
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
1467
- <horstretch>0</horstretch>
1468
- <verstretch>0</verstretch>
1469
- </sizepolicy>
1470
- </property>
1471
- <property name="text">
1472
- <string>Calibration set size:</string>
1473
- </property>
1474
- </widget>
1475
- </item>
1476
- <item>
1477
- <widget class="QSpinBox" name="calibration_spinbox">
1478
- <property name="sizePolicy">
1479
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
1480
- <horstretch>0</horstretch>
1481
- <verstretch>0</verstretch>
1482
- </sizepolicy>
1483
- </property>
1484
- <property name="suffix">
1485
- <string>%</string>
1486
- </property>
1487
- <property name="minimum">
1488
- <number>10</number>
1489
- </property>
1490
- <property name="maximum">
1491
- <number>50</number>
1492
- </property>
1493
- <property name="value">
1494
- <number>15</number>
1495
- </property>
1496
- </widget>
1497
- </item>
1498
- <item>
1499
- <spacer name="horizontalSpacer_78">
1500
- <property name="orientation">
1501
- <enum>Qt::Orientation::Horizontal</enum>
1502
- </property>
1503
- <property name="sizeType">
1504
- <enum>QSizePolicy::Policy::Preferred</enum>
1505
- </property>
1506
- <property name="sizeHint" stdset="0">
1507
- <size>
1508
- <width>10</width>
1509
- <height>10</height>
1510
- </size>
1511
- </property>
1512
- </spacer>
1513
- </item>
1514
1463
  </layout>
1515
1464
  </item>
1516
1465
  </layout>
@@ -4612,6 +4561,43 @@ Each brain state has several attributes:
4612
4561
  </item>
4613
4562
  </layout>
4614
4563
  </item>
4564
+ <item>
4565
+ <layout class="QHBoxLayout" name="horizontalLayout_79">
4566
+ <item>
4567
+ <layout class="QHBoxLayout" name="horizontalLayout_85">
4568
+ <item>
4569
+ <widget class="QCheckBox" name="delete_image_box">
4570
+ <property name="sizePolicy">
4571
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
4572
+ <horstretch>0</horstretch>
4573
+ <verstretch>0</verstretch>
4574
+ </sizepolicy>
4575
+ </property>
4576
+ <property name="text">
4577
+ <string>Delete images after training</string>
4578
+ </property>
4579
+ <property name="checked">
4580
+ <bool>true</bool>
4581
+ </property>
4582
+ </widget>
4583
+ </item>
4584
+ </layout>
4585
+ </item>
4586
+ <item>
4587
+ <spacer name="horizontalSpacer_93">
4588
+ <property name="orientation">
4589
+ <enum>Qt::Orientation::Horizontal</enum>
4590
+ </property>
4591
+ <property name="sizeHint" stdset="0">
4592
+ <size>
4593
+ <width>40</width>
4594
+ <height>20</height>
4595
+ </size>
4596
+ </property>
4597
+ </spacer>
4598
+ </item>
4599
+ </layout>
4600
+ </item>
4615
4601
  <item>
4616
4602
  <layout class="QHBoxLayout" name="horizontalLayout_74">
4617
4603
  <item>
@@ -4682,7 +4668,9 @@ Each brain state has several attributes:
4682
4668
  - Batch size: number of examples in each training iteration
4683
4669
  - Learning rate: step size for adjusting model weights
4684
4670
  - Momentum: amount of past gradients to use in the current update. Typically between 0.6 and 0.99
4685
- - Epochs: number of passes over the training data</string>
4671
+ - Epochs: number of passes over the training data
4672
+
4673
+ You can also choose whether to delete images created by the training process once it's finished. This is recommended unless you are debugging the training code.</string>
4686
4674
  </property>
4687
4675
  <property name="textFormat">
4688
4676
  <enum>Qt::TextFormat::MarkdownText</enum>
@@ -9,6 +9,7 @@ from PySide6.QtWidgets import QCheckBox, QDoubleSpinBox, QLineEdit
9
9
  from accusleepy.brain_state_set import BrainState, BrainStateSet
10
10
  from accusleepy.constants import (
11
11
  DEFAULT_BATCH_SIZE,
12
+ DEFAULT_DELETE_TRAINING_IMAGES_STATE,
12
13
  DEFAULT_EMG_BP_LOWER,
13
14
  DEFAULT_EMG_BP_UPPER,
14
15
  DEFAULT_EMG_FILTER_ORDER,
@@ -55,6 +56,7 @@ class SettingsWidget(QObject):
55
56
  self._hyperparameters = config.hyperparameters
56
57
  self._default_epochs_to_show = config.epochs_to_show
57
58
  self._default_autoscroll_state = config.autoscroll_state
59
+ self._delete_training_images = config.delete_training_images
58
60
 
59
61
  # Store default values for main tab settings (used to populate Settings tab UI)
60
62
  self._default_epoch_length = config.default_epoch_length
@@ -81,6 +83,11 @@ class SettingsWidget(QObject):
81
83
  """Model training hyperparameters"""
82
84
  return self._hyperparameters
83
85
 
86
+ @property
87
+ def delete_training_images(self) -> bool:
88
+ """Whether to delete images after training"""
89
+ return self._delete_training_images
90
+
84
91
  @property
85
92
  def default_epochs_to_show(self) -> int:
86
93
  """Default number of epochs to show in manual scoring"""
@@ -186,6 +193,7 @@ class SettingsWidget(QObject):
186
193
  self._ui.learning_rate_spinbox.setValue(self._hyperparameters.learning_rate)
187
194
  self._ui.momentum_spinbox.setValue(self._hyperparameters.momentum)
188
195
  self._ui.training_epochs_spinbox.setValue(self._hyperparameters.training_epochs)
196
+ self._ui.delete_image_box.setChecked(self._delete_training_images)
189
197
  # Brain states
190
198
  states = {b.digit: b for b in self._brain_state_set.brain_states}
191
199
  for digit in range(10):
@@ -233,6 +241,7 @@ class SettingsWidget(QObject):
233
241
  self._ui.training_epochs_spinbox.valueChanged.connect(
234
242
  self._hyperparameters_changed
235
243
  )
244
+ self._ui.delete_image_box.stateChanged.connect(self._hyperparameters_changed)
236
245
  for digit in range(10):
237
246
  state = self._settings_widgets[digit]
238
247
  state.enabled_widget.stateChanged.connect(
@@ -302,6 +311,7 @@ class SettingsWidget(QObject):
302
311
  momentum=self._ui.momentum_spinbox.value(),
303
312
  training_epochs=self._ui.training_epochs_spinbox.value(),
304
313
  )
314
+ self._delete_training_images = self._ui.delete_image_box.isChecked()
305
315
  self._ui.save_config_status.setText("")
306
316
 
307
317
  def reset_status_message(self, _new_value=None) -> None:
@@ -392,6 +402,7 @@ class SettingsWidget(QObject):
392
402
  hyperparameters=self._hyperparameters,
393
403
  epochs_to_show=self._ui.epochs_to_show_spinbox.value(),
394
404
  autoscroll_state=self._ui.autoscroll_checkbox.isChecked(),
405
+ delete_training_images=self._ui.delete_image_box.isChecked(),
395
406
  )
396
407
  self._ui.save_config_status.setText("configuration saved")
397
408
 
@@ -407,3 +418,4 @@ class SettingsWidget(QObject):
407
418
  self._ui.learning_rate_spinbox.setValue(DEFAULT_LEARNING_RATE)
408
419
  self._ui.momentum_spinbox.setValue(DEFAULT_MOMENTUM)
409
420
  self._ui.training_epochs_spinbox.setValue(DEFAULT_TRAINING_EPOCHS)
421
+ self._ui.delete_image_box.setChecked(DEFAULT_DELETE_TRAINING_IMAGES_STATE)
@@ -125,25 +125,28 @@ To train a new model on your own data:
125
125
  recording. Calibration files are not required.
126
126
  2. Click the "Model training" tab
127
127
  3. Choose the number of epochs to consider when scoring each epoch.
128
- This will be the "width" of the training images. For "default"
129
- type models, this must be an odd number. In general, about 30
130
- seconds worth of data is enough.
131
- 4. Choose whether the images used to train the model should be
132
- deleted once training is complete. It's generally best to
133
- leave this box checked. A (temporary) folder for these files
134
- will be created in the same location as the trained model.
135
- 5. Choose whether to create a "default" or "real-time"-type model.
136
- Note that scoring recordings in the primary interface requires
137
- a default-type model.
138
- 6. Choose whether to calibrate the model. This process uses part
128
+ This will be the width, in pixels, of the training images.
129
+ For "default" type models, this must be an odd number. Generally,
130
+ about 30 seconds worth of data is enough.
131
+ 4. Choose whether to create a "default" or "real-time"-type model.
132
+ Scoring recordings in the AccuSleePy interface requires a
133
+ default-type model. Only select real-time if you plan to create
134
+ your own scoring function. An example real-time scoring function
135
+ can be found in `classification.py`.
136
+ 5. Choose whether to calibrate the model. This process uses part
139
137
  of the training data to make the model's confidence scores
140
138
  more accurately reflect the probability that the output
141
139
  labels are accurate. If using calibration, choose what percent
142
140
  of the training data to set aside for calibration.
143
- 7. Click "Train classification model" and enter a
141
+ 6. Click "Train classification model" and enter a
144
142
  filename for the trained model. Training can take some time.
145
143
  The terminal will display progress updates.
146
144
 
145
+ As part of the training process, a temporary folder of images will
146
+ be created in the location where the model will be saved. This folder
147
+ is deleted by default, but if you want to keep it for debugging
148
+ purposes, you can change this behavior in the "Settings" tab.
149
+
147
150
  ## 4C. Automatic scoring
148
151
 
149
152
  Instructions for automatic scoring using this interface are below.
@@ -2,6 +2,7 @@
2
2
 
3
3
  Keyboard shortcuts:
4
4
  - Mouse click on the upper 3 plots: jump to epoch
5
+ - Mouse scroll on the upper 3 plots: zoom in/out along the x-axis
5
6
  - Ctrl + S: save labels to file
6
7
  - Right / left arrow: move one epoch forward / backward in time
7
8
  - Numbers 0-9: set current epoch to this brain state
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "accusleepy"
3
- version = "0.9.2"
3
+ version = "0.10.0"
4
4
  description = "Python implementation of AccuSleep"
5
5
  authors = [
6
6
  {name = "Zeke Barger",email = "zekebarger@gmail.com"}
@@ -20,12 +20,12 @@ dependencies = [
20
20
  "fastparquet (>=2024.11.0,<2025.0.0)",
21
21
  "pre-commit (>=4.2.0,<5.0.0)",
22
22
  "tqdm (>=4.67.1,<5.0.0)",
23
- "toml (>=0.10.2,<0.11.0)",
24
23
  "pyside6 (>=6.9.0, <6.9.3)",
25
24
  ]
26
25
 
27
26
  [tool.poetry.group.dev.dependencies]
28
27
  pytest = ">=8.3.5,<9.0.0"
28
+ pytest-qt = ">=4.4.0,<5.0.0"
29
29
  ruff = ">=0.11.2,<0.12.0"
30
30
 
31
31
  [build-system]
@@ -34,3 +34,9 @@ build-backend = "poetry.core.masonry.api"
34
34
 
35
35
  [tool.ruff]
36
36
  extend-exclude = ["*.ipynb"]
37
+
38
+ [tool.pytest.ini_options]
39
+ markers = [
40
+ "gui: marks tests as GUI tests (may require display or xvfb)",
41
+ ]
42
+ qt_api = "pyside6"