File size: 15,975 Bytes
c97487d
 
 
 
 
 
 
b402779
 
 
 
 
 
 
 
 
10472fc
c97487d
 
 
 
 
 
 
 
 
 
 
 
 
 
10472fc
 
 
 
 
 
 
b402779
 
 
 
68a3c9c
 
 
b402779
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53a3f23
b402779
 
 
 
 
53a3f23
 
 
 
 
b402779
53a3f23
 
b402779
53a3f23
 
b402779
53a3f23
 
 
10472fc
53a3f23
 
 
b402779
 
 
 
 
10472fc
 
b402779
c97487d
 
b402779
aad0af6
 
 
 
10472fc
aad0af6
 
 
 
10472fc
 
 
 
 
aad0af6
 
 
 
 
 
 
 
 
 
 
 
 
10472fc
 
 
b402779
10472fc
 
aad0af6
 
10472fc
 
 
 
 
 
 
b402779
10472fc
 
 
b402779
10472fc
 
b402779
 
 
 
 
 
 
 
 
 
 
 
10472fc
 
b402779
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10472fc
 
 
b402779
 
 
 
 
 
 
 
 
10472fc
b402779
 
 
10472fc
 
 
 
b402779
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c97487d
 
 
 
 
 
 
 
 
 
b402779
 
 
 
c97487d
 
 
b402779
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c97487d
 
 
b402779
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c97487d
affe61d
c97487d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b402779
c97487d
 
 
 
 
 
 
affe61d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c97487d
b402779
 
 
c97487d
b402779
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
try:
    from process_image import process_image
    PROCESS_IMAGE_AVAILABLE = True
except ImportError as e:
    print(f"Warning: process_image module not available: {e}")
    PROCESS_IMAGE_AVAILABLE = False

from fastapi import FastAPI, UploadFile, File, HTTPException
from pydantic import BaseModel
from typing import Optional
import shutil
import os
import base64
from io import BytesIO
from PIL import Image
import uuid
import tempfile

try:
    from search_product import search_product
    SEARCH_AVAILABLE = True
except ImportError as e:
    print(f"Warning: search_product module not available: {e}")
    SEARCH_AVAILABLE = False

try:
    from background_generator import BackgroundGenerator
    BACKGROUND_GEN_AVAILABLE = True
except ImportError as e:
    print(f"Warning: background_generator module not available: {e}")
    BACKGROUND_GEN_AVAILABLE = False

from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
import logging

# Setup logging
app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "*"  # Allow all origins for deployment - be more restrictive in production
    ],  
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Store active processors
processors = {}

# Health check endpoint
@app.get("/")
def read_root():
    return {"message": "Register File AI API is running!", "status": "healthy"}

@app.get("/health")
def health_check():
    return {"status": "healthy"}

class ImageEnhancementRequest(BaseModel):
    image_path: str
    background: str

class ImageSelectionRequest(BaseModel):
    image_path: str
    option_number: int

def pil_image_to_base64(pil_image):
    """Convert PIL Image to base64 string for JSON serialization"""
    if pil_image is None:
        return None
    if pil_image.mode != 'RGB':
        pil_image = pil_image.convert('RGB')
    
    buffer = BytesIO()
    pil_image.save(buffer, format='JPEG', quality=95)
    img_str = base64.b64encode(buffer.getvalue()).decode()
    return f"data:image/jpeg;base64,{img_str}"

def apply_background(image: Image.Image, background: str) -> Image.Image:
    """Apply a given base64 background image to an RGBA image"""
    if image.mode != 'RGBA':
        image = image.convert("RGBA")

    try:
        # Decode the base64 background image
        background_data = base64.b64decode(background.split(",")[1])  # Remove the "data:image/...;base64," prefix
        background_image = Image.open(BytesIO(background_data))

        # Ensure the background image matches the size of the input image
        background_image = background_image.resize(image.size)

        # Paste the input image (with transparency) on top of the background
        combined_image = Image.new("RGB", image.size)
        combined_image.paste(background_image, (0, 0))
        combined_image.paste(image, (0, 0), mask=image.split()[3])  # Use alpha channel as mask

        return combined_image
    except Exception as e:
        raise ValueError(f"Error applying background: {str(e)}")

@app.post("/upload")
async def upload_image(image: UploadFile = File(...)):
    """Upload an image file and return base64 data"""
    try:
        # Validate file type
        if not image.content_type.startswith('image/'):
            raise HTTPException(status_code=400, detail="File must be an image")
        
        # Read the uploaded file directly into memory
        image_data = await image.read()
        
        # Convert to PIL Image
        pil_image = Image.open(BytesIO(image_data))
        
        # Convert to base64 for frontend
        image_base64 = pil_image_to_base64(pil_image)
        
        # Generate unique ID for this upload session
        upload_id = str(uuid.uuid4())
        
        return {
            "upload_id": upload_id,
            "filename": image.filename,
            "image_base64": image_base64,
            "width": pil_image.width,
            "height": pil_image.height
        }
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error uploading file: {str(e)}")

@app.post("/enhance_and_return_all_options")
async def enhance_image(request: dict):
    """Process image through all enhancement options using base64 data"""
    try:
        if not PROCESS_IMAGE_AVAILABLE:
            raise HTTPException(status_code=503, detail="Image processing module not available")
        
        print(f"Received request: {type(request)}")
        print(f"Request keys: {request.keys()}")
        
        # Handle different request formats
        image_data = request.get("image_base64")
        if image_data is None:
            # Try alternative key names
            image_data = request.get("base64") or request.get("imageData")
        
        background_color = request.get("background")

        if not image_data:
            raise HTTPException(status_code=400, detail="image_base64 field is required")

        print(f"Image data type: {type(image_data)}")
        
        # Handle if image_data is a dict (extract the actual base64 string)
        if isinstance(image_data, dict):
            # Try common keys for base64 data in dict
            image_data = image_data.get("data") or image_data.get("base64") or image_data.get("image_base64")
            if not image_data:
                raise HTTPException(status_code=400, detail="No valid image data found in request")

        # Ensure image_data is a string
        if not isinstance(image_data, str):
            raise HTTPException(status_code=400, detail=f"Image data must be a string, got {type(image_data)}")

        # Decode base64 image
        if image_data.startswith('data:image'):
            image_data = image_data.split(',', 1)[1]
        
        try:
            image_bytes = base64.b64decode(image_data)
        except Exception as e:
            raise HTTPException(status_code=400, detail=f"Invalid base64 image data: {str(e)}")
        
        # Create a temporary file for processing
        with tempfile.NamedTemporaryFile(delete=False, suffix=".jpg") as temp_image:
            temp_image.write(image_bytes)
            temp_image_path = temp_image.name

        print(f"Starting enhancement for temporary image: {temp_image_path}")
        
        # Create a new processor instance
        processor_id = str(uuid.uuid4())
        img_processor = process_image()
        
        # Process the image step by step
        img_processor.process(temp_image_path)
        
        img_processor.raw_image.save("processed_image.png")  # Save processed image for debugging
        
        print("Step 2: Detecting objects...")
        img_processor.detect_object()
        
        img_processor.cropped_image.save("detected_objects_image.png")  # Save detected objects image for debugging
        print(img_processor.detected_objects)
        
        print("Step 3: Removing background...")
        img_processor.remove_background()
        
        if background_color:
            img_processor.no_background_image = apply_background(img_processor.no_background_image, background_color)
        
        img_processor.no_background_image.save("no_background_image.png")  # Save no background image for debugging
        
        print("Step 4: Enhancement option 1...")
        try:
            img_processor.enhance_image_option1()
            print("Enhancement option 1 completed")
        except Exception as e:
            print(f"Enhancement option 1 failed: {str(e)}")
            img_processor.enhanced_image_1 = img_processor.no_background_image
        
        print("Step 5: Enhancement option 2...")
        try:
            img_processor.enhance_image_option2()
            print("Enhancement option 2 completed")
        except Exception as e:
            print(f"Enhancement option 2 failed: {str(e)}")
            img_processor.enhanced_image_2 = img_processor.no_background_image
        
        print("Step 6: Enhancement option 3...")
        try:
            img_processor.enhance_image_option3()
            print("✓ Enhancement option 3 completed")
        except Exception as e:
            print(f"Enhancement option 3 failed: {str(e)}")
            img_processor.enhanced_image_3 = img_processor.no_background_image
        
        # Store the processor for later use
        processors[processor_id] = img_processor
        print(f"Enhancement completed successfully. Processor ID: {processor_id}")
        
        # Clean up the temporary file
        os.unlink(temp_image_path)
        
        # Convert PIL images to base64 for JSON response
        return {
            "processor_id": processor_id,
            "enhanced_image_1": pil_image_to_base64(img_processor.enhanced_image_1),
            "enhanced_image_2": pil_image_to_base64(img_processor.enhanced_image_2),
            "enhanced_image_3": pil_image_to_base64(img_processor.enhanced_image_3),
            "original_image": pil_image_to_base64(img_processor.raw_image),
            "no_background_image": pil_image_to_base64(img_processor.no_background_image)
        }
        
    except HTTPException:
        raise
    except Exception as e:
        # Clean up temp file on error
        if 'temp_image_path' in locals() and os.path.exists(temp_image_path):
            os.unlink(temp_image_path)
        
        print(f"Error during enhancement: {str(e)}")
        import traceback
        traceback.print_exc()
        raise HTTPException(status_code=500, detail=f"Error enhancing image: {str(e)}")

@app.post("/choose_image_and_generate_description")
async def choose_image_and_generate_description(

    processor_id: str,

    option_number: int

):
    """Choose an enhanced image option and generate description"""
    try:
        # Get the processor instance
        if processor_id not in processors:
            raise HTTPException(status_code=404, detail="Processor not found. Please enhance image first.")
        
        img_processor = processors[processor_id]
        
        # Choose the image
        img_processor.choose_image(option_number)
        
        # Generate description
        description = img_processor.generate_description()
        
        return {
            "chosen_image": pil_image_to_base64(img_processor.chosen_image),
            "description": description,
            "option_number": option_number
        }
    
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Error generating description: {str(e)}") 

@app.delete("/cleanup/{processor_id}")
async def cleanup_processor(processor_id: str):
    """Clean up processor instance to free memory"""
    if processor_id in processors:
        del processors[processor_id]
        return {"message": "Processor cleaned up successfully"}
    else:
        raise HTTPException(status_code=404, detail="Processor not found")

@app.get("/status")
async def status_check():
    """Status check endpoint with processor count"""
    return {"status": "healthy", "active_processors": len(processors)}

@app.get("/health")
async def health_check():
    """Health check endpoint for Hugging Face Spaces"""
    return {"status": "ok", "message": "API is running"}

@app.get("/")
async def root():
    """Root endpoint"""
    return {"message": "AI Image Enhancement API is running", "docs": "/docs"}

@app.post("/get_search_results")
async def get_search_results(query:str):
    """Get search results for a query"""
    try:
        if not SEARCH_AVAILABLE:
            raise HTTPException(status_code=503, detail="Search module not available")
            
        print(f"Searching for: {query}")
        searcher = search_product()
        results = searcher.search_products_google_cse(query, 5)
        
        print(f"Found {len(results)} results")
        for i, result in enumerate(results):
            print(f"Result {i+1}: {result.get('title', 'No title')}")
            print(f"URL: {result.get('link', 'No URL')}")
        
        return {
            "results": results, 
        }
    except Exception as e:
        print(f"Error in search: {str(e)}")
        import traceback
        traceback.print_exc()
        raise HTTPException(status_code=500, detail=f"Error searching: {str(e)}")
    
@app.post("/generate_background")
async def generate_background(promptFromUser: str):
    """Generate a background image using Google GenAI"""
    try:
        if not BACKGROUND_GEN_AVAILABLE:
            raise HTTPException(status_code=503, detail="Background generation module not available")
            
        background_gen = BackgroundGenerator()
        print("Generating background image...")
        result = background_gen.generate(prompt=promptFromUser)
        image_path = result.get("file_path")
        public_url = result.get("public_url")
        file_name = result.get("file_name")
        print("Background image generated successfully.")
        
        if not os.path.exists(image_path):
            raise HTTPException(status_code=404, detail="Generated background image not found")
        
        with Image.open(image_path) as img:
            if img.mode != 'RGB':
                img = img.convert('RGB')

            encoded_image = pil_image_to_base64(img)
            if encoded_image is None:
                raise HTTPException(status_code=500, detail="Error converting image to base64")
            return {"image": encoded_image
                    , "public_url": public_url, "file_name": file_name
                    , "file_path": image_path}
            
    except Exception as e:
        print(f"Error generating background: {str(e)}")
        import traceback
        traceback.print_exc()
        raise HTTPException(status_code=500, detail=f"Error generating background: {str(e)}")

# Create Gradio app for Hugging Face Spaces compatibility
import gradio as gr

def gradio_interface():
    """Simple Gradio interface to keep the space alive"""
    return """

    # AI Image Enhancement API

    

    This Hugging Face Space provides an AI-powered image enhancement API with the following endpoints:

    

    ## FastAPI Endpoints Available:

    

    - **POST /upload** - Upload an image file

    - **POST /enhance_and_return_all_options** - Process image through all enhancement options

    - **POST /choose_image_and_generate_description** - Choose enhanced image and generate description

    - **POST /get_search_results** - Get search results for a query

    - **POST /generate_background** - Generate background using AI

    - **GET /status** - Health check endpoint

    

    ## API Documentation:

    Access the interactive API documentation at: `/docs`

    

    ## Usage:

    The API is running on this Space and can be accessed programmatically.

    """

# Only create Gradio interface if running on Hugging Face Spaces
try:
    # Create Gradio app
    iface = gr.Interface(
        fn=gradio_interface,
        inputs=[],
        outputs=gr.Markdown(),
        title="AI Image Enhancement API",
        description="FastAPI backend for AI-powered image enhancement and processing"
    )

    # Mount Gradio app ONLY at /gradio path to avoid conflicts
    app = gr.mount_gradio_app(app, iface, path="/gradio")
except Exception as e:
    print(f"Gradio mounting failed: {e}")
    # Continue without Gradio if it fails

if __name__ == "__main__":
    import uvicorn
    import os
    port = int(os.environ.get("PORT", 7860))
    uvicorn.run(app, host="0.0.0.0", port=port)