prathameshks commited on
Commit
8f08648
·
1 Parent(s): f21d678

moved function to utils

Browse files
utils/db_utils.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy.orm import Session
2
+ from interfaces.ingredientModels import IngredientAnalysisResult
3
+ from interfaces.productModels import ProductCreate
4
+ from db.models import Marker
5
+ from logger_manager import log_info, log_error
6
+ from fastapi import HTTPException
7
+ import os
8
+ from services.product_service import ProductService
9
+ from routers.product import add_target_to_vuforia, UPLOADED_IMAGES_DIR # Assuming add_target_to_vuforia and UPLOADED_IMAGES_DIR are needed and will remain in product.py for now. If they are also moved, the import needs adjustment.
10
+
11
+
12
+ def ingredient_db_to_pydantic(db_ingredient):
13
+ """Convert a database ingredient model to a Pydantic model."""
14
+ return IngredientAnalysisResult(
15
+ name=db_ingredient.name,
16
+ alternate_names=db_ingredient.alternate_names or [],
17
+ is_found=True,
18
+ id=db_ingredient.id,
19
+ safety_rating=db_ingredient.safety_rating or 5,
20
+ description=db_ingredient.description or "No description available",
21
+ health_effects=db_ingredient.health_effects or ["Unknown"],
22
+ details_with_source=[source.data for source in db_ingredient.sources]
23
+ )
24
+
25
+
26
+ async def add_product_to_database(
27
+ product_id: int,
28
+ image_names: List[str],
29
+ db: Session,
30
+ product_data: Dict[str, Any],
31
+ ):
32
+ """
33
+ Adds markers for the product, or updates it if it exists.
34
+ """
35
+ try:
36
+ log_info(f"Adding markers to product with ID {product_id} in database")
37
+ product_service = ProductService(db)
38
+ product = product_service.get_product_by_id(product_id)
39
+ if not product:
40
+ raise Exception(f"Product with ID {product_id} not found")
41
+
42
+ # Add or update markers for the product
43
+ for image_name in image_names:
44
+ image_path = os.path.join(UPLOADED_IMAGES_DIR, image_name)
45
+
46
+ vuforia_id = await add_target_to_vuforia(image_name, image_path)
47
+ existing_marker = db.query(Marker).filter_by(image_name=image_name, product_id=product.id).first()
48
+
49
+ if not existing_marker:
50
+ marker = Marker(image_name=image_name, vuforia_id=vuforia_id, product_id=product.id)
51
+ db.add(marker)
52
+ else:
53
+ log_info(f"Marker {image_name} already exists for product {product_id}. Updating Vuforia ID.")
54
+ existing_marker.vuforia_id = vuforia_id
55
+
56
+ db.commit()
57
+ log_info(f"Product markers added/updated successfully in database")
58
+ return True
59
+ except Exception as e:
60
+ db.rollback()
61
+ log_error(f"Error adding/updating markers for product {product_id} in database: {e}",e)
62
+ raise HTTPException(status_code=500, detail=f"Error adding/updating markers for product {product_id}: {e}")
utils/external_api_utils.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ import os
4
+ from logger_manager import log_info, log_error
5
+ from dotenv import load_dotenv
6
+
7
+ load_dotenv()
8
+
9
+ VUFORIA_SERVER_ACCESS_KEY = os.getenv("VUFORIA_SERVER_ACCESS_KEY")
10
+ VUFORIA_SERVER_SECRET_KEY = os.getenv("VUFORIA_SERVER_SECRET_KEY")
11
+
12
+ def get_vuforia_auth_headers():
13
+ """
14
+ Returns the authentication headers for Vuforia API requests.
15
+ """
16
+ return {
17
+ "Authorization": f"VWS {VUFORIA_SERVER_ACCESS_KEY}:{VUFORIA_SERVER_SECRET_KEY}",
18
+ "Content-Type": "application/json",
19
+ }
20
+
21
+
22
+ async def add_target_to_vuforia(image_name: str, image_path: str) -> str:
23
+ """
24
+ Adds a target to the Vuforia database and returns the Vuforia target ID.
25
+ """
26
+ log_info(f"Adding target {image_name} to Vuforia")
27
+
28
+ try:
29
+ with open(image_path, "rb") as image_file:
30
+ image_data = image_file.read()
31
+
32
+ url = f"https://vws.vuforia.com/targets"
33
+
34
+ headers = get_vuforia_auth_headers()
35
+ payload = {
36
+ "name": image_name,
37
+ "width": 1.0, # Default width
38
+ "image": image_data.hex(), # Convert image data to hex
39
+ "active_flag": True,
40
+ }
41
+
42
+ response = requests.post(url, headers=headers, json=payload)
43
+ response_data = json.loads(response.text)
44
+ if response.status_code == 201:
45
+ log_info(
46
+ f"Target {image_name} added successfully with Vuforia ID: {response_data['target_id']}"
47
+ )
48
+ return response_data["target_id"]
49
+ else:
50
+ log_error(f"Failed to add target {image_name}: {response.text}")
51
+ raise Exception(f"Failed to add target {image_name}: {response.text}")
52
+ except Exception as e:
53
+ log_error(f"Error adding target {image_name}: {e}", e)
54
+ raise
utils/image_processing.py DELETED
@@ -1,19 +0,0 @@
1
- import cv2
2
- import pytesseract
3
-
4
- def extract_text_from_image(image_path):
5
- image = cv2.imread(image_path)
6
- gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
7
- gray = cv2.medianBlur(gray, 3)
8
- text = pytesseract.image_to_string(gray)
9
- return text
10
-
11
- def detect_barcode_from_image(image_path):
12
- image = cv2.imread(image_path)
13
- gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
14
- gray = cv2.medianBlur(gray, 3)
15
- detector = cv2.QRCodeDetector()
16
- data, bbox, _ = detector.detectAndDecode(gray)
17
- if bbox is not None:
18
- return data
19
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/image_processing_utils.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tensorflow as tf
2
+ import tensorflow_hub as hub
3
+ import numpy as np
4
+ from PIL import Image, ImageDraw, ImageFont, ImageOps
5
+ import requests
6
+ from io import BytesIO
7
+ import os
8
+
9
+
10
+ # Load the model from TF Hub
11
+ # Cache the model globally
12
+ detector = hub.load("https://tfhub.dev/google/openimages_v4/ssd/mobilenet_v2/1").signatures['default']
13
+
14
+ # Classes you care about
15
+ TARGET_CLASSES = set(["Food processor", "Fast food", "Food", "Seafood", "Snack"])
16
+
17
+ UPLOADED_IMAGES_DIR = "uploaded_images"
18
+ if not os.path.exists(UPLOADED_IMAGES_DIR):
19
+ os.makedirs(UPLOADED_IMAGES_DIR)
20
+
21
+
22
+ def load_image_from_url(url, size=(640, 480)):
23
+ response = requests.get(url)
24
+ img = Image.open(BytesIO(response.content)).convert("RGB")
25
+ img = ImageOps.fit(img, size, Image.Resampling.LANCZOS)
26
+ return img
27
+
28
+
29
+ def run_object_detection(image: Image.Image):
30
+ image_np = np.array(image)
31
+ # Convert to tensor without specifying dtype
32
+ input_tensor = tf.convert_to_tensor(image_np)[tf.newaxis, ...]
33
+ # Convert to float32 and normalize to [0,1]
34
+ input_tensor = tf.cast(input_tensor, tf.float32) / 255.0
35
+ results = detector(input_tensor)
36
+ results = {k: v.numpy() for k, v in results.items()}
37
+ return results, image_np
38
+
39
+ def get_filtered_class_boxes(results):
40
+ # for same class, keep the one with the highest score
41
+ # and remove duplicates
42
+ boxes = []
43
+ classes = []
44
+ scores = []
45
+
46
+ for i in range(len(results["detection_scores"])):
47
+ class_name = results["detection_class_entities"][i].decode("utf-8")
48
+ box = results["detection_boxes"][i]
49
+ score = results["detection_scores"][i]
50
+ if class_name in TARGET_CLASSES:
51
+ if class_name not in classes:
52
+ boxes.append(box)
53
+ classes.append(class_name)
54
+ scores.append(score)
55
+ else:
56
+ index = classes.index(class_name)
57
+ if score > scores[index]:
58
+ boxes[index] = box
59
+ classes[index] = class_name
60
+ scores[index] = score
61
+ return boxes, classes, scores
62
+
63
+
64
+ def crop_and_save(image_np, boxes, class_names, scores, min_score=0.3):
65
+ cropped_images = []
66
+ for i in range(len(scores)):
67
+ if scores[i] > min_score:
68
+ ymin, xmin, ymax, xmax = boxes[i]
69
+ im_width, im_height = image_np.shape[1], image_np.shape[0]
70
+ (left, right, top, bottom) = (xmin * im_width, xmax * im_width,
71
+ ymin * im_height, ymax * im_height)
72
+ cropped_image = image_np[int(top):int(bottom), int(left):int(right)]
73
+ cropped_images.append((cropped_image, class_names[i], scores[i]))
74
+ # Save the cropped image
75
+ pil_image = Image.fromarray(cropped_image)
76
+ pil_image.save(os.path.join(UPLOADED_IMAGES_DIR, f"{class_names[i]}_{scores[i]:.2f}.jpg"))
77
+ return cropped_images
78
+
79
+ def draw_boxes(image_np, boxes, class_names, scores, min_score=0.3):
80
+ image_pil = Image.fromarray(image_np)
81
+ draw = ImageDraw.Draw(image_pil)
82
+ font = ImageFont.load_default()
83
+
84
+ for i in range(len(scores)):
85
+ label = class_names[i]
86
+ if label in TARGET_CLASSES and scores[i] > min_score:
87
+ ymin, xmin, ymax, xmax = boxes[i]
88
+ im_width, im_height = image_pil.size
89
+ (left, right, top, bottom) = (xmin * im_width, xmax * im_width,
90
+ ymin * im_height, ymax * im_height)
91
+ draw.rectangle([left, top, right, bottom], outline="red", width=2)
92
+ draw.text((left, top), f"{label}: {scores[i]*100:.1f}%", fill="white", font=font)
93
+ return image_pil
utils/ingredient_utils.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import os
3
+ from sqlalchemy.orm import Session
4
+ from db.database import SessionLocal
5
+ from db.repositories import IngredientRepository
6
+ from interfaces.ingredientModels import IngredientAnalysisResult
7
+ from services.ingredientFinderAgent import IngredientInfoAgentLangGraph
8
+ from dotenv import load_dotenv
9
+ from langsmith import traceable
10
+ import pytz
11
+
12
+ # Load environment variables
13
+ load_dotenv()
14
+
15
+ # Get rate limit from environment variable or use default
16
+ PARALLEL_RATE_LIMIT = int(os.getenv("PARALLEL_RATE_LIMIT", 10))
17
+
18
+ # Create a semaphore to limit concurrent API calls
19
+ llm_semaphore = asyncio.Semaphore(PARALLEL_RATE_LIMIT)
20
+
21
+
22
+ @traceable
23
+ async def process_single_ingredient(ingredient_name: str):
24
+ """Process a single ingredient asynchronously with rate limiting"""
25
+ # Create a new DB session for this specific task to avoid conflicts
26
+ session = SessionLocal()
27
+
28
+ try:
29
+ # Check if ingredient exists in database
30
+ repo = IngredientRepository(session)
31
+ db_ingredient = repo.get_ingredient_by_name(ingredient_name)
32
+
33
+ if db_ingredient:
34
+ # Assuming ingredient_db_to_pydantic is now in a utils file, e.g., utils.db_utils
35
+ from .db_utils import ingredient_db_to_pydantic
36
+ ingredient_data = ingredient_db_to_pydantic(db_ingredient)
37
+ return ingredient_data
38
+ else:
39
+ # Apply rate limiting for LLM calls only if not in database
40
+ async with llm_semaphore:
41
+ # Get from agent if not in database
42
+ ingredient_finder = IngredientInfoAgentLangGraph()
43
+
44
+ ingredient_data = await ingredient_finder.process_ingredient_async(ingredient_name)
45
+
46
+ # Save to database for future use
47
+ repo.create_ingredient(ingredient_data)
48
+
49
+ return ingredient_data
50
+ except Exception as e:
51
+ # Return a minimal result on error to avoid failing the entire batch
52
+ return IngredientAnalysisResult(
53
+ name=ingredient_name,
54
+ is_found=False,
55
+ safety_rating=0,
56
+ description=f"Error during processing: {str(e)}",
57
+ health_effects=["Error during processing"],
58
+ allergic_info=[],
59
+ diet_type="unknown",
60
+ details_with_source=[]
61
+ )
62
+ finally:
63
+ # Important: Close the session when done
64
+ session.close()