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 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()
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)
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)
class ABGMImageSaver(AdaptiveBGModel): fg_model = ObjectModelImageSaver() # 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 = SleepMonitorWithTargetROIBuilder() 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, ABGMImageSaver, 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(rw, drawer)
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 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)
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)
# We use the npy tracker to save data in a npy file rdw = rawdatawriter(basename=input_video + ".npy", n_rois=len(rois), entities=entities) #rdw = None #for multifly tracking using BS subtraction if ttype == "multi": monit = Monitor(camera, MultiFlyTracker, rois, stimulators=None, data={ 'maxN': 50, 'visualise': False, 'fg_data': { 'sample_size': 400, 'normal_limits': (50, 200), 'tolerance': 0.8 } }) #For the haar tracking if ttype == "haar": monit = Monitor(camera, HaarTracker, rois, stimulators=None, data={ 'maxN': entities,
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) conditionVariables = [] try: variable = camera.metadata( "analog_gain" ) # This might fail depending on the type of camera conditionVariables.append( ConditionVariable(variable, "camera_gain")) except KeyError as error: # analog_gain is not part of the meta data for this camera logging.warning( "Unable to get the camera gain to add to the condition database" ) try: sensorChip = BufferedHIH6130() conditionVariables.append( ConditionVariableFunction(sensorChip.humidity, "humidity")) conditionVariables.append( ConditionVariableFunction(sensorChip.temperature, "temperature")) except Exception as error: logging.warning( "Unable to get the temperature or humidity sensor to add to the conditions database because: " + str(error)) try: lightSensorChip = TSL2591() lightSensorChip.powerOn() conditionVariables.append( ConditionVariableFunction(lightSensorChip.lux, "illuminance")) except Exception as error: logging.warning( "Unable to get the light sensor to add to the conditions database because: " + str(error)) self._conditionsMonitor = ConditionsMonitor(conditionVariables) dbConnectionString = "mysql://" + self._db_credentials[ "user"] + ":" + self._db_credentials[ "password"] + "@localhost/" + self._db_credentials["name"] self._info["status"] = "running" logging.info("Setting monitor status as running: '%s'" % self._info["status"]) # Set all times in the database to be relative to the start of the run. Usually this means setting # to zero, unless continuing a previous run (e.g. after power failure). self._monit handles this # internally. The "1000" is so that everything is in milliseconds. self._conditionsMonitor.setTime( (time.time() - camera.start_time) * 1000, 1000) self._conditionsMonitor.run( dbConnectionString) # This runs asynchronously self._monit.run(result_writer, self._drawer) # This blocks
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
# here, we generate ROIs automatically from the targets in the images roi_builder = SleepMonitorWithTargetROIBuilderJanelia() rois = roi_builder.build(cam) # 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()
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)
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()
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)