import tkinter as tk from tkinter import ttk, filedialog, messagebox import matplotlib.pyplot as plt from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg import pandas as pd import numpy as np from PIL import Image, ImageTk import json import os from datetime import datetime, timedelta import joblib from plant_model import PlantGrowthModel from data_handler import DataHandler from image_generator import ImageGenerator 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() self.image_generator = ImageGenerator() # 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)) # Environmental parameters 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' } 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) # Scale ranges based on parameter type if param == 'co2': scale_max = 1000 elif param == 'brightness': scale_max = 100000 elif param == 'pressure': scale_max = 1100 elif param == 'soil_acidity': scale_max = 14 else: scale_max = 100 scale = ttk.Scale(param_frame, from_=0, 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='#f8f9fa') 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.ambient_mode.set("controlled") self.update_prediction() def load_baseline_image(self): 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), 'results': self.results_text.get(1.0, tk.END) } # Show confirmation dialog messagebox.showinfo("Submission Successful", "Plant information and photo have been submitted successfully!\n\n" f"Submission ID: {datetime.now().strftime('%Y%m%d_%H%M%S')}") 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(params, prediction) # Generate plant evolution images self.generate_plant_evolution(params, prediction) # 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, params, prediction): """Update the initial plant state display""" try: # Generate initial plant image (stage 0) initial_image = self.image_generator.generate_plant_image( self.current_plant, 0.5, prediction['health_score'], 0 ) # Resize image to fit better in square layout 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 # Update plant information self.update_plant_info(params, prediction) except Exception as e: messagebox.showerror("Image Error", f"Could not generate initial plant image: {str(e)}") def update_plant_info(self, params, prediction): """Update plant information display""" self.plant_info_text.delete(1.0, tk.END) info_text = f"""๐ŸŒฑ PLANT STATUS {'='*22} Plant Type: Tomato Health Score: {prediction['health_score']:.1f}% Growth Rate: {prediction['growth_rate']:.2f} cm/day ๐ŸŒก๏ธ CONDITIONS: Temperature: {params['temperature']:.1f}ยฐC Humidity: {params['humidity']:.1f}% Soil pH: {params['soil_acidity']:.1f} Light: {params['brightness']:.0f} lux Water: {params['water']:.1f}% Nutrients: {params['nutrients']:.1f}% ๐Ÿ“Š FORECAST: Final Height: {prediction['final_height']:.1f} cm Expected Yield: {prediction.get('yield', 'N/A')} Optimal Conditions: {prediction['optimal_conditions']:.1f}% """ self.plant_info_text.insert(1.0, info_text) def generate_plant_evolution(self, params, prediction): """Generate and display plant evolution images""" try: # Generate plant evolution images images = self.image_generator.generate_evolution( self.current_plant, params, prediction, self.baseline_image_path ) if images: # Clear previous images for widget in self.image_display_frame.winfo_children(): widget.destroy() # Display evolution stages in a square grid stages_per_row = 2 # Square layout for i, image in enumerate(images): row = i // stages_per_row col = i % stages_per_row stage_frame = ttk.Frame(self.image_display_frame) stage_frame.grid(row=row, column=col, padx=4, pady=4, sticky=(tk.W, tk.E)) # Stage label ttk.Label(stage_frame, text=f"Stage {i+1}", font=('Arial', 9, 'bold')).pack() # Resize image for compact display resized_image = image.resize((180, 135), Image.Resampling.LANCZOS) # Convert PIL image to PhotoImage photo = ImageTk.PhotoImage(resized_image) image_label = tk.Label(stage_frame, image=photo) image_label.image = photo # Keep a reference image_label.pack() # Configure grid weights for even distribution for col in range(stages_per_row): self.image_display_frame.columnconfigure(col, weight=1) except Exception as e: messagebox.showerror("Evolution Error", f"Could not generate evolution images: {str(e)}") def update_results_display(self, prediction): self.results_text.delete(1.0, tk.END) results = f"""๐ŸŒฑ GROWTH FORECAST {'='*18} Final Height: {prediction['final_height']:.1f} cm Growth Rate: {prediction['growth_rate']:.2f} cm/day Health Score: {prediction['health_score']:.1f}/100 ๐Ÿ“… Growth Phases: """ for phase in prediction.get('phases', []): results += f"โ€ข {phase['name']}: Days {phase['start']}-{phase['end']}\n" results += f"\nโœ… Optimal Conditions: {prediction['optimal_conditions']:.1f}%" results += f"\n๐Ÿ… Expected Yield: {prediction.get('yield', 'N/A')}" # Add environmental summary results += f"\n\n๐ŸŒก๏ธ ENVIRONMENT SUMMARY:" results += f"\nTemperature: {self.env_params['temperature'].get():.1f}ยฐC" results += f"\nHumidity: {self.env_params['humidity'].get():.1f}%" results += f"\nSoil pH: {self.env_params['soil_acidity'].get():.1f}" results += f"\nLight Level: {self.env_params['brightness'].get():.0f} lux" results += f"\nWater Level: {self.env_params['water'].get():.1f}%" results += f"\nNutrients: {self.env_params['nutrients'].get():.1f}%" results += f"\nCO2 Level: {self.env_params['co2'].get():.0f} ppm" self.results_text.insert(1.0, results) def main(): root = tk.Tk() app = PlantGrowthDashboard(root) root.mainloop() if __name__ == "__main__": main()