diff --git a/PlantDashboard/__pycache__/main_dashboard.cpython-313.pyc b/PlantDashboard/__pycache__/main_dashboard.cpython-313.pyc index 287e694..9566346 100644 Binary files a/PlantDashboard/__pycache__/main_dashboard.cpython-313.pyc and b/PlantDashboard/__pycache__/main_dashboard.cpython-313.pyc differ diff --git a/PlantDashboard/main_dashboard.py b/PlantDashboard/main_dashboard.py index fca9b7d..fb446ff 100644 --- a/PlantDashboard/main_dashboard.py +++ b/PlantDashboard/main_dashboard.py @@ -3,9 +3,10 @@ from tkinter import ttk, filedialog, messagebox from PIL import Image, ImageTk import json import os -from datetime import datetime +from datetime import datetime, date from plant_model import PlantGrowthModel from data_handler import DataHandler +from tkcalendar import DateEntry class PlantGrowthDashboard: def __init__(self, root): @@ -35,10 +36,10 @@ class PlantGrowthDashboard: 'humidity': 65.0, 'soil_acidity': 6.5, 'pressure': 1013.25, - 'brightness': 50000.0, + 'brightness': 30, 'nutrients': 75.0, 'water': 80.0, - 'co2': 400.0 + 'co2': 850 } self.env_params = { @@ -53,7 +54,6 @@ class PlantGrowthDashboard: } self.setup_ui() - self.update_prediction() def setup_ui(self): # Main container with square layout @@ -94,8 +94,6 @@ class PlantGrowthDashboard: 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) @@ -109,12 +107,12 @@ class PlantGrowthDashboard: param_labels = { 'temperature': '🌡️ Temp (°C)', 'humidity': '💧 Humidity (%)', - 'soil_acidity': '🧪 pH', - 'pressure': '🌬️ Pressure', - 'brightness': '☀️ Light', + 'soil_acidity': '🧪 (pH)', + 'pressure': '🌬️ Pressure (Pa)', + 'brightness': '☀️ Light (DLI)', 'nutrients': '🌿 Nutrients (%)', 'water': '💦 Water (%)', - 'co2': '🫧 CO2' + 'co2': '🫧 CO2 (ppm)' } # Define bounds for each parameter (min, max) @@ -123,10 +121,10 @@ class PlantGrowthDashboard: 'humidity': (20, 90), 'soil_acidity': (4.0, 9.0), 'pressure': (950, 1100), - 'brightness': (100, 100000), + 'brightness': (5, 50), 'nutrients': (0, 100), 'water': (10, 100), - 'co2': (300, 1000) + 'co2': (400, 1200) } row = 4 @@ -158,6 +156,32 @@ class PlantGrowthDashboard: 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)) + row += 1 + + # ADD EMPTY SPACER + spacer = ttk.Label(control_frame, text="") + spacer.grid(row=row, column=0, columnspan=2, pady=10) + + # Add this after the parameters section + ttk.Label(control_frame, text="Final date of growth:", + font=('Arial', 9, 'bold')).grid(row=row, column=0, columnspan=2, sticky=tk.W, pady=(10, 4)) + + row += 1 + # Compact date entry with calendar popup + # To get the selected dat simply call self.date_entry.get_date() + self.date_entry = DateEntry(control_frame, + width=12, + background='darkblue', + foreground='white', + borderwidth=2, + font=('Arial', 8), + date_pattern='dd/mm/yyyy', + state='readonly', # Add this + cursor='hand2') # Add this + self.date_entry.grid(row=row, column=0, columnspan=2, sticky=tk.W, pady=2) + + row += 1 + control_frame.columnconfigure(0, weight=1) control_frame.columnconfigure(1, weight=1) @@ -218,29 +242,57 @@ class PlantGrowthDashboard: command=self.submit_plant_data).pack(fill=tk.X) def setup_plant_evolution_tab(self, notebook): - """Evolution tab""" + """Evolution tab with single image display""" 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) + # Create main container for image display + self.image_display_frame = ttk.Frame(evolution_frame) + self.image_display_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) - self.scrollable_frame.bind( - "", - lambda e: canvas_scroll.configure(scrollregion=canvas_scroll.bbox("all")) + # Create label for image or fallback text + self.evolution_image_label = ttk.Label( + self.image_display_frame, + text="Plant state after the prediction will appear here", + font=('Arial', 12), + foreground='gray', + anchor='center' ) - - 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) - + self.evolution_image_label.pack(expand=True) + + def update_evolution_image(self, filename=None): + """Update the evolution tab with an image from file or show fallback text""" + if filename and os.path.exists(filename): + try: + # Load and display the image + from PIL import Image, ImageTk + + # Open and resize image if needed + pil_image = Image.open(filename) + # Optional: resize to fit the display area + pil_image = pil_image.resize((400, 300), Image.Resampling.LANCZOS) + + # Convert to PhotoImage for tkinter + photo_image = ImageTk.PhotoImage(pil_image) + + # Display the image + self.evolution_image_label.config(image=photo_image, text="") + self.evolution_image_label.image = photo_image # Keep reference to prevent garbage collection + + except Exception as e: + print(f"Error loading image {filename}: {e}") + # Show fallback text on error + self.evolution_image_label.config( + image="", + text="Plant state after the prediction will appear here" + ) + else: + # Show fallback text when no filename or file doesn't exist + self.evolution_image_label.config( + image="", + text="Plant state after the prediction will appear here" + ) + 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)) @@ -249,15 +301,39 @@ class PlantGrowthDashboard: 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 = tk.Text(results_frame, height=10, width=26, wrap=tk.WORD, font=('Arial', 8), state='disabled') self.results_text.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S)) + img = Image.open("public/TransparentFlower.png") + + # Resize if needed + img = img.resize((180, 290), Image.Resampling.LANCZOS) + photo = ImageTk.PhotoImage(img) + + static_image_label = ttk.Label(results_frame, image=photo) + static_image_label.image = photo # Keep reference + static_image_label.grid(row=2, column=0, pady=(6, 0)) + results_frame.columnconfigure(0, weight=1) results_frame.rowconfigure(1, weight=1) def on_mode_change(self): - self.update_prediction() + #KEY FOCUS IS TO IMPLEMENT THIS PART + """Handle mode changes""" + current_mode = self.ambient_mode.get() + if current_mode == "controlled": + print("Switched to Controlled mode") + # Enable all parameter controls + # No need to call the meteo api + + elif current_mode == "open": + print("Switched to Open mode") + # Disable most parameter controls (temp, humidity, light) + + # Call the meteo api to retrieve all the parameters, set variable meteo_values to retrieve when submitiing all + # Inside the retrieving of all the data check if the mode is one, select which data to use + def on_param_change(self, param): value = self.env_params[param].get() @@ -269,19 +345,16 @@ class PlantGrowthDashboard: 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() + + self.date_entry.set_date(date.today()) def update_parameter_label(self, param, value): """Update the value label for a specific parameter""" @@ -302,6 +375,7 @@ class PlantGrowthDashboard: def load_baseline_image(self): self.results_text.delete(1.0, tk.END) + self.set_to_default() file_path = filedialog.askopenfilename( title="Select baseline plant image", @@ -309,7 +383,6 @@ class PlantGrowthDashboard: ) if file_path: self.baseline_image_path = file_path - self.update_prediction() def submit_plant_data(self): """Submit plant information and photo""" @@ -324,7 +397,9 @@ class PlantGrowthDashboard: 'timestamp': datetime.now().isoformat(), 'parameters': params, 'baseline_image_path': self.baseline_image_path, - 'plant_info': self.plant_info_text.get(1.0, tk.END) + 'plant_info': self.plant_info_text.get(1.0, tk.END), + 'start date': datetime.now().date().isoformat(), + 'end_date': self.date_entry.get_date().isoformat() } #Remove plant_info_text @@ -339,34 +414,35 @@ class PlantGrowthDashboard: json.dump(submission_data, f, indent=4) # Here call the bot pipeline to store results on files in plant_data + # results are in the form of (text, image) + results = "come bot please" + + text = getattr(results, 'text', None) + image_filename = getattr(results, 'image', None) + images_dir = "./plant_data" + os.makedirs(data_dir, exist_ok=True) + image_path = os.path.join(images_dir, image_filename) + self.updating_evolution_and_forecasts(text, image_path) # Here update the informations in the last box from plant_data/texts # Here update the informations in growth evolution from plant_data/images + #Calling a small advertment to notify the user that the image has been generated + messagebox.showinfo("Submission successful, go to growth evolution to see the results") 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 updating_evolution_and_forecasts(self, text, image_path): + self.results_text.config(state='normal') # Enable editing + self.results_text.delete(1.0, tk.END) # Clear existing content + if text != None: + self.results_text.insert(1.0, text) # Insert new text + self.results_text.config(state='disabled') # Disable editing again + + self.update_evolution_image(image_path) + def update_initial_plant_display(self): """Update the initial plant state display""" try: diff --git a/path.txt b/path.txt index cb50234..ad13395 100644 --- a/path.txt +++ b/path.txt @@ -1,35 +1,18 @@ ROADMAP - -understand codebase - - -add the possibility to retrieve all the data when the user click the submit button - -format the data in an usable format -differences between the three modes: Open mode: -the temp, humidity, light, water parameters are setted by the meteo api, the rest is setted by the user - + -all the parameters are controlled by the user SemiControlled mode: -the user choose how to set the parameters + -all parameters free Controlled mode: -all the values are set by the user - - - -default modes are setted with general values in an optimal condition for your plant + -the user choose which parameters are free and which are controlled by the meteo api - -The forecast results will have as input the text description obtained by the model - (the output will be in the text folder inside data/texts folder, format "date1-date2-enum.txt") - -The growth evolution will have as input the photo/s obtained by the model - (the output will be in the text folder inside data/images folder, format "date1-date2-enum.jpg) - - -set the value of light as Daily Light Integral (DLI) - - -forecast for a period of time: - -create a calendar widget with start and end data - -based on how much does it take to create an image, for each subperiod of time create a new image to show - (it this will be coded add all a button to iterate over the folder) - - -when the user click the load plant image, all previous record will be eliminated + -make the calendar widget working -final updates of README.md, hackathon page, forgejo, github page, small design adjustments.