Download app.py from drdudddd/ddd: direct link, hf CLI and curl.
- Browser
- Download file 5.5 kB
-
https://huggingface.co/spaces/drdudddd/ddd/resolve/main/app.py
- Command line
-
hf download hf://spaces/drdudddd/ddd/app.py
-
curl -L -o app.py https://huggingface.co/spaces/drdudddd/ddd/resolve/main/app.py
5.5 kB
| 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) |