BoxOfColors Claude Opus 4.7 (1M context) commited on
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>

Files changed (2) hide show
  1. app.py +21 -0
  2. 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