ibsocr1 commited on
Commit
e7889c3
·
verified ·
1 Parent(s): 84d6aee

Upload 3 files

Browse files
Files changed (2) hide show
  1. README.md +21 -18
  2. app.py +79 -37
README.md CHANGED
@@ -89,22 +89,23 @@ The Gradio conversion itself does not change this storage rule.
89
 
90
  ## Annotation workflow
91
 
92
- In the **Annotate** tab:
93
 
94
  1. Select a training image.
95
- 2. Select the class.
96
- 3. Enter the bounding box in the original image's pixel coordinates:
97
- - X
98
- - Y
 
 
99
  - Width
100
  - Height
101
- 4. Click **Add Box**.
102
- 5. Repeat for every ice cream.
103
- 6. Use **Delete Box** or **Clear All Boxes** when needed.
 
104
 
105
- The preview displays the saved boxes.
106
-
107
- This coordinate-based annotation UI is intentionally implemented entirely in Gradio/Python so it does not depend on a custom JavaScript/FastAPI frontend.
108
 
109
  ## Training
110
 
@@ -145,13 +146,15 @@ The result image also shows the detected bounding boxes and confidence values.
145
  ## Default classes
146
 
147
  ```text
148
- cornetto
149
- magnum
150
- correto
151
- cone
152
- cup
153
- sandwich
154
- stick
 
 
155
  other
156
  ```
157
 
 
89
 
90
  ## Annotation workflow
91
 
92
+ In the **Annotate** tab the actual uploaded training image is displayed directly using Gradio's Image component. This avoids browser canvas/JavaScript issues that can make the preview appear black.
93
 
94
  1. Select a training image.
95
+ 2. The real image appears in the preview.
96
+ 3. Read the image dimensions shown below it.
97
+ 4. Select the product class.
98
+ 5. Enter the bounding box in original-image pixel coordinates:
99
+ - X (left)
100
+ - Y (top)
101
  - Width
102
  - Height
103
+ 6. Click **Save Box**.
104
+ 7. Saved boxes are drawn in red on the real image.
105
+ 8. Repeat for every ice cream.
106
+ 9. Use **Delete Box** or **Clear All Boxes** when needed.
107
 
108
+ This version prioritizes a reliable visible image over the previous JavaScript canvas approach.
 
 
109
 
110
  ## Training
111
 
 
146
  ## Default classes
147
 
148
  ```text
149
+ Carnavalita
150
+ Kimo-COno
151
+ Squizz
152
+ Oreo
153
+ Moro
154
+ Dulce
155
+ KitKat
156
+ Cadbury
157
+ Mega
158
  other
159
  ```
160
 
app.py CHANGED
@@ -186,48 +186,91 @@ 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 draw_annotations(image_id):
198
  """Render the annotation canvas for the selected image."""
199
  return annotation_canvas_html(image_id)
200
 
201
 
202
  def add_annotation(image_id, cls, x, y, w, h):
203
- if not image_id:return annotation_canvas_html(None),"Select an image first.",[]
204
- if not cls:return annotation_canvas_html(image_id),"Select a class first.",[]
205
  try:x,y,w,h=map(float,[x,y,w,h])
206
- except:return annotation_canvas_html(image_id),"Draw a box on the image first.",[]
207
- if w<=0 or h<=0:return annotation_canvas_html(image_id),"Box must have a width and height.",[]
208
- data=load_dataset();item=next((z for z in data["images"] if z["id"]==image_id),None)
209
- if not item:return annotation_canvas_html(None),"Image not found.",[]
210
- 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)
211
- item.setdefault("annotations",[]).append({"class":cls,"box":[x,y,w,h]});save_dataset(data)
212
- return annotation_canvas_html(image_id),f"Saved {cls}: [{x:.0f}, {y:.0f}, {w:.0f}, {h:.0f}]",item["annotations"]
 
 
 
213
 
214
 
215
  def remove_annotation(image_id,index):
216
- if not image_id:return annotation_canvas_html(None),"Select an image first.",[]
217
- data=load_dataset();item=next((z for z in data["images"] if z["id"]==image_id),None)
218
- if not item:return annotation_canvas_html(None),"Image not found.",[]
 
219
  try:idx=int(index)-1
220
- except:return annotation_canvas_html(image_id),"Enter an annotation number.",item.get("annotations",[])
221
  anns=item.get("annotations",[])
222
- if idx<0 or idx>=len(anns):return annotation_canvas_html(image_id),"Annotation number not found.",anns
223
- deleted=anns.pop(idx);save_dataset(data);return annotation_canvas_html(image_id),f"Deleted annotation {index}: {deleted['class']}",anns
 
224
 
225
 
226
  def clear_annotations(image_id):
227
- if not image_id:return annotation_canvas_html(None),"Select an image first.",[]
228
- data=load_dataset();item=next((z for z in data["images"] if z["id"]==image_id),None)
229
- if not item:return annotation_canvas_html(None),"Image not found.",[]
230
- item["annotations"]=[];save_dataset(data);return annotation_canvas_html(image_id),"Annotations cleared.",[]
 
 
 
231
 
232
  def save_classes(text):
233
  classes = [x.strip() for x in (text or "").splitlines() if x.strip()]
@@ -413,30 +456,29 @@ with gr.Blocks(title="Ice Cream Dataset + Counter") as demo:
413
  # Class dropdown is updated after the Annotate tab creates it.
414
 
415
  with gr.Tab("2 · Annotate"):
416
- gr.Markdown("### Draw bounding boxes directly on the image")
417
- gr.Markdown("**How to use:** select a class **click and drag** around the object **release** click **Save Box**. Repeat for every object.")
418
  with gr.Row():
419
  with gr.Column(scale=3):
420
- annotation_canvas = gr.HTML(annotation_canvas_html(None), elem_id="annotation-canvas-panel")
421
  editor_info = gr.Markdown()
422
  with gr.Column(scale=1):
423
  ann_class = gr.Dropdown(choices=read_classes(), value=(read_classes()[0] if read_classes() else None), label="Class")
424
- save_class_btn.click(save_classes, class_text, [class_msg, ann_class, status], preprocess=False)
425
- x = gr.Number(label="X", value=0, elem_id="anno-x")
426
- y = gr.Number(label="Y", value=0, elem_id="anno-y")
427
- w = gr.Number(label="Width", value=0, elem_id="anno-w")
428
- h = gr.Number(label="Height", value=0, elem_id="anno-h")
429
  add_btn = gr.Button("💾 Save Box", variant="primary")
430
- gr.Markdown('<div id="anno-hint">Draw a box on the image.</div>')
431
  delete_index = gr.Number(label="Annotation # to delete", value=1, precision=0)
432
  delete_btn = gr.Button("Delete Box")
433
  clear_btn = gr.Button("Clear All Boxes")
434
  annotations = gr.JSON(label="Saved annotations")
435
  ann_msg = gr.Markdown()
436
- image_select.change(refresh_editor, image_select, [annotation_canvas, editor_info, annotations])
437
- add_btn.click(add_annotation, [image_select, ann_class, x, y, w, h], [annotation_canvas, ann_msg, annotations])
438
- delete_btn.click(remove_annotation, [image_select, delete_index], [annotation_canvas, ann_msg, annotations])
439
- clear_btn.click(clear_annotations, image_select, [annotation_canvas, ann_msg, annotations])
440
 
441
  with gr.Tab("3 · Training"):
442
  gr.Markdown("### Train RT-DETR")
 
186
  })();</script>""" % (json.dumps(src), int(item["width"]), int(item["height"]), boxes)
187
 
188
 
189
+
190
+ def annotation_preview_image(image_id):
191
+ """Return the real PIL image for Gradio's Image component."""
192
  if not image_id:
193
+ return None
194
+ p = image_path(image_id)
195
+ if not p.exists():
196
+ return None
197
+ try:
198
+ return Image.open(p).convert("RGB")
199
+ except Exception:
200
+ return None
201
+
202
+
203
+ def annotation_preview_with_boxes(image_id):
204
+ image = annotation_preview_image(image_id)
205
+ if image is None:
206
+ return None
207
+ data = load_dataset()
208
+ item = next((x for x in data["images"] if x["id"] == image_id), None)
209
+ if not item:
210
+ return image
211
+ out = image.copy()
212
+ draw = ImageDraw.Draw(out)
213
+ for i, a in enumerate(item.get("annotations", []), 1):
214
+ x, y, w, h = a["box"]
215
+ draw.rectangle([x, y, x+w, y+h], outline="red", width=5)
216
+ label = f"{i}. {a['class']}"
217
+ y0 = max(0, y-24)
218
+ draw.rectangle([x, y0, x+max(130, len(label)*9), y0+24], fill="red")
219
+ draw.text((x+4, y0+4), label, fill="white")
220
+ return out
221
 
222
 
223
+ def refresh_editor(image_id):
224
+ if not image_id:
225
+ return None, "Select an image.", []
226
+ data=load_dataset()
227
+ item=next((x for x in data["images"] if x["id"]==image_id),None)
228
+ if not item:
229
+ return None,"Image not found.",[]
230
+ return annotation_preview_with_boxes(image_id), f"**{item['filename']}** — {item['width']} × {item['height']} px", item.get("annotations",[])
231
+
232
  def draw_annotations(image_id):
233
  """Render the annotation canvas for the selected image."""
234
  return annotation_canvas_html(image_id)
235
 
236
 
237
  def add_annotation(image_id, cls, x, y, w, h):
238
+ if not image_id:return None,"Select an image first.",[]
239
+ if not cls:return annotation_preview_with_boxes(image_id),"Select a class first.",[]
240
  try:x,y,w,h=map(float,[x,y,w,h])
241
+ except:return annotation_preview_with_boxes(image_id),"Enter box coordinates first.",[]
242
+ if w<=0 or h<=0:return annotation_preview_with_boxes(image_id),"Box must have a width and height.",[]
243
+ data=load_dataset()
244
+ item=next((z for z in data["images"] if z["id"]==image_id),None)
245
+ if not item:return None,"Image not found.",[]
246
+ x=max(0,min(x,item["width"]-1)); y=max(0,min(y,item["height"]-1))
247
+ w=min(w,item["width"]-x); h=min(h,item["height"]-y)
248
+ item.setdefault("annotations",[]).append({"class":cls,"box":[x,y,w,h]})
249
+ save_dataset(data)
250
+ return annotation_preview_with_boxes(image_id),f"Saved {cls}: [{x:.0f}, {y:.0f}, {w:.0f}, {h:.0f}]",item["annotations"]
251
 
252
 
253
  def remove_annotation(image_id,index):
254
+ if not image_id:return None,"Select an image first.",[]
255
+ data=load_dataset()
256
+ item=next((z for z in data["images"] if z["id"]==image_id),None)
257
+ if not item:return None,"Image not found.",[]
258
  try:idx=int(index)-1
259
+ except:return annotation_preview_with_boxes(image_id),"Enter an annotation number.",item.get("annotations",[])
260
  anns=item.get("annotations",[])
261
+ if idx<0 or idx>=len(anns):return annotation_preview_with_boxes(image_id),"Annotation number not found.",anns
262
+ deleted=anns.pop(idx);save_dataset(data)
263
+ return annotation_preview_with_boxes(image_id),f"Deleted annotation {index}: {deleted['class']}",anns
264
 
265
 
266
  def clear_annotations(image_id):
267
+ if not image_id:return None,"Select an image first.",[]
268
+ data=load_dataset()
269
+ item=next((z for z in data["images"] if z["id"]==image_id),None)
270
+ if not item:return None,"Image not found.",[]
271
+ item["annotations"]=[];save_dataset(data)
272
+ return annotation_preview_image(image_id),"Annotations cleared.",[]
273
+
274
 
275
  def save_classes(text):
276
  classes = [x.strip() for x in (text or "").splitlines() if x.strip()]
 
456
  # Class dropdown is updated after the Annotate tab creates it.
457
 
458
  with gr.Tab("2 · Annotate"):
459
+ gr.Markdown("### See the real training image and annotate it")
460
+ gr.Markdown("The actual uploaded photo is displayed below. Select a class and enter the box coordinates in the original image pixels, then click **Save Box**. Saved boxes appear in red.")
461
  with gr.Row():
462
  with gr.Column(scale=3):
463
+ annotation_preview = gr.Image(label="Training image", type="pil", interactive=False, height=650)
464
  editor_info = gr.Markdown()
465
  with gr.Column(scale=1):
466
  ann_class = gr.Dropdown(choices=read_classes(), value=(read_classes()[0] if read_classes() else None), label="Class")
467
+ x = gr.Number(label="X (left)", value=0, precision=0)
468
+ y = gr.Number(label="Y (top)", value=0, precision=0)
469
+ w = gr.Number(label="Width", value=0, precision=0)
470
+ h = gr.Number(label="Height", value=0, precision=0)
 
471
  add_btn = gr.Button("💾 Save Box", variant="primary")
472
+ gr.Markdown("Example: X=120, Y=80, Width=180, Height=300.")
473
  delete_index = gr.Number(label="Annotation # to delete", value=1, precision=0)
474
  delete_btn = gr.Button("Delete Box")
475
  clear_btn = gr.Button("Clear All Boxes")
476
  annotations = gr.JSON(label="Saved annotations")
477
  ann_msg = gr.Markdown()
478
+ image_select.change(refresh_editor, image_select, [annotation_preview, editor_info, annotations])
479
+ add_btn.click(add_annotation, [image_select, ann_class, x, y, w, h], [annotation_preview, ann_msg, annotations])
480
+ delete_btn.click(remove_annotation, [image_select, delete_index], [annotation_preview, ann_msg, annotations])
481
+ clear_btn.click(clear_annotations, image_select, [annotation_preview, ann_msg, annotations])
482
 
483
  with gr.Tab("3 · Training"):
484
  gr.Markdown("### Train RT-DETR")