C-Achard commited on
Commit
a8ce24c
Β·
1 Parent(s): a8897e6

Fix JSON prediction coordinate exports

Browse files

Normalize all exported prediction JSON to original input-image coordinates for both legacy TF and PyTorch pipelines, including image size metadata and nulls for hidden keypoints. This also threads the source and annotated image sizes through the app, updates the Gradio SDK version, and removes the extra README demo examples.

Files changed (4) hide show
  1. README.md +1 -1
  2. app.py +17 -3
  3. ui_utils.py +0 -2
  4. viz_utils.py +83 -83
README.md CHANGED
@@ -5,7 +5,7 @@ colorFrom: blue
5
  colorTo: purple
6
  sdk: gradio
7
  python_version: 3.12
8
- sdk_version: 6.11.0
9
  app_file: app.py
10
  pinned: false
11
  ---
 
5
  colorTo: purple
6
  sdk: gradio
7
  python_version: 3.12
8
+ sdk_version: 6.29.0
9
  app_file: app.py
10
  pinned: false
11
  ---
app.py CHANGED
@@ -106,7 +106,13 @@ def predict_pipeline_pytorch(
106
 
107
  pose_model, detector = PYTORCH_MODELS[superanimal]
108
  download_file = save_results_pytorch(
109
- animals, map_label_id_to_str, superanimal, pose_model, None if flag_dlc_only else detector
 
 
 
 
 
 
110
  )
111
  return finalize_outputs(
112
  img_output,
@@ -220,7 +226,9 @@ def predict_pipeline(
220
  colormap=colormap,
221
  )
222
 
223
- donw_file = save_results_only_dlc(list_kpts_per_crop[0], map_label_id_to_str, dlc_model_name)
 
 
224
 
225
  return finalize_outputs(
226
  img_input, donw_file, [list_kpts_per_crop[0]], map_label_id_to_str, flag_color_by_confidence, colormap
@@ -260,7 +268,13 @@ def predict_pipeline(
260
 
261
  # Save detection results as json
262
  download_file = save_results_as_json(
263
- md_results, list_kpts_per_crop, list_bboxes, map_label_id_to_str, dlc_model_name, mega_model_input
 
 
 
 
 
 
264
  )
265
 
266
  return finalize_outputs(
 
106
 
107
  pose_model, detector = PYTORCH_MODELS[superanimal]
108
  download_file = save_results_pytorch(
109
+ animals,
110
+ map_label_id_to_str,
111
+ superanimal,
112
+ pose_model,
113
+ None if flag_dlc_only else detector,
114
+ image_size=img_input.size,
115
+ annotated_size=img_output.size,
116
  )
117
  return finalize_outputs(
118
  img_output,
 
226
  colormap=colormap,
227
  )
228
 
229
+ donw_file = save_results_only_dlc(
230
+ list_kpts_per_crop[0], map_label_id_to_str, dlc_model_name, image_size=img_input.size
231
+ )
232
 
233
  return finalize_outputs(
234
  img_input, donw_file, [list_kpts_per_crop[0]], map_label_id_to_str, flag_color_by_confidence, colormap
 
268
 
269
  # Save detection results as json
270
  download_file = save_results_as_json(
271
+ md_results,
272
+ list_kpts_per_crop,
273
+ list_bboxes,
274
+ map_label_id_to_str,
275
+ dlc_model_name,
276
+ mega_model_input,
277
+ image_size=img_input.size,
278
  )
279
 
280
  return finalize_outputs(
ui_utils.py CHANGED
@@ -221,8 +221,6 @@ def gradio_description_and_examples():
221
  for image in (
222
  "examples/dog.jpeg",
223
  "examples/cat.jpg",
224
- "examples/lynx.jpg",
225
- "examples/giraffe.jpg",
226
  )
227
  for font_size, marker_size in [example_sizes(image)]
228
  ]
 
221
  for image in (
222
  "examples/dog.jpeg",
223
  "examples/cat.jpg",
 
 
224
  )
225
  for font_size, marker_size in [example_sizes(image)]
226
  ]
viz_utils.py CHANGED
@@ -1,4 +1,5 @@
1
  import json
 
2
  from datetime import date
3
 
4
  import numpy as np
@@ -161,6 +162,36 @@ def label_text_color(background):
161
 
162
 
163
  ###########################################
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  def save_results_as_json(
165
  md_results,
166
  dlc_outputs,
@@ -168,110 +199,79 @@ def save_results_as_json(
168
  map_dlc_label_id_to_str,
169
  model,
170
  mega_model_input,
 
171
  path_to_output_file="download_predictions.json",
172
  ):
173
- """
174
- Output detections as json file
175
 
176
- animal_bboxes: detection rows [x1,y1,x2,y2,conf,label], one per entry of dlc_outputs
 
177
  """
178
- # initialise dict to save to json
179
- info = {}
180
- info["date"] = str(today)
181
  info["MD_model"] = str(mega_model_input)
182
- # info from megaDetector
183
- info["file"] = md_results.files[0]
184
- number_bb = len(md_results.xyxy[0].tolist())
185
- info["number_of_bb"] = number_bb
186
- # info from DLC
187
  info["dlc_model"] = model
188
- labels = [n for n in map_dlc_label_id_to_str.values()]
189
-
190
- # define aux dict for every animal bounding box above threshold
191
- for i in range(len(dlc_outputs)):
192
- aux = {}
193
- # MD output
194
- corner_x1, corner_y1, corner_x2, corner_y2, confidence, _ = animal_bboxes[i]
195
- aux["corner_1"] = (corner_x1, corner_y1)
196
- aux["corner_2"] = (corner_x2, corner_y2)
197
- aux["predict MD"] = md_results.names[0]
198
- aux["confidence MD"] = confidence
199
-
200
- # DLC output
201
- kypts = []
202
- for s in dlc_outputs[i]:
203
- aux1 = []
204
- for j in s:
205
- aux1.append(float(j))
206
-
207
- kypts.append(aux1)
208
- aux["dlc_pred"] = dict(zip(labels, kypts, strict=True))
209
- info["bb_" + str(i)] = aux
210
-
211
- # save dict as json
212
- with open(path_to_output_file, "w") as f:
213
- json.dump(info, f, indent=1)
214
- print(f"Output file saved at {path_to_output_file}")
215
-
216
- return path_to_output_file
217
-
218
-
219
- def save_results_only_dlc(dlc_outputs, map_label_id_to_str, model, output_file="dowload_predictions_dlc.json"):
220
- """
221
- write json dlc output
222
- """
223
- info = {}
224
- info["date"] = str(today)
225
- labels = [n for n in map_label_id_to_str.values()]
226
  info["dlc_model"] = model
227
- kypts = []
228
- for s in dlc_outputs:
229
- aux1 = []
230
- for j in s:
231
- aux1.append(float(j))
232
-
233
- kypts.append(aux1)
234
- info["dlc_pred"] = dict(zip(labels, kypts, strict=True))
235
-
236
- with open(output_file, "w") as f:
237
- json.dump(info, f, indent=1)
238
- print(f"Output file saved at {output_file}")
239
-
240
- return output_file
241
 
242
 
243
  def save_results_pytorch(
244
- animals, map_label_id_to_str, model, pose_model, detector, path_to_output_file="download_predictions.json"
 
 
 
 
 
 
 
245
  ):
246
- """
247
- Output PyTorch SuperAnimal predictions as json file (same layout as save_results_as_json)
248
 
249
- animals: list of {'bbox': [x1,y1,x2,y2,conf], 'kpts': (num_keypoints, 3)}, in image coords
250
  detector: None if the detector was skipped (whole image used as one animal)
251
  """
252
- info = {}
253
- info["date"] = str(today)
254
  info["backend"] = "pytorch"
255
  info["dlc_model"] = model
256
  info["pose_model"] = pose_model
257
  info["detector"] = detector
258
  info["number_of_bb"] = len(animals)
259
- labels = [n for n in map_label_id_to_str.values()]
260
 
261
  for i, animal in enumerate(animals):
262
- corner_x1, corner_y1, corner_x2, corner_y2, confidence = animal["bbox"]
263
- aux = {}
264
- aux["corner_1"] = (corner_x1, corner_y1)
265
- aux["corner_2"] = (corner_x2, corner_y2)
266
- aux["confidence"] = confidence
267
- aux["dlc_pred"] = dict(zip(labels, [[float(v) for v in kpt] for kpt in animal["kpts"]], strict=True))
268
- info["bb_" + str(i)] = aux
269
-
270
- with open(path_to_output_file, "w") as f:
271
- json.dump(info, f, indent=1)
272
- print(f"Output file saved at {path_to_output_file}")
273
-
274
- return path_to_output_file
275
 
276
 
277
  ###########################################
 
1
  import json
2
+ import math
3
  from datetime import date
4
 
5
  import numpy as np
 
162
 
163
 
164
  ###########################################
165
+ # JSON outputs: pixel coordinates in the input image, hidden keypoints as null
166
+ COORDINATES = "pixels in the input image (after EXIF orientation), origin top-left, x right, y down"
167
+
168
+
169
+ def keypoints_to_json(kpts, offset=(0, 0), scale=(1.0, 1.0)):
170
+ """[x, y, confidence] per keypoint, mapped by (k + offset) * scale; NaN (below threshold) becomes null."""
171
+ out = []
172
+ for x, y, conf in np.asarray(kpts, dtype=float)[:, :3]:
173
+ if np.isnan(conf):
174
+ out.append(None)
175
+ else:
176
+ out.append([(x + offset[0]) * scale[0], (y + offset[1]) * scale[1], conf])
177
+ return out
178
+
179
+
180
+ def write_json(info, path_to_output_file):
181
+ with open(path_to_output_file, "w") as f:
182
+ json.dump(info, f, indent=1, allow_nan=False)
183
+ return path_to_output_file
184
+
185
+
186
+ def json_header(image_size, annotated_size):
187
+ return {
188
+ "date": str(today),
189
+ "coordinates": COORDINATES,
190
+ "image_size": list(image_size),
191
+ "annotated_image_size": list(annotated_size),
192
+ }
193
+
194
+
195
  def save_results_as_json(
196
  md_results,
197
  dlc_outputs,
 
199
  map_dlc_label_id_to_str,
200
  model,
201
  mega_model_input,
202
+ image_size,
203
  path_to_output_file="download_predictions.json",
204
  ):
205
+ """TF (legacy) MegaDetector + DLC results.
 
206
 
207
+ animal_bboxes: detection rows [x1,y1,x2,y2,conf,label] in the MegaDetector frame, one per entry of dlc_outputs
208
+ dlc_outputs: keypoints relative to each crop (crops start at floor(x1), floor(y1), see crop_animal_detections)
209
  """
210
+ md_h, md_w = md_results.ims[0].shape[:2]
211
+ scale = (image_size[0] / md_w, image_size[1] / md_h)
212
+ info = json_header(image_size, (md_w, md_h))
213
  info["MD_model"] = str(mega_model_input)
214
+ info["number_of_bb"] = len(dlc_outputs)
 
 
 
 
215
  info["dlc_model"] = model
216
+ labels = list(map_dlc_label_id_to_str.values())
217
+
218
+ for i, kpts in enumerate(dlc_outputs):
219
+ x1, y1, x2, y2, confidence, _ = animal_bboxes[i]
220
+ info["bb_" + str(i)] = {
221
+ "corner_1": (x1 * scale[0], y1 * scale[1]),
222
+ "corner_2": (x2 * scale[0], y2 * scale[1]),
223
+ "predict MD": md_results.names[0],
224
+ "confidence MD": float(confidence),
225
+ "dlc_pred": dict(
226
+ zip(labels, keypoints_to_json(kpts, offset=(math.floor(x1), math.floor(y1)), scale=scale), strict=True)
227
+ ),
228
+ }
229
+ return write_json(info, path_to_output_file)
230
+
231
+
232
+ def save_results_only_dlc(
233
+ dlc_outputs, map_label_id_to_str, model, image_size, output_file="dowload_predictions_dlc.json"
234
+ ):
235
+ """TF (legacy) DLC run on the whole input image (keypoints already in input pixels)."""
236
+ info = json_header(image_size, image_size)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  info["dlc_model"] = model
238
+ info["dlc_pred"] = dict(zip(map_label_id_to_str.values(), keypoints_to_json(dlc_outputs), strict=True))
239
+ return write_json(info, output_file)
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
 
242
  def save_results_pytorch(
243
+ animals,
244
+ map_label_id_to_str,
245
+ model,
246
+ pose_model,
247
+ detector,
248
+ image_size,
249
+ annotated_size,
250
+ path_to_output_file="download_predictions.json",
251
  ):
252
+ """PyTorch SuperAnimal results (same layout as save_results_as_json).
 
253
 
254
+ animals: list of {'bbox': [x1,y1,x2,y2,conf], 'kpts': (num_keypoints, 3)}, in the annotated (resized) image
255
  detector: None if the detector was skipped (whole image used as one animal)
256
  """
257
+ scale = (image_size[0] / annotated_size[0], image_size[1] / annotated_size[1])
258
+ info = json_header(image_size, annotated_size)
259
  info["backend"] = "pytorch"
260
  info["dlc_model"] = model
261
  info["pose_model"] = pose_model
262
  info["detector"] = detector
263
  info["number_of_bb"] = len(animals)
264
+ labels = list(map_label_id_to_str.values())
265
 
266
  for i, animal in enumerate(animals):
267
+ x1, y1, x2, y2, confidence = animal["bbox"]
268
+ info["bb_" + str(i)] = {
269
+ "corner_1": (x1 * scale[0], y1 * scale[1]),
270
+ "corner_2": (x2 * scale[0], y2 * scale[1]),
271
+ "confidence": confidence,
272
+ "dlc_pred": dict(zip(labels, keypoints_to_json(animal["kpts"], scale=scale), strict=True)),
273
+ }
274
+ return write_json(info, path_to_output_file)
 
 
 
 
 
275
 
276
 
277
  ###########################################