File size: 7,906 Bytes
8e64a15
e1f3336
 
 
 
 
 
 
74c93d7
e1f3336
 
2513d15
e1f3336
2513d15
e1f3336
 
 
 
 
 
 
 
 
36f4514
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2513d15
e1f3336
2513d15
 
e1f3336
74c93d7
e1f3336
 
 
19ee958
8d64bf6
19ee958
 
e1f3336
 
74c93d7
e1f3336
 
 
 
74c93d7
e1f3336
 
19ee958
e1f3336
74c93d7
e1f3336
 
741f169
74c93d7
 
 
e1f3336
741f169
e1f3336
 
19ee958
54b8e46
74c93d7
19ee958
 
 
54b8e46
74c93d7
54b8e46
74c93d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54b8e46
74c93d7
54b8e46
 
 
74c93d7
 
 
 
 
 
54b8e46
19ee958
 
 
e1f3336
 
74c93d7
e1f3336
 
 
 
 
 
 
 
 
741f169
 
2513d15
 
 
74c93d7
 
 
 
 
 
 
 
 
 
 
 
 
 
2513d15
 
 
8e64a15
741f169
e1f3336
 
 
 
2513d15
e1f3336
741f169
e1f3336
 
2513d15
e1f3336
2513d15
 
74c93d7
 
e1f3336
 
 
 
 
 
 
 
 
 
 
74c93d7
e1f3336
 
74c93d7
 
 
 
 
e1f3336
 
 
19ee958
36f4514
e1f3336
 
 
 
 
19ee958
74c93d7
 
e1f3336
 
 
 
 
74c93d7
 
 
 
 
 
e1f3336
 
 
74c93d7
 
36f4514
74c93d7
 
 
e1f3336
2513d15
e1f3336
2513d15
 
e1f3336
74c93d7
e1f3336
74c93d7
 
 
 
e1f3336
 
8e64a15
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
import streamlit as st
from PIL import Image
import os
import base64
import io
from dotenv import load_dotenv
from groq import Groq
from reportlab.lib.pagesizes import letter
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as ReportLabImage
from reportlab.lib.styles import getSampleStyleSheet

# ======================
# CONFIGURATION SETTINGS
# ======================
st.set_page_config(
    page_title="Smart Diet Analyzer",
    page_icon="🍎",
    layout="wide",
    initial_sidebar_state="expanded"
)

ALLOWED_FILE_TYPES = ['png', 'jpg', 'jpeg']

# ======================
# RERUN HELPER FUNCTION
# ======================
def rerun():
    """
    Helper function to rerun the app.
    Tries to use st.experimental_rerun if available; otherwise, does nothing.
    """
    if hasattr(st, "experimental_rerun"):
        st.experimental_rerun()
    else:
        # Fallback: you might consider raising an exception to force a rerun.
        # However, this is not recommended for production. Instead, you might simply
        # notify the user to manually refresh the page.
        st.warning("Please refresh the page to update the results.")

# ======================
# UTILITY FUNCTIONS
# ======================

def initialize_api_client():
    """Initialize Groq API client."""
    load_dotenv()
    api_key = os.getenv("GROQ_API_KEY")
    if not api_key:
        st.error("API key not found. Please verify .env configuration.")
        st.stop()
    return Groq(api_key=api_key)


def encode_image(image_path):
    """Encode an image to base64."""
    try:
        with open(image_path, "rb") as img_file:
            return base64.b64encode(img_file.read()).decode("utf-8")
    except FileNotFoundError:
        st.error(f"Logo file not found at {image_path}")
        return ""


def process_image(uploaded_file):
    """Convert image to base64 string."""
    try:
        image = Image.open(uploaded_file)
        buffer = io.BytesIO()
        fmt = image.format if image.format else "PNG"
        image.save(buffer, format=fmt)
        return base64.b64encode(buffer.getvalue()).decode('utf-8'), fmt
    except Exception as e:
        st.error(f"Image processing error: {e}")
        return None, None


def generate_pdf(report_text, logo_b64):
    """Generate a PDF report with logo."""
    buffer = io.BytesIO()
    doc = SimpleDocTemplate(buffer, pagesize=letter)
    styles = getSampleStyleSheet()
    
    story = []
    
    # If a logo is provided, decode and add it
    if logo_b64:
        try:
            logo_data = base64.b64decode(logo_b64)
            logo_image = Image.open(io.BytesIO(logo_data))
            logo_width, logo_height = logo_image.size
            logo_aspect = logo_height / logo_width
            max_logo_width = 150
            logo_width = min(logo_width, max_logo_width)
            logo_height = int(logo_width * logo_aspect)
            
            logo = ReportLabImage(io.BytesIO(logo_data), width=logo_width, height=logo_height)
            story.append(logo)
            story.append(Spacer(1, 12))
        except Exception as e:
            st.error(f"Error adding logo to PDF: {e}")
    
    story.extend([
        Paragraph("<b>Nutrition Analysis Report</b>", styles['Title']),
        Spacer(1, 12),
        Paragraph(report_text.replace('\n', '<br/>'), styles['BodyText'])
    ])
    
    try:
        doc.build(story)
    except Exception as e:
        st.error(f"Error generating PDF: {e}")
    
    buffer.seek(0)
    return buffer


def generate_analysis(uploaded_file, client):
    """Generate AI-powered food analysis."""
    base64_image, img_format = process_image(uploaded_file)
    if not base64_image:
        return None
    
    image_url = f"data:image/{img_format.lower()};base64,{base64_image}"
    
    try:
        response = client.chat.completions.create(
            model="llama-3.2-11b-vision-preview",
            messages=[
                {
                    "role": "user",
                    "content": [
                        {"type": "text", "text": """
You are an expert nutritionist with advanced image analysis capabilities.  
Your task is to analyze the provided image, identify all visible food items, and estimate their calorie content with high accuracy.  
**Instructions:**  
- Identify and list each food item visible in the image.  
- For each item, estimate the calorie content based on standard nutritional data, considering portion size, cooking method, and food density.  
- Clearly mark any calorie estimate as "approximate" if based on assumptions due to unclear details.  
- Calculate and provide the total estimated calories for the entire meal.  
**Output Format:**  
- Food Item 1: [Name] – Estimated Calories: [value] kcal  
- Food Item 2: [Name] – Estimated Calories: [value] kcal  
- ...  
- **Total Estimated Calories:** [value] kcal  

If the image lacks sufficient detail or is unclear, specify the limitations and include your confidence level in the estimate as a percentage.
                        """},
                        {"type": "image_url", "image_url": {"url": image_url}}
                    ]
                }
            ],
            temperature=0.2,
            max_tokens=400,
            top_p=0.5
        )
        return response.choices[0].message.content
    except Exception as e:
        st.error(f"API communication error: {e}")
        return None

# ======================
# UI COMPONENTS
# ======================

def display_main_interface(logo_b64):
    """Render primary application interface."""
    st.markdown(f"""
        <div style="text-align: center;">
            <img src="data:image/png;base64,{logo_b64}" width="100">
            <h2 style="color: #4CAF50;">Smart Diet Analyzer</h2>
            <p style="color: #FF6347;">AI-Powered Food & Nutrition Analysis</p>
        </div>
    """, unsafe_allow_html=True)
    
    st.markdown("---")
    
    if st.session_state.get('analysis_result'):
        col1, col2 = st.columns(2)
        
        with col1:
            pdf_report = generate_pdf(st.session_state.analysis_result, logo_b64)
            st.download_button("πŸ“„ Download Nutrition Report",
                               data=pdf_report,
                               file_name="nutrition_report.pdf",
                               mime="application/pdf")
        
        with col2:
            if st.button("Clear Analysis πŸ—‘οΈ"):
                st.session_state.pop('analysis_result')
                rerun()
    
    if st.session_state.get('analysis_result'):
        st.markdown("### 🎯 Nutrition Analysis Report")
        st.info(st.session_state.analysis_result)


def render_sidebar(client, logo_b64):
    """Create sidebar UI elements."""
    with st.sidebar:
        st.subheader("Image Upload")
        uploaded_file = st.file_uploader("Upload Food Image", type=ALLOWED_FILE_TYPES)
        
        if uploaded_file:
            try:
                image = Image.open(uploaded_file)
                st.image(image, caption="Uploaded Food Image")
            except Exception as e:
                st.error(f"Error displaying image: {e}")
            
            if st.button("Analyze Meal 🍽️"):
                with st.spinner("Analyzing image..."):
                    report = generate_analysis(uploaded_file, client)
                    if report:
                        st.session_state.analysis_result = report
                        rerun()
                    else:
                        st.error("Failed to generate analysis.")


# ======================
# APPLICATION ENTRYPOINT
# ======================

def main():
    """Primary application controller."""
    client = initialize_api_client()
    logo_b64 = encode_image("src/logo.png")
    display_main_interface(logo_b64)
    render_sidebar(client, logo_b64)


if __name__ == "__main__":
    main()