Spaces:
Sleeping
Sleeping
Upload 4 files
Browse files- app.py +178 -0
- final_substitution.csv +0 -0
- ingredient_substitution_model.pkl +3 -0
- tfidf_vectorizer.pkl +3 -0
app.py
ADDED
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from flask import Flask, request, jsonify
|
2 |
+
from flask_cors import CORS
|
3 |
+
from werkzeug.utils import secure_filename
|
4 |
+
import os
|
5 |
+
import pandas as pd
|
6 |
+
import numpy as np
|
7 |
+
import joblib
|
8 |
+
import re
|
9 |
+
import string
|
10 |
+
from PIL import Image
|
11 |
+
from pymongo import MongoClient
|
12 |
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
13 |
+
from sklearn.linear_model import LogisticRegression
|
14 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
15 |
+
import google.generativeai as genai
|
16 |
+
import speech_recognition as sr
|
17 |
+
import pyttsx3
|
18 |
+
|
19 |
+
app = Flask(__name__)
|
20 |
+
CORS(app) # Enable CORS for all routes
|
21 |
+
|
22 |
+
# === Configuration ===
|
23 |
+
UPLOAD_FOLDER = 'uploads'
|
24 |
+
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'pdf', 'docx', 'wav', 'mp3'}
|
25 |
+
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
26 |
+
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB
|
27 |
+
|
28 |
+
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
29 |
+
|
30 |
+
# === Gemini API Config ===
|
31 |
+
genai.configure(api_key="AIzaSyC5-RTbNHj7PX7R-8JOwwUxk6RgWDQtfcA")
|
32 |
+
text_model = genai.GenerativeModel("gemini-1.5-pro-latest")
|
33 |
+
vision_model = genai.GenerativeModel("gemini-1.5-flash")
|
34 |
+
|
35 |
+
# === Voice Tools ===
|
36 |
+
recognizer = sr.Recognizer()
|
37 |
+
engine = pyttsx3.init()
|
38 |
+
|
39 |
+
# === Load Dataset ===
|
40 |
+
df = pd.read_csv(r"C:\Users\dhanyashree\OneDrive\Desktop\final_substitution.csv")
|
41 |
+
df.columns = df.columns.str.lower()
|
42 |
+
df.rename(columns={'food label': 'ingredient', 'substitution label': 'substitute'}, inplace=True)
|
43 |
+
if 'ingredient' not in df.columns or 'substitute' not in df.columns:
|
44 |
+
raise ValueError("CSV must contain 'ingredient' and 'substitute' columns.")
|
45 |
+
|
46 |
+
# === Preprocessing & Model Training ===
|
47 |
+
def preprocess_text(text):
|
48 |
+
if isinstance(text, str):
|
49 |
+
text = text.lower()
|
50 |
+
text = re.sub(r'\d+', '', text)
|
51 |
+
text = text.translate(str.maketrans('', '', string.punctuation))
|
52 |
+
text = text.strip()
|
53 |
+
return text
|
54 |
+
|
55 |
+
df['ingredient_clean'] = df['ingredient'].apply(preprocess_text)
|
56 |
+
df['substitute_clean'] = df['substitute'].apply(preprocess_text)
|
57 |
+
|
58 |
+
vectorizer = TfidfVectorizer()
|
59 |
+
X = vectorizer.fit_transform(df['ingredient_clean'])
|
60 |
+
y = df['substitute_clean']
|
61 |
+
|
62 |
+
model_ml = LogisticRegression()
|
63 |
+
model_ml.fit(X, y)
|
64 |
+
|
65 |
+
# Save models
|
66 |
+
joblib.dump(model_ml, "ingredient_substitution_model.pkl")
|
67 |
+
joblib.dump(vectorizer, "tfidf_vectorizer.pkl")
|
68 |
+
|
69 |
+
# === MongoDB for Recipe Scaling ===
|
70 |
+
client = MongoClient("mongodb://localhost:27017/")
|
71 |
+
db = client.recipe_db
|
72 |
+
|
73 |
+
# === In-memory conversation history ===
|
74 |
+
conversation_history = []
|
75 |
+
|
76 |
+
# === Allowed File Types ===
|
77 |
+
def allowed_file(filename):
|
78 |
+
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
79 |
+
|
80 |
+
# === Helper: Ask Gemini Bot ===
|
81 |
+
def ask_baking_bot(user_input):
|
82 |
+
prompt = f"""
|
83 |
+
You are a professional pastry chef and baking expert.
|
84 |
+
Only answer questions about baking, pastry, and cooking.
|
85 |
+
User's Question: {user_input}
|
86 |
+
"""
|
87 |
+
response = text_model.generate_content(prompt)
|
88 |
+
return response.text
|
89 |
+
|
90 |
+
# === Helper: Suggest Substitute ===
|
91 |
+
def suggest_substitute(query):
|
92 |
+
query = preprocess_text(query)
|
93 |
+
query_vec = vectorizer.transform([query])
|
94 |
+
try:
|
95 |
+
predicted_substitute = model_ml.predict(query_vec)[0]
|
96 |
+
ai_verification = ask_baking_bot(f"Is {predicted_substitute} a valid substitute for {query}?")
|
97 |
+
if query.lower() in ai_verification.lower():
|
98 |
+
return f"β
ML & Gemini Verified Substitute: {predicted_substitute}"
|
99 |
+
except:
|
100 |
+
pass
|
101 |
+
similarity_scores = cosine_similarity(query_vec, X)
|
102 |
+
best_match_idx = np.argmax(similarity_scores)
|
103 |
+
return f"π Suggested Substitute (Similarity-Based): {df.iloc[best_match_idx]['substitute_clean']}"
|
104 |
+
|
105 |
+
# === Endpoint: Ask Question ===
|
106 |
+
@app.route('/api/ask', methods=['POST'])
|
107 |
+
def ask_question():
|
108 |
+
data = request.get_json()
|
109 |
+
if not data or 'question' not in data:
|
110 |
+
error_response = {"error": "Missing 'question' in request"}
|
111 |
+
print("β Error:", error_response)
|
112 |
+
return jsonify(error_response), 400
|
113 |
+
|
114 |
+
question = data['question']
|
115 |
+
response = ask_baking_bot(question)
|
116 |
+
|
117 |
+
conversation_history.append({"user": question, "assistant": response})
|
118 |
+
response_data = {"question": question, "response": response}
|
119 |
+
|
120 |
+
print("β
Response to frontend:", response_data)
|
121 |
+
return jsonify(response_data)
|
122 |
+
|
123 |
+
# === Endpoint: Analyze Image ===
|
124 |
+
@app.route('/api/analyze-image', methods=['POST'])
|
125 |
+
def analyze_image_endpoint():
|
126 |
+
if 'file' not in request.files:
|
127 |
+
error_response = {"error": "No file uploaded"}
|
128 |
+
print("β Error:", error_response)
|
129 |
+
return jsonify(error_response), 400
|
130 |
+
|
131 |
+
file = request.files['file']
|
132 |
+
if file.filename == '':
|
133 |
+
error_response = {"error": "No selected file"}
|
134 |
+
print("β Error:", error_response)
|
135 |
+
return jsonify(error_response), 400
|
136 |
+
|
137 |
+
if file and allowed_file(file.filename):
|
138 |
+
filename = secure_filename(file.filename)
|
139 |
+
filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
140 |
+
file.save(filepath)
|
141 |
+
|
142 |
+
try:
|
143 |
+
analysis = analyze_food_image(filepath)
|
144 |
+
os.unlink(filepath)
|
145 |
+
response_data = {"image": filename, "analysis": analysis}
|
146 |
+
print("π· Image Analysis Response:", response_data)
|
147 |
+
return jsonify(response_data)
|
148 |
+
except Exception as e:
|
149 |
+
error_response = {"error": str(e)}
|
150 |
+
print("β Error:", error_response)
|
151 |
+
return jsonify(error_response), 500
|
152 |
+
|
153 |
+
error_response = {"error": "Invalid file type"}
|
154 |
+
print("β Error:", error_response)
|
155 |
+
return jsonify(error_response), 400
|
156 |
+
|
157 |
+
# === Helper: Analyze Image with Gemini ===
|
158 |
+
def analyze_food_image(image_path):
|
159 |
+
try:
|
160 |
+
image = Image.open(image_path)
|
161 |
+
if image.mode != "RGB":
|
162 |
+
image = image.convert("RGB")
|
163 |
+
prompt = """
|
164 |
+
You are a professional chef and baking expert.
|
165 |
+
Analyze this food image and provide:
|
166 |
+
1. Likely food item
|
167 |
+
2. Key ingredients visible
|
168 |
+
3. Comments on preparation and doneness
|
169 |
+
4. Tip for improvement if needed
|
170 |
+
"""
|
171 |
+
response = vision_model.generate_content([prompt, image])
|
172 |
+
return response.text
|
173 |
+
except Exception as e:
|
174 |
+
return f"Error analyzing image: {str(e)}"
|
175 |
+
|
176 |
+
# === Run App ===
|
177 |
+
if __name__ == '__main__':
|
178 |
+
app.run(debug=True)
|
final_substitution.csv
ADDED
The diff for this file is too large to render.
See raw diff
|
|
ingredient_substitution_model.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:b2a256abf44dfa50892d27ce6d30810993d812229ed0061b157236b3667ebd0e
|
3 |
+
size 2848919
|
tfidf_vectorizer.pkl
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:5ae3af8094996956e893987f747481e5c6b3b595c3db00259ea67397943b6f11
|
3 |
+
size 15021
|