chips commited on
Commit
4003ec5
·
1 Parent(s): e610a17

Fresh clone

Browse files
Files changed (3) hide show
  1. app.py +278 -268
  2. functions.py +20 -4
  3. 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
- talent_trigger_word: str = Form(...),
40
- talent_lora_url: str = Form(...),
41
- num_images: int = 4,
42
- top_garment_image: UploadFile = File(...),
43
- bottom_garment_image: UploadFile = File( None),
44
- top_back_garment_image: UploadFile = File(None),
45
- bottom_back_garment_image: UploadFile = File(None)):
 
 
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
- top_garment_data = await safe_read_file(top_garment_image)
57
- bottom_garment_data = await safe_read_file(bottom_garment_image)
58
- top_back_garment_data = await safe_read_file(top_back_garment_image)
59
- bottom_back_garment_data = await safe_read_file(bottom_back_garment_image)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
 
61
  # Launch background task with the actual data
62
  background_tasks.add_task(
63
  run_virtual_tryon_pipeline,
64
  request_id,
65
- top_garment_data,
66
- bottom_garment_data,
67
- top_back_garment_data,
68
- bottom_back_garment_data,
69
- talent_trigger_word,
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
- top_garment_data: bytes,
108
- bottom_garment_data: bytes,
109
- top_back_garment_data: bytes,
110
- bottom_back_garment_data: bytes,
111
- talent_trigger_word,
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
- if top_back_garment_data and bottom_back_garment_data:
132
- back_type = "double"
133
- elif top_back_garment_data or bottom_back_garment_data:
134
- back_type = "single"
135
- else:
136
- back_type = "none"
137
-
138
- print(f"front_type: {front_type}")
139
- print(f"back_type: {back_type}")
140
- front_final_image = ""
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
- base_image = base_generator.create_image(talent_lora_url, talent_trigger_word, garment_description, 1, num_images)
161
- print(f"base_image: {base_image}")
 
 
 
162
  except Exception as e:
163
- r.set(request_id, "error generating base image")
164
  r.set(f"{request_id}_error", str(e))
 
165
  return
166
 
167
- r.set(request_id, "Combining garment images")
168
- sleep(5)
169
-
170
- # STEP 2: combine garments
171
- # ...
172
-
173
- r.set(request_id, "Styling talent")
174
- sleep(5)
175
- # STEP 3: style the talent
176
- # ...
 
 
 
 
 
177
 
178
- r.set(request_id, "Creating variations")
179
- sleep(5)
180
- # STEP 4: generate variations
181
- # ...
 
 
 
 
 
 
 
 
 
 
 
 
182
 
183
- r.set(request_id, "Upscaling images")
184
- sleep(5)
185
- # STEP 5: upscale
186
- # ...
187
 
188
- # STEP 6: Final result
189
- final_result = {"images": ["url1", "url2"], "notes": "done"}
190
- r.set(f"{request_id}_result", json.dumps(final_result))
 
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
- #figure out how to deal with front vs back images of garments
198
-
199
-
200
- def combine_garment_images(Upper_garment: UploadFile = File(...), Lower_garment: UploadFile = File(...)):
201
- print("combine garment images")
202
- result = base_generator.create_image()
203
- return(result)
204
-
205
-
206
- # Endpoints related to virtual outfit try on
207
- # HOW DO WE DICESERN FRONT VS BACK IMAGES OF GARMENTS?
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
- #Save the uploaded file tempoarily
261
- file_path = f"/tmp/{image.filename}"
262
- with open(file_path, "wb") as f:
263
- f.write(image_bytes)
264
 
265
- #Upload the file to FAL
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
- # Optional: prepare image payload for external API
296
- files = {"file": (image.filename, BytesIO(image_bytes), image.content_type)}
297
 
298
- #Save the uploaded file tempoarily
299
- file_path = f"/tmp/{image.filename}"
300
- with open(file_path, "wb") as f:
301
- f.write(image_bytes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
- #Upload the file to FAL
304
- url = fal_client.upload_file(file_path)
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
- #Auxiliary functions
336
 
337
- @app.post("/describeGarment", summary="Describe a garment or garments in the image",
338
- description="Passes the garment image to openai to describe it to improve generation of base images")
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. be detailed but online describe the garments, not the rest of the images. make sure to only return the description, not the complete sentence."},
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
- @app.post("/upload")
367
- async def upload_files(
368
- name: str = Form(...),
369
- image1: UploadFile = File(...),
370
- image2: UploadFile = File(None) # Optional!
371
- ):
372
- result = {
373
- "name": name,
374
- "image1_filename": image1.filename,
375
- "image2_filename": image2.filename if image2 else "Not provided"
376
- }
377
- return result
 
 
 
 
 
 
 
 
 
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.resize((1280, 1280))
29
- img2 = img2.resize((1280, 1280))
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
+ ]