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