StatisticalControl / src /streamlit_app.py
MuhammadNabeelSh's picture
Update src/streamlit_app.py
9ee0157 verified
Raw History Blame Contribute Delete
12.3 kB
import streamlit as st
import plotly.graph_objects as go
import numpy as np
import pandas as pd
# =============================================================================
# 1. CONFIGURATION & DATA
# =============================================================================
st.set_page_config(
page_title="Biomaterials Stress-Strain Explorer",
page_icon="🦷",
layout="wide",
initial_sidebar_state="expanded"
)
# Material Database: Dental & Biomaterials
MATERIALS = {
"🦷 Dental Amalgam": {
"E": 80000, "yield": 200, "uts": 260, "strain": 0.006, "type": "brittle",
"desc": "Traditional restorative alloy. High stiffness, low ductility.",
"color": "#4A90D9"
},
"🦷 Composite Resin": {
"E": 12000, "yield": 90, "uts": 120, "strain": 0.035, "type": "semi-ductile",
"desc": "Tooth-colored restorative. Moderate stiffness and ductility.",
"color": "#E8A838"
},
"🦷 Zirconia (Y-TZP)": {
"E": 200000, "yield": 900, "uts": 1100, "strain": 0.003, "type": "brittle",
"desc": "High-strength ceramic. Extremely stiff, very brittle failure.",
"color": "#9B59B6"
},
"🦷 Gold Alloy (Type IV)": {
"E": 85000, "yield": 350, "uts": 500, "strain": 0.12, "type": "ductile",
"desc": "Cast noble metal. Excellent ductility and fatigue resistance.",
"color": "#F39C12"
},
"🦷 Titanium (Grade 5)": {
"E": 110000, "yield": 850, "uts": 950, "strain": 0.10, "type": "ductile",
"desc": "Ti-6Al-4V alloy. Standard for implants. High strength-to-weight.",
"color": "#3498DB"
},
"🦷 Glass Ionomer": {
"E": 8000, "yield": 40, "uts": 55, "strain": 0.008, "type": "brittle",
"desc": "Adhesive cement. Low strength, fluoride release.",
"color": "#1ABC9C"
},
"🦴 Cortical Bone": {
"E": 17000, "yield": 120, "uts": 150, "strain": 0.018, "type": "semi-ductile",
"desc": "Dense outer bone. Anisotropic, moderate stiffness.",
"color": "#ECF0F1"
},
"🧬 PEEK": {
"E": 3600, "yield": 95, "uts": 100, "strain": 0.06, "type": "ductile",
"desc": "Polyetheretherketone. Used in spinal implants. Low modulus matches bone.",
"color": "#E74C3C"
},
"🧬 UHMWPE": {
"E": 800, "yield": 20, "uts": 30, "strain": 0.35, "type": "ductile",
"desc": "Ultra-high molecular weight polyethylene. Joint bearing surface.",
"color": "#F1C40F"
},
"⚙️ Stainless Steel 316L": {
"E": 193000, "yield": 205, "uts": 505, "strain": 0.40, "type": "ductile",
"desc": "Austenitic steel. Surgical instruments and temporary implants.",
"color": "#7F8C8D"
}
}
# =============================================================================
# 2. PHYSICS ENGINE (Curve Generation)
# =============================================================================
def generate_stress_strain(E, sigma_y, sigma_uts, epsilon_break, material_type):
"""
Generates a realistic stress-strain curve using a piecewise model.
1. Elastic: Linear (Hooke's Law)
2. Plastic: Cosine transition to UTS
3. Necking: Power-law decay to fracture
"""
# Calculate yield strain
epsilon_y = sigma_y / E
# Determine strain at UTS based on material type
if material_type == "brittle":
epsilon_uts = epsilon_y * 1.1
elif material_type == "semi-ductile":
epsilon_uts = epsilon_y + (epsilon_break - epsilon_y) * 0.5
else: # ductile
epsilon_uts = epsilon_y + (epsilon_break - epsilon_y) * 0.4
# Generate strain points
n_points = 1000
epsilon = np.linspace(0, epsilon_break, n_points)
sigma = np.zeros_like(epsilon)
for i, eps in enumerate(epsilon):
if eps <= epsilon_y:
# Elastic Region
sigma[i] = E * eps
elif eps <= epsilon_uts:
# Strain Hardening Region (Smooth transition)
# Normalize position between yield and UTS
t = (eps - epsilon_y) / (epsilon_uts - epsilon_y)
# Use cosine for smooth hardening
sigma[i] = sigma_y + (sigma_uts - sigma_y) * (0.5 * (1 - np.cos(np.pi * t)))
else:
# Necking / Softening Region
t = (eps - epsilon_uts) / (epsilon_break - epsilon_uts)
# Stress drops from UTS to near 0
sigma[i] = sigma_uts * (1 - 0.95 * (t ** 0.7))
return epsilon, np.maximum(sigma, 0)
# =============================================================================
# 3. ACCESSIBILITY HELPERS
# =============================================================================
def inject_accessibility_css():
"""Injects CSS for high contrast and large text modes."""
st.markdown("""
<style>
/* High Contrast Mode */
.high-contrast .st-emotion-cache-1lcbcih {
background-color: #000000 !important;
color: #FFFFFF !important;
}
.high-contrast .st-emotion-cache-18ni7ap {
background-color: #000000 !important;
color: #00FF00 !important;
border: 2px solid #00FF00;
}
.high-contrast .st-emotion-cache-1v0mb8g {
color: #FFFF00 !important;
}
/* Large Text Mode */
.large-text .st-emotion-cache-1lcbcih {
font-size: 1.5em !important;
}
.large-text .st-emotion-cache-18ni7ap {
font-size: 1.5em !important;
}
.large-text .st-emotion-cache-1v0mb8g {
font-size: 1.5em !important;
}
/* Screen Reader Only Text */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
</style>
""", unsafe_allow_html=True)
# =============================================================================
# 4. MAIN APP LOGIC
# =============================================================================
def main():
inject_accessibility_css()
# --- Sidebar: Controls & Accessibility ---
with st.sidebar:
st.header("⚙️ Controls")
# Material Selection
selected_material = st.selectbox(
"Select Material Preset",
options=list(MATERIALS.keys()),
index=0
)
st.markdown("---")
st.header("📊 Parameters")
# Get current preset values
preset = MATERIALS[selected_material]
# Widgets
col1, col2 = st.columns(2)
with col1:
E = st.slider("Young's Modulus (MPa)", 100, 300000, int(preset["E"]), 100)
sigma_y = st.slider("Yield Strength (MPa)", 10, 1500, int(preset["yield"]), 10)
with col2:
sigma_uts = st.slider("UTS (MPa)", 10, 1500, int(preset["uts"]), 10)
epsilon_break = st.slider("Strain at Break", 0.001, 0.50, float(preset["strain"]), 0.001)
st.markdown("---")
st.header("♿ Accessibility")
# Accessibility Toggles
high_contrast = st.toggle("High Contrast Mode", value=False)
large_text = st.toggle("Large Text Mode", value=False)
screen_reader_mode = st.toggle("Verbose Descriptions", value=False)
# Apply CSS classes based on toggles
css_classes = []
if high_contrast: css_classes.append("high-contrast")
if large_text: css_classes.append("large-text")
if css_classes:
st.markdown(f'<div class="{" ".join(css_classes)}">', unsafe_allow_html=True)
# --- Main Content ---
# Title
st.title("Biomaterials Stress-Strain Explorer")
# Description Area
st.subheader(f"Material: {selected_material}")
st.info(preset["desc"])
# Screen Reader Description (Hidden visually but readable)
if screen_reader_mode:
st.markdown(f"""
<p class="sr-only">
Chart displays stress-strain curve for {selected_material}.
Young's Modulus is {E} MPa.
Yield Strength is {sigma_y} MPa.
Ultimate Tensile Strength is {sigma_uts} MPa.
Fracture occurs at strain {epsilon_break}.
</p>
""", unsafe_allow_html=True)
# --- Calculation ---
strain, stress = generate_stress_strain(E, sigma_y, sigma_uts, epsilon_break, preset["type"])
# --- Plotly Chart ---
fig = go.Figure()
# 1. The Stress-Strain Curve
fig.add_trace(go.Scatter(
x=strain,
y=stress,
mode='lines',
name='Stress-Strain',
line=dict(color=preset["color"], width=4),
hovertemplate="Strain: %{x:.3f}<br>Stress: %{y:.1f} MPa<extra></extra>"
))
# 2. Elastic Modulus Line (Dashed, up to yield)
epsilon_y = sigma_y / E
fig.add_trace(go.Scatter(
x=[0, epsilon_y],
y=[0, sigma_y],
mode='lines',
name='Elastic Modulus',
line=dict(color='red', width=2, dash='dash'),
hoverinfo="skip"
))
# 3. Annotations for Key Points
annotations = []
# Yield Point
annotations.append(dict(
x=epsilon_y, y=sigma_y,
xref="x", yref="y",
text=f"Yield Point<br>σ={sigma_y} MPa",
showarrow=True, arrowhead=2, arrowsize=1, arrowwidth=2, arrowcolor="orange",
bordercolor="orange", borderwidth=1, borderpad=4, bgcolor="white",
font=dict(family="Arial", size=12, color="orange")
))
# UTS Point
# Find index closest to UTS for accurate annotation placement
uts_idx = np.argmax(stress)
uts_strain = strain[uts_idx]
annotations.append(dict(
x=uts_strain, y=sigma_uts,
xref="x", yref="y",
text=f"UTS<br>σ={sigma_uts} MPa",
showarrow=True, arrowhead=2, arrowsize=1, arrowwidth=2, arrowcolor="purple",
bordercolor="purple", borderwidth=1, borderpad=4, bgcolor="white",
font=dict(family="Arial", size=12, color="purple")
))
# Fracture Point
annotations.append(dict(
x=epsilon_break, y=0,
xref="x", yref="y",
text=f"Fracture<br>ε={epsilon_break}",
showarrow=True, arrowhead=2, arrowsize=1, arrowwidth=2, arrowcolor="black",
bordercolor="black", borderwidth=1, borderpad=4, bgcolor="white",
font=dict(family="Arial", size=12, color="black")
))
fig.update_layout(
annotations=annotations,
xaxis_title="Strain (ε) - Dimensionless",
yaxis_title="Stress (σ) - MPa",
title="Stress-Strain Curve",
template="plotly_white" if not high_contrast else "plotly_dark",
hovermode="x unified",
legend=dict(x=0.01, y=0.99, bgcolor="rgba(255,255,255,0.8)"),
margin=dict(l=50, r=50, t=50, b=50)
)
# Accessibility: If high contrast, force dark background on plot
if high_contrast:
fig.update_layout(paper_bgcolor="black", plot_bgcolor="black")
fig.update_xaxes(gridcolor="gray", zerolinecolor="white", tickfont=dict(color="white"))
fig.update_yaxes(gridcolor="gray", zerolinecolor="white", tickfont=dict(color="white"))
st.plotly_chart(fig, use_container_width=True)
# --- Data Table (For Accessibility/Reference) ---
st.subheader("📋 Key Mechanical Properties")
df_props = pd.DataFrame({
"Property": ["Young's Modulus", "Yield Strength", "Ultimate Tensile Strength", "Strain at Break", "Yield Strain", "Toughness (Approx)"],
"Value": [
f"{E} MPa",
f"{sigma_y} MPa",
f"{sigma_uts} MPa",
f"{epsilon_break}",
f"{epsilon_y:.4f}",
f"{np.trapezoid(stress, strain):.1f} MJ/m³"
],
"Description": [
"Stiffness in elastic region",
"Onset of plastic deformation",
"Maximum stress before necking",
"Strain at fracture",
"Calculated as σ_y / E",
"Area under curve (Energy absorbed)"
]
})
st.dataframe(df_props, use_container_width=True, hide_index=True)
# Close the div if we opened it for CSS classes
if css_classes:
st.markdown("</div>", unsafe_allow_html=True)
if __name__ == "__main__":
main()