Lepthae commited on
Commit
15b9bc1
·
1 Parent(s): e245176

Allow admin dashboard to process images properly

Browse files
api/product_routes.py CHANGED
@@ -2,9 +2,7 @@ from fastapi import APIRouter, File, UploadFile, HTTPException, Form
2
  from utils.image_processing import read_image_file, process_product_image
3
  from product_detector.detector import ObjectDetector
4
  from config.settings import MODEL_ONNX_PATH, CLASS_NAMES, INPUT_SIZE
5
- from typing import Optional
6
- from db.supabase_client import SupabaseClient
7
- import uuid
8
 
9
  # Initialize the detector
10
  detector = ObjectDetector(
@@ -15,10 +13,12 @@ detector = ObjectDetector(
15
 
16
  router = APIRouter(tags=["Product Detection"])
17
 
 
18
  @router.options("/detect-product")
19
  async def detect_options():
20
  return {"Allow": "POST"}
21
 
 
22
  @router.post("/detect-product")
23
  async def detect_objects(file: UploadFile = File(...)):
24
  try:
@@ -35,125 +35,68 @@ async def detect_objects(file: UploadFile = File(...)):
35
  except Exception as e:
36
  raise HTTPException(500, f"Processing error: {str(e)}")
37
 
 
38
  @router.post("/process-image")
39
  async def process_image(
40
- file: UploadFile = File(...),
41
- remove_bg: bool = Form(True),
42
- upscale: bool = Form(True),
43
- scale_factor: int = Form(2),
44
- process_order: str = Form("remove_first")
45
  ):
46
  """
47
  Process product images by removing background and/or upscaling
48
-
49
- - remove_bg: Whether to remove the white background
50
- - upscale: Whether to upscale the image
51
- - scale_factor: Scale factor for upscaling (2, 3, or 4)
52
- - process_order: Order of operations ('remove_first' or 'upscale_first')
53
  """
54
  try:
55
  # Validate inputs
56
  if scale_factor not in [2, 3, 4]:
57
  raise HTTPException(400, "Scale factor must be 2, 3, or 4")
58
-
59
  if process_order not in ["remove_first", "upscale_first"]:
60
  raise HTTPException(400, "Process order must be 'remove_first' or 'upscale_first'")
61
-
62
  if not file.content_type.startswith("image/"):
63
  raise HTTPException(400, "File must be an image")
64
-
65
- # Process the image
66
- processed_image, filename = await process_product_image(
67
- file,
68
  remove_bg=remove_bg,
69
  upscale=upscale,
70
  scale_factor=scale_factor,
71
  process_order=process_order
72
  )
73
-
74
- # Generate a unique ID for the image
75
- image_id = str(uuid.uuid4())
76
- image_path = f"{image_id}_{filename}"
77
-
78
- # Upload the processed image to Supabase Storage
79
- supabase.storage.from_("product-images").upload(
80
- file=processed_image,
81
- path=image_path,
82
- file_options={"content-type": "image/png", "upsert": "true"}
83
- )
84
-
85
- # Get the public URL for the uploaded image
86
- image_url = supabase.storage.from_("product-images").get_public_url(image_path)
87
-
88
- return {
89
- "status": "success",
90
- "message": "Image processed successfully",
91
- "image_url": image_url,
92
- "image_path": image_path,
93
- "processing": {
94
- "background_removed": remove_bg,
95
- "upscaled": upscale,
96
- "scale_factor": scale_factor if upscale else None,
97
- "process_order": process_order
98
- }
99
- }
100
-
101
  except HTTPException:
102
  raise
103
  except Exception as e:
104
  raise HTTPException(500, f"Image processing error: {str(e)}")
105
-
106
-
107
  @router.post("/process-product-image")
108
  async def process_product_image_endpoint(
109
- file: UploadFile = File(...),
110
- remove_bg: bool = Form(True),
111
- upscale: bool = Form(True),
112
- scale_factor: int = Form(2),
113
- process_order: str = Form("remove_first"),
114
- product_id: str = Form(None)
115
  ):
116
  """
117
- Process a product image by removing background and upscaling,
118
- then save to Supabase Storage
119
  """
120
  try:
121
- # Process the image
122
- processed_image, filename = await process_product_image(
123
- file,
124
  remove_bg=remove_bg,
125
  upscale=upscale,
126
  scale_factor=scale_factor,
127
- process_order=process_order
128
- )
129
-
130
- # Generate a unique ID for the image
131
- image_id = str(uuid.uuid4())
132
- image_path = f"{image_id}_{filename}"
133
-
134
- # Upload the processed image to Supabase Storage
135
- supabase.storage.from_("product-images").upload(
136
- file=processed_image,
137
- path=image_path,
138
- file_options={"content-type": "image/png", "upsert": "true"}
139
  )
140
-
141
- # Get the public URL for the uploaded image
142
- image_url = supabase.storage.from_("product-images").get_public_url(image_path)
143
-
144
- # If product_id is provided, update the product record
145
- if product_id:
146
- # Update product_image column in the database
147
- result = supabase.table("products").update({
148
- "product_image": image_url
149
- }).eq("id", product_id).execute()
150
-
151
- return {
152
- "status": "success",
153
- "message": "Image processed successfully",
154
- "image_url": image_url,
155
- "image_path": image_path
156
- }
157
-
158
  except Exception as e:
159
  raise HTTPException(500, f"Image processing error: {str(e)}")
 
2
  from utils.image_processing import read_image_file, process_product_image
3
  from product_detector.detector import ObjectDetector
4
  from config.settings import MODEL_ONNX_PATH, CLASS_NAMES, INPUT_SIZE
5
+ from utils.image_processing import process_and_store_product_image
 
 
6
 
7
  # Initialize the detector
8
  detector = ObjectDetector(
 
13
 
14
  router = APIRouter(tags=["Product Detection"])
15
 
16
+
17
  @router.options("/detect-product")
18
  async def detect_options():
19
  return {"Allow": "POST"}
20
 
21
+
22
  @router.post("/detect-product")
23
  async def detect_objects(file: UploadFile = File(...)):
24
  try:
 
35
  except Exception as e:
36
  raise HTTPException(500, f"Processing error: {str(e)}")
37
 
38
+
39
  @router.post("/process-image")
40
  async def process_image(
41
+ file: UploadFile = File(...),
42
+ remove_bg: bool = Form(True),
43
+ upscale: bool = Form(True),
44
+ scale_factor: int = Form(2),
45
+ process_order: str = Form("remove_first")
46
  ):
47
  """
48
  Process product images by removing background and/or upscaling
 
 
 
 
 
49
  """
50
  try:
51
  # Validate inputs
52
  if scale_factor not in [2, 3, 4]:
53
  raise HTTPException(400, "Scale factor must be 2, 3, or 4")
54
+
55
  if process_order not in ["remove_first", "upscale_first"]:
56
  raise HTTPException(400, "Process order must be 'remove_first' or 'upscale_first'")
57
+
58
  if not file.content_type.startswith("image/"):
59
  raise HTTPException(400, "File must be an image")
60
+
61
+ # Use the combined processing and storage function
62
+ result = await process_and_store_product_image(
63
+ file,
64
  remove_bg=remove_bg,
65
  upscale=upscale,
66
  scale_factor=scale_factor,
67
  process_order=process_order
68
  )
69
+
70
+ return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  except HTTPException:
72
  raise
73
  except Exception as e:
74
  raise HTTPException(500, f"Image processing error: {str(e)}")
75
+
76
+
77
  @router.post("/process-product-image")
78
  async def process_product_image_endpoint(
79
+ file: UploadFile = File(...),
80
+ remove_bg: bool = Form(True),
81
+ upscale: bool = Form(True),
82
+ scale_factor: int = Form(2),
83
+ process_order: str = Form("remove_first"),
84
+ product_id: str = Form(None)
85
  ):
86
  """
87
+ Process a product image and update the product record
 
88
  """
89
  try:
90
+ # Use the combined processing, storage and database function
91
+ result = await process_and_store_product_image(
92
+ file,
93
  remove_bg=remove_bg,
94
  upscale=upscale,
95
  scale_factor=scale_factor,
96
+ process_order=process_order,
97
+ product_id=product_id
 
 
 
 
 
 
 
 
 
 
98
  )
99
+
100
+ return result
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  except Exception as e:
102
  raise HTTPException(500, f"Image processing error: {str(e)}")
db/receipt_repository.py CHANGED
@@ -2,7 +2,8 @@ import uuid
2
  import json
3
  from datetime import datetime
4
  from typing import Optional, Dict, Any
5
- from .supabase_client import SupabaseClient
 
6
 
7
  class ReceiptRepository:
8
  def __init__(self):
 
2
  import json
3
  from datetime import datetime
4
  from typing import Optional, Dict, Any
5
+ from supabase_client import SupabaseClient
6
+
7
 
8
  class ReceiptRepository:
9
  def __init__(self):
db/scrape_repository.py CHANGED
@@ -1,6 +1,7 @@
1
  from typing import Dict, Any, List
2
  from datetime import datetime
3
- from .supabase_client import SupabaseClient
 
4
 
5
  class PromoProductRepository:
6
  def __init__(self):
@@ -23,7 +24,7 @@ class PromoProductRepository:
23
  store = product.get("store")
24
  name = product.get("name")
25
 
26
- formatted_product = {
27
  "store": store,
28
  "picture_id": product.get("pictureId"),
29
  "name": name,
@@ -46,13 +47,15 @@ class PromoProductRepository:
46
  # Product exists, update it
47
  record_id = result.data[0]["id"]
48
  self.supabase.table("promo_products") \
49
- .update(formatted_product) \
50
  .eq("id", record_id) \
51
  .execute()
 
 
52
  else:
53
  # Product doesn't exist, insert it
54
  self.supabase.table("promo_products") \
55
- .insert(formatted_product) \
56
  .execute()
57
 
58
  successfully_upserted += 1
 
1
  from typing import Dict, Any, List
2
  from datetime import datetime
3
+ from supabase_client import SupabaseClient
4
+
5
 
6
  class PromoProductRepository:
7
  def __init__(self):
 
24
  store = product.get("store")
25
  name = product.get("name")
26
 
27
+ formatted_promo_product = {
28
  "store": store,
29
  "picture_id": product.get("pictureId"),
30
  "name": name,
 
47
  # Product exists, update it
48
  record_id = result.data[0]["id"]
49
  self.supabase.table("promo_products") \
50
+ .update(formatted_promo_product) \
51
  .eq("id", record_id) \
52
  .execute()
53
+
54
+
55
  else:
56
  # Product doesn't exist, insert it
57
  self.supabase.table("promo_products") \
58
+ .insert(formatted_promo_product) \
59
  .execute()
60
 
61
  successfully_upserted += 1
utils/image_processing.py CHANGED
@@ -1,12 +1,15 @@
1
  import numpy as np
2
  import cv2
3
  from fastapi import UploadFile, HTTPException
4
- from PIL import Image
5
- import io
6
  from rembg import remove
7
  import time
8
  import uuid
9
  from typing import Tuple, Optional
 
 
 
 
 
10
 
11
  async def read_image_file(file: UploadFile) -> np.ndarray:
12
  """Read and process an image file from FastAPI UploadFile"""
@@ -21,14 +24,20 @@ async def read_image_file(file: UploadFile) -> np.ndarray:
21
 
22
  return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
23
 
 
24
  def remove_background(image_bytes: bytes) -> bytes:
25
  """Remove white background from image using rembg"""
26
  try:
27
- return remove(image_bytes)
 
 
 
 
28
  except Exception as e:
29
  print(f"Error removing background: {str(e)}")
30
  raise Exception(f"Background removal error: {str(e)}")
31
 
 
32
  def upscale_image(image_bytes: bytes, scale_factor: int = 2) -> bytes:
33
  """Upscale image using OpenCV"""
34
  try:
@@ -72,6 +81,7 @@ def upscale_image(image_bytes: bytes, scale_factor: int = 2) -> bytes:
72
  print(f"Error upscaling image: {str(e)}")
73
  raise Exception(f"Image upscaling error: {str(e)}")
74
 
 
75
  async def process_product_image(
76
  file: UploadFile,
77
  remove_bg: bool = True,
@@ -107,4 +117,102 @@ async def process_product_image(
107
  # Create descriptive filename with processing info
108
  processed_filename = f"{base_name}_{'nobg' if remove_bg else ''}_{'upx' + str(scale_factor) if upscale else ''}_{timestamp}.{extension}"
109
 
110
- return processed_content, processed_filename
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import numpy as np
2
  import cv2
3
  from fastapi import UploadFile, HTTPException
 
 
4
  from rembg import remove
5
  import time
6
  import uuid
7
  from typing import Tuple, Optional
8
+ from db.supabase_client import SupabaseClient
9
+
10
+ # Initialize Supabase client
11
+ supabase = SupabaseClient().get_client()
12
+
13
 
14
  async def read_image_file(file: UploadFile) -> np.ndarray:
15
  """Read and process an image file from FastAPI UploadFile"""
 
24
 
25
  return cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
26
 
27
+
28
  def remove_background(image_bytes: bytes) -> bytes:
29
  """Remove white background from image using rembg"""
30
  try:
31
+ return remove(image_bytes,
32
+ alpha_matting=True,
33
+ alpha_matting_background_threshold=5,
34
+ alpha_matting_foreground_threshold=220,
35
+ alpha_matting_erode_size=5)
36
  except Exception as e:
37
  print(f"Error removing background: {str(e)}")
38
  raise Exception(f"Background removal error: {str(e)}")
39
 
40
+
41
  def upscale_image(image_bytes: bytes, scale_factor: int = 2) -> bytes:
42
  """Upscale image using OpenCV"""
43
  try:
 
81
  print(f"Error upscaling image: {str(e)}")
82
  raise Exception(f"Image upscaling error: {str(e)}")
83
 
84
+
85
  async def process_product_image(
86
  file: UploadFile,
87
  remove_bg: bool = True,
 
117
  # Create descriptive filename with processing info
118
  processed_filename = f"{base_name}_{'nobg' if remove_bg else ''}_{'upx' + str(scale_factor) if upscale else ''}_{timestamp}.{extension}"
119
 
120
+ return processed_content, processed_filename
121
+
122
+
123
+ async def upload_processed_image(
124
+ processed_image: bytes,
125
+ filename: str,
126
+ bucket: str = "product-images"
127
+ ) -> Tuple[str, str]:
128
+ """
129
+ Upload a processed image to Supabase Storage
130
+
131
+ Returns:
132
+ Tuple[str, str]: (image_path, image_url)
133
+ """
134
+ # Generate a unique ID for the image
135
+ image_id = str(uuid.uuid4())
136
+ image_path = f"{image_id}_{filename}"
137
+
138
+ # Upload the processed image to Supabase Storage
139
+ supabase.storage.from_(bucket).upload(
140
+ file=processed_image,
141
+ path=image_path,
142
+ file_options={"content-type": "image/png", "upsert": "true"}
143
+ )
144
+
145
+ # Get the public URL for the uploaded image
146
+ image_url = supabase.storage.from_(bucket).get_public_url(image_path)
147
+
148
+ return image_path, image_url
149
+
150
+ async def update_product_image(product_id: str, image_url: str) -> dict[str, any]:
151
+ """
152
+ Update the product_image field for a product
153
+
154
+ Returns:
155
+ Dict[str, Any]: The updated product data
156
+ """
157
+ if not product_id:
158
+ raise ValueError("Product ID is required")
159
+
160
+ result = supabase.table("products").update({
161
+ "product_image": image_url
162
+ }).eq("id", product_id).execute()
163
+
164
+ if not result.data:
165
+ raise Exception(f"Failed to update product {product_id}")
166
+
167
+ return result.data[0]
168
+
169
+ async def process_and_store_product_image(
170
+ file: UploadFile,
171
+ remove_bg: bool = True,
172
+ upscale: bool = True,
173
+ scale_factor: int = 2,
174
+ process_order: str = "remove_first",
175
+ product_id: Optional[str] = None
176
+ ) -> dict[str, any]:
177
+ """
178
+ Complete workflow for processing a product image and storing it
179
+
180
+ This function:
181
+ 1. Processes the image (remove background, upscale)
182
+ 2. Uploads it to storage
183
+ 3. Updates the product record if product_id is provided
184
+
185
+ Returns:
186
+ Dict[str, Any]: Result with status, urls, and processing info
187
+ """
188
+ # Process the image
189
+ processed_image, filename = await process_product_image(
190
+ file,
191
+ remove_bg=remove_bg,
192
+ upscale=upscale,
193
+ scale_factor=scale_factor,
194
+ process_order=process_order
195
+ )
196
+
197
+ # Upload to storage
198
+ image_path, image_url = await upload_processed_image(processed_image, filename)
199
+
200
+ # Update product record if needed
201
+ product_data = None
202
+ if product_id:
203
+ product_data = await update_product_image(product_id, image_url)
204
+
205
+ # Return comprehensive result
206
+ return {
207
+ "status": "success",
208
+ "message": "Image processed successfully",
209
+ "image_url": image_url,
210
+ "image_path": image_path,
211
+ "product_data": product_data,
212
+ "processing": {
213
+ "background_removed": remove_bg,
214
+ "upscaled": upscale,
215
+ "scale_factor": scale_factor if upscale else None,
216
+ "process_order": process_order
217
+ }
218
+ }