|
|
|
|
|
"""
|
|
|
ACCEPTIN - Telecom Site Quality Classification App
|
|
|
AI-powered telecom site inspection using ConvNeXt transfer learning
|
|
|
"""
|
|
|
|
|
|
import streamlit as st
|
|
|
import torch
|
|
|
import torch.nn.functional as F
|
|
|
from PIL import Image
|
|
|
import numpy as np
|
|
|
import plotly.graph_objects as go
|
|
|
import plotly.express as px
|
|
|
from plotly.subplots import make_subplots
|
|
|
import pandas as pd
|
|
|
import sys
|
|
|
import os
|
|
|
import time
|
|
|
from io import BytesIO
|
|
|
import base64
|
|
|
|
|
|
|
|
|
sys.path.append('utils')
|
|
|
from model_utils import load_model, TelecomClassifier
|
|
|
from data_utils import get_inference_transform, prepare_image_for_inference, check_data_directory
|
|
|
|
|
|
|
|
|
st.set_page_config(
|
|
|
page_title="π‘ ACCEPTIN - Telecom Site Inspector",
|
|
|
page_icon="π‘",
|
|
|
layout="wide",
|
|
|
initial_sidebar_state="expanded"
|
|
|
)
|
|
|
|
|
|
|
|
|
st.markdown("""
|
|
|
<style>
|
|
|
.main-header {
|
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
padding: 2rem;
|
|
|
border-radius: 15px;
|
|
|
text-align: center;
|
|
|
color: white;
|
|
|
margin-bottom: 2rem;
|
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
|
|
}
|
|
|
|
|
|
.main-header h1 {
|
|
|
font-size: 3rem;
|
|
|
margin: 0;
|
|
|
font-weight: bold;
|
|
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
|
|
}
|
|
|
|
|
|
.main-header p {
|
|
|
font-size: 1.2rem;
|
|
|
margin: 0.5rem 0 0 0;
|
|
|
opacity: 0.9;
|
|
|
}
|
|
|
|
|
|
.upload-section {
|
|
|
background: linear-gradient(135deg, #56ab2f 0%, #a8e6cf 100%);
|
|
|
color: white;
|
|
|
padding: 20px;
|
|
|
border-radius: 15px;
|
|
|
text-align: center;
|
|
|
margin-bottom: 20px;
|
|
|
box-shadow: 0 6px 20px rgba(86, 171, 47, 0.3);
|
|
|
}
|
|
|
|
|
|
.result-good {
|
|
|
background: linear-gradient(135deg, #28a745 0%, #20c997 100%);
|
|
|
color: white;
|
|
|
padding: 20px;
|
|
|
border-radius: 15px;
|
|
|
text-align: center;
|
|
|
margin: 20px 0;
|
|
|
box-shadow: 0 6px 20px rgba(40, 167, 69, 0.3);
|
|
|
}
|
|
|
|
|
|
.result-bad {
|
|
|
background: linear-gradient(135deg, #dc3545 0%, #e83e8c 100%);
|
|
|
color: white;
|
|
|
padding: 20px;
|
|
|
border-radius: 15px;
|
|
|
text-align: center;
|
|
|
margin: 20px 0;
|
|
|
box-shadow: 0 6px 20px rgba(220, 53, 69, 0.3);
|
|
|
}
|
|
|
|
|
|
.metric-card {
|
|
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
|
|
padding: 15px;
|
|
|
border-radius: 10px;
|
|
|
text-align: center;
|
|
|
color: white;
|
|
|
margin: 10px 0;
|
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
|
|
}
|
|
|
|
|
|
.info-card {
|
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
color: white;
|
|
|
padding: 20px;
|
|
|
border-radius: 15px;
|
|
|
margin: 15px 0;
|
|
|
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
|
|
}
|
|
|
|
|
|
.stButton > button {
|
|
|
background: linear-gradient(45deg, #667eea, #764ba2);
|
|
|
color: white;
|
|
|
border: none;
|
|
|
border-radius: 10px;
|
|
|
padding: 12px 24px;
|
|
|
font-weight: bold;
|
|
|
font-size: 1.1rem;
|
|
|
transition: all 0.3s ease;
|
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.2);
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
.stButton > button:hover {
|
|
|
transform: translateY(-2px);
|
|
|
box-shadow: 0 6px 20px rgba(0,0,0,0.3);
|
|
|
}
|
|
|
|
|
|
.sidebar .stSelectbox > div > div {
|
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
color: white;
|
|
|
}
|
|
|
</style>
|
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
@st.cache_resource
|
|
|
def load_telecom_model():
|
|
|
"""Load the trained telecom classification model"""
|
|
|
model_path = 'models/telecom_classifier.pth'
|
|
|
|
|
|
if not os.path.exists(model_path):
|
|
|
return None, "Model not found. Please train the model first."
|
|
|
|
|
|
try:
|
|
|
model, model_info = load_model(model_path, device='cpu')
|
|
|
return model, model_info
|
|
|
except Exception as e:
|
|
|
return None, f"Error loading model: {str(e)}"
|
|
|
|
|
|
def get_prediction(image, model, transform):
|
|
|
"""Get prediction from the model"""
|
|
|
try:
|
|
|
|
|
|
input_tensor = prepare_image_for_inference(image, transform)
|
|
|
|
|
|
|
|
|
with torch.no_grad():
|
|
|
model.eval()
|
|
|
outputs = model(input_tensor)
|
|
|
probabilities = F.softmax(outputs, dim=1)
|
|
|
confidence, predicted = torch.max(probabilities, 1)
|
|
|
|
|
|
|
|
|
predicted_class = predicted.item()
|
|
|
confidence_score = confidence.item()
|
|
|
all_probs = probabilities.squeeze().cpu().numpy()
|
|
|
|
|
|
return predicted_class, confidence_score, all_probs
|
|
|
|
|
|
except Exception as e:
|
|
|
st.error(f"Error during prediction: {str(e)}")
|
|
|
return None, None, None
|
|
|
|
|
|
def create_confidence_chart(probabilities, class_names):
|
|
|
"""Create confidence chart using Plotly"""
|
|
|
fig = go.Figure(data=[
|
|
|
go.Bar(
|
|
|
x=class_names,
|
|
|
y=probabilities,
|
|
|
marker_color=['#dc3545', '#28a745'],
|
|
|
text=[f'{p:.1%}' for p in probabilities],
|
|
|
textposition='auto',
|
|
|
)
|
|
|
])
|
|
|
|
|
|
fig.update_layout(
|
|
|
title="Classification Confidence",
|
|
|
xaxis_title="Site Quality",
|
|
|
yaxis_title="Confidence",
|
|
|
yaxis=dict(range=[0, 1]),
|
|
|
showlegend=False,
|
|
|
height=400,
|
|
|
template="plotly_white"
|
|
|
)
|
|
|
|
|
|
return fig
|
|
|
|
|
|
def create_quality_metrics_chart(predicted_class, confidence):
|
|
|
"""Create quality metrics visualization"""
|
|
|
if predicted_class == 1:
|
|
|
quality_score = confidence * 100
|
|
|
color = '#28a745'
|
|
|
status = 'ACCEPTED'
|
|
|
else:
|
|
|
quality_score = (1 - confidence) * 100
|
|
|
color = '#dc3545'
|
|
|
status = 'REJECTED'
|
|
|
|
|
|
fig = go.Figure(go.Indicator(
|
|
|
mode="gauge+number+delta",
|
|
|
value=quality_score,
|
|
|
domain={'x': [0, 1], 'y': [0, 1]},
|
|
|
title={'text': f"Quality Score<br><span style='font-size:0.8em;color:{color}'>{status}</span>"},
|
|
|
delta={'reference': 80},
|
|
|
gauge={
|
|
|
'axis': {'range': [None, 100]},
|
|
|
'bar': {'color': color},
|
|
|
'steps': [
|
|
|
{'range': [0, 50], 'color': "lightgray"},
|
|
|
{'range': [50, 80], 'color': "yellow"},
|
|
|
{'range': [80, 100], 'color': "lightgreen"}
|
|
|
],
|
|
|
'threshold': {
|
|
|
'line': {'color': "red", 'width': 4},
|
|
|
'thickness': 0.75,
|
|
|
'value': 90
|
|
|
}
|
|
|
}
|
|
|
))
|
|
|
|
|
|
fig.update_layout(height=400)
|
|
|
return fig
|
|
|
|
|
|
def analyze_site_quality(predicted_class, confidence):
|
|
|
"""Analyze site quality and provide detailed feedback"""
|
|
|
class_names = ['Bad', 'Good']
|
|
|
predicted_label = class_names[predicted_class]
|
|
|
|
|
|
if predicted_class == 1:
|
|
|
analysis = {
|
|
|
'status': 'ACCEPTED β
',
|
|
|
'color': 'result-good',
|
|
|
'icon': 'β
',
|
|
|
'message': 'Site installation meets quality standards',
|
|
|
'details': [
|
|
|
'β
Cable assembly appears properly organized',
|
|
|
'β
Equipment installation looks correct',
|
|
|
'β
Overall site organization is acceptable',
|
|
|
'β
No obvious safety violations detected'
|
|
|
],
|
|
|
'recommendations': [
|
|
|
'π Verify all labels are clearly readable',
|
|
|
'π§ Double-check all card installations',
|
|
|
'π Complete final inspection checklist',
|
|
|
'πΈ Document final installation state'
|
|
|
]
|
|
|
}
|
|
|
else:
|
|
|
analysis = {
|
|
|
'status': 'REJECTED β',
|
|
|
'color': 'result-bad',
|
|
|
'icon': 'β',
|
|
|
'message': 'Site installation requires attention',
|
|
|
'details': [
|
|
|
'β Cable organization may need improvement',
|
|
|
'β Equipment installation issues detected',
|
|
|
'β Site organization below standards',
|
|
|
'β Potential safety or quality concerns'
|
|
|
],
|
|
|
'recommendations': [
|
|
|
'π§ Reorganize cable routing and bundling',
|
|
|
'π Check all card installations and seating',
|
|
|
'π·οΈ Verify all labels are present and readable',
|
|
|
'β οΈ Address any safety violations',
|
|
|
'π Complete corrective actions before acceptance'
|
|
|
]
|
|
|
}
|
|
|
|
|
|
analysis['confidence'] = confidence
|
|
|
analysis['predicted_label'] = predicted_label
|
|
|
|
|
|
return analysis
|
|
|
|
|
|
def display_inspection_checklist():
|
|
|
"""Display telecom site inspection checklist"""
|
|
|
st.markdown("""
|
|
|
<div class="info-card">
|
|
|
<h3>π Telecom Site Inspection Checklist</h3>
|
|
|
<p>Use this checklist to ensure comprehensive site evaluation:</p>
|
|
|
</div>
|
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
checklist_items = {
|
|
|
"Cable Assembly": [
|
|
|
"Cables properly routed and bundled",
|
|
|
"No loose or hanging cables",
|
|
|
"Proper cable management systems used",
|
|
|
"Cable routing follows standards"
|
|
|
],
|
|
|
"Card Installation": [
|
|
|
"All required cards present",
|
|
|
"Cards properly seated and secured",
|
|
|
"No missing or damaged cards",
|
|
|
"Card configurations correct"
|
|
|
],
|
|
|
"Labeling": [
|
|
|
"All equipment properly labeled",
|
|
|
"Labels clearly readable",
|
|
|
"Label placement follows standards",
|
|
|
"No missing identification tags"
|
|
|
],
|
|
|
"Safety & Organization": [
|
|
|
"Safety covers properly installed",
|
|
|
"Grounding connections secure",
|
|
|
"Warning signs present where required",
|
|
|
"Overall rack organization acceptable"
|
|
|
]
|
|
|
}
|
|
|
|
|
|
for category, items in checklist_items.items():
|
|
|
st.subheader(f"π {category}")
|
|
|
for item in items:
|
|
|
st.write(f"β’ {item}")
|
|
|
|
|
|
def main():
|
|
|
"""Main application function"""
|
|
|
|
|
|
st.markdown("""
|
|
|
<div class="main-header">
|
|
|
<h1>π‘ ACCEPTIN</h1>
|
|
|
<p>AI-Powered Telecom Site Quality Inspector</p>
|
|
|
</div>
|
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
st.sidebar.title("π οΈ Controls")
|
|
|
|
|
|
|
|
|
model, model_info = load_telecom_model()
|
|
|
|
|
|
if model is None:
|
|
|
st.error(f"β {model_info}")
|
|
|
st.info("Please train the model first using: `python train_telecom.py`")
|
|
|
return
|
|
|
|
|
|
|
|
|
st.sidebar.success("β
Model loaded successfully")
|
|
|
if isinstance(model_info, dict):
|
|
|
st.sidebar.write(f"**Accuracy:** {model_info.get('best_acc', 'Unknown')}")
|
|
|
st.sidebar.write(f"**Architecture:** ConvNeXt Large")
|
|
|
st.sidebar.write(f"**Task:** Binary Classification")
|
|
|
|
|
|
|
|
|
col1, col2 = st.columns([1, 1])
|
|
|
|
|
|
with col1:
|
|
|
st.markdown("""
|
|
|
<div class="upload-section">
|
|
|
<h3>π€ Upload or Capture Telecom Site Image</h3>
|
|
|
<p>Upload an image or take a photo of the telecom site for quality inspection</p>
|
|
|
</div>
|
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
|
|
|
input_method = st.selectbox(
|
|
|
"Choose how to provide the telecom site image:",
|
|
|
["π Upload from device", "π· Take photo with camera"],
|
|
|
help="Select whether to upload an existing image or take a new photo"
|
|
|
)
|
|
|
|
|
|
image = None
|
|
|
if input_method == "π Upload from device":
|
|
|
uploaded_file = st.file_uploader(
|
|
|
"Choose an image...",
|
|
|
type=['jpg', 'jpeg', 'png', 'bmp', 'tiff'],
|
|
|
help="Upload a clear image of the telecom site installation"
|
|
|
)
|
|
|
if uploaded_file is not None:
|
|
|
image = Image.open(uploaded_file)
|
|
|
elif input_method == "π· Take photo with camera":
|
|
|
camera_photo = st.camera_input("Take a photo of the telecom site")
|
|
|
if camera_photo is not None:
|
|
|
image = Image.open(camera_photo)
|
|
|
|
|
|
if image is not None:
|
|
|
|
|
|
st.image(image, caption="Telecom Site Image", use_column_width=True)
|
|
|
with st.spinner("Analyzing site quality..."):
|
|
|
|
|
|
transform = get_inference_transform()
|
|
|
predicted_class, confidence, probabilities = get_prediction(
|
|
|
image, model, transform
|
|
|
)
|
|
|
if predicted_class is not None:
|
|
|
|
|
|
if max(probabilities) <= 0.8:
|
|
|
st.warning("β οΈ This image does not appear to be a telecom site. Please upload a valid telecom site photo.")
|
|
|
st.session_state.prediction_results = None
|
|
|
else:
|
|
|
|
|
|
st.session_state.prediction_results = {
|
|
|
'predicted_class': predicted_class,
|
|
|
'confidence': confidence,
|
|
|
'probabilities': probabilities,
|
|
|
'analysis': analyze_site_quality(predicted_class, confidence)
|
|
|
}
|
|
|
st.success("β
Analysis complete!")
|
|
|
|
|
|
with col2:
|
|
|
if hasattr(st.session_state, 'prediction_results'):
|
|
|
results = st.session_state.prediction_results
|
|
|
analysis = results['analysis']
|
|
|
|
|
|
st.markdown(f"""
|
|
|
<div class="{analysis['color']}">
|
|
|
<h2>{analysis['icon']} {analysis['status']}</h2>
|
|
|
<h3>{analysis['message']}</h3>
|
|
|
<p><strong>Confidence:</strong> {analysis['confidence']:.1%}</p>
|
|
|
</div>
|
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
st.plotly_chart(
|
|
|
create_confidence_chart(
|
|
|
results['probabilities'],
|
|
|
['Bad', 'Good']
|
|
|
),
|
|
|
use_container_width=True
|
|
|
)
|
|
|
|
|
|
st.plotly_chart(
|
|
|
create_quality_metrics_chart(
|
|
|
results['predicted_class'],
|
|
|
results['confidence']
|
|
|
),
|
|
|
use_container_width=True
|
|
|
)
|
|
|
|
|
|
|
|
|
if hasattr(st.session_state, 'prediction_results'):
|
|
|
st.markdown("---")
|
|
|
st.header("π Detailed Analysis")
|
|
|
|
|
|
analysis = st.session_state.prediction_results['analysis']
|
|
|
|
|
|
col1, col2 = st.columns([1, 1])
|
|
|
|
|
|
with col1:
|
|
|
st.subheader("π Quality Assessment")
|
|
|
for detail in analysis['details']:
|
|
|
st.write(detail)
|
|
|
|
|
|
with col2:
|
|
|
st.subheader("π‘ Recommendations")
|
|
|
for recommendation in analysis['recommendations']:
|
|
|
st.write(recommendation)
|
|
|
|
|
|
|
|
|
st.markdown("---")
|
|
|
tab1, tab2, tab3 = st.tabs(["π Inspection Checklist", "π Training Data", "βΉοΈ About"])
|
|
|
|
|
|
with tab1:
|
|
|
display_inspection_checklist()
|
|
|
|
|
|
with tab2:
|
|
|
st.header("π Training Data Overview")
|
|
|
|
|
|
|
|
|
data_counts = check_data_directory('data')
|
|
|
|
|
|
if data_counts:
|
|
|
|
|
|
data_list = []
|
|
|
for split, counts in data_counts.items():
|
|
|
for class_name, count in counts.items():
|
|
|
data_list.append({
|
|
|
'Split': split.title(),
|
|
|
'Class': class_name.title(),
|
|
|
'Count': count
|
|
|
})
|
|
|
|
|
|
df = pd.DataFrame(data_list)
|
|
|
|
|
|
|
|
|
fig = px.bar(
|
|
|
df,
|
|
|
x='Class',
|
|
|
y='Count',
|
|
|
color='Split',
|
|
|
title='Training Data Distribution',
|
|
|
barmode='group'
|
|
|
)
|
|
|
st.plotly_chart(fig, use_container_width=True)
|
|
|
|
|
|
|
|
|
st.subheader("π Data Summary")
|
|
|
st.dataframe(df.pivot(index='Class', columns='Split', values='Count'))
|
|
|
else:
|
|
|
st.info("No training data found. Please prepare your dataset in the `data/` directory.")
|
|
|
|
|
|
with tab3:
|
|
|
st.header("βΉοΈ About ACCEPTIN")
|
|
|
st.markdown("""
|
|
|
**ACCEPTIN** is an AI-powered telecom site quality inspection system that uses computer vision
|
|
|
to automatically classify telecom installations as "good" or "bad" based on visual criteria.
|
|
|
|
|
|
### π― Key Features:
|
|
|
- **Transfer Learning**: Leverages pre-trained ConvNeXt model (197M parameters)
|
|
|
- **Binary Classification**: Classifies sites as good/bad with confidence scores
|
|
|
- **Quality Assessment**: Evaluates cable assembly, card installation, and labeling
|
|
|
- **Real-time Analysis**: Instant feedback on site quality
|
|
|
|
|
|
### π§ Technical Details:
|
|
|
- **Model**: ConvNeXt Large with custom classification head
|
|
|
- **Training**: Transfer learning from food detection model
|
|
|
- **Input**: 224x224 RGB images
|
|
|
- **Output**: Binary classification with confidence scores
|
|
|
|
|
|
### π Quality Criteria:
|
|
|
- Cable assembly and routing
|
|
|
- Card installation and labeling
|
|
|
- General organization and safety
|
|
|
- Compliance with telecom standards
|
|
|
|
|
|
### π Usage:
|
|
|
1. Upload telecom site image
|
|
|
2. Click "Analyze Site Quality"
|
|
|
3. Review results and recommendations
|
|
|
4. Use inspection checklist for verification
|
|
|
""")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
main() |