File size: 9,828 Bytes
eb0397c 18e1844 0611867 18e1844 6fcab3c 435c5ac 0611867 18e1844 0611867 18e1844 b78c560 18e1844 044304f 18e1844 044304f 18e1844 5fd748d 18e1844 3e1039d 18e1844 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | 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) |