devjas1 commited on
Commit
7bc29cd
·
1 Parent(s): d6ff372

(FEAT)[Refactor Confidence Visualization and Update CSS]: Remove legacy confidence progress HTML function, enhance softmax confidence calculation, and implement theme-aware custom styles for better UI consistency.

Browse files

(FEAT)[Revise Sidebar and Input Column UI]: Update sidebar header and app description.
- Enhance modality selection with help text
- Improve layout for model comparison and input handling.

(FEAT)[Enhance Dataset Preparation Guidance]: Add detailed instructions for dataset structure, file naming, and public data sources in the training UI and README.

(FEAT)[Enhance Input Column with Action Buttons]: Refactor input column to use side-by-side layout for action buttons, adding a "Reset All" button and improving user interaction.

modules/TRAINING_MODELS_README.md CHANGED
@@ -28,7 +28,50 @@ This unified architecture ensures that any improvements to the training process
28
 
29
  ---
30
 
31
- ## 🛠️ How to Train Models
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  With the new unified architecture, you can train models using either the command line or the interactive web UI, depending on your needs.
34
 
 
28
 
29
  ---
30
 
31
+ ## Acquiring and Preparing Datasets
32
+
33
+ To train a model, you need a dataset of polymer spectra organized in a specific way. The training engine expects a directory containing two subdirectories:
34
+
35
+ - `stable/`: Contains spectra for unweathered, stable polymers.
36
+ - `weathered/`: Contains spectra for weathered, degraded polymers.
37
+
38
+ **Example Directory Structure:**
39
+
40
+ ```
41
+ /my_dataset
42
+ ├── /stable
43
+ │ ├── sample_01.txt
44
+ │ ├── sample_02.csv
45
+ │ └── ...
46
+ └── /weathered
47
+ ├── sample_101.txt
48
+ ├── sample_102.json
49
+ └── ...
50
+ ```
51
+
52
+ ### Data Format
53
+
54
+ Each file inside the `stable` and `weathered` folders should be a two-column text-based format representing a single spectrum:
55
+
56
+ - **Column 1**: Wavenumber (in cm⁻¹)
57
+ - **Column 2**: Intensity / Absorbance
58
+ - **Supported File Types**: `.txt`, `.csv`, `.json`
59
+ - **Separators**: Comma, space, or tab.
60
+
61
+ ### Finding Public Datasets
62
+
63
+ If you don't have your own data, you can find public datasets from various sources. Here are some starting points and keywords for your search:
64
+
65
+ - **Open Specy**: A fantastic community-driven library for Raman and FTIR spectra. You can search for specific polymers and download data.
66
+ - **RRUFF™ Project**: An integrated database of Raman spectra, X-ray diffraction, and chemistry data for minerals. While not polymer-focused, it's a great example of a spectral database.
67
+ - **NIST Chemistry WebBook**: Contains FTIR spectra for many chemical compounds.
68
+ - **GitHub & Kaggle**: Search for "polymer spectroscopy dataset", "Raman spectra plastic", or "FTIR microplastics".
69
+
70
+ When using public data, you may need to manually classify and organize the files into the `stable`/`weathered` structure based on the sample descriptions provided with the dataset.
71
+
72
+ ---
73
+
74
+ ## �🛠️ How to Train Models
75
 
76
  With the new unified architecture, you can train models using either the command line or the interactive web UI, depending on your needs.
77
 
modules/training_ui.py CHANGED
@@ -113,6 +113,32 @@ def render_dataset_selection():
113
 
114
  def render_dataset_upload():
115
  """Render dataset upload interface"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  st.markdown("##### Upload Dataset")
117
 
118
  uploaded_files = st.file_uploader(
 
113
 
114
  def render_dataset_upload():
115
  """Render dataset upload interface"""
116
+ with st.expander("ℹ️ How to Prepare Your Dataset for Training"):
117
+ st.markdown(
118
+ """
119
+ For the model to train correctly, your dataset needs to be structured properly.
120
+
121
+ **1. File Naming & Labeling:**
122
+ The system can infer the class (`stable` or `weathered`) from the filename. For example, a file named `stable_polymer_1.txt` or `weathered_sample.csv` will be automatically categorized.
123
+
124
+ Alternatively, you can upload all your files regardless of name and use the labeling tool that appears below to manually assign each file to a class.
125
+
126
+ **2. File Format:**
127
+ - Each file should contain a single spectrum.
128
+ - The format should be two columns: **Wavenumber** and **Intensity**.
129
+ - Supported file types: `.txt`, `.csv`, `.json`.
130
+
131
+ **3. Finding Data:**
132
+ If you need data, here are some great public resources to get started:
133
+ - **Open Specy**: A large, community-driven library for Raman and FTIR spectra.
134
+ - **RRUFF™ Project**: An integrated database of Raman spectra, X-ray diffraction, and chemistry data for minerals.
135
+ - **NIST Chemistry WebBook**: Contains FTIR spectra for many chemical compounds.
136
+ - **GitHub & Kaggle**: Search for "polymer spectroscopy dataset", "Raman spectra plastic", or "FTIR microplastics".
137
+
138
+ When using public data, you may need to manually classify and organize the files into the `stable`/`weathered` structure based on the sample descriptions provided with the dataset.
139
+ """
140
+ )
141
+
142
  st.markdown("##### Upload Dataset")
143
 
144
  uploaded_files = st.file_uploader(
modules/ui_components.py CHANGED
@@ -67,51 +67,6 @@ def create_spectrum_plot(x_raw, y_raw, x_resampled, y_resampled, _cache_key=None
67
  return Image.open(buf)
68
 
69
 
70
- def render_confidence_progress(
71
- probs: np.ndarray,
72
- labels: list[str] = ["Stable", "Weathered"],
73
- highlight_idx: Union[int, None] = None,
74
- side_by_side: bool = True,
75
- ):
76
- """Render Streamlit native progress bars with scientific formatting."""
77
- p = np.asarray(probs, dtype=float)
78
- p = np.clip(p, 0.0, 1.0)
79
-
80
- if side_by_side:
81
- cols = st.columns(len(labels))
82
- for i, (lbl, val, col) in enumerate(zip(labels, p, cols)):
83
- with col:
84
- is_highlighted = highlight_idx is not None and i == highlight_idx
85
- label_text = f"**{lbl}**" if is_highlighted else lbl
86
- st.markdown(f"{label_text}: {val*100:.1f}%")
87
- st.progress(int(round(val * 100)))
88
- else:
89
- # Vertical layout for better readability
90
- for i, (lbl, val) in enumerate(zip(labels, p)):
91
- is_highlighted = highlight_idx is not None and i == highlight_idx
92
-
93
- # Create a container for each probability
94
- with st.container():
95
- col1, col2 = st.columns([3, 1])
96
- with col1:
97
- if is_highlighted:
98
- st.markdown(f"**{lbl}** ← Predicted")
99
- else:
100
- st.markdown(f"{lbl}")
101
- with col2:
102
- st.metric(label="", value=f"{val*100:.1f}%", delta=None)
103
-
104
- # Progress bar with conditional styling
105
- if is_highlighted:
106
- st.progress(int(round(val * 100)))
107
- st.caption("🎯 **Model Prediction**")
108
- else:
109
- st.progress(int(round(val * 100)))
110
-
111
- if i < len(labels) - 1: # Add spacing between items
112
- st.markdown("")
113
-
114
-
115
  from typing import Optional
116
 
117
 
@@ -161,25 +116,9 @@ def render_sidebar():
161
  # Header
162
  st.header("AI-Driven Polymer Classification")
163
  st.caption(
164
- "Predict polymer degradation (Stable vs Weathered) from Raman/FTIR spectra using validated CNN models. — v0.01"
165
  )
166
 
167
- # Modality Selection
168
- st.markdown("##### Spectroscopy Modality")
169
- modality = st.selectbox(
170
- "Choose Modality",
171
- ["raman", "ftir"],
172
- index=0,
173
- key="modality_select",
174
- format_func=lambda x: f"{'Raman' if x == 'raman' else 'FTIR'}",
175
- )
176
-
177
- # Display modality info
178
- if modality == "ftir":
179
- st.info("FTIR mode: 400-4000 cm-1 range with atmospheric correction")
180
- else:
181
- st.info("Raman mode: 200-4000 cm-1 range with standard preprocessing")
182
-
183
  # Model selection
184
  st.markdown("##### AI Model Selection")
185
 
@@ -202,6 +141,7 @@ def render_sidebar():
202
  model_labels,
203
  key="model_select",
204
  on_change=on_model_change,
 
205
  )
206
  model_choice = selected_label.split(" ", 1)[1]
207
 
@@ -212,37 +152,51 @@ def render_sidebar():
212
  with st.expander("About This App", icon=":material/info:", expanded=False):
213
  st.markdown(
214
  """
215
- **AI-Driven Polymer Aging Prediction and Classification**
216
-
217
- **Purpose**: Classify polymer degradation using AI<br>
218
- **Input**: Raman spectroscopy .txt files<br>
219
- **Models**: CNN architectures for classification<br>
220
- **Modalities**: Raman and FTIR spectroscopy support<br>
221
- **Features**: Multi-model comparison and analysis<br>
222
 
 
223
 
224
- **Contributors**<br>
225
- - Dr. Sanmukh Kuppannagari (Mentor)<br>
226
- - Dr. Metin Karailyan (Mentor)<br>
227
- - Jaser Hasan (Author)<br>
228
 
 
 
 
 
 
 
229
 
230
- **Links**<br>
231
- [HF Space](https://huggingface.co/spaces/dev-jas/polymer-aging-ml)<br>
232
  [GitHub Repository](https://github.com/KLab-AI3/ml-polymer-recycling)
233
 
 
 
 
 
 
234
 
235
- **Citation Figure2CNN (baseline)**
236
  Neo et al., 2023, *Resour. Conserv. Recycl.*, 188, 106718.
237
- [https://doi.org/10.1016/j.resconrec.2022.106718](https://doi.org/10.1016/j.resconrec.2022.106718)
238
- """,
239
- unsafe_allow_html=True,
240
  )
241
 
242
 
243
  def render_input_column():
244
  st.markdown("##### Data Input")
245
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  mode = st.radio(
247
  "Input mode",
248
  ["Upload File", "Batch Upload", "Sample Data"],
@@ -332,6 +286,7 @@ def render_input_column():
332
  options,
333
  key="sample_select",
334
  on_change=on_sample_change,
 
335
  )
336
  if sel != "-- Select Sample --":
337
  st.session_state["status_message"] = (
@@ -365,19 +320,21 @@ def render_input_column():
365
  # Store for other modules to access
366
  st.session_state["inference_ready"] = inference_ready
367
 
368
- # Render buttons
369
- with st.form("analysis_form", clear_on_submit=False):
370
- submitted = st.form_submit_button(
371
- "Run Analysis", type="primary", disabled=not inference_ready
 
 
 
 
 
372
  )
373
- st.button(
374
- "Reset All",
375
- on_click=reset_ephemeral_state,
376
- help="Clear all uploaded files and results.",
377
- )
378
 
379
  # Handle form submission
380
- if submitted and inference_ready:
381
  st.session_state["run_uuid"] = uuid.uuid4().hex[:8]
382
  if st.session_state.get("batch_mode"):
383
  batch_files = st.session_state.get("batch_files", [])
@@ -604,16 +561,10 @@ def render_results_column():
604
  return -sum(p * math.log(p) for p in ps)
605
 
606
  def _badge(text, kind="info"):
607
- palette = {
608
- "info": ("#334155", "#e2e8f0"),
609
- "warn": ("#7c2d12", "#fde68a"),
610
- "good": ("#064e3b", "#bbf7d0"),
611
- "bad": ("#7f1d1d", "#fecaca"),
612
- }
613
- bg, fg = palette.get(kind, palette["info"])
614
  st.markdown(
615
- f"<span style='background:{bg};color:{fg};padding:4px 8px;"
616
- f"border-radius:6px;font-size:0.80rem;white-space:nowrap'>{text}</span>",
617
  unsafe_allow_html=True,
618
  )
619
 
@@ -935,18 +886,17 @@ def render_results_column():
935
  with st.container():
936
  st.markdown("### 🔍 Methodology & Interpretation")
937
 
938
- # Process explanation
939
- st.markdown("Analysis Pipeline")
940
  process_steps = [
941
- "📁 **Data Upload**: Raman spectrum file loaded and validated",
942
- "🔍 **Preprocessing**: Spectrum parsed and resampled to 500 data points using linear interpolation",
943
- "🧠 **AI Inference**: Convolutional Neural Network analyzes spectral patterns and molecular signatures",
944
- "📊 **Classification**: Binary prediction with confidence scoring using softmax probabilities",
945
- "✅ **Validation**: Ground truth comparison (when available from filename)",
946
  ]
947
 
948
  for step in process_steps:
949
- st.markdown(step)
950
 
951
  st.markdown("---")
952
 
@@ -959,11 +909,10 @@ def render_results_column():
959
  st.markdown("**Stable (Unweathered) Polymers:**")
960
  st.info(
961
  """
962
- - Well-preserved molecular structure
963
- - Minimal oxidative degradation
964
- - Characteristic Raman peaks intact
965
- -
966
- itable for recycling applications
967
  """
968
  )
969
 
@@ -971,49 +920,47 @@ def render_results_column():
971
  st.markdown("**Weathered (Degraded) Polymers:**")
972
  st.warning(
973
  """
974
- - Oxidized molecular bonds
975
- - Surface degradation present
976
- - Altered spectral signatures
977
- - May require additional processing
978
  """
979
  )
980
 
981
  st.markdown("---")
982
 
983
  # Applications
984
- st.markdown("#### Research Applications")
985
 
986
  applications = [
987
- "🔬 **Material Science**: Polymer degradation studies",
988
- "♻️ **Recycling Research**: Viability assessment for circular economy",
989
- "🌱 **Environmental Science**: Microplastic weathering analysis",
990
- "🏭 **Quality Control**: Manufacturing process monitoring",
991
- "📈 **Longevity Studies**: Material aging prediction",
992
  ]
993
 
994
  for app in applications:
995
- st.markdown(app)
996
 
997
  # Technical details
998
- # MODIFIED: Wrap the expander in a div with the 'expander-advanced' class
999
- with st.expander("🔧 Technical Details", expanded=False):
 
1000
  st.markdown(
1001
  """
1002
- **Model Architecture:**
1003
- - Convolutional layers for feature extraction
1004
- - Residual connections for gradient flow
1005
- - Fully connected layers for classification
1006
- - Softmax activation for probability distribution
1007
-
1008
- **Performance Metrics:**
1009
- - Accuracy: 94.8-96.2% on validation set
1010
- - F1-Score: 94.3-95.9% across classes
1011
- - Robust to spectral noise and baseline variations
1012
-
1013
- **Data Processing:**
1014
- - Input: Raman spectra (any length)
1015
- - Resampling: Linear interpolation to 500 points
1016
- - Normalization: None (preserves intensity relationships)
1017
  """
1018
  )
1019
 
@@ -1023,7 +970,8 @@ def render_results_column():
1023
  )
1024
 
1025
  with st.expander("Spectrum Preprocessing Results", expanded=False):
1026
- st.caption("<br>Spectral Analysis", unsafe_allow_html=True)
 
1027
 
1028
  # Add some context about the preprocessing
1029
  st.markdown(
@@ -1071,6 +1019,7 @@ def render_results_column():
1071
  - **Content:** Must contain two columns: `wavenumber` and `intensity`.
1072
  - **Separators:** Values can be separated by spaces or commas.
1073
  - **Preprocessing:** Your spectrum will be automatically resampled to 500 data points to match the model's input requirements.
 
1074
  """
1075
  )
1076
  else:
@@ -1094,6 +1043,7 @@ def render_results_column():
1094
  - **Content:** Must contain two columns: `wavenumber` and `intensity`.
1095
  - **Separators:** Values can be separated by spaces or commas.
1096
  - **Preprocessing:** Your spectrum will be automatically resampled to 500 data points to match the model's input requirements.
 
1097
  """
1098
  )
1099
 
@@ -1120,27 +1070,16 @@ def render_comparison_tab():
1120
  "Compare predictions across different AI models for comprehensive analysis."
1121
  )
1122
 
1123
- # Modality selector - Use independant state for comparison tab
1124
- col_mod1, col_mod2 = st.columns([1, 2])
1125
- with col_mod1:
1126
- # Get the current sidebar modality but don't try to sync back
1127
- current_modality = st.session_state.get("modality_select", "raman")
1128
- modality = st.selectbox(
1129
- "Select Modality",
1130
- ["raman", "ftir"],
1131
- index=0 if current_modality == "raman" else 1,
1132
- help="Choose the spectroscopy modality for analysis",
1133
- key="comparison_tab_modality", # Independant key for session state to avoid duplication of UI elements
1134
- ) # Note: Intentially not synching back to avoid state conflicts
1135
-
1136
- with col_mod2:
1137
- # Filter models by modality
1138
- compatible_models = models_for_modality(modality)
1139
- if not compatible_models:
1140
- st.error(f"No models available for {modality.upper()} modality")
1141
- return
1142
-
1143
- st.info(f"📊 {len(compatible_models)} models available for {modality.upper()}")
1144
 
1145
  # Enhanced model selection with metadata
1146
  st.markdown("##### Select Models for Comparison")
 
67
  return Image.open(buf)
68
 
69
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  from typing import Optional
71
 
72
 
 
116
  # Header
117
  st.header("AI-Driven Polymer Classification")
118
  st.caption(
119
+ "Analyze and classify polymer degradation with a suite of explainable AI models for Raman & FTIR spectroscopy. — v0.02"
120
  )
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  # Model selection
123
  st.markdown("##### AI Model Selection")
124
 
 
141
  model_labels,
142
  key="model_select",
143
  on_change=on_model_change,
144
+ width="stretch",
145
  )
146
  model_choice = selected_label.split(" ", 1)[1]
147
 
 
152
  with st.expander("About This App", icon=":material/info:", expanded=False):
153
  st.markdown(
154
  """
155
+ **AI-Driven Polymer Analysis Platform**
 
 
 
 
 
 
156
 
157
+ **Purpose**: Classify, analyze, and understand polymer degradation using explainable AI.
158
 
159
+ **Input**: Raman & FTIR spectra in `.txt`, `.csv`, or `.json` formats.
 
 
 
160
 
161
+ **Features**:
162
+ - Single & Batch Spectrum Analysis
163
+ - Multi-Model Performance Comparison
164
+ - Interactive Model Training Hub
165
+ - Explainable AI (XAI) with feature importance
166
+ - Modality-Aware Preprocessing
167
 
168
+ **Links**
169
+ [HF Space](https://huggingface.co/spaces/dev-jas/polymer-aging-ml)
170
  [GitHub Repository](https://github.com/KLab-AI3/ml-polymer-recycling)
171
 
172
+ **Contributors**
173
+ - Dr. Sanmukh Kuppannagari (Mentor)
174
+ - Dr. Metin Karailyan (Mentor)
175
+ - Jaser Hasan (Author)
176
+
177
 
178
+ **Citation (Baseline Model)**
179
  Neo et al., 2023, *Resour. Conserv. Recycl.*, 188, 106718.
180
+ https://doi.org/10.1016/j.resconrec.2022.106718
181
+ """
 
182
  )
183
 
184
 
185
  def render_input_column():
186
  st.markdown("##### Data Input")
187
 
188
+ # Modality Selection - Moved from sidebar to be the primary context setter
189
+ st.markdown("###### 1. Choose Spectroscopy Modality")
190
+ modality = st.selectbox(
191
+ "Choose Modality",
192
+ ["raman", "ftir"],
193
+ index=0,
194
+ key="modality_select",
195
+ format_func=lambda x: f"{'Raman' if x == 'raman' else 'FTIR'}",
196
+ help="Select the type of spectroscopy data you are analyzing. This choice affects preprocessing steps.",
197
+ width=325,
198
+ )
199
+
200
  mode = st.radio(
201
  "Input mode",
202
  ["Upload File", "Batch Upload", "Sample Data"],
 
286
  options,
287
  key="sample_select",
288
  on_change=on_sample_change,
289
+ width=350,
290
  )
291
  if sel != "-- Select Sample --":
292
  st.session_state["status_message"] = (
 
320
  # Store for other modules to access
321
  st.session_state["inference_ready"] = inference_ready
322
 
323
+ # --- Action Buttons ---
324
+ # Using columns for a side-by-side layout
325
+ col1, col2 = st.columns(2)
326
+ with col1:
327
+ submitted = st.button(
328
+ "Run Analysis",
329
+ type="primary",
330
+ disabled=not inference_ready,
331
+ use_container_width=True,
332
  )
333
+ with col2:
334
+ st.button("Reset All", on_click=reset_ephemeral_state, use_container_width=True)
 
 
 
335
 
336
  # Handle form submission
337
+ if submitted:
338
  st.session_state["run_uuid"] = uuid.uuid4().hex[:8]
339
  if st.session_state.get("batch_mode"):
340
  batch_files = st.session_state.get("batch_files", [])
 
561
  return -sum(p * math.log(p) for p in ps)
562
 
563
  def _badge(text, kind="info"):
564
+ # This function now relies on CSS classes defined in style.css
565
+ # for better separation of concerns and maintainability.
 
 
 
 
 
566
  st.markdown(
567
+ f"<span class='badge badge-{kind}'>{text}</span>",
 
568
  unsafe_allow_html=True,
569
  )
570
 
 
886
  with st.container():
887
  st.markdown("### 🔍 Methodology & Interpretation")
888
 
889
+ st.markdown("#### Analysis Pipeline")
 
890
  process_steps = [
891
+ "📁 **Data Input**: Upload a spectrum file (`.txt`, `.csv`, `.json`) and select the spectroscopy modality (Raman or FTIR).",
892
+ "🔬 **Modality-Aware Preprocessing**: The spectrum is automatically processed with steps tailored to the selected modality, including baseline correction, smoothing, normalization, and resampling to a fixed length (500 points).",
893
+ "🧠 **AI Inference**: A selected model from the registry (e.g., `Figure2CNN`, `ResNet`, `EnhancedCNN`) analyzes the processed spectrum to identify key patterns.",
894
+ "📊 **Classification & Confidence**: The model outputs a binary prediction (Stable vs. Weathered) along with a detailed probability breakdown and confidence score.",
895
+ "✅ **Validation & Explainability**: Results are presented with technical diagnostics, and where possible, explainability metrics to interpret the model's decision.",
896
  ]
897
 
898
  for step in process_steps:
899
+ st.markdown(f"- {step}")
900
 
901
  st.markdown("---")
902
 
 
909
  st.markdown("**Stable (Unweathered) Polymers:**")
910
  st.info(
911
  """
912
+ - **Spectral Signature**: Sharp, well-defined peaks corresponding to the polymer's known vibrational modes.
913
+ - **Chemical State**: Minimal evidence of oxidation or chain scission. The polymer backbone is intact.
914
+ - **Model Behavior**: The AI identifies a strong match with the spectral fingerprint of a non-degraded reference material.
915
+ - **Implication**: Suitable for high-quality recycling applications.
 
916
  """
917
  )
918
 
 
920
  st.markdown("**Weathered (Degraded) Polymers:**")
921
  st.warning(
922
  """
923
+ - **Spectral Signature**: Peak broadening, baseline shifts, and the emergence of new peaks (e.g., carbonyl group at ~1715 cm⁻¹).
924
+ - **Chemical State**: Evidence of oxidation, hydrolysis, or other degradation pathways.
925
+ - **Model Behavior**: The AI detects features that deviate significantly from the reference fingerprint, indicating chemical alteration.
926
+ - **Implication**: May require more intensive processing or be unsuitable for certain recycling streams.
927
  """
928
  )
929
 
930
  st.markdown("---")
931
 
932
  # Applications
933
+ st.markdown("#### Research & Industrial Applications")
934
 
935
  applications = [
936
+ " **Material Science**: Quantify degradation rates and study aging mechanisms in novel polymers.",
937
+ "♻️ **Circular Economy**: Automate the quality control and sorting of post-consumer plastics for recycling.",
938
+ "🌱 **Environmental Science**: Analyze the weathering of microplastics in various environmental conditions.",
939
+ "🏭 **Industrial QC**: Monitor material integrity and predict product lifetime in manufacturing processes.",
940
+ "🤖 **AI-Driven Discovery**: Use explainability features to generate new hypotheses about material behavior.",
941
  ]
942
 
943
  for app in applications:
944
+ st.markdown(f"- {app}")
945
 
946
  # Technical details
947
+ with st.expander(
948
+ "🔧 Technical Architecture Details", expanded=False
949
+ ):
950
  st.markdown(
951
  """
952
+ **Model Architectures:**
953
+ - The app features a registry of models, including the `Figure2CNN` baseline, `ResNet` variants, and more advanced custom architectures like `EnhancedCNN` and `HybridSpectralNet`.
954
+ - Each model is trained on a comprehensive dataset of stable and weathered polymer spectra.
955
+
956
+ **Unified Training Engine:**
957
+ - A central `TrainingEngine` ensures that all models are trained and validated using a consistent, reproducible 10-fold cross-validation strategy.
958
+ - This engine can be accessed via the **CLI** (`scripts/train_model.py`) for automated experiments or the **UI** ("Model Training Hub") for interactive use.
959
+
960
+ **Explainability & Transparency (XAI):**
961
+ - **Feature Importance**: The system is designed to incorporate SHAP and gradient-based methods to highlight which spectral regions most influence a prediction.
962
+ - **Uncertainty Quantification**: Advanced models can estimate both model (epistemic) and data (aleatoric) uncertainty.
963
+ - **Data Provenance**: The enhanced data pipeline tracks every preprocessing step, ensuring full traceability from raw data to final prediction.
 
 
 
964
  """
965
  )
966
 
 
970
  )
971
 
972
  with st.expander("Spectrum Preprocessing Results", expanded=False):
973
+ st.markdown("---")
974
+ st.markdown("##### Spectral Analysis")
975
 
976
  # Add some context about the preprocessing
977
  st.markdown(
 
1019
  - **Content:** Must contain two columns: `wavenumber` and `intensity`.
1020
  - **Separators:** Values can be separated by spaces or commas.
1021
  - **Preprocessing:** Your spectrum will be automatically resampled to 500 data points to match the model's input requirements.
1022
+ - **Examples:** Use the "Sample Data" input mode to see examples, or find public data on sites like Open Specy.
1023
  """
1024
  )
1025
  else:
 
1043
  - **Content:** Must contain two columns: `wavenumber` and `intensity`.
1044
  - **Separators:** Values can be separated by spaces or commas.
1045
  - **Preprocessing:** Your spectrum will be automatically resampled to 500 data points to match the model's input requirements.
1046
+ - **Examples:** Use the "Sample Data" input mode to see examples, or find public data on sites like Open Specy.
1047
  """
1048
  )
1049
 
 
1070
  "Compare predictions across different AI models for comprehensive analysis."
1071
  )
1072
 
1073
+ # Use the global modality selector from the main page
1074
+ modality = st.session_state.get("modality_select", "raman")
1075
+ st.info(
1076
+ f"Comparing models using **{modality.upper()}** preprocessing parameters. You can change this on the 'Upload and Run' page."
1077
+ )
1078
+
1079
+ compatible_models = models_for_modality(modality)
1080
+ if not compatible_models:
1081
+ st.error(f"No models available for {modality.upper()} modality")
1082
+ return
 
 
 
 
 
 
 
 
 
 
 
1083
 
1084
  # Enhanced model selection with metadata
1085
  st.markdown("##### Select Models for Comparison")
static/style.css CHANGED
@@ -1,13 +1,14 @@
1
- /* THEME-AWARE CUSTOM CSS
2
-
3
- This CSS block has been refactored to use Streamlit's internal theme
4
- variables. This ensures that all custom components will automatically adapt
5
- to both light and dark themes selected by the user in the settings menu.
6
- */
7
- /* ====== Font Imports (Optional but Recommended) ====== */
 
8
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Fira+Code:wght@400&display=swap');
9
 
10
- /* ====== Base & Typography ====== */
11
  .stApp,
12
  section[data-testid="stSidebar"],
13
  div[data-testid="stMetricValue"],
@@ -21,7 +22,7 @@ div[data-testid="stMetricLabel"] {
21
  font-family: 'Fira Code', monospace;
22
  }
23
 
24
- /* ====== Custom Containers: Tabs & Info Boxes ====== */
25
  div[data-testid="stTabs"]>div[role="tablist"]+div {
26
  min-height: 400px;
27
  /* Uses the secondary background color, which is different in light and dark modes */
@@ -41,7 +42,7 @@ div[data-testid="stTabs"]>div[role="tablist"]+div {
41
  background-color: var(--secondary-background-color);
42
  }
43
 
44
- /* ====== Key-Value Pair Styling ====== */
45
  .kv-row {
46
  display: flex;
47
  justify-content: space-between;
@@ -66,7 +67,44 @@ div[data-testid="stTabs"]>div[role="tablist"]+div {
66
  text-align: right;
67
  }
68
 
69
- /* ====== Custom Expander Styling ====== */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  div.stExpander>details>summary::-webkit-details-marker,
71
  div.stExpander>details>summary::marker,
72
  div[data-testid="stExpander"] summary svg {
@@ -74,6 +112,7 @@ div[data-testid="stExpander"] summary svg {
74
  }
75
 
76
  div.stExpander>details>summary::after {
 
77
  content: '↓';
78
  font-size: 0.75rem;
79
  font-weight: 900;
@@ -82,39 +121,21 @@ div.stExpander>details>summary::after {
82
  border-radius: 999px;
83
  /* The primary color is set in config.toml and adapted by Streamlit */
84
  background-color: var(--primary);
85
- color: var(--text-color);
86
- /* Text on the primary color needs high contrast. White works well for our chosen purple. */
87
-
88
  transition: background-color 0.2s ease-in-out;
89
  }
90
 
91
  div.stExpander>details>summary:hover::after {
92
- /* Using a fixed darker shade on hover. A more advanced solution could use color-mix() in CSS. */
93
  filter: brightness(90%);
94
  }
95
 
96
- /* Specialized Expander Labels */
97
- .expander-results div[data-testid="stExpander"] summary::after {
98
- content: "RESULTS";
99
- background-color: #16A34A;
100
- /* Green is universal for success */
101
-
102
- }
103
-
104
  div[data-testid="stExpander"] details {
105
- content: "RESULTS";
106
  background-color: var(--primary);
107
  border-radius: 10px;
108
  padding: 10px
109
  }
110
 
111
- .expander-advanced div[data-testid="stExpander"] summary::after {
112
- content: "ADVANCED";
113
- background-color: #D97706;
114
- /* Amber is universal for warning/technical */
115
-
116
- }
117
-
118
  [data-testid="stExpanderDetails"] {
119
  padding: 16px 4px 4px 4px;
120
  background-color: transparent;
@@ -122,7 +143,7 @@ div[data-testid="stExpander"] details {
122
  margin-top: 12px;
123
  }
124
 
125
- /* ====== Sidebar & Metrics ====== */
126
  section[data-testid="stSidebar"]>div:first-child {
127
  background-color: var(--secondary-background-color);
128
  border-right: 1px solid rgba(128, 128, 128, 0.2);
@@ -138,52 +159,10 @@ div[data-testid="stMetricLabel"] {
138
  opacity: 0.8;
139
  }
140
 
141
- /* ====== Interactivity & Accessibility ====== */
142
  :focus-visible {
143
  /* The focus outline now uses the theme's primary color */
144
- outline: 20px solid var(--primary);
145
  outline-offset: 2px;
146
  border-radius: 8px;
147
- }
148
-
149
- .st-key-csv-button,
150
- .st-key-json-button,
151
- .st-key-clearall-button {
152
- display: block;
153
- border: 1px double #1a1a1a98;
154
- max-width: 100%;
155
- border-radius: 8px;
156
-
157
-
158
-
159
- }
160
-
161
- .st-key-page-link-container {
162
- padding: 5px;
163
- display: inline-block;
164
- justify-items: center;
165
- align-self: center;
166
- align-content: center;
167
- border: 1px double #1a1a1a98;
168
- border-radius: 8px;
169
- background-color: var(--secondary-background-color);
170
- max-width: 100%;
171
-
172
- }
173
-
174
- .st-key-buttons-container {
175
- display: flex;
176
- max-width: 100%;
177
- }
178
-
179
-
180
-
181
- /* .st-key-csv-button:hover,
182
- .st-key-json-button:hover,
183
- .st-key-clearall-button:hover {
184
- padding: .25px;
185
- }
186
- .st-key-page-link {
187
- color: var(--text-color);
188
- text-decoration: none;
189
- }*/
 
1
+ /* ==========================================================================
2
+ POLYMEROS - THEME-AWARE CUSTOM STYLESHEET
3
+ ==========================================================================
4
+ This file uses Streamlit's theme variables (e.g., var(--text-color))
5
+ to ensure all custom components adapt to both light and dark themes.
6
+ ========================================================================== */
7
+
8
+ /* --- Font Imports --- */
9
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Fira+Code:wght@400&display=swap');
10
 
11
+ /* --- Base & Typography --- */
12
  .stApp,
13
  section[data-testid="stSidebar"],
14
  div[data-testid="stMetricValue"],
 
22
  font-family: 'Fira Code', monospace;
23
  }
24
 
25
+ /* --- Custom Containers: Tabs & Info Boxes --- */
26
  div[data-testid="stTabs"]>div[role="tablist"]+div {
27
  min-height: 400px;
28
  /* Uses the secondary background color, which is different in light and dark modes */
 
42
  background-color: var(--secondary-background-color);
43
  }
44
 
45
+ /* --- Key-Value Pair Styling --- */
46
  .kv-row {
47
  display: flex;
48
  justify-content: space-between;
 
67
  text-align: right;
68
  }
69
 
70
+ /* --- Custom Badges (replaces inline styles) --- */
71
+ .badge {
72
+ display: inline-block;
73
+ padding: 4px 8px;
74
+ font-size: 0.80rem;
75
+ font-weight: 500;
76
+ line-height: 1;
77
+ text-align: center;
78
+ white-space: nowrap;
79
+ vertical-align: baseline;
80
+ border-radius: 6px;
81
+ font-family: 'Inter', sans-serif;
82
+ }
83
+
84
+ .badge-info {
85
+ color: #e2e8f0;
86
+ background-color: #334155;
87
+ }
88
+
89
+ .badge-warn {
90
+ color: #7c2d12;
91
+ background-color: #fde68a;
92
+ }
93
+
94
+ .badge-good {
95
+ color: #064e3b;
96
+ background-color: #bbf7d0;
97
+ }
98
+
99
+ .badge-bad {
100
+ color: #7f1d1d;
101
+ background-color: #fecaca;
102
+ }
103
+
104
+
105
+ /* --- Custom Expander Styling --- */
106
+
107
+ /* Hide default expander arrow */
108
  div.stExpander>details>summary::-webkit-details-marker,
109
  div.stExpander>details>summary::marker,
110
  div[data-testid="stExpander"] summary svg {
 
112
  }
113
 
114
  div.stExpander>details>summary::after {
115
+ /* Custom indicator */
116
  content: '↓';
117
  font-size: 0.75rem;
118
  font-weight: 900;
 
121
  border-radius: 999px;
122
  /* The primary color is set in config.toml and adapted by Streamlit */
123
  background-color: var(--primary);
124
+ color: white;
125
+ /* Assuming primary color is dark enough for white text */
 
126
  transition: background-color 0.2s ease-in-out;
127
  }
128
 
129
  div.stExpander>details>summary:hover::after {
 
130
  filter: brightness(90%);
131
  }
132
 
 
 
 
 
 
 
 
 
133
  div[data-testid="stExpander"] details {
 
134
  background-color: var(--primary);
135
  border-radius: 10px;
136
  padding: 10px
137
  }
138
 
 
 
 
 
 
 
 
139
  [data-testid="stExpanderDetails"] {
140
  padding: 16px 4px 4px 4px;
141
  background-color: transparent;
 
143
  margin-top: 12px;
144
  }
145
 
146
+ /* --- Sidebar & Metrics --- */
147
  section[data-testid="stSidebar"]>div:first-child {
148
  background-color: var(--secondary-background-color);
149
  border-right: 1px solid rgba(128, 128, 128, 0.2);
 
159
  opacity: 0.8;
160
  }
161
 
162
+ /* --- Interactivity & Accessibility --- */
163
  :focus-visible {
164
  /* The focus outline now uses the theme's primary color */
165
+ outline: 2px solid var(--primary);
166
  outline-offset: 2px;
167
  border-radius: 8px;
168
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
utils/confidence.py CHANGED
@@ -1,12 +1,15 @@
1
  """Confidence calculation and visualization utilities.
2
  Provides normalized softmax confidence and color-coded badges"""
 
3
  from typing import Tuple, List
4
  import numpy as np
5
  import torch
6
  import torch.nn.functional as F
7
 
8
 
9
- def calculate_softmax_confidence(logits: torch.Tensor) -> Tuple[np.ndarray, float, str, str]:
 
 
10
  """Calculate normalized confidence using softmax
11
  Args:
12
  logits: Raw model logits tensor
@@ -63,75 +66,6 @@ def format_confidence_display(confidence: float, level: str, emoji: str) -> str:
63
  return f"{emoji} **{level}** ({confidence:.1%})"
64
 
65
 
66
- def create_confidence_progress_html(
67
- probabilities: np.ndarray,
68
- labels: List[str],
69
- highlight_idx: int
70
- ) -> str:
71
- """
72
- Create HTML for confidence progress bars
73
-
74
- Args:
75
- probabilities: Array of class probabilities
76
- labels: List of class labels
77
- highlight_idx: Index of predicted class to highlight
78
-
79
- Returns:
80
- HTML string for progress bars
81
- """
82
- if len(probabilities) == 0 or len(labels) == 0:
83
- return "<p>No confidence data available</p>"
84
-
85
- html_parts = []
86
-
87
- for i, (prob, label) in enumerate(zip(probabilities, labels)):
88
- # ===Color based on whether this is the predicted class===
89
- if i == highlight_idx:
90
- if prob >= 0.80:
91
- color = "#22c55e" # green-500
92
- text_color = "#ffffff"
93
- elif prob >= 0.60:
94
- color = "#eab308" # yellow-500
95
- text_color = "#000000"
96
- else:
97
- color = "#ef4444" # red-500
98
- text_color = "#ffffff"
99
- else:
100
- color = "#e5e7eb" # gray-200
101
- text_color = "#6b7280" # gray-500
102
-
103
- percentage = prob * 100
104
-
105
- html_parts.append(f"""
106
- <div style="margin-bottom: 8px;">
107
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
108
- <span style="font-size: 0.875rem; font-weight: 500; color: #374151;">{label}</span>
109
- <span style="font-size: 0.875rem; color: #6b7280;">{percentage:.1f}%</span>
110
- </div>
111
- <div style="width: 100%; background-color: #f3f4f6; border-radius: 0.375rem; height: 20px; overflow: hidden;">
112
- <div style="
113
- width: {percentage}%;
114
- height: 100%;
115
- background-color: {color};
116
- display: flex;
117
- align-items: center;
118
- justify-content: center;
119
- transition: width 0.3s ease;
120
- ">
121
- {f'<span style="color: {text_color}; font-size: 0.75rem; font-weight: 600;">{percentage:.1f}%</span>' if percentage > 20 else ''}
122
- </div>
123
- </div>
124
- </div>
125
- """)
126
-
127
- return f"""
128
- <div style="padding: 16px; background-color: #f9fafb; border-radius: 0.5rem; border: 1px solid #e5e7eb;">
129
- <h4 style="margin: 0 0 12px 0; font-size: 1rem; color: #374151;">Confidence Breakdown</h4>
130
- {''.join(html_parts)}
131
- </div>
132
- """
133
-
134
-
135
  def calculate_legacy_confidence(logits_list: List[float]) -> Tuple[float, str, str]:
136
  """
137
  Calculate confidence using legacy logit margin method for backward compatibility
 
1
  """Confidence calculation and visualization utilities.
2
  Provides normalized softmax confidence and color-coded badges"""
3
+
4
  from typing import Tuple, List
5
  import numpy as np
6
  import torch
7
  import torch.nn.functional as F
8
 
9
 
10
+ def calculate_softmax_confidence(
11
+ logits: torch.Tensor,
12
+ ) -> Tuple[np.ndarray, float, str, str]:
13
  """Calculate normalized confidence using softmax
14
  Args:
15
  logits: Raw model logits tensor
 
66
  return f"{emoji} **{level}** ({confidence:.1%})"
67
 
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  def calculate_legacy_confidence(logits_list: List[float]) -> Tuple[float, str, str]:
70
  """
71
  Calculate confidence using legacy logit margin method for backward compatibility