Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -6,7 +6,7 @@ import io
|
|
6 |
from dotenv import load_dotenv
|
7 |
from groq import Groq
|
8 |
from reportlab.lib.pagesizes import letter
|
9 |
-
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
|
10 |
from reportlab.lib.styles import getSampleStyleSheet
|
11 |
|
12 |
# ======================
|
@@ -26,7 +26,7 @@ ALLOWED_FILE_TYPES = ['png', 'jpg', 'jpeg']
|
|
26 |
# ======================
|
27 |
|
28 |
def initialize_api_client():
|
29 |
-
"""Initialize Groq API client"""
|
30 |
load_dotenv()
|
31 |
api_key = os.getenv("GROQ_API_KEY")
|
32 |
if not api_key:
|
@@ -36,63 +36,72 @@ def initialize_api_client():
|
|
36 |
|
37 |
|
38 |
def encode_image(image_path):
|
39 |
-
"""Encode an image to base64"""
|
40 |
try:
|
41 |
with open(image_path, "rb") as img_file:
|
42 |
return base64.b64encode(img_file.read()).decode("utf-8")
|
43 |
except FileNotFoundError:
|
|
|
44 |
return ""
|
45 |
|
46 |
|
47 |
def process_image(uploaded_file):
|
48 |
-
"""Convert image to base64 string"""
|
49 |
try:
|
50 |
image = Image.open(uploaded_file)
|
51 |
buffer = io.BytesIO()
|
52 |
-
image.
|
53 |
-
|
|
|
|
|
54 |
except Exception as e:
|
55 |
st.error(f"Image processing error: {e}")
|
56 |
return None, None
|
57 |
|
58 |
|
59 |
def generate_pdf(report_text, logo_b64):
|
60 |
-
"""Generate a PDF report with logo"""
|
61 |
buffer = io.BytesIO()
|
62 |
doc = SimpleDocTemplate(buffer, pagesize=letter)
|
63 |
styles = getSampleStyleSheet()
|
64 |
|
65 |
-
|
66 |
-
logo_data = base64.b64decode(logo_b64)
|
67 |
-
logo_image = Image.open(io.BytesIO(logo_data))
|
68 |
|
69 |
-
#
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
|
76 |
-
|
77 |
-
logo = ReportLabImage(io.BytesIO(logo_data), width=logo_width, height=logo_height)
|
78 |
-
|
79 |
-
# Build the PDF content
|
80 |
-
story = [
|
81 |
-
logo, # Add the logo at the top of the page
|
82 |
-
Spacer(1, 12), # Space after the logo
|
83 |
Paragraph("<b>Nutrition Analysis Report</b>", styles['Title']),
|
84 |
Spacer(1, 12),
|
85 |
Paragraph(report_text.replace('\n', '<br/>'), styles['BodyText'])
|
86 |
-
]
|
|
|
|
|
|
|
|
|
|
|
87 |
|
88 |
-
doc.build(story)
|
89 |
buffer.seek(0)
|
90 |
return buffer
|
91 |
|
92 |
|
93 |
-
|
94 |
def generate_analysis(uploaded_file, client):
|
95 |
-
"""Generate AI-powered food analysis"""
|
96 |
base64_image, img_format = process_image(uploaded_file)
|
97 |
if not base64_image:
|
98 |
return None
|
@@ -107,21 +116,20 @@ def generate_analysis(uploaded_file, client):
|
|
107 |
"role": "user",
|
108 |
"content": [
|
109 |
{"type": "text", "text": """
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
"""},
|
126 |
{"type": "image_url", "image_url": {"url": image_url}}
|
127 |
]
|
@@ -140,11 +148,8 @@ def generate_analysis(uploaded_file, client):
|
|
140 |
# UI COMPONENTS
|
141 |
# ======================
|
142 |
|
143 |
-
def display_main_interface():
|
144 |
-
"""Render primary application interface"""
|
145 |
-
logo_b64 = encode_image("src/logo.png")
|
146 |
-
|
147 |
-
# HTML with inline styles to change text colors
|
148 |
st.markdown(f"""
|
149 |
<div style="text-align: center;">
|
150 |
<img src="data:image/png;base64,{logo_b64}" width="100">
|
@@ -156,48 +161,62 @@ def display_main_interface():
|
|
156 |
st.markdown("---")
|
157 |
|
158 |
if st.session_state.get('analysis_result'):
|
159 |
-
# Create two columns: one for download and one for clear button
|
160 |
-
col1, col2 = st.columns(
|
161 |
|
162 |
# Left column for the Download button
|
163 |
with col1:
|
164 |
-
pdf_report = generate_pdf(st.session_state.analysis_result)
|
165 |
-
st.download_button("π Download Nutrition Report",
|
|
|
|
|
|
|
166 |
|
167 |
# Right column for the Clear button
|
168 |
with col2:
|
169 |
if st.button("Clear Analysis ποΈ"):
|
170 |
st.session_state.pop('analysis_result')
|
171 |
-
st.
|
172 |
|
173 |
if st.session_state.get('analysis_result'):
|
174 |
st.markdown("### π― Nutrition Analysis Report")
|
175 |
st.info(st.session_state.analysis_result)
|
176 |
|
177 |
|
178 |
-
def render_sidebar(client):
|
179 |
-
"""Create sidebar UI elements"""
|
180 |
with st.sidebar:
|
181 |
st.subheader("Image Upload")
|
182 |
uploaded_file = st.file_uploader("Upload Food Image", type=ALLOWED_FILE_TYPES)
|
183 |
|
184 |
if uploaded_file:
|
185 |
-
|
|
|
|
|
|
|
|
|
|
|
186 |
if st.button("Analyze Meal π½οΈ"):
|
187 |
with st.spinner("Analyzing image..."):
|
188 |
report = generate_analysis(uploaded_file, client)
|
189 |
-
|
190 |
-
|
|
|
|
|
|
|
|
|
191 |
|
192 |
# ======================
|
193 |
# APPLICATION ENTRYPOINT
|
194 |
# ======================
|
195 |
|
196 |
def main():
|
197 |
-
"""Primary application controller"""
|
198 |
client = initialize_api_client()
|
199 |
-
|
200 |
-
|
|
|
|
|
201 |
|
202 |
if __name__ == "__main__":
|
203 |
main()
|
|
|
6 |
from dotenv import load_dotenv
|
7 |
from groq import Groq
|
8 |
from reportlab.lib.pagesizes import letter
|
9 |
+
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as ReportLabImage
|
10 |
from reportlab.lib.styles import getSampleStyleSheet
|
11 |
|
12 |
# ======================
|
|
|
26 |
# ======================
|
27 |
|
28 |
def initialize_api_client():
|
29 |
+
"""Initialize Groq API client."""
|
30 |
load_dotenv()
|
31 |
api_key = os.getenv("GROQ_API_KEY")
|
32 |
if not api_key:
|
|
|
36 |
|
37 |
|
38 |
def encode_image(image_path):
|
39 |
+
"""Encode an image to base64."""
|
40 |
try:
|
41 |
with open(image_path, "rb") as img_file:
|
42 |
return base64.b64encode(img_file.read()).decode("utf-8")
|
43 |
except FileNotFoundError:
|
44 |
+
st.error(f"Logo file not found at {image_path}")
|
45 |
return ""
|
46 |
|
47 |
|
48 |
def process_image(uploaded_file):
|
49 |
+
"""Convert image to base64 string."""
|
50 |
try:
|
51 |
image = Image.open(uploaded_file)
|
52 |
buffer = io.BytesIO()
|
53 |
+
# Ensure image.format is valid; default to PNG if not available.
|
54 |
+
fmt = image.format if image.format else "PNG"
|
55 |
+
image.save(buffer, format=fmt)
|
56 |
+
return base64.b64encode(buffer.getvalue()).decode('utf-8'), fmt
|
57 |
except Exception as e:
|
58 |
st.error(f"Image processing error: {e}")
|
59 |
return None, None
|
60 |
|
61 |
|
62 |
def generate_pdf(report_text, logo_b64):
|
63 |
+
"""Generate a PDF report with logo."""
|
64 |
buffer = io.BytesIO()
|
65 |
doc = SimpleDocTemplate(buffer, pagesize=letter)
|
66 |
styles = getSampleStyleSheet()
|
67 |
|
68 |
+
story = []
|
|
|
|
|
69 |
|
70 |
+
# If a logo is provided, decode and add it
|
71 |
+
if logo_b64:
|
72 |
+
try:
|
73 |
+
logo_data = base64.b64decode(logo_b64)
|
74 |
+
logo_image = Image.open(io.BytesIO(logo_data))
|
75 |
+
# Resize the logo (adjust max width as needed)
|
76 |
+
logo_width, logo_height = logo_image.size
|
77 |
+
logo_aspect = logo_height / logo_width
|
78 |
+
max_logo_width = 150
|
79 |
+
logo_width = min(logo_width, max_logo_width)
|
80 |
+
logo_height = int(logo_width * logo_aspect)
|
81 |
+
|
82 |
+
logo = ReportLabImage(io.BytesIO(logo_data), width=logo_width, height=logo_height)
|
83 |
+
story.append(logo)
|
84 |
+
story.append(Spacer(1, 12))
|
85 |
+
except Exception as e:
|
86 |
+
st.error(f"Error adding logo to PDF: {e}")
|
87 |
|
88 |
+
story.extend([
|
|
|
|
|
|
|
|
|
|
|
|
|
89 |
Paragraph("<b>Nutrition Analysis Report</b>", styles['Title']),
|
90 |
Spacer(1, 12),
|
91 |
Paragraph(report_text.replace('\n', '<br/>'), styles['BodyText'])
|
92 |
+
])
|
93 |
+
|
94 |
+
try:
|
95 |
+
doc.build(story)
|
96 |
+
except Exception as e:
|
97 |
+
st.error(f"Error generating PDF: {e}")
|
98 |
|
|
|
99 |
buffer.seek(0)
|
100 |
return buffer
|
101 |
|
102 |
|
|
|
103 |
def generate_analysis(uploaded_file, client):
|
104 |
+
"""Generate AI-powered food analysis."""
|
105 |
base64_image, img_format = process_image(uploaded_file)
|
106 |
if not base64_image:
|
107 |
return None
|
|
|
116 |
"role": "user",
|
117 |
"content": [
|
118 |
{"type": "text", "text": """
|
119 |
+
You are an expert nutritionist with advanced image analysis capabilities.
|
120 |
+
Your task is to analyze the provided image, identify all visible food items, and estimate their calorie content with high accuracy.
|
121 |
+
**Instructions:**
|
122 |
+
- Identify and list each food item visible in the image.
|
123 |
+
- For each item, estimate the calorie content based on standard nutritional data, considering portion size, cooking method, and food density.
|
124 |
+
- Clearly mark any calorie estimate as "approximate" if based on assumptions due to unclear details.
|
125 |
+
- Calculate and provide the total estimated calories for the entire meal.
|
126 |
+
**Output Format:**
|
127 |
+
- Food Item 1: [Name] β Estimated Calories: [value] kcal
|
128 |
+
- Food Item 2: [Name] β Estimated Calories: [value] kcal
|
129 |
+
- ...
|
130 |
+
- **Total Estimated Calories:** [value] kcal
|
131 |
+
|
132 |
+
If the image lacks sufficient detail or is unclear, specify the limitations and include your confidence level in the estimate as a percentage.
|
|
|
133 |
"""},
|
134 |
{"type": "image_url", "image_url": {"url": image_url}}
|
135 |
]
|
|
|
148 |
# UI COMPONENTS
|
149 |
# ======================
|
150 |
|
151 |
+
def display_main_interface(logo_b64):
|
152 |
+
"""Render primary application interface."""
|
|
|
|
|
|
|
153 |
st.markdown(f"""
|
154 |
<div style="text-align: center;">
|
155 |
<img src="data:image/png;base64,{logo_b64}" width="100">
|
|
|
161 |
st.markdown("---")
|
162 |
|
163 |
if st.session_state.get('analysis_result'):
|
164 |
+
# Create two columns: one for download and one for the clear button
|
165 |
+
col1, col2 = st.columns(2)
|
166 |
|
167 |
# Left column for the Download button
|
168 |
with col1:
|
169 |
+
pdf_report = generate_pdf(st.session_state.analysis_result, logo_b64)
|
170 |
+
st.download_button("π Download Nutrition Report",
|
171 |
+
data=pdf_report,
|
172 |
+
file_name="nutrition_report.pdf",
|
173 |
+
mime="application/pdf")
|
174 |
|
175 |
# Right column for the Clear button
|
176 |
with col2:
|
177 |
if st.button("Clear Analysis ποΈ"):
|
178 |
st.session_state.pop('analysis_result')
|
179 |
+
st.experimental_rerun()
|
180 |
|
181 |
if st.session_state.get('analysis_result'):
|
182 |
st.markdown("### π― Nutrition Analysis Report")
|
183 |
st.info(st.session_state.analysis_result)
|
184 |
|
185 |
|
186 |
+
def render_sidebar(client, logo_b64):
|
187 |
+
"""Create sidebar UI elements."""
|
188 |
with st.sidebar:
|
189 |
st.subheader("Image Upload")
|
190 |
uploaded_file = st.file_uploader("Upload Food Image", type=ALLOWED_FILE_TYPES)
|
191 |
|
192 |
if uploaded_file:
|
193 |
+
try:
|
194 |
+
image = Image.open(uploaded_file)
|
195 |
+
st.image(image, caption="Uploaded Food Image")
|
196 |
+
except Exception as e:
|
197 |
+
st.error(f"Error displaying image: {e}")
|
198 |
+
|
199 |
if st.button("Analyze Meal π½οΈ"):
|
200 |
with st.spinner("Analyzing image..."):
|
201 |
report = generate_analysis(uploaded_file, client)
|
202 |
+
if report:
|
203 |
+
st.session_state.analysis_result = report
|
204 |
+
st.experimental_rerun()
|
205 |
+
else:
|
206 |
+
st.error("Failed to generate analysis.")
|
207 |
+
|
208 |
|
209 |
# ======================
|
210 |
# APPLICATION ENTRYPOINT
|
211 |
# ======================
|
212 |
|
213 |
def main():
|
214 |
+
"""Primary application controller."""
|
215 |
client = initialize_api_client()
|
216 |
+
logo_b64 = encode_image("src/logo.png")
|
217 |
+
display_main_interface(logo_b64)
|
218 |
+
render_sidebar(client, logo_b64)
|
219 |
+
|
220 |
|
221 |
if __name__ == "__main__":
|
222 |
main()
|