LogicGoInfotechSpaces commited on
Commit
7fa5d1a
·
1 Parent(s): c5411c1

Only log to admin media_clicks if user_id is provided; regular MongoDB logging always happens

Browse files
Files changed (1) hide show
  1. api/main.py +292 -203
api/main.py CHANGED
@@ -158,8 +158,12 @@ def _coerce_category_id(category_id: Optional[str]) -> ObjectId:
158
 
159
 
160
  def log_media_click(user_id: Optional[str], category_id: Optional[str]) -> None:
 
161
  if admin_media_clicks is None:
162
  return
 
 
 
163
  try:
164
  user_obj = _coerce_object_id(user_id)
165
  category_obj = _coerce_category_id(category_id)
@@ -442,27 +446,56 @@ def inpaint(req: InpaintRequest, _: None = Depends(bearer_auth)) -> Dict[str, st
442
  @app.post("/inpaint-url")
443
  def inpaint_url(req: InpaintRequest, request: Request, _: None = Depends(bearer_auth)) -> Dict[str, str]:
444
  """Same as /inpaint but returns a JSON with a public download URL instead of image bytes."""
445
- if req.image_id not in file_store or file_store[req.image_id]["type"] != "image":
446
- raise HTTPException(status_code=404, detail="image_id not found")
447
- if req.mask_id not in file_store or file_store[req.mask_id]["type"] != "mask":
448
- raise HTTPException(status_code=404, detail="mask_id not found")
449
 
450
- img_rgba = _load_rgba_image(file_store[req.image_id]["path"])
451
- mask_img = Image.open(file_store[req.mask_id]["path"]) # may be RGB/gray/RGBA
452
- mask_rgba = _load_rgba_mask_from_image(mask_img)
 
 
453
 
454
- if req.passthrough:
455
- result = np.array(img_rgba.convert("RGB"))
456
- else:
457
- result = process_inpaint(np.array(img_rgba), mask_rgba, invert_mask=req.invert_mask)
458
- result_name = f"output_{uuid.uuid4().hex}.png"
459
- result_path = os.path.join(OUTPUT_DIR, result_name)
460
- Image.fromarray(result).save(result_path, "PNG", optimize=False, compress_level=1)
461
 
462
- url = str(request.url_for("download_file", filename=result_name))
463
- logs.append({"result": result_name, "url": url, "timestamp": datetime.utcnow().isoformat()})
464
- log_media_click(req.user_id, req.category_id)
465
- return {"result": result_name, "url": url}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
 
467
 
468
  @app.post("/inpaint-multipart")
@@ -477,13 +510,115 @@ def inpaint_multipart(
477
  category_id: Optional[str] = Form(None),
478
  _: None = Depends(bearer_auth),
479
  ) -> Dict[str, str]:
480
- # Load in-memory
481
- img = Image.open(image.file).convert("RGBA")
482
- m = Image.open(mask.file).convert("RGBA")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
 
484
- if passthrough:
485
- # Just echo the input image, ignore mask
486
- result = np.array(img.convert("RGB"))
 
 
487
  result_name = f"output_{uuid.uuid4().hex}.png"
488
  result_path = os.path.join(OUTPUT_DIR, result_name)
489
  Image.fromarray(result).save(result_path, "PNG", optimize=False, compress_level=1)
@@ -504,216 +639,170 @@ def inpaint_multipart(
504
  resp["url"] = url
505
  log_media_click(user_id, category_id)
506
  return resp
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
 
508
- if mask_is_painted:
509
- # Auto-detect pink/magenta paint and convert to black/white mask
510
- # White pixels = areas to remove, Black pixels = areas to keep
511
- log.info("Auto-detecting pink/magenta paint from uploaded image...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
 
513
- m_rgb = cv2.cvtColor(np.array(m), cv2.COLOR_RGBA2RGB)
 
 
514
 
515
- # Detect pink/magenta using fixed RGB bounds (same as /remove-pink)
 
 
 
 
516
  lower = np.array([150, 0, 100], dtype=np.uint8)
517
  upper = np.array([255, 120, 255], dtype=np.uint8)
518
- magenta_detected = (
519
- (m_rgb[:, :, 0] >= lower[0]) & (m_rgb[:, :, 0] <= upper[0]) &
520
- (m_rgb[:, :, 1] >= lower[1]) & (m_rgb[:, :, 1] <= upper[1]) &
521
- (m_rgb[:, :, 2] >= lower[2]) & (m_rgb[:, :, 2] <= upper[2])
522
  ).astype(np.uint8) * 255
523
 
524
- # Method 2: Also check if original image was provided to find differences
525
- if img is not None:
526
- img_rgb = cv2.cvtColor(np.array(img), cv2.COLOR_RGBA2RGB)
527
- if img_rgb.shape == m_rgb.shape:
528
- diff = cv2.absdiff(img_rgb, m_rgb)
529
- gray_diff = cv2.cvtColor(diff, cv2.COLOR_RGB2GRAY)
530
- # Any significant difference (>50) could be paint
531
- diff_mask = (gray_diff > 50).astype(np.uint8) * 255
532
- # Combine with magenta detection
533
- binmask = cv2.bitwise_or(magenta_detected, diff_mask)
534
- else:
535
- binmask = magenta_detected
536
- else:
537
- # No original image provided, use magenta detection only
538
- binmask = magenta_detected
539
-
540
- # Clean up the mask: remove noise and fill small holes
541
  kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
542
- # Close small gaps in the mask
543
  binmask = cv2.morphologyEx(binmask, cv2.MORPH_CLOSE, kernel, iterations=2)
544
- # Remove small noise
545
  binmask = cv2.morphologyEx(binmask, cv2.MORPH_OPEN, kernel, iterations=1)
546
 
547
  nonzero = int((binmask > 0).sum())
548
- log.info("Pink/magenta paint detected: %d pixels marked for removal (white)", nonzero)
 
549
 
550
- # If very few pixels detected, assume the user may already be providing a BW mask
551
- # and proceed without forcing strict detection
552
 
553
  if nonzero < 50:
554
- log.error("CRITICAL: Could not detect pink/magenta paint! Returning original image.")
555
- result = np.array(img.convert("RGB")) if img else np.array(m.convert("RGB"))
556
  result_name = f"output_{uuid.uuid4().hex}.png"
557
  result_path = os.path.join(OUTPUT_DIR, result_name)
558
  Image.fromarray(result).save(result_path, "PNG", optimize=False, compress_level=1)
559
- return {"result": result_name, "error": "pink/magenta paint detection failed - very few pixels detected"}
 
 
 
560
 
561
  # Create binary mask: Pink pixels → white (255), Everything else → black (0)
562
- # Encode in RGBA format for process_inpaint
563
  # process_inpaint does: mask = 255 - mask[:,:,3]
564
  # So: alpha=0 (transparent/pink) → becomes 255 (white/remove)
565
  # alpha=255 (opaque/keep) → becomes 0 (black/keep)
566
  mask_rgba = np.zeros((binmask.shape[0], binmask.shape[1], 4), dtype=np.uint8)
567
- mask_rgba[:, :, 0] = binmask # R: white where pink (for visualization)
568
- mask_rgba[:, :, 1] = binmask # G: white where pink
 
569
  mask_rgba[:, :, 2] = binmask # B: white where pink
570
- # Alpha: invert so pink areas get alpha=0 → will become white after 255-alpha
571
- mask_rgba[:, :, 3] = 255 - binmask
 
572
 
573
- log.info("Successfully created binary mask: %d pink pixels → white (255), %d pixels → black (0)",
574
- nonzero, binmask.shape[0] * binmask.shape[1] - nonzero)
575
- else:
576
- mask_rgba = _load_rgba_mask_from_image(m)
577
-
578
- # When mask_is_painted=true, we encode pink as alpha=0, so process_inpaint's default invert_mask=True works correctly
579
- actual_invert = invert_mask # Use default True for painted masks
580
- log.info("Using invert_mask=%s (mask_is_painted=%s)", actual_invert, mask_is_painted)
581
-
582
- result = process_inpaint(np.array(img), mask_rgba, invert_mask=actual_invert)
583
- result_name = f"output_{uuid.uuid4().hex}.png"
584
- result_path = os.path.join(OUTPUT_DIR, result_name)
585
- Image.fromarray(result).save(result_path, "PNG", optimize=False, compress_level=1)
586
-
587
- url: Optional[str] = None
588
- try:
589
- if request is not None:
590
- url = str(request.url_for("download_file", filename=result_name))
591
- except Exception:
592
- url = None
593
-
594
- entry: Dict[str, str] = {"result": result_name, "timestamp": datetime.utcnow().isoformat()}
595
- if url:
596
- entry["url"] = url
597
- logs.append(entry)
598
- resp: Dict[str, str] = {"result": result_name}
599
- if url:
600
- resp["url"] = url
601
- log_media_click(user_id, category_id)
602
- return resp
603
-
604
-
605
- @app.post("/remove-pink")
606
- def remove_pink_segments(
607
- image: UploadFile = File(...),
608
- request: Request = None,
609
- user_id: Optional[str] = Form(None),
610
- category_id: Optional[str] = Form(None),
611
- _: None = Depends(bearer_auth),
612
- ) -> Dict[str, str]:
613
- """
614
- Simple endpoint: upload an image with pink/magenta segments to remove.
615
- - Pink/Magenta segments → automatically removed (white in mask)
616
- - Everything else → automatically kept (black in mask)
617
- Just paint pink/magenta on areas you want to remove, upload the image, and it works!
618
- """
619
- log.info(f"Simple remove-pink: processing image {image.filename}")
620
-
621
- # Load the image (with pink paint on it)
622
- img = Image.open(image.file).convert("RGBA")
623
- img_rgb = cv2.cvtColor(np.array(img), cv2.COLOR_RGBA2RGB)
624
-
625
- # Auto-detect pink/magenta segments to remove
626
- # Pink/Magenta → white in mask (remove)
627
- # Everything else (natural image colors, including dark areas) → black in mask (keep)
628
-
629
- # Detect pink/magenta using fixed RGB bounds per requested logic
630
- lower = np.array([150, 0, 100], dtype=np.uint8)
631
- upper = np.array([255, 120, 255], dtype=np.uint8)
632
- binmask = (
633
- (img_rgb[:, :, 0] >= lower[0]) & (img_rgb[:, :, 0] <= upper[0]) &
634
- (img_rgb[:, :, 1] >= lower[1]) & (img_rgb[:, :, 1] <= upper[1]) &
635
- (img_rgb[:, :, 2] >= lower[2]) & (img_rgb[:, :, 2] <= upper[2])
636
- ).astype(np.uint8) * 255
637
-
638
- # Clean up the pink mask
639
- kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
640
- binmask = cv2.morphologyEx(binmask, cv2.MORPH_CLOSE, kernel, iterations=2)
641
- binmask = cv2.morphologyEx(binmask, cv2.MORPH_OPEN, kernel, iterations=1)
642
-
643
- nonzero = int((binmask > 0).sum())
644
- total_pixels = binmask.shape[0] * binmask.shape[1]
645
- log.info(f"Detected {nonzero} pink pixels ({100*nonzero/total_pixels:.2f}% of image) to remove")
646
-
647
- # Debug: log bounds used
648
- log.info("Pink detection bounds used: lower=[150,0,100], upper=[255,120,255]")
649
-
650
- if nonzero < 50:
651
- log.error("No pink segments detected! Returning original image.")
652
- result = np.array(img.convert("RGB"))
653
  result_name = f"output_{uuid.uuid4().hex}.png"
654
  result_path = os.path.join(OUTPUT_DIR, result_name)
655
  Image.fromarray(result).save(result_path, "PNG", optimize=False, compress_level=1)
656
- return {
 
 
 
 
 
 
 
 
657
  "result": result_name,
658
- "error": "No pink/magenta segments detected. Please paint areas to remove with magenta/pink color (RGB 255,0,255)."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
659
  }
660
-
661
- # Create binary mask: Pink pixels → white (255), Everything else → black (0)
662
- # Encode in RGBA format that process_inpaint expects
663
- # process_inpaint does: mask = 255 - mask[:,:,3]
664
- # So: alpha=0 (transparent/pink) → becomes 255 (white/remove)
665
- # alpha=255 (opaque/keep) becomes 0 (black/keep)
666
- mask_rgba = np.zeros((binmask.shape[0], binmask.shape[1], 4), dtype=np.uint8)
667
- # RGB channels don't matter for process_inpaint, but set them to white where pink for visualization
668
- mask_rgba[:, :, 0] = binmask # R: white where pink
669
- mask_rgba[:, :, 1] = binmask # G: white where pink
670
- mask_rgba[:, :, 2] = binmask # B: white where pink
671
- # Alpha: 0 (transparent) where pink → will become white after 255-alpha
672
- # 255 (opaque) everywhere else → will become black after 255-alpha
673
- mask_rgba[:, :, 3] = 255 - binmask # Invert: pink areas get alpha=0, rest get alpha=255
674
-
675
- # Verify mask encoding
676
- alpha_zero_count = int((mask_rgba[:,:,3] == 0).sum())
677
- alpha_255_count = int((mask_rgba[:,:,3] == 255).sum())
678
- total_pixels = binmask.shape[0] * binmask.shape[1]
679
- log.info(f"Mask encoding: {alpha_zero_count} pixels with alpha=0 (pink), {alpha_255_count} pixels with alpha=255 (keep)")
680
- log.info(f"After 255-alpha conversion: {alpha_zero_count} will become white (255/remove), {alpha_255_count} will become black (0/keep)")
681
-
682
- # IMPORTANT: We need to use the ORIGINAL image WITHOUT pink paint for inpainting!
683
- # Remove pink from the original image before processing
684
- # Create a clean version: where pink was detected, keep original image colors
685
- img_clean = np.array(img.convert("RGBA"))
686
- # Where pink is detected, we want to inpaint, so we can leave it (or blend it out)
687
- # Actually, the model will inpaint over those areas, so we can pass the original
688
- # But for better results, we might want to remove the pink overlay first
689
-
690
- # Process with invert_mask=True (default) because process_inpaint expects alpha=0 for removal
691
- log.info(f"Starting inpainting process...")
692
- result = process_inpaint(img_clean, mask_rgba, invert_mask=True)
693
- log.info(f"Inpainting complete, result shape: {result.shape}")
694
- result_name = f"output_{uuid.uuid4().hex}.png"
695
- result_path = os.path.join(OUTPUT_DIR, result_name)
696
- Image.fromarray(result).save(result_path, "PNG", optimize=False, compress_level=1)
697
-
698
- url: Optional[str] = None
699
- try:
700
- if request is not None:
701
- url = str(request.url_for("download_file", filename=result_name))
702
- except Exception:
703
- url = None
704
-
705
- logs.append({
706
- "result": result_name,
707
- "filename": image.filename,
708
- "pink_pixels": nonzero,
709
- "timestamp": datetime.utcnow().isoformat()
710
- })
711
-
712
- resp: Dict[str, str] = {"result": result_name, "pink_segments_detected": str(nonzero)}
713
- if url:
714
- resp["url"] = url
715
- log_media_click(user_id, category_id)
716
- return resp
717
 
718
 
719
  @app.get("/download/{filename}")
 
158
 
159
 
160
  def log_media_click(user_id: Optional[str], category_id: Optional[str]) -> None:
161
+ """Log to admin media_clicks collection only if user_id is provided."""
162
  if admin_media_clicks is None:
163
  return
164
+ # Only log if user_id is provided (not None/empty)
165
+ if not user_id or not user_id.strip():
166
+ return
167
  try:
168
  user_obj = _coerce_object_id(user_id)
169
  category_obj = _coerce_category_id(category_id)
 
446
  @app.post("/inpaint-url")
447
  def inpaint_url(req: InpaintRequest, request: Request, _: None = Depends(bearer_auth)) -> Dict[str, str]:
448
  """Same as /inpaint but returns a JSON with a public download URL instead of image bytes."""
449
+ start_time = time.time()
450
+ status = "success"
451
+ error_msg = None
452
+ result_name = None
453
 
454
+ try:
455
+ if req.image_id not in file_store or file_store[req.image_id]["type"] != "image":
456
+ raise HTTPException(status_code=404, detail="image_id not found")
457
+ if req.mask_id not in file_store or file_store[req.mask_id]["type"] != "mask":
458
+ raise HTTPException(status_code=404, detail="mask_id not found")
459
 
460
+ img_rgba = _load_rgba_image(file_store[req.image_id]["path"])
461
+ mask_img = Image.open(file_store[req.mask_id]["path"]) # may be RGB/gray/RGBA
462
+ mask_rgba = _load_rgba_mask_from_image(mask_img)
 
 
 
 
463
 
464
+ if req.passthrough:
465
+ result = np.array(img_rgba.convert("RGB"))
466
+ else:
467
+ result = process_inpaint(np.array(img_rgba), mask_rgba, invert_mask=req.invert_mask)
468
+ result_name = f"output_{uuid.uuid4().hex}.png"
469
+ result_path = os.path.join(OUTPUT_DIR, result_name)
470
+ Image.fromarray(result).save(result_path, "PNG", optimize=False, compress_level=1)
471
+
472
+ url = str(request.url_for("download_file", filename=result_name))
473
+ logs.append({"result": result_name, "url": url, "timestamp": datetime.utcnow().isoformat()})
474
+ log_media_click(req.user_id, req.category_id)
475
+ return {"result": result_name, "url": url}
476
+ except Exception as e:
477
+ status = "fail"
478
+ error_msg = str(e)
479
+ raise
480
+ finally:
481
+ # Always log to regular MongoDB (mandatory)
482
+ end_time = time.time()
483
+ response_time_ms = (end_time - start_time) * 1000
484
+ log_doc = {
485
+ "input_image_id": req.image_id,
486
+ "input_mask_id": req.mask_id,
487
+ "output_id": result_name,
488
+ "status": status,
489
+ "timestamp": datetime.utcnow(),
490
+ "ts": int(time.time()),
491
+ "response_time_ms": response_time_ms,
492
+ }
493
+ if error_msg:
494
+ log_doc["error"] = error_msg
495
+ try:
496
+ mongo_logs.insert_one(log_doc)
497
+ except Exception as mongo_err:
498
+ log.error("Mongo log insert failed: %s", mongo_err)
499
 
500
 
501
  @app.post("/inpaint-multipart")
 
510
  category_id: Optional[str] = Form(None),
511
  _: None = Depends(bearer_auth),
512
  ) -> Dict[str, str]:
513
+ start_time = time.time()
514
+ status = "success"
515
+ error_msg = None
516
+ result_name = None
517
+
518
+ try:
519
+ # Load in-memory
520
+ img = Image.open(image.file).convert("RGBA")
521
+ m = Image.open(mask.file).convert("RGBA")
522
+
523
+ if passthrough:
524
+ # Just echo the input image, ignore mask
525
+ result = np.array(img.convert("RGB"))
526
+ result_name = f"output_{uuid.uuid4().hex}.png"
527
+ result_path = os.path.join(OUTPUT_DIR, result_name)
528
+ Image.fromarray(result).save(result_path, "PNG", optimize=False, compress_level=1)
529
+
530
+ url: Optional[str] = None
531
+ try:
532
+ if request is not None:
533
+ url = str(request.url_for("download_file", filename=result_name))
534
+ except Exception:
535
+ url = None
536
+
537
+ entry: Dict[str, str] = {"result": result_name, "timestamp": datetime.utcnow().isoformat()}
538
+ if url:
539
+ entry["url"] = url
540
+ logs.append(entry)
541
+ resp: Dict[str, str] = {"result": result_name}
542
+ if url:
543
+ resp["url"] = url
544
+ log_media_click(user_id, category_id)
545
+ return resp
546
+
547
+ if mask_is_painted:
548
+ # Auto-detect pink/magenta paint and convert to black/white mask
549
+ # White pixels = areas to remove, Black pixels = areas to keep
550
+ log.info("Auto-detecting pink/magenta paint from uploaded image...")
551
+
552
+ m_rgb = cv2.cvtColor(np.array(m), cv2.COLOR_RGBA2RGB)
553
+
554
+ # Detect pink/magenta using fixed RGB bounds (same as /remove-pink)
555
+ lower = np.array([150, 0, 100], dtype=np.uint8)
556
+ upper = np.array([255, 120, 255], dtype=np.uint8)
557
+ magenta_detected = (
558
+ (m_rgb[:, :, 0] >= lower[0]) & (m_rgb[:, :, 0] <= upper[0]) &
559
+ (m_rgb[:, :, 1] >= lower[1]) & (m_rgb[:, :, 1] <= upper[1]) &
560
+ (m_rgb[:, :, 2] >= lower[2]) & (m_rgb[:, :, 2] <= upper[2])
561
+ ).astype(np.uint8) * 255
562
+
563
+ # Method 2: Also check if original image was provided to find differences
564
+ if img is not None:
565
+ img_rgb = cv2.cvtColor(np.array(img), cv2.COLOR_RGBA2RGB)
566
+ if img_rgb.shape == m_rgb.shape:
567
+ diff = cv2.absdiff(img_rgb, m_rgb)
568
+ gray_diff = cv2.cvtColor(diff, cv2.COLOR_RGB2GRAY)
569
+ # Any significant difference (>50) could be paint
570
+ diff_mask = (gray_diff > 50).astype(np.uint8) * 255
571
+ # Combine with magenta detection
572
+ binmask = cv2.bitwise_or(magenta_detected, diff_mask)
573
+ else:
574
+ binmask = magenta_detected
575
+ else:
576
+ # No original image provided, use magenta detection only
577
+ binmask = magenta_detected
578
+
579
+ # Clean up the mask: remove noise and fill small holes
580
+ kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
581
+ # Close small gaps in the mask
582
+ binmask = cv2.morphologyEx(binmask, cv2.MORPH_CLOSE, kernel, iterations=2)
583
+ # Remove small noise
584
+ binmask = cv2.morphologyEx(binmask, cv2.MORPH_OPEN, kernel, iterations=1)
585
+
586
+ nonzero = int((binmask > 0).sum())
587
+ log.info("Pink/magenta paint detected: %d pixels marked for removal (white)", nonzero)
588
+
589
+ # If very few pixels detected, assume the user may already be providing a BW mask
590
+ # and proceed without forcing strict detection
591
+
592
+ if nonzero < 50:
593
+ log.error("CRITICAL: Could not detect pink/magenta paint! Returning original image.")
594
+ result = np.array(img.convert("RGB")) if img else np.array(m.convert("RGB"))
595
+ result_name = f"output_{uuid.uuid4().hex}.png"
596
+ result_path = os.path.join(OUTPUT_DIR, result_name)
597
+ Image.fromarray(result).save(result_path, "PNG", optimize=False, compress_level=1)
598
+ return {"result": result_name, "error": "pink/magenta paint detection failed - very few pixels detected"}
599
+
600
+ # Create binary mask: Pink pixels → white (255), Everything else → black (0)
601
+ # Encode in RGBA format for process_inpaint
602
+ # process_inpaint does: mask = 255 - mask[:,:,3]
603
+ # So: alpha=0 (transparent/pink) → becomes 255 (white/remove)
604
+ # alpha=255 (opaque/keep) → becomes 0 (black/keep)
605
+ mask_rgba = np.zeros((binmask.shape[0], binmask.shape[1], 4), dtype=np.uint8)
606
+ mask_rgba[:, :, 0] = binmask # R: white where pink (for visualization)
607
+ mask_rgba[:, :, 1] = binmask # G: white where pink
608
+ mask_rgba[:, :, 2] = binmask # B: white where pink
609
+ # Alpha: invert so pink areas get alpha=0 → will become white after 255-alpha
610
+ mask_rgba[:, :, 3] = 255 - binmask
611
+
612
+ log.info("Successfully created binary mask: %d pink pixels → white (255), %d pixels → black (0)",
613
+ nonzero, binmask.shape[0] * binmask.shape[1] - nonzero)
614
+ else:
615
+ mask_rgba = _load_rgba_mask_from_image(m)
616
 
617
+ # When mask_is_painted=true, we encode pink as alpha=0, so process_inpaint's default invert_mask=True works correctly
618
+ actual_invert = invert_mask # Use default True for painted masks
619
+ log.info("Using invert_mask=%s (mask_is_painted=%s)", actual_invert, mask_is_painted)
620
+
621
+ result = process_inpaint(np.array(img), mask_rgba, invert_mask=actual_invert)
622
  result_name = f"output_{uuid.uuid4().hex}.png"
623
  result_path = os.path.join(OUTPUT_DIR, result_name)
624
  Image.fromarray(result).save(result_path, "PNG", optimize=False, compress_level=1)
 
639
  resp["url"] = url
640
  log_media_click(user_id, category_id)
641
  return resp
642
+ except Exception as e:
643
+ status = "fail"
644
+ error_msg = str(e)
645
+ raise
646
+ finally:
647
+ # Always log to regular MongoDB (mandatory)
648
+ end_time = time.time()
649
+ response_time_ms = (end_time - start_time) * 1000
650
+ log_doc = {
651
+ "endpoint": "inpaint-multipart",
652
+ "output_id": result_name,
653
+ "status": status,
654
+ "timestamp": datetime.utcnow(),
655
+ "ts": int(time.time()),
656
+ "response_time_ms": response_time_ms,
657
+ }
658
+ if error_msg:
659
+ log_doc["error"] = error_msg
660
+ try:
661
+ mongo_logs.insert_one(log_doc)
662
+ except Exception as mongo_err:
663
+ log.error("Mongo log insert failed: %s", mongo_err)
664
 
665
+
666
+ @app.post("/remove-pink")
667
+ def remove_pink_segments(
668
+ image: UploadFile = File(...),
669
+ request: Request = None,
670
+ user_id: Optional[str] = Form(None),
671
+ category_id: Optional[str] = Form(None),
672
+ _: None = Depends(bearer_auth),
673
+ ) -> Dict[str, str]:
674
+ """
675
+ Simple endpoint: upload an image with pink/magenta segments to remove.
676
+ - Pink/Magenta segments → automatically removed (white in mask)
677
+ - Everything else → automatically kept (black in mask)
678
+ Just paint pink/magenta on areas you want to remove, upload the image, and it works!
679
+ """
680
+ start_time = time.time()
681
+ status = "success"
682
+ error_msg = None
683
+ result_name = None
684
+
685
+ try:
686
+ log.info(f"Simple remove-pink: processing image {image.filename}")
687
 
688
+ # Load the image (with pink paint on it)
689
+ img = Image.open(image.file).convert("RGBA")
690
+ img_rgb = cv2.cvtColor(np.array(img), cv2.COLOR_RGBA2RGB)
691
 
692
+ # Auto-detect pink/magenta segments to remove
693
+ # Pink/Magenta → white in mask (remove)
694
+ # Everything else (natural image colors, including dark areas) → black in mask (keep)
695
+
696
+ # Detect pink/magenta using fixed RGB bounds per requested logic
697
  lower = np.array([150, 0, 100], dtype=np.uint8)
698
  upper = np.array([255, 120, 255], dtype=np.uint8)
699
+ binmask = (
700
+ (img_rgb[:, :, 0] >= lower[0]) & (img_rgb[:, :, 0] <= upper[0]) &
701
+ (img_rgb[:, :, 1] >= lower[1]) & (img_rgb[:, :, 1] <= upper[1]) &
702
+ (img_rgb[:, :, 2] >= lower[2]) & (img_rgb[:, :, 2] <= upper[2])
703
  ).astype(np.uint8) * 255
704
 
705
+ # Clean up the pink mask
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
706
  kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
 
707
  binmask = cv2.morphologyEx(binmask, cv2.MORPH_CLOSE, kernel, iterations=2)
 
708
  binmask = cv2.morphologyEx(binmask, cv2.MORPH_OPEN, kernel, iterations=1)
709
 
710
  nonzero = int((binmask > 0).sum())
711
+ total_pixels = binmask.shape[0] * binmask.shape[1]
712
+ log.info(f"Detected {nonzero} pink pixels ({100*nonzero/total_pixels:.2f}% of image) to remove")
713
 
714
+ # Debug: log bounds used
715
+ log.info("Pink detection bounds used: lower=[150,0,100], upper=[255,120,255]")
716
 
717
  if nonzero < 50:
718
+ log.error("No pink segments detected! Returning original image.")
719
+ result = np.array(img.convert("RGB"))
720
  result_name = f"output_{uuid.uuid4().hex}.png"
721
  result_path = os.path.join(OUTPUT_DIR, result_name)
722
  Image.fromarray(result).save(result_path, "PNG", optimize=False, compress_level=1)
723
+ return {
724
+ "result": result_name,
725
+ "error": "No pink/magenta segments detected. Please paint areas to remove with magenta/pink color (RGB 255,0,255)."
726
+ }
727
 
728
  # Create binary mask: Pink pixels → white (255), Everything else → black (0)
729
+ # Encode in RGBA format that process_inpaint expects
730
  # process_inpaint does: mask = 255 - mask[:,:,3]
731
  # So: alpha=0 (transparent/pink) → becomes 255 (white/remove)
732
  # alpha=255 (opaque/keep) → becomes 0 (black/keep)
733
  mask_rgba = np.zeros((binmask.shape[0], binmask.shape[1], 4), dtype=np.uint8)
734
+ # RGB channels don't matter for process_inpaint, but set them to white where pink for visualization
735
+ mask_rgba[:, :, 0] = binmask # R: white where pink
736
+ mask_rgba[:, :, 1] = binmask # G: white where pink
737
  mask_rgba[:, :, 2] = binmask # B: white where pink
738
+ # Alpha: 0 (transparent) where pink → will become white after 255-alpha
739
+ # 255 (opaque) everywhere else → will become black after 255-alpha
740
+ mask_rgba[:, :, 3] = 255 - binmask # Invert: pink areas get alpha=0, rest get alpha=255
741
 
742
+ # Verify mask encoding
743
+ alpha_zero_count = int((mask_rgba[:,:,3] == 0).sum())
744
+ alpha_255_count = int((mask_rgba[:,:,3] == 255).sum())
745
+ total_pixels = binmask.shape[0] * binmask.shape[1]
746
+ log.info(f"Mask encoding: {alpha_zero_count} pixels with alpha=0 (pink), {alpha_255_count} pixels with alpha=255 (keep)")
747
+ log.info(f"After 255-alpha conversion: {alpha_zero_count} will become white (255/remove), {alpha_255_count} will become black (0/keep)")
748
+
749
+ # IMPORTANT: We need to use the ORIGINAL image WITHOUT pink paint for inpainting!
750
+ # Remove pink from the original image before processing
751
+ # Create a clean version: where pink was detected, keep original image colors
752
+ img_clean = np.array(img.convert("RGBA"))
753
+ # Where pink is detected, we want to inpaint, so we can leave it (or blend it out)
754
+ # Actually, the model will inpaint over those areas, so we can pass the original
755
+ # But for better results, we might want to remove the pink overlay first
756
+
757
+ # Process with invert_mask=True (default) because process_inpaint expects alpha=0 for removal
758
+ log.info(f"Starting inpainting process...")
759
+ result = process_inpaint(img_clean, mask_rgba, invert_mask=True)
760
+ log.info(f"Inpainting complete, result shape: {result.shape}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
761
  result_name = f"output_{uuid.uuid4().hex}.png"
762
  result_path = os.path.join(OUTPUT_DIR, result_name)
763
  Image.fromarray(result).save(result_path, "PNG", optimize=False, compress_level=1)
764
+
765
+ url: Optional[str] = None
766
+ try:
767
+ if request is not None:
768
+ url = str(request.url_for("download_file", filename=result_name))
769
+ except Exception:
770
+ url = None
771
+
772
+ logs.append({
773
  "result": result_name,
774
+ "filename": image.filename,
775
+ "pink_pixels": nonzero,
776
+ "timestamp": datetime.utcnow().isoformat()
777
+ })
778
+
779
+ resp: Dict[str, str] = {"result": result_name, "pink_segments_detected": str(nonzero)}
780
+ if url:
781
+ resp["url"] = url
782
+ log_media_click(user_id, category_id)
783
+ return resp
784
+ except Exception as e:
785
+ status = "fail"
786
+ error_msg = str(e)
787
+ raise
788
+ finally:
789
+ # Always log to regular MongoDB (mandatory)
790
+ end_time = time.time()
791
+ response_time_ms = (end_time - start_time) * 1000
792
+ log_doc = {
793
+ "endpoint": "remove-pink",
794
+ "output_id": result_name,
795
+ "status": status,
796
+ "timestamp": datetime.utcnow(),
797
+ "ts": int(time.time()),
798
+ "response_time_ms": response_time_ms,
799
  }
800
+ if error_msg:
801
+ log_doc["error"] = error_msg
802
+ try:
803
+ mongo_logs.insert_one(log_doc)
804
+ except Exception as mongo_err:
805
+ log.error("Mongo log insert failed: %s", mongo_err)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
806
 
807
 
808
  @app.get("/download/{filename}")