Merge branch 'main' into challenge-2-flutter

This commit is contained in:
Leon Astner 2025-08-02 08:15:41 +02:00
commit 1ba28e04c2
963 changed files with 1009 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:

3
new steps.txt Normal file
View file

@ -0,0 +1,3 @@
@todo
nel primo LLM dare come input non solo immagine ma anche testo : dove gli passo variabili {start_date} e {end_date} che sono prese da input dal front end di Giuseppe.(nel testo aggiungi anche decisioni extra utente, tipo dopo X giorni dallo start date di spostare la pianta all' interno della casa) . Cercare di avere come output finale non solo l' immagine della pianta ma anche una descrizione

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.

View file

@ -0,0 +1,7 @@
## this app requires:
- python >= 3.11.0
### How to run:
- cd inside the root of the project
- install all necessary dependencies from the txt by doing : "pip install -r requirements.txt"
- run by doing python script.py

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View file

@ -0,0 +1,17 @@
openmeteo-requests
pandas
torch
diffusers
transformers
pillow
requests-cache
retry-requests
numpy
accelerate
hf_xet
geocoder
torchvision
requests
retry-requests
scikit-learn
kaggle

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 582 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 707 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 583 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 748 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Some files were not shown because too many files have changed in this diff Show more