Fix JSON prediction coordinate exports
Browse filesNormalize 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.
- README.md +1 -1
- app.py +17 -3
- ui_utils.py +0 -2
- 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.
|
| 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,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
| 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,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 179 |
-
|
| 180 |
-
info
|
| 181 |
info["MD_model"] = str(mega_model_input)
|
| 182 |
-
|
| 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 =
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 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 |
-
|
| 228 |
-
|
| 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,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 250 |
detector: None if the detector was skipped (whole image used as one animal)
|
| 251 |
"""
|
| 252 |
-
|
| 253 |
-
info
|
| 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 =
|
| 260 |
|
| 261 |
for i, animal in enumerate(animals):
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
| 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 |
###########################################
|