Commit
·
91da7a0
1
Parent(s):
437b94f
feat(api): add /remove-pink endpoint - simple one-parameter endpoint that auto-detects pink segments and removes them
Browse files- api/main.py +88 -0
api/main.py
CHANGED
|
@@ -57,6 +57,10 @@ class InpaintRequest(BaseModel):
|
|
| 57 |
passthrough: bool = False # If True, return the original image unchanged
|
| 58 |
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
@app.get("/")
|
| 61 |
def root() -> Dict[str, object]:
|
| 62 |
return {
|
|
@@ -359,6 +363,90 @@ def inpaint_multipart(
|
|
| 359 |
return resp
|
| 360 |
|
| 361 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
@app.get("/download/{filename}")
|
| 363 |
def download_file(filename: str):
|
| 364 |
path = os.path.join(OUTPUT_DIR, filename)
|
|
|
|
| 57 |
passthrough: bool = False # If True, return the original image unchanged
|
| 58 |
|
| 59 |
|
| 60 |
+
class SimpleRemoveRequest(BaseModel):
|
| 61 |
+
image_id: str # Image with pink/magenta segments to remove
|
| 62 |
+
|
| 63 |
+
|
| 64 |
@app.get("/")
|
| 65 |
def root() -> Dict[str, object]:
|
| 66 |
return {
|
|
|
|
| 363 |
return resp
|
| 364 |
|
| 365 |
|
| 366 |
+
@app.post("/remove-pink")
|
| 367 |
+
def remove_pink_segments(
|
| 368 |
+
image: UploadFile = File(...),
|
| 369 |
+
request: Request = None,
|
| 370 |
+
_: None = Depends(bearer_auth),
|
| 371 |
+
) -> Dict[str, str]:
|
| 372 |
+
"""
|
| 373 |
+
Simple endpoint: just upload an image with pink/magenta segments you want to remove.
|
| 374 |
+
The API automatically detects pink areas and removes them, keeping everything else.
|
| 375 |
+
"""
|
| 376 |
+
log.info(f"Simple remove-pink: processing image {image.filename}")
|
| 377 |
+
|
| 378 |
+
# Load the image (with pink paint on it)
|
| 379 |
+
img = Image.open(image.file).convert("RGBA")
|
| 380 |
+
img_rgb = cv2.cvtColor(np.array(img), cv2.COLOR_RGBA2RGB)
|
| 381 |
+
|
| 382 |
+
# Auto-detect pink/magenta segments
|
| 383 |
+
magenta_detected = (
|
| 384 |
+
(img_rgb[:, :, 0] > 240) & # Red: high
|
| 385 |
+
(img_rgb[:, :, 1] < 30) & # Green: low
|
| 386 |
+
(img_rgb[:, :, 2] > 240) # Blue: high
|
| 387 |
+
).astype(np.uint8) * 255
|
| 388 |
+
|
| 389 |
+
# Clean up the mask
|
| 390 |
+
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
|
| 391 |
+
binmask = cv2.morphologyEx(magenta_detected, cv2.MORPH_CLOSE, kernel, iterations=2)
|
| 392 |
+
binmask = cv2.morphologyEx(binmask, cv2.MORPH_OPEN, kernel, iterations=1)
|
| 393 |
+
|
| 394 |
+
nonzero = int((binmask > 0).sum())
|
| 395 |
+
log.info(f"Detected {nonzero} pink pixels to remove")
|
| 396 |
+
|
| 397 |
+
if nonzero < 50:
|
| 398 |
+
log.warning("Very few pink pixels detected! Trying strict magenta detection...")
|
| 399 |
+
magenta_strict = np.all(img_rgb == [255, 0, 255], axis=2).astype(np.uint8) * 255
|
| 400 |
+
binmask = cv2.morphologyEx(magenta_strict, cv2.MORPH_CLOSE, kernel, iterations=3)
|
| 401 |
+
nonzero = int((binmask > 0).sum())
|
| 402 |
+
log.info(f"Strict detection: {nonzero} pixels")
|
| 403 |
+
|
| 404 |
+
if nonzero < 50:
|
| 405 |
+
log.error("No pink segments detected! Returning original image.")
|
| 406 |
+
result = np.array(img.convert("RGB"))
|
| 407 |
+
result_name = f"output_{uuid.uuid4().hex}.png"
|
| 408 |
+
result_path = os.path.join(OUTPUT_DIR, result_name)
|
| 409 |
+
Image.fromarray(result).save(result_path)
|
| 410 |
+
return {
|
| 411 |
+
"result": result_name,
|
| 412 |
+
"error": "No pink/magenta segments detected. Please paint areas to remove with magenta/pink color (RGB 255,0,255)."
|
| 413 |
+
}
|
| 414 |
+
|
| 415 |
+
# Create mask: white = pink areas (remove), black = everything else (keep)
|
| 416 |
+
mask_rgba = np.zeros((binmask.shape[0], binmask.shape[1], 4), dtype=np.uint8)
|
| 417 |
+
mask_rgba[:, :, 0] = binmask # R
|
| 418 |
+
mask_rgba[:, :, 1] = binmask # G
|
| 419 |
+
mask_rgba[:, :, 2] = binmask # B
|
| 420 |
+
mask_rgba[:, :, 3] = 255 # Alpha
|
| 421 |
+
|
| 422 |
+
log.info(f"Created mask: {nonzero} white pixels (remove), {binmask.shape[0]*binmask.shape[1]-nonzero} black pixels (keep)")
|
| 423 |
+
|
| 424 |
+
# Process with invert_mask=False because white pixels = remove (standard)
|
| 425 |
+
result = process_inpaint(np.array(img), mask_rgba, invert_mask=False)
|
| 426 |
+
result_name = f"output_{uuid.uuid4().hex}.png"
|
| 427 |
+
result_path = os.path.join(OUTPUT_DIR, result_name)
|
| 428 |
+
Image.fromarray(result).save(result_path)
|
| 429 |
+
|
| 430 |
+
url: Optional[str] = None
|
| 431 |
+
try:
|
| 432 |
+
if request is not None:
|
| 433 |
+
url = str(request.url_for("download_file", filename=result_name))
|
| 434 |
+
except Exception:
|
| 435 |
+
url = None
|
| 436 |
+
|
| 437 |
+
logs.append({
|
| 438 |
+
"result": result_name,
|
| 439 |
+
"filename": image.filename,
|
| 440 |
+
"pink_pixels": nonzero,
|
| 441 |
+
"timestamp": datetime.utcnow().isoformat()
|
| 442 |
+
})
|
| 443 |
+
|
| 444 |
+
resp: Dict[str, str] = {"result": result_name, "pink_segments_detected": str(nonzero)}
|
| 445 |
+
if url:
|
| 446 |
+
resp["url"] = url
|
| 447 |
+
return resp
|
| 448 |
+
|
| 449 |
+
|
| 450 |
@app.get("/download/{filename}")
|
| 451 |
def download_file(filename: str):
|
| 452 |
path = os.path.join(OUTPUT_DIR, filename)
|