prathameshks commited on
Commit
2e67005
·
1 Parent(s): 3a00b31

add product complete with vuforia

Browse files
.env.example CHANGED
@@ -29,4 +29,10 @@ GOOGLE_CSE_ID=google_cse_id # for google search tool more info on langchain tool
29
  USDA_API_KEY=usda_api_key # for searching usda dataset
30
 
31
  # pg db url
32
- DATABASE_URL=postgresql://user:password@localhost/food_analyzer_db
 
 
 
 
 
 
 
29
  USDA_API_KEY=usda_api_key # for searching usda dataset
30
 
31
  # pg db url
32
+ DATABASE_URL=postgresql://user:password@localhost/food_analyzer_db
33
+
34
+ # Vuforia keys
35
+ VUFORIA_SERVER_ACCESS_KEY=your_vuforia_server_access_key
36
+ VUFORIA_SERVER_SECRET_KEY=your_vuforia_server_secret_key
37
+ VUFORIA_TARGET_DATABASE_NAME=your_vuforia_target_database_name
38
+ VUFORIA_TARGET_DATABASE_ID=your_vuforia_target_db_id
db/models.py CHANGED
@@ -1,5 +1,5 @@
1
- from sqlalchemy import Column, Integer, String, Float, Boolean, Text, JSON, ForeignKey, DateTime
2
- from sqlalchemy.orm import relationship
3
  from sqlalchemy.sql import func
4
  from .database import Base
5
  from typing import List, Optional
@@ -36,7 +36,17 @@ class IngredientSource(Base):
36
  # Relationships
37
  ingredient = relationship("Ingredient", back_populates="sources")
38
 
 
 
 
 
 
 
 
 
 
39
  class Product(Base):
 
40
  __tablename__ = "products"
41
 
42
  id = Column(Integer, primary_key=True, index=True)
@@ -50,6 +60,8 @@ class Product(Base):
50
  nutrient_levels = Column(JSON, nullable=True)
51
  nutriments = Column(JSON, nullable=True)
52
  data_quality_warnings = Column(JSON, nullable=True)
 
 
53
 
54
 
55
  class User(Base):
 
1
+ from sqlalchemy import Column, Integer, String, Boolean, Text, JSON, ForeignKey, DateTime
2
+ from sqlalchemy.orm import relationship, Mapped, mapped_column
3
  from sqlalchemy.sql import func
4
  from .database import Base
5
  from typing import List, Optional
 
36
  # Relationships
37
  ingredient = relationship("Ingredient", back_populates="sources")
38
 
39
+ class Marker(Base):
40
+ __tablename__ = "markers"
41
+ id: Mapped[int] = mapped_column(primary_key=True)
42
+ image_name: Mapped[str]
43
+ vuforia_id: Mapped[str]
44
+ product_id: Mapped[int] = mapped_column(ForeignKey("products.id"))
45
+
46
+ product: Mapped["Product"] = relationship(back_populates="markers")
47
+
48
  class Product(Base):
49
+
50
  __tablename__ = "products"
51
 
52
  id = Column(Integer, primary_key=True, index=True)
 
60
  nutrient_levels = Column(JSON, nullable=True)
61
  nutriments = Column(JSON, nullable=True)
62
  data_quality_warnings = Column(JSON, nullable=True)
63
+
64
+ markers: Mapped[List["Marker"]] = relationship(back_populates="product")
65
 
66
 
67
  class User(Base):
routers/product.py CHANGED
@@ -1,42 +1,154 @@
1
  import io
2
- from fastapi import APIRouter, File, Request, UploadFile
3
  from fastapi.responses import JSONResponse
4
- from typing import List
5
  from logger_manager import log_info, log_error
6
- from fastapi.encoders import jsonable_encoder
7
  from PIL import Image
8
  import os
9
- import uuid
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  UPLOADED_IMAGES_DIR = "uploaded_images"
12
  if not os.path.exists(UPLOADED_IMAGES_DIR):
13
  os.makedirs(UPLOADED_IMAGES_DIR)
14
 
 
 
 
 
 
 
15
  router = APIRouter()
16
 
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  @router.post("/add")
19
  async def create_product(
20
- request: Request, image: UploadFile = File(...)
21
- ): # Changed to accept pre-processed image
22
- log_info("Create product endpoint called")
23
  try:
 
24
  data = await request.json()
25
  print("Received data:", data)
26
 
27
- # Extract product details from the request body
28
- name = data.get("name")
29
- ingredients: List[str] = data.get("ingredients")
30
- image_path: str = data.get("image_path")
31
 
32
- # TODO actual adding product to DB and Vuforia Target linking
33
- print("Product Name:", name)
34
- print("Ingredients:", ingredients)
35
- print("Image_path:", image_path)
 
 
 
 
 
 
 
 
 
 
36
  return JSONResponse(
37
- {"message": "Product data and image received and processed successfully"}
38
  )
39
 
 
 
40
  except Exception as e:
41
- print("Error:", e)
42
- return JSONResponse({"error": str(e)}, status_code=500)
 
1
  import io
2
+ from fastapi import APIRouter, Request, HTTPException
3
  from fastapi.responses import JSONResponse
4
+ from typing import List, Dict, Any
5
  from logger_manager import log_info, log_error
 
6
  from PIL import Image
7
  import os
8
+ from services.product_service import ProductService
9
+ from db.models import Marker, Ingredient
10
+ from sqlalchemy.orm import Session
11
+ from db.database import get_db
12
+ from fastapi import Depends
13
+ from typing import Generator
14
+ from dotenv import load_dotenv
15
+ import requests
16
+ import json
17
+ from services.ingredients import IngredientService
18
+
19
+ load_dotenv()
20
 
21
  UPLOADED_IMAGES_DIR = "uploaded_images"
22
  if not os.path.exists(UPLOADED_IMAGES_DIR):
23
  os.makedirs(UPLOADED_IMAGES_DIR)
24
 
25
+
26
+ VUFORIA_SERVER_ACCESS_KEY = os.getenv("VUFORIA_SERVER_ACCESS_KEY")
27
+ VUFORIA_SERVER_SECRET_KEY = os.getenv("VUFORIA_SERVER_SECRET_KEY")
28
+ VUFORIA_TARGET_DATABASE_NAME = os.getenv("VUFORIA_TARGET_DATABASE_NAME")
29
+ VUFORIA_TARGET_DATABASE_ID = os.getenv("VUFORIA_TARGET_DATABASE_ID")
30
+
31
  router = APIRouter()
32
 
33
 
34
+ def get_vuforia_auth_headers():
35
+ """
36
+ Returns the authentication headers for Vuforia API requests.
37
+ """
38
+ return {
39
+ "Authorization": f"VWS {VUFORIA_SERVER_ACCESS_KEY}:{VUFORIA_SERVER_SECRET_KEY}",
40
+ "Content-Type": "application/json",
41
+ }
42
+
43
+
44
+ async def add_target_to_vuforia(image_name: str, image_path: str) -> str:
45
+ """
46
+ Adds a target to the Vuforia database and returns the Vuforia target ID.
47
+ """
48
+ log_info(f"Adding target {image_name} to Vuforia")
49
+
50
+ try:
51
+ with open(image_path, "rb") as image_file:
52
+ image_data = image_file.read()
53
+
54
+ url = f"https://vws.vuforia.com/targets"
55
+
56
+ headers = get_vuforia_auth_headers()
57
+ payload = {
58
+ "name": image_name,
59
+ "width": 1.0, # Default width
60
+ "image": image_data.hex(), # Convert image data to hex
61
+ "active_flag": True,
62
+ }
63
+
64
+ response = await requests.post(url, headers=headers, json=payload)
65
+ response_data = json.loads(response.text)
66
+ if response.status_code == 201:
67
+ log_info(
68
+ f"Target {image_name} added successfully with Vuforia ID: {response_data['target_id']}"
69
+ )
70
+ return response_data["target_id"]
71
+ else:
72
+ log_error(f"Failed to add target {image_name}: {response.text}")
73
+ raise Exception(f"Failed to add target {image_name}: {response.text}")
74
+ except Exception as e:
75
+ log_error(f"Error adding target {image_name}: {e}")
76
+ raise
77
+
78
+
79
+ async def add_product_to_database(
80
+ product_id: int,
81
+ image_names: List[str],
82
+ db: Session,
83
+ product_data: Dict[str, Any],
84
+ ):
85
+ """
86
+ Adds markers for the product, or updates it if it exists.
87
+ """
88
+ try:
89
+ log_info(f"Adding markers to product with ID {product_id} in database")
90
+ product_service = ProductService(db)
91
+ product = product_service.get_product_by_id(product_id)
92
+ if not product:
93
+ raise Exception(f"Product with ID {product_id} not found")
94
+
95
+ # Add or update markers for the product
96
+ for image_name in image_names:
97
+ image_path = os.path.join(UPLOADED_IMAGES_DIR, image_name)
98
+
99
+ vuforia_id = await add_target_to_vuforia(image_name, image_path)
100
+ existing_marker = db.query(Marker).filter_by(image_name=image_name, product_id=product.id).first()
101
+
102
+ if not existing_marker:
103
+ marker = Marker(image_name=image_name, vuforia_id=vuforia_id, product_id=product.id)
104
+ db.add(marker)
105
+ else:
106
+ log_info(f"Marker {image_name} already exists for product {product_id}. Updating Vuforia ID.")
107
+ existing_marker.vuforia_id = vuforia_id
108
+
109
+ db.commit()
110
+ log_info(f"Product markers added/updated successfully in database")
111
+ return True
112
+ except Exception as e:
113
+ db.rollback()
114
+ log_error(f"Error adding/updating markers for product {product_id} in database: {e}")
115
+ raise HTTPException(status_code=500, detail=f"Error adding/updating markers for product {product_id}: {e}")
116
+
117
+
118
  @router.post("/add")
119
  async def create_product(
120
+ request: Request, db: Session = Depends(get_db)
121
+ ):
122
+ """Endpoint to add a new product, its ingredients, and associated markers."""
123
  try:
124
+ log_info("Create product endpoint called")
125
  data = await request.json()
126
  print("Received data:", data)
127
 
128
+ # Extract product details and data from request body
129
+ product_id = data.get("product_id")
130
+
131
+ image_names: List[str] = data.get("image_names")
132
 
133
+ product_data = {
134
+ "ingredients_text": data.get("ingredients_text", ""),
135
+ "brands": data.get("brands", ""),
136
+ "generic_name": data.get("generic_name", ""),
137
+ "nutriscore": data.get("nutriscore", None),
138
+ "nutrient_levels": data.get("nutrient_levels", None),
139
+ "nutriments": data.get("nutriments", None),
140
+ "data_quality_warnings": data.get("data_quality_warnings", None),
141
+ }
142
+ product_service = ProductService(db)
143
+ if not product_id:
144
+ product = product_service.add_product(data.get("name"), product_data["ingredients_text"])
145
+ product_id = product.id
146
+ await add_product_to_database(product_id, image_names, db, product_data)
147
  return JSONResponse(
148
+ {"message": "Product data and image processed successfully"}
149
  )
150
 
151
+ except HTTPException as e:
152
+ return JSONResponse({"error": e.detail}, status_code=e.status_code)
153
  except Exception as e:
154
+ return JSONResponse({"error": str(e)}, status_code=500)
 
services/ingredients.py CHANGED
@@ -4,54 +4,57 @@ from fastapi import HTTPException
4
  from cachetools import cached, TTLCache
5
  from typing import List, Dict, Any
6
  import requests
7
- from utils.fetch_data import fetch_product_data_from_api
8
 
9
  cache = TTLCache(maxsize=100, ttl=300)
10
 
11
- def get_ingredient_by_name(db: Session, name: str) -> Ingredient:
12
- return db.query(Ingredient).filter(Ingredient.name == name).first()
 
13
 
14
- @cached(cache)
15
- def fetch_ingredient_data_from_api(name: str) -> Dict[str, Any]:
16
- url = f"https://api.example.com/ingredients/{name}"
17
- response = requests.get(url)
18
- if response.status_code != 200:
19
- raise HTTPException(status_code=response.status_code, detail=f"Failed to fetch data for ingredient {name}")
20
- data = response.json()
21
- return {
22
- "nutritional_info": data.get("nutritional_info", {}),
23
- "description": data.get("description", ""),
24
- "origin": data.get("origin", ""),
25
- "allergens": data.get("allergens", ""),
26
- "vegan": data.get("vegan", False),
27
- "vegetarian": data.get("vegetarian", False)
28
- }
29
 
30
- def get_ingredient_data(db: Session, name: str) -> Dict[str, Any]:
31
- ingredient = get_ingredient_by_name(db, name)
32
- if ingredient:
 
 
 
 
33
  return {
34
- "nutritional_info": ingredient.nutritional_info,
35
- "description": ingredient.description,
36
- "origin": ingredient.origin,
37
- "allergens": ingredient.allergens,
38
- "vegan": ingredient.vegan,
39
- "vegetarian": ingredient.vegetarian
40
  }
41
- data = fetch_ingredient_data_from_api(name)
42
- save_ingredient_data(db, name, data)
43
- return data
44
 
45
- def save_ingredient_data(db: Session, name: str, data: Dict[str, Any]):
46
- ingredient = Ingredient(
47
- name=name,
48
- nutritional_info=data.get("nutritional_info", {}),
49
- description=data.get("description", ""),
50
- origin=data.get("origin", ""),
51
- allergens=data.get("allergens", ""),
52
- vegan=data.get("vegan", False),
53
- vegetarian=data.get("vegetarian", False)
54
- )
55
- db.add(ingredient)
56
- db.commit()
57
- db.refresh(ingredient)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  from cachetools import cached, TTLCache
5
  from typing import List, Dict, Any
6
  import requests
 
7
 
8
  cache = TTLCache(maxsize=100, ttl=300)
9
 
10
+ class IngredientService:
11
+ def __init__(self, db: Session):
12
+ self.db = db
13
 
14
+ def get_ingredient_by_name(self, name: str) -> Ingredient:
15
+ return self.db.query(Ingredient).filter(Ingredient.name == name).first()
 
 
 
 
 
 
 
 
 
 
 
 
 
16
 
17
+ @cached(cache)
18
+ def fetch_ingredient_data_from_api(self, name: str) -> Dict[str, Any]:
19
+ url = f"https://api.example.com/ingredients/{name}"
20
+ response = requests.get(url)
21
+ if response.status_code != 200:
22
+ raise HTTPException(status_code=response.status_code, detail=f"Failed to fetch data for ingredient {name}")
23
+ data = response.json()
24
  return {
25
+ "nutritional_info": data.get("nutritional_info", {}),
26
+ "description": data.get("description", ""),
27
+ "origin": data.get("origin", ""),
28
+ "allergens": data.get("allergens", ""),
29
+ "vegan": data.get("vegan", False),
30
+ "vegetarian": data.get("vegetarian", False)
31
  }
 
 
 
32
 
33
+ def get_ingredient_data(self, name: str) -> Dict[str, Any]:
34
+ ingredient = self.get_ingredient_by_name(name)
35
+ if ingredient:
36
+ return {
37
+ "nutritional_info": ingredient.nutritional_info,
38
+ "description": ingredient.description,
39
+ "origin": ingredient.origin,
40
+ "allergens": ingredient.allergens,
41
+ "vegan": ingredient.vegan,
42
+ "vegetarian": ingredient.vegetarian
43
+ }
44
+ data = self.fetch_ingredient_data_from_api(name)
45
+ self.save_ingredient_data(name, data)
46
+ return data
47
+
48
+ def save_ingredient_data(self, name: str, data: Dict[str, Any]):
49
+ ingredient = Ingredient(
50
+ name=name,
51
+ nutritional_info=data.get("nutritional_info", {}),
52
+ description=data.get("description", ""),
53
+ origin=data.get("origin", ""),
54
+ allergens=data.get("allergens", ""),
55
+ vegan=data.get("vegan", False),
56
+ vegetarian=data.get("vegetarian", False)
57
+ )
58
+ self.db.add(ingredient)
59
+ self.db.commit()
60
+ self.db.refresh(ingredient)
services/product_service.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from sqlalchemy.orm import Session
2
+ from typing import Optional
3
+ from db.models import Product
4
+
5
+ class ProductService:
6
+ def __init__(self, db: Session):
7
+ self.db = db
8
+
9
+ def add_product(self, name: str, ingredients_text: str) -> Product:
10
+ product = Product(product_name=name, ingredients_text=ingredients_text)
11
+ self.db.add(product)
12
+ self.db.commit()
13
+ self.db.refresh(product)
14
+ return product
15
+
16
+ def get_product_by_id(self, product_id: int) -> Optional[Product]:
17
+ return self.db.query(Product).filter(Product.id == product_id).first()