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]

Files changed (1) hide show
  1. app.py +94 -54
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
- from utils.preprocessing import resample_spectrum
 
 
 
 
 
 
 
 
 
 
 
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
- # ||======= UTILITY FUNCTIONS =======||
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, torch.TorchError) as e:
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
- color="steelblue", linewidth=1)
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
- "⚠️ **Prototype Notice:** v0.1 Raman-only. "
364
  "Multi-model CNN evaluation in progress. "
365
  "FTIR support planned.",
366
  icon="⚑"
@@ -381,8 +399,8 @@ def main():
381
  ---
382
 
383
  **Team**
384
- πŸ‘¨β€πŸ« Dr. Sanmukh Kuppannagari (Mentor)
385
- πŸ‘¨β€πŸ« Dr. Metin Karailyan (Mentor)
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
- key="model_select", on_change=on_model_change)
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="upload_txt",
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
- # ---- Run Analysis (form submit batches state + submit atomically) ----
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
- # Handles the submission of the analysis form and performs spectrum data processing
 
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"