late night working

This commit is contained in:
giusber2005 2025-08-02 02:41:58 +02:00
parent d44deec338
commit e9315cb55b
3 changed files with 139 additions and 80 deletions

View file

@ -3,9 +3,10 @@ from tkinter import ttk, filedialog, messagebox
from PIL import Image, ImageTk from PIL import Image, ImageTk
import json import json
import os import os
from datetime import datetime from datetime import datetime, date
from plant_model import PlantGrowthModel from plant_model import PlantGrowthModel
from data_handler import DataHandler from data_handler import DataHandler
from tkcalendar import DateEntry
class PlantGrowthDashboard: class PlantGrowthDashboard:
def __init__(self, root): def __init__(self, root):
@ -35,10 +36,10 @@ class PlantGrowthDashboard:
'humidity': 65.0, 'humidity': 65.0,
'soil_acidity': 6.5, 'soil_acidity': 6.5,
'pressure': 1013.25, 'pressure': 1013.25,
'brightness': 50000.0, 'brightness': 30,
'nutrients': 75.0, 'nutrients': 75.0,
'water': 80.0, 'water': 80.0,
'co2': 400.0 'co2': 850
} }
self.env_params = { self.env_params = {
@ -53,7 +54,6 @@ class PlantGrowthDashboard:
} }
self.setup_ui() self.setup_ui()
self.update_prediction()
def setup_ui(self): def setup_ui(self):
# Main container with square layout # Main container with square layout
@ -94,8 +94,6 @@ class PlantGrowthDashboard:
ttk.Radiobutton(mode_frame, text="Controlled", variable=self.ambient_mode, ttk.Radiobutton(mode_frame, text="Controlled", variable=self.ambient_mode,
value="controlled", command=self.on_mode_change).pack(anchor=tk.W) 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, ttk.Radiobutton(mode_frame, text="Open", variable=self.ambient_mode,
value="open", command=self.on_mode_change).pack(anchor=tk.W) value="open", command=self.on_mode_change).pack(anchor=tk.W)
@ -109,12 +107,12 @@ class PlantGrowthDashboard:
param_labels = { param_labels = {
'temperature': '🌡️ Temp (°C)', 'temperature': '🌡️ Temp (°C)',
'humidity': '💧 Humidity (%)', 'humidity': '💧 Humidity (%)',
'soil_acidity': '🧪 pH', 'soil_acidity': '🧪 (pH)',
'pressure': '🌬️ Pressure', 'pressure': '🌬️ Pressure (Pa)',
'brightness': '☀️ Light', 'brightness': '☀️ Light (DLI)',
'nutrients': '🌿 Nutrients (%)', 'nutrients': '🌿 Nutrients (%)',
'water': '💦 Water (%)', 'water': '💦 Water (%)',
'co2': '🫧 CO2' 'co2': '🫧 CO2 (ppm)'
} }
# Define bounds for each parameter (min, max) # Define bounds for each parameter (min, max)
@ -123,10 +121,10 @@ class PlantGrowthDashboard:
'humidity': (20, 90), 'humidity': (20, 90),
'soil_acidity': (4.0, 9.0), 'soil_acidity': (4.0, 9.0),
'pressure': (950, 1100), 'pressure': (950, 1100),
'brightness': (100, 100000), 'brightness': (5, 50),
'nutrients': (0, 100), 'nutrients': (0, 100),
'water': (10, 100), 'water': (10, 100),
'co2': (300, 1000) 'co2': (400, 1200)
} }
row = 4 row = 4
@ -158,6 +156,32 @@ class PlantGrowthDashboard:
ttk.Button(control_frame, text="🔄 Set to Default", 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)) 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(0, weight=1)
control_frame.columnconfigure(1, weight=1) control_frame.columnconfigure(1, weight=1)
@ -218,29 +242,57 @@ class PlantGrowthDashboard:
command=self.submit_plant_data).pack(fill=tk.X) command=self.submit_plant_data).pack(fill=tk.X)
def setup_plant_evolution_tab(self, notebook): def setup_plant_evolution_tab(self, notebook):
"""Evolution tab""" """Evolution tab with single image display"""
evolution_frame = ttk.Frame(notebook) evolution_frame = ttk.Frame(notebook)
notebook.add(evolution_frame, text="🌿 Growth Evolution") notebook.add(evolution_frame, text="🌿 Growth Evolution")
# Create scrollable frame for plant images # Create main container for image display
canvas_scroll = tk.Canvas(evolution_frame) self.image_display_frame = ttk.Frame(evolution_frame)
scrollbar = ttk.Scrollbar(evolution_frame, orient="vertical", command=canvas_scroll.yview) self.image_display_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
self.scrollable_frame = ttk.Frame(canvas_scroll)
self.scrollable_frame.bind( # Create label for image or fallback text
"<Configure>", self.evolution_image_label = ttk.Label(
lambda e: canvas_scroll.configure(scrollregion=canvas_scroll.bbox("all")) self.image_display_frame,
text="Plant state after the prediction will appear here",
font=('Arial', 12),
foreground='gray',
anchor='center'
) )
self.evolution_image_label.pack(expand=True)
canvas_scroll.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
canvas_scroll.configure(yscrollcommand=scrollbar.set) def update_evolution_image(self, filename=None):
"""Update the evolution tab with an image from file or show fallback text"""
canvas_scroll.pack(side="left", fill="both", expand=True) if filename and os.path.exists(filename):
scrollbar.pack(side="right", fill="y") try:
# Load and display the image
self.image_display_frame = ttk.Frame(self.scrollable_frame) from PIL import Image, ImageTk
self.image_display_frame.pack(fill=tk.BOTH, expand=True, padx=6, pady=6)
# 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): def setup_results_panel(self, parent):
results_frame = ttk.LabelFrame(parent, text="Growth Prediction", padding="6") 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)) 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:", ttk.Label(results_frame, text="Forecast Results:",
font=('Arial', 9, 'bold')).grid(row=0, column=0, sticky=tk.W, pady=(0, 4)) 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)) 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.columnconfigure(0, weight=1)
results_frame.rowconfigure(1, weight=1) results_frame.rowconfigure(1, weight=1)
def on_mode_change(self): 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): def on_param_change(self, param):
value = self.env_params[param].get() value = self.env_params[param].get()
@ -269,19 +345,16 @@ class PlantGrowthDashboard:
value_label.config(text=f"{value:.0f}") value_label.config(text=f"{value:.0f}")
else: else:
value_label.config(text=f"{value:.1f}") value_label.config(text=f"{value:.1f}")
# Auto-update prediction when parameters change
self.update_prediction()
def set_to_default(self): def set_to_default(self):
"""Reset all parameters to default values""" """Reset all parameters to default values"""
for param_name, default_value in self.default_params.items(): for param_name, default_value in self.default_params.items():
self.env_params[param_name].set(default_value) self.env_params[param_name].set(default_value)
self.update_parameter_label(param_name, default_value) self.update_parameter_label(param_name, default_value)
self.ambient_mode.set("controlled") self.ambient_mode.set("controlled")
self.update_prediction()
self.date_entry.set_date(date.today())
def update_parameter_label(self, param, value): def update_parameter_label(self, param, value):
"""Update the value label for a specific parameter""" """Update the value label for a specific parameter"""
@ -302,6 +375,7 @@ class PlantGrowthDashboard:
def load_baseline_image(self): def load_baseline_image(self):
self.results_text.delete(1.0, tk.END) self.results_text.delete(1.0, tk.END)
self.set_to_default()
file_path = filedialog.askopenfilename( file_path = filedialog.askopenfilename(
title="Select baseline plant image", title="Select baseline plant image",
@ -309,7 +383,6 @@ class PlantGrowthDashboard:
) )
if file_path: if file_path:
self.baseline_image_path = file_path self.baseline_image_path = file_path
self.update_prediction()
def submit_plant_data(self): def submit_plant_data(self):
"""Submit plant information and photo""" """Submit plant information and photo"""
@ -324,7 +397,9 @@ class PlantGrowthDashboard:
'timestamp': datetime.now().isoformat(), 'timestamp': datetime.now().isoformat(),
'parameters': params, 'parameters': params,
'baseline_image_path': self.baseline_image_path, '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 #Remove plant_info_text
@ -339,34 +414,35 @@ class PlantGrowthDashboard:
json.dump(submission_data, f, indent=4) json.dump(submission_data, f, indent=4)
# Here call the bot pipeline to store results on files in plant_data # 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 the last box from plant_data/texts
# Here update the informations in growth evolution from plant_data/images # 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}") print(f"Submission data saved to: {filepath}")
except Exception as e: except Exception as e:
messagebox.showerror("Submission Error", f"Error submitting data: {str(e)}") messagebox.showerror("Submission Error", f"Error submitting data: {str(e)}")
def update_prediction(self): def updating_evolution_and_forecasts(self, text, image_path):
try: self.results_text.config(state='normal') # Enable editing
# Get current parameters self.results_text.delete(1.0, tk.END) # Clear existing content
params = {param: var.get() for param, var in self.env_params.items()} if text != None:
params['plant_type'] = self.current_plant self.results_text.insert(1.0, text) # Insert new text
params['ambient_mode'] = self.ambient_mode.get() self.results_text.config(state='disabled') # Disable editing again
# Generate prediction self.update_evolution_image(image_path)
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): def update_initial_plant_display(self):
"""Update the initial plant state display""" """Update the initial plant state display"""
try: try:

View file

@ -1,35 +1,18 @@
ROADMAP 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: -differences between the three modes:
Open mode: Open mode:
-the temp, humidity, light, water parameters are setted by the meteo api, the rest is setted by the user -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: SemiControlled mode:
-the user choose how to set the parameters -the user choose how to set the parameters
-all parameters free
Controlled mode: Controlled mode:
-all the values are set by the user -all the values are set by the user
-the user choose which parameters are free and which are controlled by the meteo api
-default modes are setted with general values in an optimal condition for your plant
-The forecast results will have as input the text description obtained by the model -make the calendar widget working
(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
-final updates of README.md, hackathon page, forgejo, github page, small design adjustments. -final updates of README.md, hackathon page, forgejo, github page, small design adjustments.