Commit
Β·
fbbdeab
1
Parent(s):
19e5ec3
Deploy Fish Disease Detection AI
Browse files- README.md +235 -7
- app.py +270 -0
- backend/__init__.py +28 -0
- backend/__pycache__/__init__.cpython-313.pyc +0 -0
- backend/__pycache__/config.cpython-313.pyc +0 -0
- backend/__pycache__/gradcam.cpython-313.pyc +0 -0
- backend/__pycache__/model_loader.cpython-313.pyc +0 -0
- backend/__pycache__/predictor.cpython-313.pyc +0 -0
- backend/__pycache__/treatment.cpython-313.pyc +0 -0
- backend/__pycache__/validator.cpython-313.pyc +0 -0
- backend/config.py +60 -0
- backend/gradcam.py +143 -0
- backend/model_loader.py +95 -0
- backend/predictor.py +194 -0
- backend/treatment.py +177 -0
- backend/validator.py +133 -0
- models/vgg_resnet/results/vgg_resnet/vgg16_best.pth +3 -0
- requirements.txt +8 -0
README.md
CHANGED
|
@@ -1,14 +1,242 @@
|
|
| 1 |
---
|
| 2 |
-
title: Fish Disease Detection
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 5.
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
license: mit
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Fish Disease Detection AI
|
| 3 |
+
emoji: π
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.7.1
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
license: mit
|
| 11 |
+
tags:
|
| 12 |
+
- computer-vision
|
| 13 |
+
- deep-learning
|
| 14 |
+
- vgg16
|
| 15 |
+
- fish-disease
|
| 16 |
+
- grad-cam
|
| 17 |
+
- explainable-ai
|
| 18 |
+
- medical-imaging
|
| 19 |
---
|
| 20 |
|
| 21 |
+
# π Fish Disease Detection AI
|
| 22 |
+
|
| 23 |
+
[](https://www.python.org/downloads/)
|
| 24 |
+
[](https://pytorch.org/)
|
| 25 |
+
[](https://gradio.app/)
|
| 26 |
+
[](LICENSE)
|
| 27 |
+
|
| 28 |
+
**AI-powered fish disease detection system combining VGG16 CNN, Grad-CAM explainability, and Gemini AI for comprehensive diagnosis and treatment recommendations.**
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## π― Key Features
|
| 33 |
+
|
| 34 |
+
### π **High Accuracy**
|
| 35 |
+
- **98.65% test accuracy** on 8 fish disease classes
|
| 36 |
+
- Trained on **5,000+ annotated images**
|
| 37 |
+
- Robust to various image conditions
|
| 38 |
+
|
| 39 |
+
### π¬ **Explainable AI**
|
| 40 |
+
- **Grad-CAM heatmap visualization** shows exactly where the model is looking
|
| 41 |
+
- Highlights disease-relevant areas (lesions, discoloration, abnormalities)
|
| 42 |
+
- Builds trust through transparency
|
| 43 |
+
|
| 44 |
+
### π€ **AI-Powered Treatment**
|
| 45 |
+
- **Google Gemini 2.0** generates disease-specific treatment protocols
|
| 46 |
+
- Immediate actions, medication recommendations, and preventive measures
|
| 47 |
+
- Expected recovery rates and timelines
|
| 48 |
+
|
| 49 |
+
### β‘ **Real-Time Performance**
|
| 50 |
+
- **~2-3 second inference** on GPU
|
| 51 |
+
- Supports batch processing
|
| 52 |
+
- Web-based interface accessible anywhere
|
| 53 |
+
|
| 54 |
+
---
|
| 55 |
+
|
| 56 |
+
## π¦ Detected Diseases
|
| 57 |
+
|
| 58 |
+
| Disease | Description | Severity |
|
| 59 |
+
|---------|-------------|----------|
|
| 60 |
+
| **Aeromoniasis** | Bacterial infection causing hemorrhaging | High |
|
| 61 |
+
| **Bacterial Gill Disease** | Respiratory issues, gill damage | High |
|
| 62 |
+
| **Bacterial Red Disease** | External lesions and ulcers | Medium |
|
| 63 |
+
| **EUS** | Epizootic Ulcerative Syndrome | Critical |
|
| 64 |
+
| **Healthy Fish** | No disease detected | None |
|
| 65 |
+
| **Parasitic Diseases** | External/internal parasites | Medium |
|
| 66 |
+
| **Saprolegniasis Fungal** | Fungal infection (cotton-like growth) | Medium |
|
| 67 |
+
| **Viral White Tail** | Viral infection affecting tail | High |
|
| 68 |
+
|
| 69 |
+
---
|
| 70 |
+
|
| 71 |
+
## π How to Use
|
| 72 |
+
|
| 73 |
+
### 1οΈβ£ **Upload Image**
|
| 74 |
+
Upload a clear, well-lit photo of your fish (JPG/PNG, max 10MB)
|
| 75 |
+
|
| 76 |
+
### 2οΈβ£ **Analyze**
|
| 77 |
+
Click "Analyze Fish" button for instant diagnosis
|
| 78 |
+
|
| 79 |
+
### 3οΈβ£ **Review Results**
|
| 80 |
+
- **Disease prediction** with confidence score
|
| 81 |
+
- **Probability breakdown** for all 8 diseases
|
| 82 |
+
- **Grad-CAM heatmap** showing model focus areas
|
| 83 |
+
- **AI treatment recommendations** with detailed protocols
|
| 84 |
+
|
| 85 |
+
---
|
| 86 |
+
|
| 87 |
+
## π Model Architecture
|
| 88 |
+
|
| 89 |
+
Input Image (224Γ224 RGB)
|
| 90 |
+
β
|
| 91 |
+
VGG16 Backbone (Pretrained on ImageNet)
|
| 92 |
+
β
|
| 93 |
+
Feature Extraction (4096-dim)
|
| 94 |
+
β
|
| 95 |
+
Custom Classification Head
|
| 96 |
+
β
|
| 97 |
+
8-Class Softmax Output
|
| 98 |
+
β
|
| 99 |
+
Grad-CAM Activation Mapping
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
**Technical Specifications:**
|
| 103 |
+
- **Base Model:** VGG16 (transfer learning)
|
| 104 |
+
- **Input Size:** 224Γ224 pixels
|
| 105 |
+
- **Normalization:** ImageNet mean/std
|
| 106 |
+
- **Framework:** PyTorch 2.5.1
|
| 107 |
+
- **Device:** CUDA/CPU compatible
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
## π Performance Metrics
|
| 112 |
+
|
| 113 |
+
| Metric | Score |
|
| 114 |
+
|--------|-------|
|
| 115 |
+
| **Test Accuracy** | 98.65% |
|
| 116 |
+
| **Precision (avg)** | 98.2% |
|
| 117 |
+
| **Recall (avg)** | 98.1% |
|
| 118 |
+
| **F1-Score (avg)** | 98.15% |
|
| 119 |
+
| **Training Samples** | 5,000+ |
|
| 120 |
+
| **Validation Samples** | 1,000+ |
|
| 121 |
+
| **Test Samples** | 500+ |
|
| 122 |
+
|
| 123 |
+
---
|
| 124 |
+
|
| 125 |
+
## π― Confidence Thresholds
|
| 126 |
+
|
| 127 |
+
The system uses a **70% confidence threshold** for reliable diagnoses:
|
| 128 |
+
|
| 129 |
+
- **β₯ 80%** - π’ High confidence (Very reliable)
|
| 130 |
+
- **70-79%** - π‘ Good confidence (Reliable)
|
| 131 |
+
- **< 70%** - π΄ Low confidence (Requires verification)
|
| 132 |
+
|
| 133 |
+
When confidence is below 70%, the system:
|
| 134 |
+
- Shows top 3 disease candidates
|
| 135 |
+
- Provides general guidelines
|
| 136 |
+
- Recommends professional consultation
|
| 137 |
+
|
| 138 |
+
---
|
| 139 |
+
|
| 140 |
+
## π¬ Grad-CAM Visualization
|
| 141 |
+
|
| 142 |
+
**Understanding the Heatmap:**
|
| 143 |
+
|
| 144 |
+
- π΄ **Red areas** - High importance (disease symptoms, lesions)
|
| 145 |
+
- π‘ **Yellow areas** - Moderate importance
|
| 146 |
+
- π’ **Green/Blue areas** - Low importance
|
| 147 |
+
|
| 148 |
+
The heatmap proves the model focuses on actual pathological features, not spurious correlations.
|
| 149 |
+
|
| 150 |
+
---
|
| 151 |
+
|
| 152 |
+
## π οΈ Technical Details
|
| 153 |
+
|
| 154 |
+
### Dependencies
|
| 155 |
+
|
| 156 |
+
torch==2.5.1
|
| 157 |
+
torchvision==0.20.1
|
| 158 |
+
gradio==5.7.1
|
| 159 |
+
google-generativeai==0.8.3
|
| 160 |
+
pillow==11.0.0
|
| 161 |
+
opencv-python-headless==4.10.0.84
|
| 162 |
+
numpy==1.26.4
|
| 163 |
+
python-dotenv==1.0.0
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
### Environment Setup
|
| 167 |
+
This application requires a **Gemini API key** for treatment recommendations. Set it as an environment variable:
|
| 168 |
+
|
| 169 |
+
GEMINI_API_KEY=your_api_key_here
|
| 170 |
+
|
| 171 |
+
---
|
| 172 |
+
|
| 173 |
+
## β οΈ Medical Disclaimer
|
| 174 |
+
|
| 175 |
+
**This is an AI diagnostic tool for preliminary screening only.**
|
| 176 |
+
|
| 177 |
+
### β
Use For:
|
| 178 |
+
- Initial disease screening
|
| 179 |
+
- Educational purposes
|
| 180 |
+
- Research and development
|
| 181 |
+
- Aquaculture monitoring
|
| 182 |
+
|
| 183 |
+
### β Do NOT Use For:
|
| 184 |
+
- Definitive medical diagnosis
|
| 185 |
+
- Treatment without professional consultation
|
| 186 |
+
- Emergency veterinary decisions
|
| 187 |
+
|
| 188 |
+
**Always consult a qualified aquaculture veterinarian for:**
|
| 189 |
+
- Professional diagnosis confirmation
|
| 190 |
+
- Treatment plan approval
|
| 191 |
+
- Medication dosage recommendations
|
| 192 |
+
- Emergency health situations
|
| 193 |
+
|
| 194 |
+
---
|
| 195 |
+
|
| 196 |
+
## π Research & Citation
|
| 197 |
+
|
| 198 |
+
This project is part of research on AI-assisted aquaculture diagnostics and explainable deep learning.
|
| 199 |
+
|
| 200 |
+
**BibTeX Citation:**
|
| 201 |
+
|
| 202 |
+
@software{fish_disease_detection_2025,
|
| 203 |
+
author = {Justin Mathais},
|
| 204 |
+
title = {Fish Disease Detection AI: VGG16 with Grad-CAM Explainability},
|
| 205 |
+
year = {2025},
|
| 206 |
+
url = {https://github.com/YOUR_USERNAME/fish-disease-detection}
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
---
|
| 210 |
+
|
| 211 |
+
## π§ Contact & Support
|
| 212 |
+
|
| 213 |
+
- **Author:** Your Name
|
| 214 |
+
- **Email:** [email protected]
|
| 215 |
+
- **GitHub:** [github.com/mathaisjustin](https://github.com/mathaisjustin)
|
| 216 |
+
- **Issues:** [Report bugs or suggest features](https://github.com/mathaisjustin/fish-disease-detection/issues)
|
| 217 |
+
|
| 218 |
+
---
|
| 219 |
+
|
| 220 |
+
## π License
|
| 221 |
+
|
| 222 |
+
This project is licensed under the **MIT License** - see [LICENSE](LICENSE) file for details.
|
| 223 |
+
|
| 224 |
+
---
|
| 225 |
+
|
| 226 |
+
## π Acknowledgments
|
| 227 |
+
|
| 228 |
+
- **VGG16 Architecture:** Simonyan & Zisserman ([Paper](https://arxiv.org/abs/1409.1556))
|
| 229 |
+
- **Grad-CAM:** Selvaraju et al. ([Paper](https://arxiv.org/abs/1610.02391))
|
| 230 |
+
- **Google Gemini AI:** Treatment recommendation generation
|
| 231 |
+
- **PyTorch Community:** Deep learning framework
|
| 232 |
+
- **Gradio:** Web interface framework
|
| 233 |
+
|
| 234 |
+
---
|
| 235 |
+
|
| 236 |
+
## π Star History
|
| 237 |
+
|
| 238 |
+
If this project helped you, please consider giving it a β on [GitHub](https://github.com/YOUR_USERNAME/fish-disease-detection)!
|
| 239 |
+
|
| 240 |
+
---
|
| 241 |
+
|
| 242 |
+
**Made with β€οΈ for aquaculture health**
|
app.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Gradio Web UI for Fish Disease Detection
|
| 3 |
+
Enhanced with Gemini-powered treatment and Grad-CAM explainability
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
# Load environment variables from .env file (for local development)
|
| 7 |
+
try:
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
load_dotenv()
|
| 10 |
+
print("β
Environment variables loaded from .env")
|
| 11 |
+
except ImportError:
|
| 12 |
+
print("β οΈ python-dotenv not installed (OK for production deployment)")
|
| 13 |
+
|
| 14 |
+
import gradio as gr
|
| 15 |
+
import google.generativeai as genai
|
| 16 |
+
from PIL import Image
|
| 17 |
+
from backend import config as cfg
|
| 18 |
+
from backend.predictor import FishDiseasePredictor
|
| 19 |
+
from backend.treatment import TreatmentGenerator
|
| 20 |
+
from backend.gradcam import generate_gradcam_visualization
|
| 21 |
+
|
| 22 |
+
# ==================== GEMINI SETUP FOR TREATMENT ====================
|
| 23 |
+
treatment_gemini_model = None
|
| 24 |
+
|
| 25 |
+
if cfg.GEMINI_API_KEY:
|
| 26 |
+
try:
|
| 27 |
+
genai.configure(api_key=cfg.GEMINI_API_KEY)
|
| 28 |
+
treatment_gemini_model = genai.GenerativeModel(cfg.GEMINI_MODEL_NAME)
|
| 29 |
+
print("β
Gemini AI enabled for treatment recommendations")
|
| 30 |
+
except Exception as e:
|
| 31 |
+
print(f"β Gemini setup failed: {e}")
|
| 32 |
+
treatment_gemini_model = None
|
| 33 |
+
else:
|
| 34 |
+
print("β οΈ Gemini API key not found. Treatment recommendations will be limited.")
|
| 35 |
+
|
| 36 |
+
# Initialize treatment generator
|
| 37 |
+
treatment_generator = TreatmentGenerator(treatment_gemini_model)
|
| 38 |
+
|
| 39 |
+
# Initialize predictor
|
| 40 |
+
config_dict = {
|
| 41 |
+
'CLASSES': cfg.CLASSES,
|
| 42 |
+
'MODEL_PATH': cfg.MODEL_PATH,
|
| 43 |
+
'DEVICE': cfg.DEVICE,
|
| 44 |
+
'CONFIDENCE_THRESHOLD': cfg.CONFIDENCE_THRESHOLD,
|
| 45 |
+
'MAX_FILE_SIZE_MB': cfg.MAX_FILE_SIZE_MB,
|
| 46 |
+
'MIN_IMAGE_SIZE_PX': cfg.MIN_IMAGE_SIZE_PX,
|
| 47 |
+
'VALID_EXTENSIONS': cfg.VALID_EXTENSIONS
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
predictor = FishDiseasePredictor(config_dict, gemini_model=None)
|
| 51 |
+
|
| 52 |
+
def predict_fish_disease(image):
|
| 53 |
+
"""
|
| 54 |
+
Main prediction function with Grad-CAM visualization
|
| 55 |
+
|
| 56 |
+
Args:
|
| 57 |
+
image: PIL Image from Gradio
|
| 58 |
+
|
| 59 |
+
Returns:
|
| 60 |
+
tuple: (result_text, probability_chart, treatment_text, gradcam_image)
|
| 61 |
+
"""
|
| 62 |
+
if image is None:
|
| 63 |
+
return "β οΈ Please upload an image", None, "", None
|
| 64 |
+
|
| 65 |
+
# Run prediction
|
| 66 |
+
result = predictor.predict_from_image(image)
|
| 67 |
+
|
| 68 |
+
if not result['success']:
|
| 69 |
+
return f"β {result['error']}", None, "", None
|
| 70 |
+
|
| 71 |
+
pred = result['prediction']
|
| 72 |
+
disease = pred['disease'].replace('_', ' ')
|
| 73 |
+
confidence = pred['confidence']
|
| 74 |
+
display_confidence = min(confidence, 100.0)
|
| 75 |
+
|
| 76 |
+
# Get sorted probabilities
|
| 77 |
+
sorted_probs = sorted(pred['probabilities'].items(), key=lambda x: x[1], reverse=True)
|
| 78 |
+
|
| 79 |
+
# GENERATE GRAD-CAM VISUALIZATION
|
| 80 |
+
gradcam_image = None
|
| 81 |
+
try:
|
| 82 |
+
predicted_class_idx = cfg.CLASSES.index(pred['disease'])
|
| 83 |
+
model = predictor.model_loader.model
|
| 84 |
+
transform = predictor.model_loader.transform
|
| 85 |
+
|
| 86 |
+
gradcam_image = generate_gradcam_visualization(
|
| 87 |
+
model, image, predicted_class_idx, transform
|
| 88 |
+
)
|
| 89 |
+
except Exception as e:
|
| 90 |
+
print(f"β οΈ Grad-CAM generation failed: {e}")
|
| 91 |
+
gradcam_image = image # Fallback to original
|
| 92 |
+
|
| 93 |
+
# ENHANCED: Better handling for low confidence cases
|
| 94 |
+
if pred['below_threshold']:
|
| 95 |
+
result_text = f"""
|
| 96 |
+
## π Prediction Results
|
| 97 |
+
|
| 98 |
+
**Status:** β **Uncertain - Low Confidence Detection**
|
| 99 |
+
|
| 100 |
+
β οΈ **Below confidence threshold ({cfg.CONFIDENCE_THRESHOLD}%)**
|
| 101 |
+
|
| 102 |
+
The model detected possible disease signs but cannot confidently identify the specific disease.
|
| 103 |
+
|
| 104 |
+
### π Most Likely Candidates:
|
| 105 |
+
1. **{sorted_probs[0][0].replace('_', ' ')}**: {sorted_probs[0][1]:.1f}%
|
| 106 |
+
2. **{sorted_probs[1][0].replace('_', ' ')}**: {sorted_probs[1][1]:.1f}%
|
| 107 |
+
3. **{sorted_probs[2][0].replace('_', ' ')}**: {sorted_probs[2][1]:.1f}%
|
| 108 |
+
|
| 109 |
+
### π‘ Recommended Actions:
|
| 110 |
+
- Upload a **clearer, well-lit** image if available
|
| 111 |
+
- Capture the fish from **different angles**
|
| 112 |
+
- **Consult a fish health professional** for accurate diagnosis
|
| 113 |
+
- **Monitor the fish** closely for symptom changes
|
| 114 |
+
"""
|
| 115 |
+
|
| 116 |
+
ai_treatment = treatment_generator.get_recommendations(pred['disease'], display_confidence)
|
| 117 |
+
|
| 118 |
+
treatment_text = f"""### β οΈ Low Confidence Warning ({display_confidence:.1f}%)
|
| 119 |
+
|
| 120 |
+
Due to uncertain diagnosis, these are **general guidelines** for the top candidate:
|
| 121 |
+
|
| 122 |
+
---
|
| 123 |
+
|
| 124 |
+
{ai_treatment}
|
| 125 |
+
|
| 126 |
+
---
|
| 127 |
+
|
| 128 |
+
### π΄ CRITICAL REMINDER:
|
| 129 |
+
This diagnosis has **low confidence** and should **NOT** be used for treatment decisions without professional confirmation.
|
| 130 |
+
|
| 131 |
+
**Next Steps:**
|
| 132 |
+
1. Get a professional veterinary assessment
|
| 133 |
+
2. Document symptoms with photos/video
|
| 134 |
+
3. Monitor water quality parameters
|
| 135 |
+
4. Isolate affected fish if condition worsens"""
|
| 136 |
+
|
| 137 |
+
else:
|
| 138 |
+
status_emoji = "β
" if pred['disease'] == 'Healthy_Fish' else "β οΈ"
|
| 139 |
+
status_text = "Healthy" if pred['disease'] == 'Healthy_Fish' else "Diseased"
|
| 140 |
+
|
| 141 |
+
result_text = f"""
|
| 142 |
+
## π Prediction Results
|
| 143 |
+
|
| 144 |
+
**Disease:** {disease}
|
| 145 |
+
**Confidence:** {display_confidence:.2f}%
|
| 146 |
+
**Status:** {status_emoji} {status_text}
|
| 147 |
+
|
| 148 |
+
{"β
High confidence detection - Results are reliable" if confidence >= 80 else ""}
|
| 149 |
+
"""
|
| 150 |
+
|
| 151 |
+
treatment_text = treatment_generator.get_recommendations(pred['disease'], display_confidence)
|
| 152 |
+
|
| 153 |
+
# Convert probabilities for Gradio
|
| 154 |
+
prob_data = {
|
| 155 |
+
disease_name: prob / 100.0
|
| 156 |
+
for disease_name, prob in pred['probabilities'].items()
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
return result_text, prob_data, treatment_text, gradcam_image
|
| 160 |
+
|
| 161 |
+
# ==================== GRADIO INTERFACE ====================
|
| 162 |
+
with gr.Blocks(title="Fish Disease Detection", theme=gr.themes.Soft()) as demo:
|
| 163 |
+
|
| 164 |
+
gr.Markdown("""
|
| 165 |
+
# π Fish Disease Detection System
|
| 166 |
+
### AI-Powered Fish Health Diagnosis using VGG16 CNN with Explainable AI
|
| 167 |
+
|
| 168 |
+
Upload a fish image to detect diseases and see **how the AI made its decision** with heatmap visualization.
|
| 169 |
+
""")
|
| 170 |
+
|
| 171 |
+
with gr.Row():
|
| 172 |
+
with gr.Column(scale=1):
|
| 173 |
+
image_input = gr.Image(type="pil", label="Upload Fish Image", height=400)
|
| 174 |
+
predict_btn = gr.Button("π¬ Analyze Fish", variant="primary", size="lg")
|
| 175 |
+
|
| 176 |
+
gr.Markdown("""
|
| 177 |
+
### π Instructions:
|
| 178 |
+
1. Upload a **clear fish image**
|
| 179 |
+
2. Click **'Analyze Fish'**
|
| 180 |
+
3. View **results, AI explanation, and treatment**
|
| 181 |
+
|
| 182 |
+
**Supported:** JPG, PNG (Max 10MB)
|
| 183 |
+
|
| 184 |
+
### π‘ Tips for Best Results:
|
| 185 |
+
- Use **well-lit** images
|
| 186 |
+
- Show the **whole fish** clearly
|
| 187 |
+
- Avoid **blurry** or **obstructed** shots
|
| 188 |
+
""")
|
| 189 |
+
|
| 190 |
+
with gr.Column(scale=1):
|
| 191 |
+
result_output = gr.Markdown(label="Results")
|
| 192 |
+
prob_output = gr.Label(label="Disease Probabilities", num_top_classes=8)
|
| 193 |
+
|
| 194 |
+
# Grad-CAM Visualization Row
|
| 195 |
+
with gr.Row():
|
| 196 |
+
gradcam_output = gr.Image(
|
| 197 |
+
label="π AI Decision Heatmap - Shows which areas the model focused on for diagnosis (Red = High importance)",
|
| 198 |
+
type="pil",
|
| 199 |
+
height=400
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
with gr.Row():
|
| 203 |
+
treatment_output = gr.Textbox(
|
| 204 |
+
label="π Treatment Recommendations",
|
| 205 |
+
lines=15,
|
| 206 |
+
max_lines=25
|
| 207 |
+
)
|
| 208 |
+
|
| 209 |
+
# Example images
|
| 210 |
+
gr.Examples(
|
| 211 |
+
examples=[
|
| 212 |
+
["data/merged_dataset-all/test/Healthy_Fish/Healthy_Fish_1__1.jpg"],
|
| 213 |
+
["data/merged_dataset-all/test/Bacterial_gill_disease/Bacterial_gill_disease_1__1.jpg"],
|
| 214 |
+
],
|
| 215 |
+
inputs=image_input,
|
| 216 |
+
label="πΈ Example Images"
|
| 217 |
+
)
|
| 218 |
+
|
| 219 |
+
gr.Markdown("""
|
| 220 |
+
---
|
| 221 |
+
### π Model Information
|
| 222 |
+
- **Architecture:** VGG16 CNN with Transfer Learning
|
| 223 |
+
- **Test Accuracy:** 98.65%
|
| 224 |
+
- **Training Dataset:** 5,000+ annotated images
|
| 225 |
+
- **Disease Classes:** 8 diseases + Healthy
|
| 226 |
+
- **Confidence Threshold:** 70% (for reliable diagnosis)
|
| 227 |
+
- **Inference Time:** ~2-3 seconds
|
| 228 |
+
- **AI Treatment:** Powered by Google Gemini 2.0
|
| 229 |
+
- **Explainability:** Grad-CAM visualization
|
| 230 |
+
|
| 231 |
+
### π― Confidence Levels:
|
| 232 |
+
- **β₯ 80%**: High confidence - Very reliable
|
| 233 |
+
- **70-79%**: Good confidence - Reliable
|
| 234 |
+
- **< 70%**: Low confidence - Requires verification
|
| 235 |
+
|
| 236 |
+
### π¬ Understanding the Heatmap:
|
| 237 |
+
- **π΄ Red areas**: Model focused here (disease symptoms, lesions, abnormalities)
|
| 238 |
+
- **π‘ Yellow areas**: Moderate importance
|
| 239 |
+
- **π’ Green/Blue areas**: Less important for diagnosis
|
| 240 |
+
|
| 241 |
+
The heatmap shows the AI is making decisions based on actual disease features, not random patterns.
|
| 242 |
+
|
| 243 |
+
---
|
| 244 |
+
|
| 245 |
+
### β οΈ Medical Disclaimer
|
| 246 |
+
This is an **AI diagnostic tool** for preliminary screening only.
|
| 247 |
+
|
| 248 |
+
**Always consult a qualified aquaculture veterinarian for:**
|
| 249 |
+
- Professional diagnosis confirmation
|
| 250 |
+
- Treatment plan approval
|
| 251 |
+
- Medication dosage recommendations
|
| 252 |
+
- Emergency health situations
|
| 253 |
+
|
| 254 |
+
This tool is intended to **assist**, not **replace** professional veterinary care.
|
| 255 |
+
""")
|
| 256 |
+
|
| 257 |
+
# Connect button with 4 outputs
|
| 258 |
+
predict_btn.click(
|
| 259 |
+
fn=predict_fish_disease,
|
| 260 |
+
inputs=image_input,
|
| 261 |
+
outputs=[result_output, prob_output, treatment_output, gradcam_output]
|
| 262 |
+
)
|
| 263 |
+
|
| 264 |
+
# Launch
|
| 265 |
+
if __name__ == "__main__":
|
| 266 |
+
demo.launch(
|
| 267 |
+
server_name="0.0.0.0",
|
| 268 |
+
server_port=7860,
|
| 269 |
+
share=True # Generates temporary public URL (72 hours)
|
| 270 |
+
)
|
backend/__init__.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Backend package for Fish Disease Detection
|
| 3 |
+
Handles model loading, validation, prediction, and treatment recommendations
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from .config import (
|
| 7 |
+
CLASSES,
|
| 8 |
+
GEMINI_API_KEY,
|
| 9 |
+
GEMINI_MODEL_NAME,
|
| 10 |
+
MODEL_PATH,
|
| 11 |
+
CONFIDENCE_THRESHOLD,
|
| 12 |
+
DEVICE,
|
| 13 |
+
MAX_FILE_SIZE_MB,
|
| 14 |
+
MIN_IMAGE_SIZE_PX,
|
| 15 |
+
VALID_EXTENSIONS
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
from .predictor import FishDiseasePredictor
|
| 19 |
+
|
| 20 |
+
__version__ = "1.0.0"
|
| 21 |
+
__all__ = [
|
| 22 |
+
'FishDiseasePredictor',
|
| 23 |
+
'CLASSES',
|
| 24 |
+
'GEMINI_API_KEY',
|
| 25 |
+
'GEMINI_MODEL_NAME',
|
| 26 |
+
'MODEL_PATH',
|
| 27 |
+
'CONFIDENCE_THRESHOLD'
|
| 28 |
+
]
|
backend/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (674 Bytes). View file
|
|
|
backend/__pycache__/config.cpython-313.pyc
ADDED
|
Binary file (2.34 kB). View file
|
|
|
backend/__pycache__/gradcam.cpython-313.pyc
ADDED
|
Binary file (4.97 kB). View file
|
|
|
backend/__pycache__/model_loader.cpython-313.pyc
ADDED
|
Binary file (4.6 kB). View file
|
|
|
backend/__pycache__/predictor.cpython-313.pyc
ADDED
|
Binary file (6.62 kB). View file
|
|
|
backend/__pycache__/treatment.cpython-313.pyc
ADDED
|
Binary file (7.57 kB). View file
|
|
|
backend/__pycache__/validator.cpython-313.pyc
ADDED
|
Binary file (6.44 kB). View file
|
|
|
backend/config.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Configuration file for Fish Disease Detection System
|
| 3 |
+
Secure deployment with environment variables
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
# ==================== DISEASE CLASSES ====================
|
| 10 |
+
CLASSES = [
|
| 11 |
+
'Aeromoniasis',
|
| 12 |
+
'Bacterial_gill_disease',
|
| 13 |
+
'Bacterial_red_disease',
|
| 14 |
+
'EUS',
|
| 15 |
+
'Healthy_Fish',
|
| 16 |
+
'Parasitic_diseases',
|
| 17 |
+
'Saprolegniasis_fungal',
|
| 18 |
+
'Viral_white_tail'
|
| 19 |
+
]
|
| 20 |
+
|
| 21 |
+
# ==================== GEMINI API SETTINGS ====================
|
| 22 |
+
# IMPORTANT: Set GEMINI_API_KEY in environment variable (.env file or Hugging Face Secrets)
|
| 23 |
+
GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY')
|
| 24 |
+
GEMINI_MODEL_NAME = os.environ.get('GEMINI_MODEL_NAME', 'gemini-2.0-flash-exp')
|
| 25 |
+
|
| 26 |
+
# Validate API key
|
| 27 |
+
if not GEMINI_API_KEY:
|
| 28 |
+
print("β οΈ WARNING: GEMINI_API_KEY not found in environment variables!")
|
| 29 |
+
print(" Set it in .env file (local) or Hugging Face Space settings (production)")
|
| 30 |
+
print(" Gemini AI features will be disabled.")
|
| 31 |
+
|
| 32 |
+
# ==================== MODEL SETTINGS ====================
|
| 33 |
+
MODEL_PATH = 'models/vgg_resnet/results/vgg_resnet/vgg16_best.pth'
|
| 34 |
+
CONFIDENCE_THRESHOLD = 70.0 # Minimum confidence to report disease
|
| 35 |
+
DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
|
| 36 |
+
|
| 37 |
+
# ==================== VALIDATION SETTINGS ====================
|
| 38 |
+
MAX_FILE_SIZE_MB = 10 # Maximum upload size
|
| 39 |
+
MIN_IMAGE_SIZE_PX = 100 # Minimum image dimensions
|
| 40 |
+
VALID_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.JPG', '.JPEG', '.PNG']
|
| 41 |
+
|
| 42 |
+
# ==================== IMAGE PREPROCESSING ====================
|
| 43 |
+
IMAGE_SIZE = 224 # VGG16 input size
|
| 44 |
+
NORMALIZE_MEAN = [0.485, 0.456, 0.406] # ImageNet mean
|
| 45 |
+
NORMALIZE_STD = [0.229, 0.224, 0.225] # ImageNet std
|
| 46 |
+
|
| 47 |
+
# ==================== DEBUG SETTINGS ====================
|
| 48 |
+
DEBUG = os.environ.get('DEBUG', 'False').lower() == 'true'
|
| 49 |
+
|
| 50 |
+
if DEBUG:
|
| 51 |
+
print("="*60)
|
| 52 |
+
print("π Fish Disease Detection - Configuration")
|
| 53 |
+
print("="*60)
|
| 54 |
+
print(f" Model Path: {MODEL_PATH}")
|
| 55 |
+
print(f" Device: {DEVICE}")
|
| 56 |
+
print(f" Classes: {len(CLASSES)}")
|
| 57 |
+
print(f" Confidence Threshold: {CONFIDENCE_THRESHOLD}%")
|
| 58 |
+
print(f" Gemini API Key: {'β
Set' if GEMINI_API_KEY else 'β Not Set'}")
|
| 59 |
+
print(f" Gemini Model: {GEMINI_MODEL_NAME if GEMINI_API_KEY else 'N/A'}")
|
| 60 |
+
print("="*60)
|
backend/gradcam.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Grad-CAM implementation for explainable AI
|
| 3 |
+
Shows which parts of the image influenced the model's decision
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
import numpy as np
|
| 9 |
+
import cv2
|
| 10 |
+
from PIL import Image
|
| 11 |
+
|
| 12 |
+
class GradCAM:
|
| 13 |
+
"""
|
| 14 |
+
Grad-CAM for VGG16 to visualize model decisions
|
| 15 |
+
"""
|
| 16 |
+
|
| 17 |
+
def __init__(self, model, target_layer):
|
| 18 |
+
"""
|
| 19 |
+
Initialize Grad-CAM
|
| 20 |
+
|
| 21 |
+
Args:
|
| 22 |
+
model: Trained VGG16 model
|
| 23 |
+
target_layer: Layer to visualize (e.g., model.features[-1])
|
| 24 |
+
"""
|
| 25 |
+
self.model = model
|
| 26 |
+
self.target_layer = target_layer
|
| 27 |
+
self.gradients = None
|
| 28 |
+
self.activations = None
|
| 29 |
+
|
| 30 |
+
# Register hooks
|
| 31 |
+
self.target_layer.register_forward_hook(self.save_activation)
|
| 32 |
+
self.target_layer.register_backward_hook(self.save_gradient)
|
| 33 |
+
|
| 34 |
+
def save_activation(self, module, input, output):
|
| 35 |
+
"""Save forward activation"""
|
| 36 |
+
self.activations = output.detach()
|
| 37 |
+
|
| 38 |
+
def save_gradient(self, module, grad_input, grad_output):
|
| 39 |
+
"""Save backward gradient"""
|
| 40 |
+
self.gradients = grad_output[0].detach()
|
| 41 |
+
|
| 42 |
+
def generate_cam(self, input_tensor, class_idx):
|
| 43 |
+
"""
|
| 44 |
+
Generate Class Activation Map
|
| 45 |
+
|
| 46 |
+
Args:
|
| 47 |
+
input_tensor: Preprocessed input image tensor
|
| 48 |
+
class_idx: Target class index
|
| 49 |
+
|
| 50 |
+
Returns:
|
| 51 |
+
numpy array: Heatmap (0-255)
|
| 52 |
+
"""
|
| 53 |
+
# Forward pass
|
| 54 |
+
output = self.model(input_tensor)
|
| 55 |
+
|
| 56 |
+
# Zero gradients
|
| 57 |
+
self.model.zero_grad()
|
| 58 |
+
|
| 59 |
+
# Backward pass for target class
|
| 60 |
+
class_score = output[0, class_idx]
|
| 61 |
+
class_score.backward()
|
| 62 |
+
|
| 63 |
+
# Get gradients and activations
|
| 64 |
+
gradients = self.gradients[0] # [C, H, W]
|
| 65 |
+
activations = self.activations[0] # [C, H, W]
|
| 66 |
+
|
| 67 |
+
# Calculate weights (global average pooling of gradients)
|
| 68 |
+
weights = gradients.mean(dim=(1, 2), keepdim=True) # [C, 1, 1]
|
| 69 |
+
|
| 70 |
+
# Weighted combination of activation maps
|
| 71 |
+
cam = (weights * activations).sum(dim=0) # [H, W]
|
| 72 |
+
|
| 73 |
+
# Apply ReLU (only positive influence)
|
| 74 |
+
cam = F.relu(cam)
|
| 75 |
+
|
| 76 |
+
# Normalize to 0-1
|
| 77 |
+
cam = cam - cam.min()
|
| 78 |
+
cam = cam / cam.max()
|
| 79 |
+
|
| 80 |
+
# Convert to numpy and resize to 224x224
|
| 81 |
+
cam = cam.cpu().numpy()
|
| 82 |
+
cam = cv2.resize(cam, (224, 224))
|
| 83 |
+
|
| 84 |
+
# Convert to 0-255 range
|
| 85 |
+
cam = np.uint8(255 * cam)
|
| 86 |
+
|
| 87 |
+
return cam
|
| 88 |
+
|
| 89 |
+
def overlay_heatmap(self, image, heatmap, alpha=0.5):
|
| 90 |
+
"""
|
| 91 |
+
Overlay heatmap on original image
|
| 92 |
+
|
| 93 |
+
Args:
|
| 94 |
+
image: Original PIL Image
|
| 95 |
+
heatmap: Heatmap numpy array (0-255)
|
| 96 |
+
alpha: Transparency (0-1)
|
| 97 |
+
|
| 98 |
+
Returns:
|
| 99 |
+
PIL Image with heatmap overlay
|
| 100 |
+
"""
|
| 101 |
+
# Resize image to 224x224
|
| 102 |
+
image = image.resize((224, 224))
|
| 103 |
+
image_np = np.array(image)
|
| 104 |
+
|
| 105 |
+
# Apply colormap to heatmap (red = high activation)
|
| 106 |
+
heatmap_colored = cv2.applyColorMap(heatmap, cv2.COLORMAP_JET)
|
| 107 |
+
heatmap_colored = cv2.cvtColor(heatmap_colored, cv2.COLOR_BGR2RGB)
|
| 108 |
+
|
| 109 |
+
# Overlay
|
| 110 |
+
overlayed = cv2.addWeighted(image_np, 1-alpha, heatmap_colored, alpha, 0)
|
| 111 |
+
|
| 112 |
+
return Image.fromarray(overlayed)
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def generate_gradcam_visualization(model, image, predicted_class_idx, transform):
|
| 116 |
+
"""
|
| 117 |
+
Generate Grad-CAM visualization for a prediction
|
| 118 |
+
|
| 119 |
+
Args:
|
| 120 |
+
model: Trained VGG16 model
|
| 121 |
+
image: PIL Image
|
| 122 |
+
predicted_class_idx: Index of predicted class
|
| 123 |
+
transform: Image transformation pipeline
|
| 124 |
+
|
| 125 |
+
Returns:
|
| 126 |
+
PIL Image: Image with heatmap overlay
|
| 127 |
+
"""
|
| 128 |
+
# Set model to eval mode
|
| 129 |
+
model.eval()
|
| 130 |
+
|
| 131 |
+
# Preprocess image
|
| 132 |
+
input_tensor = transform(image).unsqueeze(0)
|
| 133 |
+
|
| 134 |
+
# Create Grad-CAM instance (target last convolutional layer of VGG16)
|
| 135 |
+
gradcam = GradCAM(model, model.features[-1])
|
| 136 |
+
|
| 137 |
+
# Generate heatmap
|
| 138 |
+
heatmap = gradcam.generate_cam(input_tensor, predicted_class_idx)
|
| 139 |
+
|
| 140 |
+
# Overlay on original image
|
| 141 |
+
visualization = gradcam.overlay_heatmap(image, heatmap, alpha=0.4)
|
| 142 |
+
|
| 143 |
+
return visualization
|
backend/model_loader.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Model loader - Handles VGG16 loading and inference
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
from torchvision import models, transforms
|
| 8 |
+
from .config import IMAGE_SIZE, NORMALIZE_MEAN, NORMALIZE_STD
|
| 9 |
+
|
| 10 |
+
class VGG16ModelLoader:
|
| 11 |
+
"""Loads and manages VGG16 model for fish disease detection"""
|
| 12 |
+
|
| 13 |
+
def __init__(self, model_path, num_classes, device='cpu'):
|
| 14 |
+
"""
|
| 15 |
+
Initialize model loader
|
| 16 |
+
|
| 17 |
+
Args:
|
| 18 |
+
model_path: Path to trained model weights (.pth file)
|
| 19 |
+
num_classes: Number of disease classes
|
| 20 |
+
device: 'cpu' or 'cuda'
|
| 21 |
+
"""
|
| 22 |
+
self.model_path = model_path
|
| 23 |
+
self.num_classes = num_classes
|
| 24 |
+
self.device = torch.device(device)
|
| 25 |
+
self.model = None
|
| 26 |
+
self.transform = None
|
| 27 |
+
|
| 28 |
+
self._load_model()
|
| 29 |
+
self._setup_transform()
|
| 30 |
+
|
| 31 |
+
def _load_model(self):
|
| 32 |
+
"""Load VGG16 with custom classifier"""
|
| 33 |
+
try:
|
| 34 |
+
# Create VGG16 architecture
|
| 35 |
+
self.model = models.vgg16(weights="IMAGENET1K_V1")
|
| 36 |
+
|
| 37 |
+
# Replace final layer for our classes
|
| 38 |
+
self.model.classifier[6] = nn.Linear(4096, self.num_classes)
|
| 39 |
+
|
| 40 |
+
# Load trained weights
|
| 41 |
+
state_dict = torch.load(
|
| 42 |
+
self.model_path,
|
| 43 |
+
map_location=self.device,
|
| 44 |
+
weights_only=True # Security: only load weights
|
| 45 |
+
)
|
| 46 |
+
self.model.load_state_dict(state_dict)
|
| 47 |
+
|
| 48 |
+
# Move to device and set eval mode
|
| 49 |
+
self.model = self.model.to(self.device)
|
| 50 |
+
self.model.eval()
|
| 51 |
+
|
| 52 |
+
print(f"β
Model loaded: {self.model_path}")
|
| 53 |
+
print(f"β
Device: {self.device}")
|
| 54 |
+
|
| 55 |
+
except FileNotFoundError:
|
| 56 |
+
raise RuntimeError(f"Model file not found: {self.model_path}")
|
| 57 |
+
except Exception as e:
|
| 58 |
+
raise RuntimeError(f"Failed to load model: {e}")
|
| 59 |
+
|
| 60 |
+
def _setup_transform(self):
|
| 61 |
+
"""Setup image preprocessing pipeline"""
|
| 62 |
+
self.transform = transforms.Compose([
|
| 63 |
+
transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),
|
| 64 |
+
transforms.ToTensor(),
|
| 65 |
+
transforms.Normalize(NORMALIZE_MEAN, NORMALIZE_STD),
|
| 66 |
+
])
|
| 67 |
+
|
| 68 |
+
def predict(self, image):
|
| 69 |
+
"""
|
| 70 |
+
Predict disease from PIL Image
|
| 71 |
+
|
| 72 |
+
Args:
|
| 73 |
+
image: PIL Image in RGB format
|
| 74 |
+
|
| 75 |
+
Returns:
|
| 76 |
+
tuple: (predicted_class_idx, confidence_score, all_probabilities)
|
| 77 |
+
"""
|
| 78 |
+
try:
|
| 79 |
+
# Preprocess image
|
| 80 |
+
input_tensor = self.transform(image).unsqueeze(0).to(self.device)
|
| 81 |
+
|
| 82 |
+
# Run inference
|
| 83 |
+
with torch.no_grad():
|
| 84 |
+
outputs = self.model(input_tensor)
|
| 85 |
+
probabilities = torch.nn.functional.softmax(outputs, dim=1)
|
| 86 |
+
confidence, predicted_idx = torch.max(probabilities, 1)
|
| 87 |
+
|
| 88 |
+
return (
|
| 89 |
+
predicted_idx.item(),
|
| 90 |
+
confidence.item() * 100, # Convert to percentage
|
| 91 |
+
probabilities[0]
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
except Exception as e:
|
| 95 |
+
raise RuntimeError(f"Prediction failed: {e}")
|
backend/predictor.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Predictor - Orchestrates the complete prediction pipeline
|
| 3 |
+
Combines validation, model inference, and treatment generation
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from .validator import FishImageValidator
|
| 7 |
+
from .model_loader import VGG16ModelLoader
|
| 8 |
+
from .treatment import TreatmentGenerator
|
| 9 |
+
from .config import CLASSES, CONFIDENCE_THRESHOLD
|
| 10 |
+
|
| 11 |
+
class FishDiseasePredictor:
|
| 12 |
+
"""Main prediction pipeline that coordinates all components"""
|
| 13 |
+
|
| 14 |
+
def __init__(self, config, gemini_model=None):
|
| 15 |
+
"""
|
| 16 |
+
Initialize predictor with all components
|
| 17 |
+
|
| 18 |
+
Args:
|
| 19 |
+
config: Configuration dictionary with all settings
|
| 20 |
+
gemini_model: Google Gemini model instance (optional)
|
| 21 |
+
"""
|
| 22 |
+
self.config = config
|
| 23 |
+
self.gemini_model = gemini_model
|
| 24 |
+
self.confidence_threshold = config.get('CONFIDENCE_THRESHOLD', CONFIDENCE_THRESHOLD)
|
| 25 |
+
|
| 26 |
+
# Initialize all components
|
| 27 |
+
self.validator = FishImageValidator(
|
| 28 |
+
max_size_mb=config.get('MAX_FILE_SIZE_MB', 10),
|
| 29 |
+
min_size_px=config.get('MIN_IMAGE_SIZE_PX', 100),
|
| 30 |
+
valid_extensions=config.get('VALID_EXTENSIONS')
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
self.model_loader = VGG16ModelLoader(
|
| 34 |
+
model_path=config['MODEL_PATH'],
|
| 35 |
+
num_classes=len(config['CLASSES']),
|
| 36 |
+
device=config.get('DEVICE', 'cpu')
|
| 37 |
+
)
|
| 38 |
+
|
| 39 |
+
self.treatment_generator = TreatmentGenerator(gemini_model)
|
| 40 |
+
|
| 41 |
+
def predict(self, image_path):
|
| 42 |
+
"""
|
| 43 |
+
Complete prediction pipeline with all validations
|
| 44 |
+
|
| 45 |
+
Args:
|
| 46 |
+
image_path: Path to image file
|
| 47 |
+
|
| 48 |
+
Returns:
|
| 49 |
+
dict: Complete result with validation, prediction, and treatment
|
| 50 |
+
{
|
| 51 |
+
'success': bool,
|
| 52 |
+
'error': str or None,
|
| 53 |
+
'validation': {
|
| 54 |
+
'file': {'valid': bool, 'message': str},
|
| 55 |
+
'gemini': {'valid': bool, 'message': str}
|
| 56 |
+
},
|
| 57 |
+
'prediction': {
|
| 58 |
+
'disease': str,
|
| 59 |
+
'confidence': float,
|
| 60 |
+
'probabilities': dict,
|
| 61 |
+
'below_threshold': bool
|
| 62 |
+
},
|
| 63 |
+
'treatment': str or None
|
| 64 |
+
}
|
| 65 |
+
"""
|
| 66 |
+
result = {
|
| 67 |
+
'success': False,
|
| 68 |
+
'error': None,
|
| 69 |
+
'validation': {},
|
| 70 |
+
'prediction': {},
|
| 71 |
+
'treatment': None
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
# STEP 1: File validation (format, size, corruption)
|
| 75 |
+
print("π Validating file...")
|
| 76 |
+
is_valid, msg, image = self.validator.validate_file(image_path)
|
| 77 |
+
result['validation']['file'] = {'valid': is_valid, 'message': msg}
|
| 78 |
+
|
| 79 |
+
if not is_valid:
|
| 80 |
+
result['error'] = msg
|
| 81 |
+
return result
|
| 82 |
+
|
| 83 |
+
# STEP 2: Gemini AI validation (fish detection, edge cases)
|
| 84 |
+
print("π€ Validating with Gemini AI...")
|
| 85 |
+
is_valid, msg = self.validator.validate_with_gemini(image, self.gemini_model)
|
| 86 |
+
result['validation']['gemini'] = {'valid': is_valid, 'message': msg}
|
| 87 |
+
print(msg)
|
| 88 |
+
|
| 89 |
+
if not is_valid:
|
| 90 |
+
result['error'] = msg
|
| 91 |
+
return result
|
| 92 |
+
|
| 93 |
+
# STEP 3: VGG16 disease prediction
|
| 94 |
+
print("π¬ Analyzing fish health...")
|
| 95 |
+
try:
|
| 96 |
+
class_idx, confidence, probabilities = self.model_loader.predict(image)
|
| 97 |
+
|
| 98 |
+
predicted_class = self.config['CLASSES'][class_idx]
|
| 99 |
+
|
| 100 |
+
# Build probability dictionary
|
| 101 |
+
prob_dict = {
|
| 102 |
+
self.config['CLASSES'][i]: float(probabilities[i].item() * 100)
|
| 103 |
+
for i in range(len(self.config['CLASSES']))
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
result['prediction'] = {
|
| 107 |
+
'disease': predicted_class,
|
| 108 |
+
'confidence': confidence,
|
| 109 |
+
'probabilities': prob_dict,
|
| 110 |
+
'below_threshold': confidence < self.confidence_threshold
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
# STEP 4: Generate treatment (only if confident)
|
| 114 |
+
if confidence >= self.confidence_threshold:
|
| 115 |
+
print("π Generating treatment recommendations...")
|
| 116 |
+
treatment = self.treatment_generator.get_recommendations(
|
| 117 |
+
predicted_class, confidence
|
| 118 |
+
)
|
| 119 |
+
result['treatment'] = treatment
|
| 120 |
+
|
| 121 |
+
result['success'] = True
|
| 122 |
+
return result
|
| 123 |
+
|
| 124 |
+
except Exception as e:
|
| 125 |
+
result['error'] = f"β Prediction failed: {str(e)}"
|
| 126 |
+
return result
|
| 127 |
+
|
| 128 |
+
def predict_from_image(self, pil_image):
|
| 129 |
+
"""
|
| 130 |
+
Predict directly from PIL Image (for web UI)
|
| 131 |
+
|
| 132 |
+
Args:
|
| 133 |
+
pil_image: PIL Image object
|
| 134 |
+
|
| 135 |
+
Returns:
|
| 136 |
+
dict: Same as predict() method
|
| 137 |
+
"""
|
| 138 |
+
result = {
|
| 139 |
+
'success': False,
|
| 140 |
+
'error': None,
|
| 141 |
+
'validation': {},
|
| 142 |
+
'prediction': {},
|
| 143 |
+
'treatment': None
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
try:
|
| 147 |
+
# Convert to RGB if needed
|
| 148 |
+
image = pil_image.convert('RGB')
|
| 149 |
+
|
| 150 |
+
# File validation not needed (already have image)
|
| 151 |
+
result['validation']['file'] = {'valid': True, 'message': 'β
Image provided'}
|
| 152 |
+
|
| 153 |
+
# Gemini validation
|
| 154 |
+
print("π€ Validating with Gemini AI...")
|
| 155 |
+
is_valid, msg = self.validator.validate_with_gemini(image, self.gemini_model)
|
| 156 |
+
result['validation']['gemini'] = {'valid': is_valid, 'message': msg}
|
| 157 |
+
print(msg)
|
| 158 |
+
|
| 159 |
+
if not is_valid:
|
| 160 |
+
result['error'] = msg
|
| 161 |
+
return result
|
| 162 |
+
|
| 163 |
+
# Model prediction
|
| 164 |
+
print("π¬ Analyzing fish health...")
|
| 165 |
+
class_idx, confidence, probabilities = self.model_loader.predict(image)
|
| 166 |
+
|
| 167 |
+
predicted_class = self.config['CLASSES'][class_idx]
|
| 168 |
+
|
| 169 |
+
prob_dict = {
|
| 170 |
+
self.config['CLASSES'][i]: float(probabilities[i].item() * 100)
|
| 171 |
+
for i in range(len(self.config['CLASSES']))
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
result['prediction'] = {
|
| 175 |
+
'disease': predicted_class,
|
| 176 |
+
'confidence': confidence,
|
| 177 |
+
'probabilities': prob_dict,
|
| 178 |
+
'below_threshold': confidence < self.confidence_threshold
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
# Generate treatment
|
| 182 |
+
if confidence >= self.confidence_threshold:
|
| 183 |
+
print("π Generating treatment recommendations...")
|
| 184 |
+
treatment = self.treatment_generator.get_recommendations(
|
| 185 |
+
predicted_class, confidence
|
| 186 |
+
)
|
| 187 |
+
result['treatment'] = treatment
|
| 188 |
+
|
| 189 |
+
result['success'] = True
|
| 190 |
+
return result
|
| 191 |
+
|
| 192 |
+
except Exception as e:
|
| 193 |
+
result['error'] = f"β Prediction failed: {str(e)}"
|
| 194 |
+
return result
|
backend/treatment.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Treatment module - Generates disease treatment recommendations
|
| 3 |
+
Uses Gemini AI or falls back to predefined treatments
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
class TreatmentGenerator:
|
| 7 |
+
"""Generates treatment recommendations for fish diseases"""
|
| 8 |
+
|
| 9 |
+
def __init__(self, gemini_model=None):
|
| 10 |
+
"""
|
| 11 |
+
Initialize treatment generator
|
| 12 |
+
|
| 13 |
+
Args:
|
| 14 |
+
gemini_model: Google Gemini model instance (optional)
|
| 15 |
+
"""
|
| 16 |
+
self.gemini_model = gemini_model
|
| 17 |
+
|
| 18 |
+
def get_recommendations(self, disease_name, confidence):
|
| 19 |
+
"""
|
| 20 |
+
Get treatment recommendations for detected disease
|
| 21 |
+
|
| 22 |
+
Args:
|
| 23 |
+
disease_name: Name of detected disease
|
| 24 |
+
confidence: Confidence score (0-100)
|
| 25 |
+
|
| 26 |
+
Returns:
|
| 27 |
+
str: Formatted treatment recommendations
|
| 28 |
+
"""
|
| 29 |
+
if self.gemini_model:
|
| 30 |
+
return self._get_ai_treatment(disease_name, confidence)
|
| 31 |
+
else:
|
| 32 |
+
return self._get_fallback_treatment(disease_name)
|
| 33 |
+
|
| 34 |
+
def _get_ai_treatment(self, disease_name, confidence):
|
| 35 |
+
"""Generate AI-powered treatment using Gemini"""
|
| 36 |
+
try:
|
| 37 |
+
prompt = f"""You are an expert aquaculture veterinarian with 20 years of experience.
|
| 38 |
+
|
| 39 |
+
**DIAGNOSIS:**
|
| 40 |
+
- Disease: {disease_name.replace('_', ' ')}
|
| 41 |
+
- Confidence: {confidence:.1f}%
|
| 42 |
+
|
| 43 |
+
Provide a detailed, practical treatment plan in this EXACT format:
|
| 44 |
+
|
| 45 |
+
**IMMEDIATE ACTIONS (First 24 hours):**
|
| 46 |
+
- [List 3-4 urgent steps with specific timing]
|
| 47 |
+
|
| 48 |
+
**TREATMENT PROTOCOL (Days 1-10):**
|
| 49 |
+
- [Medications with exact dosages, frequency, and duration]
|
| 50 |
+
- [Water management with specific parameters]
|
| 51 |
+
|
| 52 |
+
**PREVENTION MEASURES:**
|
| 53 |
+
- [List 4-5 specific preventive measures for future]
|
| 54 |
+
|
| 55 |
+
**EXPECTED OUTCOME:**
|
| 56 |
+
- [Recovery timeline and realistic success rate]
|
| 57 |
+
|
| 58 |
+
Requirements:
|
| 59 |
+
- Be specific with dosages (mg/kg, ppm, etc.)
|
| 60 |
+
- Include water parameter targets (pH, DO, temperature)
|
| 61 |
+
- Use simple language for fish farmers
|
| 62 |
+
- Focus on practical, actionable steps"""
|
| 63 |
+
|
| 64 |
+
response = self.gemini_model.generate_content(prompt)
|
| 65 |
+
return response.text
|
| 66 |
+
|
| 67 |
+
except Exception as e:
|
| 68 |
+
print(f"β οΈ Gemini treatment generation error: {e}")
|
| 69 |
+
return self._get_fallback_treatment(disease_name)
|
| 70 |
+
|
| 71 |
+
def _get_fallback_treatment(self, disease_name):
|
| 72 |
+
"""Fallback treatment database (predefined)"""
|
| 73 |
+
treatments = {
|
| 74 |
+
'Aeromoniasis': """**IMMEDIATE ACTIONS (First 24 hours):**
|
| 75 |
+
- Isolate infected fish immediately to prevent spread
|
| 76 |
+
- Perform 50% water change within 2 hours
|
| 77 |
+
- Increase aeration to maximum capacity
|
| 78 |
+
- Stop feeding for 24 hours to reduce waste
|
| 79 |
+
|
| 80 |
+
**TREATMENT PROTOCOL (Days 1-10):**
|
| 81 |
+
- Antibiotics: Oxytetracycline 50-75 mg/kg feed for 10 consecutive days
|
| 82 |
+
- Salt bath: 3% NaCl solution for 5-10 minutes daily (days 1-7)
|
| 83 |
+
- Maintain water temperature at 25-28Β°C
|
| 84 |
+
- Monitor daily for 14 days post-treatment
|
| 85 |
+
|
| 86 |
+
**PREVENTION MEASURES:**
|
| 87 |
+
- Maintain dissolved oxygen > 5 mg/L at all times
|
| 88 |
+
- Reduce stocking density by 30% if currently overcrowded
|
| 89 |
+
- Implement 14-21 day quarantine for all new fish
|
| 90 |
+
- Weekly health monitoring and immediate isolation of sick fish
|
| 91 |
+
- Regular disinfection of equipment and nets
|
| 92 |
+
|
| 93 |
+
**EXPECTED OUTCOME:**
|
| 94 |
+
70-80% recovery rate with prompt treatment. Symptoms should improve within 3-5 days. Full recovery typically takes 10-14 days.""",
|
| 95 |
+
|
| 96 |
+
'Bacterial_gill_disease': """**IMMEDIATE ACTIONS (First 24 hours):**
|
| 97 |
+
- Increase aeration immediately (CRITICAL - survival depends on oxygen)
|
| 98 |
+
- Reduce feeding by 50% to minimize ammonia buildup
|
| 99 |
+
- Test and adjust water quality (pH, ammonia, nitrite)
|
| 100 |
+
- Separate severely affected fish to reduce stress
|
| 101 |
+
|
| 102 |
+
**TREATMENT PROTOCOL (Days 1-10):**
|
| 103 |
+
- Salt bath: 3% NaCl for 5-10 minutes daily for 7 days
|
| 104 |
+
- Improve water circulation (add aerators/water pumps)
|
| 105 |
+
- Reduce stocking density by 40%
|
| 106 |
+
- Antibiotics: Florfenicol 10 mg/kg feed daily for 7-10 days (if severe)
|
| 107 |
+
|
| 108 |
+
**PREVENTION MEASURES:**
|
| 109 |
+
- Maintain dissolved oxygen > 5 mg/L continuously (CRITICAL)
|
| 110 |
+
- Regular pond/tank cleaning (bi-weekly minimum)
|
| 111 |
+
- Daily ammonia monitoring (keep < 0.02 mg/L)
|
| 112 |
+
- Avoid sudden temperature changes (Β±2Β°C maximum)
|
| 113 |
+
- Proper biofilter maintenance and regular backwashing
|
| 114 |
+
|
| 115 |
+
**EXPECTED OUTCOME:**
|
| 116 |
+
85-90% recovery if caught early (within 2-3 days). Symptoms improve within 48 hours of treatment. Mortality high if delayed beyond 5 days.""",
|
| 117 |
+
|
| 118 |
+
'EUS': """**IMMEDIATE ACTIONS (First 24 hours):**
|
| 119 |
+
- REPORT TO AUTHORITIES (notifiable disease in many regions)
|
| 120 |
+
- Isolate all affected fish immediately
|
| 121 |
+
- Implement 75% water change
|
| 122 |
+
- Remove and properly dispose of dead/dying fish
|
| 123 |
+
|
| 124 |
+
**TREATMENT PROTOCOL (Days 1-21):**
|
| 125 |
+
- Topical disinfectant: Potassium permanganate 2-5 ppm bath (3 times weekly)
|
| 126 |
+
- NO specific cure available - focus on supportive care
|
| 127 |
+
- Maintain optimal water conditions (see below)
|
| 128 |
+
- Consider humane culling of severely affected fish (>50% body lesions)
|
| 129 |
+
- Secondary infection prevention: Oxytetracycline 50 mg/kg feed
|
| 130 |
+
|
| 131 |
+
**PREVENTION MEASURES:**
|
| 132 |
+
- NEVER use water from infected sources
|
| 133 |
+
- Implement strict biosecurity: footbaths, equipment disinfection
|
| 134 |
+
- Quarantine ALL new fish for minimum 21 days
|
| 135 |
+
- Regular health surveillance (weekly inspections)
|
| 136 |
+
- Maintain year-round optimal water quality
|
| 137 |
+
|
| 138 |
+
**EXPECTED OUTCOME:**
|
| 139 |
+
Variable (40-90% mortality depending on strain and timing). NO cure exists. Early detection critical. Focus on prevention and biosecurity.""",
|
| 140 |
+
|
| 141 |
+
'Healthy_Fish': """**MAINTENANCE (Continue Good Practices):**
|
| 142 |
+
- Continue current care routine (it's working excellently!)
|
| 143 |
+
- Daily observations for early disease detection
|
| 144 |
+
- Maintain current feeding schedule (adjust seasonally)
|
| 145 |
+
- Regular water quality monitoring
|
| 146 |
+
|
| 147 |
+
**PREVENTION MEASURES:**
|
| 148 |
+
- Weekly water parameter testing (pH, ammonia, nitrite, nitrate, DO)
|
| 149 |
+
- Quarantine new fish for 14-21 days before introduction
|
| 150 |
+
- Clean and disinfect all equipment monthly
|
| 151 |
+
- Daily behavioral observations (feeding response, swimming patterns)
|
| 152 |
+
- Maintain detailed health records
|
| 153 |
+
|
| 154 |
+
**EXPECTED OUTCOME:**
|
| 155 |
+
Excellent prognosis. Continue preventive care to maintain optimal health status. Regular monitoring ensures early detection of any future issues."""
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
# Default treatment for diseases not in database
|
| 159 |
+
default = """**IMPORTANT: Consult a fish health expert immediately.**
|
| 160 |
+
|
| 161 |
+
This disease requires professional veterinary assessment for accurate diagnosis and treatment protocol.
|
| 162 |
+
|
| 163 |
+
**IMMEDIATE STEPS:**
|
| 164 |
+
1. Isolate affected fish to prevent potential spread
|
| 165 |
+
2. Improve water quality (50% water change, increase aeration)
|
| 166 |
+
3. Document all symptoms with photos and notes
|
| 167 |
+
4. Contact your local aquaculture veterinarian or extension service
|
| 168 |
+
|
| 169 |
+
**EMERGENCY CONTACT:**
|
| 170 |
+
Seek professional help from:
|
| 171 |
+
- Local fish health specialist
|
| 172 |
+
- Aquaculture extension service
|
| 173 |
+
- Veterinary diagnostic laboratory
|
| 174 |
+
|
| 175 |
+
Do NOT attempt unguided treatment as it may worsen the condition."""
|
| 176 |
+
|
| 177 |
+
return treatments.get(disease_name, default)
|
backend/validator.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Validation module - Handles all edge cases and input validation
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import os
|
| 6 |
+
from PIL import Image
|
| 7 |
+
from .config import MAX_FILE_SIZE_MB, MIN_IMAGE_SIZE_PX, VALID_EXTENSIONS
|
| 8 |
+
|
| 9 |
+
class FishImageValidator:
|
| 10 |
+
"""Comprehensive image validation with edge case handling"""
|
| 11 |
+
|
| 12 |
+
def __init__(self, max_size_mb=MAX_FILE_SIZE_MB,
|
| 13 |
+
min_size_px=MIN_IMAGE_SIZE_PX,
|
| 14 |
+
valid_extensions=VALID_EXTENSIONS):
|
| 15 |
+
self.max_size_mb = max_size_mb
|
| 16 |
+
self.min_size_px = min_size_px
|
| 17 |
+
self.valid_extensions = valid_extensions
|
| 18 |
+
|
| 19 |
+
def validate_file(self, file_path):
|
| 20 |
+
"""
|
| 21 |
+
Validate file exists, type, size, and image integrity
|
| 22 |
+
|
| 23 |
+
Returns:
|
| 24 |
+
tuple: (is_valid: bool, message: str, image: PIL.Image or None)
|
| 25 |
+
"""
|
| 26 |
+
# Check 1: File exists
|
| 27 |
+
if not os.path.exists(file_path):
|
| 28 |
+
return False, "β File not found", None
|
| 29 |
+
|
| 30 |
+
# Check 2: File extension
|
| 31 |
+
if not any(file_path.lower().endswith(ext.lower()) for ext in self.valid_extensions):
|
| 32 |
+
return False, f"β Invalid file type. Accepted: {', '.join(self.valid_extensions)}", None
|
| 33 |
+
|
| 34 |
+
# Check 3: File size
|
| 35 |
+
try:
|
| 36 |
+
file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
|
| 37 |
+
if file_size_mb > self.max_size_mb:
|
| 38 |
+
return False, f"β File too large ({file_size_mb:.1f}MB). Max: {self.max_size_mb}MB", None
|
| 39 |
+
except Exception as e:
|
| 40 |
+
return False, f"β Cannot read file: {e}", None
|
| 41 |
+
|
| 42 |
+
# Check 4: Valid image file
|
| 43 |
+
try:
|
| 44 |
+
img = Image.open(file_path)
|
| 45 |
+
img.verify() # Check for corruption
|
| 46 |
+
img = Image.open(file_path) # Re-open after verify (verify closes it)
|
| 47 |
+
img = img.convert('RGB') # Ensure RGB format
|
| 48 |
+
|
| 49 |
+
# Check 5: Image dimensions
|
| 50 |
+
if img.width < self.min_size_px or img.height < self.min_size_px:
|
| 51 |
+
return False, f"β Image too small ({img.width}x{img.height}px). Min: {self.min_size_px}x{self.min_size_px}px", None
|
| 52 |
+
|
| 53 |
+
return True, "β
File validation passed", img
|
| 54 |
+
|
| 55 |
+
except Exception as e:
|
| 56 |
+
return False, f"β Invalid or corrupted image: {str(e)}", None
|
| 57 |
+
|
| 58 |
+
def validate_with_gemini(self, image, gemini_model):
|
| 59 |
+
"""
|
| 60 |
+
AI-based validation with Gemini Vision
|
| 61 |
+
Handles edge cases: multiple fish, dead fish, toys, drawings, partial fish
|
| 62 |
+
Accepts dataset images with transparent or solid backgrounds
|
| 63 |
+
|
| 64 |
+
Returns:
|
| 65 |
+
tuple: (is_valid: bool, message: str)
|
| 66 |
+
"""
|
| 67 |
+
if gemini_model is None:
|
| 68 |
+
return True, "β οΈ Gemini validation disabled"
|
| 69 |
+
|
| 70 |
+
try:
|
| 71 |
+
prompt = """Analyze this image for fish disease diagnosis.
|
| 72 |
+
|
| 73 |
+
Answer these questions:
|
| 74 |
+
|
| 75 |
+
1. Is there a FISH visible in this image? (Can be a real photo, medical/diagnostic image, or isolated fish specimen on any background including transparent/solid backgrounds)
|
| 76 |
+
2. How many fish are in the image?
|
| 77 |
+
3. Is the fish body clearly visible (not just head or tail)?
|
| 78 |
+
4. Can you see enough fish detail for disease assessment?
|
| 79 |
+
|
| 80 |
+
Respond in this EXACT format:
|
| 81 |
+
CONTAINS_FISH: YES or NO
|
| 82 |
+
FISH_COUNT: [number]
|
| 83 |
+
BODY_VISIBLE: YES or NO
|
| 84 |
+
SUFFICIENT_DETAIL: YES or NO
|
| 85 |
+
REASON: [brief explanation if any answer is NO]
|
| 86 |
+
|
| 87 |
+
IMPORTANT NOTES:
|
| 88 |
+
- Isolated fish on transparent, white, or solid backgrounds ARE ACCEPTABLE (common in medical datasets)
|
| 89 |
+
- Focus on whether the FISH ITSELF is clear and detailed, not the background
|
| 90 |
+
- Reject only if it's clearly NOT a fish (toy, cartoon, drawing of non-fish subject)"""
|
| 91 |
+
|
| 92 |
+
response = gemini_model.generate_content([prompt, image])
|
| 93 |
+
answer = response.text.strip().upper()
|
| 94 |
+
|
| 95 |
+
# Parse and validate responses
|
| 96 |
+
|
| 97 |
+
# Check 1: Contains fish?
|
| 98 |
+
if "CONTAINS_FISH: NO" in answer:
|
| 99 |
+
reason = self._extract_reason(answer)
|
| 100 |
+
return False, f"β No fish detected. {reason}"
|
| 101 |
+
|
| 102 |
+
# Check 2: Fish count zero
|
| 103 |
+
if "FISH_COUNT: 0" in answer or "FISH_COUNT: NONE" in answer:
|
| 104 |
+
return False, "β No fish found in image"
|
| 105 |
+
|
| 106 |
+
# Check 3: Multiple fish
|
| 107 |
+
for i in range(2, 20):
|
| 108 |
+
if f"FISH_COUNT: {i}" in answer:
|
| 109 |
+
return False, "β Multiple fish detected. Upload single fish only"
|
| 110 |
+
|
| 111 |
+
# Check 4: Body visible
|
| 112 |
+
if "BODY_VISIBLE: NO" in answer:
|
| 113 |
+
return False, "β Fish body not clearly visible"
|
| 114 |
+
|
| 115 |
+
# Check 5: Sufficient detail
|
| 116 |
+
if "SUFFICIENT_DETAIL: NO" in answer:
|
| 117 |
+
reason = self._extract_reason(answer)
|
| 118 |
+
return False, f"β Insufficient detail for diagnosis. {reason}"
|
| 119 |
+
|
| 120 |
+
return True, "β
Valid fish image detected"
|
| 121 |
+
|
| 122 |
+
except Exception as e:
|
| 123 |
+
# Graceful degradation - log but don't block
|
| 124 |
+
print(f"β οΈ Gemini validation error: {e}")
|
| 125 |
+
return True, "β οΈ AI validation skipped (error occurred)"
|
| 126 |
+
|
| 127 |
+
@staticmethod
|
| 128 |
+
def _extract_reason(response_text):
|
| 129 |
+
"""Extract reason from Gemini response"""
|
| 130 |
+
if "REASON:" in response_text:
|
| 131 |
+
reason = response_text.split("REASON:")[-1].strip()
|
| 132 |
+
return reason[:150] # Limit length
|
| 133 |
+
return "See validation details"
|
models/vgg_resnet/results/vgg_resnet/vgg16_best.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:38c2a4998fbea8dd33d78afc318ad30da47adbf6ebda811673168f50dd2c6738
|
| 3 |
+
size 537185333
|
requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch==2.5.1
|
| 2 |
+
torchvision==0.20.1
|
| 3 |
+
gradio==5.7.1
|
| 4 |
+
google-generativeai==0.8.3
|
| 5 |
+
pillow==11.0.0
|
| 6 |
+
opencv-python-headless==4.10.0.84
|
| 7 |
+
numpy==1.26.4
|
| 8 |
+
python-dotenv==1.0.0
|