Merge branch 'main' into challenge-2-flutter
|
@ -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
|
@ -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
|
25
path.txt
|
@ -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.
|
||||||
|
|
||||||
|
|
BIN
test2_with_training_tuned_with_basil_and_tomaetos/.cache.sqlite
Normal 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
|
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 81 KiB |
|
@ -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
|
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 165 KiB |
After Width: | Height: | Size: 132 KiB |
After Width: | Height: | Size: 177 KiB |
After Width: | Height: | Size: 524 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 213 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 238 KiB |
After Width: | Height: | Size: 208 KiB |
After Width: | Height: | Size: 582 KiB |
After Width: | Height: | Size: 101 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 134 KiB |
After Width: | Height: | Size: 128 KiB |
After Width: | Height: | Size: 930 KiB |
After Width: | Height: | Size: 91 KiB |
After Width: | Height: | Size: 151 KiB |
After Width: | Height: | Size: 223 KiB |
After Width: | Height: | Size: 145 KiB |
After Width: | Height: | Size: 704 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 123 KiB |
After Width: | Height: | Size: 149 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 180 KiB |
After Width: | Height: | Size: 702 KiB |
After Width: | Height: | Size: 115 KiB |
After Width: | Height: | Size: 200 KiB |
After Width: | Height: | Size: 134 KiB |
After Width: | Height: | Size: 224 KiB |
After Width: | Height: | Size: 90 KiB |
After Width: | Height: | Size: 707 KiB |
After Width: | Height: | Size: 375 KiB |
After Width: | Height: | Size: 127 KiB |
After Width: | Height: | Size: 131 KiB |
After Width: | Height: | Size: 725 KiB |
After Width: | Height: | Size: 98 KiB |
After Width: | Height: | Size: 295 KiB |
After Width: | Height: | Size: 544 KiB |
After Width: | Height: | Size: 125 KiB |
After Width: | Height: | Size: 72 KiB |
After Width: | Height: | Size: 403 KiB |
After Width: | Height: | Size: 122 KiB |
After Width: | Height: | Size: 158 KiB |
After Width: | Height: | Size: 262 KiB |
After Width: | Height: | Size: 192 KiB |
After Width: | Height: | Size: 220 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 188 KiB |
After Width: | Height: | Size: 161 KiB |
After Width: | Height: | Size: 175 KiB |
After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 295 KiB |
After Width: | Height: | Size: 301 KiB |
After Width: | Height: | Size: 201 KiB |
After Width: | Height: | Size: 583 KiB |
After Width: | Height: | Size: 298 KiB |
After Width: | Height: | Size: 172 KiB |
After Width: | Height: | Size: 122 KiB |
After Width: | Height: | Size: 1.7 MiB |
After Width: | Height: | Size: 340 KiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 239 KiB |
After Width: | Height: | Size: 200 KiB |
After Width: | Height: | Size: 770 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 90 KiB |
After Width: | Height: | Size: 1.7 MiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 186 KiB |
After Width: | Height: | Size: 138 KiB |
After Width: | Height: | Size: 137 KiB |
After Width: | Height: | Size: 250 KiB |
After Width: | Height: | Size: 214 KiB |
After Width: | Height: | Size: 200 KiB |
After Width: | Height: | Size: 67 KiB |
After Width: | Height: | Size: 300 KiB |
After Width: | Height: | Size: 749 KiB |
After Width: | Height: | Size: 590 KiB |
After Width: | Height: | Size: 748 KiB |
After Width: | Height: | Size: 468 KiB |
After Width: | Height: | Size: 159 KiB |
After Width: | Height: | Size: 186 KiB |
After Width: | Height: | Size: 289 KiB |
After Width: | Height: | Size: 290 KiB |