prithivMLmods commited on
Commit
1aba00a
Β·
verified Β·
1 Parent(s): 10475c0

update bug fix -- @cpu

Browse files
Files changed (1) hide show
  1. app.py +33 -47
app.py CHANGED
@@ -11,7 +11,7 @@ import base64
11
  import json
12
  from io import BytesIO
13
  from PIL import Image
14
- from transformers import pipeline as hf_pipeline
15
 
16
  MAX_SEED = np.iinfo(np.int32).max
17
  LANCZOS = getattr(Image, "Resampling", Image).LANCZOS
@@ -52,37 +52,35 @@ try:
52
  except Exception as e:
53
  print(f"Warning: Could not set FA3 processor: {e}")
54
 
55
- # ── NCII safety guard (CPU-only, loaded at startup) ──────────────────────────
56
- NCII_MODEL_ID = "hfmlsoc/ncii-guard-v02"
57
  NCII_UNSAFE_LABEL = "ncii"
58
  NCII_THRESHOLD = 0.5
59
  NCII_BLOCK_MESSAGE = "You entered prompt is NCII (non-consensual intimate imagery) and your request will not be processed. Try with Safe Prompts."
60
 
61
- print("Loading NCII safety guard model (CPU-only)...")
62
  try:
63
- # device=-1 forces CPU inference β€” never touches the GPU
64
- ncii_guard = hf_pipeline("text-classification", model=NCII_MODEL_ID, device=-1)
65
- print("NCII guard loaded successfully on CPU.")
66
  except Exception as e:
67
- ncii_guard = None
68
- print(f"Warning: Could not load NCII guard model ({NCII_MODEL_ID}): {e}")
69
 
70
 
71
  def check_ncii_safety(prompt_text):
72
- """Run NCII classification on CPU. Returns (is_unsafe, label, score)."""
73
- if ncii_guard is None or not prompt_text or not prompt_text.strip():
 
 
 
74
  return False, "unknown", 0.0
75
  try:
76
- out = ncii_guard(prompt_text.strip())
77
- if isinstance(out, list) and len(out) > 0:
78
- top = out[0]
79
- label = str(top.get("label", "")).lower()
80
- score = float(top.get("score", 0.0))
81
- is_unsafe = (label == NCII_UNSAFE_LABEL) and (score >= NCII_THRESHOLD)
82
- return is_unsafe, label, score
83
  except Exception as e:
84
  print(f"NCII guard inference error: {e}")
85
- return False, "unknown", 0.0
86
 
87
 
88
  # ── Examples Config ───────────────────────────────────────────────────────────
@@ -95,7 +93,6 @@ EXAMPLES_CONFIG = [
95
  {"images": ["examples/10.jpg", "examples/11.png"], "prompt": "Replace the current clothing with the clothing from the reference image 2. Keep the person's face, hairstyle, body pose, background, lighting, and camera angle unchanged. Ensure the new outfit fits naturally with realistic fabric texture, proper shadows, folds, and accurate proportions. Match the lighting, color tone, and overall style for a seamless and high-quality result."},
96
  ]
97
 
98
-
99
  def make_thumb_b64(path, max_dim=220):
100
  if not os.path.exists(path):
101
  return ""
@@ -109,7 +106,6 @@ def make_thumb_b64(path, max_dim=220):
109
  print(f"Thumbnail error for {path}: {e}")
110
  return ""
111
 
112
-
113
  def encode_full_image(path):
114
  if not os.path.exists(path):
115
  return ""
@@ -123,7 +119,6 @@ def encode_full_image(path):
123
  print(f"Encode error for {path}: {e}")
124
  return ""
125
 
126
-
127
  def build_client_config():
128
  """Static config consumed by the frontend: example cards."""
129
  examples = []
@@ -138,12 +133,10 @@ def build_client_config():
138
  "examples": examples,
139
  }
140
 
141
-
142
  print("Building client config (example thumbnails)…")
143
  CLIENT_CONFIG = build_client_config()
144
  print(f"Built config with {len(EXAMPLES_CONFIG)} examples.")
145
 
146
-
147
  def b64_to_pil_list(b64_json_str):
148
  if not b64_json_str or b64_json_str.strip() in ("", "[]"):
149
  return []
@@ -166,13 +159,11 @@ def b64_to_pil_list(b64_json_str):
166
  print(f"Error decoding image: {e}")
167
  return pil_images
168
 
169
-
170
  def pil_to_b64_png(image: Image.Image) -> str:
171
  buf = BytesIO()
172
  image.save(buf, format="PNG")
173
  return f"data:image/png;base64,{base64.b64encode(buf.getvalue()).decode()}"
174
 
175
-
176
  def update_dimensions_on_upload(image):
177
  if image is None:
178
  return 1024, 1024
@@ -190,31 +181,22 @@ def update_dimensions_on_upload(image):
190
  app = Server(title="FireRed-Image-Edit-1.0-Fast")
191
 
192
 
193
- # ── CPU-only safety check endpoint ───────────────────────────────────────────
194
- # This endpoint is NOT decorated with @spaces.GPU, so it runs entirely on CPU
195
- # and never triggers GPU allocation. The frontend calls this BEFORE submitting
196
- # the GPU job, so blocked prompts are caught early without wasting GPU resources.
197
  @app.api(name="check_safety", queue=False)
198
  def check_safety(prompt: str) -> dict:
199
- """Check if a prompt is safe (NCII guard). Runs on CPU only β€” no GPU allocation.
200
 
201
- Returns {"safe": bool, "label": str, "score": float, "message": str}.
 
 
202
  """
203
  if not prompt or not prompt.strip():
204
- return {"safe": True, "label": "empty", "score": 0.0, "message": ""}
205
-
206
  is_unsafe, label, score = check_ncii_safety(prompt)
207
  if is_unsafe:
208
- return {
209
- "safe": False,
210
- "label": label,
211
- "score": score,
212
- "message": NCII_BLOCK_MESSAGE,
213
- }
214
- return {"safe": True, "label": label, "score": score, "message": ""}
215
 
216
 
217
- # ── GPU image-edit endpoint ──────────────────────────────────────────────────
218
  @app.mcp.tool(name="edit_image")
219
  @app.api(name="edit_image")
220
  @spaces.GPU(size="xlarge")
@@ -228,11 +210,8 @@ def infer(
228
  ) -> dict:
229
  """Edit one or more images with FireRed-Image-Edit-1.1.
230
 
231
- Pre-condition: The frontend must call /check_safety BEFORE this endpoint
232
- to ensure the prompt is not blocked by the NCII guard. This function assumes
233
- the prompt has already been validated and focuses solely on GPU inference.
234
-
235
- Returns {"image": <base64 PNG data URL>, "seed": <seed used>, "status": "success"}.
236
  """
237
  gc.collect()
238
  torch.cuda.empty_cache()
@@ -243,6 +222,13 @@ def infer(
243
  if not prompt or prompt.strip() == "":
244
  raise gr.Error("Please enter an edit prompt.")
245
 
 
 
 
 
 
 
 
246
  if randomize_seed:
247
  seed = random.randint(0, MAX_SEED)
248
 
 
11
  import json
12
  from io import BytesIO
13
  from PIL import Image
14
+ import ncii_guard
15
 
16
  MAX_SEED = np.iinfo(np.int32).max
17
  LANCZOS = getattr(Image, "Resampling", Image).LANCZOS
 
52
  except Exception as e:
53
  print(f"Warning: Could not set FA3 processor: {e}")
54
 
55
+ # ── NCII safety guard (pure CPU subprocess, never touches spaces/CUDA) ──────
 
56
  NCII_UNSAFE_LABEL = "ncii"
57
  NCII_THRESHOLD = 0.5
58
  NCII_BLOCK_MESSAGE = "You entered prompt is NCII (non-consensual intimate imagery) and your request will not be processed. Try with Safe Prompts."
59
 
60
+ print("Starting NCII safety guard subprocess (CPU-only)…")
61
  try:
62
+ ncii_guard.start()
63
+ print("NCII guard subprocess ready.")
 
64
  except Exception as e:
65
+ print(f"Warning: Could not start NCII guard subprocess: {e}")
 
66
 
67
 
68
  def check_ncii_safety(prompt_text):
69
+ """Ask the CPU-side subprocess. No GPU is requested by this call.
70
+
71
+ Returns: (is_unsafe: bool, label: str, score: float)
72
+ """
73
+ if not prompt_text or not prompt_text.strip():
74
  return False, "unknown", 0.0
75
  try:
76
+ out = ncii_guard.classify(prompt_text.strip())
77
+ label = str(out.get("label", "")).lower()
78
+ score = float(out.get("score", 0.0))
79
+ is_unsafe = (label == NCII_UNSAFE_LABEL) and (score >= NCII_THRESHOLD)
80
+ return is_unsafe, label, score
 
 
81
  except Exception as e:
82
  print(f"NCII guard inference error: {e}")
83
+ return False, "unknown", 0.0
84
 
85
 
86
  # ── Examples Config ───────────────────────────────────────────────────────────
 
93
  {"images": ["examples/10.jpg", "examples/11.png"], "prompt": "Replace the current clothing with the clothing from the reference image 2. Keep the person's face, hairstyle, body pose, background, lighting, and camera angle unchanged. Ensure the new outfit fits naturally with realistic fabric texture, proper shadows, folds, and accurate proportions. Match the lighting, color tone, and overall style for a seamless and high-quality result."},
94
  ]
95
 
 
96
  def make_thumb_b64(path, max_dim=220):
97
  if not os.path.exists(path):
98
  return ""
 
106
  print(f"Thumbnail error for {path}: {e}")
107
  return ""
108
 
 
109
  def encode_full_image(path):
110
  if not os.path.exists(path):
111
  return ""
 
119
  print(f"Encode error for {path}: {e}")
120
  return ""
121
 
 
122
  def build_client_config():
123
  """Static config consumed by the frontend: example cards."""
124
  examples = []
 
133
  "examples": examples,
134
  }
135
 
 
136
  print("Building client config (example thumbnails)…")
137
  CLIENT_CONFIG = build_client_config()
138
  print(f"Built config with {len(EXAMPLES_CONFIG)} examples.")
139
 
 
140
  def b64_to_pil_list(b64_json_str):
141
  if not b64_json_str or b64_json_str.strip() in ("", "[]"):
142
  return []
 
159
  print(f"Error decoding image: {e}")
160
  return pil_images
161
 
 
162
  def pil_to_b64_png(image: Image.Image) -> str:
163
  buf = BytesIO()
164
  image.save(buf, format="PNG")
165
  return f"data:image/png;base64,{base64.b64encode(buf.getvalue()).decode()}"
166
 
 
167
  def update_dimensions_on_upload(image):
168
  if image is None:
169
  return 1024, 1024
 
181
  app = Server(title="FireRed-Image-Edit-1.0-Fast")
182
 
183
 
 
 
 
 
184
  @app.api(name="check_safety", queue=False)
185
  def check_safety(prompt: str) -> dict:
186
+ """Pure-CPU safety check. Runs BEFORE any @spaces.GPU resource is requested.
187
 
188
+ Returns:
189
+ {"status": "ok"} β€” prompt is safe, proceed
190
+ {"status": "blocked", "message": ...} β€” NCII detected
191
  """
192
  if not prompt or not prompt.strip():
193
+ return {"status": "ok"}
 
194
  is_unsafe, label, score = check_ncii_safety(prompt)
195
  if is_unsafe:
196
+ return {"status": "blocked", "message": NCII_BLOCK_MESSAGE}
197
+ return {"status": "ok", "label": label, "score": score}
 
 
 
 
 
198
 
199
 
 
200
  @app.mcp.tool(name="edit_image")
201
  @app.api(name="edit_image")
202
  @spaces.GPU(size="xlarge")
 
210
  ) -> dict:
211
  """Edit one or more images with FireRed-Image-Edit-1.1.
212
 
213
+ The frontend is expected to have already called /check_safety and
214
+ only submit here when safe. This is a defense-in-depth re-check.
 
 
 
215
  """
216
  gc.collect()
217
  torch.cuda.empty_cache()
 
222
  if not prompt or prompt.strip() == "":
223
  raise gr.Error("Please enter an edit prompt.")
224
 
225
+ # ── Defense-in-depth NCII re-check inside the GPU worker ──
226
+ is_unsafe, _, _ = check_ncii_safety(prompt)
227
+ if is_unsafe:
228
+ gc.collect()
229
+ torch.cuda.empty_cache()
230
+ return {"image": "", "seed": seed, "status": "blocked", "message": NCII_BLOCK_MESSAGE}
231
+
232
  if randomize_seed:
233
  seed = random.randint(0, MAX_SEED)
234