Esempio n. 1
0
def test_stimulator(StimulatorClass,
                    InterfaceClass,
                    remove_db_file=True,
                    *args,
                    **kwargs):
    tmp = tempfile.mkstemp(suffix="_ethoscope_test.db")[1]

    print("Making a tmp db: " + tmp)
    cam = MovieVirtualCamera(VIDEO, drop_each=15)
    rb = SleepMonitorWithTargetROIBuilder()
    rois = rb.build(cam)
    cam.restart()

    connection = HardwareConnection(InterfaceClass)
    try:
        # stimulators = [MockSDStimulator(connection,min_inactive_time= 10) for _ in rois ]
        stimulators = [
            StimulatorClass(connection, *args, **kwargs) for _ in rois
        ]
        mon = Monitor(cam, AdaptiveBGModel, rois, stimulators=stimulators)
        drawer = DefaultDrawer(draw_frames=DRAW_FRAMES)

        with SQLiteResultWriter(tmp, rois) as rw:
            mon.run(result_writer=rw, drawer=drawer)
        # cred = {"name": "ethoscope_db",
        #  "user": "******",
        #  "password": "******"}
        # with ResultWriter( cred , rois) as rw:
        #     mon.run(result_writer=rw, drawer=drawer)

    finally:
        if remove_db_file:
            print("Removing temp db (" + tmp + ")")
            os.remove(tmp)
        else:
            print("db file lives in (" + tmp + ")")
        connection.stop()
Esempio n. 2
0
    def test_API(self):
        random.seed(1)
        cam = MovieVirtualCamera(VIDEO)
        rb = SleepMonitorWithTargetROIBuilder()
        rois = rb.build(cam)
        hc = HardwareConnection(MockInterface)
        stimulators = [MockStimulator(hc) for _ in rois]

        cam.restart()
        mon = Monitor(cam, AdaptiveBGModel, rois, stimulators)

        drawer = DefaultDrawer(draw_frames=DRAW_FRAMES)
        tmp = tempfile.mkstemp(suffix="_ethoscope_test.db")[1]
        try:
            print("Making a tmp db: " + tmp)
            with SQLiteResultWriter(tmp, rois) as rw:
                mon.run(result_writer=rw, drawer=drawer)
        except:
            self.fail("testAPI raised ExceptionType unexpectedly!")
        finally:
            hc.stop()
            cam._close()
            print("Removing temp db (" + tmp + ")")
            os.remove(tmp)
Esempio n. 3
0
class MockSDInterface(MockLynxMotionInterface, SleepDepriverInterface):
    pass


class MockSDStimulator(SleepDepStimulator):
    _HardwareInterfaceClass = MockSDInterface


tmp = tempfile.mkstemp(suffix="_ethoscope_test.db")[1]

print("Making a tmp db: " + tmp)
cam = MovieVirtualCamera(VIDEO, drop_each=15)
rb = SleepMonitorWithTargetROIBuilder()
rois = rb.build(cam)
cam.restart()

connection = HardwareConnection(MockSDInterface)
stimulators = [
    MockSDStimulator(connection, min_inactive_time=10) for _ in rois
]
mon = Monitor(cam, AdaptiveBGModel, rois, stimulators=stimulators)
drawer = DefaultDrawer(draw_frames=DRAW_FRAMES)

try:
    with SQLiteResultWriter(tmp, rois) as rw:
        mon.run(result_writer=rw, drawer=drawer)
finally:
    print("Removing temp db (" + tmp + ")")
    os.remove(tmp)
    connection.stop()
Esempio n. 4
0
# Build the stimulator
hc = HardwareConnection(JaneliaAdaptiveSleepDepriverInterface)
stimulators = [JaneliaAdaptiveSleepDepStimultor(hc) for _ in rois]

# Then, we go back to the first frame of the video
cam.restart()

# we use a drawer to show inferred position for each animal, display frames and save them as a video
drawer = DefaultDrawer(OUTPUT_VIDEO, draw_frames=False)

# We build our monitor
monitor = Monitor(cam, AdaptiveBGModel, rois, stimulators)

try:
    # Now everything is ready, we run the monitor with a result writer and a drawer
    with SQLiteResultWriter(OUTPUT_DB, rois) as rw:
        monitor.run(rw, drawer)

finally:
    hc.stop()
    cam._close()
    print('----Put the motor controller in low power mode----')
    devs = ModularClients()  # Might automatically find device if one available
    if devs is not None:
        dev0 = devs['ethoscope_stepper_controller']['5x3'][0]
        dev1 = devs['ethoscope_stepper_controller']['5x3'][1]
        dev0.sleep_all()
        dev1.sleep_all()
    else:
        raise Exception('Could not put motor controllers to sleep')
Esempio n. 5
0
class ControlThread(Thread):
    """
    The versatile control thread
    From this thread, the PI passes option to the node.
    Note: Options are passed and shown only if the remote class contains a "_description" field!
    """
    _evanescent = False
    _option_dict = {
        "roi_builder": {
            "possible_classes": [
                FSLSleepMonitorWithTargetROIBuilder,
                SleepMonitorWithTargetROIBuilder, HighContrastTargetROIBuilder,
                ManualROIBuilder, DefaultROIBuilder, TargetGridROIBuilder,
                OlfactionAssayROIBuilder, ElectricShockAssayROIBuilder
            ],
        },
        "tracker": {
            "possible_classes": [AdaptiveBGModel, RichAdaptiveBGModel],
        },
        "interactor": {
            "possible_classes": [
                DefaultStimulator,
                SleepDepStimulator,
                OptomotorSleepDepriver,
                GearOptomotorSleepDepriver,
                RobustSleepDepriver,
                MiddleCrossingStimulator,
                #SystematicSleepDepInteractor,
                ExperimentalSleepDepStimulator,
                #GearMotorSleepDepStimulator,
                #DynamicOdourDeliverer,
                DynamicOdourSleepDepriver,
                OptoMidlineCrossStimulator,
                MotoMidlineCrossStimulator,
                RobustMotoMidlineCrossStimulator,
                OptomotorSleepDepriverSystematic,
                MiddleCrossingOdourStimulator,
                MiddleCrossingOdourStimulatorFlushed,
                SegmentedStimulator
            ],
        },
        "drawer": {
            "possible_classes": [DefaultDrawer, NullDrawer],
        },
        "camera": {
            "possible_classes": [
                FSLPiCameraAsync, OurPiCameraAsync, MovieVirtualCamera,
                DummyPiCameraAsync, V4L2Camera, FSLVirtualCamera
            ],
        },
        "result_writer": {
            "possible_classes":
            [ResultWriter, DebugResultWriter, SQLiteResultWriter],
        },
        "experimental_info": {
            "possible_classes": [ExperimentalInformation],
        }
    }

    #some classes do not need to be offered as choices to the user in normal conditions
    #these are shown only if the machine is not a PI
    _is_a_rPi = isMachinePI() and hasPiCamera() and not isExperimental()
    _hidden_options = {}  #{'result_writer'}

    for k in _option_dict:
        _option_dict[k]["class"] = _option_dict[k]["possible_classes"][0]
        _option_dict[k]["kwargs"] = {}

    _tmp_last_img_file = "last_img.jpg"
    _dbg_img_file = "dbg_img.png"
    _log_file = "ethoscope.log"
    #give the database an ethoscope specific name
    #future proof in case we want to use a remote server
    _db_credentials = {
        "name": "%s_db" % get_machine_name(),
        "user": "******",
        "password": "******"
    }

    _default_monitor_info = {
        #fixme, not needed
        "last_positions": None,
        "last_time_stamp": 0,
        "fps": 0
    }
    _persistent_state_file = "/var/cache/ethoscope/persistent_state.pkl"

    def __init__(self,
                 machine_id,
                 name,
                 version,
                 ethoscope_dir,
                 data=None,
                 *args,
                 **kwargs):

        self._monit_args = args
        self._monit_kwargs = kwargs
        self._metadata = None

        # for FPS computation
        self._last_info_t_stamp = 0
        self._last_info_frame_idx = 0

        # We wipe off previous logs and debug images
        try:
            os.remove(os.path.join(ethoscope_dir, self._log_file))
        except OSError:
            pass
        try:
            os.remove(os.path.join(ethoscope_dir, self._dbg_img_file))
        except OSError:
            pass

        try:
            os.makedirs(ethoscope_dir)
        except OSError:
            pass

        self._tmp_dir = tempfile.mkdtemp(prefix="ethoscope_")
        #todo add 'data' -> how monitor was started to metadata
        self._info = {
            "status": "stopped",
            "time": time.time(),
            "error": None,
            "log_file": os.path.join(ethoscope_dir, self._log_file),
            "dbg_img": os.path.join(ethoscope_dir, self._dbg_img_file),
            "last_drawn_img": os.path.join(self._tmp_dir,
                                           self._tmp_last_img_file),
            "id": machine_id,
            "name": name,
            "version": version,
            "db_name": self._db_credentials["name"],
            "monitor_info": self._default_monitor_info,
            #"user_options": self._get_user_options(),
            "experimental_info": {}
        }
        self._monit = None

        self._parse_user_options(data)

        DrawerClass = self._option_dict["drawer"]["class"]
        drawer_kwargs = self._option_dict["drawer"]["kwargs"]
        self._drawer = DrawerClass(**drawer_kwargs)

        super(ControlThread, self).__init__()

    @property
    def info(self):
        self._update_info()
        return self._info

    @property
    def was_interrupted(self):
        return os.path.exists(self._persistent_state_file)

    @classmethod
    def user_options(self):
        out = {}
        for key, value in list(self._option_dict.items()):
            # check if the options for the remote class will be visible
            # they will be visible only if they have a description, and if we are on a PC or they are not hidden
            if (self._is_a_rPi
                    and key not in self._hidden_options) or not self._is_a_rPi:
                out[key] = []
                for p in value["possible_classes"]:
                    try:
                        d = p.__dict__["_description"]
                    except KeyError:
                        continue

                    d["name"] = p.__name__
                    out[key].append(d)

        out_curated = {}
        for key, value in list(out.items()):
            if len(value) > 0:
                out_curated[key] = value

        return out_curated

    def _parse_one_user_option(self, field, data):

        logging.warning(field)
        logging.warning(data)

        try:
            subdata = data[field]
        except KeyError:
            logging.warning("No field %s, using default" % field)
            return None, (), {}

        Class = eval(subdata["name"])
        try:
            args = subdata["args"]
        except KeyError:
            args = ()

        try:
            kwargs = subdata["kwargs"]
        except KeyError:
            try:
                kwargs = subdata["arguments"]
            except KeyError:
                kwargs = {}

        logging.warning("Parsing %s", Class)
        logging.warning("args:")
        logging.warning(args)
        logging.warning("kwargs:")
        logging.warning(kwargs)
        logging.warning("subdata")
        logging.warning(subdata)

        return Class, args, kwargs

    def _parse_user_options(self, data):

        if data is None:
            return
        #FIXME DEBUG
        logging.warning("Starting control thread with data:")
        logging.warning(str(data))

        for key in list(self._option_dict.keys()):

            x = self._parse_one_user_option(key, data)
            if len(x) == 3:
                Class, args, kwargs = x
            else:
                Class, args = x
                kwargs = {}

            # when no field is present in the JSON config, we get the default class

            if Class is None:
                self._option_dict[key]["class"] = self._option_dict[key][
                    "possible_classes"][0]
                self._option_dict[key]["args"] = ()
                self._option_dict[key]["kwargs"] = {}
                continue

            self._option_dict[key]["class"] = Class
            self._option_dict[key]["args"] = args
            self._option_dict[key]["kwargs"] = kwargs

    def _update_info(self):
        if self._monit is None:
            return
        t = self._monit.last_time_stamp

        frame_idx = self._monit.last_frame_idx
        wall_time = time.time()
        dt = wall_time - self._last_info_t_stamp
        df = float(frame_idx - self._last_info_frame_idx)

        if self._last_info_t_stamp == 0 or dt > 0:
            f = round(df / dt, 2)
        else:
            f = "NaN"

        if t is not None:  # and p is not None:
            self._info["monitor_info"] = {
                # "last_positions":pos,
                "last_time_stamp": t,
                "fps": f
            }

        frame = self._drawer.last_drawn_frame
        if frame is not None:
            tempdir = os.path.dirname(self._info["last_drawn_img"])
            os.makedirs(tempdir, exist_ok=True)
            cv2.imwrite(self._info["last_drawn_img"], frame,
                        [int(cv2.IMWRITE_JPEG_QUALITY), 50])

        self._last_info_t_stamp = wall_time
        self._last_info_frame_idx = frame_idx

    def _start_tracking(self, camera, result_writer, rois, M, TrackerClass,
                        tracker_kwargs, hardware_connection, StimulatorClass,
                        stimulator_kwargs):

        #Here the stimulator passes args. Hardware connection was previously open as thread.
        stimulators = [
            StimulatorClass(hardware_connection, **stimulator_kwargs)
            for _ in rois
        ]

        kwargs = self._monit_kwargs.copy()
        kwargs.update(tracker_kwargs)

        # todo: pickle hardware connection, camera, rois, tracker class, stimulator class,.
        # then rerun stimulators and Monitor(......)
        self._monit = Monitor(camera,
                              TrackerClass,
                              rois,
                              stimulators=stimulators,
                              *self._monit_args,
                              **self._monit_kwargs)

        self._info["status"] = "running"
        logging.info("Setting monitor status as running: '%s'" %
                     self._info["status"])

        quality_controller = QualityControl(result_writer)

        self._monit.run(result_writer, self._drawer, quality_controller, M)

    def _has_pickle_file(self):
        """
        """
        return os.path.exists(self._persistent_state_file)

    def _set_tracking_from_pickled(self):
        """
        """
        with open(self._persistent_state_file, "rb") as f:
            time.sleep(15)
            return pickle.load(f)

    def _save_pickled_state(self, camera, result_writer, rois, M, TrackerClass,
                            tracker_kwargs, hardware_connection,
                            StimulatorClass, stimulator_kwargs, running_info):
        """
        note that cv2.videocapture is not a serializable object and cannot be pickled
        """

        tpl = (camera, result_writer, rois, M, TrackerClass, tracker_kwargs,
               hardware_connection, StimulatorClass, stimulator_kwargs,
               running_info)

        if not os.path.exists(os.path.dirname(self._persistent_state_file)):
            logging.warning("No cache dir detected. making one")
            os.makedirs(os.path.dirname(self._persistent_state_file))

        with open(self._persistent_state_file, "wb") as f:
            return pickle.dump(tpl, f)

    def _set_tracking_from_scratch(self):
        """
        """
        CameraClass = self._option_dict["camera"]["class"]
        camera_args = self._option_dict["camera"]["args"]
        camera_kwargs = self._option_dict["camera"]["kwargs"]

        ROIBuilderClass = self._option_dict["roi_builder"]["class"]
        roi_builder_kwargs = self._option_dict["roi_builder"]["kwargs"]

        StimulatorClass = self._option_dict["interactor"]["class"]
        stimulator_kwargs = self._option_dict["interactor"]["kwargs"]
        logging.warning(StimulatorClass)
        logging.warning(StimulatorClass.__dict__)
        HardWareInterfaceClass = StimulatorClass.__dict__[
            "_HardwareInterfaceClass"]

        TrackerClass = self._option_dict["tracker"]["class"]
        tracker_kwargs = self._option_dict["tracker"]["kwargs"]

        ResultWriterClass = self._option_dict["result_writer"]["class"]
        result_writer_kwargs = self._option_dict["result_writer"]["kwargs"]

        print("camera_args")
        print(camera_args)
        print("camera_kwargs")
        print(camera_kwargs)
        cam = CameraClass(*camera_args, **camera_kwargs)

        if isinstance(cam, OurPiCameraAsync):
            for i, (t, frame) in cam:
                cv2.imwrite(os.path.join(os.environ["HOME"], 'last_img.png'),
                            frame)
                break

        logging.info(ROIBuilderClass)
        logging.info(roi_builder_kwargs)

        roi_builder = ROIBuilderClass(args=(), kwargs=roi_builder_kwargs)

        try:
            if roi_builder.__class__.__name__ == "HighContrastTargetROIBuilder":
                img, M, rois = roi_builder.build(cam)
            else:
                rois = roi_builder.build(cam)
                M = None

        except EthoscopeException as e:
            cam._close()
            raise e

        logging.info(f"PID {os.getpid()}: Initialising monitor")
        cam.restart()
        # the camera start time is the reference 0

        ExpInfoClass = self._option_dict["experimental_info"]["class"]
        exp_info_kwargs = self._option_dict["experimental_info"]["kwargs"]
        self._info["experimental_info"] = ExpInfoClass(
            **exp_info_kwargs).info_dic
        self._info["time"] = cam.start_time

        #here the hardwareconnection call the interface class without passing any argument!
        hardware_connection = HardwareConnection(HardWareInterfaceClass)

        #creates a unique tracking id to label this tracking run
        self._info["experimental_info"]["run_id"] = secrets.token_hex(8)

        if self._info["experimental_info"]["sensor"]:
            #if is URL:
            sensor = EthoscopeSensor(self._info["experimental_info"]["sensor"])
            logging.info("Using sensor with URL %s" %
                         self._info["experimental_info"]["sensor"])
        else:
            sensor = None

        logging.info("Generating metadata of this run")
        logging.info(self._info)
        self._metadata = {
            "machine_id": self._info["id"],
            "machine_name": self._info["name"],
            "date_time":
            cam.start_time,  # the camera start time is the reference 0
            "frame_width": cam.width,
            "frame_height": cam.height,
            "version": self._info["version"]["id"],
            "experimental_info": str(self._info["experimental_info"]),
            "selected_options": str(self._option_dict),
        }

        logging.warning("Detected camera start time is %d", cam.start_time)
        # hardware_interface is a running thread
        rw = ResultWriterClass(self._db_credentials,
                               rois,
                               metadata=self._metadata,
                               sensor=sensor,
                               **result_writer_kwargs)
        return (cam, rw, rois, M, TrackerClass, tracker_kwargs,
                hardware_connection, StimulatorClass, stimulator_kwargs)

    def run(self):
        cam = None
        hardware_connection = None

        try:
            self._info["status"] = "initialising"
            logging.info("Starting Monitor thread")
            self._info["error"] = None
            self._last_info_t_stamp = 0
            self._last_info_frame_idx = 0

            if self._has_pickle_file():
                try:
                    cam, rw, rois, M, TrackerClass, tracker_kwargs, hardware_connection, StimulatorClass, stimulator_kwargs, self._info = self._set_tracking_from_pickled(
                    )

                except Exception as e:
                    logging.error(
                        "Could not load previous state for unexpected reason:")
                    raise e

            else:
                cam, rw, rois, M, TrackerClass, tracker_kwargs, hardware_connection, StimulatorClass, stimulator_kwargs = self._set_tracking_from_scratch(
                )
                # return  (cam, rw, rois, M, TrackerClass, tracker_kwargs,
                # hardware_connection, StimulatorClass, stimulator_kwargs)

            with rw as result_writer:
                if cam.canbepickled:
                    self._save_pickled_state(cam, rw, rois, M, TrackerClass,
                                             tracker_kwargs,
                                             hardware_connection,
                                             StimulatorClass,
                                             stimulator_kwargs, self._info)

                if M is not None:
                    logging.info(type(M))
                    logging.info(M.shape)

                else:
                    logging.info('M is None!')

                self._start_tracking(cam, result_writer, rois, M, TrackerClass,
                                     tracker_kwargs, hardware_connection,
                                     StimulatorClass, stimulator_kwargs)
            self.stop()

        except EthoscopeException as e:
            if e.img is not None:
                cv2.imwrite(self._info["dbg_img"], e.img)
            self.stop(traceback.format_exc())

        except Exception as e:
            self.stop(traceback.format_exc())

        finally:

            try:
                os.remove(self._persistent_state_file)
            except:
                logging.warning("Failed to remove persistent file")
            try:
                if cam is not None:
                    cam._close()

            except:
                logging.warning("Could not close camera properly")
                pass
            try:
                if hardware_connection is not None:
                    hardware_connection.stop()
            except:
                logging.warning("Could not close hardware connection properly")
                pass

            #for testing purposes
            if self._evanescent:
                del self._drawer
                self.stop()
                os._exit(0)

    def stop(self, error=None):
        self._info["status"] = "stopping"
        self._info["time"] = time.time()

        # we reset all the user data of the latest experiment except the run_id
        # a new run_id will be created when we start another experiment

        if "experimental_info" in self._info and "run_id" in self._info[
                "experimental_info"]:
            self._info["experimental_info"] = {
                "run_id": self._info["experimental_info"]["run_id"]
            }

        logging.info("Stopping monitor")
        if not self._monit is None:
            self._monit.stop()
            self._monit = None

        self._info["status"] = "stopped"
        self._info["time"] = time.time()
        self._info["error"] = error
        self._info["monitor_info"] = self._default_monitor_info

        if error is not None:
            logging.error("Monitor closed with an error:")
            logging.error(error)
        else:
            logging.info("Monitor closed all right")

    def __del__(self):
        self.stop()
        shutil.rmtree(self._tmp_dir, ignore_errors=True)
        shutil.rmtree(self._persistent_state_file, ignore_errors=True)

    def set_evanescent(self, value=True):
        self._evanescent = value
class ControlThread(Thread):
    """
    The versatile control thread
    From this thread, the PI passes option to the node.
    Note: Options are passed and shown only if the remote class contains a "_description" field!
    """
    _evanescent = False
    _option_dict = {
        "roi_builder": {
            "possible_classes": [
                DefaultROIBuilder, SleepMonitorWithTargetROIBuilder,
                TargetGridROIBuilder, OlfactionAssayROIBuilder,
                ElectricShockAssayROIBuilder
            ],
        },
        "tracker": {
            "possible_classes": [AdaptiveBGModel],
        },
        "interactor": {
            "possible_classes": [
                DefaultStimulator,
                SleepDepStimulator,
                OptomotorSleepDepriver,
                MiddleCrossingStimulator,
                #SystematicSleepDepInteractor,
                ExperimentalSleepDepStimulator,
                #GearMotorSleepDepStimulator,
                #DynamicOdourDeliverer,
                DynamicOdourSleepDepriver,
                OptoMidlineCrossStimulator,
                OptomotorSleepDepriverSystematic,
                MiddleCrossingOdourStimulator,
                MiddleCrossingOdourStimulatorFlushed
            ],
        },
        "drawer": {
            "possible_classes": [DefaultDrawer, NullDrawer],
        },
        "camera": {
            "possible_classes": [
                OurPiCameraAsync, MovieVirtualCamera, DummyPiCameraAsync,
                V4L2Camera
            ],
        },
        "result_writer": {
            "possible_classes": [ResultWriter, SQLiteResultWriter],
        },
        "experimental_info": {
            "possible_classes": [ExperimentalInformations],
        }
    }

    #some classes do not need to be offered as choices to the user in normal conditions
    #these are shown only if the machine is not a PI
    _is_a_rPi = isMachinePI() and hasPiCamera() and not isExperimental()
    _hidden_options = {'camera', 'result_writer'}

    for k in _option_dict:
        _option_dict[k]["class"] = _option_dict[k]["possible_classes"][0]
        _option_dict[k]["kwargs"] = {}

    _tmp_last_img_file = "last_img.jpg"
    _dbg_img_file = "dbg_img.png"
    _log_file = "ethoscope.log"
    _db_credentials = {
        "name": "ethoscope_db",
        "user": "******",
        "password": "******"
    }

    _default_monitor_info = {
        #fixme, not needed
        "last_positions": None,
        "last_time_stamp": 0,
        "fps": 0
    }
    _persistent_state_file = "/var/cache/ethoscope/persistent_state.pkl"

    def __init__(self,
                 machine_id,
                 name,
                 version,
                 ethoscope_dir,
                 data=None,
                 *args,
                 **kwargs):

        self._monit_args = args
        self._monit_kwargs = kwargs
        self._metadata = None

        # for FPS computation
        self._last_info_t_stamp = 0
        self._last_info_frame_idx = 0

        # We wipe off previous data
        # DOES THIS MAKE SENSE? WE LOST LOTS OF DATA DOING THIS in THE PAST!! [TODO]
        try:
            os.remove(os.path.join(ethoscope_dir, self._log_file))
        except OSError:
            pass
        try:
            os.remove(os.path.join(ethoscope_dir, self._dbg_img_file))
        except OSError:
            pass

        try:
            os.makedirs(ethoscope_dir)
        except OSError:
            pass

        self._tmp_dir = tempfile.mkdtemp(prefix="ethoscope_")
        #todo add 'data' -> how monitor was started to metadata
        self._info = {
            "status": "stopped",
            "time": time.time(),
            "error": None,
            "log_file": os.path.join(ethoscope_dir, self._log_file),
            "dbg_img": os.path.join(ethoscope_dir, self._dbg_img_file),
            "last_drawn_img": os.path.join(self._tmp_dir,
                                           self._tmp_last_img_file),
            "id": machine_id,
            "name": name,
            "version": version,
            "db_name": self._db_credentials["name"],
            "monitor_info": self._default_monitor_info,
            #"user_options": self._get_user_options(),
            "experimental_info": {}
        }
        self._monit = None

        self._parse_user_options(data)

        DrawerClass = self._option_dict["drawer"]["class"]
        drawer_kwargs = self._option_dict["drawer"]["kwargs"]
        self._drawer = DrawerClass(**drawer_kwargs)

        super(ControlThread, self).__init__()

    @property
    def info(self):
        self._update_info()
        return self._info

    @property
    def was_interrupted(self):
        return os.path.exists(self._persistent_state_file)

    @classmethod
    def user_options(self):
        out = {}
        for key, value in list(self._option_dict.items()):
            # check if the options for the remote class will be visible
            # they will be visible only if they have a description, and if we are on a PC or they are not hidden
            if (self._is_a_rPi
                    and key not in self._hidden_options) or not self._is_a_rPi:
                out[key] = []
                for p in value["possible_classes"]:
                    try:
                        d = p.__dict__["_description"]
                    except KeyError:
                        continue

                    d["name"] = p.__name__
                    out[key].append(d)

        out_curated = {}
        for key, value in list(out.items()):
            if len(value) > 0:
                out_curated[key] = value

        return out_curated

    def _parse_one_user_option(self, field, data):

        try:
            subdata = data[field]
        except KeyError:
            logging.warning("No field %s, using default" % field)
            return None, {}

        Class = eval(subdata["name"])
        kwargs = subdata["arguments"]

        return Class, kwargs

    def _parse_user_options(self, data):

        if data is None:
            return
        #FIXME DEBUG
        logging.warning("Starting control thread with data:")
        logging.warning(str(data))

        for key in list(self._option_dict.keys()):

            Class, kwargs = self._parse_one_user_option(key, data)
            # when no field is present in the JSON config, we get the default class

            if Class is None:

                self._option_dict[key]["class"] = self._option_dict[key][
                    "possible_classes"][0]
                self._option_dict[key]["kwargs"] = {}
                continue

            self._option_dict[key]["class"] = Class
            self._option_dict[key]["kwargs"] = kwargs

    def _update_info(self):
        if self._monit is None:
            return
        t = self._monit.last_time_stamp

        frame_idx = self._monit.last_frame_idx
        wall_time = time.time()
        dt = wall_time - self._last_info_t_stamp
        df = float(frame_idx - self._last_info_frame_idx)

        if self._last_info_t_stamp == 0 or dt > 0:
            f = round(df / dt, 2)
        else:
            f = "NaN"

        if t is not None:  # and p is not None:
            self._info["monitor_info"] = {
                # "last_positions":pos,
                "last_time_stamp": t,
                "fps": f
            }

        frame = self._drawer.last_drawn_frame
        if frame is not None:
            cv2.imwrite(self._info["last_drawn_img"], frame,
                        [int(cv2.IMWRITE_JPEG_QUALITY), 50])

        self._last_info_t_stamp = wall_time
        self._last_info_frame_idx = frame_idx

    def _start_tracking(self, camera, result_writer, rois, TrackerClass,
                        tracker_kwargs, hardware_connection, StimulatorClass,
                        stimulator_kwargs):

        #Here the stimulator passes args. Hardware connection was previously open as thread.
        stimulators = [
            StimulatorClass(hardware_connection, **stimulator_kwargs)
            for _ in rois
        ]

        kwargs = self._monit_kwargs.copy()
        kwargs.update(tracker_kwargs)

        # todo: pickle hardware connection, camera, rois, tracker class, stimulator class,.
        # then rerun stimulators and Monitor(......)
        self._monit = Monitor(camera,
                              TrackerClass,
                              rois,
                              stimulators=stimulators,
                              *self._monit_args)
        self._info["status"] = "running"
        logging.info("Setting monitor status as running: '%s'" %
                     self._info["status"])

        self._monit.run(result_writer, self._drawer)

    def _set_tracking_from_pickled(self):
        """
        """
        with open(self._persistent_state_file, "rb") as f:
            time.sleep(15)
            return pickle.load(f)

    def _save_pickled_state(self, camera, result_writer, rois, TrackerClass,
                            tracker_kwargs, hardware_connection,
                            StimulatorClass, stimulator_kwargs):
        """
        note that cv2.videocapture is not a serializable object and cannot be pickled
        """

        tpl = (camera, result_writer, rois, TrackerClass, tracker_kwargs,
               hardware_connection, StimulatorClass, stimulator_kwargs)

        if not os.path.exists(os.path.dirname(self._persistent_state_file)):
            logging.warning("No cache dir detected. making one")
            os.makedirs(os.path.dirname(self._persistent_state_file))

        with open(self._persistent_state_file, "wb") as f:
            return pickle.dump(tpl, f)

    def _set_tracking_from_scratch(self):
        CameraClass = self._option_dict["camera"]["class"]
        camera_kwargs = self._option_dict["camera"]["kwargs"]

        ROIBuilderClass = self._option_dict["roi_builder"]["class"]
        roi_builder_kwargs = self._option_dict["roi_builder"]["kwargs"]

        StimulatorClass = self._option_dict["interactor"]["class"]
        stimulator_kwargs = self._option_dict["interactor"]["kwargs"]
        HardWareInterfaceClass = StimulatorClass.__dict__[
            "_HardwareInterfaceClass"]

        TrackerClass = self._option_dict["tracker"]["class"]
        tracker_kwargs = self._option_dict["tracker"]["kwargs"]

        ResultWriterClass = self._option_dict["result_writer"]["class"]
        result_writer_kwargs = self._option_dict["result_writer"]["kwargs"]

        cam = CameraClass(**camera_kwargs)

        roi_builder = ROIBuilderClass(**roi_builder_kwargs)

        try:
            rois = roi_builder.build(cam)
        except EthoscopeException as e:
            cam._close()
            raise e

        logging.info("Initialising monitor")
        cam.restart()
        # the camera start time is the reference 0

        ExpInfoClass = self._option_dict["experimental_info"]["class"]
        exp_info_kwargs = self._option_dict["experimental_info"]["kwargs"]
        self._info["experimental_info"] = ExpInfoClass(
            **exp_info_kwargs).info_dic
        self._info["time"] = cam.start_time

        #here the hardwareconnection call the interface class without passing any argument!
        hardware_connection = HardwareConnection(HardWareInterfaceClass)

        self._metadata = {
            "machine_id": self._info["id"],
            "machine_name": self._info["name"],
            "date_time":
            cam.start_time,  # the camera start time is the reference 0
            "frame_width": cam.width,
            "frame_height": cam.height,
            "version": self._info["version"]["id"],
            "experimental_info": str(self._info["experimental_info"]),
            "selected_options": str(self._option_dict),
        }
        # hardware_interface is a running thread
        rw = ResultWriter(self._db_credentials,
                          rois,
                          self._metadata,
                          take_frame_shots=True)

        return (cam, rw, rois, TrackerClass, tracker_kwargs,
                hardware_connection, StimulatorClass, stimulator_kwargs)

    def run(self):
        cam = None
        hardware_connection = None

        try:
            self._info["status"] = "initialising"
            logging.info("Starting Monitor thread")
            self._info["error"] = None
            self._last_info_t_stamp = 0
            self._last_info_frame_idx = 0

            try:
                cam, rw, rois, TrackerClass, tracker_kwargs, hardware_connection, StimulatorClass, stimulator_kwargs = self._set_tracking_from_pickled(
                )

            except FileNotFoundError:
                cam, rw, rois, TrackerClass, tracker_kwargs, hardware_connection, StimulatorClass, stimulator_kwargs = self._set_tracking_from_scratch(
                )

            except Exception as e:
                logging.error(
                    "Could not load previous state for unexpected reason:")
                raise e

            with rw as result_writer:
                if cam.canbepickled:
                    self._save_pickled_state(cam, rw, rois, TrackerClass,
                                             tracker_kwargs,
                                             hardware_connection,
                                             StimulatorClass,
                                             stimulator_kwargs)

                self._start_tracking(cam, result_writer, rois, TrackerClass,
                                     tracker_kwargs, hardware_connection,
                                     StimulatorClass, stimulator_kwargs)
            self.stop()

        except EthoscopeException as e:
            if e.img is not None:
                cv2.imwrite(self._info["dbg_img"], e.img)
            self.stop(traceback.format_exc())

        except Exception as e:
            self.stop(traceback.format_exc())

        finally:

            try:
                os.remove(self._persistent_state_file)
            except:
                logging.warning("Failed to remove persistent file")
            try:
                if cam is not None:
                    cam._close()

            except:
                logging.warning("Could not close camera properly")
                pass
            try:
                if hardware_connection is not None:
                    hardware_connection.stop()
            except:
                logging.warning("Could not close hardware connection properly")
                pass

            #for testing purposes
            if self._evanescent:
                del self._drawer
                self.stop()
                os._exit(0)

    def stop(self, error=None):
        self._info["status"] = "stopping"
        self._info["time"] = time.time()
        self._info["experimental_info"] = {}

        logging.info("Stopping monitor")
        if not self._monit is None:
            self._monit.stop()
            self._monit = None

        self._info["status"] = "stopped"
        self._info["time"] = time.time()
        self._info["error"] = error
        self._info["monitor_info"] = self._default_monitor_info

        if error is not None:
            logging.error("Monitor closed with an error:")
            logging.error(error)
        else:
            logging.info("Monitor closed all right")

    def __del__(self):
        self.stop()
        shutil.rmtree(self._tmp_dir, ignore_errors=True)
        shutil.rmtree(self._persistent_state_file, ignore_errors=True)

    def set_evanescent(self, value=True):
        self._evanescent = value
    parser.add_option("-p",
                      "--prefix",
                      dest="prefix",
                      help="The prefix for result dir")

    (options, args) = parser.parse_args()
    option_dict = vars(options)
    INPUT = option_dict["input"]
    OUTPUT = os.path.splitext(INPUT)[0] + ".db"
    OUTPUT = option_dict["prefix"] + "/" + OUTPUT
    try:
        os.makedirs(os.path.dirname(OUTPUT))
    except OSError:
        pass
    print INPUT + " ===> " + OUTPUT

    cam = MovieVirtualCamera(INPUT)
    rois = SleepMonitorWithTargetROIBuilder().build(cam)
    drawer = DefaultDrawer(draw_frames=True)
    mon = Monitor(cam, AdaptiveBGModel, rois)

    #fixme
    date = datetime.datetime.strptime("2016-05-03_08-25-02",
                                      "%Y-%m-%d_%H-%M-%S")
    ts = int(calendar.timegm(date.timetuple()))
    #todo parse metadata from filename
    # metadata = {}

    with SQLiteResultWriter(OUTPUT, rois) as rw:
        mon.run(rw, drawer)
Esempio n. 8
0
from ethoscope.utils.io import SQLiteResultWriter
from ethoscope.hardware.input.cameras import MovieVirtualCamera
from ethoscope.drawers.drawers import DefaultDrawer

# You can also load other types of ROI builder. This one is for 20 tubes (two columns of ten rows)
from ethoscope.roi_builders.roi_builders import DefaultROIBuilder
from ethoscope.roi_builders.target_roi_builder import TargetGridROIBuilder

# change these three variables according to how you name your input/output files
INPUT_VIDEO = "/home/quentin/comput/ethoscope-git/src/ethoscope/tests/integration_server_tests/test_video.mp4"
OUTPUT_VIDEO = "/tmp/my_output.avi"
OUTPUT_DB = "/tmp/results.db"

# We use a video input file as if it was a "camera"
cam = MovieVirtualCamera(INPUT_VIDEO)

# here, we generate ROIs automatically from the targets in the images
roi_builder = TargetGridROIBuilder(n_rows=1, n_cols=2)
rois = roi_builder.build(cam)
# Then, we go back to the first frame of the video
cam.restart()

# we use a drawer to show inferred position for each animal, display frames and save them as a video
drawer = DefaultDrawer(OUTPUT_VIDEO, draw_frames=True)
# We build our monitor
monitor = Monitor(cam, MultiFlyTracker, rois)

# Now everything ius ready, we run the monitor with a result writer and a drawer
# with SQLiteResultWriter(OUTPUT_DB, rois) as rw:
monitor.run(None, drawer)
Esempio n. 9
0
def main(argv):

    parser = ArgumentParser(
        description='Runs an Ethoscope machinery on the given video file,' +
        ' which is meant to be a recording of single daphnia moving' +
        ' in a bunch of wells.' +
        ' The area of each well is determined by non-black regions' +
        ' in the supplied regions of interest (ROI) image file.' +
        ' Optionally an output video may be produced, documenting the ' +
        ' detection of animals.')
    parser.add_argument("-i",
                        "--input-video",
                        dest="inp_video_filename",
                        required=True,
                        help="The video file to be processed.",
                        metavar='<input video filename>')
    parser.add_argument("-o",
                        "--output-db",
                        dest="db_filename",
                        required=True,
                        help="Create Sqlite DB file  for storing results.",
                        metavar='<output DB filename>')
    parser.add_argument(
        "-r",
        "--roi_image",
        dest="roi_filename",
        required=True,
        help="Create Sqlite DB file DB_FILENAME for storing results.",
        metavar='<roi image>')
    parser.add_argument("-a",
                        "--output-video",
                        dest="outp_video_filename",
                        help="The annotated output video file.",
                        metavar='<output video filename>')
    parser.add_argument(
        "-b",
        "--single-roi-video",
        dest="single_roi_video_filename",
        help=
        "For debugging purpose a video file of a single roi may be produced.",
        metavar='<single roi video filename>')
    parser.add_argument(
        "-n",
        "--single-roi-video-roi-nbr",
        dest="single_roi_video_roi_nbr",
        type=int,
        default=0,
        help="Select the number of the roi to produce a debugging video from."
        + " If not specified, roi 0 is the default.",
        metavar='<single roi video roi number>')

    args = parser.parse_args()

    # change these variables according to how you name your input/output files
    INPUT_DATA_DIR = "/home/lukas/tmp/AAA-Video/"
    OUTPUT_DATA_DIR = "/home/lukas/tmp/ethoscope/"

    #ROI_IMAGE = INPUT_DATA_DIR + "irbacklit_20200109_1525_4x4x6_squaremask_valued.png"
    ROI_IMAGE = INPUT_DATA_DIR + "4xWellplates4x6_registred_squaremask_tight.png"
    #INPUT_VIDEO = INPUT_DATA_DIR + "Basler_acA5472-17um__23065142__20200109_152536071.mp4"
    INPUT_VIDEO = INPUT_DATA_DIR + "Basler_acA5472-17um__23065142__20200205_172106124.mp4"

    logfile = OUTPUT_DATA_DIR + '20200205.log'
    OUTPUT_VIDEO = OUTPUT_DATA_DIR + "20200205.avi"
    OUTPUT_DB = OUTPUT_DATA_DIR + "results20200205.db"
    #logfile = OUTPUT_DATA_DIR + 'results.log'
    #OUTPUT_VIDEO = OUTPUT_DATA_DIR + "output.avi"
    #OUTPUT_DB = OUTPUT_DATA_DIR + "output.db"

    dbgImgWinSizeX = 2200
    dbgImgWinSizeY = 1500

    # setup logging
    logging.basicConfig(filename=logfile, level=logging.INFO)
    #logging.basicConfig(filename=logfile, level=logging.DEBUG)
    # define a Handler which writes INFO messages or higher to the sys.stderr
    console = logging.StreamHandler()
    console.setLevel(logging.INFO)
    # add the handler to the root logger
    logging.getLogger('').addHandler(console)

    # Make the ethoscope packages accessible
    package_path = os.path.join(os.path.dirname(sys.path[0]), '')
    logging.info("path of ethoscope package: %s" % package_path)
    sys.path.insert(0, package_path)

    import cv2

    # import the bricks from ethoscope package
    # Use a mask image to define rois. Mask image must have black background, every non-black
    # region defines a roi.
    from ethoscope.roi_builders.img_roi_builder import ImgMaskROIBuilder
    from ethoscope.core.monitor import Monitor
    from ethoscope.trackers.adaptive_bg_tracker import AdaptiveBGModel
    from ethoscope.utils.io import SQLiteResultWriter
    from ethoscope.hardware.input.cameras import MovieVirtualCamera
    from ethoscope.drawers.drawers import DefaultDrawer

    # Generate ROIs from the mask image
    logging.info("reading roi mask")
    roi_builder = ImgMaskROIBuilder(args.roi_filename)

    logging.info("building rois from \"%s\"" % args.roi_filename)
    roi_builder.build(
        None)  # use image already loaded by ImgMaskROIBuilder instance
    rois = roi_builder.gridSort(50, 50)

    #for r in rois:
    #  print("Roi %d: value: %d, (%d,%d)" % (r.idx, r._value, r._rectangle[0], r._rectangle[1]))

    # We use a video input file as if it were a camera
    cam = MovieVirtualCamera(args.inp_video_filename)
    logging.info("Loading \"%s\"-encoded movie with %d FPS of duration %d s." %
                 (cam.fourcc, cam.frames_per_sec,
                  cam._total_n_frames / cam.frames_per_sec))

    # we use a drawer to show inferred position for each animal, display frames and save them as a video
    do_draw_frames = False
    if args.outp_video_filename is not None:
        do_draw_frames = True
    drawer = DefaultDrawer(args.outp_video_filename,
                           draw_frames=do_draw_frames,
                           framesWinSizeX=dbgImgWinSizeX,
                           framesWinSizeY=dbgImgWinSizeY)

    # We build our monitor
    #monitor = Monitor(cam, AdaptiveBGModel, rois)
    if args.single_roi_video_filename is not None:
        monitor = Monitor(
            cam,
            AdaptiveBGModel,
            rois,
            dbg_roi_value=args.single_roi_video_roi_nbr,
            dbg_roi_video_filename=args.single_roi_video_filename)
    else:
        monitor = Monitor(cam, AdaptiveBGModel, rois)

    # Now everything is ready, we run the monitor with a result writer and a drawer
    logging.info("run monitor with drawer")
    with SQLiteResultWriter(args.db_filename, rois) as rw:
        monitor.run(rw, drawer)
Esempio n. 10
0
class ControlThread(Thread):
    """
    The versatile control thread
    From this thread, the PI passes option to the node.
    Note: Options are passed and shown only if the remote class contains a "_description" field!
    """

    _auto_SQL_backup_at_stop = False

    _option_dict = {
        "roi_builder": {
            "possible_classes": [
                DefaultROIBuilder, SleepMonitorWithTargetROIBuilder,
                TargetGridROIBuilder, OlfactionAssayROIBuilder,
                ElectricShockAssayROIBuilder
            ],
        },
        "tracker": {
            "possible_classes": [AdaptiveBGModel],
        },
        "interactor": {
            "possible_classes": [
                DefaultStimulator,
                SleepDepStimulator,
                OptomotorSleepDepriver,
                MiddleCrossingStimulator,
                #SystematicSleepDepInteractor,
                ExperimentalSleepDepStimulator,
                #GearMotorSleepDepStimulator,
                #DynamicOdourDeliverer,
                DynamicOdourSleepDepriver,
                OptoMidlineCrossStimulator,
                OptomotorSleepDepriverSystematic,
                MiddleCrossingOdourStimulator,
                MiddleCrossingOdourStimulatorFlushed,
                mAGO
            ],
        },
        "drawer": {
            "possible_classes": [DefaultDrawer, NullDrawer],
        },
        "camera": {
            "possible_classes": [
                OurPiCameraAsync, MovieVirtualCamera, DummyPiCameraAsync,
                V4L2Camera
            ],
        },
        "result_writer": {
            "possible_classes": [ResultWriter, SQLiteResultWriter],
        },
        "experimental_info": {
            "possible_classes": [ExperimentalInformation],
        }
    }

    #some classes do not need to be offered as choices to the user in normal conditions
    #these are shown only if the machine is not a PI
    _is_a_rPi = isMachinePI() and hasPiCamera() and not isExperimental()
    _hidden_options = {'camera', 'result_writer', 'tracker'}

    for k in _option_dict:
        _option_dict[k]["class"] = _option_dict[k]["possible_classes"][0]
        _option_dict[k]["kwargs"] = {}

    _tmp_last_img_file = "last_img.jpg"
    _dbg_img_file = "dbg_img.png"
    _log_file = "ethoscope.log"

    #give the database an ethoscope specific name
    #future proof in case we want to use a remote server
    _db_credentials = {
        "name": "%s_db" % get_machine_name(),
        "user": "******",
        "password": "******"
    }

    _default_monitor_info = {
        #fixme, not needed
        "last_positions": None,
        "last_time_stamp": 0,
        "fps": 0
    }

    _persistent_state_file = PERSISTENT_STATE

    def __init__(self,
                 machine_id,
                 name,
                 version,
                 ethoscope_dir,
                 data=None,
                 *args,
                 **kwargs):

        self._monit_args = args
        self._monit_kwargs = kwargs
        self._metadata = None

        # for FPS computation
        self._last_info_t_stamp = 0
        self._last_info_frame_idx = 0

        # We wipe off previous logs and debug images
        try:
            os.remove(os.path.join(ethoscope_dir, self._log_file))
        except OSError:
            pass

        try:
            os.remove(os.path.join(ethoscope_dir, self._dbg_img_file))
        except OSError:
            pass

        try:
            os.remove("/tmp/ethoscope_*")
        except OSError:
            pass

        try:
            os.makedirs(ethoscope_dir)
        except OSError:
            pass

        self._tmp_dir = tempfile.mkdtemp(prefix="ethoscope_")

        #todo add 'data' -> how monitor was started to metadata
        self._info = {
            "status": "stopped",
            "time": time.time(),
            "error": None,
            "log_file": os.path.join(ethoscope_dir, self._log_file),
            "dbg_img": os.path.join(ethoscope_dir, self._dbg_img_file),
            "last_drawn_img": os.path.join(self._tmp_dir,
                                           self._tmp_last_img_file),
            "db_name": self._db_credentials["name"],
            "monitor_info": self._default_monitor_info,
            #"user_options": self._get_user_options(),
            "experimental_info": {},
            "id": machine_id,
            "name": name,
            "version": version
        }
        self._monit = None

        self._parse_user_options(data)

        DrawerClass = self._option_dict["drawer"]["class"]
        drawer_kwargs = self._option_dict["drawer"]["kwargs"]
        self._drawer = DrawerClass(**drawer_kwargs)

        logging.info('Starting a new monitor control thread')

        super(ControlThread, self).__init__()

    @property
    def hw_info(self):
        """
        This is information about the ethoscope that is not changing in time such as hardware specs and configuration parameters
        """

        _hw_info = {}

        _hw_info['kernel'] = os.uname()[2]
        _hw_info['pi_version'] = pi_version()
        _hw_info['camera'] = getPiCameraVersion()
        _hw_info['SD_CARD_AGE'] = get_SD_CARD_AGE()
        _hw_info['partitions'] = get_partition_infos()
        _hw_info['SD_CARD_NAME'] = get_SD_CARD_NAME()

        return _hw_info

    @property
    def info(self):
        self._update_info()
        return self._info

    @property
    def was_interrupted(self):
        return os.path.exists(self._persistent_state_file)

    @classmethod
    def user_options(self):
        out = {}
        for key, value in list(self._option_dict.items()):
            # check if the options for the remote class will be visible
            # they will be visible only if they have a description, and if we are on a PC or they are not hidden
            if (self._is_a_rPi
                    and key not in self._hidden_options) or not self._is_a_rPi:
                out[key] = []
                for p in value["possible_classes"]:
                    try:
                        d = p.__dict__["_description"]
                    except KeyError:
                        continue

                    d["name"] = p.__name__
                    out[key].append(d)

        out_curated = {}
        for key, value in list(out.items()):
            if len(value) > 0:
                out_curated[key] = value

        return out_curated

    def _parse_one_user_option(self, field, data):

        try:
            subdata = data[field]
        except KeyError:
            logging.warning("No field %s, using default" % field)
            return None, {}

        Class = eval(subdata["name"])
        kwargs = subdata["arguments"]

        return Class, kwargs

    def _parse_user_options(self, data):

        if data is None:
            return

        for key in list(self._option_dict.keys()):

            Class, kwargs = self._parse_one_user_option(key, data)
            # when no field is present in the JSON config, we get the default class

            if Class is None:

                self._option_dict[key]["class"] = self._option_dict[key][
                    "possible_classes"][0]
                self._option_dict[key]["kwargs"] = {}
                continue

            self._option_dict[key]["class"] = Class
            self._option_dict[key]["kwargs"] = kwargs

    def _update_info(self):
        '''
        Updates a dictionary with information that relates to the current status of the machine, ie data linked for instance to data acquisition
        Information that is not related to control and it is not experiment-dependent will come from elsewhere
        '''

        if self._monit is None:
            return
        t = self._monit.last_time_stamp

        frame_idx = self._monit.last_frame_idx
        wall_time = time.time()
        dt = wall_time - self._last_info_t_stamp
        df = float(frame_idx - self._last_info_frame_idx)

        if self._last_info_t_stamp == 0 or dt > 0:
            f = round(df / dt, 2)
        else:
            f = "NaN"

        if t is not None:  # and p is not None:
            self._info["monitor_info"] = {
                # "last_positions":pos,
                "last_time_stamp": t,
                "fps": f
            }

        frame = self._drawer.last_drawn_frame
        if frame is not None:
            cv2.imwrite(self._info["last_drawn_img"], frame,
                        [int(cv2.IMWRITE_JPEG_QUALITY), 50])

        self._last_info_t_stamp = wall_time
        self._last_info_frame_idx = frame_idx

    def _start_tracking(self, camera, result_writer, rois, reference_points,
                        TrackerClass, tracker_kwargs, hardware_connection,
                        StimulatorClass, stimulator_kwargs):

        #Here the stimulator passes args. Hardware connection was previously open as thread.
        stimulators = [
            StimulatorClass(hardware_connection, **stimulator_kwargs)
            for _ in rois
        ]

        kwargs = self._monit_kwargs.copy()
        kwargs.update(tracker_kwargs)

        # todo: pickle hardware connection, camera, rois, tracker class, stimulator class,.
        # then rerun stimulators and Monitor(......)
        self._monit = Monitor(camera,
                              TrackerClass,
                              rois,
                              reference_points=reference_points,
                              stimulators=stimulators,
                              *self._monit_args)

        self._info["status"] = "running"
        logging.info("Setting monitor status as running: '%s'" %
                     self._info["status"])

        self._monit.run(result_writer, self._drawer)

    def _has_pickle_file(self):
        """
        """
        return os.path.exists(self._persistent_state_file)

    def _set_tracking_from_pickled(self):
        """
        """
        with open(self._persistent_state_file, "rb") as f:
            time.sleep(15)
            return pickle.load(f)

    def _save_pickled_state(self, camera, result_writer, rois,
                            reference_points, TrackerClass, tracker_kwargs,
                            hardware_connection, StimulatorClass,
                            stimulator_kwargs, running_info):
        """
        note that cv2.videocapture is not a serializable object and cannot be pickled
        """

        tpl = (camera, result_writer, rois, reference_points, TrackerClass,
               tracker_kwargs, hardware_connection, StimulatorClass,
               stimulator_kwargs, running_info)

        if not os.path.exists(os.path.dirname(self._persistent_state_file)):
            logging.warning("No cache dir detected. making one")
            os.makedirs(os.path.dirname(self._persistent_state_file))

        with open(self._persistent_state_file, "wb") as f:
            return pickle.dump(tpl, f)

    def _set_tracking_from_scratch(self):
        """
        """
        CameraClass = self._option_dict["camera"]["class"]
        camera_kwargs = self._option_dict["camera"]["kwargs"]

        ROIBuilderClass = self._option_dict["roi_builder"]["class"]
        roi_builder_kwargs = self._option_dict["roi_builder"]["kwargs"]

        StimulatorClass = self._option_dict["interactor"]["class"]
        stimulator_kwargs = self._option_dict["interactor"]["kwargs"]
        HardWareInterfaceClass = StimulatorClass.__dict__[
            "_HardwareInterfaceClass"]

        TrackerClass = self._option_dict["tracker"]["class"]
        tracker_kwargs = self._option_dict["tracker"]["kwargs"]

        ResultWriterClass = self._option_dict["result_writer"]["class"]
        result_writer_kwargs = self._option_dict["result_writer"]["kwargs"]

        cam = CameraClass(**camera_kwargs)

        roi_builder = ROIBuilderClass(**roi_builder_kwargs)

        try:
            reference_points, rois = roi_builder.build(cam)
        except EthoscopeException as e:
            cam._close()
            raise e

        logging.info("Initialising monitor")
        cam.restart()
        # the camera start time is the reference 0

        ExpInfoClass = self._option_dict["experimental_info"]["class"]
        exp_info_kwargs = self._option_dict["experimental_info"]["kwargs"]
        self._info["experimental_info"] = ExpInfoClass(
            **exp_info_kwargs).info_dic
        self._info["time"] = cam.start_time

        #here the hardwareconnection call the interface class without passing any argument!
        hardware_connection = HardwareConnection(HardWareInterfaceClass)

        #creates a unique tracking id to label this tracking run
        self._info["experimental_info"]["run_id"] = secrets.token_hex(8)

        if self._info["experimental_info"]["sensor"]:
            #if is URL:
            sensor = EthoscopeSensor(self._info["experimental_info"]["sensor"])
            logging.info("Using sensor with URL %s" %
                         self._info["experimental_info"]["sensor"])
        else:
            sensor = None

        if "append" in self._info["experimental_info"]:
            append_to_db = self._info["experimental_info"]["append"]
            logging.info([
                "Recreating a new database",
                "Appending tracking data to the existing database"
            ][append_to_db])
        else:
            append_to_db = False

        #this will be saved in the metadata table
        self._metadata = {
            "machine_id": self._info["id"],
            "machine_name": self._info["name"],
            "date_time":
            cam.start_time,  # the camera start time is the reference 0
            "frame_width": cam.width,
            "frame_height": cam.height,
            "version": self._info["version"]["id"],
            "experimental_info": str(self._info["experimental_info"]),
            "selected_options": str(self._option_dict),
            "hardware_info": str(self.hw_info),
            "reference_points": str([(p[0], p[1]) for p in reference_points])
        }

        # hardware_interface is a running thread
        rw = ResultWriter(
            self._db_credentials,
            rois,
            self._metadata,
            take_frame_shots=True,
            erase_old_db=(not append_to_db),
            sensor=sensor,
        )

        return (cam, rw, rois, reference_points, TrackerClass, tracker_kwargs,
                hardware_connection, StimulatorClass, stimulator_kwargs)

    def run(self):
        cam = None
        hardware_connection = None

        try:
            self._info["status"] = "initialising"
            logging.info("Starting Monitor thread")
            self._info["error"] = None
            self._last_info_t_stamp = 0
            self._last_info_frame_idx = 0

            #check if a previous instance exist and if it does attempts to start from there
            if self._has_pickle_file():
                logging.warning(
                    "Attempting to resume a previously interrupted state")

                try:
                    cam, rw, rois, reference_points, TrackerClass, tracker_kwargs, hardware_connection, StimulatorClass, stimulator_kwargs, self._info = self._set_tracking_from_pickled(
                    )

                except Exception as e:
                    logging.error(
                        "Could not load previous state for unexpected reason:")
                    raise e

            #a previous instance does not exist, hence we create a new one
            else:
                cam, rw, rois, reference_points, TrackerClass, tracker_kwargs, hardware_connection, StimulatorClass, stimulator_kwargs = self._set_tracking_from_scratch(
                )

            with rw as result_writer:

                # and we save it if we can
                if cam.canbepickled:
                    self._save_pickled_state(cam, rw, rois, reference_points,
                                             TrackerClass, tracker_kwargs,
                                             hardware_connection,
                                             StimulatorClass,
                                             stimulator_kwargs, self._info)

                # then we start tracking
                self._start_tracking(cam, result_writer, rois,
                                     reference_points, TrackerClass,
                                     tracker_kwargs, hardware_connection,
                                     StimulatorClass, stimulator_kwargs)

            #self.stop()

        except EthoscopeException as e:
            if e.img is not None:
                cv2.imwrite(self._info["dbg_img"], e.img)
            self.stop(traceback.format_exc())

        except Exception as e:
            self.stop(traceback.format_exc())

        finally:

            if os.path.exists(self._persistent_state_file):
                try:
                    os.remove(self._persistent_state_file)
                except:
                    logging.warning("Failed to remove persistent file")
            try:
                if cam is not None:
                    cam._close()

            except:
                logging.warning("Could not close camera properly")
                pass
            try:
                if hardware_connection is not None:
                    hardware_connection.stop()
            except:
                logging.warning("Could not close hardware connection properly")
                pass

    def stop(self, error=None):
        """
        """

        self._info["status"] = "stopping"
        self._info["time"] = time.time()

        # we reset all the user data of the latest experiment except the run_id
        # a new run_id will be created when we start another experiment

        if "experimental_info" in self._info and "run_id" in self._info[
                "experimental_info"]:
            self._info["experimental_info"] = {
                "run_id": self._info["experimental_info"]["run_id"]
            }

        logging.info("Stopping monitor")
        if not self._monit is None:
            self._monit.stop()
            self._monit = None

            if self._auto_SQL_backup_at_stop:
                logging.info("Performing a SQL dump of the database.")
                t = Thread(target=SQL_dump)
                t.start()

        self._info["status"] = "stopped"
        self._info["time"] = time.time()
        self._info["error"] = error
        self._info["monitor_info"] = self._default_monitor_info

        if error is not None:
            logging.error("Monitor closed with an error:")
            logging.error(error)
        else:
            logging.info("Monitor closed all right")

    def __del__(self):
        """
        """

        self.stop()
        shutil.rmtree(self._tmp_dir, ignore_errors=True)
        shutil.rmtree(self._persistent_state_file, ignore_errors=True)
Esempio n. 11
0
                            }
                        })

    #For the haar tracking
    if ttype == "haar":
        monit = Monitor(camera,
                        HaarTracker,
                        rois,
                        stimulators=None,
                        data={
                            'maxN': entities,
                            'cascade': cascade,
                            'scaleFactor': 1.1,
                            'minNeighbors': 3,
                            'flags': 0,
                            'minSize': (15, 15),
                            'maxSize': (20, 20),
                            'visualise': False
                        })

    if ttype == "default":
        monit = Monitor(camera, AdaptiveBGModel, rois)

    # Starts the tracking monitor
    monit.run(drawer=drawer, result_writer=rdw, verbose=True)

    #convert the anpy to npy
    print("Tracking done, now converting")
    fh = npyAppendableFile(input_video + "_001.anpy")
    fh.convert()
Esempio n. 12
0
class ControlThread(Thread):
    _evanescent = False
    _option_dict = {
        "roi_builder": {
            "possible_classes": [
                DefaultROIBuilder, SleepMonitorWithTargetROIBuilder,
                TargetGridROIBuilder, OlfactionAssayROIBuilder
            ],
        },
        "tracker": {
            "possible_classes": [AdaptiveBGModel],
        },
        "interactor": {
            "possible_classes": [
                DefaultInteractor, SleepDepInteractor,
                SystematicSleepDepInteractor, ExperimentalSleepDepInteractor,
                FakeSystematicSleepDepInteractor
            ],
        },
        "drawer": {
            "possible_classes": [DefaultDrawer, NullDrawer],
        },
        "camera": {
            "possible_classes": [OurPiCameraAsync, MovieVirtualCamera],
        },
        "result_writer": {
            "possible_classes": [ResultWriter, SQLiteResultWriter],
        },
        "experimental_info": {
            "possible_classes": [ExperimentalInformations],
        }
    }
    for k in _option_dict:
        _option_dict[k]["class"] = _option_dict[k]["possible_classes"][0]
        _option_dict[k]["kwargs"] = {}

    _tmp_last_img_file = "last_img.jpg"
    _dbg_img_file = "dbg_img.png"
    _log_file = "ethoscope.log"
    _db_credentials = {
        "name": "ethoscope_db",
        "user": "******",
        "password": "******"
    }

    _default_monitor_info = {
        #fixme, not needed
        "last_positions": None,
        "last_time_stamp": 0,
        "fps": 0
    }

    def __init__(self,
                 machine_id,
                 name,
                 version,
                 ethoscope_dir,
                 data=None,
                 *args,
                 **kwargs):

        self._monit_args = args
        self._monit_kwargs = kwargs
        self._metadata = None

        # for FPS computation
        self._last_info_t_stamp = 0
        self._last_info_frame_idx = 0

        # We wipe off previous data
        shutil.rmtree(ethoscope_dir, ignore_errors=True)
        try:
            os.makedirs(ethoscope_dir)
        except OSError:
            pass

        self._tmp_dir = tempfile.mkdtemp(prefix="ethoscope_")
        #todo add 'data' -> how monitor was started to metadata
        self._info = {
            "status": "stopped",
            "time": time.time(),
            "error": None,
            "log_file": os.path.join(ethoscope_dir, self._log_file),
            "dbg_img": os.path.join(ethoscope_dir, self._dbg_img_file),
            "last_drawn_img": os.path.join(self._tmp_dir,
                                           self._tmp_last_img_file),
            "id": machine_id,
            "name": name,
            "version": version,
            "db_name": self._db_credentials["name"],
            "monitor_info": self._default_monitor_info,
            "user_options": self._get_user_options(),
            "experimental_info": {}
        }
        self._monit = None

        self._parse_user_options(data)

        DrawerClass = self._option_dict["drawer"]["class"]
        drawer_kwargs = self._option_dict["drawer"]["kwargs"]
        self._drawer = DrawerClass(**drawer_kwargs)

        super(ControlThread, self).__init__()

    @property
    def info(self):
        self._update_info()
        return self._info

    def _get_user_options(self):
        out = {}
        for key, value in self._option_dict.iteritems():
            out[key] = []
            for p in value["possible_classes"]:
                try:
                    d = p.__dict__["_description"]
                except KeyError:
                    continue

                d["name"] = p.__name__
                out[key].append(d)
        out_currated = {}

        for key, value in out.iteritems():
            if len(value) > 0:
                out_currated[key] = value

        return out_currated

    def _parse_one_user_option(self, field, data):

        try:
            subdata = data[field]
        except KeyError:
            logging.warning("No field %s, using default" % field)
            return None, {}

        Class = eval(subdata["name"])
        kwargs = subdata["arguments"]

        return Class, kwargs

    def _parse_user_options(self, data):

        if data is None:
            return
        #FIXME DEBUG
        logging.warning("Starting control thread with data:")
        logging.warning(str(data))

        for key in self._option_dict.iterkeys():

            Class, kwargs = self._parse_one_user_option(key, data)
            # when no field is present in the JSON config, we get the default class

            if Class is None:

                self._option_dict[key]["class"] = self._option_dict[key][
                    "possible_classes"][0]
                self._option_dict[key]["kwargs"] = {}
                continue

            self._option_dict[key]["class"] = Class
            self._option_dict[key]["kwargs"] = kwargs

    def _update_info(self):
        if self._monit is None:
            return
        t = self._monit.last_time_stamp

        frame_idx = self._monit.last_frame_idx
        wall_time = time.time()
        dt = wall_time - self._last_info_t_stamp
        df = float(frame_idx - self._last_info_frame_idx)

        if self._last_info_t_stamp == 0 or dt > 0:
            f = round(df / dt, 2)
        else:
            f = "NaN"

        #fixme, this is not available anymore since we have position list now

        # possibly, we just don't need it altogether
        # p = self._monit.last_positions
        # pos = {}
        # for k,v in p.items():
        #     if v is None:
        #         continue
        #     pos[k] = dict(v)
        #     pos[k]["roi_idx"] = k

        if t is not None:  # and p is not None:
            self._info["monitor_info"] = {
                # "last_positions":pos,
                "last_time_stamp": t,
                "fps": f
            }

        f = self._drawer.last_drawn_frame
        if not f is None:
            cv2.imwrite(self._info["last_drawn_img"], f)

        self._last_info_t_stamp = wall_time
        self._last_info_frame_idx = frame_idx

    def run(self):

        try:
            self._info["status"] = "initialising"
            logging.info("Starting Monitor thread")

            self._info["error"] = None

            self._last_info_t_stamp = 0
            self._last_info_frame_idx = 0
            try:

                CameraClass = self._option_dict["camera"]["class"]

                camera_kwargs = self._option_dict["camera"]["kwargs"]

                ROIBuilderClass = self._option_dict["roi_builder"]["class"]
                roi_builder_kwargs = self._option_dict["roi_builder"]["kwargs"]

                InteractorClass = self._option_dict["interactor"]["class"]
                interactor_kwargs = self._option_dict["interactor"]["kwargs"]
                HardWareInterfaceClass = InteractorClass.__dict__[
                    "_hardwareInterfaceClass"]

                TrackerClass = self._option_dict["tracker"]["class"]
                tracker_kwargs = self._option_dict["tracker"]["kwargs"]

                ResultWriterClass = self._option_dict["result_writer"]["class"]
                result_writer_kwargs = self._option_dict["result_writer"][
                    "kwargs"]

                cam = CameraClass(**camera_kwargs)

                roi_builder = ROIBuilderClass(**roi_builder_kwargs)
                rois = roi_builder.build(cam)

                logging.info("Initialising monitor")
                cam.restart()
                #the camera start time is the reference 0

                ExpInfoClass = self._option_dict["experimental_info"]["class"]
                exp_info_kwargs = self._option_dict["experimental_info"][
                    "kwargs"]
                self._info["experimental_info"] = ExpInfoClass(
                    **exp_info_kwargs).info_dic
                self._info["time"] = cam.start_time

                self._metadata = {
                    "machine_id": self._info["id"],
                    "machine_name": self._info["name"],
                    "date_time":
                    cam.start_time,  #the camera start time is the reference 0
                    "frame_width": cam.width,
                    "frame_height": cam.height,
                    "version": self._info["version"]["id"],
                    "experimental_info": str(self._info["experimental_info"]),
                    "selected_options": str(self._option_dict)
                }

                hardware_interface = HardWareInterfaceClass()
                interactors = [
                    InteractorClass(hardware_interface, **interactor_kwargs)
                    for _ in rois
                ]
                kwargs = self._monit_kwargs.copy()
                kwargs.update(tracker_kwargs)

                self._monit = Monitor(cam,
                                      TrackerClass,
                                      rois,
                                      interactors=interactors,
                                      *self._monit_args,
                                      **kwargs)

                logging.info("Starting monitor")

                #fixme
                with ResultWriter(self._db_credentials,
                                  rois,
                                  self._metadata,
                                  take_frame_shots=True) as rw:
                    self._info["status"] = "running"
                    logging.info("Setting monitor status as running: '%s'" %
                                 self._info["status"])
                    self._monit.run(rw, self._drawer)
                logging.info("Stopping Monitor thread")
                self.stop()

            finally:
                try:
                    cam._close()
                except:
                    logging.warning("Could not close camera properly")
                    pass

        except EthoscopeException as e:
            if e.img is not None:
                cv2.imwrite(self._info["dbg_img"], e.img)
            self.stop(traceback.format_exc(e))
        except Exception as e:
            self.stop(traceback.format_exc(e))

        #for testing purposes
        if self._evanescent:
            import os
            del self._drawer
            self.stop()
            os._exit(0)

    def stop(self, error=None):
        self._info["status"] = "stopping"
        self._info["time"] = time.time()
        self._info["experimental_info"] = {}

        logging.info("Stopping monitor")
        if not self._monit is None:
            self._monit.stop()
            self._monit = None

        self._info["status"] = "stopped"
        self._info["time"] = time.time()
        self._info["error"] = error
        self._info["monitor_info"] = self._default_monitor_info

        if error is not None:
            logging.error("Monitor closed with an error:")
            logging.error(error)
        else:
            logging.info("Monitor closed all right")

    def __del__(self):
        self.stop()
        shutil.rmtree(self._tmp_dir, ignore_errors=True)

    def set_evanescent(self, value=True):
        self._evanescent = value
Esempio n. 13
0
                            self._crop_counter[0] += 1
        
                        except Exception as e:
                            print ("Error saving the positive file")
        
        print("Saved %s positives and %s negatives so far" % (self._crop_counter[0], self._crop_counter[1]))



# the input video - we work on an mp4 acquired with the Record function
positive_video = "/home/gg/Downloads/test_video.mp4"
negative_video = "/home/gg/Downloads/whole_2020-08-25_13-10-07_2020f8bceb334c1c84518f62359ddc76_emptyarena_1280x960@25_00000.mp4"



camera = MovieVirtualCamera(negative_video)

# we use the default drawer and we show the video as we track - this is useful to understand how things are going
# disabling the video will speed things up
#drawer = DefaultDrawer(draw_frames = True, video_out = output_video, video_out_fps=25)
drawer = HaarCutter(filepath="/home/gg/haar_training", draw_frames = False, create_positives=False, create_negatives=True, interval=10, negatives_per_frame=30)


# One Big ROI using the Default ROIBuilder
roi_builder = DefaultROIBuilder()
rois = roi_builder.build(camera)

# Starts the tracking monitor
monit = Monitor(camera, MultiFlyTracker, rois, stimulators=None )
monit.run(drawer=drawer, result_writer = None)