def start_eye_process(self, eye_id): potential_locs = [ os.path.join(self.g_pool.rec_dir, "eye{}{}".format(eye_id, ext)) for ext in (".mjpeg", ".mp4", ".mkv") ] existing_locs = [loc for loc in potential_locs if os.path.exists(loc)] if not existing_locs: logger.error("no eye video for eye '{}' found.".format(eye_id)) self.detection_status[eye_id] = "No eye video found." return rec, file_ = os.path.split(existing_locs[0]) set_name = os.path.splitext(file_)[0] self.videoset = VideoSet(rec, set_name, fill_gaps=False) self.videoset.load_or_build_lookup() timestamp_len = (self.videoset.lookup.container_idx > -1).sum() if not timestamp_len: logger.error( "no timestamps for eye video for eye '{}' found.".format( eye_id)) self.detection_status[eye_id] = "No eye video found." return video_loc = existing_locs[0] self.eye_frame_num[eye_id] = timestamp_len capure_settings = "File_Source", { "source_path": video_loc, "timing": None } self.notify_all({ "subject": "eye_process.should_start", "eye_id": eye_id, "overwrite_cap_settings": capure_settings, }) self.eye_video_loc[eye_id] = video_loc self.detection_status[eye_id] = "Detecting..."
def start_eye_process(self, eye_id): potential_locs = [ os.path.join(self.g_pool.rec_dir, "eye{}{}".format(eye_id, ext)) for ext in (".mjpeg", ".mp4", ".mkv") ] existing_locs = [loc for loc in potential_locs if os.path.exists(loc)] if not existing_locs: logger.error("no eye video for eye '{}' found.".format(eye_id)) self.detection_status[eye_id] = "No eye video found." return rec, file_ = os.path.split(existing_locs[0]) set_name = os.path.splitext(file_)[0] self.videoset = VideoSet(rec, set_name, fill_gaps=False) self.videoset.load_or_build_lookup() timestamp_len = (self.videoset.lookup.container_idx > -1).sum() if not timestamp_len: logger.error( "no timestamps for eye video for eye '{}' found.".format(eye_id) ) self.detection_status[eye_id] = "No eye video found." return video_loc = existing_locs[0] self.eye_frame_num[eye_id] = timestamp_len capure_settings = "File_Source", {"source_path": video_loc, "timing": None} self.notify_all( { "subject": "eye_process.should_start", "eye_id": eye_id, "overwrite_cap_settings": capure_settings, } ) self.eye_video_loc[eye_id] = video_loc self.detection_status[eye_id] = "Detecting..."
def generate_frame_indices_with_deserialized_gaze(g_pool): recording = PupilRecording(g_pool.rec_dir) video_name = recording.files().world().videos()[0].stem videoset = VideoSet(rec=g_pool.rec_dir, name=video_name, fill_gaps=True) videoset.load_or_build_lookup() frame_indices = np.flatnonzero(videoset.lookup.container_idx > -1) frame_count = len(frame_indices) for frame_index in frame_indices: progress = (frame_index + 1) / frame_count frame_ts_window = pm.enclosing_window(g_pool.timestamps, frame_index) gaze_data = g_pool.gaze_positions.by_ts_window(frame_ts_window) gaze_data = [(frame_index, g["timestamp"], g["norm_pos"][0], g["norm_pos"][1]) for g in gaze_data if g["confidence"] >= g_pool.min_data_confidence] gaze_data = scan_path_numpy_array_from(gaze_data) yield progress, gaze_data
class Offline_Pupil_Detection(Pupil_Producer_Base): """docstring for Offline_Pupil_Detection""" session_data_version = 3 session_data_name = "offline_pupil" def __init__(self, g_pool): super().__init__(g_pool) zmq_ctx = zmq.Context() self.data_sub = zmq_tools.Msg_Receiver( zmq_ctx, g_pool.ipc_sub_url, topics=("pupil", "notify.file_source.video_finished"), hwm=100_000, ) self.data_dir = os.path.join(g_pool.rec_dir, "offline_data") os.makedirs(self.data_dir, exist_ok=True) try: session_meta_data = fm.load_object( os.path.join(self.data_dir, self.session_data_name + ".meta")) assert session_meta_data.get( "version") == self.session_data_version except (AssertionError, FileNotFoundError): session_meta_data = {} session_meta_data["detection_method"] = "3d" session_meta_data["detection_status"] = ["unknown", "unknown"] self.detection_method = session_meta_data["detection_method"] self.detection_status = session_meta_data["detection_status"] pupil = fm.load_pldata_file(self.data_dir, self.session_data_name) ts_data_zip = zip(pupil.timestamps, pupil.data) ts_topic_zip = zip(pupil.timestamps, pupil.topics) self.pupil_positions = collections.OrderedDict(ts_data_zip) self.id_topics = collections.OrderedDict(ts_topic_zip) self.eye_video_loc = [None, None] self.eye_frame_num = [0, 0] for topic in self.id_topics.values(): eye_id = int(topic[-1]) self.eye_frame_num[eye_id] += 1 self.pause_switch = None self.detection_paused = False # start processes for eye_id in range(2): if self.detection_status[eye_id] != "complete": self.start_eye_process(eye_id) # either we did not start them or they failed to start (mono setup etc) # either way we are done and can publish if self.eye_video_loc == [None, None]: self.correlate_publish() def start_eye_process(self, eye_id): potential_locs = [ os.path.join(self.g_pool.rec_dir, "eye{}{}".format(eye_id, ext)) for ext in (".mjpeg", ".mp4", ".mkv") ] existing_locs = [loc for loc in potential_locs if os.path.exists(loc)] if not existing_locs: logger.error("no eye video for eye '{}' found.".format(eye_id)) self.detection_status[eye_id] = "No eye video found." return rec, file_ = os.path.split(existing_locs[0]) set_name = os.path.splitext(file_)[0] self.videoset = VideoSet(rec, set_name, fill_gaps=False) self.videoset.load_or_build_lookup() timestamp_len = (self.videoset.lookup.container_idx > -1).sum() if not timestamp_len: logger.error( "no timestamps for eye video for eye '{}' found.".format( eye_id)) self.detection_status[eye_id] = "No eye video found." return video_loc = existing_locs[0] self.eye_frame_num[eye_id] = timestamp_len capure_settings = "File_Source", { "source_path": video_loc, "timing": None } self.notify_all({ "subject": "eye_process.should_start", "eye_id": eye_id, "overwrite_cap_settings": capure_settings, }) self.eye_video_loc[eye_id] = video_loc self.detection_status[eye_id] = "Detecting..." def stop_eye_process(self, eye_id): self.notify_all({ "subject": "eye_process.should_stop", "eye_id": eye_id }) self.eye_video_loc[eye_id] = None def recent_events(self, events): super().recent_events(events) while self.data_sub.new_data: topic = self.data_sub.recv_topic() remaining_frames = self.data_sub.recv_remaining_frames() if topic.startswith("pupil."): # pupil data only has one remaining frame payload_serialized = next(remaining_frames) pupil_datum = fm.Serialized_Dict( msgpack_bytes=payload_serialized) assert int(topic[-1]) == pupil_datum["id"] self.pupil_positions[pupil_datum["timestamp"]] = pupil_datum self.id_topics[pupil_datum["timestamp"]] = topic else: payload = self.data_sub.deserialize_payload(*remaining_frames) if payload["subject"] == "file_source.video_finished": for eyeid in (0, 1): if self.eye_video_loc[eyeid] == payload["source_path"]: logger.debug( "eye {} process complete".format(eyeid)) self.detection_status[eyeid] = "complete" self.stop_eye_process(eyeid) break if self.eye_video_loc == [None, None]: self.correlate_publish() total = sum(self.eye_frame_num) self.menu_icon.indicator_stop = (len(self.pupil_positions) / total if total else 0.0) def correlate_publish(self): self.g_pool.pupil_positions = pm.Bisector( tuple(self.pupil_positions.values()), tuple(self.pupil_positions.keys())) self.g_pool.pupil_positions_by_id = self.create_pupil_positions_by_id( self.pupil_positions, self.id_topics) self._pupil_changed_announcer.announce_new() logger.debug("pupil positions changed") self.save_offline_data() def on_notify(self, notification): super().on_notify(notification) if notification["subject"] == "eye_process.started": self.set_detection_mapping_mode(self.detection_method) elif notification["subject"] == "eye_process.stopped": self.eye_video_loc[notification["eye_id"]] = None def cleanup(self): self.stop_eye_process(0) self.stop_eye_process(1) # close sockets before context is terminated self.data_sub = None self.save_offline_data() def save_offline_data(self): topic_data_ts = ((self.id_topics[ts], datum, ts) for ts, datum in self.pupil_positions.items()) with fm.PLData_Writer(self.data_dir, "offline_pupil") as writer: for topic, datum, timestamp in topic_data_ts: writer.append_serialized(timestamp, topic, datum.serialized) session_data = {} session_data["detection_method"] = self.detection_method session_data["detection_status"] = self.detection_status session_data["version"] = self.session_data_version cache_path = os.path.join(self.data_dir, "offline_pupil.meta") fm.save_object(session_data, cache_path) logger.info("Cached detected pupil data to {}".format(cache_path)) def redetect(self): self.pupil_positions.clear( ) # delete previously detected pupil positions self.id_topics.clear() self.g_pool.pupil_positions = pm.Bisector([], []) self.detection_finished_flag = False self.detection_paused = False for eye_id in range(2): if self.eye_video_loc[eye_id] is None: self.start_eye_process(eye_id) else: self.notify_all({ "subject": "file_source.seek", "frame_index": 0, "source_path": self.eye_video_loc[eye_id], }) def set_detection_mapping_mode(self, new_mode): n = {"subject": "set_detection_mapping_mode", "mode": new_mode} self.notify_all(n) self.redetect() self.detection_method = new_mode def init_ui(self): super().init_ui() self.menu.label = "Offline Pupil Detector" self.menu.append( ui.Info_Text( "Detects pupil positions from the recording's eye videos.")) self.menu.append( ui.Selector( "detection_method", self, label="Detection Method", selection=["2d", "3d"], setter=self.set_detection_mapping_mode, )) self.menu.append( ui.Switch("detection_paused", self, label="Pause detection")) self.menu.append(ui.Button("Redetect", self.redetect)) self.menu.append( ui.Text_Input( "0", label="eye0:", getter=lambda: self.detection_status[0], setter=lambda _: _, )) self.menu.append( ui.Text_Input( "1", label="eye1:", getter=lambda: self.detection_status[1], setter=lambda _: _, )) def detection_progress(): total = sum(self.eye_frame_num) return 100 * len(self.pupil_positions) / total if total else 0.0 progress_slider = ui.Slider( "detection_progress", label="Detection Progress", getter=detection_progress, setter=lambda _: _, ) progress_slider.display_format = "%3.0f%%" self.menu.append(progress_slider) @property def detection_paused(self): return self._detection_paused @detection_paused.setter def detection_paused(self, should_pause): self._detection_paused = should_pause for eye_id in range(2): if self.eye_video_loc[eye_id] is not None: subject = "file_source." + ("should_pause" if should_pause else "should_play") self.notify_all({ "subject": subject, "source_path": self.eye_video_loc[eye_id] })
class Offline_Pupil_Detection(Pupil_Producer_Base): """docstring for Offline_Pupil_Detection""" session_data_version = 3 session_data_name = "offline_pupil" def __init__(self, g_pool): super().__init__(g_pool) zmq_ctx = zmq.Context() self.data_sub = zmq_tools.Msg_Receiver( zmq_ctx, g_pool.ipc_sub_url, topics=("pupil", "notify.file_source.video_finished"), hwm=100_000, ) self.data_dir = os.path.join(g_pool.rec_dir, "offline_data") os.makedirs(self.data_dir, exist_ok=True) try: session_meta_data = fm.load_object( os.path.join(self.data_dir, self.session_data_name + ".meta") ) assert session_meta_data.get("version") == self.session_data_version except (AssertionError, FileNotFoundError): session_meta_data = {} session_meta_data["detection_method"] = "3d" session_meta_data["detection_status"] = ["unknown", "unknown"] self.detection_method = session_meta_data["detection_method"] self.detection_status = session_meta_data["detection_status"] pupil = fm.load_pldata_file(self.data_dir, self.session_data_name) ts_data_zip = zip(pupil.timestamps, pupil.data) ts_topic_zip = zip(pupil.timestamps, pupil.topics) self.pupil_positions = collections.OrderedDict(ts_data_zip) self.id_topics = collections.OrderedDict(ts_topic_zip) self.eye_video_loc = [None, None] self.eye_frame_num = [0, 0] for topic in self.id_topics.values(): eye_id = int(topic[-1]) self.eye_frame_num[eye_id] += 1 self.pause_switch = None self.detection_paused = False # start processes for eye_id in range(2): if self.detection_status[eye_id] != "complete": self.start_eye_process(eye_id) # either we did not start them or they failed to start (mono setup etc) # either way we are done and can publish if self.eye_video_loc == [None, None]: self.correlate_publish() def start_eye_process(self, eye_id): potential_locs = [ os.path.join(self.g_pool.rec_dir, "eye{}{}".format(eye_id, ext)) for ext in (".mjpeg", ".mp4", ".mkv") ] existing_locs = [loc for loc in potential_locs if os.path.exists(loc)] if not existing_locs: logger.error("no eye video for eye '{}' found.".format(eye_id)) self.detection_status[eye_id] = "No eye video found." return rec, file_ = os.path.split(existing_locs[0]) set_name = os.path.splitext(file_)[0] self.videoset = VideoSet(rec, set_name, fill_gaps=False) self.videoset.load_or_build_lookup() timestamp_len = (self.videoset.lookup.container_idx > -1).sum() if not timestamp_len: logger.error( "no timestamps for eye video for eye '{}' found.".format(eye_id) ) self.detection_status[eye_id] = "No eye video found." return video_loc = existing_locs[0] self.eye_frame_num[eye_id] = timestamp_len capure_settings = "File_Source", {"source_path": video_loc, "timing": None} self.notify_all( { "subject": "eye_process.should_start", "eye_id": eye_id, "overwrite_cap_settings": capure_settings, } ) self.eye_video_loc[eye_id] = video_loc self.detection_status[eye_id] = "Detecting..." def stop_eye_process(self, eye_id): self.notify_all({"subject": "eye_process.should_stop", "eye_id": eye_id}) self.eye_video_loc[eye_id] = None def recent_events(self, events): super().recent_events(events) while self.data_sub.new_data: topic = self.data_sub.recv_topic() remaining_frames = self.data_sub.recv_remaining_frames() if topic.startswith("pupil."): # pupil data only has one remaining frame payload_serialized = next(remaining_frames) pupil_datum = fm.Serialized_Dict(msgpack_bytes=payload_serialized) assert int(topic[-1]) == pupil_datum["id"] self.pupil_positions[pupil_datum["timestamp"]] = pupil_datum self.id_topics[pupil_datum["timestamp"]] = topic else: payload = self.data_sub.deserialize_payload(*remaining_frames) if payload["subject"] == "file_source.video_finished": for eyeid in (0, 1): if self.eye_video_loc[eyeid] == payload["source_path"]: logger.debug("eye {} process complete".format(eyeid)) self.detection_status[eyeid] = "complete" self.stop_eye_process(eyeid) break if self.eye_video_loc == [None, None]: self.correlate_publish() total = sum(self.eye_frame_num) self.menu_icon.indicator_stop = ( len(self.pupil_positions) / total if total else 0.0 ) def correlate_publish(self): self.g_pool.pupil_positions = pm.Bisector( tuple(self.pupil_positions.values()), tuple(self.pupil_positions.keys()) ) self.g_pool.pupil_positions_by_id = self.create_pupil_positions_by_id( self.pupil_positions, self.id_topics ) self._pupil_changed_announcer.announce_new() logger.debug("pupil positions changed") self.save_offline_data() def on_notify(self, notification): super().on_notify(notification) if notification["subject"] == "eye_process.started": self.set_detection_mapping_mode(self.detection_method) elif notification["subject"] == "eye_process.stopped": self.eye_video_loc[notification["eye_id"]] = None def cleanup(self): self.stop_eye_process(0) self.stop_eye_process(1) # close sockets before context is terminated self.data_sub = None self.save_offline_data() def save_offline_data(self): topic_data_ts = ( (self.id_topics[ts], datum, ts) for ts, datum in self.pupil_positions.items() ) with fm.PLData_Writer(self.data_dir, "offline_pupil") as writer: for topic, datum, timestamp in topic_data_ts: writer.append_serialized(timestamp, topic, datum.serialized) session_data = {} session_data["detection_method"] = self.detection_method session_data["detection_status"] = self.detection_status session_data["version"] = self.session_data_version cache_path = os.path.join(self.data_dir, "offline_pupil.meta") fm.save_object(session_data, cache_path) logger.info("Cached detected pupil data to {}".format(cache_path)) def redetect(self): self.pupil_positions.clear() # delete previously detected pupil positions self.id_topics.clear() self.g_pool.pupil_positions = pm.Bisector([], []) self.detection_finished_flag = False self.detection_paused = False for eye_id in range(2): if self.eye_video_loc[eye_id] is None: self.start_eye_process(eye_id) else: self.notify_all( { "subject": "file_source.seek", "frame_index": 0, "source_path": self.eye_video_loc[eye_id], } ) def set_detection_mapping_mode(self, new_mode): n = {"subject": "set_detection_mapping_mode", "mode": new_mode} self.notify_all(n) self.redetect() self.detection_method = new_mode def init_ui(self): super().init_ui() self.menu.label = "Offline Pupil Detector" self.menu.append( ui.Info_Text("Detects pupil positions from the recording's eye videos.") ) self.menu.append( ui.Selector( "detection_method", self, label="Detection Method", selection=["2d", "3d"], setter=self.set_detection_mapping_mode, ) ) self.menu.append(ui.Switch("detection_paused", self, label="Pause detection")) self.menu.append(ui.Button("Redetect", self.redetect)) self.menu.append( ui.Text_Input( "0", label="eye0:", getter=lambda: self.detection_status[0], setter=lambda _: _, ) ) self.menu.append( ui.Text_Input( "1", label="eye1:", getter=lambda: self.detection_status[1], setter=lambda _: _, ) ) def detection_progress(): total = sum(self.eye_frame_num) return 100 * len(self.pupil_positions) / total if total else 0.0 progress_slider = ui.Slider( "detection_progress", label="Detection Progress", getter=detection_progress, setter=lambda _: _, ) progress_slider.display_format = "%3.0f%%" self.menu.append(progress_slider) @property def detection_paused(self): return self._detection_paused @detection_paused.setter def detection_paused(self, should_pause): self._detection_paused = should_pause for eye_id in range(2): if self.eye_video_loc[eye_id] is not None: subject = "file_source." + ( "should_pause" if should_pause else "should_play" ) self.notify_all( {"subject": subject, "source_path": self.eye_video_loc[eye_id]} )
class Offline_Pupil_Detection(Pupil_Producer_Base): """docstring for Offline_Pupil_Detection""" session_data_version = 4 session_data_name = "offline_pupil" @classmethod def is_available_within_context(cls, g_pool) -> bool: if g_pool.app == "player": recording = PupilRecording(rec_dir=g_pool.rec_dir) meta_info = recording.meta_info if ( meta_info.recording_software_name == RecordingInfo.RECORDING_SOFTWARE_NAME_PUPIL_INVISIBLE ): # Disable post-hoc pupil detector in Player if Pupil Invisible recording return False return super().is_available_within_context(g_pool) @classmethod def plugin_menu_label(cls) -> str: return "Post-Hoc Pupil Detection" @classmethod def pupil_data_source_selection_order(cls) -> float: return 2.0 def __init__(self, g_pool): super().__init__(g_pool) self._detection_paused = False zmq_ctx = zmq.Context() self.data_sub = zmq_tools.Msg_Receiver( zmq_ctx, g_pool.ipc_sub_url, topics=("pupil", "notify.file_source"), hwm=100_000, ) self.data_dir = os.path.join(g_pool.rec_dir, "offline_data") os.makedirs(self.data_dir, exist_ok=True) try: session_meta_data = fm.load_object( os.path.join(self.data_dir, self.session_data_name + ".meta") ) assert session_meta_data.get("version") == self.session_data_version except (AssertionError, FileNotFoundError): session_meta_data = {} session_meta_data["detection_status"] = ["unknown", "unknown"] self.detection_status = session_meta_data["detection_status"] self._pupil_data_store = pm.PupilDataCollector() pupil_data_from_cache = pm.PupilDataBisector.load_from_file( self.data_dir, self.session_data_name ) self.publish_existing(pupil_data_from_cache) # Start offline pupil detection if not complete yet: self.eye_video_loc = [None, None] self.eye_frame_num = [0, 0] self.eye_frame_idx = [-1, -1] # start processes for eye_id in range(2): if self.detection_status[eye_id] != "complete": self.start_eye_process(eye_id) def start_eye_process(self, eye_id): potential_locs = [ os.path.join(self.g_pool.rec_dir, "eye{}{}".format(eye_id, ext)) for ext in (".mjpeg", ".mp4", ".mkv") ] existing_locs = [loc for loc in potential_locs if os.path.exists(loc)] if not existing_locs: logger.error("no eye video for eye '{}' found.".format(eye_id)) self.detection_status[eye_id] = "No eye video found." return rec, file_ = os.path.split(existing_locs[0]) set_name = os.path.splitext(file_)[0] self.videoset = VideoSet(rec, set_name, fill_gaps=False) self.videoset.load_or_build_lookup() if self.videoset.is_empty(): logger.error(f"No videos for eye '{eye_id}' found.") self.detection_status[eye_id] = "No eye video found." return video_loc = existing_locs[0] n_valid_frames = np.count_nonzero(self.videoset.lookup.container_idx > -1) self.eye_frame_num[eye_id] = n_valid_frames self.eye_frame_idx = [-1, -1] capure_settings = "File_Source", {"source_path": video_loc, "timing": None} self.notify_all( { "subject": "eye_process.should_start", "eye_id": eye_id, "overwrite_cap_settings": capure_settings, } ) self.eye_video_loc[eye_id] = video_loc self.detection_status[eye_id] = "Detecting..." @property def detection_progress(self) -> float: if not sum(self.eye_frame_num): return 0.0 progress_by_eye = [0.0, 0.0] for eye_id in (0, 1): total_frames = self.eye_frame_num[eye_id] if total_frames > 0: current_index = self.eye_frame_idx[eye_id] progress = (current_index + 1) / total_frames progress = max(0.0, min(progress, 1.0)) else: progress = 1.0 progress_by_eye[eye_id] = progress return min(progress_by_eye) def stop_eye_process(self, eye_id): self.notify_all({"subject": "eye_process.should_stop", "eye_id": eye_id}) self.eye_video_loc[eye_id] = None def recent_events(self, events): super().recent_events(events) while self.data_sub.new_data: topic = self.data_sub.recv_topic() remaining_frames = self.data_sub.recv_remaining_frames() if topic.startswith("pupil."): # pupil data only has one remaining frame payload_serialized = next(remaining_frames) pupil_datum = fm.Serialized_Dict(msgpack_bytes=payload_serialized) assert pm.PupilTopic.match(topic, eye_id=pupil_datum["id"]) timestamp = pupil_datum["timestamp"] self._pupil_data_store.append(topic, pupil_datum, timestamp) else: payload = self.data_sub.deserialize_payload(*remaining_frames) if payload["subject"] == "file_source.video_finished": for eye_id in (0, 1): if self.eye_video_loc[eye_id] == payload["source_path"]: logger.debug("eye {} process complete".format(eye_id)) self.eye_frame_idx[eye_id] = self.eye_frame_num[eye_id] self.detection_status[eye_id] = "complete" self.stop_eye_process(eye_id) break if self.eye_video_loc == [None, None]: data = self._pupil_data_store.as_pupil_data_bisector() self.publish_new(pupil_data_bisector=data) if payload["subject"] == "file_source.current_frame_index": for eye_id in (0, 1): if self.eye_video_loc[eye_id] == payload["source_path"]: self.eye_frame_idx[eye_id] = payload["index"] self.menu_icon.indicator_stop = self.detection_progress def publish_existing(self, pupil_data_bisector): self.g_pool.pupil_positions = pupil_data_bisector self._pupil_changed_announcer.announce_existing() def publish_new(self, pupil_data_bisector): self.g_pool.pupil_positions = pupil_data_bisector self._pupil_changed_announcer.announce_new() logger.debug("pupil positions changed") self.save_offline_data() def on_notify(self, notification): super().on_notify(notification) if notification["subject"] == "eye_process.started": pass elif notification["subject"] == "eye_process.stopped": self.eye_video_loc[notification["eye_id"]] = None def cleanup(self): self.stop_eye_process(0) self.stop_eye_process(1) # close sockets before context is terminated self.data_sub = None self.save_offline_data() def save_offline_data(self): self.g_pool.pupil_positions.save_to_file(self.data_dir, "offline_pupil") session_data = {} session_data["detection_status"] = self.detection_status session_data["version"] = self.session_data_version cache_path = os.path.join(self.data_dir, "offline_pupil.meta") fm.save_object(session_data, cache_path) logger.info("Cached detected pupil data to {}".format(cache_path)) def redetect(self): self._pupil_data_store.clear() self.g_pool.pupil_positions = self._pupil_data_store.as_pupil_data_bisector() self._pupil_changed_announcer.announce_new() self.detection_finished_flag = False self.detection_paused = False for eye_id in range(2): if self.eye_video_loc[eye_id] is None: self.start_eye_process(eye_id) else: self.notify_all( { "subject": "file_source.seek", "frame_index": 0, "source_path": self.eye_video_loc[eye_id], } ) def init_ui(self): super().init_ui() self.menu.append(ui.Info_Text("Detect pupil positions from eye videos.")) self.menu.append(ui.Switch("detection_paused", self, label="Pause detection")) self.menu.append(ui.Button("Redetect", self.redetect)) self.menu.append( ui.Text_Input( "0", label="eye0:", getter=lambda: self.detection_status[0], setter=lambda _: _, ) ) self.menu.append( ui.Text_Input( "1", label="eye1:", getter=lambda: self.detection_status[1], setter=lambda _: _, ) ) progress_slider = ui.Slider( "detection_progress", label="Detection Progress", getter=lambda: 100 * self.detection_progress, setter=lambda _: _, ) progress_slider.display_format = "%3.0f%%" self.menu.append(progress_slider) @property def detection_paused(self): return self._detection_paused @detection_paused.setter def detection_paused(self, should_pause): self._detection_paused = should_pause for eye_id in range(2): if self.eye_video_loc[eye_id] is not None: subject = "file_source." + ( "should_pause" if should_pause else "should_play" ) self.notify_all( {"subject": subject, "source_path": self.eye_video_loc[eye_id]} )