workbykait commited on
Commit
f4395b3
Β·
verified Β·
1 Parent(s): 838f4ec

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +190 -0
app.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.express as px
5
+ from datetime import datetime, timedelta
6
+ from scipy import stats
7
+
8
+ def generate_synthetic_data(n=800):
9
+ np.random.seed(42)
10
+ start = datetime(2025, 1, 1, 0, 0)
11
+ ts = [start + timedelta(minutes=5 * i) for i in range(n)]
12
+
13
+ signal_dbm = np.random.normal(-82, 4, n)
14
+ snr_db = np.random.normal(24, 3, n)
15
+ ber = np.random.lognormal(-6.5, 0.6, n) # ~ 10^-6 to 10^-4
16
+ packet_loss = np.random.uniform(0, 8, n)
17
+
18
+ # Inject realistic interference (2 events)
19
+ signal_dbm[180:230] -= 22 # deep fade / jamming
20
+ snr_db[180:230] -= 18
21
+ packet_loss[180:230] = np.random.uniform(25, 60, 50)
22
+
23
+ signal_dbm[520:560] -= 12 # mild interference
24
+ snr_db[520:560] -= 9
25
+ packet_loss[520:560] = np.random.uniform(12, 30, 40)
26
+
27
+ df = pd.DataFrame({
28
+ "Timestamp": ts,
29
+ "Signal_Strength_dBm": signal_dbm,
30
+ "SNR_dB": snr_db,
31
+ "BER": ber,
32
+ "Packet_Loss_%": packet_loss
33
+ })
34
+ return df
35
+
36
+ def detect_anomalies_and_alerts(df):
37
+ df = df.copy()
38
+ # Z-score on SNR and Signal
39
+ df["SNR_z"] = np.abs(stats.zscore(df["SNR_dB"], nan_policy="omit"))
40
+ df["Signal_z"] = np.abs(stats.zscore(df["Signal_Strength_dBm"], nan_policy="omit"))
41
+ df["is_anomaly"] = (df["SNR_z"] > 3) | (df["Signal_z"] > 3) | (df["Packet_Loss_%"] > 20)
42
+
43
+ # Sudden drops (interference)
44
+ df["Signal_Diff"] = df["Signal_Strength_dBm"].diff()
45
+ sudden_drops = df[df["Signal_Diff"] < -12]
46
+
47
+ alerts = []
48
+ if len(sudden_drops) > 0:
49
+ alerts.append(f"🚨 {len(sudden_drops)} sudden signal drops (>12 dB) detected – possible interference/jamming.")
50
+ if (df["SNR_dB"] < 12).mean() > 0.1:
51
+ alerts.append("⚠️ More than 10% of time SNR < 12 dB – link degradation likely.")
52
+ if df["Packet_Loss_%"].mean() > 15:
53
+ alerts.append("⚠️ High average packet loss – interference or rain fade suspected.")
54
+ if len(alerts) == 0:
55
+ alerts.append("βœ… No major interference or anomalies detected.")
56
+
57
+ return df, "\n".join(alerts)
58
+
59
+ def calculate_risk_score(df):
60
+ score = 0
61
+ # SNR penalties
62
+ avg_snr = df["SNR_dB"].mean()
63
+ low_snr_pct = (df["SNR_dB"] < 15).mean() * 100
64
+ score += max(0, 40 - avg_snr) * 1.5
65
+ score += low_snr_pct * 0.8
66
+
67
+ # Signal & loss
68
+ if df["Signal_Strength_dBm"].min() < -105:
69
+ score += 25
70
+ score += df["Packet_Loss_%"].mean() * 1.2
71
+
72
+ # Anomaly count
73
+ anomaly_pct = df["is_anomaly"].mean() * 100 if "is_anomaly" in df.columns else 0
74
+ score += anomaly_pct * 0.7
75
+
76
+ risk = min(100, max(0, int(score)))
77
+ level = "Low 🟒" if risk < 35 else "Medium 🟑" if risk < 70 else "High πŸ”΄"
78
+ return f"**Risk Score: {risk}/100 – {level}**", risk
79
+
80
+ def create_plots(df):
81
+ fig_snr = px.line(df, x="Timestamp", y="SNR_dB", title="SNR Trend (dB)", markers=False)
82
+ fig_snr.update_traces(line_color="#00ff88")
83
+
84
+ fig_signal = px.line(df, x="Timestamp", y="Signal_Strength_dBm", title="Signal Strength (dBm)", markers=False)
85
+ fig_signal.update_traces(line_color="#ffaa00")
86
+
87
+ # Highlight anomalies
88
+ if "is_anomaly" in df.columns:
89
+ anomalies = df[df["is_anomaly"]]
90
+ if not anomalies.empty:
91
+ fig_snr.add_scatter(x=anomalies["Timestamp"], y=anomalies["SNR_dB"],
92
+ mode="markers", marker=dict(color="red", size=8, symbol="x"),
93
+ name="Anomaly / Interference")
94
+ fig_signal.add_scatter(x=anomalies["Timestamp"], y=anomalies["Signal_Strength_dBm"],
95
+ mode="markers", marker=dict(color="red", size=8, symbol="x"),
96
+ name="Anomaly / Interference")
97
+
98
+ return fig_snr, fig_signal
99
+
100
+ def analyze(file):
101
+ if file is None:
102
+ df = generate_synthetic_data()
103
+ demo_note = "πŸ”¬ Using built-in synthetic dataset with injected interference events (for demo)."
104
+ else:
105
+ df = pd.read_csv(file)
106
+ demo_note = "πŸ“ Uploaded file loaded successfully."
107
+
108
+ df["Timestamp"] = pd.to_datetime(df["Timestamp"], errors="coerce")
109
+ df = df.dropna(subset=["Timestamp"]).sort_values("Timestamp").reset_index(drop=True)
110
+
111
+ df, alerts = detect_anomalies_and_alerts(df)
112
+ risk_text, risk_val = calculate_risk_score(df)
113
+ fig_snr, fig_signal = create_plots(df)
114
+
115
+ stats = df.describe().round(3).to_html()
116
+
117
+ preview = df.head(15).to_html(index=False)
118
+
119
+ return (
120
+ preview,
121
+ fig_snr,
122
+ fig_signal,
123
+ risk_text,
124
+ alerts,
125
+ stats,
126
+ demo_note
127
+ )
128
+
129
+ # ==================== Gradio UI ====================
130
+ with gr.Blocks(title="Satellite Signal Log Analyzer", theme=gr.themes.Soft()) as demo:
131
+ gr.Markdown("# πŸ›°οΈ Satellite Signal Log Analyzer\n**Risk scoring β€’ Interference alerts β€’ Trend visualization**\nUpload your CSV or try the demo.")
132
+
133
+ with gr.Row():
134
+ with gr.Column(scale=1):
135
+ file_input = gr.File(label="Upload CSV Log (or leave empty for synthetic demo)", file_types=[".csv"])
136
+ analyze_btn = gr.Button("πŸš€ Analyze", variant="primary", size="large")
137
+
138
+ with gr.Column(scale=1):
139
+ sample_btn = gr.Button("πŸ“₯ Load Synthetic Demo Data", variant="secondary")
140
+
141
+ gr.Markdown("**Expected columns**: `Timestamp`, `Signal_Strength_dBm`, `SNR_dB` (optional: `BER`, `Packet_Loss_%`).")
142
+
143
+ with gr.Tabs():
144
+ with gr.Tab("πŸ“Š Overview"):
145
+ gr.Markdown("### Data Preview")
146
+ preview_html = gr.HTML()
147
+ gr.Markdown("### Summary Statistics")
148
+ stats_html = gr.HTML()
149
+
150
+ with gr.Tab("πŸ“ˆ Visualizations"):
151
+ with gr.Row():
152
+ snr_plot = gr.Plot(label="SNR Trend")
153
+ signal_plot = gr.Plot(label="Signal Strength Trend")
154
+
155
+ with gr.Tab("⚠️ Alerts & Risk"):
156
+ risk_output = gr.Markdown()
157
+ alerts_output = gr.Markdown()
158
+
159
+ with gr.Tab("ℹ️ Info"):
160
+ gr.Markdown("""
161
+ **How the risk score works**
162
+ β€’ Weighted penalties for low SNR, deep fades, high packet loss, and statistical outliers (Z-score > 3).
163
+ β€’ Interference alerts use sudden drops (>12 dB) and threshold crossings.
164
+ **Public datasets** to test with:
165
+ β€’ OPSSAT-AD (Kaggle) – real CubeSat telemetry with anomalies
166
+ β€’ LENS (Zenodo) – Starlink/LEO network measurements
167
+ β€’ GOCE Telemetry (Kaggle)
168
+ """)
169
+
170
+ gr.Markdown("### Demo Note")
171
+ demo_note_out = gr.Markdown()
172
+
173
+ # Button actions
174
+ analyze_btn.click(
175
+ fn=analyze,
176
+ inputs=file_input,
177
+ outputs=[preview_html, snr_plot, signal_plot, risk_output, alerts_output, stats_html, demo_note_out]
178
+ )
179
+
180
+ sample_btn.click(
181
+ fn=lambda: (None, None, None, None, None, None, None), # clear file
182
+ outputs=[file_input, preview_html, snr_plot, signal_plot, risk_output, alerts_output, stats_html]
183
+ ).then(
184
+ fn=analyze,
185
+ inputs=[file_input], # still None β†’ uses synthetic
186
+ outputs=[preview_html, snr_plot, signal_plot, risk_output, alerts_output, stats_html, demo_note_out]
187
+ )
188
+
189
+ if __name__ == "__main__":
190
+ demo.launch()