Update app requirements and code
#4
by C-Achard - opened
- .gitignore +1 -14
- .pre-commit-config.yaml +0 -28
- README.md +2 -3
- app.py +139 -261
- detection_utils.py +59 -43
- dlc_utils.py +20 -16
- pre-requirements.txt +0 -1
- pyproject.toml +0 -33
- pytorch_utils.py +0 -118
- requirements.txt +14 -7
- save_results.py +56 -0
- ui_utils.py +64 -218
- viz_utils.py +142 -222
.gitignore
CHANGED
|
@@ -1,16 +1,3 @@
|
|
| 1 |
# Byte-compiled / optimized / DLL files
|
| 2 |
__pycache__/
|
| 3 |
-
model/__pycache__/
|
| 4 |
-
.vscode/settings.json
|
| 5 |
-
|
| 6 |
-
# Local environment and tool caches
|
| 7 |
-
.venv/
|
| 8 |
-
.ruff_cache/
|
| 9 |
-
|
| 10 |
-
# Written by the app at runtime
|
| 11 |
-
.gradio/
|
| 12 |
-
download_predictions.json
|
| 13 |
-
dowload_predictions_dlc.json
|
| 14 |
-
download_annotated.png
|
| 15 |
-
# TF (legacy) models downloaded on first use; DLC_models/readme.md stays tracked
|
| 16 |
-
DLC_models/*/
|
|
|
|
| 1 |
# Byte-compiled / optimized / DLL files
|
| 2 |
__pycache__/
|
| 3 |
+
model/__pycache__/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.pre-commit-config.yaml
DELETED
|
@@ -1,28 +0,0 @@
|
|
| 1 |
-
# Adapted from DeepLabCut's .pre-commit-config.yaml.
|
| 2 |
-
repos:
|
| 3 |
-
- repo: https://github.com/pre-commit/pre-commit-hooks
|
| 4 |
-
rev: v6.0.0
|
| 5 |
-
hooks:
|
| 6 |
-
- id: check-added-large-files
|
| 7 |
-
- id: check-yaml
|
| 8 |
-
- id: check-toml
|
| 9 |
-
- id: check-merge-conflict
|
| 10 |
-
- id: end-of-file-fixer
|
| 11 |
-
- id: trailing-whitespace
|
| 12 |
-
|
| 13 |
-
- repo: https://github.com/tox-dev/pyproject-fmt
|
| 14 |
-
rev: v2.19.0
|
| 15 |
-
hooks:
|
| 16 |
-
- id: pyproject-fmt
|
| 17 |
-
|
| 18 |
-
- repo: https://github.com/abravalheri/validate-pyproject
|
| 19 |
-
rev: v0.25
|
| 20 |
-
hooks:
|
| 21 |
-
- id: validate-pyproject
|
| 22 |
-
|
| 23 |
-
- repo: https://github.com/astral-sh/ruff-pre-commit
|
| 24 |
-
rev: v0.15.6
|
| 25 |
-
hooks:
|
| 26 |
-
- id: ruff-check
|
| 27 |
-
args: [--fix, --unsafe-fixes]
|
| 28 |
-
- id: ruff-format
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -4,10 +4,9 @@ emoji: πππ΄π»πππΏππ¦π¦
|
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
-
|
| 8 |
-
sdk_version: 6.29.0
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
| 11 |
---
|
| 12 |
|
| 13 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 4 |
colorFrom: blue
|
| 5 |
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 6.11.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
---
|
| 11 |
|
| 12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
CHANGED
|
@@ -1,314 +1,192 @@
|
|
| 1 |
-
# Adapted from https://huggingface.co/spaces/hlydecker/MegaDetector_v5
|
| 2 |
# Adapted from https://huggingface.co/spaces/sofmi/MegaDetector_DLClive/blob/main/app.py
|
| 3 |
-
# Adapted from https://huggingface.co/spaces/Neslihan/megadetector_dlcmodels/blob/main/app.py
|
| 4 |
# Adapted from https://huggingface.co/spaces/DeepLabCut/MegaDetector_DeepLabCut
|
| 5 |
|
| 6 |
import os
|
| 7 |
-
import threading
|
| 8 |
-
|
| 9 |
-
import gradio as gr
|
| 10 |
-
import numpy as np
|
| 11 |
import yaml
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
-
|
| 18 |
-
|
| 19 |
|
| 20 |
-
from
|
|
|
|
| 21 |
from dlc_utils import predict_dlc
|
| 22 |
-
from
|
| 23 |
-
from ui_utils import (
|
| 24 |
-
confidence_legend_html,
|
| 25 |
-
dlc_theme,
|
| 26 |
-
gradio_description_and_examples,
|
| 27 |
-
gradio_inputs_for_MD_DLC,
|
| 28 |
-
gradio_outputs_for_MD_DLC,
|
| 29 |
-
)
|
| 30 |
-
from viz_utils import (
|
| 31 |
-
draw_bbox_w_text,
|
| 32 |
-
draw_keypoints_on_image,
|
| 33 |
-
keypoint_confidence_rows,
|
| 34 |
-
save_annotated_image,
|
| 35 |
-
save_results_as_json,
|
| 36 |
-
save_results_only_dlc,
|
| 37 |
-
save_results_pytorch,
|
| 38 |
-
)
|
| 39 |
-
|
| 40 |
-
# TESTING (passes) download the SuperAnimal models:
|
| 41 |
-
# model = 'superanimal_topviewmouse'
|
| 42 |
-
# train_dir = 'DLC_models/sa-tvm'
|
| 43 |
-
# download_huggingface_model(model, train_dir)
|
| 44 |
-
|
| 45 |
-
# megadetector and dlc model look up
|
| 46 |
-
MD_models_dict = {
|
| 47 |
-
"md_v5a": "MD_models/md_v5a.0.0.pt", #
|
| 48 |
-
"md_v5b": "MD_models/md_v5b.0.0.pt",
|
| 49 |
-
}
|
| 50 |
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
|
| 53 |
-
# TF (legacy) DLC models: model zoo name and target dir, per SuperAnimal
|
| 54 |
-
DLC_models_dict = {
|
| 55 |
-
"superanimal_topviewmouse": ("superanimal_topviewmouse_dlcrnet", "DLC_models/sa-tvm"),
|
| 56 |
-
"superanimal_quadruped": ("superanimal_quadruped_dlcrnet", "DLC_models/sa-q"),
|
| 57 |
-
}
|
| 58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
-
#
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
confidence_rows = keypoint_confidence_rows(kpts_per_animal, map_label_id_to_str)
|
| 64 |
-
legend = confidence_legend_html(colormap) if color_by_confidence else ""
|
| 65 |
-
return img_output, legend, download_file, annotated_file, confidence_rows
|
| 66 |
|
|
|
|
|
|
|
|
|
|
| 67 |
|
| 68 |
-
#
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
flag_dlc_only,
|
| 73 |
-
flag_show_str_labels,
|
| 74 |
-
bbox_likelihood_th,
|
| 75 |
-
kpts_likelihood_th,
|
| 76 |
-
font_style,
|
| 77 |
-
font_size,
|
| 78 |
-
keypt_color,
|
| 79 |
-
marker_size,
|
| 80 |
-
flag_color_by_confidence,
|
| 81 |
-
colormap,
|
| 82 |
-
bbox_color,
|
| 83 |
-
):
|
| 84 |
-
# detection + pose with the SuperAnimal PyTorch models (keypoints in image coords)
|
| 85 |
-
img_output, animals, bodyparts = predict_superanimal(
|
| 86 |
-
img_input, superanimal, bbox_likelihood_th, kpts_likelihood_th, full_image=flag_dlc_only
|
| 87 |
-
)
|
| 88 |
-
map_label_id_to_str = dict(enumerate(bodyparts))
|
| 89 |
-
|
| 90 |
-
for animal in animals:
|
| 91 |
-
draw_keypoints_on_image(
|
| 92 |
-
img_output,
|
| 93 |
-
animal["kpts"],
|
| 94 |
-
map_label_id_to_str,
|
| 95 |
-
flag_show_str_labels,
|
| 96 |
-
use_normalized_coordinates=False,
|
| 97 |
-
font_style=font_style,
|
| 98 |
-
font_size=font_size,
|
| 99 |
-
keypt_color=keypt_color,
|
| 100 |
-
marker_size=marker_size,
|
| 101 |
-
color_by_confidence=flag_color_by_confidence,
|
| 102 |
-
colormap=colormap,
|
| 103 |
-
)
|
| 104 |
-
if not flag_dlc_only:
|
| 105 |
-
draw_bbox_w_text(img_output, animal["bbox"], font_size=font_size, bbox_color=bbox_color)
|
| 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,
|
| 119 |
-
download_file,
|
| 120 |
-
[animal["kpts"] for animal in animals],
|
| 121 |
-
map_label_id_to_str,
|
| 122 |
-
flag_color_by_confidence,
|
| 123 |
-
colormap,
|
| 124 |
-
)
|
| 125 |
|
| 126 |
|
| 127 |
#####################################################
|
| 128 |
-
def predict_pipeline(
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
marker_size,
|
| 141 |
-
flag_color_by_confidence,
|
| 142 |
-
colormap,
|
| 143 |
-
bbox_color,
|
| 144 |
-
):
|
| 145 |
-
|
| 146 |
-
if backend == "PyTorch":
|
| 147 |
-
return predict_pipeline_pytorch(
|
| 148 |
-
img_input,
|
| 149 |
-
dlc_model_input_str,
|
| 150 |
-
flag_dlc_only,
|
| 151 |
-
flag_show_str_labels,
|
| 152 |
-
bbox_likelihood_th,
|
| 153 |
-
kpts_likelihood_th,
|
| 154 |
-
font_style,
|
| 155 |
-
font_size,
|
| 156 |
-
keypt_color,
|
| 157 |
-
marker_size,
|
| 158 |
-
flag_color_by_confidence,
|
| 159 |
-
colormap,
|
| 160 |
-
bbox_color,
|
| 161 |
-
)
|
| 162 |
-
|
| 163 |
-
# TensorFlow (legacy): MegaDetector crops + DLCLive
|
| 164 |
-
dlc_model_name, dlc_model_dir = DLC_models_dict[dlc_model_input_str]
|
| 165 |
|
| 166 |
if not flag_dlc_only:
|
| 167 |
-
############################################################
|
| 168 |
# ### Run Megadetector
|
| 169 |
-
md_results = predict_md(
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
size=640,
|
| 173 |
-
) # Image.fromarray(results.imgs[0])
|
| 174 |
|
| 175 |
################################################################
|
| 176 |
-
# Obtain animal crops
|
| 177 |
-
list_crops
|
|
|
|
|
|
|
| 178 |
|
| 179 |
############################################################
|
| 180 |
|
| 181 |
-
## Get DLC model and label map
|
| 182 |
-
|
| 183 |
# If model is found: do not download (previous execution is likely within same day)
|
| 184 |
# TODO: can we ask the user whether to reload dlc model if a directory is found?
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
|
|
|
|
|
|
|
|
|
| 188 |
|
| 189 |
# extract map label ids to strings
|
| 190 |
-
pose_cfg_path = os.path.join(
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
], # pose_cfg_dict['all_joints'] is a list of one-element lists,
|
| 200 |
-
pose_cfg_dict["all_joints_names"],
|
| 201 |
-
strict=True,
|
| 202 |
-
)
|
| 203 |
-
]
|
| 204 |
-
)
|
| 205 |
-
|
| 206 |
-
##############################################################
|
| 207 |
# Run DLC and visualize results
|
| 208 |
-
dlc_proc = Processor()
|
| 209 |
|
| 210 |
# if required: ignore MD crops and run DLC on full image [mostly for testing]
|
| 211 |
if flag_dlc_only:
|
| 212 |
# compute kpts on input img
|
| 213 |
-
list_kpts_per_crop = predict_dlc([np.asarray(img_input)],
|
|
|
|
|
|
|
|
|
|
| 214 |
# draw kpts on input img #fix!
|
| 215 |
-
draw_keypoints_on_image(
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 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
|
| 235 |
-
)
|
| 236 |
|
| 237 |
else:
|
| 238 |
# Compute kpts for each crop
|
| 239 |
-
list_kpts_per_crop = predict_dlc(list_crops,
|
| 240 |
-
|
|
|
|
|
|
|
|
|
|
| 241 |
# resize input image to match megadetector output
|
| 242 |
-
img_background = img_input.resize((md_results.ims[0].shape[1],
|
| 243 |
-
|
|
|
|
| 244 |
# draw keypoints on each crop and paste to background img
|
| 245 |
-
for np_crop, kpts_crop
|
|
|
|
|
|
|
| 246 |
img_crop = Image.fromarray(np_crop)
|
| 247 |
|
| 248 |
# Draw keypts on crop
|
| 249 |
-
draw_keypoints_on_image(
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
| 256 |
-
|
| 257 |
-
|
| 258 |
-
marker_size=marker_size,
|
| 259 |
-
color_by_confidence=flag_color_by_confidence,
|
| 260 |
-
colormap=colormap,
|
| 261 |
-
)
|
| 262 |
|
| 263 |
# Paste crop in original image
|
| 264 |
-
img_background.paste(img_crop,
|
|
|
|
| 265 |
|
| 266 |
# Plot bbox
|
| 267 |
-
|
| 268 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 269 |
# Save detection results as json
|
| 270 |
-
download_file
|
| 271 |
-
|
| 272 |
-
|
| 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(
|
| 281 |
-
img_background, download_file, list_kpts_per_crop, map_label_id_to_str, flag_color_by_confidence, colormap
|
| 282 |
-
)
|
| 283 |
|
| 284 |
|
| 285 |
#########################################################
|
| 286 |
# Define user interface and launch
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
run_button.click(predict_pipeline, inputs=inputs, outputs=outputs, api_name="predict")
|
| 305 |
-
|
| 306 |
-
# cached on first click, so a failing download cannot block startup
|
| 307 |
-
gr.Examples(examples, inputs=inputs, outputs=outputs, fn=predict_pipeline, cache_examples=True, cache_mode="lazy")
|
| 308 |
-
|
| 309 |
-
# download and build the default model while the app starts; a request arriving
|
| 310 |
-
# earlier waits on the same lock instead of downloading again
|
| 311 |
-
threading.Thread(target=load_superanimal, args=("superanimal_quadruped",), daemon=True).start()
|
| 312 |
-
|
| 313 |
-
demo.queue(default_concurrency_limit=1) # PyTorch runners are not thread-safe
|
| 314 |
-
demo.launch(theme=dlc_theme())
|
|
|
|
| 1 |
+
# Adapted from https://huggingface.co/spaces/hlydecker/MegaDetector_v5
|
| 2 |
# Adapted from https://huggingface.co/spaces/sofmi/MegaDetector_DLClive/blob/main/app.py
|
| 3 |
+
# Adapted from https://huggingface.co/spaces/Neslihan/megadetector_dlcmodels/blob/main/app.py
|
| 4 |
# Adapted from https://huggingface.co/spaces/DeepLabCut/MegaDetector_DeepLabCut
|
| 5 |
|
| 6 |
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
import yaml
|
| 8 |
+
import numpy as np
|
| 9 |
+
from matplotlib import cm
|
| 10 |
+
import gradio as gr
|
| 11 |
+
import deeplabcut
|
| 12 |
+
import dlclibrary
|
| 13 |
+
import dlclive
|
| 14 |
+
import transformers
|
| 15 |
|
| 16 |
+
from PIL import Image, ImageColor, ImageFont, ImageDraw
|
| 17 |
+
import requests
|
| 18 |
|
| 19 |
+
from viz_utils import save_results_as_json, draw_keypoints_on_image, draw_bbox_w_text, save_results_only_dlc
|
| 20 |
+
from detection_utils import predict_md, crop_animal_detections
|
| 21 |
from dlc_utils import predict_dlc
|
| 22 |
+
from ui_utils import gradio_inputs_for_MD_DLC, gradio_outputs_for_MD_DLC, gradio_description_and_examples
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
+
from deeplabcut.utils import auxiliaryfunctions
|
| 25 |
+
from dlclibrary.dlcmodelzoo.modelzoo_download import (
|
| 26 |
+
download_huggingface_model,
|
| 27 |
+
MODELOPTIONS,
|
| 28 |
+
)
|
| 29 |
+
from dlclive import DLCLive, Processor
|
| 30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
+
# TESTING (passes) download the SuperAnimal models:
|
| 33 |
+
#model = 'superanimal_topviewmouse'
|
| 34 |
+
#train_dir = 'DLC_models/sa-tvm'
|
| 35 |
+
#download_huggingface_model(model, train_dir)
|
| 36 |
|
| 37 |
+
# grab demo data cooco cat:
|
| 38 |
+
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
|
| 39 |
+
image = Image.open(requests.get(url, stream=True).raw)
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
# megadetector and dlc model look up
|
| 42 |
+
MD_models_dict = {'md_v5a': "MD_models/md_v5a.0.0.pt", #
|
| 43 |
+
'md_v5b': "MD_models/md_v5b.0.0.pt"}
|
| 44 |
|
| 45 |
+
# DLC models target dirs
|
| 46 |
+
DLC_models_dict = {'superanimal_topviewmouse_dlcrnet': "DLC_models/sa-tvm",
|
| 47 |
+
'superanimal_quadruped_dlcrnet': "DLC_models/sa-q"}
|
| 48 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
|
| 51 |
#####################################################
|
| 52 |
+
def predict_pipeline(img_input,
|
| 53 |
+
mega_model_input,
|
| 54 |
+
dlc_model_input_str,
|
| 55 |
+
flag_dlc_only,
|
| 56 |
+
flag_show_str_labels,
|
| 57 |
+
bbox_likelihood_th,
|
| 58 |
+
kpts_likelihood_th,
|
| 59 |
+
font_style,
|
| 60 |
+
font_size,
|
| 61 |
+
keypt_color,
|
| 62 |
+
marker_size,
|
| 63 |
+
):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
if not flag_dlc_only:
|
| 66 |
+
############################################################
|
| 67 |
# ### Run Megadetector
|
| 68 |
+
md_results = predict_md(img_input,
|
| 69 |
+
MD_models_dict[mega_model_input], #mega_model_input,
|
| 70 |
+
size=640) #Image.fromarray(results.imgs[0])
|
|
|
|
|
|
|
| 71 |
|
| 72 |
################################################################
|
| 73 |
+
# Obtain animal crops for bboxes with confidence above th
|
| 74 |
+
list_crops = crop_animal_detections(img_input,
|
| 75 |
+
md_results,
|
| 76 |
+
bbox_likelihood_th)
|
| 77 |
|
| 78 |
############################################################
|
| 79 |
|
| 80 |
+
## Get DLC model and label map
|
| 81 |
+
|
| 82 |
# If model is found: do not download (previous execution is likely within same day)
|
| 83 |
# TODO: can we ask the user whether to reload dlc model if a directory is found?
|
| 84 |
+
if os.path.isdir(DLC_models_dict[dlc_model_input_str]) and \
|
| 85 |
+
len(os.listdir(DLC_models_dict[dlc_model_input_str])) > 0:
|
| 86 |
+
path_to_DLCmodel = DLC_models_dict[dlc_model_input_str]
|
| 87 |
+
else:
|
| 88 |
+
path_to_DLCmodel = DLC_models_dict[dlc_model_input_str]
|
| 89 |
+
download_huggingface_model(dlc_model_input_str, path_to_DLCmodel)
|
| 90 |
|
| 91 |
# extract map label ids to strings
|
| 92 |
+
pose_cfg_path = os.path.join(DLC_models_dict[dlc_model_input_str],
|
| 93 |
+
'pose_cfg.yaml')
|
| 94 |
+
with open(pose_cfg_path, "r") as stream:
|
| 95 |
+
pose_cfg_dict = yaml.safe_load(stream)
|
| 96 |
+
map_label_id_to_str = dict([(k,v) for k,v in zip([el[0] for el in pose_cfg_dict['all_joints']], # pose_cfg_dict['all_joints'] is a list of one-element lists,
|
| 97 |
+
pose_cfg_dict['all_joints_names'])])
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
##############################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
# Run DLC and visualize results
|
| 102 |
+
dlc_proc = Processor() #TODO: update deeplabcut.video_inference_superanimal() once merged
|
| 103 |
|
| 104 |
# if required: ignore MD crops and run DLC on full image [mostly for testing]
|
| 105 |
if flag_dlc_only:
|
| 106 |
# compute kpts on input img
|
| 107 |
+
list_kpts_per_crop = predict_dlc([np.asarray(img_input)],
|
| 108 |
+
kpts_likelihood_th,
|
| 109 |
+
path_to_DLCmodel,
|
| 110 |
+
dlc_proc)
|
| 111 |
# draw kpts on input img #fix!
|
| 112 |
+
draw_keypoints_on_image(img_input,
|
| 113 |
+
list_kpts_per_crop[0], # a numpy array with shape [num_keypoints, 2].
|
| 114 |
+
map_label_id_to_str,
|
| 115 |
+
flag_show_str_labels,
|
| 116 |
+
use_normalized_coordinates=False,
|
| 117 |
+
font_style=font_style,
|
| 118 |
+
font_size=font_size,
|
| 119 |
+
keypt_color=keypt_color,
|
| 120 |
+
marker_size=marker_size)
|
| 121 |
+
|
| 122 |
+
donw_file = save_results_only_dlc(list_kpts_per_crop[0], map_label_id_to_str,dlc_model_input_str)
|
| 123 |
+
|
| 124 |
+
return img_input, donw_file
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
|
| 126 |
else:
|
| 127 |
# Compute kpts for each crop
|
| 128 |
+
list_kpts_per_crop = predict_dlc(list_crops,
|
| 129 |
+
kpts_likelihood_th,
|
| 130 |
+
path_to_DLCmodel,
|
| 131 |
+
dlc_proc)
|
| 132 |
+
|
| 133 |
# resize input image to match megadetector output
|
| 134 |
+
img_background = img_input.resize((md_results.ims[0].shape[1],
|
| 135 |
+
md_results.ims[0].shape[0]))
|
| 136 |
+
|
| 137 |
# draw keypoints on each crop and paste to background img
|
| 138 |
+
for ic, (np_crop, kpts_crop) in enumerate(zip(list_crops,
|
| 139 |
+
list_kpts_per_crop)):
|
| 140 |
+
|
| 141 |
img_crop = Image.fromarray(np_crop)
|
| 142 |
|
| 143 |
# Draw keypts on crop
|
| 144 |
+
draw_keypoints_on_image(img_crop,
|
| 145 |
+
kpts_crop, # a numpy array with shape [num_keypoints, 2].
|
| 146 |
+
map_label_id_to_str,
|
| 147 |
+
flag_show_str_labels,
|
| 148 |
+
use_normalized_coordinates=False, # if True, then I should use md_results.xyxyn for list_kpts_crop
|
| 149 |
+
font_style=font_style,
|
| 150 |
+
font_size=font_size,
|
| 151 |
+
keypt_color=keypt_color,
|
| 152 |
+
marker_size=marker_size)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
|
| 154 |
# Paste crop in original image
|
| 155 |
+
img_background.paste(img_crop,
|
| 156 |
+
box = tuple([int(t) for t in md_results.xyxy[0][ic,:2]]))
|
| 157 |
|
| 158 |
# Plot bbox
|
| 159 |
+
bb_per_animal = md_results.xyxy[0].tolist()[ic]
|
| 160 |
+
pred = md_results.xyxy[0].tolist()[ic][4]
|
| 161 |
+
if bbox_likelihood_th < pred:
|
| 162 |
+
draw_bbox_w_text(img_background,
|
| 163 |
+
bb_per_animal,
|
| 164 |
+
font_size=font_size) # TODO: add selectable color for bbox?
|
| 165 |
+
|
| 166 |
+
|
| 167 |
# Save detection results as json
|
| 168 |
+
download_file = save_results_as_json(md_results,list_kpts_per_crop,map_label_id_to_str, bbox_likelihood_th,dlc_model_input_str,mega_model_input)
|
| 169 |
+
|
| 170 |
+
return img_background, download_file
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
|
|
|
|
|
|
|
|
|
|
| 172 |
|
| 173 |
|
| 174 |
#########################################################
|
| 175 |
# Define user interface and launch
|
| 176 |
+
inputs = gradio_inputs_for_MD_DLC(list(MD_models_dict.keys()),
|
| 177 |
+
list(DLC_models_dict.keys()))
|
| 178 |
+
outputs = gradio_outputs_for_MD_DLC()
|
| 179 |
+
[gr_title,
|
| 180 |
+
gr_description,
|
| 181 |
+
examples] = gradio_description_and_examples()
|
| 182 |
+
|
| 183 |
+
# launch
|
| 184 |
+
demo = gr.Interface(predict_pipeline,
|
| 185 |
+
inputs=inputs,
|
| 186 |
+
outputs=outputs,
|
| 187 |
+
title=gr_title,
|
| 188 |
+
description=gr_description,
|
| 189 |
+
examples=examples,
|
| 190 |
+
theme="huggingface")
|
| 191 |
+
|
| 192 |
+
demo.launch(enable_queue=True, share=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
detection_utils.py
CHANGED
|
@@ -1,74 +1,90 @@
|
|
| 1 |
-
import math
|
| 2 |
|
| 3 |
-
|
| 4 |
-
import
|
|
|
|
| 5 |
import torch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
|
| 7 |
|
|
|
|
|
|
|
|
|
|
| 8 |
############################################
|
| 9 |
# Predict detections with MegaDetector v5a model
|
| 10 |
-
def predict_md(
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
):
|
| 15 |
-
|
| 16 |
# resize image
|
| 17 |
-
g = size / max(im.size) # multipl factor to make max size of the image equal to input size
|
| 18 |
-
im = im.resize((int(x * g) for x in im.size),
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
## detect objects
|
| 33 |
-
# vars(results).keys()
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
return results
|
| 37 |
|
| 38 |
|
| 39 |
##########################################
|
| 40 |
-
def crop_animal_detections(img_in,
|
|
|
|
|
|
|
| 41 |
|
| 42 |
## Extract animal crops
|
| 43 |
-
list_labels_as_str = [i for i in yolo_results.names.values()] # ['animal', 'person', 'vehicle']
|
| 44 |
list_np_animal_crops = []
|
| 45 |
-
list_animal_bboxes = [] # detection rows [x1,y1,x2,y2,conf,label] matching each crop
|
| 46 |
|
| 47 |
# image to crop (scale as input for megadetector)
|
| 48 |
-
img_in = img_in.resize((yolo_results.ims[0].shape[1],
|
| 49 |
-
|
|
|
|
| 50 |
for det_array in yolo_results.xyxy:
|
|
|
|
| 51 |
# for every detection
|
| 52 |
for j in range(det_array.shape[0]):
|
|
|
|
| 53 |
# compute coords around bbox rounded to the nearest integer (for pasting later)
|
| 54 |
-
xmin_rd = int(math.floor(det_array[j,
|
| 55 |
-
ymin_rd = int(math.floor(det_array[j,
|
| 56 |
|
| 57 |
-
xmax_rd = int(math.ceil(det_array[j,
|
| 58 |
-
ymax_rd = int(math.ceil(det_array[j,
|
| 59 |
|
| 60 |
-
pred_llk = det_array[j,
|
| 61 |
-
pred_label = det_array[j,
|
| 62 |
# keep animal crops above threshold
|
| 63 |
-
if (pred_label == list_labels_as_str.index(
|
|
|
|
| 64 |
area = (xmin_rd, ymin_rd, xmax_rd, ymax_rd)
|
| 65 |
|
| 66 |
-
#
|
| 67 |
-
crop = img_in.crop(area)
|
| 68 |
crop_np = np.asarray(crop)
|
| 69 |
|
| 70 |
# add to list
|
| 71 |
list_np_animal_crops.append(crop_np)
|
| 72 |
-
list_animal_bboxes.append(det_array[j, :].tolist())
|
| 73 |
|
| 74 |
-
return list_np_animal_crops
|
|
|
|
|
|
|
| 1 |
|
| 2 |
+
from tkinter import W
|
| 3 |
+
import gradio as gr
|
| 4 |
+
from matplotlib import cm
|
| 5 |
import torch
|
| 6 |
+
import torchvision
|
| 7 |
+
import matplotlib
|
| 8 |
+
import PIL
|
| 9 |
+
from PIL import Image, ImageColor, ImageFont, ImageDraw
|
| 10 |
+
import numpy as np
|
| 11 |
+
import math
|
| 12 |
|
| 13 |
|
| 14 |
+
import yaml
|
| 15 |
+
import pdb
|
| 16 |
+
|
| 17 |
############################################
|
| 18 |
# Predict detections with MegaDetector v5a model
|
| 19 |
+
def predict_md(im,
|
| 20 |
+
megadetector_model, #Megadet_Models[mega_model_input]
|
| 21 |
+
size=640):
|
| 22 |
+
|
|
|
|
|
|
|
| 23 |
# resize image
|
| 24 |
+
g = (size / max(im.size)) # multipl factor to make max size of the image equal to input size
|
| 25 |
+
im = im.resize((int(x * g) for x in im.size),
|
| 26 |
+
PIL.Image.Resampling.LANCZOS) # resize
|
| 27 |
+
# device
|
| 28 |
+
if torch.cuda.is_available():
|
| 29 |
+
md_device = torch.device('cuda')
|
| 30 |
+
else:
|
| 31 |
+
md_device = torch.device('cpu')
|
| 32 |
+
|
| 33 |
+
# megadetector
|
| 34 |
+
MD_model = torch.hub.load('ultralytics/yolov5', # repo_or_dir
|
| 35 |
+
'custom', #model
|
| 36 |
+
megadetector_model, # args for callable model
|
| 37 |
+
force_reload=True,
|
| 38 |
+
device=md_device)
|
| 39 |
+
|
| 40 |
+
# send model to gpu if possible
|
| 41 |
+
if (md_device == torch.device('cuda')):
|
| 42 |
+
print('Sending model to GPU')
|
| 43 |
+
MD_model.to(md_device)
|
| 44 |
|
| 45 |
## detect objects
|
| 46 |
+
results = MD_model(im) # inference # vars(results).keys()= dict_keys(['imgs', 'pred', 'names', 'files', 'times', 'xyxy', 'xywh', 'xyxyn', 'xywhn', 'n', 't', 's'])
|
| 47 |
+
|
| 48 |
+
return results
|
|
|
|
| 49 |
|
| 50 |
|
| 51 |
##########################################
|
| 52 |
+
def crop_animal_detections(img_in,
|
| 53 |
+
yolo_results,
|
| 54 |
+
likelihood_th):
|
| 55 |
|
| 56 |
## Extract animal crops
|
| 57 |
+
list_labels_as_str = [i for i in yolo_results.names.values()] # ['animal', 'person', 'vehicle']
|
| 58 |
list_np_animal_crops = []
|
|
|
|
| 59 |
|
| 60 |
# image to crop (scale as input for megadetector)
|
| 61 |
+
img_in = img_in.resize((yolo_results.ims[0].shape[1],
|
| 62 |
+
yolo_results.ims[0].shape[0]))
|
| 63 |
+
# for every detection in the img
|
| 64 |
for det_array in yolo_results.xyxy:
|
| 65 |
+
|
| 66 |
# for every detection
|
| 67 |
for j in range(det_array.shape[0]):
|
| 68 |
+
|
| 69 |
# compute coords around bbox rounded to the nearest integer (for pasting later)
|
| 70 |
+
xmin_rd = int(math.floor(det_array[j,0])) # int() should suffice?
|
| 71 |
+
ymin_rd = int(math.floor(det_array[j,1]))
|
| 72 |
|
| 73 |
+
xmax_rd = int(math.ceil(det_array[j,2]))
|
| 74 |
+
ymax_rd = int(math.ceil(det_array[j,3]))
|
| 75 |
|
| 76 |
+
pred_llk = det_array[j,4]
|
| 77 |
+
pred_label = det_array[j,5]
|
| 78 |
# keep animal crops above threshold
|
| 79 |
+
if (pred_label == list_labels_as_str.index('animal')) and \
|
| 80 |
+
(pred_llk >= likelihood_th):
|
| 81 |
area = (xmin_rd, ymin_rd, xmax_rd, ymax_rd)
|
| 82 |
|
| 83 |
+
#pdb.set_trace()
|
| 84 |
+
crop = img_in.crop(area) #Image.fromarray(img_in).crop(area)
|
| 85 |
crop_np = np.asarray(crop)
|
| 86 |
|
| 87 |
# add to list
|
| 88 |
list_np_animal_crops.append(crop_np)
|
|
|
|
| 89 |
|
| 90 |
+
return list_np_animal_crops
|
dlc_utils.py
CHANGED
|
@@ -1,28 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import numpy as np
|
| 2 |
-
from dlclive import DLCLive
|
| 3 |
|
| 4 |
|
| 5 |
##########################################
|
| 6 |
-
def predict_dlc(list_np_crops,
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
# DLCLive always converts 3-channel frames BGR->RGB, but our crops are
|
| 13 |
-
# already RGB (PIL), so pass them as BGR for the model to see RGB
|
| 14 |
-
list_np_crops = [np.ascontiguousarray(crop[..., ::-1]) for crop in list_np_crops]
|
| 15 |
-
|
| 16 |
# run dlc thru list of crops
|
| 17 |
dlc_live = DLCLive(dlc_model_folder, processor=dlc_proc)
|
| 18 |
dlc_live.init_inference(list_np_crops[0])
|
| 19 |
|
| 20 |
list_kpts_per_crop = []
|
|
|
|
|
|
|
| 21 |
for crop in list_np_crops:
|
| 22 |
-
|
|
|
|
| 23 |
# set kpts below threhsold to nan
|
| 24 |
-
|
| 25 |
-
#
|
|
|
|
|
|
|
| 26 |
list_kpts_per_crop.append(keypts_xyp)
|
| 27 |
-
|
| 28 |
-
|
|
|
|
|
|
| 1 |
+
import deeplabcut
|
| 2 |
+
from tkinter import W
|
| 3 |
+
import gradio as gr
|
| 4 |
import numpy as np
|
| 5 |
+
from dlclive import DLCLive, Processor
|
| 6 |
|
| 7 |
|
| 8 |
##########################################
|
| 9 |
+
def predict_dlc(list_np_crops,
|
| 10 |
+
kpts_likelihood_th,
|
| 11 |
+
dlc_model_folder,
|
| 12 |
+
dlc_proc):
|
| 13 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
# run dlc thru list of crops
|
| 15 |
dlc_live = DLCLive(dlc_model_folder, processor=dlc_proc)
|
| 16 |
dlc_live.init_inference(list_np_crops[0])
|
| 17 |
|
| 18 |
list_kpts_per_crop = []
|
| 19 |
+
all_kypts = []
|
| 20 |
+
np_aux = np.empty((1,3)) # can I avoid hardcoding here?
|
| 21 |
for crop in list_np_crops:
|
| 22 |
+
# scale crop here?
|
| 23 |
+
keypts_xyp = dlc_live.get_pose(crop) # third column is llk!
|
| 24 |
# set kpts below threhsold to nan
|
| 25 |
+
|
| 26 |
+
#pdb.set_trace()
|
| 27 |
+
keypts_xyp[keypts_xyp[:,-1] < kpts_likelihood_th,:] = np_aux.fill(np.nan)
|
| 28 |
+
# add kpts of this crop to list
|
| 29 |
list_kpts_per_crop.append(keypts_xyp)
|
| 30 |
+
all_kypts.append(keypts_xyp)
|
| 31 |
+
|
| 32 |
+
return list_kpts_per_crop
|
pre-requirements.txt
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
pip>=26.2
|
|
|
|
|
|
pyproject.toml
DELETED
|
@@ -1,33 +0,0 @@
|
|
| 1 |
-
[project]
|
| 2 |
-
name = "deeplabcut-modelzoo-superanimals"
|
| 3 |
-
version = "0.1.0"
|
| 4 |
-
description = "Gradio Space for the DeepLabCut Model Zoo SuperAnimal models"
|
| 5 |
-
readme = "README.md"
|
| 6 |
-
requires-python = ">=3.12,<3.13"
|
| 7 |
-
classifiers = [
|
| 8 |
-
"Programming Language :: Python :: 3 :: Only",
|
| 9 |
-
"Programming Language :: Python :: 3.12",
|
| 10 |
-
]
|
| 11 |
-
# Hugging Face Spaces install from requirements.txt only: keep both lists in sync.
|
| 12 |
-
dependencies = [
|
| 13 |
-
"deeplabcut[modelzoo,tf]==3.0.2",
|
| 14 |
-
"deeplabcut-live",
|
| 15 |
-
"dlclibrary",
|
| 16 |
-
"gitpython>=3.1.30",
|
| 17 |
-
"gradio==6.29",
|
| 18 |
-
"humanfriendly",
|
| 19 |
-
"psutil",
|
| 20 |
-
"ruamel-yaml==0.17.21",
|
| 21 |
-
"seaborn",
|
| 22 |
-
"ultralytics",
|
| 23 |
-
]
|
| 24 |
-
|
| 25 |
-
[tool.ruff]
|
| 26 |
-
target-version = "py312"
|
| 27 |
-
line-length = 120
|
| 28 |
-
format.docstring-code-format = true
|
| 29 |
-
lint.select = [ "B", "E", "F", "I", "PIE", "UP" ]
|
| 30 |
-
lint.ignore = [ "B007", "E741" ]
|
| 31 |
-
|
| 32 |
-
[tool.pyproject-fmt]
|
| 33 |
-
max_supported_python = "3.12"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pytorch_utils.py
DELETED
|
@@ -1,118 +0,0 @@
|
|
| 1 |
-
import threading
|
| 2 |
-
from pathlib import Path
|
| 3 |
-
|
| 4 |
-
import numpy as np
|
| 5 |
-
import PIL
|
| 6 |
-
from deeplabcut.pose_estimation_pytorch.apis.utils import get_inference_runners
|
| 7 |
-
from deeplabcut.pose_estimation_pytorch.config.pose import PoseConfig
|
| 8 |
-
from deeplabcut.pose_estimation_pytorch.modelzoo.utils import MODEL_FILENAME_MAPPING
|
| 9 |
-
from dlclibrary import download_huggingface_model
|
| 10 |
-
|
| 11 |
-
# SuperAnimal (pose model, detector) used by the PyTorch backend
|
| 12 |
-
PYTORCH_MODELS = {
|
| 13 |
-
"superanimal_quadruped": ("hrnet_w32", "fasterrcnn_resnet50_fpn_v2"),
|
| 14 |
-
"superanimal_topviewmouse": ("hrnet_w32", "fasterrcnn_resnet50_fpn_v2"),
|
| 15 |
-
}
|
| 16 |
-
|
| 17 |
-
MAX_INDIVIDUALS = 10
|
| 18 |
-
MAX_IMAGE_SIZE = 1280 # longest side fed to the models (and drawn on)
|
| 19 |
-
# next to the TF models, not deeplabcut/modelzoo/checkpoints: site-packages is read-only for the Space's non-root user
|
| 20 |
-
WEIGHTS_DIR = Path(__file__).parent / "DLC_models" / "pytorch"
|
| 21 |
-
|
| 22 |
-
_runners = {}
|
| 23 |
-
_build_lock = threading.Lock()
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
##########################################
|
| 27 |
-
def snapshot_path(superanimal, model_name):
|
| 28 |
-
"""Path to a SuperAnimal snapshot in WEIGHTS_DIR, downloaded on first use (as deeplabcut does)."""
|
| 29 |
-
name = f"{superanimal}_{model_name}"
|
| 30 |
-
path = WEIGHTS_DIR / f"{name}.pt"
|
| 31 |
-
if not path.exists():
|
| 32 |
-
source = MODEL_FILENAME_MAPPING.get(name, path.name)
|
| 33 |
-
rename = None if source == path.name else {source: path.name}
|
| 34 |
-
download_huggingface_model(name, target_dir=str(WEIGHTS_DIR), rename_mapping=rename)
|
| 35 |
-
return path
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
##########################################
|
| 39 |
-
def load_superanimal(superanimal, device="auto"):
|
| 40 |
-
"""Build (once) the detector and pose runners for a SuperAnimal model; weights are downloaded on first use."""
|
| 41 |
-
with _build_lock:
|
| 42 |
-
if superanimal not in _runners:
|
| 43 |
-
pose_model, detector = PYTORCH_MODELS[superanimal]
|
| 44 |
-
cfg = PoseConfig.build_for_superanimal_inference(
|
| 45 |
-
superanimal,
|
| 46 |
-
model_name=pose_model,
|
| 47 |
-
detector_name=detector,
|
| 48 |
-
max_individuals=MAX_INDIVIDUALS,
|
| 49 |
-
device=device,
|
| 50 |
-
)
|
| 51 |
-
# keep low-score boxes: the UI threshold filters them afterwards
|
| 52 |
-
cfg["detector"]["model"]["box_score_thresh"] = 0.05
|
| 53 |
-
pose_runner, detector_runner = get_inference_runners(
|
| 54 |
-
cfg,
|
| 55 |
-
snapshot_path=snapshot_path(superanimal, pose_model),
|
| 56 |
-
detector_path=snapshot_path(superanimal, detector),
|
| 57 |
-
max_individuals=MAX_INDIVIDUALS,
|
| 58 |
-
inference_cfg={"multithreading": {"enabled": False}},
|
| 59 |
-
)
|
| 60 |
-
_runners[superanimal] = {
|
| 61 |
-
"pose": pose_runner,
|
| 62 |
-
"detector": detector_runner,
|
| 63 |
-
"bodyparts": list(cfg["metadata"]["bodyparts"]),
|
| 64 |
-
"lock": threading.Lock(),
|
| 65 |
-
} # runners are not thread-safe
|
| 66 |
-
return _runners[superanimal]
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
##########################################
|
| 70 |
-
def resize_max_side(img, max_size=MAX_IMAGE_SIZE):
|
| 71 |
-
scale = max_size / max(img.size)
|
| 72 |
-
if scale >= 1:
|
| 73 |
-
return img
|
| 74 |
-
return img.resize([int(x * scale) for x in img.size], PIL.Image.Resampling.LANCZOS)
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
##########################################
|
| 78 |
-
def predict_superanimal(img_input, superanimal, bbox_likelihood_th, kpts_likelihood_th, full_image=False):
|
| 79 |
-
"""Detect animals and estimate their pose with a PyTorch SuperAnimal model.
|
| 80 |
-
|
| 81 |
-
Returns the (resized) RGB image the predictions refer to, the list of animals
|
| 82 |
-
as dicts {'bbox': [x1,y1,x2,y2,conf], 'kpts': (num_keypoints, 3) array of x,y,llk
|
| 83 |
-
in image pixels, NaN below kpts_likelihood_th}, and the bodypart names.
|
| 84 |
-
"""
|
| 85 |
-
img = resize_max_side(img_input.convert("RGB"))
|
| 86 |
-
img_np = np.asarray(img)
|
| 87 |
-
runners = load_superanimal(superanimal)
|
| 88 |
-
|
| 89 |
-
with runners["lock"]:
|
| 90 |
-
if full_image:
|
| 91 |
-
# skip the detector and treat the whole image as one animal
|
| 92 |
-
h, w = img_np.shape[:2]
|
| 93 |
-
detections = {
|
| 94 |
-
"bboxes": np.array([[0, 0, w, h]], dtype=np.float32),
|
| 95 |
-
"bbox_scores": np.array([1.0], dtype=np.float32),
|
| 96 |
-
}
|
| 97 |
-
else:
|
| 98 |
-
detections = runners["detector"].inference([img_np])[0] # bboxes in xywh
|
| 99 |
-
keep = detections["bbox_scores"] >= bbox_likelihood_th
|
| 100 |
-
detections = {"bboxes": detections["bboxes"][keep], "bbox_scores": detections["bbox_scores"][keep]}
|
| 101 |
-
|
| 102 |
-
if len(detections["bboxes"]) == 0:
|
| 103 |
-
return img, [], runners["bodyparts"]
|
| 104 |
-
|
| 105 |
-
predictions = runners["pose"].inference([(img_np, detections)])[0]
|
| 106 |
-
|
| 107 |
-
animals = []
|
| 108 |
-
# outputs are padded to MAX_INDIVIDUALS with -1
|
| 109 |
-
for kpts, (x, y, w, h), score in zip(
|
| 110 |
-
predictions["bodyparts"], predictions["bboxes"], predictions["bbox_scores"], strict=True
|
| 111 |
-
):
|
| 112 |
-
if score < 0:
|
| 113 |
-
continue
|
| 114 |
-
kpts = kpts.astype(float)
|
| 115 |
-
kpts[kpts[:, 2] < kpts_likelihood_th, :] = np.nan
|
| 116 |
-
animals.append({"bbox": [float(x), float(y), float(x + w), float(y + h), float(score)], "kpts": kpts})
|
| 117 |
-
|
| 118 |
-
return img, animals, runners["bodyparts"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
|
@@ -1,13 +1,20 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
gradio
|
| 5 |
-
|
|
|
|
|
|
|
|
|
|
| 6 |
seaborn
|
| 7 |
-
deeplabcut[
|
| 8 |
deeplabcut-live
|
| 9 |
ruamel.yaml==0.17.21
|
| 10 |
dlclibrary
|
|
|
|
| 11 |
humanfriendly
|
|
|
|
|
|
|
|
|
|
| 12 |
psutil
|
| 13 |
-
ultralytics
|
|
|
|
| 1 |
+
ipython
|
| 2 |
+
transformers
|
| 3 |
+
Pillow
|
| 4 |
+
gradio
|
| 5 |
+
numpy
|
| 6 |
+
torch
|
| 7 |
+
torchvision
|
| 8 |
+
timm
|
| 9 |
seaborn
|
| 10 |
+
deeplabcut[tf,modelzoo]>=3.0.0rc13
|
| 11 |
deeplabcut-live
|
| 12 |
ruamel.yaml==0.17.21
|
| 13 |
dlclibrary
|
| 14 |
+
argparse
|
| 15 |
humanfriendly
|
| 16 |
+
pandas
|
| 17 |
+
statistics
|
| 18 |
+
tqdm
|
| 19 |
psutil
|
| 20 |
+
ultralytics
|
save_results.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import numpy as np
|
| 3 |
+
import pdb
|
| 4 |
+
|
| 5 |
+
dict_pred = {0: 'animal', 1: 'person', 2: 'vehicle'}
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def save_results(md_results, dlc_outputs,map_label_id_to_str,thr,output_file = 'dowload_predictions.json'):
|
| 9 |
+
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
write json
|
| 13 |
+
|
| 14 |
+
"""
|
| 15 |
+
info = {}
|
| 16 |
+
## info megaDetector
|
| 17 |
+
info['file']= md_results.files[0]
|
| 18 |
+
number_bb = len(md_results.xyxy[0].tolist())
|
| 19 |
+
info['number_of_bb'] = number_bb
|
| 20 |
+
number_bb_thr = len(dlc_outputs)
|
| 21 |
+
labels = [n for n in map_label_id_to_str.values()]
|
| 22 |
+
#pdb.set_trace()
|
| 23 |
+
new_index = []
|
| 24 |
+
for i in range(number_bb):
|
| 25 |
+
corner_x1,corner_y1,corner_x2,corner_y2,confidence, _ = md_results.xyxy[0].tolist()[i]
|
| 26 |
+
|
| 27 |
+
if confidence > thr:
|
| 28 |
+
new_index.append(i)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
for i in range(number_bb_thr):
|
| 32 |
+
aux={}
|
| 33 |
+
corner_x1,corner_y1,corner_x2,corner_y2,confidence, _ = md_results.xyxy[0].tolist()[new_index[i]]
|
| 34 |
+
aux['corner_1'] = (corner_x1,corner_y1)
|
| 35 |
+
aux['corner_2'] = (corner_x2,corner_y2)
|
| 36 |
+
aux['predict MD'] = md_results.names[0]
|
| 37 |
+
aux['confidence MD'] = confidence
|
| 38 |
+
|
| 39 |
+
## info dlc
|
| 40 |
+
kypts = []
|
| 41 |
+
for s in dlc_outputs[i]:
|
| 42 |
+
aux1 = []
|
| 43 |
+
for j in s:
|
| 44 |
+
aux1.append(float(j))
|
| 45 |
+
|
| 46 |
+
kypts.append(aux1)
|
| 47 |
+
aux['dlc_pred'] = dict(zip(labels,kypts))
|
| 48 |
+
info['bb_' + str(new_index[i]) ]=aux
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
with open(output_file, 'w') as f:
|
| 52 |
+
json.dump(info, f, indent=1)
|
| 53 |
+
print('Output file saved at {}'.format(output_file))
|
| 54 |
+
|
| 55 |
+
return output_file
|
| 56 |
+
|
ui_utils.py
CHANGED
|
@@ -1,228 +1,74 @@
|
|
| 1 |
import gradio as gr
|
| 2 |
-
from matplotlib import colormaps
|
| 3 |
-
from matplotlib.colors import to_hex
|
| 4 |
-
from PIL import Image
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
DLC_PURPLE = gr.themes.Color(
|
| 11 |
-
name="dlc_purple",
|
| 12 |
-
c50="#f5edff",
|
| 13 |
-
c100="#ead7ff",
|
| 14 |
-
c200="#d9b8ff",
|
| 15 |
-
c300="#c084fc",
|
| 16 |
-
c400="#ac72f0",
|
| 17 |
-
c500="#9b5de5",
|
| 18 |
-
c600="#8550c4",
|
| 19 |
-
c700="#73439a",
|
| 20 |
-
c800="#5c357c",
|
| 21 |
-
c900="#4b236f",
|
| 22 |
-
c950="#2e1546",
|
| 23 |
-
)
|
| 24 |
-
DLC_TEAL = gr.themes.Color(
|
| 25 |
-
name="dlc_teal",
|
| 26 |
-
c50="#e8f8f7",
|
| 27 |
-
c100="#c9efec",
|
| 28 |
-
c200="#9fe2dd",
|
| 29 |
-
c300="#7ad3ce",
|
| 30 |
-
c400="#57c4be",
|
| 31 |
-
c500="#2fb0a8",
|
| 32 |
-
c600="#21a197",
|
| 33 |
-
c700="#1a8078",
|
| 34 |
-
c800="#16655f",
|
| 35 |
-
c900="#124f4a",
|
| 36 |
-
c950="#0a2e2b",
|
| 37 |
-
)
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
def dlc_theme():
|
| 41 |
-
# white text on purple 500 is 4.1:1, so filled buttons use 700 (7.0:1) and 600 (5.3:1)
|
| 42 |
-
return gr.themes.Default(primary_hue=DLC_PURPLE, secondary_hue=DLC_TEAL, neutral_hue="slate").set(
|
| 43 |
-
button_primary_background_fill="*primary_700",
|
| 44 |
-
button_primary_background_fill_hover="*primary_600",
|
| 45 |
-
button_primary_background_fill_dark="*primary_600",
|
| 46 |
-
button_primary_background_fill_hover_dark="*primary_500",
|
| 47 |
-
button_primary_text_color="white",
|
| 48 |
-
button_primary_text_color_dark="white",
|
| 49 |
-
button_primary_border_color="*primary_700",
|
| 50 |
-
button_primary_border_color_dark="*primary_600",
|
| 51 |
-
)
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
def gradio_inputs_for_MD_DLC(backends_list, md_models_list, dlc_models_list):
|
| 55 |
# Input image
|
| 56 |
-
gr_image_input = gr.Image(type="pil", label="Input Image")
|
| 57 |
-
|
| 58 |
-
# Models
|
| 59 |
-
gr_backend_input = gr.Radio(
|
| 60 |
-
choices=backends_list,
|
| 61 |
-
value=backends_list[0],
|
| 62 |
-
label="Select backend",
|
| 63 |
-
)
|
| 64 |
-
|
| 65 |
-
gr_mega_model_input = gr.Dropdown(
|
| 66 |
-
choices=md_models_list,
|
| 67 |
-
value="md_v5a",
|
| 68 |
-
type="value",
|
| 69 |
-
label="Select Detector model (TensorFlow legacy only)",
|
| 70 |
-
visible=gr_backend_input.value != "PyTorch",
|
| 71 |
-
)
|
| 72 |
|
| 73 |
-
gr_dlc_model_input = gr.Dropdown(
|
| 74 |
-
choices=dlc_models_list,
|
| 75 |
-
value="superanimal_quadruped",
|
| 76 |
-
type="value",
|
| 77 |
-
label="Select DeepLabCut model",
|
| 78 |
-
)
|
| 79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
# Other inputs
|
| 81 |
-
gr_dlc_only_checkbox = gr.Checkbox(
|
| 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 |
-
value="viridis",
|
| 118 |
-
type="value",
|
| 119 |
-
label="Keypoint colormap",
|
| 120 |
-
)
|
| 121 |
-
|
| 122 |
-
gr_keypt_color = gr.ColorPicker(
|
| 123 |
-
value="#862db7",
|
| 124 |
-
label="Choose color for keypoint label",
|
| 125 |
-
)
|
| 126 |
-
|
| 127 |
-
gr_bbox_color = gr.ColorPicker(
|
| 128 |
-
value="#ff0000",
|
| 129 |
-
label="Choose color for bounding boxes",
|
| 130 |
-
)
|
| 131 |
-
|
| 132 |
-
gr_labels_font_style = gr.Dropdown(
|
| 133 |
-
choices=["amiko", "animals", "nature", "painter", "zen"],
|
| 134 |
-
value="amiko",
|
| 135 |
-
type="value",
|
| 136 |
-
label="Select keypoint label font",
|
| 137 |
-
)
|
| 138 |
-
|
| 139 |
-
gr_slider_font_size = gr.Slider(
|
| 140 |
-
minimum=5,
|
| 141 |
-
maximum=30,
|
| 142 |
-
value=18,
|
| 143 |
-
step=1,
|
| 144 |
-
label="Set font size",
|
| 145 |
-
)
|
| 146 |
-
|
| 147 |
-
gr_slider_marker_size = gr.Slider(
|
| 148 |
-
minimum=1,
|
| 149 |
-
maximum=20,
|
| 150 |
-
value=6,
|
| 151 |
-
step=1,
|
| 152 |
-
label="Set marker size",
|
| 153 |
-
)
|
| 154 |
-
|
| 155 |
-
return [
|
| 156 |
-
gr_image_input,
|
| 157 |
-
gr_backend_input,
|
| 158 |
-
gr_mega_model_input,
|
| 159 |
-
gr_dlc_model_input,
|
| 160 |
-
gr_dlc_only_checkbox,
|
| 161 |
-
gr_str_labels_checkbox,
|
| 162 |
-
gr_slider_conf_bboxes,
|
| 163 |
-
gr_slider_conf_keypoints,
|
| 164 |
-
gr_labels_font_style,
|
| 165 |
-
gr_slider_font_size,
|
| 166 |
-
gr_keypt_color,
|
| 167 |
-
gr_slider_marker_size,
|
| 168 |
-
gr_color_by_confidence_checkbox,
|
| 169 |
-
gr_colormap,
|
| 170 |
-
gr_bbox_color,
|
| 171 |
-
]
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
def confidence_legend_html(colormap="viridis"):
|
| 175 |
-
# the colormap as a CSS gradient, matching the keypoint fill in draw_keypoints_on_image
|
| 176 |
-
gradient = ", ".join(f"{to_hex(colormaps[colormap](i / 10))} {i * 10}%" for i in range(11))
|
| 177 |
-
# no leading newline: gradio prefixes "'" to cached example values starting with one (CSV injection guard)
|
| 178 |
-
return f"""<div style="display:flex; align-items:flex-start; gap:12px; flex-wrap:wrap; font-size:var(--text-sm);">
|
| 179 |
-
<span style="white-space:nowrap; line-height:12px;">Keypoint confidence</span>
|
| 180 |
-
<div style="flex:1; min-width:160px; max-width:360px;">
|
| 181 |
-
<div style="height:12px; border-radius:6px; background:linear-gradient(to right, {gradient});"></div>
|
| 182 |
-
<div style="display:flex; justify-content:space-between; margin-top:2px; font-variant-numeric:tabular-nums;">
|
| 183 |
-
<span>0</span><span>0.5</span><span>1</span>
|
| 184 |
-
</div>
|
| 185 |
-
</div>
|
| 186 |
-
</div>"""
|
| 187 |
-
|
| 188 |
-
|
| 189 |
def gradio_outputs_for_MD_DLC():
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
interactive=False,
|
| 199 |
-
)
|
| 200 |
-
return [gr_image_output, gr_confidence_legend, gr_file_download, gr_image_download, gr_confidence_table]
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
def example_sizes(path):
|
| 204 |
-
# font and marker sizes for the resolution the PyTorch backend draws on
|
| 205 |
-
side = min(max(Image.open(path).size), MAX_IMAGE_SIZE)
|
| 206 |
-
return round(side / 64), round(side / 200)
|
| 207 |
-
|
| 208 |
-
|
| 209 |
def gradio_description_and_examples():
|
| 210 |
-
title = "DeepLabCut Model Zoo
|
| 211 |
-
description =
|
| 212 |
-
|
| 213 |
-
"[DeepLabCut Model Zoo](http://www.mackenziemathislab.org/dlc-modelzoo) "
|
| 214 |
-
"([paper](https://arxiv.org/abs/2203.07436)). "
|
| 215 |
-
"Upload an image or pick an example below; to run on videos, see the Model Zoo page."
|
| 216 |
-
)
|
| 217 |
|
| 218 |
-
examples = [
|
| 219 |
-
[image, "PyTorch", "md_v5a", "superanimal_quadruped", False, True, 0.5, 0.4, "amiko"]
|
| 220 |
-
+ [font_size, "#ff0000", marker_size, False, "viridis", "#ff0000"]
|
| 221 |
-
for image in (
|
| 222 |
-
"examples/dog.jpeg",
|
| 223 |
-
"examples/cat.jpg",
|
| 224 |
-
)
|
| 225 |
-
for font_size, marker_size in [example_sizes(image)]
|
| 226 |
-
]
|
| 227 |
|
| 228 |
-
return [title,
|
|
|
|
| 1 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
+
##############################
|
| 4 |
+
def gradio_inputs_for_MD_DLC(md_models_list, # list(MD_models_dict.keys())
|
| 5 |
+
dlc_models_list, # list(DLC_models_dict.keys())
|
| 6 |
+
):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
# Input image
|
| 8 |
+
gr_image_input = gr.inputs.Image(type="pil", label="Input Image")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
+
# Models
|
| 12 |
+
gr_mega_model_input = gr.inputs.Dropdown(choices=md_models_list,
|
| 13 |
+
default='md_v5a', # default option
|
| 14 |
+
type='value', # Type of value to be returned by component. "value" returns the string of the choice selected, "index" returns the index of the choice selected.
|
| 15 |
+
label='Select Detector model')
|
| 16 |
+
gr_dlc_model_input = gr.inputs.Dropdown(choices=dlc_models_list, # choices
|
| 17 |
+
default='superanimal_quadruped_dlcrnet', # default option
|
| 18 |
+
type='value', # Type of value to be returned by component. "value" returns the string of the choice selected, "index" returns the index of the choice selected.
|
| 19 |
+
label='Select DeepLabCut model')
|
| 20 |
+
|
| 21 |
# Other inputs
|
| 22 |
+
gr_dlc_only_checkbox = gr.inputs.Checkbox(False,
|
| 23 |
+
label='Run DLClive only, directly on input image?')
|
| 24 |
+
gr_str_labels_checkbox = gr.inputs.Checkbox(True,
|
| 25 |
+
label='Show bodypart labels?')
|
| 26 |
+
|
| 27 |
+
gr_slider_conf_bboxes = gr.inputs.Slider(0,1,.05,0.2,
|
| 28 |
+
label='Set confidence threshold for animal detections')
|
| 29 |
+
gr_slider_conf_keypoints = gr.inputs.Slider(0,1,.05,0.4,
|
| 30 |
+
label='Set confidence threshold for keypoints')
|
| 31 |
+
|
| 32 |
+
# Data viz
|
| 33 |
+
gr_keypt_color = gr.ColorPicker(value ="#862db7", label="choose color for keypoint label")
|
| 34 |
+
|
| 35 |
+
gr_labels_font_style = gr.inputs.Dropdown(choices=['amiko', 'animals', 'nature', 'painter', 'zen'],
|
| 36 |
+
default='amiko',
|
| 37 |
+
type='value',
|
| 38 |
+
label='Select keypoint label font')
|
| 39 |
+
gr_slider_font_size = gr.inputs.Slider(5,30,1,8,
|
| 40 |
+
label='Set font size')
|
| 41 |
+
gr_slider_marker_size = gr.inputs.Slider(1,20,1,9,
|
| 42 |
+
label='Set marker size')
|
| 43 |
+
|
| 44 |
+
# list of inputs
|
| 45 |
+
return [gr_image_input,
|
| 46 |
+
gr_mega_model_input,
|
| 47 |
+
gr_dlc_model_input,
|
| 48 |
+
gr_dlc_only_checkbox,
|
| 49 |
+
gr_str_labels_checkbox,
|
| 50 |
+
gr_slider_conf_bboxes,
|
| 51 |
+
gr_slider_conf_keypoints,
|
| 52 |
+
gr_labels_font_style,
|
| 53 |
+
gr_slider_font_size,
|
| 54 |
+
gr_keypt_color,
|
| 55 |
+
gr_slider_marker_size]
|
| 56 |
+
|
| 57 |
+
####################################################
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
def gradio_outputs_for_MD_DLC():
|
| 59 |
+
# User interface: outputs
|
| 60 |
+
gr_image_output = gr.outputs.Image(type="pil", label="Output Image")
|
| 61 |
+
gr_file_download = gr.File(label="Download JSON file")
|
| 62 |
+
return [gr_image_output,
|
| 63 |
+
gr_file_download]
|
| 64 |
+
|
| 65 |
+
##############################################
|
| 66 |
+
# User interace: description
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
def gradio_description_and_examples():
|
| 68 |
+
title = "DeepLabCut Model Zoo SuperAnimals"
|
| 69 |
+
description = "Test the SuperAnimal models from the <a href='http://www.mackenziemathislab.org/dlc-modelzoo'>DeepLabCut ModelZoo Project</a>\, and read more on arXiv: https://arxiv.org/abs/2203.07436! Simply upload an image and see how it does. Want to run on videos on the cloud or locally? See the <a href='http://www.mackenziemathislab.org/dlc-modelzoo'>DeepLabCut ModelZoo</a>\."
|
| 70 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
+
examples = [['examples/dog.jpeg', 'md_v5a', 'superanimal_quadruped_dlcrnet', False, True, 0.5, 0.00, 'amiko',9, 'red', 3]]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
+
return [title,description,examples]
|
viz_utils.py
CHANGED
|
@@ -1,38 +1,31 @@
|
|
| 1 |
-
import json
|
| 2 |
-
import math
|
| 3 |
-
from datetime import date
|
| 4 |
-
|
| 5 |
import numpy as np
|
| 6 |
-
from matplotlib import colormaps
|
| 7 |
-
from PIL import ImageColor, ImageDraw, ImageFont
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
today = date.today()
|
| 10 |
-
FONTS = {
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
"zen": "fonts/ZEN.TTF",
|
| 16 |
-
}
|
| 17 |
-
# perceptually uniform maps first; turbo separates neighbouring bodyparts best
|
| 18 |
-
COLORMAPS = ["viridis", "plasma", "magma", "cividis", "turbo"]
|
| 19 |
-
|
| 20 |
|
| 21 |
#########################################
|
| 22 |
# Draw keypoints on image
|
| 23 |
-
def draw_keypoints_on_image(
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
color_by_confidence=True,
|
| 34 |
-
colormap="viridis",
|
| 35 |
-
):
|
| 36 |
"""Draws keypoints on an image.
|
| 37 |
Modified from:
|
| 38 |
https://www.programcreek.com/python/?code=fjchange%2Fobject_centric_VAD%2Fobject_centric_VAD-master%2Fobject_detection%2Futils%2Fvisualization_utils.py
|
|
@@ -46,232 +39,159 @@ def draw_keypoints_on_image(
|
|
| 46 |
use_normalized_coordinates: if True (default), treat keypoint values as
|
| 47 |
relative to the image. Otherwise treat them as absolute.
|
| 48 |
|
| 49 |
-
|
| 50 |
"""
|
| 51 |
# get a drawing context
|
| 52 |
-
draw = ImageDraw.Draw(image,
|
| 53 |
|
| 54 |
im_width, im_height = image.size
|
| 55 |
keypoints_x = [k[0] for k in keypoints]
|
| 56 |
keypoints_y = [k[1] for k in keypoints]
|
| 57 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
# adjust keypoints coords if required
|
| 60 |
if use_normalized_coordinates:
|
| 61 |
keypoints_x = tuple([im_width * x for x in keypoints_x])
|
| 62 |
keypoints_y = tuple([im_height * y for y in keypoints_y])
|
| 63 |
-
|
| 64 |
-
cmap =
|
|
|
|
| 65 |
# draw ellipses around keypoints
|
| 66 |
-
for i, (keypoint_x, keypoint_y) in enumerate(zip(keypoints_x, keypoints_y
|
|
|
|
| 67 |
# handling potential nans in the keypoints
|
| 68 |
if np.isnan(keypoint_x).any():
|
| 69 |
continue
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
round_fill[3] = round(confidence * 255)
|
| 79 |
-
round_fill = tuple(round_fill)
|
| 80 |
-
draw.ellipse(
|
| 81 |
-
[
|
| 82 |
-
(keypoint_x - marker_size, keypoint_y - marker_size),
|
| 83 |
-
(keypoint_x + marker_size, keypoint_y + marker_size),
|
| 84 |
-
],
|
| 85 |
-
fill=tuple(round_fill),
|
| 86 |
-
outline="black",
|
| 87 |
-
width=1,
|
| 88 |
-
) # fill and outline: [0,255]
|
| 89 |
|
| 90 |
# add string labels around keypoints
|
| 91 |
if flag_show_str_labels:
|
| 92 |
-
font = ImageFont.truetype(FONTS[font_style],
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
)
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
#########################################
|
| 102 |
-
# Bodypart names for display
|
| 103 |
-
# display names where the SuperAnimal definitions misspell (quadruped "thai") or read oddly
|
| 104 |
-
# (top-view mouse "backend"); the JSON output keeps the model's names
|
| 105 |
-
BODYPART_DISPLAY_NAMES = {
|
| 106 |
-
"front_left_thai": "front left thigh",
|
| 107 |
-
"front_right_thai": "front right thigh",
|
| 108 |
-
"back_left_thai": "back left thigh",
|
| 109 |
-
"back_right_thai": "back right thigh",
|
| 110 |
-
"mid_backend": "mid back end",
|
| 111 |
-
"mid_backend2": "mid back end 2",
|
| 112 |
-
"mid_backend3": "mid back end 3",
|
| 113 |
-
}
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
def display_bodypart(name):
|
| 117 |
-
return BODYPART_DISPLAY_NAMES.get(name, name.replace("_", " "))
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
#########################################
|
| 121 |
-
# Keypoint confidences as table rows
|
| 122 |
-
def keypoint_confidence_rows(kpts_per_animal, map_label_id_to_str):
|
| 123 |
-
"""(animal, bodypart, confidence) for every keypoint kept (not NaN), lowest confidence first."""
|
| 124 |
-
rows = []
|
| 125 |
-
for i_animal, kpts in enumerate(kpts_per_animal):
|
| 126 |
-
for i_kpt, kpt in enumerate(kpts):
|
| 127 |
-
if not np.isnan(kpt[2]):
|
| 128 |
-
rows.append([i_animal, display_bodypart(map_label_id_to_str[i_kpt]), round(float(kpt[2]), 3)])
|
| 129 |
-
return sorted(rows, key=lambda row: row[2])
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
#########################################
|
| 133 |
-
# Save the annotated image for download
|
| 134 |
-
def save_annotated_image(image, path_to_output_file="download_annotated.png"):
|
| 135 |
-
image.save(path_to_output_file)
|
| 136 |
-
return path_to_output_file
|
| 137 |
-
|
| 138 |
|
| 139 |
#########################################
|
| 140 |
# Draw bboxes on image
|
| 141 |
-
def draw_bbox_w_text(img,
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
|
| 164 |
###########################################
|
| 165 |
-
|
| 166 |
-
COORDINATES = "pixels in the input image (after EXIF orientation), origin top-left, x right, y down"
|
| 167 |
-
|
| 168 |
|
| 169 |
-
|
| 170 |
-
|
| 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
|
| 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,
|
| 198 |
-
animal_bboxes,
|
| 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 |
-
|
| 211 |
-
|
| 212 |
-
info =
|
| 213 |
-
info[
|
| 214 |
-
|
| 215 |
-
info[
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 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 |
-
|
| 243 |
-
|
| 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 |
-
|
| 255 |
-
|
| 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 |
-
|
| 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 |
-
###########################################
|
|
|
|
| 1 |
+
import json
|
|
|
|
|
|
|
|
|
|
| 2 |
import numpy as np
|
|
|
|
|
|
|
| 3 |
|
| 4 |
+
from matplotlib import cm
|
| 5 |
+
import matplotlib
|
| 6 |
+
from PIL import Image, ImageColor, ImageFont, ImageDraw
|
| 7 |
+
import numpy as np
|
| 8 |
+
import pdb
|
| 9 |
+
from datetime import date
|
| 10 |
today = date.today()
|
| 11 |
+
FONTS = {'amiko': "fonts/Amiko-Regular.ttf",
|
| 12 |
+
'nature': "fonts/LoveNature.otf",
|
| 13 |
+
'painter':"fonts/PainterDecorator.otf",
|
| 14 |
+
'animals': "fonts/UncialAnimals.ttf",
|
| 15 |
+
'zen': "fonts/ZEN.TTF"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
#########################################
|
| 18 |
# Draw keypoints on image
|
| 19 |
+
def draw_keypoints_on_image(image,
|
| 20 |
+
keypoints,
|
| 21 |
+
map_label_id_to_str,
|
| 22 |
+
flag_show_str_labels,
|
| 23 |
+
use_normalized_coordinates=True,
|
| 24 |
+
font_style='amiko',
|
| 25 |
+
font_size=8,
|
| 26 |
+
keypt_color="#ff0000",
|
| 27 |
+
marker_size=2,
|
| 28 |
+
):
|
|
|
|
|
|
|
|
|
|
| 29 |
"""Draws keypoints on an image.
|
| 30 |
Modified from:
|
| 31 |
https://www.programcreek.com/python/?code=fjchange%2Fobject_centric_VAD%2Fobject_centric_VAD-master%2Fobject_detection%2Futils%2Fvisualization_utils.py
|
|
|
|
| 39 |
use_normalized_coordinates: if True (default), treat keypoint values as
|
| 40 |
relative to the image. Otherwise treat them as absolute.
|
| 41 |
|
| 42 |
+
|
| 43 |
"""
|
| 44 |
# get a drawing context
|
| 45 |
+
draw = ImageDraw.Draw(image,"RGBA")
|
| 46 |
|
| 47 |
im_width, im_height = image.size
|
| 48 |
keypoints_x = [k[0] for k in keypoints]
|
| 49 |
keypoints_y = [k[1] for k in keypoints]
|
| 50 |
+
alpha = [k[2] for k in keypoints]
|
| 51 |
+
norm = matplotlib.colors.Normalize(vmin=0, vmax=255)
|
| 52 |
+
|
| 53 |
+
# debugging keypoints
|
| 54 |
+
print (keypoints)
|
| 55 |
+
|
| 56 |
+
names_for_color = [i for i in map_label_id_to_str.keys()]
|
| 57 |
+
colores = np.linspace(0, 255, num=len(names_for_color),dtype= int)
|
| 58 |
|
| 59 |
# adjust keypoints coords if required
|
| 60 |
if use_normalized_coordinates:
|
| 61 |
keypoints_x = tuple([im_width * x for x in keypoints_x])
|
| 62 |
keypoints_y = tuple([im_height * y for y in keypoints_y])
|
| 63 |
+
|
| 64 |
+
#cmap = matplotlib.cm.get_cmap('hsv')
|
| 65 |
+
cmap2 = matplotlib.cm.get_cmap('Greys')
|
| 66 |
# draw ellipses around keypoints
|
| 67 |
+
for i, (keypoint_x, keypoint_y) in enumerate(zip(keypoints_x, keypoints_y)):
|
| 68 |
+
round_fill = list(cm.viridis(norm(colores[i]),bytes=True))#[round(num*255) for num in list(cmap(i))[:3]] #check!
|
| 69 |
# handling potential nans in the keypoints
|
| 70 |
if np.isnan(keypoint_x).any():
|
| 71 |
continue
|
| 72 |
+
|
| 73 |
+
if np.isnan(alpha[i]) == False :
|
| 74 |
+
round_fill[3] = round(alpha[i] *255)
|
| 75 |
+
#print(round_fill)
|
| 76 |
+
#round_outline = [round(num*255) for num in list(cmap2(alpha[i]))[:3]]
|
| 77 |
+
draw.ellipse([(keypoint_x - marker_size, keypoint_y - marker_size),
|
| 78 |
+
(keypoint_x + marker_size, keypoint_y + marker_size)],
|
| 79 |
+
fill=tuple(round_fill), outline= 'black', width=1) #fill and outline: [0,255]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
# add string labels around keypoints
|
| 82 |
if flag_show_str_labels:
|
| 83 |
+
font = ImageFont.truetype(FONTS[font_style],
|
| 84 |
+
font_size)
|
| 85 |
+
draw.text((keypoint_x + marker_size, keypoint_y + marker_size),#(0.5*im_width, 0.5*im_height), #-------
|
| 86 |
+
map_label_id_to_str[i],
|
| 87 |
+
ImageColor.getcolor(keypt_color, "RGB"), # rgb #
|
| 88 |
+
font=font)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
#########################################
|
| 91 |
# Draw bboxes on image
|
| 92 |
+
def draw_bbox_w_text(img,
|
| 93 |
+
results,
|
| 94 |
+
font_style='amiko',
|
| 95 |
+
font_size=8): #TODO: select color too?
|
| 96 |
+
#pdb.set_trace()
|
| 97 |
+
bbxyxy = results
|
| 98 |
+
w, h = bbxyxy[2], bbxyxy[3]
|
| 99 |
+
shape = [(bbxyxy[0], bbxyxy[1]), (w , h)]
|
| 100 |
+
imgR = ImageDraw.Draw(img)
|
| 101 |
+
imgR.rectangle(shape, outline ="red",width=5) ##bb for animal
|
| 102 |
+
|
| 103 |
+
confidence = bbxyxy[4]
|
| 104 |
+
string_bb = 'animal ' + str(round(confidence, 2))
|
| 105 |
+
font = ImageFont.truetype(FONTS[font_style], font_size)
|
| 106 |
+
|
| 107 |
+
text_size = font.getbbox(string_bb) # (h,w)
|
| 108 |
+
position = (bbxyxy[0],bbxyxy[1] - text_size[1] -2 )
|
| 109 |
+
left, top, right, bottom = imgR.textbbox(position, string_bb, font=font)
|
| 110 |
+
imgR.rectangle((left, top-5, right+5, bottom+5), fill="red")
|
| 111 |
+
imgR.text((bbxyxy[0] + 3 ,bbxyxy[1] - text_size[1] -2 ), string_bb, font=font, fill="black")
|
| 112 |
+
|
| 113 |
+
return imgR
|
| 114 |
|
| 115 |
###########################################
|
| 116 |
+
def save_results_as_json(md_results, dlc_outputs, map_dlc_label_id_to_str, thr,model,mega_model_input, path_to_output_file = 'download_predictions.json'):
|
|
|
|
|
|
|
| 117 |
|
| 118 |
+
"""
|
| 119 |
+
Output detections as json file
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
+
"""
|
| 122 |
+
# initialise dict to save to json
|
| 123 |
+
info = {}
|
| 124 |
+
info['date'] = str(today)
|
| 125 |
+
info['MD_model'] = str(mega_model_input)
|
| 126 |
+
# info from megaDetector
|
| 127 |
+
info['file']= md_results.files[0]
|
| 128 |
+
number_bb = len(md_results.xyxy[0].tolist())
|
| 129 |
+
info['number_of_bb'] = number_bb
|
| 130 |
+
# info from DLC
|
| 131 |
+
number_bb_thr = len(dlc_outputs)
|
| 132 |
+
labels = [n for n in map_dlc_label_id_to_str.values()]
|
| 133 |
+
|
| 134 |
+
# create list of bboxes above th
|
| 135 |
+
new_index = []
|
| 136 |
+
for i in range(number_bb):
|
| 137 |
+
corner_x1,corner_y1,corner_x2,corner_y2,confidence, _ = md_results.xyxy[0].tolist()[i]
|
| 138 |
+
|
| 139 |
+
if confidence > thr:
|
| 140 |
+
new_index.append(i)
|
| 141 |
+
|
| 142 |
+
# define aux dict for every bounding box above threshold
|
| 143 |
+
for i in range(number_bb_thr):
|
| 144 |
+
aux={}
|
| 145 |
+
# MD output
|
| 146 |
+
corner_x1,corner_y1,corner_x2,corner_y2,confidence, _ = md_results.xyxy[0].tolist()[new_index[i]]
|
| 147 |
+
aux['corner_1'] = (corner_x1,corner_y1)
|
| 148 |
+
aux['corner_2'] = (corner_x2,corner_y2)
|
| 149 |
+
aux['predict MD'] = md_results.names[0]
|
| 150 |
+
aux['confidence MD'] = confidence
|
| 151 |
+
|
| 152 |
+
# DLC output
|
| 153 |
+
info['dlc_model'] = model
|
| 154 |
+
kypts = []
|
| 155 |
+
for s in dlc_outputs[i]:
|
| 156 |
+
aux1 = []
|
| 157 |
+
for j in s:
|
| 158 |
+
aux1.append(float(j))
|
| 159 |
+
|
| 160 |
+
kypts.append(aux1)
|
| 161 |
+
aux['dlc_pred'] = dict(zip(labels,kypts))
|
| 162 |
+
info['bb_' + str(new_index[i]) ]=aux
|
| 163 |
+
|
| 164 |
+
# save dict as json
|
| 165 |
+
with open(path_to_output_file, 'w') as f:
|
| 166 |
+
json.dump(info, f, indent=1)
|
| 167 |
+
print('Output file saved at {}'.format(path_to_output_file))
|
| 168 |
|
|
|
|
|
|
|
|
|
|
| 169 |
return path_to_output_file
|
| 170 |
|
| 171 |
|
| 172 |
+
def save_results_only_dlc(dlc_outputs,map_label_id_to_str,model,output_file = 'dowload_predictions_dlc.json'):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
"""
|
| 175 |
+
write json dlc output
|
| 176 |
+
"""
|
| 177 |
+
info = {}
|
| 178 |
+
info['date'] = str(today)
|
| 179 |
+
labels = [n for n in map_label_id_to_str.values()]
|
| 180 |
+
info['dlc_model'] = model
|
| 181 |
+
kypts = []
|
| 182 |
+
for s in dlc_outputs:
|
| 183 |
+
aux1 = []
|
| 184 |
+
for j in s:
|
| 185 |
+
aux1.append(float(j))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
|
| 187 |
+
kypts.append(aux1)
|
| 188 |
+
info['dlc_pred'] = dict(zip(labels,kypts))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
|
| 190 |
+
with open(output_file, 'w') as f:
|
| 191 |
+
json.dump(info, f, indent=1)
|
| 192 |
+
print('Output file saved at {}'.format(output_file))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
+
return output_file
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
|
| 197 |
+
###########################################
|