[tasks] automatic countdown trial start

This commit is contained in:
Jan Grewe 2021-03-06 12:41:25 +01:00
parent 3680178129
commit 08696619f1
3 changed files with 128 additions and 40 deletions

View File

@ -5,6 +5,7 @@ from PyQt5.QtMultimedia import QMediaPlayer
import os
import blipblop.constants as cnst
from blipblop.ui.countdownlabel import CountdownLabel
import datetime as dt
class SettingsPanel(QWidget):
@ -14,7 +15,8 @@ class SettingsPanel(QWidget):
self._trial_spinner = QSpinBox()
self._trial_spinner.setMinimum(5)
self._trial_spinner.setMaximum(25)
self._trial_spinner.setValue(3)
self._trial_spinner.setValue(5)
self._trial_spinner.setToolTip("Number of consecutive trials (5 - 25)")
self._min_delay_spinner = QSpinBox()
self._min_delay_spinner.setMinimum(1)
@ -28,11 +30,11 @@ class SettingsPanel(QWidget):
self._max_delay_spinner.setValue(5)
self._max_delay_spinner.setToolTip("Maximum delay between start of trial and stimulus display [s]")
self._pause_spinner = QSpinBox()
self._pause_spinner.setMinimum(1)
self._pause_spinner.setMaximum(10)
self._pause_spinner.setValue(3)
self._pause_spinner.setToolTip("Pause between trials [s]")
self._countdown_spinner = QSpinBox()
self._countdown_spinner.setMinimum(1)
self._countdown_spinner.setMaximum(10)
self._countdown_spinner.setValue(3)
self._countdown_spinner.setToolTip("Pause between trials [s]")
self._saliency_slider = QSlider(Qt.Horizontal)
self._saliency_slider.setMinimum(0)
@ -40,7 +42,7 @@ class SettingsPanel(QWidget):
self._saliency_slider.setSliderPosition(100)
self._saliency_slider.setTickInterval(25)
self._saliency_slider.setTickPosition(QSlider.TicksBelow)
self._saliency_slider.setToolTip("Saliency of the stimulus, i.e. its opacity")
self._saliency_slider.setToolTip("Saliency of the stimulus, i.e. its loudness")
self._sound_combo = QComboBox()
for k in cnst.SNDS_DICT.keys():
@ -56,7 +58,7 @@ class SettingsPanel(QWidget):
form_layout.addRow("number of trials", self._trial_spinner)
form_layout.addRow("minimum delay [s]", self._min_delay_spinner)
form_layout.addRow("maximum delay [s]", self._max_delay_spinner)
form_layout.addRow("pause [s]", self._pause_spinner)
form_layout.addRow("pause [s]", self._countdown_spinner)
form_layout.addRow("stimulus saliency", self._saliency_slider)
form_layout.addRow("stimulus sound", self._sound_combo)
form_layout.addRow("instructions", self._instructions)
@ -83,8 +85,8 @@ class SettingsPanel(QWidget):
return self._max_delay_spinner.value()
@property
def pause(self):
return self._pause_spinner.value()
def countdown(self):
return self._countdown_spinner.value()
@property
def sound(self):
@ -93,7 +95,7 @@ class SettingsPanel(QWidget):
def set_enabled(self, enabled):
self._trial_spinner.setEnabled(enabled)
self._saliency_slider.setEnabled(enabled)
self._pause_spinner.setEnabled(enabled)
self._countdown_spinner.setEnabled(enabled)
self._min_delay_spinner.setEnabled(enabled)
self._max_delay_spinner.setEnabled(enabled)
self._sound_combo.setEnabled(False)
@ -135,6 +137,10 @@ class AudioBlop(QWidget):
self._status_label = QLabel("Ready to start, press enter ...")
grid.addWidget(self._status_label, 3, 0, Qt.AlignLeft)
self._countdown_label = CountdownLabel(text="Next trial in:")
grid.addWidget(self._countdown_label, 3, 1, Qt.AlignCenter)
self._countdown_label.countdown_done.connect(self.run_trial)
self._draw_area = QLabel()
self._draw_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
grid.addWidget(self._draw_area, 2, 1)
@ -186,15 +192,20 @@ class AudioBlop(QWidget):
self._response_time = dt.datetime.now()
if self._trial_counter < self._settings.trials:
self._status_label.setText("Trial %i of %i, press enter for next trial" % (self._trial_counter, self._settings.trials))
self._status_label.setText("Trial %i of %i" % (self._trial_counter, self._settings.trials))
if self._start_time is None:
self._reaction_times.append(-1000)
else:
reaction_time = self._response_time - self._start_time
self._reaction_times.append(reaction_time.total_seconds())
self._trial_running = False
if self._timer.isActive():
self._timer.stop()
if self._trial_counter >= self._settings.trials:
self.task_done.emit()
return
self._countdown_label.start(self._settings.countdown)
def reset_canvas(self):
bkg_color = QColor()
@ -230,6 +241,9 @@ class AudioBlop(QWidget):
if not self._session_running:
self._settings.set_enabled(False)
self._session_running = True
self._countdown_label.start(time=self._settings.countdown)
def run_trial(self):
self._trial_running = True
if self._trial_counter >= self._settings.trials:
self.task_done.emit
@ -244,11 +258,11 @@ class AudioBlop(QWidget):
max_interval = int(self._settings.max_delay * 10)
interval = self._random_generator.bounded(min_interval, max_interval) * 100
self._start_time = None
timer = QTimer(self)
timer.setSingleShot(True)
timer.setInterval(int(interval))
timer.timeout.connect(self.blip)
timer.start()
self._timer = QTimer(self)
self._timer.setSingleShot(True)
self._timer.setInterval(int(interval))
self._timer.timeout.connect(self.blip)
self._timer.start()
def on_abort(self):
self.reset()
@ -266,6 +280,7 @@ class AudioBlop(QWidget):
self._session_running = False
self._status_label.setText("Ready to start...")
self._settings.set_enabled(True)
self._countdown_label.stop()
def on_toggle_settings(self):
if self._splitter.sizes()[1] > 0:

View File

@ -0,0 +1,42 @@
from PyQt5.QtWidgets import QLabel
from PyQt5.QtCore import QTimer, pyqtSignal
from PyQt5.QtGui import QFont
class CountdownLabel(QLabel):
countdown_done = pyqtSignal()
def __init__(self, parent=None, text=""):
super().__init__(parent=parent)
self._text = text
self._count = 0
self._timer = QTimer()
self._timer.timeout.connect(self.on_timeout)
font = QFont()
font.setBold(True)
font.setPointSize(20)
self.setFont(font)
self.setStyleSheet("color: #2D4B9A")
def start(self, time=3):
if time < 1:
time = 1
self._count = time
self.setText("%s %i" % (self._text, self._count))
self.update()
self._timer.start(1000)
def stop(self):
if self._timer.isActive():
self._timer.stop()
self.setText("")
def on_timeout(self):
self._count -= 1
self.setText("%s %i" % (self._text, self._count))
self.update()
if self._count <= 0:
self._timer.stop()
self.setText("")
self.update()
self.countdown_done.emit()

View File

@ -4,6 +4,7 @@ from PyQt5.QtGui import QColor, QFont, QKeySequence, QPainter, QBrush, QPen, QPi
import os
import blipblop.constants as cnst
from blipblop.ui.countdownlabel import CountdownLabel
import datetime as dt
class SettingsPanel(QWidget):
@ -13,17 +14,20 @@ class SettingsPanel(QWidget):
self._trial_spinner = QSpinBox()
self._trial_spinner.setMinimum(5)
self._trial_spinner.setMaximum(25)
self._trial_spinner.setValue(3)
self._trial_spinner.setValue(5)
self._trial_spinner.setToolTip("Number of consecutive trials (5 - 25)")
self._min_delay_edit = QLineEdit()
self._min_delay_edit.setText(str("1"))
self._min_delay_edit.setToolTip("Minimum delay between start of trial and stimulus display [s]")
self._min_delay_edit.setEnabled(False)
self._min_delay_spinner = QSpinBox()
self._min_delay_spinner.setMinimum(1)
self._min_delay_spinner.setMaximum(10)
self._min_delay_spinner.setValue(1)
self._min_delay_spinner.setToolTip("Minimum delay between start of trial and stimulus display [s]")
self._max_delay_edit = QLineEdit()
self._max_delay_edit.setText(str("5"))
self._max_delay_edit.setToolTip("Maximum delay between start of trial and stimulus display [s]")
self._max_delay_edit.setEnabled(False)
self._max_delay_spinner = QSpinBox()
self._max_delay_spinner.setMinimum(1)
self._max_delay_spinner.setMaximum(10)
self._max_delay_spinner.setValue(5)
self._max_delay_spinner.setToolTip("Maximum delay between start of trial and stimulus display [s]")
self._saliency_slider = QSlider(Qt.Horizontal)
self._saliency_slider.setMinimum(0)
@ -40,7 +44,13 @@ class SettingsPanel(QWidget):
self._size_slider.setTickInterval(25)
self._size_slider.setTickPosition(QSlider.TicksBelow)
self._size_slider.setToolTip("Diameter of the stimulus in pixel")
self._countdown_spinner = QSpinBox()
self._countdown_spinner.setMinimum(2)
self._countdown_spinner.setMaximum(30)
self._countdown_spinner.setValue(5)
self._countdown_spinner.setToolTip("Pause/countdown for next trial")
self._instructions = QTextEdit()
self._instructions.setMarkdown("* fixate central cross\n * press start (enter) when ready\n * press space bar as soon as the stimulus occurs")
self._instructions.setMinimumHeight(200)
@ -49,8 +59,9 @@ class SettingsPanel(QWidget):
form_layout = QFormLayout()
form_layout.addRow("Settings", None)
form_layout.addRow("number of trials", self._trial_spinner)
form_layout.addRow("minimum delay [s]", self._min_delay_edit)
form_layout.addRow("maximum delay [s]", self._max_delay_edit)
form_layout.addRow("pause until next trial [s]", self._countdown_spinner)
form_layout.addRow("minimum delay [s]", self._min_delay_spinner)
form_layout.addRow("maximum delay [s]", self._max_delay_spinner)
form_layout.addRow("stimulus saliency", self._saliency_slider)
form_layout.addRow("stimulus size", self._size_slider)
form_layout.addRow("instructions", self._instructions)
@ -70,15 +81,23 @@ class SettingsPanel(QWidget):
@property
def min_delay(self):
return int(self._min_delay_edit.text())
return self._min_delay_spinner.value()
@property
def max_delay(self):
return int(self._max_delay_edit.text())
return self._max_delay_spinner.value()
@property
def countdown(self):
return self._countdown_spinner.value()
def set_enabled(self, enabled):
self._trial_spinner.setEnabled(enabled)
self._saliency_slider.setEnabled(enabled)
self._size_slider.setEnabled(enabled)
self._countdown_spinner.setEnabled(enabled)
self._min_delay_spinner.setEnabled(enabled)
self._max_delay_spinner.setEnabled(enabled)
class VisualBlip(QWidget):
@ -100,7 +119,7 @@ class VisualBlip(QWidget):
l.setPixmap(QPixmap(os.path.join(cnst.ICONS_FOLDER, "visual_task.png")))
grid.addWidget(l, 0, 0, Qt.AlignLeft)
l2 =QLabel("Measurement of visual reaction times\npress enter to start")
l2 = QLabel("Measurement of visual reaction times\npress enter to start")
font = QFont()
font.setBold(True)
font.setPointSize(20)
@ -116,7 +135,11 @@ class VisualBlip(QWidget):
self._status_label = QLabel("Ready to start, press enter ...")
grid.addWidget(self._status_label, 3, 0, Qt.AlignBaseline)
self._countdown_label = CountdownLabel(text="Next trial in:")
grid.addWidget(self._countdown_label, 3, 1, Qt.AlignCenter)
self._countdown_label.countdown_done.connect(self.run_trial)
self._draw_area = QLabel()
self._draw_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
grid.addWidget(self._draw_area, 2, 1)
@ -168,7 +191,7 @@ class VisualBlip(QWidget):
self._response_time = dt.datetime.now()
if self._trial_counter < self._settings.trials:
self._status_label.setText("Trial %i of %i, press enter for next trial" % (self._trial_counter, self._settings.trials))
self._status_label.setText("Trial %i of %i" % (self._trial_counter, self._settings.trials))
if self._start_time is None:
self._reaction_times.append(-1000)
else:
@ -176,8 +199,12 @@ class VisualBlip(QWidget):
self._reaction_times.append(reaction_time.total_seconds())
self.reset_canvas()
self._trial_running = False
if self._timer.isActive():
self._timer.stop()
if self._trial_counter >= self._settings.trials:
self.task_done.emit()
return
self._countdown_label.start(self._settings.countdown)
def reset_canvas(self):
bkg_color = QColor()
@ -220,7 +247,10 @@ class VisualBlip(QWidget):
return
if not self._session_running:
self._settings.set_enabled(False)
self._session_running = True
self._session_running = True
self._countdown_label.start(time=self._settings.countdown)
def run_trial(self):
self._trial_running = True
if self._trial_counter >= self._settings.trials:
self.task_done.emit
@ -232,11 +262,11 @@ class VisualBlip(QWidget):
max_interval = int(self._settings.max_delay * 10)
interval = self._random_generator.bounded(min_interval, max_interval) * 100
self._start_time = None
timer = QTimer(self)
timer.setSingleShot(True)
timer.setInterval(int(interval))
timer.timeout.connect(self.blip)
timer.start()
self._timer = QTimer(self)
self._timer.setSingleShot(True)
self._timer.setInterval(int(interval))
self._timer.timeout.connect(self.blip)
self._timer.start()
def on_abort(self):
self.reset()
@ -254,6 +284,7 @@ class VisualBlip(QWidget):
self._trial_running = False
self._session_running = False
self._status_label.setText("Ready to start...")
self._countdown_label.stop()
self._settings.set_enabled(True)
def on_toggle_settings(self):