-
Notifications
You must be signed in to change notification settings - Fork 713
Expand file tree
/
Copy pathmot.py
More file actions
159 lines (133 loc) · 5.97 KB
/
Copy pathmot.py
File metadata and controls
159 lines (133 loc) · 5.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
"""
nuScenes dev-kit.
Code written by Holger Caesar, Caglayan Dicle and Oscar Beijbom, 2019.
This code is based on:
py-motmetrics at:
https://github.com/cheind/py-motmetrics
Notes by Michael Hoss:
For Python 3.10, we need to update the version of py-motmetrics to 1.4.0.
Then, to keep this code working, we need to change back the types of OId HId to object because they are
strings in nuscenes-devkit, whereas motmetrics changed these types to float from 1.1.3 to 1.4.0.
"""
from collections import OrderedDict
from itertools import count
import numpy as np
import pandas as pd
from motmetrics import MOTAccumulator
_INDEX_FIELDS = ['FrameId', 'Event']
class MOTAccumulatorCustom(MOTAccumulator):
"""This custom class was created by nuscenes-devkit to use a faster implementation of
`new_event_dataframe_with_data` under compatibility with motmetrics<=1.1.3.
Now that we use motmetrics==1.4.0, we need to use this custom implementation to use
objects instead of strings for OId and HId.
"""
def __init__(self):
super().__init__()
@staticmethod
def new_event_dataframe_with_data(indices, events):
"""Create a new DataFrame filled with data.
Params
------
indices: dict
dict of lists with fields 'FrameId' and 'Event'
events: dict
dict of lists with fields 'Type', 'OId', 'HId', 'D'
"""
if len(events) == 0:
return MOTAccumulatorCustom.new_event_dataframe()
raw_type = pd.Categorical(
events['Type'],
categories=['RAW', 'FP', 'MISS', 'SWITCH', 'MATCH', 'TRANSFER', 'ASCEND', 'MIGRATE'],
ordered=False)
series = [
pd.Series(raw_type, name='Type'),
pd.Series(events['OId'], dtype=object, name='OId'), # OId is string in nuscenes-devkit
pd.Series(events['HId'], dtype=object, name='HId'), # HId is string in nuscenes-devkit
pd.Series(events['D'], dtype=float, name='D')
]
idx = pd.MultiIndex.from_arrays(
[indices[field] for field in _INDEX_FIELDS],
names=_INDEX_FIELDS)
df = pd.concat(series, axis=1)
df.index = idx
return df
@staticmethod
def new_event_dataframe():
"""Create a new DataFrame for event tracking."""
idx = pd.MultiIndex(levels=[[], []], codes=[[], []], names=['FrameId', 'Event'])
cats = pd.Categorical([], categories=['RAW', 'FP', 'MISS', 'SWITCH', 'MATCH', 'TRANSFER', 'ASCEND', 'MIGRATE'])
df = pd.DataFrame(
OrderedDict([
('Type', pd.Series(cats)), # Type of event. One of FP (false positive), MISS, SWITCH, MATCH
('OId', pd.Series(dtype=object)), # Object ID or -1 if FP. Using float as missing values will be converted to NaN anyways.
('HId', pd.Series(dtype=object)), # Hypothesis ID or NaN if MISS. Using float as missing values will be converted to NaN anyways.
('D', pd.Series(dtype=float)), # Distance or NaN when FP or MISS
]),
index=idx
)
return df
@property
def events(self):
if self.dirty_events:
self.cached_events_df = MOTAccumulatorCustom.new_event_dataframe_with_data(self._indices, self._events)
self.dirty_events = False
return self.cached_events_df
@staticmethod
def merge_event_dataframes(dfs, update_frame_indices=True, update_oids=True, update_hids=True, return_mappings=False):
"""Merge dataframes.
Params
------
dfs : list of pandas.DataFrame or MotAccumulator
A list of event containers to merge
Kwargs
------
update_frame_indices : boolean, optional
Ensure that frame indices are unique in the merged container
update_oids : boolean, unique
Ensure that object ids are unique in the merged container
update_hids : boolean, unique
Ensure that hypothesis ids are unique in the merged container
return_mappings : boolean, unique
Whether or not to return mapping information
Returns
-------
df : pandas.DataFrame
Merged event data frame
"""
mapping_infos = []
new_oid = count()
new_hid = count()
r = MOTAccumulatorCustom.new_event_dataframe()
for df in dfs:
if isinstance(df, MOTAccumulatorCustom):
df = df.events
copy = df.copy()
infos = {}
# Update index
if update_frame_indices:
# pylint: disable=cell-var-from-loop
next_frame_id = max(r.index.get_level_values(0).max() + 1, r.index.get_level_values(0).unique().shape[0])
if np.isnan(next_frame_id):
next_frame_id = 0
if not copy.index.empty:
copy.index = copy.index.map(lambda x: (x[0] + next_frame_id, x[1]))
infos['frame_offset'] = next_frame_id
# Update object / hypothesis ids
if update_oids:
# pylint: disable=cell-var-from-loop
oid_map = dict([oid, str(next(new_oid))] for oid in copy['OId'].dropna().unique())
copy['OId'] = copy['OId'].map(lambda x: oid_map[x], na_action='ignore')
infos['oid_map'] = oid_map
if update_hids:
# pylint: disable=cell-var-from-loop
hid_map = dict([hid, str(next(new_hid))] for hid in copy['HId'].dropna().unique())
copy['HId'] = copy['HId'].map(lambda x: hid_map[x], na_action='ignore')
infos['hid_map'] = hid_map
# Avoid pandas warning. But is this legit/do we need such a column later on again?
# copy = copy.dropna(axis=1, how='all')
r = pd.concat((r, copy))
mapping_infos.append(infos)
if return_mappings:
return r, mapping_infos
else:
return r