accusleepy 0.1.2__tar.gz → 0.4.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 (37) hide show
  1. {accusleepy-0.1.2 → accusleepy-0.4.0}/PKG-INFO +10 -5
  2. {accusleepy-0.1.2 → accusleepy-0.4.0}/README.md +8 -4
  3. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/config.json +3 -2
  4. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/constants.py +2 -0
  5. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/fileio.py +14 -6
  6. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/main.py +54 -32
  7. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/manual_scoring.py +3 -3
  8. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/primary_window.py +138 -7
  9. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/primary_window.ui +241 -119
  10. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/text/config_guide.txt +3 -1
  11. accusleepy-0.1.2/accusleepy/gui/text/main_guide.txt → accusleepy-0.4.0/accusleepy/gui/text/main_guide_text.py +52 -21
  12. {accusleepy-0.1.2 → accusleepy-0.4.0}/pyproject.toml +2 -1
  13. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/__init__.py +0 -0
  14. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/__main__.py +0 -0
  15. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/brain_state_set.py +0 -0
  16. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/classification.py +0 -0
  17. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/__init__.py +0 -0
  18. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/icons/brightness_down.png +0 -0
  19. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/icons/brightness_up.png +0 -0
  20. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/icons/double_down_arrow.png +0 -0
  21. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/icons/double_up_arrow.png +0 -0
  22. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/icons/down_arrow.png +0 -0
  23. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/icons/home.png +0 -0
  24. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/icons/question.png +0 -0
  25. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/icons/save.png +0 -0
  26. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/icons/up_arrow.png +0 -0
  27. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/icons/zoom_in.png +0 -0
  28. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/icons/zoom_out.png +0 -0
  29. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/mplwidget.py +0 -0
  30. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/resources.qrc +0 -0
  31. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/resources_rc.py +0 -0
  32. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/text/manual_scoring_guide.txt +0 -0
  33. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/viewer_window.py +0 -0
  34. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/gui/viewer_window.ui +0 -0
  35. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/models.py +0 -0
  36. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/multitaper.py +0 -0
  37. {accusleepy-0.1.2 → accusleepy-0.4.0}/accusleepy/signal_processing.py +0 -0
@@ -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
 
@@ -3,16 +3,19 @@
3
3
  ## Description
4
4
 
5
5
  AccuSleePy is a python implementation of AccuSleep--a set of graphical user interfaces for scoring rodent
6
- sleep using EEG and EMG recordings. If you use AccuSleep in your research, please cite our
6
+ sleep using EEG and EMG recordings. It offers several improvements over the original MATLAB version
7
+ and is the only version that will be actively maintained.
8
+
9
+ If you use AccuSleep in your research, please cite our
7
10
  [publication](https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0224642):
8
11
 
9
12
  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.
10
13
 
11
- The data used for training and testing AccuSleep are available at https://osf.io/py5eb/
14
+ The data and models associated with AccuSleep are available at https://osf.io/py5eb/
12
15
 
13
16
  Please contact zekebarger (at) gmail (dot) com with any questions or comments about the software.
14
17
 
15
- ## Installation instructions
18
+ ## Installation
16
19
 
17
20
  - (recommended) create a new virtual environment (using
18
21
  [venv](https://docs.python.org/3/library/venv.html),
@@ -20,10 +23,11 @@ Please contact zekebarger (at) gmail (dot) com with any questions or comments ab
20
23
  etc.) using python >=3.10,<3.13
21
24
  - (optional) if you have a CUDA device and want to speed up model training, [install PyTorch](https://pytorch.org/)
22
25
  - `pip install accusleepy`
26
+ - (optional) download a classification model from https://osf.io/py5eb/ under /python_format/models/
23
27
 
24
28
  ## Usage
25
29
 
26
- `python -m accusleepy`
30
+ `python -m accusleepy` will open the primary interface.
27
31
 
28
32
  ## Acknowledgements
29
33
 
@@ -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
+ }
@@ -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"
@@ -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]:
@@ -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