Spaces:
Sleeping
Sleeping
root-sajjan
commited on
new space
Browse files- cropped_images/crop_0.jpg +0 -0
- cropped_images/crop_1.jpg +0 -0
- cropped_images/crop_2.jpg +0 -0
- llm/fridge.JPG +0 -0
- llm/image_description.py +39 -0
- llm/inference.py +119 -0
- llm/upload_image.py +39 -0
- main.py +333 -0
- main2.py +188 -0
- model.py +149 -0
- predictions_with_images.xlsx +0 -0
- users.db +0 -0
cropped_images/crop_0.jpg
ADDED
cropped_images/crop_1.jpg
ADDED
cropped_images/crop_2.jpg
ADDED
llm/fridge.JPG
ADDED
llm/image_description.py
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
2 |
+
import torch
|
3 |
+
from PIL import Image
|
4 |
+
|
5 |
+
model = AutoModelForCausalLM.from_pretrained(
|
6 |
+
"openbmb/MiniCPM-Llama3-V-2_5",
|
7 |
+
trust_remote_code=True
|
8 |
+
)
|
9 |
+
|
10 |
+
print(model)
|
11 |
+
|
12 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
13 |
+
"openbmb/MiniCPM-Llama3-V-2_5",
|
14 |
+
trust_remote_code=True
|
15 |
+
)
|
16 |
+
|
17 |
+
image = Image.open("fridge.JPG")
|
18 |
+
prompt = "What is the main object shown in the image? Describe in less than 5 words, as a name for it."
|
19 |
+
|
20 |
+
# First round chat
|
21 |
+
# question = "Tell me the model of this aircraft."
|
22 |
+
msgs = [{'role': 'user', 'content': [image, prompt]}]
|
23 |
+
|
24 |
+
answer = model.chat(
|
25 |
+
image=None,
|
26 |
+
msgs=msgs,
|
27 |
+
tokenizer=tokenizer
|
28 |
+
)
|
29 |
+
print(answer)
|
30 |
+
|
31 |
+
|
32 |
+
# chat = model.chat(
|
33 |
+
# image=image,
|
34 |
+
# question=prompt,
|
35 |
+
# tokenizer=tokenizer,
|
36 |
+
# generate_args={"temperature": 0.8}
|
37 |
+
# )
|
38 |
+
|
39 |
+
# print(chat)
|
llm/inference.py
ADDED
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from huggingface_hub import InferenceClient
|
2 |
+
import nltk
|
3 |
+
import re
|
4 |
+
import requests
|
5 |
+
import os
|
6 |
+
|
7 |
+
api_key = os.getenv("HF_KEY")
|
8 |
+
|
9 |
+
nltk.download('punkt_tab')
|
10 |
+
nltk.download('averaged_perceptron_tagger')
|
11 |
+
|
12 |
+
|
13 |
+
client = InferenceClient(api_key=api_key)
|
14 |
+
|
15 |
+
|
16 |
+
def extract_product_info(text):
|
17 |
+
# Initialize result dictionary
|
18 |
+
result = {"brand": None, "model": None, "description": None, "price": None}
|
19 |
+
|
20 |
+
# Improved regex to prioritize currency-related patterns
|
21 |
+
price_match = re.search(
|
22 |
+
r'(\$\s?\d{1,3}(?:,\d{3})*(?:\.\d{2})?|(?:\d{1,3}(?:,\d{3})*(?:\.\d{2})?\s?(?:USD|usd|dollars|DOLLARS)))',
|
23 |
+
text
|
24 |
+
)
|
25 |
+
if price_match:
|
26 |
+
price = price_match.group().strip()
|
27 |
+
# Clean up the price format
|
28 |
+
if "$" in price or "USD" in price or "usd" in price:
|
29 |
+
result["price"] = re.sub(r'[^\d.]', '', price) # Keep only digits and decimals
|
30 |
+
else:
|
31 |
+
result["price"] = price
|
32 |
+
# Remove the price part from the text to prevent it from being included in the brand/model extraction
|
33 |
+
text = text.replace(price_match.group(), "").strip()
|
34 |
+
|
35 |
+
# Tokenize the remaining text and tag parts of speech
|
36 |
+
tokens = nltk.word_tokenize(text)
|
37 |
+
pos_tags = nltk.pos_tag(tokens)
|
38 |
+
|
39 |
+
# Extract brand and model (Proper Nouns + Alphanumeric patterns)
|
40 |
+
brand_parts = []
|
41 |
+
model_parts = []
|
42 |
+
description_parts = []
|
43 |
+
|
44 |
+
for word, tag in pos_tags:
|
45 |
+
if tag == 'NNP' or re.match(r'[A-Za-z0-9-]+', word):
|
46 |
+
if len(brand_parts) == 0: # Assume the first proper noun is the brand
|
47 |
+
brand_parts.append(word)
|
48 |
+
else: # Model number tends to follow the brand
|
49 |
+
model_parts.append(word)
|
50 |
+
else:
|
51 |
+
description_parts.append(word)
|
52 |
+
|
53 |
+
# Assign brand and model to result dictionary
|
54 |
+
if brand_parts:
|
55 |
+
result["brand"] = " ".join(brand_parts)
|
56 |
+
if model_parts:
|
57 |
+
result["model"] = " ".join(model_parts)
|
58 |
+
|
59 |
+
# Combine the remaining parts as description
|
60 |
+
result["description"] = " ".join(description_parts)
|
61 |
+
|
62 |
+
return result
|
63 |
+
|
64 |
+
|
65 |
+
|
66 |
+
def extract_info(text):
|
67 |
+
API_URL = "https://api-inference.huggingface.co/models/google/flan-t5-large"
|
68 |
+
headers = {"Authorization": f"Bearer {api_key}"}
|
69 |
+
payload = {"inputs": f"From the given text, extract brand name, model number, description about it, and its average price in today's market. Give me back a python dictionary with keys as brand_name, model_number, desc, price. The text is {text}.",}
|
70 |
+
response = requests.post(API_URL, headers=headers, json=payload)
|
71 |
+
print('GOOGLEE LLM OUTPUTTTTTTT\n\n',response )
|
72 |
+
output = response.json()
|
73 |
+
print(output)
|
74 |
+
|
75 |
+
|
76 |
+
|
77 |
+
def get_name(url, object):
|
78 |
+
messages = [
|
79 |
+
{
|
80 |
+
"role": "user",
|
81 |
+
"content": [
|
82 |
+
{
|
83 |
+
"type": "text",
|
84 |
+
"text": f"Is this a {object}?. Can you guess what it is and give me the closest brand it resembles to? or a model number? And give me its average price in today's market in USD. In output, give me its normal name, model name, model number and price. separated by commas. No description is needed."
|
85 |
+
},
|
86 |
+
{
|
87 |
+
"type": "image_url",
|
88 |
+
"image_url": {
|
89 |
+
"url": url
|
90 |
+
}
|
91 |
+
}
|
92 |
+
]
|
93 |
+
}
|
94 |
+
]
|
95 |
+
|
96 |
+
completion = client.chat.completions.create(
|
97 |
+
model="meta-llama/Llama-3.2-11B-Vision-Instruct",
|
98 |
+
messages=messages,
|
99 |
+
max_tokens=500
|
100 |
+
)
|
101 |
+
|
102 |
+
|
103 |
+
print(f'\n\nNow output of LLM:\n')
|
104 |
+
llm_result = completion.choices[0].message['content']
|
105 |
+
print(llm_result)
|
106 |
+
print(f'\n\nThat is the output')
|
107 |
+
|
108 |
+
result = extract_product_info(llm_result)
|
109 |
+
print(f'\n\nResult brand and price:{result}')
|
110 |
+
|
111 |
+
# result2 = extract_info(llm_result)
|
112 |
+
# print(f'\n\nFrom Google llm:{result2}')
|
113 |
+
|
114 |
+
return result
|
115 |
+
|
116 |
+
# url = "https://i.ibb.co/mNYvqDL/crop_39.jpg"
|
117 |
+
# object="fridge"
|
118 |
+
|
119 |
+
# get_name(url, object)
|
llm/upload_image.py
ADDED
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
|
3 |
+
def upload_image_to_imgbb(image_path, api_key="0e7fb6d183b8db925812dee230f71079"):
|
4 |
+
"""
|
5 |
+
Uploads an image to ImgBB and returns the URL.
|
6 |
+
|
7 |
+
:param image_path: Path to the local image
|
8 |
+
:param api_key: ImgBB API key
|
9 |
+
:return: URL of the uploaded image
|
10 |
+
"""
|
11 |
+
try:
|
12 |
+
# API endpoint for ImgBB
|
13 |
+
url = "https://api.imgbb.com/1/upload"
|
14 |
+
|
15 |
+
# Open the image in binary mode
|
16 |
+
with open(image_path, "rb") as image_file:
|
17 |
+
# Send POST request to upload the image
|
18 |
+
response = requests.post(
|
19 |
+
url,
|
20 |
+
data={"key": api_key},
|
21 |
+
files={"image": image_file}
|
22 |
+
)
|
23 |
+
|
24 |
+
# Check if the request was successful
|
25 |
+
if response.status_code == 200:
|
26 |
+
data = response.json()
|
27 |
+
print(f'Uploaded to {data["data"]["url"]}')
|
28 |
+
return data["data"]["url"]
|
29 |
+
else:
|
30 |
+
raise Exception(f"Error uploading image: {response.status_code}, {response.text}")
|
31 |
+
except Exception as e:
|
32 |
+
return str(e)
|
33 |
+
|
34 |
+
# # Replace with your local image path and ImgBB API key
|
35 |
+
# image_path = "fridge.JPG" # Replace this with your local image path
|
36 |
+
# api_key = "0e7fb6d183b8db925812dee230f71079" # Get your API key from https://api.imgbb.com/
|
37 |
+
|
38 |
+
# uploaded_url = upload_image_to_imgbb(image_path, api_key)
|
39 |
+
# print(f"Uploaded image URL: {uploaded_url}")
|
main.py
ADDED
@@ -0,0 +1,333 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI, File, UploadFile, Response, HTTPException
|
2 |
+
from fastapi.responses import JSONResponse, FileResponse
|
3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
4 |
+
from PIL import Image
|
5 |
+
import io
|
6 |
+
import requests
|
7 |
+
import sqlite3
|
8 |
+
from pydantic import BaseModel, EmailStr
|
9 |
+
from typing import List, Optional
|
10 |
+
|
11 |
+
|
12 |
+
from pathlib import Path
|
13 |
+
from model import YOLOModel
|
14 |
+
import shutil
|
15 |
+
|
16 |
+
from openpyxl import Workbook
|
17 |
+
from openpyxl.drawing.image import Image as ExcelImage
|
18 |
+
from openpyxl.styles import Alignment
|
19 |
+
import os
|
20 |
+
|
21 |
+
yolo = YOLOModel()
|
22 |
+
|
23 |
+
UPLOAD_FOLDER = Path("./uploads")
|
24 |
+
UPLOAD_FOLDER.mkdir(exist_ok=True)
|
25 |
+
|
26 |
+
app = FastAPI()
|
27 |
+
|
28 |
+
cropped_images_dir = "cropped_images"
|
29 |
+
|
30 |
+
# Initialize SQLite database
|
31 |
+
def init_db():
|
32 |
+
conn = sqlite3.connect('users.db')
|
33 |
+
c = conn.cursor()
|
34 |
+
c.execute('''
|
35 |
+
CREATE TABLE IF NOT EXISTS users (
|
36 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
37 |
+
firstName TEXT NOT NULL,
|
38 |
+
lastName TEXT NOT NULL,
|
39 |
+
country TEXT,
|
40 |
+
number TEXT, -- Phone number stored as TEXT to allow various formats
|
41 |
+
email TEXT UNIQUE NOT NULL, -- Email should be unique and non-null
|
42 |
+
password TEXT NOT NULL -- Password will be stored as a string (hashed ideally)
|
43 |
+
)
|
44 |
+
''')
|
45 |
+
conn.commit()
|
46 |
+
conn.close()
|
47 |
+
|
48 |
+
init_db()
|
49 |
+
|
50 |
+
class UserSignup(BaseModel):
|
51 |
+
firstName: str
|
52 |
+
lastName: str
|
53 |
+
country: str
|
54 |
+
number: str
|
55 |
+
email: EmailStr
|
56 |
+
password: str
|
57 |
+
|
58 |
+
class UserLogin(BaseModel):
|
59 |
+
email: str
|
60 |
+
password: str
|
61 |
+
|
62 |
+
@app.post("/signup")
|
63 |
+
async def signup(user_data: UserSignup):
|
64 |
+
try:
|
65 |
+
conn = sqlite3.connect('users.db')
|
66 |
+
c = conn.cursor()
|
67 |
+
|
68 |
+
# Check if user already exists
|
69 |
+
c.execute("SELECT * FROM users WHERE email = ?", (user_data.email,))
|
70 |
+
if c.fetchone():
|
71 |
+
raise HTTPException(status_code=400, detail="Email already registered")
|
72 |
+
|
73 |
+
# Insert new user
|
74 |
+
c.execute("""
|
75 |
+
INSERT INTO users (firstName, lastName, country, number, email, password)
|
76 |
+
VALUES (?, ?, ?, ?, ?, ?)
|
77 |
+
""", (user_data.firstName, user_data.lastName, user_data.country, user_data.number, user_data.email, user_data.password))
|
78 |
+
|
79 |
+
conn.commit()
|
80 |
+
conn.close()
|
81 |
+
|
82 |
+
return {"message": "User registered successfully", "email": user_data.email}
|
83 |
+
except Exception as e:
|
84 |
+
raise HTTPException(status_code=500, detail=str(e))
|
85 |
+
|
86 |
+
@app.post("/login")
|
87 |
+
async def login(user_data: UserLogin):
|
88 |
+
try:
|
89 |
+
conn = sqlite3.connect('users.db')
|
90 |
+
c = conn.cursor()
|
91 |
+
|
92 |
+
# Find user
|
93 |
+
c.execute("SELECT * FROM users WHERE email = ? AND password = ?",
|
94 |
+
(user_data.email, user_data.password))
|
95 |
+
user = c.fetchone()
|
96 |
+
|
97 |
+
conn.close()
|
98 |
+
|
99 |
+
if not user:
|
100 |
+
raise HTTPException(status_code=401, detail="Invalid credentials")
|
101 |
+
|
102 |
+
return {
|
103 |
+
"message": "Login successful",
|
104 |
+
"user": {
|
105 |
+
"firstName": user[1],
|
106 |
+
"lastName": user[2],
|
107 |
+
"email": user[3]
|
108 |
+
}
|
109 |
+
}
|
110 |
+
except Exception as e:
|
111 |
+
raise HTTPException(status_code=500, detail=str(e))
|
112 |
+
|
113 |
+
|
114 |
+
|
115 |
+
@app.post("/upload")
|
116 |
+
async def upload_image(image: UploadFile = File(...)):
|
117 |
+
# print(f'\n\t\tUPLOADED!!!!')
|
118 |
+
try:
|
119 |
+
file_path = UPLOAD_FOLDER / image.filename
|
120 |
+
with file_path.open("wb") as buffer:
|
121 |
+
shutil.copyfileobj(image.file, buffer)
|
122 |
+
# print(f'Starting to pass into model, {file_path}')
|
123 |
+
# Perform YOLO inference
|
124 |
+
predictions = yolo.predict(str(file_path))
|
125 |
+
print(f'\n\n\n{predictions}\n\n\ \n\t\t\t\tare predictions')
|
126 |
+
# Clean up uploaded file
|
127 |
+
file_path.unlink() # Remove file after processing
|
128 |
+
return JSONResponse(content={"items": predictions})
|
129 |
+
|
130 |
+
|
131 |
+
except Exception as e:
|
132 |
+
return JSONResponse(content={"error": str(e)}, status_code=500)
|
133 |
+
|
134 |
+
|
135 |
+
@app.get("/download_cropped_image/{image_idx}")
|
136 |
+
def download_cropped_image(image_idx: int):
|
137 |
+
cropped_image_path = cropped_images_dir / f"crop_{image_idx}.jpg"
|
138 |
+
if cropped_image_path.exists():
|
139 |
+
return FileResponse(cropped_image_path, media_type="image/jpeg")
|
140 |
+
return JSONResponse(content={"error": "Cropped image not found"}, status_code=404)
|
141 |
+
|
142 |
+
|
143 |
+
def cleanup_images(directory: str):
|
144 |
+
"""Remove all images in the directory."""
|
145 |
+
for file in Path(directory).glob("*"):
|
146 |
+
file.unlink()
|
147 |
+
'''
|
148 |
+
|
149 |
+
@app.post("/generate-excel/")
|
150 |
+
async def generate_excel(predictions: list):
|
151 |
+
# Create an Excel workbook
|
152 |
+
workbook = Workbook()
|
153 |
+
sheet = workbook.active
|
154 |
+
sheet.title = "Predictions"
|
155 |
+
|
156 |
+
# Add headers
|
157 |
+
headers = ["Category", "Confidence", "Predicted Brand", "Price", "Details", "Detected Text", "Image"]
|
158 |
+
sheet.append(headers)
|
159 |
+
|
160 |
+
for idx, prediction in enumerate(predictions):
|
161 |
+
# Extract details from the prediction
|
162 |
+
category = prediction["category"]
|
163 |
+
confidence = prediction["confidence"]
|
164 |
+
predicted_brand = prediction["predicted_brand"]
|
165 |
+
price = prediction["price"]
|
166 |
+
details = prediction["details"]
|
167 |
+
detected_text = prediction["detected_text"]
|
168 |
+
cropped_image_path = prediction["image_path"]
|
169 |
+
|
170 |
+
# Append data row
|
171 |
+
sheet.append([category, confidence, predicted_brand, price, details, detected_text])
|
172 |
+
|
173 |
+
# Add the image to the Excel file (if it exists)
|
174 |
+
if os.path.exists(cropped_image_path):
|
175 |
+
img = ExcelImage(cropped_image_path)
|
176 |
+
img.width, img.height = 50, 50 # Resize image to fit into the cell
|
177 |
+
sheet.add_image(img, f"G{idx + 2}") # Place in the "Image" column
|
178 |
+
|
179 |
+
excel_file_path = "predictions_with_images.xlsx"
|
180 |
+
workbook.save(excel_file_path)
|
181 |
+
|
182 |
+
# Cleanup after saving
|
183 |
+
cleanup_images(cropped_images_dir)
|
184 |
+
|
185 |
+
# Serve the Excel file as a response
|
186 |
+
return FileResponse(
|
187 |
+
excel_file_path,
|
188 |
+
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
189 |
+
filename="predictions_with_images.xlsx"
|
190 |
+
)
|
191 |
+
|
192 |
+
'''
|
193 |
+
|
194 |
+
# Define the Prediction model
|
195 |
+
class Prediction(BaseModel):
|
196 |
+
category: Optional[str]
|
197 |
+
confidence: Optional[float]
|
198 |
+
predicted_brand: Optional[str]
|
199 |
+
price: Optional[str]
|
200 |
+
details: Optional[str]
|
201 |
+
detected_text: Optional[str]
|
202 |
+
image_url: Optional[str]
|
203 |
+
image_path: Optional[str]
|
204 |
+
|
205 |
+
|
206 |
+
@app.post("/generate-excel/")
|
207 |
+
async def generate_excel(predictions: List[Prediction]):
|
208 |
+
print('Generate excel called')
|
209 |
+
|
210 |
+
# Create an Excel workbook
|
211 |
+
workbook = Workbook()
|
212 |
+
sheet = workbook.active
|
213 |
+
sheet.title = "Predictions"
|
214 |
+
|
215 |
+
# Add headers
|
216 |
+
headers = ["Category", "Confidence", "Predicted Brand", "Price", "Image URL", "Details", "Detected Text", ]
|
217 |
+
sheet.append(headers)
|
218 |
+
|
219 |
+
# Set header style and alignment
|
220 |
+
for cell in sheet[1]:
|
221 |
+
cell.alignment = Alignment(horizontal="center", vertical="center", wrap_text=True)
|
222 |
+
sheet.row_dimensions[1].height = 30 # Adjust header row height
|
223 |
+
|
224 |
+
# Set column widths based on data type
|
225 |
+
column_widths = {
|
226 |
+
"A": 20, # Category
|
227 |
+
"B": 15, # Confidence
|
228 |
+
"C": 40, # Predicted Brand
|
229 |
+
"D": 15, # Price
|
230 |
+
"E": 50, # Image URL
|
231 |
+
"F": 30, # Details
|
232 |
+
"G": 30 # Detected Text
|
233 |
+
}
|
234 |
+
for col, width in column_widths.items():
|
235 |
+
sheet.column_dimensions[col].width = width
|
236 |
+
|
237 |
+
# Add prediction rows
|
238 |
+
for idx, prediction in enumerate(predictions):
|
239 |
+
row_index = idx + 2 # Start from the second row
|
240 |
+
|
241 |
+
# Add data to the row
|
242 |
+
sheet.append([
|
243 |
+
prediction.category,
|
244 |
+
prediction.confidence,
|
245 |
+
prediction.predicted_brand,
|
246 |
+
prediction.price,
|
247 |
+
prediction.image_url,
|
248 |
+
prediction.details,
|
249 |
+
prediction.detected_text,
|
250 |
+
|
251 |
+
])
|
252 |
+
|
253 |
+
# Adjust row height for multiline text
|
254 |
+
sheet.row_dimensions[row_index].height = 180 # Default height for rows
|
255 |
+
|
256 |
+
# Wrap text in all cells of the row
|
257 |
+
for col_idx in range(1, 8): # Columns A to G
|
258 |
+
cell = sheet.cell(row=row_index, column=col_idx)
|
259 |
+
cell.alignment = Alignment(wrap_text=True, vertical="top")
|
260 |
+
|
261 |
+
# Add image if the path exists
|
262 |
+
if os.path.exists(prediction.image_path):
|
263 |
+
img = ExcelImage(prediction.image_path)
|
264 |
+
img.width, img.height = 160, 160 # Resize image to fit into the cell
|
265 |
+
img_cell = f"G{row_index}" # Image column
|
266 |
+
sheet.add_image(img, img_cell)
|
267 |
+
|
268 |
+
# Save the Excel file
|
269 |
+
excel_file_path = "predictions_with_images.xlsx"
|
270 |
+
workbook.save(excel_file_path)
|
271 |
+
|
272 |
+
# Serve the Excel file as a response
|
273 |
+
return FileResponse(
|
274 |
+
excel_file_path,
|
275 |
+
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
276 |
+
filename="predictions_with_images.xlsx"
|
277 |
+
)
|
278 |
+
'''
|
279 |
+
|
280 |
+
@app.post("/generate-excel/")
|
281 |
+
async def generate_excel(predictions: list):
|
282 |
+
print('Generate excel called')
|
283 |
+
# Create an Excel workbook
|
284 |
+
workbook = Workbook()
|
285 |
+
sheet = workbook.active
|
286 |
+
sheet.title = "Predictions"
|
287 |
+
|
288 |
+
# Add headers
|
289 |
+
headers = ["Category", "Confidence", "Predicted Brand", "Price", "Details", "Detected Text", "Image URL"]
|
290 |
+
sheet.append(headers)
|
291 |
+
|
292 |
+
# Format the header row
|
293 |
+
for cell in sheet[1]:
|
294 |
+
cell.alignment = Alignment(horizontal="center", vertical="center")
|
295 |
+
|
296 |
+
for idx, prediction in enumerate(predictions):
|
297 |
+
# Extract details from the prediction
|
298 |
+
category = prediction["category"]
|
299 |
+
confidence = prediction["confidence"]
|
300 |
+
predicted_brand = prediction["predicted_brand"]
|
301 |
+
price = prediction["price"]
|
302 |
+
details = prediction["details"]
|
303 |
+
detected_text = prediction["detected_text"]
|
304 |
+
image_url = prediction["image_url"] # URL to the image
|
305 |
+
cropped_image_path = prediction["image_path"] # Path to local image file for Excel embedding
|
306 |
+
|
307 |
+
# Append data row
|
308 |
+
sheet.append([category, confidence, predicted_brand, price, details, detected_text, image_url])
|
309 |
+
|
310 |
+
# If the image path exists, add the image to the Excel file
|
311 |
+
if os.path.exists(cropped_image_path):
|
312 |
+
img = ExcelImage(cropped_image_path)
|
313 |
+
img.width, img.height = 50, 50 # Resize image to fit into the cell
|
314 |
+
sheet.add_image(img, f"G{idx + 2}") # Place in the "Image" column
|
315 |
+
|
316 |
+
excel_file_path = "predictions_with_images.xlsx"
|
317 |
+
workbook.save(excel_file_path)
|
318 |
+
|
319 |
+
# Serve the Excel file as a response
|
320 |
+
return FileResponse(
|
321 |
+
excel_file_path,
|
322 |
+
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
323 |
+
filename="predictions_with_images.xlsx"
|
324 |
+
)
|
325 |
+
'''
|
326 |
+
|
327 |
+
# code to accept the localhost to get images from
|
328 |
+
app.add_middleware(
|
329 |
+
CORSMiddleware,
|
330 |
+
allow_origins=["http://192.168.56.1:3000", "http://192.168.56.1:3001", "http://localhost:3000"],
|
331 |
+
allow_methods=["*"],
|
332 |
+
allow_headers=["*"],
|
333 |
+
)
|
main2.py
ADDED
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI, File, UploadFile, Response, HTTPException
|
2 |
+
from fastapi.responses import JSONResponse, FileResponse
|
3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
4 |
+
from PIL import Image
|
5 |
+
import io
|
6 |
+
|
7 |
+
import sqlite3
|
8 |
+
from pydantic import BaseModel, EmailStr
|
9 |
+
|
10 |
+
from pathlib import Path
|
11 |
+
from model import YOLOModel
|
12 |
+
import shutil
|
13 |
+
|
14 |
+
from openpyxl import Workbook
|
15 |
+
from openpyxl.drawing.image import Image as ExcelImage
|
16 |
+
import os
|
17 |
+
|
18 |
+
yolo = YOLOModel()
|
19 |
+
|
20 |
+
UPLOAD_FOLDER = Path("./uploads")
|
21 |
+
UPLOAD_FOLDER.mkdir(exist_ok=True)
|
22 |
+
|
23 |
+
app = FastAPI()
|
24 |
+
|
25 |
+
cropped_images_dir = "cropped_images"
|
26 |
+
|
27 |
+
# Initialize SQLite database
|
28 |
+
def init_db():
|
29 |
+
conn = sqlite3.connect('users.db')
|
30 |
+
c = conn.cursor()
|
31 |
+
c.execute('''
|
32 |
+
CREATE TABLE IF NOT EXISTS users (
|
33 |
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
34 |
+
firstName TEXT NOT NULL,
|
35 |
+
lastName TEXT NOT NULL,
|
36 |
+
country TEXT,
|
37 |
+
number TEXT, -- Phone number stored as TEXT to allow various formats
|
38 |
+
email TEXT UNIQUE NOT NULL, -- Email should be unique and non-null
|
39 |
+
password TEXT NOT NULL -- Password will be stored as a string (hashed ideally)
|
40 |
+
)
|
41 |
+
''')
|
42 |
+
conn.commit()
|
43 |
+
conn.close()
|
44 |
+
|
45 |
+
init_db()
|
46 |
+
|
47 |
+
class UserSignup(BaseModel):
|
48 |
+
firstName: str
|
49 |
+
lastName: str
|
50 |
+
country: str
|
51 |
+
number: str
|
52 |
+
email: EmailStr
|
53 |
+
password: str
|
54 |
+
|
55 |
+
class UserLogin(BaseModel):
|
56 |
+
email: str
|
57 |
+
password: str
|
58 |
+
|
59 |
+
@app.post("/signup")
|
60 |
+
async def signup(user_data: UserSignup):
|
61 |
+
try:
|
62 |
+
conn = sqlite3.connect('users.db')
|
63 |
+
c = conn.cursor()
|
64 |
+
|
65 |
+
# Check if user already exists
|
66 |
+
c.execute("SELECT * FROM users WHERE email = ?", (user_data.email,))
|
67 |
+
if c.fetchone():
|
68 |
+
raise HTTPException(status_code=400, detail="Email already registered")
|
69 |
+
|
70 |
+
# Insert new user
|
71 |
+
c.execute("""
|
72 |
+
INSERT INTO users (firstName, lastName, country, number, email, password)
|
73 |
+
VALUES (?, ?, ?, ?, ?, ?)
|
74 |
+
""", (user_data.firstName, user_data.lastName, user_data.country, user_data.number, user_data.email, user_data.password))
|
75 |
+
|
76 |
+
conn.commit()
|
77 |
+
conn.close()
|
78 |
+
|
79 |
+
return {"message": "User registered successfully", "email": user_data.email}
|
80 |
+
except Exception as e:
|
81 |
+
raise HTTPException(status_code=500, detail=str(e))
|
82 |
+
|
83 |
+
@app.post("/login")
|
84 |
+
async def login(user_data: UserLogin):
|
85 |
+
try:
|
86 |
+
conn = sqlite3.connect('users.db')
|
87 |
+
c = conn.cursor()
|
88 |
+
|
89 |
+
# Find user
|
90 |
+
c.execute("SELECT * FROM users WHERE email = ? AND password = ?",
|
91 |
+
(user_data.email, user_data.password))
|
92 |
+
user = c.fetchone()
|
93 |
+
|
94 |
+
conn.close()
|
95 |
+
|
96 |
+
if not user:
|
97 |
+
raise HTTPException(status_code=401, detail="Invalid credentials")
|
98 |
+
|
99 |
+
return {
|
100 |
+
"message": "Login successful",
|
101 |
+
"user": {
|
102 |
+
"firstName": user[1],
|
103 |
+
"lastName": user[2],
|
104 |
+
"email": user[3]
|
105 |
+
}
|
106 |
+
}
|
107 |
+
except Exception as e:
|
108 |
+
raise HTTPException(status_code=500, detail=str(e))
|
109 |
+
|
110 |
+
|
111 |
+
|
112 |
+
@app.post("/upload")
|
113 |
+
async def upload_image(image: UploadFile = File(...)):
|
114 |
+
# print(f'\n\t\tUPLOADED!!!!')
|
115 |
+
try:
|
116 |
+
file_path = UPLOAD_FOLDER / image.filename
|
117 |
+
with file_path.open("wb") as buffer:
|
118 |
+
shutil.copyfileobj(image.file, buffer)
|
119 |
+
# print(f'Starting to pass into model, {file_path}')
|
120 |
+
# Perform YOLO inference
|
121 |
+
predictions = yolo.predict(str(file_path))
|
122 |
+
print(f'\n\n\n{predictions}\n\n\ \n\t\t\t\tare predictions')
|
123 |
+
# Clean up uploaded file
|
124 |
+
file_path.unlink() # Remove file after processing
|
125 |
+
return JSONResponse(content={"items": predictions})
|
126 |
+
|
127 |
+
|
128 |
+
except Exception as e:
|
129 |
+
return JSONResponse(content={"error": str(e)}, status_code=500)
|
130 |
+
|
131 |
+
|
132 |
+
def cleanup_images(directory: str):
|
133 |
+
"""Remove all images in the directory."""
|
134 |
+
for file in Path(directory).glob("*"):
|
135 |
+
file.unlink()
|
136 |
+
|
137 |
+
|
138 |
+
@app.post("/generate-excel/")
|
139 |
+
async def generate_excel(predictions: list):
|
140 |
+
# Create an Excel workbook
|
141 |
+
workbook = Workbook()
|
142 |
+
sheet = workbook.active
|
143 |
+
sheet.title = "Predictions"
|
144 |
+
|
145 |
+
# Add headers
|
146 |
+
headers = ["Category", "Confidence", "Predicted Brand", "Price", "Details", "Detected Text", "Image"]
|
147 |
+
sheet.append(headers)
|
148 |
+
|
149 |
+
for idx, prediction in enumerate(predictions):
|
150 |
+
# Extract details from the prediction
|
151 |
+
category = prediction["category"]
|
152 |
+
confidence = prediction["confidence"]
|
153 |
+
predicted_brand = prediction["predicted_brand"]
|
154 |
+
price = prediction["price"]
|
155 |
+
details = prediction["details"]
|
156 |
+
detected_text = prediction["detected_text"]
|
157 |
+
cropped_image_path = prediction["image_path"]
|
158 |
+
|
159 |
+
# Append data row
|
160 |
+
sheet.append([category, confidence, predicted_brand, price, details, detected_text])
|
161 |
+
|
162 |
+
# Add the image to the Excel file (if it exists)
|
163 |
+
if os.path.exists(cropped_image_path):
|
164 |
+
img = ExcelImage(cropped_image_path)
|
165 |
+
img.width, img.height = 50, 50 # Resize image to fit into the cell
|
166 |
+
sheet.add_image(img, f"G{idx + 2}") # Place in the "Image" column
|
167 |
+
|
168 |
+
excel_file_path = "predictions_with_images.xlsx"
|
169 |
+
workbook.save(excel_file_path)
|
170 |
+
|
171 |
+
# Cleanup after saving
|
172 |
+
cleanup_images(cropped_images_dir)
|
173 |
+
|
174 |
+
# Serve the Excel file as a response
|
175 |
+
return FileResponse(
|
176 |
+
excel_file_path,
|
177 |
+
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
178 |
+
filename="predictions_with_images.xlsx"
|
179 |
+
)
|
180 |
+
|
181 |
+
|
182 |
+
# code to accept the localhost to get images from
|
183 |
+
app.add_middleware(
|
184 |
+
CORSMiddleware,
|
185 |
+
allow_origins=["http://192.168.56.1:3000", "http://192.168.56.1:3001", "http://localhost:3000"],
|
186 |
+
allow_methods=["*"],
|
187 |
+
allow_headers=["*"],
|
188 |
+
)
|
model.py
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import torch
|
2 |
+
from pathlib import Path
|
3 |
+
from transformers import CLIPProcessor, CLIPModel
|
4 |
+
from PIL import Image, ImageDraw
|
5 |
+
import pytesseract
|
6 |
+
import requests
|
7 |
+
import os
|
8 |
+
from llm import inference, upload_image
|
9 |
+
from fastapi.responses import FileResponse, JSONResponse
|
10 |
+
|
11 |
+
import re
|
12 |
+
|
13 |
+
from io import BytesIO
|
14 |
+
|
15 |
+
cropped_images_dir = "cropped_images"
|
16 |
+
os.makedirs(cropped_images_dir, exist_ok=True)
|
17 |
+
|
18 |
+
# Load YOLO model
|
19 |
+
class YOLOModel:
|
20 |
+
def __init__(self, model_path="yolov5s.pt"):
|
21 |
+
"""
|
22 |
+
Initialize the YOLO model. Downloads YOLOv5 pretrained model if not available.
|
23 |
+
"""
|
24 |
+
torch.hub._validate_not_a_forked_repo=lambda a,b,c: True
|
25 |
+
self.model = torch.hub.load("ultralytics/yolov5", "custom", path=model_path, force_reload=True)
|
26 |
+
|
27 |
+
|
28 |
+
def predict_clip(self, image, brand_names):
|
29 |
+
"""
|
30 |
+
Predict the most probable brand using CLIP.
|
31 |
+
"""
|
32 |
+
inputs = self.clip_processor(
|
33 |
+
text=brand_names,
|
34 |
+
images=image,
|
35 |
+
return_tensors="pt",
|
36 |
+
padding=True
|
37 |
+
)
|
38 |
+
# print(f'Inputs to clip processor:{inputs}')
|
39 |
+
outputs = self.clip_model(**inputs)
|
40 |
+
logits_per_image = outputs.logits_per_image
|
41 |
+
probs = logits_per_image.softmax(dim=1) # Convert logits to probabilities
|
42 |
+
best_idx = probs.argmax().item()
|
43 |
+
return brand_names[best_idx], probs[0, best_idx].item()
|
44 |
+
|
45 |
+
|
46 |
+
def predict_text(self, image):
|
47 |
+
grayscale = image.convert('L')
|
48 |
+
text = pytesseract.image_to_string(grayscale)
|
49 |
+
return text.strip()
|
50 |
+
|
51 |
+
|
52 |
+
def predict(self, image_path):
|
53 |
+
"""
|
54 |
+
Run YOLO inference on an image.
|
55 |
+
|
56 |
+
:param image_path: Path to the input image
|
57 |
+
:return: List of predictions with labels and bounding boxes
|
58 |
+
"""
|
59 |
+
results = self.model(image_path)
|
60 |
+
image = Image.open(image_path).convert("RGB")
|
61 |
+
draw = ImageDraw.Draw(image)
|
62 |
+
predictions = results.pandas().xyxy[0] # Get predictions as pandas DataFrame
|
63 |
+
print(f'YOLO predictions:\n\n{predictions}')
|
64 |
+
|
65 |
+
|
66 |
+
output = []
|
67 |
+
file_responses = []
|
68 |
+
|
69 |
+
|
70 |
+
for idx, row in predictions.iterrows():
|
71 |
+
category = row['name']
|
72 |
+
confidence = row['confidence']
|
73 |
+
bbox = [row["xmin"], row["ymin"], row["xmax"], row["ymax"]]
|
74 |
+
|
75 |
+
# Crop the detected region
|
76 |
+
cropped_image = image.crop((bbox[0], bbox[1], bbox[2], bbox[3]))
|
77 |
+
cropped_image_path = os.path.join(cropped_images_dir, f"crop_{idx}.jpg")
|
78 |
+
cropped_image.save(cropped_image_path, "JPEG")
|
79 |
+
|
80 |
+
# uploading to cloud for getting URL to pass into LLM
|
81 |
+
print(f'Uploading now to image url')
|
82 |
+
image_url = upload_image.upload_image_to_imgbb(cropped_image_path)
|
83 |
+
print(f'Image URL received as{image_url}')
|
84 |
+
# inferencing llm for possible brands
|
85 |
+
result_llms = inference.get_name(image_url, category)
|
86 |
+
|
87 |
+
detected_text = self.predict_text(cropped_image)
|
88 |
+
print(f'Details:{detected_text}')
|
89 |
+
print(f'Predicted brand: {result_llms["model"]}')
|
90 |
+
# Draw bounding box and label on the image
|
91 |
+
draw.rectangle(bbox, outline="red", width=3)
|
92 |
+
draw.text(
|
93 |
+
(bbox[0], bbox[1] - 10),
|
94 |
+
f'{result_llms["brand"]})',
|
95 |
+
fill="red"
|
96 |
+
)
|
97 |
+
|
98 |
+
cropped_image_io = BytesIO()
|
99 |
+
cropped_image.save(cropped_image_io, format="JPEG")
|
100 |
+
cropped_image_io.seek(0)
|
101 |
+
|
102 |
+
# Append result
|
103 |
+
output.append({
|
104 |
+
"category": category,
|
105 |
+
"bbox": bbox,
|
106 |
+
"confidence": confidence,
|
107 |
+
"category_llm":result_llms["brand"],
|
108 |
+
"predicted_brand": result_llms["model"],
|
109 |
+
# "clip_confidence": clip_confidence,
|
110 |
+
"price":result_llms["price"],
|
111 |
+
"details":result_llms["description"],
|
112 |
+
"detected_text":detected_text,
|
113 |
+
"image_path":cropped_image_path,
|
114 |
+
"image_url":image_url,
|
115 |
+
})
|
116 |
+
|
117 |
+
# file_responses.append(f"/download_cropped_image/{idx}")
|
118 |
+
|
119 |
+
valid_indices = set(range(len(predictions)))
|
120 |
+
|
121 |
+
# Iterate over all files in the directory
|
122 |
+
for filename in os.listdir(cropped_images_dir):
|
123 |
+
# Check if the filename matches the pattern for cropped images
|
124 |
+
if filename.startswith("crop_") and filename.endswith(".jpg"):
|
125 |
+
# Extract the index from the filename
|
126 |
+
try:
|
127 |
+
file_idx = int(filename.split("_")[1].split(".")[0])
|
128 |
+
if file_idx not in valid_indices:
|
129 |
+
# Delete the file if its index is not valid
|
130 |
+
file_path = os.path.join(cropped_images_dir, filename)
|
131 |
+
os.remove(file_path)
|
132 |
+
print(f"Deleted excess file: {filename}")
|
133 |
+
except ValueError:
|
134 |
+
# Skip files that don't match the pattern
|
135 |
+
continue
|
136 |
+
|
137 |
+
return output
|
138 |
+
# return JSONResponse(
|
139 |
+
# content={
|
140 |
+
# "metadata": results,
|
141 |
+
# "cropped_image_urls": [
|
142 |
+
# f"/download_cropped_image/{idx}" for idx in range(len(file_responses))
|
143 |
+
# ],
|
144 |
+
# }
|
145 |
+
# )
|
146 |
+
# return {"metadata": results, "cropped_image_urls": file_responses}
|
147 |
+
|
148 |
+
|
149 |
+
|
predictions_with_images.xlsx
ADDED
Binary file (21.9 kB). View file
|
|
users.db
ADDED
Binary file (16.4 kB). View file
|
|