Spaces:
Sleeping
Sleeping
Upload 38 files
Browse files- .gitattributes +15 -0
- merged_models_and_api/.env +2 -0
- merged_models_and_api/.gitignore +4 -0
- merged_models_and_api/__pycache__/background_generator.cpython-311.pyc +0 -0
- merged_models_and_api/__pycache__/background_generator.cpython-313.pyc +0 -0
- merged_models_and_api/__pycache__/image_enhancement_option3_helper.cpython-311.pyc +0 -0
- merged_models_and_api/__pycache__/image_enhancement_option3_helper.cpython-313.pyc +0 -0
- merged_models_and_api/__pycache__/process_image.cpython-311.pyc +0 -0
- merged_models_and_api/__pycache__/process_image.cpython-313.pyc +0 -0
- merged_models_and_api/__pycache__/rf_ai_api.cpython-311.pyc +0 -0
- merged_models_and_api/__pycache__/search_product.cpython-311.pyc +0 -0
- merged_models_and_api/__pycache__/search_product.cpython-313.pyc +0 -0
- merged_models_and_api/background_generator.py +102 -0
- merged_models_and_api/image_enhancement_option3_helper.py +214 -0
- merged_models_and_api/process_image.py +357 -0
- merged_models_and_api/rf_ai_api.py +294 -0
- merged_models_and_api/search_product.py +105 -0
- merged_models_and_api/temp_image.png +3 -0
- merged_models_and_api/uploads/1ad196f4-9c68-4e25-a11f-f78d0067591b.jpg +3 -0
- merged_models_and_api/uploads/1c837ff1-19ea-47a7-ad48-7a2b78feddac.jpg +3 -0
- merged_models_and_api/uploads/34d20ae0-092f-4fc1-a957-38296d6d4f07.png +3 -0
- merged_models_and_api/uploads/35a753c4-8b4e-4784-91d4-935af8d968ec.jpg +0 -0
- merged_models_and_api/uploads/3e585a79-d927-4ba0-bfe8-294e2a6228d8.jpg +3 -0
- merged_models_and_api/uploads/4a23733a-104d-4804-90b8-694a3186edcf.jpg +3 -0
- merged_models_and_api/uploads/73b35923-3a5a-4a0b-9ba7-6ad3b544362f.jpg +3 -0
- merged_models_and_api/uploads/78c1bafc-55ab-4ff9-894f-4cb1b35ca1bb.jpeg +0 -0
- merged_models_and_api/uploads/8b2085b4-c44f-4925-a1f4-182eac070bbf.jpg +3 -0
- merged_models_and_api/uploads/9d014739-7e54-4886-94e9-a52f4ca7bdf7.jpg +0 -0
- merged_models_and_api/uploads/a3d25bdd-ca68-4167-9c74-a50e3a43e448.jpg +3 -0
- merged_models_and_api/uploads/a770eb04-2469-4102-af71-53afc5ea08ef.jpg +3 -0
- merged_models_and_api/uploads/ae4878b8-ccdf-4363-a53e-0172f5a5c6cb.jpg +0 -0
- merged_models_and_api/uploads/be1f9c7f-e3ba-4890-9034-337219e69180.jpg +0 -0
- merged_models_and_api/uploads/bfcedb9d-acab-420d-95ba-aa239c121810.png +3 -0
- merged_models_and_api/uploads/cce44ef5-15af-4532-8197-db4cb51ec4d9.jpg +3 -0
- merged_models_and_api/uploads/cfafcaad-95d0-47d9-aac1-098a200323c0.jpg +0 -0
- merged_models_and_api/uploads/dc87fea8-2351-4837-9d21-46d47935b818.jpg +0 -0
- merged_models_and_api/uploads/e177e7dc-50da-4ef8-8746-d428193f98e5.jpg +3 -0
- merged_models_and_api/uploads/f05a4dae-d69e-48a0-a6e5-28508c2d1147.png +3 -0
- merged_models_and_api/uploads/f2046fa5-79e5-4c20-91f6-f0f70fab2232.jpg +3 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,18 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
merged_models_and_api/temp_image.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
merged_models_and_api/uploads/1ad196f4-9c68-4e25-a11f-f78d0067591b.jpg filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
merged_models_and_api/uploads/1c837ff1-19ea-47a7-ad48-7a2b78feddac.jpg filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
merged_models_and_api/uploads/34d20ae0-092f-4fc1-a957-38296d6d4f07.png filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
merged_models_and_api/uploads/3e585a79-d927-4ba0-bfe8-294e2a6228d8.jpg filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
merged_models_and_api/uploads/4a23733a-104d-4804-90b8-694a3186edcf.jpg filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
merged_models_and_api/uploads/73b35923-3a5a-4a0b-9ba7-6ad3b544362f.jpg filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
merged_models_and_api/uploads/8b2085b4-c44f-4925-a1f4-182eac070bbf.jpg filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
merged_models_and_api/uploads/a3d25bdd-ca68-4167-9c74-a50e3a43e448.jpg filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
merged_models_and_api/uploads/a770eb04-2469-4102-af71-53afc5ea08ef.jpg filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
merged_models_and_api/uploads/bfcedb9d-acab-420d-95ba-aa239c121810.png filter=lfs diff=lfs merge=lfs -text
|
| 47 |
+
merged_models_and_api/uploads/cce44ef5-15af-4532-8197-db4cb51ec4d9.jpg filter=lfs diff=lfs merge=lfs -text
|
| 48 |
+
merged_models_and_api/uploads/e177e7dc-50da-4ef8-8746-d428193f98e5.jpg filter=lfs diff=lfs merge=lfs -text
|
| 49 |
+
merged_models_and_api/uploads/f05a4dae-d69e-48a0-a6e5-28508c2d1147.png filter=lfs diff=lfs merge=lfs -text
|
| 50 |
+
merged_models_and_api/uploads/f2046fa5-79e5-4c20-91f6-f0f70fab2232.jpg filter=lfs diff=lfs merge=lfs -text
|
merged_models_and_api/.env
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
SECRET_API_KEY="AIzaSyBQ1l8bsM59ux19c7eOl91wir8A5VDH5uQ"
|
| 2 |
+
SEARCH_API_KEY="AIzaSyD4UYlJlpktwEyCtM_ONEpQWMrbyKyzmFg"
|
merged_models_and_api/.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.env
|
| 2 |
+
.env.local
|
| 3 |
+
.env.development
|
| 4 |
+
.env.test
|
merged_models_and_api/__pycache__/background_generator.cpython-311.pyc
ADDED
|
Binary file (3.65 kB). View file
|
|
|
merged_models_and_api/__pycache__/background_generator.cpython-313.pyc
ADDED
|
Binary file (4.22 kB). View file
|
|
|
merged_models_and_api/__pycache__/image_enhancement_option3_helper.cpython-311.pyc
ADDED
|
Binary file (12.3 kB). View file
|
|
|
merged_models_and_api/__pycache__/image_enhancement_option3_helper.cpython-313.pyc
ADDED
|
Binary file (10.9 kB). View file
|
|
|
merged_models_and_api/__pycache__/process_image.cpython-311.pyc
ADDED
|
Binary file (18.1 kB). View file
|
|
|
merged_models_and_api/__pycache__/process_image.cpython-313.pyc
ADDED
|
Binary file (16.5 kB). View file
|
|
|
merged_models_and_api/__pycache__/rf_ai_api.cpython-311.pyc
ADDED
|
Binary file (11.9 kB). View file
|
|
|
merged_models_and_api/__pycache__/search_product.cpython-311.pyc
ADDED
|
Binary file (3.65 kB). View file
|
|
|
merged_models_and_api/__pycache__/search_product.cpython-313.pyc
ADDED
|
Binary file (3.34 kB). View file
|
|
|
merged_models_and_api/background_generator.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import base64
|
| 2 |
+
import mimetypes
|
| 3 |
+
import os
|
| 4 |
+
import time
|
| 5 |
+
import uuid
|
| 6 |
+
from google import genai
|
| 7 |
+
from google.genai import types
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
load_dotenv()
|
| 12 |
+
|
| 13 |
+
class BackgroundGenerator:
|
| 14 |
+
def save_binary_file(self, file_name, data):
|
| 15 |
+
f = open(file_name, "wb")
|
| 16 |
+
f.write(data)
|
| 17 |
+
f.close()
|
| 18 |
+
print(f"File saved to to: {file_name}")
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def generate(self, prompt):
|
| 22 |
+
client = genai.Client(
|
| 23 |
+
api_key=os.getenv("SECRET_API_KEY"),
|
| 24 |
+
)
|
| 25 |
+
|
| 26 |
+
model = "gemini-2.0-flash-preview-image-generation"
|
| 27 |
+
contents = [
|
| 28 |
+
types.Content(
|
| 29 |
+
role="user",
|
| 30 |
+
parts=[
|
| 31 |
+
types.Part.from_text(text=prompt),
|
| 32 |
+
],
|
| 33 |
+
),
|
| 34 |
+
]
|
| 35 |
+
generate_content_config = types.GenerateContentConfig(
|
| 36 |
+
response_modalities=[
|
| 37 |
+
"IMAGE",
|
| 38 |
+
"TEXT",
|
| 39 |
+
],
|
| 40 |
+
safety_settings=[
|
| 41 |
+
types.SafetySetting(
|
| 42 |
+
category="HARM_CATEGORY_HARASSMENT",
|
| 43 |
+
threshold="BLOCK_NONE",
|
| 44 |
+
),
|
| 45 |
+
types.SafetySetting(
|
| 46 |
+
category="HARM_CATEGORY_HATE_SPEECH",
|
| 47 |
+
threshold="BLOCK_NONE",
|
| 48 |
+
),
|
| 49 |
+
types.SafetySetting(
|
| 50 |
+
category="HARM_CATEGORY_SEXUALLY_EXPLICIT",
|
| 51 |
+
threshold="BLOCK_NONE",
|
| 52 |
+
),
|
| 53 |
+
types.SafetySetting(
|
| 54 |
+
category="HARM_CATEGORY_DANGEROUS_CONTENT",
|
| 55 |
+
threshold="BLOCK_NONE",
|
| 56 |
+
),
|
| 57 |
+
],
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
timestamp = int(time.time())
|
| 61 |
+
unique_id = str(uuid.uuid4())
|
| 62 |
+
|
| 63 |
+
file_index = 0
|
| 64 |
+
for chunk in client.models.generate_content_stream(
|
| 65 |
+
model=model,
|
| 66 |
+
contents=contents,
|
| 67 |
+
config=generate_content_config,
|
| 68 |
+
):
|
| 69 |
+
if (
|
| 70 |
+
chunk.candidates is None
|
| 71 |
+
or chunk.candidates[0].content is None
|
| 72 |
+
or chunk.candidates[0].content.parts is None
|
| 73 |
+
):
|
| 74 |
+
continue
|
| 75 |
+
if chunk.candidates[0].content.parts[0].inline_data and chunk.candidates[0].content.parts[0].inline_data.data:
|
| 76 |
+
inline_data = chunk.candidates[0].content.parts[0].inline_data
|
| 77 |
+
data_buffer = inline_data.data
|
| 78 |
+
file_extension = mimetypes.guess_extension(inline_data.mime_type)
|
| 79 |
+
|
| 80 |
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
| 81 |
+
project_root = os.path.dirname(os.path.dirname(script_dir)) # Go up 2 levels from merged_models_and_api
|
| 82 |
+
|
| 83 |
+
# Path to frontend public folder
|
| 84 |
+
frontend_public = os.path.join(project_root, "frontend", "public")
|
| 85 |
+
|
| 86 |
+
# Create the directory if it doesn't exist
|
| 87 |
+
os.makedirs(frontend_public, exist_ok=True)
|
| 88 |
+
|
| 89 |
+
unique_filename = f"background_{timestamp}_{unique_id[:8]}{file_extension}"
|
| 90 |
+
output_path = os.path.join(frontend_public, unique_filename)
|
| 91 |
+
|
| 92 |
+
self.save_binary_file(output_path, data_buffer)
|
| 93 |
+
|
| 94 |
+
# Return both the full path and the public URL path
|
| 95 |
+
public_url = f"/backgrounds/{unique_filename}"
|
| 96 |
+
return {
|
| 97 |
+
'file_path': output_path,
|
| 98 |
+
'public_url': public_url,
|
| 99 |
+
'file_name': unique_filename
|
| 100 |
+
}
|
| 101 |
+
else:
|
| 102 |
+
print(chunk.text)
|
merged_models_and_api/image_enhancement_option3_helper.py
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import uuid
|
| 3 |
+
from PIL import Image, ImageEnhance, ImageFilter
|
| 4 |
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
|
| 7 |
+
load_dotenv()
|
| 8 |
+
|
| 9 |
+
class image_enhancement_option3_helper:
|
| 10 |
+
def __init__(self, model):
|
| 11 |
+
self.model = model
|
| 12 |
+
|
| 13 |
+
def analyze_image(self, img) -> dict:
|
| 14 |
+
"""Analyzes an image and returns its properties."""
|
| 15 |
+
try:
|
| 16 |
+
width, height = img.size
|
| 17 |
+
mode = img.mode
|
| 18 |
+
|
| 19 |
+
# Simple brightness analysis
|
| 20 |
+
grayscale = img.convert('L')
|
| 21 |
+
pixels = list(grayscale.getdata())
|
| 22 |
+
avg_brightness = sum(pixels) / len(pixels)
|
| 23 |
+
|
| 24 |
+
analysis = {
|
| 25 |
+
'width': width,
|
| 26 |
+
'height': height,
|
| 27 |
+
'mode': mode,
|
| 28 |
+
'avg_brightness': avg_brightness,
|
| 29 |
+
'is_dark': avg_brightness < 100,
|
| 30 |
+
'is_small': width < 500 or height < 500,
|
| 31 |
+
'aspect_ratio': width / height,
|
| 32 |
+
'recommendations': []
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
# Generate recommendations
|
| 36 |
+
if analysis['is_dark']:
|
| 37 |
+
analysis['recommendations'].append(f"Increase brightness (current: {avg_brightness:.1f})")
|
| 38 |
+
|
| 39 |
+
analysis['recommendations'].append("Enhance contrast for better dynamic range")
|
| 40 |
+
|
| 41 |
+
if analysis['is_small']:
|
| 42 |
+
analysis['recommendations'].append("Apply sharpening (small image)")
|
| 43 |
+
|
| 44 |
+
analysis['recommendations'].append("Light noise reduction for smoothing")
|
| 45 |
+
return analysis
|
| 46 |
+
|
| 47 |
+
except Exception as e:
|
| 48 |
+
print(f"Error analyzing image: {e}")
|
| 49 |
+
return {}
|
| 50 |
+
|
| 51 |
+
def ai_enhanced_image_processing(self, image: Image) -> str:
|
| 52 |
+
"""
|
| 53 |
+
Uses AI to analyze the image and decide on enhancements, then applies them.
|
| 54 |
+
This is a hybrid approach that uses AI for decision making but direct Python for processing.
|
| 55 |
+
"""
|
| 56 |
+
# Step 1: Analyze the image
|
| 57 |
+
analysis = self.analyze_image(image)
|
| 58 |
+
if not analysis:
|
| 59 |
+
return None
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
# Step 2: Using Google Generative AI to decide on enhancements
|
| 63 |
+
llm = ChatGoogleGenerativeAI(
|
| 64 |
+
model="gemini-2.5-flash",
|
| 65 |
+
google_api_key=os.getenv("SECRET_API_KEY"),
|
| 66 |
+
temperature=0.1,
|
| 67 |
+
)
|
| 68 |
+
|
| 69 |
+
ai_prompt = f"""
|
| 70 |
+
You are an expert image enhancement specialist. Based on this image analysis, decide on the optimal enhancement strategy:
|
| 71 |
+
|
| 72 |
+
Image Analysis:
|
| 73 |
+
- Dimensions: {analysis['width']}x{analysis['height']} pixels
|
| 74 |
+
- Color mode: {analysis['mode']}
|
| 75 |
+
- Average brightness: {analysis['avg_brightness']:.1f} (0-255 scale)
|
| 76 |
+
- Is dark: {analysis['is_dark']}
|
| 77 |
+
- Is small: {analysis['is_small']}
|
| 78 |
+
- Aspect ratio: {analysis['aspect_ratio']:.2f}
|
| 79 |
+
|
| 80 |
+
Recommendations from analysis:
|
| 81 |
+
{chr(10).join(f"- {rec}" for rec in analysis['recommendations'])}
|
| 82 |
+
|
| 83 |
+
Please provide a specific enhancement plan in this exact format:
|
| 84 |
+
BRIGHTNESS_FACTOR: [number between 0.8-1.5, or SKIP]
|
| 85 |
+
CONTRAST_FACTOR: [number between 0.8-1.5, or SKIP]
|
| 86 |
+
SHARPNESS_FACTOR: [number between 0.8-2.0, or SKIP]
|
| 87 |
+
NOISE_REDUCTION_RADIUS: [number between 0.3-2.0, or SKIP]
|
| 88 |
+
|
| 89 |
+
Consider:
|
| 90 |
+
- If brightness < 90, suggest 1.2-1.4 brightness factor
|
| 91 |
+
- If brightness > 90, suggest 1.0-1.2 or SKIP
|
| 92 |
+
- Always enhance contrast slightly (1.1-1.3)
|
| 93 |
+
- For small images, use higher sharpness (1.3-1.8)
|
| 94 |
+
- Use light noise reduction (0.5-0.8) for final smoothing
|
| 95 |
+
"""
|
| 96 |
+
|
| 97 |
+
try:
|
| 98 |
+
ai_response = llm.invoke(ai_prompt)
|
| 99 |
+
print(f"AI Enhancement Plan:\n{ai_response.content}")
|
| 100 |
+
|
| 101 |
+
enhancement_plan = self.parse_ai_response(ai_response.content)
|
| 102 |
+
|
| 103 |
+
if enhancement_plan.get('brightness') != 'SKIP':
|
| 104 |
+
print(f"Applying brightness enhancement (factor: {enhancement_plan['brightness']})")
|
| 105 |
+
current_image = self.increase_brightness(image, enhancement_plan['brightness'])
|
| 106 |
+
|
| 107 |
+
if enhancement_plan.get('contrast') != 'SKIP':
|
| 108 |
+
print(f"Applying contrast enhancement (factor: {enhancement_plan['contrast']})")
|
| 109 |
+
current_image = self.increase_contrast(current_image, enhancement_plan['contrast'])
|
| 110 |
+
|
| 111 |
+
if enhancement_plan.get('sharpness') != 'SKIP':
|
| 112 |
+
print(f"Applying sharpness enhancement (factor: {enhancement_plan['sharpness']})")
|
| 113 |
+
current_image = self.increase_sharpness(current_image, enhancement_plan['sharpness'])
|
| 114 |
+
|
| 115 |
+
if enhancement_plan.get('noise_reduction') != 'SKIP':
|
| 116 |
+
print(f"Applying noise reduction (radius: {enhancement_plan['noise_reduction']})")
|
| 117 |
+
current_image = self.noise_reduction(current_image, enhancement_plan['noise_reduction'])
|
| 118 |
+
return current_image
|
| 119 |
+
|
| 120 |
+
except Exception as e:
|
| 121 |
+
return self.rule_based_enhancement(image, analysis)
|
| 122 |
+
|
| 123 |
+
def parse_ai_response(self,response: str) -> dict:
|
| 124 |
+
"""Parse the AI response to extract enhancement parameters."""
|
| 125 |
+
plan = {}
|
| 126 |
+
lines = response.split('\n')
|
| 127 |
+
|
| 128 |
+
for line in lines:
|
| 129 |
+
if 'BRIGHTNESS_FACTOR:' in line:
|
| 130 |
+
value = line.split(':')[1].strip()
|
| 131 |
+
plan['brightness'] = float(value) if value != 'SKIP' else 'SKIP'
|
| 132 |
+
elif 'CONTRAST_FACTOR:' in line:
|
| 133 |
+
value = line.split(':')[1].strip()
|
| 134 |
+
plan['contrast'] = float(value) if value != 'SKIP' else 'SKIP'
|
| 135 |
+
elif 'SHARPNESS_FACTOR:' in line:
|
| 136 |
+
value = line.split(':')[1].strip()
|
| 137 |
+
plan['sharpness'] = float(value) if value != 'SKIP' else 'SKIP'
|
| 138 |
+
elif 'NOISE_REDUCTION_RADIUS:' in line:
|
| 139 |
+
value = line.split(':')[1].strip()
|
| 140 |
+
plan['noise_reduction'] = float(value) if value != 'SKIP' else 'SKIP'
|
| 141 |
+
|
| 142 |
+
# Set defaults if not provided
|
| 143 |
+
plan.setdefault('brightness', 1.1)
|
| 144 |
+
plan.setdefault('contrast', 1.2)
|
| 145 |
+
plan.setdefault('sharpness', 1.3)
|
| 146 |
+
plan.setdefault('noise_reduction', 0.6)
|
| 147 |
+
|
| 148 |
+
return plan
|
| 149 |
+
|
| 150 |
+
def rule_based_enhancement(self, image, analysis: dict):
|
| 151 |
+
"""Fallback rule-based enhancement if AI fails."""
|
| 152 |
+
print("🔧 Applying rule-based enhancement...")
|
| 153 |
+
|
| 154 |
+
current_image = image
|
| 155 |
+
|
| 156 |
+
if analysis['is_dark']:
|
| 157 |
+
current_image = self.increase_brightness(current_image, 1.3)
|
| 158 |
+
|
| 159 |
+
current_image = self.increase_contrast(current_image, 1.2)
|
| 160 |
+
|
| 161 |
+
if analysis['is_small']:
|
| 162 |
+
current_image = self.increase_sharpness(current_image, 1.4)
|
| 163 |
+
else:
|
| 164 |
+
current_image = self.increase_sharpness(current_image, 1.2)
|
| 165 |
+
|
| 166 |
+
current_image = self.noise_reduction(current_image, 0.3)
|
| 167 |
+
|
| 168 |
+
return current_image
|
| 169 |
+
|
| 170 |
+
def increase_brightness(self, img, factor: float) -> str:
|
| 171 |
+
"""Increases the brightness of the image."""
|
| 172 |
+
try:
|
| 173 |
+
enhancer = ImageEnhance.Brightness(img)
|
| 174 |
+
enhanced_img = enhancer.enhance(factor)
|
| 175 |
+
return enhanced_img
|
| 176 |
+
except Exception as e:
|
| 177 |
+
print(f"Error: {e}")
|
| 178 |
+
return img
|
| 179 |
+
|
| 180 |
+
def increase_contrast(self, img, factor: float) -> str:
|
| 181 |
+
"""Increases the contrast of the image."""
|
| 182 |
+
try:
|
| 183 |
+
enhancer = ImageEnhance.Contrast(img)
|
| 184 |
+
enhanced_img = enhancer.enhance(factor)
|
| 185 |
+
return enhanced_img
|
| 186 |
+
except Exception as e:
|
| 187 |
+
print(f"Error: {e}")
|
| 188 |
+
return img
|
| 189 |
+
except Exception as e:
|
| 190 |
+
print(f"Error: {e}")
|
| 191 |
+
return img
|
| 192 |
+
|
| 193 |
+
def increase_sharpness(self, img, factor: float) -> str:
|
| 194 |
+
"""Increases the sharpness of the image."""
|
| 195 |
+
try:
|
| 196 |
+
enhancer = ImageEnhance.Sharpness(img)
|
| 197 |
+
enhanced_img = enhancer.enhance(factor)
|
| 198 |
+
return enhanced_img
|
| 199 |
+
except Exception as e:
|
| 200 |
+
print(f"Error: {e}")
|
| 201 |
+
return img
|
| 202 |
+
|
| 203 |
+
def noise_reduction(self, img, radius: float) -> str:
|
| 204 |
+
"""Reduces noise in the image using Gaussian blur."""
|
| 205 |
+
try:
|
| 206 |
+
filtered_img = img.filter(ImageFilter.GaussianBlur(radius=radius))
|
| 207 |
+
return filtered_img
|
| 208 |
+
except Exception as e:
|
| 209 |
+
print(f"Error: {e}")
|
| 210 |
+
return img
|
| 211 |
+
except Exception as e:
|
| 212 |
+
print(f"Error: {e}")
|
| 213 |
+
return img
|
| 214 |
+
|
merged_models_and_api/process_image.py
ADDED
|
@@ -0,0 +1,357 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from matplotlib import image
|
| 2 |
+
from transformers import OwlViTProcessor, OwlViTForObjectDetection
|
| 3 |
+
from PIL import Image
|
| 4 |
+
import torch
|
| 5 |
+
import matplotlib.pyplot as plt
|
| 6 |
+
import matplotlib.patches as patches
|
| 7 |
+
from rembg import remove
|
| 8 |
+
import os
|
| 9 |
+
import cv2
|
| 10 |
+
import numpy as np
|
| 11 |
+
from PIL import Image, ImageEnhance, ImageFilter
|
| 12 |
+
from gradio_client import Client, handle_file
|
| 13 |
+
import requests
|
| 14 |
+
import shutil
|
| 15 |
+
import json
|
| 16 |
+
import google.generativeai as genai
|
| 17 |
+
import base64
|
| 18 |
+
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 19 |
+
import image_enhancement_option3_helper
|
| 20 |
+
from dotenv import load_dotenv
|
| 21 |
+
|
| 22 |
+
load_dotenv()
|
| 23 |
+
|
| 24 |
+
class process_image:
|
| 25 |
+
def __init__(self):
|
| 26 |
+
self.image_path = None
|
| 27 |
+
self.raw_image = None
|
| 28 |
+
self.detected_objects = []
|
| 29 |
+
self.cropped_image = None
|
| 30 |
+
self.no_background_image = None
|
| 31 |
+
self.enhanced_image_1 = None
|
| 32 |
+
self.enhanced_image_2 = None
|
| 33 |
+
self.enhanced_image_3 = None
|
| 34 |
+
self.chosen_image = None
|
| 35 |
+
self.description = ""
|
| 36 |
+
|
| 37 |
+
def detect_object(self):
|
| 38 |
+
processor = OwlViTProcessor.from_pretrained("google/owlvit-base-patch32")
|
| 39 |
+
model = OwlViTForObjectDetection.from_pretrained("google/owlvit-base-patch32")
|
| 40 |
+
texts = [[
|
| 41 |
+
# Giyim
|
| 42 |
+
"clothing",
|
| 43 |
+
"topwear",
|
| 44 |
+
"bottomwear",
|
| 45 |
+
"outerwear",
|
| 46 |
+
"apparel",
|
| 47 |
+
"sportswear",
|
| 48 |
+
"uniform",
|
| 49 |
+
"underwear",
|
| 50 |
+
"dress",
|
| 51 |
+
"outfit",
|
| 52 |
+
|
| 53 |
+
# Ayakkabı
|
| 54 |
+
"footwear",
|
| 55 |
+
"shoes",
|
| 56 |
+
"boots",
|
| 57 |
+
"sneakers",
|
| 58 |
+
|
| 59 |
+
# Aksesuarlar
|
| 60 |
+
"accessory",
|
| 61 |
+
"bag",
|
| 62 |
+
"backpack",
|
| 63 |
+
"handbag",
|
| 64 |
+
"wallet",
|
| 65 |
+
"belt",
|
| 66 |
+
"hat",
|
| 67 |
+
"cap",
|
| 68 |
+
"scarf",
|
| 69 |
+
"glasses",
|
| 70 |
+
"watch",
|
| 71 |
+
"jewelry",
|
| 72 |
+
|
| 73 |
+
# Elektronik
|
| 74 |
+
"electronics",
|
| 75 |
+
"device",
|
| 76 |
+
"gadget",
|
| 77 |
+
"smartphone",
|
| 78 |
+
"laptop",
|
| 79 |
+
"tablet",
|
| 80 |
+
"headphones",
|
| 81 |
+
"smartwatch",
|
| 82 |
+
|
| 83 |
+
# Kozmetik / Kişisel Bakım
|
| 84 |
+
"cosmetics",
|
| 85 |
+
"beauty product",
|
| 86 |
+
"skincare",
|
| 87 |
+
"makeup",
|
| 88 |
+
"perfume",
|
| 89 |
+
"hair product",
|
| 90 |
+
|
| 91 |
+
# Bebek ve çocuk
|
| 92 |
+
"baby product",
|
| 93 |
+
"baby clothes",
|
| 94 |
+
"toy",
|
| 95 |
+
"stroller",
|
| 96 |
+
"pacifier",
|
| 97 |
+
|
| 98 |
+
# Ev ve yaşam
|
| 99 |
+
"home item",
|
| 100 |
+
"furniture",
|
| 101 |
+
"appliance",
|
| 102 |
+
"decor",
|
| 103 |
+
"kitchenware",
|
| 104 |
+
"bedding",
|
| 105 |
+
"cleaning tool",
|
| 106 |
+
|
| 107 |
+
# Spor ve outdoor
|
| 108 |
+
"sports gear",
|
| 109 |
+
"fitness equipment",
|
| 110 |
+
"gym accessory",
|
| 111 |
+
"camping gear",
|
| 112 |
+
"bicycle equipment"
|
| 113 |
+
]
|
| 114 |
+
]
|
| 115 |
+
|
| 116 |
+
inputs = processor(text=texts, images=self.raw_image, return_tensors="pt")
|
| 117 |
+
|
| 118 |
+
with torch.no_grad():
|
| 119 |
+
outputs = model(**inputs)
|
| 120 |
+
|
| 121 |
+
target_sizes = torch.tensor([self.raw_image.size[::-1]])
|
| 122 |
+
results = processor.post_process_grounded_object_detection(
|
| 123 |
+
outputs=outputs,
|
| 124 |
+
target_sizes=target_sizes,
|
| 125 |
+
threshold=0.2
|
| 126 |
+
)[0]
|
| 127 |
+
self.detected_objects = results["labels"].tolist()
|
| 128 |
+
|
| 129 |
+
# Collect all valid bounding boxes
|
| 130 |
+
valid_boxes = []
|
| 131 |
+
detected_labels = []
|
| 132 |
+
for score, label_id, box in zip(results["scores"], results["labels"], results["boxes"]):
|
| 133 |
+
if score < 0.05:
|
| 134 |
+
continue
|
| 135 |
+
valid_boxes.append(box.tolist())
|
| 136 |
+
detected_labels.append(texts[0][label_id])
|
| 137 |
+
|
| 138 |
+
if len(valid_boxes) == 0:
|
| 139 |
+
self.cropped_image = self.raw_image
|
| 140 |
+
elif len(valid_boxes) == 1:
|
| 141 |
+
# Single object detected
|
| 142 |
+
xmin, ymin, xmax, ymax = map(int, valid_boxes[0])
|
| 143 |
+
self.cropped_image = self.raw_image.crop((xmin, ymin, xmax, ymax))
|
| 144 |
+
print(f"Single object detected: {detected_labels[0]}")
|
| 145 |
+
else:
|
| 146 |
+
# Multiple objects detected and they are pairs
|
| 147 |
+
similar_items = ['shoes', 'boots', 'sneakers', 'footwear', 'glasses', 'earrings',
|
| 148 |
+
'gloves', 'socks', 'jewelry', 'watch', 'bracelet']
|
| 149 |
+
clothing_items = ['clothing', 'topwear', 'bottomwear', 'dress', 'outfit', 'apparel']
|
| 150 |
+
|
| 151 |
+
has_similar_items = any(any(item in label.lower() for item in similar_items)
|
| 152 |
+
for label in detected_labels)
|
| 153 |
+
has_clothing_items = any(any(item in label.lower() for item in clothing_items)
|
| 154 |
+
for label in detected_labels)
|
| 155 |
+
|
| 156 |
+
if has_similar_items or has_clothing_items or len(valid_boxes) <= 3:
|
| 157 |
+
# Combining them
|
| 158 |
+
all_xmin = min(box[0] for box in valid_boxes)
|
| 159 |
+
all_ymin = min(box[1] for box in valid_boxes)
|
| 160 |
+
all_xmax = max(box[2] for box in valid_boxes)
|
| 161 |
+
all_ymax = max(box[3] for box in valid_boxes)
|
| 162 |
+
|
| 163 |
+
self.cropped_image = self.raw_image.crop((all_xmin, all_ymin, all_xmax, all_ymax))
|
| 164 |
+
else: # If there are too many different objects
|
| 165 |
+
self.cropped_image = self.raw_image
|
| 166 |
+
|
| 167 |
+
def remove_background(self):
|
| 168 |
+
if self.cropped_image is None:
|
| 169 |
+
print("No cropped image available. Using entire image.")
|
| 170 |
+
self.cropped_image = self.raw_image
|
| 171 |
+
|
| 172 |
+
self.no_background_image = remove(self.cropped_image)
|
| 173 |
+
|
| 174 |
+
def enhance_image_option1(self):
|
| 175 |
+
sharpened = self.no_background_image.filter(ImageFilter.UnsharpMask(
|
| 176 |
+
radius=1,
|
| 177 |
+
percent=120,
|
| 178 |
+
threshold=1
|
| 179 |
+
))
|
| 180 |
+
|
| 181 |
+
enhancer = ImageEnhance.Contrast(sharpened)
|
| 182 |
+
contrast_enhanced = enhancer.enhance(1.1) # 10% more contrast
|
| 183 |
+
|
| 184 |
+
enhancer = ImageEnhance.Brightness(contrast_enhanced)
|
| 185 |
+
brightness_enhanced = enhancer.enhance(1.02) # 2% brighter
|
| 186 |
+
|
| 187 |
+
enhancer = ImageEnhance.Color(brightness_enhanced)
|
| 188 |
+
color_enhanced = enhancer.enhance(1.05) # 5% more vibrant
|
| 189 |
+
|
| 190 |
+
img_array = np.array(color_enhanced)
|
| 191 |
+
|
| 192 |
+
img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
|
| 193 |
+
denoised = cv2.bilateralFilter(img_bgr, 3, 10, 10)
|
| 194 |
+
img_rgb = cv2.cvtColor(denoised, cv2.COLOR_BGR2RGB)
|
| 195 |
+
|
| 196 |
+
self.enhanced_image_1 = Image.fromarray(img_rgb)
|
| 197 |
+
scale = 1.5
|
| 198 |
+
original_size = self.enhanced_image_1.size
|
| 199 |
+
new_size = (int(original_size[0] * scale), int(original_size[1] * scale))
|
| 200 |
+
|
| 201 |
+
self.enhanced_image_1 = self.enhanced_image_1.resize(new_size, Image.Resampling.LANCZOS)
|
| 202 |
+
return self.enhanced_image_1
|
| 203 |
+
|
| 204 |
+
def enhance_image_option2(self):
|
| 205 |
+
|
| 206 |
+
client = Client("finegrain/finegrain-image-enhancer")
|
| 207 |
+
|
| 208 |
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
| 209 |
+
output_path = os.path.join(script_dir, "temp_image.png")
|
| 210 |
+
|
| 211 |
+
self.no_background_image.save(output_path)
|
| 212 |
+
|
| 213 |
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
| 214 |
+
temp_image_path = os.path.join(script_dir, "temp_image.png")
|
| 215 |
+
result = client.predict(
|
| 216 |
+
input_image=handle_file(temp_image_path),
|
| 217 |
+
prompt="",
|
| 218 |
+
negative_prompt="",
|
| 219 |
+
seed=0,
|
| 220 |
+
upscale_factor=2.6,
|
| 221 |
+
controlnet_scale=0.5,
|
| 222 |
+
controlnet_decay=0.6,
|
| 223 |
+
condition_scale=5,
|
| 224 |
+
tile_width=200,
|
| 225 |
+
tile_height=200,
|
| 226 |
+
denoise_strength=0,
|
| 227 |
+
num_inference_steps=23,
|
| 228 |
+
solver="DPMSolver",
|
| 229 |
+
api_name="/process"
|
| 230 |
+
)
|
| 231 |
+
# Get the image from result[1] - local file path, not a URL
|
| 232 |
+
image_path = result[1]
|
| 233 |
+
|
| 234 |
+
self.enhanced_image_2 = Image.open(image_path)
|
| 235 |
+
return self.enhanced_image_2
|
| 236 |
+
|
| 237 |
+
def enhance_image_option3(self):
|
| 238 |
+
enhancer = image_enhancement_option3_helper.image_enhancement_option3_helper(model=None)
|
| 239 |
+
self.enhanced_image_3 = enhancer.ai_enhanced_image_processing(self.no_background_image)
|
| 240 |
+
|
| 241 |
+
def generate_description_from_image(self, image_b64: str,
|
| 242 |
+
tone: str = "professional",
|
| 243 |
+
lang: str = "en") -> str:
|
| 244 |
+
|
| 245 |
+
API_KEY = os.getenv("SECRET_API_KEY")
|
| 246 |
+
|
| 247 |
+
genai.configure(api_key=API_KEY) # ← ONLY this line
|
| 248 |
+
|
| 249 |
+
model = genai.GenerativeModel("gemini-2.0-flash-exp") # Updated model name
|
| 250 |
+
|
| 251 |
+
prompt = (
|
| 252 |
+
f"Analyze this product image and generate an SEO-optimized e-commerce product listing in {lang}. "
|
| 253 |
+
f"Tone: {tone}. Respond ONLY with valid JSON (no markdown formatting) containing these exact keys: "
|
| 254 |
+
f"'title', 'description', 'features', 'tags'. "
|
| 255 |
+
f"The 'features' and 'tags' must be arrays of strings. "
|
| 256 |
+
f"Do not include any other text or formatting."
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
try:
|
| 260 |
+
response = model.generate_content(
|
| 261 |
+
[
|
| 262 |
+
{"inline_data": {"mime_type": "image/jpeg", "data": image_b64}},
|
| 263 |
+
prompt
|
| 264 |
+
]
|
| 265 |
+
)
|
| 266 |
+
text = response.text.strip()
|
| 267 |
+
|
| 268 |
+
# Remove markdown code blocks
|
| 269 |
+
if text.startswith("```json"):
|
| 270 |
+
text = text[7:] # Remove ```json
|
| 271 |
+
if text.startswith("```"):
|
| 272 |
+
text = text[3:] # Remove ```
|
| 273 |
+
if text.endswith("```"):
|
| 274 |
+
text = text[:-3] # Remove trailing ```
|
| 275 |
+
|
| 276 |
+
text = text.strip()
|
| 277 |
+
|
| 278 |
+
# Parsing the JSON response
|
| 279 |
+
try:
|
| 280 |
+
parsed_json = json.loads(text)
|
| 281 |
+
print("Successfully parsed JSON response")
|
| 282 |
+
return text
|
| 283 |
+
except json.JSONDecodeError:
|
| 284 |
+
return "Invalid JSON response: " + text
|
| 285 |
+
except Exception as err:
|
| 286 |
+
return "Error generating description: " + str(err)
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def choose_image(self, number: int):
|
| 290 |
+
if number == 1:
|
| 291 |
+
self.chosen_image = self.enhanced_image_1
|
| 292 |
+
elif number == 2:
|
| 293 |
+
self.chosen_image = self.enhanced_image_2
|
| 294 |
+
elif number == 3:
|
| 295 |
+
self.chosen_image = self.enhanced_image_3
|
| 296 |
+
else:
|
| 297 |
+
raise ValueError("Invalid image number. Choose 1, 2, or 3.")
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
def generate_description(self):
|
| 301 |
+
print("Starting description generation...")
|
| 302 |
+
|
| 303 |
+
if self.chosen_image is None:
|
| 304 |
+
print("Error: No image chosen for description generation")
|
| 305 |
+
self.description = "Error: No image selected for description generation"
|
| 306 |
+
return self.description
|
| 307 |
+
|
| 308 |
+
try:
|
| 309 |
+
print("Converting image to base64...")
|
| 310 |
+
from io import BytesIO
|
| 311 |
+
buffer = BytesIO()
|
| 312 |
+
|
| 313 |
+
# It handles RGBA images by converting to RGB
|
| 314 |
+
image_to_save = self.chosen_image
|
| 315 |
+
if image_to_save.mode == 'RGBA':
|
| 316 |
+
background = Image.new('RGB', image_to_save.size, (255, 255, 255))
|
| 317 |
+
background.paste(image_to_save, mask=image_to_save.split()[-1]) # Use alpha channel as mask
|
| 318 |
+
image_to_save = background
|
| 319 |
+
elif image_to_save.mode != 'RGB':
|
| 320 |
+
image_to_save = image_to_save.convert('RGB')
|
| 321 |
+
|
| 322 |
+
image_to_save.save(buffer, format='JPEG', quality=95)
|
| 323 |
+
img_b64 = base64.b64encode(buffer.getvalue()).decode()
|
| 324 |
+
print(f"Image converted to base64, size: {len(img_b64)} characters")
|
| 325 |
+
|
| 326 |
+
tone = "professional"
|
| 327 |
+
lang = "en"
|
| 328 |
+
self.description = self.generate_description_from_image(img_b64, tone, lang)
|
| 329 |
+
|
| 330 |
+
|
| 331 |
+
if len(self.description) > 15000:
|
| 332 |
+
self.description = self.description[:15000] + "..."
|
| 333 |
+
|
| 334 |
+
return self.description
|
| 335 |
+
except Exception as e:
|
| 336 |
+
print(f"Error in generate_description: {str(e)}")
|
| 337 |
+
import traceback
|
| 338 |
+
traceback.print_exc()
|
| 339 |
+
self.description = f"Error generating description: {str(e)}"
|
| 340 |
+
return self.description
|
| 341 |
+
|
| 342 |
+
def process(self, image_path):
|
| 343 |
+
if os.path.isabs(image_path):
|
| 344 |
+
# If absolute path, use it directly
|
| 345 |
+
self.image_path = image_path
|
| 346 |
+
else:
|
| 347 |
+
# If relative path, join with script directory
|
| 348 |
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
| 349 |
+
self.image_path = os.path.join(script_dir, image_path)
|
| 350 |
+
|
| 351 |
+
self.raw_image = Image.open(self.image_path).convert("RGB")
|
| 352 |
+
|
| 353 |
+
def get_enhanced_images(self):
|
| 354 |
+
return self.enhanced_image_1, self.enhanced_image_2, self.enhanced_image_3
|
| 355 |
+
|
| 356 |
+
def get_description(self):
|
| 357 |
+
return self.description
|
merged_models_and_api/rf_ai_api.py
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from process_image import process_image
|
| 2 |
+
from fastapi import FastAPI, UploadFile, File, HTTPException
|
| 3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
+
from pydantic import BaseModel
|
| 5 |
+
from typing import Optional
|
| 6 |
+
import shutil
|
| 7 |
+
import os
|
| 8 |
+
import base64
|
| 9 |
+
from io import BytesIO
|
| 10 |
+
from PIL import Image
|
| 11 |
+
import uuid
|
| 12 |
+
from search_product import search_product
|
| 13 |
+
|
| 14 |
+
app = FastAPI()
|
| 15 |
+
|
| 16 |
+
# Add CORS middleware to allow frontend to call the API
|
| 17 |
+
app.add_middleware(
|
| 18 |
+
CORSMiddleware,
|
| 19 |
+
allow_origins=["http://localhost:3000", "http://localhost:3001", "http://localhost:5173", "http://127.0.0.1:3000", "http://127.0.0.1:5173"], # React dev servers
|
| 20 |
+
allow_credentials=True,
|
| 21 |
+
allow_methods=["*"],
|
| 22 |
+
allow_headers=["*"],
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
# Store active processors
|
| 26 |
+
processors = {}
|
| 27 |
+
|
| 28 |
+
class ImageEnhancementRequest(BaseModel):
|
| 29 |
+
image_path: str
|
| 30 |
+
background: str
|
| 31 |
+
|
| 32 |
+
class ImageSelectionRequest(BaseModel):
|
| 33 |
+
image_path: str
|
| 34 |
+
option_number: int
|
| 35 |
+
|
| 36 |
+
def pil_image_to_base64(pil_image):
|
| 37 |
+
"""Convert PIL Image to base64 string for JSON serialization"""
|
| 38 |
+
if pil_image is None:
|
| 39 |
+
return None
|
| 40 |
+
if pil_image.mode != 'RGB':
|
| 41 |
+
pil_image = pil_image.convert('RGB')
|
| 42 |
+
|
| 43 |
+
buffer = BytesIO()
|
| 44 |
+
pil_image.save(buffer, format='JPEG', quality=95)
|
| 45 |
+
img_str = base64.b64encode(buffer.getvalue()).decode()
|
| 46 |
+
return f"data:image/jpeg;base64,{img_str}"
|
| 47 |
+
|
| 48 |
+
def apply_background(image: Image.Image, background: str) -> Image.Image:
|
| 49 |
+
"""Apply a given base64 background image to an RGBA image"""
|
| 50 |
+
if image.mode != 'RGBA':
|
| 51 |
+
image = image.convert("RGBA")
|
| 52 |
+
|
| 53 |
+
try:
|
| 54 |
+
# Decode the base64 background image
|
| 55 |
+
background_data = base64.b64decode(background.split(",")[1]) # Remove the "data:image/...;base64," prefix
|
| 56 |
+
background_image = Image.open(BytesIO(background_data))
|
| 57 |
+
|
| 58 |
+
# Ensure the background image matches the size of the input image
|
| 59 |
+
background_image = background_image.resize(image.size)
|
| 60 |
+
|
| 61 |
+
# Paste the input image (with transparency) on top of the background
|
| 62 |
+
combined_image = Image.new("RGB", image.size)
|
| 63 |
+
combined_image.paste(background_image, (0, 0))
|
| 64 |
+
combined_image.paste(image, (0, 0), mask=image.split()[3]) # Use alpha channel as mask
|
| 65 |
+
|
| 66 |
+
return combined_image
|
| 67 |
+
except Exception as e:
|
| 68 |
+
raise ValueError(f"Error applying background: {str(e)}")
|
| 69 |
+
|
| 70 |
+
@app.post("/upload")
|
| 71 |
+
async def upload_image(image: UploadFile = File(...)):
|
| 72 |
+
"""Upload an image file and return the file path"""
|
| 73 |
+
try:
|
| 74 |
+
# Validate file type
|
| 75 |
+
if not image.content_type.startswith('image/'):
|
| 76 |
+
raise HTTPException(status_code=400, detail="File must be an image")
|
| 77 |
+
|
| 78 |
+
# Create uploads directory if it doesn't exist
|
| 79 |
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
| 80 |
+
upload_dir = os.path.join(script_dir, "uploads")
|
| 81 |
+
|
| 82 |
+
os.makedirs(upload_dir, exist_ok=True)
|
| 83 |
+
|
| 84 |
+
# Generate unique filename
|
| 85 |
+
file_extension = os.path.splitext(image.filename)[1]
|
| 86 |
+
unique_filename = f"{uuid.uuid4()}{file_extension}"
|
| 87 |
+
file_path = os.path.join(upload_dir, unique_filename)
|
| 88 |
+
|
| 89 |
+
# Save the uploaded file
|
| 90 |
+
with open(file_path, "wb") as buffer:
|
| 91 |
+
shutil.copyfileobj(image.file, buffer)
|
| 92 |
+
|
| 93 |
+
return {"file_path": file_path, "filename": unique_filename}
|
| 94 |
+
|
| 95 |
+
except Exception as e:
|
| 96 |
+
raise HTTPException(status_code=500, detail=f"Error uploading file: {str(e)}")
|
| 97 |
+
|
| 98 |
+
@app.post("/enhance_and_return_all_options")
|
| 99 |
+
async def enhance_image(request: ImageEnhancementRequest):
|
| 100 |
+
"""Process image through all enhancement options"""
|
| 101 |
+
try:
|
| 102 |
+
print(f"Starting enhancement for image: {request.image_path}")
|
| 103 |
+
background_color = request.background
|
| 104 |
+
print(f"Using background color: {background_color}")
|
| 105 |
+
# Create a new processor instance
|
| 106 |
+
processor_id = str(uuid.uuid4())
|
| 107 |
+
img_processor = process_image()
|
| 108 |
+
|
| 109 |
+
# Check if the image path is absolute or relative
|
| 110 |
+
if os.path.isabs(request.image_path):
|
| 111 |
+
# If absolute path, convert to relative from the script directory
|
| 112 |
+
script_dir = os.path.dirname(os.path.abspath(__file__))
|
| 113 |
+
relative_path = os.path.relpath(request.image_path, script_dir)
|
| 114 |
+
image_path_to_use = relative_path
|
| 115 |
+
else:
|
| 116 |
+
# If relative path, use as is
|
| 117 |
+
image_path_to_use = request.image_path
|
| 118 |
+
|
| 119 |
+
print(f"Using image path: {image_path_to_use}")
|
| 120 |
+
|
| 121 |
+
# Check if file exists before processing
|
| 122 |
+
full_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), image_path_to_use)
|
| 123 |
+
if not os.path.exists(full_path):
|
| 124 |
+
raise HTTPException(status_code=404, detail=f"Image file not found: {full_path}")
|
| 125 |
+
|
| 126 |
+
# Process the image step by step with error handling
|
| 127 |
+
print("Step 1: Processing image...")
|
| 128 |
+
img_processor.process(image_path_to_use)
|
| 129 |
+
|
| 130 |
+
img_processor.raw_image.save("processed_image.png") # Save processed image for debugging
|
| 131 |
+
|
| 132 |
+
print("Step 2: Detecting objects...")
|
| 133 |
+
img_processor.detect_object()
|
| 134 |
+
|
| 135 |
+
img_processor.cropped_image.save("detected_objects_image.png") # Save detected objects image for debugging
|
| 136 |
+
print(img_processor.detected_objects)
|
| 137 |
+
|
| 138 |
+
print("Step 3: Removing background...")
|
| 139 |
+
img_processor.remove_background()
|
| 140 |
+
|
| 141 |
+
img_processor.no_background_image = apply_background(img_processor.no_background_image, background_color)
|
| 142 |
+
|
| 143 |
+
img_processor.no_background_image.save("no_background_image.png") # Save no background image for debugging
|
| 144 |
+
|
| 145 |
+
print("Step 4: Enhancement option 1...")
|
| 146 |
+
try:
|
| 147 |
+
img_processor.enhance_image_option1()
|
| 148 |
+
print("Enhancement option 1 completed")
|
| 149 |
+
except Exception as e:
|
| 150 |
+
print(f"Enhancement option 1 failed: {str(e)}")
|
| 151 |
+
# Set a placeholder or skip this enhancement
|
| 152 |
+
img_processor.enhanced_image_1 = img_processor.no_background_image
|
| 153 |
+
|
| 154 |
+
print("Step 5: Enhancement option 2...")
|
| 155 |
+
try:
|
| 156 |
+
img_processor.enhance_image_option2()
|
| 157 |
+
print("Enhancement option 2 completed")
|
| 158 |
+
except Exception as e:
|
| 159 |
+
print(f"Enhancement option 2 failed: {str(e)}")
|
| 160 |
+
# Set a placeholder or skip this enhancement
|
| 161 |
+
img_processor.enhanced_image_2 = img_processor.no_background_image
|
| 162 |
+
|
| 163 |
+
print("Step 6: Enhancement option 3...")
|
| 164 |
+
try:
|
| 165 |
+
img_processor.enhance_image_option3()
|
| 166 |
+
print("✓ Enhancement option 3 completed")
|
| 167 |
+
except Exception as e:
|
| 168 |
+
print(f"Enhancement option 3 failed: {str(e)}")
|
| 169 |
+
# Set a placeholder or skip this enhancement
|
| 170 |
+
img_processor.enhanced_image_3 = img_processor.no_background_image
|
| 171 |
+
|
| 172 |
+
# Store the processor for later use
|
| 173 |
+
processors[processor_id] = img_processor
|
| 174 |
+
print(f"Enhancement completed successfully. Processor ID: {processor_id}")
|
| 175 |
+
|
| 176 |
+
# Convert PIL images to base64 for JSON response
|
| 177 |
+
return {
|
| 178 |
+
"processor_id": processor_id,
|
| 179 |
+
"enhanced_image_1": pil_image_to_base64(img_processor.enhanced_image_1),
|
| 180 |
+
"enhanced_image_2": pil_image_to_base64(img_processor.enhanced_image_2),
|
| 181 |
+
"enhanced_image_3": pil_image_to_base64(img_processor.enhanced_image_3),
|
| 182 |
+
"original_image": pil_image_to_base64(img_processor.raw_image),
|
| 183 |
+
"no_background_image": pil_image_to_base64(img_processor.no_background_image)
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
except HTTPException:
|
| 187 |
+
# Re-raise HTTP exceptions
|
| 188 |
+
raise
|
| 189 |
+
except Exception as e:
|
| 190 |
+
print(f"Error during enhancement: {str(e)}")
|
| 191 |
+
import traceback
|
| 192 |
+
traceback.print_exc()
|
| 193 |
+
raise HTTPException(status_code=500, detail=f"Error enhancing image: {str(e)}")
|
| 194 |
+
|
| 195 |
+
@app.post("/choose_image_and_generate_description")
|
| 196 |
+
async def choose_image_and_generate_description(
|
| 197 |
+
processor_id: str,
|
| 198 |
+
option_number: int
|
| 199 |
+
):
|
| 200 |
+
"""Choose an enhanced image option and generate description"""
|
| 201 |
+
try:
|
| 202 |
+
# Get the processor instance
|
| 203 |
+
if processor_id not in processors:
|
| 204 |
+
raise HTTPException(status_code=404, detail="Processor not found. Please enhance image first.")
|
| 205 |
+
|
| 206 |
+
img_processor = processors[processor_id]
|
| 207 |
+
|
| 208 |
+
# Choose the image
|
| 209 |
+
img_processor.choose_image(option_number)
|
| 210 |
+
|
| 211 |
+
# Generate description
|
| 212 |
+
description = img_processor.generate_description()
|
| 213 |
+
|
| 214 |
+
return {
|
| 215 |
+
"chosen_image": pil_image_to_base64(img_processor.chosen_image),
|
| 216 |
+
"description": description,
|
| 217 |
+
"option_number": option_number
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
except Exception as e:
|
| 221 |
+
raise HTTPException(status_code=500, detail=f"Error generating description: {str(e)}")
|
| 222 |
+
|
| 223 |
+
@app.delete("/cleanup/{processor_id}")
|
| 224 |
+
async def cleanup_processor(processor_id: str):
|
| 225 |
+
"""Clean up processor instance to free memory"""
|
| 226 |
+
if processor_id in processors:
|
| 227 |
+
del processors[processor_id]
|
| 228 |
+
return {"message": "Processor cleaned up successfully"}
|
| 229 |
+
else:
|
| 230 |
+
raise HTTPException(status_code=404, detail="Processor not found")
|
| 231 |
+
|
| 232 |
+
@app.get("/health")
|
| 233 |
+
async def health_check():
|
| 234 |
+
"""Health check endpoint"""
|
| 235 |
+
return {"status": "healthy", "active_processors": len(processors)}
|
| 236 |
+
|
| 237 |
+
@app.post("/get_search_results")
|
| 238 |
+
async def get_search_results(query:str):
|
| 239 |
+
"""Get search results for a query"""
|
| 240 |
+
try:
|
| 241 |
+
print(f"Searching for: {query}")
|
| 242 |
+
searcher = search_product()
|
| 243 |
+
results = searcher.search_products_google_cse(query, 5)
|
| 244 |
+
|
| 245 |
+
print(f"Found {len(results)} results")
|
| 246 |
+
for i, result in enumerate(results):
|
| 247 |
+
print(f"Result {i+1}: {result.get('title', 'No title')}")
|
| 248 |
+
print(f"URL: {result.get('link', 'No URL')}")
|
| 249 |
+
|
| 250 |
+
return {
|
| 251 |
+
"results": results,
|
| 252 |
+
}
|
| 253 |
+
except Exception as e:
|
| 254 |
+
print(f"Error in search: {str(e)}")
|
| 255 |
+
import traceback
|
| 256 |
+
traceback.print_exc()
|
| 257 |
+
raise HTTPException(status_code=500, detail=f"Error searching: {str(e)}")
|
| 258 |
+
|
| 259 |
+
@app.post("/generate_background")
|
| 260 |
+
async def generate_background(promptFromUser: str):
|
| 261 |
+
"""Generate a background image using Google GenAI"""
|
| 262 |
+
try:
|
| 263 |
+
from background_generator import BackgroundGenerator
|
| 264 |
+
background_gen = BackgroundGenerator()
|
| 265 |
+
print("Generating background image...")
|
| 266 |
+
result = background_gen.generate(prompt=promptFromUser)
|
| 267 |
+
image_path = result.get("file_path")
|
| 268 |
+
public_url = result.get("public_url")
|
| 269 |
+
file_name = result.get("file_name")
|
| 270 |
+
print("Background image generated successfully.")
|
| 271 |
+
|
| 272 |
+
if not os.path.exists(image_path):
|
| 273 |
+
raise HTTPException(status_code=404, detail="Generated background image not found")
|
| 274 |
+
|
| 275 |
+
with Image.open(image_path) as img:
|
| 276 |
+
if img.mode != 'RGB':
|
| 277 |
+
img = img.convert('RGB')
|
| 278 |
+
|
| 279 |
+
encoded_image = pil_image_to_base64(img)
|
| 280 |
+
if encoded_image is None:
|
| 281 |
+
raise HTTPException(status_code=500, detail="Error converting image to base64")
|
| 282 |
+
return {"image": encoded_image
|
| 283 |
+
, "public_url": public_url, "file_name": file_name
|
| 284 |
+
, "file_path": image_path}
|
| 285 |
+
|
| 286 |
+
except Exception as e:
|
| 287 |
+
print(f"Error generating background: {str(e)}")
|
| 288 |
+
import traceback
|
| 289 |
+
traceback.print_exc()
|
| 290 |
+
raise HTTPException(status_code=500, detail=f"Error generating background: {str(e)}")
|
| 291 |
+
|
| 292 |
+
if __name__ == "__main__":
|
| 293 |
+
import uvicorn
|
| 294 |
+
uvicorn.run(app, host="127.0.0.1", port=8001)
|
merged_models_and_api/search_product.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import requests
|
| 2 |
+
import json
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
import os
|
| 5 |
+
import re
|
| 6 |
+
from fastapi import HTTPException
|
| 7 |
+
|
| 8 |
+
load_dotenv()
|
| 9 |
+
|
| 10 |
+
class search_product:
|
| 11 |
+
def __init__(self):
|
| 12 |
+
self.results = []
|
| 13 |
+
|
| 14 |
+
self.product_patterns = [
|
| 15 |
+
r'/product/',
|
| 16 |
+
r'/products/',
|
| 17 |
+
r'/item/',
|
| 18 |
+
r'/items/',
|
| 19 |
+
r'/p/',
|
| 20 |
+
r'/dp/', # Amazon
|
| 21 |
+
r'/pd/', # Some sites use pd
|
| 22 |
+
r'/shop/',
|
| 23 |
+
r'/buy/',
|
| 24 |
+
r'/store/',
|
| 25 |
+
r'product-',
|
| 26 |
+
r'item-',
|
| 27 |
+
r'/goods/',
|
| 28 |
+
r'/merchandise/',
|
| 29 |
+
r'/catalog/',
|
| 30 |
+
r'/detail/',
|
| 31 |
+
r'/details/',
|
| 32 |
+
r'/product-detail/',
|
| 33 |
+
r'productId=',
|
| 34 |
+
r'itemId=',
|
| 35 |
+
r'sku=',
|
| 36 |
+
r'/sku/',
|
| 37 |
+
]
|
| 38 |
+
|
| 39 |
+
def is_product_url(self, url):
|
| 40 |
+
"""Check if URL looks like a product page"""
|
| 41 |
+
url_lower = url.lower()
|
| 42 |
+
|
| 43 |
+
# Check for product patterns in URL
|
| 44 |
+
for pattern in self.product_patterns:
|
| 45 |
+
if re.search(pattern, url_lower):
|
| 46 |
+
return True
|
| 47 |
+
|
| 48 |
+
return False
|
| 49 |
+
|
| 50 |
+
def search_products_google_cse(self, query, num_results=5):
|
| 51 |
+
"""
|
| 52 |
+
Search using Google Custom Search Engine API
|
| 53 |
+
"""
|
| 54 |
+
url = "https://www.googleapis.com/customsearch/v1"
|
| 55 |
+
|
| 56 |
+
params = {
|
| 57 |
+
'key': os.getenv("SEARCH_API_KEY"),
|
| 58 |
+
'cx': "068bf089d39b74b14",
|
| 59 |
+
'q': query,
|
| 60 |
+
'num': min(num_results, 10),
|
| 61 |
+
'safe': 'active'
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
try:
|
| 65 |
+
response = requests.get(url, params=params)
|
| 66 |
+
response.raise_for_status()
|
| 67 |
+
|
| 68 |
+
data = response.json()
|
| 69 |
+
results = [] # Fresh results for this search
|
| 70 |
+
first_result = None
|
| 71 |
+
if 'items' in data:
|
| 72 |
+
for i, item in enumerate(data['items']):
|
| 73 |
+
link = item.get('link', '')
|
| 74 |
+
if(i == 0):
|
| 75 |
+
first_result = {
|
| 76 |
+
'title': item.get('title', ''),
|
| 77 |
+
'link': link,
|
| 78 |
+
'url': link,
|
| 79 |
+
'snippet': item.get('snippet', ''),
|
| 80 |
+
'displayLink': item.get('displayLink', ''),
|
| 81 |
+
'is_product': True
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
# Filter for product URLs
|
| 85 |
+
if self.is_product_url(link):
|
| 86 |
+
result = {
|
| 87 |
+
'title': item.get('title', ''),
|
| 88 |
+
'link': link,
|
| 89 |
+
'url': link,
|
| 90 |
+
'snippet': item.get('snippet', ''),
|
| 91 |
+
'displayLink': item.get('displayLink', ''),
|
| 92 |
+
'is_product': True
|
| 93 |
+
}
|
| 94 |
+
results.append(result)
|
| 95 |
+
break
|
| 96 |
+
|
| 97 |
+
# Update instance results and return current results
|
| 98 |
+
self.results = results
|
| 99 |
+
if not self.results:
|
| 100 |
+
results.append(first_result)
|
| 101 |
+
return results
|
| 102 |
+
|
| 103 |
+
except requests.exceptions.RequestException as e:
|
| 104 |
+
print(f"Error making request: {e}")
|
| 105 |
+
return []
|
merged_models_and_api/temp_image.png
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/1ad196f4-9c68-4e25-a11f-f78d0067591b.jpg
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/1c837ff1-19ea-47a7-ad48-7a2b78feddac.jpg
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/34d20ae0-092f-4fc1-a957-38296d6d4f07.png
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/35a753c4-8b4e-4784-91d4-935af8d968ec.jpg
ADDED
|
merged_models_and_api/uploads/3e585a79-d927-4ba0-bfe8-294e2a6228d8.jpg
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/4a23733a-104d-4804-90b8-694a3186edcf.jpg
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/73b35923-3a5a-4a0b-9ba7-6ad3b544362f.jpg
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/78c1bafc-55ab-4ff9-894f-4cb1b35ca1bb.jpeg
ADDED
|
merged_models_and_api/uploads/8b2085b4-c44f-4925-a1f4-182eac070bbf.jpg
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/9d014739-7e54-4886-94e9-a52f4ca7bdf7.jpg
ADDED
|
merged_models_and_api/uploads/a3d25bdd-ca68-4167-9c74-a50e3a43e448.jpg
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/a770eb04-2469-4102-af71-53afc5ea08ef.jpg
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/ae4878b8-ccdf-4363-a53e-0172f5a5c6cb.jpg
ADDED
|
merged_models_and_api/uploads/be1f9c7f-e3ba-4890-9034-337219e69180.jpg
ADDED
|
merged_models_and_api/uploads/bfcedb9d-acab-420d-95ba-aa239c121810.png
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/cce44ef5-15af-4532-8197-db4cb51ec4d9.jpg
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/cfafcaad-95d0-47d9-aac1-098a200323c0.jpg
ADDED
|
merged_models_and_api/uploads/dc87fea8-2351-4837-9d21-46d47935b818.jpg
ADDED
|
merged_models_and_api/uploads/e177e7dc-50da-4ef8-8746-d428193f98e5.jpg
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/f05a4dae-d69e-48a0-a6e5-28508c2d1147.png
ADDED
|
Git LFS Details
|
merged_models_and_api/uploads/f2046fa5-79e5-4c20-91f6-f0f70fab2232.jpg
ADDED
|
Git LFS Details
|