Packaged-Food-Explorer/app.py
2023-05-11 20:39:48 +07:00

327 lines
13 KiB
Python

import os
import sqlite3
import tkinter as tk
from tkinter import ttk
import pandas as pd
import requests
import io
from PIL import Image, ImageTk
from Essential.prepare_db import prepare_db
from Essential.FoodSearch import FoodSearch
import threading
import sqlite3
import pandas as pd
from Essential.plotter import plotter
import tempfile
import webview
class App:
def __init__(self, master):
self.master = master
self.master.title('Package Food Database')
self.df = pd.read_sql_query("SELECT * FROM food_data", sqlite3.connect(r"Essential\data\food_data.db"))
self.plotter = plotter()
# Search food from database -----------------
try:
self.food_search = FoodSearch()
except FileNotFoundError:
prepare_db()
self.food_search = FoodSearch()
self.search_var = tk.StringVar()
self.search_var.trace('w', self.search_callback)
self.results_frame = ttk.Frame(self.master)
self.results_frame.grid(row=0, column=0, columnspan=1, rowspan=2, sticky="nsew")
self.results_frame.rowconfigure(0, weight=1)
self.results_frame.columnconfigure(0, weight=1)
self.scrollbar = ttk.Scrollbar(self.results_frame)
self.scrollbar.grid(row=0, column=2,rowspan=2, sticky="ns")
self.results_listbox = tk.Listbox(self.results_frame, yscrollcommand=self.scrollbar.set,height=20,width=20,selectmode=tk.SINGLE)
self.results_listbox.grid(row=0, column=0, columnspan=1, padx=5, pady=5, sticky="nsew")
self.scrollbar.config(command=self.results_listbox.yview)
# Filter frame -----------------
self.filter_frame = ttk.LabelFrame(self.master, text="Filter")
self.filter_frame.grid(row=0, column=1, padx=10, pady=10, sticky="nsew")
# Filter components -----------------
# Search
self.search_entry = ttk.Entry(self.filter_frame, textvariable=self.search_var)
self.search_entry.grid(row=0, column=3, padx=10, pady=10, sticky="nsew")
self.search_button = ttk.Button(self.filter_frame, text="Search", command=self.start_search)
self.search_button.grid(row=0, column=4, padx=10, pady=10, sticky="nsew")
# Filter
self.country_var = tk.StringVar()
self.country_dropdown = ttk.Combobox(self.filter_frame, textvariable=self.country_var, values=["Any" ,"Thai", "Japan", "US"])
self.country_dropdown.grid(row=0, column=0, padx=10, pady=5, sticky="nsew")
self.organic_var = tk.IntVar()
self.organic_checkbox = ttk.Checkbutton(self.filter_frame, text="Organic", variable=self.organic_var)
self.organic_checkbox.grid(row=1, column=0, padx=10, pady=5, sticky="nsew")
self.plant_based_var = tk.IntVar()
self.plant_based_checkbox = ttk.Checkbutton(self.filter_frame, text="Plant-Based", variable=self.plant_based_var)
self.plant_based_checkbox.grid(row=2, column=0, padx=10, pady=5, sticky="nsew")
self.beverages_var = tk.IntVar()
self.beverages_checkbox = ttk.Checkbutton(self.filter_frame, text="Beverages", variable=self.beverages_var)
self.beverages_checkbox.grid(row=3, column=0, padx=10, pady=5, sticky="nsew")
self.snack_var = tk.IntVar()
self.snack_checkbox = ttk.Checkbutton(self.filter_frame, text="Snack", variable=self.snack_var)
self.snack_checkbox.grid(row=4, column=0, padx=10, pady=5, sticky="nsew")
self.calories_var = tk.StringVar()
self.calories_entry = ttk.Entry(self.filter_frame, textvariable=self.calories_var)
self.calories_entry.grid(row=5, column=0, padx=10, pady=5, sticky="nsew")
self.nutrient_var = tk.StringVar()
self.nutrient_dropdown = ttk.Combobox(self.filter_frame, textvariable=self.nutrient_var, values=["Protein", "Carbohydrates", "Fat", ""])
self.nutrient_dropdown.grid(row=6, column=0, padx=10, pady=5, sticky="nsew")
self.nutrient_value_var = tk.StringVar()
self.nutrient_value_entry = ttk.Entry(self.filter_frame, textvariable=self.nutrient_value_var)
self.nutrient_value_entry.grid(row=7, column=0, padx=10, pady=5, sticky="nsew")
# Filter components (continued) -----------------
self.nutrient_operator_var = tk.StringVar()
self.nutrient_operator_dropdown = ttk.Combobox(self.filter_frame, textvariable=self.nutrient_operator_var, values=["<", ">", "="])
self.nutrient_operator_dropdown.grid(row=8, column=0, padx=10, pady=5, sticky="nsew")
# Image of food -----------------
self.image_frame = ttk.Frame(self.master, borderwidth=2, relief=tk.SUNKEN)
self.image_frame.grid(row=2, column=0, padx=10, pady=10, sticky="nsew", rowspan=2)
self.image_frame.grid_propagate(1)
self.image_label = ttk.Label(self.image_frame)
self.image_label.pack(fill=tk.BOTH, expand=True)
# Progress bar -----------------
self.process_frame = tk.Frame(root)
self.process_frame.grid(row=4, column=0, columnspan=1, padx=10, pady=10, sticky="ew")
s = ttk.Style()
s.configure("red.Horizontal.TProgressbar", foreground='red', background='red')
# Stackoverflow.com https://stackoverflow.com/questions/13510882/how-to-change-ttk-progressbar-color-in-python
self.progress_bar = ttk.Progressbar(self.process_frame, style="red.Horizontal.TProgressbar", orient="horizontal",
length=700, mode="indeterminate")
self.progress_bar.grid(row=0, column=0)
# Default image (Not Found) -----------------
self.default_image_path = 'resources/notfound.png' # Replace with the correct path to your default image
self.default_image = ImageTk.PhotoImage(Image.open(self.default_image_path))
self.default_image_label = ttk.Label(self.image_frame, image=self.default_image)
# self.default_image_label.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
self.default_image_label.pack(anchor='w', fill=tk.NONE)
# Nutrient Frame
self.nutrient_frame = ttk.LabelFrame(self.master, text="Nutrient")
self.nutrient_frame.grid(row=2, column=1, padx=10, pady=10, sticky="nsew", rowspan=2)
self.nutrient_table = NutrientTableHolder(self.nutrient_frame)
self.nutrient_table.create_table()
# Others Frame (Graph/Analyze)
self.graph_frame = ttk.LabelFrame(self.master, text="Bar Macronutrients Graph")
self.graph_frame.grid(row=0, column=2, padx=10, pady=10, sticky="nsew")
self.fullview = ttk.LabelFrame(self.master, text="Options")
self.fullview.grid(row=1, column=2, padx=10, pady=10, sticky="nsew")
# * Configure the window size and position
# self.master.attributes('-fullscreen', True)
width= self.master.winfo_screenwidth()
height= self.master.winfo_screenheight()
self.master.geometry("%dx%d" % (width, height))
# LIST BOX selected FUNC
def on_item_selected(self, event):
widget = event.widget
selection = widget.curselection()
if selection:
index = selection[0]
value = widget.get(index)
self.selected_item = value
self.show_image(self.selected_item)
self.nutrient_table.nutrient_labeler(self.food_search.nutrient_show(self.selected_item))
self.plot_preview(self.graph_frame, self.df, row_index=selection[0], nutrient_indices=[25, 26, 35, 17], g_type='bar')
# SEARCH FUNC
def search_callback(self, *args):
pass
def start_search(self):
# Create a new thread to execute the search function
search_thread = threading.Thread(target=self.search)
search_thread.start()
def search(self):
results = self.food_search.search(self.search_var.get())
# Call the update_results function on the main thread to update the GUI
self.master.after(0, self.update_results, results)
def update_results(self, results):
self.results_listbox.delete(0, tk.END)
for result in results:
self.results_listbox.insert(tk.END, result[1])
def clear_results(self):
self.results_listbox.delete(0, tk.END)
# Image Showing Section -----------------
def show_image(self, item):
image_url = None
for result in self.food_search.search(self.search_var.get()):
if result[1] == item:
image_url = result[13]
break
if not image_url:
self.display_default_image()
return
threading.Thread(target=self.fetch_and_display_image, args=(image_url,)).start()
def fetch_and_display_image(self, image_url):
self.master.after(0, self.show_progress_bar)
# Fetch the image
response = requests.get(image_url)
img_data = response.content
img = Image.open(io.BytesIO(img_data))
img.thumbnail((300, 300), Image.LANCZOS)
img_tk = ImageTk.PhotoImage(img)
self.master.after(0, self.hide_progress_bar)
self.master.after(0, self.update_image_label, img_tk)
def show_progress_bar(self):
self.progress_bar.start()
self.progress_bar.grid(row=3, column=1)
def hide_progress_bar(self):
self.progress_bar.stop()
self.progress_bar.pack_forget()
def display_default_image(self):
self.master.after(0, self.update_image_label, self.default_image)
def update_image_label(self, image):
# Remove old image label widget
for widget in self.image_frame.winfo_children():
widget.destroy()
image_label = tk.Label(self.image_frame, image=image)
image_label.image = image
image_label.pack(anchor='w', fill=tk.BOTH)
# ---------------------
# Plot zone
def plot_preview(self, frame, df, row_index: int, nutrient_indices: list, g_type: str):
for widget in frame.winfo_children():
widget.pack_forget()
self.p = plotter()
self.p.nutrient_plotter(df, row_index, nutrient_indices, g_type, popup=False, frame=frame)
def plot_popup(self, df, row_index: int, nutrient_indices: list, g_type: str):
self.p = plotter()
self.p.nutrient_plotter(df, row_index, nutrient_indices, g_type, popup=True)
class NutrientTableHolder:
def __init__(self, root):
self.root = root
self.treeview = None
def create_table(self):
selected_nutrients = [
'energy-kcal_100g',
'proteins_100g',
'carbohydrates_100g',
'fat_100g',
'fiber_100g',
'sugars_100g',
'saturated-fat_100g',
'unsaturated-fat_100g',
'sodium_100g',
'vitamin-a_100g',
'vitamin-c_100g',
'calcium_100g',
'iron_100g',
'potassium_100g',
'cholesterol_100g',
'trans-fat_100g'
]
self.treeview = ttk.Treeview(self.root)
self.treeview['columns'] = ('Value')
self.treeview.heading("#0", text="Nutrient")
self.treeview.heading("Value", text="Value")
for name in selected_nutrients:
value = 0
value_text = f"{value:.3f}"
self.treeview.insert('', 'end', text=name, values=(value_text,))
self.treeview.pack(fill='both', expand=True)
def nutrient_labeler(self, nutrient_dict_product):
selected_nutrients = [
'energy-kcal_100g',
'proteins_100g',
'carbohydrates_100g',
'fat_100g',
'fiber_100g',
'sugars_100g',
'saturated-fat_100g',
'unsaturated-fat_100g',
'sodium_100g',
'vitamin-a_100g',
'vitamin-c_100g',
'calcium_100g',
'iron_100g',
'potassium_100g',
'cholesterol_100g',
'trans-fat_100g'
]
for widget in self.root.winfo_children():
widget.pack_forget()
treeview = ttk.Treeview(self.root)
treeview['columns'] = ('Value')
treeview.heading("#0", text="Nutrient")
treeview.heading("Value", text="Value")
for name, value in nutrient_dict_product.items():
if name in selected_nutrients:
if value is None:
value_text = "N/A"
else:
value_text = f"{value:.3f}"
treeview.insert('', 'end', text=name, values=(value_text,))
treeview.pack(fill='both', expand=True)
root = tk.Tk()
app = App(root)
app.results_listbox.bind('<<ListboxSelect>>', app.on_item_selected)
root.mainloop()