예제 #1
0
    def __init__(self, *args, camera, camera_queue_mb=100, **kwargs):
        """
        :param video_file: if not using a camera, the video file
        file for the test input
        :param kwargs:
        """
        super().__init__(*args, **kwargs)
        if camera.get("video_file", None) is None:
            self.camera = CameraSource(
                camera["type"],
                rotation=camera.get("rotation", 0),
                downsampling=camera.get("downsampling", 1),
                roi=camera.get("roi", (-1, -1, -1, -1)),
                max_mbytes_queue=camera_queue_mb,
                camera_params=camera.get("camera_params", dict()),
            )
            self.camera_state = CameraControlParameters(tree=self.dc)
        else:
            self.camera = VideoFileSource(
                camera["video_file"],
                rotation=camera.get("rotation", 0),
                max_mbytes_queue=camera_queue_mb,
            )
            self.camera_state = VideoControlParameters(tree=self.dc)

        self.acc_camera_framerate = FramerateQueueAccumulator(
            self,
            queue=self.camera.framerate_queue,
            goal_framerate=camera.get("min_framerate", None),
            name="camera",  # TODO implement no goal
        )

        # New parameters are sent with GUI timer:
        self.gui_timer.timeout.connect(self.send_gui_parameters)
        self.gui_timer.timeout.connect(self.acc_camera_framerate.update_list)
예제 #2
0
    def __init__(self,
                 *args,
                 camera: dict,
                 camera_queue_mb: int = 100,
                 recording: Optional[Dict[str, Any]] = None,
                 **kwargs) -> None:
        """
        Parameters
        ----------
        camera
            dictionary containing the parameters for the camera setup (i.e. for offline processing it would contain
            an entry 'video_file' with the path to the video).
        camera_queue_mb
            the maximum size of frames that are kept at once, if the limit is exceeded, frames will be dropped.
        recording
            dictionary containing the parameters for the recording (i.e. to save to an mp4 file, add the 'extension'
            entry with the 'mp4' value). If None, no recording is performed.
        """
        super().__init__(*args, **kwargs)
        if camera.get("video_file", None) is None:
            self.camera = CameraSource(
                camera["type"],
                rotation=camera.get("rotation", 0),
                downsampling=camera.get("downsampling", 1),
                roi=camera.get("roi", (-1, -1, -1, -1)),
                max_mbytes_queue=camera_queue_mb,
                camera_params=camera.get("camera_params", dict()),
            )
            self.camera_state = CameraControlParameters(tree=self.dc)
        else:
            self.camera = VideoFileSource(
                camera["video_file"],
                rotation=camera.get("rotation", 0),
                max_mbytes_queue=camera_queue_mb,
            )
            self.camera_state = VideoControlParameters(tree=self.dc)

        self.acc_camera_framerate = FramerateQueueAccumulator(
            self,
            queue=self.camera.framerate_queue,
            goal_framerate=camera.get("min_framerate", None),
            name="camera",  # TODO implement no goal
        )

        # New parameters are sent with GUI timer:
        self.gui_timer.timeout.connect(self.send_gui_parameters)
        self.gui_timer.timeout.connect(self.acc_camera_framerate.update_list)

        self.recording = recording
        if recording is not None:
            self._setup_recording(
                kbit_framerate=recording.get("kbit_rate", 1000),
                extension=recording["extension"],
            )
예제 #3
0
    def __init__(self, *args, **kwargs):
        """
        :param video_file: if not using a camera, the video file
        file for the test input
        :param kwargs:
        """
        super().__init__(*args, **kwargs)

        self.finished_evt = Event()
        self.saving_evt = Event()

        self.frame_dispatcher = DispatchProcess(self.camera.frame_queue,
                                                self.finished_evt,
                                                self.saving_evt)

        # start frame dispatcher process:
        self.frame_dispatcher.start()

        # Create and connect framerate accumulator:
        self.acc_tracking_framerate = FramerateQueueAccumulator(
            self,
            queue=self.frame_dispatcher.framerate_queue,
            name="tracking",
            goal_framerate=kwargs["camera"].get("min_framerate", None),
        )
        self.gui_timer.timeout.connect(self.acc_tracking_framerate.update_list)

        # self.filename_queue = Queue()

        self.set_id()
        self.video_writer = H5VideoWriter(
            self.frame_dispatcher.output_frame_queue, self.finished_evt,
            self.saving_evt)

        self.video_writer.start()
예제 #4
0
class CameraVisualExperiment(VisualExperiment):
    """General class for Experiment that need to handle a camera.
    It implements a view of frames from the camera in the control GUI, and the
    respective parameters.
    For debugging it can be used with a video read from file with the
    VideoFileSource class.

    Parameters
    ----------

    Returns
    -------

    """

    def __init__(self, *args, camera, camera_queue_mb=100, **kwargs):
        """
        :param video_file: if not using a camera, the video file
        file for the test input
        :param kwargs:
        """
        super().__init__(*args, **kwargs)
        if camera.get("video_file", None) is None:
            self.camera = CameraSource(
                camera["type"],
                rotation=camera.get("rotation", 0),
                downsampling=camera.get("downsampling", 1),
                roi=camera.get("roi", (-1, -1, -1, -1)),
                max_mbytes_queue=camera_queue_mb,
                camera_params=camera.get("camera_params", dict()),
            )
            self.camera_state = CameraControlParameters(tree=self.dc)
        else:
            self.camera = VideoFileSource(
                camera["video_file"],
                rotation=camera.get("rotation", 0),
                max_mbytes_queue=camera_queue_mb,
            )
            self.camera_state = VideoControlParameters(tree=self.dc)

        self.acc_camera_framerate = FramerateQueueAccumulator(
            self,
            queue=self.camera.framerate_queue,
            goal_framerate=camera.get("min_framerate", None),
            name="camera",  # TODO implement no goal
        )

        # New parameters are sent with GUI timer:
        self.gui_timer.timeout.connect(self.send_gui_parameters)
        self.gui_timer.timeout.connect(self.acc_camera_framerate.update_list)

    def reset(self):
        super().reset()
        self.acc_camera_framerate.reset()

    def initialize_plots(self):
        super().initialize_plots()

    def send_gui_parameters(self):
        self.camera.control_queue.put(self.camera_state.params.changed_values())
        self.camera_state.params.acknowledge_changes()

    def start_experiment(self):
        """ """
        self.go_live()
        super().start_experiment()

    def make_window(self):
        """ """
        self.window_main = CameraExperimentWindow(experiment=self)
        self.window_main.construct_ui()
        self.window_main.show()
        self.restore_window_state()
        self.initialize_plots()

    def go_live(self):
        """ """
        sys.excepthook = self.excepthook
        self.camera.start()

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

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

        Returns
        -------

        """
        self.gui_timer.stop()
        super().wrap_up(*args, **kwargs)
        self.camera.kill_event.set()

        for q in [self.camera.frame_queue]:
            q.clear()

        self.camera.join()

    def excepthook(self, exctype, value, tb):
        """

        Parameters
        ----------
        exctype :
            
        value :
            
        tb :
            

        Returns
        -------

        """
        traceback.print_tb(tb)
        print("{0}: {1}".format(exctype, value))
        self.camera.kill_event.set()
        self.camera.join()
예제 #5
0
    def __init__(self, *args, tracking, recording=None, **kwargs):
        """
        :param tracking_method: class with the parameters for tracking (instance
                                of TrackingMethod class, defined in the child);
        :param header_list: headers for the data accumulator (list of strings,
                            defined in the child);
        :param data_name:  name of the data in the final experiment log (defined
                           in the child).
        """

        self.processing_params_queue = Queue()
        self.tracking_output_queue = NamedTupleQueue()
        self.finished_sig = Event()
        super().__init__(*args, **kwargs)
        self.arguments.update(locals())

        self.recording_event = (
            Event() if (recording is not None or recording is False) else None
        )

        self.pipeline_cls = (
            pipeline_dict.get(tracking["method"], None)
            if isinstance(tracking["method"], str)
            else tracking["method"]
        )

        self.frame_dispatcher = TrackingProcess(
            in_frame_queue=self.camera.frame_queue,
            finished_signal=self.camera.kill_event,
            pipeline=self.pipeline_cls,
            processing_parameter_queue=self.processing_params_queue,
            output_queue=self.tracking_output_queue,
            recording_signal=self.recording_event,
            gui_framerate=20,
        )
        if self.pipeline_cls is None:
            raise NameError("The selected tracking method does not exist!")
        self.pipeline = self.pipeline_cls()
        assert isinstance(self.pipeline, Pipeline)
        self.pipeline.setup(tree=self.dc)

        self.acc_tracking = QueueDataAccumulator(
            name="tracking",
            experiment=self,
            data_queue=self.tracking_output_queue,
            monitored_headers=self.pipeline.headers_to_plot,
        )
        self.acc_tracking.sig_acc_init.connect(self.refresh_plots)

        # Data accumulator is updated with GUI timer:
        self.gui_timer.timeout.connect(self.acc_tracking.update_list)

        # Tracking is reset at experiment start:
        self.protocol_runner.sig_protocol_started.connect(self.acc_tracking.reset)

        # start frame dispatcher process:
        self.frame_dispatcher.start()

        est_type = tracking.get("estimator", None)
        if est_type is None:
            est = None
        elif isinstance(est_type, str):
            est = estimator_dict.get(est_type, None)
        else:
            est = est_type

        if est is not None:
            self.estimator_log = EstimatorLog(experiment=self)
            self.estimator = est(
                self.acc_tracking,
                experiment=self,
                **tracking.get("estimator_params", {})
            )
            self.estimator_log.sig_acc_init.connect(self.refresh_plots)
        else:
            self.estimator = None

        self.acc_tracking_framerate = FramerateQueueAccumulator(
            self,
            queue=self.frame_dispatcher.framerate_queue,
            name="tracking",
            goal_framerate=kwargs["camera"].get("min_framerate", None),
        )

        if recording is not None:
            if recording["extension"] == "h5":
                self.frame_recorder = H5VideoWriter(
                    self.filename_base(),
                    self.frame_dispatcher.frame_copy_queue,
                    self.finished_sig,
                    self.recording_event,
                    log_format=self.log_format,
                )
            else:
                self.frame_recorder = StreamingVideoWriter(
                    self.frame_dispatcher.frame_copy_queue,
                    self.finished_sig,
                    self.recording_event,
                    kbit_rate=recording.get("kbit_rate", 1000),
                    log_format=self.log_format,
                )
            self.frame_recorder.start()

        self.gui_timer.timeout.connect(self.acc_tracking_framerate.update_list)
예제 #6
0
class TrackingExperiment(CameraVisualExperiment):
    """Abstract class for an experiment which contains tracking.

    This class is the base for any experiment that tracks behavior (being it
    eyes, tail, or anything else).
    The general purpose of the class is handle a frame dispatcher,
    the relative parameters queue and the output queue.
    
    The frame dispatcher take two input queues:

        - frame queue from the camera;
        - parameters queue from parameter window.
    
    and it puts data in three queues:

        - subset of frames are dispatched to the GUI, for displaying;
        - all the frames, together with the parameters, are dispatched
          to perform tracking;
        - the result of the tracking function, is dispatched to a data
          accumulator for saving or other purposes (e.g. VR control).

    Parameters
    ----------
        tracking: dict
            containing fields:  tracking_method
                                estimator: can be vigor for embedded fish, position
                                    for freely-swimming, or a custom subclass of Estimator

    Returns
    -------

    """

    def __init__(self, *args, tracking, recording=None, **kwargs):
        """
        :param tracking_method: class with the parameters for tracking (instance
                                of TrackingMethod class, defined in the child);
        :param header_list: headers for the data accumulator (list of strings,
                            defined in the child);
        :param data_name:  name of the data in the final experiment log (defined
                           in the child).
        """

        self.processing_params_queue = Queue()
        self.tracking_output_queue = NamedTupleQueue()
        self.finished_sig = Event()
        super().__init__(*args, **kwargs)
        self.arguments.update(locals())

        self.recording_event = (
            Event() if (recording is not None or recording is False) else None
        )

        self.pipeline_cls = (
            pipeline_dict.get(tracking["method"], None)
            if isinstance(tracking["method"], str)
            else tracking["method"]
        )

        self.frame_dispatcher = TrackingProcess(
            in_frame_queue=self.camera.frame_queue,
            finished_signal=self.camera.kill_event,
            pipeline=self.pipeline_cls,
            processing_parameter_queue=self.processing_params_queue,
            output_queue=self.tracking_output_queue,
            recording_signal=self.recording_event,
            gui_framerate=20,
        )
        if self.pipeline_cls is None:
            raise NameError("The selected tracking method does not exist!")
        self.pipeline = self.pipeline_cls()
        assert isinstance(self.pipeline, Pipeline)
        self.pipeline.setup(tree=self.dc)

        self.acc_tracking = QueueDataAccumulator(
            name="tracking",
            experiment=self,
            data_queue=self.tracking_output_queue,
            monitored_headers=self.pipeline.headers_to_plot,
        )
        self.acc_tracking.sig_acc_init.connect(self.refresh_plots)

        # Data accumulator is updated with GUI timer:
        self.gui_timer.timeout.connect(self.acc_tracking.update_list)

        # Tracking is reset at experiment start:
        self.protocol_runner.sig_protocol_started.connect(self.acc_tracking.reset)

        # start frame dispatcher process:
        self.frame_dispatcher.start()

        est_type = tracking.get("estimator", None)
        if est_type is None:
            est = None
        elif isinstance(est_type, str):
            est = estimator_dict.get(est_type, None)
        else:
            est = est_type

        if est is not None:
            self.estimator_log = EstimatorLog(experiment=self)
            self.estimator = est(
                self.acc_tracking,
                experiment=self,
                **tracking.get("estimator_params", {})
            )
            self.estimator_log.sig_acc_init.connect(self.refresh_plots)
        else:
            self.estimator = None

        self.acc_tracking_framerate = FramerateQueueAccumulator(
            self,
            queue=self.frame_dispatcher.framerate_queue,
            name="tracking",
            goal_framerate=kwargs["camera"].get("min_framerate", None),
        )

        if recording is not None:
            if recording["extension"] == "h5":
                self.frame_recorder = H5VideoWriter(
                    self.filename_base(),
                    self.frame_dispatcher.frame_copy_queue,
                    self.finished_sig,
                    self.recording_event,
                    log_format=self.log_format,
                )
            else:
                self.frame_recorder = StreamingVideoWriter(
                    self.frame_dispatcher.frame_copy_queue,
                    self.finished_sig,
                    self.recording_event,
                    kbit_rate=recording.get("kbit_rate", 1000),
                    log_format=self.log_format,
                )
            self.frame_recorder.start()

        self.gui_timer.timeout.connect(self.acc_tracking_framerate.update_list)

    def reset(self):
        super().reset()
        self.acc_tracking_framerate.reset()
        self.acc_tracking.reset()
        if self.estimator is not None:
            self.estimator.reset()
            self.estimator_log.reset()

    def make_window(self):
        self.window_main = TrackingExperimentWindow(experiment=self)
        self.window_main.construct_ui()
        self.initialize_plots()
        self.window_main.show()
        self.restore_window_state()

    def initialize_plots(self):
        super().initialize_plots()
        self.refresh_plots()

    def refresh_plots(self):
        self.window_main.stream_plot.remove_streams()
        self.window_main.stream_plot.add_stream(self.acc_tracking)
        if self.estimator is not None:
            self.window_main.stream_plot.add_stream(self.estimator_log)

            # We display the stimulus log only if we have vigor estimator, meaning 1D closed-loop experiments
            self.window_main.stream_plot.add_stream(self.protocol_runner.dynamic_log)

        if self.stim_plot:  # but also if forced:
            self.window_main.stream_plot.add_stream(self.protocol_runner.dynamic_log)

    def send_gui_parameters(self):
        """Called upon gui timeout, put tracking parameters in the relative
        queue.

        Parameters
        ----------

        Returns
        -------

        """
        super().send_gui_parameters()
        self.processing_params_queue.put(self.pipeline.serialize_changed_params())

    def start_protocol(self):
        # Freeze the plots so the plotting does not interfere with
        # stimulus display
        if not self.window_main.stream_plot.frozen:
            self.window_main.stream_plot.toggle_freeze()

        # Reset data accumulator when starting the protocol.
        self.gui_timer.stop()

        super().start_protocol()

        if self.recording_event is not None:
            fb = self.filename_base()
            self.frame_recorder.filename_queue.put(fb)
            self.dc.add_static_data(fb, "recording/filename")
            self.recording_event.set()

        self.gui_timer.start(1000 // 60)

    def end_protocol(self, save=True):
        if self.recording_event is not None:
            self.recording_event.clear()

        super().end_protocol(save)
        if self.window_main.stream_plot.frozen:
            self.window_main.stream_plot.toggle_freeze()

    def save_data(self):
        """Save tail position and dynamic parameters and terminate.

        """

        self.window_main.camera_display.save_image(
            name=self.filename_base() + "img.png"
        )
        self.dc.add_static_data(self.filename_prefix() + "img.png", "tracking/image")

        # Save log and estimators:
        self.save_log(self.acc_tracking, "behavior_log")
        try:
            self.save_log(self.estimator.log, "estimator_log")
        except AttributeError:
            pass

        super().save_data()

    def set_protocol(self, protocol):
        """Connect new protocol start to resetting of the data accumulator.

        Parameters
        ----------
        protocol :
            

        Returns
        -------

        """
        super().set_protocol(protocol)
        self.protocol.sig_protocol_started.connect(self.acc_tracking.reset)

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

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

        Returns
        -------

        """

        if self.recording_event is not None:
            self.frame_recorder.finished_signal.set()
            self.frame_recorder.join()

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

        self.frame_dispatcher.gui_queue.clear()

        self.frame_dispatcher.join()

    def excepthook(self, exctype, value, tb):
        """ If an exception happens in the main loop, close all the
        processes so nothing is left hanging.

        """
        traceback.print_tb(tb)
        print("{0}: {1}".format(exctype, value))
        self.finished_sig.set()
        self.camera.join()
        self.frame_dispatcher.join()
예제 #7
0
    def __init__(self, *args, tracking, **kwargs):
        """
        :param tracking_method: class with the parameters for tracking (instance
                                of TrackingMethod class, defined in the child);
        :param header_list: headers for the data accumulator (list of strings,
                            defined in the child);
        :param data_name:  name of the data in the final experiment log (defined
                           in the child).
        """

        self.processing_params_queue = Queue()
        self.tracking_output_queue = NamedTupleQueue()
        self.finished_sig = Event()
        super().__init__(*args, **kwargs)
        self.arguments.update(locals())

        self.pipeline_cls = pipeline_dict.get(tracking["method"], None) if isinstance(tracking["method"], str) else tracking["method"]

        self.frame_dispatcher = TrackingProcess(
                in_frame_queue=self.camera.frame_queue,
                finished_signal=self.camera.kill_event,
                pipeline=self.pipeline_cls,
                processing_parameter_queue=self.processing_params_queue,
                output_queue=self.tracking_output_queue,
                gui_dispatcher=True,
                gui_framerate=20,
            )
        if self.pipeline_cls is None:
            raise NameError("The selected tracking method does not exist!")
        self.pipeline = self.pipeline_cls()
        assert isinstance(self.pipeline, Pipeline)
        self.pipeline.setup(tree=self.dc)

        self.acc_tracking = QueueDataAccumulator(
            name="tracking",
            experiment=self,
            data_queue=self.tracking_output_queue,
            monitored_headers=self.pipeline.headers_to_plot
        )
        self.acc_tracking.sig_acc_init.connect(self.refresh_plots)

        # Data accumulator is updated with GUI timer:
        self.gui_timer.timeout.connect(self.acc_tracking.update_list)

        # Tracking is reset at experiment start:
        self.protocol_runner.sig_protocol_started.connect(self.acc_tracking.reset)

        # start frame dispatcher process:
        self.frame_dispatcher.start()

        est_type = tracking.get("estimator", None)
        if est_type == "position":
            est = PositionEstimator
        elif est_type == "vigor":
            est = VigorMotionEstimator
        elif isclass(est_type) and issubclass(est_type, Estimator):
            est = est_type
        else:
            est = None

        if est is not None:
            self.estimator_log = EstimatorLog(experiment=self)
            self.estimator = est(self.acc_tracking,
                                 experiment=self,
                                 **tracking.get("estimator_params", {}))
            self.estimator_log.sig_acc_init.connect(self.refresh_plots)
        else:
            self.estimator = None

        self.acc_tracking_framerate = FramerateQueueAccumulator(self,
                                                                queue=self.frame_dispatcher.framerate_queue, name="tracking",
                                                                goal_framerate=kwargs["camera"].get("min_framerate", None)
                                                                )

        self.gui_timer.timeout.connect(self.acc_tracking_framerate.update_list)
예제 #8
0
class CameraVisualExperiment(VisualExperiment):
    """
    General class for Experiment that need to handle a camera.
    It implements a view of frames from the camera in the control GUI, and the respective parameters.
    For debugging it can be used with a video read from file with the VideoFileSource class.
    """
    def __init__(self,
                 *args,
                 camera: dict,
                 camera_queue_mb: int = 100,
                 recording: Optional[Dict[str, Any]] = None,
                 **kwargs) -> None:
        """
        Parameters
        ----------
        camera
            dictionary containing the parameters for the camera setup (i.e. for offline processing it would contain
            an entry 'video_file' with the path to the video).
        camera_queue_mb
            the maximum size of frames that are kept at once, if the limit is exceeded, frames will be dropped.
        recording
            dictionary containing the parameters for the recording (i.e. to save to an mp4 file, add the 'extension'
            entry with the 'mp4' value). If None, no recording is performed.
        """
        super().__init__(*args, **kwargs)
        if camera.get("video_file", None) is None:
            self.camera = CameraSource(
                camera["type"],
                rotation=camera.get("rotation", 0),
                downsampling=camera.get("downsampling", 1),
                roi=camera.get("roi", (-1, -1, -1, -1)),
                max_mbytes_queue=camera_queue_mb,
                camera_params=camera.get("camera_params", dict()),
            )
            self.camera_state = CameraControlParameters(tree=self.dc)
        else:
            self.camera = VideoFileSource(
                camera["video_file"],
                rotation=camera.get("rotation", 0),
                max_mbytes_queue=camera_queue_mb,
            )
            self.camera_state = VideoControlParameters(tree=self.dc)

        self.acc_camera_framerate = FramerateQueueAccumulator(
            self,
            queue=self.camera.framerate_queue,
            goal_framerate=camera.get("min_framerate", None),
            name="camera",  # TODO implement no goal
        )

        # New parameters are sent with GUI timer:
        self.gui_timer.timeout.connect(self.send_gui_parameters)
        self.gui_timer.timeout.connect(self.acc_camera_framerate.update_list)

        self.recording = recording
        if recording is not None:
            self._setup_recording(
                kbit_framerate=recording.get("kbit_rate", 1000),
                extension=recording["extension"],
            )

    def reset(self) -> None:
        super().reset()
        self.acc_camera_framerate.reset()

    def initialize_plots(self) -> None:
        super().initialize_plots()

    def send_gui_parameters(self) -> None:
        self.camera.control_queue.put(
            self.camera_state.params.changed_values())
        self.camera_state.params.acknowledge_changes()

    def start_experiment(self) -> None:
        """ """
        self.go_live()
        super().start_experiment()

    def start_protocol(self) -> None:
        """
        Starts the recording if the recording parameters are set.
        """
        if self.recording is not None:
            # Slight work around, the problem is in when set_id() is updated.
            # See issue #71.
            p = Path()
            fb = p.joinpath(self.folder_name,
                            self.current_timestamp.strftime("%H%M%S") + "_")
            self.dc.add_static_data(fb, "recording/filename")
            self._start_recording(fb)

        super().start_protocol()

    def end_protocol(self, save: bool = True) -> None:
        """
        Stops the recording if the recording parameters are set.
        """
        if self.recording is not None:
            self._stop_recording()

        super().end_protocol(save=save)

    def make_window(self) -> None:
        """ """
        self.window_main = CameraExperimentWindow(experiment=self)
        self.window_main.construct_ui()
        self.window_main.show()
        self.restore_window_state()
        self.initialize_plots()

    def go_live(self) -> None:
        """ """
        sys.excepthook = self.excepthook
        self.camera.start()

    def wrap_up(self, *args, **kwargs) -> None:
        self.gui_timer.stop()
        super().wrap_up(*args, **kwargs)
        self.camera.kill_event.set()

        for q in [self.camera.frame_queue]:
            q.clear()

        self.camera.join()

    def _setup_frame_dispatcher(self,
                                recording_event: Event = None
                                ) -> DispatchProcess:
        """
        Creates a dispatcher that handles the frames of the camera. It will trigger the recording (i.e. stop it) using
        the given 'recording_event' event.

        Parameters
        ----------
        recording_event
            The event used for recording (if relevant).
        """
        return DispatchProcess(self.camera.frame_queue, self.camera.kill_event,
                               recording_event)

    def _setup_recording(self,
                         kbit_framerate: int = 1000,
                         extension: str = "mp4") -> None:
        """
        Does the necessary setup before performing the recording, such as creating events, setting up the dispatcher
        (via _setup_frame_dispatcher) and initialising the VideoWriter.

        Parameters
        ----------
        kbit_framerate
            the byte rate at which the video is encoded.
        extension
            the extension used at the end of the video file.
        """
        self.recording_event = Event()
        self.reset_event = Event()
        self.finish_event = Event()

        self.frame_dispatcher = self._setup_frame_dispatcher(
            self.recording_event)
        self.frame_dispatcher.start()

        if extension == "h5":
            self.frame_recorder = H5VideoWriter(
                input_queue=self.frame_dispatcher.frame_copy_queue,
                recording_event=self.recording_event,
                reset_event=self.reset_event,
                finish_event=self.finish_event,
                log_format=self.log_format,
            )
        else:
            self.frame_recorder = StreamingVideoWriter(
                input_queue=self.frame_dispatcher.frame_copy_queue,
                recording_event=self.recording_event,
                reset_event=self.reset_event,
                finish_event=self.finish_event,
                kbit_rate=kbit_framerate,
                log_format=self.log_format,
            )

        self.frame_recorder.start()

    def _start_recording(self, filename: str) -> None:
        """
        Pushes the filename to the queue and sets the recording event in order to start the recording.

        Parameters
        ----------
        filename
            a unique identifier that will be added to the video file.
        """
        self.frame_recorder.filename_queue.put(filename)
        self.recording_event.set()

    def _stop_recording(self) -> None:
        """
        Stops the recording by clearing the recording event.
        """
        self.recording_event.clear()

    def _finish_recording(self) -> None:
        """
        Finishes the recording process and joins the frame recorder.
        """
        self.frame_recorder.finish_event.set()
        self.frame_recorder.join()

    def excepthook(self, exctype, value, tb) -> None:
        if self.recording is not None:
            self._finish_recording()

        traceback.print_tb(tb)
        print("{0}: {1}".format(exctype, value))
        self.camera.kill_event.set()
        self.camera.join()
예제 #9
0
    def __init__(self,
                 *args,
                 tracking: dict,
                 recording: Optional[Dict[str, Any]] = None,
                 second_output_queue: Queue = None,
                 **kwargs) -> None:
        """
        tracking
            containing fields:  tracking_method
                                estimator: can be vigor for embedded fish, position
                                    for freely-swimming, or a custom subclass of Estimator
        recording
            dictionary containing the parameters for the recording (i.e. to save to an mp4 file, add the 'extension'
            entry with the 'mp4' value). If None, no recording is performed.
        data_name
            name of the data in the final experiment log (defined in the child).
        """

        self.processing_params_queue = Queue()
        self.second_output_queue = second_output_queue
        self.tracking_output_queue = NamedTupleQueue()
        self.finished_sig = Event()

        self.pipeline_cls = (pipeline_dict.get(tracking["method"], None)
                             if isinstance(tracking["method"], str) else
                             tracking["method"])

        super().__init__(recording=recording, *args, **kwargs)
        self.arguments.update(locals())

        if self.pipeline_cls is None:
            raise NameError("The selected tracking method does not exist!")
        self.pipeline = self.pipeline_cls()
        assert isinstance(self.pipeline, Pipeline)
        self.pipeline.setup(tree=self.dc)

        if recording is None:
            # start frame dispatcher process:
            self.frame_dispatcher = self._setup_frame_dispatcher()
            self.frame_dispatcher.start()

        self.acc_tracking = QueueDataAccumulator(
            name="tracking",
            experiment=self,
            data_queue=self.tracking_output_queue,
            monitored_headers=self.pipeline.headers_to_plot,
        )
        self.acc_tracking.sig_acc_init.connect(self.refresh_plots)

        # Create and connect framerate accumulator.
        self.acc_tracking_framerate = FramerateQueueAccumulator(
            self,
            queue=self.frame_dispatcher.framerate_queue,
            name="tracking",
            goal_framerate=kwargs["camera"].get("min_framerate", None),
        )

        self.gui_timer.timeout.connect(self.acc_tracking_framerate.update_list)

        # Data accumulator is updated with GUI timer:
        self.gui_timer.timeout.connect(self.acc_tracking.update_list)

        # Tracking is reset at experiment start:
        self.protocol_runner.sig_protocol_started.connect(
            self.acc_tracking.reset)

        est_type = tracking.get("estimator", None)
        if est_type is None:
            est = None
        elif isinstance(est_type, str):
            est = estimator_dict.get(est_type, None)
        else:
            est = est_type

        if est is not None:
            self.estimator_log = EstimatorLog(experiment=self)
            self.estimator = est(self.acc_tracking,
                                 experiment=self,
                                 **tracking.get("estimator_params", {}))
            self.estimator_log.sig_acc_init.connect(self.refresh_plots)
        else:
            self.estimator = None