Пример #1
0
    def __init__(self, protocol_runner: ProtocolRunner, main_window=None):
        """ """
        super().__init__("Protocol running")
        self.setIconSize(QSize(32, 32))
        self.main_window = main_window
        self.protocol_runner = protocol_runner

        self.update_duration_each = 120
        self._update_duration_i = 0

        self.toggleStatus = ToggleIconButton(
            icon_off="play", icon_on="stop", action_on="play", on=False
        )
        self.toggleStatus.clicked.connect(self.toggle_protocol_running)
        self.addWidget(self.toggleStatus)

        # Progress bar for monitoring the protocol:
        self.progress_bar = QProgressBar()
        self.addSeparator()
        self.addWidget(self.progress_bar)

        # Window with the protocol parameters:
        self.act_edit = IconButton(
            action_name="Edit protocol parameters", icon_name="edit_protocol"
        )
        self.act_edit.clicked.connect(self.show_stim_params_gui)
        self.addWidget(self.act_edit)

        # Connect events and signals from the ProtocolRunner to update the GUI:
        self.update_progress()
        self.protocol_runner.sig_timestep.connect(self.update_progress)

        self.protocol_runner.sig_protocol_finished.connect(self.toggle_icon)
        self.protocol_runner.sig_protocol_updated.connect(self.update_progress)
        self.protocol_runner.sig_protocol_interrupted.connect(self.toggle_icon)
Пример #2
0
    def __init__(self, experiment, **kwargs):
        """ """
        super().__init__(**kwargs)
        self.experiment = experiment

        self.setWindowTitle("Stytra | " +
                            pretty_name(type(experiment.protocol).name))

        self.docks = dict()

        # self.label_debug = DebugLabel(debug_on=experiment.debug_mode)
        # if not self.experiment.offline:
        #     self.widget_projection = ProjectorAndCalibrationWidget(experiment)
        self.toolbar_control = ProtocolControlToolbar(
            experiment.protocol_runner, self)
        self.toolbar_control.setObjectName("toolbar")

        # Connect signals from the protocol_control:
        self.toolbar_control.sig_start_protocol.connect(
            experiment.start_protocol)
        self.toolbar_control.sig_stop_protocol.connect(experiment.end_protocol)

        self.btn_metadata = IconButton(icon_name="edit_fish",
                                       action_name="Edit metadata")
        self.btn_metadata.clicked.connect(self.show_metadata_gui)
        self.toolbar_control.addWidget(self.btn_metadata)

        self.act_folder = self.toolbar_control.addAction("Save in {}".format(
            self.experiment.base_dir))
        self.act_folder.triggered.connect(self.change_folder_gui)

        if self.experiment.database is not None:
            self.chk_db = ToggleIconButton(
                action_on="Use DB",
                icon_on="dbON",
                icon_off="dbOFF",
                on=self.experiment.use_db,
            )
            self.chk_db.toggled.connect(self.toggle_db)
            self.toolbar_control.addWidget(self.chk_db)

        if experiment.trigger is not None:
            self.chk_scope = QCheckBox("Wait for trigger signal")

        self.logger = QPlainTextEditLogger()
        self.experiment.logger.addHandler(self.logger)

        self.status_display = StatusMessageDisplay()
        self.statusBar().addWidget(self.status_display)

        self.plot_framerate = MultiFrameratesWidget()
        self.plot_framerate.add_framerate(
            self.experiment.protocol_runner.framerate_acc)

        self.metadata_win = None
Пример #3
0
class ProtocolControlToolbar(QToolBar):
    """GUI for controlling a ProtocolRunner.

    This class implements the toolbar for controlling the protocol:

        - toggle button for starting/stopping the Protocol;
        - progress bar to display progression of the Protocol.
        - a button and  window for controlling Protocol parameters;

    Parameters
    ----------
    protocol_runner: :class:`ProtocolRunner <stytra.stimulation.ProtocolRunner>` object
        ProtocolRunner that is controlled by the GUI.



    Signals:
    """

    sig_start_protocol = pyqtSignal()
    """ Emitted via the toggle button click, meant to
                         start the protocol."""
    sig_stop_protocol = pyqtSignal()
    """ Emitted via the toggle button click, meant to
                         abort the protocol."""

    def __init__(self, protocol_runner: ProtocolRunner, main_window=None):
        """ """
        super().__init__("Protocol running")
        self.setIconSize(QSize(32, 32))
        self.main_window = main_window
        self.protocol_runner = protocol_runner

        self.update_duration_each = 120
        self._update_duration_i = 0

        self.toggleStatus = ToggleIconButton(
            icon_off="play", icon_on="stop", action_on="play", on=False
        )
        self.toggleStatus.clicked.connect(self.toggle_protocol_running)
        self.addWidget(self.toggleStatus)

        # Progress bar for monitoring the protocol:
        self.progress_bar = QProgressBar()
        self.addSeparator()
        self.addWidget(self.progress_bar)

        # Window with the protocol parameters:
        self.act_edit = IconButton(
            action_name="Edit protocol parameters", icon_name="edit_protocol"
        )
        self.act_edit.clicked.connect(self.show_stim_params_gui)
        self.addWidget(self.act_edit)

        # Connect events and signals from the ProtocolRunner to update the GUI:
        self.update_progress()
        self.protocol_runner.sig_timestep.connect(self.update_progress)

        self.protocol_runner.sig_protocol_finished.connect(self.toggle_icon)
        self.protocol_runner.sig_protocol_updated.connect(self.update_progress)
        self.protocol_runner.sig_protocol_interrupted.connect(self.toggle_icon)

    def show_stim_params_gui(self):
        """Create and show window to update protocol parameters."""
        self.prot_param_win = ParameterGui(self.protocol_runner.protocol)
        self.prot_param_win.show()

    def toggle_protocol_running(self):
        """Emit the start and stop signals. These can be used in the Experiment
        class or directly connected with the respective ProtocolRunner
        start() and stop() methods.

        Parameters
        ----------

        Returns
        -------

        """
        # Start/stop the protocol:
        if not self.protocol_runner.running:
            self.progress_bar.setValue(0)
            self.sig_start_protocol.emit()
        else:
            self.sig_stop_protocol.emit()

    def toggle_icon(self):
        self.toggleStatus.flip_icon(self.protocol_runner.running)
        self.update_progress()

    def update_progress(self):
        """Update progress bar"""

        # if self._update_duration_i == 0:
        #   pass
        # self.protocol_runner.duration = self.protocol_runner.get_duration()
        self._update_duration_i = (
            self._update_duration_i + 1
        ) % self.update_duration_each

        self.progress_bar.setMaximum(int(self.protocol_runner.duration))
        self.progress_bar.setValue(int(self.protocol_runner.t))

        rem = ceil(self.protocol_runner.duration - self.protocol_runner.t)
        rem_min = int(floor(rem / 60))
        time_info = "{}/{}s ({}:{} remaining)".format(
            int(self.protocol_runner.t),
            int(self.protocol_runner.duration),
            rem_min,
            int(rem - rem_min * 60),
        )

        # If experiment started, add expected end time:
        if self.protocol_runner.running:
            exp_end_time = self.protocol_runner.experiment.t0 + datetime.timedelta(
                seconds=self.protocol_runner.duration
            )
            time_info += " - Ending at {}:{}:{}".format(
                exp_end_time.hour, exp_end_time.minute, exp_end_time.second
            )

        self.progress_bar.setFormat(time_info)
Пример #4
0
    def __init__(self, *args, experiment=None, **kwargs):
        """
        :param experiment: experiment to which this belongs
                           (:class:Experiment <stytra.Experiment> object)
        """

        super().__init__(*args, **kwargs)

        self.experiment = experiment
        if experiment is not None:
            self.camera = experiment.camera
            experiment.gui_timer.timeout.connect(self.retrieve_image)
        else:
            self.gui_timer = QTimer()
            self.gui_timer.setSingleShot(False)

        self.control_params = self.experiment.camera_state

        # Create the layout for the camera view:
        self.camera_display_widget = pg.GraphicsLayoutWidget()

        # Display area for showing the camera image:
        self.display_area = pg.ViewBox(lockAspect=1, invertY=False)
        self.display_area.setRange(
            QRectF(0, 0, 640, 480), update=True, disableAutoRange=True
        )
        self.scale = 640

        self.display_area.invertY(True)
        # Image to which the frame will be set, initially black:
        self.image_item = pg.ImageItem()
        self.image_item.setImage(np.zeros((640, 480), dtype=np.uint8))
        self.display_area.addItem(self.image_item)

        self.camera_display_widget.addItem(self.display_area)

        # Queue of frames coming from the camera
        if hasattr(experiment, "frame_dispatcher"):
            self.frame_queue = self.experiment.frame_dispatcher.gui_queue
        else:
            self.frame_queue = self.camera.frame_queue

        # Queue of control parameters for the camera:
        self.control_queue = self.camera.control_queue
        self.camera_rotation = self.camera.rotation

        self.layout = QVBoxLayout()
        self.layout.setContentsMargins(0, 0, 0, 0)

        self.layout.addWidget(self.camera_display_widget)

        self.layout_control = QHBoxLayout()
        self.layout_control.setContentsMargins(10, 0, 10, 10)

        self.btn_pause = ControlToggleIcon(
            self.experiment.camera_state,
            "paused",
            icon_on=get_icon("play"),
            icon_off=get_icon("pause"),
            action_off="Pause",
            action_on="Play",
        )
        self.layout_control.addWidget(self.btn_pause)

        if hasattr(self.experiment.camera_state, "replay"):
            self.experiment.camera_state.replay = False

            self.btn_rewind = ControlToggleIcon(
                self.experiment.camera_state,
                "replay",
                icon_on=get_icon("rewind"),
                action_off="Resume",
                action_on="Replay",
            )
            self.layout_control.addWidget(self.btn_rewind)

        if self.control_queue is not None:
            self.btn_camera_param = IconButton(
                icon_name="edit_camera", action_name="Configure camera"
            )
            self.btn_camera_param.clicked.connect(self.show_params_gui)
            self.layout_control.addWidget(self.btn_camera_param)

        self.btn_capture = IconButton(
            icon_name="camera_flash", action_name="Capture frame"
        )
        self.btn_capture.clicked.connect(self.save_image)
        self.layout_control.addWidget(self.btn_capture)

        self.btn_autorange = ToggleIconButton(
            icon_off="autoscale", icon_on="autoscaleOFF", action_on="Autoscale"
        )
        self.layout_control.addWidget(self.btn_autorange)

        self.layout.addLayout(self.layout_control)
        self.current_image = None

        self.setLayout(self.layout)
        self.current_frame_time = None

        self.param_widget = None
Пример #5
0
class CameraViewWidget(QWidget):
    """A widget to show images from a frame source and display the camera controls.


    Parameters
    ----------

    Returns
    -------

    """

    def __init__(self, *args, experiment=None, **kwargs):
        """
        :param experiment: experiment to which this belongs
                           (:class:Experiment <stytra.Experiment> object)
        """

        super().__init__(*args, **kwargs)

        self.experiment = experiment
        if experiment is not None:
            self.camera = experiment.camera
            experiment.gui_timer.timeout.connect(self.retrieve_image)
        else:
            self.gui_timer = QTimer()
            self.gui_timer.setSingleShot(False)

        self.control_params = self.experiment.camera_state

        # Create the layout for the camera view:
        self.camera_display_widget = pg.GraphicsLayoutWidget()

        # Display area for showing the camera image:
        self.display_area = pg.ViewBox(lockAspect=1, invertY=False)
        self.display_area.setRange(
            QRectF(0, 0, 640, 480), update=True, disableAutoRange=True
        )
        self.scale = 640

        self.display_area.invertY(True)
        # Image to which the frame will be set, initially black:
        self.image_item = pg.ImageItem()
        self.image_item.setImage(np.zeros((640, 480), dtype=np.uint8))
        self.display_area.addItem(self.image_item)

        self.camera_display_widget.addItem(self.display_area)

        # Queue of frames coming from the camera
        if hasattr(experiment, "frame_dispatcher"):
            self.frame_queue = self.experiment.frame_dispatcher.gui_queue
        else:
            self.frame_queue = self.camera.frame_queue

        # Queue of control parameters for the camera:
        self.control_queue = self.camera.control_queue
        self.camera_rotation = self.camera.rotation

        self.layout = QVBoxLayout()
        self.layout.setContentsMargins(0, 0, 0, 0)

        self.layout.addWidget(self.camera_display_widget)

        self.layout_control = QHBoxLayout()
        self.layout_control.setContentsMargins(10, 0, 10, 10)

        self.btn_pause = ControlToggleIcon(
            self.experiment.camera_state,
            "paused",
            icon_on=get_icon("play"),
            icon_off=get_icon("pause"),
            action_off="Pause",
            action_on="Play",
        )
        self.layout_control.addWidget(self.btn_pause)

        if hasattr(self.experiment.camera_state, "replay"):
            self.experiment.camera_state.replay = False

            self.btn_rewind = ControlToggleIcon(
                self.experiment.camera_state,
                "replay",
                icon_on=get_icon("rewind"),
                action_off="Resume",
                action_on="Replay",
            )
            self.layout_control.addWidget(self.btn_rewind)

        if self.control_queue is not None:
            self.btn_camera_param = IconButton(
                icon_name="edit_camera", action_name="Configure camera"
            )
            self.btn_camera_param.clicked.connect(self.show_params_gui)
            self.layout_control.addWidget(self.btn_camera_param)

        self.btn_capture = IconButton(
            icon_name="camera_flash", action_name="Capture frame"
        )
        self.btn_capture.clicked.connect(self.save_image)
        self.layout_control.addWidget(self.btn_capture)

        self.btn_autorange = ToggleIconButton(
            icon_off="autoscale", icon_on="autoscaleOFF", action_on="Autoscale"
        )
        self.layout_control.addWidget(self.btn_autorange)

        self.layout.addLayout(self.layout_control)
        self.current_image = None

        self.setLayout(self.layout)
        self.current_frame_time = None

        self.param_widget = None

    def retrieve_image(self):
        """Update displayed frame while emptying frame source queue. This is done
        through a while loop that takes all available frames at every update.
        
        # TODO fix this somehow?
        
        **Important!** if the input queue is too fast this will produce an
        infinite loop and block the interface!

        Parameters
        ----------

        Returns
        -------

        """

        first = True
        while True:
            try:
                # In this way, the frame displayed is actually the most
                # recent one added to the queue, as a queue is FILO:
                if first:
                    qr = self.frame_queue.get(timeout=0.0001)
                    self.current_image = qr[-1]
                    self.current_frame_time = qr[0]
                    # first = False
                else:
                    # Else, get to free the queue:
                    _, _ = self.frame_queue.get(timeout=0.001)
            except Empty:
                break

        # Once obtained current image, display it:
        if self.isVisible():
            if self.current_image is not None:
                if self.current_image.shape[0] != self.scale:
                    self.scale = self.current_image.shape[0]
                    self.scale_changed()
                    self.display_area.setRange(
                        QRectF(
                            0,
                            0,
                            self.current_image.shape[1],
                            self.current_image.shape[0],
                        ),
                        update=True,
                        disableAutoRange=True,
                    )
                self.image_item.setImage(
                    self.current_image, autoLevels=self.btn_autorange.isChecked()
                )

    def scale_changed(self):
        self.display_area.setRange(
            QRectF(0, 0, self.current_image.shape[1], self.current_image.shape[0]),
            update=True,
            disableAutoRange=True,
        )

    def save_image(self, name=None):
        """Save a frame to the current directory."""
        if name is None or not name:
            timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
            name = self.experiment.filename_base() + timestamp + "_img.png"
        imsave(name, self.image_item.image)

    def show_params_gui(self):
        """ """
        self.param_widget = ParameterGui(self.control_params)
        self.param_widget.show()
Пример #6
0
class ExperimentWindow(QMainWindow):
    """Window for controlling a simple experiment including only a monitor
    the relative controls and the buttons for data_log and protocol control.
    All widgets objects are created and connected in the `__init__` and then added
    ti the GUI in the `construct_ui` method

    Parameters
    ----------
    experiment : `Experiment <stytra.experiments.Experiment>` object
        experiment for which the window is built.

    Returns
    -------

    """
    def __init__(self, experiment, **kwargs):
        """ """
        super().__init__(**kwargs)
        self.experiment = experiment

        self.setWindowTitle("Stytra | " +
                            pretty_name(type(experiment.protocol).name))

        self.docks = dict()

        self.toolbar_control = ProtocolControlToolbar(
            experiment.protocol_runner, self)
        self.toolbar_control.setObjectName("toolbar")

        # Connect signals from the protocol_control:
        self.toolbar_control.sig_start_protocol.connect(
            experiment.start_protocol)
        self.toolbar_control.sig_stop_protocol.connect(experiment.end_protocol)

        self.btn_metadata = IconButton(icon_name="edit_fish",
                                       action_name="Edit metadata")
        self.btn_metadata.clicked.connect(self.show_metadata_gui)
        self.toolbar_control.addWidget(self.btn_metadata)

        self.act_folder = self.toolbar_control.addAction("Save in {}".format(
            self.experiment.base_dir))
        self.act_folder.triggered.connect(self.change_folder_gui)

        if self.experiment.database is not None:
            self.chk_db = ToggleIconButton(
                action_on="Use DB",
                icon_on="dbON",
                icon_off="dbOFF",
                on=self.experiment.use_db,
            )
            self.chk_db.toggled.connect(self.toggle_db)
            self.toolbar_control.addWidget(self.chk_db)

        if experiment.trigger is not None:
            self.chk_scope = QCheckBox("Wait for trigger signal")

        self.logger = QPlainTextEditLogger()
        self.experiment.logger.addHandler(self.logger)

        self.status_display = StatusMessageDisplay()
        self.statusBar().addWidget(self.status_display)

        self.plot_framerate = MultiFrameratesWidget()
        self.plot_framerate.add_framerate(
            self.experiment.protocol_runner.framerate_acc)

        self.metadata_win = None

    def change_folder_gui(self):
        """Open dialog window to specify a new saving directory."""
        folder = QFileDialog.getExistingDirectory(
            caption="Results folder", directory=self.experiment.base_dir)
        print(folder)
        if folder is not None:
            self.experiment.base_dir = folder
            self.act_folder.setText("Save in {}".format(
                self.experiment.base_dir))

    def show_metadata_gui(self):
        """Open Param GUI to control general experiment and animal metadata."""
        # Create widget, horizontal layout
        self.metadata_win = QWidget()
        self.metadata_win.setLayout(QHBoxLayout())
        # Add metadata widgets to the main one
        self.metadata_win.layout().addWidget(
            ParameterGui(self.experiment.metadata))
        self.metadata_win.layout().addWidget(
            ParameterGui(self.experiment.metadata_animal))
        self.metadata_win.show()

    def add_dock(self, item: QDockWidget):
        """Adding a new DockWidget updating the docks dictionary."""
        self.docks[item.objectName()] = item

    def construct_ui(self):
        """UI construction function."""
        self.addToolBar(Qt.TopToolBarArea, self.toolbar_control)

        log_dock = QDockWidget("Log", self)
        log_dock.setObjectName("dock_log")
        log_dock.setWidget(self.logger.widget)
        self.add_dock(log_dock)
        self.addDockWidget(Qt.RightDockWidgetArea, log_dock)

        dockFramerate = QDockWidget("Frame rates", self)
        dockFramerate.setWidget(self.plot_framerate)
        dockFramerate.setObjectName("dock_framerates")
        self.addDockWidget(Qt.RightDockWidgetArea, dockFramerate)
        self.add_dock(dockFramerate)

        if self.experiment.trigger is not None:
            self.toolbar_control.addWidget(self.chk_scope)

        self.experiment.gui_timer.timeout.connect(self.plot_framerate.update)

        self.toolbar_control.setObjectName("toolbar_control")
        self.setCentralWidget(None)

    def write_log(self, msg):
        """Write something in the log window."""
        self.log_widget.textCursor().appendPlainText(msg)

    def toggle_db(self, tg):
        """Toggle database button."""
        if self.chk_db.isChecked():
            self.experiment.use_db = True
        else:
            self.experiment.use_db = False

    def closeEvent(self, *args, **kwargs):
        """

        Parameters
        ----------
        *args :

        **kwargs :


        Returns
        -------

        """
        self.experiment.wrap_up()