import logging
import datetime
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (
QLabel,
QWidget,
QHBoxLayout,
QPlainTextEdit,
QMainWindow,
QCheckBox,
QVBoxLayout,
QDockWidget,
QFileDialog,
)
from stytra.gui.monitor_control import ProjectorAndCalibrationWidget
from stytra.gui.multiscope import MultiStreamPlot, FrameratePlot
from stytra.gui.protocol_control import ProtocolControlToolbar
from stytra.gui.camera_display import CameraViewWidget
from stytra.gui.buttons import IconButton, ToggleIconButton
from stytra.gui.status_display import StatusMessageDisplay
from stytra.gui.framerate_viewer import MultiFrameratesWidget
from lightparam.gui import ParameterGui, pretty_name, ControlCombo, ControlButton
[docs]class QPlainTextEditLogger(logging.Handler):
def __init__(self):
super().__init__()
self.widget = QPlainTextEdit()
self.widget.setReadOnly(True)
[docs] def emit(self, record):
msg = "{} {}".format(
datetime.datetime.now().strftime("[%H:%M:%S]"), self.format(record)
)
try:
self.widget.appendPlainText(msg)
except:
pass
[docs]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.
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.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
[docs] def change_folder_gui(self):
folder = QFileDialog.getExistingDirectory(
caption="Results folder", directory=self.experiment.base_dir
)
if folder is not None:
self.experiment.base_dir = folder
self.act_folder.setText("Save in {}".format(self.experiment.base_dir))
[docs] def add_dock(self, item: QDockWidget):
self.docks[item.objectName()] = item
[docs] def construct_ui(self):
""" """
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)
[docs] def write_log(self, msg):
self.log_widget.textCursor().appendPlainText(msg)
[docs] def toggle_db(self, tg):
if self.chk_db.isChecked():
self.experiment.use_db = True
else:
self.experiment.use_db = False
[docs] def closeEvent(self, *args, **kwargs):
"""
Parameters
----------
*args :
**kwargs :
Returns
-------
"""
self.experiment.wrap_up()
[docs]class VisualExperimentWindow(ExperimentWindow):
"""Window for controlling a simple experiment including only a monitor
the relative controls and the buttons for data_log and protocol control.
Parameters
----------
experiment : `Experiment <stytra.experiments.Experiment>` object
experiment for which the window is built.
Returns
-------
"""
def __init__(self, *args, **kwargs):
""" """
super().__init__(*args, **kwargs)
if not self.experiment.offline:
self.widget_projection = ProjectorAndCalibrationWidget(self.experiment)
[docs] def construct_ui(self):
""" """
super().construct_ui()
if not self.experiment.offline:
proj_dock = QDockWidget("Projector configuration", self)
proj_dock.setWidget(self.widget_projection)
proj_dock.setObjectName("dock_projector")
self.add_dock(proj_dock)
self.addDockWidget(Qt.RightDockWidgetArea, proj_dock)
[docs]class CameraExperimentWindow(VisualExperimentWindow):
""" """
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.camera_display = None
if (
hasattr(self.experiment, "pipeline")
and self.experiment.pipeline.display_overlay is not None
):
self.camera_display = self.experiment.pipeline.display_overlay(
experiment=self.experiment
)
else:
self.camera_display = CameraViewWidget(experiment=kwargs["experiment"])
self.plot_framerate.setMaximumHeight(120)
self.status_display.addMessageQueue(self.experiment.camera.message_queue)
[docs] def construct_ui(self):
super().construct_ui()
self.experiment.gui_timer.timeout.connect(self.status_display.refresh)
dockCamera = QDockWidget("Camera", self)
dockCamera.setWidget(self.camera_display)
dockCamera.setObjectName("dock_camera")
self.plot_framerate.add_framerate(self.experiment.acc_camera_framerate)
self.addDockWidget(Qt.LeftDockWidgetArea, dockCamera)
# moving the framerate dock
self.removeDockWidget(self.docks["dock_framerates"])
self.addDockWidget(Qt.LeftDockWidgetArea, self.docks["dock_framerates"])
self.docks["dock_framerates"].setVisible(True)
self.add_dock(dockCamera)
[docs]class DynamicStimExperimentWindow(VisualExperimentWindow):
"""Window for plotting a dynamically varying stimulus.
Parameters
----------
Returns
-------
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.monitoring_widget = QWidget()
self.monitoring_layout = QVBoxLayout()
self.monitoring_widget.setLayout(self.monitoring_layout)
# Stream plot:
self.stream_plot = MultiStreamPlot(experiment=self.experiment)
self.monitoring_layout.addWidget(self.stream_plot)
[docs] def construct_ui(self):
""" """
super().construct_ui()
self.experiment.gui_timer.timeout.connect(self.stream_plot.update)
# TODO put in right place
monitoring_widget = QWidget()
monitoring_widget.setLayout(self.monitoring_layout)
monitoring_dock = QDockWidget("Tracking", self)
monitoring_dock.setWidget(monitoring_widget)
monitoring_dock.setObjectName("monitoring_dock")
self.addDockWidget(Qt.RightDockWidgetArea, monitoring_dock)
self.add_dock(monitoring_dock)
[docs]class TrackingExperimentWindow(CameraExperimentWindow):
"""Window for controlling an experiment where the tail of an
embedded fish is tracked.
Parameters
----------
Returns
-------
"""
def __init__(self, tracking=True, *args, **kwargs):
super().__init__(*args, **kwargs)
# TODO refactor movement detection
self.tracking = tracking
self.monitoring_widget = QWidget()
self.monitoring_layout = QVBoxLayout()
self.monitoring_widget.setLayout(self.monitoring_layout)
self.stream_plot = MultiStreamPlot(experiment=self.experiment)
self.monitoring_layout.addWidget(self.stream_plot)
self.extra_widget = (
self.experiment.pipeline.extra_widget(self.experiment.acc_tracking)
if self.experiment.pipeline.extra_widget is not None
else None
)
# Display dropdown
self.drop_display = ControlCombo(
self.experiment.pipeline.all_params["diagnostics"], "image"
)
if hasattr(self.camera_display, "set_pos_from_tree"):
self.drop_display.control.currentTextChanged.connect(
self.camera_display.set_pos_from_tree
)
# Tracking params button:
self.button_tracking_params = IconButton(
icon_name="edit_tracking", action_name="Change tracking parameters"
)
self.button_tracking_params.clicked.connect(self.open_tracking_params_tree)
self.camera_display.layout_control.addStretch(10)
self.camera_display.layout_control.addWidget(self.drop_display)
self.camera_display.layout_control.addWidget(self.button_tracking_params)
self.track_params_wnd = None
self.status_display.addMessageQueue(
self.experiment.frame_dispatcher.message_queue
)
[docs] def construct_ui(self):
""" """
previous_widget = super().construct_ui()
self.experiment.gui_timer.timeout.connect(self.stream_plot.update)
monitoring_widget = QWidget()
monitoring_widget.setLayout(self.monitoring_layout)
monitoring_dock = QDockWidget("Monitoring", self)
monitoring_dock.setObjectName("dock_monitoring")
monitoring_dock.setWidget(monitoring_widget)
self.addDockWidget(Qt.RightDockWidgetArea, monitoring_dock)
self.add_dock(monitoring_dock)
self.plot_framerate.add_framerate(self.experiment.acc_tracking_framerate)
if self.extra_widget:
self.experiment.gui_timer.timeout.connect(self.extra_widget.update)
dock_extra = QDockWidget(self.extra_widget.title, self)
dock_extra.setObjectName("dock_extra")
dock_extra.setWidget(self.extra_widget)
self.add_dock(dock_extra)
self.addDockWidget(Qt.RightDockWidgetArea, dock_extra)
dock_extra.setVisible(False)
return previous_widget
[docs] 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()