Commit Β·
ccd9487
1
Parent(s): f26f977
feat: add mask-clear button and reject too-small / too-large watermarks
Browse files- Clear Mask button wipes paint layers while keeping the loaded frame
- mask_to_bbox: reject masks under 100 px (filters stray clicks)
- compute_crop_region: reject bboxes covering >50% of frame area
(inpainting models need surrounding context to reconstruct)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- app.py +21 -0
- pipeline/crop.py +26 -0
app.py
CHANGED
|
@@ -428,6 +428,17 @@ def on_preview_crop(editor_value: dict | None, meta_state: dict | None, context_
|
|
| 428 |
return gr.update(), f"β {e}"
|
| 429 |
|
| 430 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
@spaces.GPU(duration=180)
|
| 432 |
def _inpaint_composite_save_gpu(
|
| 433 |
frame_paths: list,
|
|
@@ -635,6 +646,10 @@ with gr.Blocks(title="Video Watermark Remover", css=CSS) as demo:
|
|
| 635 |
|
| 636 |
# ββ Action buttons βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 637 |
with gr.Row():
|
|
|
|
|
|
|
|
|
|
|
|
|
| 638 |
preview_btn = gr.Button(
|
| 639 |
"π Preview Crop Region",
|
| 640 |
elem_classes=["btn-secondary"],
|
|
@@ -677,6 +692,12 @@ with gr.Blocks(title="Video Watermark Remover", css=CSS) as demo:
|
|
| 677 |
outputs=[editor, crop_preview, meta_state, status_box],
|
| 678 |
)
|
| 679 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 680 |
preview_btn.click(
|
| 681 |
fn=on_preview_crop,
|
| 682 |
inputs=[editor, meta_state, context_slider],
|
|
|
|
| 428 |
return gr.update(), f"β {e}"
|
| 429 |
|
| 430 |
|
| 431 |
+
def on_clear_mask(editor_value: dict | None):
|
| 432 |
+
"""Clear all paint layers from the editor while preserving the loaded frame."""
|
| 433 |
+
if editor_value is None:
|
| 434 |
+
return gr.update(), "Upload a video to begin."
|
| 435 |
+
bg = editor_value.get("background")
|
| 436 |
+
return (
|
| 437 |
+
gr.update(value={"background": bg, "layers": [], "composite": None}),
|
| 438 |
+
"Mask cleared. Draw over the watermark to start again.",
|
| 439 |
+
)
|
| 440 |
+
|
| 441 |
+
|
| 442 |
@spaces.GPU(duration=180)
|
| 443 |
def _inpaint_composite_save_gpu(
|
| 444 |
frame_paths: list,
|
|
|
|
| 646 |
|
| 647 |
# ββ Action buttons βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 648 |
with gr.Row():
|
| 649 |
+
clear_btn = gr.Button(
|
| 650 |
+
"π§Ή Clear Mask",
|
| 651 |
+
elem_classes=["btn-secondary"],
|
| 652 |
+
)
|
| 653 |
preview_btn = gr.Button(
|
| 654 |
"π Preview Crop Region",
|
| 655 |
elem_classes=["btn-secondary"],
|
|
|
|
| 692 |
outputs=[editor, crop_preview, meta_state, status_box],
|
| 693 |
)
|
| 694 |
|
| 695 |
+
clear_btn.click(
|
| 696 |
+
fn=on_clear_mask,
|
| 697 |
+
inputs=[editor],
|
| 698 |
+
outputs=[editor, status_box],
|
| 699 |
+
)
|
| 700 |
+
|
| 701 |
preview_btn.click(
|
| 702 |
fn=on_preview_crop,
|
| 703 |
inputs=[editor, meta_state, context_slider],
|
pipeline/crop.py
CHANGED
|
@@ -204,6 +204,16 @@ def mask_to_bbox(mask: np.ndarray) -> BBox:
|
|
| 204 |
"The mask has no drawn pixels. "
|
| 205 |
"Please draw around the watermark before processing."
|
| 206 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 207 |
|
| 208 |
return BBox(
|
| 209 |
x1=int(xs.min()),
|
|
@@ -312,6 +322,22 @@ def compute_crop_region(
|
|
| 312 |
f"Watermark bbox {watermark_bbox} is larger than the frame "
|
| 313 |
f"({frame_w}x{frame_h}). Check your mask."
|
| 314 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 315 |
|
| 316 |
# ------------------------------------------------------------------
|
| 317 |
# 1. Padded required dimensions
|
|
|
|
| 204 |
"The mask has no drawn pixels. "
|
| 205 |
"Please draw around the watermark before processing."
|
| 206 |
)
|
| 207 |
+
# Reject stray clicks / accidental dots β a real watermark covers more
|
| 208 |
+
# than ~100 pixels even when scaled down. This prevents an errant
|
| 209 |
+
# 1-pixel mask from producing a min_crop_dim crop centred on noise.
|
| 210 |
+
MIN_MASK_AREA_PX = 100
|
| 211 |
+
if len(xs) < MIN_MASK_AREA_PX:
|
| 212 |
+
raise ValueError(
|
| 213 |
+
f"The drawn area is too small ({len(xs)} px). "
|
| 214 |
+
f"Please paint over the watermark with the brush "
|
| 215 |
+
f"(minimum {MIN_MASK_AREA_PX} pixels)."
|
| 216 |
+
)
|
| 217 |
|
| 218 |
return BBox(
|
| 219 |
x1=int(xs.min()),
|
|
|
|
| 322 |
f"Watermark bbox {watermark_bbox} is larger than the frame "
|
| 323 |
f"({frame_w}x{frame_h}). Check your mask."
|
| 324 |
)
|
| 325 |
+
# Reject impractically large watermarks. Inpainting models (both LaMa
|
| 326 |
+
# and VACE) are designed for localised removal; a watermark covering
|
| 327 |
+
# most of the frame leaves no scene context to reconstruct from and
|
| 328 |
+
# produces useless output.
|
| 329 |
+
bbox_area = watermark_bbox.width * watermark_bbox.height
|
| 330 |
+
frame_area = frame_w * frame_h
|
| 331 |
+
MAX_BBOX_FRACTION = 0.5
|
| 332 |
+
if bbox_area > MAX_BBOX_FRACTION * frame_area:
|
| 333 |
+
raise ValueError(
|
| 334 |
+
f"Watermark area is too large "
|
| 335 |
+
f"({bbox_area / frame_area * 100:.0f}% of frame). "
|
| 336 |
+
f"This pipeline is designed for localised watermark removal; "
|
| 337 |
+
f"the model has too little surrounding context to reconstruct "
|
| 338 |
+
f"areas larger than {int(MAX_BBOX_FRACTION * 100)}% of the frame. "
|
| 339 |
+
f"Try cropping the video first or paint a tighter mask."
|
| 340 |
+
)
|
| 341 |
|
| 342 |
# ------------------------------------------------------------------
|
| 343 |
# 1. Padded required dimensions
|