Esempio n. 1
0
class ChainedTransmittersBlock(TransmitterBase):
    """Block with stacked transmitter and receiver decorators."""

    start = Transmitter(str)
    intermediate = Transmitter(str)

    def finish(self, msg):
        self.message = msg
Esempio n. 2
0
    class CustomTask(Task):

        tx = Transmitter()

        def rx(self):
            global count
            count += 1
Esempio n. 3
0
class ComplicatedBlock(TransmitterBase):
    """Block with more complicated transmitter signatures."""
    tx = Transmitter(int, tuple, float)

    def __init__(self):
        super(ComplicatedBlock, self).__init__()
        self.coords = None

    def set_coords(self, i, c, h):
        self.coords = c
Esempio n. 4
0
class Timer(TransmitterBase):
    """Real-time one-shot timer.

    This is useful in situations where you want to wait for some amount of time
    and locking the timing to data acquisition updates is not important. For
    example, inserting a waiting period between trials of a task can be done by
    connecting the ``timeout`` transmitter to your task's
    :meth:`~axopy.task.Task.next_trial` method.

    Parameters
    ----------
    duration : float
        Duration of the timer, in seconds.

    Attributes
    ----------
    timeout : Transmitter
        Transmitted when the timer has finished.
    """

    timeout = Transmitter()

    def __init__(self, duration):
        super(Timer, self).__init__()
        self.duration = duration

        self._qtimer = QtCore.QTimer()
        self._qtimer.setInterval(int(1000 * self.duration))
        self._qtimer.setSingleShot(True)
        self._qtimer.timeout.connect(self.timeout)

    def start(self):
        """Start the timer."""
        self._qtimer.start()

    def stop(self):
        """Stop the timer.

        If you stop the timer early, the timeout event won't be transmitted.
        """
        self._qtimer.stop()
Esempio n. 5
0
class Experiment(TransmitterBase):
    """Experiment workflow manager.

    Presents the researcher with a prompt for entering session details and then
    presents the appropriate tasks.

    Parameters
    ----------
    daq : object, optional
        A data acquisition device that follows the AxoPy DAQ protocol. See
        :mod:`axopy.daq`. For mutliple devices, a dictionary, list or tuple
        is expected.
    data : str, optional
        Path to the data. The directory is created for you if it doesn't exist.
    subject : str, optional
        The subject ID to use. If not specified, a configuration screen is
        shown before running the tasks so you can enter it there. This is
        mostly for experiment writing (to avoid the extra configuration step).
    allow_overwrite : bool, optional
        If ``True``, overwrite protection in :class:`Storage` is disabled. This
        is mostly for experiment writing purposes.
    """

    key_pressed = Transmitter(str)

    def __init__(self,
                 daq=None,
                 data='data',
                 subject=None,
                 allow_overwrite=False):
        super(Experiment, self).__init__()
        self.daq = daq

        self.storage = Storage(data, allow_overwrite=allow_overwrite)

        self._receive_keys = False

        self.subject = subject

        # main screen
        self.screen = _MainWindow()

        # Prepare daqstream(s)
        self._prepare_daqstream()

    def configure(self, **options):
        """Configure the experiment with custom options.

        This method allows you to specify a number of options that you want to
        configure with a graphical interface prior to running the tasks. Use
        keyword arguments to specify which options you want to configure. The
        options selected/specified in the graphical interface are then returned
        by this method so that you can alter setup before running the
        experiment.

        Each keyword argument should list the data type to configure, such as
        ``float``, ``str``, or ``int``. You can also provide a list or tuple of
        available choices for that option.

        You *do not* need to add an option for the subject name/ID -- that is
        added automatically if the subject ID was not specified when creating
        the experiment.
        """
        options['subject'] = str
        config = _SessionConfig(options).run()
        self.subject = config['subject']
        return config

    def run(self, *tasks):
        """Run the experimental tasks."""
        if self.subject is None:
            self.configure()

        self.screen.key_pressed.connect(self.key_press)

        # screen to show "Ready" between tasks
        self.confirm_screen = Canvas(draw_border=False)
        self.confirm_screen.add_item(Text("Ready (enter to start)"))

        self.storage.subject_id = self.subject
        self.tasks = tasks

        self.current_task = None
        self.task_iter = iter(self.tasks)
        self._task_finished()

        self.screen.run()

    @property
    def status(self):
        return "subject: {} | task: {}".format(
            self.subject, self.current_task.__class__.__name__)

    def _run_task(self):
        self._receive_keys = False

        # wait for task to finish
        self.current_task.finished.connect(self._task_finished)
        # forward key presses to the task
        self.key_pressed.connect(self.current_task.key_press)

        self.screen.set_status(self.status)

        # add a task view
        con = self.screen.new_container()

        self.current_task.prepare_graphics(con)
        self.current_task.prepare_daq(self.daqstream)
        self.current_task.prepare_storage(self.storage)
        self.current_task.run()

    def _task_finished(self):
        if self.current_task is not None:
            self.current_task.disconnect_all()
            self.current_task.finished.disconnect(self._task_finished)
            self.key_pressed.disconnect(self.current_task.key_press)

        try:
            self.current_task = next(self.task_iter)
        except StopIteration:
            self.screen.quit()

        self.screen.set_container(self.confirm_screen)
        self._receive_keys = True

    def key_press(self, key):
        if self._receive_keys:
            if key == util.key_escape:
                self.screen.quit()
            elif key == util.key_return:
                self._run_task()
        else:
            self.key_pressed.emit(key)

    def _prepare_daqstream(self):
        if isinstance(self.daq, (list, tuple)):
            self.daqstream = []
            for daq_ in self.daq:
                self.daqstream.append(DaqStream(daq_))
        elif isinstance(self.daq, dict):
            self.daqstream = dict()
            for daq_name, daq_ in self.daq.items():
                self.daqstream[daq_name] = DaqStream(daq_)
        else:
            self.daqstream = DaqStream(self.daq)
Esempio n. 6
0
class EventTransmitterBlock(TransmitterBase):
    """Block with a blank transmitter."""
    trigger = Transmitter()

    def on_event(self):
        self.event_occurred = True
Esempio n. 7
0
class RelayBlock(TransmitterBase):
    """Just transmits the data its transmitter is called with."""
    relay = Transmitter(int)
Esempio n. 8
0
class StepCounter(Counter):
    """Multiple step counter.
       Counts to a given number then transmits a timeout event.
       Transmits events at multiple values up to timeout.

    Parameters
    ----------
    max_count : int
        Number of iterations to go through before transmitting the `timeout`
        event. Must be greater than 1.
    reset_on_timeout : bool, optional
        Specifies whether or not the timer should reset its count back to zero
        once the timeout event occurs. The default behavior is to reset.

    Attributes
    ----------
    count : int
        Current count.
    timeout : Transmitter
        Transmitted when ``max_count`` has been reached.

    Examples
    --------
    Basic usage:

    >>> from axopy.timing import StepCounter
    >>> timer = StepCounter(3)
    >>> timer.add_step(1, function1)
    >>> timer.add_step(2, function2)
    >>> timer.increment()
    "function 1"
    >>> timer.increment()
    "function 2"
    >>> timer.increment()
    >>> timer.count
    0
    """

    timeout = Transmitter()

    # README: No way to create an array of pyqtSignal(s) ...
    # https://stackoverflow.com/questions/38506979/creating-an-array-of-pyqtsignal
    step_max = 10
    for i in range(step_max):
        vars()['step' + str(i)] = Transmitter()

    def __init__(self, max_count=1, reset_on_timeout=True):
        super(StepCounter, self).__init__(max_count, reset_on_timeout)
        self.step_inc = 0
        self.step_count = []

    @staticmethod
    def _dummy():
        """A default method for add_step."""
        pass

    def add_step(self, count=0, event=_dummy):
        """Add a step if we have enough emitters."""
        if self.step_inc < self.step_max:
            getattr(self, 'step' + str(self.step_inc)).connect(event)
            self.step_count.append(count)
            self.step_inc += 1

    def increment(self):
        """Increment the counter.

        If a count is reached which is found in `step_count` the event
        associated with the count is transmitted.

        If `max_count` is reached, the ``timeout`` event is transmitted. If
        `reset_on_timeout` has been set to True (default), the timer is also
        reset.
        """
        self.count += 1

        if self.count in self.step_count:
            # _index = self.step_count.index(self.count)
            # getattr(self, 'step' + str(_index)).emit()
            _index = [
                i for i, x in enumerate(self.step_count) if x == self.count
            ]
            for i in _index:
                getattr(self, 'step' + str(i)).emit()

        if self.count == self.max_count:
            if self.reset_on_timeout:
                self.reset()
            self.timeout.emit()
Esempio n. 9
0
class Counter(TransmitterBase):
    """Counts to a given number then transmits a timeout event.

    Parameters
    ----------
    max_count : int
        Number of iterations to go through before transmitting the `timeout`
        event. Must be greater than 1.
    reset_on_timeout : bool, optional
        Specifies whether or not the timer should reset its count back to zero
        once the timeout event occurs. The default behavior is to reset.

    Attributes
    ----------
    count : int
        Current count.
    timeout : Transmitter
        Transmitted when ``max_count`` has been reached.

    Examples
    --------
    Basic usage:

    >>> from axopy.timing import Counter
    >>> timer = Counter(2)
    >>> timer.increment()
    >>> timer.count
    1
    >>> timer.progress
    0.5
    >>> timer.increment()
    >>> timer.count
    0
    """

    timeout = Transmitter()

    def __init__(self, max_count=1, reset_on_timeout=True):
        super(Counter, self).__init__()
        max_count = int(max_count)
        if max_count < 1:
            raise ValueError('max_count must be > 1')

        self.reset_on_timeout = reset_on_timeout

        self.max_count = max_count
        self.count = 0

    @property
    def progress(self):
        """Progress toward timeout, from 0 to 1."""
        return self.count / self.max_count

    def increment(self):
        """Increment the counter.

        If `max_count` is reached, the ``timeout`` event is transmitted. If
        `reset_on_timeout` has been set to True (default), the timer is also
        reset.
        """
        self.count += 1

        if self.count == self.max_count:
            if self.reset_on_timeout:
                self.reset()

            self.timeout.emit()

    def reset(self):
        """Resets the count to 0 to start over."""
        self.count = 0
Esempio n. 10
0
class DaqStream(QtCore.QThread):
    """Asynchronous interface to an input device.

    Runs a persistent while loop wherein the device is repeatedly polled for
    data. When the data becomes available, it is emitted and the loop
    continues.

    There are effectively two methods of this class: start and stop. These
    methods do as their names suggest -- they start and stop the underlying
    device from sampling new data.

    The device used to create the DaqStream is also accessible via the
    ``device`` attribute so you can change settings on the underlying device
    any time (e.g. sampling rate, number of samples per update, etc.).

    Parameters
    ----------
    device : daq
        Any object implementing the AxoPy data acquisition interface. See
        :class:`NoiseGenerator` for an example.

    Attributes
    ----------
    updated : Transmitter
        Transmitted when the latest chunk of data is available. The data type
        depends on the underlying input device, but it is often a numpy
        ndarray.
    disconnected : Transmitter
        Transmitted if the device cannot be read from (it has disconnected
        somehow).
    finished : Transmitter
        Transmitted when the device has stopped and samping is finished.
    """

    updated = Transmitter(object)
    disconnected = Transmitter()
    finished = Transmitter()

    def __init__(self, device):
        super(DaqStream, self).__init__()
        self.device = device

        self._running = False

    @property
    def running(self):
        """Boolean value indicating whether or not the stream is running."""
        return self._running

    def start(self):
        """Start the device and begin reading from it."""
        super(DaqStream, self).start()

    def run(self):
        """Implementation for the underlying QThread.

        Don't call this method directly -- use :meth:`start` instead.
        """
        self._running = True

        self.device.start()

        while True:
            if not self._running:
                break

            try:
                d = self.device.read()
            except IOError:
                self.disconnected.emit()
                return

            if self._running:
                self.updated.emit(d)

        self.device.stop()
        self.finished.emit()

    def stop(self, wait=True):
        """Stop the stream.

        Parameters
        ----------
        wait : bool, optional
            Whether or not to wait for the underlying device to stop before
            returning.
        """
        self._running = False
        if wait:
            self.wait()
Esempio n. 11
0
class _MainWindow(QtWidgets.QMainWindow):
    """The window containing all graphical content of the application.

    It is a very simple GUI implemented as a `QMainWindow` with a
    `QStackedLayout` holding a list of :class:`Container` objects. The
    containers, which in turn house all of the interesting graphical content.
    """

    key_pressed = Transmitter(str)

    def __init__(self):
        app = get_qtapp()
        super(_MainWindow, self).__init__()

        app.installEventFilter(self)

        self._central_widget = QtWidgets.QWidget(self)
        self._layout = QtWidgets.QStackedLayout(self._central_widget)
        self.setCentralWidget(self._central_widget)

        status_bar = QtWidgets.QStatusBar(self)
        self.setStatusBar(status_bar)
        self._statusbar_label = QtWidgets.QLabel("status")
        status_bar.addPermanentWidget(self._statusbar_label)

        self.show()

    def run(self):
        """Start the application."""
        get_qtapp().exec_()

    def new_container(self):
        """Add a new container to the stack and give it back.

        Returns
        -------
        container : Container
            The newly added container.
        """
        c = Container()
        self._layout.addWidget(c)
        self._layout.setCurrentWidget(c)
        return c

    def set_container(self, container):
        """Make the given container visible.

        If the container is already somewhere in the stack, it is just made
        visible, otherwise it is added to the stack.
        """
        if self._layout.indexOf(container) == -1:
            self._layout.addWidget(container)
        self._layout.setCurrentWidget(container)

    def set_status(self, message):
        """Set the status bar message.

        Parameters
        ----------
        message : str
            Message to display in the status bar.
        """
        self._statusbar_label.setText(message)

    def quit(self):
        """Quit the application."""
        get_qtapp().quit()

    def keyPressEvent(self, event):
        """Qt callback for key presses.

        This overrides the `QMainWindow` method. It does not need to be called
        directly and it doesn't need to be overriden. Connect to the
        ``key_pressed`` transmitter to handle key press events.
        """
        try:
            key = key_map[event.key()]
        except KeyError:
            return super().keyPressEvent(event)

        self.key_pressed.emit(key)
Esempio n. 12
0
class Task(TransmitterBase):
    """Base class for tasks.

    This base class handles iteration through the trials of the task in blocks.

    Most task implementations will want to override the `prepare` and
    `run_trial` methods, while the rest can be left to default behavior.

    If you need to implement a custom constructor (``__init__``), you *must*
    call the base task ``__init__``::

        class CustomTask(Task):

            def __init__(self, custom_param):
                super(CustomTask, self).__init__()

    Attributes
    ----------
    trial : dict
        Dictionary containing the current trial's attributes.
    advance_block_key : str
        Key for the user to press in order to advance to the next block. Can
        set to ``None`` to disable the feature (next block starts immediately
        after one finishes).
    finished : Transmitter
        Emitted when the last trial of the last block has run. This is
        primarily for the :class:`axopy.experiment.Experiment` to know when the
        task has finished so it can run the next one. You shouldn't need to
        use this transmitter at all.
    """

    advance_block_key = util.key_return
    finished = Transmitter()

    def __init__(self):
        super(Task, self).__init__()
        self._connections = {}

        design = Design()
        self.iter = _TaskIter(design)
        self.prepare_design(design)

    def connect(self, transmitter, receiver):
        """Connect a transmitter to a receiver.

        This method helps the task keep track of connections so that all of the
        manually specified connections can be torn down by the
        :class:`axopy.experiment.Experiment`.
        """
        name = _connection_name(transmitter, receiver)
        self._connections[name] = (transmitter, receiver)
        transmitter.connect(receiver)

    def disconnect(self, transmitter, receiver):
        """Disconnect a transmitter from a receiver."""
        name = _connection_name(transmitter, receiver)
        try:
            del self._connections[name]
            transmitter.disconnect(receiver)
        except KeyError:
            # tx/rx pair already removed/disconnected
            pass

    def disconnect_all(self):
        """Disconnect all of the task's manually-created connections."""
        for name, (tx, rx) in self._connections.items():
            tx.disconnect(rx)
        self._connections.clear()

    def prepare_design(self, design):
        """Callback for setting up the task design.

        See :class:`axopy.design.Design` for details on how to design the task.
        By default, nothing is added to the design.

        Parameters
        ----------
        design : Design
            The task design object you can use to add blocks and trials.
        """
        pass

    def prepare_graphics(self, container):
        """Initialize graphical elements and messaging connections.

        This method should be overridden if the task uses any graphics (which
        most do). It is important to defer initializing any graphical elements
        until this method is called so that the graphical backend has a chance
        to start.

        Parameters
        ----------
        container : axopy.gui.Container
            The graphical container you can add objects to.
        """
        pass

    def prepare_daq(self, daqstream):
        """Set up the input device, if applicable.

        Parameters
        ----------
        daqstream : DaqStream
            Interface to the data acquisition device.
        """
        pass

    def prepare_storage(self, storage):
        """Initialize data storage.

        Override to read or write task data. A :class:`axopy.storage.Storage`
        object is given, which can be used to create a new
        :class:`axopy.storage.TaskWriter` for storing new data or
        a :class:`axopy.storage.TaskReader` for reading in existing data. Note
        that the subject ID has already been set.

        Parameters
        ----------
        storage : Storage
            The top-level storage object with which new storage can be
            allocated and existing data can be read.
        """
        pass

    def run(self):
        """Start running the task.

        Simply calls `next_block` to start running trials in the first block.
        This method is called automatically if the task is added to an
        :class:`~axopy.experiment.Experiment`. Tasks that have a block design
        shouldn't normally need to override this method. Tasks that are
        "free-running" for experimenter interaction (e.g. a plot visualization
        task that the experimenter controls) should override.
        """
        self.next_block()

    def next_block(self):
        """Get the next block of trials and starts running them.

        Before starting the block, a prompt is shown to verify that the user is
        ready to proceed. If there are no more blocks to run, the `finish`
        method is called. You usually do not need to override this method.
        """
        block = self.iter.next_block()
        if block is None:
            self.finish()
            return

        self.block = block

        # wait for confirmation between blocks
        if self.advance_block_key is None:
            self.next_trial()
        else:
            self._awaiting_key = True

    def next_trial(self):
        """Get the next trial in the block and starts running it.

        If there are no more trials in the block, the `finish_block` method is
        called.
        """
        trial = self.iter.next_trial()
        if trial is None:
            self.finish_block()
            return

        self.trial = trial
        self.run_trial(trial)

    def run_trial(self, trial):
        """Initiate a trial.

        By default, this method does nothing. Override to implement what
        happens in a trial. When a trial is complete, use `next_trial` to
        start the next.

        Parameters
        ----------
        trial : object
            Trial data. This is whatever data is put into the `design` passed
            in.
        """
        pass

    def finish_block(self):
        """Finishes the block and starts the next one.

        Override if you need to do some cleanup between blocks.
        """
        self.next_block()

    def finish(self):
        """Clean up at the end of the task.

        Override if you need to clean up once the task is completely finished.
        If you do override this method, you should call the base
        :meth:`Task.finish()` method or call the ``finished`` transmitter
        yourself.
        """
        self.finished.emit()

    def key_press(self, key):
        """Handle key press events.

        Override this method to receive key press events. Available keys can be
        found in :mod:`axopy.util` (named `key_<keyname>`, e.g. `key_k`).

        Important note: if relying on the ``advance_block_key`` to advance the
        task, make sure to call this super implementation.
        """
        if getattr(self, '_awaiting_key', False) and \
                key == self.advance_block_key:
            self._awaiting_key = False
            self.next_trial()
Esempio n. 13
0
class EnvelopeCalibrationWidget(QtGui.QWidget):
    """EMG envelope calibration widget.

    Consists of two sub-widgets, a ``PlotWidget`` and a ``BarWidget``. There
    are two pushbuttons and optionally a dropdown menu, all of which are
    connected to pyqtsignals emitting the widget's id and the selected value
    in the case of the dropdown menu.

    Parameters
    ----------
    id : object, optional (default=None)
        Widget identifier. This is emitted every time a button is pressed or
        a selection is made from the dropdown menu. If multiple widgets are
        used at the same time, their id's have to be unique so that they are
        distinguishable.
    task_channels : list of str, optional (default=None)
        The task channels that will be offered as options in the dropdown menu.
        If not provided, the dropdown menu will not show up.
    name : str, optional (default=None)
        Widget name that will be displayed.
    size : tuple, optional (default=None)
        Widget size.
    pos : tuple, optional (default=None)
        Widget position.
    autorange : boolean, optional (default=True)
        If ``False`` the autorange option will be disabled from the
        ``PlotWidget``.
    yrange : tuple, optional (default=(-1, 1))
        When ``autorange`` is ``False``, this is the yrange for the
        ``PlotWidget``. When ``autorange`` is ``True``, this will be ignored.

    Attributes
    ----------
    max : Transmitter
        Emits the widget ``id`` (or ``None`` when not provided) when the
        ``max`` button is pressed.
    min : Transmitter
        Emits the widget ``id`` (or ``None`` when not provided) when the
        ``min`` button is pressed.
    reset : Transmitter
        Emits the widget ``id`` (or ``None`` when not provided) when the
        ``reset`` button is pressed.
    active : Transmitter
        Emits the widget ``id`` (or ``None`` when not provided) when the
        widget is activated.
    selected : Transmitter
        Emits the widget ``id`` (or ``None`` when not provided) and the
        selected value from the dropdown menu.
    """

    max = Transmitter(object)
    min = Transmitter(object)
    reset = Transmitter(object)
    active = Transmitter(object)
    selected = Transmitter(object)

    def __init__(self,
                 id=None,
                 task_channels=None,
                 name=None,
                 size=None,
                 pos=None,
                 autorange=True,
                 yrange=(-1, 1)):
        super(EnvelopeCalibrationWidget, self).__init__()
        self.id = id
        self.task_channels = task_channels
        self.name = name
        self.size = size
        self.pos = pos
        self.autorange = autorange
        self.yrange = yrange

        self.init_widget()

    def init_widget(self):
        """Initializes the main widget and adds sub-widgets and menus. """
        if self.name is not None:
            self.setWindowTitle(self.name)

        layout = QGridLayout()
        layout.setSpacing(20)
        self.setLayout(layout)

        # Sub-widgets
        self.emgWidget = pg.PlotWidget(background=None)
        self.emgItem = self.emgWidget.plot(pen='b')
        self.emgWidget.hideAxis('left')
        self.emgWidget.hideAxis('bottom')
        if self.autorange is False:
            self.emgWidget.disableAutoRange(pg.ViewBox.YAxis)
            self.emgWidget.setYRange(*self.yrange)

        self.barWidget = pg.PlotWidget(background=None)
        self.barItem = pg.BarGraphItem(x=[1.], height=[0.], width=1, brush='b')
        self.barWidget.addItem(self.barItem)
        self.barWidget.setYRange(0, 1.3)
        self.barWidget.hideAxis('bottom')
        self.barWidget.showGrid(y=True, alpha=0.5)

        self.reset_button = QPushButton('Reset')
        self.reset_button.resize(self.reset_button.sizeHint())
        self.reset_button.clicked.connect(self.resetButtonClicked)

        self.max_button = QPushButton('max')
        self.max_button.resize(self.max_button.sizeHint())
        self.max_button.clicked.connect(self.maxButtonClicked)

        self.min_button = QPushButton('min')
        self.min_button.resize(self.min_button.sizeHint())
        self.min_button.clicked.connect(self.minButtonClicked)

        if self.task_channels is not None:
            self.select = QComboBox()
            self.select.addItem('Select')
            for task_channel in self.task_channels:
                self.select.addItem(task_channel)
            self.select.currentIndexChanged[str].connect(self.selectActivated)

        layout.addWidget(self.emgWidget, 0, 0, 4, 1)
        layout.addWidget(self.barWidget, 0, 1, 4, 1)
        layout.addWidget(self.reset_button, 1, 2)
        layout.addWidget(self.max_button, 2, 2)
        layout.addWidget(self.min_button, 3, 2)
        if self.task_channels is not None:
            layout.addWidget(self.select, 0, 2)

        # determine layout window
        layout.setColumnStretch(1, 10)
        layout.setColumnStretch(2, 2)
        layout.setColumnStretch(3, 2)

        if self.size is not None:
            self.resize(*self.size)

        if self.pos is not None:
            self.move(*self.pos)

        self.installEventFilter(self)

    def maxButtonClicked(self):
        self.max.emit(self.id)

    def minButtonClicked(self):
        self.min.emit(self.id)

    def resetButtonClicked(self):
        if self.task_channels is not None:
            self.select.setCurrentText('Select')

        self.reset.emit(self.id)

    def selectActivated(self, text):
        if text == 'Select':
            value = None
        else:
            value = text

        self.selected.emit((self.id, value))

    def eventFilter(self, obj, event):
        """Returns ``True`` if the widget is activated. """
        if event.type() == QEvent.WindowActivate:
            self.active.emit(self.id)
            return True
        else:
            return False

    def keyPressEvent(self, e):
        """Keyboard shortcuts.  """
        if e.key() == QtCore.Qt.Key_Escape:
            self.close()
        if e.key() == QtCore.Qt.Key_R:
            self.minButtonClicked()
        if e.key() == QtCore.Qt.Key_C:
            self.maxButtonClicked()

    def set_emg_color(self, color):
        """Sets the color for the raw EMG plot widget. """
        self.emgItem.setPen(color)