Spaces:
Sleeping
Sleeping
devjas1
commited on
Commit
Β·
d0109c7
1
Parent(s):
114376b
(UI): add Reset control and state guards
Browse files- Introduce versioned 'file_uploader' key (uploader_version)
- Preserve 'model_select' and 'input_mode' across resets (KEEP_KEYS)
- Single source of truth for upload
- Reset clears right column artifacts/logs/status without nuking global UI
- Local run [GRN]
app.py
CHANGED
|
@@ -1,9 +1,3 @@
|
|
| 1 |
-
# BUILD_LABEL = "proof-2025-08-24-01"
|
| 2 |
-
# import os, streamlit as st, sys
|
| 3 |
-
# st.sidebar.caption(
|
| 4 |
-
# f"Build: {BUILD_LABEL} | __file__: {__file__} | cwd: {os.getcwd()} | py: {sys.version.split()[0]}"
|
| 5 |
-
# )
|
| 6 |
-
|
| 7 |
from models.resnet_cnn import ResNet1D
|
| 8 |
from models.figure2_cnn import Figure2CNN
|
| 9 |
import logging
|
|
@@ -31,8 +25,19 @@ matplotlib.use("Agg") # ensure headless rendering in Spaces
|
|
| 31 |
# Prefer canonical script; fallback to local utils for HF hard-copy scenario
|
| 32 |
try:
|
| 33 |
from scripts.preprocess_dataset import resample_spectrum
|
| 34 |
-
except ImportError:
|
| 35 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
|
| 37 |
# Configuration
|
| 38 |
st.set_page_config(
|
|
@@ -84,7 +89,30 @@ MODEL_CONFIG = {
|
|
| 84 |
LABEL_MAP = {0: "Stable (Unweathered)", 1: "Weathered (Degraded)"}
|
| 85 |
|
| 86 |
|
| 87 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 88 |
def label_file(filename: str) -> int:
|
| 89 |
"""Extract label from filename based on naming convention"""
|
| 90 |
name = Path(filename).name.lower()
|
|
@@ -102,7 +130,7 @@ def load_state_dict(_mtime, model_path):
|
|
| 102 |
"""Load state dict with mtime in cache key to detect file changes"""
|
| 103 |
try:
|
| 104 |
return torch.load(model_path, map_location="cpu")
|
| 105 |
-
except (FileNotFoundError,
|
| 106 |
st.warning(f"Error loading state dict: {e}")
|
| 107 |
return None
|
| 108 |
|
|
@@ -222,7 +250,7 @@ def create_spectrum_plot(x_raw, y_raw, y_resampled):
|
|
| 222 |
# Resampled spectrum
|
| 223 |
x_resampled = np.linspace(min(x_raw), max(x_raw), TARGET_LEN)
|
| 224 |
ax[1].plot(x_resampled, y_resampled, label="Resampled",
|
| 225 |
-
|
| 226 |
ax[1].set_title(f"Resampled ({TARGET_LEN} points)")
|
| 227 |
ax[1].set_xlabel("Wavenumber (cmβ»ΒΉ)")
|
| 228 |
ax[1].set_ylabel("Intensity")
|
|
@@ -252,27 +280,6 @@ def get_confidence_description(logit_margin):
|
|
| 252 |
return "LOW", "π΄"
|
| 253 |
|
| 254 |
|
| 255 |
-
def init_session_state():
|
| 256 |
-
defaults = {
|
| 257 |
-
"status_message": "Ready to analyze polymer spectra π¬",
|
| 258 |
-
"status_type": "info",
|
| 259 |
-
"input_text": None,
|
| 260 |
-
"filename": None,
|
| 261 |
-
"input_source": None, # "upload" or "sample"
|
| 262 |
-
"sample_select": "-- Select Sample --",
|
| 263 |
-
"input_mode": "Upload File", # controls which pane is visible
|
| 264 |
-
"inference_run_once": False,
|
| 265 |
-
"x_raw": None, "y_raw": None, "y_resampled": None,
|
| 266 |
-
"log_messages": [],
|
| 267 |
-
}
|
| 268 |
-
for k, v in defaults.items():
|
| 269 |
-
st.session_state.setdefault(k, v)
|
| 270 |
-
|
| 271 |
-
for key, default_value in defaults.items():
|
| 272 |
-
if key not in st.session_state:
|
| 273 |
-
st.session_state[key] = default_value
|
| 274 |
-
|
| 275 |
-
|
| 276 |
def log_message(msg: str):
|
| 277 |
"""Append a timestamped line to the in-app log, creating the buffer if needed."""
|
| 278 |
if "log_messages" not in st.session_state or st.session_state["log_messages"] is None:
|
|
@@ -287,21 +294,7 @@ def trigger_run():
|
|
| 287 |
st.session_state['run_requested'] = True
|
| 288 |
|
| 289 |
|
| 290 |
-
def on_upload_change():
|
| 291 |
-
"""Read uploaded file once and persist as text."""
|
| 292 |
-
up = st.session_state.get("upload_txt") # the uploader's key
|
| 293 |
-
if not up:
|
| 294 |
-
return
|
| 295 |
-
raw = up.read()
|
| 296 |
-
text = raw.decode("utf-8") if isinstance(raw, bytes) else raw
|
| 297 |
-
st.session_state["input_text"] = text
|
| 298 |
-
st.session_state["filename"] = getattr(up, "name", "uploaded.txt")
|
| 299 |
-
st.session_state["input_source"] = "upload"
|
| 300 |
|
| 301 |
-
# π§ Clear previous results so the right column resets immediately
|
| 302 |
-
reset_results("New file selected")
|
| 303 |
-
st.session_state["status_message"] = f"π File '{st.session_state['filename']}' ready for analysis"
|
| 304 |
-
st.session_state["status_type"] = "success"
|
| 305 |
|
| 306 |
|
| 307 |
def on_sample_change():
|
|
@@ -351,6 +344,31 @@ def reset_results(reason: str = ""):
|
|
| 351 |
)
|
| 352 |
st.session_state["status_type"] = "info"
|
| 353 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
|
| 355 |
# Main app
|
| 356 |
def main():
|
|
@@ -360,7 +378,7 @@ def main():
|
|
| 360 |
st.markdown(
|
| 361 |
"**Predict polymer degradation states using Raman spectroscopy and deep learning**")
|
| 362 |
st.info(
|
| 363 |
-
"
|
| 364 |
"Multi-model CNN evaluation in progress. "
|
| 365 |
"FTIR support planned.",
|
| 366 |
icon="β‘"
|
|
@@ -381,8 +399,8 @@ def main():
|
|
| 381 |
---
|
| 382 |
|
| 383 |
**Team**
|
| 384 |
-
|
| 385 |
-
|
| 386 |
π¨βπ» Jaser Hasan (Author)
|
| 387 |
|
| 388 |
---
|
|
@@ -409,7 +427,7 @@ def main():
|
|
| 409 |
model_labels = [
|
| 410 |
f"{MODEL_CONFIG[name]['emoji']} {name}" for name in MODEL_CONFIG.keys()]
|
| 411 |
selected_label = st.selectbox("Choose AI model:", model_labels,
|
| 412 |
-
|
| 413 |
model_choice = selected_label.split(" ", 1)[1]
|
| 414 |
|
| 415 |
# Model info
|
|
@@ -439,13 +457,29 @@ def main():
|
|
| 439 |
|
| 440 |
# ---- Upload tab ----
|
| 441 |
if mode == "Upload File":
|
|
|
|
| 442 |
up = st.file_uploader(
|
| 443 |
"Upload Raman spectrum (.txt)",
|
| 444 |
type="txt",
|
| 445 |
help="Upload a text file with wavenumber and intensity columns",
|
| 446 |
-
key=
|
| 447 |
-
on_change=on_upload_change, # <-- critical
|
| 448 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
if up:
|
| 450 |
st.success(f"β
Loaded: {up.name}")
|
| 451 |
|
|
@@ -486,7 +520,7 @@ def main():
|
|
| 486 |
inference_ready = bool(st.session_state.get(
|
| 487 |
"input_text")) and (model is not None)
|
| 488 |
|
| 489 |
-
#
|
| 490 |
with st.form("analysis_form", clear_on_submit=False):
|
| 491 |
submitted = st.form_submit_button(
|
| 492 |
"βΆοΈ Run Analysis",
|
|
@@ -494,8 +528,14 @@ def main():
|
|
| 494 |
disabled=not inference_ready,
|
| 495 |
)
|
| 496 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 497 |
if submitted and inference_ready:
|
| 498 |
-
|
|
|
|
| 499 |
try:
|
| 500 |
raw_text = st.session_state["input_text"]
|
| 501 |
filename = st.session_state.get("filename") or "unknown.txt"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
from models.resnet_cnn import ResNet1D
|
| 2 |
from models.figure2_cnn import Figure2CNN
|
| 3 |
import logging
|
|
|
|
| 25 |
# Prefer canonical script; fallback to local utils for HF hard-copy scenario
|
| 26 |
try:
|
| 27 |
from scripts.preprocess_dataset import resample_spectrum
|
| 28 |
+
except (ImportError, ModuleNotFoundError):
|
| 29 |
+
try:
|
| 30 |
+
from utils.preprocessing import resample_spectrum
|
| 31 |
+
except (ImportError, ModuleNotFoundError):
|
| 32 |
+
raise ImportError("Could not import 'resample_spectrum' from either 'scripts.preprocess_dataset' or 'utils.preprocessing'. Please ensure the function exists in one of these modules.")
|
| 33 |
+
|
| 34 |
+
KEEP_KEYS = {
|
| 35 |
+
# === global UI context we want to keep after "Reset" ===
|
| 36 |
+
"model_select", # sidebar model key
|
| 37 |
+
"input_mode", # radio for Upload|Sample
|
| 38 |
+
"uploader_version", # version counter for file uploader
|
| 39 |
+
"input_registry", # radio controlling Upload vs Sample
|
| 40 |
+
}
|
| 41 |
|
| 42 |
# Configuration
|
| 43 |
st.set_page_config(
|
|
|
|
| 89 |
LABEL_MAP = {0: "Stable (Unweathered)", 1: "Weathered (Degraded)"}
|
| 90 |
|
| 91 |
|
| 92 |
+
# === UTILITY FUNCTIONS ===
|
| 93 |
+
def init_session_state():
|
| 94 |
+
defaults = {
|
| 95 |
+
"status_message": "Ready to analyze polymer spectra π¬",
|
| 96 |
+
"status_type": "info",
|
| 97 |
+
"input_text": None,
|
| 98 |
+
"filename": None,
|
| 99 |
+
"input_source": None, # "upload" or "sample"
|
| 100 |
+
"sample_select": "-- Select Sample --",
|
| 101 |
+
"input_mode": "Upload File", # controls which pane is visible
|
| 102 |
+
"inference_run_once": False,
|
| 103 |
+
"x_raw": None, "y_raw": None, "y_resampled": None,
|
| 104 |
+
"log_messages": [],
|
| 105 |
+
"uploader_version": 0,
|
| 106 |
+
"current_upload_key": "upload_txt_0",
|
| 107 |
+
}
|
| 108 |
+
for k, v in defaults.items():
|
| 109 |
+
st.session_state.setdefault(k, v)
|
| 110 |
+
|
| 111 |
+
for key, default_value in defaults.items():
|
| 112 |
+
if key not in st.session_state:
|
| 113 |
+
st.session_state[key] = default_value
|
| 114 |
+
|
| 115 |
+
|
| 116 |
def label_file(filename: str) -> int:
|
| 117 |
"""Extract label from filename based on naming convention"""
|
| 118 |
name = Path(filename).name.lower()
|
|
|
|
| 130 |
"""Load state dict with mtime in cache key to detect file changes"""
|
| 131 |
try:
|
| 132 |
return torch.load(model_path, map_location="cpu")
|
| 133 |
+
except (FileNotFoundError, RuntimeError) as e:
|
| 134 |
st.warning(f"Error loading state dict: {e}")
|
| 135 |
return None
|
| 136 |
|
|
|
|
| 250 |
# Resampled spectrum
|
| 251 |
x_resampled = np.linspace(min(x_raw), max(x_raw), TARGET_LEN)
|
| 252 |
ax[1].plot(x_resampled, y_resampled, label="Resampled",
|
| 253 |
+
color="steelblue", linewidth=1)
|
| 254 |
ax[1].set_title(f"Resampled ({TARGET_LEN} points)")
|
| 255 |
ax[1].set_xlabel("Wavenumber (cmβ»ΒΉ)")
|
| 256 |
ax[1].set_ylabel("Intensity")
|
|
|
|
| 280 |
return "LOW", "π΄"
|
| 281 |
|
| 282 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 283 |
def log_message(msg: str):
|
| 284 |
"""Append a timestamped line to the in-app log, creating the buffer if needed."""
|
| 285 |
if "log_messages" not in st.session_state or st.session_state["log_messages"] is None:
|
|
|
|
| 294 |
st.session_state['run_requested'] = True
|
| 295 |
|
| 296 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
|
| 299 |
|
| 300 |
def on_sample_change():
|
|
|
|
| 344 |
)
|
| 345 |
st.session_state["status_type"] = "info"
|
| 346 |
|
| 347 |
+
def reset_ephemeral_state():
|
| 348 |
+
# === remove everything except KEPT global UI context ===
|
| 349 |
+
for k in list(st.session_state.keys()):
|
| 350 |
+
if k not in KEEP_KEYS:
|
| 351 |
+
st.session_state.pop(k, None)
|
| 352 |
+
|
| 353 |
+
# == bump the uploader version β new widget instance with empty value ==
|
| 354 |
+
st.session_state["uploader_version"] += 1
|
| 355 |
+
st.session_state["current_upload_key"] = f"upload_txt_{st.session_state['uploader_version']}"
|
| 356 |
+
|
| 357 |
+
# == reseed other emphemeral state ==
|
| 358 |
+
st.session_state["input_text"] = None
|
| 359 |
+
st.session_state["filename"] = None
|
| 360 |
+
st.session_state["input_source"] = None
|
| 361 |
+
st.session_state["sample_select"] = "-- Select Sample --"
|
| 362 |
+
# == return the UI to a clean state ==
|
| 363 |
+
st.session_state["inference_run_once"] = False
|
| 364 |
+
st.session_state["x_raw"] = None
|
| 365 |
+
st.session_state["y_raw"] = None
|
| 366 |
+
st.session_state["y_resampled"] = None
|
| 367 |
+
st.session_state["log_messages"] = []
|
| 368 |
+
st.session_state["status_message"] = "Ready to analyze polymer spectra π¬"
|
| 369 |
+
st.session_state["status_type"] = "info"
|
| 370 |
+
|
| 371 |
+
st.rerun()
|
| 372 |
|
| 373 |
# Main app
|
| 374 |
def main():
|
|
|
|
| 378 |
st.markdown(
|
| 379 |
"**Predict polymer degradation states using Raman spectroscopy and deep learning**")
|
| 380 |
st.info(
|
| 381 |
+
"**Prototype Notice:** v0.1 Raman-only. "
|
| 382 |
"Multi-model CNN evaluation in progress. "
|
| 383 |
"FTIR support planned.",
|
| 384 |
icon="β‘"
|
|
|
|
| 399 |
---
|
| 400 |
|
| 401 |
**Team**
|
| 402 |
+
Dr. Sanmukh Kuppannagari (Mentor)
|
| 403 |
+
Dr. Metin Karailyan (Mentor)
|
| 404 |
π¨βπ» Jaser Hasan (Author)
|
| 405 |
|
| 406 |
---
|
|
|
|
| 427 |
model_labels = [
|
| 428 |
f"{MODEL_CONFIG[name]['emoji']} {name}" for name in MODEL_CONFIG.keys()]
|
| 429 |
selected_label = st.selectbox("Choose AI model:", model_labels,
|
| 430 |
+
key="model_select", on_change=on_model_change)
|
| 431 |
model_choice = selected_label.split(" ", 1)[1]
|
| 432 |
|
| 433 |
# Model info
|
|
|
|
| 457 |
|
| 458 |
# ---- Upload tab ----
|
| 459 |
if mode == "Upload File":
|
| 460 |
+
upload_key = st.session_state["current_upload_key"]
|
| 461 |
up = st.file_uploader(
|
| 462 |
"Upload Raman spectrum (.txt)",
|
| 463 |
type="txt",
|
| 464 |
help="Upload a text file with wavenumber and intensity columns",
|
| 465 |
+
key=upload_key, # β versioned key
|
|
|
|
| 466 |
)
|
| 467 |
+
|
| 468 |
+
# == process change immediately (no on_change; simpler & reliable) ==
|
| 469 |
+
if up is not None:
|
| 470 |
+
raw = up.read()
|
| 471 |
+
text = raw.decode("utf-8") if isinstance(raw, bytes) else raw
|
| 472 |
+
# == only reparse if its a different file|source ==
|
| 473 |
+
if st.session_state.get("filename") != getattr(up, "name", None) or st.session_state.get("input_source") != "upload":
|
| 474 |
+
st.session_state["input_text"] = text
|
| 475 |
+
st.session_state["filename"] = getattr(up, "name", "uploaded.txt")
|
| 476 |
+
st.session_state["input_source"] = "upload"
|
| 477 |
+
|
| 478 |
+
# == clear right column immediately ==
|
| 479 |
+
reset_results("New file selected")
|
| 480 |
+
st.session_state["status_message"] = f"π File '{st.session_state['filename']}' ready for analysis"
|
| 481 |
+
st.session_state["status_type"] = "success"
|
| 482 |
+
|
| 483 |
if up:
|
| 484 |
st.success(f"β
Loaded: {up.name}")
|
| 485 |
|
|
|
|
| 520 |
inference_ready = bool(st.session_state.get(
|
| 521 |
"input_text")) and (model is not None)
|
| 522 |
|
| 523 |
+
# === Run Analysis (form submit batches state) ===
|
| 524 |
with st.form("analysis_form", clear_on_submit=False):
|
| 525 |
submitted = st.form_submit_button(
|
| 526 |
"βΆοΈ Run Analysis",
|
|
|
|
| 528 |
disabled=not inference_ready,
|
| 529 |
)
|
| 530 |
|
| 531 |
+
if st.button("Reset", help="Clear current file(s), plots, and results"):
|
| 532 |
+
reset_ephemeral_state()
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
|
| 536 |
if submitted and inference_ready:
|
| 537 |
+
# parse β preprocess β predict β render
|
| 538 |
+
# Handles the submission of the analysis form and performs spectrum data processing
|
| 539 |
try:
|
| 540 |
raw_text = st.session_state["input_text"]
|
| 541 |
filename = st.session_state.get("filename") or "unknown.txt"
|