accusleepy 0.1.2__py3-none-any.whl → 0.3.1__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
@@ -40,6 +40,7 @@ from accusleepy.fileio import (
40
40
  save_model,
41
41
  save_recording_list,
42
42
  )
43
+ from accusleepy.gui.text.main_guide_text import MAIN_GUIDE_TEXT
43
44
  from accusleepy.gui.manual_scoring import ManualScoringWindow
44
45
  from accusleepy.gui.primary_window import Ui_PrimaryWindow
45
46
  from accusleepy.signal_processing import (
@@ -52,8 +53,7 @@ from accusleepy.signal_processing import (
52
53
  # max number of messages to display
53
54
  MESSAGE_BOX_MAX_DEPTH = 50
54
55
  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"
56
+ # relative path to config guide txt file
57
57
  CONFIG_GUIDE_FILE = "text/config_guide.txt"
58
58
 
59
59
 
@@ -80,12 +80,12 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
80
80
  self.setWindowTitle("AccuSleePy")
81
81
 
82
82
  # fill in settings tab
83
- self.brain_state_set = load_config()
83
+ self.brain_state_set, self.epoch_length = load_config()
84
84
  self.settings_widgets = None
85
85
  self.initialize_settings_tab()
86
86
 
87
87
  # initialize info about the recordings, classification data / settings
88
- self.epoch_length = 0
88
+ self.ui.epoch_length_input.setValue(self.epoch_length)
89
89
  self.model = None
90
90
  self.only_overwrite_undefined = False
91
91
  self.min_bout_length = 5
@@ -290,23 +290,28 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
290
290
  if not model_filename:
291
291
  self.show_message("Model training canceled, no filename given")
292
292
 
293
- # create image folder
294
- if os.path.exists(self.training_image_dir):
293
+ # create (probably temporary) image folder
294
+ temp_image_dir = os.path.join(
295
+ self.training_image_dir,
296
+ "images_" + datetime.datetime.now().strftime("%Y%m%d%H%M"),
297
+ )
298
+
299
+ if os.path.exists(temp_image_dir): # unlikely
295
300
  self.show_message(
296
301
  "Warning: training image folder exists, will be overwritten"
297
302
  )
298
- os.makedirs(self.training_image_dir, exist_ok=True)
303
+ os.makedirs(temp_image_dir, exist_ok=True)
299
304
 
300
305
  # create training images
301
306
  self.show_message(
302
- (f"Creating training images in {self.training_image_dir}, please wait...")
307
+ (f"Creating training images in {temp_image_dir}, please wait...")
303
308
  )
304
309
  self.ui.message_area.repaint()
305
310
  QtWidgets.QApplication.processEvents()
306
311
  print("Creating training images")
307
312
  failed_recordings = create_training_images(
308
313
  recordings=self.recordings,
309
- output_path=self.training_image_dir,
314
+ output_path=temp_image_dir,
310
315
  epoch_length=self.epoch_length,
311
316
  epochs_per_img=self.training_epochs_per_img,
312
317
  brain_state_set=self.brain_state_set,
@@ -330,10 +335,8 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
330
335
  QtWidgets.QApplication.processEvents()
331
336
  print("Training model")
332
337
  model = train_model(
333
- annotations_file=os.path.join(
334
- self.training_image_dir, ANNOTATIONS_FILENAME
335
- ),
336
- img_dir=self.training_image_dir,
338
+ annotations_file=os.path.join(temp_image_dir, ANNOTATIONS_FILENAME),
339
+ img_dir=temp_image_dir,
337
340
  mixture_weights=self.brain_state_set.mixture_weights,
338
341
  n_classes=self.brain_state_set.n_classes,
339
342
  )
@@ -350,20 +353,18 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
350
353
 
351
354
  # optionally delete images
352
355
  if self.delete_training_images:
353
- shutil.rmtree(self.training_image_dir)
356
+ shutil.rmtree(temp_image_dir)
354
357
 
355
358
  self.show_message(f"Training complete, saved model to {model_filename}")
356
359
 
357
- def set_training_folder(self):
360
+ def set_training_folder(self) -> None:
361
+ """Select location in which to create a folder for training images"""
358
362
  training_folder_parent = QtWidgets.QFileDialog.getExistingDirectory(
359
363
  self, "Select directory for training images"
360
364
  )
361
365
  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)
366
+ self.training_image_dir = training_folder_parent
367
+ self.ui.image_folder_label.setText(training_folder_parent)
367
368
 
368
369
  def update_image_deletion(self) -> None:
369
370
  """Update choice of whether to delete images after training"""
@@ -1004,15 +1005,8 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
1004
1005
 
1005
1006
  def show_user_manual(self) -> None:
1006
1007
  """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
1008
  label_widget = QtWidgets.QLabel()
1015
- label_widget.setText(user_manual_text)
1009
+ label_widget.setText(MAIN_GUIDE_TEXT)
1016
1010
  scroll_area = QtWidgets.QScrollArea()
1017
1011
  scroll_area.setStyleSheet("background-color: white;")
1018
1012
  scroll_area.setWidget(label_widget)
@@ -1110,6 +1104,7 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
1110
1104
  }
1111
1105
 
1112
1106
  # update widget state to display current config
1107
+ self.ui.default_epoch_input.setValue(self.epoch_length)
1113
1108
  states = {b.digit: b for b in self.brain_state_set.brain_states}
1114
1109
  for digit in range(10):
1115
1110
  if digit in states.keys():
@@ -1247,7 +1242,7 @@ class AccuSleepWindow(QtWidgets.QMainWindow):
1247
1242
  self.brain_state_set = BrainStateSet(brain_states, UNDEFINED_LABEL)
1248
1243
 
1249
1244
  # save to file
1250
- save_config(self.brain_state_set)
1245
+ save_config(self.brain_state_set, self.ui.default_epoch_input.value())
1251
1246
  self.ui.save_config_status.setText("configuration saved")
1252
1247
 
1253
1248
 
@@ -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
@@ -8,8 +8,20 @@
8
8
  ## WARNING! All changes made in this file will be lost when recompiling UI file!
9
9
  ################################################################################
10
10
 
11
- from PySide6.QtCore import QCoreApplication, QMetaObject, QRect, QSize, Qt
12
- from PySide6.QtGui import QBrush, QColor, QFont, QIcon, QPalette
11
+ from PySide6.QtCore import (
12
+ QCoreApplication,
13
+ QMetaObject,
14
+ QRect,
15
+ QSize,
16
+ Qt,
17
+ )
18
+ from PySide6.QtGui import (
19
+ QBrush,
20
+ QColor,
21
+ QFont,
22
+ QIcon,
23
+ QPalette,
24
+ )
13
25
  from PySide6.QtWidgets import (
14
26
  QCheckBox,
15
27
  QDoubleSpinBox,
@@ -100,6 +112,11 @@ class Ui_PrimaryWindow(object):
100
112
  self.epoch_length_input.sizePolicy().hasHeightForWidth()
101
113
  )
102
114
  self.epoch_length_input.setSizePolicy(sizePolicy1)
115
+ self.epoch_length_input.setAlignment(
116
+ Qt.AlignmentFlag.AlignLeading
117
+ | Qt.AlignmentFlag.AlignLeft
118
+ | Qt.AlignmentFlag.AlignVCenter
119
+ )
103
120
  self.epoch_length_input.setMaximum(100000.000000000000000)
104
121
  self.epoch_length_input.setSingleStep(0.500000000000000)
105
122
 
@@ -293,6 +310,11 @@ class Ui_PrimaryWindow(object):
293
310
  self.sampling_rate_input.sizePolicy().hasHeightForWidth()
294
311
  )
295
312
  self.sampling_rate_input.setSizePolicy(sizePolicy1)
313
+ self.sampling_rate_input.setAlignment(
314
+ Qt.AlignmentFlag.AlignLeading
315
+ | Qt.AlignmentFlag.AlignLeft
316
+ | Qt.AlignmentFlag.AlignVCenter
317
+ )
296
318
  self.sampling_rate_input.setMinimum(0.000000000000000)
297
319
  self.sampling_rate_input.setMaximum(100000.000000000000000)
298
320
 
@@ -608,6 +630,11 @@ class Ui_PrimaryWindow(object):
608
630
  self.bout_length_input.sizePolicy().hasHeightForWidth()
609
631
  )
610
632
  self.bout_length_input.setSizePolicy(sizePolicy1)
633
+ self.bout_length_input.setAlignment(
634
+ Qt.AlignmentFlag.AlignLeading
635
+ | Qt.AlignmentFlag.AlignLeft
636
+ | Qt.AlignmentFlag.AlignVCenter
637
+ )
611
638
  self.bout_length_input.setDecimals(2)
612
639
  self.bout_length_input.setMaximum(1000.000000000000000)
613
640
  self.bout_length_input.setValue(5.000000000000000)
@@ -2044,6 +2071,44 @@ class Ui_PrimaryWindow(object):
2044
2071
 
2045
2072
  self.verticalLayout_6.addLayout(self.horizontalLayout_4)
2046
2073
 
2074
+ self.default_epoch_layout = QHBoxLayout()
2075
+ self.default_epoch_layout.setObjectName("default_epoch_layout")
2076
+ self.horizontalSpacer_71 = QSpacerItem(
2077
+ 10, 10, QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Minimum
2078
+ )
2079
+
2080
+ self.default_epoch_layout.addItem(self.horizontalSpacer_71)
2081
+
2082
+ self.horizontalLayout_60 = QHBoxLayout()
2083
+ self.horizontalLayout_60.setObjectName("horizontalLayout_60")
2084
+ self.label_17 = QLabel(self.settings_tab)
2085
+ self.label_17.setObjectName("label_17")
2086
+ sizePolicy1.setHeightForWidth(self.label_17.sizePolicy().hasHeightForWidth())
2087
+ self.label_17.setSizePolicy(sizePolicy1)
2088
+
2089
+ self.horizontalLayout_60.addWidget(self.label_17)
2090
+
2091
+ self.default_epoch_input = QDoubleSpinBox(self.settings_tab)
2092
+ self.default_epoch_input.setObjectName("default_epoch_input")
2093
+ sizePolicy1.setHeightForWidth(
2094
+ self.default_epoch_input.sizePolicy().hasHeightForWidth()
2095
+ )
2096
+ self.default_epoch_input.setSizePolicy(sizePolicy1)
2097
+ self.default_epoch_input.setMaximum(100000.000000000000000)
2098
+ self.default_epoch_input.setSingleStep(0.500000000000000)
2099
+
2100
+ self.horizontalLayout_60.addWidget(self.default_epoch_input)
2101
+
2102
+ self.default_epoch_layout.addLayout(self.horizontalLayout_60)
2103
+
2104
+ self.horizontalSpacer_70 = QSpacerItem(
2105
+ 10, 10, QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Minimum
2106
+ )
2107
+
2108
+ self.default_epoch_layout.addItem(self.horizontalSpacer_70)
2109
+
2110
+ self.verticalLayout_6.addLayout(self.default_epoch_layout)
2111
+
2047
2112
  self.horizontalLayout_18 = QHBoxLayout()
2048
2113
  self.horizontalLayout_18.setSpacing(10)
2049
2114
  self.horizontalLayout_18.setObjectName("horizontalLayout_18")
@@ -2079,6 +2144,7 @@ class Ui_PrimaryWindow(object):
2079
2144
  self.verticalLayout_6.addLayout(self.horizontalLayout_18)
2080
2145
 
2081
2146
  self.verticalLayout_6.setStretch(0, 2)
2147
+ self.verticalLayout_6.setStretch(1, 2)
2082
2148
  self.verticalLayout_6.setStretch(2, 2)
2083
2149
  self.verticalLayout_6.setStretch(3, 2)
2084
2150
  self.verticalLayout_6.setStretch(4, 2)
@@ -2088,7 +2154,8 @@ class Ui_PrimaryWindow(object):
2088
2154
  self.verticalLayout_6.setStretch(8, 2)
2089
2155
  self.verticalLayout_6.setStretch(9, 2)
2090
2156
  self.verticalLayout_6.setStretch(10, 2)
2091
- self.verticalLayout_6.setStretch(11, 3)
2157
+ self.verticalLayout_6.setStretch(11, 2)
2158
+ self.verticalLayout_6.setStretch(12, 3)
2092
2159
 
2093
2160
  self.settings_tab_layout.addLayout(self.verticalLayout_6, 0, 0, 1, 1)
2094
2161
 
@@ -2352,6 +2419,9 @@ class Ui_PrimaryWindow(object):
2352
2419
  self.label_3.setText(QCoreApplication.translate("PrimaryWindow", "0", None))
2353
2420
  self.enable_state_0.setText("")
2354
2421
  self.state_scored_0.setText("")
2422
+ self.label_17.setText(
2423
+ QCoreApplication.translate("PrimaryWindow", "Default epoch length:", None)
2424
+ )
2355
2425
  self.save_config_button.setText(
2356
2426
  QCoreApplication.translate("PrimaryWindow", "Save", None)
2357
2427
  )
@@ -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>
@@ -512,6 +515,9 @@ color: rgb(244, 195, 68);</string>
512
515
  <verstretch>0</verstretch>
513
516
  </sizepolicy>
514
517
  </property>
518
+ <property name="alignment">
519
+ <set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
520
+ </property>
515
521
  <property name="minimum">
516
522
  <double>0.000000000000000</double>
517
523
  </property>
@@ -982,6 +988,9 @@ color: rgb(244, 195, 68);</string>
982
988
  <verstretch>0</verstretch>
983
989
  </sizepolicy>
984
990
  </property>
991
+ <property name="alignment">
992
+ <set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
993
+ </property>
985
994
  <property name="decimals">
986
995
  <number>2</number>
987
996
  </property>
@@ -1465,7 +1474,7 @@ color: rgb(244, 195, 68);</string>
1465
1474
  </layout>
1466
1475
  </item>
1467
1476
  <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">
1477
+ <layout class="QVBoxLayout" name="verticalLayout_6" stretch="2,2,2,2,2,2,2,2,2,2,2,2,3">
1469
1478
  <item>
1470
1479
  <layout class="QHBoxLayout" name="horizontalLayout_3" stretch="3,3,4,3,4">
1471
1480
  <property name="spacing">
@@ -3446,6 +3455,75 @@ color: rgb(244, 195, 68);</string>
3446
3455
  </item>
3447
3456
  </layout>
3448
3457
  </item>
3458
+ <item>
3459
+ <layout class="QHBoxLayout" name="default_epoch_layout">
3460
+ <item>
3461
+ <spacer name="horizontalSpacer_71">
3462
+ <property name="orientation">
3463
+ <enum>Qt::Orientation::Horizontal</enum>
3464
+ </property>
3465
+ <property name="sizeType">
3466
+ <enum>QSizePolicy::Policy::Preferred</enum>
3467
+ </property>
3468
+ <property name="sizeHint" stdset="0">
3469
+ <size>
3470
+ <width>10</width>
3471
+ <height>10</height>
3472
+ </size>
3473
+ </property>
3474
+ </spacer>
3475
+ </item>
3476
+ <item>
3477
+ <layout class="QHBoxLayout" name="horizontalLayout_60">
3478
+ <item>
3479
+ <widget class="QLabel" name="label_17">
3480
+ <property name="sizePolicy">
3481
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
3482
+ <horstretch>0</horstretch>
3483
+ <verstretch>0</verstretch>
3484
+ </sizepolicy>
3485
+ </property>
3486
+ <property name="text">
3487
+ <string>Default epoch length:</string>
3488
+ </property>
3489
+ </widget>
3490
+ </item>
3491
+ <item>
3492
+ <widget class="QDoubleSpinBox" name="default_epoch_input">
3493
+ <property name="sizePolicy">
3494
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
3495
+ <horstretch>0</horstretch>
3496
+ <verstretch>0</verstretch>
3497
+ </sizepolicy>
3498
+ </property>
3499
+ <property name="maximum">
3500
+ <double>100000.000000000000000</double>
3501
+ </property>
3502
+ <property name="singleStep">
3503
+ <double>0.500000000000000</double>
3504
+ </property>
3505
+ </widget>
3506
+ </item>
3507
+ </layout>
3508
+ </item>
3509
+ <item>
3510
+ <spacer name="horizontalSpacer_70">
3511
+ <property name="orientation">
3512
+ <enum>Qt::Orientation::Horizontal</enum>
3513
+ </property>
3514
+ <property name="sizeType">
3515
+ <enum>QSizePolicy::Policy::Preferred</enum>
3516
+ </property>
3517
+ <property name="sizeHint" stdset="0">
3518
+ <size>
3519
+ <width>10</width>
3520
+ <height>10</height>
3521
+ </size>
3522
+ </property>
3523
+ </spacer>
3524
+ </item>
3525
+ </layout>
3526
+ </item>
3449
3527
  <item>
3450
3528
  <layout class="QHBoxLayout" name="horizontalLayout_18" stretch="6,1,7">
3451
3529
  <property name="spacing">
@@ -1,5 +1,6 @@
1
1
  This is the current brain state configuration.
2
- If you change it, click 'Save' to store the changes.
2
+ You can also choose the default epoch length.
3
+ If you make changes, click 'Save' to store them.
3
4
 
4
5
  Each brain state has several attributes:
5
6
 
@@ -1,3 +1,6 @@
1
+ from accusleepy.constants import BRAIN_STATE_COL, EEG_COL, EMG_COL, UNDEFINED_LABEL
2
+
3
+ MAIN_GUIDE_TEXT = f"""
1
4
  Section 1: Overview of the GUI
2
5
  Section 2: AccuSleePy file types
3
6
  Section 3: Manually assigning brain state labels
@@ -41,17 +44,16 @@ To select a file in the primary interface, you can either use the
41
44
  associated button, or drag/drop the file into the empty box adjacent
42
45
  to the button.
43
46
  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.
47
+ data and one column of EMG data. The column names must be
48
+ {EEG_COL} and {EMG_COL}.
49
+ Label file: a .csv file with one column titled {BRAIN_STATE_COL}
50
+ with entries that are either the undefined brain state, {UNDEFINED_LABEL},
51
+ or one of the digits in your brain state configuration.
52
+ By default, these are 1-3 where REM = 1, wake = 2, NREM = 3.
51
53
  Calibration data file: required for automatic labeling. See Section 4
52
- for details.
54
+ for details. These have .csv format.
53
55
  Trained classification model: required for automatic labeling. See
54
- Section 4 for details.
56
+ Section 4 for details. These have .pth format.
55
57
 
56
58
  -----------------------------------------------------------------------
57
59
  Section 3: Manually assigning brain state labels
@@ -75,6 +77,12 @@ Section 4: Automatically scoring recordings with a classification model
75
77
  Automatic brain state scoring requires the inputs described in
76
78
  Section 3, as well as calibration data files and a trained classifier.
77
79
  If you already have all of these files, proceed to Section 4C.
80
+ Models trained on the AccuSleep dataset are provided at
81
+ https://osf.io/py5eb under /python_format/models/ for epoch lengths of
82
+ 2.5, 4, 5, and 10 seconds. These models are the "default" type, in that
83
+ they use several epochs of data before and after any given epoch when
84
+ scoring that epoch. (The other model type, called "real-time", only
85
+ uses data from the current epoch and several preceding epochs.)
78
86
 
79
87
  --- Section 4A: Creating calibration data files ---
80
88
  Each recording must have a calibration file assigned to it.
@@ -97,12 +105,6 @@ To create a calibration data file:
97
105
  selected recording.
98
106
 
99
107
  --- 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
108
  To train a new model on your own data:
107
109
  1. Add your scored recordings to the recording list. Make sure the
108
110
  sampling rate, recording file, and label file are set for each
@@ -113,11 +115,15 @@ To train a new model on your own data:
113
115
  type models, this must be an odd number. In general, about 30
114
116
  seconds worth of data is enough.
115
117
  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
118
+ deleted once training is complete. (It's generally best to
119
+ leave this box checked.)
120
+ 5. Choose whether to create a "default" or "real-time"-type model.
121
+ Note that scoring recordings in the GUI requires a default-type
122
+ model.
123
+ 6. Select a directory where the training images will be saved. A
118
124
  new directory with an automatically generated name will be
119
125
  created inside the directory you choose.
120
- 6. Click the "Train classification model" button and enter a
126
+ 7. Click the "Train classification model" button and enter a
121
127
  filename for the trained model. Training can take some time.
122
128
 
123
129
  --- Section 4C: Automatic labeling ---
@@ -127,8 +133,8 @@ Instructions for automatic labeling using this GUI are below.
127
133
  for each recording. See section 4A for instructions on creating
128
134
  calibration files.
129
135
  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.
136
+ model. It's important that the epoch length used when training this
137
+ model is the same as the current epoch length.
132
138
  4. If you wish to preserve any existing labels in the label file, and
133
139
  only overwrite undefined epochs, check the box labeled
134
140
  'Only overwrite undefined epochs'.
@@ -140,3 +146,4 @@ Instructions for automatic labeling using this GUI are below.
140
146
  recording list. Labels will be saved to the file specified by
141
147
  the 'Select or create label file' field of each recording. You can
142
148
  click 'Score manually' to visualize the results.
149
+ """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: accusleepy
3
- Version: 0.1.2
3
+ Version: 0.3.1
4
4
  Summary: Python implementation of AccuSleep
5
5
  License: GPL-3.0-only
6
6
  Author: Zeke Barger
@@ -30,16 +30,19 @@ Description-Content-Type: text/markdown
30
30
  ## Description
31
31
 
32
32
  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
33
+ sleep using EEG and EMG recordings. It offers several improvements over the original MATLAB version
34
+ and is the only version that will be actively maintained.
35
+
36
+ If you use AccuSleep in your research, please cite our
34
37
  [publication](https://journals.plos.org/plosone/article?id=10.1371/journal.pone.0224642):
35
38
 
36
39
  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
40
 
38
- The data used for training and testing AccuSleep are available at https://osf.io/py5eb/
41
+ The data and models associated with AccuSleep are available at https://osf.io/py5eb/
39
42
 
40
43
  Please contact zekebarger (at) gmail (dot) com with any questions or comments about the software.
41
44
 
42
- ## Installation instructions
45
+ ## Installation
43
46
 
44
47
  - (recommended) create a new virtual environment (using
45
48
  [venv](https://docs.python.org/3/library/venv.html),
@@ -47,10 +50,11 @@ Please contact zekebarger (at) gmail (dot) com with any questions or comments ab
47
50
  etc.) using python >=3.10,<3.13
48
51
  - (optional) if you have a CUDA device and want to speed up model training, [install PyTorch](https://pytorch.org/)
49
52
  - `pip install accusleepy`
53
+ - (optional) download a classification model from https://osf.io/py5eb/ under /python_format/models/
50
54
 
51
55
  ## Usage
52
56
 
53
- `python -m accusleepy`
57
+ `python -m accusleepy` will open the primary interface.
54
58
 
55
59
  ## Acknowledgements
56
60
 
@@ -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=IGhSD9vSTO09H72Ou1icbujQfzCy1twTCdeR2Dv8I7s,53055
21
+ accusleepy/gui/manual_scoring.py,sha256=Jfu7uxXk6W4mmq81h4LnLSArP735l0XvbuZq9yYG650,40686
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=eK9yrU6PK5hX1RORjc7DvqCDuDt55iA1LylXp3oh0j8,101850
24
+ accusleepy/gui/primary_window.ui,sha256=NqCSnvlC1mKrWNn034u8avpMF7WudlR8G5jLjFNdTmk,144812
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=wz2QtRgd1eXUZMmtfAIVgqvDOtjkOnTOK-IGkLALg4U,1073
28
+ accusleepy/gui/text/main_guide_text.py,sha256=YP-2sexvU5VIeKc1FXHsG0cUCgGfcU4qLD59JRlOMMo,8038
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.3.1.dist-info/METADATA,sha256=xgwUR8pWzqQp3fi_g6UXCVbvxrL3CdVahoq476bIEaQ,2729
36
+ accusleepy-0.3.1.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
37
+ accusleepy-0.3.1.dist-info/RECORD,,