accusleepy 0.1.2__py3-none-any.whl → 0.4.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.
accusleepy/config.json CHANGED
@@ -18,5 +18,6 @@
18
18
  "is_scored": true,
19
19
  "frequency": 0.55
20
20
  }
21
- ]
22
- }
21
+ ],
22
+ "default_epoch_length": 2.5
23
+ }
accusleepy/constants.py CHANGED
@@ -35,3 +35,5 @@ LABEL_COL = "label"
35
35
  # recording list file header:
36
36
  RECORDING_LIST_NAME = "recording_list"
37
37
  RECORDING_LIST_FILE_TYPE = ".json"
38
+ # key for default epoch length in config
39
+ DEFAULT_EPOCH_LENGTH_KEY = "default_epoch_length"
accusleepy/fileio.py CHANGED
@@ -11,6 +11,7 @@ from accusleepy.brain_state_set import BRAIN_STATES_KEY, BrainState, BrainStateS
11
11
  from accusleepy.constants import (
12
12
  BRAIN_STATE_COL,
13
13
  CONFIG_FILE,
14
+ DEFAULT_EPOCH_LENGTH_KEY,
14
15
  EEG_COL,
15
16
  EMG_COL,
16
17
  MIXTURE_MEAN_COL,
@@ -82,7 +83,9 @@ def load_model(filename: str) -> tuple[SSANN, int | float, int, str, dict]:
82
83
  (default or real-time), set of brain state options
83
84
  used when training the model
84
85
  """
85
- state_dict = torch.load(filename, weights_only=True)
86
+ state_dict = torch.load(
87
+ filename, weights_only=True, map_location=torch.device("cpu")
88
+ )
86
89
  epoch_length = state_dict.pop("epoch_length")
87
90
  epochs_per_img = state_dict.pop("epochs_per_img")
88
91
  model_type = state_dict.pop("model_type")
@@ -141,10 +144,10 @@ def save_labels(labels: np.array, filename: str) -> None:
141
144
  pd.DataFrame({BRAIN_STATE_COL: labels}).to_csv(filename, index=False)
142
145
 
143
146
 
144
- def load_config() -> BrainStateSet:
147
+ def load_config() -> tuple[BrainStateSet, int | float]:
145
148
  """Load configuration file with brain state options
146
149
 
147
- :return: set of brain state options
150
+ :return: set of brain state options and default epoch length
148
151
  """
149
152
  with open(
150
153
  os.path.join(os.path.dirname(os.path.abspath(__file__)), CONFIG_FILE), "r"
@@ -152,18 +155,23 @@ def load_config() -> BrainStateSet:
152
155
  data = json.load(f)
153
156
  return BrainStateSet(
154
157
  [BrainState(**b) for b in data[BRAIN_STATES_KEY]], UNDEFINED_LABEL
155
- )
158
+ ), data[DEFAULT_EPOCH_LENGTH_KEY]
156
159
 
157
160
 
158
- def save_config(brain_state_set: BrainStateSet) -> None:
161
+ def save_config(
162
+ brain_state_set: BrainStateSet, default_epoch_length: int | float
163
+ ) -> None:
159
164
  """Save configuration of brain state options to json file
160
165
 
161
166
  :param brain_state_set: set of brain state options
167
+ :param default_epoch_length: epoch length to use when the GUI starts
162
168
  """
169
+ output_dict = brain_state_set.to_output_dict()
170
+ output_dict.update({DEFAULT_EPOCH_LENGTH_KEY: default_epoch_length})
163
171
  with open(
164
172
  os.path.join(os.path.dirname(os.path.abspath(__file__)), CONFIG_FILE), "w"
165
173
  ) as f:
166
- json.dump(brain_state_set.to_output_dict(), f, indent=4)
174
+ json.dump(output_dict, f, indent=4)
167
175
 
168
176
 
169
177
  def load_recording_list(filename: str) -> list[Recording]:
accusleepy/gui/main.py CHANGED
@@ -5,6 +5,7 @@ import datetime
5
5
  import os
6
6
  import shutil
7
7
  import sys
8
+ import toml
8
9
  from dataclasses import dataclass
9
10
  from functools import partial
10
11
 
@@ -40,6 +41,7 @@ from accusleepy.fileio import (
40
41
  save_model,
41
42
  save_recording_list,
42
43
  )
44
+ from accusleepy.gui.text.main_guide_text import MAIN_GUIDE_TEXT
43
45
  from accusleepy.gui.manual_scoring import ManualScoringWindow
44
46
  from accusleepy.gui.primary_window import Ui_PrimaryWindow
45
47
  from accusleepy.signal_processing import (
@@ -52,9 +54,8 @@ from accusleepy.signal_processing import (
52
54
  # max number of messages to display
53
55
  MESSAGE_BOX_MAX_DEPTH = 50
54
56
  LABEL_LENGTH_ERROR = "label file length does not match recording length"
55
- # relative path to user manual txt file
56
- USER_MANUAL_FILE = "text/main_guide.txt"
57
- CONFIG_GUIDE_FILE = "text/config_guide.txt"
57
+ # relative path to config guide txt file
58
+ CONFIG_GUIDE_FILE = os.path.normpath(r"text/config_guide.txt")
58
59
 
59
60
 
60
61
  @dataclass
@@ -80,12 +81,12 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
80
81
  self.setWindowTitle("AccuSleePy")
81
82
 
82
83
  # fill in settings tab
83
- self.brain_state_set = load_config()
84
+ self.brain_state_set, self.epoch_length = load_config()
84
85
  self.settings_widgets = None
85
86
  self.initialize_settings_tab()
86
87
 
87
88
  # initialize info about the recordings, classification data / settings
88
- self.epoch_length = 0
89
+ self.ui.epoch_length_input.setValue(self.epoch_length)
89
90
  self.model = None
90
91
  self.only_overwrite_undefined = False
91
92
  self.min_bout_length = 5
@@ -116,6 +117,20 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
116
117
  # messages to display
117
118
  self.messages = []
118
119
 
120
+ # display current version
121
+ version = ""
122
+ toml_file = os.path.join(
123
+ os.path.dirname(
124
+ os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
125
+ ),
126
+ "pyproject.toml",
127
+ )
128
+ if os.path.isfile(toml_file):
129
+ toml_data = toml.load(toml_file)
130
+ if "project" in toml_data and "version" in toml_data["project"]:
131
+ version = toml_data["project"]["version"]
132
+ self.ui.version_label.setText(f"v{version}")
133
+
119
134
  # user input: keyboard shortcuts
120
135
  keypress_quit = QtGui.QShortcut(
121
136
  QtGui.QKeySequence(
@@ -179,6 +194,7 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
179
194
  )
180
195
  if not filename:
181
196
  return
197
+ filename = os.path.normpath(filename)
182
198
  save_recording_list(filename=filename, recordings=self.recordings)
183
199
  self.show_message(f"Saved list of recordings to {filename}")
184
200
 
@@ -193,6 +209,7 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
193
209
  if file_dialog.exec():
194
210
  selected_files = file_dialog.selectedFiles()
195
211
  filename = selected_files[0]
212
+ filename = os.path.normpath(filename)
196
213
  else:
197
214
  return
198
215
 
@@ -229,7 +246,7 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
229
246
  if event.type() == QtCore.QEvent.Drop:
230
247
  urls = event.mimeData().urls()
231
248
  if len(urls) == 1:
232
- filename = urls[0].toLocalFile()
249
+ filename = os.path.normpath(urls[0].toLocalFile())
233
250
 
234
251
  if filename is None:
235
252
  return super().eventFilter(obj, event)
@@ -289,24 +306,31 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
289
306
  )
290
307
  if not model_filename:
291
308
  self.show_message("Model training canceled, no filename given")
309
+ return
310
+ model_filename = os.path.normpath(model_filename)
311
+
312
+ # create (probably temporary) image folder
313
+ temp_image_dir = os.path.join(
314
+ self.training_image_dir,
315
+ "images_" + datetime.datetime.now().strftime("%Y%m%d%H%M"),
316
+ )
292
317
 
293
- # create image folder
294
- if os.path.exists(self.training_image_dir):
318
+ if os.path.exists(temp_image_dir): # unlikely
295
319
  self.show_message(
296
320
  "Warning: training image folder exists, will be overwritten"
297
321
  )
298
- os.makedirs(self.training_image_dir, exist_ok=True)
322
+ os.makedirs(temp_image_dir, exist_ok=True)
299
323
 
300
324
  # create training images
301
325
  self.show_message(
302
- (f"Creating training images in {self.training_image_dir}, please wait...")
326
+ (f"Creating training images in {temp_image_dir}, please wait...")
303
327
  )
304
328
  self.ui.message_area.repaint()
305
329
  QtWidgets.QApplication.processEvents()
306
330
  print("Creating training images")
307
331
  failed_recordings = create_training_images(
308
332
  recordings=self.recordings,
309
- output_path=self.training_image_dir,
333
+ output_path=temp_image_dir,
310
334
  epoch_length=self.epoch_length,
311
335
  epochs_per_img=self.training_epochs_per_img,
312
336
  brain_state_set=self.brain_state_set,
@@ -330,10 +354,8 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
330
354
  QtWidgets.QApplication.processEvents()
331
355
  print("Training model")
332
356
  model = train_model(
333
- annotations_file=os.path.join(
334
- self.training_image_dir, ANNOTATIONS_FILENAME
335
- ),
336
- img_dir=self.training_image_dir,
357
+ annotations_file=os.path.join(temp_image_dir, ANNOTATIONS_FILENAME),
358
+ img_dir=temp_image_dir,
337
359
  mixture_weights=self.brain_state_set.mixture_weights,
338
360
  n_classes=self.brain_state_set.n_classes,
339
361
  )
@@ -350,20 +372,19 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
350
372
 
351
373
  # optionally delete images
352
374
  if self.delete_training_images:
353
- shutil.rmtree(self.training_image_dir)
375
+ shutil.rmtree(temp_image_dir)
354
376
 
355
377
  self.show_message(f"Training complete, saved model to {model_filename}")
356
378
 
357
- def set_training_folder(self):
379
+ def set_training_folder(self) -> None:
380
+ """Select location in which to create a folder for training images"""
358
381
  training_folder_parent = QtWidgets.QFileDialog.getExistingDirectory(
359
382
  self, "Select directory for training images"
360
383
  )
361
384
  if training_folder_parent:
362
- self.training_image_dir = os.path.join(
363
- training_folder_parent,
364
- "images_" + datetime.datetime.now().strftime("%Y%m%d%H%M"),
365
- )
366
- self.ui.image_folder_label.setText(self.training_image_dir)
385
+ training_folder_parent = os.path.normpath(training_folder_parent)
386
+ self.training_image_dir = training_folder_parent
387
+ self.ui.image_folder_label.setText(training_folder_parent)
367
388
 
368
389
  def update_image_deletion(self) -> None:
369
390
  """Update choice of whether to delete images after training"""
@@ -559,6 +580,7 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
559
580
  if file_dialog.exec():
560
581
  selected_files = file_dialog.selectedFiles()
561
582
  filename = selected_files[0]
583
+ filename = os.path.normpath(filename)
562
584
  else:
563
585
  return
564
586
 
@@ -706,6 +728,7 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
706
728
  )
707
729
  if not filename:
708
730
  return
731
+ filename = os.path.normpath(filename)
709
732
 
710
733
  create_calibration_file(
711
734
  filename=filename,
@@ -870,6 +893,7 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
870
893
  filter="*" + LABEL_FILE_TYPE,
871
894
  )
872
895
  if filename:
896
+ filename = os.path.normpath(filename)
873
897
  self.recordings[self.recording_index].label_file = filename
874
898
  self.ui.label_file_label.setText(filename)
875
899
 
@@ -884,6 +908,7 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
884
908
  if file_dialog.exec():
885
909
  selected_files = file_dialog.selectedFiles()
886
910
  filename = selected_files[0]
911
+ filename = os.path.normpath(filename)
887
912
  self.recordings[self.recording_index].label_file = filename
888
913
  self.ui.label_file_label.setText(filename)
889
914
 
@@ -898,6 +923,7 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
898
923
  if file_dialog.exec():
899
924
  selected_files = file_dialog.selectedFiles()
900
925
  filename = selected_files[0]
926
+ filename = os.path.normpath(filename)
901
927
  self.recordings[self.recording_index].calibration_file = filename
902
928
  self.ui.calibration_file_label.setText(filename)
903
929
 
@@ -912,6 +938,7 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
912
938
  if file_dialog.exec():
913
939
  selected_files = file_dialog.selectedFiles()
914
940
  filename = selected_files[0]
941
+ filename = os.path.normpath(filename)
915
942
  self.recordings[self.recording_index].recording_file = filename
916
943
  self.ui.recording_file_label.setText(filename)
917
944
 
@@ -1004,15 +1031,8 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
1004
1031
 
1005
1032
  def show_user_manual(self) -> None:
1006
1033
  """Show a popup window with the user manual"""
1007
- user_manual_file = open(
1008
- os.path.join(os.path.dirname(os.path.abspath(__file__)), USER_MANUAL_FILE),
1009
- "r",
1010
- )
1011
- user_manual_text = user_manual_file.read()
1012
- user_manual_file.close()
1013
-
1014
1034
  label_widget = QtWidgets.QLabel()
1015
- label_widget.setText(user_manual_text)
1035
+ label_widget.setText(MAIN_GUIDE_TEXT)
1016
1036
  scroll_area = QtWidgets.QScrollArea()
1017
1037
  scroll_area.setStyleSheet("background-color: white;")
1018
1038
  scroll_area.setWidget(label_widget)
@@ -1110,6 +1130,7 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
1110
1130
  }
1111
1131
 
1112
1132
  # update widget state to display current config
1133
+ self.ui.default_epoch_input.setValue(self.epoch_length)
1113
1134
  states = {b.digit: b for b in self.brain_state_set.brain_states}
1114
1135
  for digit in range(10):
1115
1136
  if digit in states.keys():
@@ -1247,7 +1268,7 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
1247
1268
  self.brain_state_set = BrainStateSet(brain_states, UNDEFINED_LABEL)
1248
1269
 
1249
1270
  # save to file
1250
- save_config(self.brain_state_set)
1271
+ save_config(self.brain_state_set, self.ui.default_epoch_input.value())
1251
1272
  self.ui.save_config_status.setText("configuration saved")
1252
1273
 
1253
1274
 
@@ -1257,7 +1278,7 @@ def check_label_validity(
1257
1278
  sampling_rate: int | float,
1258
1279
  epoch_length: int | float,
1259
1280
  brain_state_set: BrainStateSet,
1260
- ) -> str:
1281
+ ) -> str | None:
1261
1282
  """Check whether a set of brain state labels is valid
1262
1283
 
1263
1284
  This returns an error message if a problem is found with the
@@ -1357,6 +1378,7 @@ def check_config_consistency(
1357
1378
  output.append(
1358
1379
  (
1359
1380
  "Warning: the epoch length used when training this model "
1381
+ f"({model_epoch_length} seconds) "
1360
1382
  "does not match the current epoch length setting."
1361
1383
  )
1362
1384
  )
@@ -27,7 +27,7 @@ LABEL_CMAP = np.concatenate(
27
27
  [np.array([[0, 0, 0, 0]]), plt.colormaps["tab10"](range(10))], axis=0
28
28
  )
29
29
  # relative path to user manual text file
30
- USER_MANUAL_FILE = "text/manual_scoring_guide.txt"
30
+ USER_MANUAL_FILE = os.path.normpath(r"text/manual_scoring_guide.txt")
31
31
 
32
32
  # constants used by callback functions
33
33
  # label formats
@@ -111,7 +111,7 @@ class ManualScoringWindow(QtWidgets.QDialog):
111
111
  self.setWindowTitle("AccuSleePy manual scoring window")
112
112
 
113
113
  # load set of valid brain states
114
- self.brain_state_set = load_config()
114
+ self.brain_state_set, _ = load_config()
115
115
 
116
116
  # initial setting for number of epochs to show in the lower plot
117
117
  self.epochs_to_show = 5
@@ -833,7 +833,7 @@ class ManualScoringWindow(QtWidgets.QDialog):
833
833
  self.adjust_upper_figure_x_limits()
834
834
 
835
835
  # update parts of lower plot
836
- old_window_center = round(self.epochs_to_show / 2) + self.lower_left_epoch
836
+ old_window_center = round((self.epochs_to_show - 1) / 2) + self.lower_left_epoch
837
837
  # change the window bounds if needed
838
838
  if self.epoch < old_window_center and self.lower_left_epoch > 0:
839
839
  self.lower_left_epoch -= 1
@@ -100,6 +100,11 @@ class Ui_PrimaryWindow(object):
100
100
  self.epoch_length_input.sizePolicy().hasHeightForWidth()
101
101
  )
102
102
  self.epoch_length_input.setSizePolicy(sizePolicy1)
103
+ self.epoch_length_input.setAlignment(
104
+ Qt.AlignmentFlag.AlignLeading
105
+ | Qt.AlignmentFlag.AlignLeft
106
+ | Qt.AlignmentFlag.AlignVCenter
107
+ )
103
108
  self.epoch_length_input.setMaximum(100000.000000000000000)
104
109
  self.epoch_length_input.setSingleStep(0.500000000000000)
105
110
 
@@ -185,6 +190,8 @@ class Ui_PrimaryWindow(object):
185
190
 
186
191
  self.left_scoring_vlayout.addWidget(self.recordinglistgroupbox)
187
192
 
193
+ self.logo_and_version_layout = QVBoxLayout()
194
+ self.logo_and_version_layout.setObjectName("logo_and_version_layout")
188
195
  self.logo_layout = QVBoxLayout()
189
196
  self.logo_layout.setObjectName("logo_layout")
190
197
  self.frame = QFrame(self.scoring_tab)
@@ -235,7 +242,24 @@ class Ui_PrimaryWindow(object):
235
242
 
236
243
  self.logo_layout.addWidget(self.frame)
237
244
 
238
- self.left_scoring_vlayout.addLayout(self.logo_layout)
245
+ self.logo_and_version_layout.addLayout(self.logo_layout)
246
+
247
+ self.version_label = QLabel(self.scoring_tab)
248
+ self.version_label.setObjectName("version_label")
249
+ sizePolicy3 = QSizePolicy(
250
+ QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed
251
+ )
252
+ sizePolicy3.setHorizontalStretch(0)
253
+ sizePolicy3.setVerticalStretch(0)
254
+ sizePolicy3.setHeightForWidth(
255
+ self.version_label.sizePolicy().hasHeightForWidth()
256
+ )
257
+ self.version_label.setSizePolicy(sizePolicy3)
258
+ self.version_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
259
+
260
+ self.logo_and_version_layout.addWidget(self.version_label)
261
+
262
+ self.left_scoring_vlayout.addLayout(self.logo_and_version_layout)
239
263
 
240
264
  self.user_manual_layout = QHBoxLayout()
241
265
  self.user_manual_layout.setObjectName("user_manual_layout")
@@ -293,6 +317,11 @@ class Ui_PrimaryWindow(object):
293
317
  self.sampling_rate_input.sizePolicy().hasHeightForWidth()
294
318
  )
295
319
  self.sampling_rate_input.setSizePolicy(sizePolicy1)
320
+ self.sampling_rate_input.setAlignment(
321
+ Qt.AlignmentFlag.AlignLeading
322
+ | Qt.AlignmentFlag.AlignLeft
323
+ | Qt.AlignmentFlag.AlignVCenter
324
+ )
296
325
  self.sampling_rate_input.setMinimum(0.000000000000000)
297
326
  self.sampling_rate_input.setMaximum(100000.000000000000000)
298
327
 
@@ -318,11 +347,6 @@ class Ui_PrimaryWindow(object):
318
347
  self.horizontalLayout.setObjectName("horizontalLayout")
319
348
  self.recording_file_button = QPushButton(self.selected_recording_groupbox)
320
349
  self.recording_file_button.setObjectName("recording_file_button")
321
- sizePolicy3 = QSizePolicy(
322
- QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Fixed
323
- )
324
- sizePolicy3.setHorizontalStretch(0)
325
- sizePolicy3.setVerticalStretch(0)
326
350
  sizePolicy3.setHeightForWidth(
327
351
  self.recording_file_button.sizePolicy().hasHeightForWidth()
328
352
  )
@@ -608,6 +632,11 @@ class Ui_PrimaryWindow(object):
608
632
  self.bout_length_input.sizePolicy().hasHeightForWidth()
609
633
  )
610
634
  self.bout_length_input.setSizePolicy(sizePolicy1)
635
+ self.bout_length_input.setAlignment(
636
+ Qt.AlignmentFlag.AlignLeading
637
+ | Qt.AlignmentFlag.AlignLeft
638
+ | Qt.AlignmentFlag.AlignVCenter
639
+ )
611
640
  self.bout_length_input.setDecimals(2)
612
641
  self.bout_length_input.setMaximum(1000.000000000000000)
613
642
  self.bout_length_input.setValue(5.000000000000000)
@@ -2044,6 +2073,44 @@ class Ui_PrimaryWindow(object):
2044
2073
 
2045
2074
  self.verticalLayout_6.addLayout(self.horizontalLayout_4)
2046
2075
 
2076
+ self.default_epoch_layout = QHBoxLayout()
2077
+ self.default_epoch_layout.setObjectName("default_epoch_layout")
2078
+ self.horizontalSpacer_71 = QSpacerItem(
2079
+ 10, 10, QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Minimum
2080
+ )
2081
+
2082
+ self.default_epoch_layout.addItem(self.horizontalSpacer_71)
2083
+
2084
+ self.horizontalLayout_60 = QHBoxLayout()
2085
+ self.horizontalLayout_60.setObjectName("horizontalLayout_60")
2086
+ self.label_17 = QLabel(self.settings_tab)
2087
+ self.label_17.setObjectName("label_17")
2088
+ sizePolicy1.setHeightForWidth(self.label_17.sizePolicy().hasHeightForWidth())
2089
+ self.label_17.setSizePolicy(sizePolicy1)
2090
+
2091
+ self.horizontalLayout_60.addWidget(self.label_17)
2092
+
2093
+ self.default_epoch_input = QDoubleSpinBox(self.settings_tab)
2094
+ self.default_epoch_input.setObjectName("default_epoch_input")
2095
+ sizePolicy1.setHeightForWidth(
2096
+ self.default_epoch_input.sizePolicy().hasHeightForWidth()
2097
+ )
2098
+ self.default_epoch_input.setSizePolicy(sizePolicy1)
2099
+ self.default_epoch_input.setMaximum(100000.000000000000000)
2100
+ self.default_epoch_input.setSingleStep(0.500000000000000)
2101
+
2102
+ self.horizontalLayout_60.addWidget(self.default_epoch_input)
2103
+
2104
+ self.default_epoch_layout.addLayout(self.horizontalLayout_60)
2105
+
2106
+ self.horizontalSpacer_70 = QSpacerItem(
2107
+ 10, 10, QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Minimum
2108
+ )
2109
+
2110
+ self.default_epoch_layout.addItem(self.horizontalSpacer_70)
2111
+
2112
+ self.verticalLayout_6.addLayout(self.default_epoch_layout)
2113
+
2047
2114
  self.horizontalLayout_18 = QHBoxLayout()
2048
2115
  self.horizontalLayout_18.setSpacing(10)
2049
2116
  self.horizontalLayout_18.setObjectName("horizontalLayout_18")
@@ -2079,6 +2146,7 @@ class Ui_PrimaryWindow(object):
2079
2146
  self.verticalLayout_6.addLayout(self.horizontalLayout_18)
2080
2147
 
2081
2148
  self.verticalLayout_6.setStretch(0, 2)
2149
+ self.verticalLayout_6.setStretch(1, 2)
2082
2150
  self.verticalLayout_6.setStretch(2, 2)
2083
2151
  self.verticalLayout_6.setStretch(3, 2)
2084
2152
  self.verticalLayout_6.setStretch(4, 2)
@@ -2088,7 +2156,8 @@ class Ui_PrimaryWindow(object):
2088
2156
  self.verticalLayout_6.setStretch(8, 2)
2089
2157
  self.verticalLayout_6.setStretch(9, 2)
2090
2158
  self.verticalLayout_6.setStretch(10, 2)
2091
- self.verticalLayout_6.setStretch(11, 3)
2159
+ self.verticalLayout_6.setStretch(11, 2)
2160
+ self.verticalLayout_6.setStretch(12, 3)
2092
2161
 
2093
2162
  self.settings_tab_layout.addLayout(self.verticalLayout_6, 0, 0, 1, 1)
2094
2163
 
@@ -2150,9 +2219,23 @@ class Ui_PrimaryWindow(object):
2150
2219
  self.remove_button.setText(
2151
2220
  QCoreApplication.translate("PrimaryWindow", "remove", None)
2152
2221
  )
2222
+ # if QT_CONFIG(tooltip)
2223
+ self.export_button.setToolTip(
2224
+ QCoreApplication.translate(
2225
+ "PrimaryWindow", "Export recording list to file", None
2226
+ )
2227
+ )
2228
+ # endif // QT_CONFIG(tooltip)
2153
2229
  self.export_button.setText(
2154
2230
  QCoreApplication.translate("PrimaryWindow", "export", None)
2155
2231
  )
2232
+ # if QT_CONFIG(tooltip)
2233
+ self.import_button.setToolTip(
2234
+ QCoreApplication.translate(
2235
+ "PrimaryWindow", "Import recording list from file", None
2236
+ )
2237
+ )
2238
+ # endif // QT_CONFIG(tooltip)
2156
2239
  self.import_button.setText(
2157
2240
  QCoreApplication.translate("PrimaryWindow", "import", None)
2158
2241
  )
@@ -2165,6 +2248,9 @@ class Ui_PrimaryWindow(object):
2165
2248
  self.accusleepy1.setText(
2166
2249
  QCoreApplication.translate("PrimaryWindow", "AccuSleePy", None)
2167
2250
  )
2251
+ self.version_label.setText(
2252
+ QCoreApplication.translate("PrimaryWindow", "TextLabel", None)
2253
+ )
2168
2254
  # if QT_CONFIG(tooltip)
2169
2255
  self.user_manual_button.setToolTip(
2170
2256
  QCoreApplication.translate("PrimaryWindow", "User manual", None)
@@ -2179,6 +2265,13 @@ class Ui_PrimaryWindow(object):
2179
2265
  self.samplingratelabel.setText(
2180
2266
  QCoreApplication.translate("PrimaryWindow", "Sampling rate (Hz):", None)
2181
2267
  )
2268
+ # if QT_CONFIG(tooltip)
2269
+ self.recording_file_button.setToolTip(
2270
+ QCoreApplication.translate(
2271
+ "PrimaryWindow", "Select EEG+EMG recording", None
2272
+ )
2273
+ )
2274
+ # endif // QT_CONFIG(tooltip)
2182
2275
  self.recording_file_button.setText(
2183
2276
  QCoreApplication.translate("PrimaryWindow", "Select recording file", None)
2184
2277
  )
@@ -2221,10 +2314,24 @@ class Ui_PrimaryWindow(object):
2221
2314
  QCoreApplication.translate("PrimaryWindow", "Score manually", None)
2222
2315
  )
2223
2316
  self.manual_scoring_status.setText("")
2317
+ # if QT_CONFIG(tooltip)
2318
+ self.create_calibration_button.setToolTip(
2319
+ QCoreApplication.translate(
2320
+ "PrimaryWindow", "Create calibration file for this subject", None
2321
+ )
2322
+ )
2323
+ # endif // QT_CONFIG(tooltip)
2224
2324
  self.create_calibration_button.setText(
2225
2325
  QCoreApplication.translate("PrimaryWindow", "Create calibration file", None)
2226
2326
  )
2227
2327
  self.calibration_status.setText("")
2328
+ # if QT_CONFIG(tooltip)
2329
+ self.select_calibration_button.setToolTip(
2330
+ QCoreApplication.translate(
2331
+ "PrimaryWindow", "Load calibration file for this recording", None
2332
+ )
2333
+ )
2334
+ # endif // QT_CONFIG(tooltip)
2228
2335
  self.select_calibration_button.setText(
2229
2336
  QCoreApplication.translate("PrimaryWindow", "Select calibration file", None)
2230
2337
  )
@@ -2252,6 +2359,13 @@ class Ui_PrimaryWindow(object):
2252
2359
  "PrimaryWindow", "Minimum bout length (sec):", None
2253
2360
  )
2254
2361
  )
2362
+ # if QT_CONFIG(tooltip)
2363
+ self.load_model_button.setToolTip(
2364
+ QCoreApplication.translate(
2365
+ "PrimaryWindow", "Load a trained sleep scoring classifier", None
2366
+ )
2367
+ )
2368
+ # endif // QT_CONFIG(tooltip)
2255
2369
  self.load_model_button.setText(
2256
2370
  QCoreApplication.translate(
2257
2371
  "PrimaryWindow", "Load classification model", None
@@ -2279,6 +2393,13 @@ class Ui_PrimaryWindow(object):
2279
2393
  self.real_time_button.setText(
2280
2394
  QCoreApplication.translate("PrimaryWindow", "Real-time", None)
2281
2395
  )
2396
+ # if QT_CONFIG(tooltip)
2397
+ self.train_model_button.setToolTip(
2398
+ QCoreApplication.translate(
2399
+ "PrimaryWindow", "Begin training the classification model", None
2400
+ )
2401
+ )
2402
+ # endif // QT_CONFIG(tooltip)
2282
2403
  self.train_model_button.setText(
2283
2404
  QCoreApplication.translate(
2284
2405
  "PrimaryWindow", "Train classification model", None
@@ -2352,6 +2473,16 @@ class Ui_PrimaryWindow(object):
2352
2473
  self.label_3.setText(QCoreApplication.translate("PrimaryWindow", "0", None))
2353
2474
  self.enable_state_0.setText("")
2354
2475
  self.state_scored_0.setText("")
2476
+ self.label_17.setText(
2477
+ QCoreApplication.translate("PrimaryWindow", "Default epoch length:", None)
2478
+ )
2479
+ # if QT_CONFIG(tooltip)
2480
+ self.save_config_button.setToolTip(
2481
+ QCoreApplication.translate(
2482
+ "PrimaryWindow", "Save current configuration", None
2483
+ )
2484
+ )
2485
+ # endif // QT_CONFIG(tooltip)
2355
2486
  self.save_config_button.setText(
2356
2487
  QCoreApplication.translate("PrimaryWindow", "Save", None)
2357
2488
  )
@@ -164,6 +164,9 @@
164
164
  <verstretch>0</verstretch>
165
165
  </sizepolicy>
166
166
  </property>
167
+ <property name="alignment">
168
+ <set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
169
+ </property>
167
170
  <property name="maximum">
168
171
  <double>100000.000000000000000</double>
169
172
  </property>
@@ -260,6 +263,9 @@
260
263
  <verstretch>0</verstretch>
261
264
  </sizepolicy>
262
265
  </property>
266
+ <property name="toolTip">
267
+ <string>Export recording list to file</string>
268
+ </property>
263
269
  <property name="text">
264
270
  <string>export</string>
265
271
  </property>
@@ -273,6 +279,9 @@
273
279
  <verstretch>0</verstretch>
274
280
  </sizepolicy>
275
281
  </property>
282
+ <property name="toolTip">
283
+ <string>Import recording list from file</string>
284
+ </property>
276
285
  <property name="text">
277
286
  <string>import</string>
278
287
  </property>
@@ -284,132 +293,152 @@
284
293
  </widget>
285
294
  </item>
286
295
  <item>
287
- <layout class="QVBoxLayout" name="logo_layout">
296
+ <layout class="QVBoxLayout" name="logo_and_version_layout">
297
+ <item>
298
+ <layout class="QVBoxLayout" name="logo_layout">
299
+ <item>
300
+ <widget class="QFrame" name="frame">
301
+ <property name="sizePolicy">
302
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
303
+ <horstretch>0</horstretch>
304
+ <verstretch>0</verstretch>
305
+ </sizepolicy>
306
+ </property>
307
+ <property name="minimumSize">
308
+ <size>
309
+ <width>180</width>
310
+ <height>80</height>
311
+ </size>
312
+ </property>
313
+ <property name="styleSheet">
314
+ <string notr="true">background-color: transparent;</string>
315
+ </property>
316
+ <property name="frameShape">
317
+ <enum>QFrame::Shape::NoFrame</enum>
318
+ </property>
319
+ <property name="frameShadow">
320
+ <enum>QFrame::Shadow::Raised</enum>
321
+ </property>
322
+ <widget class="QLabel" name="accusleepy2">
323
+ <property name="geometry">
324
+ <rect>
325
+ <x>11</x>
326
+ <y>15</y>
327
+ <width>160</width>
328
+ <height>50</height>
329
+ </rect>
330
+ </property>
331
+ <property name="sizePolicy">
332
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
333
+ <horstretch>0</horstretch>
334
+ <verstretch>0</verstretch>
335
+ </sizepolicy>
336
+ </property>
337
+ <property name="font">
338
+ <font>
339
+ <pointsize>21</pointsize>
340
+ <italic>true</italic>
341
+ <bold>true</bold>
342
+ </font>
343
+ </property>
344
+ <property name="styleSheet">
345
+ <string notr="true">background-color: transparent;
346
+ color: rgb(130, 169, 68);</string>
347
+ </property>
348
+ <property name="text">
349
+ <string>AccuSleePy</string>
350
+ </property>
351
+ <property name="alignment">
352
+ <set>Qt::AlignmentFlag::AlignCenter</set>
353
+ </property>
354
+ </widget>
355
+ <widget class="QLabel" name="accusleepy3">
356
+ <property name="geometry">
357
+ <rect>
358
+ <x>13</x>
359
+ <y>17</y>
360
+ <width>160</width>
361
+ <height>50</height>
362
+ </rect>
363
+ </property>
364
+ <property name="sizePolicy">
365
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
366
+ <horstretch>0</horstretch>
367
+ <verstretch>0</verstretch>
368
+ </sizepolicy>
369
+ </property>
370
+ <property name="font">
371
+ <font>
372
+ <pointsize>21</pointsize>
373
+ <italic>true</italic>
374
+ <bold>true</bold>
375
+ </font>
376
+ </property>
377
+ <property name="styleSheet">
378
+ <string notr="true">background-color: transparent;
379
+ color: rgb(46, 63, 150);</string>
380
+ </property>
381
+ <property name="text">
382
+ <string>AccuSleePy</string>
383
+ </property>
384
+ <property name="alignment">
385
+ <set>Qt::AlignmentFlag::AlignCenter</set>
386
+ </property>
387
+ </widget>
388
+ <widget class="QLabel" name="accusleepy1">
389
+ <property name="geometry">
390
+ <rect>
391
+ <x>9</x>
392
+ <y>13</y>
393
+ <width>160</width>
394
+ <height>50</height>
395
+ </rect>
396
+ </property>
397
+ <property name="sizePolicy">
398
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
399
+ <horstretch>0</horstretch>
400
+ <verstretch>0</verstretch>
401
+ </sizepolicy>
402
+ </property>
403
+ <property name="font">
404
+ <font>
405
+ <pointsize>21</pointsize>
406
+ <italic>true</italic>
407
+ <bold>true</bold>
408
+ </font>
409
+ </property>
410
+ <property name="styleSheet">
411
+ <string notr="true">background-color: transparent;
412
+ color: rgb(244, 195, 68);</string>
413
+ </property>
414
+ <property name="text">
415
+ <string>AccuSleePy</string>
416
+ </property>
417
+ <property name="alignment">
418
+ <set>Qt::AlignmentFlag::AlignCenter</set>
419
+ </property>
420
+ </widget>
421
+ <zorder>accusleepy1</zorder>
422
+ <zorder>accusleepy2</zorder>
423
+ <zorder>accusleepy3</zorder>
424
+ </widget>
425
+ </item>
426
+ </layout>
427
+ </item>
288
428
  <item>
289
- <widget class="QFrame" name="frame">
429
+ <widget class="QLabel" name="version_label">
290
430
  <property name="sizePolicy">
291
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
431
+ <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
292
432
  <horstretch>0</horstretch>
293
433
  <verstretch>0</verstretch>
294
434
  </sizepolicy>
295
435
  </property>
296
- <property name="minimumSize">
297
- <size>
298
- <width>180</width>
299
- <height>80</height>
300
- </size>
301
- </property>
302
- <property name="styleSheet">
303
- <string notr="true">background-color: transparent;</string>
304
- </property>
305
- <property name="frameShape">
306
- <enum>QFrame::Shape::NoFrame</enum>
436
+ <property name="text">
437
+ <string>TextLabel</string>
307
438
  </property>
308
- <property name="frameShadow">
309
- <enum>QFrame::Shadow::Raised</enum>
439
+ <property name="alignment">
440
+ <set>Qt::AlignmentFlag::AlignCenter</set>
310
441
  </property>
311
- <widget class="QLabel" name="accusleepy2">
312
- <property name="geometry">
313
- <rect>
314
- <x>11</x>
315
- <y>15</y>
316
- <width>160</width>
317
- <height>50</height>
318
- </rect>
319
- </property>
320
- <property name="sizePolicy">
321
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
322
- <horstretch>0</horstretch>
323
- <verstretch>0</verstretch>
324
- </sizepolicy>
325
- </property>
326
- <property name="font">
327
- <font>
328
- <pointsize>21</pointsize>
329
- <italic>true</italic>
330
- <bold>true</bold>
331
- </font>
332
- </property>
333
- <property name="styleSheet">
334
- <string notr="true">background-color: transparent;
335
- color: rgb(130, 169, 68);</string>
336
- </property>
337
- <property name="text">
338
- <string>AccuSleePy</string>
339
- </property>
340
- <property name="alignment">
341
- <set>Qt::AlignmentFlag::AlignCenter</set>
342
- </property>
343
- </widget>
344
- <widget class="QLabel" name="accusleepy3">
345
- <property name="geometry">
346
- <rect>
347
- <x>13</x>
348
- <y>17</y>
349
- <width>160</width>
350
- <height>50</height>
351
- </rect>
352
- </property>
353
- <property name="sizePolicy">
354
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
355
- <horstretch>0</horstretch>
356
- <verstretch>0</verstretch>
357
- </sizepolicy>
358
- </property>
359
- <property name="font">
360
- <font>
361
- <pointsize>21</pointsize>
362
- <italic>true</italic>
363
- <bold>true</bold>
364
- </font>
365
- </property>
366
- <property name="styleSheet">
367
- <string notr="true">background-color: transparent;
368
- color: rgb(46, 63, 150);</string>
369
- </property>
370
- <property name="text">
371
- <string>AccuSleePy</string>
372
- </property>
373
- <property name="alignment">
374
- <set>Qt::AlignmentFlag::AlignCenter</set>
375
- </property>
376
- </widget>
377
- <widget class="QLabel" name="accusleepy1">
378
- <property name="geometry">
379
- <rect>
380
- <x>9</x>
381
- <y>13</y>
382
- <width>160</width>
383
- <height>50</height>
384
- </rect>
385
- </property>
386
- <property name="sizePolicy">
387
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
388
- <horstretch>0</horstretch>
389
- <verstretch>0</verstretch>
390
- </sizepolicy>
391
- </property>
392
- <property name="font">
393
- <font>
394
- <pointsize>21</pointsize>
395
- <italic>true</italic>
396
- <bold>true</bold>
397
- </font>
398
- </property>
399
- <property name="styleSheet">
400
- <string notr="true">background-color: transparent;
401
- color: rgb(244, 195, 68);</string>
402
- </property>
403
- <property name="text">
404
- <string>AccuSleePy</string>
405
- </property>
406
- <property name="alignment">
407
- <set>Qt::AlignmentFlag::AlignCenter</set>
408
- </property>
409
- </widget>
410
- <zorder>accusleepy1</zorder>
411
- <zorder>accusleepy2</zorder>
412
- <zorder>accusleepy3</zorder>
413
442
  </widget>
414
443
  </item>
415
444
  </layout>
@@ -512,6 +541,9 @@ color: rgb(244, 195, 68);</string>
512
541
  <verstretch>0</verstretch>
513
542
  </sizepolicy>
514
543
  </property>
544
+ <property name="alignment">
545
+ <set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
546
+ </property>
515
547
  <property name="minimum">
516
548
  <double>0.000000000000000</double>
517
549
  </property>
@@ -556,6 +588,9 @@ color: rgb(244, 195, 68);</string>
556
588
  <verstretch>0</verstretch>
557
589
  </sizepolicy>
558
590
  </property>
591
+ <property name="toolTip">
592
+ <string>Select EEG+EMG recording</string>
593
+ </property>
559
594
  <property name="text">
560
595
  <string>Select recording file</string>
561
596
  </property>
@@ -747,6 +782,9 @@ color: rgb(244, 195, 68);</string>
747
782
  <verstretch>0</verstretch>
748
783
  </sizepolicy>
749
784
  </property>
785
+ <property name="toolTip">
786
+ <string>Create calibration file for this subject</string>
787
+ </property>
750
788
  <property name="text">
751
789
  <string>Create calibration file</string>
752
790
  </property>
@@ -807,6 +845,9 @@ color: rgb(244, 195, 68);</string>
807
845
  <verstretch>0</verstretch>
808
846
  </sizepolicy>
809
847
  </property>
848
+ <property name="toolTip">
849
+ <string>Load calibration file for this recording</string>
850
+ </property>
810
851
  <property name="text">
811
852
  <string>Select calibration file</string>
812
853
  </property>
@@ -982,6 +1023,9 @@ color: rgb(244, 195, 68);</string>
982
1023
  <verstretch>0</verstretch>
983
1024
  </sizepolicy>
984
1025
  </property>
1026
+ <property name="alignment">
1027
+ <set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
1028
+ </property>
985
1029
  <property name="decimals">
986
1030
  <number>2</number>
987
1031
  </property>
@@ -1033,6 +1077,9 @@ color: rgb(244, 195, 68);</string>
1033
1077
  <verstretch>0</verstretch>
1034
1078
  </sizepolicy>
1035
1079
  </property>
1080
+ <property name="toolTip">
1081
+ <string>Load a trained sleep scoring classifier</string>
1082
+ </property>
1036
1083
  <property name="text">
1037
1084
  <string>Load classification model</string>
1038
1085
  </property>
@@ -1287,6 +1334,9 @@ color: rgb(244, 195, 68);</string>
1287
1334
  <verstretch>0</verstretch>
1288
1335
  </sizepolicy>
1289
1336
  </property>
1337
+ <property name="toolTip">
1338
+ <string>Begin training the classification model</string>
1339
+ </property>
1290
1340
  <property name="text">
1291
1341
  <string>Train classification model</string>
1292
1342
  </property>
@@ -1465,7 +1515,7 @@ color: rgb(244, 195, 68);</string>
1465
1515
  </layout>
1466
1516
  </item>
1467
1517
  <item row="0" column="0">
1468
- <layout class="QVBoxLayout" name="verticalLayout_6" stretch="2,0,2,2,2,2,2,2,2,2,2,3">
1518
+ <layout class="QVBoxLayout" name="verticalLayout_6" stretch="2,2,2,2,2,2,2,2,2,2,2,2,3">
1469
1519
  <item>
1470
1520
  <layout class="QHBoxLayout" name="horizontalLayout_3" stretch="3,3,4,3,4">
1471
1521
  <property name="spacing">
@@ -3446,6 +3496,75 @@ color: rgb(244, 195, 68);</string>
3446
3496
  </item>
3447
3497
  </layout>
3448
3498
  </item>
3499
+ <item>
3500
+ <layout class="QHBoxLayout" name="default_epoch_layout">
3501
+ <item>
3502
+ <spacer name="horizontalSpacer_71">
3503
+ <property name="orientation">
3504
+ <enum>Qt::Orientation::Horizontal</enum>
3505
+ </property>
3506
+ <property name="sizeType">
3507
+ <enum>QSizePolicy::Policy::Preferred</enum>
3508
+ </property>
3509
+ <property name="sizeHint" stdset="0">
3510
+ <size>
3511
+ <width>10</width>
3512
+ <height>10</height>
3513
+ </size>
3514
+ </property>
3515
+ </spacer>
3516
+ </item>
3517
+ <item>
3518
+ <layout class="QHBoxLayout" name="horizontalLayout_60">
3519
+ <item>
3520
+ <widget class="QLabel" name="label_17">
3521
+ <property name="sizePolicy">
3522
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
3523
+ <horstretch>0</horstretch>
3524
+ <verstretch>0</verstretch>
3525
+ </sizepolicy>
3526
+ </property>
3527
+ <property name="text">
3528
+ <string>Default epoch length:</string>
3529
+ </property>
3530
+ </widget>
3531
+ </item>
3532
+ <item>
3533
+ <widget class="QDoubleSpinBox" name="default_epoch_input">
3534
+ <property name="sizePolicy">
3535
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
3536
+ <horstretch>0</horstretch>
3537
+ <verstretch>0</verstretch>
3538
+ </sizepolicy>
3539
+ </property>
3540
+ <property name="maximum">
3541
+ <double>100000.000000000000000</double>
3542
+ </property>
3543
+ <property name="singleStep">
3544
+ <double>0.500000000000000</double>
3545
+ </property>
3546
+ </widget>
3547
+ </item>
3548
+ </layout>
3549
+ </item>
3550
+ <item>
3551
+ <spacer name="horizontalSpacer_70">
3552
+ <property name="orientation">
3553
+ <enum>Qt::Orientation::Horizontal</enum>
3554
+ </property>
3555
+ <property name="sizeType">
3556
+ <enum>QSizePolicy::Policy::Preferred</enum>
3557
+ </property>
3558
+ <property name="sizeHint" stdset="0">
3559
+ <size>
3560
+ <width>10</width>
3561
+ <height>10</height>
3562
+ </size>
3563
+ </property>
3564
+ </spacer>
3565
+ </item>
3566
+ </layout>
3567
+ </item>
3449
3568
  <item>
3450
3569
  <layout class="QHBoxLayout" name="horizontalLayout_18" stretch="6,1,7">
3451
3570
  <property name="spacing">
@@ -3475,6 +3594,9 @@ color: rgb(244, 195, 68);</string>
3475
3594
  <verstretch>0</verstretch>
3476
3595
  </sizepolicy>
3477
3596
  </property>
3597
+ <property name="toolTip">
3598
+ <string>Save current configuration</string>
3599
+ </property>
3478
3600
  <property name="text">
3479
3601
  <string>Save</string>
3480
3602
  </property>
@@ -1,5 +1,5 @@
1
1
  This is the current brain state configuration.
2
- If you change it, click 'Save' to store the changes.
2
+ If you make changes, click 'Save' to store them.
3
3
 
4
4
  Each brain state has several attributes:
5
5
 
@@ -25,3 +25,5 @@ Important notes:
25
25
  - Changing these settings can invalidate existing label files,
26
26
  calibration files, and trained models!
27
27
  - Reinstalling AccuSleePy will overwrite this configuration.
28
+ - You can also choose the default epoch length that is shown
29
+ when the primary interface starts up.
@@ -1,8 +1,31 @@
1
+ from accusleepy.constants import (
2
+ BRAIN_STATE_COL,
3
+ CALIBRATION_FILE_TYPE,
4
+ EEG_COL,
5
+ EMG_COL,
6
+ LABEL_FILE_TYPE,
7
+ MODEL_FILE_TYPE,
8
+ RECORDING_FILE_TYPES,
9
+ UNDEFINED_LABEL,
10
+ )
11
+
12
+ MAIN_GUIDE_TEXT = f"""
13
+ Section 0: Definitions
1
14
  Section 1: Overview of the GUI
2
15
  Section 2: AccuSleePy file types
3
16
  Section 3: Manually assigning brain state labels
4
17
  Section 4: Automatically assigning brain state labels
5
18
 
19
+ -----------------------------------------------------------------------
20
+ Section 0: Definitions
21
+ -----------------------------------------------------------------------
22
+ Recording: a table containing one channel of EEG data and one channel
23
+ of EMG data collected at a constant sampling rate.
24
+ Epoch: the temporal resolution of brain state scoring. If, for example,
25
+ the epoch length is 5 seconds, then a brain state label will be
26
+ assigned to each 5-second segment of a recording.
27
+ Bout: a contiguous set of epochs with the same brain state.
28
+
6
29
  -----------------------------------------------------------------------
7
30
  Section 1: Overview of the primary interface
8
31
  -----------------------------------------------------------------------
@@ -40,18 +63,17 @@ There are four types of files associated with AccuSleePy.
40
63
  To select a file in the primary interface, you can either use the
41
64
  associated button, or drag/drop the file into the empty box adjacent
42
65
  to the button.
43
- Recording file: a .csv or .parquet file containing one column of EEG
44
- data and one column of EMG data. The column names must match the
45
- ones listed in constants.py (EEG_COL, EMG_COL).
46
- Label file: a .csv file with one column whose title matches
47
- BRAIN_STATE_COL in config.py, with entries that are either the
48
- undefined brain state (UNDEFINED_LABEL) or one of the valid
49
- "digit" attributes of your brain states. By default, these are 1-3
50
- and REM = 1, wake = 2, NREM = 3.
66
+ Recording file: a {" or ".join(RECORDING_FILE_TYPES)} file containing one
67
+ column of EEG data and one column of EMG data.
68
+ The column names must be {EEG_COL} and {EMG_COL}.
69
+ Label file: a {LABEL_FILE_TYPE} file with one column titled {BRAIN_STATE_COL}
70
+ with entries that are either the undefined brain state ({UNDEFINED_LABEL})
71
+ or one of the digits in your brain state configuration.
72
+ By default, these are 1-3 where REM = 1, wake = 2, NREM = 3.
51
73
  Calibration data file: required for automatic labeling. See Section 4
52
- for details.
74
+ for details. These have {CALIBRATION_FILE_TYPE} format.
53
75
  Trained classification model: required for automatic labeling. See
54
- Section 4 for details.
76
+ Section 4 for details. These have {MODEL_FILE_TYPE} format.
55
77
 
56
78
  -----------------------------------------------------------------------
57
79
  Section 3: Manually assigning brain state labels
@@ -75,6 +97,12 @@ Section 4: Automatically scoring recordings with a classification model
75
97
  Automatic brain state scoring requires the inputs described in
76
98
  Section 3, as well as calibration data files and a trained classifier.
77
99
  If you already have all of these files, proceed to Section 4C.
100
+ Models trained on the AccuSleep dataset are provided at
101
+ https://osf.io/py5eb under /python_format/models/ for epoch lengths of
102
+ 2.5, 4, 5, and 10 seconds. These models are the "default" type, in that
103
+ they use several epochs of data before and after any given epoch when
104
+ scoring that epoch. (The other model type, called "real-time", only
105
+ uses data from the current epoch and several preceding epochs.)
78
106
 
79
107
  --- Section 4A: Creating calibration data files ---
80
108
  Each recording must have a calibration file assigned to it.
@@ -95,14 +123,12 @@ To create a calibration data file:
95
123
  4. Enter a filename for the calibration data file.
96
124
  5. The calibration file will automatically be assigned to the currently
97
125
  selected recording.
126
+ Note that epoch length can affect the calibration process. If you make
127
+ a calibration file for a subject using one epoch length, but want to
128
+ score another recording from the same subject with a different epoch
129
+ length, it's best to create a new calibration file.
98
130
 
99
131
  --- Section 4B: Training your own classification model ---
100
- Pre-trained classification models are provided with AccuSleePy for
101
- epoch lengths of 2.5, 4, 5, and 10 seconds. These models are the
102
- "default" type, in that they use several epochs of data before and
103
- after any given epoch when scoring that epoch. (The other model type,
104
- called "real-time", only uses data from the current epoch and several
105
- preceding epochs.
106
132
  To train a new model on your own data:
107
133
  1. Add your scored recordings to the recording list. Make sure the
108
134
  sampling rate, recording file, and label file are set for each
@@ -113,11 +139,15 @@ To train a new model on your own data:
113
139
  type models, this must be an odd number. In general, about 30
114
140
  seconds worth of data is enough.
115
141
  4. Choose whether the images used to train the model should be
116
- deleted once training is complete.
117
- 5. Select a directory where the training images will be saved. A
142
+ deleted once training is complete. (It's generally best to
143
+ leave this box checked.)
144
+ 5. Choose whether to create a "default" or "real-time"-type model.
145
+ Note that scoring recordings in the GUI requires a default-type
146
+ model.
147
+ 6. Select a directory where the training images will be saved. A
118
148
  new directory with an automatically generated name will be
119
149
  created inside the directory you choose.
120
- 6. Click the "Train classification model" button and enter a
150
+ 7. Click the "Train classification model" button and enter a
121
151
  filename for the trained model. Training can take some time.
122
152
 
123
153
  --- Section 4C: Automatic labeling ---
@@ -127,8 +157,8 @@ Instructions for automatic labeling using this GUI are below.
127
157
  for each recording. See section 4A for instructions on creating
128
158
  calibration files.
129
159
  3. Click 'Load classification model' to load the trained classification
130
- model. The epoch length used when training this network should be
131
- the same as the current epoch length.
160
+ model. It's important that the epoch length used when training this
161
+ model is the same as the current epoch length.
132
162
  4. If you wish to preserve any existing labels in the label file, and
133
163
  only overwrite undefined epochs, check the box labeled
134
164
  'Only overwrite undefined epochs'.
@@ -140,3 +170,4 @@ Instructions for automatic labeling using this GUI are below.
140
170
  recording list. Labels will be saved to the file specified by
141
171
  the 'Select or create label file' field of each recording. You can
142
172
  click 'Score manually' to visualize the results.
173
+ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: accusleepy
3
- Version: 0.1.2
3
+ Version: 0.4.0
4
4
  Summary: Python implementation of AccuSleep
5
5
  License: GPL-3.0-only
6
6
  Author: Zeke Barger
@@ -20,6 +20,7 @@ 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.7.1,<6.8.0)
22
22
  Requires-Dist: scipy (>=1.15.2,<2.0.0)
23
+ Requires-Dist: toml (>=0.10.2,<0.11.0)
23
24
  Requires-Dist: torch (>=2.6.0,<3.0.0)
24
25
  Requires-Dist: torchvision (>=0.21.0,<0.22.0)
25
26
  Requires-Dist: tqdm (>=4.67.1,<5.0.0)
@@ -30,16 +31,19 @@ Description-Content-Type: text/markdown
30
31
  ## Description
31
32
 
32
33
  AccuSleePy is a python implementation of AccuSleep--a set of graphical user interfaces for scoring rodent
33
- sleep using EEG and EMG recordings. If you use AccuSleep in your research, please cite our
34
+ sleep using EEG and EMG recordings. It offers several improvements over the original MATLAB version
35
+ and is the only version that will be actively maintained.
36
+
37
+ If you use AccuSleep in your research, please cite our
34
38
  [publication](https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0224642):
35
39
 
36
40
  Barger, Z., Frye, C. G., Liu, D., Dan, Y., & Bouchard, K. E. (2019). Robust, automated sleep scoring by a compact neural network with distributional shift correction. *PLOS ONE, 14*(12), 1–18.
37
41
 
38
- The data used for training and testing AccuSleep are available at https://osf.io/py5eb/
42
+ The data and models associated with AccuSleep are available at https://osf.io/py5eb/
39
43
 
40
44
  Please contact zekebarger (at) gmail (dot) com with any questions or comments about the software.
41
45
 
42
- ## Installation instructions
46
+ ## Installation
43
47
 
44
48
  - (recommended) create a new virtual environment (using
45
49
  [venv](https://docs.python.org/3/library/venv.html),
@@ -47,10 +51,11 @@ Please contact zekebarger (at) gmail (dot) com with any questions or comments ab
47
51
  etc.) using python >=3.10,<3.13
48
52
  - (optional) if you have a CUDA device and want to speed up model training, [install PyTorch](https://pytorch.org/)
49
53
  - `pip install accusleepy`
54
+ - (optional) download a classification model from https://osf.io/py5eb/ under /python_format/models/
50
55
 
51
56
  ## Usage
52
57
 
53
- `python -m accusleepy`
58
+ `python -m accusleepy` will open the primary interface.
54
59
 
55
60
  ## Acknowledgements
56
61
 
@@ -2,9 +2,9 @@ accusleepy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  accusleepy/__main__.py,sha256=dKzl2N2Hg9lD264CWYNxThRyDKzWwyMwHRXmJxOmMis,104
3
3
  accusleepy/brain_state_set.py,sha256=fRkrArHLIbEKimub804yt_mUXoyfsjJEfiJnTjeCMkY,3233
4
4
  accusleepy/classification.py,sha256=xrmPyMHlzYh0QfNCID1PRIYEIyNkWduOi7g1Bdb6xfg,8573
5
- accusleepy/config.json,sha256=GZV0d1E44NEj-P7X5GKjbxbjjYKWUtXAm29a3vGABVA,430
6
- accusleepy/constants.py,sha256=ceRcWmhec5sATiAsIynf3XFKl0H2IsT_JDfeg7xYbqg,1189
7
- accusleepy/fileio.py,sha256=ojuzDkcJkXok9dny75g8LDieKeMT7P2SFVLyJek6Nd8,6011
5
+ accusleepy/config.json,sha256=F76WRLarMEW38BBMPwFlQ_d7Dur-ptqYmW8BxqnQF4A,464
6
+ accusleepy/constants.py,sha256=PnsPANggyIfMfd6OCR-kNztFOTybUEhMnPeibu5_eEU,1280
7
+ accusleepy/fileio.py,sha256=S5pf_hE-btJPMbrTplKLaQTULSJQoOJ-56LBH79Uz3I,6383
8
8
  accusleepy/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  accusleepy/gui/icons/brightness_down.png,sha256=PLT1fb83RHIhSRuU7MMMx0G7oJAY7o9wUcnqM8veZfM,12432
10
10
  accusleepy/gui/icons/brightness_up.png,sha256=64GnUqgPvN5xZ6Um3wOzwqvUmdAWYZT6eFmWpBsHyks,12989
@@ -17,21 +17,21 @@ accusleepy/gui/icons/save.png,sha256=J3EA8iU1BqLYRSsrq_OdoZlqrv2yfL7oV54DklTy_DI
17
17
  accusleepy/gui/icons/up_arrow.png,sha256=V9yF9t1WgjPaUu-mF1YGe_DfaRHg2dUpR_sUVVcvVvY,3329
18
18
  accusleepy/gui/icons/zoom_in.png,sha256=MFWnKZp7Rvh4bLPq4Cqo4sB_jQYedUUtT8-ZO8tNYyc,13589
19
19
  accusleepy/gui/icons/zoom_out.png,sha256=IB8Jecb3i0U4qjWRR46ridjLpvLCSe7PozBaLqQqYSw,13055
20
- accusleepy/gui/main.py,sha256=8W2UvYwEvVXNS88Z5MoAOnyj5jmsRG-c_lXk58WnI98,53049
21
- accusleepy/gui/manual_scoring.py,sha256=Ce-X4A7pm3Hb0PtiqebF59rpSEMcHhWWhoO-L7rYtTE,40677
20
+ accusleepy/gui/main.py,sha256=r6NWxwWkkkOOeXDfKggrGA6d8d3RS0M4Ylf2HhtnoPk,54234
21
+ accusleepy/gui/manual_scoring.py,sha256=RaOpFso9whXib3MfiZblGJoTS-_dehBnSA-rtvO0qBo,40705
22
22
  accusleepy/gui/mplwidget.py,sha256=f9O3u_96whQGUwpi3o_QGc7yjiETX5vE0oj3ePXTJWE,12279
23
- accusleepy/gui/primary_window.py,sha256=1CsHrX9jenL_22imlnI4CDsNUYVCfrDwI2lR4tapcgU,99422
24
- accusleepy/gui/primary_window.ui,sha256=r4d3g7CRC2DZof9FjR12lid1kxWP3wb5JpPuPm0ijY0,141515
23
+ accusleepy/gui/primary_window.py,sha256=RXpDvcb7zy8Ea4Da1VhMG1T6GC54KW3vyGjqqQJN45k,104582
24
+ accusleepy/gui/primary_window.ui,sha256=09k4xFcjgOL9mlhFlg6mXCc_tgj4_FY9CZ3H03e3z3A,147074
25
25
  accusleepy/gui/resources.qrc,sha256=ByNEmJqr0YbKBqoZGvONZtjyNYr4ST4enO6TEdYSqWg,802
26
26
  accusleepy/gui/resources_rc.py,sha256=Z2e34h30U4snJjnYdZVV9B6yjATKxxfvgTRt5uXtQdo,329727
27
- accusleepy/gui/text/config_guide.txt,sha256=4JL9rPd8yr6aYHborzx5c1GQlq8r0_sckINzqmNoNjw,1031
28
- accusleepy/gui/text/main_guide.txt,sha256=bIDL8SQKK88MhVqmgKYQWygR4oSfS-CsKNuUUOi2hzU,7684
27
+ accusleepy/gui/text/config_guide.txt,sha256=7xyo5ifQjWgXmCdgHuei0DCsXZcU4vo5VRlpOb2KVs4,1130
28
+ accusleepy/gui/text/main_guide_text.py,sha256=4WpPYpuXCX811roitKuEI68dWIBNFrNrMyiKO2u8xyY,9050
29
29
  accusleepy/gui/text/manual_scoring_guide.txt,sha256=onBnUZJyX18oN1CgjD2HSnlEQHsUscHpOYf11kTKZ4U,1460
30
30
  accusleepy/gui/viewer_window.py,sha256=5PkbuYMXUegH1CExCoqSGDZ9GeJqCCUz0-3WWkM8Vfc,24049
31
31
  accusleepy/gui/viewer_window.ui,sha256=D1LwUFR-kZ_GWGZFFtXvGJdFWghLrOWZTblQeLQt9kI,30525
32
32
  accusleepy/models.py,sha256=Muapsw088AUHqRIbW97Rkbv0oiwCtQvO9tEoBCC-MYg,1476
33
33
  accusleepy/multitaper.py,sha256=V6MJDk0OSWhg2MFhrnt9dvYrHiNsk2T7IxAA7paZVyE,25549
34
34
  accusleepy/signal_processing.py,sha256=-aXnywfp1LBsk3DcbIMmZlgv3f8j6sZ6js0bizZId0o,21718
35
- accusleepy-0.1.2.dist-info/METADATA,sha256=UiI9nm7G9U9v5m-UqHA8Uy035XNZh2Ud84ygcV-W9Oc,2488
36
- accusleepy-0.1.2.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
37
- accusleepy-0.1.2.dist-info/RECORD,,
35
+ accusleepy-0.4.0.dist-info/METADATA,sha256=QRIDk41bldqfRDcdCoQCVnVk4GkcwHSpV1cQm8dmRdI,2768
36
+ accusleepy-0.4.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
37
+ accusleepy-0.4.0.dist-info/RECORD,,