Spaces:
Running on Zero
Running on Zero
Upload 4 files
Browse files- README.md +7 -9
- __pycache__/app.cpython-313.pyc +0 -0
- app.py +82 -52
README.md
CHANGED
|
@@ -145,15 +145,13 @@ The result image also shows the detected bounding boxes and confidence values.
|
|
| 145 |
## Default classes
|
| 146 |
|
| 147 |
```text
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
Cadbury
|
| 156 |
-
Mega
|
| 157 |
other
|
| 158 |
```
|
| 159 |
|
|
|
|
| 145 |
## Default classes
|
| 146 |
|
| 147 |
```text
|
| 148 |
+
cornetto
|
| 149 |
+
magnum
|
| 150 |
+
correto
|
| 151 |
+
cone
|
| 152 |
+
cup
|
| 153 |
+
sandwich
|
| 154 |
+
stick
|
|
|
|
|
|
|
| 155 |
other
|
| 156 |
```
|
| 157 |
|
__pycache__/app.cpython-313.pyc
ADDED
|
Binary file (37.8 kB). View file
|
|
|
app.py
CHANGED
|
@@ -148,44 +148,81 @@ def upload_training_images(files):
|
|
| 148 |
return dataset_status(), gr.update(choices=image_choices()), msg
|
| 149 |
|
| 150 |
|
| 151 |
-
def
|
| 152 |
-
|
| 153 |
-
return None, "Select an image.", [], None, None
|
| 154 |
-
data = load_dataset()
|
| 155 |
-
item = next((x for x in data["images"] if x["id"] == image_id), None)
|
| 156 |
-
if not item:
|
| 157 |
-
return None, "Image not found.", [], None, None
|
| 158 |
p = image_path(image_id)
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
item.get("annotations", []),
|
| 163 |
-
item["width"],
|
| 164 |
-
item["height"],
|
| 165 |
-
)
|
| 166 |
|
| 167 |
|
| 168 |
-
def
|
| 169 |
if not image_id:
|
| 170 |
-
return
|
| 171 |
-
p = image_path(image_id)
|
| 172 |
-
if not p.exists():
|
| 173 |
-
return None
|
| 174 |
-
image = Image.open(p).convert("RGB")
|
| 175 |
data = load_dataset()
|
| 176 |
item = next((x for x in data["images"] if x["id"] == image_id), None)
|
| 177 |
if not item:
|
| 178 |
-
return
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 189 |
|
| 190 |
def save_classes(text):
|
| 191 |
classes = [x.strip() for x in (text or "").splitlines() if x.strip()]
|
|
@@ -402,6 +439,7 @@ def count_image(image):
|
|
| 402 |
CSS = """
|
| 403 |
.gradio-container { max-width: 1250px !important; }
|
| 404 |
h1 { margin-bottom: 0.2rem !important; }
|
|
|
|
| 405 |
.status { padding: 10px 14px; border-radius: 10px; }
|
| 406 |
"""
|
| 407 |
|
|
@@ -426,38 +464,30 @@ with gr.Blocks(title="Ice Cream Dataset + Counter") as demo:
|
|
| 426 |
# Class dropdown is updated after the Annotate tab creates it.
|
| 427 |
|
| 428 |
with gr.Tab("2 · Annotate"):
|
| 429 |
-
gr.Markdown(
|
| 430 |
-
|
| 431 |
-
"The preview shows saved boxes. This is deliberately simple and works reliably inside Gradio Spaces."
|
| 432 |
-
)
|
| 433 |
with gr.Row():
|
| 434 |
-
with gr.Column(scale=
|
| 435 |
-
|
| 436 |
editor_info = gr.Markdown()
|
| 437 |
with gr.Column(scale=1):
|
| 438 |
ann_class = gr.Dropdown(choices=read_classes(), value=(read_classes()[0] if read_classes() else None), label="Class")
|
| 439 |
save_class_btn.click(save_classes, class_text, [class_msg, ann_class, status], preprocess=False)
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
add_btn = gr.Button("➕ Add Box", variant="primary")
|
| 447 |
delete_index = gr.Number(label="Annotation # to delete", value=1, precision=0)
|
| 448 |
delete_btn = gr.Button("Delete Box")
|
| 449 |
clear_btn = gr.Button("Clear All Boxes")
|
| 450 |
annotations = gr.JSON(label="Saved annotations")
|
| 451 |
ann_msg = gr.Markdown()
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
image_select.change(refresh_editor, image_select, [editor_image, editor_info, annotations])
|
| 458 |
-
add_btn.click(add_annotation, [image_select, ann_class, x, y, w, h], [editor_image, ann_msg, annotations, annotations])
|
| 459 |
-
delete_btn.click(remove_annotation, [image_select, delete_index], [editor_image, ann_msg, annotations])
|
| 460 |
-
clear_btn.click(clear_annotations, image_select, [editor_image, ann_msg, annotations])
|
| 461 |
|
| 462 |
with gr.Tab("3 · Training"):
|
| 463 |
gr.Markdown("### Train RT-DETR")
|
|
|
|
| 148 |
return dataset_status(), gr.update(choices=image_choices()), msg
|
| 149 |
|
| 150 |
|
| 151 |
+
def image_data_uri(image_id):
|
| 152 |
+
import base64
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 153 |
p = image_path(image_id)
|
| 154 |
+
if not p.exists():
|
| 155 |
+
return ""
|
| 156 |
+
return "data:image/jpeg;base64," + base64.b64encode(p.read_bytes()).decode("ascii")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
|
| 159 |
+
def annotation_canvas_html(image_id):
|
| 160 |
if not image_id:
|
| 161 |
+
return '<div class="anno-empty">Select an image from the Dataset tab.</div>'
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
data = load_dataset()
|
| 163 |
item = next((x for x in data["images"] if x["id"] == image_id), None)
|
| 164 |
if not item:
|
| 165 |
+
return '<div class="anno-empty">Image not found.</div>'
|
| 166 |
+
src = image_data_uri(image_id)
|
| 167 |
+
boxes = json.dumps(item.get("annotations", []), ensure_ascii=False)
|
| 168 |
+
return """<div class="anno-wrap">
|
| 169 |
+
<div class="anno-toolbar"><b>Draw boxes directly on the image</b><span>Click + drag + release = create box</span><span>Choose the class first</span></div>
|
| 170 |
+
<div class="anno-canvas-wrap"><canvas id="anno-canvas"></canvas></div>
|
| 171 |
+
<div class="anno-help">Drag from one corner of the object to the opposite corner, then release. Repeat for every object.</div>
|
| 172 |
+
</div>
|
| 173 |
+
<script>
|
| 174 |
+
(function(){
|
| 175 |
+
const imgSrc=%s, imageW=%d, imageH=%d, saved=%s;
|
| 176 |
+
const canvas=document.getElementById('anno-canvas'); if(!canvas)return;
|
| 177 |
+
const ctx=canvas.getContext('2d'), img=new Image(); let drawing=false,sx=0,sy=0,current=null;
|
| 178 |
+
function fit(){const maxW=Math.min(1100,window.innerWidth-80),maxH=Math.max(400,window.innerHeight*.62),scale=Math.min(maxW/imageW,maxH/imageH,1);canvas.width=Math.max(1,Math.round(imageW*scale));canvas.height=Math.max(1,Math.round(imageH*scale));canvas.dataset.scale=scale;redraw();}
|
| 179 |
+
function redraw(){if(!img.complete)return;const sc=+canvas.dataset.scale||1;ctx.clearRect(0,0,canvas.width,canvas.height);ctx.drawImage(img,0,0,canvas.width,canvas.height);saved.forEach((a,i)=>{const b=a.box||[];const x=b[0]*sc,y=b[1]*sc,w=b[2]*sc,h=b[3]*sc;ctx.strokeStyle='#ff3030';ctx.lineWidth=3;ctx.strokeRect(x,y,w,h);ctx.fillStyle='#ff3030';ctx.fillRect(x,Math.max(0,y-22),120,22);ctx.fillStyle='#fff';ctx.font='14px sans-serif';ctx.fillText((i+1)+'. '+a.class,x+5,Math.max(16,y-6));});if(current){ctx.strokeStyle='#00ff88';ctx.lineWidth=3;ctx.setLineDash([7,5]);ctx.strokeRect(current.x,current.y,current.w,current.h);ctx.setLineDash([]);}}
|
| 180 |
+
function pos(e){const r=canvas.getBoundingClientRect();return{x:e.clientX-r.left,y:e.clientY-r.top};}
|
| 181 |
+
function setField(id,val){const box=document.querySelector('#'+id);const el=box?.querySelector('input,textarea');if(!el)return;const setter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value')?.set||Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype,'value')?.set;if(setter)setter.call(el,String(val));else el.value=String(val);el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}));}
|
| 182 |
+
canvas.addEventListener('pointerdown',e=>{e.preventDefault();canvas.setPointerCapture(e.pointerId);const p=pos(e);sx=p.x;sy=p.y;drawing=true;current={x:sx,y:sy,w:0,h:0};redraw();});
|
| 183 |
+
canvas.addEventListener('pointermove',e=>{if(!drawing)return;const p=pos(e);current={x:Math.min(sx,p.x),y:Math.min(sy,p.y),w:Math.abs(p.x-sx),h:Math.abs(p.y-sy)};redraw();});
|
| 184 |
+
canvas.addEventListener('pointerup',e=>{if(!drawing)return;drawing=false;const p=pos(e),sc=+canvas.dataset.scale||1;const x=Math.min(sx,p.x)/sc,y=Math.min(sy,p.y)/sc,w=Math.abs(p.x-sx)/sc,h=Math.abs(p.y-sy)/sc;current=null;redraw();if(w<3||h<3)return;setField('anno-x',Math.round(x));setField('anno-y',Math.round(y));setField('anno-w',Math.round(w));setField('anno-h',Math.round(h));const hint=document.getElementById('anno-hint');if(hint)hint.textContent='Box created. Click “Save Box” to store it.';});
|
| 185 |
+
img.onload=fit;img.src=imgSrc;window.addEventListener('resize',fit);
|
| 186 |
+
})();</script>""" % (json.dumps(src), int(item["width"]), int(item["height"]), boxes)
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def refresh_editor(image_id):
|
| 190 |
+
if not image_id:
|
| 191 |
+
return annotation_canvas_html(None), "Select an image.", []
|
| 192 |
+
data=load_dataset(); item=next((x for x in data["images"] if x["id"]==image_id),None)
|
| 193 |
+
if not item:return annotation_canvas_html(None),"Image not found.",[]
|
| 194 |
+
return annotation_canvas_html(image_id),f"**{item['filename']}** — {item['width']} × {item['height']} px",item.get("annotations",[])
|
| 195 |
+
|
| 196 |
|
| 197 |
+
def add_annotation(image_id, cls, x, y, w, h):
|
| 198 |
+
if not image_id:return annotation_canvas_html(None),"Select an image first.",[]
|
| 199 |
+
if not cls:return annotation_canvas_html(image_id),"Select a class first.",[]
|
| 200 |
+
try:x,y,w,h=map(float,[x,y,w,h])
|
| 201 |
+
except:return annotation_canvas_html(image_id),"Draw a box on the image first.",[]
|
| 202 |
+
if w<=0 or h<=0:return annotation_canvas_html(image_id),"Box must have a width and height.",[]
|
| 203 |
+
data=load_dataset();item=next((z for z in data["images"] if z["id"]==image_id),None)
|
| 204 |
+
if not item:return annotation_canvas_html(None),"Image not found.",[]
|
| 205 |
+
x=max(0,min(x,item["width"]-1));y=max(0,min(y,item["height"]-1));w=min(w,item["width"]-x);h=min(h,item["height"]-y)
|
| 206 |
+
item.setdefault("annotations",[]).append({"class":cls,"box":[x,y,w,h]});save_dataset(data)
|
| 207 |
+
return annotation_canvas_html(image_id),f"Saved {cls}: [{x:.0f}, {y:.0f}, {w:.0f}, {h:.0f}]",item["annotations"]
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def remove_annotation(image_id,index):
|
| 211 |
+
if not image_id:return annotation_canvas_html(None),"Select an image first.",[]
|
| 212 |
+
data=load_dataset();item=next((z for z in data["images"] if z["id"]==image_id),None)
|
| 213 |
+
if not item:return annotation_canvas_html(None),"Image not found.",[]
|
| 214 |
+
try:idx=int(index)-1
|
| 215 |
+
except:return annotation_canvas_html(image_id),"Enter an annotation number.",item.get("annotations",[])
|
| 216 |
+
anns=item.get("annotations",[])
|
| 217 |
+
if idx<0 or idx>=len(anns):return annotation_canvas_html(image_id),"Annotation number not found.",anns
|
| 218 |
+
deleted=anns.pop(idx);save_dataset(data);return annotation_canvas_html(image_id),f"Deleted annotation {index}: {deleted['class']}",anns
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def clear_annotations(image_id):
|
| 222 |
+
if not image_id:return annotation_canvas_html(None),"Select an image first.",[]
|
| 223 |
+
data=load_dataset();item=next((z for z in data["images"] if z["id"]==image_id),None)
|
| 224 |
+
if not item:return annotation_canvas_html(None),"Image not found.",[]
|
| 225 |
+
item["annotations"]=[];save_dataset(data);return annotation_canvas_html(image_id),"Annotations cleared.",[]
|
| 226 |
|
| 227 |
def save_classes(text):
|
| 228 |
classes = [x.strip() for x in (text or "").splitlines() if x.strip()]
|
|
|
|
| 439 |
CSS = """
|
| 440 |
.gradio-container { max-width: 1250px !important; }
|
| 441 |
h1 { margin-bottom: 0.2rem !important; }
|
| 442 |
+
.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}
|
| 443 |
.status { padding: 10px 14px; border-radius: 10px; }
|
| 444 |
"""
|
| 445 |
|
|
|
|
| 464 |
# Class dropdown is updated after the Annotate tab creates it.
|
| 465 |
|
| 466 |
with gr.Tab("2 · Annotate"):
|
| 467 |
+
gr.Markdown("### Draw bounding boxes directly on the image")
|
| 468 |
+
gr.Markdown("**How to use:** select a class → **click and drag** around the object → **release** → click **Save Box**. Repeat for every object.")
|
|
|
|
|
|
|
| 469 |
with gr.Row():
|
| 470 |
+
with gr.Column(scale=3):
|
| 471 |
+
annotation_canvas = gr.HTML(annotation_canvas_html(None), elem_id="annotation-canvas-panel")
|
| 472 |
editor_info = gr.Markdown()
|
| 473 |
with gr.Column(scale=1):
|
| 474 |
ann_class = gr.Dropdown(choices=read_classes(), value=(read_classes()[0] if read_classes() else None), label="Class")
|
| 475 |
save_class_btn.click(save_classes, class_text, [class_msg, ann_class, status], preprocess=False)
|
| 476 |
+
x = gr.Number(label="X", value=0, elem_id="anno-x")
|
| 477 |
+
y = gr.Number(label="Y", value=0, elem_id="anno-y")
|
| 478 |
+
w = gr.Number(label="Width", value=0, elem_id="anno-w")
|
| 479 |
+
h = gr.Number(label="Height", value=0, elem_id="anno-h")
|
| 480 |
+
add_btn = gr.Button("💾 Save Box", variant="primary")
|
| 481 |
+
gr.Markdown('<div id="anno-hint">Draw a box on the image.</div>')
|
|
|
|
| 482 |
delete_index = gr.Number(label="Annotation # to delete", value=1, precision=0)
|
| 483 |
delete_btn = gr.Button("Delete Box")
|
| 484 |
clear_btn = gr.Button("Clear All Boxes")
|
| 485 |
annotations = gr.JSON(label="Saved annotations")
|
| 486 |
ann_msg = gr.Markdown()
|
| 487 |
+
image_select.change(refresh_editor, image_select, [annotation_canvas, editor_info, annotations])
|
| 488 |
+
add_btn.click(add_annotation, [image_select, ann_class, x, y, w, h], [annotation_canvas, ann_msg, annotations])
|
| 489 |
+
delete_btn.click(remove_annotation, [image_select, delete_index], [annotation_canvas, ann_msg, annotations])
|
| 490 |
+
clear_btn.click(clear_annotations, image_select, [annotation_canvas, ann_msg, annotations])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
|
| 492 |
with gr.Tab("3 · Training"):
|
| 493 |
gr.Markdown("### Train RT-DETR")
|