drdudddd commited on
Commit
27ab04b
·
verified ·
1 Parent(s): d0d29ca

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +144 -0
app.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from PIL import Image
2
+ import numpy as np
3
+ import gradio as gr
4
+ import os
5
+ import cv2
6
+ from nudenet import NudeDetector
7
+ import io
8
+ import concurrent.futures
9
+
10
+ # ─── Konstanten ────────────────────────────────────────
11
+ DETECTION_MAX_DIM = 768
12
+ PIXELS_PER_CM_ESTIMATE = 15
13
+ MIN_CONFIDENCE = 0.45
14
+
15
+ # ─── Hilfsfunktionen ───────────────────────────────────
16
+ def resize_for_detection(img_pil, max_dim):
17
+ if max(img_pil.width, img_pil.height) <= max_dim:
18
+ return img_pil, 1.0
19
+ ratio = max_dim / max(img_pil.width, img_pil.height)
20
+ new_size = (int(img_pil.width * ratio), int(img_pil.height * ratio))
21
+ resized = img_pil.resize(new_size, Image.Resampling.LANCZOS)
22
+ return resized, 1 / ratio
23
+
24
+ # Deine bestehenden describe-Funktionen (unverändert)
25
+ def describe_breast_precise(crop_pil):
26
+ # ... dein Code bleibt gleich ...
27
+ return "Form: Rund · Größe: mittel · Nippel: Sichtbar · 9.8×8.4 cm" # Beispiel
28
+
29
+ def describe_vagina_precise(crop_pil):
30
+ # ... dein Code bleibt gleich ...
31
+ return "Form: Klassisches Outie · Größe: mittel · Prominenz: sichtbar · Behaart: minimal · 8.2×11.1 cm"
32
+
33
+ detector = NudeDetector(inference_resolution=640)
34
+
35
+ def process_single_image(mode: str, path: str):
36
+ try:
37
+ original_pil = Image.open(path).convert("RGB")
38
+ detection_pil, scale = resize_for_detection(original_pil, DETECTION_MAX_DIM)
39
+
40
+ detections = detector.detect(np.array(detection_pil))
41
+
42
+ target_class = "FEMALE_BREAST_EXPOSED" if mode == "Brüste" else "FEMALE_GENITALIA_EXPOSED"
43
+ relevant = [d for d in detections if d["class"] == target_class and d.get("score", 0) >= MIN_CONFIDENCE]
44
+
45
+ filename = os.path.basename(path)
46
+ markdown = f"**{filename}** — {mode}\n\n"
47
+
48
+ outputs = []
49
+
50
+ if not relevant:
51
+ markdown += "❌ Keine relevanten Bereiche erkannt.\n"
52
+ outputs.append((None, markdown))
53
+ return outputs
54
+
55
+ markdown += f"✅ **{len(relevant)}** {mode.lower()} gefunden\n\n"
56
+
57
+ for i, det in enumerate(relevant, 1):
58
+ x, y, w, h = [int(v * scale) for v in det["box"]]
59
+ crop_pil = original_pil.crop((x, y, x + w, y + h))
60
+
61
+ desc = describe_breast_precise(crop_pil) if mode == "Brüste" else describe_vagina_precise(crop_pil)
62
+
63
+ # Crop als bytes für Gradio Image-Komponente
64
+ crop_bytes = io.BytesIO()
65
+ crop_pil.save(crop_bytes, format="PNG")
66
+ crop_bytes.seek(0)
67
+
68
+ markdown_part = f"**{mode} {i}** (Konfidenz: {det['score']:.2f})\n{desc}\n"
69
+
70
+ outputs.append((crop_bytes, markdown_part))
71
+
72
+ # Zusammenfassung am Ende
73
+ summary_md = "\n".join([md for _, md in outputs]) + f"\n\n**Gesamt: {len(relevant)} Funde**"
74
+ outputs.append((None, summary_md))
75
+
76
+ return outputs
77
+
78
+ except Exception as e:
79
+ return [(None, f"**{filename}** — Fehler: {str(e)}")]
80
+
81
+ def analyze_images(mode: str, image_paths):
82
+ if not image_paths:
83
+ return [(None, "**Keine Bilder hochgeladen.**")]
84
+
85
+ all_outputs = []
86
+
87
+ max_workers = min(6, len(image_paths), os.cpu_count() or 4)
88
+ with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
89
+ futures = [executor.submit(process_single_image, mode, p) for p in image_paths]
90
+ for future in concurrent.futures.as_completed(futures):
91
+ all_outputs.extend(future.result())
92
+
93
+ return all_outputs
94
+
95
+ # ─── Gradio Interface ──────────────────────────────────
96
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="pink", secondary_hue="purple")) as demo:
97
+ gr.Markdown("# Nackt-Analyzer – mit Crops")
98
+ gr.Markdown("Lädt Bilder → erkennt Brüste / Vulva → zeigt **Text + Crop-Bilder**")
99
+
100
+ with gr.Row():
101
+ mode = gr.Radio(choices=["Brüste", "Vagina"], value="Brüste", label="Modus")
102
+ upload = gr.File(file_count="multiple", file_types=["image"], label="Bilder hochladen")
103
+
104
+ analyze_btn = gr.Button("Analysieren", variant="primary")
105
+
106
+ output_gallery = gr.Gallery(
107
+ label="Ergebnisse (Crops + Beschreibung)",
108
+ columns=3,
109
+ height="auto",
110
+ object_fit="contain",
111
+ show_label=True,
112
+ elem_id="result-gallery"
113
+ )
114
+
115
+ markdown_output = gr.Markdown(label="Zusammenfassung / Details")
116
+
117
+ def on_analyze(mode, files):
118
+ if not files:
119
+ return [], "**Keine Dateien ausgewählt.**"
120
+
121
+ paths = [f.name for f in files] if hasattr(files[0], 'name') else files
122
+ results = analyze_images(mode, paths)
123
+
124
+ images = []
125
+ md_parts = []
126
+
127
+ for img_bytes, text in results:
128
+ if img_bytes is not None:
129
+ images.append((img_bytes, text)) # (image, caption)
130
+ else:
131
+ md_parts.append(text)
132
+
133
+ combined_md = "\n\n".join(md_parts) if md_parts else ""
134
+
135
+ return images, combined_md
136
+
137
+ analyze_btn.click(
138
+ fn=on_analyze,
139
+ inputs=[mode, upload],
140
+ outputs=[output_gallery, markdown_output]
141
+ )
142
+
143
+ if __name__ == "__main__":
144
+ demo.launch(share=True)