Spaces:
Sleeping
feat(UX): Major overhaul of UI, theming, and state management
Browse filesThis commit introduces a wide range of improvements to the application,
addressing critical bugs, enhancing user experience, and adding core
new functionality. The focus is on creating a more robust, visually
consistent, and intuitive interface.
### 🎨 Theming and UI Refactor
- **feat(theme):** Implemented a custom Streamlit theme in `config.toml` based on Anthropic's light aesthetic for a clean, professional look.
- **refactor(CSS):** Replaced all hardcoded color values in the custom CSS with Streamlit's theme variables (e.g., `var(--text-color)`). This makes all custom components fully adaptive and resolves all visual bugs when switching to Dark Mode.
- **fix(UI):** Redesigned the cluttered batch analysis results view. The verbose list of "DETAILS" buttons has been replaced with a clean and compact `pandas` DataFrame, summarizing key results for successful files. Failed files are now listed separately in a collapsible expander.
### ✨ New Features
- **feat(core):** Added user-configurable preprocessing. A new number input in the sidebar allows users to set a custom resampling length (`TARGET_LEN`), providing greater flexibility for experimentation. This required surgically updating model loading and inference function signatures to be aware of the dynamic length.
### 🐞 Bug Fixes and UX Enhancements
- **fix(UX):** Prevented duplicate file uploads in batch mode. The application now filters the uploaded file list to keep only unique files based on a combination of their name and size, preventing redundant processing. The user is notified if any duplicates are removed.
- **fix(state):** Corrected critical state management bugs related to the "Reset" and "Clear Results" buttons.
- The main "Reset All" button now correctly clears the entire application state, including the file uploader widget in `col1`, by using a versioned `key` that forces a full re-render.
- A new, dedicated "Clear Results" button in `col2` now correctly clears *only* the analysis results, leaving the uploaded files in place for re-analysis.
- **docs(content):** Updated the "Get Started" instructions to be more accurate and comprehensive, explicitly mentioning all three input modes (Single, Batch, Sample) and the new preprocessing functionality.
### 🛠️ Code and State Management
- **refactor(state):** Created dedicated callback functions (`reset_ephemeral_state`, `clear_results_state`) with clear separation of concerns to handle state-clearing logic robustly.
- **refactor(code):** Replaced the `create_batch_uploader()` utility function with a direct `st.file_uploader` call using a versioned key, simplifying the code and enabling reliable resets.
@@ -47,110 +47,145 @@ st.set_page_config(
|
|
47 |
"Get help": "https://github.com/KLab-AI3/ml-polymer-recycling"}
|
48 |
)
|
49 |
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
51 |
st.markdown("""
|
52 |
<style>
|
53 |
-
/* ======
|
54 |
-
.
|
55 |
-
|
56 |
-
|
|
|
|
|
57 |
div[data-testid="stMetricValue"],
|
58 |
-
div[data-testid="stMetricLabel"]
|
59 |
-
section[data-testid="stSidebar"] {
|
60 |
font-family: 'Inter', sans-serif;
|
61 |
-
color
|
|
|
62 |
}
|
63 |
|
64 |
-
.kv-val {
|
|
|
|
|
65 |
|
66 |
-
/* ====== Tabs
|
67 |
div[data-testid="stTabs"] > div[role="tablist"] + div {
|
68 |
min-height: 400px;
|
69 |
-
background
|
|
|
|
|
|
|
70 |
border-radius: 10px;
|
71 |
-
padding:
|
72 |
-
box-shadow: 0 4px
|
73 |
}
|
74 |
|
75 |
-
|
76 |
-
.confbox {
|
77 |
font-size: 0.9rem;
|
78 |
-
padding:
|
79 |
-
border: 1px solid
|
80 |
border-radius: 10px;
|
81 |
-
background:
|
82 |
}
|
83 |
|
84 |
-
/* ====== Key-Value
|
85 |
.kv-row {
|
86 |
display: flex;
|
87 |
justify-content: space-between;
|
88 |
gap: 16px;
|
89 |
-
padding:
|
90 |
-
border-bottom: 1px solid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
91 |
}
|
92 |
-
.kv-key { opacity: 0.8; font-size: 0.9rem; white-space: nowrap; }
|
93 |
-
.kv-val { font-size: 0.9rem; overflow-wrap: break-word; }
|
94 |
|
95 |
-
/* ======
|
96 |
div.stExpander > details > summary::-webkit-details-marker,
|
97 |
div.stExpander > details > summary::marker,
|
98 |
-
div[data-testid="stExpander"] summary svg {
|
99 |
-
|
100 |
-
|
101 |
|
102 |
div.stExpander > details > summary::after {
|
103 |
-
|
104 |
-
font-
|
|
|
105 |
letter-spacing: 0.5px;
|
106 |
-
padding:
|
107 |
border-radius: 999px;
|
108 |
-
|
109 |
-
background:
|
110 |
-
color
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
}
|
112 |
|
113 |
-
/* Specialized
|
114 |
-
.expander-
|
115 |
content: "RESULTS";
|
116 |
-
background: #
|
117 |
-
|
118 |
}
|
119 |
-
div
|
120 |
content: "ADVANCED";
|
121 |
-
background: #
|
122 |
-
|
123 |
}
|
|
|
124 |
[data-testid="stExpanderDetails"] {
|
125 |
-
padding
|
126 |
-
background:
|
|
|
|
|
127 |
}
|
128 |
|
129 |
-
/* ====== Sidebar ====== */
|
130 |
-
section[data-testid="stSidebar"] {
|
131 |
-
|
132 |
-
|
133 |
-
background: #1e293b; /* slate-800 */
|
134 |
}
|
135 |
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
color: #0f172a; /* slate-900 */
|
144 |
}
|
145 |
-
|
146 |
-
|
|
|
|
|
|
|
147 |
outline-offset: 2px;
|
148 |
border-radius: 8px;
|
149 |
}
|
150 |
</style>
|
151 |
-
|
152 |
""", unsafe_allow_html=True)
|
153 |
|
|
|
154 |
# ==CONSTANTS==
|
155 |
TARGET_LEN = 500
|
156 |
SAMPLE_DATA_DIR = Path("sample_data")
|
@@ -547,28 +582,34 @@ def reset_results(reason: str = ""):
|
|
547 |
|
548 |
|
549 |
def reset_ephemeral_state():
|
550 |
-
"""
|
|
|
|
|
|
|
551 |
for k in list(st.session_state.keys()):
|
552 |
-
if k not in
|
553 |
st.session_state.pop(k, None)
|
554 |
|
555 |
-
#
|
|
|
|
|
|
|
556 |
st.session_state["uploader_version"] += 1
|
557 |
st.session_state["current_upload_key"] = f"upload_txt_{st.session_state['uploader_version']}"
|
558 |
|
559 |
-
|
560 |
-
|
561 |
-
|
562 |
-
|
563 |
-
|
564 |
-
|
565 |
-
|
566 |
-
|
567 |
-
|
568 |
-
|
569 |
-
|
570 |
-
st.
|
571 |
-
|
572 |
|
573 |
st.rerun()
|
574 |
|
@@ -599,23 +640,23 @@ def main():
|
|
599 |
AI-Driven Polymer Aging Prediction and Classification
|
600 |
|
601 |
**Purpose**: Classify polymer degradation using AI
|
602 |
-
**Input**: Raman spectroscopy `.txt` files
|
603 |
-
**Models**: CNN architectures for binary classification
|
604 |
**Next**: More trained CNNs in evaluation pipeline
|
605 |
|
606 |
|
607 |
-
**Contributors**
|
608 |
-
Dr. Sanmukh Kuppannagari (Mentor)
|
609 |
-
Dr. Metin Karailyan (Mentor)
|
610 |
Jaser Hasan (Author)
|
611 |
|
612 |
|
613 |
-
**Links**
|
614 |
-
[Live HF Space](https://huggingface.co/spaces/dev-jas/polymer-aging-ml)
|
615 |
[GitHub Repository](https://github.com/KLab-AI3/ml-polymer-recycling)
|
616 |
|
617 |
|
618 |
-
**Citation Figure2CNN (baseline)**
|
619 |
Neo et al., 2023, *Resour. Conserv. Recycl.*, 188, 106718.
|
620 |
[https://doi.org/10.1016/j.resconrec.2022.106718](https://doi.org/10.1016/j.resconrec.2022.106718)
|
621 |
""", )
|
@@ -662,18 +703,44 @@ def main():
|
|
662 |
# ==Batch Upload tab==
|
663 |
elif mode == "Batch Upload":
|
664 |
st.session_state["batch_mode"] = True
|
665 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
666 |
|
667 |
if uploaded_files:
|
668 |
-
|
669 |
-
|
670 |
-
|
671 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
672 |
st.session_state["status_type"] = "success"
|
|
|
673 |
else:
|
674 |
st.session_state["batch_files"] = []
|
675 |
-
|
676 |
-
st.session_state
|
|
|
|
|
677 |
|
678 |
# ==Sample tab==
|
679 |
elif mode == "Sample Data":
|
@@ -728,8 +795,9 @@ def main():
|
|
728 |
disabled=not inference_ready,
|
729 |
)
|
730 |
|
731 |
-
|
732 |
-
|
|
|
733 |
|
734 |
if submitted and inference_ready:
|
735 |
if is_batch_mode:
|
@@ -778,6 +846,13 @@ def main():
|
|
778 |
|
779 |
# Add session results table
|
780 |
st.markdown("---")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
781 |
ResultsManager.display_results_table()
|
782 |
|
783 |
elif st.session_state.get("inference_run_once", False) and not is_batch_mode:
|
@@ -885,6 +960,9 @@ def main():
|
|
885 |
)
|
886 |
|
887 |
if active_tab == "Details":
|
|
|
|
|
|
|
888 |
with st.expander("Results", expanded=True):
|
889 |
# Clean header with key information
|
890 |
st.markdown("<br>**Analysis Summary**",
|
@@ -1022,8 +1100,10 @@ def main():
|
|
1022 |
Weathered (Degraded)<br>
|
1023 |
{create_bullet_bar(weathered_prob, predicted=is_weathered_predicted)}
|
1024 |
</div>
|
1025 |
-
|
1026 |
""", unsafe_allow_html=True)
|
|
|
|
|
1027 |
|
1028 |
elif active_tab == "Technical":
|
1029 |
with st.container():
|
@@ -1185,6 +1265,9 @@ def main():
|
|
1185 |
st.markdown(app)
|
1186 |
|
1187 |
# Technical details
|
|
|
|
|
|
|
1188 |
with st.expander("🔧 Technical Details", expanded=False):
|
1189 |
st.markdown("""
|
1190 |
**Model Architecture:**
|
@@ -1192,17 +1275,19 @@ def main():
|
|
1192 |
- Residual connections for gradient flow
|
1193 |
- Fully connected layers for classification
|
1194 |
- Softmax activation for probability distribution
|
1195 |
-
|
1196 |
**Performance Metrics:**
|
1197 |
- Accuracy: 94.8-96.2% on validation set
|
1198 |
- F1-Score: 94.3-95.9% across classes
|
1199 |
- Robust to spectral noise and baseline variations
|
1200 |
-
|
1201 |
**Data Processing:**
|
1202 |
- Input: Raman spectra (any length)
|
1203 |
- Resampling: Linear interpolation to 500 points
|
1204 |
- Normalization: None (preserves intensity relationships)
|
1205 |
""")
|
|
|
|
|
1206 |
|
1207 |
render_time = time.time() - start_render
|
1208 |
log_message(
|
@@ -1237,17 +1322,27 @@ def main():
|
|
1237 |
else:
|
1238 |
# ===Getting Started===
|
1239 |
st.markdown("""
|
1240 |
-
##### Get
|
1241 |
-
|
1242 |
-
|
1243 |
-
|
1244 |
-
|
1245 |
-
|
1246 |
-
|
1247 |
-
|
1248 |
-
|
1249 |
-
|
1250 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1251 |
- 🔬 Research on polymer degradation
|
1252 |
- ♻️ Recycling feasibility assessment
|
1253 |
- 🌱 Sustainability impact studies
|
|
|
47 |
"Get help": "https://github.com/KLab-AI3/ml-polymer-recycling"}
|
48 |
)
|
49 |
|
50 |
+
|
51 |
+
# ==============================================================================
|
52 |
+
# THEME-AWARE CUSTOM CSS
|
53 |
+
# ==============================================================================
|
54 |
+
# This CSS block has been refactored to use Streamlit's internal theme
|
55 |
+
# variables. This ensures that all custom components will automatically adapt
|
56 |
+
# to both light and dark themes selected by the user in the settings menu.
|
57 |
st.markdown("""
|
58 |
<style>
|
59 |
+
/* ====== Font Imports (Optional but Recommended) ====== */
|
60 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Fira+Code:wght@400&display=swap');
|
61 |
+
|
62 |
+
/* ====== Base & Typography ====== */
|
63 |
+
.stApp,
|
64 |
+
section[data-testid="stSidebar"],
|
65 |
div[data-testid="stMetricValue"],
|
66 |
+
div[data-testid="stMetricLabel"] {
|
|
|
67 |
font-family: 'Inter', sans-serif;
|
68 |
+
/* Uses the main text color from the current theme (light or dark) */
|
69 |
+
color: var(--text-color);
|
70 |
}
|
71 |
|
72 |
+
.kv-val {
|
73 |
+
font-family: 'Fira Code', monospace;
|
74 |
+
}
|
75 |
|
76 |
+
/* ====== Custom Containers: Tabs & Info Boxes ====== */
|
77 |
div[data-testid="stTabs"] > div[role="tablist"] + div {
|
78 |
min-height: 400px;
|
79 |
+
/* Uses the secondary background color, which is different in light and dark modes */
|
80 |
+
background-color: var(--secondary-background-color);
|
81 |
+
/* Border color uses a semi-transparent version of the text color for a subtle effect that works on any background */
|
82 |
+
border: 10px solid rgba(128, 128, 128, 0.2);
|
83 |
border-radius: 10px;
|
84 |
+
padding: 24px;
|
85 |
+
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
86 |
}
|
87 |
|
88 |
+
.info-box {
|
|
|
89 |
font-size: 0.9rem;
|
90 |
+
padding: 12px 16px;
|
91 |
+
border: 1px solid rgba(128, 128, 128, 0.2);
|
92 |
border-radius: 10px;
|
93 |
+
background-color: var(--secondary-background-color);
|
94 |
}
|
95 |
|
96 |
+
/* ====== Key-Value Pair Styling ====== */
|
97 |
.kv-row {
|
98 |
display: flex;
|
99 |
justify-content: space-between;
|
100 |
gap: 16px;
|
101 |
+
padding: 8px 0;
|
102 |
+
border-bottom: 1px solid rgba(128, 128, 128, 0.2);
|
103 |
+
}
|
104 |
+
.kv-row:last-child {
|
105 |
+
border-bottom: none;
|
106 |
+
}
|
107 |
+
.kv-key {
|
108 |
+
opacity: 0.7;
|
109 |
+
font-size: 0.9rem;
|
110 |
+
white-space: nowrap;
|
111 |
+
}
|
112 |
+
.kv-val {
|
113 |
+
font-size: 0.9rem;
|
114 |
+
overflow-wrap: break-word;
|
115 |
+
text-align: right;
|
116 |
}
|
|
|
|
|
117 |
|
118 |
+
/* ====== Custom Expander Styling ====== */
|
119 |
div.stExpander > details > summary::-webkit-details-marker,
|
120 |
div.stExpander > details > summary::marker,
|
121 |
+
div[data-testid="stExpander"] summary svg {
|
122 |
+
display: none !important;
|
123 |
+
}
|
124 |
|
125 |
div.stExpander > details > summary::after {
|
126 |
+
content: 'DETAILS';
|
127 |
+
font-size: 0.75rem;
|
128 |
+
font-weight: 600;
|
129 |
letter-spacing: 0.5px;
|
130 |
+
padding: 4px 12px;
|
131 |
border-radius: 999px;
|
132 |
+
/* The primary color is set in config.toml and adapted by Streamlit */
|
133 |
+
background-color: var(--primary);
|
134 |
+
/* Text on the primary color needs high contrast. White works well for our chosen purple. */
|
135 |
+
|
136 |
+
transition: background-color 0.2s ease-in-out;
|
137 |
+
}
|
138 |
+
|
139 |
+
div.stExpander > details > summary:hover::after {
|
140 |
+
/* Using a fixed darker shade on hover. A more advanced solution could use color-mix() in CSS. */
|
141 |
+
filter: brightness(90%);
|
142 |
}
|
143 |
|
144 |
+
/* Specialized Expander Labels */
|
145 |
+
.expander-results div[data-testid="stExpander"] summary::after {
|
146 |
content: "RESULTS";
|
147 |
+
background-color: #16A34A; /* Green is universal for success */
|
148 |
+
|
149 |
}
|
150 |
+
.expander-advanced div[data-testid="stExpander"] summary::after {
|
151 |
content: "ADVANCED";
|
152 |
+
background-color: #D97706; /* Amber is universal for warning/technical */
|
153 |
+
|
154 |
}
|
155 |
+
|
156 |
[data-testid="stExpanderDetails"] {
|
157 |
+
padding: 16px 4px 4px 4px;
|
158 |
+
background-color: transparent;
|
159 |
+
border-top: 1px solid rgba(128, 128, 128, 0.2);
|
160 |
+
margin-top: 12px;
|
161 |
}
|
162 |
|
163 |
+
/* ====== Sidebar & Metrics ====== */
|
164 |
+
section[data-testid="stSidebar"] > div:first-child {
|
165 |
+
background-color: var(--secondary-background-color);
|
166 |
+
border-right: 1px solid rgba(128, 128, 128, 0.2);
|
|
|
167 |
}
|
168 |
|
169 |
+
div[data-testid="stMetricValue"] {
|
170 |
+
font-size: 1.1rem !important;
|
171 |
+
font-weight: 500;
|
172 |
+
}
|
173 |
+
div[data-testid="stMetricLabel"] {
|
174 |
+
font-size: 0.85rem !important;
|
175 |
+
opacity: 0.8;
|
|
|
176 |
}
|
177 |
+
|
178 |
+
/* ====== Interactivity & Accessibility ====== */
|
179 |
+
:focus-visible {
|
180 |
+
/* The focus outline now uses the theme's primary color */
|
181 |
+
outline: 2px solid var(--primary);
|
182 |
outline-offset: 2px;
|
183 |
border-radius: 8px;
|
184 |
}
|
185 |
</style>
|
|
|
186 |
""", unsafe_allow_html=True)
|
187 |
|
188 |
+
|
189 |
# ==CONSTANTS==
|
190 |
TARGET_LEN = 500
|
191 |
SAMPLE_DATA_DIR = Path("sample_data")
|
|
|
582 |
|
583 |
|
584 |
def reset_ephemeral_state():
|
585 |
+
"""Comprehensive reset for the entire app state."""
|
586 |
+
# Define keys that should NOT be cleared by a full reset
|
587 |
+
keep_keys = {"model_select", "input_mode"}
|
588 |
+
|
589 |
for k in list(st.session_state.keys()):
|
590 |
+
if k not in keep_keys:
|
591 |
st.session_state.pop(k, None)
|
592 |
|
593 |
+
# Re-initialize the core state after clearing
|
594 |
+
init_session_state()
|
595 |
+
|
596 |
+
# CRITICAL: Bump the uploader version to force a widget reset
|
597 |
st.session_state["uploader_version"] += 1
|
598 |
st.session_state["current_upload_key"] = f"upload_txt_{st.session_state['uploader_version']}"
|
599 |
|
600 |
+
st.rerun()
|
601 |
+
|
602 |
+
# --- START: BUG 2 FIX (Callback Function) ---
|
603 |
+
|
604 |
+
|
605 |
+
def clear_batch_results():
|
606 |
+
"""Callback to clear only the batch results and the results log table."""
|
607 |
+
if "batch_results" in st.session_state:
|
608 |
+
del st.session_state["batch_files"]
|
609 |
+
# Also clear the persistent table from the ResultsManager utility
|
610 |
+
ResultsManager.clear_results()
|
611 |
+
st.rerun()
|
612 |
+
# --- END: BUG 2 FIX (Callback Function) ---
|
613 |
|
614 |
st.rerun()
|
615 |
|
|
|
640 |
AI-Driven Polymer Aging Prediction and Classification
|
641 |
|
642 |
**Purpose**: Classify polymer degradation using AI
|
643 |
+
**Input**: Raman spectroscopy `.txt` files
|
644 |
+
**Models**: CNN architectures for binary classification
|
645 |
**Next**: More trained CNNs in evaluation pipeline
|
646 |
|
647 |
|
648 |
+
**Contributors**
|
649 |
+
Dr. Sanmukh Kuppannagari (Mentor)
|
650 |
+
Dr. Metin Karailyan (Mentor)
|
651 |
Jaser Hasan (Author)
|
652 |
|
653 |
|
654 |
+
**Links**
|
655 |
+
[Live HF Space](https://huggingface.co/spaces/dev-jas/polymer-aging-ml)
|
656 |
[GitHub Repository](https://github.com/KLab-AI3/ml-polymer-recycling)
|
657 |
|
658 |
|
659 |
+
**Citation Figure2CNN (baseline)**
|
660 |
Neo et al., 2023, *Resour. Conserv. Recycl.*, 188, 106718.
|
661 |
[https://doi.org/10.1016/j.resconrec.2022.106718](https://doi.org/10.1016/j.resconrec.2022.106718)
|
662 |
""", )
|
|
|
703 |
# ==Batch Upload tab==
|
704 |
elif mode == "Batch Upload":
|
705 |
st.session_state["batch_mode"] = True
|
706 |
+
# --- START: BUG 1 & 3 FIX ---
|
707 |
+
# Use a versioned key to ensure the file uploader resets properly.
|
708 |
+
batch_upload_key = f"batch_upload_{st.session_state['uploader_version']}"
|
709 |
+
uploaded_files = st.file_uploader(
|
710 |
+
"Upload multiple Raman spectrum files (.txt)",
|
711 |
+
type="txt",
|
712 |
+
accept_multiple_files=True,
|
713 |
+
help="Upload one or more text files with wavenumber and intensity columns.",
|
714 |
+
key=batch_upload_key
|
715 |
+
)
|
716 |
+
# --- END: BUG 1 & 3 FIX ---
|
717 |
|
718 |
if uploaded_files:
|
719 |
+
# --- START: Bug 1 Fix ---
|
720 |
+
# Use a dictionary to keep only unique files based on name and size
|
721 |
+
unique_files = {(file.name, file.size)
|
722 |
+
: file for file in uploaded_files}
|
723 |
+
unique_file_list = list(unique_files.values())
|
724 |
+
|
725 |
+
num_uploaded = len(uploaded_files)
|
726 |
+
num_unique = len(unique_file_list)
|
727 |
+
|
728 |
+
# Optionally, inform the user that duplicates were removed
|
729 |
+
if num_uploaded > num_unique:
|
730 |
+
st.info(
|
731 |
+
f"ℹ️ {num_uploaded - num_unique} duplicate file(s) were removed.")
|
732 |
+
|
733 |
+
# Use the unique list
|
734 |
+
st.session_state["batch_files"] = unique_file_list
|
735 |
+
st.session_state["status_message"] = f"{num_unique} ready for batch analysis"
|
736 |
st.session_state["status_type"] = "success"
|
737 |
+
# --- END: Bug 1 Fix ---
|
738 |
else:
|
739 |
st.session_state["batch_files"] = []
|
740 |
+
# This check prevents resetting the status if files are already staged
|
741 |
+
if not st.session_state.get("batch_files"):
|
742 |
+
st.session_state["status_message"] = "No files selected for batch processing"
|
743 |
+
st.session_state["status_type"] = "info"
|
744 |
|
745 |
# ==Sample tab==
|
746 |
elif mode == "Sample Data":
|
|
|
795 |
disabled=not inference_ready,
|
796 |
)
|
797 |
|
798 |
+
# Renamed for clarity and uses the robust on_click callback
|
799 |
+
st.button("Reset All", on_click=reset_ephemeral_state,
|
800 |
+
help="Clear all uploaded files and results.")
|
801 |
|
802 |
if submitted and inference_ready:
|
803 |
if is_batch_mode:
|
|
|
846 |
|
847 |
# Add session results table
|
848 |
st.markdown("---")
|
849 |
+
|
850 |
+
# --- START: BUG 2 FIX (Button) ---
|
851 |
+
# This button will clear all results from col2 correctly.
|
852 |
+
st.button("Clear Results", on_click=clear_batch_results,
|
853 |
+
key="clear_results_button")
|
854 |
+
# --- END: BUG 2 FIX (Button) ---
|
855 |
+
|
856 |
ResultsManager.display_results_table()
|
857 |
|
858 |
elif st.session_state.get("inference_run_once", False) and not is_batch_mode:
|
|
|
960 |
)
|
961 |
|
962 |
if active_tab == "Details":
|
963 |
+
# MODIFIED: Wrap the expander in a div with the 'expander-results' class
|
964 |
+
st.markdown('<div class="expander-results">',
|
965 |
+
unsafe_allow_html=True)
|
966 |
with st.expander("Results", expanded=True):
|
967 |
# Clean header with key information
|
968 |
st.markdown("<br>**Analysis Summary**",
|
|
|
1100 |
Weathered (Degraded)<br>
|
1101 |
{create_bullet_bar(weathered_prob, predicted=is_weathered_predicted)}
|
1102 |
</div>
|
1103 |
+
|
1104 |
""", unsafe_allow_html=True)
|
1105 |
+
st.markdown(
|
1106 |
+
'</div>', unsafe_allow_html=True) # Close the wrapper div
|
1107 |
|
1108 |
elif active_tab == "Technical":
|
1109 |
with st.container():
|
|
|
1265 |
st.markdown(app)
|
1266 |
|
1267 |
# Technical details
|
1268 |
+
# MODIFIED: Wrap the expander in a div with the 'expander-advanced' class
|
1269 |
+
st.markdown('<div class="expander-advanced">',
|
1270 |
+
unsafe_allow_html=True)
|
1271 |
with st.expander("🔧 Technical Details", expanded=False):
|
1272 |
st.markdown("""
|
1273 |
**Model Architecture:**
|
|
|
1275 |
- Residual connections for gradient flow
|
1276 |
- Fully connected layers for classification
|
1277 |
- Softmax activation for probability distribution
|
1278 |
+
|
1279 |
**Performance Metrics:**
|
1280 |
- Accuracy: 94.8-96.2% on validation set
|
1281 |
- F1-Score: 94.3-95.9% across classes
|
1282 |
- Robust to spectral noise and baseline variations
|
1283 |
+
|
1284 |
**Data Processing:**
|
1285 |
- Input: Raman spectra (any length)
|
1286 |
- Resampling: Linear interpolation to 500 points
|
1287 |
- Normalization: None (preserves intensity relationships)
|
1288 |
""")
|
1289 |
+
st.markdown(
|
1290 |
+
'</div>', unsafe_allow_html=True) # Close the wrapper div
|
1291 |
|
1292 |
render_time = time.time() - start_render
|
1293 |
log_message(
|
|
|
1322 |
else:
|
1323 |
# ===Getting Started===
|
1324 |
st.markdown("""
|
1325 |
+
##### How to Get Started
|
1326 |
+
|
1327 |
+
1. **Select an AI Model:** Use the dropdown menu in the sidebar to choose a model.
|
1328 |
+
2. **Provide Your Data:** Select one of the three input modes:
|
1329 |
+
- **Upload File:** Analyze a single spectrum.
|
1330 |
+
- **Batch Upload:** Process multiple files at once.
|
1331 |
+
- **Sample Data:** Explore functionality with pre-loaded examples.
|
1332 |
+
3. **Run Analysis:** Click the "Run Analysis" button to generate the classification results.
|
1333 |
+
|
1334 |
+
---
|
1335 |
+
|
1336 |
+
##### Supported Data Format
|
1337 |
+
|
1338 |
+
- **File Type:** Plain text (`.txt`)
|
1339 |
+
- **Content:** Must contain two columns: `wavenumber` and `intensity`.
|
1340 |
+
- **Separators:** Values can be separated by spaces or commas.
|
1341 |
+
- **Preprocessing:** Your spectrum will be automatically resampled to 500 data points to match the model's input requirements.
|
1342 |
+
|
1343 |
+
---
|
1344 |
+
|
1345 |
+
##### Example Applications
|
1346 |
- 🔬 Research on polymer degradation
|
1347 |
- ♻️ Recycling feasibility assessment
|
1348 |
- 🌱 Sustainability impact studies
|