accusleepy 0.5.0__py3-none-any.whl → 0.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,7 +32,7 @@ from PySide6.QtWidgets import (
32
32
  )
33
33
 
34
34
  from accusleepy.constants import UNDEFINED_LABEL
35
- from accusleepy.fileio import load_config, save_labels
35
+ from accusleepy.fileio import load_config, save_labels, EMGFilter
36
36
  from accusleepy.gui.mplwidget import resample_x_ticks
37
37
  from accusleepy.gui.viewer_window import Ui_ViewerWindow
38
38
  from accusleepy.signal_processing import create_spectrogram, get_emg_power
@@ -99,8 +99,10 @@ class ManualScoringWindow(QDialog):
99
99
  emg: np.array,
100
100
  label_file: str,
101
101
  labels: np.array,
102
+ confidence_scores: np.array,
102
103
  sampling_rate: int | float,
103
104
  epoch_length: int | float,
105
+ emg_filter: EMGFilter,
104
106
  ):
105
107
  """Initialize the manual scoring window
106
108
 
@@ -108,8 +110,10 @@ class ManualScoringWindow(QDialog):
108
110
  :param emg: EMG signal
109
111
  :param label_file: filename for labels
110
112
  :param labels: brain state labels
113
+ :param confidence_scores: confidence scores
111
114
  :param sampling_rate: sampling rate, in Hz
112
115
  :param epoch_length: epoch length, in seconds
116
+ :param emg_filter: EMG filter parameters
113
117
  """
114
118
  super(ManualScoringWindow, self).__init__()
115
119
 
@@ -117,8 +121,10 @@ class ManualScoringWindow(QDialog):
117
121
  self.eeg = eeg
118
122
  self.emg = emg
119
123
  self.labels = labels
124
+ self.confidence_scores = confidence_scores
120
125
  self.sampling_rate = sampling_rate
121
126
  self.epoch_length = epoch_length
127
+ self.emg_filter = emg_filter
122
128
 
123
129
  self.n_epochs = len(self.labels)
124
130
 
@@ -128,7 +134,7 @@ class ManualScoringWindow(QDialog):
128
134
  self.setWindowTitle("AccuSleePy manual scoring window")
129
135
 
130
136
  # load set of valid brain states
131
- self.brain_state_set, _ = load_config()
137
+ self.brain_state_set, _, _, _, _, _, _ = load_config()
132
138
 
133
139
  # initial setting for number of epochs to show in the lower plot
134
140
  self.epochs_to_show = 5
@@ -150,7 +156,7 @@ class ManualScoringWindow(QDialog):
150
156
 
151
157
  # calculate RMS of EMG for each epoch and apply a ceiling
152
158
  self.upper_emg = create_upper_emg_signal(
153
- self.emg, self.sampling_rate, self.epoch_length
159
+ self.emg, self.sampling_rate, self.epoch_length, self.emg_filter
154
160
  )
155
161
 
156
162
  # center and scale the EEG and EMG signals to fit the display
@@ -161,6 +167,8 @@ class ManualScoringWindow(QDialog):
161
167
  self.label_img = create_label_img(
162
168
  self.display_labels, self.label_display_options
163
169
  )
170
+ # same sort of thing for confidence scores
171
+ self.confidence_img = create_confidence_img(self.confidence_scores)
164
172
 
165
173
  # history of changes to the brain state labels
166
174
  self.history = list()
@@ -172,6 +180,8 @@ class ManualScoringWindow(QDialog):
172
180
  self.ui.upperfigure.setup_upper_figure(
173
181
  self.n_epochs,
174
182
  self.label_img,
183
+ self.confidence_scores,
184
+ self.confidence_img,
175
185
  spectrogram,
176
186
  spectrogram_frequencies,
177
187
  self.upper_emg,
@@ -702,11 +712,11 @@ class ManualScoringWindow(QDialog):
702
712
 
703
713
  def adjust_upper_figure_x_limits(self) -> None:
704
714
  """Update the x-axis limits of the upper figure subplots"""
705
- for i in [0, 1, 3]:
715
+ for i in [0, 1, 2, 4]:
706
716
  self.ui.upperfigure.canvas.axes[i].set_xlim(
707
717
  (self.upper_left_epoch - 0.5, self.upper_right_epoch + 0.5)
708
718
  )
709
- self.ui.upperfigure.canvas.axes[2].set_xlim(
719
+ self.ui.upperfigure.canvas.axes[3].set_xlim(
710
720
  (self.upper_left_epoch, self.upper_right_epoch + 1)
711
721
  )
712
722
 
@@ -949,7 +959,7 @@ class ManualScoringWindow(QDialog):
949
959
  # if it's on the spectrogram, we have to adjust it slightly
950
960
  # since that uses a different x-axis range
951
961
  ax_index = self.ui.upperfigure.canvas.axes.index(event.inaxes)
952
- if ax_index == 2:
962
+ if ax_index == 3:
953
963
  x -= 0.5
954
964
 
955
965
  # get the "zoom level" so we can preserve that
@@ -1040,22 +1050,42 @@ def create_label_img(labels: np.array, label_display_options: np.array) -> np.ar
1040
1050
  return label_img
1041
1051
 
1042
1052
 
1053
+ def create_confidence_img(confidence_scores: np.array) -> np.array:
1054
+ """Create an image to display confidence scores
1055
+
1056
+ :param confidence_scores: confidence scores
1057
+ :return: confidence score image
1058
+ """
1059
+ if confidence_scores is None:
1060
+ return None
1061
+
1062
+ confidence_img = np.ones([1, len(confidence_scores), 3])
1063
+ for i, c in enumerate(confidence_scores):
1064
+ confidence_img[0, i, 1:] = c
1065
+ return confidence_img
1066
+
1067
+
1043
1068
  def create_upper_emg_signal(
1044
- emg: np.array, sampling_rate: int | float, epoch_length: int | float
1069
+ emg: np.array,
1070
+ sampling_rate: int | float,
1071
+ epoch_length: int | float,
1072
+ emg_filter: EMGFilter,
1045
1073
  ) -> np.array:
1046
1074
  """Calculate RMS of EMG for each epoch and apply a ceiling
1047
1075
 
1048
1076
  :param emg: EMG signal
1049
1077
  :param sampling_rate: sampling rate, in Hz
1050
1078
  :param epoch_length: epoch length, in seconds
1079
+ :param emg_filter: EMG filter parameters
1051
1080
  :return: processed EMG signal
1052
1081
  """
1053
1082
  emg_rms = get_emg_power(
1054
1083
  emg,
1055
1084
  sampling_rate,
1056
1085
  epoch_length,
1086
+ emg_filter,
1057
1087
  )
1058
- return np.clip(emg_rms, 0, np.mean(emg_rms) + np.std(emg_rms) * 2.5)
1088
+ return np.clip(emg_rms, np.min(emg_rms), np.mean(emg_rms) + np.std(emg_rms) * 2.5)
1059
1089
 
1060
1090
 
1061
1091
  def transform_eeg_emg(eeg: np.array, emg: np.array) -> (np.array, np.array):
@@ -66,6 +66,8 @@ class MplWidget(QtWidgets.QWidget):
66
66
  self,
67
67
  n_epochs: int,
68
68
  label_img: np.array,
69
+ confidence_scores: np.array,
70
+ confidence_img: np.array,
69
71
  spec: np.array,
70
72
  f: np.array,
71
73
  emg: np.array,
@@ -78,6 +80,8 @@ class MplWidget(QtWidgets.QWidget):
78
80
 
79
81
  :param n_epochs: number of epochs in the recording
80
82
  :param label_img: brain state labels, as an image
83
+ :param confidence_scores: confidence scores
84
+ :param confidence_img: confidence scores, as an image
81
85
  :param spec: EEG spectrogram image
82
86
  :param f: EEG spectrogram frequency axis
83
87
  :param emg: EMG RMS per epoch
@@ -90,39 +94,60 @@ class MplWidget(QtWidgets.QWidget):
90
94
  self.upper_marker = list()
91
95
 
92
96
  # subplot layout
93
- height_ratios = [8, 2, 12, 13]
94
- gs1 = GridSpec(4, 1, hspace=0, height_ratios=height_ratios)
95
- gs2 = GridSpec(4, 1, hspace=0.4, height_ratios=height_ratios)
97
+ height_ratios = [2, 8, 2, 12, 13]
98
+ gs1 = GridSpec(5, 1, hspace=0.02, height_ratios=height_ratios)
99
+ gs2 = GridSpec(5, 1, hspace=0.4, height_ratios=height_ratios)
96
100
  axes = list()
97
101
  axes.append(self.canvas.figure.add_subplot(gs1[0]))
98
102
  axes.append(self.canvas.figure.add_subplot(gs1[1]))
99
103
  axes.append(self.canvas.figure.add_subplot(gs1[2]))
100
- axes.append(self.canvas.figure.add_subplot(gs2[3]))
104
+ axes.append(self.canvas.figure.add_subplot(gs1[3]))
105
+ axes.append(self.canvas.figure.add_subplot(gs2[4]))
101
106
 
102
107
  # subplots have different axes limits
103
- for i in [0, 1, 3]:
108
+ for i in [0, 1, 2, 4]:
104
109
  axes[i].set_xlim((-0.5, n_epochs - 0.5))
105
- axes[2].set_xlim(0, n_epochs)
110
+ axes[3].set_xlim(0, n_epochs)
111
+
112
+ # confidence score subplot
113
+ if confidence_scores is None:
114
+ axes[0].set_visible(False)
115
+ else:
116
+ axes[0].set_ylim([-0.5, 0.5])
117
+ axes[0].set_xticks([])
118
+ axes[0].set_yticks([0])
119
+ axes[0].set_yticklabels(["Conf."])
120
+ axes[0].tick_params(axis="y", color="white")
121
+ axes[0].imshow(
122
+ confidence_img, aspect="auto", origin="lower", interpolation="None"
123
+ )
124
+ confidence_x = (
125
+ np.repeat(list(range(len(confidence_scores) + 1)), 2)[1:-1] - 0.5
126
+ )
127
+ confidence_y = np.repeat(confidence_scores, 2) - 0.5
128
+ axes[0].plot(confidence_x, confidence_y, "k", linewidth=0.5)
129
+ for side in ["left", "right", "bottom", "top"]:
130
+ axes[0].spines[side].set_visible(False)
106
131
 
107
132
  # brain state subplot
108
- axes[0].set_ylim(
133
+ axes[1].set_ylim(
109
134
  [-0.5, np.max(label_display_options) - np.min(label_display_options) + 0.5]
110
135
  )
111
- axes[0].set_xticks([])
112
- axes[0].set_yticks(
136
+ axes[1].set_xticks([])
137
+ axes[1].set_yticks(
113
138
  label_display_options - np.min(label_display_options),
114
139
  )
115
- axes[0].set_yticklabels([b.name for b in brain_state_set.brain_states])
116
- ax2 = axes[0].secondary_yaxis("right")
140
+ axes[1].set_yticklabels([b.name for b in brain_state_set.brain_states])
141
+ ax2 = axes[1].secondary_yaxis("right")
117
142
  ax2.set_yticks(
118
143
  label_display_options - np.min(label_display_options),
119
144
  )
120
145
  ax2.set_yticklabels([b.digit for b in brain_state_set.brain_states])
121
- self.label_img_ref = axes[0].imshow(
146
+ self.label_img_ref = axes[1].imshow(
122
147
  label_img, aspect="auto", origin="lower", interpolation="None"
123
148
  )
124
149
  # add patch to dim the display when creating an ROI
125
- self.editing_patch = axes[0].add_patch(
150
+ self.editing_patch = axes[1].add_patch(
126
151
  Rectangle(
127
152
  xy=(-0.5, -0.5),
128
153
  width=n_epochs,
@@ -137,7 +162,7 @@ class MplWidget(QtWidgets.QWidget):
137
162
  )
138
163
  # add the ROI selection widget, but disable it until it's needed
139
164
  self.roi = RectangleSelector(
140
- ax=axes[0],
165
+ ax=axes[1],
141
166
  onselect=roi_function,
142
167
  interactive=False,
143
168
  button=MouseButton(1),
@@ -145,34 +170,34 @@ class MplWidget(QtWidgets.QWidget):
145
170
  self.roi.set_active(False)
146
171
  # keep a reference to the ROI patch so we can change its color later
147
172
  # index 0 is the "editing_patch" created earlier
148
- self.roi_patch = [c for c in axes[0].get_children() if type(c) is Rectangle][1]
173
+ self.roi_patch = [c for c in axes[1].get_children() if type(c) is Rectangle][1]
149
174
 
150
175
  # epoch marker subplot
151
- axes[1].set_ylim((0, 1))
152
- axes[1].axis("off")
176
+ axes[2].set_ylim((0, 1))
177
+ axes[2].axis("off")
153
178
  self.upper_marker.append(
154
- axes[1].plot([-0.5, epochs_to_show - 0.5], [0.5, 0.5], "r")[0]
179
+ axes[2].plot([-0.5, epochs_to_show - 0.5], [0.5, 0.5], "r")[0]
155
180
  )
156
- self.upper_marker.append(axes[1].plot([0], [0.5], "rD")[0])
181
+ self.upper_marker.append(axes[2].plot([0], [0.5], "rD")[0])
157
182
 
158
183
  # EEG spectrogram subplot
159
184
  # select subset of frequencies to show
160
185
  f = f[f <= SPEC_UPPER_F]
161
186
  spec = spec[0 : len(f), :]
162
- axes[2].set_ylabel("EEG", rotation="horizontal", ha="right")
163
- axes[2].set_yticks(
187
+ axes[3].set_ylabel("EEG", rotation="horizontal", ha="right")
188
+ axes[3].set_yticks(
164
189
  np.linspace(
165
190
  0,
166
191
  len(f),
167
192
  1 + round(SPEC_UPPER_F / SPEC_Y_TICK_INTERVAL),
168
193
  ),
169
194
  )
170
- axes[2].set_yticklabels(
195
+ axes[3].set_yticklabels(
171
196
  np.arange(0, SPEC_UPPER_F + SPEC_Y_TICK_INTERVAL, SPEC_Y_TICK_INTERVAL)
172
197
  )
173
- axes[2].tick_params(axis="both", which="major", labelsize=8)
174
- axes[2].xaxis.set_major_formatter(mticker.FuncFormatter(self.time_formatter))
175
- self.spec_ref = axes[2].imshow(
198
+ axes[3].tick_params(axis="both", which="major", labelsize=8)
199
+ axes[3].xaxis.set_major_formatter(mticker.FuncFormatter(self.time_formatter))
200
+ self.spec_ref = axes[3].imshow(
176
201
  spec,
177
202
  vmin=np.percentile(spec, 2),
178
203
  vmax=np.percentile(spec, 98),
@@ -188,10 +213,10 @@ class MplWidget(QtWidgets.QWidget):
188
213
  )
189
214
 
190
215
  # EMG subplot
191
- axes[3].set_xticks([])
192
- axes[3].set_yticks([])
193
- axes[3].set_ylabel("EMG", rotation="horizontal", ha="right")
194
- axes[3].plot(
216
+ axes[4].set_xticks([])
217
+ axes[4].set_yticks([])
218
+ axes[4].set_ylabel("EMG", rotation="horizontal", ha="right")
219
+ axes[4].plot(
195
220
  emg,
196
221
  "k",
197
222
  linewidth=0.5,