FatimahEmadEldin's picture
Update app.py
18e1844 verified
import gradio as gr
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
import joblib
import os
import warnings
warnings.filterwarnings('ignore')
# --- 1. ROBUST FILE LOADING ---
try:
def find_file(filename, search_paths=['./', './data/']):
for path in search_paths:
filepath = os.path.join(path, filename)
if os.path.exists(filepath):
print(f"Found '{filename}' at: {filepath}")
return filepath
return None
scaler_path = find_file('scaler.joblib')
kmeans_path = find_file('kmeans_model.joblib')
forecasting_path = find_file('forecasting_models.joblib')
data_path = find_file('consolidated_farm_data.csv')
if not all([scaler_path, kmeans_path, forecasting_path, data_path]):
raise FileNotFoundError("Could not find all required model (.joblib) and data (.csv) files.")
scaler = joblib.load(scaler_path)
kmeans_model = joblib.load(kmeans_path)
forecasting_models = joblib.load(forecasting_path)
df_historical = pd.read_csv(data_path)
df_historical['timestamp'] = pd.to_datetime(df_historical['timestamp'])
ALL_FARMS = sorted(df_historical['farm_name'].unique())
FARM_COORDINATES = {
'alia': [24.434117, 39.624376], 'Abdula altazi': [24.499210, 39.661664],
'albadr': [24.499454, 39.666633], 'alhabibah': [24.499002, 39.667079],
'alia almadinah': [24.450111, 39.627500], 'almarbad': [24.442014, 39.628323],
'alosba': [24.431591, 39.605149], 'abuonoq': [24.494620, 39.623123],
'wahaa nakeel': [24.442692, 39.623028], 'wahaa 2': [24.442388, 39.621116]
}
farm_coords_df = pd.DataFrame.from_dict(FARM_COORDINATES, orient='index', columns=['lat', 'lon']).reset_index().rename(columns={'index':'farm_name'})
except FileNotFoundError as e:
raise FileNotFoundError(f"CRITICAL ERROR: {e}")
# --- 2. DEFINE CORE FUNCTIONS ---
def get_performance_report():
kpi_df = df_historical.groupby('farm_name').agg(
mean_ndvi=('NDVI', 'mean'), mean_evi=('EVI', 'mean'), std_ndvi=('NDVI', 'std')
).reset_index().dropna()
features = kpi_df[['mean_ndvi', 'mean_evi', 'std_ndvi']]
scaled_features = scaler.transform(features)
kpi_df['cluster'] = kmeans_model.predict(scaled_features)
cluster_centers = pd.DataFrame(scaler.inverse_transform(kmeans_model.cluster_centers_), columns=['mean_ndvi', 'mean_evi', 'std_ndvi'])
sorted_clusters = cluster_centers.sort_values(by='mean_ndvi', ascending=False).index
tier_map = {sorted_clusters[0]: 'Tier 1 (High)', sorted_clusters[1]: 'Tier 2 (Medium)', sorted_clusters[2]: 'Tier 3 (Low)'}
kpi_df['Performance Tier'] = kpi_df['cluster'].map(tier_map)
return kpi_df[['farm_name', 'Performance Tier', 'mean_ndvi', 'mean_evi']].sort_values('Performance Tier')
def detect_and_classify_anomalies(farm_name):
farm_data = df_historical[df_historical['farm_name'] == farm_name].set_index('timestamp').sort_index()
df_resampled = farm_data[['NDVI', 'NDWI', 'SAR_VV']].resample('W').mean().interpolate(method='linear')
df_change = df_resampled.diff().dropna()
rolling_std = df_change.rolling(window=12, min_periods=4).std()
thresholds = {'NDVI': rolling_std['NDVI'] * 1.5, 'NDWI': rolling_std['NDWI'] * 1.5, 'SAR_VV': rolling_std['SAR_VV'] * 1.5}
anomalies_found = []
for date, row in df_change.iterrows():
ndvi_change, ndwi_change, sar_vv_change = row['NDVI'], row['NDWI'], row['SAR_VV']
ndvi_thresh, ndwi_thresh, sar_thresh = thresholds['NDVI'].get(date, 0.07), thresholds['NDWI'].get(date, 0.07), thresholds['SAR_VV'].get(date, 1.0)
classification = "Normal"
if ndvi_change < -ndvi_thresh and sar_vv_change < -sar_thresh:
classification = 'Harvest Event'
elif ndvi_change < -ndvi_thresh and ndwi_change < -ndwi_thresh:
classification = 'Potential Drought Stress'
elif ndvi_change < -ndvi_thresh:
classification = 'General Stress Event'
if classification != "Normal":
anomalies_found.append({'Date': date, 'Classification': classification, 'NDVI Change': f"{ndvi_change:.3f}"})
fig = go.Figure()
fig.add_trace(go.Scatter(x=farm_data.index, y=farm_data['NDVI'], mode='lines', name='NDVI', line=dict(color='green')))
colors = {'Harvest Event': 'red', 'Potential Drought Stress': 'orange', 'General Stress Event': 'purple'}
# ✨ FINAL FIX: Manually add shapes and annotations instead of using fig.add_vline()
for anomaly in anomalies_found:
anomaly_date = anomaly['Date'].to_pydatetime()
line_color = colors.get(anomaly['Classification'])
# Add the vertical line shape
fig.add_shape(
type='line',
x0=anomaly_date, y0=0, x1=anomaly_date, y1=1,
yref='paper', # This makes the line span the full height of the plot
line=dict(color=line_color, width=2, dash='dash')
)
# Add the annotation text
fig.add_annotation(
x=anomaly_date, y=1.0, yref='paper', # Position text at the top
text=anomaly['Classification'],
showarrow=False,
yshift=10, # Shift text slightly above the top line
font=dict(color=line_color)
)
fig.update_layout(title=f'NDVI Timeline & Detected Anomalies for {farm_name}', xaxis_title='Date', yaxis_title='NDVI')
display_anomalies = [{'Date': a['Date'].strftime('%Y-%m-%d'), 'Classification': a['Classification'], 'NDVI Change': a['NDVI Change']} for a in anomalies_found]
return pd.DataFrame(display_anomalies), fig
def run_forecast(farm_name):
model = forecasting_models.get(farm_name)
last_date = df_historical['timestamp'].max()
future_dates = pd.to_datetime(pd.date_range(start=last_date, periods=12, freq='W'))
future_df = pd.DataFrame(index=future_dates)
future_df['day_of_year'] = future_df.index.dayofyear
farm_data = df_historical[df_historical['farm_name'] == farm_name]
future_df['EVI'] = farm_data['EVI'].iloc[-1]
future_df['NDWI'] = farm_data['NDWI'].iloc[-1]
predictions = model.predict(future_df[['day_of_year', 'EVI', 'NDWI']])
fig = go.Figure()
fig.add_trace(go.Scatter(x=farm_data['timestamp'], y=farm_data['NDVI'], mode='lines', name='Historical NDVI'))
fig.add_trace(go.Scatter(x=future_dates, y=predictions, mode='lines', name='Forecasted NDVI', line=dict(color='red', dash='dash')))
fig.update_layout(title=f'3-Month NDVI Forecast for {farm_name}')
return fig, pd.DataFrame({'Forecast Date': future_dates.strftime('%Y-%m-%d'), 'Predicted NDVI': np.round(predictions, 3)})
def plot_tier_distribution(report_df):
tier_counts = report_df['Performance Tier'].value_counts().reset_index()
tier_counts.columns = ['Performance Tier', 'Count']
fig = px.bar(tier_counts, x='Performance Tier', y='Count', title='Farm Distribution by Performance Tier',
color='Performance Tier', text_auto=True,
color_discrete_map={'Tier 1 (High)': 'green', 'Tier 2 (Medium)': 'orange', 'Tier 3 (Low)': 'red'})
fig.update_layout(showlegend=False)
return fig
# --- 3. BUILD GRADIO INTERFACE ---
df_performance_report = get_performance_report()
with gr.Blocks(theme=gr.themes.Soft(), title="Palm Farm Intelligence") as demo:
gr.Markdown("# Palm Farm Intelligence Platform")
with gr.Tabs():
with gr.TabItem("Performance Overview"):
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### All Farms Performance Tiers")
gr.DataFrame(df_performance_report)
gr.Markdown("### Tier Distribution")
tier_plot = gr.Plot()
with gr.Column(scale=2):
gr.Markdown("### Farm Locations")
map_plot = gr.Plot()
with gr.TabItem(" Anomaly Detection"):
gr.Markdown("### Intelligent Anomaly Detection")
anomaly_farm_selector = gr.Dropdown(ALL_FARMS, label="Select a Farm", value=ALL_FARMS[0])
with gr.Row():
anomaly_table = gr.DataFrame(headers=["Date", "Classification", "NDVI Change"])
anomaly_plot = gr.Plot()
with gr.TabItem(" NDVI Forecasting"):
gr.Markdown("### 3-Month Vegetation Health Forecast")
forecast_farm_selector = gr.Dropdown(ALL_FARMS, label="Select Farm to Forecast", value=ALL_FARMS[0])
forecast_plot = gr.Plot()
forecast_data = gr.DataFrame()
def update_anomaly_view(farm_name):
return detect_and_classify_anomalies(farm_name)
anomaly_farm_selector.change(fn=update_anomaly_view, inputs=anomaly_farm_selector, outputs=[anomaly_table, anomaly_plot])
def update_forecast_view(farm_name):
return run_forecast(farm_name)
forecast_farm_selector.change(fn=update_forecast_view, inputs=forecast_farm_selector, outputs=[forecast_plot, forecast_data])
def initial_load():
fig_map = px.scatter_mapbox(farm_coords_df, lat="lat", lon="lon", hover_name="farm_name",
color_discrete_sequence=["green"], zoom=8, height=500)
fig_map.update_layout(mapbox_style="open-street-map", margin={"r":0,"t":0,"l":0,"b":0})
fig_tier = plot_tier_distribution(df_performance_report)
an_table, an_plot = detect_and_classify_anomalies(ALL_FARMS[0])
fc_plot, fc_data = run_forecast(ALL_FARMS[0])
return fig_map, fig_tier, an_table, an_plot, fc_plot, fc_data
demo.load(fn=initial_load, outputs=[map_plot, tier_plot, anomaly_table, anomaly_plot, forecast_plot, forecast_data])
if __name__ == "__main__":
demo.launch(debug=True)