adding dashboard to the commit
This commit is contained in:
parent
f84ca4b517
commit
a014734a9d
21 changed files with 2259 additions and 1 deletions
BIN
NOI Hackathon 2025 - Gruppo FOS.pdf
Normal file
BIN
NOI Hackathon 2025 - Gruppo FOS.pdf
Normal file
Binary file not shown.
BIN
PlantDashboard/.DS_Store
vendored
Normal file
BIN
PlantDashboard/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
PlantDashboard/__pycache__/data_handler.cpython-313.pyc
Normal file
BIN
PlantDashboard/__pycache__/data_handler.cpython-313.pyc
Normal file
Binary file not shown.
BIN
PlantDashboard/__pycache__/image_generator.cpython-313.pyc
Normal file
BIN
PlantDashboard/__pycache__/image_generator.cpython-313.pyc
Normal file
Binary file not shown.
BIN
PlantDashboard/__pycache__/plant_model.cpython-313.pyc
Normal file
BIN
PlantDashboard/__pycache__/plant_model.cpython-313.pyc
Normal file
Binary file not shown.
133
PlantDashboard/data_handler.py
Normal file
133
PlantDashboard/data_handler.py
Normal file
|
@ -0,0 +1,133 @@
|
|||
import pandas as pd
|
||||
import json
|
||||
import csv
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
class DataHandler:
|
||||
def __init__(self):
|
||||
self.data_directory = "plant_data"
|
||||
self.ensure_data_directory()
|
||||
|
||||
def ensure_data_directory(self):
|
||||
"""Ensure the data directory exists"""
|
||||
if not os.path.exists(self.data_directory):
|
||||
os.makedirs(self.data_directory)
|
||||
|
||||
def save_prediction_data(self, parameters, prediction, filename=None):
|
||||
"""Save prediction data to CSV"""
|
||||
if filename is None:
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"prediction_{timestamp}.csv"
|
||||
|
||||
filepath = os.path.join(self.data_directory, filename)
|
||||
|
||||
# Combine parameters and prediction results
|
||||
data = {**parameters}
|
||||
data.update({
|
||||
'final_height': prediction['final_height'],
|
||||
'growth_rate': prediction['growth_rate'],
|
||||
'health_score': prediction['health_score'],
|
||||
'optimal_conditions': prediction['optimal_conditions'],
|
||||
'yield': prediction['yield'],
|
||||
'timestamp': datetime.now().isoformat()
|
||||
})
|
||||
|
||||
# Convert to DataFrame
|
||||
df = pd.DataFrame([data])
|
||||
|
||||
try:
|
||||
df.to_csv(filepath, index=False)
|
||||
return filepath
|
||||
except Exception as e:
|
||||
print(f"Error saving data: {e}")
|
||||
return None
|
||||
|
||||
def load_historical_data(self):
|
||||
"""Load all historical prediction data"""
|
||||
data_files = [f for f in os.listdir(self.data_directory) if f.endswith('.csv')]
|
||||
|
||||
if not data_files:
|
||||
return pd.DataFrame()
|
||||
|
||||
all_data = []
|
||||
for file in data_files:
|
||||
filepath = os.path.join(self.data_directory, file)
|
||||
try:
|
||||
df = pd.read_csv(filepath)
|
||||
all_data.append(df)
|
||||
except Exception as e:
|
||||
print(f"Error loading {file}: {e}")
|
||||
|
||||
if all_data:
|
||||
return pd.concat(all_data, ignore_index=True)
|
||||
else:
|
||||
return pd.DataFrame()
|
||||
|
||||
def export_to_json(self, data, filename):
|
||||
"""Export data to JSON format"""
|
||||
filepath = os.path.join(self.data_directory, filename)
|
||||
|
||||
try:
|
||||
with open(filepath, 'w') as f:
|
||||
json.dump(data, f, indent=2, default=str)
|
||||
return filepath
|
||||
except Exception as e:
|
||||
print(f"Error exporting to JSON: {e}")
|
||||
return None
|
||||
|
||||
def import_from_json(self, filename):
|
||||
"""Import data from JSON format"""
|
||||
filepath = os.path.join(self.data_directory, filename)
|
||||
|
||||
try:
|
||||
with open(filepath, 'r') as f:
|
||||
data = json.load(f)
|
||||
return data
|
||||
except Exception as e:
|
||||
print(f"Error importing from JSON: {e}")
|
||||
return None
|
||||
|
||||
def get_plant_statistics(self, plant_type=None):
|
||||
"""Get statistics for plant predictions"""
|
||||
df = self.load_historical_data()
|
||||
|
||||
if df.empty:
|
||||
return {}
|
||||
|
||||
if plant_type:
|
||||
df = df[df['plant_type'] == plant_type]
|
||||
|
||||
if df.empty:
|
||||
return {}
|
||||
|
||||
stats = {
|
||||
'total_predictions': len(df),
|
||||
'avg_final_height': df['final_height'].mean(),
|
||||
'avg_health_score': df['health_score'].mean(),
|
||||
'avg_growth_rate': df['growth_rate'].mean(),
|
||||
'best_conditions': df['optimal_conditions'].max(),
|
||||
'worst_conditions': df['optimal_conditions'].min()
|
||||
}
|
||||
|
||||
return stats
|
||||
|
||||
def create_comparison_report(self, plant_types):
|
||||
"""Create a comparison report for different plant types"""
|
||||
df = self.load_historical_data()
|
||||
|
||||
if df.empty:
|
||||
return {}
|
||||
|
||||
report = {}
|
||||
for plant_type in plant_types:
|
||||
plant_data = df[df['plant_type'] == plant_type]
|
||||
if not plant_data.empty:
|
||||
report[plant_type] = {
|
||||
'count': len(plant_data),
|
||||
'avg_height': plant_data['final_height'].mean(),
|
||||
'avg_health': plant_data['health_score'].mean(),
|
||||
'success_rate': len(plant_data[plant_data['health_score'] > 70]) / len(plant_data) * 100
|
||||
}
|
||||
|
||||
return report
|
77
PlantDashboard/demo_launcher.py
Normal file
77
PlantDashboard/demo_launcher.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
"""
|
||||
Plant Growth Graphics Demo Launcher
|
||||
Simple version focusing on visual capabilities
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the current directory to the Python path
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, current_dir)
|
||||
|
||||
def check_requirements():
|
||||
"""Check if required packages are available"""
|
||||
required_packages = {
|
||||
'tkinter': 'tkinter',
|
||||
'matplotlib': 'matplotlib',
|
||||
'seaborn': 'seaborn',
|
||||
'numpy': 'numpy',
|
||||
'PIL': 'Pillow'
|
||||
}
|
||||
|
||||
missing_packages = []
|
||||
|
||||
for package, pip_name in required_packages.items():
|
||||
try:
|
||||
__import__(package)
|
||||
print(f"✅ {package} - OK")
|
||||
except ImportError:
|
||||
print(f"❌ {package} - Missing")
|
||||
missing_packages.append(pip_name)
|
||||
|
||||
if missing_packages:
|
||||
print(f"\n📦 Install missing packages with:")
|
||||
print(f"pip install {' '.join(missing_packages)}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
print("🎨 Plant Growth Graphics Demo")
|
||||
print("=" * 40)
|
||||
print("Checking requirements...")
|
||||
|
||||
if not check_requirements():
|
||||
print("\n❌ Please install missing packages first!")
|
||||
return
|
||||
|
||||
print("\n🚀 Starting graphics demo...")
|
||||
|
||||
try:
|
||||
from graphics_demo import PlantGrowthGraphicsDemo
|
||||
import tkinter as tk
|
||||
|
||||
root = tk.Tk()
|
||||
app = PlantGrowthGraphicsDemo(root)
|
||||
|
||||
print("✅ Graphics demo ready!")
|
||||
print("\n🎮 Demo Features:")
|
||||
print(" • Real-time parameter visualization")
|
||||
print(" • Interactive plant growth charts")
|
||||
print(" • Dynamic plant evolution images")
|
||||
print(" • Parameter heatmaps and 3D plots")
|
||||
print(" • Live health indicators")
|
||||
print(" • Animated growth sequences")
|
||||
print("\n🎛️ Try changing parameters with the sliders!")
|
||||
print("🎬 Click 'Animate Growth' for dynamic effects!")
|
||||
|
||||
root.mainloop()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error starting demo: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
694
PlantDashboard/graphics_demo.py
Normal file
694
PlantDashboard/graphics_demo.py
Normal file
|
@ -0,0 +1,694 @@
|
|||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||
import seaborn as sns
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import random
|
||||
import math
|
||||
from datetime import datetime
|
||||
|
||||
class PlantGrowthGraphicsDemo:
|
||||
def __init__(self, root):
|
||||
self.root = root
|
||||
self.root.title("🌱 Plant Growth Graphics Demo")
|
||||
self.root.geometry("1400x900")
|
||||
self.root.configure(bg='#f0f0f0')
|
||||
|
||||
# Demo variables
|
||||
self.current_plant = tk.StringVar(value="tomato")
|
||||
self.ambient_mode = tk.StringVar(value="controlled")
|
||||
|
||||
# Environmental parameters for demo
|
||||
self.env_params = {
|
||||
'temperature': tk.DoubleVar(value=22.0),
|
||||
'humidity': tk.DoubleVar(value=65.0),
|
||||
'soil_acidity': tk.DoubleVar(value=6.5),
|
||||
'pressure': tk.DoubleVar(value=1013.25),
|
||||
'brightness': tk.DoubleVar(value=50.0),
|
||||
'nutrients': tk.DoubleVar(value=75.0),
|
||||
'water': tk.DoubleVar(value=80.0),
|
||||
'co2': tk.DoubleVar(value=40.0)
|
||||
}
|
||||
|
||||
# Plant colors for visualization
|
||||
self.plant_colors = {
|
||||
'tomato': {'stem': '#228B22', 'leaf': '#32CD32', 'fruit': '#FF6347'},
|
||||
'basil': {'stem': '#228B22', 'leaf': '#90EE90', 'fruit': '#FFFFFF'},
|
||||
'mint': {'stem': '#228B22', 'leaf': '#98FB98', 'fruit': '#FFFFFF'},
|
||||
'lettuce': {'stem': '#228B22', 'leaf': '#ADFF2F', 'fruit': '#FFFFFF'},
|
||||
'rosemary': {'stem': '#8B4513', 'leaf': '#556B2F', 'fruit': '#FFFFFF'},
|
||||
'strawberry': {'stem': '#228B22', 'leaf': '#32CD32', 'fruit': '#FF1493'}
|
||||
}
|
||||
|
||||
self.setup_ui()
|
||||
self.update_all_graphics()
|
||||
|
||||
# Auto-update timer for dynamic effects
|
||||
self.auto_update()
|
||||
|
||||
def setup_ui(self):
|
||||
# Main container
|
||||
main_frame = ttk.Frame(self.root, padding="10")
|
||||
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
|
||||
# Configure grid weights
|
||||
self.root.columnconfigure(0, weight=1)
|
||||
self.root.rowconfigure(0, weight=1)
|
||||
main_frame.columnconfigure(1, weight=1)
|
||||
main_frame.rowconfigure(1, weight=1)
|
||||
|
||||
# Title with animated effect
|
||||
self.title_label = ttk.Label(main_frame, text="🌱 Plant Growth Graphics Demo",
|
||||
font=('Arial', 16, 'bold'))
|
||||
self.title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
|
||||
|
||||
# Left panel - Controls
|
||||
self.setup_control_panel(main_frame)
|
||||
|
||||
# Center panel - Main Visualization
|
||||
self.setup_main_visualization(main_frame)
|
||||
|
||||
# Right panel - Additional Graphics
|
||||
self.setup_additional_graphics(main_frame)
|
||||
|
||||
def setup_control_panel(self, parent):
|
||||
control_frame = ttk.LabelFrame(parent, text="🎛️ Graphics Controls", padding="10")
|
||||
control_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 10))
|
||||
|
||||
# Plant selection with immediate visual feedback
|
||||
ttk.Label(control_frame, text="Plant Type:", font=('Arial', 10, 'bold')).grid(row=0, column=0, sticky=tk.W, pady=5)
|
||||
plant_combo = ttk.Combobox(control_frame, textvariable=self.current_plant,
|
||||
values=["tomato", "basil", "mint", "lettuce", "rosemary", "strawberry"])
|
||||
plant_combo.grid(row=0, column=1, sticky=(tk.W, tk.E), pady=5)
|
||||
plant_combo.bind('<<ComboboxSelected>>', self.on_plant_change)
|
||||
|
||||
# Ambient mode with visual indicators
|
||||
ttk.Label(control_frame, text="Visual Mode:", font=('Arial', 10, 'bold')).grid(row=1, column=0, sticky=tk.W, pady=5)
|
||||
mode_frame = ttk.Frame(control_frame)
|
||||
mode_frame.grid(row=1, column=1, sticky=(tk.W, tk.E), pady=5)
|
||||
|
||||
ttk.Radiobutton(mode_frame, text="🎯 Controlled", variable=self.ambient_mode,
|
||||
value="controlled", command=self.update_all_graphics).pack(side=tk.LEFT)
|
||||
ttk.Radiobutton(mode_frame, text="🌊 Dynamic", variable=self.ambient_mode,
|
||||
value="semi-controlled", command=self.update_all_graphics).pack(side=tk.LEFT)
|
||||
ttk.Radiobutton(mode_frame, text="🌪️ Chaotic", variable=self.ambient_mode,
|
||||
value="open", command=self.update_all_graphics).pack(side=tk.LEFT)
|
||||
|
||||
# Visual effects controls
|
||||
ttk.Separator(control_frame, orient='horizontal').grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=10)
|
||||
|
||||
ttk.Label(control_frame, text="🎨 Visual Parameters:",
|
||||
font=('Arial', 10, 'bold')).grid(row=3, column=0, columnspan=2, pady=(10, 5))
|
||||
|
||||
# Parameter sliders with real-time visual updates
|
||||
param_labels = {
|
||||
'temperature': '🌡️ Temperature',
|
||||
'humidity': '💧 Humidity',
|
||||
'soil_acidity': '🧪 Soil pH',
|
||||
'pressure': '🌬️ Pressure',
|
||||
'brightness': '☀️ Light',
|
||||
'nutrients': '🌿 Nutrients',
|
||||
'water': '💦 Water',
|
||||
'co2': '🫧 CO2'
|
||||
}
|
||||
|
||||
row = 4
|
||||
for param, label in param_labels.items():
|
||||
ttk.Label(control_frame, text=label).grid(row=row, column=0, sticky=tk.W, pady=2)
|
||||
|
||||
# Create frame for slider and value display
|
||||
slider_frame = ttk.Frame(control_frame)
|
||||
slider_frame.grid(row=row, column=1, sticky=(tk.W, tk.E), pady=2)
|
||||
|
||||
scale = ttk.Scale(slider_frame, from_=0, to=100,
|
||||
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)
|
||||
|
||||
# Value display
|
||||
value_label = ttk.Label(slider_frame, text="0", width=4)
|
||||
value_label.pack(side=tk.RIGHT)
|
||||
|
||||
# Store reference for updates
|
||||
setattr(self, f"{param}_label", value_label)
|
||||
|
||||
row += 1
|
||||
|
||||
# Action buttons with visual feedback
|
||||
button_frame = ttk.Frame(control_frame)
|
||||
button_frame.grid(row=row, column=0, columnspan=2, pady=20)
|
||||
|
||||
ttk.Button(button_frame, text="🎬 Animate Growth",
|
||||
command=self.animate_growth).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(button_frame, text="🎲 Randomize",
|
||||
command=self.randomize_parameters).pack(fill=tk.X, pady=2)
|
||||
ttk.Button(button_frame, text="🔄 Reset Demo",
|
||||
command=self.reset_demo).pack(fill=tk.X, pady=2)
|
||||
|
||||
control_frame.columnconfigure(1, weight=1)
|
||||
|
||||
def setup_main_visualization(self, parent):
|
||||
viz_frame = ttk.LabelFrame(parent, text="📊 Main Visualization", padding="10")
|
||||
viz_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5)
|
||||
|
||||
# Notebook for different visualization modes
|
||||
self.notebook = ttk.Notebook(viz_frame)
|
||||
self.notebook.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
|
||||
|
||||
# Growth Chart Tab
|
||||
self.setup_growth_chart_tab()
|
||||
|
||||
# Plant Evolution Tab
|
||||
self.setup_plant_evolution_tab()
|
||||
|
||||
# Parameter Heatmap Tab
|
||||
self.setup_heatmap_tab()
|
||||
|
||||
# 3D Visualization Tab
|
||||
self.setup_3d_visualization_tab()
|
||||
|
||||
viz_frame.columnconfigure(0, weight=1)
|
||||
viz_frame.rowconfigure(0, weight=1)
|
||||
|
||||
def setup_growth_chart_tab(self):
|
||||
chart_frame = ttk.Frame(self.notebook)
|
||||
self.notebook.add(chart_frame, text="📈 Growth Chart")
|
||||
|
||||
# Create matplotlib figure
|
||||
self.fig, self.ax = plt.subplots(figsize=(8, 6))
|
||||
self.fig.patch.set_facecolor('#f0f0f0')
|
||||
|
||||
self.canvas = FigureCanvasTkAgg(self.fig, chart_frame)
|
||||
self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
def setup_plant_evolution_tab(self):
|
||||
evolution_frame = ttk.Frame(self.notebook)
|
||||
self.notebook.add(evolution_frame, text="🌱 Plant Evolution")
|
||||
|
||||
# Create scrollable frame for plant images
|
||||
canvas_frame = tk.Canvas(evolution_frame, bg='white')
|
||||
scrollbar = ttk.Scrollbar(evolution_frame, orient="vertical", command=canvas_frame.yview)
|
||||
self.scrollable_frame = ttk.Frame(canvas_frame)
|
||||
|
||||
self.scrollable_frame.bind(
|
||||
"<Configure>",
|
||||
lambda e: canvas_frame.configure(scrollregion=canvas_frame.bbox("all"))
|
||||
)
|
||||
|
||||
canvas_frame.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
|
||||
canvas_frame.configure(yscrollcommand=scrollbar.set)
|
||||
|
||||
canvas_frame.pack(side="left", fill="both", expand=True)
|
||||
scrollbar.pack(side="right", fill="y")
|
||||
|
||||
# Plant evolution display
|
||||
self.plant_images_frame = ttk.Frame(self.scrollable_frame)
|
||||
self.plant_images_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
|
||||
|
||||
def setup_heatmap_tab(self):
|
||||
heatmap_frame = ttk.Frame(self.notebook)
|
||||
self.notebook.add(heatmap_frame, text="🔥 Parameter Heatmap")
|
||||
|
||||
# Create seaborn heatmap
|
||||
self.heatmap_fig, self.heatmap_ax = plt.subplots(figsize=(8, 6))
|
||||
self.heatmap_fig.patch.set_facecolor('#f0f0f0')
|
||||
|
||||
self.heatmap_canvas = FigureCanvasTkAgg(self.heatmap_fig, heatmap_frame)
|
||||
self.heatmap_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
def setup_3d_visualization_tab(self):
|
||||
viz_3d_frame = ttk.Frame(self.notebook)
|
||||
self.notebook.add(viz_3d_frame, text="🎯 3D Analysis")
|
||||
|
||||
# Create 3D plot
|
||||
self.fig_3d = plt.figure(figsize=(8, 6))
|
||||
self.fig_3d.patch.set_facecolor('#f0f0f0')
|
||||
self.ax_3d = self.fig_3d.add_subplot(111, projection='3d')
|
||||
|
||||
self.canvas_3d = FigureCanvasTkAgg(self.fig_3d, viz_3d_frame)
|
||||
self.canvas_3d.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
def setup_additional_graphics(self, parent):
|
||||
additional_frame = ttk.LabelFrame(parent, text="📋 Live Stats & Info", padding="10")
|
||||
additional_frame.grid(row=1, column=2, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(10, 0))
|
||||
|
||||
# Real-time statistics display
|
||||
stats_frame = ttk.LabelFrame(additional_frame, text="📊 Live Statistics", padding="5")
|
||||
stats_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
self.stats_text = tk.Text(stats_frame, height=8, width=25, wrap=tk.WORD,
|
||||
font=('Courier', 9), bg='#f8f9fa')
|
||||
self.stats_text.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Visual health indicator
|
||||
health_frame = ttk.LabelFrame(additional_frame, text="🏥 Plant Health", padding="5")
|
||||
health_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
self.health_canvas = tk.Canvas(health_frame, height=100, bg='white')
|
||||
self.health_canvas.pack(fill=tk.X)
|
||||
|
||||
# Parameter radar chart
|
||||
radar_frame = ttk.LabelFrame(additional_frame, text="🎯 Parameter Radar", padding="5")
|
||||
radar_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
self.radar_fig, self.radar_ax = plt.subplots(figsize=(4, 4), subplot_kw=dict(projection='polar'))
|
||||
self.radar_fig.patch.set_facecolor('#f0f0f0')
|
||||
|
||||
self.radar_canvas = FigureCanvasTkAgg(self.radar_fig, radar_frame)
|
||||
self.radar_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
def on_plant_change(self, event=None):
|
||||
self.update_all_graphics()
|
||||
|
||||
def on_param_change(self, param):
|
||||
# Update value display
|
||||
value = self.env_params[param].get()
|
||||
label = getattr(self, f"{param}_label")
|
||||
label.config(text=f"{value:.1f}")
|
||||
|
||||
# Update graphics in real-time
|
||||
self.update_all_graphics()
|
||||
|
||||
def update_all_graphics(self):
|
||||
self.update_growth_chart()
|
||||
self.update_plant_evolution()
|
||||
self.update_parameter_heatmap()
|
||||
self.update_3d_visualization()
|
||||
self.update_statistics()
|
||||
self.update_health_indicator()
|
||||
self.update_radar_chart()
|
||||
|
||||
def update_growth_chart(self):
|
||||
self.ax.clear()
|
||||
|
||||
# Generate mock growth data based on parameters
|
||||
days = np.arange(0, 100, 1)
|
||||
|
||||
# Base growth influenced by parameters
|
||||
temp_factor = self.env_params['temperature'].get() / 100
|
||||
water_factor = self.env_params['water'].get() / 100
|
||||
light_factor = self.env_params['brightness'].get() / 100
|
||||
|
||||
# Create realistic growth curve
|
||||
growth_rate = (temp_factor + water_factor + light_factor) / 3
|
||||
|
||||
if self.ambient_mode.get() == "open":
|
||||
# Add chaos/randomness
|
||||
noise = np.random.normal(0, 0.1, len(days))
|
||||
heights = np.cumsum(np.maximum(0, growth_rate + noise)) * 2
|
||||
elif self.ambient_mode.get() == "semi-controlled":
|
||||
# Add some variation
|
||||
noise = np.random.normal(0, 0.05, len(days))
|
||||
heights = np.cumsum(np.maximum(0, growth_rate + noise)) * 2
|
||||
else:
|
||||
# Controlled growth
|
||||
heights = np.cumsum([growth_rate] * len(days)) * 2
|
||||
|
||||
# Apply plant-specific characteristics
|
||||
plant_multipliers = {
|
||||
'tomato': 1.5, 'basil': 0.8, 'mint': 0.6,
|
||||
'lettuce': 0.4, 'rosemary': 1.2, 'strawberry': 0.5
|
||||
}
|
||||
|
||||
multiplier = plant_multipliers.get(self.current_plant.get(), 1.0)
|
||||
heights = heights * multiplier
|
||||
|
||||
# Plot with plant-specific colors
|
||||
colors = self.plant_colors[self.current_plant.get()]
|
||||
self.ax.plot(days, heights, color=colors['leaf'], linewidth=2, marker='o', markersize=2)
|
||||
|
||||
# Add growth phases with different colors
|
||||
phase_colors = ['lightblue', 'lightgreen', 'lightyellow', 'lightcoral']
|
||||
phase_names = ['Germination', 'Seedling', 'Vegetative', 'Mature']
|
||||
|
||||
for i, (color, name) in enumerate(zip(phase_colors, phase_names)):
|
||||
start_day = i * 25
|
||||
end_day = (i + 1) * 25
|
||||
if end_day <= len(days):
|
||||
self.ax.axvspan(start_day, end_day, alpha=0.3, color=color, label=name)
|
||||
|
||||
self.ax.set_xlabel('Days')
|
||||
self.ax.set_ylabel('Height (cm)')
|
||||
self.ax.set_title(f'{self.current_plant.get().title()} Growth Simulation')
|
||||
self.ax.grid(True, alpha=0.3)
|
||||
self.ax.legend(loc='upper left')
|
||||
|
||||
# Add current parameter indicators
|
||||
current_day = int(self.env_params['temperature'].get())
|
||||
if current_day < len(heights):
|
||||
self.ax.axvline(current_day, color='red', linestyle='--', alpha=0.7, label='Current Day')
|
||||
self.ax.plot(current_day, heights[current_day], 'ro', markersize=8)
|
||||
|
||||
self.canvas.draw()
|
||||
|
||||
def update_plant_evolution(self):
|
||||
# Clear previous images
|
||||
for widget in self.plant_images_frame.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
# Generate evolution stages
|
||||
stages = ['Seed', 'Sprout', 'Young', 'Mature', 'Full Growth']
|
||||
|
||||
for i, stage in enumerate(stages):
|
||||
stage_frame = ttk.Frame(self.plant_images_frame)
|
||||
stage_frame.pack(fill=tk.X, pady=5)
|
||||
|
||||
# Stage label
|
||||
ttk.Label(stage_frame, text=f"Stage {i+1}: {stage}",
|
||||
font=('Arial', 10, 'bold')).pack()
|
||||
|
||||
# Generate plant image
|
||||
plant_image = self.generate_plant_stage_image(i+1)
|
||||
|
||||
# Convert to PhotoImage and display
|
||||
photo = tk.PhotoImage(data=self.pil_to_tk_data(plant_image))
|
||||
image_label = tk.Label(stage_frame, image=photo)
|
||||
image_label.image = photo # Keep reference
|
||||
image_label.pack()
|
||||
|
||||
def generate_plant_stage_image(self, stage):
|
||||
"""Generate a plant image for a specific growth stage"""
|
||||
img = Image.new('RGB', (200, 150), color='#87CEEB') # Sky blue
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# Draw ground
|
||||
ground_y = 130
|
||||
draw.rectangle([0, ground_y, 200, 150], fill='#8B4513') # Brown ground
|
||||
|
||||
# Get plant colors
|
||||
colors = self.plant_colors[self.current_plant.get()]
|
||||
|
||||
# Calculate plant size based on stage and parameters
|
||||
health_factor = (sum(param.get() for param in self.env_params.values()) / len(self.env_params)) / 100
|
||||
plant_height = stage * 15 * health_factor
|
||||
plant_width = plant_height * 0.6
|
||||
|
||||
center_x = 100
|
||||
base_y = ground_y
|
||||
top_y = base_y - plant_height
|
||||
|
||||
# Draw stem
|
||||
stem_width = max(2, int(plant_height * 0.1))
|
||||
draw.rectangle([center_x - stem_width//2, int(top_y),
|
||||
center_x + stem_width//2, base_y], fill=colors['stem'])
|
||||
|
||||
# Draw leaves
|
||||
num_leaves = min(stage * 2, 8)
|
||||
for i in range(num_leaves):
|
||||
leaf_y = base_y - (i + 1) * (plant_height / (num_leaves + 1))
|
||||
side = 1 if i % 2 == 0 else -1
|
||||
leaf_x = center_x + side * (plant_width * 0.3)
|
||||
leaf_size = plant_width * 0.2
|
||||
|
||||
# Draw leaf based on plant type
|
||||
if self.current_plant.get() == 'lettuce':
|
||||
# Broad leaves
|
||||
draw.ellipse([leaf_x - leaf_size, leaf_y - leaf_size//2,
|
||||
leaf_x + leaf_size, leaf_y + leaf_size//2], fill=colors['leaf'])
|
||||
else:
|
||||
# Regular leaves
|
||||
draw.ellipse([leaf_x - leaf_size//2, leaf_y - leaf_size//3,
|
||||
leaf_x + leaf_size//2, leaf_y + leaf_size//3], fill=colors['leaf'])
|
||||
|
||||
# Draw fruits for mature stages
|
||||
if stage >= 4 and self.current_plant.get() in ['tomato', 'strawberry']:
|
||||
fruit_color = colors['fruit']
|
||||
for i in range(min(stage - 3, 3)):
|
||||
fruit_x = center_x + random.randint(-15, 15)
|
||||
fruit_y = int(top_y + random.randint(10, int(plant_height//2)))
|
||||
fruit_size = 5 + stage
|
||||
draw.ellipse([fruit_x - fruit_size, fruit_y - fruit_size,
|
||||
fruit_x + fruit_size, fruit_y + fruit_size], fill=fruit_color)
|
||||
|
||||
# Add stage indicator
|
||||
draw.text((5, 5), f"Stage {stage}", fill='black')
|
||||
|
||||
return img
|
||||
|
||||
def pil_to_tk_data(self, pil_image):
|
||||
"""Convert PIL image to tkinter PhotoImage data"""
|
||||
import io
|
||||
import base64
|
||||
|
||||
# Convert to PNG bytes
|
||||
buffer = io.BytesIO()
|
||||
pil_image.save(buffer, format='PNG')
|
||||
|
||||
# Encode to base64
|
||||
img_data = base64.b64encode(buffer.getvalue())
|
||||
|
||||
return img_data
|
||||
|
||||
def update_parameter_heatmap(self):
|
||||
self.heatmap_ax.clear()
|
||||
|
||||
# Create parameter correlation matrix
|
||||
param_names = list(self.env_params.keys())
|
||||
param_values = [param.get() for param in self.env_params.values()]
|
||||
|
||||
# Create a correlation-like matrix for visualization
|
||||
matrix = np.zeros((len(param_names), len(param_names)))
|
||||
|
||||
for i, val_i in enumerate(param_values):
|
||||
for j, val_j in enumerate(param_values):
|
||||
if i == j:
|
||||
matrix[i][j] = val_i
|
||||
else:
|
||||
# Create interesting correlations
|
||||
correlation = abs(val_i - val_j) / 100
|
||||
matrix[i][j] = correlation * 50
|
||||
|
||||
# Create heatmap
|
||||
sns.heatmap(matrix, annot=True, fmt='.1f', cmap='RdYlGn',
|
||||
xticklabels=[name.replace('_', ' ').title() for name in param_names],
|
||||
yticklabels=[name.replace('_', ' ').title() for name in param_names],
|
||||
ax=self.heatmap_ax, cbar_kws={'label': 'Parameter Intensity'})
|
||||
|
||||
self.heatmap_ax.set_title(f'{self.current_plant.get().title()} Parameter Heatmap')
|
||||
plt.setp(self.heatmap_ax.get_xticklabels(), rotation=45, ha='right')
|
||||
plt.setp(self.heatmap_ax.get_yticklabels(), rotation=0)
|
||||
|
||||
self.heatmap_canvas.draw()
|
||||
|
||||
def update_3d_visualization(self):
|
||||
self.ax_3d.clear()
|
||||
|
||||
# Create 3D scatter plot of parameters
|
||||
temp = self.env_params['temperature'].get()
|
||||
humidity = self.env_params['humidity'].get()
|
||||
light = self.env_params['brightness'].get()
|
||||
|
||||
# Generate some sample data points around current parameters
|
||||
n_points = 50
|
||||
temps = np.random.normal(temp, 5, n_points)
|
||||
humids = np.random.normal(humidity, 5, n_points)
|
||||
lights = np.random.normal(light, 5, n_points)
|
||||
|
||||
# Color points based on "health" (distance from optimal)
|
||||
optimal_temp, optimal_humid, optimal_light = 25, 60, 50
|
||||
distances = np.sqrt((temps - optimal_temp)**2 +
|
||||
(humids - optimal_humid)**2 +
|
||||
(lights - optimal_light)**2)
|
||||
|
||||
colors = plt.cm.RdYlGn_r(distances / distances.max())
|
||||
|
||||
scatter = self.ax_3d.scatter(temps, humids, lights, c=colors, s=50, alpha=0.7)
|
||||
|
||||
# Highlight current point
|
||||
self.ax_3d.scatter([temp], [humidity], [light], c='red', s=200, marker='*')
|
||||
|
||||
self.ax_3d.set_xlabel('Temperature (°C)')
|
||||
self.ax_3d.set_ylabel('Humidity (%)')
|
||||
self.ax_3d.set_zlabel('Light Intensity')
|
||||
self.ax_3d.set_title(f'3D Parameter Space - {self.current_plant.get().title()}')
|
||||
|
||||
self.canvas_3d.draw()
|
||||
|
||||
def update_statistics(self):
|
||||
self.stats_text.delete(1.0, tk.END)
|
||||
|
||||
# Calculate mock statistics
|
||||
params = {name: param.get() for name, param in self.env_params.items()}
|
||||
|
||||
avg_param = sum(params.values()) / len(params)
|
||||
health_score = min(100, avg_param * 1.2)
|
||||
growth_rate = health_score / 20
|
||||
|
||||
stats_text = f"""🌱 PLANT STATISTICS
|
||||
{'='*25}
|
||||
|
||||
Plant Type: {self.current_plant.get().title()}
|
||||
Mode: {self.ambient_mode.get().title()}
|
||||
|
||||
📊 Current Metrics:
|
||||
Health Score: {health_score:.1f}%
|
||||
Growth Rate: {growth_rate:.2f} cm/day
|
||||
Avg Parameter: {avg_param:.1f}
|
||||
|
||||
🌡️ Environment:
|
||||
Temperature: {params['temperature']:.1f}°C
|
||||
Humidity: {params['humidity']:.1f}%
|
||||
Soil pH: {params['soil_acidity']:.1f}
|
||||
Light: {params['brightness']:.1f} lux
|
||||
|
||||
💧 Resources:
|
||||
Water: {params['water']:.1f}%
|
||||
Nutrients: {params['nutrients']:.1f}%
|
||||
CO2: {params['co2']:.1f} ppm
|
||||
|
||||
⏰ Updated: {datetime.now().strftime('%H:%M:%S')}
|
||||
"""
|
||||
|
||||
self.stats_text.insert(1.0, stats_text)
|
||||
|
||||
def update_health_indicator(self):
|
||||
self.health_canvas.delete("all")
|
||||
|
||||
# Calculate health score
|
||||
params = list(self.env_params.values())
|
||||
health_score = sum(param.get() for param in params) / len(params)
|
||||
|
||||
# Draw health bar
|
||||
bar_width = 180
|
||||
bar_height = 20
|
||||
x_start = 10
|
||||
y_start = 40
|
||||
|
||||
# Background
|
||||
self.health_canvas.create_rectangle(x_start, y_start,
|
||||
x_start + bar_width, y_start + bar_height,
|
||||
fill='lightgray', outline='black')
|
||||
|
||||
# Health bar
|
||||
health_width = (health_score / 100) * bar_width
|
||||
if health_score > 70:
|
||||
color = 'green'
|
||||
elif health_score > 40:
|
||||
color = 'orange'
|
||||
else:
|
||||
color = 'red'
|
||||
|
||||
self.health_canvas.create_rectangle(x_start, y_start,
|
||||
x_start + health_width, y_start + bar_height,
|
||||
fill=color, outline='')
|
||||
|
||||
# Health text
|
||||
self.health_canvas.create_text(100, 25, text=f"Health: {health_score:.1f}%",
|
||||
font=('Arial', 12, 'bold'))
|
||||
|
||||
# Status emoji
|
||||
if health_score > 80:
|
||||
emoji = "🌟"
|
||||
status = "Excellent"
|
||||
elif health_score > 60:
|
||||
emoji = "😊"
|
||||
status = "Good"
|
||||
elif health_score > 40:
|
||||
emoji = "😐"
|
||||
status = "Fair"
|
||||
else:
|
||||
emoji = "😟"
|
||||
status = "Poor"
|
||||
|
||||
self.health_canvas.create_text(100, 75, text=f"{emoji} {status}",
|
||||
font=('Arial', 10))
|
||||
|
||||
def update_radar_chart(self):
|
||||
self.radar_ax.clear()
|
||||
|
||||
# Parameter names and values
|
||||
param_names = ['Temp', 'Humid', 'pH', 'Press', 'Light', 'Nutri', 'Water', 'CO2']
|
||||
param_values = [param.get() for param in self.env_params.values()]
|
||||
|
||||
# Number of variables
|
||||
N = len(param_names)
|
||||
|
||||
# Compute angle for each axis
|
||||
angles = [n / float(N) * 2 * np.pi for n in range(N)]
|
||||
angles += angles[:1] # Complete the circle
|
||||
|
||||
# Add values
|
||||
param_values += param_values[:1] # Complete the circle
|
||||
|
||||
# Plot
|
||||
self.radar_ax.plot(angles, param_values, 'o-', linewidth=2,
|
||||
color=self.plant_colors[self.current_plant.get()]['leaf'])
|
||||
self.radar_ax.fill(angles, param_values, alpha=0.25,
|
||||
color=self.plant_colors[self.current_plant.get()]['leaf'])
|
||||
|
||||
# Add labels
|
||||
self.radar_ax.set_xticks(angles[:-1])
|
||||
self.radar_ax.set_xticklabels(param_names)
|
||||
self.radar_ax.set_ylim(0, 100)
|
||||
self.radar_ax.set_title(f'{self.current_plant.get().title()} Parameters',
|
||||
pad=20, fontsize=10)
|
||||
self.radar_ax.grid(True)
|
||||
|
||||
self.radar_canvas.draw()
|
||||
|
||||
def animate_growth(self):
|
||||
"""Animate parameter changes to show dynamic growth"""
|
||||
def animate_step(step):
|
||||
if step < 50: # 50 animation steps
|
||||
# Gradually change parameters
|
||||
for param in self.env_params.values():
|
||||
current = param.get()
|
||||
target = random.uniform(20, 80)
|
||||
new_value = current + (target - current) * 0.1
|
||||
param.set(new_value)
|
||||
|
||||
self.update_all_graphics()
|
||||
self.root.after(100, lambda: animate_step(step + 1))
|
||||
|
||||
animate_step(0)
|
||||
|
||||
def randomize_parameters(self):
|
||||
"""Randomize all parameters for demo purposes"""
|
||||
for param in self.env_params.values():
|
||||
param.set(random.uniform(10, 90))
|
||||
self.update_all_graphics()
|
||||
|
||||
def reset_demo(self):
|
||||
"""Reset all parameters to default values"""
|
||||
defaults = {
|
||||
'temperature': 22.0,
|
||||
'humidity': 65.0,
|
||||
'soil_acidity': 6.5,
|
||||
'pressure': 50.0, # Normalized for demo
|
||||
'brightness': 50.0,
|
||||
'nutrients': 75.0,
|
||||
'water': 80.0,
|
||||
'co2': 40.0
|
||||
}
|
||||
|
||||
for param_name, default_value in defaults.items():
|
||||
self.env_params[param_name].set(default_value)
|
||||
|
||||
self.current_plant.set("tomato")
|
||||
self.ambient_mode.set("controlled")
|
||||
self.update_all_graphics()
|
||||
|
||||
def auto_update(self):
|
||||
"""Auto-update for dynamic effects"""
|
||||
if self.ambient_mode.get() == "open":
|
||||
# Add small random variations in open mode
|
||||
for param in self.env_params.values():
|
||||
current = param.get()
|
||||
variation = random.uniform(-1, 1)
|
||||
new_value = max(0, min(100, current + variation))
|
||||
param.set(new_value)
|
||||
|
||||
self.update_all_graphics()
|
||||
|
||||
# Schedule next update
|
||||
self.root.after(2000, self.auto_update) # Update every 2 seconds
|
||||
|
||||
def main():
|
||||
root = tk.Tk()
|
||||
app = PlantGrowthGraphicsDemo(root)
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
239
PlantDashboard/image_generator.py
Normal file
239
PlantDashboard/image_generator.py
Normal file
|
@ -0,0 +1,239 @@
|
|||
from PIL import Image, ImageDraw, ImageFont
|
||||
import random
|
||||
import math
|
||||
import os
|
||||
|
||||
class ImageGenerator:
|
||||
def __init__(self):
|
||||
self.image_size = (400, 300)
|
||||
self.plant_colors = {
|
||||
'tomato': {'stem': '#228B22', 'leaf': '#32CD32', 'fruit': '#FF6347'},
|
||||
'basil': {'stem': '#228B22', 'leaf': '#90EE90', 'fruit': '#FFFFFF'},
|
||||
'mint': {'stem': '#228B22', 'leaf': '#98FB98', 'fruit': '#FFFFFF'},
|
||||
'lettuce': {'stem': '#228B22', 'leaf': '#ADFF2F', 'fruit': '#FFFFFF'},
|
||||
'rosemary': {'stem': '#8B4513', 'leaf': '#556B2F', 'fruit': '#FFFFFF'},
|
||||
'strawberry': {'stem': '#228B22', 'leaf': '#32CD32', 'fruit': '#FF1493'}
|
||||
}
|
||||
|
||||
def generate_evolution(self, plant_type, parameters, prediction, baseline_path=None):
|
||||
"""Generate a series of images showing plant evolution"""
|
||||
growth_stages = prediction['growth_stages']
|
||||
health_score = prediction['health_score']
|
||||
|
||||
images = []
|
||||
|
||||
# Generate images for key growth stages
|
||||
stage_indices = [0, len(growth_stages)//4, len(growth_stages)//2,
|
||||
3*len(growth_stages)//4, len(growth_stages)-1]
|
||||
|
||||
for i, stage_idx in enumerate(stage_indices):
|
||||
if stage_idx < len(growth_stages):
|
||||
height = growth_stages[stage_idx]
|
||||
image = self.generate_plant_image(plant_type, height, health_score, i+1)
|
||||
images.append(image)
|
||||
|
||||
return images
|
||||
|
||||
def generate_plant_image(self, plant_type, height, health_score, stage):
|
||||
"""Generate a single plant image"""
|
||||
img = Image.new('RGB', self.image_size, color='#87CEEB') # Sky blue background
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
# Draw ground
|
||||
ground_y = self.image_size[1] - 50
|
||||
draw.rectangle([0, ground_y, self.image_size[0], self.image_size[1]],
|
||||
fill='#8B4513') # Brown ground
|
||||
|
||||
# Get plant colors (default to tomato since we removed plant type selection)
|
||||
colors = self.plant_colors.get(plant_type, self.plant_colors['tomato'])
|
||||
|
||||
# Calculate plant dimensions based on height and health
|
||||
if stage == 0: # Initial/seed stage
|
||||
plant_height = 5 # Very small initial plant
|
||||
plant_width = 3
|
||||
else:
|
||||
plant_height = min(height * 2, self.image_size[1] - 100) # Scale for display
|
||||
plant_width = plant_height * 0.6
|
||||
|
||||
# Adjust colors based on health
|
||||
health_factor = health_score / 100.0
|
||||
stem_color = self._adjust_color_health(colors['stem'], health_factor)
|
||||
leaf_color = self._adjust_color_health(colors['leaf'], health_factor)
|
||||
|
||||
# Plant center position
|
||||
center_x = self.image_size[0] // 2
|
||||
base_y = ground_y
|
||||
|
||||
if stage == 0:
|
||||
# Draw seed/initial state
|
||||
self._draw_seed(draw, center_x, base_y - 10, health_factor)
|
||||
else:
|
||||
# Draw stem
|
||||
stem_width = max(2, int(plant_height * 0.05))
|
||||
stem_top_y = base_y - plant_height
|
||||
draw.rectangle([center_x - stem_width//2, int(stem_top_y),
|
||||
center_x + stem_width//2, base_y], fill=stem_color)
|
||||
|
||||
# Draw leaves based on plant type and stage
|
||||
self._draw_leaves(draw, plant_type, center_x, stem_top_y, base_y,
|
||||
plant_width, leaf_color, stage)
|
||||
|
||||
# Draw fruits/flowers if applicable
|
||||
if stage >= 3 and plant_type in ['tomato', 'strawberry']:
|
||||
self._draw_fruits(draw, plant_type, center_x, stem_top_y, base_y,
|
||||
colors['fruit'], stage)
|
||||
|
||||
# Add stage label
|
||||
try:
|
||||
font = ImageFont.load_default()
|
||||
except:
|
||||
font = None
|
||||
|
||||
if stage == 0:
|
||||
stage_text = f"Initial State - Seed"
|
||||
else:
|
||||
stage_text = f"Stage {stage} - Height: {height:.1f}cm"
|
||||
|
||||
if font:
|
||||
draw.text((10, 10), stage_text, fill='black', font=font)
|
||||
else:
|
||||
draw.text((10, 10), stage_text, fill='black')
|
||||
|
||||
# Add health indicator
|
||||
health_text = f"Health: {health_score:.1f}%"
|
||||
health_color = 'green' if health_score > 70 else 'orange' if health_score > 40 else 'red'
|
||||
if font:
|
||||
draw.text((10, 30), health_text, fill=health_color, font=font)
|
||||
else:
|
||||
draw.text((10, 30), health_text, fill=health_color)
|
||||
|
||||
return img
|
||||
|
||||
def _draw_seed(self, draw, x, y, health_factor):
|
||||
"""Draw a seed for the initial state"""
|
||||
seed_size = 8
|
||||
seed_color = '#8B4513' # Brown seed
|
||||
|
||||
# Adjust seed color based on health
|
||||
if health_factor > 0.7:
|
||||
seed_color = '#654321' # Healthy brown
|
||||
elif health_factor > 0.4:
|
||||
seed_color = '#8B4513' # Normal brown
|
||||
else:
|
||||
seed_color = '#A0522D' # Pale brown
|
||||
|
||||
# Draw seed
|
||||
draw.ellipse([x - seed_size, y - seed_size//2,
|
||||
x + seed_size, y + seed_size//2], fill=seed_color)
|
||||
|
||||
# Draw small sprout if health is good
|
||||
if health_factor > 0.5:
|
||||
sprout_color = '#90EE90'
|
||||
draw.line([x, y - seed_size//2, x, y - seed_size//2 - 5],
|
||||
fill=sprout_color, width=2)
|
||||
|
||||
def _draw_leaves(self, draw, plant_type, center_x, top_y, base_y, width, color, stage):
|
||||
"""Draw leaves based on plant type"""
|
||||
plant_height = base_y - top_y
|
||||
num_leaves = min(stage * 2, 8) # More leaves as plant grows
|
||||
|
||||
for i in range(num_leaves):
|
||||
# Calculate leaf position
|
||||
y_pos = base_y - (i + 1) * (plant_height / (num_leaves + 1))
|
||||
side = 1 if i % 2 == 0 else -1 # Alternate sides
|
||||
|
||||
leaf_x = center_x + side * (width * 0.3)
|
||||
leaf_size = width * 0.2 * (1 + stage * 0.1)
|
||||
|
||||
if plant_type == 'lettuce':
|
||||
# Draw broad leaves for lettuce
|
||||
self._draw_broad_leaf(draw, leaf_x, y_pos, leaf_size, color)
|
||||
elif plant_type == 'rosemary':
|
||||
# Draw needle-like leaves for rosemary
|
||||
self._draw_needle_leaf(draw, leaf_x, y_pos, leaf_size, color)
|
||||
else:
|
||||
# Draw regular oval leaves (default for tomato)
|
||||
self._draw_oval_leaf(draw, leaf_x, y_pos, leaf_size, color)
|
||||
|
||||
def _draw_oval_leaf(self, draw, x, y, size, color):
|
||||
"""Draw an oval leaf"""
|
||||
draw.ellipse([x - size//2, y - size//3, x + size//2, y + size//3], fill=color)
|
||||
|
||||
def _draw_broad_leaf(self, draw, x, y, size, color):
|
||||
"""Draw a broad leaf for lettuce"""
|
||||
points = [
|
||||
(x, y - size//2),
|
||||
(x + size//2, y),
|
||||
(x, y + size//2),
|
||||
(x - size//2, y)
|
||||
]
|
||||
draw.polygon(points, fill=color)
|
||||
|
||||
def _draw_needle_leaf(self, draw, x, y, size, color):
|
||||
"""Draw needle-like leaves for rosemary"""
|
||||
for i in range(3):
|
||||
offset = (i - 1) * 3
|
||||
draw.line([x + offset, y - size//4, x + offset, y + size//4],
|
||||
fill=color, width=2)
|
||||
|
||||
def _draw_fruits(self, draw, plant_type, center_x, top_y, base_y, color, stage):
|
||||
"""Draw fruits based on plant type"""
|
||||
if plant_type == 'tomato':
|
||||
# Draw tomatoes
|
||||
num_fruits = min(stage - 2, 4)
|
||||
for i in range(num_fruits):
|
||||
fruit_x = center_x + random.randint(-20, 20)
|
||||
fruit_y = int(top_y + random.randint(10, (base_y - top_y) // 2))
|
||||
fruit_size = 8 + stage * 2
|
||||
draw.ellipse([fruit_x - fruit_size, fruit_y - fruit_size,
|
||||
fruit_x + fruit_size, fruit_y + fruit_size], fill=color)
|
||||
|
||||
elif plant_type == 'strawberry':
|
||||
# Draw strawberries
|
||||
num_fruits = min(stage - 2, 3)
|
||||
for i in range(num_fruits):
|
||||
fruit_x = center_x + random.randint(-15, 15)
|
||||
fruit_y = base_y - random.randint(20, 40)
|
||||
self._draw_strawberry(draw, fruit_x, fruit_y, color)
|
||||
|
||||
def _draw_strawberry(self, draw, x, y, color):
|
||||
"""Draw a strawberry shape"""
|
||||
# Draw strawberry body
|
||||
points = [(x, y - 8), (x + 6, y), (x, y + 8), (x - 6, y)]
|
||||
draw.polygon(points, fill=color)
|
||||
|
||||
# Draw strawberry top (green)
|
||||
draw.polygon([(x - 3, y - 8), (x, y - 12), (x + 3, y - 8)], fill='green')
|
||||
|
||||
def _adjust_color_health(self, color_hex, health_factor):
|
||||
"""Adjust color based on plant health"""
|
||||
# Convert hex to RGB
|
||||
color_hex = color_hex.lstrip('#')
|
||||
r, g, b = tuple(int(color_hex[i:i+2], 16) for i in (0, 2, 4))
|
||||
|
||||
# Adjust brightness based on health
|
||||
factor = 0.5 + health_factor * 0.5 # Range from 0.5 to 1.0
|
||||
r = int(r * factor)
|
||||
g = int(g * factor)
|
||||
b = int(b * factor)
|
||||
|
||||
# Ensure values are within valid range
|
||||
r = max(0, min(255, r))
|
||||
g = max(0, min(255, g))
|
||||
b = max(0, min(255, b))
|
||||
|
||||
return f'#{r:02x}{g:02x}{b:02x}'
|
||||
|
||||
def save_evolution_sequence(self, images, filename):
|
||||
"""Save evolution images as separate files"""
|
||||
if images:
|
||||
try:
|
||||
base_name = filename.rsplit('.', 1)[0]
|
||||
for i, image in enumerate(images):
|
||||
stage_filename = f"{base_name}_stage_{i+1}.png"
|
||||
image.save(stage_filename)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error saving images: {e}")
|
||||
return False
|
||||
return False
|
102
PlantDashboard/install_requirements.py
Normal file
102
PlantDashboard/install_requirements.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
"""
|
||||
Installation script for Plant Growth Forecasting Dashboard
|
||||
(Without opencv-cv and seaborn dependencies)
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
def install_package(package):
|
||||
"""Install a package using pip"""
|
||||
try:
|
||||
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
def check_tkinter():
|
||||
"""Check if tkinter is available"""
|
||||
try:
|
||||
import tkinter
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def main():
|
||||
print("🌱 Plant Growth Forecasting Dashboard - Installation Script")
|
||||
print("=" * 60)
|
||||
|
||||
# Check Python version
|
||||
if sys.version_info < (3.7, 0):
|
||||
print("❌ Error: Python 3.7 or higher is required")
|
||||
print(f"Current version: {sys.version}")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"✅ Python version: {sys.version}")
|
||||
|
||||
# Check tkinter availability
|
||||
if not check_tkinter():
|
||||
print("❌ tkinter is not available!")
|
||||
print("Please install tkinter using your system package manager:")
|
||||
print(" Ubuntu/Debian: sudo apt-get install python3-tk")
|
||||
print(" CentOS/RHEL: sudo yum install tkinter")
|
||||
print(" macOS: tkinter should be included with Python")
|
||||
print(" Windows: tkinter should be included with Python")
|
||||
sys.exit(1)
|
||||
|
||||
print("✅ tkinter is available")
|
||||
|
||||
# Install requirements
|
||||
requirements_file = os.path.join(os.path.dirname(__file__), "..", "requirements.txt")
|
||||
|
||||
if not os.path.exists(requirements_file):
|
||||
print("❌ requirements.txt not found!")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n📦 Installing required packages...")
|
||||
|
||||
try:
|
||||
subprocess.check_call([
|
||||
sys.executable, "-m", "pip", "install", "-r", requirements_file
|
||||
])
|
||||
print("✅ All packages installed successfully!")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"❌ Error installing packages: {e}")
|
||||
print("\nTrying to install core packages individually...")
|
||||
|
||||
core_packages = [
|
||||
"matplotlib>=3.5.0",
|
||||
"pandas>=1.3.0",
|
||||
"numpy>=1.21.0",
|
||||
"Pillow>=8.3.0",
|
||||
"joblib>=1.1.0"
|
||||
]
|
||||
|
||||
failed_packages = []
|
||||
for package in core_packages:
|
||||
print(f"Installing {package}...")
|
||||
if install_package(package):
|
||||
print(f"✅ {package} installed")
|
||||
else:
|
||||
print(f"❌ Failed to install {package}")
|
||||
failed_packages.append(package)
|
||||
|
||||
if failed_packages:
|
||||
print(f"\n❌ Failed to install: {', '.join(failed_packages)}")
|
||||
print("Please install these packages manually")
|
||||
sys.exit(1)
|
||||
|
||||
print("\n🎉 Installation completed successfully!")
|
||||
print("\nTo run the application:")
|
||||
print(" python scripts/launcher.py")
|
||||
print("\nOr run the main dashboard directly:")
|
||||
print(" python scripts/main_dashboard.py")
|
||||
|
||||
print("\n📋 Note: This version excludes opencv-cv and seaborn")
|
||||
print(" • Heatmaps use matplotlib instead of seaborn")
|
||||
print(" • Video functionality has been removed")
|
||||
print(" • All other features remain fully functional")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
52
PlantDashboard/launcher.py
Normal file
52
PlantDashboard/launcher.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
"""
|
||||
Plant Growth Forecasting Dashboard Launcher
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Add the current directory to the Python path
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, current_dir)
|
||||
|
||||
try:
|
||||
from main_dashboard import PlantGrowthDashboard
|
||||
import tkinter as tk
|
||||
|
||||
def main():
|
||||
print("🌱 Starting Plant Growth Forecasting Dashboard...")
|
||||
print("Loading AI models and initializing interface...")
|
||||
|
||||
root = tk.Tk()
|
||||
|
||||
# Set application icon and styling
|
||||
try:
|
||||
root.iconname("Plant Growth Forecaster")
|
||||
except:
|
||||
pass
|
||||
|
||||
app = PlantGrowthDashboard(root)
|
||||
|
||||
print("✅ Dashboard ready!")
|
||||
print("Features available:")
|
||||
print(" • AI-powered plant growth prediction")
|
||||
print(" • Real-time parameter control")
|
||||
print(" • Visual growth simulation")
|
||||
print(" • Environmental analysis")
|
||||
print(" • Data export/import")
|
||||
print(" • Multiple ambient modes")
|
||||
print(" • Plant evolution visualization")
|
||||
|
||||
root.mainloop()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
except ImportError as e:
|
||||
print(f"❌ Error importing required modules: {e}")
|
||||
print("Please ensure all required packages are installed:")
|
||||
print(" pip install matplotlib pandas pillow numpy joblib")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"❌ Error starting application: {e}")
|
||||
sys.exit(1)
|
473
PlantDashboard/main_dashboard.py
Normal file
473
PlantDashboard/main_dashboard.py
Normal file
|
@ -0,0 +1,473 @@
|
|||
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(
|
||||
"<Configure>",
|
||||
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()
|
303
PlantDashboard/plant_model.py
Normal file
303
PlantDashboard/plant_model.py
Normal file
|
@ -0,0 +1,303 @@
|
|||
import numpy as np
|
||||
import joblib
|
||||
import pickle
|
||||
from datetime import datetime, timedelta
|
||||
import random
|
||||
|
||||
class PlantGrowthModel:
|
||||
def __init__(self):
|
||||
self.plant_characteristics = {
|
||||
'tomato': {
|
||||
'max_height': 150,
|
||||
'growth_rate_base': 2.5,
|
||||
'optimal_temp': (20, 25),
|
||||
'optimal_humidity': (60, 70),
|
||||
'optimal_ph': (6.0, 6.8),
|
||||
'phases': [
|
||||
{'name': 'Germination', 'duration': 7, 'growth_factor': 0.1},
|
||||
{'name': 'Seedling', 'duration': 14, 'growth_factor': 0.3},
|
||||
{'name': 'Vegetative', 'duration': 30, 'growth_factor': 1.0},
|
||||
{'name': 'Flowering', 'duration': 21, 'growth_factor': 0.7},
|
||||
{'name': 'Fruiting', 'duration': 28, 'growth_factor': 0.4}
|
||||
]
|
||||
},
|
||||
'basil': {
|
||||
'max_height': 60,
|
||||
'growth_rate_base': 1.8,
|
||||
'optimal_temp': (18, 24),
|
||||
'optimal_humidity': (50, 65),
|
||||
'optimal_ph': (6.0, 7.0),
|
||||
'phases': [
|
||||
{'name': 'Germination', 'duration': 5, 'growth_factor': 0.1},
|
||||
{'name': 'Seedling', 'duration': 10, 'growth_factor': 0.4},
|
||||
{'name': 'Vegetative', 'duration': 25, 'growth_factor': 1.0},
|
||||
{'name': 'Mature', 'duration': 30, 'growth_factor': 0.6}
|
||||
]
|
||||
},
|
||||
'mint': {
|
||||
'max_height': 40,
|
||||
'growth_rate_base': 2.0,
|
||||
'optimal_temp': (15, 22),
|
||||
'optimal_humidity': (65, 75),
|
||||
'optimal_ph': (6.0, 7.0),
|
||||
'phases': [
|
||||
{'name': 'Germination', 'duration': 7, 'growth_factor': 0.1},
|
||||
{'name': 'Seedling', 'duration': 12, 'growth_factor': 0.3},
|
||||
{'name': 'Vegetative', 'duration': 20, 'growth_factor': 1.0},
|
||||
{'name': 'Mature', 'duration': 35, 'growth_factor': 0.5}
|
||||
]
|
||||
},
|
||||
'lettuce': {
|
||||
'max_height': 25,
|
||||
'growth_rate_base': 1.2,
|
||||
'optimal_temp': (16, 20),
|
||||
'optimal_humidity': (70, 80),
|
||||
'optimal_ph': (6.0, 7.0),
|
||||
'phases': [
|
||||
{'name': 'Germination', 'duration': 4, 'growth_factor': 0.1},
|
||||
{'name': 'Seedling', 'duration': 8, 'growth_factor': 0.4},
|
||||
{'name': 'Vegetative', 'duration': 20, 'growth_factor': 1.0},
|
||||
{'name': 'Mature', 'duration': 18, 'growth_factor': 0.3}
|
||||
]
|
||||
},
|
||||
'rosemary': {
|
||||
'max_height': 120,
|
||||
'growth_rate_base': 0.8,
|
||||
'optimal_temp': (18, 25),
|
||||
'optimal_humidity': (40, 55),
|
||||
'optimal_ph': (6.0, 7.5),
|
||||
'phases': [
|
||||
{'name': 'Germination', 'duration': 14, 'growth_factor': 0.05},
|
||||
{'name': 'Seedling', 'duration': 21, 'growth_factor': 0.2},
|
||||
{'name': 'Vegetative', 'duration': 60, 'growth_factor': 1.0},
|
||||
{'name': 'Mature', 'duration': 90, 'growth_factor': 0.4}
|
||||
]
|
||||
},
|
||||
'strawberry': {
|
||||
'max_height': 30,
|
||||
'growth_rate_base': 1.5,
|
||||
'optimal_temp': (18, 24),
|
||||
'optimal_humidity': (60, 70),
|
||||
'optimal_ph': (5.5, 6.5),
|
||||
'phases': [
|
||||
{'name': 'Germination', 'duration': 10, 'growth_factor': 0.1},
|
||||
{'name': 'Seedling', 'duration': 15, 'growth_factor': 0.3},
|
||||
{'name': 'Vegetative', 'duration': 25, 'growth_factor': 1.0},
|
||||
{'name': 'Flowering', 'duration': 20, 'growth_factor': 0.6},
|
||||
{'name': 'Fruiting', 'duration': 30, 'growth_factor': 0.4}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def predict_growth(self, parameters):
|
||||
"""Predict plant growth based on environmental parameters"""
|
||||
plant_type = parameters.get('plant_type', 'tomato')
|
||||
ambient_mode = parameters.get('ambient_mode', 'controlled')
|
||||
|
||||
if plant_type not in self.plant_characteristics:
|
||||
plant_type = 'tomato'
|
||||
|
||||
plant_info = self.plant_characteristics[plant_type]
|
||||
|
||||
# Calculate environmental stress factors
|
||||
stress_factors = self._calculate_stress_factors(parameters, plant_info)
|
||||
|
||||
# Apply ambient mode effects
|
||||
if ambient_mode == 'open':
|
||||
# Add random variations for open environment
|
||||
for factor in stress_factors:
|
||||
stress_factors[factor] *= (0.8 + random.random() * 0.4)
|
||||
elif ambient_mode == 'semi-controlled':
|
||||
# Moderate variations
|
||||
for factor in stress_factors:
|
||||
stress_factors[factor] *= (0.9 + random.random() * 0.2)
|
||||
|
||||
# Calculate overall health score
|
||||
health_score = np.mean(list(stress_factors.values())) * 100
|
||||
|
||||
# Generate growth stages
|
||||
growth_stages = self._simulate_growth_stages(plant_info, stress_factors)
|
||||
|
||||
# Calculate final metrics
|
||||
final_height = growth_stages[-1] if growth_stages else 0
|
||||
growth_rate = final_height / len(growth_stages) if growth_stages else 0
|
||||
|
||||
# Calculate optimal conditions percentage
|
||||
optimal_conditions = self._calculate_optimal_conditions(parameters, plant_info)
|
||||
|
||||
# Estimate yield
|
||||
yield_estimate = self._estimate_yield(plant_type, health_score, final_height)
|
||||
|
||||
# Generate phase information
|
||||
phases = self._generate_phase_info(plant_info)
|
||||
|
||||
return {
|
||||
'growth_stages': growth_stages,
|
||||
'final_height': final_height,
|
||||
'growth_rate': growth_rate,
|
||||
'health_score': health_score,
|
||||
'optimal_conditions': optimal_conditions,
|
||||
'yield': yield_estimate,
|
||||
'phases': phases,
|
||||
'stress_factors': stress_factors
|
||||
}
|
||||
|
||||
def _calculate_stress_factors(self, params, plant_info):
|
||||
"""Calculate stress factors for each environmental parameter"""
|
||||
factors = {}
|
||||
|
||||
# Temperature stress
|
||||
temp = params.get('temperature', 20)
|
||||
opt_temp = plant_info['optimal_temp']
|
||||
if opt_temp[0] <= temp <= opt_temp[1]:
|
||||
factors['temperature'] = 1.0
|
||||
else:
|
||||
deviation = min(abs(temp - opt_temp[0]), abs(temp - opt_temp[1]))
|
||||
factors['temperature'] = max(0.1, 1.0 - deviation / 20.0)
|
||||
|
||||
# Humidity stress
|
||||
humidity = params.get('humidity', 60)
|
||||
opt_humidity = plant_info['optimal_humidity']
|
||||
if opt_humidity[0] <= humidity <= opt_humidity[1]:
|
||||
factors['humidity'] = 1.0
|
||||
else:
|
||||
deviation = min(abs(humidity - opt_humidity[0]), abs(humidity - opt_humidity[1]))
|
||||
factors['humidity'] = max(0.1, 1.0 - deviation / 40.0)
|
||||
|
||||
# pH stress
|
||||
ph = params.get('soil_acidity', 6.5)
|
||||
opt_ph = plant_info['optimal_ph']
|
||||
if opt_ph[0] <= ph <= opt_ph[1]:
|
||||
factors['ph'] = 1.0
|
||||
else:
|
||||
deviation = min(abs(ph - opt_ph[0]), abs(ph - opt_ph[1]))
|
||||
factors['ph'] = max(0.1, 1.0 - deviation / 2.0)
|
||||
|
||||
# Water stress
|
||||
water = params.get('water', 70) / 100.0
|
||||
factors['water'] = min(1.0, max(0.1, water))
|
||||
|
||||
# Nutrient stress
|
||||
nutrients = params.get('nutrients', 70) / 100.0
|
||||
factors['nutrients'] = min(1.0, max(0.1, nutrients))
|
||||
|
||||
# Light stress
|
||||
brightness = params.get('brightness', 30000)
|
||||
optimal_light = 40000 # Optimal light in lux
|
||||
light_factor = min(1.0, brightness / optimal_light)
|
||||
factors['light'] = max(0.1, light_factor)
|
||||
|
||||
# CO2 stress
|
||||
co2 = params.get('co2', 400)
|
||||
optimal_co2 = 600 # Optimal CO2 in ppm
|
||||
co2_factor = min(1.0, co2 / optimal_co2)
|
||||
factors['co2'] = max(0.3, co2_factor)
|
||||
|
||||
return factors
|
||||
|
||||
def _simulate_growth_stages(self, plant_info, stress_factors):
|
||||
"""Simulate daily growth stages"""
|
||||
base_rate = plant_info['growth_rate_base']
|
||||
max_height = plant_info['max_height']
|
||||
phases = plant_info['phases']
|
||||
|
||||
overall_stress = np.mean(list(stress_factors.values()))
|
||||
adjusted_rate = base_rate * overall_stress
|
||||
|
||||
growth_stages = []
|
||||
current_height = 0
|
||||
day = 0
|
||||
|
||||
for phase in phases:
|
||||
phase_duration = phase['duration']
|
||||
growth_factor = phase['growth_factor']
|
||||
|
||||
for _ in range(phase_duration):
|
||||
daily_growth = adjusted_rate * growth_factor
|
||||
# Add some randomness
|
||||
daily_growth *= (0.8 + random.random() * 0.4)
|
||||
|
||||
current_height += daily_growth
|
||||
current_height = min(current_height, max_height)
|
||||
growth_stages.append(current_height)
|
||||
day += 1
|
||||
|
||||
return growth_stages
|
||||
|
||||
def _calculate_optimal_conditions(self, params, plant_info):
|
||||
"""Calculate percentage of optimal conditions met"""
|
||||
optimal_count = 0
|
||||
total_conditions = 3 # temp, humidity, pH
|
||||
|
||||
temp = params.get('temperature', 20)
|
||||
if plant_info['optimal_temp'][0] <= temp <= plant_info['optimal_temp'][1]:
|
||||
optimal_count += 1
|
||||
|
||||
humidity = params.get('humidity', 60)
|
||||
if plant_info['optimal_humidity'][0] <= humidity <= plant_info['optimal_humidity'][1]:
|
||||
optimal_count += 1
|
||||
|
||||
ph = params.get('soil_acidity', 6.5)
|
||||
if plant_info['optimal_ph'][0] <= ph <= plant_info['optimal_ph'][1]:
|
||||
optimal_count += 1
|
||||
|
||||
return (optimal_count / total_conditions) * 100
|
||||
|
||||
def _estimate_yield(self, plant_type, health_score, final_height):
|
||||
"""Estimate plant yield based on health and growth"""
|
||||
yield_factors = {
|
||||
'tomato': {'base': 2.0, 'unit': 'kg'},
|
||||
'basil': {'base': 0.3, 'unit': 'kg'},
|
||||
'mint': {'base': 0.2, 'unit': 'kg'},
|
||||
'lettuce': {'base': 0.5, 'unit': 'kg'},
|
||||
'rosemary': {'base': 0.1, 'unit': 'kg'},
|
||||
'strawberry': {'base': 0.8, 'unit': 'kg'}
|
||||
}
|
||||
|
||||
if plant_type in yield_factors:
|
||||
base_yield = yield_factors[plant_type]['base']
|
||||
unit = yield_factors[plant_type]['unit']
|
||||
|
||||
# Adjust yield based on health and height
|
||||
health_factor = health_score / 100.0
|
||||
height_factor = min(1.0, final_height / 50.0) # Normalize height
|
||||
|
||||
estimated_yield = base_yield * health_factor * height_factor
|
||||
return f"{estimated_yield:.2f} {unit}"
|
||||
|
||||
return "N/A"
|
||||
|
||||
def _generate_phase_info(self, plant_info):
|
||||
"""Generate phase information with start/end days"""
|
||||
phases = []
|
||||
current_day = 0
|
||||
|
||||
for phase in plant_info['phases']:
|
||||
phases.append({
|
||||
'name': phase['name'],
|
||||
'start': current_day,
|
||||
'end': current_day + phase['duration'] - 1
|
||||
})
|
||||
current_day += phase['duration']
|
||||
|
||||
return phases
|
||||
|
||||
def save_model(self, filename):
|
||||
"""Save the model to a file"""
|
||||
try:
|
||||
with open(filename, 'wb') as f:
|
||||
pickle.dump(self.plant_characteristics, f)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error saving model: {e}")
|
||||
return False
|
||||
|
||||
def load_model(self, filename):
|
||||
"""Load the model from a file"""
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
self.plant_characteristics = pickle.load(f)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error loading model: {e}")
|
||||
return False
|
BIN
PlantDashboard/public/.DS_Store
vendored
Normal file
BIN
PlantDashboard/public/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
PlantDashboard/public/Flower.jpg
Normal file
BIN
PlantDashboard/public/Flower.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
BIN
PlantDashboard/public/TransparentFlower.png
Normal file
BIN
PlantDashboard/public/TransparentFlower.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 277 KiB |
BIN
PlantDashboard/public/logo.jpg
Normal file
BIN
PlantDashboard/public/logo.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.7 KiB |
BIN
PlantDashboard/public/transparentLogo.png
Normal file
BIN
PlantDashboard/public/transparentLogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
36
PlantDashboard/requirements.txt
Normal file
36
PlantDashboard/requirements.txt
Normal file
|
@ -0,0 +1,36 @@
|
|||
# Core data science and visualization libraries
|
||||
matplotlib>=3.5.0
|
||||
pandas>=1.3.0
|
||||
numpy>=1.21.0
|
||||
|
||||
# Image processing
|
||||
Pillow>=8.3.0
|
||||
|
||||
# Machine learning and model persistence
|
||||
joblib>=1.1.0
|
||||
scikit-learn>=1.0.0
|
||||
|
||||
# Additional utilities
|
||||
python-dateutil>=2.8.0
|
||||
pytz>=2021.1
|
||||
|
||||
# Data export formats
|
||||
openpyxl>=3.0.0
|
||||
xlsxwriter>=3.0.0
|
||||
|
||||
# Configuration management
|
||||
pyyaml>=5.4.0
|
||||
|
||||
# Logging enhancements
|
||||
colorlog>=6.6.0
|
||||
|
||||
# Performance monitoring (optional)
|
||||
psutil>=5.8.0
|
||||
memory-profiler>=0.60.0
|
||||
|
||||
# Note: tkinter comes pre-installed with most Python distributions
|
||||
# If tkinter is not available, install it using your system package manager:
|
||||
# Ubuntu/Debian: sudo apt-get install python3-tk
|
||||
# CentOS/RHEL: sudo yum install tkinter
|
||||
# macOS: tkinter is included with Python from python.org
|
||||
# Windows: tkinter is included with Python installer
|
110
README.md
110
README.md
|
@ -1,2 +1,110 @@
|
|||
# team-2
|
||||
# WeGrow
|
||||
|
||||
> *Hackathon Project - [NOI Hackathon] [2025]*
|
||||
|
||||
## 🚀 Overview
|
||||
|
||||
Hi everyone! We are WeGrow,
|
||||
|
||||
## 🎯 Problem Statement
|
||||
|
||||
Describe the challenge or problem your team is addressing in this hackathon.
|
||||
|
||||
## 💡 Solution
|
||||
|
||||
Explain your approach and how your solution addresses the problem statement.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- [ ] Feature 1
|
||||
- [ ] Feature 2
|
||||
- [ ] Feature 3
|
||||
- [ ] Feature 4
|
||||
|
||||
## 🛠️ Tech Stack
|
||||
|
||||
**Frontend:**
|
||||
|
||||
- Technology 1
|
||||
- Technology 2
|
||||
|
||||
**Backend:**
|
||||
|
||||
- Technology 1
|
||||
- Technology 2
|
||||
|
||||
**Other Tools:**
|
||||
|
||||
- Tool 1
|
||||
- Tool 2
|
||||
|
||||
## 🏗️ Architecture
|
||||
|
||||
```text
|
||||
[Add architecture diagram or description here]
|
||||
```
|
||||
|
||||
## 🚀 Using the application
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```bash
|
||||
# List any prerequisites here
|
||||
# e.g., Node.js 18+, Python 3.9+
|
||||
```
|
||||
|
||||
### Installation
|
||||
|
||||
1. Clone the repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/your-username/NOIProject.git
|
||||
cd NOIProject
|
||||
```
|
||||
|
||||
1. Install dependencies
|
||||
|
||||
```bash
|
||||
# Add installation commands here
|
||||
# e.g., npm install, pip install -r requirements.txt
|
||||
```
|
||||
|
||||
1. Set up environment variables
|
||||
|
||||
```bash
|
||||
# Copy example environment file
|
||||
cp .env.example .env
|
||||
# Edit .env with your configuration
|
||||
```
|
||||
|
||||
1. Run the application
|
||||
|
||||
```bash
|
||||
# Add run commands here
|
||||
# e.g., npm start, python app.py
|
||||
```
|
||||
|
||||
## 📸 Screenshots
|
||||
|
||||
Add screenshots of your application here
|
||||
|
||||
## 🎥 Demo
|
||||
|
||||
Add link to demo video or live deployment
|
||||
|
||||
## 🧑💻 Team
|
||||
|
||||
Meet our amazing team of 4:
|
||||
|
||||
| Name | Role | GitHub | LinkedIn |
|
||||
|------|------|---------|----------|
|
||||
| Member 1 | Role | [@username](https://github.com/username) | [LinkedIn](https://linkedin.com/in/username) |
|
||||
| Member 2 | Role | [@username](https://github.com/username) | [LinkedIn](https://linkedin.com/in/username) |
|
||||
| Member 3 | Role | [@username](https://github.com/username) | [LinkedIn](https://linkedin.com/in/username) |
|
||||
| Member 4 | Role | [@username](https://github.com/username) | [LinkedIn](https://linkedin.com/in/username) |
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
---
|
||||
|
|
41
path.txt
Normal file
41
path.txt
Normal file
|
@ -0,0 +1,41 @@
|
|||
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
|
||||
|
||||
-resolve numpy.float64 bug
|
||||
-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
|
||||
|
||||
SemiControlled mode:
|
||||
-the user choose how to set the parameters
|
||||
|
||||
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 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)
|
||||
|
||||
-make the plant information as a text field, remove all the connections with other functions
|
||||
|
||||
-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.
|
||||
|
||||
|
||||
DURING DINNER
|
||||
-update all on forgejo, hackathon page, forgejo
|
Loading…
Add table
Add a link
Reference in a new issue