from __future__ import annotations import base64 import json import os import smtplib from datetime import datetime, timezone from email.message import EmailMessage from io import BytesIO import gradio as gr import requests from PIL import Image from aminatron_model import detect_image, format_detections, render_result_image, summarize_detections SPACE_MODEL = os.getenv("AMINATRON_MODEL") or None EMAIL_TO = os.getenv("EMAIL_TO", "mammadov.amin2000@gmail.com") EMAIL_FROM = os.getenv("EMAIL_FROM", "Aminatron ") RESEND_API_KEY = os.getenv("RESEND_API_KEY") SMTP_HOST = os.getenv("SMTP_HOST", "smtp.gmail.com") SMTP_PORT = int(os.getenv("SMTP_PORT", "587")) SMTP_USER = os.getenv("SMTP_USER") SMTP_PASSWORD = os.getenv("SMTP_PASSWORD") or os.getenv("GMAIL_APP_PASSWORD") def image_to_png_bytes(image: Image.Image) -> bytes: """Convert a PIL image to PNG bytes for email attachment.""" buffer = BytesIO() image.convert("RGB").save(buffer, format="PNG") return buffer.getvalue() def build_email_payload( original: Image.Image, annotated: Image.Image, detections: list[dict[str, object]], summary: dict[str, int], ) -> tuple[str, str, bytes, bytes]: """Build reusable email subject/body/attachments.""" timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") subject = f"Aminatron prediction review - {timestamp}" body = ( "A new Aminatron Space prediction was processed.\n\n" f"Time: {timestamp}\n" f"Summary: {json.dumps(summary, ensure_ascii=False)}\n\n" f"Detections:\n{json.dumps(detections, ensure_ascii=False, indent=2)}\n" ) return subject, body, image_to_png_bytes(original), image_to_png_bytes(annotated) def send_review_email_resend( original: Image.Image, annotated: Image.Image, detections: list[dict[str, object]], summary: dict[str, int], ) -> str: """Send review email through Resend HTTPS API.""" if not RESEND_API_KEY: return "Resend is not configured. Add RESEND_API_KEY to Space secrets." subject, body, original_png, result_png = build_email_payload( original, annotated, detections, summary, ) response = requests.post( "https://api.resend.com/emails", headers={ "Authorization": f"Bearer {RESEND_API_KEY}", "Content-Type": "application/json", }, json={ "from": EMAIL_FROM, "to": [EMAIL_TO], "subject": subject, "text": body, "attachments": [ { "filename": "original.png", "content": base64.b64encode(original_png).decode("ascii"), }, { "filename": "aminatron_result.png", "content": base64.b64encode(result_png).decode("ascii"), }, ], }, timeout=30, ) if response.status_code >= 400: return f"Resend email failed: {response.status_code} {response.text}" return f"Email sent to {EMAIL_TO} through Resend." def send_review_email( original: Image.Image, annotated: Image.Image, detections: list[dict[str, object]], summary: dict[str, int], ) -> str: """Send original and processed images to email.""" if RESEND_API_KEY: try: return send_review_email_resend(original, annotated, detections, summary) except Exception as error: error_message = f"Resend email failed: {error}" print(error_message) return error_message if not SMTP_USER or not SMTP_PASSWORD: return ( "Email is not configured. Add RESEND_API_KEY, or SMTP_USER and " "SMTP_PASSWORD/GMAIL_APP_PASSWORD." ) subject, body, original_png, result_png = build_email_payload( original, annotated, detections, summary, ) message = EmailMessage() message["Subject"] = subject message["From"] = SMTP_USER message["To"] = EMAIL_TO message.set_content(body) message.add_attachment( original_png, maintype="image", subtype="png", filename="original.png", ) message.add_attachment( result_png, maintype="image", subtype="png", filename="aminatron_result.png", ) try: smtp_class = smtplib.SMTP_SSL if SMTP_PORT == 465 else smtplib.SMTP with smtp_class(SMTP_HOST, SMTP_PORT, timeout=20) as smtp: if SMTP_PORT != 465: smtp.starttls() smtp.login(SMTP_USER, SMTP_PASSWORD) smtp.send_message(message) return f"Email sent to {EMAIL_TO}." except Exception as error: error_message = f"Email notification failed: {error}" print(error_message) return error_message def predict(image: Image.Image, confidence: float, iou: float, image_size: int, max_detections: int): """Detect objects for the Gradio interface.""" if image is None: return None, {}, [], None result = detect_image( image, model_path=SPACE_MODEL, confidence=confidence, iou=iou, image_size=image_size, max_detections=max_detections, ) detections = format_detections(result) summary = summarize_detections(detections) annotated_image = render_result_image(result) rows = [ [item["class"], f"{item['confidence'] * 100:.2f}%", item["box"]] for item in detections ] email_status = send_review_email(image, annotated_image, detections, summary) print(email_status) share_state = { "original": image, "annotated": annotated_image, "detections": detections, "summary": summary, } return annotated_image, summary, rows, share_state def share_result(share_state): """Send the latest prediction result to email manually from the Share button.""" if not share_state: print("No result to share yet. Run Detect first.") return email_status = send_review_email( share_state["original"], share_state["annotated"], share_state["detections"], share_state["summary"], ) print(email_status) with gr.Blocks(title="Aminatron") as demo: gr.Markdown( """ # Aminatron Aminatron is a multi-object detection model. Upload one image and it can find several COCO objects at once: people, cats, dogs, birds, cows, cars, chairs and more. """ ) with gr.Row(): image_input = gr.Image(type="pil", label="Upload image") image_output = gr.Image(type="pil", label="Detected objects") with gr.Row(): confidence = gr.Slider(0.05, 0.9, value=0.25, step=0.05, label="Confidence") iou = gr.Slider(0.1, 0.9, value=0.45, step=0.05, label="IoU") image_size = gr.Dropdown([416, 512, 640, 768], value=640, label="Image size") max_detections = gr.Slider(1, 100, value=50, step=1, label="Max detections") share_state = gr.State(None) with gr.Row(): detect_button = gr.Button("Detect") share_button = gr.Button("Share result") summary_output = gr.JSON(label="Summary") detections_output = gr.Dataframe( headers=["Class", "Confidence", "Box [x1, y1, x2, y2]"], label="Detections", interactive=False, ) detect_button.click( fn=predict, inputs=[image_input, confidence, iou, image_size, max_detections], outputs=[image_output, summary_output, detections_output, share_state], ) share_button.click( fn=share_result, inputs=[share_state], outputs=[], ) if __name__ == "__main__": demo.launch()