Example #1
0
    def __init__(self, state: State, timer):
        super().__init__()
        self.state = state
        self.timer = timer
        self.setLayout(QVBoxLayout())

        self.wid_save_options = ParameterGui(state.save_settings)
        self.save_location_button = QPushButton()

        self.manual_duration_chk = QCheckBox("Triggered experiment")

        self.wid_manual_duration = ParameterGui(self.state.trigger_settings)

        self.layout().addWidget(self.wid_save_options)
        self.layout().addWidget(self.save_location_button)
        self.layout().addWidget(self.wid_manual_duration)
        self.layout().addWidget(self.manual_duration_chk)

        self.set_locationbutton()

        self.save_location_button.clicked.connect(self.set_save_location)
        self.manual_duration_chk.stateChanged.connect(self.update_triggered_option)
        self.state.trigger_settings.sig_param_changed.connect(
            self.state.send_manual_duration
        )

        self.manual_duration_chk.setChecked(True)
Example #2
0
class SaveWidget(QWidget):
    def __init__(self, state: State, timer):
        super().__init__()
        self.state = state
        self.timer = timer
        self.setLayout(QVBoxLayout())

        self.wid_save_options = ParameterGui(state.save_settings)
        self.save_location_button = QPushButton()

        self.manual_duration_chk = QCheckBox("Triggered experiment")

        self.wid_manual_duration = ParameterGui(self.state.trigger_settings)

        self.layout().addWidget(self.wid_save_options)
        self.layout().addWidget(self.save_location_button)
        self.layout().addWidget(self.wid_manual_duration)
        self.layout().addWidget(self.manual_duration_chk)

        self.set_locationbutton()

        self.save_location_button.clicked.connect(self.set_save_location)
        self.manual_duration_chk.stateChanged.connect(self.update_triggered_option)
        self.state.trigger_settings.sig_param_changed.connect(
            self.state.send_manual_duration
        )

        self.manual_duration_chk.setChecked(True)

    def set_save_location(self):
        save_dir = QFileDialog.getExistingDirectory()
        self.state.save_settings.save_dir = save_dir
        self.set_locationbutton()

    def set_locationbutton(self):
        pathtext = self.state.save_settings.save_dir
        # check if there is a stack in this location
        if (Path(pathtext) / "original" / "stack_metadata.json").is_file():
            self.save_location_button.setText("Overwrite " + pathtext)
            self.save_location_button.setStyleSheet(
                "background-color:#b5880d; border-color:#fcc203"
            )
            self.state.save_settings.overwrite_save_folder = 1
        else:
            self.save_location_button.setText("Save in " + pathtext)
            self.save_location_button.setStyleSheet("")
            self.state.save_settings.overwrite_save_folder = 0

    def update_triggered_option(self, is_checked):
        if is_checked:
            self.wid_manual_duration.setEnabled(False)
            self.state.trigger_settings.is_triggered = True
            self.state.set_trigger_mode(True)
        else:
            self.wid_manual_duration.setEnabled(True)
            self.state.trigger_settings.is_triggered = False
            self.state.set_trigger_mode(False)
Example #3
0
 def show_metadata_gui(self):
     """ """
     self.metadata_win = QWidget()
     self.metadata_win.setLayout(QHBoxLayout())
     self.metadata_win.layout().addWidget(
         ParameterGui(self.experiment.metadata))
     self.metadata_win.layout().addWidget(
         ParameterGui(self.experiment.metadata_animal))
     self.metadata_win.show()
Example #4
0
 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()
Example #5
0
    def __init__(self, state, wid_display, timer):
        super().__init__()
        self.wid_display = wid_display
        self.roi = wid_display.roi
        self.roi_state = RoiState.FULL
        self.state = state
        self.state.camera_settings.sig_param_changed.connect(
            self.update_on_bin_change)
        self.timer = timer

        self.camera_msg_queue = Queue()

        self.sensor_resolution = self.wid_display.max_sensor_resolution

        self.full_size = True
        self.current_binning = conf["camera"]["default_binning"]

        self.wid_camera_settings = ParameterGui(self.state.camera_settings)

        self.setLayout(QVBoxLayout())

        self.btn_roi = QPushButton(ROI_TEXTS[self.roi_state])
        self.btn_cancel_roi = QPushButton("Cancel")
        self.btn_cancel_roi.hide()

        self.layout().addWidget(self.wid_camera_settings)
        self.layout().addWidget(self.btn_roi)
        self.layout().addWidget(self.btn_cancel_roi)

        self.btn_roi.clicked.connect(self.iterate_roi_state)
        self.btn_cancel_roi.clicked.connect(self.cancel_roi_selection)
Example #6
0
    def __init__(self, state: ExperimentState):
        self.state = state
        super().__init__()
        self.scanning_layout = QVBoxLayout()

        self.scanning_settings_gui = ParameterGui(self.state.scanning_settings)
        self.scanning_calc = CalculatedParameterDisplay()
        self.pause_button = QPushButton()
        self.pause_button.clicked.connect(self.toggle_pause)

        self.scanning_layout.addWidget(self.scanning_settings_gui)
        self.scanning_layout.addWidget(self.scanning_calc)
        self.scanning_layout.addWidget(self.pause_button)
        self.setLayout(self.scanning_layout)

        self.state.sig_scanning_changed.connect(self.update_calc_display)
        self.update_button()
Example #7
0
    def __init__(self, state, timer):
        super().__init__()
        self.state = state
        self.timer = timer
        self.setLayout(QVBoxLayout())
        self.wid_volume = ParameterGui(state.volume_setting)
        self.chk_pause = QCheckBox("Pause after experiment")

        self.wid_wave = WaveformWidget(timer=self.timer, state=self.state)
        self.wid_collapsible_wave = CollapsibleWidget(
            child=self.wid_wave, name="Piezo impulse-response waveform")
        self.wid_collapsible_wave.toggle_collapse()

        self.layout().addWidget(self.wid_volume)
        self.layout().addWidget(self.chk_pause)
        self.layout().addWidget(self.wid_collapsible_wave)

        self.chk_pause.clicked.connect(self.change_pause_status)

        self.chk_pause.click()
Example #8
0
    def open_tracking_params_tree(self):
        """ """
        self.track_params_wnd = QWidget()
        self.track_params_wnd.setLayout(QVBoxLayout())
        if hasattr(self.experiment, "pipeline"):
            for paramsname, paramspar in self.experiment.pipeline.all_params.items(
            ):
                if (paramsname == "diagnostics" or paramsname == "reset"
                        or len(paramspar.params.items()) == 0):
                    continue
                self.track_params_wnd.layout().addWidget(
                    QLabel(paramsname.replace("/", "→")))
                self.track_params_wnd.layout().addWidget(
                    ParameterGui(paramspar))

        self.track_params_wnd.layout().addWidget(
            ControlButton(self.experiment.pipeline.all_params["reset"],
                          "reset"))

        self.track_params_wnd.setWindowTitle("Tracking parameters")

        self.track_params_wnd.show()
Example #9
0
    def __init__(self, state, timer):
        super().__init__()
        self.state = state

        self.main_layout = QHBoxLayout()

        self.lbl_text = QLabel("Light source")

        self.btn_off = QPushButton("ON")
        self.btn_off.clicked.connect(self.toggle)

        self.main_layout.addWidget(self.lbl_text)
        self.main_layout.addWidget(self.btn_off)
        self.wid_settings = ParameterGui(self.state.light_source_settings)
        self.main_layout.addWidget(self.wid_settings)

        self.main_layout.setContentsMargins(0, 0, 0, 0)

        self.setLayout(self.main_layout)
        self.laser_on = False
        self.previous_current = self.state.light_source_settings.intensity
        timer.timeout.connect(self.update_current)
Example #10
0
    def __init__(self, state: ExperimentState):
        super().__init__()
        self.state = state
        self.experiment_settings_gui = ParameterGui(state.experiment_settings)
        self.save_location_button = QPushButton()
        self.set_locationbutton()
        self.save_location_button.clicked.connect(self.set_save_location)
        self.startstop_button = QPushButton()
        self.set_saving()
        self.chk_pause = QCheckBox("Pause after experiment")
        self.stack_progress = QProgressBar()
        self.plane_progress = QProgressBar()
        self.plane_progress.setFormat("Frame %v of %m")
        self.stack_progress.setFormat("Plane %v of %m")
        self.startstop_button.clicked.connect(self.toggle_start)

        self.setLayout(QVBoxLayout())
        self.layout().addWidget(self.experiment_settings_gui)
        self.layout().addWidget(self.save_location_button)
        self.layout().addWidget(self.startstop_button)
        self.layout().addWidget(self.chk_pause)
        self.layout().addWidget(self.plane_progress)
        self.layout().addWidget(self.stack_progress)
Example #11
0
 def __init__(self, state):
     super().__init__()
     self.state = state
     self.setLayout(QVBoxLayout())
     self.wid_planar = ParameterGui(state.planar_setting)
     self.layout().addWidget(self.wid_planar)
Example #12
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()
Example #13
0
class ViewingWidget(QWidget):
    """Central widget that displays the images/volumes streamed from the camera, and progress bar.
    Internally, the display is powered via a Napari viewer.
    The get_stuff methods replace properties, which seem to be an issue for children of QWidget when
    used in the __init__.

    Parameters
    ----------
    state : State object
        Main state.
    timer : QTimer
        Timer from the main GUI.
    """

    _DELAY_REFRESH_COUNT = 10

    def __init__(self, state: State, timer: QTimer, style: str):
        super().__init__()
        self.state = state
        timer.timeout.connect(self.refresh)

        # Note: here we are assuming that the camera has a square sensor, and resolution is
        # described by only one number (most scientific camera are)
        if conf["scopeless"]:
            self.max_sensor_resolution = [256, 256]
        else:
            self.max_sensor_resolution = conf["camera"][
                "max_sensor_resolution"]

        self.noise_subtraction_set = False
        self.count_from_change = None
        self.refresh_display = True
        self.is_first_frame = True
        self.auto_contrast = True

        s = self.get_fullframe_size()
        self.image_shape = (1, s, s)

        self.viewer = napari.Viewer(show=False)
        # setting napari style to sashimi's
        self.viewer.window.qt_viewer.setStyleSheet(style)

        # Add image layer that will be used to show frames/volumes:
        self.frame_layer = self.viewer.add_image(
            np.zeros([
                1,
            ] + self.max_sensor_resolution),
            blending="translucent",
            name="frame_layer",
        )

        # Add square ROI of size max image size:
        self.roi = self.viewer.add_shapes(
            [np.array([[0, 0], [s[0], 0], [s[0], s[1]], [0, s[1]]])],
            blending="translucent",
            face_color="transparent",
            face_contrast_limits=(0, 0),
            opacity=1,
            visible=False,
            name="roi_layer",
        )

        self.main_layout = QVBoxLayout()
        self.bottom_layout = QHBoxLayout()

        self.viewer.window.qt_viewer.viewerButtons.consoleButton.hide()
        self.viewer.window.qt_viewer.viewerButtons.rollDimsButton.hide()
        self.viewer.window.qt_viewer.viewerButtons.gridViewButton.hide()
        self.viewer.window.qt_viewer.viewerButtons.transposeDimsButton.hide()
        self.viewer.window.qt_viewer.viewerButtons.resetViewButton.setText(
            "Reset view")

        self.ndisplay_button = self.viewer.window.qt_viewer.viewerButtons.ndisplayButton
        self.ndisplay_button.setText("3D mode")
        self.ndisplay_button.clicked.connect(self.toggle_ndims)

        self.viewer.dims.events.connect(self.update_current_plane)

        self.bottom_layout.addWidget(
            self.viewer.window.qt_viewer.viewerButtons)

        self.auto_contrast_chk = QCheckBox("Autoadjust contrast")

        self.contrast_range = ContrastSettings()
        self.wid_contrast_range = ParameterGui(self.contrast_range)

        self.bottom_layout.addWidget(self.auto_contrast_chk)
        self.bottom_layout.addWidget(self.wid_contrast_range)

        self.bottom_layout.addStretch()

        self.main_layout.addWidget(self.viewer.window.qt_viewer)
        self.main_layout.addLayout(self.bottom_layout)
        self.setLayout(self.main_layout)

        # Connect changes of camera and laser to contrast reset:
        self.auto_contrast_chk.stateChanged.connect(self.update_auto_contrast)
        self.contrast_range.sig_param_changed.connect(self.set_manual_contrast)

        self.auto_contrast_chk.setChecked(True)

        self.state.camera_settings.sig_param_changed.connect(
            self.launch_delayed_contrast_reset)
        self.state.light_source_settings.sig_param_changed.connect(
            self.launch_delayed_contrast_reset)
        self.viewer.window.qt_viewer.viewerButtons.resetViewButton.pressed.connect(
            self.reset_contrast)

    def get_fullframe_size(self):
        """Maximum size of the image at current binning. As stated above, we assume square sensors."""
        binning = int(self.state.camera_settings.binning)
        return [r // binning for r in self.max_sensor_resolution]

    @property
    def voxel_size(self):
        return get_voxel_size(
            self.state.volume_setting,
            self.state.camera_settings,
        )

    def refresh(self) -> None:
        """Main refresh loop called by timeout of the main timer."""
        self.refresh_image()

        self.check_noise_subtraction_state()

        # This pattern with the counting is required to update the image range with some delay,
        # as the first received afterwards might still be the wrong one when changing exposure or noise subtraction
        if self.count_from_change is not None:
            self.count_from_change += 1
            if self.count_from_change == self._DELAY_REFRESH_COUNT:
                self.reset_contrast()
                self.count_from_change = None

    def check_noise_subtraction_state(self):
        """If we toggled noise subtraction, reset contrast."""
        noise_subtraction_set = self.state.noise_subtraction_active.is_set()
        if noise_subtraction_set != self.noise_subtraction_set:
            self.noise_subtraction_set = noise_subtraction_set
            self.launch_delayed_contrast_reset()

    def launch_delayed_contrast_reset(self):
        """By setting this from None to 0, we schedule a delayed contrast reset.
        The actual counting happens in the main refresh, connected to the timer.
        """
        self.count_from_change = 0

    def refresh_image(self):
        current_image = self.state.get_volume()
        if current_image is None:
            return

        # If not volumetric or out of range, reset indexes:
        if current_image.shape[0] == 1:
            self.viewer.dims.reset()
        self.frame_layer.data = current_image
        # self.frame_layer.scale = [self.voxel_size[0] / self.voxel_size[1], 1.0, 1.0]

        # Check if anything changed in the image shape, which would mean that changes of the contrast
        # are required (in case a parameter update was missed).
        if self.is_first_frame or self.image_shape != current_image.shape:
            self.reset_contrast()
            self.viewer.reset_view()

        self.image_shape = current_image.shape
        self.is_first_frame = False

        # Keep current plane in synch when the number of planes changes
        self.viewer.dims.set_current_step(0, self.state.current_plane)

    def toggle_ndims(self):
        """We set the scale only if we are in 3D mode, otherwise there can be funny problems with
        the image slider in the 2D view.
        Hopefully all of this will be improved in newer versions of Napari
        """
        if self.ndisplay_button.isChecked():
            self.frame_layer.scale = [
                self.voxel_size[0] / self.voxel_size[1], 1.0, 1.0
            ]
        else:
            self.frame_layer.scale = [1.0, 1.0, 1.0]

    def update_current_plane(self, _):
        self.state.current_plane = self.viewer.dims.current_step[0]

    def reset_contrast(self):
        if self.auto_contrast:
            self.frame_layer.reset_contrast_limits()
        else:
            self.set_manual_contrast()

    def update_auto_contrast(self, is_checked):
        if is_checked:
            self.wid_contrast_range.setEnabled(False)
            self.auto_contrast = True
            self.reset_contrast()
        else:
            self.wid_contrast_range.setEnabled(True)
            self.auto_contrast = False
            self.set_manual_contrast()

    def set_manual_contrast(self):
        self.frame_layer.contrast_limits = self.contrast_range.contrast_range
Example #14
0
    def __init__(self, state: State, timer: QTimer, style: str):
        super().__init__()
        self.state = state
        timer.timeout.connect(self.refresh)

        # Note: here we are assuming that the camera has a square sensor, and resolution is
        # described by only one number (most scientific camera are)
        if conf["scopeless"]:
            self.max_sensor_resolution = [256, 256]
        else:
            self.max_sensor_resolution = conf["camera"][
                "max_sensor_resolution"]

        self.noise_subtraction_set = False
        self.count_from_change = None
        self.refresh_display = True
        self.is_first_frame = True
        self.auto_contrast = True

        s = self.get_fullframe_size()
        self.image_shape = (1, s, s)

        self.viewer = napari.Viewer(show=False)
        # setting napari style to sashimi's
        self.viewer.window.qt_viewer.setStyleSheet(style)

        # Add image layer that will be used to show frames/volumes:
        self.frame_layer = self.viewer.add_image(
            np.zeros([
                1,
            ] + self.max_sensor_resolution),
            blending="translucent",
            name="frame_layer",
        )

        # Add square ROI of size max image size:
        self.roi = self.viewer.add_shapes(
            [np.array([[0, 0], [s[0], 0], [s[0], s[1]], [0, s[1]]])],
            blending="translucent",
            face_color="transparent",
            face_contrast_limits=(0, 0),
            opacity=1,
            visible=False,
            name="roi_layer",
        )

        self.main_layout = QVBoxLayout()
        self.bottom_layout = QHBoxLayout()

        self.viewer.window.qt_viewer.viewerButtons.consoleButton.hide()
        self.viewer.window.qt_viewer.viewerButtons.rollDimsButton.hide()
        self.viewer.window.qt_viewer.viewerButtons.gridViewButton.hide()
        self.viewer.window.qt_viewer.viewerButtons.transposeDimsButton.hide()
        self.viewer.window.qt_viewer.viewerButtons.resetViewButton.setText(
            "Reset view")

        self.ndisplay_button = self.viewer.window.qt_viewer.viewerButtons.ndisplayButton
        self.ndisplay_button.setText("3D mode")
        self.ndisplay_button.clicked.connect(self.toggle_ndims)

        self.viewer.dims.events.connect(self.update_current_plane)

        self.bottom_layout.addWidget(
            self.viewer.window.qt_viewer.viewerButtons)

        self.auto_contrast_chk = QCheckBox("Autoadjust contrast")

        self.contrast_range = ContrastSettings()
        self.wid_contrast_range = ParameterGui(self.contrast_range)

        self.bottom_layout.addWidget(self.auto_contrast_chk)
        self.bottom_layout.addWidget(self.wid_contrast_range)

        self.bottom_layout.addStretch()

        self.main_layout.addWidget(self.viewer.window.qt_viewer)
        self.main_layout.addLayout(self.bottom_layout)
        self.setLayout(self.main_layout)

        # Connect changes of camera and laser to contrast reset:
        self.auto_contrast_chk.stateChanged.connect(self.update_auto_contrast)
        self.contrast_range.sig_param_changed.connect(self.set_manual_contrast)

        self.auto_contrast_chk.setChecked(True)

        self.state.camera_settings.sig_param_changed.connect(
            self.launch_delayed_contrast_reset)
        self.state.light_source_settings.sig_param_changed.connect(
            self.launch_delayed_contrast_reset)
        self.viewer.window.qt_viewer.viewerButtons.resetViewButton.pressed.connect(
            self.reset_contrast)
Example #15
0
 def __init__(self, state):
     super().__init__()
     self.state = state
     self.setLayout(QVBoxLayout())
     self.wid_singleplane = ParameterGui(state.single_plane_settings)
     self.layout().addWidget(self.wid_singleplane)
Example #16
0
 def show_params_gui(self):
     """ """
     self.param_widget = ParameterGui(self.control_params)
     self.param_widget.show()
Example #17
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)
Example #18
0
 def edit_params(self):
     self.wnd_params = ParameterGui(self.detection_params)
     self.wnd_params.show()
Example #19
0
    app = QApplication([])

    class TestParametrized1(ParametrizedWidget):
        def __init__(self, **kwargs):
            super().__init__(name="a/gino", **kwargs)
            self.random = 5
            self.a = Param(1)
            self.b = Param(2.0)
            self.c = Param(5)
            self.a_list = Param("a", ["a", "b", "c"])
            self.sig_param_changed.connect(self.update_param)
            # self.show()

        def update_param(self):
            """Calculate mm/px from calibrator length"""
            if self.c is not None:
                self.block_signal = True
                self.c = self.a / self.b
                self.block_signal = False

    class TestParametrized2(ParametrizedQt):
        def __init__(self, **kwargs):
            super().__init__(name="b/c/pino", **kwargs)
            self.an_int = Param(4)
            self.a_float = Param(1.0, (-1.0, 10.0))

    p = ParameterGui(TestParametrized1())
    p.show()
    app.exec_()
Example #20
0
 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()
Example #21
0
class BoutPlot(QWidget):
    """ Plots the last few bouts in normalized coordinates, with fish facing
    to the right.

    """
    def __init__(self,
                 acc: QueueDataAccumulator,
                 i_fish=0,
                 n_bouts=10,
                 n_save_max=300):
        super().__init__()
        self.title = "Bout shape"
        self.acc = acc
        self.bouts = deque()
        self.i_fish = i_fish
        self.processed_index = 0
        self.detection_params = Parametrized(params=dict(
            threshold=Param(0.2, (0.01, 5.0)),
            n_without_crossing=Param(5, (0, 10)),
            pad_before=Param(5, (0, 20)),
            pad_after=Param(5, (0, 20)),
            min_bout_len=Param(1, (1, 30)),
        ))
        self.n_bouts = n_bouts
        self.old_coords = None
        self.i_curve = 0
        self.n_save_max = n_save_max

        self.setLayout(QVBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.btn_editparam = QPushButton("Detection parameters")
        self.btn_editparam.clicked.connect(self.edit_params)
        self.layout().addWidget(self.btn_editparam)
        self.wnd_params = None

        self.vmax = 0
        self.lbl_vmax = QLabel()
        self.layout().addWidget(self.lbl_vmax)

        self.display_widget = pg.GraphicsLayoutWidget()
        self.layout().addWidget(self.display_widget)
        self.vb_display = pg.ViewBox()
        self.vb_display.setAspectLocked(True, 1)
        self.vb_display.setRange(xRange=[-1, 5], disableAutoRange=True)
        self.vb_display.invertY(True)
        self.display_widget.addItem(self.vb_display)

        self.bout_curves = [
            pg.PlotCurveItem(connect="finite") for _ in range(self.n_bouts)
        ]

        self.colors = np.zeros(self.n_bouts)
        self.decay_constant = 0.99

        self.bout_coords = None
        self.bout_state = BoutState(0, 0.0, 0, 0, 0)

        for c in self.bout_curves:
            self.vb_display.addItem(c)

    def edit_params(self):
        self.wnd_params = ParameterGui(self.detection_params)
        self.wnd_params.show()

    def update(self):
        if not self.isVisible():
            return

        current_index = len(self.acc.stored_data)
        if current_index == 0 or current_index < self.processed_index + 2:
            return

        # Pull the new data from the accumulator
        new_coords = np.array(
            self.acc.stored_data[max(self.processed_index, current_index -
                                     self.n_save_max):current_index])
        self.processed_index = current_index

        ix, iy, ith = (self.acc.header_dict["f{:d}_{}".format(
            self.i_fish, var)] for var in ["x", "y", "theta"])

        new_coords = new_coords[:, [ix, iy, ith]]

        # if in the previous refresh we ended up inside a bout, there are still
        # coordinates left to process
        if self.old_coords is not None:
            pre_start = len(self.old_coords)
            new_coords = np.concatenate([self.old_coords, new_coords], 0)
        else:
            pre_start = 0

        vel = np.sum(np.diff(new_coords[:, :2], axis=0)**2, axis=1)

        self.vmax = np.nanmax(vel)
        self.lbl_vmax.setText("max velocity sq {:.1f}".format(self.vmax))

        if self.bout_coords is None:
            self.bout_coords = [new_coords[0, :]]

        new_bout = None
        if self.detection_params.threshold > 0:
            self.bout_coords, bout_finished, self.bout_state = find_bouts_online(
                vel,
                new_coords,
                self.bout_state,
                bout_coords=self.bout_coords,
                shift=pre_start,
                **self.detection_params.params.values,
            )
            if bout_finished:
                new_bout = self.bout_coords
                self.bout_coords = [new_coords[0, :]]

        self.old_coords = new_coords[-self.n_save_max:, :]

        self.colors *= self.decay_constant

        if new_bout and len(new_bout) > 2:
            nb = normalise_bout(np.array(new_bout[1:]))
            self.bout_curves[self.i_curve].setData(x=nb[:, 0], y=nb[:, 1])
            self.colors[self.i_curve] = 255
            self.i_curve = (self.i_curve + 1) % self.n_bouts

        for i_c, (curve, color) in enumerate(zip(self.bout_curves,
                                                 self.colors)):
            col = int(color)
            if col < 10:
                curve.setData(x=[], y=[])
            else:
                curve.setPen((col, col, col))