Cherie Ho commited on
Commit
a78b279
·
1 Parent(s): 90226e0

added robot position, toggle how many to display, just show data pair

Browse files
Files changed (1) hide show
  1. main.py +171 -97
main.py CHANGED
@@ -9,6 +9,9 @@ import matplotlib.pyplot as plt
9
  import pandas as pd
10
  import geopandas as gpd
11
  from pyproj.transformer import Transformer
 
 
 
12
 
13
  sys.path.append(os.path.dirname(os.path.realpath(__file__)))
14
  from MapItAnywhere.mia.bev import get_bev
@@ -72,8 +75,7 @@ def split_dataframe(df, chunk_size = 100):
72
  chunks.append(df[i*chunk_size:(i+1)*chunk_size])
73
  return chunks
74
 
75
- async def fetch(location, filter_undistort, disable_cam_filter, map_length, mpp):
76
- N=1
77
  TOTAL_LOOKED_INTO_LIMIT = 10000
78
 
79
  ################ FPV
@@ -109,36 +111,37 @@ async def fetch(location, filter_undistort, disable_cam_filter, map_length, mpp)
109
 
110
  dfs_meta.append(df_meta)
111
  total_rows = sum([len(x) for x in dfs_meta])
112
- if total_rows > N:
113
  break
114
  elif total_looked_into > TOTAL_LOOKED_INTO_LIMIT:
115
  yield (f"Went through {total_looked_into} images and could not find images satisfying the filters."
116
  "\nPlease rerun or run the data engine locally for bulk time consuming operations.", None, None)
117
  return
118
- if total_rows > N:
119
  break
120
  except:
121
  pass
122
 
123
  df_meta = pd.concat(dfs_meta)
124
- df_meta = df_meta.sample(N)
125
 
126
  # Calc derrivative attributes
127
- df_meta["loc_descrip"] = filters.haversine_np(
128
  lon1=df_meta["geometry.long"], lat1=df_meta["geometry.lat"],
129
  lon2=df_meta["computed_geometry.long"], lat2=df_meta["computed_geometry.lat"]
130
  )
131
 
132
- df_meta["angle_descrip"] = filters.angle_dist(
133
  df_meta["compass_angle"],
134
  df_meta["computed_compass_angle"]
135
  )
136
-
137
  for index, row in df_meta.iterrows():
 
138
  desc = list()
139
  # Display attributes
140
  keys = ["id", "geometry.long", "geometry.lat", "compass_angle",
141
- "loc_descrip", "angle_descrip",
142
  "make", "model", "camera_type",
143
  "quality_score"]
144
  for k in keys:
@@ -148,96 +151,166 @@ async def fetch(location, filter_undistort, disable_cam_filter, map_length, mpp)
148
  bullet = f"{k}: {v}"
149
  desc.append(bullet)
150
  metadata_fmt = "\n".join(desc)
151
-
152
- yield metadata_fmt, None, None
153
- image_urls = list(df_meta.set_index("id")["thumb_2048_url"].items())
154
- num_fail = await get_fpv.fetch_images_pixels(image_urls, downloader, raw_image_dir)
155
- if num_fail > 0:
156
- logger.error(f"Failed to download {num_fail} images.")
157
-
158
- seq_to_image_ids = df_meta.groupby('sequence')['id'].agg(list).to_dict()
159
- lon_center = (bbox['east'] + bbox['west']) / 2
160
- lat_center = (bbox['north'] + bbox['south']) / 2
161
- projection = get_fpv.Projection(lat_center, lon_center, max_extent=200e3)
162
 
163
- df_meta.index = df_meta["id"]
164
- image_infos = df_meta.to_dict(orient="index")
165
- process_sequence_args = get_fpv.default_cfg
166
 
167
- if filter_undistort:
168
- for seq_id, seq_image_ids in seq_to_image_ids.items():
169
- try:
170
- d, pi = get_fpv.process_sequence(
171
- seq_image_ids,
172
- image_infos,
173
- projection,
174
- process_sequence_args,
175
- raw_image_dir,
176
- out_image_dir,
177
- )
178
- if d is None or pi is None:
179
- raise Exception("process_sequence returned None")
180
- except Exception as e:
181
- logger.error(f"Failed to process sequence {seq_id} skipping it. Error: {repr(e)}.")
182
-
183
- fpv = plt.imread(out_image_dir/ f"{row['id']}_undistorted.jpg")
184
- else:
185
- fpv = plt.imread(raw_image_dir/ f"{row['id']}.jpg")
186
- yield metadata_fmt, fpv, None
187
- ################ BEV
188
- df = df_meta
189
- # convert pandas dataframe to geopandas dataframe
190
- gdf = gpd.GeoDataFrame(df,
191
- geometry=gpd.points_from_xy(
192
- df['computed_geometry.long'],
193
- df['computed_geometry.lat']),
194
- crs=4326)
195
-
196
- # convert the geopandas dataframe to UTM
197
- utm_crs = gdf.estimate_utm_crs()
198
- gdf_utm = gdf.to_crs(utm_crs)
199
- transformer = Transformer.from_crs(utm_crs, 4326)
200
- # load OSM data, if available
201
- padding = 50
202
- # calculate the required distance from the center to the edge of the image
203
- # so that the image will not be out of bounds when we rotate it
204
- map_length = map_length
205
- map_length = np.ceil(np.sqrt(map_length**2 + map_length**2))
206
- distance = map_length * mpp
 
 
 
 
 
 
 
207
 
208
- # create bounding boxes for each point
209
- gdf_utm['bounding_box_utm_p1'] = gdf_utm.apply(lambda row: (
210
- row.geometry.x - distance - padding,
211
- row.geometry.y - distance - padding,
212
- ), axis=1)
213
 
214
- gdf_utm['bounding_box_utm_p2'] = gdf_utm.apply(lambda row: (
215
- row.geometry.x + distance + padding,
216
- row.geometry.y + distance + padding,
217
- ), axis=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
- # convert the bounding box back to lat, long
220
- gdf_utm['bounding_box_lat_long_p1'] = gdf_utm.apply(lambda row: transformer.transform(*row['bounding_box_utm_p1']), axis=1)
221
- gdf_utm['bounding_box_lat_long_p2'] = gdf_utm.apply(lambda row: transformer.transform(*row['bounding_box_utm_p2']), axis=1)
222
- gdf_utm['bbox_min_lat'] = gdf_utm['bounding_box_lat_long_p1'].apply(lambda x: x[0])
223
- gdf_utm['bbox_min_long'] = gdf_utm['bounding_box_lat_long_p1'].apply(lambda x: x[1])
224
- gdf_utm['bbox_max_lat'] = gdf_utm['bounding_box_lat_long_p2'].apply(lambda x: x[0])
225
- gdf_utm['bbox_max_long'] = gdf_utm['bounding_box_lat_long_p2'].apply(lambda x: x[1])
226
- gdf_utm['bbox_formatted'] = gdf_utm.apply(lambda row: f"{row['bbox_min_long']},{row['bbox_min_lat']},{row['bbox_max_long']},{row['bbox_max_lat']}", axis=1)
 
 
 
 
 
 
 
 
227
 
228
- # iterate over the dataframe and get BEV images
229
- jobs = gdf_utm[['id', 'bbox_formatted', 'computed_compass_angle']] # only need the id and bbox_formatted columns for the jobs
230
- jobs = jobs.to_dict(orient='records').copy()
231
 
232
- get_bev.get_bev_from_bbox_worker_init(osm_cache_dir, bev_dir, semantic_mask_dir, rendered_mask_dir,
233
- "MapItAnywhere/mia/bev/styles/mia.yml", map_length, mpp,
234
- None, True, False, True, True, 1)
235
- for job_dict in jobs:
236
- get_bev.get_bev_from_bbox_worker(job_dict)
237
 
238
- bev = plt.imread(rendered_mask_dir / f"{row['id']}.png")
239
 
240
- yield metadata_fmt, fpv, bev
241
 
242
  filter_pipeline = filters.FilterPipeline.load_from_yaml("MapItAnywhere/mia/fpv/filter_pipelines/mia.yaml")
243
  filter_pipeline.verbose=False
@@ -261,15 +334,16 @@ logger.info(f"Current working directory: {os.getcwd()}, listdir: {os.listdir('.'
261
 
262
  demo = gr.Interface(
263
  fn=fetch,
264
- inputs=[gr.Text("Pittsburgh, PA, United States", label="Location"),
265
- gr.Checkbox(value=False, label="Filter & Undistort"),
266
- gr.Checkbox(value=False, label="Disable camera model filtering"),
 
267
  gr.Slider(minimum=64, maximum=512, step=1, label="BEV Dimension", value=224),
268
  gr.Slider(minimum=0.1, maximum=2, label="Meters Per Pixel", value=0.5)],
269
- outputs=[gr.Text(label="METADATA"), gr.Image(label="FPV"), gr.Image(label="BEV")],
270
- title="MapItAnywhere (Data Engine)",
271
- description="A demo showcasing samples of MIA's capability to retrieve FPV-BEV pairs worldwide."
272
- "For bulk download/heavy filtering please visit the github and follow the instructions to run locally"
273
  )
274
 
275
  logger.info("Starting server")
 
9
  import pandas as pd
10
  import geopandas as gpd
11
  from pyproj.transformer import Transformer
12
+ import cv2
13
+ from matplotlib import patches as mpatches
14
+ from matplotlib import gridspec
15
 
16
  sys.path.append(os.path.dirname(os.path.realpath(__file__)))
17
  from MapItAnywhere.mia.bev import get_bev
 
75
  chunks.append(df[i*chunk_size:(i+1)*chunk_size])
76
  return chunks
77
 
78
+ async def fetch(location, num_images, filter_undistort, disable_cam_filter, map_length, mpp):
 
79
  TOTAL_LOOKED_INTO_LIMIT = 10000
80
 
81
  ################ FPV
 
111
 
112
  dfs_meta.append(df_meta)
113
  total_rows = sum([len(x) for x in dfs_meta])
114
+ if total_rows > num_images:
115
  break
116
  elif total_looked_into > TOTAL_LOOKED_INTO_LIMIT:
117
  yield (f"Went through {total_looked_into} images and could not find images satisfying the filters."
118
  "\nPlease rerun or run the data engine locally for bulk time consuming operations.", None, None)
119
  return
120
+ if total_rows > num_images:
121
  break
122
  except:
123
  pass
124
 
125
  df_meta = pd.concat(dfs_meta)
126
+ df_meta = df_meta.sample(num_images)
127
 
128
  # Calc derrivative attributes
129
+ df_meta["loc_discrepancy"] = filters.haversine_np(
130
  lon1=df_meta["geometry.long"], lat1=df_meta["geometry.lat"],
131
  lon2=df_meta["computed_geometry.long"], lat2=df_meta["computed_geometry.lat"]
132
  )
133
 
134
+ df_meta["angle_discrepancy"] = filters.angle_dist(
135
  df_meta["compass_angle"],
136
  df_meta["computed_compass_angle"]
137
  )
138
+ img_list_to_show = list()
139
  for index, row in df_meta.iterrows():
140
+ print("Processing image", row["id"])
141
  desc = list()
142
  # Display attributes
143
  keys = ["id", "geometry.long", "geometry.lat", "compass_angle",
144
+ "loc_discrepancy", "angle_discrepancy",
145
  "make", "model", "camera_type",
146
  "quality_score"]
147
  for k in keys:
 
151
  bullet = f"{k}: {v}"
152
  desc.append(bullet)
153
  metadata_fmt = "\n".join(desc)
154
+ # yield metadata_fmt, None, None
155
+ image_urls = list(df_meta.set_index("id")["thumb_2048_url"].items())
156
+ num_fail = await get_fpv.fetch_images_pixels(image_urls, downloader, raw_image_dir)
157
+ if num_fail > 0:
158
+ logger.error(f"Failed to download {num_fail} images.")
159
+
160
+ seq_to_image_ids = df_meta.groupby('sequence')['id'].agg(list).to_dict()
161
+ lon_center = (bbox['east'] + bbox['west']) / 2
162
+ lat_center = (bbox['north'] + bbox['south']) / 2
163
+ projection = get_fpv.Projection(lat_center, lon_center, max_extent=200e3)
 
164
 
165
+ df_meta.index = df_meta["id"]
166
+ image_infos = df_meta.to_dict(orient="index")
167
+ process_sequence_args = get_fpv.default_cfg
168
 
169
+ if filter_undistort:
170
+ for seq_id, seq_image_ids in seq_to_image_ids.items():
171
+ try:
172
+ d, pi = get_fpv.process_sequence(
173
+ seq_image_ids,
174
+ image_infos,
175
+ projection,
176
+ process_sequence_args,
177
+ raw_image_dir,
178
+ out_image_dir,
179
+ )
180
+ if d is None or pi is None:
181
+ raise Exception("process_sequence returned None")
182
+ except Exception as e:
183
+ logger.error(f"Failed to process sequence {seq_id} skipping it. Error: {repr(e)}.")
184
+
185
+ fpv = plt.imread(out_image_dir/ f"{row['id']}_undistorted.jpg")
186
+ else:
187
+ print("Loading raw image")
188
+ fpv = plt.imread(raw_image_dir/ f"{row['id']}.jpg")
189
+ # yield metadata_fmt, fpv, None
190
+ ################ BEV
191
+ df = df_meta
192
+ # convert pandas dataframe to geopandas dataframe
193
+ gdf = gpd.GeoDataFrame(df,
194
+ geometry=gpd.points_from_xy(
195
+ df['computed_geometry.long'],
196
+ df['computed_geometry.lat']),
197
+ crs=4326)
198
+
199
+ # convert the geopandas dataframe to UTM
200
+ utm_crs = gdf.estimate_utm_crs()
201
+ gdf_utm = gdf.to_crs(utm_crs)
202
+ transformer = Transformer.from_crs(utm_crs, 4326)
203
+ # load OSM data, if available
204
+ padding = 50
205
+ # calculate the required distance from the center to the edge of the image
206
+ # so that the image will not be out of bounds when we rotate it
207
+ map_length = map_length
208
+ map_length = np.ceil(np.sqrt(map_length**2 + map_length**2))
209
+ distance = map_length * mpp
210
+
211
+ # create bounding boxes for each point
212
+ gdf_utm['bounding_box_utm_p1'] = gdf_utm.apply(lambda row: (
213
+ row.geometry.x - distance - padding,
214
+ row.geometry.y - distance - padding,
215
+ ), axis=1)
216
 
217
+ gdf_utm['bounding_box_utm_p2'] = gdf_utm.apply(lambda row: (
218
+ row.geometry.x + distance + padding,
219
+ row.geometry.y + distance + padding,
220
+ ), axis=1)
 
221
 
222
+ # convert the bounding box back to lat, long
223
+ gdf_utm['bounding_box_lat_long_p1'] = gdf_utm.apply(lambda row: transformer.transform(*row['bounding_box_utm_p1']), axis=1)
224
+ gdf_utm['bounding_box_lat_long_p2'] = gdf_utm.apply(lambda row: transformer.transform(*row['bounding_box_utm_p2']), axis=1)
225
+ gdf_utm['bbox_min_lat'] = gdf_utm['bounding_box_lat_long_p1'].apply(lambda x: x[0])
226
+ gdf_utm['bbox_min_long'] = gdf_utm['bounding_box_lat_long_p1'].apply(lambda x: x[1])
227
+ gdf_utm['bbox_max_lat'] = gdf_utm['bounding_box_lat_long_p2'].apply(lambda x: x[0])
228
+ gdf_utm['bbox_max_long'] = gdf_utm['bounding_box_lat_long_p2'].apply(lambda x: x[1])
229
+ gdf_utm['bbox_formatted'] = gdf_utm.apply(lambda row: f"{row['bbox_min_long']},{row['bbox_min_lat']},{row['bbox_max_long']},{row['bbox_max_lat']}", axis=1)
230
+
231
+ # iterate over the dataframe and get BEV images
232
+ jobs = gdf_utm[['id', 'bbox_formatted', 'computed_compass_angle']] # only need the id and bbox_formatted columns for the jobs
233
+ jobs = jobs.to_dict(orient='records').copy()
234
+
235
+ get_bev.get_bev_from_bbox_worker_init(osm_cache_dir, bev_dir, semantic_mask_dir, rendered_mask_dir,
236
+ "MapItAnywhere/mia/bev/styles/mia.yml", map_length, mpp,
237
+ None, True, False, True, True, 1)
238
+ for job_dict in jobs:
239
+ get_bev.get_bev_from_bbox_worker(job_dict)
240
+
241
+ bev = cv2.imread(rendered_mask_dir / f"{row['id']}.png")
242
+ bev = cv2.cvtColor(bev, cv2.COLOR_BGR2RGB)
243
+ print("BEV shape", bev.shape)
244
+
245
+ img_list_to_show_i = [fpv, bev, metadata_fmt]
246
+
247
+ img_list_to_show.append(img_list_to_show_i)
248
+
249
+ # Make plt figure
250
+ plt_row = len(img_list_to_show)
251
+ print("plt_row", plt_row)
252
+ plt_col = 3
253
+
254
+ for i in range(plt_row):
255
+
256
+ fpv, bev, metadata_fmt = img_list_to_show[i]
257
+ if i == 0:
258
+ imgs = [fpv, bev]
259
+ ratios = [i.shape[1] / i.shape[0] for i in imgs] # W / H
260
+ ratios.append(0.5) # Metadata
261
+ figsize = [sum(ratios) * 4.5, 4.5 * plt_row]
262
+ dpi = 100
263
+ fig, ax = plt.subplots(
264
+ plt_row, plt_col, figsize=figsize, dpi=dpi, gridspec_kw={"width_ratios": ratios}
265
+ )
266
+
267
+ # Plot FPV image
268
+ if plt_row == 1:
269
+ ax0 = ax[0]
270
+ ax1 = ax[1]
271
+ ax2 = ax[2]
272
+ else:
273
+ ax0 = ax[i, 0]
274
+ ax1 = ax[i, 1]
275
+ ax2 = ax[i, 2]
276
+ ax0.imshow(fpv)
277
+ ax0.set_title("First Person View Image")
278
+ ax0.axis('off')
279
+
280
+ # Plot BEV image
281
+ ax1.imshow(bev)
282
+ # Put a white upward triangle at the center of the image
283
+ ax1.scatter(bev.shape[1]//2, bev.shape[0]//2, s=200, c='white', marker='^', edgecolors='black')
284
+ ax1.set_title("Bird's Eye View Map")
285
+ ax1.axis('off')
286
 
287
+ # Add legend to BEV image
288
+ class_colors = {
289
+ 'Road': (68, 68, 68), # 0: Black
290
+ 'Crossing': (244, 162, 97), # 1; Red
291
+ 'Sidewalk': (233, 196, 106), # 2: Yellow
292
+ 'Building': (231, 111, 81), # 5: Magenta
293
+ 'Terrain': (42, 157, 143), # 7: Cyan
294
+ 'Parking': (204, 204, 204), # 8: Dark Grey
295
+ }
296
+ patches = [mpatches.Patch(color=[c/255.0 for c in color], label=label) for label, color in class_colors.items()]
297
+ ax1.legend(handles=patches, loc='upper center', bbox_to_anchor=(0.5, -0.05), ncol=3)
298
+
299
+ # Plot metadata text
300
+ ax2.axis('off')
301
+ ax2.text(0.1, 0.5, metadata_fmt, fontsize=12, va='center', ha='left', wrap=True)
302
+ ax2.set_title("Metadata")
303
 
304
+ plt.tight_layout(pad=2.0)
305
+
 
306
 
307
+ # Save figure and then read
308
+ fig_img_path = 'fpv_bev.png'
309
+ fig.savefig(fig_img_path)
310
+ fig_img = plt.imread(fig_img_path)
 
311
 
 
312
 
313
+ yield fig_img
314
 
315
  filter_pipeline = filters.FilterPipeline.load_from_yaml("MapItAnywhere/mia/fpv/filter_pipelines/mia.yaml")
316
  filter_pipeline.verbose=False
 
334
 
335
  demo = gr.Interface(
336
  fn=fetch,
337
+ inputs=[gr.Text("Pittsburgh, PA, United States", label="Location (City, {Optional: State,} Country)"),
338
+ gr.Number(value=1, label="Number of Data Pairs to Generate (Max: 3)", minimum=1, maximum=3),
339
+ gr.Checkbox(value=False, label="Filter & Undistort (True in paper. Results in better robot position estimate, but slower.)"),
340
+ gr.Checkbox(value=False, label="Disable camera model filtering (Enabled in paper. Results in better quality labels, but slower.)"),
341
  gr.Slider(minimum=64, maximum=512, step=1, label="BEV Dimension", value=224),
342
  gr.Slider(minimum=0.1, maximum=2, label="Meters Per Pixel", value=0.5)],
343
+ outputs=[gr.Image(label="Data Pair")],
344
+ title="MapItAnywhere (MIA) Data Engine",
345
+
346
+ description="Use our Data Engine to sample first-person view images and bird's-eye view semantic map pairs from locations around the globe. Simply pick a location to see the results! For faster bulk downloads and more stringent filtering, visit our repository and follow the instructions to run the data curation locally."
347
  )
348
 
349
  logger.info("Starting server")