A360-croptool / app.py
nicka360's picture
Add initial A360 Croptool Gradio app scaffold
047076d
import gradio as gr
from PIL import Image
from typing import Tuple, Optional
# -------------------------------------------------------------------
# A360 Croptool – Model-Agnostic Cropping Test Harness
#
# NOTE:
# - This version only stubs the model calls.
# - I will later plug in:
# • RetinaFace / InsightFace (face + landmarks)
# • YOLOv8/YOLOv9 (face/body/region detection)
# • Face Alignment nets
# • SAM / SAM-HQ
# • BiSeNet (face parsing)
# • CLIPSeg (text-guided cropping)
# -------------------------------------------------------------------
FACE_MODEL_CHOICES = [
"RetinaFace (InsightFace)",
"InsightFace 2D Landmarks (2d106)",
"Face-Alignment (1adrianb)",
]
BODY_MODEL_CHOICES = [
"YOLOv8 Body Detector",
"YOLOv9 Body / Part Detector",
"Human Pose (OpenPose-style)",
]
SEGMENTATION_MODEL_CHOICES = [
"SAM / SAM-HQ",
"BiSeNet Face Parsing",
"CLIPSeg (text-guided)",
]
CROP_TARGET_CHOICES = [
"Full Face",
"Eyes / Upper Face",
"Lips / Lower Face",
"Jawline / Chin",
"Neck",
"Chest / Breasts",
"Abdomen",
"Waist / Hips",
"Arms",
"Thighs / Legs",
"Custom Region",
]
def stub_crop(
image: Optional[Image.Image],
crop_target: str,
face_model: str,
body_model: str,
seg_model: str,
text_prompt: str,
) -> Tuple[Image.Image, str]:
"""
Placeholder cropping callback.
For now, simply returns the original image and a text summary of
the options the user selected. I'll replace this with real model
calls (RetinaFace / YOLO / SAM / CLIPSeg) later.
"""
if image is None:
# Gradio will show this as an error toast
raise gr.Error("Please upload an image first.")
summary_lines = [
"Cropping request received:",
f"• Target region: {crop_target}",
f"• Face model: {face_model or 'None selected'}",
f"• Body model: {body_model or 'None selected'}",
f"• Segmentation model: {seg_model or 'None selected'}",
]
if text_prompt.strip():
summary_lines.append(f"• Text prompt (for CLIPSeg / SAM): {text_prompt.strip()}")
summary_lines.append("")
summary_lines.append("NOTE: This is a stub implementation. "
"Model hooks are ready; image is returned unmodified for now.")
return image, "\n".join(summary_lines)
def create_app() -> gr.Blocks:
with gr.Blocks(theme="gradio/soft", css="""
.a360-header { font-size: 1.8rem; font-weight: 700; }
.a360-subtitle { opacity: 0.8; }
""") as demo:
gr.Markdown(
"<div class='a360-header'>A360 Croptool 🧬</div>"
"<div class='a360-subtitle'>"
"Test and prototype clinical cropping models for faces, bodies, and regions of interest."
"</div>"
)
with gr.Row():
with gr.Column(scale=1):
input_image = gr.Image(
label="Input Image",
type="pil",
height=480,
elem_id="input_image",
)
crop_target = gr.Dropdown(
CROP_TARGET_CHOICES,
value="Full Face",
label="Target Region",
info="What do you want to crop to?",
)
gr.Markdown("### Model Stack (configuration only – models wired later)")
face_model = gr.Radio(
FACE_MODEL_CHOICES,
value="RetinaFace (InsightFace)",
label="Face / Landmark Model",
)
body_model = gr.Radio(
BODY_MODEL_CHOICES,
value="YOLOv8 Body Detector",
label="Body / Region Model",
)
seg_model = gr.Radio(
SEGMENTATION_MODEL_CHOICES,
value="SAM / SAM-HQ",
label="Segmentation / Mask Model",
)
text_prompt = gr.Textbox(
label="Optional Text Prompt (for CLIPSeg / SAM)",
placeholder="e.g., 'crop to lips', 'crop to abdomen', 'crop to jawline'",
lines=2,
)
run_btn = gr.Button("Run Cropping Prototype", variant="primary")
with gr.Column(scale=1):
output_image = gr.Image(
label="Cropped Output (stub – currently same as input)",
height=480,
)
debug_text = gr.Markdown(label="Cropping Summary")
run_btn.click(
stub_crop,
inputs=[input_image, crop_target, face_model, body_model, seg_model, text_prompt],
outputs=[output_image, debug_text],
)
return demo
app = create_app()
if __name__ == "__main__":
app.launch()