samuelolubukun's picture
Update app.py
9db4904 verified
"""
Poultry Disease Classification System
Professional diagnostic tool using EfficientNetV2
"""
import gradio as gr
import numpy as np
import tensorflow as tf
from PIL import Image
import keras
# Load model
MODEL_PATH = "poultry_disease_classification_efficientnet_model.keras"
print("Loading model...")
try:
model = keras.models.load_model(MODEL_PATH)
print("Model loaded successfully")
except Exception as e:
print(f"Error loading model: {e}")
raise
# Class definitions
CLASS_NAMES = ['Coccidiosis', 'Healthy', 'Newcastle Disease', 'Salmonella']
# Disease information - clinical and concise
DISEASE_INFO = {
'Coccidiosis': {
'description': 'Parasitic infection of the intestinal tract caused by coccidia protozoa.',
'symptoms': [
'Bloody or watery droppings',
'Lethargy and weakness',
'Reduced appetite',
'Pale comb and wattles'
],
'treatment': [
'Anticoccidial medication (Amprolium, Sulfonamides)',
'Electrolyte supplementation',
'Isolation of affected birds',
'Veterinary consultation required'
],
'prevention': [
'Maintain dry, clean housing',
'Regular disinfection protocols',
'Adequate ventilation'
]
},
'Healthy': {
'description': 'No signs of disease detected. Bird appears to be in good health.',
'symptoms': [
'Alert and active behavior',
'Normal appetite and droppings',
'Clear eyes',
'Well-groomed feathers'
],
'treatment': [
'Continue standard care protocols',
'Monitor regularly',
'Maintain balanced nutrition'
],
'prevention': [
'Balanced feed formulation',
'Clean environment',
'Vaccination schedule'
]
},
'Newcastle Disease': {
'description': 'Highly contagious viral infection affecting respiratory, nervous, and digestive systems.',
'symptoms': [
'Respiratory distress',
'Neurological signs (tremors, paralysis)',
'Green watery diarrhea',
'Reduced egg production',
'Head and neck swelling'
],
'treatment': [
'No specific antiviral treatment available',
'Supportive care and isolation',
'Vaccination of unaffected birds',
'Strict biosecurity enforcement'
],
'prevention': [
'Vaccination program (primary prevention)',
'Biosecurity protocols',
'Quarantine procedures for new birds'
]
},
'Salmonella': {
'description': 'Bacterial infection with zoonotic potential. Affects gastrointestinal system.',
'symptoms': [
'Greenish diarrhea',
'Decreased appetite',
'Dehydration',
'Lethargy',
'Poor growth in young birds'
],
'treatment': [
'Antibiotic therapy (veterinary prescribed)',
'Fluid and electrolyte therapy',
'Probiotic supplementation',
'Enhanced sanitation measures'
],
'prevention': [
'Biosecurity compliance',
'Water and feed quality control',
'Proper waste management'
]
}
}
def preprocess_image(image):
"""Preprocess image for model input"""
if isinstance(image, np.ndarray):
image = Image.fromarray(image.astype('uint8'))
image = image.resize((360, 360))
img_array = np.array(image)
img_array = img_array.astype('float32') / 255.0
img_array = np.expand_dims(img_array, axis=0)
return img_array
def predict_disease(image):
"""Generate disease prediction and analysis"""
if image is None:
return None, "<div class='error-msg'>Please upload an image to begin analysis</div>"
try:
processed_image = preprocess_image(image)
predictions = model.predict(processed_image, verbose=0)[0]
predicted_idx = np.argmax(predictions)
predicted_class = CLASS_NAMES[predicted_idx]
confidence = predictions[predicted_idx] * 100
# Strict threshold - only proceed if confidence >= 99%
CONFIDENCE_THRESHOLD = 98.7
if confidence < CONFIDENCE_THRESHOLD:
# Simple rejection message - NO analysis details
error_html = """
<div class='error-container'>
<strong>⚠️ Invalid Image Detected</strong><br><br>
The uploaded image does not appear to be a poultry bird or is not suitable for analysis.<br><br>
<strong>Please upload:</strong>
<ul style='margin: 0.5rem 0; padding-left: 1.5rem; line-height: 1.6;'>
<li>A clear photograph of a chicken or poultry bird</li>
<li>Well-lit image with good focus</li>
<li>Bird clearly visible in the frame</li>
<li>No other animals or unrelated objects</li>
</ul>
<p style='margin-top: 1rem; font-size: 0.85rem; color: #6c757d;'>
This system is specifically trained to classify poultry diseases and requires
high-confidence detections to ensure diagnostic accuracy.
</p>
</div>
"""
return None, error_html
# Proceed with normal analysis for high-confidence predictions (≥99%)
prediction_dict = {CLASS_NAMES[i]: float(predictions[i]) for i in range(len(CLASS_NAMES))}
disease_info = DISEASE_INFO[predicted_class]
# Confidence assessment (for valid predictions only)
if confidence >= 99.5:
conf_level = "Very High"
conf_class = "conf-high"
elif confidence >= 99.0:
conf_level = "High"
conf_class = "conf-high"
else:
# This shouldn't happen, but keeping as fallback
conf_level = "Moderate"
conf_class = "conf-moderate"
# Build clean, professional result display
result_html = f"""
<div class='result-container'>
<div class='result-header'>
<div class='diagnosis'>
<span class='label'>Diagnosis</span>
<span class='value'>{predicted_class}</span>
</div>
<div class='confidence {conf_class}'>
<span class='label'>Confidence</span>
<span class='value'>{confidence:.2f}%</span>
<span class='level'>{conf_level}</span>
</div>
</div>
<div class='info-section'>
<h3>Clinical Overview</h3>
<p>{disease_info['description']}</p>
</div>
<div class='info-section'>
<h3>Key Symptoms</h3>
<ul>
{''.join([f'<li>{symptom}</li>' for symptom in disease_info['symptoms']])}
</ul>
</div>
<div class='info-section'>
<h3>Treatment Protocol</h3>
<ul>
{''.join([f'<li>{treatment}</li>' for treatment in disease_info['treatment']])}
</ul>
</div>
<div class='info-section'>
<h3>Prevention</h3>
<ul>
{''.join([f'<li>{prevention}</li>' for prevention in disease_info['prevention']])}
</ul>
</div>
<div class='disclaimer'>
<strong>Professional Consultation Required</strong><br>
This AI diagnostic tool provides preliminary screening only.
Consult a licensed veterinarian for definitive diagnosis and treatment.
</div>
</div>
"""
return prediction_dict, result_html
except Exception as e:
error_html = f"""
<div class='error-container'>
<strong>Analysis Error</strong><br>
{str(e)}<br><br>
Please verify the image and try again.
</div>
"""
return None, error_html
# Professional CSS styling
custom_css = """
* {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
}
.gradio-container {
max-width: 1200px !important;
margin: 0 auto !important;
}
.header {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
color: white !important;
padding: 2.5rem 2rem;
border-radius: 8px;
margin-bottom: 2rem;
}
.header h1 {
font-size: 1.75rem;
font-weight: 600;
margin: 0 0 0.5rem 0;
letter-spacing: -0.02em;
color: white !important;
}
.header p {
font-size: 0.95rem;
margin: 0;
opacity: 0.95;
font-weight: 400;
color: white !important;
}
.classes-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
margin: 2rem 0;
padding: 0 0.5rem;
}
.class-card {
background: #f8f9fa;
padding: 1rem;
border-radius: 6px;
text-align: center;
border: 1px solid #e9ecef;
}
.class-card h4 {
font-size: 0.9rem;
font-weight: 600;
color: #212529;
margin: 0 0 0.25rem 0;
}
.class-card p {
font-size: 0.8rem;
color: #6c757d;
margin: 0;
}
/* Dark mode support */
@media (prefers-color-scheme: dark) {
.class-card {
background: #2d3748;
border: 1px solid #4a5568;
}
.class-card h4 {
color: #e2e8f0 !important;
}
.class-card p {
color: #cbd5e0 !important;
}
}
/* Hide the label/title above upload section */
.upload-section label.block {
display: none !important;
}
.upload-section .gr-block {
border: none !important;
padding: 0 !important;
}
.upload-section {
background: transparent;
padding: 0;
border: none;
}
.section-title {
font-size: 1rem;
font-weight: 600;
color: #212529;
margin: 0 0 1rem 0;
background: white;
padding: 1rem;
border-radius: 8px 8px 0 0;
border: 1px solid #e9ecef;
border-bottom: none;
}
/* Dark mode for section title */
@media (prefers-color-scheme: dark) {
.section-title {
background: #1a202c;
color: #e2e8f0 !important;
border-color: #4a5568;
}
}
.image-tips {
background: #f8f9fa;
padding: 1rem;
border-radius: 6px;
margin-top: 1rem;
border-left: 3px solid #2a5298;
}
.image-tips h4 {
font-size: 0.85rem;
font-weight: 600;
color: #212529;
margin: 0 0 0.5rem 0;
}
.image-tips ul {
margin: 0;
padding-left: 1.25rem;
font-size: 0.85rem;
color: #495057;
line-height: 1.6;
}
/* Dark mode for image tips */
@media (prefers-color-scheme: dark) {
.image-tips {
background: #2d3748;
border-left-color: #4299e1;
}
.image-tips h4 {
color: #e2e8f0 !important;
}
.image-tips ul {
color: #cbd5e0 !important;
}
}
.result-container {
background: #ffffff;
border-radius: 8px;
overflow: hidden;
border: 1px solid #e9ecef;
}
/* Dark mode for results */
@media (prefers-color-scheme: dark) {
.result-container {
background: #1a202c;
border-color: #4a5568;
}
}
.result-header {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
padding: 1.5rem;
background: #f8f9fa;
border-bottom: 1px solid #e9ecef;
}
/* Dark mode for result header */
@media (prefers-color-scheme: dark) {
.result-header {
background: #2d3748;
border-bottom-color: #4a5568;
}
}
.diagnosis, .confidence {
display: flex;
flex-direction: column;
}
.label {
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: #6c757d;
font-weight: 600;
margin-bottom: 0.25rem;
}
.value {
font-size: 1.5rem;
font-weight: 600;
color: #212529;
}
.level {
font-size: 0.85rem;
color: #6c757d;
margin-top: 0.25rem;
}
/* Dark mode for labels and values */
@media (prefers-color-scheme: dark) {
.label {
color: #a0aec0 !important;
}
.value {
color: #e2e8f0 !important;
}
.level {
color: #a0aec0 !important;
}
}
.conf-high .value { color: #28a745 !important; }
.conf-moderate .value { color: #ffc107 !important; }
.conf-low .value { color: #dc3545 !important; }
.info-section {
padding: 1.5rem;
border-bottom: 1px solid #e9ecef;
}
.info-section:last-of-type {
border-bottom: none;
}
.info-section h3 {
font-size: 0.95rem;
font-weight: 600;
color: #212529;
margin: 0 0 0.75rem 0;
}
.info-section p {
font-size: 0.9rem;
color: #495057;
line-height: 1.6;
margin: 0;
}
.info-section ul {
margin: 0;
padding-left: 1.25rem;
font-size: 0.9rem;
color: #495057;
line-height: 1.7;
}
.info-section li {
margin-bottom: 0.5rem;
}
/* Dark mode for info sections */
@media (prefers-color-scheme: dark) {
.info-section {
border-bottom-color: #4a5568;
}
.info-section h3 {
color: #e2e8f0 !important;
}
.info-section p {
color: #cbd5e0 !important;
}
.info-section ul {
color: #cbd5e0 !important;
}
}
.disclaimer {
background: #fff3cd;
padding: 1rem;
font-size: 0.85rem;
color: #856404;
line-height: 1.5;
margin-top: 1rem;
border-radius: 6px;
}
/* Dark mode for disclaimer */
@media (prefers-color-scheme: dark) {
.disclaimer {
background: #744210;
color: #fef3c7 !important;
}
}
.error-msg, .error-container {
background: #f8d7da;
color: #721c24;
padding: 1rem;
border-radius: 6px;
font-size: 0.9rem;
}
/* Dark mode for errors */
@media (prefers-color-scheme: dark) {
.error-msg, .error-container {
background: #742a2a;
color: #feb2b2 !important;
}
}
.model-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
margin: 2rem 0;
padding: 0 0.5rem;
}
.stat-card {
background: #f8f9fa;
padding: 1.25rem;
border-radius: 6px;
text-align: center;
border: 1px solid #e9ecef;
}
.stat-value {
font-size: 1.75rem;
font-weight: 600;
color: #2a5298;
display: block;
}
.stat-label {
font-size: 0.85rem;
color: #6c757d;
margin-top: 0.25rem;
}
/* Dark mode for stats */
@media (prefers-color-scheme: dark) {
.stat-card {
background: #2d3748;
border-color: #4a5568;
}
.stat-value {
color: #4299e1 !important;
}
.stat-label {
color: #cbd5e0 !important;
}
}
.footer-note {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 8px;
margin-top: 2rem;
text-align: center;
border: 1px solid #e9ecef;
}
.footer-note h4 {
font-size: 0.9rem;
font-weight: 600;
color: #212529;
margin: 0 0 0.5rem 0;
}
.footer-note p {
font-size: 0.85rem;
color: #6c757d;
line-height: 1.6;
margin: 0 0 1rem 0;
}
.tech-stack {
font-size: 0.8rem;
color: #868e96;
margin-top: 1rem;
}
/* Dark mode for footer */
@media (prefers-color-scheme: dark) {
.footer-note {
background: #2d3748;
border-color: #4a5568;
}
.footer-note h4 {
color: #e2e8f0 !important;
}
.footer-note p {
color: #cbd5e0 !important;
}
.tech-stack {
color: #a0aec0 !important;
}
}
button {
font-weight: 500 !important;
border-radius: 6px !important;
}
.primary {
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%) !important;
border: none !important;
}
footer {
display: none !important;
}
/* Mobile Responsiveness */
@media (max-width: 768px) {
.header h1 {
font-size: 1.25rem;
}
.header p {
font-size: 0.85rem;
}
.classes-grid {
grid-template-columns: repeat(2, 1fr);
gap: 0.75rem;
}
.model-stats {
grid-template-columns: repeat(2, 1fr);
gap: 0.75rem;
}
.result-header {
grid-template-columns: 1fr;
gap: 1rem;
}
.value {
font-size: 1.25rem;
}
.stat-value {
font-size: 1.5rem;
}
.gradio-container {
padding: 0.5rem;
}
.header {
padding: 1.5rem 1rem;
}
}
@media (max-width: 480px) {
.classes-grid {
grid-template-columns: 1fr;
}
.model-stats {
grid-template-columns: 1fr;
}
.header h1 {
font-size: 1.1rem;
}
.header p {
font-size: 0.8rem;
}
}
"""
# Build interface
with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="Poultry Disease Classifier") as demo:
# Header
gr.HTML("""
<div class='header'>
<h1>Poultry Disease Classification System</h1>
<p>AI-powered diagnostic screening using EfficientNetV2 deep learning architecture</p>
</div>
""")
# Disease classes
gr.HTML("""
<div class='classes-grid'>
<div class='class-card'>
<h4>Coccidiosis</h4>
<p>Parasitic</p>
</div>
<div class='class-card'>
<h4>Healthy</h4>
<p>No disease</p>
</div>
<div class='class-card'>
<h4>Newcastle</h4>
<p>Viral</p>
</div>
<div class='class-card'>
<h4>Salmonella</h4>
<p>Bacterial</p>
</div>
</div>
""")
# Main interface
with gr.Row():
with gr.Column(scale=1):
input_image = gr.Image(
type="pil",
label="Upload Image",
height=400
)
with gr.Row():
clear_btn = gr.ClearButton(
components=[input_image],
value="Clear",
size="lg"
)
predict_btn = gr.Button(
"Analyze",
variant="primary",
size="lg",
elem_classes="primary"
)
gr.HTML("""
<div class='image-tips'>
<h4>Image Guidelines</h4>
<ul>
<li>Use clear, well-lit images</li>
<li>Ensure bird is clearly visible</li>
<li>Avoid blurry or dark photos</li>
<li>Close-up shots preferred</li>
</ul>
</div>
""")
with gr.Column(scale=1):
output_label = gr.Label(
label="Analysis Results - Classification Confidence",
num_top_classes=4
)
output_text = gr.HTML()
# Model information
gr.HTML("""
<div class='model-stats'>
<div class='stat-card'>
<span class='stat-value'>97%</span>
<span class='stat-label'>Accuracy</span>
</div>
<div class='stat-card'>
<span class='stat-value'>5.9M</span>
<span class='stat-label'>Parameters</span>
</div>
<div class='stat-card'>
<span class='stat-value'>360px</span>
<span class='stat-label'>Input Size</span>
</div>
<div class='stat-card'>
<span class='stat-value'>4</span>
<span class='stat-label'>Classes</span>
</div>
</div>
<div class='footer-note'>
<h4>Clinical Notice</h4>
<p>This diagnostic tool is intended for preliminary screening and educational purposes only.
Results should not replace professional veterinary examination and diagnosis.
Always consult a licensed veterinarian for accurate diagnosis and treatment recommendations.</p>
<div class='tech-stack'>
TensorFlow • Keras • EfficientNetV2-B0 • Gradio | Updated February 2026
</div>
</div>
""")
# Connect prediction
predict_btn.click(
fn=predict_disease,
inputs=input_image,
outputs=[output_label, output_text]
)
if __name__ == "__main__":
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=True,
show_error=True
)