devjas1 commited on
Commit
56c06a1
·
1 Parent(s): 6926d1c

feat(UX): Major overhaul of UI, theming, and state management

Browse files

This 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.

Files changed (1) hide show
  1. app.py +200 -105
app.py CHANGED
@@ -47,110 +47,145 @@ st.set_page_config(
47
  "Get help": "https://github.com/KLab-AI3/ml-polymer-recycling"}
48
  )
49
 
50
- # ==Custom CSS Page + Element Styling==
 
 
 
 
 
 
51
  st.markdown("""
52
  <style>
53
- /* ====== Base Styles ====== */
54
- .confbox,
55
- .kv-key,
56
- .kv-val,
 
 
57
  div[data-testid="stMetricValue"],
58
- div[data-testid="stMetricLabel"],
59
- section[data-testid="stSidebar"] {
60
  font-family: 'Inter', sans-serif;
61
- color: #e2e8f0; /* slate-200 */
 
62
  }
63
 
64
- .kv-val { font-family: 'Fira Code', monospace; }
 
 
65
 
66
- /* ====== Tabs Content ====== */
67
  div[data-testid="stTabs"] > div[role="tablist"] + div {
68
  min-height: 400px;
69
- background: #1e293b; /* slate-800 */
 
 
 
70
  border-radius: 10px;
71
- padding: 20px;
72
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
73
  }
74
 
75
- /* ====== Confidence Box ====== */
76
- .confbox {
77
  font-size: 0.9rem;
78
- padding: 10px 12px;
79
- border: 1px solid #2d3748; /* slate-700 */
80
  border-radius: 10px;
81
- background: #1e293b; /* slate-800 */
82
  }
83
 
84
- /* ====== Key-Value Rows ====== */
85
  .kv-row {
86
  display: flex;
87
  justify-content: space-between;
88
  gap: 16px;
89
- padding: 4px 0;
90
- border-bottom: 1px solid #2d3748; /* slate-700 */
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- /* ====== Expanders ====== */
96
  div.stExpander > details > summary::-webkit-details-marker,
97
  div.stExpander > details > summary::marker,
98
- div[data-testid="stExpander"] summary svg { display: none !important; }
99
-
100
- div.stExpander > details[open] > summary { background: #2d3748; /* slate-700 */ }
101
 
102
  div.stExpander > details > summary::after {
103
- font-size: 1.2rem;
104
- font-weight: 700;
 
105
  letter-spacing: 0.5px;
106
- padding: 3px 10px;
107
  border-radius: 999px;
108
- border: 1px solid #0ea5e9; /* sky-500 */
109
- background: #2d3748;
110
- color: #e2e8f0;
 
 
 
 
 
 
 
111
  }
112
 
113
- /* Specialized Expanders */
114
- .expander-marker + div[data-testid="stExpander"] summary::after {
115
  content: "RESULTS";
116
- background: #2dd4bf; /* teal-400 */
117
- color: #0f172a; /* slate-900 */
118
  }
119
- div.stExpander:has(summary:contains("Technical")) > details > summary::after {
120
  content: "ADVANCED";
121
- background: #ea580c; /* orange-600 */
122
- border-color: #f97316; /* orange-500 */
123
  }
 
124
  [data-testid="stExpanderDetails"] {
125
- padding-top: 10px;
126
- background: #0f172a; /* slate-900 */
 
 
127
  }
128
 
129
- /* ====== Sidebar ====== */
130
- section[data-testid="stSidebar"] {
131
- font-size: 0.95rem !important;
132
- line-height: 1.25;
133
- background: #1e293b; /* slate-800 */
134
  }
135
 
136
- /* Metric labels/values */
137
- div[data-testid="stMetricValue"] { font-size: 0.95rem !important; }
138
- div[data-testid="stMetricLabel"] { font-size: 0.85rem !important; opacity: 0.8; }
139
-
140
- /* ====== Interactivity ====== */
141
- button:hover, a:hover, [role="button"]:hover {
142
- background: #2dd4bf; /* teal-400 */
143
- color: #0f172a; /* slate-900 */
144
  }
145
- :focus {
146
- outline: 2px solid #67e8f9; /* cyan-300 */
 
 
 
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
- """remove everything except KEPT global UI context"""
 
 
 
551
  for k in list(st.session_state.keys()):
552
- if k not in KEEP_KEYS:
553
  st.session_state.pop(k, None)
554
 
555
- # == bump the uploader version new widget instance with empty value ==
 
 
 
556
  st.session_state["uploader_version"] += 1
557
  st.session_state["current_upload_key"] = f"upload_txt_{st.session_state['uploader_version']}"
558
 
559
- # == reseed other emphemeral state ==
560
- st.session_state["input_text"] = None
561
- st.session_state["filename"] = None
562
- st.session_state["input_source"] = None
563
- st.session_state["sample_select"] = "-- Select Sample --"
564
- # == return the UI to a clean state ==
565
- st.session_state["inference_run_once"] = False
566
- st.session_state["x_raw"] = None
567
- st.session_state["y_raw"] = None
568
- st.session_state["y_resampled"] = None
569
- st.session_state["log_messages"] = []
570
- st.session_state["status_message"] = "Ready to analyze polymer spectra 🔬"
571
- st.session_state["status_type"] = "info"
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
- uploaded_files = create_batch_uploader()
 
 
 
 
 
 
 
 
 
 
666
 
667
  if uploaded_files:
668
- st.success(
669
- f"{len(uploaded_files)} files selected for batch processing")
670
- st.session_state["batch_files"] = uploaded_files
671
- st.session_state["status_message"] = f"{len(uploaded_files)} ready for batch analysis"
 
 
 
 
 
 
 
 
 
 
 
 
 
672
  st.session_state["status_type"] = "success"
 
673
  else:
674
  st.session_state["batch_files"] = []
675
- st.session_state["status_message"] = "No files selected for batch processing"
676
- st.session_state["status_type"] = "info"
 
 
677
 
678
  # ==Sample tab==
679
  elif mode == "Sample Data":
@@ -728,8 +795,9 @@ def main():
728
  disabled=not inference_ready,
729
  )
730
 
731
- if st.button("Reset", help="Clear current file(s), plots, and results"):
732
- reset_ephemeral_state()
 
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 started by:
1241
- 1. Select an AI model in the sidebar
1242
- 2. Upload a Raman spectrum file or choose a sample
1243
- 3. Click "Run Analysis" to get predictions
1244
-
1245
- ##### Supported formats:
1246
- - Text files (.txt) with wavenumber and intensity columns
1247
- - Space or comma-separated values
1248
- - Any length (automatically resampled to 500 points)
1249
-
1250
- ##### Example applications:
 
 
 
 
 
 
 
 
 
 
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