ibsocr1 commited on
Commit
2ac4faa
·
verified ·
1 Parent(s): ede824e

Upload 4 files

Browse files
Files changed (3) hide show
  1. README.md +7 -9
  2. __pycache__/app.cpython-313.pyc +0 -0
  3. 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
- Carnavalita
149
- Kimo-COno
150
- Squizz
151
- Oreo
152
- Moro
153
- Dulce
154
- KitKat
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 load_editor(image_id):
152
- if not image_id:
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
- return (
160
- str(p),
161
- f"**{item['filename']}** {item['width']} × {item['height']} px",
162
- item.get("annotations", []),
163
- item["width"],
164
- item["height"],
165
- )
166
 
167
 
168
- def draw_annotations(image_id):
169
  if not image_id:
170
- return None
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 image
179
- draw = ImageDraw.Draw(image)
180
- for i, a in enumerate(item.get("annotations", []), 1):
181
- x, y, w, h = a["box"]
182
- color = "red"
183
- draw.rectangle([x, y, x+w, y+h], outline=color, width=4)
184
- label = f"{i}. {a['class']}"
185
- draw.rectangle([x, max(0, y-22), x+max(100, len(label)*8), y], fill=color)
186
- draw.text((x+3, max(0, y-20)), label, fill="white")
187
- return image
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- "Select an image. To create a box, enter **X, Y, Width, Height** in original image pixels. "
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=2):
435
- editor_image = gr.Image(label="Training image", type="pil", interactive=False)
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
- with gr.Row():
441
- x = gr.Number(label="X", value=0)
442
- y = gr.Number(label="Y", value=0)
443
- with gr.Row():
444
- w = gr.Number(label="Width", value=100)
445
- h = gr.Number(label="Height", value=100)
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
- def refresh_editor(image_id):
454
- img, info, anns, _, _ = load_editor(image_id)
455
- return draw_annotations(image_id), info, anns
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")