sync19 commited on
Commit
f53d446
Β·
verified Β·
1 Parent(s): 5516262

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +339 -300
app.py CHANGED
@@ -1,300 +1,339 @@
1
- from fastapi import FastAPI, UploadFile, File, Form, HTTPException
2
- from fastapi.responses import JSONResponse, StreamingResponse
3
- from fastapi.middleware.cors import CORSMiddleware
4
- from PIL import Image
5
- import torch
6
- import io
7
- import base64
8
- import tempfile
9
- import os
10
- from diffusers import AutoPipelineForInpainting, AutoencoderKL
11
- from typing import Optional
12
- import time
13
-
14
- # Initialize FastAPI app
15
- app = FastAPI(
16
- title="Virtual Try-On API",
17
- description="API for virtual clothing try-on using Stable Diffusion XL",
18
- version="1.0.0"
19
- )
20
-
21
- # Add CORS middleware for React Native
22
- app.add_middleware(
23
- CORSMiddleware,
24
- allow_origins=["*"], # Adjust for production
25
- allow_credentials=True,
26
- allow_methods=["*"],
27
- allow_headers=["*"],
28
- )
29
-
30
- # Global variables for models
31
- pipeline = None
32
- segment_body = None
33
-
34
- def load_models():
35
- """Load all required models"""
36
- global pipeline, segment_body
37
-
38
- print("πŸ”„ Loading VAE...")
39
- vae = AutoencoderKL.from_pretrained(
40
- "madebyollin/sdxl-vae-fp16-fix",
41
- torch_dtype=torch.float16
42
- )
43
-
44
- print("πŸ”„ Loading inpainting pipeline...")
45
- pipeline = AutoPipelineForInpainting.from_pretrained(
46
- "diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
47
- vae=vae,
48
- torch_dtype=torch.float16,
49
- variant="fp16",
50
- use_safetensors=True
51
- )
52
-
53
- # Use CPU for Hugging Face Spaces
54
- device = "cuda" if torch.cuda.is_available() else "cpu"
55
- pipeline = pipeline.to(device)
56
-
57
- print("πŸ”„ Loading IP-Adapter...")
58
- pipeline.load_ip_adapter(
59
- "h94/IP-Adapter",
60
- subfolder="sdxl_models",
61
- weight_name="ip-adapter_sdxl.bin",
62
- low_cpu_mem_usage=True
63
- )
64
-
65
- print("πŸ”„ Loading body segmentation...")
66
- try:
67
- from SegBody import segment_body as seg_func
68
- segment_body = seg_func
69
- print("βœ… Body segmentation loaded!")
70
- except ImportError:
71
- print("⚠️ SegBody module not found, segmentation will be disabled")
72
-
73
- print("βœ… All models loaded successfully!")
74
-
75
- @app.on_event("startup")
76
- async def startup_event():
77
- """Load models on startup"""
78
- load_models()
79
-
80
- @app.get("/")
81
- async def root():
82
- """Health check endpoint"""
83
- return {
84
- "status": "running",
85
- "message": "Virtual Try-On API is running!",
86
- "cuda_available": torch.cuda.is_available(),
87
- "device": "cuda" if torch.cuda.is_available() else "cpu"
88
- }
89
-
90
- @app.get("/health")
91
- async def health():
92
- """Health check endpoint"""
93
- return {"status": "healthy", "models_loaded": pipeline is not None}
94
-
95
- def image_to_base64(image: Image.Image) -> str:
96
- """Convert PIL Image to base64 string"""
97
- buffered = io.BytesIO()
98
- image.save(buffered, format="PNG")
99
- img_str = base64.b64encode(buffered.getvalue()).decode()
100
- return img_str
101
-
102
- def base64_to_image(base64_str: str) -> Image.Image:
103
- """Convert base64 string to PIL Image"""
104
- img_data = base64.b64decode(base64_str)
105
- return Image.open(io.BytesIO(img_data)).convert('RGB')
106
-
107
- @app.post("/tryon")
108
- async def virtual_tryon(
109
- person_image: UploadFile = File(..., description="Image of the person"),
110
- clothing_image: UploadFile = File(..., description="Image of the clothing"),
111
- prompt: str = Form("photorealistic, perfect body, beautiful skin, realistic skin, natural skin"),
112
- negative_prompt: str = Form("ugly, bad quality, bad anatomy, deformed body, deformed hands, deformed feet, deformed face, deformed clothing, deformed skin, bad skin, leggings, tights, stockings"),
113
- ip_scale: float = Form(0.8),
114
- strength: float = Form(0.99),
115
- guidance_scale: float = Form(7.5),
116
- num_steps: int = Form(50),
117
- return_format: str = Form("base64", description="base64 or image")
118
- ):
119
- """
120
- Virtual Try-On endpoint
121
-
122
- Args:
123
- person_image: Image file of the person
124
- clothing_image: Image file of the clothing
125
- prompt: Generation prompt
126
- negative_prompt: Negative prompt
127
- ip_scale: IP-Adapter influence (0.0-1.0)
128
- strength: Inpainting strength (0.0-1.0)
129
- guidance_scale: CFG scale
130
- num_steps: Number of inference steps
131
- return_format: Response format (base64 or image)
132
-
133
- Returns:
134
- Generated image in specified format
135
- """
136
- try:
137
- if pipeline is None:
138
- raise HTTPException(status_code=503, detail="Models not loaded yet")
139
-
140
- start_time = time.time()
141
-
142
- # Load and resize images
143
- print("πŸ“₯ Loading images...")
144
- person_img = Image.open(person_image.file).convert('RGB').resize((512, 512))
145
- clothing_img = Image.open(clothing_image.file).convert('RGB').resize((512, 512))
146
-
147
- # Generate body segmentation mask
148
- print("🎭 Generating segmentation mask...")
149
- if segment_body is None:
150
- # Create a simple fallback mask (full body) if segmentation not available
151
- mask_img = Image.new('L', (512, 512), 255)
152
- else:
153
- try:
154
- # Save person image to temp file for segmentation
155
- with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file:
156
- temp_path = tmp_file.name
157
- person_img.save(temp_path)
158
-
159
- seg_image, mask_img = segment_body(temp_path, face=False)
160
- mask_img = mask_img.resize((512, 512))
161
-
162
- # Clean up
163
- os.unlink(temp_path)
164
- except Exception as e:
165
- print(f"⚠️ Segmentation failed: {e}, using full mask")
166
- mask_img = Image.new('L', (512, 512), 255)
167
-
168
- # Set IP-Adapter scale
169
- pipeline.set_ip_adapter_scale(ip_scale)
170
-
171
- # Generate virtual try-on
172
- print("🎨 Generating virtual try-on...")
173
- result = pipeline(
174
- prompt=prompt,
175
- negative_prompt=negative_prompt,
176
- image=person_img,
177
- mask_image=mask_img,
178
- ip_adapter_image=clothing_img,
179
- strength=strength,
180
- guidance_scale=guidance_scale,
181
- num_inference_steps=num_steps,
182
- )
183
-
184
- generated_image = result.images[0]
185
-
186
- processing_time = time.time() - start_time
187
- print(f"βœ… Generation completed in {processing_time:.2f}s")
188
-
189
- # Return based on format
190
- if return_format == "image":
191
- # Return as image file
192
- img_byte_arr = io.BytesIO()
193
- generated_image.save(img_byte_arr, format='PNG')
194
- img_byte_arr.seek(0)
195
- return StreamingResponse(img_byte_arr, media_type="image/png")
196
- else:
197
- # Return as base64 JSON
198
- img_base64 = image_to_base64(generated_image)
199
- return JSONResponse({
200
- "success": True,
201
- "image": img_base64,
202
- "processing_time": processing_time,
203
- "parameters": {
204
- "prompt": prompt,
205
- "ip_scale": ip_scale,
206
- "strength": strength,
207
- "guidance_scale": guidance_scale,
208
- "num_steps": num_steps
209
- }
210
- })
211
-
212
- except Exception as e:
213
- print(f"❌ Error: {str(e)}")
214
- raise HTTPException(status_code=500, detail=str(e))
215
-
216
- @app.post("/tryon-base64")
217
- async def virtual_tryon_base64(
218
- person_image_base64: str = Form(..., description="Base64 encoded person image"),
219
- clothing_image_base64: str = Form(..., description="Base64 encoded clothing image"),
220
- prompt: str = Form("photorealistic, perfect body, beautiful skin, realistic skin, natural skin"),
221
- negative_prompt: str = Form("ugly, bad quality, bad anatomy, deformed body, deformed hands, deformed feet, deformed face, deformed clothing, deformed skin, bad skin, leggings, tights, stockings"),
222
- ip_scale: float = Form(0.8),
223
- strength: float = Form(0.99),
224
- guidance_scale: float = Form(7.5),
225
- num_steps: int = Form(50)
226
- ):
227
- """
228
- Virtual Try-On endpoint accepting base64 encoded images
229
- (Alternative endpoint for easier React Native integration)
230
- """
231
- try:
232
- if pipeline is None:
233
- raise HTTPException(status_code=503, detail="Models not loaded yet")
234
-
235
- start_time = time.time()
236
-
237
- # Decode base64 images
238
- print("πŸ“₯ Decoding base64 images...")
239
- person_img = base64_to_image(person_image_base64).resize((512, 512))
240
- clothing_img = base64_to_image(clothing_image_base64).resize((512, 512))
241
-
242
- # Generate body segmentation mask
243
- print("🎭 Generating segmentation mask...")
244
- if segment_body is None:
245
- mask_img = Image.new('L', (512, 512), 255)
246
- else:
247
- try:
248
- with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file:
249
- temp_path = tmp_file.name
250
- person_img.save(temp_path)
251
-
252
- seg_image, mask_img = segment_body(temp_path, face=False)
253
- mask_img = mask_img.resize((512, 512))
254
- os.unlink(temp_path)
255
- except Exception as e:
256
- print(f"⚠️ Segmentation failed: {e}, using full mask")
257
- mask_img = Image.new('L', (512, 512), 255)
258
-
259
- # Set IP-Adapter scale
260
- pipeline.set_ip_adapter_scale(ip_scale)
261
-
262
- # Generate virtual try-on
263
- print("🎨 Generating virtual try-on...")
264
- result = pipeline(
265
- prompt=prompt,
266
- negative_prompt=negative_prompt,
267
- image=person_img,
268
- mask_image=mask_img,
269
- ip_adapter_image=clothing_img,
270
- strength=strength,
271
- guidance_scale=guidance_scale,
272
- num_inference_steps=num_steps,
273
- )
274
-
275
- generated_image = result.images[0]
276
- processing_time = time.time() - start_time
277
- print(f"βœ… Generation completed in {processing_time:.2f}s")
278
-
279
- # Return as base64
280
- img_base64 = image_to_base64(generated_image)
281
- return JSONResponse({
282
- "success": True,
283
- "image": img_base64,
284
- "processing_time": processing_time,
285
- "parameters": {
286
- "prompt": prompt,
287
- "ip_scale": ip_scale,
288
- "strength": strength,
289
- "guidance_scale": guidance_scale,
290
- "num_steps": num_steps
291
- }
292
- })
293
-
294
- except Exception as e:
295
- print(f"❌ Error: {str(e)}")
296
- raise HTTPException(status_code=500, detail=str(e))
297
-
298
- if __name__ == "__main__":
299
- import uvicorn
300
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, UploadFile, File, Form, HTTPException
2
+ from fastapi.responses import JSONResponse, StreamingResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from PIL import Image
5
+ import torch
6
+ import io
7
+ import base64
8
+ import tempfile
9
+ import os
10
+ from diffusers import AutoPipelineForInpainting, AutoencoderKL
11
+ from typing import Optional
12
+ import time
13
+
14
+ # Initialize FastAPI app
15
+ app = FastAPI(
16
+ title="Virtual Try-On API",
17
+ description="API for virtual clothing try-on using Stable Diffusion XL",
18
+ version="1.0.0"
19
+ )
20
+
21
+ # Add CORS middleware for React Native
22
+ app.add_middleware(
23
+ CORSMiddleware,
24
+ allow_origins=["*"], # Adjust for production
25
+ allow_credentials=True,
26
+ allow_methods=["*"],
27
+ allow_headers=["*"],
28
+ )
29
+
30
+ # Global variables for models
31
+ pipeline = None
32
+ segment_body = None
33
+
34
+ def load_models():
35
+ """Load all required models"""
36
+ global pipeline, segment_body
37
+
38
+ # Determine device and dtype
39
+ device = "cuda" if torch.cuda.is_available() else "cpu"
40
+ dtype = torch.float16 if torch.cuda.is_available() else torch.float32
41
+
42
+ print(f"πŸ”„ Using device: {device}, dtype: {dtype}")
43
+
44
+ print("πŸ”„ Loading VAE...")
45
+ if torch.cuda.is_available():
46
+ vae = AutoencoderKL.from_pretrained(
47
+ "madebyollin/sdxl-vae-fp16-fix",
48
+ torch_dtype=dtype
49
+ )
50
+ else:
51
+ # Use standard VAE for CPU (fp32)
52
+ vae = AutoencoderKL.from_pretrained(
53
+ "stabilityai/sdxl-vae",
54
+ torch_dtype=dtype
55
+ )
56
+
57
+ print("πŸ”„ Loading inpainting pipeline...")
58
+ if torch.cuda.is_available():
59
+ pipeline = AutoPipelineForInpainting.from_pretrained(
60
+ "diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
61
+ vae=vae,
62
+ torch_dtype=dtype,
63
+ variant="fp16",
64
+ use_safetensors=True
65
+ )
66
+ else:
67
+ # Load without fp16 variant for CPU
68
+ pipeline = AutoPipelineForInpainting.from_pretrained(
69
+ "diffusers/stable-diffusion-xl-1.0-inpainting-0.1",
70
+ vae=vae,
71
+ torch_dtype=dtype,
72
+ use_safetensors=True
73
+ )
74
+
75
+ pipeline = pipeline.to(device)
76
+
77
+ print("πŸ”„ Loading IP-Adapter...")
78
+ pipeline.load_ip_adapter(
79
+ "h94/IP-Adapter",
80
+ subfolder="sdxl_models",
81
+ weight_name="ip-adapter_sdxl.bin",
82
+ low_cpu_mem_usage=True
83
+ )
84
+
85
+ print("πŸ”„ Loading body segmentation...")
86
+ try:
87
+ from SegBody import segment_body as seg_func
88
+ segment_body = seg_func
89
+ print("βœ… Body segmentation loaded!")
90
+ except ImportError:
91
+ print("⚠️ SegBody module not found, segmentation will be disabled")
92
+
93
+ print("βœ… All models loaded successfully!")
94
+
95
+ @app.on_event("startup")
96
+ async def startup_event():
97
+ """Load models on startup"""
98
+ load_models()
99
+
100
+ @app.get("/")
101
+ async def root():
102
+ """Health check endpoint"""
103
+ return {
104
+ "status": "running",
105
+ "message": "Virtual Try-On API is running!",
106
+ "cuda_available": torch.cuda.is_available(),
107
+ "device": "cuda" if torch.cuda.is_available() else "cpu"
108
+ }
109
+
110
+ @app.get("/health")
111
+ async def health():
112
+ """Health check endpoint"""
113
+ return {"status": "healthy", "models_loaded": pipeline is not None}
114
+
115
+ def image_to_base64(image: Image.Image) -> str:
116
+ """Convert PIL Image to base64 string"""
117
+ buffered = io.BytesIO()
118
+ image.save(buffered, format="PNG")
119
+ img_str = base64.b64encode(buffered.getvalue()).decode()
120
+ return img_str
121
+
122
+ def base64_to_image(base64_str: str) -> Image.Image:
123
+ """Convert base64 string to PIL Image"""
124
+ img_data = base64.b64decode(base64_str)
125
+ return Image.open(io.BytesIO(img_data)).convert('RGB')
126
+
127
+ @app.post("/tryon")
128
+ async def virtual_tryon(
129
+ person_image: UploadFile = File(..., description="Image of the person"),
130
+ clothing_image: UploadFile = File(..., description="Image of the clothing"),
131
+ prompt: str = Form("photorealistic, perfect body, beautiful skin, realistic skin, natural skin"),
132
+ negative_prompt: str = Form("ugly, bad quality, bad anatomy, deformed body, deformed hands, deformed feet, deformed face, deformed clothing, deformed skin, bad skin, leggings, tights, stockings"),
133
+ ip_scale: float = Form(0.8),
134
+ strength: float = Form(0.99),
135
+ guidance_scale: float = Form(7.5),
136
+ num_steps: int = Form(50),
137
+ return_format: str = Form("base64", description="base64 or image")
138
+ ):
139
+ """
140
+ Virtual Try-On endpoint
141
+
142
+ Args:
143
+ person_image: Image file of the person
144
+ clothing_image: Image file of the clothing
145
+ prompt: Generation prompt
146
+ negative_prompt: Negative prompt
147
+ ip_scale: IP-Adapter influence (0.0-1.0)
148
+ strength: Inpainting strength (0.0-1.0)
149
+ guidance_scale: CFG scale
150
+ num_steps: Number of inference steps
151
+ return_format: Response format (base64 or image)
152
+
153
+ Returns:
154
+ Generated image in specified format
155
+ """
156
+ try:
157
+ if pipeline is None:
158
+ raise HTTPException(status_code=503, detail="Models not loaded yet")
159
+
160
+ start_time = time.time()
161
+
162
+ # Load and resize images
163
+ print("πŸ“₯ Loading images...")
164
+ person_img = Image.open(person_image.file).convert('RGB').resize((512, 512))
165
+ clothing_img = Image.open(clothing_image.file).convert('RGB').resize((512, 512))
166
+
167
+ # Generate body segmentation mask
168
+ print("🎭 Generating segmentation mask...")
169
+ if segment_body is None:
170
+ # Create a simple fallback mask (full body) if segmentation not available
171
+ mask_img = Image.new('L', (512, 512), 255)
172
+ else:
173
+ try:
174
+ # Try calling segment_body - it might expect a file path or PIL Image
175
+ try:
176
+ # First try with PIL Image directly
177
+ seg_image, mask_img = segment_body(person_img, face=False)
178
+ except (TypeError, AttributeError):
179
+ # If that fails, try with file path
180
+ with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file:
181
+ temp_path = tmp_file.name
182
+ person_img.save(temp_path)
183
+
184
+ seg_image, mask_img = segment_body(temp_path, face=False)
185
+ os.unlink(temp_path)
186
+
187
+ # Ensure mask is PIL Image and resize
188
+ if isinstance(mask_img, str):
189
+ mask_img = Image.open(mask_img).convert('L')
190
+ mask_img = mask_img.resize((512, 512))
191
+
192
+ except Exception as e:
193
+ print(f"⚠️ Segmentation failed: {e}, using full mask")
194
+ mask_img = Image.new('L', (512, 512), 255)
195
+
196
+ # Set IP-Adapter scale
197
+ pipeline.set_ip_adapter_scale(ip_scale)
198
+
199
+ # Generate virtual try-on
200
+ print("🎨 Generating virtual try-on...")
201
+ result = pipeline(
202
+ prompt=prompt,
203
+ negative_prompt=negative_prompt,
204
+ image=person_img,
205
+ mask_image=mask_img,
206
+ ip_adapter_image=clothing_img,
207
+ strength=strength,
208
+ guidance_scale=guidance_scale,
209
+ num_inference_steps=num_steps,
210
+ )
211
+
212
+ generated_image = result.images[0]
213
+
214
+ processing_time = time.time() - start_time
215
+ print(f"βœ… Generation completed in {processing_time:.2f}s")
216
+
217
+ # Return based on format
218
+ if return_format == "image":
219
+ # Return as image file
220
+ img_byte_arr = io.BytesIO()
221
+ generated_image.save(img_byte_arr, format='PNG')
222
+ img_byte_arr.seek(0)
223
+ return StreamingResponse(img_byte_arr, media_type="image/png")
224
+ else:
225
+ # Return as base64 JSON
226
+ img_base64 = image_to_base64(generated_image)
227
+ return JSONResponse({
228
+ "success": True,
229
+ "image": img_base64,
230
+ "processing_time": processing_time,
231
+ "parameters": {
232
+ "prompt": prompt,
233
+ "ip_scale": ip_scale,
234
+ "strength": strength,
235
+ "guidance_scale": guidance_scale,
236
+ "num_steps": num_steps
237
+ }
238
+ })
239
+
240
+ except Exception as e:
241
+ print(f"❌ Error: {str(e)}")
242
+ raise HTTPException(status_code=500, detail=str(e))
243
+
244
+ @app.post("/tryon-base64")
245
+ async def virtual_tryon_base64(
246
+ person_image_base64: str = Form(..., description="Base64 encoded person image"),
247
+ clothing_image_base64: str = Form(..., description="Base64 encoded clothing image"),
248
+ prompt: str = Form("photorealistic, perfect body, beautiful skin, realistic skin, natural skin"),
249
+ negative_prompt: str = Form("ugly, bad quality, bad anatomy, deformed body, deformed hands, deformed feet, deformed face, deformed clothing, deformed skin, bad skin, leggings, tights, stockings"),
250
+ ip_scale: float = Form(0.8),
251
+ strength: float = Form(0.99),
252
+ guidance_scale: float = Form(7.5),
253
+ num_steps: int = Form(50)
254
+ ):
255
+ """
256
+ Virtual Try-On endpoint accepting base64 encoded images
257
+ (Alternative endpoint for easier React Native integration)
258
+ """
259
+ try:
260
+ if pipeline is None:
261
+ raise HTTPException(status_code=503, detail="Models not loaded yet")
262
+
263
+ start_time = time.time()
264
+
265
+ # Decode base64 images
266
+ print("πŸ“₯ Decoding base64 images...")
267
+ person_img = base64_to_image(person_image_base64).resize((512, 512))
268
+ clothing_img = base64_to_image(clothing_image_base64).resize((512, 512))
269
+
270
+ # Generate body segmentation mask
271
+ print("🎭 Generating segmentation mask...")
272
+ if segment_body is None:
273
+ mask_img = Image.new('L', (512, 512), 255)
274
+ else:
275
+ try:
276
+ # Try calling segment_body - it might expect a file path or PIL Image
277
+ try:
278
+ # First try with PIL Image directly
279
+ seg_image, mask_img = segment_body(person_img, face=False)
280
+ except (TypeError, AttributeError):
281
+ # If that fails, try with file path
282
+ with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file:
283
+ temp_path = tmp_file.name
284
+ person_img.save(temp_path)
285
+
286
+ seg_image, mask_img = segment_body(temp_path, face=False)
287
+ os.unlink(temp_path)
288
+
289
+ # Ensure mask is PIL Image and resize
290
+ if isinstance(mask_img, str):
291
+ mask_img = Image.open(mask_img).convert('L')
292
+ mask_img = mask_img.resize((512, 512))
293
+
294
+ except Exception as e:
295
+ print(f"⚠️ Segmentation failed: {e}, using full mask")
296
+ mask_img = Image.new('L', (512, 512), 255)
297
+
298
+ # Set IP-Adapter scale
299
+ pipeline.set_ip_adapter_scale(ip_scale)
300
+
301
+ # Generate virtual try-on
302
+ print("🎨 Generating virtual try-on...")
303
+ result = pipeline(
304
+ prompt=prompt,
305
+ negative_prompt=negative_prompt,
306
+ image=person_img,
307
+ mask_image=mask_img,
308
+ ip_adapter_image=clothing_img,
309
+ strength=strength,
310
+ guidance_scale=guidance_scale,
311
+ num_inference_steps=num_steps,
312
+ )
313
+
314
+ generated_image = result.images[0]
315
+ processing_time = time.time() - start_time
316
+ print(f"βœ… Generation completed in {processing_time:.2f}s")
317
+
318
+ # Return as base64
319
+ img_base64 = image_to_base64(generated_image)
320
+ return JSONResponse({
321
+ "success": True,
322
+ "image": img_base64,
323
+ "processing_time": processing_time,
324
+ "parameters": {
325
+ "prompt": prompt,
326
+ "ip_scale": ip_scale,
327
+ "strength": strength,
328
+ "guidance_scale": guidance_scale,
329
+ "num_steps": num_steps
330
+ }
331
+ })
332
+
333
+ except Exception as e:
334
+ print(f"❌ Error: {str(e)}")
335
+ raise HTTPException(status_code=500, detail=str(e))
336
+
337
+ if __name__ == "__main__":
338
+ import uvicorn
339
+ uvicorn.run(app, host="0.0.0.0", port=7860)