o
    @Tj.                     @   s   d Z ddlZddlZddlZddlmZ ddlmZ ddlZddl	Z
ddlmZ ddlmZ ddlmZmZ ddlmZ dd	lmZ dd
lmZ ddlmZ ddlmZmZmZ eeZ eG dd dZ!G dd dZ"dS )u  VTON (Virtual Try-On) pipeline: person + ghost garments → composited image.

Stateless — no DB record is created for the result. The composite image is
saved under ``storage/vton/{user_id}/{uuid}.png`` and the URL is returned.

Flow:
1. Validate ``garment_ids`` (non-empty).
2. Detect a face in the person image — if none, fail fast with ``ValueError``
   so the router can surface a 400.
3. Fetch the selected garments from DB, preserving request order.
4. Require each garment to have a ``ghost_image_path`` (source_image is a
   full-body person photo, not an isolated garment — unsuitable for VTON).
5. Resize person + load each ghost image bytes from disk.
6. Call ``llm.generate_vton`` (multi-image) → composite bytes + optional note.
7. Remove background from the LLM output (it renders the person on the same
   white backdrop we fed it) so the saved composite has a transparent PNG
   background matching the original RGBA input.
8. Save composite image; parse "#N" references in the note to split
   ``used_garment_ids`` / ``skipped_garment_ids``.

Upper-body handling is delegated to the LLM via prompt instruction: when the
person image is an upper-body crop, the LLM skips bottom-category garments
and reports the skip in its text response (see ``VTON_PROMPT``).
    N)	dataclass)Path)select)AsyncSession)GLOBAL_USER_IDsettings)Garment)BaseLLMProvider)SegmentationService)FaceAnalyzer)resize_image_if_neededresize_png_if_neededsave_vton_imagec                   @   sB   e Zd ZU dZeed< eej ed< eej ed< edB ed< dS )
VtonResultz<Internal result object returned by ``VtonService.generate``.pathused_idsskipped_idsNnote)	__name__
__module____qualname____doc__str__annotations__listuuidUUID r   r   9/home/ubuntu/htdocs/ootd-api/app/services/vton_service.pyr   3   s   
 r   c                   @   sX   e Zd ZdededefddZ	ddeded	e	e
j d
e
jdB dedB defddZdS )VtonServicellm_providerface_analyzersegmentation_servicec                 C   s   || _ || _|| _d S N)llmfacesegmentation)selfr    r!   r"   r   r   r   __init__>   s   
zVtonService.__init__Ndbperson_imagegarment_idsuser_idimage_modelreturnc              
      s2  |st d|p
t}tj|tjd}t|tj}|d u r"t d| j	|}	|	s.t d|
tttj|I d H }
t|
  }dd |D   fdd|D }|rbtd	|d
   fdd|D }|D ]}|js{t d|j dqmt|dd}g }|D ]!}ttj|j }| st d|j |t| dd qdd |D }z| j||||I d H \}}W n ty } zt d| |d }~ww |d u rt dzt!"| j#j$|I d H }W n ty   t%j&ddd |}Y nw t'||}t|}g }t%(d|t)|| t*|||d dS )Nu5   garment_ids는 최소 1개 이상이어야 합니다.)dtypeu+   이미지를 디코딩할 수 없습니다.uG   얼굴을 감지할 수 없습니다. 인물 사진이 필요합니다.c                 S   s   i | ]}|j |qS r   )id.0gr   r   r   
<dictcomp>a       z(VtonService.generate.<locals>.<dictcomp>c                    s   g | ]}| vr|qS r   r   r2   gidby_idr   r   
<listcomp>c   s    z(VtonService.generate.<locals>.<listcomp>u#   의류를 찾을 수 없습니다: r   c                    s   g | ]} | qS r   r   r6   r8   r   r   r:   g   r5   u<   고스트 마네킹이 생성되지 않은 의류입니다: uK   . 먼저 POST /api/v1/garments/{id}/ghost 로 고스트를 생성하세요.i   )max_sizeu,   고스트 이미지 파일이 없습니다: c                 S   s   g | ]}|j qS r   )category_mainr1   r   r   r   r:   ~   s    u   LLM VTON 생성 실패: u5   LLM이 VTON 이미지를 반환하지 않았습니다z5VTON background removal failed; saving raw LLM outputT)exc_infoz%VTON done user=%s garments=%d path=%s)r   r   r   r   )+
ValueErrorr   np
frombufferuint8cv2imdecodeIMREAD_COLORr%   get_embeddingsexecuter   r   wherer0   in_r   scalarsallLookupErrorghost_image_pathr   r   r   
UPLOAD_DIRexistsappendr   
read_bytesr$   generate_vton	ExceptionRuntimeErrorasyncio	to_threadr&   remove_backgroundloggerwarningr   infolenr   )r'   r)   r*   r+   r,   r-   resolved_user_idarrdecoded
embeddingsresultgarmentsmissingorderedr3   person_resizedghost_bytes_list
ghost_full
categoriesimage_bytes_efinal_bytesr   r   r   r   r8   r   generateH   s~   "	

zVtonService.generater#   )r   r   r   r	   r   r
   r(   r   bytesr   r   r   r   r   rk   r   r   r   r   r   =   s,    
r   )#r   rT   loggingr   dataclassesr   pathlibr   rB   numpyr?   
sqlalchemyr   sqlalchemy.ext.asyncior   
app.configr   r   app.models.garmentr   app.services.llm.baser	   !app.services.segmentation_servicer
   app.utils.face_analyzerr   app.utils.imager   r   r   	getLoggerr   rW   r   r   r   r   r   r   <module>   s(    
	