def calibrate_section(self,sec): if sec['bg_task']: sec['bg_task'].cancel() sec['status'] = 'starting calibration'#this will be overwritten on sucess sec['gaze_positions'] = [] # reset interim buffer for given section calib_list = list(chain(*self.g_pool.pupil_positions_by_frame[slice(*sec['calibration_range'])])) map_list = list(chain(*self.g_pool.pupil_positions_by_frame[slice(*sec['mapping_range'])])) if sec['calibration_method'] == 'circle_marker': ref_list = [r for r in self.circle_marker_positions if sec['calibration_range'][0] <= r['index'] <= sec['calibration_range'][1]] elif sec['calibration_method'] == 'natural_features': ref_list = self.manual_ref_positions if not calib_list: logger.error('No pupil data to calibrate section "{}"'.format(self.sections.index(sec) + 1)) sec['status'] = 'calibration failed' return if not calib_list: logger.error('No referece marker data to calibrate section "{}"'.format(self.sections.index(sec) + 1)) sec['status'] = 'calibration failed' return if sec["mapping_method"] == '3d' and '2d' in calib_list[len(calib_list)//2]['method']: # select median pupil datum from calibration list and use its detection method as mapping method logger.warning("Pupil data is 2d, calibration and mapping mode forced to 2d.") sec["mapping_method"] = '2d' fake = setup_fake_pool(self.g_pool.capture.frame_size, detection_mode=sec["mapping_method"],rec_dir=self.g_pool.rec_dir) generator_args = (fake, ref_list, calib_list, map_list) logger.info('Calibrating "{}" in {} mode...'.format(self.sections.index(sec) + 1,sec["mapping_method"])) sec['bg_task'] = bh.Task_Proxy('{}'.format(self.sections.index(sec) + 1), calibrate_and_map, args=generator_args)
def _classify(self): ''' classify fixations ''' if self.g_pool.app == 'exporter': return if self.bg_task: self.bg_task.cancel() gaze_data = [ gp for gp in self.g_pool.gaze_positions if gp['confidence'] >= self.g_pool.min_data_confidence ] if not gaze_data: logger.error('No gaze data available to find fixations') self.status = 'Fixation detection failed' return cap = Empty() cap.frame_size = self.g_pool.capture.frame_size cap.intrinsics = self.g_pool.capture.intrinsics cap.timestamps = self.g_pool.capture.timestamps generator_args = (cap, gaze_data, np.deg2rad(self.max_dispersion), self.min_duration / 1000, self.max_duration / 1000) self.fixations = deque() self.bg_task = bh.Task_Proxy('Fixation detection', detect_fixations, args=generator_args)
def add_export(self, export_range, export_dir): logger.warning("Adding new video export process.") rec_dir = self.g_pool.rec_dir user_dir = self.g_pool.user_dir # export_range.stop is exclusive start_frame, end_frame = export_range # Here we make clones of every plugin that supports it. # So it runs in the current config when we lauch the exporter. plugins = self.g_pool.plugins.get_initializers() out_file_path = verify_out_file_path(self.rec_name, export_dir) pre_computed = { 'gaze_positions': self.g_pool.gaze_positions, 'pupil_positions': self.g_pool.pupil_positions, 'pupil_data': self.g_pool.pupil_data, 'fixations': self.g_pool.fixations } args = (rec_dir, user_dir, self.g_pool.min_data_confidence, start_frame, end_frame, plugins, out_file_path, pre_computed) process = bh.Task_Proxy('Pupil Export {}'.format(out_file_path), export, args=args) process.out_file_path = out_file_path process.frames_to_export = end_frame - start_frame process.status = '' process.progress = 0 self.exports.append(process) logger.debug("Starting export as new process {}".format(process)) self._update_ui()
def _classify(self): ''' classify fixations ''' if self.g_pool.app == 'exporter': return if self.bg_task: self.bg_task.cancel() gaze_data = [gp.serialized for gp in self.g_pool.gaze_positions] cap = Empty() cap.frame_size = self.g_pool.capture.frame_size cap.intrinsics = self.g_pool.capture.intrinsics cap.timestamps = self.g_pool.capture.timestamps generator_args = (cap, gaze_data, np.deg2rad(self.max_dispersion), self.min_duration / 1000, self.max_duration / 1000, self.g_pool.min_data_confidence) self.fixation_data = deque() self.fixation_start_ts = deque() self.fixation_stop_ts = deque() self.bg_task = bh.Task_Proxy('Fixation detection', detect_fixations, args=generator_args)
def calibrate_section(self, sec): if 'bg_task' in sec: sec['bg_task'].cancel() sec['status'] = 'Starting calibration' # This will be overwritten on success try: sec['gaze'].clear() sec['gaze_ts'].clear() except KeyError: sec['gaze'] = collections.deque() sec['gaze_ts'] = collections.deque() calibration_window = pm.exact_window(self.g_pool.timestamps, sec['calibration_range']) mapping_window = pm.exact_window(self.g_pool.timestamps, sec['mapping_range']) calibration_pupil_pos = self.g_pool.pupil_positions.by_ts_window(calibration_window) mapping_pupil_pos = self.g_pool.pupil_positions.by_ts_window(mapping_window) if sec['calibration_method'] == 'circle_marker': ref_list = self.circle_marker_positions elif sec['calibration_method'] == 'natural_features': ref_list = self.manual_ref_positions start = sec['calibration_range'][0] end = sec['calibration_range'][1] ref_list = [r for r in ref_list if start <= r['index'] <= end] if not len(calibration_pupil_pos): logger.error('No pupil data to calibrate section "{}"'.format(self.sections.index(sec) + 1)) sec['status'] = 'Calibration failed. Not enough pupil positions.' return if not ref_list: logger.error('No referece marker data to calibrate section "{}"'.format(self.sections.index(sec) + 1)) sec['status'] = 'Calibration failed. Not enough reference positions.' return if sec["mapping_method"] == '3d' and '2d' in calibration_pupil_pos[len(calibration_pupil_pos)//2]['method']: # select median pupil datum from calibration list and use its detection method as mapping method logger.warning("Pupil data is 2d, calibration and mapping mode forced to 2d.") sec["mapping_method"] = '2d' fake = setup_fake_pool(self.g_pool.capture.frame_size, self.g_pool.capture.intrinsics, sec["mapping_method"], self.g_pool.rec_dir, self.g_pool.min_calibration_confidence) calibration_pupil_pos = [pp.serialized for pp in calibration_pupil_pos] mapping_pupil_pos = [pp.serialized for pp in mapping_pupil_pos] generator_args = (fake, ref_list, calibration_pupil_pos, mapping_pupil_pos, sec['x_offset'], sec['y_offset']) logger.info('Calibrating section {} ({}) in {} mode...'.format(self.sections.index(sec) + 1, sec['label'], sec["mapping_method"])) sec['bg_task'] = bh.Task_Proxy('{}'.format(self.sections.index(sec) + 1), calibrate_and_map, args=generator_args)
def detect_recordings(self): if self.search_task: self.search_task.cancel() self.search_task = None self.search_button.outer_label = '' self.search_button.label = 'Search' else: self.search_button.outer_label = 'Searching...' self.search_button.label = 'Cancel' self.search_task = bh.Task_Proxy('Searching recordings in {}'.format(self.source_dir), get_recording_dirs, args=[self.source_dir])
def init_export(self): self.in_queue = False args = (self.rec_dir, self.g_pool.user_dir, self.g_pool.min_data_confidence, None, None, self.plugins, self.out_file_path, {}) self.process = bh.Task_Proxy('Pupil Batch Export {}'.format( self.out_file_path), export_function, args=args) self.notify_all({ 'subject': 'batch_export.started', 'out_file_path': self.out_file_path })
def add_export_job( self, export_range, export_dir, plugin_name, input_name, output_name, process_frame, export_timestamps, ): os.makedirs(export_dir, exist_ok=True) logger.info("Exporting to {}".format(export_dir)) try: distorted_video_loc = [ f for f in glob(os.path.join(self.g_pool.rec_dir, input_name + ".*")) if os.path.splitext(f)[-1] in (".mp4", ".mkv", ".avi", ".mjpeg") ][0] except IndexError: raise FileNotFoundError("No Video " + input_name + " found") target_video_loc = os.path.join(export_dir, output_name + ".mp4") generator_args = ( self.g_pool.timestamps, distorted_video_loc, target_video_loc, export_range, process_frame, export_timestamps, ) task = bh.Task_Proxy( plugin_name + " Video Export", export_processed_h264, args=generator_args ) self.export_tasks.append(task) return {"export_folder": export_dir}
def export_data(self, export_range, export_dir): rec_start = self.get_recording_start_date() im_dir = os.path.join(export_dir, 'iMotions_{}'.format(rec_start)) os.makedirs(im_dir, exist_ok=True) user_warned_3d_only = False self.output = im_dir logger.info('Exporting to {}'.format(im_dir)) if self.export_task: self.export_task.cancel() distorted_video_loc = [ f for f in glob(os.path.join(self.g_pool.rec_dir, "world.*")) if os.path.splitext(f)[-1] in ('.mp4', '.mkv', '.avi', '.mjpeg') ][0] target_video_loc = os.path.join(im_dir, 'scene.mp4') generator_args = (distorted_video_loc, target_video_loc, export_range) self.export_task = bh.Task_Proxy('iMotions Video Export', export_undistorted_h264, args=generator_args) info_src = os.path.join(self.g_pool.rec_dir, 'info.csv') info_dest = os.path.join(im_dir, 'iMotions_info.csv') copy2(info_src, info_dest) # copy info.csv file with open(os.path.join(im_dir, 'gaze.tlv'), 'w', encoding='utf-8', newline='') as csvfile: csv_writer = csv.writer(csvfile, delimiter='\t') csv_writer.writerow( ('GazeTimeStamp', 'MediaTimeStamp', 'MediaFrameIndex', 'Gaze3dX', 'Gaze3dY', 'Gaze3dZ', 'Gaze2dX', 'Gaze2dY', 'PupilDiaLeft', 'PupilDiaRight', 'Confidence')) for media_idx in range(*export_range): media_timestamp = self.g_pool.timestamps[media_idx] media_window = pm.enclosing_window(self.g_pool.timestamps, media_idx) for g in self.g_pool.gaze_positions.by_ts_window(media_window): try: pupil_dia = {} for p in g['base_data']: pupil_dia[p['id']] = p['diameter_3d'] pixel_pos = denormalize(g['norm_pos'], self.g_pool.capture.frame_size, flip_y=True) undistorted3d = self.g_pool.capture.intrinsics.unprojectPoints( pixel_pos) undistorted2d = self.g_pool.capture.intrinsics.projectPoints( undistorted3d, use_distortion=False) data = ( g['timestamp'], media_timestamp, media_idx - export_range[0], *g['gaze_point_3d'], # Gaze3dX/Y/Z *undistorted2d.flat, # Gaze2dX/Y pupil_dia.get(1, 0.), # PupilDiaLeft pupil_dia.get(0, 0.), # PupilDiaRight g['confidence']) # Confidence except KeyError: if not user_warned_3d_only: logger.error( 'Currently, the iMotions export only supports 3d gaze data' ) user_warned_3d_only = True continue csv_writer.writerow(data)