Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
    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)
Exemplo n.º 3
0
    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()
Exemplo n.º 4
0
    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)
Exemplo n.º 6
0
 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])
Exemplo n.º 7
0
 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
     })
Exemplo n.º 8
0
    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}
Exemplo n.º 9
0
    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)