Spaces:
Running on Zero
Running on Zero
update app
Browse files
app.py
CHANGED
|
@@ -104,8 +104,8 @@ except Exception as e:
|
|
| 104 |
|
| 105 |
ADAPTER_SPECS = {
|
| 106 |
"Object-Remover": {
|
| 107 |
-
"repo": "prithivMLmods/QIE-
|
| 108 |
-
"weights": "
|
| 109 |
"adapter_name": "object-remover",
|
| 110 |
},
|
| 111 |
}
|
|
@@ -115,6 +115,7 @@ DEFAULT_PROMPT = "Remove the red highlighted object from the scene"
|
|
| 115 |
|
| 116 |
|
| 117 |
def burn_boxes_onto_image(pil_image: Image.Image, boxes_json_str: str) -> Image.Image:
|
|
|
|
| 118 |
import json
|
| 119 |
if not pil_image:
|
| 120 |
return pil_image
|
|
@@ -125,10 +126,9 @@ def burn_boxes_onto_image(pil_image: Image.Image, boxes_json_str: str) -> Image.
|
|
| 125 |
if not boxes:
|
| 126 |
return pil_image
|
| 127 |
|
| 128 |
-
img = pil_image.copy().convert("
|
| 129 |
w, h = img.size
|
| 130 |
-
|
| 131 |
-
draw = ImageDraw.Draw(overlay)
|
| 132 |
bw = max(3, w // 250)
|
| 133 |
|
| 134 |
for b in boxes:
|
|
@@ -138,10 +138,10 @@ def burn_boxes_onto_image(pil_image: Image.Image, boxes_json_str: str) -> Image.
|
|
| 138 |
y2 = int(b["y2"] * h)
|
| 139 |
lx, rx = min(x1, x2), max(x1, x2)
|
| 140 |
ty, by_ = min(y1, y2), max(y1, y2)
|
| 141 |
-
|
| 142 |
-
draw.rectangle([lx, ty, rx, by_], outline=(255, 0, 0
|
| 143 |
|
| 144 |
-
return
|
| 145 |
|
| 146 |
|
| 147 |
@spaces.GPU
|
|
@@ -341,7 +341,6 @@ label{font-weight:600!important;color:#333!important}
|
|
| 341 |
}
|
| 342 |
.dark #bbox-debug-count{color:#C084FC;background:rgba(168,85,247,.12)}
|
| 343 |
|
| 344 |
-
/* ===== FIX: hide the sync textbox visually but keep it in the DOM ===== */
|
| 345 |
#boxes-json-input{
|
| 346 |
max-height:0!important;
|
| 347 |
overflow:hidden!important;
|
|
@@ -372,7 +371,7 @@ bbox_drawer_js = r"""
|
|
| 372 |
const btnClear = document.getElementById('tb-clear');
|
| 373 |
|
| 374 |
if (!canvas || !wrap || !debugCount || !btnDraw) {
|
| 375 |
-
console.log('[BBox] waiting for DOM
|
| 376 |
setTimeout(initCanvasBbox, 250);
|
| 377 |
return;
|
| 378 |
}
|
|
@@ -394,8 +393,10 @@ bbox_drawer_js = r"""
|
|
| 394 |
let dragStart = {x:0, y:0};
|
| 395 |
let dragOrig = null;
|
| 396 |
const HANDLE = 7;
|
|
|
|
|
|
|
|
|
|
| 397 |
|
| 398 |
-
/* ── helpers ── */
|
| 399 |
function n2px(b) {
|
| 400 |
return {x1:b.x1*dispW, y1:b.y1*dispH, x2:b.x2*dispW, y2:b.y2*dispH};
|
| 401 |
}
|
|
@@ -422,12 +423,10 @@ bbox_drawer_js = r"""
|
|
| 422 |
y: Math.max(0,Math.min(dispH, cy-r.top))};
|
| 423 |
}
|
| 424 |
|
| 425 |
-
/* ── FIX: always update debug + badge, then try textbox ── */
|
| 426 |
function syncToGradio() {
|
| 427 |
window.__bboxBoxes = boxes;
|
| 428 |
const jsonStr = JSON.stringify(boxes);
|
| 429 |
|
| 430 |
-
/* 1) update on-screen debug no matter what */
|
| 431 |
if (debugCount) {
|
| 432 |
debugCount.textContent = boxes.length > 0
|
| 433 |
? '\u2705 ' + boxes.length + ' box' + (boxes.length > 1 ? 'es' : '') +
|
|
@@ -436,10 +435,9 @@ bbox_drawer_js = r"""
|
|
| 436 |
: '\u2B1C No boxes drawn yet';
|
| 437 |
}
|
| 438 |
|
| 439 |
-
/* 2) try to push into the Gradio Textbox (best-effort) */
|
| 440 |
const container = document.getElementById('boxes-json-input');
|
| 441 |
if (!container) {
|
| 442 |
-
console.warn('[BBox] #boxes-json-input not in DOM
|
| 443 |
return;
|
| 444 |
}
|
| 445 |
const targets = [
|
|
@@ -461,7 +459,6 @@ bbox_drawer_js = r"""
|
|
| 461 |
});
|
| 462 |
}
|
| 463 |
|
| 464 |
-
/* ── draw ── */
|
| 465 |
function placeholder() {
|
| 466 |
ctx.fillStyle='#2a2a2a'; ctx.fillRect(0,0,dispW,dispH);
|
| 467 |
ctx.strokeStyle='#444'; ctx.lineWidth=2; ctx.setLineDash([8,4]);
|
|
@@ -482,19 +479,20 @@ bbox_drawer_js = r"""
|
|
| 482 |
const p = n2px(b);
|
| 483 |
const lx=p.x1, ty=p.y1, w=p.x2-p.x1, h=p.y2-p.y1;
|
| 484 |
|
| 485 |
-
|
| 486 |
-
ctx.fillRect(lx,ty,w,h);
|
| 487 |
-
|
| 488 |
if (i === selectedIdx) {
|
| 489 |
-
ctx.strokeStyle =
|
| 490 |
-
ctx.lineWidth =
|
|
|
|
| 491 |
} else {
|
| 492 |
-
ctx.strokeStyle =
|
| 493 |
-
ctx.lineWidth =
|
|
|
|
| 494 |
}
|
| 495 |
-
ctx.strokeRect(lx,ty,w,h);
|
| 496 |
ctx.setLineDash([]);
|
| 497 |
|
|
|
|
| 498 |
ctx.fillStyle = i===selectedIdx ? 'rgba(0,120,255,0.85)' : 'rgba(255,0,0,0.85)';
|
| 499 |
ctx.font = 'bold 11px IBM Plex Mono,monospace';
|
| 500 |
ctx.textAlign = 'left'; ctx.textBaseline = 'top';
|
|
@@ -507,16 +505,17 @@ bbox_drawer_js = r"""
|
|
| 507 |
if (i === selectedIdx) drawHandles(p);
|
| 508 |
});
|
| 509 |
|
|
|
|
| 510 |
if (tempRect) {
|
| 511 |
const rx = Math.min(tempRect.x1,tempRect.x2);
|
| 512 |
const ry = Math.min(tempRect.y1,tempRect.y2);
|
| 513 |
const rw = Math.abs(tempRect.x2-tempRect.x1);
|
| 514 |
const rh = Math.abs(tempRect.y2-tempRect.y1);
|
| 515 |
-
ctx.
|
| 516 |
-
ctx.
|
| 517 |
-
ctx.
|
| 518 |
-
ctx.
|
| 519 |
-
ctx.
|
| 520 |
}
|
| 521 |
updateBadge();
|
| 522 |
}
|
|
@@ -582,7 +581,6 @@ bbox_drawer_js = r"""
|
|
| 582 |
}
|
| 583 |
function hideStatus() { status.style.display = 'none'; }
|
| 584 |
|
| 585 |
-
/* ── pointer events ── */
|
| 586 |
function onDown(e) {
|
| 587 |
if (!baseImg) return;
|
| 588 |
e.preventDefault();
|
|
@@ -711,7 +709,6 @@ bbox_drawer_js = r"""
|
|
| 711 |
canvas.addEventListener('touchend', onUp, {passive:false});
|
| 712 |
canvas.addEventListener('touchcancel',(e)=>{e.preventDefault();dragging=false;redraw();},{passive:false});
|
| 713 |
|
| 714 |
-
/* ── toolbar ── */
|
| 715 |
btnDraw.addEventListener('click', ()=>setMode('draw'));
|
| 716 |
btnSelect.addEventListener('click', ()=>setMode('select'));
|
| 717 |
|
|
@@ -739,16 +736,14 @@ bbox_drawer_js = r"""
|
|
| 739 |
});
|
| 740 |
|
| 741 |
btnClear.addEventListener('click', () => {
|
| 742 |
-
boxes.length = 0;
|
| 743 |
window.__bboxBoxes = boxes;
|
| 744 |
selectedIdx = -1;
|
| 745 |
syncToGradio(); redraw(); hideStatus();
|
| 746 |
});
|
| 747 |
|
| 748 |
-
/* ── FIX: robust image polling with multiple selectors ── */
|
| 749 |
let lastSrc = null;
|
| 750 |
setInterval(() => {
|
| 751 |
-
/* try several selectors Gradio might use */
|
| 752 |
const imgs = document.querySelectorAll('#source-image-component img');
|
| 753 |
let el = null;
|
| 754 |
for (const img of imgs) {
|
|
@@ -794,7 +789,6 @@ bbox_drawer_js = r"""
|
|
| 794 |
}
|
| 795 |
}, 300);
|
| 796 |
|
| 797 |
-
/* periodic re-sync every 500 ms */
|
| 798 |
setInterval(() => { syncToGradio(); }, 500);
|
| 799 |
|
| 800 |
new ResizeObserver(() => {
|
|
@@ -811,10 +805,11 @@ bbox_drawer_js = r"""
|
|
| 811 |
"""
|
| 812 |
|
| 813 |
|
| 814 |
-
with gr.Blocks() as demo:
|
| 815 |
gr.Markdown("# **QIE-Object-Remover-Bbox**", elem_id="main-title")
|
| 816 |
gr.Markdown(
|
| 817 |
-
"Perform diverse image edits using a specialized [LoRA](https://huggingface.co/prithivMLmods/QIE-2511-Object-Remover-v2).
|
|
|
|
| 818 |
"Multiple boxes supported. Select, move, resize or delete individual boxes.",
|
| 819 |
elem_id="subtitle",
|
| 820 |
)
|
|
@@ -832,7 +827,7 @@ with gr.Blocks() as demo:
|
|
| 832 |
gr.HTML(
|
| 833 |
'<div class="bbox-hint">'
|
| 834 |
"<b>Draw mode:</b> Click & drag to create red rectangles. "
|
| 835 |
-
"<b>Select mode:</b> Click a box to select it
|
| 836 |
"drag handles to <b>resize</b>. Use <b>Delete Selected</b> to remove one box."
|
| 837 |
"</div>"
|
| 838 |
)
|
|
@@ -862,7 +857,7 @@ with gr.Blocks() as demo:
|
|
| 862 |
"""
|
| 863 |
)
|
| 864 |
|
| 865 |
-
gr.HTML('<div id="bbox-debug-count">
|
| 866 |
|
| 867 |
boxes_json = gr.Textbox(
|
| 868 |
value="[]",
|
|
@@ -879,7 +874,7 @@ with gr.Blocks() as demo:
|
|
| 879 |
info="Edit the prompt if needed",
|
| 880 |
)
|
| 881 |
|
| 882 |
-
run_btn = gr.Button("
|
| 883 |
|
| 884 |
with gr.Column(scale=1):
|
| 885 |
result = gr.Image(label="Output Image", height=449)
|
|
@@ -901,7 +896,7 @@ with gr.Blocks() as demo:
|
|
| 901 |
"[prithivMLmods](https://huggingface.co/prithivMLmods). "
|
| 902 |
"Adapter: [QIE-2511-Object-Remover-v2]"
|
| 903 |
"(https://huggingface.co/prithivMLmods/QIE-2511-Object-Remover-v2). "
|
| 904 |
-
"More adapters
|
| 905 |
"(https://huggingface.co/models?other=base_model:adapter:Qwen/Qwen-Image-Edit-2509)."
|
| 906 |
)
|
| 907 |
|
|
|
|
| 104 |
|
| 105 |
ADAPTER_SPECS = {
|
| 106 |
"Object-Remover": {
|
| 107 |
+
"repo": "prithivMLmods/QIE-2509-Object-Remover-Bbox",
|
| 108 |
+
"weights": "QIE-2509-Object-Remover-Bbox-5000.safetensors",
|
| 109 |
"adapter_name": "object-remover",
|
| 110 |
},
|
| 111 |
}
|
|
|
|
| 115 |
|
| 116 |
|
| 117 |
def burn_boxes_onto_image(pil_image: Image.Image, boxes_json_str: str) -> Image.Image:
|
| 118 |
+
"""Burn red outline-only rectangles onto the image (no fill)."""
|
| 119 |
import json
|
| 120 |
if not pil_image:
|
| 121 |
return pil_image
|
|
|
|
| 126 |
if not boxes:
|
| 127 |
return pil_image
|
| 128 |
|
| 129 |
+
img = pil_image.copy().convert("RGB")
|
| 130 |
w, h = img.size
|
| 131 |
+
draw = ImageDraw.Draw(img)
|
|
|
|
| 132 |
bw = max(3, w // 250)
|
| 133 |
|
| 134 |
for b in boxes:
|
|
|
|
| 138 |
y2 = int(b["y2"] * h)
|
| 139 |
lx, rx = min(x1, x2), max(x1, x2)
|
| 140 |
ty, by_ = min(y1, y2), max(y1, y2)
|
| 141 |
+
# Red outline only — no fill
|
| 142 |
+
draw.rectangle([lx, ty, rx, by_], outline=(255, 0, 0), width=bw)
|
| 143 |
|
| 144 |
+
return img
|
| 145 |
|
| 146 |
|
| 147 |
@spaces.GPU
|
|
|
|
| 341 |
}
|
| 342 |
.dark #bbox-debug-count{color:#C084FC;background:rgba(168,85,247,.12)}
|
| 343 |
|
|
|
|
| 344 |
#boxes-json-input{
|
| 345 |
max-height:0!important;
|
| 346 |
overflow:hidden!important;
|
|
|
|
| 371 |
const btnClear = document.getElementById('tb-clear');
|
| 372 |
|
| 373 |
if (!canvas || !wrap || !debugCount || !btnDraw) {
|
| 374 |
+
console.log('[BBox] waiting for DOM...');
|
| 375 |
setTimeout(initCanvasBbox, 250);
|
| 376 |
return;
|
| 377 |
}
|
|
|
|
| 393 |
let dragStart = {x:0, y:0};
|
| 394 |
let dragOrig = null;
|
| 395 |
const HANDLE = 7;
|
| 396 |
+
const RED_STROKE = 'rgba(255,0,0,0.95)';
|
| 397 |
+
const RED_STROKE_WIDTH = 3;
|
| 398 |
+
const SEL_STROKE = 'rgba(0,120,255,0.95)';
|
| 399 |
|
|
|
|
| 400 |
function n2px(b) {
|
| 401 |
return {x1:b.x1*dispW, y1:b.y1*dispH, x2:b.x2*dispW, y2:b.y2*dispH};
|
| 402 |
}
|
|
|
|
| 423 |
y: Math.max(0,Math.min(dispH, cy-r.top))};
|
| 424 |
}
|
| 425 |
|
|
|
|
| 426 |
function syncToGradio() {
|
| 427 |
window.__bboxBoxes = boxes;
|
| 428 |
const jsonStr = JSON.stringify(boxes);
|
| 429 |
|
|
|
|
| 430 |
if (debugCount) {
|
| 431 |
debugCount.textContent = boxes.length > 0
|
| 432 |
? '\u2705 ' + boxes.length + ' box' + (boxes.length > 1 ? 'es' : '') +
|
|
|
|
| 435 |
: '\u2B1C No boxes drawn yet';
|
| 436 |
}
|
| 437 |
|
|
|
|
| 438 |
const container = document.getElementById('boxes-json-input');
|
| 439 |
if (!container) {
|
| 440 |
+
console.warn('[BBox] #boxes-json-input not in DOM');
|
| 441 |
return;
|
| 442 |
}
|
| 443 |
const targets = [
|
|
|
|
| 459 |
});
|
| 460 |
}
|
| 461 |
|
|
|
|
| 462 |
function placeholder() {
|
| 463 |
ctx.fillStyle='#2a2a2a'; ctx.fillRect(0,0,dispW,dispH);
|
| 464 |
ctx.strokeStyle='#444'; ctx.lineWidth=2; ctx.setLineDash([8,4]);
|
|
|
|
| 479 |
const p = n2px(b);
|
| 480 |
const lx=p.x1, ty=p.y1, w=p.x2-p.x1, h=p.y2-p.y1;
|
| 481 |
|
| 482 |
+
/* RED OUTLINE ONLY — no fill */
|
|
|
|
|
|
|
| 483 |
if (i === selectedIdx) {
|
| 484 |
+
ctx.strokeStyle = SEL_STROKE;
|
| 485 |
+
ctx.lineWidth = RED_STROKE_WIDTH + 1;
|
| 486 |
+
ctx.setLineDash([6,3]);
|
| 487 |
} else {
|
| 488 |
+
ctx.strokeStyle = RED_STROKE;
|
| 489 |
+
ctx.lineWidth = RED_STROKE_WIDTH;
|
| 490 |
+
ctx.setLineDash([]);
|
| 491 |
}
|
| 492 |
+
ctx.strokeRect(lx, ty, w, h);
|
| 493 |
ctx.setLineDash([]);
|
| 494 |
|
| 495 |
+
/* label tag */
|
| 496 |
ctx.fillStyle = i===selectedIdx ? 'rgba(0,120,255,0.85)' : 'rgba(255,0,0,0.85)';
|
| 497 |
ctx.font = 'bold 11px IBM Plex Mono,monospace';
|
| 498 |
ctx.textAlign = 'left'; ctx.textBaseline = 'top';
|
|
|
|
| 505 |
if (i === selectedIdx) drawHandles(p);
|
| 506 |
});
|
| 507 |
|
| 508 |
+
/* temp drawing rect — outline only */
|
| 509 |
if (tempRect) {
|
| 510 |
const rx = Math.min(tempRect.x1,tempRect.x2);
|
| 511 |
const ry = Math.min(tempRect.y1,tempRect.y2);
|
| 512 |
const rw = Math.abs(tempRect.x2-tempRect.x1);
|
| 513 |
const rh = Math.abs(tempRect.y2-tempRect.y1);
|
| 514 |
+
ctx.strokeStyle = RED_STROKE;
|
| 515 |
+
ctx.lineWidth = RED_STROKE_WIDTH;
|
| 516 |
+
ctx.setLineDash([6,3]);
|
| 517 |
+
ctx.strokeRect(rx, ry, rw, rh);
|
| 518 |
+
ctx.setLineDash([]);
|
| 519 |
}
|
| 520 |
updateBadge();
|
| 521 |
}
|
|
|
|
| 581 |
}
|
| 582 |
function hideStatus() { status.style.display = 'none'; }
|
| 583 |
|
|
|
|
| 584 |
function onDown(e) {
|
| 585 |
if (!baseImg) return;
|
| 586 |
e.preventDefault();
|
|
|
|
| 709 |
canvas.addEventListener('touchend', onUp, {passive:false});
|
| 710 |
canvas.addEventListener('touchcancel',(e)=>{e.preventDefault();dragging=false;redraw();},{passive:false});
|
| 711 |
|
|
|
|
| 712 |
btnDraw.addEventListener('click', ()=>setMode('draw'));
|
| 713 |
btnSelect.addEventListener('click', ()=>setMode('select'));
|
| 714 |
|
|
|
|
| 736 |
});
|
| 737 |
|
| 738 |
btnClear.addEventListener('click', () => {
|
| 739 |
+
boxes.length = 0;
|
| 740 |
window.__bboxBoxes = boxes;
|
| 741 |
selectedIdx = -1;
|
| 742 |
syncToGradio(); redraw(); hideStatus();
|
| 743 |
});
|
| 744 |
|
|
|
|
| 745 |
let lastSrc = null;
|
| 746 |
setInterval(() => {
|
|
|
|
| 747 |
const imgs = document.querySelectorAll('#source-image-component img');
|
| 748 |
let el = null;
|
| 749 |
for (const img of imgs) {
|
|
|
|
| 789 |
}
|
| 790 |
}, 300);
|
| 791 |
|
|
|
|
| 792 |
setInterval(() => { syncToGradio(); }, 500);
|
| 793 |
|
| 794 |
new ResizeObserver(() => {
|
|
|
|
| 805 |
"""
|
| 806 |
|
| 807 |
|
| 808 |
+
with gr.Blocks(css=css, theme=purple_theme) as demo:
|
| 809 |
gr.Markdown("# **QIE-Object-Remover-Bbox**", elem_id="main-title")
|
| 810 |
gr.Markdown(
|
| 811 |
+
"Perform diverse image edits using a specialized [LoRA](https://huggingface.co/prithivMLmods/QIE-2511-Object-Remover-v2). "
|
| 812 |
+
"Upload an image, draw red bounding boxes over the objects you want to remove, and click Remove Object. "
|
| 813 |
"Multiple boxes supported. Select, move, resize or delete individual boxes.",
|
| 814 |
elem_id="subtitle",
|
| 815 |
)
|
|
|
|
| 827 |
gr.HTML(
|
| 828 |
'<div class="bbox-hint">'
|
| 829 |
"<b>Draw mode:</b> Click & drag to create red rectangles. "
|
| 830 |
+
"<b>Select mode:</b> Click a box to select it \u2192 drag to <b>move</b>, "
|
| 831 |
"drag handles to <b>resize</b>. Use <b>Delete Selected</b> to remove one box."
|
| 832 |
"</div>"
|
| 833 |
)
|
|
|
|
| 857 |
"""
|
| 858 |
)
|
| 859 |
|
| 860 |
+
gr.HTML('<div id="bbox-debug-count">\u2B1C No boxes drawn yet</div>')
|
| 861 |
|
| 862 |
boxes_json = gr.Textbox(
|
| 863 |
value="[]",
|
|
|
|
| 874 |
info="Edit the prompt if needed",
|
| 875 |
)
|
| 876 |
|
| 877 |
+
run_btn = gr.Button("\U0001F5D1\uFE0F Remove Object", variant="primary", size="lg")
|
| 878 |
|
| 879 |
with gr.Column(scale=1):
|
| 880 |
result = gr.Image(label="Output Image", height=449)
|
|
|
|
| 896 |
"[prithivMLmods](https://huggingface.co/prithivMLmods). "
|
| 897 |
"Adapter: [QIE-2511-Object-Remover-v2]"
|
| 898 |
"(https://huggingface.co/prithivMLmods/QIE-2511-Object-Remover-v2). "
|
| 899 |
+
"More adapters \u2192 [Qwen-Image-Edit-LoRAs]"
|
| 900 |
"(https://huggingface.co/models?other=base_model:adapter:Qwen/Qwen-Image-Edit-2509)."
|
| 901 |
)
|
| 902 |
|