Spaces:
Sleeping
Sleeping
chips
commited on
Commit
·
4003ec5
1
Parent(s):
e610a17
Fresh clone
Browse files- app.py +278 -268
- functions.py +20 -4
- poses.py +5 -0
app.py
CHANGED
@@ -12,10 +12,14 @@ import uuid
|
|
12 |
from time import sleep
|
13 |
import json
|
14 |
from functions import combine_images_side_by_side
|
|
|
15 |
from typing import Optional
|
16 |
-
|
17 |
from dotenv import load_dotenv
|
|
|
|
|
18 |
load_dotenv()
|
|
|
19 |
|
20 |
|
21 |
app = FastAPI()
|
@@ -26,319 +30,316 @@ r = redis.Redis(
|
|
26 |
host='cute-wildcat-41430.upstash.io',
|
27 |
port=6379,
|
28 |
password='AaHWAAIjcDFhZDVlOGUyMDQ0ZGQ0MTZmODA4ZjdkNzc4ZDVhZjUzZHAxMA',
|
29 |
-
ssl=True
|
|
|
30 |
)
|
31 |
-
@app.get("/")
|
32 |
-
def greet_json():
|
33 |
-
return {"Hello": "World!"}
|
34 |
|
35 |
@app.post("/virtualTryOn", summary="Virtual try on single call",
|
36 |
description="Virtual try on single call for complete outfit try on")
|
37 |
async def virtual_try_on(
|
38 |
background_tasks: BackgroundTasks,
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
|
|
46 |
"""
|
47 |
Pass in the top garment image, bottom garment image, top back garment image, bottom back garment image, talent trigger word, talent lora url and number of images to generate.
|
48 |
Only one front garment image is required, the rest are optional.
|
49 |
Default number of images is 4.
|
50 |
"""
|
51 |
-
print(top_garment_image)
|
52 |
request_id = str(uuid.uuid4())
|
53 |
r.set(request_id, "pending")
|
54 |
-
|
|
|
|
|
55 |
# Read all files first
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
|
61 |
# Launch background task with the actual data
|
62 |
background_tasks.add_task(
|
63 |
run_virtual_tryon_pipeline,
|
64 |
request_id,
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
talent_lora_url,
|
71 |
-
num_images
|
72 |
)
|
73 |
return {"request_id": request_id}
|
74 |
|
75 |
-
@app.get("/status/{request_id}")
|
76 |
-
async def check_status(request_id: str):
|
77 |
-
status = r.get(request_id)
|
78 |
-
if not status:
|
79 |
-
return {"error": "Invalid request_id"}
|
80 |
-
return {"request_id": request_id, "status": status}
|
81 |
-
|
82 |
-
#endpoints related to base image generation
|
83 |
-
|
84 |
-
@app.get("/makeBaseImage", summary="Make a base image of the character,before adding the outfit",
|
85 |
-
description="Make one or more base image(s) of the character,before adding the outfit. supports multiple poses by passing in a list of pose ids")
|
86 |
-
async def make_base_image(character_lora: str, character_keyword: str, outfit_desc: str, pose_id: int , num_outputs: int = 1):
|
87 |
-
"""
|
88 |
-
Pass in the character lora, character keyword, outfit description and pose id to make a base image of the character,before adding the outfit.
|
89 |
-
supports multiple poses by passing a number of outputs.
|
90 |
-
"""
|
91 |
-
print("make base image")
|
92 |
-
result = base_generator.create_image(character_lora, character_keyword, outfit_desc, pose_id, num_outputs )
|
93 |
-
return(result)
|
94 |
-
|
95 |
-
# Function related to virtual outfit try on
|
96 |
-
async def safe_read_file(file: UploadFile):
|
97 |
-
print("safe read file")
|
98 |
-
print(file)
|
99 |
-
if file is None:
|
100 |
-
return None
|
101 |
-
content = await file.read()
|
102 |
-
return content if content else None
|
103 |
-
|
104 |
|
105 |
async def run_virtual_tryon_pipeline(
|
106 |
request_id,
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
talent_lora_url,
|
113 |
-
num_images
|
114 |
):
|
|
|
115 |
try:
|
|
|
116 |
r.set(request_id, "checking incoming data...")
|
117 |
-
front_type = ""
|
118 |
-
back_type = ""
|
119 |
-
|
120 |
-
# Now we can use the data directly
|
121 |
-
if top_garment_data is None and bottom_garment_data is None:
|
122 |
-
return {"error": "Missing garment images"}
|
123 |
-
|
124 |
-
if top_garment_data and bottom_garment_data:
|
125 |
-
front_type = "double"
|
126 |
-
elif top_garment_data or bottom_garment_data:
|
127 |
-
front_type = "single"
|
128 |
-
else:
|
129 |
-
front_type = "none"
|
130 |
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
back_final_image = ""
|
142 |
-
|
143 |
-
if front_type == "double":
|
144 |
-
front_final_image = await combine_images_side_by_side(top_garment_data, bottom_garment_data)
|
145 |
-
if back_type == "double":
|
146 |
-
back_final_image = await combine_images_side_by_side(top_back_garment_data, bottom_back_garment_data)
|
147 |
-
if front_type == "single":
|
148 |
-
front_final_image = top_garment_data or bottom_garment_data
|
149 |
-
if back_type == "single":
|
150 |
-
back_final_image = top_back_garment_data or bottom_back_garment_data
|
151 |
-
r.set(request_id, "Garment images combined")
|
152 |
-
#Check incoming data. do we have the required data. Are the images what we expect? (ie flatlay image file etc.)
|
153 |
-
#lets use openai for this. Also, return errors where needed
|
154 |
-
r.set(request_id, "Creating base image")
|
155 |
-
# STEP 1: make base image
|
156 |
-
|
157 |
-
## analyse the garment images
|
158 |
-
garment_description = describe_garment(front_final_image)
|
159 |
try:
|
160 |
-
|
161 |
-
|
|
|
|
|
|
|
162 |
except Exception as e:
|
163 |
-
r.set(request_id, "error
|
164 |
r.set(f"{request_id}_error", str(e))
|
|
|
165 |
return
|
166 |
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
|
|
|
|
|
|
|
|
|
|
177 |
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
182 |
|
183 |
-
r.set(request_id, "Upscaling images")
|
184 |
-
sleep(5)
|
185 |
-
# STEP 5: upscale
|
186 |
-
# ...
|
187 |
|
188 |
-
#
|
189 |
-
|
190 |
-
|
|
|
191 |
r.set(request_id, "completed")
|
|
|
192 |
except Exception as e:
|
193 |
-
r.set(request_id, "error")
|
194 |
r.set(f"{request_id}_error", str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
195 |
|
196 |
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
@app.post("/styleTalent", summary="Style talent", description="Style talent")
|
210 |
-
async def style_talent( talent_image: UploadFile = File(...), garment_image: UploadFile = File(...)):
|
211 |
-
#Save the uploaded talent image tempoarily
|
212 |
-
talent_image_bytes = await talent_image.read()
|
213 |
-
file_path = f"/tmp/{talent_image.filename}"
|
214 |
-
with open(file_path, "wb") as f:
|
215 |
-
f.write(talent_image_bytes)
|
216 |
-
|
217 |
-
#Upload the file to FAL
|
218 |
-
talent_image_url = fal_client.upload_file(file_path)
|
219 |
-
|
220 |
-
#Save the uploaded garment image tempoarily
|
221 |
-
garment_image_bytes = await garment_image.read()
|
222 |
-
file_path = f"/tmp/{garment_image.filename}"
|
223 |
-
with open(file_path, "wb") as f:
|
224 |
-
f.write(garment_image_bytes)
|
225 |
-
|
226 |
-
#Upload the file to FAL
|
227 |
-
garment_image_url = fal_client.upload_file(file_path)
|
228 |
-
|
229 |
-
handler = fal_client.submit(
|
230 |
-
"fal-ai/kling/v1-5/kolors-virtual-try-on",
|
231 |
-
arguments={
|
232 |
-
"human_image_url": talent_image_url,
|
233 |
-
"garment_image_url": garment_image_url
|
234 |
-
},
|
235 |
-
)
|
236 |
-
request_id = handler.request_id
|
237 |
-
return(request_id)
|
238 |
-
|
239 |
-
# Endpoints related to making more stills versions from image
|
240 |
-
|
241 |
-
@app.get("/versionStatus")
|
242 |
-
def versionStatus(request_id: str):
|
243 |
-
status = fal_client.status("fal-ai/instant-character", request_id, with_logs=True)
|
244 |
-
return(status)
|
245 |
-
|
246 |
-
@app.get("/versionResult")
|
247 |
-
async def versionResult(request_id: str):
|
248 |
-
result = await fal_client.result_async("fal-ai/instant-character", request_id)
|
249 |
-
return(result)
|
250 |
-
|
251 |
-
|
252 |
-
@app.post("/makeVersions")
|
253 |
-
async def make_versions(image: UploadFile = File(...)):
|
254 |
-
# Read image data
|
255 |
-
image_bytes = await image.read()
|
256 |
-
|
257 |
-
# Optional: prepare image payload for external API
|
258 |
-
files = {"file": (image.filename, BytesIO(image_bytes), image.content_type)}
|
259 |
|
260 |
-
|
261 |
-
file_path = f"/tmp/{image.filename}"
|
262 |
-
with open(file_path, "wb") as f:
|
263 |
-
f.write(image_bytes)
|
264 |
|
265 |
-
|
266 |
-
url = fal_client.upload_file(file_path)
|
267 |
-
|
268 |
-
# Call external API
|
269 |
-
handler = fal_client.submit(
|
270 |
-
"fal-ai/instant-character",
|
271 |
-
arguments={
|
272 |
-
"prompt": "Model posing",
|
273 |
-
"image_url": url,
|
274 |
-
"num_inference_steps": 35,
|
275 |
-
"num_images": 2,
|
276 |
-
},
|
277 |
-
)
|
278 |
-
# Clean up file
|
279 |
-
os.remove(file_path)
|
280 |
-
request_id = handler.request_id
|
281 |
-
return(f"check request id: {request_id}")
|
282 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
283 |
|
284 |
|
285 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
286 |
|
287 |
|
288 |
-
# Endpoints related to image to video generation
|
289 |
|
290 |
-
@app.post("/makeVideo")
|
291 |
-
async def make_video(image: UploadFile = File(...)):
|
292 |
-
# Read image data
|
293 |
-
image_bytes = await image.read()
|
294 |
|
295 |
-
|
296 |
-
files = {"file": (image.filename, BytesIO(image_bytes), image.content_type)}
|
297 |
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
302 |
|
303 |
-
#
|
304 |
-
|
305 |
-
|
306 |
-
# Call external API
|
307 |
-
handler = fal_client.submit(
|
308 |
-
"fal-ai/kling-video/v1.6/pro/image-to-video",
|
309 |
-
arguments={
|
310 |
-
"prompt": "Model moving between front facing fashion poses.",
|
311 |
-
"image_url": url,
|
312 |
-
"duration": "10",
|
313 |
-
"aspect_ratio": "9:16",
|
314 |
-
"negative_prompt": "blur, distort, and low quality, moire, warping, unrealistic, uncanny valley",
|
315 |
-
"cfg_scale": 0.5
|
316 |
-
},
|
317 |
-
)
|
318 |
-
# Clean up file
|
319 |
-
os.remove(file_path)
|
320 |
-
request_id = handler.request_id
|
321 |
-
return(f"check request id: {request_id}")
|
322 |
-
|
323 |
-
@app.get("/videoStatus")
|
324 |
-
def videoStatus(request_id: str):
|
325 |
-
status = fal_client.status("fal-ai/kling-video/v1.6/pro/image-to-video", request_id, with_logs=True)
|
326 |
-
return(status)
|
327 |
-
|
328 |
-
@app.get("/videoResult")
|
329 |
-
async def videoResult(request_id: str):
|
330 |
-
result = await fal_client.result_async("fal-ai/kling-video/v1.6/pro/image-to-video", request_id)
|
331 |
return(result)
|
332 |
|
333 |
|
334 |
|
335 |
-
|
336 |
|
337 |
-
|
338 |
-
|
339 |
-
async def describe_garment(image: UploadFile = File(...)):
|
340 |
-
image_bytes = await image.read()
|
341 |
base64_image = base64.b64encode(image_bytes).decode("utf-8")
|
|
|
342 |
|
343 |
response = openai_client.chat.completions.create(
|
344 |
model="gpt-4o",
|
@@ -346,7 +347,7 @@ async def describe_garment(image: UploadFile = File(...)):
|
|
346 |
{
|
347 |
"role": "user",
|
348 |
"content": [
|
349 |
-
{"type": "text", "text": "Describe this garment or garments in the image. the format should be ready to be inserted into the sentence: a man wearing ..... in front of a white background.
|
350 |
{
|
351 |
"type": "image_url",
|
352 |
"image_url": {
|
@@ -358,20 +359,29 @@ async def describe_garment(image: UploadFile = File(...)):
|
|
358 |
],
|
359 |
max_tokens=300
|
360 |
)
|
361 |
-
|
362 |
return {"description": response.choices[0].message.content}
|
363 |
|
364 |
|
|
|
365 |
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
"
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
from time import sleep
|
13 |
import json
|
14 |
from functions import combine_images_side_by_side
|
15 |
+
from poses import poses
|
16 |
from typing import Optional
|
17 |
+
from base64 import decodebytes
|
18 |
from dotenv import load_dotenv
|
19 |
+
import random
|
20 |
+
import asyncio
|
21 |
load_dotenv()
|
22 |
+
import ast
|
23 |
|
24 |
|
25 |
app = FastAPI()
|
|
|
30 |
host='cute-wildcat-41430.upstash.io',
|
31 |
port=6379,
|
32 |
password='AaHWAAIjcDFhZDVlOGUyMDQ0ZGQ0MTZmODA4ZjdkNzc4ZDVhZjUzZHAxMA',
|
33 |
+
ssl=True,
|
34 |
+
decode_responses=True
|
35 |
)
|
|
|
|
|
|
|
36 |
|
37 |
@app.post("/virtualTryOn", summary="Virtual try on single call",
|
38 |
description="Virtual try on single call for complete outfit try on")
|
39 |
async def virtual_try_on(
|
40 |
background_tasks: BackgroundTasks,
|
41 |
+
num_images: int = 2,
|
42 |
+
num_variations: int = 2,
|
43 |
+
person_reference: str = "",
|
44 |
+
person_image: UploadFile = File(None),
|
45 |
+
person_face_image: UploadFile = File(None),
|
46 |
+
garment_image_1: UploadFile = File(None),
|
47 |
+
garment_image_2: UploadFile = File(None),
|
48 |
+
garment_image_3: UploadFile = File(None),
|
49 |
+
garment_image_4: UploadFile = File(None),):
|
50 |
"""
|
51 |
Pass in the top garment image, bottom garment image, top back garment image, bottom back garment image, talent trigger word, talent lora url and number of images to generate.
|
52 |
Only one front garment image is required, the rest are optional.
|
53 |
Default number of images is 4.
|
54 |
"""
|
|
|
55 |
request_id = str(uuid.uuid4())
|
56 |
r.set(request_id, "pending")
|
57 |
+
print("request_id: ", request_id)
|
58 |
+
model_images = []
|
59 |
+
garment_images = []
|
60 |
# Read all files first
|
61 |
+
person_image_data = await safe_read_file(person_image)
|
62 |
+
person_face_image_data = await safe_read_file(person_face_image)
|
63 |
+
if person_image is not None:
|
64 |
+
model_images.append(person_image_data)
|
65 |
+
if person_face_image is not None:
|
66 |
+
model_images.append(person_face_image_data)
|
67 |
+
if garment_image_1 is not None:
|
68 |
+
garment_image_1_data = await safe_read_file(garment_image_1)
|
69 |
+
garment_images.append(garment_image_1_data)
|
70 |
+
if garment_image_2 is not None:
|
71 |
+
garment_image_2_data = await safe_read_file(garment_image_2)
|
72 |
+
garment_images.append(garment_image_2_data)
|
73 |
+
if garment_image_3 is not None:
|
74 |
+
garment_image_3_data = await safe_read_file(garment_image_3)
|
75 |
+
garment_images.append(garment_image_3_data)
|
76 |
+
if garment_image_4 is not None:
|
77 |
+
garment_image_4_data = await safe_read_file(garment_image_4)
|
78 |
+
garment_images.append(garment_image_4_data)
|
79 |
|
80 |
# Launch background task with the actual data
|
81 |
background_tasks.add_task(
|
82 |
run_virtual_tryon_pipeline,
|
83 |
request_id,
|
84 |
+
person_reference,
|
85 |
+
model_images,
|
86 |
+
garment_images,
|
87 |
+
num_images,
|
88 |
+
num_variations
|
|
|
|
|
89 |
)
|
90 |
return {"request_id": request_id}
|
91 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
|
93 |
async def run_virtual_tryon_pipeline(
|
94 |
request_id,
|
95 |
+
person_reference,
|
96 |
+
model_images: list,
|
97 |
+
garment_images: list,
|
98 |
+
num_images: int,
|
99 |
+
num_variations: int,
|
|
|
|
|
100 |
):
|
101 |
+
output_images = []
|
102 |
try:
|
103 |
+
#Step 1: Check incoming data
|
104 |
r.set(request_id, "checking incoming data...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
|
106 |
+
#Step 2: Describe garment
|
107 |
+
r.set(request_id, "Describing garment")
|
108 |
+
garment_descriptions = []
|
109 |
+
for image in garment_images:
|
110 |
+
garment_description = await describe_garment(image)
|
111 |
+
garment_descriptions.append(garment_description)
|
112 |
+
|
113 |
+
|
114 |
+
#Step 4: Create prompt
|
115 |
+
r.set(request_id, "Creating prompt")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
116 |
try:
|
117 |
+
completed_outfit = await complete_outfit(garment_descriptions)
|
118 |
+
prompt = f"{person_reference} wearing {completed_outfit['description']} in front of a white background"
|
119 |
+
pose = random.choice(poses)
|
120 |
+
prompt += f" {pose}"
|
121 |
+
r.set(request_id + "_prompt", prompt)
|
122 |
except Exception as e:
|
123 |
+
r.set(request_id, "error creating prompt")
|
124 |
r.set(f"{request_id}_error", str(e))
|
125 |
+
print(f"error creating prompt: {e}")
|
126 |
return
|
127 |
|
128 |
+
#Step 5: Create base images
|
129 |
+
r.set(request_id, "Creating base images")
|
130 |
+
r.set(request_id + "_content", "")
|
131 |
+
try:
|
132 |
+
base_images = await create_image(garment_images, model_images, prompt, num_images, request_id)
|
133 |
+
output_images.append(base_images)
|
134 |
+
r.set(request_id, "base images created")
|
135 |
+
print(str(base_images))
|
136 |
+
r.set(request_id + "_content", str(base_images))
|
137 |
+
except Exception as e:
|
138 |
+
r.set(request_id, "error creating base images")
|
139 |
+
r.set(f"{request_id}_error", str(e))
|
140 |
+
if isinstance(base_images, asyncio.Future):
|
141 |
+
base_images.cancel()
|
142 |
+
return
|
143 |
|
144 |
+
#Step 6: Create variations
|
145 |
+
if num_variations > 0:
|
146 |
+
r.set(request_id, "Creating variations")
|
147 |
+
r.set(request_id + "_variations", "")
|
148 |
+
try:
|
149 |
+
for image in base_images['images' ]:
|
150 |
+
variations = await make_versions(image['url'], num_variations)
|
151 |
+
output_images.append(variations)
|
152 |
+
r.set(request_id + "_variations", str(variations))
|
153 |
+
r.set(request_id, "variations created")
|
154 |
+
except Exception as e:
|
155 |
+
r.set(request_id, "error creating variations")
|
156 |
+
r.set(f"{request_id}_error", str(e))
|
157 |
+
return
|
158 |
+
else:
|
159 |
+
r.set(request_id, "no variations created")
|
160 |
|
|
|
|
|
|
|
|
|
161 |
|
162 |
+
#Step 7: Final result
|
163 |
+
r.set(request_id, "Final result")
|
164 |
+
result_images = get_result_images(request_id)
|
165 |
+
r.set(f"{request_id}_result", result_images)
|
166 |
r.set(request_id, "completed")
|
167 |
+
return {"request_id": request_id, "status": "completed", "result": result_images}
|
168 |
except Exception as e:
|
169 |
+
#r.set(request_id, "error")
|
170 |
r.set(f"{request_id}_error", str(e))
|
171 |
+
return {"request_id": request_id, "status": "error", "error": str(e)}
|
172 |
+
|
173 |
+
def get_result_images(request_id):
|
174 |
+
result_images = []
|
175 |
+
images = ast.literal_eval( r.get(request_id + "_content"))
|
176 |
+
print(type(images))
|
177 |
+
print("images coming here: ")
|
178 |
+
print(images)
|
179 |
+
variations = ast.literal_eval(r.get(request_id + "_variations")["images"])
|
180 |
+
print("variations coming here: ")
|
181 |
+
print(variations)
|
182 |
+
print("all images coming here: ")
|
183 |
+
all_images = images + variations
|
184 |
+
print(all_images)
|
185 |
+
for image in all_images:
|
186 |
+
result_images.append(image['url'])
|
187 |
+
return result_images
|
188 |
+
|
189 |
+
@app.get("/result/{request_id}")
|
190 |
+
async def check_status(request_id: str):
|
191 |
+
output_images = []
|
192 |
+
status = r.get(request_id)
|
193 |
+
images = ast.literal_eval( r.get(request_id + "_content"))
|
194 |
+
for image in images['images']:
|
195 |
+
output_images.append(image['url'])
|
196 |
+
variations = ast.literal_eval(r.get(request_id + "_variations"))
|
197 |
+
for image in variations['images']:
|
198 |
+
output_images.append(image['url'])
|
199 |
+
prompt = r.get(request_id + "_prompt")
|
200 |
+
result = r.get(request_id + "_result")
|
201 |
+
error = r.get(request_id + "_error")
|
202 |
+
if not status:
|
203 |
+
return {"error": "Invalid request_id"}
|
204 |
+
#return {"request_id": request_id, "status": status, "\n images": images, "\n variations": variations, "\n prompt": prompt, "\n result": result, "\n error": error}
|
205 |
+
return {"images": output_images }
|
206 |
|
207 |
|
208 |
+
@app.get("/status/{request_id}")
|
209 |
+
async def check_status(request_id: str):
|
210 |
+
status = r.get(request_id)
|
211 |
+
images = r.get(request_id + "_content")
|
212 |
+
variations = r.get(request_id + "_variations")
|
213 |
+
prompt = r.get(request_id + "_prompt")
|
214 |
+
result = r.get(request_id + "_result")
|
215 |
+
error = r.get(request_id + "_error")
|
216 |
+
if not status:
|
217 |
+
return {"error": "Invalid request_id"}
|
218 |
+
return {"request_id": request_id, "status": status, "\n images": images, "\n variations": variations, "\n prompt": prompt, "\n result": result, "\n error": error}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
219 |
|
220 |
+
#endpoints related to base image generation
|
|
|
|
|
|
|
221 |
|
222 |
+
# Function related to virtual outfit try on
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
223 |
|
224 |
+
async def safe_read_file(file: UploadFile):
|
225 |
+
print("safe read file")
|
226 |
+
#print(file)
|
227 |
+
if file is None:
|
228 |
+
#print("file is none")
|
229 |
+
return None
|
230 |
+
#print("file is not none")
|
231 |
+
content = await file.read()
|
232 |
+
#print("content read")
|
233 |
+
return content if content else None
|
234 |
|
235 |
|
236 |
|
237 |
+
async def make_versions(input_image_url: str, num_images: int):
|
238 |
+
try:
|
239 |
+
# Call external API
|
240 |
+
handler = await fal_client.submit_async(
|
241 |
+
"fal-ai/instant-character",
|
242 |
+
arguments={
|
243 |
+
"prompt": "Model posing in front of white background",
|
244 |
+
"image_url": input_image_url,
|
245 |
+
"num_inference_steps": 50,
|
246 |
+
"num_images": num_images,
|
247 |
+
"safety_tolerance": "5",
|
248 |
+
"guidance_scale": 20,
|
249 |
+
"image_size": "portrait_16_9"
|
250 |
+
}
|
251 |
+
)
|
252 |
+
|
253 |
+
# Get the result first
|
254 |
+
result = await handler.get()
|
255 |
+
|
256 |
+
# Now we can safely iterate events
|
257 |
+
async for event in handler.iter_events(with_logs=False):
|
258 |
+
print(event)
|
259 |
+
|
260 |
+
return result
|
261 |
+
except Exception as e:
|
262 |
+
print(f"Error in make_versions: {e}")
|
263 |
+
raise e
|
264 |
|
265 |
|
|
|
266 |
|
|
|
|
|
|
|
|
|
267 |
|
268 |
+
#Auxiliary functions
|
|
|
269 |
|
270 |
+
#@app.post("/describeGarment", summary="Describe a garment or garments in the image",
|
271 |
+
# description="Passes the garment image to openai to describe it to improve generation of base images")
|
272 |
+
#async def describe_garment(image: UploadFile = File(...)):
|
273 |
+
|
274 |
+
async def create_image(garment_images: list,
|
275 |
+
model_images: list,
|
276 |
+
prompt: str,
|
277 |
+
num_images: int,
|
278 |
+
request_id: str,
|
279 |
+
):
|
280 |
+
print("Lets create the images!")
|
281 |
+
#create urls for image
|
282 |
+
image_urls = []
|
283 |
+
img_no = 0
|
284 |
+
for image in garment_images:
|
285 |
+
#Save the uploaded file tempoarily
|
286 |
+
file_path = f"/tmp/img_{request_id}_{img_no}.jpg"
|
287 |
+
with open(file_path, "wb") as f:
|
288 |
+
f.write(image)
|
289 |
+
image_url = fal_client.upload_file(file_path)
|
290 |
+
image_urls.append(image_url)
|
291 |
+
os.remove(file_path)
|
292 |
+
#print("Garment image uploaded")
|
293 |
+
img_no += 1
|
294 |
+
for image in model_images:
|
295 |
+
file_path = f"/tmp/img_{request_id}_{img_no}.jpg"
|
296 |
+
with open(file_path, "wb") as f:
|
297 |
+
f.write(image)
|
298 |
+
image_url = fal_client.upload_file(file_path)
|
299 |
+
image_urls.append(image_url)
|
300 |
+
os.remove(file_path)
|
301 |
+
#print("Model image uploaded")
|
302 |
+
|
303 |
+
|
304 |
+
#Generate images using flux kontext
|
305 |
+
try:
|
306 |
+
handler = await fal_client.submit_async(
|
307 |
+
"fal-ai/flux-pro/kontext/multi",
|
308 |
+
arguments={
|
309 |
+
"prompt": prompt,
|
310 |
+
"guidance_scale": 18,
|
311 |
+
"num_images": num_images,
|
312 |
+
"safety_tolerance": "5",
|
313 |
+
"output_format": "jpeg",
|
314 |
+
"image_urls": image_urls,
|
315 |
+
"aspect_ratio": "9:16"
|
316 |
+
}
|
317 |
+
)
|
318 |
+
|
319 |
+
# Get the result first before iterating events
|
320 |
+
result = await handler.get()
|
321 |
+
|
322 |
+
# Now we can safely iterate events
|
323 |
+
async for event in handler.iter_events(with_logs=True):
|
324 |
+
print(event)
|
325 |
+
|
326 |
+
return result
|
327 |
+
except Exception as e:
|
328 |
+
print(f"Error in create_image: {e}")
|
329 |
+
raise e
|
330 |
|
331 |
+
#print(result)
|
332 |
+
#print("Images generated")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
333 |
return(result)
|
334 |
|
335 |
|
336 |
|
337 |
+
async def describe_garment(image):
|
338 |
|
339 |
+
print("describe garment process running")
|
340 |
+
image_bytes = image
|
|
|
|
|
341 |
base64_image = base64.b64encode(image_bytes).decode("utf-8")
|
342 |
+
#print(base64_image)
|
343 |
|
344 |
response = openai_client.chat.completions.create(
|
345 |
model="gpt-4o",
|
|
|
347 |
{
|
348 |
"role": "user",
|
349 |
"content": [
|
350 |
+
{"type": "text", "text": "Describe this garment or garments in the image. the format should be ready to be inserted into the sentence: a man wearing ..... in front of a white background. describe the garments as type and fit, but not colors and design, not the rest of the images. make sure to only return the description, not the complete sentence."},
|
351 |
{
|
352 |
"type": "image_url",
|
353 |
"image_url": {
|
|
|
359 |
],
|
360 |
max_tokens=300
|
361 |
)
|
362 |
+
#print(response)
|
363 |
return {"description": response.choices[0].message.content}
|
364 |
|
365 |
|
366 |
+
async def complete_outfit(outfit_desc):
|
367 |
|
368 |
+
print("complete outfit process running")
|
369 |
+
current_outfit = ""
|
370 |
+
for description in outfit_desc:
|
371 |
+
print(f"description: {description['description']}")
|
372 |
+
current_outfit += description['description']
|
373 |
+
|
374 |
+
response = openai_client.chat.completions.create(
|
375 |
+
model="gpt-4o",
|
376 |
+
messages=[
|
377 |
+
{
|
378 |
+
"role": "user",
|
379 |
+
"content": [
|
380 |
+
{"type": "text", "text": f"If thie outfit is not complete (ie, it is missing a top or bottom, or a pair of pants or a pair of shoes), complete it, making sure the model is wearing a complete outfit. for the garments not already mentioned, keep it simple to not draw too much attention to those new garments. Complete this outfit description: {current_outfit}. Return only the description of the complete outfit including the current outfit, not the complete sentence in the format: blue strap top and pink skirt and white sneakers"},
|
381 |
+
]
|
382 |
+
}
|
383 |
+
],
|
384 |
+
max_tokens=300
|
385 |
+
)
|
386 |
+
#print(response)
|
387 |
+
return {"description": response.choices[0].message.content}
|
functions.py
CHANGED
@@ -4,6 +4,22 @@ from io import BytesIO
|
|
4 |
import base64
|
5 |
import uuid
|
6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
async def combine_images_side_by_side(image1: bytes, image2: bytes) -> str:
|
8 |
"""
|
9 |
Takes two images, scales them to 1280x1280, combines them side by side,
|
@@ -25,8 +41,8 @@ async def combine_images_side_by_side(image1: bytes, image2: bytes) -> str:
|
|
25 |
img2 = Image.open(BytesIO(image2))
|
26 |
|
27 |
# Scale both images to 1280x1280
|
28 |
-
img1 = img1
|
29 |
-
img2 = img2
|
30 |
|
31 |
# Create new image with combined width (2560) and height (1280)
|
32 |
combined = Image.new('RGB', (2560, 1280))
|
@@ -37,7 +53,7 @@ async def combine_images_side_by_side(image1: bytes, image2: bytes) -> str:
|
|
37 |
|
38 |
# Generate unique filename
|
39 |
filename = f"tmp/combined_{uuid.uuid4()}.png"
|
40 |
-
|
41 |
# Save image
|
42 |
combined.save(filename)
|
43 |
|
@@ -46,7 +62,7 @@ async def combine_images_side_by_side(image1: bytes, image2: bytes) -> str:
|
|
46 |
encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
|
47 |
|
48 |
# Clean up
|
49 |
-
os.remove(filename)
|
50 |
print("combined images side by side")
|
51 |
return encoded_string
|
52 |
|
|
|
4 |
import base64
|
5 |
import uuid
|
6 |
|
7 |
+
|
8 |
+
def resize_to_fit(image, max_size):
|
9 |
+
"""
|
10 |
+
Resize an image to fit within max_size (width, height) while preserving aspect ratio.
|
11 |
+
Returns a new image with transparent background and the resized image centered.
|
12 |
+
"""
|
13 |
+
image.thumbnail(max_size, Image.LANCZOS) # Resize while keeping aspect ratio
|
14 |
+
new_image = Image.new("RGB", max_size, (255, 255, 255)) # white background
|
15 |
+
paste_position = (
|
16 |
+
(max_size[0] - image.width) // 2,
|
17 |
+
(max_size[1] - image.height) // 2,
|
18 |
+
)
|
19 |
+
new_image.paste(image, paste_position)
|
20 |
+
return new_image
|
21 |
+
|
22 |
+
|
23 |
async def combine_images_side_by_side(image1: bytes, image2: bytes) -> str:
|
24 |
"""
|
25 |
Takes two images, scales them to 1280x1280, combines them side by side,
|
|
|
41 |
img2 = Image.open(BytesIO(image2))
|
42 |
|
43 |
# Scale both images to 1280x1280
|
44 |
+
img1 = resize_to_fit(img1, (1280, 1280))
|
45 |
+
img2 = resize_to_fit(img2, (1280, 1280))
|
46 |
|
47 |
# Create new image with combined width (2560) and height (1280)
|
48 |
combined = Image.new('RGB', (2560, 1280))
|
|
|
53 |
|
54 |
# Generate unique filename
|
55 |
filename = f"tmp/combined_{uuid.uuid4()}.png"
|
56 |
+
print("filename: " + filename)
|
57 |
# Save image
|
58 |
combined.save(filename)
|
59 |
|
|
|
62 |
encoded_string = base64.b64encode(image_file.read()).decode('utf-8')
|
63 |
|
64 |
# Clean up
|
65 |
+
#os.remove(filename)
|
66 |
print("combined images side by side")
|
67 |
return encoded_string
|
68 |
|
poses.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
poses = [
|
2 |
+
"weight shifted to one leg (her right leg), causing a slight bend in the opposite hip. Her right hand is placed casually in her pocket, while her left arm hangs loosely by her side. Her torso is upright but slightly tilted with a subtle S-curve to the posture, and head is turned slightly toward the camera with a neutral, self-assured expression. One leg is straight while the other is relaxed, with both feet flat on the ground and spaced slightly apart. The overall stance is athletic, laid-back, and stylish.",
|
3 |
+
"stands upright in a neutral, relaxed position facing forward. Both arms hang naturally at her sides with a slight bend in the elbows. Her weight appears evenly distributed across both feet, which are flat on the ground and positioned hip-width apart. Expression is calm and direct, with shoulders relaxed and back straight. The overall stance is symmetrical, grounded, and natural.",
|
4 |
+
"stands upright with arms crossed over chest, projecting a confident and casual demeanor. weight is shifted slightly to one leg ( right), while the other leg is relaxed with the foot lightly angled outward. head is turned slightly to left, looking off-camera with a neutral to thoughtful expression. The overall pose is relaxed yet assertive, often used in athletic or lifestyle apparel shoots to convey strength, ease, and focus.",
|
5 |
+
]
|