workbykait commited on
Commit
491c55a
Β·
verified Β·
1 Parent(s): b7bf59a

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +286 -34
src/streamlit_app.py CHANGED
@@ -1,40 +1,292 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
 
 
 
12
 
13
- In the meantime, below is an example of what you can do with just a few lines of code:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  """
 
 
 
15
 
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
1
  import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+ from datetime import datetime
7
+ import tempfile
8
+ import os
9
+ from gradio_client import Client, handle_file
10
+ from sklearn.ensemble import IsolationForest
11
+ from PIL import Image
12
+ import io
13
 
14
+ st.set_page_config(
15
+ page_title="πŸ›°οΈ Satellite Log Companion",
16
+ layout="wide",
17
+ initial_sidebar_state="expanded",
18
+ page_icon="πŸ›°οΈ"
19
+ )
20
+
21
+ # Custom CSS for a clean mission-control look
22
+ st.markdown("""
23
+ <style>
24
+ .main {background-color: #0e1117;}
25
+ .stPlotlyChart {background-color: #1a1f2e; border-radius: 10px; padding: 10px;}
26
+ .kpi {font-size: 2rem; font-weight: bold; text-align: center;}
27
+ </style>
28
+ """, unsafe_allow_html=True)
29
+
30
+ # ====================== SESSION STATE & CACHING ======================
31
+ if "df" not in st.session_state:
32
+ st.session_state.df = None
33
+ if "gradio_client" not in st.session_state:
34
+ st.session_state.gradio_client = None
35
+
36
+ @st.cache_resource
37
+ def get_gradio_client(space_url: str, hf_token: str = None):
38
+ try:
39
+ if hf_token:
40
+ client = Client.duplicate(space_url, hf_token=hf_token)
41
+ else:
42
+ client = Client(space_url)
43
+ return client
44
+ except Exception as e:
45
+ st.error(f"Failed to connect to Gradio: {e}")
46
+ return None
47
+
48
+ @st.cache_data
49
+ def load_sample_data():
50
+ """Synthetic realistic satellite telemetry with injected anomalies"""
51
+ dates = pd.date_range("2025-02-01", periods=500, freq="5min")
52
+ np.random.seed(42)
53
+ data = {
54
+ "timestamp": dates,
55
+ "temperature_C": np.random.normal(25, 5, 500).clip(10, 45),
56
+ "voltage_V": np.random.normal(28, 1, 500).clip(24, 32),
57
+ "signal_dB": np.random.normal(-90, 5, 500).clip(-110, -70),
58
+ "altitude_km": np.random.normal(400, 10, 500).clip(380, 420),
59
+ "error_rate": np.random.poisson(2, 500) / 1000,
60
+ }
61
+ df = pd.DataFrame(data)
62
+ # Inject anomalies
63
+ df.loc[100:105, "temperature_C"] += 25 # thermal spike
64
+ df.loc[250:255, "voltage_V"] -= 8 # power drop
65
+ df.loc[400:405, "signal_dB"] += 30 # signal surge
66
+ return df
67
+
68
+ def parse_uploaded_file(uploaded):
69
+ if uploaded.name.endswith(".csv"):
70
+ return pd.read_csv(uploaded)
71
+ else:
72
+ st.warning("Only CSV supported for now. Your Gradio app can handle other formats.")
73
+ return None
74
+
75
+ # ====================== SIDEBAR ======================
76
+ with st.sidebar:
77
+ st.title("πŸ›°οΈ Mission Control")
78
+ st.markdown("**Companion to your Gradio Satellite Log Analyzer**")
79
+
80
+ gradio_url = st.text_input(
81
+ "Your Gradio Analyzer Space URL",
82
+ value="https://yourusername-satellite-log-analyzer.hf.space",
83
+ help="Paste the full URL of your Gradio Space"
84
+ )
85
+
86
+ hf_token = st.text_input("HF Token (optional - for private/duplicated Gradio)", type="password")
87
+
88
+ if st.button("πŸ”— Connect to Gradio"):
89
+ st.session_state.gradio_client = get_gradio_client(gradio_url, hf_token)
90
+ if st.session_state.gradio_client:
91
+ st.success("Connected! Use the Gradio tab to analyze.")
92
+ st.session_state.gradio_client.view_api() # shows in console/logs
93
+
94
+ st.divider()
95
+ if st.button("πŸ“₯ Load Sample Satellite Telemetry"):
96
+ st.session_state.df = load_sample_data()
97
+ st.success("Sample data loaded (with injected anomalies)!")
98
+
99
+ # ====================== TABS ======================
100
+ tab1, tab2, tab3, tab4, tab5, tab6, tab7 = st.tabs([
101
+ "🏠 Home", "πŸ“€ Upload & Parse", "πŸ” Data Explorer",
102
+ "πŸ“ˆ Visualizations", "πŸ•΅οΈ Local Analysis", "πŸ€– Gradio Integration", "πŸ“Š Reports"
103
+ ])
104
+
105
+ with tab1:
106
+ st.title("πŸ›°οΈ Satellite Log Companion Dashboard")
107
+ st.markdown("""
108
+ This app **pairs perfectly** with your Gradio Satellite Log Analyzer.
109
+ β€’ Upload & explore raw telemetry
110
+ β€’ Interactive plots & local ML anomalies
111
+ β€’ One-click send to your Gradio app for deep AI analysis
112
+ β€’ Export professional reports
113
+ """)
114
+ st.info("πŸ‘ˆ Set your Gradio URL in the sidebar β†’ then use the **Gradio Integration** tab")
115
+
116
+ with tab2:
117
+ st.header("Upload Logs")
118
+ uploaded = st.file_uploader("Upload satellite log (CSV)", type=["csv"], accept_multiple_files=False)
119
+ if uploaded:
120
+ df = parse_uploaded_file(uploaded)
121
+ if df is not None:
122
+ st.session_state.df = df
123
+ st.success(f"Loaded {len(df)} rows β€’ Columns: {list(df.columns)}")
124
+
125
+ if st.session_state.df is not None:
126
+ st.subheader("Preview")
127
+ st.dataframe(st.session_state.df.head(100), use_container_width=True)
128
+
129
+ with tab3:
130
+ if st.session_state.df is None:
131
+ st.warning("Upload data or load sample first")
132
+ else:
133
+ st.header("Data Explorer")
134
+ col1, col2 = st.columns(2)
135
+ with col1:
136
+ st.metric("Rows", len(st.session_state.df))
137
+ st.metric("Columns", len(st.session_state.df.columns))
138
+ with col2:
139
+ if "timestamp" in st.session_state.df.columns:
140
+ st.session_state.df["timestamp"] = pd.to_datetime(st.session_state.df["timestamp"], errors="coerce")
141
+ st.metric("Time Span", f"{st.session_state.df['timestamp'].min().date()} β†’ {st.session_state.df['timestamp'].max().date()}")
142
+
143
+ st.dataframe(st.session_state.df.describe(), use_container_width=True)
144
+
145
+ # Column filter
146
+ cols = st.multiselect("Select columns to view", st.session_state.df.columns, default=st.session_state.df.columns[:6])
147
+ st.dataframe(st.session_state.df[cols], use_container_width=True)
148
 
149
+ with tab4:
150
+ if st.session_state.df is None:
151
+ st.warning("No data yet")
152
+ else:
153
+ st.header("Interactive Visualizations")
154
+ numeric_cols = st.session_state.df.select_dtypes(include=np.number).columns.tolist()
155
 
156
+ y_cols = st.multiselect("Telemetry parameters (Y-axis)", numeric_cols, default=numeric_cols[:3])
157
+ if "timestamp" in st.session_state.df.columns and y_cols:
158
+ fig = px.line(st.session_state.df, x="timestamp", y=y_cols, title="Telemetry Time Series")
159
+ st.plotly_chart(fig, use_container_width=True)
160
+
161
+ # Correlation heatmap
162
+ if len(numeric_cols) > 1:
163
+ corr = st.session_state.df[numeric_cols].corr()
164
+ fig_heat = px.imshow(corr, text_auto=True, aspect="auto", title="Parameter Correlation")
165
+ st.plotly_chart(fig_heat, use_container_width=True)
166
+
167
+ # If lat/lon present β†’ map
168
+ if {"latitude", "longitude"}.issubset(st.session_state.df.columns):
169
+ st.map(st.session_state.df.rename(columns={"latitude":"lat", "longitude":"lon"}))
170
+
171
+ with tab5:
172
+ if st.session_state.df is None:
173
+ st.warning("No data")
174
+ else:
175
+ st.header("Local Anomaly Detection")
176
+ numeric_cols = st.session_state.df.select_dtypes(include=np.number).columns.tolist()
177
+
178
+ method = st.radio("Detection method", ["Z-Score (simple)", "Isolation Forest (ML)"])
179
+
180
+ if method == "Z-Score (simple)":
181
+ threshold = st.slider("Z-Score threshold", 2.0, 5.0, 3.0)
182
+ for col in numeric_cols:
183
+ z = np.abs((st.session_state.df[col] - st.session_state.df[col].mean()) / st.session_state.df[col].std())
184
+ anomalies = z > threshold
185
+ st.session_state.df[f"{col}_anomaly"] = anomalies
186
+ st.success(f"Found anomalies in {sum(anomalies)} rows")
187
+ st.dataframe(st.session_state.df[st.session_state.df.filter(like="_anomaly").any(axis=1)])
188
+
189
+ else: # Isolation Forest
190
+ if st.button("Run Isolation Forest"):
191
+ X = st.session_state.df[numeric_cols].fillna(0)
192
+ iso = IsolationForest(contamination=0.05, random_state=42)
193
+ preds = iso.fit_predict(X)
194
+ st.session_state.df["isolation_anomaly"] = preds == -1
195
+ st.success(f"Isolation Forest flagged {sum(preds == -1)} anomalies")
196
+ fig = px.scatter(st.session_state.df, x="timestamp" if "timestamp" in st.session_state.df else numeric_cols[0],
197
+ y=numeric_cols[0], color="isolation_anomaly", title="Anomalies Highlighted")
198
+ st.plotly_chart(fig, use_container_width=True)
199
+
200
+ with tab6:
201
+ st.header("πŸ€– Send to Your Gradio Analyzer")
202
+ if st.session_state.gradio_client is None:
203
+ st.warning("Connect your Gradio Space in the sidebar first")
204
+ elif st.session_state.df is None:
205
+ st.warning("Load/upload data first")
206
+ else:
207
+ st.info("The app will save your current dataframe as CSV and send it to your Gradio Space.")
208
+
209
+ col1, col2 = st.columns([3,1])
210
+ with col1:
211
+ if st.button("πŸš€ Send Current Log to Gradio Analyzer", type="primary", use_container_width=True):
212
+ with tempfile.NamedTemporaryFile(suffix=".csv", delete=False) as tmp:
213
+ st.session_state.df.to_csv(tmp.name, index=False)
214
+ tmp_path = tmp.name
215
+
216
+ client = st.session_state.gradio_client
217
+
218
+ with st.spinner("Sending to Gradio... (may queue if busy)"):
219
+ try:
220
+ # Use submit for long-running satellite log jobs
221
+ job = client.submit(
222
+ log_file=handle_file(tmp_path), # your Gradio likely has a file input named "log_file" or similar
223
+ api_name="/predict" # change if your function name is different (check view_api)
224
+ )
225
+
226
+ status_placeholder = st.empty()
227
+ while not job.done():
228
+ status = job.status()
229
+ status_placeholder.info(f"Status: {status.code} | Queue: {getattr(status, 'rank', 'N/A')}")
230
+ # st.progress would need polling, but this works
231
+
232
+ result = job.result()
233
+
234
+ st.success("Gradio analysis complete!")
235
+ st.subheader("Gradio Results")
236
+ # Flexible rendering of any output type(s)
237
+ if isinstance(result, (list, tuple)):
238
+ for i, out in enumerate(result):
239
+ st.markdown(f"**Output {i+1}**")
240
+ if isinstance(out, str):
241
+ st.markdown(out)
242
+ elif isinstance(out, pd.DataFrame):
243
+ st.dataframe(out)
244
+ elif isinstance(out, (bytes, io.BytesIO)):
245
+ st.image(out)
246
+ elif isinstance(out, Image.Image):
247
+ st.image(out)
248
+ else:
249
+ st.write(out)
250
+ else:
251
+ st.write(result)
252
+
253
+ except Exception as e:
254
+ st.error(f"Gradio call failed: {e}")
255
+ finally:
256
+ os.unlink(tmp_path)
257
+
258
+ st.caption("Tip: Open your Gradio Space in another tab to compare side-by-side")
259
+
260
+ with tab7:
261
+ if st.session_state.df is None:
262
+ st.warning("No data")
263
+ else:
264
+ st.header("Generate Reports")
265
+ st.download_button(
266
+ "πŸ“₯ Download full CSV",
267
+ st.session_state.df.to_csv(index=False),
268
+ file_name=f"satellite_log_{datetime.now().strftime('%Y%m%d_%H%M')}.csv",
269
+ mime="text/csv"
270
+ )
271
+
272
+ # Simple HTML report
273
+ report_md = f"""
274
+ # Satellite Telemetry Report
275
+ **Generated:** {datetime.now()}
276
+
277
+ ## Summary
278
+ - Rows: {len(st.session_state.df)}
279
+ - Time range: {st.session_state.df['timestamp'].min() if 'timestamp' in st.session_state.df.columns else 'N/A'}
280
+
281
+ ## Key Stats
282
+ {st.session_state.df.describe().to_markdown()}
283
+
284
+ ## Anomalies (if detected)
285
+ Check the Local Analysis tab.
286
  """
287
+ st.download_button("πŸ“„ Download Markdown Report", report_md, file_name="report.md")
288
+
289
+ st.success("All done! Your Gradio + Streamlit combo is now a full satellite operations suite.")
290
 
291
+ # Footer
292
+ st.caption("Built as a companion to your Hugging Face Gradio Satellite Log Analyzer β€’ Powered by Streamlit + gradio_client")