UCS2014 commited on
Commit
c2abfb5
·
verified ·
1 Parent(s): 65ff775

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -66
app.py CHANGED
@@ -6,7 +6,7 @@ import pandas as pd
6
  import numpy as np
7
  import joblib
8
 
9
- # matplotlib for PREVIEW modal and for the CROSS-PLOT (static)
10
  import matplotlib
11
  matplotlib.use("Agg")
12
  import matplotlib.pyplot as plt
@@ -26,14 +26,13 @@ MODEL_FALLBACKS = [MODELS_DIR / "model.joblib", MODELS_DIR / "model.pkl"]
26
  COLORS = {"pred": "#1f77b4", "actual": "#f2b702", "ref": "#5a5a5a"}
27
 
28
  # ---- Plot sizing controls ----
29
- CROSS_W = None # let the chart fill the column
30
  CROSS_H = 420
31
- TRACK_W = None # let the chart fill the column
32
  TRACK_H = 820
33
  FONT_SZ = 15
34
- PLOT_COLS = [42, 2, 30] # wider left & right, a thicker spacer in the middle
35
- CROSS_NUDGE = 0.0
36
-
37
 
38
  # =========================
39
  # Page / CSS
@@ -41,40 +40,32 @@ CROSS_NUDGE = 0.0
41
  st.set_page_config(page_title="ST_GeoMech_UCS", page_icon="logo.png", layout="wide")
42
  st.markdown("""
43
  <style>
44
- /* Reusable logo style */
45
- .brand-logo { width: 100px; height: auto; object-fit: contain; }
46
 
47
- /* Sidebar header layout */
48
- .sidebar-header { display:flex; align-items:center; gap:12px; }
49
- .sidebar-header .text h1 { font-size: 1.05rem; margin:0; line-height:1.1; }
50
- .sidebar-header .text .tag { font-size: .85rem; color:#6b7280; margin:2px 0 0; }
51
  </style>
52
  """, unsafe_allow_html=True)
53
-
54
- # Hide file-uploader helper text + center dataframes (headers & cells)
55
  st.markdown("""
56
  <style>
57
- /* Keep the uploader and its Browse button, but hide the drag/drop helper lines */
58
-
59
  /* Older builds (helper wrapped in a Markdown container) */
60
- section[data-testid="stFileUploader"] div[data-testid="stMarkdownContainer"] {
61
- display: none !important;
62
- }
63
-
64
  /* 1.31–1.34 style (first child inside the dropzone is the helper row) */
65
- section[data-testid="stFileUploader"] [data-testid="stFileUploaderDropzone"] > div:first-child {
66
- display: none !important;
67
- }
68
-
69
  /* 1.35+ explicit helper container */
70
- section[data-testid="stFileUploader"] [data-testid="stFileUploaderInstructions"] {
71
- display: none !important;
72
- }
73
-
74
- /* Fallback: remove any paragraph/small text inside the uploader */
75
- section[data-testid="stFileUploader"] p,
76
- section[data-testid="stFileUploader"] small {
77
- display: none !important;
 
78
  }
79
  </style>
80
  """, unsafe_allow_html=True)
@@ -85,7 +76,8 @@ section[data-testid="stFileUploader"] small {
85
  def inline_logo(path="logo.png") -> str:
86
  try:
87
  p = Path(path)
88
- if not p.exists(): return ""
 
89
  return f"data:image/png;base64,{base64.b64encode(p.read_bytes()).decode('ascii')}"
90
  except Exception:
91
  return ""
@@ -103,7 +95,7 @@ def add_password_gate() -> None:
103
  if st.session_state.get("auth_ok", False):
104
  return
105
 
106
- st.sidebar.image("logo.png", use_column_width=True)
107
  st.sidebar.markdown("### ST_GeoMech_UCS\nSmart Thinking • Secure Access")
108
  pwd = st.sidebar.text_input("Access key", type="password", placeholder="••••••••")
109
  if st.sidebar.button("Unlock", type="primary"):
@@ -136,7 +128,8 @@ def rmse(y_true, y_pred) -> float:
136
  def pearson_r(y_true, y_pred) -> float:
137
  a = np.asarray(y_true, dtype=float)
138
  p = np.asarray(y_pred, dtype=float)
139
- if a.size < 2: return float("nan")
 
140
  return float(np.corrcoef(a, p)[0, 1])
141
 
142
  @st.cache_resource(show_spinner=False)
@@ -149,7 +142,8 @@ def parse_excel(data_bytes: bytes):
149
  xl = pd.ExcelFile(bio)
150
  return {sh: xl.parse(sh) for sh in xl.sheet_names}
151
 
152
- def read_book_bytes(b: bytes): return parse_excel(b) if b else {}
 
153
 
154
  def ensure_cols(df, cols):
155
  miss = [c for c in cols if c not in df.columns]
@@ -161,15 +155,13 @@ def ensure_cols(df, cols):
161
  def find_sheet(book, names):
162
  low2orig = {k.lower(): k for k in book.keys()}
163
  for nm in names:
164
- if nm.lower() in low2orig: return low2orig[nm.lower()]
 
165
  return None
166
 
167
  def _nice_tick0(xmin: float, step: int = 100) -> float:
168
  return step * math.floor(xmin / step) if np.isfinite(xmin) else xmin
169
 
170
- # =========================
171
- # Cross-plot (Matplotlib, static)
172
- # =========================
173
  # =========================
174
  # Cross plot (Matplotlib, fixed limits & ticks)
175
  # =========================
@@ -180,11 +172,10 @@ def cross_plot_static(actual, pred):
180
  fixed_min, fixed_max = 6000, 10000
181
  ticks = np.arange(fixed_min, fixed_max + 1, 1000)
182
 
183
- # use constrained_layout + generous margins so big labels never overlap
184
  fig, ax = plt.subplots(
185
- figsize=((CROSS_W or 500)/100, (CROSS_H or 500)/100),
186
  dpi=100,
187
- constrained_layout=True
188
  )
189
 
190
  # points
@@ -220,20 +211,90 @@ def cross_plot_static(actual, pred):
220
 
221
  # extra room for the large labels
222
  fig.subplots_adjust(left=0.23, bottom=0.20, right=0.98, top=0.98)
223
-
224
  return fig
225
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
- try:
228
- dialog = st.dialog
229
- except AttributeError:
230
- def dialog(title):
231
- def deco(fn):
232
- def wrapper(*args, **kwargs):
233
- with st.expander(title, expanded=True):
234
- return fn(*args, **kwargs)
235
- return wrapper
236
- return deco
 
 
 
 
 
 
 
 
 
237
 
238
  @dialog("Preview data")
239
  def preview_modal(book: dict[str, pd.DataFrame]):
@@ -304,7 +365,7 @@ st.session_state.setdefault("dev_preview",False)
304
  # =========================
305
  # Branding in Sidebar
306
  # =========================
307
- st.sidebar.image("logo.png", use_column_width=True)
308
  st.sidebar.markdown(
309
  "<div style='font-weight:800;font-size:1.2rem;'>ST_GeoMech_UCS</div>"
310
  "<div style='color:#667085;'>Real-Time UCS Tracking While Drilling</div>",
@@ -396,10 +457,13 @@ if st.session_state.app_step == "dev":
396
  c1.metric("R", f"{m['R']:.2f}"); c2.metric("RMSE", f"{m['RMSE']:.2f}"); c3.metric("MAE", f"{m['MAE']:.2f}")
397
 
398
  left, spacer, right = st.columns(PLOT_COLS)
399
- with left:
 
400
  pad, plotcol = left.columns([CROSS_NUDGE, 1])
401
- with plotcol:
402
- st.pyplot(cross_plot_static(df[TARGET], df["UCS_Pred"]), use_container_width=False)
 
 
403
  with right:
404
  st.plotly_chart(
405
  track_plot(df, include_actual=True),
@@ -451,7 +515,9 @@ if st.session_state.app_step == "validate":
451
  for c in FEATURES:
452
  if pd.api.types.is_numeric_dtype(tbl[c]):
453
  tbl[c] = tbl[c].round(2)
454
- tbl["Violations"] = pd.DataFrame({f:(df[f]<ranges[f][0])|(df[f]>ranges[f][1]) for f in FEATURES}).loc[any_viol].apply(lambda r:", ".join([c for c,v in r.items() if v]), axis=1)
 
 
455
  st.session_state.results["m_val"]={
456
  "R": pearson_r(df[TARGET], df["UCS_Pred"]),
457
  "RMSE": rmse(df[TARGET], df["UCS_Pred"]),
@@ -466,14 +532,16 @@ if st.session_state.app_step == "validate":
466
  c1.metric("R", f"{m['R']:.2f}"); c2.metric("RMSE", f"{m['RMSE']:.2f}"); c3.metric("MAE", f"{m['MAE']:.2f}")
467
 
468
  left, spacer, right = st.columns(PLOT_COLS)
469
- with left:
470
  pad, plotcol = left.columns([CROSS_NUDGE, 1])
471
- with plotcol:
472
- st.pyplot(
473
- cross_plot_static(st.session_state.results["Validate"][TARGET],
474
- st.session_state.results["Validate"]["UCS_Pred"]),
475
- use_container_width=False
476
- )
 
 
477
  with right:
478
  st.plotly_chart(
479
  track_plot(st.session_state.results["Validate"], include_actual=True),
 
6
  import numpy as np
7
  import joblib
8
 
9
+ # Matplotlib for PREVIEW modal and CROSS-PLOT (static)
10
  import matplotlib
11
  matplotlib.use("Agg")
12
  import matplotlib.pyplot as plt
 
26
  COLORS = {"pred": "#1f77b4", "actual": "#f2b702", "ref": "#5a5a5a"}
27
 
28
  # ---- Plot sizing controls ----
29
+ CROSS_W = None # Matplotlib cross-plot width (in px) — None -> fallback used in fn
30
  CROSS_H = 420
31
+ TRACK_W = 380 # Plotly track width in px (kept modest to avoid overlap)
32
  TRACK_H = 820
33
  FONT_SZ = 15
34
+ PLOT_COLS = [42, 2, 30] # left spacer right
35
+ CROSS_NUDGE = 0.0 # set >0 to shove cross-plot inward with a small left pad column
 
36
 
37
  # =========================
38
  # Page / CSS
 
40
  st.set_page_config(page_title="ST_GeoMech_UCS", page_icon="logo.png", layout="wide")
41
  st.markdown("""
42
  <style>
43
+   /* Reusable logo style */
44
+   .brand-logo { width: 16px; height: auto; object-fit: contain; }
45
 
46
+   /* Sidebar header layout */
47
+   .sidebar-header { display:flex; align-items:center; gap:12px; }
48
+   .sidebar-header .text h1 { font-size: 1.05rem; margin:0; line-height:1.1; }
49
+   .sidebar-header .text .tag { font-size: .85rem; color:#6b7280; margin:2px 0 0; }
50
  </style>
51
  """, unsafe_allow_html=True)
52
+ # Hide file-uploader helper text; keep only Browse button
 
53
  st.markdown("""
54
  <style>
 
 
55
  /* Older builds (helper wrapped in a Markdown container) */
56
+ section[data-testid="stFileUploader"] div[data-testid="stMarkdownContainer"] { display:none !important; }
 
 
 
57
  /* 1.31–1.34 style (first child inside the dropzone is the helper row) */
58
+ section[data-testid="stFileUploader"] [data-testid="stFileUploaderDropzone"] > div:first-child { display:none !important; }
 
 
 
59
  /* 1.35+ explicit helper container */
60
+ section[data-testid="stFileUploader"] [data-testid="stFileUploaderInstructions"] { display:none !important; }
61
+ /* Fallback: any paragraph/small helper inside uploader */
62
+ section[data-testid="stFileUploader"] p, section[data-testid="stFileUploader"] small { display:none !important; }
63
+
64
+ /* Center headers & cells in all st.dataframe tables */
65
+ [data-testid="stDataFrame"] table td,
66
+ [data-testid="stDataFrame"] table th {
67
+ text-align: center !important;
68
+ vertical-align: middle !important;
69
  }
70
  </style>
71
  """, unsafe_allow_html=True)
 
76
  def inline_logo(path="logo.png") -> str:
77
  try:
78
  p = Path(path)
79
+ if not p.exists():
80
+ return ""
81
  return f"data:image/png;base64,{base64.b64encode(p.read_bytes()).decode('ascii')}"
82
  except Exception:
83
  return ""
 
95
  if st.session_state.get("auth_ok", False):
96
  return
97
 
98
+ st.sidebar.image("logo.png", use_column_width=False)
99
  st.sidebar.markdown("### ST_GeoMech_UCS\nSmart Thinking • Secure Access")
100
  pwd = st.sidebar.text_input("Access key", type="password", placeholder="••••••••")
101
  if st.sidebar.button("Unlock", type="primary"):
 
128
  def pearson_r(y_true, y_pred) -> float:
129
  a = np.asarray(y_true, dtype=float)
130
  p = np.asarray(y_pred, dtype=float)
131
+ if a.size < 2:
132
+ return float("nan")
133
  return float(np.corrcoef(a, p)[0, 1])
134
 
135
  @st.cache_resource(show_spinner=False)
 
142
  xl = pd.ExcelFile(bio)
143
  return {sh: xl.parse(sh) for sh in xl.sheet_names}
144
 
145
+ def read_book_bytes(b: bytes):
146
+ return parse_excel(b) if b else {}
147
 
148
  def ensure_cols(df, cols):
149
  miss = [c for c in cols if c not in df.columns]
 
155
  def find_sheet(book, names):
156
  low2orig = {k.lower(): k for k in book.keys()}
157
  for nm in names:
158
+ if nm.lower() in low2orig:
159
+ return low2orig[nm.lower()]
160
  return None
161
 
162
  def _nice_tick0(xmin: float, step: int = 100) -> float:
163
  return step * math.floor(xmin / step) if np.isfinite(xmin) else xmin
164
 
 
 
 
165
  # =========================
166
  # Cross plot (Matplotlib, fixed limits & ticks)
167
  # =========================
 
172
  fixed_min, fixed_max = 6000, 10000
173
  ticks = np.arange(fixed_min, fixed_max + 1, 1000)
174
 
 
175
  fig, ax = plt.subplots(
176
+ figsize=((CROSS_W or 500)/100, (CROSS_H or 420)/100),
177
  dpi=100,
178
+ constrained_layout=False
179
  )
180
 
181
  # points
 
211
 
212
  # extra room for the large labels
213
  fig.subplots_adjust(left=0.23, bottom=0.20, right=0.98, top=0.98)
 
214
  return fig
215
 
216
+ # =========================
217
+ # Track plot (Plotly)
218
+ # =========================
219
+ def track_plot(df, include_actual=True):
220
+ depth_col = next((c for c in df.columns if 'depth' in str(c).lower()), None)
221
+ if depth_col is not None:
222
+ y = pd.Series(df[depth_col]).astype(float)
223
+ ylab = depth_col
224
+ y_range = [float(y.max()), float(y.min())] # reverse
225
+ else:
226
+ y = pd.Series(np.arange(1, len(df) + 1))
227
+ ylab = "Point Index"
228
+ y_range = [float(y.max()), float(y.min())]
229
+
230
+ # X (UCS) range & ticks
231
+ x_series = pd.Series(df.get("UCS_Pred", pd.Series(dtype=float))).astype(float)
232
+ if include_actual and TARGET in df.columns:
233
+ x_series = pd.concat([x_series, pd.Series(df[TARGET]).astype(float)], ignore_index=True)
234
+ x_lo, x_hi = float(x_series.min()), float(x_series.max())
235
+ x_pad = 0.03 * (x_hi - x_lo if x_hi > x_lo else 1.0)
236
+ xmin, xmax = x_lo - x_pad, x_hi + x_pad
237
+ tick0 = _nice_tick0(xmin, step=100)
238
+
239
+ fig = go.Figure()
240
+ fig.add_trace(go.Scatter(
241
+ x=df["UCS_Pred"], y=y, mode="lines",
242
+ line=dict(color=COLORS["pred"], width=1.8),
243
+ name="UCS_Pred",
244
+ hovertemplate="UCS_Pred: %{x:.0f}<br>"+ylab+": %{y}<extra></extra>"
245
+ ))
246
+ if include_actual and TARGET in df.columns:
247
+ fig.add_trace(go.Scatter(
248
+ x=df[TARGET], y=y, mode="lines",
249
+ line=dict(color=COLORS["actual"], width=2.0, dash="dot"),
250
+ name="UCS (actual)",
251
+ hovertemplate="UCS (actual): %{x:.0f}<br>"+ylab+": %{y}<extra></extra>"
252
+ ))
253
+
254
+ fig.update_layout(
255
+ width=TRACK_W, height=TRACK_H, paper_bgcolor="#fff", plot_bgcolor="#fff",
256
+ margin=dict(l=72, r=18, t=36, b=48), hovermode="closest",
257
+ font=dict(size=FONT_SZ),
258
+ legend=dict(
259
+ x=0.98, y=0.05, xanchor="right", yanchor="bottom",
260
+ bgcolor="rgba(255,255,255,0.75)", bordercolor="#ccc", borderwidth=1
261
+ ),
262
+ legend_title_text=""
263
+ )
264
+ fig.update_xaxes(
265
+ title_text="<b>UCS (psi)</b>", title_font=dict(size=16),
266
+ side="top", range=[xmin, xmax],
267
+ ticks="outside", tickformat=",.0f", tickmode="auto", tick0=tick0,
268
+ showline=True, linewidth=1.2, linecolor="#444", mirror=True,
269
+ showgrid=True, gridcolor="rgba(0,0,0,0.12)", automargin=True
270
+ )
271
+ fig.update_yaxes(
272
+ title_text=f"<b>{ylab}</b>", title_font=dict(size=16),
273
+ range=y_range, ticks="outside",
274
+ showline=True, linewidth=1.2, linecolor="#444", mirror=True,
275
+ showgrid=True, gridcolor="rgba(0,0,0,0.12)", automargin=True
276
+ )
277
+ return fig
278
 
279
+ # ---------- Preview modal (matplotlib) ----------
280
+ def preview_tracks(df: pd.DataFrame, cols: list[str]):
281
+ cols = [c for c in cols if c in df.columns]
282
+ n = len(cols)
283
+ if n == 0:
284
+ fig, ax = plt.subplots(figsize=(4, 2))
285
+ ax.text(0.5,0.5,"No selected columns",ha="center",va="center")
286
+ ax.axis("off")
287
+ return fig
288
+ fig, axes = plt.subplots(1, n, figsize=(2.2*n, 7.0), sharey=True, dpi=100)
289
+ if n == 1: axes = [axes]
290
+ idx = np.arange(1, len(df) + 1)
291
+ for ax, col in zip(axes, cols):
292
+ ax.plot(df[col], idx, '-', lw=1.4, color="#333")
293
+ ax.set_xlabel(col); ax.xaxis.set_label_position('top'); ax.xaxis.tick_top(); ax.invert_yaxis()
294
+ ax.grid(True, linestyle=":", alpha=0.3)
295
+ for s in ax.spines.values(): s.set_visible(True)
296
+ axes[0].set_ylabel("Point Index")
297
+ return fig
298
 
299
  @dialog("Preview data")
300
  def preview_modal(book: dict[str, pd.DataFrame]):
 
365
  # =========================
366
  # Branding in Sidebar
367
  # =========================
368
+ st.sidebar.image("logo.png", use_column_width=False)
369
  st.sidebar.markdown(
370
  "<div style='font-weight:800;font-size:1.2rem;'>ST_GeoMech_UCS</div>"
371
  "<div style='color:#667085;'>Real-Time UCS Tracking While Drilling</div>",
 
457
  c1.metric("R", f"{m['R']:.2f}"); c2.metric("RMSE", f"{m['RMSE']:.2f}"); c3.metric("MAE", f"{m['MAE']:.2f}")
458
 
459
  left, spacer, right = st.columns(PLOT_COLS)
460
+ # Robust nudge: only make the inner columns if weight > 0
461
+ if CROSS_NUDGE and CROSS_NUDGE > 0:
462
  pad, plotcol = left.columns([CROSS_NUDGE, 1])
463
+ else:
464
+ plotcol = left
465
+ with plotcol:
466
+ st.pyplot(cross_plot_static(df[TARGET], df["UCS_Pred"]), use_container_width=False)
467
  with right:
468
  st.plotly_chart(
469
  track_plot(df, include_actual=True),
 
515
  for c in FEATURES:
516
  if pd.api.types.is_numeric_dtype(tbl[c]):
517
  tbl[c] = tbl[c].round(2)
518
+ tbl["Violations"] = pd.DataFrame({f:(df[f]<ranges[f][0])|(df[f]>ranges[f][1]) for f in FEATURES}).loc[any_viol].apply(
519
+ lambda r:", ".join([c for c,v in r.items() if v]), axis=1
520
+ )
521
  st.session_state.results["m_val"]={
522
  "R": pearson_r(df[TARGET], df["UCS_Pred"]),
523
  "RMSE": rmse(df[TARGET], df["UCS_Pred"]),
 
532
  c1.metric("R", f"{m['R']:.2f}"); c2.metric("RMSE", f"{m['RMSE']:.2f}"); c3.metric("MAE", f"{m['MAE']:.2f}")
533
 
534
  left, spacer, right = st.columns(PLOT_COLS)
535
+ if CROSS_NUDGE and CROSS_NUDGE > 0:
536
  pad, plotcol = left.columns([CROSS_NUDGE, 1])
537
+ else:
538
+ plotcol = left
539
+ with plotcol:
540
+ st.pyplot(
541
+ cross_plot_static(st.session_state.results["Validate"][TARGET],
542
+ st.session_state.results["Validate"]["UCS_Pred"]),
543
+ use_container_width=False
544
+ )
545
  with right:
546
  st.plotly_chart(
547
  track_plot(st.session_state.results["Validate"], include_actual=True),