Exemplo n.º 1
0
 def setUp(self):
     ldfh = LegacyDataFileSystemHelper(head_path=self.__class__.HEAD_PATH, )
     self.latitude_series = ldfh.get_field_from_npz_file(
         session_dir_name=self.__class__.SESSION_DIR,
         filename=LegacyDataFileSystemHelper.TAG_NPZ_FILE,
         fieldname=LegacyDataFileSystemHelper.TAG_LATITUDE_FIELD,
     )
     self.longitude_series = ldfh.get_field_from_npz_file(
         session_dir_name=self.__class__.SESSION_DIR,
         filename=LegacyDataFileSystemHelper.TAG_NPZ_FILE,
         fieldname=LegacyDataFileSystemHelper.TAG_LONGITUDE_FIELD,
     )
     self.time_series = ldfh.get_field_from_npz_file(
         session_dir_name=self.__class__.SESSION_DIR,
         filename=LegacyDataFileSystemHelper.TAG_NPZ_FILE,
         fieldname=LegacyDataFileSystemHelper.TAG_TIME_FIELD,
     )
     self.lat_long = self.latitude_series[0], self.longitude_series[0]
 def setUp(self):
     ldfh = LegacyDataFileSystemHelper(self.__class__.HEAD_PATH)
     self.latitude_series = ldfh.get_field_from_npz_file(
         self.__class__.SESSION_DIR_NAME,
         LegacyDataFileSystemHelper.TAG_NPZ_FILE,
         LegacyDataFileSystemHelper.TAG_LATITUDE_FIELD,
     )
     self.longitude_series = ldfh.get_field_from_npz_file(
         self.__class__.SESSION_DIR_NAME,
         LegacyDataFileSystemHelper.TAG_NPZ_FILE,
         LegacyDataFileSystemHelper.TAG_LONGITUDE_FIELD,
     )
     self.time_series = ldfh.get_field_from_npz_file(
         self.__class__.SESSION_DIR_NAME,
         LegacyDataFileSystemHelper.TAG_NPZ_FILE,
         LegacyDataFileSystemHelper.TAG_TIME_FIELD,
     )
     return self
    def __init__(
            self,
            session_dir: str,
            input_data_head_path=Path('/Volumes/WD'),
            results_head_path=Path('.') / 'data/calibration_data',
            cache_dir_path=Path('/Volumes/WD/image_cache'),
            num_points=1,
    ):
        self._session_dir = session_dir
        self._input_data_head_path = input_data_head_path
        self._results_head_path = results_head_path
        self._cache_dir_path = cache_dir_path
        self._num_points = num_points

        # Helpers
        self._ldfh = LDFH(self._input_data_head_path)
        self._calibration_data_filer = CalibrationDataFiler(
            self._results_head_path)

        self.maxima_text_color = 'red'
        self.minima_text_color = 'green'
        self.fontsize = 8

        # Lazy initializers
        self._pan_motor_read_data_series = None
        self._base_time_series = None
        self._motor_maxima = None
        self._motor_minima = None
        self._motor_maxima_from_series = None
        self._motor_minima_from_series = None
        self._motor_maxima_from_archive = None
        self._motor_minima_from_archive = None
        self._archived_results = None
        self._selected_frames_from_archive = None

        # State
        self._base_time = None
        self._motor_idx = None
        self._min_or_max = None
        self._alignment_results = []
    def __init__(self,
                 session_dir: str,
                 input_data_head_path=Path('/Volumes/WD'),
                 results_head_path=Path('.')  / 'data/calibration_data',
                 cache_dir_path=Path('/Volumes/WD/image_cache'),
                ):
        self._session_dir = session_dir
        self._input_data_head_path = input_data_head_path
        self._results_head_path = results_head_path
        self._cache_dir_path = cache_dir_path

        # helpers
        self._ldfh = LDFH(self._input_data_head_path)
        self._calibration_data_filer = CalibrationDataFiler(self._results_head_path)

        # state
        self._alignment_data = []

        # lazy inits
        self._latitude_series = None
        self._longitude_series = None
        self._time_series = None
    def __init__(
            self,
            session_dir: str,
            input_data_head_path=Path('/Volumes/WD'),
            results_head_path=Path('.') / 'data/calibration_data',
            cache_dir_path=Path('/Volumes/WD/image_cache'),
    ):
        self._session_dir = session_dir
        self._input_data_head_path = input_data_head_path
        self._results_head_path = results_head_path
        self._cache_dir_path = cache_dir_path

        # settings
        self._anotation_color = 'green'
        self._anotation_fontsize = '8'

        self._ldfh = LDFH(self._input_data_head_path)
        self._calibration_data_filer = CalibrationDataFiler(
            self._results_head_path)

        # lazy inits
        self._fov_time_series = None
        self._fov_data_series = None
        self._unique_fovs = None
        self._transition_indexes = None
        self._transition_levels = None
        self._unique_transitions = None

        # state
        self._alignment_results = {}
        self._current_transition = None
        self._alignment_stats_delays = []
        self._alignment_stats_durations = []
        self._alignment_stats_zoom_out_delays = []
        self._alignment_stats_zoom_out_durations = []
        self._alignment_stats_zoom_in_delays = []
        self._alignment_stats_zoom_in_durations = []
    def setUp(self):
        ldfh = LegacyDataFileSystemHelper(self.__class__.INPUT_DATA_HEAD_PATH)
        image_from_video_grabber = ImageFromVideoGrabber(
            ldfh.get_video_path(self.__class__.SESSION_DIR),
            cache_dir_path=self.__class__.IMAGE_CACHE_DIR_PATH,
        )

        middle_frame = int(image_from_video_grabber.get_frame_count() / 2)
        images_from_video = image_from_video_grabber.get_images_around_frame_number(
            middle_frame, 100, 100)
        video_scrub_picker = ScrubPicker(images_from_video=images_from_video, )

        latitude_series = ldfh.get_field_from_npz_file(
            session_dir_name=self.__class__.SESSION_DIR,
            filename=LegacyDataFileSystemHelper.TAG_NPZ_FILE,
            fieldname=LegacyDataFileSystemHelper.TAG_LATITUDE_FIELD,
        )
        longitude_series = ldfh.get_field_from_npz_file(
            session_dir_name=self.__class__.SESSION_DIR,
            filename=LegacyDataFileSystemHelper.TAG_NPZ_FILE,
            fieldname=LegacyDataFileSystemHelper.TAG_LONGITUDE_FIELD,
        )
        time_series = ldfh.get_field_from_npz_file(
            session_dir_name=self.__class__.SESSION_DIR,
            filename=LegacyDataFileSystemHelper.TAG_NPZ_FILE,
            fieldname=LegacyDataFileSystemHelper.TAG_TIME_FIELD,
        )
        geo_map_scrubber = GeoMapScrubber(
            latitude_series=latitude_series,
            longitude_series=longitude_series,
            time_series=time_series,
            width=700,
        )

        self.video_plus_map_scrub_picker = VideoPlusMapScrubPicker(
            video_scrub_picker=video_scrub_picker,
            geo_map_scrubber=geo_map_scrubber,
        )

        return self
Exemplo n.º 7
0
 def setUp(self):
     self.ldfh = LDFH(self.__class__.HEAD_PATH)
Exemplo n.º 8
0
class TestInputDataStructure(unittest.TestCase):
    '''Verifies that the legacy data files are in the expected directory structure and
    contain the expected data. Currently the head of the file heirarchy is the
    SESSION_DB folder'''

    # Change this to the path containing the SESSION_DB folder
    HEAD_PATH = Path('/Volumes/WD')

    def setUp(self):
        self.ldfh = LDFH(self.__class__.HEAD_PATH)

    def test_top_level_dir_exists(self):
        self.assertTrue(self.ldfh.get_top_level_dir_path().exists())

    def test_at_least_one_session_dir_exists(self):
        try:
            num_session_dirs = sum(1 for _ in self.ldfh.get_session_dirs())
            self.assertGreater(num_session_dirs, 0)
        except FileNotFoundError:
            self.fail('Top level dir not found')

    def test_all_children_of_top_level_dir_are_dirs(self):
        try:
            for session_dir in self.ldfh.get_session_dirs():
                self.assertTrue(session_dir.is_dir())
        except FileNotFoundError:
            self.fail('Top level dir not found')

    def test_directory_structure_correct(self):
        for session_dir_path in self.ldfh.get_session_dirs():
            self.verify_directory_and_sub_directories(
                LDFH.DIRECTORY_STRUCTURE,
                session_dir_path,
            )

    def test_no_extra_files_in_npz_dirs(self):
        for session_dir_path in self.ldfh.get_session_dirs():
            npz_dir = session_dir_path / LDFH.NPZ_DIR
            for p_path in npz_dir.iterdir():
                if not p_path.name in LDFH.DIRECTORY_STRUCTURE[LDFH.NPZ_DIR]:
                    if '.DS_Store' not in p_path.name:
                        self.fail(f'Extra NPZ file: {p_path}')

    def test_npz_fields_exist_with_no_extras(self):
        for session_dir_path in self.ldfh.get_session_dirs():
            for npz_file, expected_npz_fields in LDFH.NPZ_FIELDS.items():
                npz_file_path = session_dir_path / LDFH.NPZ_DIR / npz_file
                actual_npz_fields = np.load(npz_file_path).files
                for expected_field in expected_npz_fields:
                    if expected_field not in actual_npz_fields:
                        self.fail(f'No {expected_field} in {npz_file_path}')
                for actual_field in actual_npz_fields:
                    if actual_field not in expected_npz_fields:
                        self.fail(
                            f'Extra field: {actual_field} in {npz_file_path}')

    def test_npz_fields_have_the_same_length(self):
        for npz_file_path in self.ldfh.get_all_npz_file_paths():
            np_data = np.load(npz_file_path)
            actual_npz_fields = np_data.files
            name_of_first_field = actual_npz_fields[0]
            length_of_first_field = np_data[name_of_first_field].size
            self.assertGreater(
                length_of_first_field,
                0,
                msg=(f'{name_of_first_field} in {npz_file_path} '
                     f'is too short ({length_of_first_field}).'))
            for actual_field in actual_npz_fields:
                field_len = np_data[actual_field].size
                self.assertEqual(
                    field_len,
                    length_of_first_field,
                    msg=
                    (f'{actual_field} ({field_len}) '
                     f'not same length as {name_of_first_field} ({length_of_first_field}) '
                     f'in {npz_file_path}'))

    def test_non_implemented_fields_are_full_of_nones(self):
        for npz_file_path in self.ldfh.get_all_npz_file_paths():
            np_data = np.load(npz_file_path)
            for field in np_data.files:
                if field in LDFH.UNIMPLEMENTED_FIELDS:
                    field_data = np_data[field]
                    if field_data.size == 0:
                        self.fail(
                            f'Apparent unimplemented field: {field} in {npz_file_path} is empty'
                        )
                    if (field_data != None).all():  # pylint: disable=C0121
                        self.fail(
                            f'Apparent unimplemented field: {field} in {npz_file_path} not full of Nones'
                        )

    def test_implemented_fields_have_no_nones(self):
        for npz_file_path in self.ldfh.get_all_npz_file_paths():
            np_data = np.load(npz_file_path)
            for field in np_data.files:
                if field not in LDFH.UNIMPLEMENTED_FIELDS:
                    field_data = np_data[field]
                    if field_data.size == 0:
                        self.fail(
                            f'Apparent implemented field: {field} in {npz_file_path} is empty'
                        )
                    if (field_data == None).any():  # pylint: disable=C0121
                        self.fail(
                            f'Apparent implemented field: {field} in {npz_file_path} has a None'
                        )

    def test_video_file_can_be_opened_with_cv2(self):
        for video_path in self.ldfh.get_video_paths():
            if not cv2.VideoCapture(str(video_path.resolve())).isOpened():
                self.fail(f'Could not open {video_path} with cv2.VideoCapture')

    def test_time_fields_duration_same_as_video_duration(self):
        for session_dir in self.ldfh.get_session_dirs():
            video_path = self.ldfh.get_video_path(session_dir)
            assert video_path is not None,\
                   (f'No video found in {session_dir.name} '
                    f'cannot run {self.test_time_fields_duration_same_as_video_duration.__name__}')
            video_duration = self.get_duration_ms_of_video_at_path(video_path)

            for npz_path in self.ldfh.get_npz_file_paths(session_dir):
                npz_duration = self.get_duration_in_ms_for_npz_with_a_time_field(
                    npz_path)
                if npz_duration is None:
                    continue
                delta = 200
                self.assertAlmostEqual(
                    video_duration,
                    npz_duration,
                    delta=delta,
                    msg=
                    (f'{session_dir.name}: video duration: ({video_duration}) '
                     f'not within {delta} ms of '
                     f'{npz_path.name} timefield duration ({npz_duration})'))

    def test_all_time_fields_monotonically_asscend(self):
        for session_dir in self.ldfh.get_session_dirs():
            for field, data in self.ldfh.get_npz_time_fields(
                    session_dir).items():
                self.assertTrue(
                    self.field_is_monotonically_increasing(data),
                    msg=(f'{session_dir.name}: '
                         f'{field} is not monotonically increasing.'))

    def test_maximum_time_tick_size(self):
        for session_dir in self.ldfh.get_session_dirs():
            for field_name, data in self.ldfh.get_npz_time_fields(
                    session_dir).items():
                max_tick_size = np.max(np.diff(data))
                # min_tick_size = np.min(np.diff(data))
                self.assertLess(
                    max_tick_size,
                    340,
                    msg=(f'{session_dir.name}: {field_name}: '
                         f'max tick size to large ({max_tick_size})'))

    def verify_directory_and_sub_directories(self, dir_structure, path: Path):
        if isinstance(dir_structure, dict):
            for child in dir_structure.keys():
                self.verify_wild_path_exists(path, child)
                for wild in path.glob(child):
                    self.verify_directory_and_sub_directories(
                        dir_structure[child], path / wild)
        elif isinstance(dir_structure, list):
            for filename in dir_structure:
                self.verify_wild_path_exists(path, filename)

    def verify_wild_path_exists(self, path, dir_or_file_with_wild_chars):
        wilds = path.glob(dir_or_file_with_wild_chars)
        num_wilds = sum(1 for _ in wilds)
        if num_wilds < 1:
            self.fail(f'{path / dir_or_file_with_wild_chars} not found.')
        if num_wilds > 1 and dir_or_file_with_wild_chars not in LDFH.MULTIPLE_MATCHING_FILES_ALLOWED:
            self.fail(f'Too many {path / dir_or_file_with_wild_chars} found.')
        for wild in wilds:
            self.assertTrue((path / wild).exists())

    def get_duration_ms_of_video_at_path(self, video_path):
        cap = cv2.VideoCapture(str(video_path.resolve()))
        if not cap.isOpened():
            return 0
        cap.set(cv2.CAP_PROP_POS_AVI_RATIO, 1)
        return cap.get(cv2.CAP_PROP_POS_MSEC)

    def get_duration_in_ms_for_npz_with_a_time_field(self, npz_path):
        npz_data = np.load(npz_path)
        time_fields = [
            field for field in npz_data.files if field in LDFH.TIME_FIELDS
        ]
        if not time_fields:
            return None
        time_data = npz_data[time_fields[0]]
        return time_data[-1] - time_data[0]

    def field_is_monotonically_increasing(self, data):
        return np.all(np.diff(data) >= 0)
    def setUp(self):
        head_path = Path('/Volumes/WD')
        session_dir = 'Aug_17_Palo_Alto_High_2nd_time_B80_ottofillmore'

        ldfh = LDFH(head_path)

        tag_latitude_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.TAG_NPZ_FILE,
            fieldname=LDFH.TAG_LATITUDE_FIELD,
        )
        tag_longitude_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.TAG_NPZ_FILE,
            fieldname=LDFH.TAG_LONGITUDE_FIELD,
        )
        self.tag_time_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.TAG_NPZ_FILE,
            fieldname=LDFH.TAG_TIME_FIELD,
        )

        self.geo_map_scrubber = GeoMapScrubber(
            latitude_series=tag_latitude_series,
            longitude_series=tag_longitude_series,
            time_series=self.tag_time_series,
        )

        tag = Tag(
            latitude_series=tag_latitude_series,
            longitude_series=tag_longitude_series,
            time_series=self.tag_time_series,
            map_coordinate_transformer=self.geo_map_scrubber.
            _get_map_coordinate_transformer(),
        )

        self.base_latitude_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.TAG_NPZ_FILE,
            fieldname=LDFH.BASE_LATITUDE_FIELD,
        )
        self.base_longitude_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.TAG_NPZ_FILE,
            fieldname=LDFH.BASE_LONGITUDE_FIELD,
        )

        reported_base = Base(
            gps_latitude_series=self.base_latitude_series,
            gps_longitude_series=self.base_longitude_series,
            map_coordinate_transformer=self.geo_map_scrubber.
            _get_map_coordinate_transformer(),
        )

        # Base position we believe to be correct based on where we believe we set up
        # the base on the field. Point picked on map using GeoMapScrubber
        # x: 132 y: 612 latitude: 37.38639419602273 longitude: -122.11008779357967
        actual_base_latitude_series = np.full(self.base_latitude_series.size,
                                              37.38639419602273)
        actual_base_longitude_series = np.full(self.base_longitude_series.size,
                                               -122.11008779357967)

        actual_base = Base(
            gps_latitude_series=actual_base_latitude_series,
            gps_longitude_series=actual_base_longitude_series,
            map_coordinate_transformer=self.geo_map_scrubber.
            _get_map_coordinate_transformer(),
        )

        fov_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.LENS_NPZ_FILE,
            fieldname=LDFH.LENS_FOV_FIELD,
        )
        fov_time_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.LENS_NPZ_FILE,
            fieldname=LDFH.LENS_TIME_FIELD,
        )

        self.tag_position_analyzer_with_actual_base = TagPositionInStableFovSegmentsAnalyzer(
            fov_series=fov_series,
            fov_time_series=fov_time_series,
            tag=tag,
            base=actual_base,
        )

        self.tag_position_analyzer_with_reported_base = TagPositionInStableFovSegmentsAnalyzer(
            fov_series=fov_series,
            fov_time_series=fov_time_series,
            tag=tag,
            base=reported_base,
        )

        return self
Exemplo n.º 10
0
class FovTimebaseAligner:
    '''Determines with user assistance via scrubber picker statistics for offset in
       time between fov timebase and video time'''

    AGGREGATE_RESULTS = 'aggregate_results'
    UNIQUE_FOVS = 'unique_fovs'
    UNIQUE_TRANSITIONS = 'unique_transitions'

    INDIVIDUAL_RESULTS = 'individual_results'

    FOV_DATA = 'fov_data'
    TRANSITION_LEVELS = 'transition_levels'
    TRANSITION_INDEX = 'transition_index'
    TRANSITION_TIME = 'transition_time'

    BEGIN = 'begin'
    END = 'end'

    ALL = 'all'
    ZOOM_IN = 'zoom_in'
    ZOOM_OUT = 'zoom_out'

    VIDEO_DATA = 'video_data'
    FRAME_NUM = 'frame_num'
    TIME_MS = 'time_ms'
    DURATION = 'duration'
    FRAMES = 'frames'

    ALIGNMENT = 'alignment'
    DELAY_FOV_TO_VIDEO = 'delay_fov_to_video'

    ALIGNMENT_STATS = 'alignment_stats'
    DURATION = 'duration'
    DELAY = 'delay'
    MIN = 'min'
    MAX = 'max'
    MEAN = 'mean'

    def __init__(
            self,
            session_dir: str,
            input_data_head_path=Path('/Volumes/WD'),
            results_head_path=Path('.') / 'data/calibration_data',
            cache_dir_path=Path('/Volumes/WD/image_cache'),
    ):
        self._session_dir = session_dir
        self._input_data_head_path = input_data_head_path
        self._results_head_path = results_head_path
        self._cache_dir_path = cache_dir_path

        # settings
        self._anotation_color = 'green'
        self._anotation_fontsize = '8'

        self._ldfh = LDFH(self._input_data_head_path)
        self._calibration_data_filer = CalibrationDataFiler(
            self._results_head_path)

        # lazy inits
        self._fov_time_series = None
        self._fov_data_series = None
        self._unique_fovs = None
        self._transition_indexes = None
        self._transition_levels = None
        self._unique_transitions = None

        # state
        self._alignment_results = {}
        self._current_transition = None
        self._alignment_stats_delays = []
        self._alignment_stats_durations = []
        self._alignment_stats_zoom_out_delays = []
        self._alignment_stats_zoom_out_durations = []
        self._alignment_stats_zoom_in_delays = []
        self._alignment_stats_zoom_in_durations = []

    def visualize(self):
        fig, axis = plt.subplots()  # pylint: disable=W0612
        axis.plot(self.get_normalized_fov_time(), self.get_fov_data_series())

        for key, hsh in self.get_unique_transitions().items():
            axis.text(
                self.get_normalized_fov_time()[hsh[
                    self.__class__.TRANSITION_INDEX]],
                hsh[self.__class__.TRANSITION_LEVELS][0],
                key,
                color=self._anotation_color,
                fontsize=self._anotation_fontsize,
            )

        plt.show()

    def get_fov_time_series(self):
        if self._fov_time_series is None:
            self._fov_time_series =\
                self._ldfh.get_field_from_npz_file(self._session_dir,
                                                   LDFH.LENS_NPZ_FILE,
                                                   LDFH.LENS_TIME_FIELD,
                                                  )
        return self._fov_time_series

    def get_fov_data_series(self):
        if self._fov_data_series is None:
            self._fov_data_series =\
                self._ldfh.get_field_from_npz_file(self._session_dir,
                                                   LDFH.LENS_NPZ_FILE,
                                                   LDFH.LENS_FOV_FIELD,
                                                  )
        return self._fov_data_series

    def get_normalized_fov_time(self):
        return self.get_fov_time_series() - self.get_fov_data_series()[0]

    def get_unique_fovs(self):
        if self._unique_fovs is None:
            self._unique_fovs = np.unique(self.get_fov_data_series())
        return self._unique_fovs

    def get_fov_transition_indexes(self):
        if self._transition_indexes is None:
            self._transition_indexes = self.get_fov_data_series().transitions()
        return self._transition_indexes

    def get_fov_transition_levels(self):
        if self._transition_levels is None:
            self._transition_levels =\
                self.get_fov_data_series().levels_around_transitions(self.get_fov_transition_indexes())
        return self._transition_levels

    def get_unique_transitions(self):
        if self._unique_transitions is None:
            r = {}
            for idx, transition_index in enumerate(
                    self.get_fov_transition_indexes()):
                transition_levels = self.get_fov_transition_levels()[idx]

                if str(transition_levels) in r:
                    continue

                r[str(transition_levels)] = {
                    self.__class__.TRANSITION_LEVELS:
                    transition_levels,
                    self.__class__.TRANSITION_INDEX:
                    transition_index,
                    self.__class__.TRANSITION_TIME:
                    self.get_fov_time_series()[transition_index],
                }

            self._unique_transitions = r
        return self._unique_transitions

    def show_scrub_picker_for_transitions(self):
        for _, transition in self.get_unique_transitions().items():
            self._current_transition = transition
            self.show_scrub_picker_for_time(
                transition[self.__class__.TRANSITION_INDEX],
                transition[self.__class__.TRANSITION_TIME])

    def show_scrub_picker_for_time(self, idx, time):
        ScrubPicker(
            images_from_video=self.get_images_around_time(time),
            selector_type=ScrubPicker.SELECT_RANGE,
            callback=self._scrubber_callback,
            selected_start_frame_num=None,
        ).run()

    def get_images_around_time(self, time):
        return self.get_ifv_grabber().get_images_around_time_ms(time,
                                                                before=50,
                                                                after=80)

    def get_ifv_grabber(self) -> ImageFromVideoGrabber:
        return ImageFromVideoGrabber(self.get_video_path(),
                                     cache_dir_path=self._cache_dir_path)

    def get_video_path(self):
        return self._ldfh.get_video_path(self._session_dir)

    def _get_aggregate_data(self):
        r = {
            self.__class__.UNIQUE_FOVS:
            self.get_unique_fovs().tolist(),
            self.__class__.UNIQUE_TRANSITIONS: [
                self._get_name_for_transition(transition)
                for _, transition in self.get_unique_transitions().items()
            ],
            self.__class__.ALIGNMENT_STATS:
            self._get_alignment_stats(),
        }
        return r

    def _get_alignment_stats(self):
        return {
            self.__class__.ALL: {
                self.__class__.DELAY: {
                    self.__class__.MIN:
                    int(np.min(np.array(self._alignment_stats_delays))),
                    self.__class__.MAX:
                    int(np.max(np.array(self._alignment_stats_delays))),
                    self.__class__.MEAN:
                    int(np.mean(np.array(self._alignment_stats_delays))),
                },
                self.__class__.DURATION: {
                    self.__class__.MIN:
                    int(np.min(np.array(self._alignment_stats_durations))),
                    self.__class__.MAX:
                    int(np.max(np.array(self._alignment_stats_durations))),
                    self.__class__.MEAN:
                    int(np.mean(np.array(self._alignment_stats_durations))),
                },
            },
            self.__class__.ZOOM_IN: {
                self.__class__.DELAY: {
                    self.__class__.MIN:
                    int(np.min(np.array(
                        self._alignment_stats_zoom_in_delays))),
                    self.__class__.MAX:
                    int(np.max(np.array(
                        self._alignment_stats_zoom_in_delays))),
                    self.__class__.MEAN:
                    int(np.mean(np.array(
                        self._alignment_stats_zoom_in_delays))),
                },
                self.__class__.DURATION: {
                    self.__class__.MIN:
                    int(
                        np.min(
                            np.array(
                                self._alignment_stats_zoom_in_durations))),
                    self.__class__.MAX:
                    int(
                        np.max(
                            np.array(
                                self._alignment_stats_zoom_in_durations))),
                    self.__class__.MEAN:
                    int(
                        np.mean(
                            np.array(
                                self._alignment_stats_zoom_in_durations))),
                },
            },
            self.__class__.ZOOM_OUT: {
                self.__class__.DELAY: {
                    self.__class__.MIN:
                    int(np.min(np.array(
                        self._alignment_stats_zoom_out_delays))),
                    self.__class__.MAX:
                    int(np.max(np.array(
                        self._alignment_stats_zoom_out_delays))),
                    self.__class__.MEAN:
                    int(
                        np.mean(np.array(
                            self._alignment_stats_zoom_out_delays))),
                },
                self.__class__.DURATION: {
                    self.__class__.MIN:
                    int(
                        np.min(
                            np.array(
                                self._alignment_stats_zoom_out_durations))),
                    self.__class__.MAX:
                    int(
                        np.max(
                            np.array(
                                self._alignment_stats_zoom_out_durations))),
                    self.__class__.MEAN:
                    int(
                        np.mean(
                            np.array(
                                self._alignment_stats_zoom_out_durations))),
                },
            },
        }

    def _get_results_object(self):
        r = {}
        r[self.__class__.AGGREGATE_RESULTS] = self._get_aggregate_data()
        r[self.__class__.INDIVIDUAL_RESULTS] = self._alignment_results
        return r

    def save_results(self):
        self._calibration_data_filer.save_as_yml(
            self._get_results_object(),
            self._session_dir,
            CalibrationDataFiler.FOV_TIMEBASE_ALIGNMENT,
        )

    def _scrubber_callback(self, selection: list):
        delay_fov_to_video = \
            int(selection[0].get_time_ms() - self._current_transition[self.__class__.TRANSITION_TIME])

        duration = self._get_duration_ms(selection[0], selection[1])

        r = {
            self.__class__.VIDEO_DATA: {
                self.__class__.BEGIN: self._get_ifv_info(selection[0]),
                self.__class__.END: self._get_ifv_info(selection[1]),
                self.__class__.DURATION: {
                    self.__class__.TIME_MS:
                    duration,
                    self.__class__.FRAMES:
                    self._get_duration_frames(selection[0], selection[1]),
                },
            },
            self.__class__.FOV_DATA: {
                self.__class__.TRANSITION_INDEX:
                int(self._current_transition[self.__class__.TRANSITION_INDEX]),
                self.__class__.TRANSITION_TIME:
                int(self._current_transition[self.__class__.TRANSITION_TIME]),
            },
            self.__class__.ALIGNMENT: {
                self.__class__.DELAY_FOV_TO_VIDEO: delay_fov_to_video,
            },
        }
        self._alignment_results[self._get_name_for_transition(
            self._current_transition)] = r
        self._alignment_stats_delays.append(delay_fov_to_video)
        self._alignment_stats_durations.append(duration)

        if self._get_zoom_type_for_transition(
                self._current_transition) == self.__class__.ZOOM_IN:
            self._alignment_stats_zoom_in_delays.append(delay_fov_to_video)
            self._alignment_stats_zoom_in_durations.append(duration)
        else:
            self._alignment_stats_zoom_out_delays.append(delay_fov_to_video)
            self._alignment_stats_zoom_out_durations.append(duration)

    def _get_ifv_info(self, ivf):
        return {
            self.__class__.FRAME_NUM: ivf.get_frame_num(),
            self.__class__.TIME_MS: ivf.get_time_ms(),
        }

    def _get_duration_ms(self, begin_ifv: ImageFromVideo,
                         end_ifv: ImageFromVideo):
        return end_ifv.get_time_ms() - begin_ifv.get_time_ms()

    def _get_duration_frames(self, begin_ifv: ImageFromVideo,
                             end_ifv: ImageFromVideo):
        return end_ifv.get_frame_num() - begin_ifv.get_frame_num()

    def _get_name_for_transition(self, transition):
        return (f'{self._get_zoom_type_for_transition(transition)}: '
                f'{str(transition[self.__class__.TRANSITION_LEVELS])}')

    def _get_zoom_type_for_transition(self, transition: dict):
        if transition[self.__class__.TRANSITION_LEVELS][0] < transition[
                self.__class__.TRANSITION_LEVELS][1]:
            return self.__class__.ZOOM_OUT
        else:
            return self.__class__.ZOOM_IN

    def run(self):
        self.show_scrub_picker_for_transitions()
        self.save_results()
Exemplo n.º 11
0
    def setUp(self):
        head_path = Path('/Volumes/WD')
        session_dir = 'Aug_17_Palo_Alto_High_2nd_time_B80_ottofillmore'
        results_head_path = Path('.')  / 'data/calibration_data',

        ldfh = LDFH(
            head_path=head_path,
        )
        cdf = CDF(
            top_level_dir=results_head_path
        )

        fov_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.LENS_NPZ_FILE,
            fieldname=LDFH.LENS_FOV_FIELD,
        )
        fov_time_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.LENS_NPZ_FILE,
            fieldname=LDFH.LENS_TIME_FIELD,
        )
        tag_latitude_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.TAG_NPZ_FILE,
            fieldname=LDFH.TAG_LATITUDE_FIELD,
        )
        tag_longitude_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.TAG_NPZ_FILE,
            fieldname=LDFH.TAG_LONGITUDE_FIELD,
        )
        tag_time_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.TAG_NPZ_FILE,
            fieldname=LDFH.TAG_TIME_FIELD,
        )

        self.geo_map_scrubber = GeoMapScrubber(
            latitude_series=tag_latitude_series,
            longitude_series=tag_longitude_series,
            time_series=tag_time_series,
        )

        self.video_path = ldfh.get_video_path(session_dir)

        map_fitter = MapFitter(
            latitude_series=tag_latitude_series,
            longitude_series=tag_longitude_series,
        )

        map_coordinate_transformer = MapCoordinateTransformer.init_with_map_fitter(
            map_fitter=map_fitter,
        )

        tag = Tag(
            latitude_series=tag_latitude_series,
            longitude_series=tag_longitude_series,
            time_series=tag_time_series,
            alignment_offset_video_to_tag_ms=-340, # From calibration data
            map_coordinate_transformer=map_coordinate_transformer,
        )

        base_gps_latitude = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.TAG_NPZ_FILE,
            fieldname=LDFH.BASE_LATITUDE_FIELD,
        )
        base_gps_longitude = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.TAG_NPZ_FILE,
            fieldname=LDFH.BASE_LONGITUDE_FIELD,
        )
        base_motor_time_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.BASE_NPZ_FILE,
            fieldname=LDFH.BASE_TIME_FIELD,
        )

        pan_motor_angle_series = ldfh.get_field_from_npz_file(
            session_dir_name=session_dir,
            filename=LDFH.BASE_NPZ_FILE,
            fieldname=LDFH.PAN_MOTOR_READ_FIELD,
        )

        self.base = Base(
            gps_latitude_series=base_gps_latitude,
            gps_longitude_series=base_gps_longitude,
            base_motor_time_series=base_motor_time_series,
            map_coordinate_transformer=map_coordinate_transformer,
            pan_motor_angle_series=pan_motor_angle_series,
            actual_latitude=37.386206696022725,
            actual_longitude=-122.11009181597608,
            # actual_latitude=37.386203,
            # actual_longitude=-122.110091,
        )

        self.tag_position_analyzer = TagPositionInStableFovSegmentsAnalyzer(
            fov_series=fov_series,
            fov_time_series=fov_time_series,
            tag=tag,
            base=self.base,
        )

        self.frames_limit = 1
        self.threshold_deg = 20
        self.min_distance_to_camera = 100
        # self.frames_limit = None
        # self.threshold_deg = 20
        # self.min_distance_to_camera = 100
        self.legacy_data_base_position_calibrator = LegacyDataBasePositionCalibrator(
            session_dir=session_dir,
            tag_position_in_stable_fov_segments_analyzer=self.tag_position_analyzer,
            legacy_data_file_system_helper=ldfh,
            calibration_data_filer=cdf,
            frames_limit=self.frames_limit,
            angle_threshold_rad=np.radians(self.threshold_deg),
            min_distance_to_camera=self.min_distance_to_camera,
        )

        return self
class PanMotorTimeBaseAligner:
    '''Determines with user assistance via scrubber picker statistics for offset in
       time between pan_motor_read timebase and video time'''

    RAW_RESULTS = 'raw_results'
    AGGREGATE_RESULTS = 'aggregate_results'

    MIN_OR_MAX = 'min_or_max'
    MIN = 'min'
    MAX = 'max'
    MEAN = 'mean'
    N = 'n'
    DATA = 'data'

    DELAY_MOTOR_TO_VIDEO = 'delay_motor_to_video'
    BASE_TIME = 'base_time'
    MOTOR_IDX = 'motor_idx'

    PAN_LOCAL_MINIMA = 'pan_local_minima'
    PAN_LOCAL_MAXIMA = 'pan_local_maxima'
    PAN_COMBINED = 'pan_combined'

    def __init__(
            self,
            session_dir: str,
            input_data_head_path=Path('/Volumes/WD'),
            results_head_path=Path('.') / 'data/calibration_data',
            cache_dir_path=Path('/Volumes/WD/image_cache'),
            num_points=1,
    ):
        self._session_dir = session_dir
        self._input_data_head_path = input_data_head_path
        self._results_head_path = results_head_path
        self._cache_dir_path = cache_dir_path
        self._num_points = num_points

        # Helpers
        self._ldfh = LDFH(self._input_data_head_path)
        self._calibration_data_filer = CalibrationDataFiler(
            self._results_head_path)

        self.maxima_text_color = 'red'
        self.minima_text_color = 'green'
        self.fontsize = 8

        # Lazy initializers
        self._pan_motor_read_data_series = None
        self._base_time_series = None
        self._motor_maxima = None
        self._motor_minima = None
        self._motor_maxima_from_series = None
        self._motor_minima_from_series = None
        self._motor_maxima_from_archive = None
        self._motor_minima_from_archive = None
        self._archived_results = None
        self._selected_frames_from_archive = None

        # State
        self._base_time = None
        self._motor_idx = None
        self._min_or_max = None
        self._alignment_results = []

    def visualize_motor_local_maxima_and_minima(self):
        fig, axis = plt.subplots()  # pylint: disable=W0612
        axis.plot(self.get_normalized_base_time(), self.get_motor_data())

        # maxima_shoulder_steepness = [self.get_motor_data().get_shoulder_steepness_around_idx(maximum, 4)
        #                              for maximum in self.get_motor_maxima()]

        for idx, val in enumerate(self._get_motor_values_at_maxima()):
            axis.text(
                self.get_normalized_base_time()[self.get_motor_maxima()[idx]],
                val,
                str(idx),
                color=self.maxima_text_color,
                fontsize=self.fontsize,
            )

        for idx, val in enumerate(self._get_motor_values_at_minima()):
            axis.text(
                self.get_normalized_base_time()[self.get_motor_minima()[idx]],
                val,
                str(idx),
                color=self.minima_text_color,
                fontsize=self.fontsize,
            )

        plt.show()

    def get_motor_maxima(self):
        if self._motor_maxima is None:
            if self._get_motor_maxima_from_archived_results() is not None \
                        and self._get_motor_maxima_from_archived_results().size == self._num_points:
                self._motor_maxima = self._get_motor_maxima_from_archived_results(
                )
            else:
                self._motor_maxima = self._get_motor_maxima_from_series()
        return self._motor_maxima

    def _get_motor_maxima_from_series(self):
        if self._motor_maxima_from_series is None:
            self._motor_maxima_from_series = self.get_motor_data().\
                                           local_maxima_sorted_by_peakyness_and_monotonic_with_steep_shoulders(8, 4, 2) # pylint: disable=C0301
        return self._motor_maxima_from_series[:self._num_points]

    def _get_motor_values_at_maxima(self):
        return np.array(
            [self.get_motor_data()[idx] for idx in self.get_motor_maxima()])

    def _get_time_at_maxima(self):
        return [(maximum, self.get_normalized_base_time()[maximum])
                for maximum in self.get_motor_maxima()]

    def get_motor_minima(self):
        if self._motor_minima is None:
            if self._get_motor_minima_from_archived_results() is not None and \
               self._get_motor_minima_from_archived_results().size == self._num_points:
                self._motor_minima = self._get_motor_minima_from_archived_results(
                )
            else:
                self._motor_minima = self._get_motor_minima_from_series()
        return self._motor_minima

    def _get_motor_minima_from_series(self):
        if self._motor_minima_from_series is None:
            self._motor_minima_from_series = self.get_motor_data().\
                                           local_minima_sorted_by_peakyness_and_monotonic_with_steep_shoulders(8, 4, 2) # pylint: disable=C0301
        return self._motor_minima_from_series[:self._num_points]

    def _get_motor_values_at_minima(self):
        return np.array(
            [self.get_motor_data()[idx] for idx in self.get_motor_minima()])

    def _get_time_at_minima(self):
        return [(minimum, self.get_normalized_base_time()[minimum])
                for minimum in self.get_motor_minima()]

    def show_scrub_picker_for_maxima(self):
        self._min_or_max = self.__class__.MAX
        for idx, time in self._get_time_at_maxima():
            self._motor_idx = idx
            self._base_time = time
            self.show_scrub_picker_for_time(idx, time)

    def show_scrub_picker_for_minima(self):
        self._min_or_max = self.__class__.MIN
        for idx, time in self._get_time_at_minima():
            self._motor_idx = idx
            self._base_time = time
            self.show_scrub_picker_for_time(idx, time)

    def show_scrub_picker_for_min_and_max(self):
        self.show_scrub_picker_for_minima()
        self.show_scrub_picker_for_maxima()

    def show_scrub_picker_for_time(self, motor_idx, time):
        ScrubPicker(images_from_video=self.get_images_around_time(time),
                    selector_type=ScrubPicker.SELECT_SINGLE_IMAGE,
                    callback=self._scrubber_callback,
                    selected_start_frame_num=self.
                    _get_selected_frame_num_for_motor_idx(motor_idx)).run()

    def get_images_around_time(self, time):
        return self.get_ifv_grabber().get_images_around_time_ms(time,
                                                                before=40,
                                                                after=40)

    def get_motor_data(self) -> np.ndarray:
        if self._pan_motor_read_data_series is None:
            self._pan_motor_read_data_series = \
                self._ldfh.get_field_from_npz_file(self._session_dir,
                                                   LDFH.BASE_NPZ_FILE,
                                                   LDFH.PAN_MOTOR_READ_FIELD,
                                                  )
        return self._pan_motor_read_data_series

    def get_base_time(self):
        if self._base_time_series is None:
            self._base_time_series = self._ldfh.get_field_from_npz_file(
                self._session_dir,
                LDFH.BASE_NPZ_FILE,
                LDFH.BASE_TIME_FIELD,
            )
        return self._base_time_series

    def get_normalized_base_time(self):
        return self.get_base_time() - self.get_base_time()[0]

    def get_motor_data_time_tuples(self):
        return np.dstack(
            (self.get_normalized_base_time(), self.get_motor_data()))[0]

    def get_video_path(self):
        return self._ldfh.get_video_path(self._session_dir)

    def get_ifv_grabber(self) -> ImageFromVideoGrabber:
        return ImageFromVideoGrabber(self.get_video_path(),
                                     cache_dir_path=self._cache_dir_path)

    def _scrubber_callback(self, images_from_video: List[ImageFromVideo]):
        ifv = images_from_video[0]
        r = ifv.get_as_dict()
        r[self.__class__.MIN_OR_MAX] = self._min_or_max
        r[self.__class__.MOTOR_IDX] = int(self._motor_idx)
        r[self.__class__.BASE_TIME] = int(self._base_time)
        r[self.__class__.DELAY_MOTOR_TO_VIDEO] = ifv.get_time_ms() - int(
            self._base_time)
        self._alignment_results.append(r)

    def _get_aggregate_data(self):
        r = {}
        local_min_data = np.array([])
        local_max_data = np.array([])
        for result in self._alignment_results:
            if result[self.__class__.MIN_OR_MAX] == self.__class__.MIN:
                local_min_data = np.append(
                    local_min_data,
                    result[self.__class__.DELAY_MOTOR_TO_VIDEO])
            else:
                local_max_data = np.append(
                    local_max_data,
                    result[self.__class__.DELAY_MOTOR_TO_VIDEO])
        combined_data = np.concatenate((local_min_data, local_max_data))

        r[self.__class__.PAN_LOCAL_MINIMA] = self._get_stats_from_data(
            local_min_data)
        r[self.__class__.PAN_LOCAL_MAXIMA] = self._get_stats_from_data(
            local_max_data)
        r[self.__class__.PAN_COMBINED] = self._get_stats_from_data(
            combined_data)
        return r

    def _get_stats_from_data(self, data: np.ndarray):
        r = {}
        r[self.__class__.N] = int(data.size)
        r[self.__class__.DATA] = data.tolist()
        r[self.__class__.MIN] = int(np.min(data))
        r[self.__class__.MAX] = int(np.max(data))
        r[self.__class__.MEAN] = int(np.mean(data))
        return r

    def _get_results_object(self):
        r = {}
        r[self.__class__.RAW_RESULTS] = self._alignment_results
        r[self.__class__.AGGREGATE_RESULTS] = self._get_aggregate_data()
        return r

    def save_results(self):
        self._calibration_data_filer.save_as_yml(
            self._get_results_object(),
            self._session_dir,
            CalibrationDataFiler.PAN_MOTOR_TIMEBASE_ALIGNMENT,
        )

    def _get_archived_results(self):
        if self._archived_results is None:
            self._archived_results = self._calibration_data_filer.load(
                self._session_dir,
                CalibrationDataFiler.PAN_MOTOR_TIMEBASE_ALIGNMENT,  # pylint: disable=C0301
            )
        return self._archived_results

    def _get_motor_idxs_from_archived_results(self, min_or_max: str):
        if self._get_archived_results() is None:
            return None

        results = self._get_archived_results()[self.__class__.RAW_RESULTS]
        return [
            result[self.__class__.MOTOR_IDX] for result in results
            if result[self.__class__.MIN_OR_MAX] == min_or_max
        ]

    def _get_motor_maxima_from_archived_results(self):
        if self._motor_maxima_from_archive is None:
            self._motor_maxima_from_archive = self._get_motor_idxs_from_archived_results(
                self.__class__.MAX)
            if self._motor_maxima_from_archive is not None:
                self._motor_maxima_from_archive = np.array(
                    self._motor_maxima_from_archive)[:self._num_points]
        return self._motor_maxima_from_archive

    def _get_motor_minima_from_archived_results(self):
        if self._motor_minima_from_archive is None:
            self._motor_minima_from_archive = self._get_motor_idxs_from_archived_results(
                self.__class__.MIN)
            if self._motor_minima_from_archive is not None:
                self._motor_minima_from_archive = np.array(
                    self._motor_minima_from_archive)[:self._num_points]
        return self._motor_minima_from_archive

    def _get_selected_frames_from_archived_results(self):
        if self._selected_frames_from_archive is None:
            self._selected_frames_from_archive = {}
            if self._get_archived_results() is not None:
                results = self._get_archived_results()[
                    self.__class__.RAW_RESULTS]
                for result in results:
                    self._selected_frames_from_archive[result[self.__class__.MOTOR_IDX]] = \
                            result[ImageFromVideo.FRAME_NUM]
        return self._selected_frames_from_archive

    def _get_selected_frame_num_for_motor_idx(self, motor_idx):
        if motor_idx not in self._get_selected_frames_from_archived_results():
            return None
        else:
            return self._get_selected_frames_from_archived_results()[motor_idx]

    def run(self):
        self.show_scrub_picker_for_min_and_max()
        self.save_results()
class TagGpsTimebaseAligner:
    '''Determines with user assistance statistics for offset in
       time between tag_gps timebase and video time'''

    IFV = 'ifv'
    TAG_IDX = 'tag_idx'
    TAG_TIME = 'tag_time'

    AGGREGATE_RESULTS = 'aggregate_results'
    ALIGNMENT_STATS = 'alignment_stats'
    DELAY_VIDEO_TO_TAG_GPS = 'delay_video_to_tag_gps'

    MAX = 'max'
    MEAN = 'mean'
    MIN = 'min'
    NUM = 'num'

    INDIVIDUAL_RESULTS = 'individual_results'

    TAG_DATA = 'tag_data'
    TAG_IDX = 'tag_idx'
    TAG_TIME = 'tag_time'

    VIDEO_DATA = 'video_data'
    FRAME_NUM = 'frame_num'
    TIME_MS = 'time_ms'
    VIDEO_ID = 'video_id'
    VIDEO_URL = 'video_url'

    def __init__(self,
                 session_dir: str,
                 input_data_head_path=Path('/Volumes/WD'),
                 results_head_path=Path('.')  / 'data/calibration_data',
                 cache_dir_path=Path('/Volumes/WD/image_cache'),
                ):
        self._session_dir = session_dir
        self._input_data_head_path = input_data_head_path
        self._results_head_path = results_head_path
        self._cache_dir_path = cache_dir_path

        # helpers
        self._ldfh = LDFH(self._input_data_head_path)
        self._calibration_data_filer = CalibrationDataFiler(self._results_head_path)

        # state
        self._alignment_data = []

        # lazy inits
        self._latitude_series = None
        self._longitude_series = None
        self._time_series = None

    def _display_ui(self):
        geo_map_scrubber = GeoMapScrubber(
            latitude_series=self._get_latitude_series(),
            longitude_series=self._get_longitude_series(),
            time_series=self._get_time_series(),
        )
        TagGpsTimebaseAlignerUi(
            geo_map_scrubber=geo_map_scrubber,
            video_path=self._get_video_path(),
            save_alignment_callback=self._save_alignment_callback,
            done_callback=self._done_callback,
        ).run()

    def _save_alignment_callback(self, image_from_video, tag_idx, tag_time):
        self._alignment_data.append(
            {
                self.__class__.IFV: image_from_video,
                self.__class__.TAG_IDX: tag_idx,
                self.__class__.TAG_TIME: tag_time,
            }
        )
        print('Saved alignment delay: ', self._get_delay_video_to_tag(self._alignment_data[-1]))

    def _done_callback(self):
        self._calibration_data_filer.save_as_yml(
            obj=self._get_stats(),
            data_dir_name=self._session_dir,
            calibration_file=CalibrationDataFiler.TAG_GPS_TIMEBASE_ALIGNMENT,
        )

    def _get_stats(self):
        return {
            self.__class__.AGGREGATE_RESULTS: self._get_aggregate_stats(),
            self.__class__.INDIVIDUAL_RESULTS: self._get_individual_stats_list(),
        }

    def _get_aggregate_stats(self):
        delays = [stat[self.__class__.DELAY_VIDEO_TO_TAG_GPS]
                  for stat in self._get_individual_stats_list()]
        delays = np.array(delays)
        return {
            self.__class__.DELAY_VIDEO_TO_TAG_GPS:
                {
                    self.__class__.MEAN: int(round(np.mean(delays))),
                    self.__class__.MAX: int(round(np.max(delays))),
                    self.__class__.MIN: int(round(np.min(delays))),
                }
        }

    def _get_individual_stats_list(self):
        r = []
        for datum in self._alignment_data:
            r.append(
                {
                    self.__class__.TAG_DATA:
                        {
                            self.__class__.TAG_IDX: int(datum[self.__class__.TAG_IDX]),
                            self.__class__.TAG_TIME: int(datum[self.__class__.TAG_TIME]),
                        },
                    self.__class__.VIDEO_DATA: datum[self.__class__.IFV].get_as_dict(),
                    self.__class__.DELAY_VIDEO_TO_TAG_GPS: self._get_delay_video_to_tag(datum),
                }
            )
        return r

    def _get_delay_video_to_tag(self, datum):
        video_time = datum[self.__class__.IFV].get_time_ms()
        return int(datum[self.__class__.TAG_TIME] - video_time)

    def _get_latitude_series(self):
        if self._latitude_series is None:
            self._latitude_series =\
                self._ldfh.get_field_from_npz_file(self._session_dir,
                                                   LDFH.TAG_NPZ_FILE,
                                                   LDFH.TAG_LATITUDE_FIELD,
                                                  )
        return self._latitude_series

    def _get_longitude_series(self):
        if self._longitude_series is None:
            self._longitude_series =\
                self._ldfh.get_field_from_npz_file(self._session_dir,
                                                   LDFH.TAG_NPZ_FILE,
                                                   LDFH.TAG_LONGITUDE_FIELD,
                                                  )
        return self._longitude_series

    def _get_time_series(self):
        if self._time_series is None:
            self._time_series =\
                self._ldfh.get_field_from_npz_file(self._session_dir,
                                                   LDFH.TAG_NPZ_FILE,
                                                   LDFH.TAG_TIME_FIELD,
                                                  )
        return self._time_series

    def _get_video_path(self):
        return self._ldfh.get_video_path(self._session_dir)