SLSLK commited on
Commit
741b5b2
·
verified ·
1 Parent(s): 72ae5cb

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +279 -114
app.py CHANGED
@@ -1,119 +1,284 @@
1
- import gradio as gr
2
- import numpy as np
3
- from PIL import Image, ImageDraw, ImageFont
4
- import cv2
5
- from nudenet import NudeDetector
6
-
7
- # --- Konstanten ---
8
- DETECTION_MAX_DIM = 768
9
- PIXELS_PER_CM_ESTIMATE = 15
10
- MIN_CONFIDENCE = 0.45
11
-
12
- # --- NudeNet Detector ---
13
- detector = NudeDetector(inference_resolution=640)
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
- scale = 1 / ratio
23
- return resized, scale
24
-
25
- def describe_breast_precise(crop_pil):
26
- w,h = crop_pil.size
27
- if w*h == 0:
28
- return "Fehler: leeres Crop"
29
- gray = cv2.cvtColor(np.array(crop_pil), cv2.COLOR_RGB2GRAY)
30
- _, thresh = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
31
- contours,_ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
32
- nipple_detected = any(
33
- 40 < cv2.contourArea(c) < (w*h/4) and (p:=cv2.arcLength(c,True))>0 and
34
- (4*np.pi*cv2.contourArea(c)/(p*p))>0.55
35
- for c in contours
36
- )
37
- ratio = w/h
38
- shape = "Breit" if ratio>1.15 else "Hoch" if ratio<0.85 else "Rund"
39
- size = "klein" if w*h<28000 else "mittel" if w*h<75000 else "groß" if w*h<140000 else "sehr groß"
40
- w_cm = round(w/PIXELS_PER_CM_ESTIMATE,1)
41
- h_cm = round(h/PIXELS_PER_CM_ESTIMATE,1)
42
- return f"Brust: {shape}, {size}, Nippel: {'Ja' if nipple_detected else 'Nein'}, {w_cm}x{h_cm}cm"
43
-
44
- def describe_vagina_precise(crop_pil):
45
- w,h = crop_pil.size
46
- if w*h == 0:
47
- return "Fehler: leeres Crop"
48
- gray = cv2.cvtColor(np.array(crop_pil), cv2.COLOR_RGB2GRAY)
49
- hair_ratio = np.sum(cv2.inRange(gray, 35, 145) > 0) / (w*h) # <--- korrigiert
50
- shaved = "rasiert" if hair_ratio < 0.04 else "minimal" if hair_ratio < 0.13 else "Brazilian" if hair_ratio < 0.36 else "behaart"
51
- ratio = w/h
52
- area = w*h
53
- if area < 18000:
54
- form_desc = "Innie"
55
- elif area > 65000 and ratio > 1.45:
56
- form_desc = "Outie (Puff)"
57
- elif ratio > 1.45:
58
- form_desc = "Outie"
59
- else:
60
- form_desc = "Innie/Outie"
61
- size = "winzig" if area<18000 else "klein" if area<38000 else "mittel" if area<65000 else "groß"
62
- w_cm = round(w/PIXELS_PER_CM_ESTIMATE,1)
63
- h_cm = round(h/PIXELS_PER_CM_ESTIMATE,1)
64
- return f"Vagina: {form_desc}, {size}, {shaved}, {w_cm}x{h_cm}cm"
65
-
66
- # --- Bildverarbeitung ---
67
- def process_image(image):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  try:
69
- original_pil = Image.fromarray(image).convert("RGB") if isinstance(image,np.ndarray) else image.convert("RGB")
70
- detection_pil, scale = resize_for_detection(original_pil, DETECTION_MAX_DIM)
71
- detections = detector.detect(np.array(detection_pil))
72
- draw = ImageDraw.Draw(original_pil)
73
- font = ImageFont.load_default()
74
- results_text = []
75
-
76
- for det in detections:
77
- label = det["class"]
78
- score = det.get("score",0)
79
- if score < MIN_CONFIDENCE:
80
- continue
81
- if label not in ["FEMALE_BREAST_EXPOSED","FEMALE_GENITALIA_EXPOSED"]:
82
- continue
83
- x,y,w,h = [int(v*scale) for v in det["box"]]
84
- crop_pil = original_pil.crop((x,y,x+w,y+h))
85
- if label=="FEMALE_BREAST_EXPOSED":
86
- desc = describe_breast_precise(crop_pil)
87
- color = (255,46,130)
88
- else:
89
- desc = describe_vagina_precise(crop_pil)
90
- color = (138,43,226)
91
- draw.rectangle([x,y,x+w,y+h],outline=color,width=4)
92
- text_pos = (x,y-15 if y>15 else y+h)
93
- draw.text(text_pos,desc,fill=color,font=font)
94
- results_text.append(desc)
95
-
96
- if not results_text:
97
- draw.text((10,10),"Keine relevanten Bereiche erkannt.",fill=(255,0,0),font=font)
98
-
99
- return np.array(original_pil)
100
 
101
  except Exception as e:
102
- print(f"Fehler: {e}")
103
  return None
104
 
105
- # --- Gradio App ---
106
- css = """
107
- body { background: #0f0f1a; color: #e0e0ff; }
108
- .gradio-container { max-width: 900px !important; margin: auto; }
109
- h1 { color: #ff2e82; text-align: center; }
110
- """
111
-
112
- with gr.Blocks(css=css) as demo:
113
- gr.Markdown("# 👙 Automatischer Nackt-Analyzer")
114
- gr.Markdown("Lade ein Bild hoch und erhalte direkt das analysierte Bild mit Annotationen.")
115
- input_image = gr.Image(type="numpy", label="Bild hochladen")
116
- output_image = gr.Image(label="Analyse-Ergebnis")
117
- input_image.change(fn=process_image, inputs=input_image, outputs=output_image)
118
-
119
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ from gradio_client import Client, handle_file
3
+ import re
4
+ import threading
5
+ import time
6
+ import requests
7
+ import os
8
+ import uuid
9
+ import tempfile
10
+
11
+ # ---------------------------
12
+ # CONFIG
13
+ # ---------------------------
14
+ IMAGE_DIR = "data/images"
15
+ os.makedirs(IMAGE_DIR, exist_ok=True)
16
+
17
+ client_main = Client("selfit-camera/Omni-Image-Editor")
18
+ client_process = Client("SLSLK/n8")
19
+
20
+ # ---------------------------
21
+ # SESSION
22
+ # ---------------------------
23
+ if "gallery" not in st.session_state:
24
+ st.session_state.gallery = []
25
+
26
+ if "selected_image" not in st.session_state:
27
+ st.session_state.selected_image = None
28
+
29
+ if "page" not in st.session_state:
30
+ st.session_state.page = "🖼️ Generieren"
31
+
32
+ # ---------------------------
33
+ # HELPERS
34
+ # ---------------------------
35
+ def extract_url(html):
36
+ match = re.search(r"src='([^']+)'", html)
37
+ return match.group(1) if match else None
38
+
39
+
40
+ def save_image_from_url(url):
41
+ try:
42
+ r = requests.get(url)
43
+ if r.status_code != 200:
44
+ st.error(f"❌ Download Error HTTP {r.status_code}")
45
+ return None
46
+
47
+ path = os.path.join(IMAGE_DIR, f"{uuid.uuid4()}.png")
48
+ with open(path, "wb") as f:
49
+ f.write(r.content)
50
+
51
+ return path
52
+ except Exception as e:
53
+ st.error(f"❌ Save Error: {e}")
54
+ return None
55
+
56
+
57
+ def prepare_image_for_api(input_image):
58
+ """
59
+ 🔥 UNIVERSAL FIX:
60
+ - Upload → temp file
61
+ - Path direkt
62
+ - URL → direkt
63
+ immer handle_file(...)
64
+ """
65
+ try:
66
+ # 📥 Upload (Streamlit)
67
+ if hasattr(input_image, "getvalue"):
68
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".png")
69
+ tmp.write(input_image.getvalue())
70
+ tmp.close()
71
+ return handle_file(tmp.name)
72
+
73
+ # 📁 Lokaler Pfad oder URL
74
+ elif isinstance(input_image, str):
75
+ return handle_file(input_image)
76
+
77
+ else:
78
+ return None
79
+
80
+ except Exception as e:
81
+ st.error(f"❌ Prepare Error: {e}")
82
+ return None
83
+
84
+
85
+ # ---------------------------
86
+ # API FUNCTIONS
87
+ # ---------------------------
88
+ def generate_image(prompt):
89
+ try:
90
+ result = client_main.predict(
91
+ prompt=prompt,
92
+ aspect_ratio="16:9",
93
+ api_name="/text_to_image_interface"
94
+ )
95
+ return extract_url(result[0])
96
+ except Exception as e:
97
+ st.error(f"❌ Generate Error: {e}")
98
+ return None
99
+
100
+
101
+ def edit_image(input_image, prompt):
102
+ try:
103
+ image_input = prepare_image_for_api(input_image)
104
+ if not image_input:
105
+ st.error("❌ Invalid image input")
106
+ return None
107
+
108
+ result = client_main.predict(
109
+ input_image=image_input,
110
+ prompt=prompt,
111
+ api_name="/edit_image_interface"
112
+ )
113
+
114
+ html = result[0]
115
+ return extract_url(html)
116
+
117
+ except Exception as e:
118
+ st.error(f"❌ Edit Error: {e}")
119
+ return None
120
+
121
+
122
+ def process_image(input_image):
123
  try:
124
+ image_input = prepare_image_for_api(input_image)
125
+ if not image_input:
126
+ st.error("❌ Invalid image input")
127
+ return None
128
+
129
+ result = client_process.predict(
130
+ image=image_input,
131
+ api_name="/process_image"
132
+ )
133
+
134
+ return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
  except Exception as e:
137
+ st.error(f"❌ Process Error: {e}")
138
  return None
139
 
140
+
141
+ # ---------------------------
142
+ # ASYNC
143
+ # ---------------------------
144
+ def run_async(func, *args):
145
+ result = {"done": False, "data": None}
146
+
147
+ def task():
148
+ result["data"] = func(*args)
149
+ result["done"] = True
150
+
151
+ threading.Thread(target=task).start()
152
+ return result
153
+
154
+
155
+ def show_progress(job):
156
+ progress_bar = st.progress(0)
157
+ percent = 0
158
+
159
+ while not job["done"]:
160
+ percent += 5
161
+ if percent > 95:
162
+ percent = 95
163
+
164
+ progress_bar.progress(percent)
165
+ time.sleep(0.2)
166
+
167
+ progress_bar.progress(100)
168
+
169
+
170
+ # ---------------------------
171
+ # UI
172
+ # ---------------------------
173
+ st.set_page_config(layout="wide")
174
+ st.title("🎨 Omni Image Studio")
175
+
176
+ page = st.radio(
177
+ "Navigation",
178
+ ["🖼️ Generieren", "✨ Bearbeiten", "🧪 Analyse", "📚 Galerie"],
179
+ index=["🖼️ Generieren", "✨ Bearbeiten", "🧪 Analyse", "📚 Galerie"].index(st.session_state.page),
180
+ horizontal=True
181
+ )
182
+
183
+ st.session_state.page = page
184
+
185
+ # ---------------------------
186
+ # GENERATE
187
+ # ---------------------------
188
+ if page == "🖼️ Generieren":
189
+ prompt = st.text_area("Prompt")
190
+
191
+ if st.button("Generieren"):
192
+ job = run_async(generate_image, prompt)
193
+ show_progress(job)
194
+
195
+ url = job["data"]
196
+ if url:
197
+ path = save_image_from_url(url)
198
+ st.image(path)
199
+
200
+ st.session_state.gallery.append({
201
+ "path": path,
202
+ "prompt": prompt
203
+ })
204
+
205
+ # ---------------------------
206
+ # EDIT
207
+ # ---------------------------
208
+ elif page == "✨ Bearbeiten":
209
+ if st.session_state.selected_image:
210
+ st.image(st.session_state.selected_image)
211
+ input_image = st.session_state.selected_image
212
+ else:
213
+ input_image = st.file_uploader("Upload Image")
214
+
215
+ prompt = st.text_area("Edit Prompt")
216
+
217
+ if st.button("Bearbeiten"):
218
+ job = run_async(edit_image, input_image, prompt)
219
+ show_progress(job)
220
+
221
+ url = job["data"]
222
+ if url:
223
+ path = save_image_from_url(url)
224
+ st.image(path)
225
+
226
+ st.session_state.gallery.append({
227
+ "path": path,
228
+ "prompt": prompt
229
+ })
230
+
231
+ # ---------------------------
232
+ # ANALYSE
233
+ # ---------------------------
234
+ elif page == "🧪 Analyse":
235
+ if st.session_state.selected_image:
236
+ st.image(st.session_state.selected_image)
237
+ input_image = st.session_state.selected_image
238
+ else:
239
+ input_image = st.file_uploader("Upload Image")
240
+
241
+ if st.button("Analysieren"):
242
+ job = run_async(process_image, input_image)
243
+ show_progress(job)
244
+
245
+ result = job["data"]
246
+
247
+ if isinstance(result, str) and result.startswith("http"):
248
+ path = save_image_from_url(result)
249
+ st.image(path)
250
+
251
+ st.session_state.gallery.append({
252
+ "path": path,
253
+ "prompt": "Analyse"
254
+ })
255
+ else:
256
+ st.write(result)
257
+
258
+ # ---------------------------
259
+ # GALLERY
260
+ # ---------------------------
261
+ elif page == "📚 Galerie":
262
+ if not st.session_state.gallery:
263
+ st.info("Keine Bilder")
264
+ else:
265
+ cols = st.columns(3)
266
+
267
+ for i, item in enumerate(st.session_state.gallery):
268
+ with cols[i % 3]:
269
+ st.image(item["path"], caption=item["prompt"])
270
+
271
+ c1, c2 = st.columns(2)
272
+
273
+ if c1.button("✏️ Edit", key=f"e{i}"):
274
+ st.session_state.selected_image = item["path"]
275
+ st.session_state.page = "✨ Bearbeiten"
276
+ st.rerun()
277
+
278
+ if c2.button("🧪 Analyse", key=f"a{i}"):
279
+ st.session_state.selected_image = item["path"]
280
+ st.session_state.page = "🧪 Analyse"
281
+ st.rerun()
282
+
283
+ if st.button("🗑️ Clear"):
284
+ st.session_state.gallery = []