File size: 6,220 Bytes
8495b3a
f319748
 
8495b3a
 
f319748
 
8495b3a
f319748
 
 
 
8495b3a
 
 
 
 
 
 
f319748
 
8495b3a
 
 
bb682cf
f319748
 
8495b3a
 
f319748
8495b3a
 
f319748
 
8495b3a
 
f319748
8495b3a
f319748
8495b3a
 
f319748
 
 
 
 
 
 
 
 
 
 
 
a0ed299
f319748
8495b3a
 
 
 
f319748
 
 
 
 
 
8495b3a
 
 
f319748
8495b3a
 
f319748
8495b3a
f319748
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8495b3a
f319748
 
8495b3a
 
 
f319748
 
8495b3a
 
f319748
8495b3a
 
 
 
f319748
 
8495b3a
 
 
 
 
 
f319748
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
"""
StableVITON Inference Wrapper (Remote API Version)
Abstraction layer for virtual try-on inference using Gradio API
"""

import os
import io
import logging
from PIL import Image
from typing import Optional
from gradio_client import Client, handle_file
import tempfile

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


class StableVITONInference:
    """
    Wrapper for Virtual Try-On inference via Fashn AI API.
    Handles remote connection, preprocessing, and result retrieval.
    """
    def __init__(
        self,
        model_path: str = "merve/fashn-vton-1.5",
        hf_token: Optional[str] = None,
        **kwargs
    ):
        """
        Initialize the remote API client.
        
        Args:
            model_path: Hugging Face Space ID (default: fashn-ai/fashn-vton-1.5)
            hf_token: Optional Hugging Face token for private/pro spaces
        """
        self.model_path = model_path
        self.hf_token = hf_token or os.getenv("HF_TOKEN")
        
        logger.info(f"Connecting to Gradio API: {self.model_path}")
        
        try:
            # Add timeout settings via httpx_kwargs for production reliability
            httpx_kwargs = {
                "timeout": 120.0,  # 2 minute timeout for AI inference
            }
            self.client = Client(
                self.model_path, 
                token=self.hf_token,
                httpx_kwargs=httpx_kwargs
            )
            logger.info("Gradio API connected successfully")
        except Exception:
            logger.exception(f"Failed to connect to Gradio API at {self.model_path}")
            raise

    def tryon(
        self,
        person_image: Image.Image,
        garment_image: Image.Image,
        category: str = "tops",
        garment_photo_type: str = "model",
        num_timesteps: int = 50,
        guidance_scale: float = 1.5,
        seed: int = 42,
        segmentation_free: bool = True,
        **kwargs
    ) -> Image.Image:
        """
        Perform virtual try-on inference via remote API.
        """
        try:
            logger.info(f"Starting remote try-on inference (category: {category})")
            
            # Use temporary files to pass images to Gradio client
            # delete=False is required for handle_file() to access the path later
            with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as p_file, \
                 tempfile.NamedTemporaryFile(suffix=".png", delete=False) as g_file:
                
                person_image.save(p_file.name)
                garment_image.save(g_file.name)
                
                # Flush and close to ensure data is written on all OSs (like Windows)
                p_file.flush()
                g_file.flush()
                
                person_path = p_file.name
                garment_path = g_file.name

            # Ensure boolean conversion for segmentation_free (handles string "true"/"false" from FormData)
            if isinstance(segmentation_free, str):
                seg_free = segmentation_free.lower() == "true"
            else:
                seg_free = bool(segmentation_free)

            try:
                logger.info("Step 4a: Preparing temporary files for API transfer")
                result = self.client.predict(
                    person_image=handle_file(person_path),
                    garment_image=handle_file(garment_path),
                    category=category,
                    garment_photo_type=garment_photo_type,
                    num_timesteps=int(num_timesteps),
                    guidance_scale=float(guidance_scale),
                    seed=int(seed),
                    segmentation_free=seg_free,
                    api_name="/try_on"
                )
                
                # The result can be a string (path), a dict with 'path', or a list/tuple
                logger.info("Step 4b: API response received, processing result path")
                print(f"API Result Type: {type(result)}")
                print(f"API Result: {result}")
                
                result_image_path = None
                if isinstance(result, str):
                    result_image_path = result
                elif isinstance(result, dict) and 'path' in result:
                    result_image_path = result['path']
                elif isinstance(result, (list, tuple)) and len(result) > 0:
                    # If it's a list, take the first item (often the path)
                    first_item = result[0]
                    if isinstance(first_item, str):
                        result_image_path = first_item
                    elif isinstance(first_item, dict) and 'path' in first_item:
                        result_image_path = first_item['path']

                if not result_image_path:
                    raise ValueError(f"Could not extract image path from API result: {result}")

                result_image = Image.open(result_image_path)
                logger.info("Step 4c: Result image opened successfully")
                
                return result_image

                
            finally:
                # Cleanup local temp input files
                if os.path.exists(person_path): os.remove(person_path)
                if os.path.exists(garment_path): os.remove(garment_path)
            
        except Exception:
            logger.exception("Remote inference failed with traceback:")
            raise
    
    def cleanup(self):
        """No local memory cleanup needed for API version"""
        pass
    
    def __del__(self):
        pass


# Example usage
if __name__ == "__main__":
    # Test the API wrapper
    print("Testing Fashn-AI Gradio API Wrapper")
    
    # Create dummy images for testing
    person_img = Image.new("RGB", (512, 768), color=(200, 200, 200))
    garment_img = Image.new("RGB", (512, 512), color=(100, 150, 200))
    
    # Initialize wrapper
    try:
        wrapper = StableVITONInference()
        # Note: Actual prediction might fail without real images or token
        print("Initialized successfully")
    except Exception as e:
        print(f"Error during initialization: {e}")