import threading import io import json import os import shutil import subprocess import sys from collections import Counter from pathlib import Path import spaces import gradio as gr import torch from PIL import Image, ImageDraw from transformers import RTDetrForObjectDetection, RTDetrImageProcessor # Hugging Face Spaces can mount persistent storage at /data. # DATA_DIR can be overridden in Space Settings -> Variables. if os.getenv("DATA_DIR"): BASE = Path(os.environ["DATA_DIR"]) elif Path("/data").exists() and os.access("/data", os.W_OK): BASE = Path("/data") / "icecream_counter" else: BASE = Path("./data") ROOT = Path(__file__).resolve().parent # Self-heal the training script if a deployment omitted the training/ directory. _TRAINING_SCRIPT_B64 = "aW1wb3J0IGFyZ3BhcnNlCmltcG9ydCBqc29uCmZyb20gcGF0aGxpYiBpbXBvcnQgUGF0aAoKaW1wb3J0IHRvcmNoCmZyb20gUElMIGltcG9ydCBJbWFnZQpmcm9tIHRvcmNoLnV0aWxzLmRhdGEgaW1wb3J0IERhdGFzZXQsIERhdGFMb2FkZXIKZnJvbSB0cWRtIGltcG9ydCB0cWRtCmZyb20gdHJhbnNmb3JtZXJzIGltcG9ydCBSVERldHJJbWFnZVByb2Nlc3NvciwgUlREZXRyRm9yT2JqZWN0RGV0ZWN0aW9uCgpCQVNFX01PREVMID0gIlBla2luZ1UvcnRkZXRyX3I1MHZkIgoKZGVmIGxvYWRfY2xhc3NlcyhwYXRoKToKICAgIHJldHVybiBbeC5zdHJpcCgpIGZvciB4IGluIFBhdGgocGF0aCkucmVhZF90ZXh0KCkuc3BsaXRsaW5lcygpIGlmIHguc3RyaXAoKV0KCmNsYXNzIENPQ09EZXRlY3Rpb25EYXRhc2V0KERhdGFzZXQpOgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGltYWdlX2RpciwgYW5ub3RhdGlvbl9maWxlLCBwcm9jZXNzb3IpOgogICAgICAgIHNlbGYuaW1hZ2VfZGlyID0gUGF0aChpbWFnZV9kaXIpCiAgICAgICAgc2VsZi5wcm9jZXNzb3IgPSBwcm9jZXNzb3IKICAgICAgICBjb2NvID0ganNvbi5sb2FkcyhQYXRoKGFubm90YXRpb25fZmlsZSkucmVhZF90ZXh0KCkpCiAgICAgICAgc2VsZi5pbWFnZXMgPSB7eFsiaWQiXTogeCBmb3IgeCBpbiBjb2NvWyJpbWFnZXMiXX0KICAgICAgICBjYXRzID0gc29ydGVkKGNvY29bImNhdGVnb3JpZXMiXSwga2V5PWxhbWJkYSB4OiB4WyJpZCJdKQogICAgICAgIHNlbGYuY2F0ZWdvcnlfaWRfdG9fbGFiZWwgPSB7Y1siaWQiXTogaSBmb3IgaSxjIGluIGVudW1lcmF0ZShjYXRzKX0KICAgICAgICBhbm5zID0ge30KICAgICAgICBmb3IgYSBpbiBjb2NvWyJhbm5vdGF0aW9ucyJdOgogICAgICAgICAgICBpZiBub3QgYS5nZXQoImlzY3Jvd2QiLCAwKToKICAgICAgICAgICAgICAgIGFubnMuc2V0ZGVmYXVsdChhWyJpbWFnZV9pZCJdLCBbXSkuYXBwZW5kKGEpCiAgICAgICAgc2VsZi5yZWNvcmRzID0gW10KICAgICAgICBmb3IgaW1hZ2VfaWQsIGluZm8gaW4gc2VsZi5pbWFnZXMuaXRlbXMoKToKICAgICAgICAgICAgc2VsZi5yZWNvcmRzLmFwcGVuZCh7CiAgICAgICAgICAgICAgICAiaW1hZ2VfaWQiOiBpbWFnZV9pZCwgImZpbGVfbmFtZSI6IGluZm9bImZpbGVfbmFtZSJdLAogICAgICAgICAgICAgICAgIndpZHRoIjogaW5mb1sid2lkdGgiXSwgImhlaWdodCI6IGluZm9bImhlaWdodCJdLAogICAgICAgICAgICAgICAgImFubm90YXRpb25zIjogYW5ucy5nZXQoaW1hZ2VfaWQsIFtdKQogICAgICAgICAgICB9KQoKICAgIGRlZiBfX2xlbl9fKHNlbGYpOiByZXR1cm4gbGVuKHNlbGYucmVjb3JkcykKCiAgICBkZWYgX19nZXRpdGVtX18oc2VsZiwgaWR4KToKICAgICAgICByID0gc2VsZi5yZWNvcmRzW2lkeF0KICAgICAgICBpbWFnZSA9IEltYWdlLm9wZW4oc2VsZi5pbWFnZV9kaXIgLyByWyJmaWxlX25hbWUiXSkuY29udmVydCgiUkdCIikKICAgICAgICBhbm5zID0gW10KICAgICAgICBmb3IgYSBpbiByWyJhbm5vdGF0aW9ucyJdOgogICAgICAgICAgICB4LHksdyxoID0gYVsiYmJveCJdCiAgICAgICAgICAgIGlmIHcgPD0gMCBvciBoIDw9IDA6IGNvbnRpbnVlCiAgICAgICAgICAgIGFubnMuYXBwZW5kKHsKICAgICAgICAgICAgICAgICJpZCI6IGFbImlkIl0sICJpbWFnZV9pZCI6IGludChpZHgpLAogICAgICAgICAgICAgICAgImNhdGVnb3J5X2lkIjogc2VsZi5jYXRlZ29yeV9pZF90b19sYWJlbFthWyJjYXRlZ29yeV9pZCJdXSwKICAgICAgICAgICAgICAgICJiYm94IjogW3gseSx3LGhdLCAiYXJlYSI6IGZsb2F0KGEuZ2V0KCJhcmVhIix3KmgpKSwKICAgICAgICAgICAgICAgICJpc2Nyb3dkIjogMAogICAgICAgICAgICB9KQogICAgICAgIGVuY29kZWQgPSBzZWxmLnByb2Nlc3NvcigKICAgICAgICAgICAgaW1hZ2VzPWltYWdlLAogICAgICAgICAgICBhbm5vdGF0aW9ucz17ImltYWdlX2lkIjogaW50KGlkeCksICJhbm5vdGF0aW9ucyI6IGFubnN9LAogICAgICAgICAgICByZXR1cm5fdGVuc29ycz0icHQiCiAgICAgICAgKQogICAgICAgIGVuY29kZWRbInBpeGVsX3ZhbHVlcyJdID0gZW5jb2RlZFsicGl4ZWxfdmFsdWVzIl0uc3F1ZWV6ZSgwKQogICAgICAgIGlmICJwaXhlbF9tYXNrIiBpbiBlbmNvZGVkOgogICAgICAgICAgICBlbmNvZGVkWyJwaXhlbF9tYXNrIl0gPSBlbmNvZGVkWyJwaXhlbF9tYXNrIl0uc3F1ZWV6ZSgwKQogICAgICAgIGVuY29kZWRbImxhYmVscyJdID0gZW5jb2RlZFsibGFiZWxzIl1bMF0KICAgICAgICByZXR1cm4gZW5jb2RlZAoKZGVmIGNvbGxhdGVfZm4oYmF0Y2gpOgogICAgb3V0ID0geyJwaXhlbF92YWx1ZXMiOiB0b3JjaC5zdGFjayhbeFsicGl4ZWxfdmFsdWVzIl0gZm9yIHggaW4gYmF0Y2hdKSwKICAgICAgICAgICAibGFiZWxzIjogW3hbImxhYmVscyJdIGZvciB4IGluIGJhdGNoXX0KICAgIGlmICJwaXhlbF9tYXNrIiBpbiBiYXRjaFswXToKICAgICAgICBvdXRbInBpeGVsX21hc2siXSA9IHRvcmNoLnN0YWNrKFt4WyJwaXhlbF9tYXNrIl0gZm9yIHggaW4gYmF0Y2hdKQogICAgcmV0dXJuIG91dAoKZGVmIG1vdmVfdG9fZGV2aWNlKG9iaiwgZGV2aWNlKToKICAgIGlmIHRvcmNoLmlzX3RlbnNvcihvYmopOgogICAgICAgIHJldHVybiBvYmoudG8oZGV2aWNlKQogICAgaWYgaXNpbnN0YW5jZShvYmosIGRpY3QpOgogICAgICAgIHJldHVybiB7azogbW92ZV90b19kZXZpY2UodiwgZGV2aWNlKSBmb3IgaywgdiBpbiBvYmouaXRlbXMoKX0KICAgIGlmIGlzaW5zdGFuY2Uob2JqLCBsaXN0KToKICAgICAgICByZXR1cm4gW21vdmVfdG9fZGV2aWNlKHYsIGRldmljZSkgZm9yIHYgaW4gb2JqXQogICAgaWYgaXNpbnN0YW5jZShvYmosIHR1cGxlKToKICAgICAgICByZXR1cm4gdHVwbGUobW92ZV90b19kZXZpY2UodiwgZGV2aWNlKSBmb3IgdiBpbiBvYmopCiAgICByZXR1cm4gb2JqCgpkZWYgZXZhbHVhdGUobW9kZWwsIGxvYWRlciwgZGV2aWNlKToKICAgIG1vZGVsLmV2YWwoKTsgdG90YWw9MDsgbj0wCiAgICB3aXRoIHRvcmNoLm5vX2dyYWQoKToKICAgICAgICBmb3IgYmF0Y2ggaW4gbG9hZGVyOgogICAgICAgICAgICBiYXRjaD1tb3ZlX3RvX2RldmljZShiYXRjaCwgZGV2aWNlKQogICAgICAgICAgICB0b3RhbCArPSBmbG9hdChtb2RlbCgqKmJhdGNoKS5sb3NzLml0ZW0oKSk7IG4gKz0gMQogICAgbW9kZWwudHJhaW4oKQogICAgcmV0dXJuIHRvdGFsL21heChuLDEpCgoKZGVmIHBhdGNoX3J0ZGV0cl9ib3hfaW91X2RldmljZSgpOgogICAgIiIiTWFrZSBSVC1ERVRSIGJveCBJb1UgaGVscGVycyBkZXZpY2Utc2FmZSBhY3Jvc3MgVHJhbnNmb3JtZXJzIHJlbGVhc2VzLgoKICAgIFNvbWUgVHJhbnNmb3JtZXJzIFJULURFVFIgbG9zcyBpbXBsZW1lbnRhdGlvbnMgY2FuIGNvbnN0cnVjdCB0YXJnZXQgYm94ZXMKICAgIG9uIENQVSBldmVuIHdoZW4gcHJlZGljdGlvbnMgYXJlIG9uIENVREEuIFRoZSBmYWlsdXJlIHRoZW4gb2NjdXJzIGluc2lkZQogICAgbG9zc19sYWJlbHNfdmZsIC0+IGJveF9pb3UuIEFsaWduIGJvdGggb3BlcmFuZHMgdG8gdGhlIHByZWRpY3Rpb24gZGV2aWNlCiAgICBpbW1lZGlhdGVseSBiZWZvcmUgdGhlIElvVSBjYWxjdWxhdGlvbi4KICAgICIiIgogICAgdHJ5OgogICAgICAgIGltcG9ydCBpbXBvcnRsaWIKCiAgICAgICAgbG9zc19mb3JfYm94ID0gaW1wb3J0bGliLmltcG9ydF9tb2R1bGUoCiAgICAgICAgICAgICJ0cmFuc2Zvcm1lcnMubG9zcy5sb3NzX2Zvcl9vYmplY3RfZGV0ZWN0aW9uIgogICAgICAgICkKICAgICAgICBtb2R1bGVzID0gW2xvc3NfZm9yX2JveF0KCiAgICAgICAgdHJ5OgogICAgICAgICAgICBsb3NzX3J0ID0gaW1wb3J0bGliLmltcG9ydF9tb2R1bGUoInRyYW5zZm9ybWVycy5sb3NzLmxvc3NfcnRfZGV0ciIpCiAgICAgICAgICAgIG1vZHVsZXMuYXBwZW5kKGxvc3NfcnQpCiAgICAgICAgZXhjZXB0IEltcG9ydEVycm9yOgogICAgICAgICAgICBwYXNzCgogICAgICAgIG9yaWdpbmFsID0gZ2V0YXR0cihsb3NzX2Zvcl9ib3gsICJib3hfaW91IiwgTm9uZSkKICAgICAgICBpZiBvcmlnaW5hbCBpcyBOb25lOgogICAgICAgICAgICByYWlzZSBSdW50aW1lRXJyb3IoInRyYW5zZm9ybWVycy5sb3NzLmxvc3NfZm9yX29iamVjdF9kZXRlY3Rpb24uYm94X2lvdSBub3QgZm91bmQiKQoKICAgICAgICBpZiBnZXRhdHRyKG9yaWdpbmFsLCAiX2ljZWNyZWFtX2RldmljZV9wYXRjaCIsIEZhbHNlKToKICAgICAgICAgICAgcHJpbnQoIlJULURFVFIgYm94X2lvdSBkZXZpY2UgcGF0Y2ggYWxyZWFkeSBpbnN0YWxsZWQiKQogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgZGVmIGRldmljZV9zYWZlX2JveF9pb3UoYm94ZXMxLCBib3hlczIpOgogICAgICAgICAgICBpZiB0b3JjaC5pc190ZW5zb3IoYm94ZXMxKSBhbmQgdG9yY2guaXNfdGVuc29yKGJveGVzMik6CiAgICAgICAgICAgICAgICBpZiBib3hlczEuZGV2aWNlICE9IGJveGVzMi5kZXZpY2U6CiAgICAgICAgICAgICAgICAgICAgYm94ZXMyID0gYm94ZXMyLnRvKGJveGVzMS5kZXZpY2UpCiAgICAgICAgICAgIHJldHVybiBvcmlnaW5hbChib3hlczEsIGJveGVzMikKCiAgICAgICAgZGV2aWNlX3NhZmVfYm94X2lvdS5faWNlY3JlYW1fZGV2aWNlX3BhdGNoID0gVHJ1ZQoKICAgICAgICAjIGxvc3NfcnRfZGV0ciBpbXBvcnRzIGJveF9pb3UgaW50byBpdHMgb3duIG1vZHVsZSBuYW1lc3BhY2UgaW4gdGhlCiAgICAgICAgIyBUcmFuc2Zvcm1lcnMgdmVyc2lvbnMgdGhhdCBwcm9kdWNlZCB0aGlzIHRyYWNlYmFjaywgc28gcGF0Y2ggYm90aC4KICAgICAgICBsb3NzX2Zvcl9ib3guYm94X2lvdSA9IGRldmljZV9zYWZlX2JveF9pb3UKICAgICAgICBmb3IgbW9kdWxlIGluIG1vZHVsZXNbMTpdOgogICAgICAgICAgICBpZiBoYXNhdHRyKG1vZHVsZSwgImJveF9pb3UiKToKICAgICAgICAgICAgICAgIG1vZHVsZS5ib3hfaW91ID0gZGV2aWNlX3NhZmVfYm94X2lvdQoKICAgICAgICBwcmludCgiUlQtREVUUiBib3hfaW91IGRldmljZSBwYXRjaCBpbnN0YWxsZWQiKQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGM6CiAgICAgICAgcmFpc2UgUnVudGltZUVycm9yKGYiQ291bGQgbm90IGluc3RhbGwgUlQtREVUUiBib3hfaW91IGRldmljZSBwYXRjaDoge2V4Y30iKSBmcm9tIGV4YwoKZGVmIHBhdGNoX3J0ZGV0cl9kZXZpY2VfY29uc2lzdGVuY3koKToKICAgICIiIk1ha2UgUlQtREVUUiBIdW5nYXJpYW4gbWF0Y2hpbmcgZGV2aWNlLXNhZmUgYWNyb3NzIFRyYW5zZm9ybWVycyByZWxlYXNlcy4KCiAgICBJbiB0aGUgVHJhbnNmb3JtZXJzIHZlcnNpb24gdXNlZCBieSB0aGUgYXBwLCBSVERldHJIdW5nYXJpYW5NYXRjaGVyIGxpdmVzCiAgICBpbiB0cmFuc2Zvcm1lcnMubG9zcy5sb3NzX3J0X2RldHIgKG5vdCBtb2RlbGluZ19ydF9kZXRyKS4gSXRzIGZvcndhcmQgcGF0aAogICAgY2FuIHJlY2VpdmUgdGFyZ2V0IGJveGVzIG9uIENQVSB3aGlsZSBtb2RlbCBwcmVkaWN0aW9ucyBhcmUgb24gQ1VEQS4gVGhhdAogICAgYnJlYWtzIGJvdGggdG9yY2guY2Rpc3QoKSBhbmQgZ2VuZXJhbGl6ZWRfYm94X2lvdSgpLiBQYXRjaCB0aGUgbWF0Y2hlcidzCiAgICBmb3J3YXJkIG1ldGhvZCBkaXJlY3RseSBzbyBldmVyeSB0ZW5zb3IgaW4gdGhlIHRhcmdldHMgaXMgbW92ZWQgdG8gdGhlCiAgICBwcmVkaWN0aW9uIGRldmljZSBiZWZvcmUgdGhlIG9yaWdpbmFsIG1hdGNoZXIgcnVucy4KICAgICIiIgogICAgdHJ5OgogICAgICAgIGltcG9ydCBpbXBvcnRsaWIKCiAgICAgICAgbG9zc19tb2QgPSBOb25lCiAgICAgICAgZm9yIG1vZHVsZV9uYW1lIGluICgKICAgICAgICAgICAgInRyYW5zZm9ybWVycy5sb3NzLmxvc3NfcnRfZGV0ciIsCiAgICAgICAgICAgICJ0cmFuc2Zvcm1lcnMubW9kZWxzLnJ0X2RldHIubG9zcy5sb3NzX3J0X2RldHIiLAogICAgICAgICk6CiAgICAgICAgICAgIHRyeToKICAgICAgICAgICAgICAgIGxvc3NfbW9kID0gaW1wb3J0bGliLmltcG9ydF9tb2R1bGUobW9kdWxlX25hbWUpCiAgICAgICAgICAgICAgICBpZiBoYXNhdHRyKGxvc3NfbW9kLCAiUlREZXRySHVuZ2FyaWFuTWF0Y2hlciIpOgogICAgICAgICAgICAgICAgICAgIGJyZWFrCiAgICAgICAgICAgIGV4Y2VwdCBJbXBvcnRFcnJvcjoKICAgICAgICAgICAgICAgIGNvbnRpbnVlCgogICAgICAgIGlmIGxvc3NfbW9kIGlzIE5vbmUgb3Igbm90IGhhc2F0dHIobG9zc19tb2QsICJSVERldHJIdW5nYXJpYW5NYXRjaGVyIik6CiAgICAgICAgICAgIHJhaXNlIFJ1bnRpbWVFcnJvcigiUlREZXRySHVuZ2FyaWFuTWF0Y2hlciBjb3VsZCBub3QgYmUgbG9jYXRlZCBpbiB0aGUgaW5zdGFsbGVkIFRyYW5zZm9ybWVycyBwYWNrYWdlIikKCiAgICAgICAgbWF0Y2hlcl9jbHMgPSBsb3NzX21vZC5SVERldHJIdW5nYXJpYW5NYXRjaGVyCiAgICAgICAgb3JpZ2luYWxfZm9yd2FyZCA9IG1hdGNoZXJfY2xzLmZvcndhcmQKICAgICAgICBpZiBnZXRhdHRyKG9yaWdpbmFsX2ZvcndhcmQsICJfaWNlY3JlYW1fZGV2aWNlX3BhdGNoIiwgRmFsc2UpOgogICAgICAgICAgICBwcmludCgiUlQtREVUUiBkZXZpY2UtY29uc2lzdGVuY3kgcGF0Y2ggYWxyZWFkeSBpbnN0YWxsZWQiKQogICAgICAgICAgICByZXR1cm4KCiAgICAgICAgZGVmIG1vdmVfdGFyZ2V0cyh0YXJnZXRzLCBkZXZpY2UpOgogICAgICAgICAgICBmaXhlZCA9IFtdCiAgICAgICAgICAgIGZvciB0YXJnZXQgaW4gdGFyZ2V0czoKICAgICAgICAgICAgICAgIGlmIGlzaW5zdGFuY2UodGFyZ2V0LCBkaWN0KToKICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBkaWN0KHRhcmdldCkKICAgICAgICAgICAgICAgICAgICBmb3Iga2V5LCB2YWx1ZSBpbiBsaXN0KHRhcmdldC5pdGVtcygpKToKICAgICAgICAgICAgICAgICAgICAgICAgaWYgdG9yY2guaXNfdGVuc29yKHZhbHVlKToKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhcmdldFtrZXldID0gdmFsdWUudG8oZGV2aWNlKQogICAgICAgICAgICAgICAgZWxpZiBoYXNhdHRyKHRhcmdldCwgIml0ZW1zIik6CiAgICAgICAgICAgICAgICAgICAgdGFyZ2V0ID0gZGljdCh0YXJnZXQpCiAgICAgICAgICAgICAgICAgICAgZm9yIGtleSwgdmFsdWUgaW4gbGlzdCh0YXJnZXQuaXRlbXMoKSk6CiAgICAgICAgICAgICAgICAgICAgICAgIGlmIHRvcmNoLmlzX3RlbnNvcih2YWx1ZSk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRba2V5XSA9IHZhbHVlLnRvKGRldmljZSkKICAgICAgICAgICAgICAgIGZpeGVkLmFwcGVuZCh0YXJnZXQpCiAgICAgICAgICAgIHJldHVybiBmaXhlZAoKICAgICAgICBkZWYgZGV2aWNlX3NhZmVfZm9yd2FyZChzZWxmLCBvdXRwdXRzLCB0YXJnZXRzLCAqYXJncywgKiprd2FyZ3MpOgogICAgICAgICAgICBkZXZpY2UgPSBOb25lCiAgICAgICAgICAgICMgUlQtREVUUiBwYXNzZXMgYSBNb2RlbE91dHB1dCBvYmplY3QgaGVyZSwgbm90IG5lY2Vzc2FyaWx5IGEgZGljdC4KICAgICAgICAgICAgIyBTdXBwb3J0IGJvdGggZm9ybXMgc28gd2UgYWx3YXlzIGRpc2NvdmVyIHRoZSBwcmVkaWN0aW9uIGRldmljZS4KICAgICAgICAgICAgZm9yIGtleSBpbiAoInByZWRfYm94ZXMiLCAicHJlZF9sb2dpdHMiKToKICAgICAgICAgICAgICAgIHZhbHVlID0gTm9uZQogICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZShvdXRwdXRzLCBkaWN0KToKICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IG91dHB1dHMuZ2V0KGtleSkKICAgICAgICAgICAgICAgIGVsc2U6CiAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSBnZXRhdHRyKG91dHB1dHMsIGtleSwgTm9uZSkKICAgICAgICAgICAgICAgIGlmIHRvcmNoLmlzX3RlbnNvcih2YWx1ZSk6CiAgICAgICAgICAgICAgICAgICAgZGV2aWNlID0gdmFsdWUuZGV2aWNlCiAgICAgICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgaWYgZGV2aWNlIGlzIG5vdCBOb25lOgogICAgICAgICAgICAgICAgdGFyZ2V0cyA9IG1vdmVfdGFyZ2V0cyh0YXJnZXRzLCBkZXZpY2UpCiAgICAgICAgICAgIHJldHVybiBvcmlnaW5hbF9mb3J3YXJkKHNlbGYsIG91dHB1dHMsIHRhcmdldHMsICphcmdzLCAqKmt3YXJncykKCiAgICAgICAgZGV2aWNlX3NhZmVfZm9yd2FyZC5faWNlY3JlYW1fZGV2aWNlX3BhdGNoID0gVHJ1ZQogICAgICAgIG1hdGNoZXJfY2xzLmZvcndhcmQgPSBkZXZpY2Vfc2FmZV9mb3J3YXJkCiAgICAgICAgcHJpbnQoIlJULURFVFIgSHVuZ2FyaWFuIG1hdGNoZXIgZGV2aWNlIHBhdGNoIGluc3RhbGxlZCIpCgogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBleGM6CiAgICAgICAgcmFpc2UgUnVudGltZUVycm9yKGYiQ291bGQgbm90IGluc3RhbGwgUlQtREVUUiBkZXZpY2UgcGF0Y2g6IHtleGN9IikgZnJvbSBleGMKCmRlZiBwYXRjaF9ydGRldHJfZGVub2lzaW5nX2RldmljZSgpOgogICAgIiIiUGF0Y2ggdGhlIFJULURFVFIgZGVub2lzaW5nIGhlbHBlciB3aXRob3V0IGFzc3VtaW5nIG1hdGNoZXIgaW50ZXJuYWxzLiIiIgogICAgdHJ5OgogICAgICAgIGltcG9ydCB0cmFuc2Zvcm1lcnMubW9kZWxzLnJ0X2RldHIubW9kZWxpbmdfcnRfZGV0ciBhcyBydGRldHJfbW9kCiAgICAgICAgaW1wb3J0IHRvcmNoLm5uIGFzIG5uCgogICAgICAgIG9yaWdpbmFsID0gZ2V0YXR0cihydGRldHJfbW9kLCAiZ2V0X2NvbnRyYXN0aXZlX2Rlbm9pc2luZ190cmFpbmluZ19ncm91cCIsIE5vbmUpCiAgICAgICAgaWYgb3JpZ2luYWwgaXMgTm9uZSBvciBnZXRhdHRyKG9yaWdpbmFsLCAiX2ljZWNyZWFtX2RldmljZV9wYXRjaCIsIEZhbHNlKToKICAgICAgICAgICAgcmV0dXJuCgogICAgICAgIGNsYXNzIERldmljZVNhZmVFbWJlZGRpbmcobm4uTW9kdWxlKToKICAgICAgICAgICAgZGVmIF9faW5pdF9fKHNlbGYsIGVtYmVkZGluZyk6CiAgICAgICAgICAgICAgICBzdXBlcigpLl9faW5pdF9fKCkKICAgICAgICAgICAgICAgIHNlbGYuZW1iZWRkaW5nID0gZW1iZWRkaW5nCgogICAgICAgICAgICBkZWYgZm9yd2FyZChzZWxmLCBpbmRpY2VzKToKICAgICAgICAgICAgICAgIGlmIHRvcmNoLmlzX3RlbnNvcihpbmRpY2VzKToKICAgICAgICAgICAgICAgICAgICBpbmRpY2VzID0gaW5kaWNlcy50byhzZWxmLmVtYmVkZGluZy53ZWlnaHQuZGV2aWNlKQogICAgICAgICAgICAgICAgcmV0dXJuIHNlbGYuZW1iZWRkaW5nKGluZGljZXMpCgogICAgICAgIGRlZiB3cmFwcGVkKHRhcmdldHMsIG51bV9jbGFzc2VzLCBudW1fcXVlcmllcywgY2xhc3NfZW1iZWQsCiAgICAgICAgICAgICAgICAgICAgbnVtX2Rlbm9pc2luZ19xdWVyaWVzPTEwMCwgbGFiZWxfbm9pc2VfcmF0aW89MC41LAogICAgICAgICAgICAgICAgICAgIGJveF9ub2lzZV9zY2FsZT0xLjAsICoqa3dhcmdzKToKICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgZW1iZWRfZGV2aWNlID0gY2xhc3NfZW1iZWQud2VpZ2h0LmRldmljZQogICAgICAgICAgICBleGNlcHQgRXhjZXB0aW9uOgogICAgICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgICAgIGVtYmVkX2RldmljZSA9IG5leHQoY2xhc3NfZW1iZWQucGFyYW1ldGVycygpKS5kZXZpY2UKICAgICAgICAgICAgICAgIGV4Y2VwdCBFeGNlcHRpb246CiAgICAgICAgICAgICAgICAgICAgZW1iZWRfZGV2aWNlID0gTm9uZQoKICAgICAgICAgICAgaWYgZW1iZWRfZGV2aWNlIGlzIG5vdCBOb25lOgogICAgICAgICAgICAgICAgZml4ZWRfdGFyZ2V0cyA9IFtdCiAgICAgICAgICAgICAgICBmb3IgdGFyZ2V0IGluIHRhcmdldHM6CiAgICAgICAgICAgICAgICAgICAgaWYgaXNpbnN0YW5jZSh0YXJnZXQsIGRpY3QpOgogICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBkaWN0KHRhcmdldCkKICAgICAgICAgICAgICAgICAgICAgICAgZm9yIGtleSwgdmFsdWUgaW4gbGlzdCh0YXJnZXQuaXRlbXMoKSk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiB0b3JjaC5pc190ZW5zb3IodmFsdWUpOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRhcmdldFtrZXldID0gdmFsdWUudG8oZW1iZWRfZGV2aWNlKQogICAgICAgICAgICAgICAgICAgIGZpeGVkX3RhcmdldHMuYXBwZW5kKHRhcmdldCkKICAgICAgICAgICAgICAgIHRhcmdldHMgPSBmaXhlZF90YXJnZXRzCiAgICAgICAgICAgICAgICBjbGFzc19lbWJlZCA9IERldmljZVNhZmVFbWJlZGRpbmcoY2xhc3NfZW1iZWQpCgogICAgICAgICAgICByZXR1cm4gb3JpZ2luYWwoCiAgICAgICAgICAgICAgICB0YXJnZXRzPXRhcmdldHMsCiAgICAgICAgICAgICAgICBudW1fY2xhc3Nlcz1udW1fY2xhc3NlcywKICAgICAgICAgICAgICAgIG51bV9xdWVyaWVzPW51bV9xdWVyaWVzLAogICAgICAgICAgICAgICAgY2xhc3NfZW1iZWQ9Y2xhc3NfZW1iZWQsCiAgICAgICAgICAgICAgICBudW1fZGVub2lzaW5nX3F1ZXJpZXM9bnVtX2Rlbm9pc2luZ19xdWVyaWVzLAogICAgICAgICAgICAgICAgbGFiZWxfbm9pc2VfcmF0aW89bGFiZWxfbm9pc2VfcmF0aW8sCiAgICAgICAgICAgICAgICBib3hfbm9pc2Vfc2NhbGU9Ym94X25vaXNlX3NjYWxlLAogICAgICAgICAgICAgICAgKiprd2FyZ3MsCiAgICAgICAgICAgICkKCiAgICAgICAgd3JhcHBlZC5faWNlY3JlYW1fZGV2aWNlX3BhdGNoID0gVHJ1ZQogICAgICAgIHJ0ZGV0cl9tb2QuZ2V0X2NvbnRyYXN0aXZlX2Rlbm9pc2luZ190cmFpbmluZ19ncm91cCA9IHdyYXBwZWQKICAgICAgICBwcmludCgiUlQtREVUUiBkZW5vaXNpbmcgZGV2aWNlIHBhdGNoIGluc3RhbGxlZCIpCiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGV4YzoKICAgICAgICAjIERlbm9pc2luZyBpcyBkaXNhYmxlZCBiZWxvdyB3aGVyZSBzdXBwb3J0ZWQsIHNvIHRoaXMgaGVscGVyIGlzCiAgICAgICAgIyBvcHRpb25hbC4gRG8gbm90IG1ha2UgdHJhaW5pbmcgZmFpbCBiZWNhdXNlIGFuIGludGVybmFsIGhlbHBlciBtb3ZlZC4KICAgICAgICBwcmludChmIlJULURFVFIgZGVub2lzaW5nIHBhdGNoIHNraXBwZWQ6IHtleGN9IikKCmRlZiBtYWluKCk6CiAgICBwPWFyZ3BhcnNlLkFyZ3VtZW50UGFyc2VyKCkKICAgIHAuYWRkX2FyZ3VtZW50KCItLXRyYWluLWRpciIscmVxdWlyZWQ9VHJ1ZSk7IHAuYWRkX2FyZ3VtZW50KCItLXZhbC1kaXIiLHJlcXVpcmVkPVRydWUpCiAgICBwLmFkZF9hcmd1bWVudCgiLS1jbGFzc2VzIixyZXF1aXJlZD1UcnVlKTsgcC5hZGRfYXJndW1lbnQoIi0tb3V0cHV0LWRpciIsZGVmYXVsdD0ibW9kZWwiKQogICAgcC5hZGRfYXJndW1lbnQoIi0tZXBvY2hzIix0eXBlPWludCxkZWZhdWx0PTMwKTsgcC5hZGRfYXJndW1lbnQoIi0tYmF0Y2gtc2l6ZSIsdHlwZT1pbnQsZGVmYXVsdD0yKQogICAgcC5hZGRfYXJndW1lbnQoIi0tbGVhcm5pbmctcmF0ZSIsdHlwZT1mbG9hdCxkZWZhdWx0PTFlLTUpOyBwLmFkZF9hcmd1bWVudCgiLS13ZWlnaHQtZGVjYXkiLHR5cGU9ZmxvYXQsZGVmYXVsdD0xZS00KQogICAgcC5hZGRfYXJndW1lbnQoIi0tbnVtLXdvcmtlcnMiLHR5cGU9aW50LGRlZmF1bHQ9MikKICAgIGE9cC5wYXJzZV9hcmdzKCkKCiAgICBjbGFzc2VzPWxvYWRfY2xhc3NlcyhhLmNsYXNzZXMpCiAgICBpZDJsYWJlbD17aTpuIGZvciBpLG4gaW4gZW51bWVyYXRlKGNsYXNzZXMpfQogICAgbGFiZWwyaWQ9e246aSBmb3IgaSxuIGluIGVudW1lcmF0ZShjbGFzc2VzKX0KCiAgICBwcm9jPVJURGV0ckltYWdlUHJvY2Vzc29yLmZyb21fcHJldHJhaW5lZChCQVNFX01PREVMKQogICAgdHJhaW49Q09DT0RldGVjdGlvbkRhdGFzZXQoUGF0aChhLnRyYWluX2RpcikvImltYWdlcyIsUGF0aChhLnRyYWluX2RpcikvImFubm90YXRpb25zLmpzb24iLHByb2MpCiAgICB2YWw9Q09DT0RldGVjdGlvbkRhdGFzZXQoUGF0aChhLnZhbF9kaXIpLyJpbWFnZXMiLFBhdGgoYS52YWxfZGlyKS8iYW5ub3RhdGlvbnMuanNvbiIscHJvYykKCiAgICBpZiBsZW4odHJhaW4pPT0wIG9yIGxlbih2YWwpPT0wOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIlRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIGRhdGFzZXRzIG11c3QgY29udGFpbiBhdCBsZWFzdCBvbmUgaW1hZ2UuIikKICAgIGlmIGxlbih0cmFpbi5jYXRlZ29yeV9pZF90b19sYWJlbCkhPWxlbihjbGFzc2VzKSBvciBsZW4odmFsLmNhdGVnb3J5X2lkX3RvX2xhYmVsKSE9bGVuKGNsYXNzZXMpOgogICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoIkNPQ08gY2F0ZWdvcmllcyBkbyBub3QgbWF0Y2ggY2xhc3Nlcy50eHQuIFJlYnVpbGQgdGhlIGRhdGFzZXQgYWZ0ZXIgc2F2aW5nIHRoZSBjbGFzc2VzLiIpCgogICAgbW9kZWw9UlREZXRyRm9yT2JqZWN0RGV0ZWN0aW9uLmZyb21fcHJldHJhaW5lZCgKICAgICAgICBCQVNFX01PREVMLG51bV9sYWJlbHM9bGVuKGNsYXNzZXMpLGlkMmxhYmVsPWlkMmxhYmVsLGxhYmVsMmlkPWxhYmVsMmlkLAogICAgICAgIGlnbm9yZV9taXNtYXRjaGVkX3NpemVzPVRydWUKICAgICkKICAgICMgU29tZSBUcmFuc2Zvcm1lcnMgUlQtREVUUiByZWxlYXNlcyBlbnRlciB0aGUgZGVub2lzaW5nIHBhdGggd2hlbmV2ZXIKICAgICMgdHJhaW5pbmcsIHJlZ2FyZGxlc3Mgb2YgbnVtX2Rlbm9pc2luZy4gS2VlcCB0aGUgY29uZmlnIGRpc2FibGVkIHdoZXJlCiAgICAjIHN1cHBvcnRlZCwgYnV0IGFsc28gaW5zdGFsbCB0aGUgZGV2aWNlLXNhZmUgZW1iZWRkaW5nIHBhdGNoIGJlbG93LgogICAgZm9yIGNmZ19vd25lciBpbiAobW9kZWwsIGdldGF0dHIobW9kZWwsICJtb2RlbCIsIE5vbmUpKToKICAgICAgICBjZmcgPSBnZXRhdHRyKGNmZ19vd25lciwgImNvbmZpZyIsIE5vbmUpCiAgICAgICAgaWYgY2ZnIGlzIG5vdCBOb25lOgogICAgICAgICAgICBmb3IgbmFtZSBpbiAoIm51bV9kZW5vaXNpbmciLCAibnVtX2Rlbm9pc2luZ19xdWVyaWVzIik6CiAgICAgICAgICAgICAgICBpZiBoYXNhdHRyKGNmZywgbmFtZSk6CiAgICAgICAgICAgICAgICAgICAgc2V0YXR0cihjZmcsIG5hbWUsIDApCiAgICBkZXZpY2U9dG9yY2guZGV2aWNlKCJjdWRhIiBpZiB0b3JjaC5jdWRhLmlzX2F2YWlsYWJsZSgpIGVsc2UgImNwdSIpCiAgICBtb2RlbC50byhkZXZpY2UpCiAgICBwYXRjaF9ydGRldHJfYm94X2lvdV9kZXZpY2UoKQogICAgcGF0Y2hfcnRkZXRyX2RldmljZV9jb25zaXN0ZW5jeSgpCiAgICBwYXRjaF9ydGRldHJfZGVub2lzaW5nX2RldmljZSgpCgogICAgdHI9RGF0YUxvYWRlcih0cmFpbixiYXRjaF9zaXplPWEuYmF0Y2hfc2l6ZSxzaHVmZmxlPVRydWUsbnVtX3dvcmtlcnM9MCxjb2xsYXRlX2ZuPWNvbGxhdGVfZm4pCiAgICB2YT1EYXRhTG9hZGVyKHZhbCxiYXRjaF9zaXplPWEuYmF0Y2hfc2l6ZSxzaHVmZmxlPUZhbHNlLG51bV93b3JrZXJzPTAsY29sbGF0ZV9mbj1jb2xsYXRlX2ZuKQogICAgb3B0PXRvcmNoLm9wdGltLkFkYW1XKG1vZGVsLnBhcmFtZXRlcnMoKSxscj1hLmxlYXJuaW5nX3JhdGUsd2VpZ2h0X2RlY2F5PWEud2VpZ2h0X2RlY2F5KQoKICAgIG91dGRpcj1QYXRoKGEub3V0cHV0X2Rpcik7IG91dGRpci5ta2RpcihwYXJlbnRzPVRydWUsZXhpc3Rfb2s9VHJ1ZSkKICAgIGJlc3Q9ZmxvYXQoImluZiIpCgogICAgZm9yIGVwb2NoIGluIHJhbmdlKGEuZXBvY2hzKToKICAgICAgICBtb2RlbC50cmFpbigpOyBydW5uaW5nPTAKICAgICAgICBiYXI9dHFkbSh0cixkZXNjPWYiZXBvY2gge2Vwb2NoKzF9L3thLmVwb2Noc30iKQogICAgICAgIGZvciBzdGVwLGJhdGNoIGluIGVudW1lcmF0ZShiYXIpOgogICAgICAgICAgICBiYXRjaD1tb3ZlX3RvX2RldmljZShiYXRjaCwgZGV2aWNlKQogICAgICAgICAgICAjIFJULURFVFIncyBsb3NzIG1hdGNoZXIgdXNlcyBuZXN0ZWQgdGFyZ2V0IHRlbnNvcnMgKGJveGVzL2NsYXNzZXMpLgogICAgICAgICAgICAjIE1vdmUgZXZlcnkgdGVuc29yIGluIGxhYmVscyB0byB0aGUgc2FtZSBkZXZpY2UgYXMgdGhlIG1vZGVsLgogICAgICAgICAgICBpZiAibGFiZWxzIiBpbiBiYXRjaDoKICAgICAgICAgICAgICAgICMgUlQtREVUUiBleHBlY3RzIGV2ZXJ5IG5lc3RlZCB0YXJnZXQgdGVuc29yIG9uIHRoZSBzYW1lIGRldmljZSBhcyB0aGUgbW9kZWwuCiAgICAgICAgICAgICAgICBmb3IgdGFyZ2V0IGluIGJhdGNoWyJsYWJlbHMiXToKICAgICAgICAgICAgICAgICAgICBpZiBpc2luc3RhbmNlKHRhcmdldCwgZGljdCk6CiAgICAgICAgICAgICAgICAgICAgICAgIGZvciBrZXksIHZhbHVlIGluIGxpc3QodGFyZ2V0Lml0ZW1zKCkpOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgdG9yY2guaXNfdGVuc29yKHZhbHVlKToKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXRba2V5XSA9IHZhbHVlLnRvKGRldmljZSkKICAgICAgICAgICAgbG9zcz1tb2RlbCgqKmJhdGNoKS5sb3NzCiAgICAgICAgICAgIGxvc3MuYmFja3dhcmQoKTsgb3B0LnN0ZXAoKTsgb3B0Lnplcm9fZ3JhZChzZXRfdG9fbm9uZT1UcnVlKQogICAgICAgICAgICBydW5uaW5nICs9IGZsb2F0KGxvc3MuaXRlbSgpKQogICAgICAgICAgICBiYXIuc2V0X3Bvc3RmaXgobG9zcz1mIntydW5uaW5nLyhzdGVwKzEpOi40Zn0iKQogICAgICAgIHZsPWV2YWx1YXRlKG1vZGVsLHZhLGRldmljZSkKICAgICAgICBwcmludChmInZhbGlkYXRpb25fbG9zcz17dmw6LjRmfSIpCiAgICAgICAgaWYgdmw8YmVzdDoKICAgICAgICAgICAgYmVzdD12bAogICAgICAgICAgICBtb2RlbC5zYXZlX3ByZXRyYWluZWQob3V0ZGlyKQogICAgICAgICAgICBwcm9jLnNhdmVfcHJldHJhaW5lZChvdXRkaXIpCiAgICAgICAgICAgIChvdXRkaXIvImNsYXNzZXMuanNvbiIpLndyaXRlX3RleHQoanNvbi5kdW1wcyh7ImlkMmxhYmVsIjppZDJsYWJlbCwibGFiZWwyaWQiOmxhYmVsMmlkfSxpbmRlbnQ9MikpCiAgICBtb2RlbC5zYXZlX3ByZXRyYWluZWQob3V0ZGlyKTsgcHJvYy5zYXZlX3ByZXRyYWluZWQob3V0ZGlyKQoKaWYgX19uYW1lX189PSJfX21haW5fXyI6IG1haW4oKQo=" TRAINING_DIR = ROOT / "training" TRAIN_SCRIPT = TRAINING_DIR / "train.py" if not TRAIN_SCRIPT.exists(): TRAINING_DIR.mkdir(parents=True, exist_ok=True) TRAIN_SCRIPT.write_bytes(__import__("base64").b64decode(_TRAINING_SCRIPT_B64)) IMAGE_DIR = BASE / "images" DATASET_FILE = BASE / "dataset.json" MODEL_DIR = BASE / "model" GENERATED_DIR = BASE / "generated_dataset" CLASSES_FILE = BASE / "classes.txt" IMAGE_DIR.mkdir(parents=True, exist_ok=True) BASE.mkdir(parents=True, exist_ok=True) MODEL_DIR.mkdir(parents=True, exist_ok=True) DEFAULT_CLASSES = [ "Carnavalita", "Kimo-COno", "Squizz", "Oreo", "Moro", "Dulce", "KitKat", "Cadbury", "Mega", "other" ] if not CLASSES_FILE.exists(): CLASSES_FILE.write_text("\n".join(DEFAULT_CLASSES) + "\n", encoding="utf-8") CONFIDENCE_THRESHOLD = float(os.getenv("CONFIDENCE_THRESHOLD", "0.35")) MAX_IMAGE_MB = int(os.getenv("MAX_IMAGE_MB", "15")) _training = {"running": False, "message": "not started", "error": None} _model = None _processor = None _model_lock = threading.Lock() _annotation_click = None def load_dataset(): if not DATASET_FILE.exists(): return {"images": [], "classes": read_classes()} try: data = json.loads(DATASET_FILE.read_text(encoding="utf-8")) data.setdefault("images", []) data["classes"] = read_classes() return data except Exception: return {"images": [], "classes": read_classes()} def save_dataset(data): data["classes"] = read_classes() tmp = DATASET_FILE.with_suffix(".tmp") tmp.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8") tmp.replace(DATASET_FILE) def read_classes(): if not CLASSES_FILE.exists(): return [] return [x.strip() for x in CLASSES_FILE.read_text(encoding="utf-8").splitlines() if x.strip()] def image_path(image_id): return IMAGE_DIR / f"{image_id}.jpg" def model_ready(): return (MODEL_DIR / "config.json").exists() def load_model(): global _model, _processor if not model_ready(): raise RuntimeError("No trained model yet. Train the model first.") with _model_lock: if _model is None: _processor = RTDetrImageProcessor.from_pretrained(str(MODEL_DIR)) _model = RTDetrForObjectDetection.from_pretrained(str(MODEL_DIR)) _model.to("cuda" if torch.cuda.is_available() else "cpu") _model.eval() return _processor, _model def dataset_status(): data = load_dataset() annotated = sum(bool(x.get("annotations")) for x in data["images"]) return ( f"**Dataset:** {len(data['images'])} images | " f"**Annotated:** {annotated} | " f"**Classes:** {len(read_classes())} | " f"**Model:** {'READY' if model_ready() else 'NOT TRAINED'} | " f"**Storage:** `{BASE}`" ) def image_choices(): data = load_dataset() return [(x["filename"], x["id"]) for x in data["images"]] def upload_training_images(files): if not files: return dataset_status(), gr.update(choices=image_choices()), "No files selected." data = load_dataset() saved = 0 skipped = [] for f in files: try: # Gradio 6 may return FileData objects or plain dictionaries. if isinstance(f, dict): raw_path = f.get("path") or f.get("name") or f.get("filepath") else: raw_path = getattr(f, "path", None) or getattr(f, "name", None) or f path = Path(raw_path) raw = path.read_bytes() if len(raw) > MAX_IMAGE_MB * 1024 * 1024: skipped.append(f"{path.name}: over {MAX_IMAGE_MB} MB") continue im = Image.open(io.BytesIO(raw)).convert("RGB") image_id = __import__("uuid").uuid4().hex out = image_path(image_id) im.save(out, "JPEG", quality=95) data["images"].append({ "id": image_id, "filename": path.name, "width": im.width, "height": im.height, "annotations": [], }) saved += 1 except Exception as e: skipped.append(f"{path.name}: {e}") save_dataset(data) msg = f"Saved {saved} image(s)." if skipped: msg += "\nSkipped:\n- " + "\n- ".join(skipped) return dataset_status(), gr.update(choices=image_choices()), msg def image_data_uri(image_id): import base64 p = image_path(image_id) if not p.exists(): return "" return "data:image/jpeg;base64," + base64.b64encode(p.read_bytes()).decode("ascii") def annotation_canvas_html(image_id): if not image_id: return '