import tkinter as tk from tkinter import ttk, filedialog, messagebox from PIL import Image, ImageTk import json import os from datetime import datetime from plant_model import PlantGrowthModel from data_handler import DataHandler class PlantGrowthDashboard: def __init__(self, root): self.root = root self.root.title("WeGrow") self.root.geometry("1000x800") # More square dimensions self.root.configure(bg='#f0f0f0') image = Image.open("public/transparentLogo.png") # Convert to PhotoImage icon = ImageTk.PhotoImage(image) # Set as window icon self.root.iconphoto(False, icon) # Initialize components self.plant_model = PlantGrowthModel() self.data_handler = DataHandler() # Variables - fixed plant type self.current_plant = "tomato" # Fixed plant type self.ambient_mode = tk.StringVar(value="controlled") self.baseline_image_path = None # Environmental parameters with defaults self.default_params = { 'temperature': 22.0, 'humidity': 65.0, 'soil_acidity': 6.5, 'pressure': 1013.25, 'brightness': 50000.0, 'nutrients': 75.0, 'water': 80.0, 'co2': 400.0 } self.env_params = { 'temperature': tk.DoubleVar(value=self.default_params['temperature']), 'humidity': tk.DoubleVar(value=self.default_params['humidity']), 'soil_acidity': tk.DoubleVar(value=self.default_params['soil_acidity']), 'pressure': tk.DoubleVar(value=self.default_params['pressure']), 'brightness': tk.DoubleVar(value=self.default_params['brightness']), 'nutrients': tk.DoubleVar(value=self.default_params['nutrients']), 'water': tk.DoubleVar(value=self.default_params['water']), 'co2': tk.DoubleVar(value=self.default_params['co2']) } self.setup_ui() self.update_prediction() def setup_ui(self): # Main container with square layout main_frame = ttk.Frame(self.root, padding="8") main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # Configure grid weights for square layout self.root.columnconfigure(0, weight=1) self.root.rowconfigure(0, weight=1) main_frame.columnconfigure(0, weight=1) main_frame.columnconfigure(1, weight=2) # Center panel wider main_frame.columnconfigure(2, weight=1) main_frame.rowconfigure(1, weight=1) # Title title_label = ttk.Label(main_frame, text="๐ŸŒฑ Plant Growth Dashboard", font=('Arial', 14, 'bold')) title_label.grid(row=0, column=0, columnspan=3, pady=(0, 10)) # Left panel - Controls self.setup_control_panel(main_frame) # Center panel - Plant Visualization self.setup_visualization_panel(main_frame) # Right panel - Results only (no system messages) self.setup_results_panel(main_frame) def setup_control_panel(self, parent): control_frame = ttk.LabelFrame(parent, text="Environmental Controls", padding="6") control_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 6)) # Ambient mode ttk.Label(control_frame, text="Environment Mode:", font=('Arial', 9, 'bold')).grid(row=0, column=0, columnspan=2, sticky=tk.W, pady=(0, 6)) mode_frame = ttk.Frame(control_frame) mode_frame.grid(row=1, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 8)) ttk.Radiobutton(mode_frame, text="Controlled", variable=self.ambient_mode, value="controlled", command=self.on_mode_change).pack(anchor=tk.W) ttk.Radiobutton(mode_frame, text="Semi-Controlled", variable=self.ambient_mode, value="semi-controlled", command=self.on_mode_change).pack(anchor=tk.W) ttk.Radiobutton(mode_frame, text="Open", variable=self.ambient_mode, value="open", command=self.on_mode_change).pack(anchor=tk.W) # Baseline image ttk.Button(control_frame, text="๐Ÿ“ท Load Plant Image", command=self.load_baseline_image).grid(row=2, column=0, columnspan=2, pady=(0, 10), sticky=(tk.W, tk.E)) ttk.Label(control_frame, text="Parameters:", font=('Arial', 9, 'bold')).grid(row=3, column=0, columnspan=2, sticky=tk.W, pady=(0, 4)) param_labels = { 'temperature': '๐ŸŒก๏ธ Temp (ยฐC)', 'humidity': '๐Ÿ’ง Humidity (%)', 'soil_acidity': '๐Ÿงช pH', 'pressure': '๐ŸŒฌ๏ธ Pressure', 'brightness': 'โ˜€๏ธ Light', 'nutrients': '๐ŸŒฟ Nutrients (%)', 'water': '๐Ÿ’ฆ Water (%)', 'co2': '๐Ÿซง CO2' } # Define bounds for each parameter (min, max) param_bounds = { 'temperature': (10, 40), 'humidity': (20, 90), 'soil_acidity': (4.0, 9.0), 'pressure': (950, 1100), 'brightness': (100, 100000), 'nutrients': (0, 100), 'water': (10, 100), 'co2': (300, 1000) } row = 4 for param, label in param_labels.items(): # Compact parameter layout param_frame = ttk.Frame(control_frame) param_frame.grid(row=row, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=1) ttk.Label(param_frame, text=label, width=11, font=('Arial', 8)).pack(side=tk.LEFT) # Get bounds for this parameter scale_min, scale_max = param_bounds.get(param, (0, 100)) scale = ttk.Scale(param_frame, from_=scale_min, to=scale_max, variable=self.env_params[param], orient=tk.HORIZONTAL, command=lambda x, p=param: self.on_param_change(p)) scale.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(4, 4)) # Value label value_label = ttk.Label(param_frame, text=f"{self.env_params[param].get():.1f}", width=5, font=('Arial', 8)) value_label.pack(side=tk.RIGHT) # Store reference for updates setattr(self, f"{param}_value_label", value_label) row += 1 # Set to Default button ttk.Button(control_frame, text="๐Ÿ”„ Set to Default", command=self.set_to_default).grid(row=row, column=0, columnspan=2, pady=(10, 0), sticky=(tk.W, tk.E)) control_frame.columnconfigure(0, weight=1) control_frame.columnconfigure(1, weight=1) def setup_visualization_panel(self, parent): viz_frame = ttk.LabelFrame(parent, text="Plant Visualization", padding="6") viz_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=3) # Notebook for different views notebook = ttk.Notebook(viz_frame) notebook.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) # Plant Before Growth tab self.setup_before_growth_tab(notebook) # Plant Evolution tab self.setup_plant_evolution_tab(notebook) viz_frame.columnconfigure(0, weight=1) viz_frame.rowconfigure(0, weight=1) def setup_before_growth_tab(self, notebook): """Tab showing the plant before growth starts""" before_frame = ttk.Frame(notebook) notebook.add(before_frame, text="๐ŸŒฑ Initial Plant") # Create a frame for the initial plant display display_frame = ttk.Frame(before_frame) display_frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=8) # Title for initial state title_label = ttk.Label(display_frame, text="Plant Initial State", font=('Arial', 11, 'bold')) title_label.pack(pady=(0, 8)) # Frame for the plant image self.initial_plant_frame = ttk.Frame(display_frame) self.initial_plant_frame.pack(fill=tk.BOTH, expand=True) # Initial plant image label self.initial_plant_label = ttk.Label(self.initial_plant_frame, text="Initial plant state will appear here", font=('Arial', 9)) self.initial_plant_label.pack(expand=True) # Plant info display info_frame = ttk.LabelFrame(display_frame, text="Plant Information", padding="4") info_frame.pack(fill=tk.X, pady=(8, 0)) self.plant_info_text = tk.Text(info_frame, height=8, width=35, wrap=tk.WORD, font=('Arial', 8), bg="#000000") self.plant_info_text.pack(fill=tk.BOTH, expand=True) # Submit button submit_frame = ttk.Frame(display_frame) submit_frame.pack(fill=tk.X, pady=(8, 0)) ttk.Button(submit_frame, text="๐Ÿ“ค Submit Plant Information & Photo", command=self.submit_plant_data).pack(fill=tk.X) def setup_plant_evolution_tab(self, notebook): """Evolution tab""" evolution_frame = ttk.Frame(notebook) notebook.add(evolution_frame, text="๐ŸŒฟ Growth Evolution") # Create scrollable frame for plant images canvas_scroll = tk.Canvas(evolution_frame) scrollbar = ttk.Scrollbar(evolution_frame, orient="vertical", command=canvas_scroll.yview) self.scrollable_frame = ttk.Frame(canvas_scroll) self.scrollable_frame.bind( "", lambda e: canvas_scroll.configure(scrollregion=canvas_scroll.bbox("all")) ) canvas_scroll.create_window((0, 0), window=self.scrollable_frame, anchor="nw") canvas_scroll.configure(yscrollcommand=scrollbar.set) canvas_scroll.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") self.image_display_frame = ttk.Frame(self.scrollable_frame) self.image_display_frame.pack(fill=tk.BOTH, expand=True, padx=6, pady=6) def setup_results_panel(self, parent): results_frame = ttk.LabelFrame(parent, text="Growth Prediction", padding="6") results_frame.grid(row=1, column=2, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(6, 0)) # Prediction results only (no system messages) ttk.Label(results_frame, text="Forecast Results:", font=('Arial', 9, 'bold')).grid(row=0, column=0, sticky=tk.W, pady=(0, 4)) self.results_text = tk.Text(results_frame, height=20, width=26, wrap=tk.WORD, font=('Arial', 8)) self.results_text.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) results_frame.columnconfigure(0, weight=1) results_frame.rowconfigure(1, weight=1) def on_mode_change(self): self.update_prediction() def on_param_change(self, param): value = self.env_params[param].get() # Update value label value_label = getattr(self, f"{param}_value_label") if param == 'soil_acidity': value_label.config(text=f"{value:.1f}") elif param in ['brightness', 'pressure', 'co2']: value_label.config(text=f"{value:.0f}") else: value_label.config(text=f"{value:.1f}") # Auto-update prediction when parameters change self.update_prediction() def set_to_default(self): """Reset all parameters to default values""" for param_name, default_value in self.default_params.items(): self.env_params[param_name].set(default_value) self.update_parameter_label(param_name, default_value) self.ambient_mode.set("controlled") self.update_prediction() def update_parameter_label(self, param, value): """Update the value label for a specific parameter""" try: # Get the value label for this parameter value_label = getattr(self, f"{param}_value_label") # Format the value based on parameter type if param == 'soil_acidity': value_label.config(text=f"{value:.1f}") elif param in ['brightness', 'pressure', 'co2']: value_label.config(text=f"{value:.0f}") else: value_label.config(text=f"{value:.1f}") except AttributeError: # Handle case where label doesn't exist print(f"Warning: No label found for parameter {param}") def load_baseline_image(self): self.results_text.delete(1.0, tk.END) file_path = filedialog.askopenfilename( title="Select baseline plant image", filetypes=[("Image files", "*.jpg *.jpeg *.png *.bmp *.gif")] ) if file_path: self.baseline_image_path = file_path self.update_prediction() def submit_plant_data(self): """Submit plant information and photo""" try: # Get current parameters params = {param: var.get() for param, var in self.env_params.items()} params['plant_type'] = self.current_plant params['ambient_mode'] = self.ambient_mode.get() # Create submission data submission_data = { 'timestamp': datetime.now().isoformat(), 'parameters': params, 'baseline_image_path': self.baseline_image_path, 'plant_info': self.plant_info_text.get(1.0, tk.END) } #Remove plant_info_text self.plant_info_text.delete(1.0, tk.END) data_dir = "../data" os.makedirs(data_dir, exist_ok=True) current_date = datetime.now().strftime('%Y%m%d') filename = f"{current_date}-{current_date}.txt" filepath = os.path.join(data_dir, filename) with open(filepath, 'w') as f: json.dump(submission_data, f, indent=4) # Here call the bot pipeline to store results on files in plant_data # Here update the informations in the last box from plant_data/texts # Here update the informations in growth evolution from plant_data/images print(f"Submission data saved to: {filepath}") except Exception as e: messagebox.showerror("Submission Error", f"Error submitting data: {str(e)}") def update_prediction(self): try: # Get current parameters params = {param: var.get() for param, var in self.env_params.items()} params['plant_type'] = self.current_plant params['ambient_mode'] = self.ambient_mode.get() # Generate prediction prediction = self.plant_model.predict_growth(params) # Update initial plant display self.update_initial_plant_display() # Update results text self.update_results_display(prediction) except Exception as e: messagebox.showerror("Prediction Error", f"Error generating prediction: {str(e)}") def update_initial_plant_display(self): """Update the initial plant state display""" try: initial_image = None # Generate initial plant image (stage 0) try: if self.baseline_image_path != None and os.path.exists(self.baseline_image_path): initial_image = Image.open(self.baseline_image_path) except Exception as e: print(f"Error loading image from {self.baseline_image_path}: {e}") # Resize image to fit better in square layout if initial_image != None: initial_image = initial_image.resize((280, 210), Image.Resampling.LANCZOS) # Convert to PhotoImage and display photo = ImageTk.PhotoImage(initial_image) self.initial_plant_label.configure(image=photo, text="") self.initial_plant_label.image = photo # Keep reference except Exception as e: messagebox.showerror("Image Error", f"Could not generate initial plant image: {str(e)}") def update_results_display(self, prediction): self.results_text.delete(1.0, tk.END) #Here update the results display after submitting the photo and the message with the parameters and receveing the output results = "" self.results_text.insert(1.0, results) def main(): root = tk.Tk() app = PlantGrowthDashboard(root) root.mainloop() if __name__ == "__main__": main()