Spaces:
Running on Zero
Running on Zero
Upload 6 files
Browse files- __pycache__/app.cpython-313.pyc +0 -0
- app.py +68 -29
- training/classes.txt +9 -7
__pycache__/app.cpython-313.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
|
|
|
app.py
CHANGED
|
@@ -238,11 +238,11 @@ def draw_annotations(image_id):
|
|
| 238 |
|
| 239 |
|
| 240 |
def add_annotation(image_id, cls, x, y, w, h):
|
| 241 |
-
if not image_id:return
|
| 242 |
-
if not cls:return
|
| 243 |
try:x,y,w,h=map(float,[x,y,w,h])
|
| 244 |
-
except:return
|
| 245 |
-
if w<=0 or h<=0:return
|
| 246 |
data=load_dataset()
|
| 247 |
item=next((z for z in data["images"] if z["id"]==image_id),None)
|
| 248 |
if not item:return None,"Image not found.",[]
|
|
@@ -250,41 +250,72 @@ def add_annotation(image_id, cls, x, y, w, h):
|
|
| 250 |
w=min(w,item["width"]-x); h=min(h,item["height"]-y)
|
| 251 |
item.setdefault("annotations",[]).append({"class":cls,"box":[x,y,w,h]})
|
| 252 |
save_dataset(data)
|
| 253 |
-
return
|
| 254 |
|
| 255 |
|
| 256 |
def remove_annotation(image_id,index):
|
| 257 |
-
if not image_id:return
|
| 258 |
data=load_dataset()
|
| 259 |
item=next((z for z in data["images"] if z["id"]==image_id),None)
|
| 260 |
if not item:return None,"Image not found.",[]
|
| 261 |
try:idx=int(index)-1
|
| 262 |
-
except:return
|
| 263 |
anns=item.get("annotations",[])
|
| 264 |
-
if idx<0 or idx>=len(anns):return
|
| 265 |
deleted=anns.pop(idx);save_dataset(data)
|
| 266 |
-
return
|
| 267 |
|
| 268 |
|
| 269 |
def clear_annotations(image_id):
|
| 270 |
-
if not image_id:return
|
| 271 |
data=load_dataset()
|
| 272 |
item=next((z for z in data["images"] if z["id"]==image_id),None)
|
| 273 |
if not item:return None,"Image not found.",[]
|
| 274 |
item["annotations"]=[];save_dataset(data)
|
| 275 |
-
return
|
| 276 |
|
| 277 |
|
| 278 |
def save_classes(text):
|
| 279 |
classes = [x.strip() for x in (text or "").splitlines() if x.strip()]
|
| 280 |
if not classes:
|
| 281 |
-
return "At least one class is required.", gr.update(choices=
|
| 282 |
if len(set(classes)) != len(classes):
|
| 283 |
-
return "Classes must be unique.", gr.update(choices=
|
| 284 |
CLASSES_FILE.write_text("\n".join(classes) + "\n", encoding="utf-8")
|
| 285 |
data = load_dataset()
|
| 286 |
save_dataset(data)
|
| 287 |
-
return f"Saved {len(classes)} classes.", gr.update(choices=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 288 |
|
| 289 |
|
| 290 |
def build_coco():
|
|
@@ -435,6 +466,7 @@ CSS = """
|
|
| 435 |
.gradio-container { max-width: 1250px !important; }
|
| 436 |
h1 { margin-bottom: 0.2rem !important; }
|
| 437 |
.anno-wrap{width:100%}.anno-toolbar{display:flex;gap:14px;flex-wrap:wrap;padding:10px 12px;margin-bottom:8px;border-radius:10px;background:#20242a}.anno-toolbar span{opacity:.85}.anno-canvas-wrap{width:100%;overflow:auto;border:1px solid #555;border-radius:10px;background:#111;padding:8px}.anno-canvas-wrap canvas{display:block;max-width:none;cursor:crosshair;touch-action:none;margin:auto}.anno-help{padding:8px 2px;opacity:.75}.anno-empty{padding:50px;text-align:center;border:1px dashed #777;border-radius:10px}
|
|
|
|
| 438 |
.status { padding: 10px 14px; border-radius: 10px; }
|
| 439 |
"""
|
| 440 |
|
|
@@ -459,29 +491,35 @@ with gr.Blocks(title="Ice Cream Dataset + Counter") as demo:
|
|
| 459 |
# Class dropdown is updated after the Annotate tab creates it.
|
| 460 |
|
| 461 |
with gr.Tab("2 · Annotate"):
|
| 462 |
-
gr.Markdown("###
|
| 463 |
-
gr.Markdown("
|
| 464 |
with gr.Row():
|
| 465 |
with gr.Column(scale=3):
|
| 466 |
-
|
| 467 |
-
editor_info = gr.Markdown()
|
| 468 |
with gr.Column(scale=1):
|
| 469 |
-
ann_class = gr.Dropdown(choices=read_classes(), value=(read_classes()[0] if read_classes() else None), label="Class")
|
| 470 |
-
x = gr.Number(label="X (left)", value=0, precision=0
|
| 471 |
-
y = gr.Number(label="Y (top)", value=0, precision=0
|
| 472 |
-
w = gr.Number(label="Width", value=0, precision=0
|
| 473 |
-
h = gr.Number(label="Height", value=0, precision=0
|
| 474 |
add_btn = gr.Button("💾 Save Box", variant="primary")
|
| 475 |
-
gr.Markdown("
|
| 476 |
delete_index = gr.Number(label="Annotation # to delete", value=1, precision=0)
|
| 477 |
delete_btn = gr.Button("Delete Box")
|
| 478 |
clear_btn = gr.Button("Clear All Boxes")
|
| 479 |
annotations = gr.JSON(label="Saved annotations")
|
| 480 |
ann_msg = gr.Markdown()
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 485 |
|
| 486 |
with gr.Tab("3 · Training"):
|
| 487 |
gr.Markdown("### Train RT-DETR")
|
|
@@ -513,9 +551,10 @@ with gr.Blocks(title="Ice Cream Dataset + Counter") as demo:
|
|
| 513 |
preprocess=False,
|
| 514 |
queue=False,
|
| 515 |
)
|
|
|
|
| 516 |
# The earlier placeholder event is harmlessly superseded by this real event.
|
| 517 |
|
| 518 |
-
demo.load(lambda: (dataset_status(), gr.update(choices=image_choices()), gr.update(choices=read_classes())),
|
| 519 |
None, [status, image_select, ann_class])
|
| 520 |
|
| 521 |
if __name__ == "__main__":
|
|
|
|
| 238 |
|
| 239 |
|
| 240 |
def add_annotation(image_id, cls, x, y, w, h):
|
| 241 |
+
if not image_id:return annotation_preview_with_boxes(image_id),"Select an image first.",[]
|
| 242 |
+
if not cls:return annotation_preview_with_boxes(image_id),"Select a class first.",[]
|
| 243 |
try:x,y,w,h=map(float,[x,y,w,h])
|
| 244 |
+
except:return annotation_preview_with_boxes(image_id),"Enter box coordinates first.",[]
|
| 245 |
+
if w<=0 or h<=0:return annotation_preview_with_boxes(image_id),"Box must have a width and height.",[]
|
| 246 |
data=load_dataset()
|
| 247 |
item=next((z for z in data["images"] if z["id"]==image_id),None)
|
| 248 |
if not item:return None,"Image not found.",[]
|
|
|
|
| 250 |
w=min(w,item["width"]-x); h=min(h,item["height"]-y)
|
| 251 |
item.setdefault("annotations",[]).append({"class":cls,"box":[x,y,w,h]})
|
| 252 |
save_dataset(data)
|
| 253 |
+
return annotation_preview_with_boxes(image_id),f"Saved {cls}: [{x:.0f}, {y:.0f}, {w:.0f}, {h:.0f}]",item["annotations"]
|
| 254 |
|
| 255 |
|
| 256 |
def remove_annotation(image_id,index):
|
| 257 |
+
if not image_id:return annotation_preview_with_boxes(image_id),"Select an image first.",[]
|
| 258 |
data=load_dataset()
|
| 259 |
item=next((z for z in data["images"] if z["id"]==image_id),None)
|
| 260 |
if not item:return None,"Image not found.",[]
|
| 261 |
try:idx=int(index)-1
|
| 262 |
+
except:return annotation_preview_with_boxes(image_id),"Enter an annotation number.",item.get("annotations",[])
|
| 263 |
anns=item.get("annotations",[])
|
| 264 |
+
if idx<0 or idx>=len(anns):return annotation_preview_with_boxes(image_id),"Annotation number not found.",anns
|
| 265 |
deleted=anns.pop(idx);save_dataset(data)
|
| 266 |
+
return annotation_preview_with_boxes(image_id),f"Deleted annotation {index}: {deleted['class']}",anns
|
| 267 |
|
| 268 |
|
| 269 |
def clear_annotations(image_id):
|
| 270 |
+
if not image_id:return annotation_preview_with_boxes(image_id),"Select an image first.",[]
|
| 271 |
data=load_dataset()
|
| 272 |
item=next((z for z in data["images"] if z["id"]==image_id),None)
|
| 273 |
if not item:return None,"Image not found.",[]
|
| 274 |
item["annotations"]=[];save_dataset(data)
|
| 275 |
+
return annotation_preview_with_boxes(image_id),"Annotations cleared.",[]
|
| 276 |
|
| 277 |
|
| 278 |
def save_classes(text):
|
| 279 |
classes = [x.strip() for x in (text or "").splitlines() if x.strip()]
|
| 280 |
if not classes:
|
| 281 |
+
return "At least one class is required.", gr.update(choices=read_classes()), dataset_status()
|
| 282 |
if len(set(classes)) != len(classes):
|
| 283 |
+
return "Classes must be unique.", gr.update(choices=read_classes()), dataset_status()
|
| 284 |
CLASSES_FILE.write_text("\n".join(classes) + "\n", encoding="utf-8")
|
| 285 |
data = load_dataset()
|
| 286 |
save_dataset(data)
|
| 287 |
+
return f"Saved {len(classes)} classes.", gr.update(choices=classes, value=classes[0]), dataset_status()
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
def handle_annotation_click(image_id, cls, click_state, evt: gr.SelectData):
|
| 291 |
+
"""Use two clicks on the real Gradio image to define a box.
|
| 292 |
+
First click = top-left corner, second click = opposite corner.
|
| 293 |
+
This avoids the unreliable HTML canvas/script path and works in Gradio itself.
|
| 294 |
+
"""
|
| 295 |
+
if not image_id:
|
| 296 |
+
return 0, 0, 0, 0, [], "Select an image first."
|
| 297 |
+
if not cls:
|
| 298 |
+
return 0, 0, 0, 0, [], "Select a class first."
|
| 299 |
+
data = load_dataset()
|
| 300 |
+
item = next((x for x in data["images"] if x["id"] == image_id), None)
|
| 301 |
+
if not item:
|
| 302 |
+
return 0, 0, 0, 0, [], "Image not found."
|
| 303 |
+
try:
|
| 304 |
+
point = evt.index
|
| 305 |
+
px, py = float(point[0]), float(point[1])
|
| 306 |
+
except Exception:
|
| 307 |
+
return 0, 0, 0, 0, click_state or [], "Could not read the image click position."
|
| 308 |
+
px = max(0, min(px, item["width"] - 1))
|
| 309 |
+
py = max(0, min(py, item["height"] - 1))
|
| 310 |
+
state = list(click_state or [])
|
| 311 |
+
if not state:
|
| 312 |
+
return round(px), round(py), 0, 0, [px, py], f"First corner: ({px:.0f}, {py:.0f}). Now click the opposite corner."
|
| 313 |
+
x0, y0 = state[:2]
|
| 314 |
+
x = min(x0, px); y = min(y0, py)
|
| 315 |
+
w = abs(px - x0); h = abs(py - y0)
|
| 316 |
+
if w < 2 or h < 2:
|
| 317 |
+
return round(x), round(y), 0, 0, [], "Box is too small. Click the first corner again."
|
| 318 |
+
return round(x), round(y), round(w), round(h), [], f"Box ready: [{x:.0f}, {y:.0f}, {w:.0f}, {h:.0f}] for {cls}. Click Save Box."
|
| 319 |
|
| 320 |
|
| 321 |
def build_coco():
|
|
|
|
| 466 |
.gradio-container { max-width: 1250px !important; }
|
| 467 |
h1 { margin-bottom: 0.2rem !important; }
|
| 468 |
.anno-wrap{width:100%}.anno-toolbar{display:flex;gap:14px;flex-wrap:wrap;padding:10px 12px;margin-bottom:8px;border-radius:10px;background:#20242a}.anno-toolbar span{opacity:.85}.anno-canvas-wrap{width:100%;overflow:auto;border:1px solid #555;border-radius:10px;background:#111;padding:8px}.anno-canvas-wrap canvas{display:block;max-width:none;cursor:crosshair;touch-action:none;margin:auto}.anno-help{padding:8px 2px;opacity:.75}.anno-empty{padding:50px;text-align:center;border:1px dashed #777;border-radius:10px}
|
| 469 |
+
#annotation-image img { max-height: 650px !important; object-fit: contain !important; }
|
| 470 |
.status { padding: 10px 14px; border-radius: 10px; }
|
| 471 |
"""
|
| 472 |
|
|
|
|
| 491 |
# Class dropdown is updated after the Annotate tab creates it.
|
| 492 |
|
| 493 |
with gr.Tab("2 · Annotate"):
|
| 494 |
+
gr.Markdown("### Annotate training images")
|
| 495 |
+
gr.Markdown("Select an image above, choose a class, then **click the first corner and click the opposite corner** of each object. The real uploaded image is shown below. Click **Save Box** after each box.")
|
| 496 |
with gr.Row():
|
| 497 |
with gr.Column(scale=3):
|
| 498 |
+
annotation_image = gr.Image(value=None, type="pil", interactive=False, label="Training image", height=650, elem_id="annotation-image")
|
| 499 |
+
editor_info = gr.Markdown("Select an image from the Dataset tab.")
|
| 500 |
with gr.Column(scale=1):
|
| 501 |
+
ann_class = gr.Dropdown(choices=read_classes(), value=(read_classes()[0] if read_classes() else None), label="Class", interactive=True)
|
| 502 |
+
x = gr.Number(label="X (left)", value=0, precision=0)
|
| 503 |
+
y = gr.Number(label="Y (top)", value=0, precision=0)
|
| 504 |
+
w = gr.Number(label="Width", value=0, precision=0)
|
| 505 |
+
h = gr.Number(label="Height", value=0, precision=0)
|
| 506 |
add_btn = gr.Button("💾 Save Box", variant="primary")
|
| 507 |
+
gr.Markdown("**Box method:** click corner 1 → click corner 2 → Save Box.")
|
| 508 |
delete_index = gr.Number(label="Annotation # to delete", value=1, precision=0)
|
| 509 |
delete_btn = gr.Button("Delete Box")
|
| 510 |
clear_btn = gr.Button("Clear All Boxes")
|
| 511 |
annotations = gr.JSON(label="Saved annotations")
|
| 512 |
ann_msg = gr.Markdown()
|
| 513 |
+
click_state = gr.State([])
|
| 514 |
+
|
| 515 |
+
def load_annotation_image(image_id):
|
| 516 |
+
return annotation_preview_with_boxes(image_id), refresh_editor(image_id)[1], refresh_editor(image_id)[2], []
|
| 517 |
+
|
| 518 |
+
image_select.change(load_annotation_image, image_select, [annotation_image, editor_info, annotations, click_state])
|
| 519 |
+
annotation_image.select(handle_annotation_click, [image_select, ann_class, click_state], [x, y, w, h, click_state, ann_msg])
|
| 520 |
+
add_btn.click(add_annotation, [image_select, ann_class, x, y, w, h], [annotation_image, ann_msg, annotations])
|
| 521 |
+
delete_btn.click(remove_annotation, [image_select, delete_index], [annotation_image, ann_msg, annotations])
|
| 522 |
+
clear_btn.click(clear_annotations, image_select, [annotation_image, ann_msg, annotations])
|
| 523 |
|
| 524 |
with gr.Tab("3 · Training"):
|
| 525 |
gr.Markdown("### Train RT-DETR")
|
|
|
|
| 551 |
preprocess=False,
|
| 552 |
queue=False,
|
| 553 |
)
|
| 554 |
+
save_class_btn.click(save_classes, class_text, [class_msg, ann_class, status])
|
| 555 |
# The earlier placeholder event is harmlessly superseded by this real event.
|
| 556 |
|
| 557 |
+
demo.load(lambda: (dataset_status(), gr.update(choices=image_choices()), gr.update(choices=read_classes(), value=(read_classes()[0] if read_classes() else None))),
|
| 558 |
None, [status, image_select, ann_class])
|
| 559 |
|
| 560 |
if __name__ == "__main__":
|
training/classes.txt
CHANGED
|
@@ -1,8 +1,10 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
| 8 |
other
|
|
|
|
| 1 |
+
Carnavalita
|
| 2 |
+
Kimo-COno
|
| 3 |
+
Squizz
|
| 4 |
+
Oreo
|
| 5 |
+
Moro
|
| 6 |
+
Dulce
|
| 7 |
+
KitKat
|
| 8 |
+
Cadbury
|
| 9 |
+
Mega
|
| 10 |
other
|