Spaces:
Sleeping
Sleeping
Commit
·
db9ae22
1
Parent(s):
69c4c90
add: async image upload, nto optimize
Browse files- app.py +6 -1
- src/api/nto_api.py +86 -54
app.py
CHANGED
@@ -22,4 +22,9 @@ app.add_middleware(
|
|
22 |
allow_credentials=True,
|
23 |
allow_methods=["*"],
|
24 |
allow_headers=["*"],
|
25 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
22 |
allow_credentials=True,
|
23 |
allow_methods=["*"],
|
24 |
allow_headers=["*"],
|
25 |
+
)
|
26 |
+
|
27 |
+
if __name__ == '__main__':
|
28 |
+
import uvicorn
|
29 |
+
|
30 |
+
uvicorn.run(app)
|
src/api/nto_api.py
CHANGED
@@ -26,6 +26,9 @@ import replicate
|
|
26 |
import requests
|
27 |
from src.utils.logger import logger
|
28 |
import secrets
|
|
|
|
|
|
|
29 |
|
30 |
pipeline = Pipeline()
|
31 |
|
@@ -360,31 +363,64 @@ async def parse_necklace_try_on_id(necklaceImageId: str = Form(...),
|
|
360 |
)
|
361 |
|
362 |
|
363 |
-
def supabase_upload_and_return_url(prefix: str, image: Image.Image,quality: int = 85)
|
364 |
-
|
365 |
try:
|
366 |
-
filename = f"{prefix}_{secrets.token_hex(
|
367 |
-
|
368 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
369 |
|
|
|
|
|
|
|
|
|
|
|
370 |
|
371 |
-
|
372 |
-
image.save(buffer, format='WEBP', quality=quality, optimize=True)
|
373 |
-
image_bytes = buffer.getvalue()
|
374 |
|
375 |
-
|
376 |
-
|
377 |
-
|
378 |
-
|
|
|
|
|
|
|
379 |
|
380 |
return bucket.get_public_url(filename)
|
381 |
|
382 |
except Exception as e:
|
383 |
-
|
384 |
return None
|
385 |
-
|
386 |
-
|
387 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
388 |
|
389 |
|
390 |
@nto_cto_router.post("/necklaceTryOnID")
|
@@ -394,18 +430,6 @@ async def necklace_try_on_id(necklace_try_on_id: NecklaceTryOnIDEntity = Depends
|
|
394 |
logger.info(f">>> NECKLACE TRY ON ID STARTED :: {necklace_try_on_id.storename} <<<")
|
395 |
start_time = time.time()
|
396 |
|
397 |
-
try:
|
398 |
-
data, _ = supabase.table("APIKeyList").select("*").filter("API_KEY", "eq",
|
399 |
-
necklace_try_on_id.api_token).execute()
|
400 |
-
api_key_actual = data[1][0]['API_KEY']
|
401 |
-
if api_key_actual != necklace_try_on_id.api_token:
|
402 |
-
logger.error(">>> API KEY VALIDATION FAILED <<<")
|
403 |
-
return JSONResponse(content={"error": "Invalid API Key"}, status_code=401)
|
404 |
-
logger.info(">>> API KEY VALIDATION SUCCESSFUL <<<")
|
405 |
-
except Exception as e:
|
406 |
-
logger.error(f">>> API KEY VALIDATION ERROR: {str(e)} <<<")
|
407 |
-
return JSONResponse(content={"error": f"Error validating API key", "code": 500}, status_code=500)
|
408 |
-
|
409 |
try:
|
410 |
imageBytes = await image.read()
|
411 |
jewellery_url = f"https://lvuhhlrkcuexzqtsbqyu.supabase.co/storage/v1/object/public/Stores/{necklace_try_on_id.storename}/{necklace_try_on_id.necklaceCategory}/image/{necklace_try_on_id.necklaceImageId}.png"
|
@@ -436,39 +460,47 @@ async def necklace_try_on_id(necklace_try_on_id: NecklaceTryOnIDEntity = Depends
|
|
436 |
logger.info(">>> SAVING RESULT IMAGES <<<")
|
437 |
start_time_saving = time.time()
|
438 |
|
439 |
-
|
440 |
-
|
|
|
|
|
|
|
|
|
441 |
|
442 |
-
|
|
|
443 |
|
|
|
444 |
logger.info(">>> RESULT IMAGES SAVED <<<")
|
445 |
except Exception as e:
|
446 |
logger.error(f">>> RESULT SAVING ERROR: {str(e)} <<<")
|
447 |
return JSONResponse(content={"error": f"Error saving result images", "code": 500}, status_code=500)
|
448 |
-
|
449 |
try:
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
|
|
|
|
|
|
472 |
|
473 |
|
474 |
@nto_cto_router.post("/canvasPoints")
|
|
|
26 |
import requests
|
27 |
from src.utils.logger import logger
|
28 |
import secrets
|
29 |
+
import aiohttp
|
30 |
+
import asyncio
|
31 |
+
import gc
|
32 |
|
33 |
pipeline = Pipeline()
|
34 |
|
|
|
363 |
)
|
364 |
|
365 |
|
366 |
+
async def supabase_upload_and_return_url(prefix: str, image: Image.Image, quality: int = 85):
|
|
|
367 |
try:
|
368 |
+
filename = f"{prefix}_{secrets.token_hex(8)}.webp"
|
369 |
+
|
370 |
+
loop = asyncio.get_event_loop()
|
371 |
+
image_bytes = await loop.run_in_executor(
|
372 |
+
None,
|
373 |
+
process_image,
|
374 |
+
image,
|
375 |
+
quality
|
376 |
+
)
|
377 |
|
378 |
+
async with aiohttp.ClientSession() as session:
|
379 |
+
headers = {
|
380 |
+
"Authorization": f"Bearer {key}",
|
381 |
+
"Content-Type": "image/webp"
|
382 |
+
}
|
383 |
|
384 |
+
upload_url = f"{url}/storage/v1/object/JewelMirrorOutputs/{filename}"
|
|
|
|
|
385 |
|
386 |
+
async with session.post(
|
387 |
+
upload_url,
|
388 |
+
data=image_bytes,
|
389 |
+
headers=headers
|
390 |
+
) as response:
|
391 |
+
if response.status != 200:
|
392 |
+
raise Exception(f"Upload failed with status {response.status}")
|
393 |
|
394 |
return bucket.get_public_url(filename)
|
395 |
|
396 |
except Exception as e:
|
397 |
+
logger.error(f"Failed to upload image: {str(e)}")
|
398 |
return None
|
399 |
+
|
400 |
+
|
401 |
+
def process_image(image: Image.Image, quality: int) -> bytes:
|
402 |
+
try:
|
403 |
+
if image.mode in ['RGBA', 'P']:
|
404 |
+
image = image.convert('RGB')
|
405 |
+
|
406 |
+
max_size = 3000
|
407 |
+
if image.width > max_size or image.height > max_size:
|
408 |
+
ratio = min(max_size / image.width, max_size / image.height)
|
409 |
+
new_size = (int(image.width * ratio), int(image.height * ratio))
|
410 |
+
image = image.resize(new_size, Image.Resampling.LANCZOS)
|
411 |
+
|
412 |
+
with BytesIO() as buffer:
|
413 |
+
image.save(
|
414 |
+
buffer,
|
415 |
+
format='WEBP',
|
416 |
+
quality=quality,
|
417 |
+
optimize=True,
|
418 |
+
method=6
|
419 |
+
)
|
420 |
+
return buffer.getvalue()
|
421 |
+
except Exception as e:
|
422 |
+
logger.error(f"Image processing failed: {str(e)}")
|
423 |
+
raise
|
424 |
|
425 |
|
426 |
@nto_cto_router.post("/necklaceTryOnID")
|
|
|
430 |
logger.info(f">>> NECKLACE TRY ON ID STARTED :: {necklace_try_on_id.storename} <<<")
|
431 |
start_time = time.time()
|
432 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
433 |
try:
|
434 |
imageBytes = await image.read()
|
435 |
jewellery_url = f"https://lvuhhlrkcuexzqtsbqyu.supabase.co/storage/v1/object/public/Stores/{necklace_try_on_id.storename}/{necklace_try_on_id.necklaceCategory}/image/{necklace_try_on_id.necklaceImageId}.png"
|
|
|
460 |
logger.info(">>> SAVING RESULT IMAGES <<<")
|
461 |
start_time_saving = time.time()
|
462 |
|
463 |
+
# Upload both images concurrently
|
464 |
+
upload_tasks = [
|
465 |
+
supabase_upload_and_return_url(prefix="necklace_try_on", image=result),
|
466 |
+
supabase_upload_and_return_url(prefix="necklace_try_on_mask", image=mask)
|
467 |
+
]
|
468 |
+
result_url, mask_url = await asyncio.gather(*upload_tasks)
|
469 |
|
470 |
+
if not result_url or not mask_url:
|
471 |
+
raise Exception("Failed to upload one or both images")
|
472 |
|
473 |
+
logger.info(f">>> RESULT IMAGES SAVED IN {round((time.time() - start_time_saving), 2)}s <<<")
|
474 |
logger.info(">>> RESULT IMAGES SAVED <<<")
|
475 |
except Exception as e:
|
476 |
logger.error(f">>> RESULT SAVING ERROR: {str(e)} <<<")
|
477 |
return JSONResponse(content={"error": f"Error saving result images", "code": 500}, status_code=500)
|
|
|
478 |
try:
|
479 |
+
try:
|
480 |
+
total_backend_time = round((time.time() - start_time), 2)
|
481 |
+
response = {
|
482 |
+
"code": 200,
|
483 |
+
"output": f"{result_url}",
|
484 |
+
"mask": f"{mask_url}",
|
485 |
+
"inference_time": total_backend_time
|
486 |
+
}
|
487 |
+
|
488 |
+
|
489 |
+
|
490 |
+
except Exception as e:
|
491 |
+
logger.error(f">>> RESPONSE GENERATION ERROR: {str(e)} <<<")
|
492 |
+
return JSONResponse(content={"error": f"Error generating response", "code": 500}, status_code=500)
|
493 |
+
|
494 |
+
logger.info(f">>> TOTAL INFERENCE TIME: {total_backend_time}s <<<")
|
495 |
+
logger.info(f">>> NECKLACE TRY ON COMPLETED :: {necklace_try_on_id.storename} <<<")
|
496 |
+
logger.info("-" * 50)
|
497 |
+
|
498 |
+
return JSONResponse(content=response, status_code=200)
|
499 |
+
|
500 |
+
finally:
|
501 |
+
if 'result' in locals(): del result
|
502 |
+
if 'mask' in locals(): del mask
|
503 |
+
gc.collect()
|
504 |
|
505 |
|
506 |
@nto_cto_router.post("/canvasPoints")
|