ddd / app.py
drdudddd's picture
Create app.py
27ab04b verified
from PIL import Image
import numpy as np
import gradio as gr
import os
import cv2
from nudenet import NudeDetector
import io
import concurrent.futures
# ─── Konstanten ────────────────────────────────────────
DETECTION_MAX_DIM = 768
PIXELS_PER_CM_ESTIMATE = 15
MIN_CONFIDENCE = 0.45
# ─── Hilfsfunktionen ───────────────────────────────────
def resize_for_detection(img_pil, max_dim):
if max(img_pil.width, img_pil.height) <= max_dim:
return img_pil, 1.0
ratio = max_dim / max(img_pil.width, img_pil.height)
new_size = (int(img_pil.width * ratio), int(img_pil.height * ratio))
resized = img_pil.resize(new_size, Image.Resampling.LANCZOS)
return resized, 1 / ratio
# Deine bestehenden describe-Funktionen (unverändert)
def describe_breast_precise(crop_pil):
# ... dein Code bleibt gleich ...
return "Form: Rund · Größe: mittel · Nippel: Sichtbar · 9.8×8.4 cm" # Beispiel
def describe_vagina_precise(crop_pil):
# ... dein Code bleibt gleich ...
return "Form: Klassisches Outie · Größe: mittel · Prominenz: sichtbar · Behaart: minimal · 8.2×11.1 cm"
detector = NudeDetector(inference_resolution=640)
def process_single_image(mode: str, path: str):
try:
original_pil = Image.open(path).convert("RGB")
detection_pil, scale = resize_for_detection(original_pil, DETECTION_MAX_DIM)
detections = detector.detect(np.array(detection_pil))
target_class = "FEMALE_BREAST_EXPOSED" if mode == "Brüste" else "FEMALE_GENITALIA_EXPOSED"
relevant = [d for d in detections if d["class"] == target_class and d.get("score", 0) >= MIN_CONFIDENCE]
filename = os.path.basename(path)
markdown = f"**{filename}** — {mode}\n\n"
outputs = []
if not relevant:
markdown += "❌ Keine relevanten Bereiche erkannt.\n"
outputs.append((None, markdown))
return outputs
markdown += f"✅ **{len(relevant)}** {mode.lower()} gefunden\n\n"
for i, det in enumerate(relevant, 1):
x, y, w, h = [int(v * scale) for v in det["box"]]
crop_pil = original_pil.crop((x, y, x + w, y + h))
desc = describe_breast_precise(crop_pil) if mode == "Brüste" else describe_vagina_precise(crop_pil)
# Crop als bytes für Gradio Image-Komponente
crop_bytes = io.BytesIO()
crop_pil.save(crop_bytes, format="PNG")
crop_bytes.seek(0)
markdown_part = f"**{mode} {i}** (Konfidenz: {det['score']:.2f})\n{desc}\n"
outputs.append((crop_bytes, markdown_part))
# Zusammenfassung am Ende
summary_md = "\n".join([md for _, md in outputs]) + f"\n\n**Gesamt: {len(relevant)} Funde**"
outputs.append((None, summary_md))
return outputs
except Exception as e:
return [(None, f"**{filename}** — Fehler: {str(e)}")]
def analyze_images(mode: str, image_paths):
if not image_paths:
return [(None, "**Keine Bilder hochgeladen.**")]
all_outputs = []
max_workers = min(6, len(image_paths), os.cpu_count() or 4)
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = [executor.submit(process_single_image, mode, p) for p in image_paths]
for future in concurrent.futures.as_completed(futures):
all_outputs.extend(future.result())
return all_outputs
# ─── Gradio Interface ──────────────────────────────────
with gr.Blocks(theme=gr.themes.Soft(primary_hue="pink", secondary_hue="purple")) as demo:
gr.Markdown("# Nackt-Analyzer – mit Crops")
gr.Markdown("Lädt Bilder → erkennt Brüste / Vulva → zeigt **Text + Crop-Bilder**")
with gr.Row():
mode = gr.Radio(choices=["Brüste", "Vagina"], value="Brüste", label="Modus")
upload = gr.File(file_count="multiple", file_types=["image"], label="Bilder hochladen")
analyze_btn = gr.Button("Analysieren", variant="primary")
output_gallery = gr.Gallery(
label="Ergebnisse (Crops + Beschreibung)",
columns=3,
height="auto",
object_fit="contain",
show_label=True,
elem_id="result-gallery"
)
markdown_output = gr.Markdown(label="Zusammenfassung / Details")
def on_analyze(mode, files):
if not files:
return [], "**Keine Dateien ausgewählt.**"
paths = [f.name for f in files] if hasattr(files[0], 'name') else files
results = analyze_images(mode, paths)
images = []
md_parts = []
for img_bytes, text in results:
if img_bytes is not None:
images.append((img_bytes, text)) # (image, caption)
else:
md_parts.append(text)
combined_md = "\n\n".join(md_parts) if md_parts else ""
return images, combined_md
analyze_btn.click(
fn=on_analyze,
inputs=[mode, upload],
outputs=[output_gallery, markdown_output]
)
if __name__ == "__main__":
demo.launch(share=True)