itsluckysharma01 commited on
Commit
b5f303c
·
verified ·
1 Parent(s): 29bbefa

Upload 57 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +2 -0
  2. README.md +874 -11
  3. app.py +625 -0
  4. models/1.h5 +3 -0
  5. models/1/fingerprint.pb +3 -0
  6. models/1/saved_model.pb +3 -0
  7. models/1/variables/variables.data-00000-of-00001 +3 -0
  8. models/1/variables/variables.index +0 -0
  9. models/4.keras +3 -0
  10. requirements.txt +7 -0
  11. runtime.txt +1 -0
  12. static/content/android-icon-144x144.png +0 -0
  13. static/content/android-icon-192x192.png +0 -0
  14. static/content/android-icon-36x36.png +0 -0
  15. static/content/android-icon-48x48.png +0 -0
  16. static/content/android-icon-72x72.png +0 -0
  17. static/content/android-icon-96x96.png +0 -0
  18. static/content/apple-icon-114x114.png +0 -0
  19. static/content/apple-icon-120x120.png +0 -0
  20. static/content/apple-icon-144x144.png +0 -0
  21. static/content/apple-icon-152x152.png +0 -0
  22. static/content/apple-icon-180x180.png +0 -0
  23. static/content/apple-icon-57x57.png +0 -0
  24. static/content/apple-icon-60x60.png +0 -0
  25. static/content/apple-icon-72x72.png +0 -0
  26. static/content/apple-icon-76x76.png +0 -0
  27. static/content/apple-icon-precomposed.png +0 -0
  28. static/content/apple-icon.png +0 -0
  29. static/content/browserconfig.xml +2 -0
  30. static/content/favicon-16x16.png +0 -0
  31. static/content/favicon-32x32.png +0 -0
  32. static/content/favicon-96x96.png +0 -0
  33. static/content/favicon.ico +0 -0
  34. static/content/manifest.json +41 -0
  35. static/content/ms-icon-144x144.png +0 -0
  36. static/content/ms-icon-150x150.png +0 -0
  37. static/content/ms-icon-310x310.png +0 -0
  38. static/content/ms-icon-70x70.png +0 -0
  39. static/css/style.css +1334 -0
  40. static/js/script.js +988 -0
  41. static/uploads/20250711_012123_1cd053f6-0016-4680-a924-af15aecd7fb2___RS_LB_4414.JPG +0 -0
  42. static/uploads/20250711_012557_0eb24a67-a174-43db-86c7-cca8795942a2___RS_LB_4722.JPG +0 -0
  43. static/uploads/20250711_014017_2f81d148-c62f-4d3c-baf4-72b77abea41a___RS_Early.B_7493.JPG +0 -0
  44. static/uploads/20250711_015310_1e671694-5713-4568-b8ad-06f15688d25e___RS_Early.B_7659.JPG +0 -0
  45. static/uploads/20250711_015412_0a79700b-f834-41f5-ae51-6ceda6f67a48___RS_Early.B_8951.JPG +0 -0
  46. static/uploads/20250711_022739_414f6249-9f78-4af5-9593-9d5a7e7d979f___RS_HL_1918.JPG +0 -0
  47. static/uploads/20250711_234352_2f7b6898-a342-42a5-a0e5-a9f2bad7eaf1___RS_LB_2831.JPG +0 -0
  48. static/uploads/20250711_234419_0e7f0484-16eb-4183-b702-0a5b4f94d015___RS_LB_4000.JPG +0 -0
  49. static/uploads/20250711_234838_early1.jpeg +0 -0
  50. static/uploads/20250711_234852_healthy.jpeg +0 -0
.gitattributes CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ models/1/variables/variables.data-00000-of-00001 filter=lfs diff=lfs merge=lfs -text
37
+ models/4.keras filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,11 +1,874 @@
1
- ---
2
- title: Potato Diseases Detection With Deep Learning
3
- emoji: 📈
4
- colorFrom: gray
5
- colorTo: red
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🥔 Potato Skin Disease Detection Using Deep Learning
2
+
3
+ [![Python](https://img.shields.io/badge/Python-3.8+-blue.svg)](https://www.python.org/)
4
+ [![TensorFlow](https://img.shields.io/badge/TensorFlow-2.x-orange.svg)](https://tensorflow.org/)
5
+ [![Keras](https://img.shields.io/badge/Keras-2.x-red.svg)](https://keras.io/)
6
+ [![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
7
+
8
+ > 🔬 An AI-powered computer vision system for detecting and classifying potato skin diseases using deep learning techniques.
9
+
10
+ ## 📋 Table of Contents
11
+
12
+ - [🎯 Project Overview](#-project-overview)
13
+ - [🌟 Features](#-features)
14
+ - [📊 Dataset](#-dataset)
15
+ - [🚀 Getting Started](#-getting-started)
16
+ - [💻 Usage](#-usage)
17
+ - [🏗️ Model Architecture](#-model-architecture)
18
+ - [📈 Results](#-results)
19
+ - [🚀 Next Steps](#-Next-Steps)
20
+ - [📄 License](#-license)
21
+
22
+ ## 🎯 Project Overview
23
+
24
+ This project implements a **Convolutional Neural Network (CNN)** using TensorFlow/Keras to automatically detect and classify potato skin diseases from digital images. The system can identify three main categories:
25
+
26
+ - 🍃 **Healthy Potatoes**
27
+ - 🦠 **Early Blight Disease**
28
+ - 🍄 **Late Blight Disease**
29
+
30
+ ### 🎥 Demo
31
+
32
+ <details>
33
+ <summary>Click to see sample predictions</summary>
34
+
35
+ ```
36
+ Input: potato_image.jpg
37
+ Output: "Early Blight Disease" (Confidence: 94.2%)
38
+ ```
39
+
40
+ </details>
41
+
42
+ ## 🌟 Features
43
+
44
+ - ✅ **Multi-class Classification**: Detects 3 types of potato conditions
45
+ - ✅ **Data Augmentation**: Improves model robustness with image transformations
46
+ - ✅ **Interactive Visualization**: Displays sample images with predictions
47
+ - ✅ **Optimized Performance**: Uses caching and prefetching for faster training
48
+ - ✅ **Scalable Architecture**: Easy to extend to more disease types
49
+ - ✅ **Real-time Inference**: Fast prediction on new images
50
+
51
+ ## 📊 Dataset
52
+
53
+ ### 📈 Dataset Statistics
54
+
55
+ - **Total Images**: 2,152
56
+ - **Classes**: 3 (Early Blight, Late Blight, Healthy)
57
+ - **Image Size**: 256×256 pixels
58
+ - **Color Channels**: RGB (3 channels)
59
+ - **Data Split**: 80% Train, 10% Validation, 10% Test
60
+
61
+ ## 📁 Project Structure
62
+
63
+ ```
64
+ potato-disease-detection/
65
+ ├── 📓 POTATO_Skin_Diseases_Detection_Using_Deep_Learning.ipynb
66
+ ├── 📄 README.md
67
+ ├── 📋 requirements.txt
68
+ ├── 📁 PlantVillage/
69
+ │ ├── 📁 Potato___Early_blight/
70
+ │ ├── 📁 Potato___Late_blight/
71
+ │ └── 📁 Potato___healthy/
72
+ ├── 📁 models/
73
+ │ └── 💾 trained_model.h5
74
+ └── 📁 results/
75
+ ├── 📊 training_plots.png
76
+ └── 📈 confusion_matrix.png
77
+ ```
78
+
79
+ 📂 Root Directory/
80
+ ├── 🐍 app.py # Main Flask application
81
+ ├── 📦 requirements.txt # Dependencies
82
+ ├── 🚀 run_flask_app.bat # Easy startup script
83
+ ├── 📚 README_Flask.md # Complete documentation
84
+ ├── 📂 templates/
85
+ │ └── 🌐 index.html # Web interface
86
+ └── 📂 static/
87
+ ├── 📂 css/
88
+ │ └── 💄 style.css # Beautiful styling
89
+ └── 📂 js/
90
+ └── ⚡ script.js # Interactive functionality
91
+
92
+ ## 🚀 Getting Started
93
+
94
+ ### 📋 Prerequisites
95
+
96
+ ```bash
97
+ Python 3.8+
98
+ TensorFlow 2.x
99
+ Matplotlib
100
+ NumPy
101
+ ```
102
+
103
+ ### ⚡ Quick Start and Installation
104
+
105
+ ### 🐍 Environment Setup
106
+
107
+ ```bash
108
+ # Create virtual environment
109
+ python -m venv potato_env
110
+
111
+ # Activate environment
112
+ # Windows:
113
+ potato_env\Scripts\activate
114
+ # macOS/Linux:
115
+ source potato_env/bin/activate
116
+
117
+ # Install packages
118
+ pip install -r requirements.txt
119
+ ```
120
+
121
+ # Run Application
122
+
123
+ #### **Step 1: Install Dependencies**
124
+
125
+ ```cmd
126
+ pip install -r requirements.txt
127
+ ```
128
+
129
+ #### **Step 2: Run the Application**
130
+
131
+ ```cmd
132
+ python app.py
133
+ ```
134
+
135
+ #### **Step 3: Open Your Browser**
136
+
137
+ - **Main App**: http://localhost:5000
138
+ - **Health Check**: http://localhost:5000/health
139
+
140
+ ## 💻 Usage
141
+
142
+ ### 🔧 Training the Model
143
+
144
+ The notebook includes the complete pipeline:
145
+
146
+ 1. **Data Loading & Preprocessing**
147
+
148
+ ```python
149
+ # Load dataset
150
+ dataset = tf.keras.preprocessing.image_dataset_from_directory(
151
+ "PlantVillage",
152
+ image_size=(256, 256),
153
+ batch_size=32
154
+ )
155
+ ```
156
+
157
+ 2. **Data Augmentation**
158
+
159
+ ```python
160
+ # Apply data augmentation
161
+ data_augmentation = tf.keras.Sequential([
162
+ tf.keras.layers.RandomFlip("horizontal_and_vertical"),
163
+ tf.keras.layers.RandomRotation(0.2)
164
+ ])
165
+ ```
166
+
167
+ 3. **Model Configuration**
168
+ ```python
169
+ IMAGE_SIZE = 256
170
+ BATCH_SIZE = 32
171
+ CHANNELS = 3
172
+ EPOCHS = 50
173
+ ```
174
+
175
+ ### 🎯 Making Predictions
176
+
177
+ ```python
178
+ # Load your trained model
179
+ model = tf.keras.models.load_model('potato_disease_model.h5')
180
+
181
+ # Make prediction
182
+ prediction = model.predict(new_image)
183
+ predicted_class = class_names[np.argmax(prediction)]
184
+ ```
185
+
186
+ ## 🏗️ Model Architecture
187
+
188
+ ### 🧠 Network Components
189
+
190
+ 1. **Input Layer**: 256×256×3 RGB images
191
+ 2. **Preprocessing**:
192
+ - Image resizing and rescaling (1.0/255)
193
+ - Data augmentation (RandomFlip, RandomRotation)
194
+ 3. **Feature Extraction**: CNN layers for pattern recognition
195
+ 4. **Classification**: Dense layers for final prediction
196
+
197
+ ### ⚙️ Training Configuration
198
+
199
+ - **Optimizer**: Adam (recommended)
200
+ - **Loss Function**: Sparse Categorical Crossentropy
201
+ - **Metrics**: Accuracy
202
+ - **Epochs**: 50
203
+ - **Batch Size**: 32
204
+
205
+ ## 📈 Results
206
+
207
+ ### 📊 Performance Metrics
208
+
209
+ | Metric | Score |
210
+ | ------------------- | ----- |
211
+ | Training Accuracy | XX.X% |
212
+ | Validation Accuracy | XX.X% |
213
+ | Test Accuracy | XX.X% |
214
+ | F1-Score | XX.X% |
215
+
216
+ ### 🎨 Visualization
217
+
218
+ The notebook includes:
219
+
220
+ - ✅ Sample image visualization
221
+ - ✅ Training/validation loss curves
222
+ - ✅ Confusion matrix
223
+ - ✅ Class-wise accuracy
224
+
225
+ # 🥔 Potato Disease Detection - Flask Web Application
226
+
227
+ A modern Flask web application for detecting potato diseases using deep learning. Upload images or use your camera to get instant disease predictions with confidence scores and treatment recommendations.
228
+
229
+ ## ✨ Features
230
+
231
+ ### 🖼️ **Dual Input Methods**
232
+
233
+ - **📁 File Upload**: Drag & drop or browse to select images
234
+ - **📸 Camera Capture**: Take photos directly from your device camera
235
+
236
+ ### 🧠 **AI-Powered Detection**
237
+
238
+ - **🎯 Accurate Predictions**: Uses trained CNN model for disease detection
239
+ - **📊 Confidence Scores**: Shows prediction confidence with color-coded badges
240
+ - **📈 Probability Breakdown**: Displays probabilities for all disease classes
241
+
242
+ ### 💡 **Smart Recommendations**
243
+
244
+ - **🏥 Treatment Advice**: Provides specific recommendations for each condition
245
+ - **🚨 Urgency Levels**: Different advice based on disease severity
246
+ - **📋 Downloadable Reports**: Generate and download analysis reports
247
+
248
+ ### 🎨 **Modern Interface**
249
+
250
+ - **📱 Responsive Design**: Works perfectly on mobile and desktop
251
+ - **🌟 Beautiful UI**: Modern design with smooth animations
252
+ - **🔄 Real-time Analysis**: Instant predictions with loading indicators
253
+
254
+ ## 🦠 Detected Diseases
255
+
256
+ 1. **🍂 Early Blight** - Common fungal disease affecting potato leaves
257
+ 2. **💀 Late Blight** - Serious disease that can destroy entire crops
258
+ 3. **✅ Healthy** - No disease detected
259
+
260
+ ## 🎯 How to Use
261
+
262
+ ### **📁 Upload Method**
263
+
264
+ 1. **Select Upload** tab (default)
265
+ 2. **Drag & drop** an image or **click to browse**
266
+ 3. **Click "Analyze Disease"** button
267
+ 4. **View results** with predictions and recommendations
268
+
269
+ ### **📸 Camera Method**
270
+
271
+ 1. **Click Camera** tab
272
+ 2. **Click "Start Camera"** (allow permissions)
273
+ 3. **Click "Capture Photo"** when ready
274
+ 4. **Click "Analyze Disease"** button
275
+ 5. **View results** with predictions and recommendations
276
+
277
+ ### **📊 Understanding Results**
278
+
279
+ - **🎯 Primary Diagnosis**: Main prediction with confidence score
280
+ - **📈 Probability Breakdown**: All disease probabilities
281
+ - **💡 Recommendations**: Treatment and care advice
282
+ - **📋 Download Report**: Save results as text file
283
+
284
+ ## 🔧 Technical Details
285
+
286
+ - **🐍 Backend**: Flask 2.3+ with Python
287
+ - **🧠 AI Model**: TensorFlow/Keras CNN
288
+ - **🖼️ Image Processing**: PIL/Pillow for preprocessing
289
+ - **🎨 Frontend**: HTML5, CSS3, Vanilla JavaScript
290
+ - **📱 Camera**: WebRTC getUserMedia API
291
+ - **💾 Storage**: Local file system for uploads
292
+
293
+ ## 📋 Requirements
294
+
295
+ - **🐍 Python**: 3.8+ (Recommended: 3.10+)
296
+ - **💻 OS**: Windows, macOS, or Linux
297
+ - **🧠 Memory**: 4GB+ RAM (8GB recommended)
298
+ - **💾 Storage**: ~2GB for dependencies and models
299
+ - **🌐 Browser**: Chrome, Firefox, Safari, Edge (latest versions)
300
+
301
+ ## 🛠️ Troubleshooting
302
+
303
+ ### ❌ **Model Not Loading**
304
+
305
+ ```
306
+ Error: Model not loaded! Please check the model file path.
307
+ ```
308
+
309
+ **Solution:**
310
+
311
+ - Ensure `models/1.h5` exists
312
+ - Check TensorFlow installation: `pip install tensorflow>=2.13.0`
313
+
314
+ ### ❌ **Camera Not Working**
315
+
316
+ ```
317
+ Could not access camera. Please check permissions.
318
+ ```
319
+
320
+ **Solution:**
321
+
322
+ - Allow camera permissions in your browser
323
+ - Use HTTPS for camera access (or localhost)
324
+ - Check if another app is using the camera
325
+
326
+ ### ❌ **Port Already in Use**
327
+
328
+ ```
329
+ Address already in use
330
+ ```
331
+
332
+ **Solution:**
333
+
334
+ - Close other Flask applications
335
+ - Change port in `app.py`: `app.run(port=5001)`
336
+ - Kill process: `taskkill /f /im python.exe` (Windows)
337
+
338
+ ### ❌ **File Upload Issues**
339
+
340
+ ```
341
+ Invalid file type or File too large
342
+ ```
343
+
344
+ **Solution:**
345
+
346
+ - Use supported formats: PNG, JPG, JPEG
347
+ - Keep file size under 16MB
348
+ - Check image isn't corrupted
349
+
350
+ ## 🎨 Customization
351
+
352
+ ### **🎯 Add New Disease Classes**
353
+
354
+ 1. Update `CLASS_NAMES` in `app.py`
355
+ 2. Add descriptions in `CLASS_DESCRIPTIONS`
356
+ 3. Update recommendations in `get_recommendations()`
357
+ 4. Retrain model with new classes
358
+
359
+ ## 📱 Mobile Responsiveness
360
+
361
+ The application is now **fully responsive** and optimized for mobile devices:
362
+
363
+ ### 📲 Mobile Features:
364
+
365
+ - ✅ **Touch-friendly interface** with larger touch targets (44px minimum)
366
+ - ✅ **Responsive design** that adapts to screen sizes from 320px to desktop
367
+ - ✅ **Mobile camera support** with environment (back) camera preference
368
+ - ✅ **Optimized image display** for mobile viewports
369
+ - ✅ **Landscape/Portrait orientation** support
370
+ - ✅ **iOS Safari compatibility** with viewport fixes
371
+ - ✅ **Prevent accidental zoom** on form inputs
372
+ - ✅ **Touch-optimized drag & drop** for file uploads
373
+
374
+ ### **🎨 Modify UI**
375
+
376
+ - **Colors**: Edit CSS variables in `style.css`
377
+ - **Layout**: Modify templates in `templates/`
378
+ - **Functionality**: Update JavaScript in `static/js/`
379
+
380
+ ### **⚙️ Configuration**
381
+
382
+ - **Upload size**: Change `MAX_CONTENT_LENGTH` in `app.py`
383
+ - **Image size**: Modify `IMAGE_SIZE` parameter
384
+ - **Port**: Update `app.run(port=5000)` line
385
+
386
+ ## 🔒 Security Notes
387
+
388
+ - **🚫 Production Use**: This is for development/research only
389
+ - **🔐 Secret Key**: Change `app.secret_key` for production
390
+ - **📁 File Validation**: Only accepts image files
391
+ - **💾 File Cleanup**: Consider automatic cleanup of old uploads
392
+
393
+ ## 📈 Performance Tips
394
+
395
+ - **📸 Image Quality**: Use clear, well-lit potato leaf images
396
+ - **🎯 Focus**: Ensure leaves fill most of the frame
397
+ - **📏 Size**: Optimal size is 256x256 pixels or larger
398
+ - **🌟 Lighting**: Good natural lighting gives best results
399
+
400
+ ## 🌐 Browser Compatibility
401
+
402
+ - ✅ **Chrome**: 90+
403
+ - ✅ **Firefox**: 88+
404
+ - ✅ **Safari**: 14+
405
+ - ✅ **Edge**: 90+
406
+ - ⚠️ **Mobile**: Camera features may vary
407
+
408
+ ## 📄 API Endpoints
409
+
410
+ - `GET /` - Main web interface
411
+ - `POST /predict` - Upload image prediction
412
+ - `POST /predict_camera` - Camera image prediction
413
+ - `GET /health` - Application health check
414
+
415
+ ## 🤝 Support
416
+
417
+ For issues or questions:
418
+
419
+ 1. Check the troubleshooting section above
420
+ 2. Verify your Python and dependencies versions
421
+ 3. Ensure model files are in the correct location
422
+ 4. Test with the provided sample images
423
+
424
+ ---
425
+
426
+ ## 🚀 Next Steps
427
+
428
+ ### 🔮 Future Enhancements
429
+
430
+ - [ ] **Model Optimization**: Implement transfer learning with pre-trained models
431
+ - [ ] **Web Application**: Create a Flask/Streamlit web interface
432
+ - [ ] **Mobile App**: Develop a mobile application for field use
433
+ - [ ] **More Diseases**: Expand to detect additional potato diseases
434
+ - [ ] **Real-time Detection**: Implement live camera feed processing
435
+ - [ ] **API Development**: Create REST API for integration
436
+
437
+ ### 🎯 Improvement Ideas
438
+
439
+ - [ ] **Hyperparameter Tuning**: Optimize model parameters
440
+ - [ ] **Cross-validation**: Implement k-fold cross-validation
441
+ - [ ] **Ensemble Methods**: Combine multiple models
442
+ - [ ] **Data Balancing**: Handle class imbalance if present
443
+
444
+ ### 🐛 Bug Reports
445
+
446
+ If you find a bug, please create an issue with:
447
+
448
+ - Description of the problem
449
+ - Steps to reproduce
450
+ - Expected vs actual behavior
451
+ - System information
452
+
453
+ ### 💡 Feature Requests
454
+
455
+ For new features, please provide:
456
+
457
+ - Clear description of the feature
458
+ - Use case and benefits
459
+ - Implementation suggestions```
460
+
461
+ # ==================DEBUGGING AND TROUBLESHOOTING GUIDE:===========================
462
+
463
+ # 🥔 Potato Disease Detection - Upload Functionality Guide
464
+
465
+ ## 🚀 Quick Start
466
+
467
+ 1. **Run the Application**:
468
+
469
+ ```bash
470
+ python app.py
471
+ ```
472
+
473
+ Or double-click `run_and_test.bat`
474
+
475
+ 2. **Access the App**:
476
+ - Main app: http://localhost:5000
477
+ - Debug upload page: http://localhost:5000/debug
478
+ - Health check: http://localhost:5000/health
479
+
480
+ ## 📋 Testing Upload Functionality
481
+
482
+ ### Step 1: Check System Health
483
+
484
+ 1. Go to http://localhost:5000/debug
485
+ 2. Click "🔍 Check System Health"
486
+ 3. Verify all items show ✅:
487
+ - Status: healthy
488
+ - Model Loaded: Yes
489
+ - Upload Dir Exists: Yes
490
+ - Upload Dir Writable: Yes
491
+
492
+ ### Step 2: Test Upload Directory
493
+
494
+ 1. Click "📂 Test Upload Directory"
495
+ 2. Should show "Upload directory is working correctly"
496
+
497
+ ### Step 3: Test Image Upload
498
+
499
+ 1. Click "📁 Click here to select an image" or drag an image
500
+ 2. Select a potato leaf image (JPG, PNG, JPEG)
501
+ 3. Preview should appear
502
+ 4. Click "🔬 Analyze Disease"
503
+ 5. Results should show:
504
+ - Disease name and confidence
505
+ - Recommendations
506
+ - The analyzed image displayed
507
+
508
+ ## 🔧 Troubleshooting Upload Issues
509
+
510
+ ### Issue: "No file uploaded" Error
511
+
512
+ **Solutions:**
513
+
514
+ 1. Ensure you're clicking the upload area or browse link
515
+ 2. Check browser console for JavaScript errors (F12)
516
+ 3. Try the debug page: http://localhost:5000/debug
517
+ 4. **Mobile**: Tap firmly on upload area, wait for file picker
518
+
519
+ ### Issue: File Not Saving
520
+
521
+ **Solutions:**
522
+
523
+ 1. Check upload directory permissions:
524
+ ```bash
525
+ mkdir static/uploads
526
+ ```
527
+ 2. Run as administrator if on Windows
528
+ 3. Check disk space
529
+ 4. **Mobile**: Ensure stable network connection
530
+
531
+ ### Issue: Camera Not Working (Mobile)
532
+
533
+ **Solutions:**
534
+
535
+ 1. **Grant camera permissions** when prompted
536
+ 2. **Use HTTPS** for camera access on mobile (required by browsers)
537
+ 3. **Check camera availability** - some devices block camera access
538
+ 4. **Try different browsers** (Chrome/Safari work best)
539
+ 5. **Close other camera apps** that might be using the camera
540
+
541
+ ### Issue: Touch/Tap Not Working (Mobile)
542
+
543
+ **Solutions:**
544
+
545
+ 1. **Clear browser cache** and reload
546
+ 2. **Disable browser zoom** if enabled
547
+ 3. **Try two-finger tap** if single tap doesn't work
548
+ 4. **Check touch targets** - buttons should be at least 44px
549
+ 5. **Restart browser app** on mobile device
550
+
551
+ ### Issue: Image Too Small/Large on Mobile
552
+
553
+ **Solutions:**
554
+
555
+ 1. **Portrait orientation** usually works better
556
+ 2. **Pinch to zoom** on images if needed
557
+ 3. **Landscape mode** available for wider screens
558
+ 4. **Image auto-resizes** based on screen size
559
+
560
+ ### Issue: Slow Performance on Mobile
561
+
562
+ **Solutions:**
563
+
564
+ 1. **Close other browser tabs** to free memory
565
+ 2. **Use smaller image files** (under 5MB recommended)
566
+ 3. **Ensure good network connection** for uploads
567
+ 4. **Clear browser cache** regularly
568
+ 5. **Restart browser** if app becomes unresponsive
569
+
570
+ ### Issue: Model Not Loading
571
+
572
+ **Solutions:**
573
+
574
+ 1. Verify model file exists: `models/1.h5`
575
+ 2. Install required packages:
576
+ ```bash
577
+ pip install tensorflow pillow flask
578
+ ```
579
+
580
+ ### Issue: JavaScript Errors
581
+
582
+ **Solutions:**
583
+
584
+ 1. Clear browser cache (Ctrl+F5)
585
+ 2. Check browser console (F12)
586
+ 3. Try a different browser
587
+ 4. Disable browser extensions
588
+
589
+ ### Issue: Image Not Displaying in Results
590
+
591
+ **Solutions:**
592
+
593
+ 1. Check browser network tab (F12) for failed requests
594
+ 2. Verify uploaded file in `static/uploads/` folder
595
+ 3. Check Flask console for file save errors
596
+
597
+ ## 🧪 Debug Features
598
+
599
+ ### Console Logging
600
+
601
+ The JavaScript includes extensive console logging. Open browser developer tools (F12) to see:
602
+
603
+ - File selection events
604
+ - Upload progress
605
+ - Server responses
606
+ - Error details
607
+
608
+ ### Debug Endpoints
609
+
610
+ - `/health` - System status
611
+ - `/debug/upload-test` - Upload directory test
612
+ - `/debug` - Interactive upload test page
613
+
614
+ ### Manual Testing
615
+
616
+ 1. **File Input Test**:
617
+
618
+ ```javascript
619
+ document.getElementById("fileInput").click();
620
+ ```
621
+
622
+ 2. **Check Selected File**:
623
+
624
+ ```javascript
625
+ console.log(selectedFile);
626
+ ```
627
+
628
+ 3. **Test FormData**:
629
+ ```javascript
630
+ const formData = new FormData();
631
+ formData.append("file", selectedFile);
632
+ console.log([...formData.entries()]);
633
+ ```
634
+
635
+ ## 💡 Tips for Success
636
+
637
+ 1. **Use supported image formats**: JPG, PNG, JPEG, GIF
638
+ 2. **Keep file size under 16MB**
639
+ 3. **Use clear potato leaf images**
640
+ 4. **Check browser compatibility** (modern browsers work best)
641
+ 5. **Enable JavaScript**
642
+ 6. **Allow camera permissions** (for camera capture feature)
643
+
644
+ ## 🆘 Getting Help
645
+
646
+ If upload functionality still doesn't work:
647
+
648
+ 1. **Check Flask console output** for error messages
649
+ 2. **Check browser console** (F12 → Console tab)
650
+ 3. **Try the debug page** at `/debug`
651
+ 4. **Test with different image files**
652
+ 5. **Restart the Flask app**
653
+ 6. **Check file permissions** on the upload directory
654
+
655
+ ## 🎯 Expected Results
656
+
657
+ After successful upload and analysis:
658
+
659
+ - ✅ Disease classification (Early Blight, Late Blight, or Healthy)
660
+ - ✅ Confidence percentage
661
+ - ✅ Treatment recommendations
662
+ - ✅ Analyzed image displayed in results
663
+ - ✅ Timestamp of analysis
664
+
665
+ # PDF Report Download Upgrade Guide
666
+
667
+ ## 🎉 New Features Added
668
+
669
+ ### ✨ **PDF Format**
670
+
671
+ - Professional PDF reports instead of simple text files
672
+ - Includes header, footer, tables, and proper formatting
673
+ - Company branding and professional layout
674
+
675
+ ### 📁 **Folder Selection**
676
+
677
+ - Choose where to save your PDF reports
678
+ - Modern file picker dialog (supported browsers)
679
+ - Automatic fallback to default downloads folder
680
+
681
+ ### 🎨 **Enhanced Report Content**
682
+
683
+ - **Report Header**: Timestamp, analysis method, model version
684
+ - **Analyzed Image**: Embedded image (if available)
685
+ - **Diagnosis Section**: Disease name, confidence, risk assessment
686
+ - **Probability Breakdown**: Table showing all class probabilities
687
+ - **Treatment Recommendations**: Numbered list of actionable advice
688
+ - **Professional Footer**: Branding and copyright information
689
+
690
+ ## 🚀 Installation Requirements
691
+
692
+ Add to your `requirements.txt`:
693
+
694
+ ```
695
+ reportlab>=4.0.0
696
+ ```
697
+
698
+ Install the new dependency:
699
+
700
+ ```bash
701
+ pip install reportlab>=4.0.0
702
+ ```
703
+
704
+ # PDF Generation Troubleshooting Guide
705
+
706
+ ## 🔧 If PDF Generation is Failing
707
+
708
+ ### Quick Fix Steps
709
+
710
+ 1. **Install ReportLab Library**
711
+
712
+ ```bash
713
+ pip install reportlab>=4.0.0
714
+ ```
715
+
716
+ 2. **Run Installation Script**
717
+
718
+ - **Windows**: Double-click `install_pdf_deps.bat`
719
+ - **Linux/Mac**: Run `bash install_pdf_deps.sh`
720
+
721
+ 3. **Restart the Application**
722
+ ```bash
723
+ python app.py
724
+ ```
725
+
726
+ ### Common Issues and Solutions
727
+
728
+ #### ❌ **"ReportLab not available" Error**
729
+
730
+ **Problem**: ReportLab library is not installed.
731
+
732
+ **Solution**:
733
+
734
+ ```bash
735
+ pip install reportlab
736
+ # or
737
+ pip install reportlab>=4.0.0
738
+ ```
739
+
740
+ **Alternative**: Use virtual environment
741
+
742
+ ```bash
743
+ python -m venv pdf_env
744
+ source pdf_env/bin/activate # Linux/Mac
745
+ # or
746
+ pdf_env\Scripts\activate # Windows
747
+ pip install reportlab
748
+ ```
749
+
750
+ #### ❌ **"Permission denied" or "Access denied" Errors**
751
+
752
+ **Problem**: Insufficient permissions to install packages.
753
+
754
+ **Solutions**:
755
+
756
+ 1. **Use --user flag**:
757
+
758
+ ```bash
759
+ pip install --user reportlab
760
+ ```
761
+
762
+ 2. **Run as administrator** (Windows):
763
+
764
+ - Right-click Command Prompt → "Run as administrator"
765
+ - Then run: `pip install reportlab`
766
+
767
+ 3. **Use sudo** (Linux/Mac):
768
+ ```bash
769
+ sudo pip install reportlab
770
+ ```
771
+
772
+ #### ❌ **"Module not found" Error Despite Installation**
773
+
774
+ **Problem**: ReportLab installed in different Python environment.
775
+
776
+ **Solutions**:
777
+
778
+ 1. **Check Python version**:
779
+
780
+ ```bash
781
+ python --version
782
+ which python # Linux/Mac
783
+ where python # Windows
784
+ ```
785
+
786
+ 2. **Install for specific Python version**:
787
+
788
+ ```bash
789
+ python3 -m pip install reportlab
790
+ # or
791
+ python3.9 -m pip install reportlab
792
+ ```
793
+
794
+ 3. **Verify installation**:
795
+ ```bash
796
+ python -c "import reportlab; print('ReportLab available')"
797
+ ```
798
+
799
+ #### ❌ **PDF Generation Works but Images Missing**
800
+
801
+ **Problem**: Image files not accessible or corrupted.
802
+
803
+ **Solutions**:
804
+
805
+ 1. **Check upload folder permissions**:
806
+
807
+ ```bash
808
+ ls -la static/uploads/ # Linux/Mac
809
+ dir static\uploads\ # Windows
810
+ ```
811
+
812
+ 2. **Verify image exists**:
813
+
814
+ - Check browser developer tools for 404 errors
815
+ - Ensure images are properly saved during upload
816
+
817
+ 3. **Check image format**:
818
+ - Ensure images are JPG, PNG, or supported formats
819
+ - ReportLab may have issues with some image formats
820
+
821
+ #### ❌ **Client-side PDF Generation Fails**
822
+
823
+ **Problem**: jsPDF library not loading.
824
+
825
+ **Solutions**:
826
+
827
+ 1. **Check internet connection** (jsPDF loads from CDN)
828
+
829
+ 2. **Check browser console** for JavaScript errors
830
+
831
+ #### ❌ **Folder Selection Not Working**
832
+
833
+ **Problem**: File System Access API not supported.
834
+
835
+ **Solutions**:
836
+
837
+ 1. **Update browser**:
838
+
839
+ - Chrome 86+ or Edge 86+ required for folder selection
840
+ - Firefox and Safari will use default download folder
841
+
842
+ 2. **Enable experimental features** (Chrome):
843
+
844
+ - Go to `chrome://flags`
845
+ - Enable "Experimental Web Platform features"
846
+
847
+ 3. **Accept automatic download** to default folder
848
+
849
+ The system should work with any clear image of a potato plant leaf!
850
+
851
+ ## 📄 License
852
+
853
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
854
+
855
+ ## 🙏 Acknowledgments
856
+
857
+ - **PlantVillage Dataset**: For providing the potato disease dataset
858
+ - **TensorFlow Team**: For the amazing deep learning framework
859
+ - **Open Source Community**: For inspiration and resources
860
+
861
+ ## 📞 Contact
862
+
863
+ - **Author**: Lucky Sharma
864
+ - **Email**: [email protected]
865
+ - **LinkedIn**: https://www.linkedin.com/in/lucky-sharma918894599977
866
+ - **GitHub**: https://github.com/itsluckysharma01
867
+
868
+ ---
869
+
870
+ <div align="center">
871
+ <p>⭐ Star this repository if you found it helpful!</p>
872
+ <p>🍀 Happy coding and may your potatoes be healthy!</p>
873
+ </div>
874
+ "
app.py ADDED
@@ -0,0 +1,625 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ import tensorflow as tf
4
+ from flask import Flask, render_template, request, jsonify, redirect, url_for, flash, send_from_directory, send_file
5
+ from werkzeug.utils import secure_filename
6
+ from PIL import Image
7
+ import io
8
+ import base64
9
+ from datetime import datetime
10
+ import tempfile
11
+
12
+ # PDF generation dependencies with error handling
13
+ try:
14
+ from reportlab.lib.pagesizes import letter, A4
15
+ from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image as RLImage, Table, TableStyle
16
+ from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
17
+ from reportlab.lib.units import inch
18
+ from reportlab.lib import colors
19
+ from reportlab.lib.enums import TA_CENTER, TA_LEFT
20
+ REPORTLAB_AVAILABLE = True
21
+ print("✅ ReportLab library loaded successfully!")
22
+ except ImportError as e:
23
+ REPORTLAB_AVAILABLE = False
24
+ print(f"⚠️ ReportLab not available: {e}")
25
+ print("📝 PDF generation will use client-side fallback only")
26
+
27
+ app = Flask(__name__)
28
+ app.secret_key = 'your-secret-key-here' # Change this to a secure secret key
29
+
30
+ # Configuration
31
+ UPLOAD_FOLDER = 'static/uploads'
32
+ ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
33
+ MAX_CONTENT_LENGTH = 16 * 1024 * 1024 # 16MB max file size
34
+
35
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
36
+ app.config['MAX_CONTENT_LENGTH'] = MAX_CONTENT_LENGTH
37
+
38
+ # Create upload directory if it doesn't exist
39
+ os.makedirs(UPLOAD_FOLDER, exist_ok=True)
40
+ os.makedirs('static/css', exist_ok=True)
41
+ os.makedirs('static/js', exist_ok=True)
42
+ os.makedirs('templates', exist_ok=True)
43
+
44
+ @app.route('/uploads/<filename>')
45
+ def uploaded_file(filename):
46
+ """Serve uploaded files"""
47
+ return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
48
+
49
+ # Load the trained model
50
+ MODEL_PATH = "models/1.h5" # Update this path if needed
51
+ try:
52
+ model = tf.keras.models.load_model(MODEL_PATH)
53
+ print("✅ Model loaded successfully!")
54
+ MODEL_LOADED = True
55
+ except Exception as e:
56
+ print(f"❌ Error loading model: {e}")
57
+ MODEL_LOADED = False
58
+ model = None
59
+
60
+ # Class names from your training (must match the exact order from training)
61
+ # Training order: ['Potato___Early_blight', 'Potato___Late_blight', 'Potato___healthy']
62
+ CLASS_NAMES = ["Potato___Early_blight", "Potato___Late_blight", "Potato___healthy"]
63
+ CLASS_DISPLAY_NAMES = ["Early Blight", "Late Blight", "Healthy"]
64
+ CLASS_DESCRIPTIONS = {
65
+ "Potato___Early_blight": "A common fungal disease that causes dark spots on potato leaves. Treatment with copper-based fungicides is recommended.",
66
+ "Potato___Late_blight": "A serious disease caused by Phytophthora infestans. Immediate action required - remove infected plants and apply appropriate fungicides.",
67
+ "Potato___healthy": "The potato plant appears healthy with no signs of disease detected. Continue good agricultural practices."
68
+ }
69
+
70
+ # Image preprocessing parameters
71
+ IMAGE_SIZE = 256
72
+
73
+ def allowed_file(filename):
74
+ """Check if file extension is allowed"""
75
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
76
+
77
+ def preprocess_image(image):
78
+ """Preprocess the uploaded image for prediction"""
79
+ try:
80
+ # Convert to RGB if necessary
81
+ if image.mode != "RGB":
82
+ image = image.convert("RGB")
83
+
84
+ # Resize image
85
+ image = image.resize((IMAGE_SIZE, IMAGE_SIZE))
86
+
87
+ # Convert to numpy array
88
+ img_array = np.array(image)
89
+
90
+ # DO NOT normalize here - the model has built-in rescaling layer
91
+ # The model expects pixel values in range [0, 255]
92
+ # img_array = img_array / 255.0 # Removed this line
93
+
94
+ # Add batch dimension
95
+ img_array = np.expand_dims(img_array, axis=0)
96
+
97
+ # Debug: Print image statistics
98
+ print(f"Image shape: {img_array.shape}")
99
+ print(f"Image pixel range: [{img_array.min():.2f}, {img_array.max():.2f}]")
100
+
101
+ return img_array
102
+ except Exception as e:
103
+ print(f"Error preprocessing image: {e}")
104
+ return None
105
+
106
+ def predict_disease(image):
107
+ """Make prediction on the preprocessed image"""
108
+ if not MODEL_LOADED or model is None:
109
+ return {"error": "Model not loaded"}
110
+
111
+ try:
112
+ # Preprocess image
113
+ processed_image = preprocess_image(image)
114
+ if processed_image is None:
115
+ return {"error": "Failed to preprocess image"}
116
+
117
+ # Make prediction
118
+ predictions = model.predict(processed_image)
119
+ predicted_class_index = np.argmax(predictions[0])
120
+ confidence = float(np.max(predictions[0]))
121
+
122
+ # Debug: Print prediction details
123
+ print(f"Raw predictions: {predictions[0]}")
124
+ print(f"Predicted class index: {predicted_class_index}")
125
+ print(f"Confidence: {confidence:.4f}")
126
+
127
+ # Get class name
128
+ predicted_class = CLASS_NAMES[predicted_class_index]
129
+ predicted_display_name = CLASS_DISPLAY_NAMES[predicted_class_index]
130
+
131
+ # Create detailed results
132
+ all_predictions = {}
133
+ for i, (class_name, display_name) in enumerate(zip(CLASS_NAMES, CLASS_DISPLAY_NAMES)):
134
+ all_predictions[display_name] = {
135
+ 'probability': round(float(predictions[0][i]) * 100, 2),
136
+ 'description': CLASS_DESCRIPTIONS[class_name]
137
+ }
138
+
139
+ return {
140
+ "predicted_class": predicted_display_name,
141
+ "confidence": round(confidence * 100, 2),
142
+ "description": CLASS_DESCRIPTIONS[predicted_class],
143
+ "all_predictions": all_predictions,
144
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
145
+ }
146
+
147
+ except Exception as e:
148
+ print(f"Prediction error: {e}")
149
+ return {"error": f"Prediction failed: {str(e)}"}
150
+
151
+ def get_recommendations(disease_name, confidence):
152
+ """Get treatment recommendations based on prediction"""
153
+ recommendations = {
154
+ 'Early Blight': [
155
+ "Remove affected leaves immediately and dispose properly",
156
+ "Apply copper-based fungicide spray",
157
+ "Improve air circulation around plants",
158
+ "Avoid overhead watering",
159
+ "Consider crop rotation for next season"
160
+ ],
161
+ 'Late Blight': [
162
+ "URGENT: Remove and destroy infected plants immediately",
163
+ "Apply systemic fungicides (metalaxyl-based)",
164
+ "Monitor weather conditions closely",
165
+ "Increase plant spacing for better air circulation",
166
+ "Harvest healthy tubers as soon as possible"
167
+ ],
168
+ 'Healthy': [
169
+ "Continue current care practices",
170
+ "Maintain proper watering schedule",
171
+ "Monitor plants regularly for early signs of disease",
172
+ "Ensure good soil drainage",
173
+ "Apply balanced fertilizer as needed"
174
+ ]
175
+ }
176
+ return recommendations.get(disease_name, ["Consult agricultural expert for specific advice"])
177
+
178
+ @app.route('/')
179
+ def index():
180
+ """Main page"""
181
+ return render_template('index.html', model_loaded=MODEL_LOADED)
182
+
183
+ @app.route('/test')
184
+ def test_upload():
185
+ """Simple upload test page"""
186
+ return render_template('test_upload.html')
187
+
188
+ @app.route('/debug')
189
+ def debug_upload():
190
+ """Debug upload test page"""
191
+ return render_template('debug_upload.html')
192
+
193
+ @app.route('/predict', methods=['POST'])
194
+ def predict():
195
+ """Handle image upload and prediction"""
196
+ try:
197
+ print(f"Received prediction request. Files: {list(request.files.keys())}")
198
+
199
+ if 'file' not in request.files:
200
+ print("No file in request")
201
+ return jsonify({'error': 'No file uploaded'}), 400
202
+
203
+ file = request.files['file']
204
+ print(f"File received: {file.filename}, size: {file.content_length if hasattr(file, 'content_length') else 'unknown'}")
205
+
206
+ if file.filename == '':
207
+ print("Empty filename")
208
+ return jsonify({'error': 'No file selected'}), 400
209
+
210
+ if not allowed_file(file.filename):
211
+ print(f"Invalid file type: {file.filename}")
212
+ return jsonify({'error': 'Invalid file type. Please upload PNG, JPG, or JPEG files.'}), 400
213
+
214
+ # Ensure upload directory exists
215
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
216
+
217
+ # Save uploaded file
218
+ filename = secure_filename(file.filename)
219
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
220
+ filename = f"{timestamp}_{filename}"
221
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
222
+
223
+ print(f"Saving file to: {filepath}")
224
+ file.save(filepath)
225
+ print(f"File saved successfully")
226
+
227
+ # Verify file exists
228
+ if not os.path.exists(filepath):
229
+ print(f"File was not saved properly: {filepath}")
230
+ return jsonify({'error': 'Failed to save uploaded file'}), 500
231
+
232
+ print(f"File size on disk: {os.path.getsize(filepath)} bytes")
233
+
234
+ # Open and predict
235
+ try:
236
+ image = Image.open(filepath)
237
+ print(f"Image opened successfully: {image.size}, mode: {image.mode}")
238
+ except Exception as e:
239
+ print(f"Failed to open image: {e}")
240
+ return jsonify({'error': f'Invalid image file: {str(e)}'}), 400
241
+
242
+ result = predict_disease(image)
243
+ print(f"Prediction result: {result}")
244
+
245
+ if 'error' in result:
246
+ return jsonify(result), 500
247
+
248
+ # Add recommendations and image URL for upload method
249
+ result['recommendations'] = get_recommendations(result['predicted_class'], result['confidence'])
250
+ result['image_url'] = url_for('uploaded_file', filename=filename)
251
+
252
+ print(f"Final result with image URL: {result['image_url']}")
253
+ return jsonify(result)
254
+
255
+ except Exception as e:
256
+ print(f"Prediction error: {e}")
257
+ import traceback
258
+ traceback.print_exc()
259
+ return jsonify({'error': f'Prediction failed: {str(e)}'}), 500
260
+
261
+ @app.route('/predict_camera', methods=['POST'])
262
+ def predict_camera():
263
+ """Handle camera image prediction"""
264
+ try:
265
+ data = request.get_json()
266
+
267
+ if 'image' not in data:
268
+ return jsonify({'error': 'No image data provided'}), 400
269
+
270
+ # Decode base64 image
271
+ image_data = data['image'].split(',')[1] # Remove data:image/png;base64, prefix
272
+ image_bytes = base64.b64decode(image_data)
273
+ image = Image.open(io.BytesIO(image_bytes))
274
+
275
+ # Save camera image
276
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
277
+ filename = f"camera_{timestamp}.png"
278
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
279
+ image.save(filepath)
280
+
281
+ # Make prediction
282
+ result = predict_disease(image)
283
+
284
+ if 'error' in result:
285
+ return jsonify(result), 500
286
+
287
+ # Add recommendations and image URL for camera method
288
+ result['recommendations'] = get_recommendations(result['predicted_class'], result['confidence'])
289
+ result['image_url'] = url_for('uploaded_file', filename=filename)
290
+
291
+ return jsonify(result)
292
+
293
+ except Exception as e:
294
+ return jsonify({'error': f'Camera prediction failed: {str(e)}'}), 500
295
+
296
+ @app.route('/health')
297
+ def health():
298
+ """Health check endpoint"""
299
+ upload_dir_exists = os.path.exists(app.config['UPLOAD_FOLDER'])
300
+ upload_dir_writable = os.access(app.config['UPLOAD_FOLDER'], os.W_OK) if upload_dir_exists else False
301
+
302
+ return jsonify({
303
+ 'status': 'healthy',
304
+ 'model_loaded': MODEL_LOADED,
305
+ 'upload_dir_exists': upload_dir_exists,
306
+ 'upload_dir_writable': upload_dir_writable,
307
+ 'upload_path': app.config['UPLOAD_FOLDER'],
308
+ 'timestamp': datetime.now().isoformat()
309
+ })
310
+
311
+ @app.route('/debug/upload-test')
312
+ def debug_upload_test():
313
+ """Debug endpoint to test upload directory"""
314
+ try:
315
+ # Ensure upload directory exists
316
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
317
+
318
+ # Test file creation
319
+ test_file = os.path.join(app.config['UPLOAD_FOLDER'], 'test.txt')
320
+ with open(test_file, 'w') as f:
321
+ f.write('test')
322
+
323
+ # Clean up test file
324
+ os.remove(test_file)
325
+
326
+ return jsonify({
327
+ 'status': 'success',
328
+ 'message': 'Upload directory is working correctly',
329
+ 'path': app.config['UPLOAD_FOLDER']
330
+ })
331
+ except Exception as e:
332
+ return jsonify({
333
+ 'status': 'error',
334
+ 'message': f'Upload directory test failed: {str(e)}',
335
+ 'path': app.config['UPLOAD_FOLDER']
336
+ }), 500
337
+
338
+ def test_model_predictions():
339
+ """Test the model with some dummy data to verify it's working correctly"""
340
+ if not MODEL_LOADED or model is None:
341
+ return {"error": "Model not loaded"}
342
+
343
+ try:
344
+ # Create dummy test data - same shape as expected input
345
+ dummy_image = np.random.randint(0, 255, (1, IMAGE_SIZE, IMAGE_SIZE, 3), dtype=np.uint8)
346
+
347
+ # Make prediction
348
+ predictions = model.predict(dummy_image)
349
+
350
+ print(f"Model test - Input shape: {dummy_image.shape}")
351
+ print(f"Model test - Output shape: {predictions.shape}")
352
+ print(f"Model test - Predictions: {predictions[0]}")
353
+ print(f"Model test - Sum of predictions: {np.sum(predictions[0])}")
354
+ print(f"Model test - Class names order: {CLASS_NAMES}")
355
+
356
+ return {
357
+ "status": "success",
358
+ "input_shape": str(dummy_image.shape),
359
+ "output_shape": str(predictions.shape),
360
+ "predictions": predictions[0].tolist(),
361
+ "prediction_sum": float(np.sum(predictions[0])),
362
+ "class_names": CLASS_NAMES
363
+ }
364
+ except Exception as e:
365
+ print(f"Model test error: {e}")
366
+ return {"error": f"Model test failed: {str(e)}"}
367
+
368
+ @app.route('/debug/model-test')
369
+ def debug_model_test():
370
+ """Debug endpoint to test model functionality"""
371
+ result = test_model_predictions()
372
+ return jsonify(result)
373
+
374
+ @app.errorhandler(413)
375
+ def too_large(e):
376
+ return jsonify({'error': 'File too large. Maximum size is 16MB.'}), 413
377
+
378
+ @app.errorhandler(404)
379
+ def not_found(e):
380
+ return render_template('index.html', model_loaded=MODEL_LOADED)
381
+
382
+ def generate_pdf_report(prediction_data, image_path=None):
383
+ """Generate a professional PDF report for the disease prediction"""
384
+ if not REPORTLAB_AVAILABLE:
385
+ print("❌ ReportLab not available - cannot generate server-side PDF")
386
+ return None
387
+
388
+ try:
389
+ # Create a temporary file for the PDF
390
+ temp_pdf = tempfile.NamedTemporaryFile(delete=False, suffix='.pdf')
391
+
392
+ # Create the PDF document
393
+ doc = SimpleDocTemplate(temp_pdf.name, pagesize=A4)
394
+ styles = getSampleStyleSheet()
395
+ story = []
396
+
397
+ # Custom styles
398
+ title_style = ParagraphStyle(
399
+ 'CustomTitle',
400
+ parent=styles['Title'],
401
+ fontSize=24,
402
+ spaceAfter=30,
403
+ alignment=TA_CENTER,
404
+ textColor=colors.darkgreen
405
+ )
406
+
407
+ heading_style = ParagraphStyle(
408
+ 'CustomHeading',
409
+ parent=styles['Heading2'],
410
+ fontSize=16,
411
+ spaceAfter=12,
412
+ textColor=colors.darkblue
413
+ )
414
+
415
+ # Title
416
+ story.append(Paragraph("🥔 POTATO DISEASE DETECTION REPORT", title_style))
417
+ story.append(Spacer(1, 20))
418
+
419
+ # Header info table
420
+ header_data = [
421
+ ['Report Generated:', prediction_data.get('timestamp', datetime.now().strftime("%Y-%m-%d %H:%M:%S"))],
422
+ ['Analysis Method:', 'Deep Learning AI Classification'],
423
+ ['Model Version:', 'TensorFlow/Keras CNN v1.0']
424
+ ]
425
+
426
+ header_table = Table(header_data, colWidths=[2*inch, 4*inch])
427
+ header_table.setStyle(TableStyle([
428
+ ('BACKGROUND', (0, 0), (0, -1), colors.lightgrey),
429
+ ('TEXTCOLOR', (0, 0), (-1, -1), colors.black),
430
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
431
+ ('FONTNAME', (0, 0), (-1, -1), 'Helvetica-Bold'),
432
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
433
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 12),
434
+ ('GRID', (0, 0), (-1, -1), 1, colors.black)
435
+ ]))
436
+
437
+ story.append(header_table)
438
+ story.append(Spacer(1, 30))
439
+
440
+ # Add image if provided
441
+ if image_path and os.path.exists(image_path):
442
+ story.append(Paragraph("📸 ANALYZED IMAGE", heading_style))
443
+ try:
444
+ # Resize image to fit in PDF
445
+ img = RLImage(image_path)
446
+ img.drawHeight = 3 * inch
447
+ img.drawWidth = 3 * inch
448
+ story.append(img)
449
+ story.append(Spacer(1, 20))
450
+ except Exception as img_error:
451
+ print(f"Warning: Could not add image to PDF: {img_error}")
452
+ story.append(Paragraph("Image could not be embedded in PDF", styles['Normal']))
453
+ story.append(Spacer(1, 20))
454
+
455
+ # Diagnosis Section
456
+ story.append(Paragraph("🎯 DIAGNOSIS RESULTS", heading_style))
457
+
458
+ # Main diagnosis
459
+ diagnosis_data = [
460
+ ['Predicted Disease:', prediction_data.get('predicted_class', 'Unknown')],
461
+ ['Confidence Level:', f"{prediction_data.get('confidence', 0):.2f}%"],
462
+ ['Risk Assessment:', get_risk_level(prediction_data.get('confidence', 0))]
463
+ ]
464
+
465
+ diagnosis_table = Table(diagnosis_data, colWidths=[2*inch, 4*inch])
466
+ diagnosis_table.setStyle(TableStyle([
467
+ ('BACKGROUND', (0, 0), (0, -1), colors.lightblue),
468
+ ('TEXTCOLOR', (0, 0), (-1, -1), colors.black),
469
+ ('ALIGN', (0, 0), (-1, -1), 'LEFT'),
470
+ ('FONTNAME', (0, 0), (-1, -1), 'Helvetica'),
471
+ ('FONTSIZE', (0, 0), (-1, -1), 12),
472
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 12),
473
+ ('GRID', (0, 0), (-1, -1), 1, colors.black)
474
+ ]))
475
+
476
+ story.append(diagnosis_table)
477
+ story.append(Spacer(1, 20))
478
+
479
+ # Description
480
+ story.append(Paragraph("📋 DESCRIPTION", heading_style))
481
+ description = Paragraph(prediction_data.get('description', 'No description available.'), styles['Normal'])
482
+ story.append(description)
483
+ story.append(Spacer(1, 20))
484
+
485
+ # Probability breakdown
486
+ story.append(Paragraph("📊 PROBABILITY BREAKDOWN", heading_style))
487
+
488
+ prob_data = [['Disease Type', 'Probability']]
489
+ all_predictions = prediction_data.get('all_predictions', {})
490
+ for disease, info in all_predictions.items():
491
+ prob_data.append([disease, f"{info.get('probability', 0):.2f}%"])
492
+
493
+ prob_table = Table(prob_data, colWidths=[3*inch, 2*inch])
494
+ prob_table.setStyle(TableStyle([
495
+ ('BACKGROUND', (0, 0), (-1, 0), colors.grey),
496
+ ('TEXTCOLOR', (0, 0), (-1, 0), colors.whitesmoke),
497
+ ('ALIGN', (0, 0), (-1, -1), 'CENTER'),
498
+ ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
499
+ ('FONTNAME', (0, 1), (-1, -1), 'Helvetica'),
500
+ ('FONTSIZE', (0, 0), (-1, -1), 10),
501
+ ('BOTTOMPADDING', (0, 0), (-1, -1), 12),
502
+ ('GRID', (0, 0), (-1, -1), 1, colors.black)
503
+ ]))
504
+
505
+ story.append(prob_table)
506
+ story.append(Spacer(1, 20))
507
+
508
+ # Recommendations
509
+ story.append(Paragraph("💡 TREATMENT RECOMMENDATIONS", heading_style))
510
+
511
+ recommendations = prediction_data.get('recommendations', [])
512
+ for i, rec in enumerate(recommendations, 1):
513
+ rec_text = f"{i}. {rec}"
514
+ story.append(Paragraph(rec_text, styles['Normal']))
515
+ story.append(Spacer(1, 8))
516
+
517
+ story.append(Spacer(1, 30))
518
+
519
+ # Footer
520
+ footer_style = ParagraphStyle(
521
+ 'Footer',
522
+ parent=styles['Normal'],
523
+ fontSize=10,
524
+ alignment=TA_CENTER,
525
+ textColor=colors.grey
526
+ )
527
+
528
+ story.append(Paragraph("_______________________________________________", footer_style))
529
+ story.append(Spacer(1, 10))
530
+ story.append(Paragraph("Generated by Potato Disease Detection System", footer_style))
531
+ story.append(Paragraph("Powered by Flask & TensorFlow | Lucky Sharma", footer_style))
532
+ story.append(Paragraph("© 2025 All Rights Reserved", footer_style))
533
+
534
+ # Build PDF
535
+ doc.build(story)
536
+
537
+ print(f"✅ PDF report generated successfully: {temp_pdf.name}")
538
+ return temp_pdf.name
539
+
540
+ except Exception as e:
541
+ print(f"❌ Error generating PDF: {e}")
542
+ import traceback
543
+ traceback.print_exc()
544
+ return None
545
+
546
+ def get_risk_level(confidence):
547
+ """Determine risk level based on confidence"""
548
+ if confidence >= 80:
549
+ return "High Confidence"
550
+ elif confidence >= 60:
551
+ return "Medium Confidence"
552
+ else:
553
+ return "Low Confidence - Manual Verification Recommended"
554
+
555
+ @app.route('/generate-pdf-report', methods=['POST'])
556
+ def generate_pdf_report_route():
557
+ """Generate and download PDF report"""
558
+ try:
559
+ data = request.get_json()
560
+
561
+ if not data:
562
+ return jsonify({'error': 'No data provided'}), 400
563
+
564
+ # Check if ReportLab is available
565
+ if not REPORTLAB_AVAILABLE:
566
+ print("⚠️ ReportLab not available, suggesting client-side fallback")
567
+ return jsonify({
568
+ 'error': 'Server-side PDF generation not available',
569
+ 'fallback': 'client',
570
+ 'message': 'ReportLab library not installed. Using client-side fallback.'
571
+ }), 503
572
+
573
+ # Get image path if provided
574
+ image_path = None
575
+ if 'image_url' in data:
576
+ # Extract filename from URL and construct full path
577
+ image_filename = data['image_url'].split('/')[-1]
578
+ image_path = os.path.join(app.config['UPLOAD_FOLDER'], image_filename)
579
+
580
+ # Verify image exists
581
+ if not os.path.exists(image_path):
582
+ image_path = None
583
+
584
+ # Generate PDF
585
+ pdf_path = generate_pdf_report(data, image_path)
586
+
587
+ if not pdf_path:
588
+ print("❌ PDF generation failed, suggesting client-side fallback")
589
+ return jsonify({
590
+ 'error': 'Server-side PDF generation failed',
591
+ 'fallback': 'client',
592
+ 'message': 'Could not generate PDF on server. Using client-side fallback.'
593
+ }), 503
594
+
595
+ # Create filename
596
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
597
+ disease_name = data.get('predicted_class', 'unknown').replace(' ', '_')
598
+ pdf_filename = f"potato_disease_report_{disease_name}_{timestamp}.pdf"
599
+
600
+ print(f"✅ PDF generated successfully: {pdf_filename}")
601
+ return send_file(
602
+ pdf_path,
603
+ as_attachment=True,
604
+ download_name=pdf_filename,
605
+ mimetype='application/pdf'
606
+ )
607
+
608
+ except Exception as e:
609
+ print(f"❌ PDF generation error: {e}")
610
+ import traceback
611
+ traceback.print_exc()
612
+ return jsonify({
613
+ 'error': f'PDF generation failed: {str(e)}',
614
+ 'fallback': 'client',
615
+ 'message': 'Server error occurred. Using client-side fallback.'
616
+ }), 503
617
+
618
+ if __name__ == '__main__':
619
+ print("🚀 Starting Potato Disease Detection Flask App...")
620
+ print(f"📁 Upload folder: {UPLOAD_FOLDER}")
621
+ print(f"🤖 Model loaded: {MODEL_LOADED}")
622
+ print("🌐 Access the app at: http://localhost:5000")
623
+ print("💡 Press Ctrl+C to stop the server")
624
+
625
+ app.run(debug=True, host='0.0.0.0', port=5000)
models/1.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6a45a2a92332af1909dc22a358df71f5bda8a8a0d8e8f20b8a418058f1d6bb05
3
+ size 2284808
models/1/fingerprint.pb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cb44ef89d6bb9784adb55b8e23814c1d68d97b3fc64dd3cf7af384477086d64c
3
+ size 75
models/1/saved_model.pb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e363b45246a41fa1857bd8f17447da4aac0058b6539ee50e3d043cc9ad0dafa6
3
+ size 162792
models/1/variables/variables.data-00000-of-00001 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fd04fe5bed6a7d85490a35ee700b54e39c4b6e46876fd28f7d8c471b00558374
3
+ size 1474148
models/1/variables/variables.index ADDED
Binary file (2.2 kB). View file
 
models/4.keras ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7fafa6d490669d331044ed2cca67a6c437773725be035f13e64e7f4c6145554b
3
+ size 2284916
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ Flask>=2.3.0
2
+ tensorflow>=2.13.0
3
+ Pillow>=10.0.0
4
+ numpy>=1.24.0
5
+ Werkzeug>=2.3.0
6
+ reportlab>=4.0.0
7
+
runtime.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ python-3.10.12
static/content/android-icon-144x144.png ADDED
static/content/android-icon-192x192.png ADDED
static/content/android-icon-36x36.png ADDED
static/content/android-icon-48x48.png ADDED
static/content/android-icon-72x72.png ADDED
static/content/android-icon-96x96.png ADDED
static/content/apple-icon-114x114.png ADDED
static/content/apple-icon-120x120.png ADDED
static/content/apple-icon-144x144.png ADDED
static/content/apple-icon-152x152.png ADDED
static/content/apple-icon-180x180.png ADDED
static/content/apple-icon-57x57.png ADDED
static/content/apple-icon-60x60.png ADDED
static/content/apple-icon-72x72.png ADDED
static/content/apple-icon-76x76.png ADDED
static/content/apple-icon-precomposed.png ADDED
static/content/apple-icon.png ADDED
static/content/browserconfig.xml ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
static/content/favicon-16x16.png ADDED
static/content/favicon-32x32.png ADDED
static/content/favicon-96x96.png ADDED
static/content/favicon.ico ADDED
static/content/manifest.json ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "App",
3
+ "icons": [
4
+ {
5
+ "src": "\/android-icon-36x36.png",
6
+ "sizes": "36x36",
7
+ "type": "image\/png",
8
+ "density": "0.75"
9
+ },
10
+ {
11
+ "src": "\/android-icon-48x48.png",
12
+ "sizes": "48x48",
13
+ "type": "image\/png",
14
+ "density": "1.0"
15
+ },
16
+ {
17
+ "src": "\/android-icon-72x72.png",
18
+ "sizes": "72x72",
19
+ "type": "image\/png",
20
+ "density": "1.5"
21
+ },
22
+ {
23
+ "src": "\/android-icon-96x96.png",
24
+ "sizes": "96x96",
25
+ "type": "image\/png",
26
+ "density": "2.0"
27
+ },
28
+ {
29
+ "src": "\/android-icon-144x144.png",
30
+ "sizes": "144x144",
31
+ "type": "image\/png",
32
+ "density": "3.0"
33
+ },
34
+ {
35
+ "src": "\/android-icon-192x192.png",
36
+ "sizes": "192x192",
37
+ "type": "image\/png",
38
+ "density": "4.0"
39
+ }
40
+ ]
41
+ }
static/content/ms-icon-144x144.png ADDED
static/content/ms-icon-150x150.png ADDED
static/content/ms-icon-310x310.png ADDED
static/content/ms-icon-70x70.png ADDED
static/css/style.css ADDED
@@ -0,0 +1,1334 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* CSS Variables for mobile viewport fix */
2
+ :root {
3
+ --vh: 1vh;
4
+ }
5
+
6
+ /* Mobile-specific body classes */
7
+ .mobile-device {
8
+ -webkit-text-size-adjust: 100%;
9
+ -webkit-font-smoothing: antialiased;
10
+ -moz-osx-font-smoothing: grayscale;
11
+ }
12
+
13
+ .ios-device {
14
+ -webkit-overflow-scrolling: touch;
15
+ }
16
+
17
+ /* Tooltip styles for unsupported features */
18
+ .tooltip {
19
+ position: absolute;
20
+ bottom: -30px;
21
+ left: 50%;
22
+ transform: translateX(-50%);
23
+ background: rgba(0, 0, 0, 0.8);
24
+ color: white;
25
+ padding: 5px 10px;
26
+ border-radius: 4px;
27
+ font-size: 0.8rem;
28
+ white-space: nowrap;
29
+ z-index: 1000;
30
+ opacity: 0;
31
+ pointer-events: none;
32
+ transition: opacity 0.3s ease;
33
+ }
34
+
35
+ .method-card:hover .tooltip {
36
+ opacity: 1;
37
+ }
38
+
39
+ /* Reset and Base Styles */
40
+ * {
41
+ margin: 0;
42
+ padding: 0;
43
+ box-sizing: border-box;
44
+ }
45
+
46
+ body {
47
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
48
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
49
+ min-height: 100vh;
50
+ line-height: 1.6;
51
+ color: #333;
52
+ }
53
+
54
+ .container {
55
+ max-width: 1200px;
56
+ margin: 0 auto;
57
+ padding: 20px;
58
+ }
59
+
60
+ /* Header */
61
+ .header {
62
+ text-align: center;
63
+ margin-bottom: 30px;
64
+ color: white;
65
+ }
66
+
67
+ .header-content {
68
+ background: rgba(255, 255, 255, 0.1);
69
+ backdrop-filter: blur(10px);
70
+ border-radius: 20px;
71
+ padding: 30px;
72
+ border: 1px solid rgba(255, 255, 255, 0.2);
73
+ }
74
+
75
+ .logo-icon {
76
+ font-size: 4rem;
77
+ margin-bottom: 15px;
78
+ color: #4ade80;
79
+ }
80
+
81
+ .header h1 {
82
+ font-size: 2.5rem;
83
+ margin-bottom: 10px;
84
+ font-weight: 700;
85
+ }
86
+
87
+ .header p {
88
+ font-size: 1.1rem;
89
+ opacity: 0.9;
90
+ margin-bottom: 15px;
91
+ }
92
+
93
+ .alert {
94
+ padding: 15px;
95
+ border-radius: 10px;
96
+ margin-top: 15px;
97
+ display: flex;
98
+ align-items: center;
99
+ gap: 10px;
100
+ }
101
+
102
+ .alert-error {
103
+ background: rgba(239, 68, 68, 0.2);
104
+ border: 1px solid rgba(239, 68, 68, 0.3);
105
+ color: #fee2e2;
106
+ }
107
+
108
+ /* Main Content */
109
+ .main-content {
110
+ background: white;
111
+ border-radius: 20px;
112
+ padding: 40px;
113
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
114
+ margin-bottom: 30px;
115
+ position: relative;
116
+ }
117
+
118
+ /* Upload Methods */
119
+ .upload-methods {
120
+ display: flex;
121
+ gap: 20px;
122
+ margin-bottom: 30px;
123
+ justify-content: center;
124
+ }
125
+
126
+ .method-card {
127
+ flex: 1;
128
+ max-width: 200px;
129
+ padding: 25px;
130
+ border: 2px solid #e2e8f0;
131
+ border-radius: 15px;
132
+ text-align: center;
133
+ cursor: pointer;
134
+ transition: all 0.3s ease;
135
+ background: #f8fafc;
136
+ }
137
+
138
+ .method-card:hover {
139
+ border-color: #667eea;
140
+ transform: translateY(-2px);
141
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.2);
142
+ }
143
+
144
+ .method-card.active {
145
+ border-color: #667eea;
146
+ background: linear-gradient(135deg, #667eea, #764ba2);
147
+ color: white;
148
+ }
149
+
150
+ .method-card i {
151
+ font-size: 2.5rem;
152
+ margin-bottom: 10px;
153
+ color: #667eea;
154
+ }
155
+
156
+ .method-card.active i {
157
+ color: white;
158
+ }
159
+
160
+ .method-card h3 {
161
+ margin-bottom: 5px;
162
+ font-size: 1.2rem;
163
+ }
164
+
165
+ .method-card p {
166
+ font-size: 0.9rem;
167
+ opacity: 0.8;
168
+ }
169
+
170
+ /* Upload Section */
171
+ .upload-area {
172
+ border: 3px dashed #cbd5e1;
173
+ border-radius: 15px;
174
+ padding: 60px 20px;
175
+ text-align: center;
176
+ cursor: pointer;
177
+ transition: all 0.3s ease;
178
+ background: #f8fafc;
179
+ }
180
+
181
+ .upload-area:hover, .upload-area.dragover {
182
+ border-color: #667eea;
183
+ background: #f0f9ff;
184
+ transform: translateY(-2px);
185
+ }
186
+
187
+ .upload-icon {
188
+ font-size: 4rem;
189
+ margin-bottom: 20px;
190
+ color: #667eea;
191
+ }
192
+
193
+ .upload-content h3 {
194
+ font-size: 1.5rem;
195
+ margin-bottom: 10px;
196
+ color: #1e293b;
197
+ }
198
+
199
+ .browse-text {
200
+ color: #667eea;
201
+ text-decoration: underline;
202
+ cursor: pointer;
203
+ }
204
+
205
+ .supported-formats {
206
+ margin-top: 15px;
207
+ color: #94a3b8;
208
+ }
209
+
210
+ /* Camera Section */
211
+ .camera-container {
212
+ text-align: center;
213
+ padding: 20px;
214
+ border: 2px dashed #cbd5e1;
215
+ border-radius: 15px;
216
+ background: #f8fafc;
217
+ }
218
+
219
+ #video {
220
+ max-width: 100%;
221
+ max-height: 400px;
222
+ border-radius: 10px;
223
+ margin-bottom: 20px;
224
+ }
225
+
226
+ .camera-controls {
227
+ display: flex;
228
+ gap: 15px;
229
+ justify-content: center;
230
+ flex-wrap: wrap;
231
+ }
232
+
233
+ /* Image Preview */
234
+ .image-preview {
235
+ text-align: center;
236
+ margin-top: 30px;
237
+ }
238
+
239
+ .image-preview img {
240
+ max-width: 100%;
241
+ max-height: 400px;
242
+ border-radius: 10px;
243
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
244
+ margin-bottom: 20px;
245
+ }
246
+
247
+ .image-actions {
248
+ display: flex;
249
+ justify-content: center;
250
+ gap: 15px;
251
+ margin-top: 20px;
252
+ flex-wrap: wrap;
253
+ }
254
+
255
+ /* Buttons */
256
+ .btn {
257
+ padding: 12px 25px;
258
+ border: none;
259
+ border-radius: 8px;
260
+ cursor: pointer;
261
+ font-size: 1rem;
262
+ font-weight: 600;
263
+ transition: all 0.3s ease;
264
+ display: inline-flex;
265
+ align-items: center;
266
+ gap: 8px;
267
+ text-decoration: none;
268
+ white-space: nowrap;
269
+ }
270
+
271
+ .btn-predict {
272
+ background: linear-gradient(135deg, #4ade80, #22c55e);
273
+ color: white;
274
+ }
275
+
276
+ .btn-predict:hover {
277
+ transform: translateY(-2px);
278
+ box-shadow: 0 10px 20px rgba(34, 197, 94, 0.3);
279
+ }
280
+
281
+ .btn-primary {
282
+ background: linear-gradient(135deg, #667eea, #764ba2);
283
+ color: white;
284
+ }
285
+
286
+ .btn-primary:hover {
287
+ transform: translateY(-2px);
288
+ box-shadow: 0 10px 20px rgba(102, 126, 234, 0.3);
289
+ }
290
+
291
+ .btn-camera {
292
+ background: linear-gradient(135deg, #f59e0b, #d97706);
293
+ color: white;
294
+ }
295
+
296
+ .btn-camera:hover {
297
+ transform: translateY(-2px);
298
+ box-shadow: 0 10px 20px rgba(245, 158, 11, 0.3);
299
+ }
300
+
301
+ .btn-capture {
302
+ background: linear-gradient(135deg, #ef4444, #dc2626);
303
+ color: white;
304
+ }
305
+
306
+ .btn-capture:hover {
307
+ transform: translateY(-2px);
308
+ box-shadow: 0 10px 20px rgba(239, 68, 68, 0.3);
309
+ }
310
+
311
+ .btn-secondary {
312
+ background: #e2e8f0;
313
+ color: #475569;
314
+ }
315
+
316
+ .btn-secondary:hover {
317
+ background: #cbd5e1;
318
+ transform: translateY(-2px);
319
+ }
320
+
321
+ /* Loading */
322
+ .loading-overlay {
323
+ position: absolute;
324
+ top: 0;
325
+ left: 0;
326
+ right: 0;
327
+ bottom: 0;
328
+ background: rgba(255, 255, 255, 0.95);
329
+ display: flex;
330
+ justify-content: center;
331
+ align-items: center;
332
+ border-radius: 20px;
333
+ z-index: 1000;
334
+ }
335
+
336
+ .loading-content {
337
+ text-align: center;
338
+ color: #667eea;
339
+ }
340
+
341
+ .spinner {
342
+ width: 50px;
343
+ height: 50px;
344
+ border: 4px solid #e2e8f0;
345
+ border-top: 4px solid #667eea;
346
+ border-radius: 50%;
347
+ animation: spin 1s linear infinite;
348
+ margin: 0 auto 20px;
349
+ }
350
+
351
+ @keyframes spin {
352
+ 0% { transform: rotate(0deg); }
353
+ 100% { transform: rotate(360deg); }
354
+ }
355
+
356
+ /* Results Section */
357
+ .results-section {
358
+ margin-top: 40px;
359
+ }
360
+
361
+ .results-section h2 {
362
+ color: #1e293b;
363
+ margin-bottom: 30px;
364
+ font-size: 2rem;
365
+ display: flex;
366
+ align-items: center;
367
+ gap: 15px;
368
+ }
369
+
370
+ /* Analyzed Image Display */
371
+ .analyzed-image {
372
+ background: #f8fafc;
373
+ border-radius: 15px;
374
+ padding: 25px;
375
+ margin-bottom: 30px;
376
+ text-align: center;
377
+ }
378
+
379
+ .analyzed-image h3 {
380
+ color: #1e293b;
381
+ margin-bottom: 20px;
382
+ font-size: 1.4rem;
383
+ }
384
+
385
+ .analyzed-image-container {
386
+ display: flex;
387
+ justify-content: center;
388
+ align-items: center;
389
+ }
390
+
391
+ .analyzed-img {
392
+ max-width: 100%;
393
+ max-height: 300px;
394
+ border-radius: 10px;
395
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.15);
396
+ border: 3px solid #e2e8f0;
397
+ }
398
+
399
+ .prediction-card {
400
+ background: linear-gradient(135deg, #f8fafc, #e2e8f0);
401
+ border-radius: 15px;
402
+ padding: 30px;
403
+ margin-bottom: 30px;
404
+ border: 1px solid #e2e8f0;
405
+ }
406
+
407
+ .prediction-header {
408
+ display: flex;
409
+ justify-content: space-between;
410
+ align-items: center;
411
+ margin-bottom: 20px;
412
+ flex-wrap: wrap;
413
+ gap: 15px;
414
+ }
415
+
416
+ .confidence-badge {
417
+ background: linear-gradient(135deg, #667eea, #764ba2);
418
+ color: white;
419
+ padding: 8px 20px;
420
+ border-radius: 20px;
421
+ font-weight: 700;
422
+ font-size: 1.1rem;
423
+ }
424
+
425
+ .prediction-result {
426
+ display: flex;
427
+ align-items: center;
428
+ gap: 20px;
429
+ flex-wrap: wrap;
430
+ }
431
+
432
+ .disease-icon {
433
+ font-size: 3rem;
434
+ color: #667eea;
435
+ background: rgba(102, 126, 234, 0.1);
436
+ padding: 20px;
437
+ border-radius: 50%;
438
+ flex-shrink: 0;
439
+ }
440
+
441
+ .disease-info h4 {
442
+ font-size: 1.8rem;
443
+ color: #1e293b;
444
+ margin-bottom: 10px;
445
+ }
446
+
447
+ .disease-info p {
448
+ color: #64748b;
449
+ font-size: 1.1rem;
450
+ margin-bottom: 8px;
451
+ }
452
+
453
+ .timestamp {
454
+ color: #94a3b8;
455
+ font-size: 0.9rem;
456
+ }
457
+
458
+ /* Detailed Analysis */
459
+ .detailed-analysis {
460
+ background: #f8fafc;
461
+ border-radius: 15px;
462
+ padding: 30px;
463
+ margin-bottom: 30px;
464
+ }
465
+
466
+ .detailed-analysis h3 {
467
+ color: #1e293b;
468
+ margin-bottom: 20px;
469
+ font-size: 1.3rem;
470
+ }
471
+
472
+ .probability-item {
473
+ display: flex;
474
+ justify-content: space-between;
475
+ align-items: center;
476
+ margin-bottom: 15px;
477
+ padding: 15px;
478
+ background: white;
479
+ border-radius: 10px;
480
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
481
+ }
482
+
483
+ .probability-label {
484
+ font-weight: 600;
485
+ color: #1e293b;
486
+ flex: 1;
487
+ min-width: 120px;
488
+ }
489
+
490
+ .probability-bar {
491
+ flex: 2;
492
+ height: 8px;
493
+ background: #e2e8f0;
494
+ border-radius: 4px;
495
+ margin: 0 15px;
496
+ overflow: hidden;
497
+ min-width: 100px;
498
+ }
499
+
500
+ .probability-fill {
501
+ height: 100%;
502
+ border-radius: 4px;
503
+ transition: width 0.5s ease;
504
+ }
505
+
506
+ .probability-value {
507
+ font-weight: 700;
508
+ color: #1e293b;
509
+ min-width: 50px;
510
+ text-align: right;
511
+ }
512
+
513
+ /* Recommendations */
514
+ .recommendations {
515
+ background: linear-gradient(135deg, #fef3c7, #fed7aa);
516
+ border-radius: 15px;
517
+ padding: 30px;
518
+ border-left: 5px solid #f59e0b;
519
+ margin-bottom: 30px;
520
+ }
521
+
522
+ .recommendations h3 {
523
+ color: #92400e;
524
+ margin-bottom: 20px;
525
+ font-size: 1.3rem;
526
+ display: flex;
527
+ align-items: center;
528
+ gap: 10px;
529
+ }
530
+
531
+ .recommendation-item {
532
+ background: rgba(255, 255, 255, 0.7);
533
+ padding: 15px;
534
+ border-radius: 10px;
535
+ margin-bottom: 10px;
536
+ border-left: 3px solid #f59e0b;
537
+ display: flex;
538
+ align-items: flex-start;
539
+ gap: 10px;
540
+ }
541
+
542
+ .recommendation-item i {
543
+ color: #f59e0b;
544
+ margin-top: 2px;
545
+ flex-shrink: 0;
546
+ }
547
+
548
+ .recommendation-item span {
549
+ color: #78350f;
550
+ line-height: 1.5;
551
+ }
552
+
553
+ /* Result Actions */
554
+ .result-actions {
555
+ display: flex;
556
+ gap: 15px;
557
+ justify-content: center;
558
+ flex-wrap: wrap;
559
+ align-items: flex-start;
560
+ }
561
+
562
+ .download-group {
563
+ margin-top: 20px;
564
+ padding: 20px;
565
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
566
+ border-radius: 12px;
567
+ border: 1px solid #e2e8f0;
568
+ position: relative;
569
+ overflow: hidden;
570
+ display: flex;
571
+ flex-direction: column;
572
+ align-items: center;
573
+ gap: 15px;
574
+ }
575
+
576
+ .download-group::before {
577
+ content: '';
578
+ position: absolute;
579
+ top: 0;
580
+ left: 0;
581
+ right: 0;
582
+ height: 3px;
583
+ background: linear-gradient(90deg, #3b82f6, #8b5cf6, #06b6d4);
584
+ animation: shimmer 2s infinite;
585
+ }
586
+
587
+ @keyframes shimmer {
588
+ 0% { transform: translateX(-100%); }
589
+ 100% { transform: translateX(100%); }
590
+ }
591
+
592
+ .download-help {
593
+ font-size: 14px;
594
+ margin-bottom: 5px;
595
+ padding: 12px 16px;
596
+ border-radius: 8px;
597
+ display: flex;
598
+ align-items: center;
599
+ gap: 10px;
600
+ font-weight: 500;
601
+ border: 1px solid;
602
+ transition: all 0.3s ease;
603
+ text-align: center;
604
+ max-width: 300px;
605
+ line-height: 1.4;
606
+ }
607
+
608
+ .download-help.supported {
609
+ background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
610
+ color: #065f46;
611
+ border-color: #10b981;
612
+ }
613
+
614
+ .download-help.supported .help-icon {
615
+ color: #10b981;
616
+ }
617
+
618
+ .download-help.not-supported {
619
+ background: linear-gradient(135deg, #f3f4f6 0%, #e5e7eb 100%);
620
+ color: #6b7280;
621
+ border-color: #d1d5db;
622
+ }
623
+
624
+ .download-help.not-supported .help-icon {
625
+ color: #9ca3af;
626
+ }
627
+
628
+ .help-icon {
629
+ font-size: 16px;
630
+ display: flex;
631
+ align-items: center;
632
+ }
633
+
634
+ .download-help i {
635
+ color: inherit;
636
+ font-size: 1rem;
637
+ }
638
+
639
+ #downloadResultBtn {
640
+ width: 100%;
641
+ max-width: 300px;
642
+ padding: 15px 20px;
643
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
644
+ color: white;
645
+ border: none;
646
+ border-radius: 10px;
647
+ font-size: 16px;
648
+ font-weight: 600;
649
+ cursor: pointer;
650
+ transition: all 0.3s ease;
651
+ display: flex;
652
+ align-items: center;
653
+ justify-content: center;
654
+ gap: 10px;
655
+ box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
656
+ position: relative;
657
+ overflow: hidden;
658
+ }
659
+
660
+ #downloadResultBtn:hover:not(:disabled) {
661
+ background: linear-gradient(135deg, #2563eb 0%, #1e40af 100%);
662
+ box-shadow: 0 6px 20px rgba(59, 130, 246, 0.4);
663
+ transform: translateY(-2px);
664
+ }
665
+
666
+ #downloadResultBtn:active {
667
+ transform: translateY(0);
668
+ box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
669
+ }
670
+
671
+ #downloadResultBtn:disabled {
672
+ opacity: 0.7;
673
+ cursor: not-allowed;
674
+ transform: none;
675
+ }
676
+
677
+ #downloadResultBtn.folder-supported {
678
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
679
+ box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
680
+ }
681
+
682
+ #downloadResultBtn.folder-supported:hover:not(:disabled) {
683
+ background: linear-gradient(135deg, #059669 0%, #047857 100%);
684
+ box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4);
685
+ }
686
+
687
+ #downloadResultBtn .fas.fa-folder-open {
688
+ margin-right: 0;
689
+ }
690
+
691
+ /* Footer */
692
+ .footer {
693
+ text-align: center;
694
+ color: white;
695
+ opacity: 0.9;
696
+ padding: 20px;
697
+ display: flex;
698
+ justify-content: space-between;
699
+ align-items: center;
700
+ flex-wrap: wrap;
701
+ gap: 15px;
702
+ }
703
+
704
+ .status-indicator {
705
+ display: flex;
706
+ align-items: center;
707
+ gap: 8px;
708
+ font-size: 0.9rem;
709
+ }
710
+
711
+ .status-good {
712
+ color: #4ade80;
713
+ }
714
+
715
+ .status-error {
716
+ color: #ef4444;
717
+ }
718
+
719
+ /* Touch and Mobile Specific Styles */
720
+ .upload-area,
721
+ .method-card,
722
+ .btn,
723
+ .browse-text {
724
+ -webkit-tap-highlight-color: rgba(102, 126, 234, 0.3);
725
+ touch-action: manipulation;
726
+ }
727
+
728
+ /* Improve touch targets for mobile */
729
+ @media (max-width: 768px) {
730
+ .btn {
731
+ min-height: 44px; /* Apple's recommended minimum touch target */
732
+ display: flex;
733
+ align-items: center;
734
+ justify-content: center;
735
+ }
736
+
737
+ .method-card {
738
+ min-height: 120px;
739
+ display: flex;
740
+ flex-direction: column;
741
+ align-items: center;
742
+ justify-content: center;
743
+ }
744
+
745
+ .upload-area {
746
+ min-height: 150px;
747
+ display: flex;
748
+ flex-direction: column;
749
+ align-items: center;
750
+ justify-content: center;
751
+ }
752
+
753
+ .browse-text {
754
+ padding: 8px 12px;
755
+ border-radius: 4px;
756
+ background: rgba(102, 126, 234, 0.1);
757
+ margin: 0 4px;
758
+ }
759
+
760
+ /* Larger touch targets for camera controls */
761
+ .camera-controls .btn {
762
+ min-height: 50px;
763
+ font-size: 1rem;
764
+ }
765
+
766
+ /* Better spacing for recommendation items */
767
+ .recommendation-item {
768
+ min-height: 50px;
769
+ display: flex;
770
+ align-items: center;
771
+ }
772
+
773
+ /* Improve probability item touch area */
774
+ .probability-item {
775
+ min-height: 60px;
776
+ display: flex;
777
+ align-items: center;
778
+ justify-content: space-between;
779
+ }
780
+ }
781
+
782
+ /* Prevent zoom on input focus for iOS */
783
+ @media screen and (-webkit-min-device-pixel-ratio: 0) {
784
+ input[type="file"] {
785
+ font-size: 16px;
786
+ }
787
+ }
788
+
789
+ /* High DPI display optimizations */
790
+ @media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
791
+ .analyzed-img,
792
+ .image-preview img,
793
+ #video {
794
+ image-rendering: -webkit-optimize-contrast;
795
+ image-rendering: crisp-edges;
796
+ }
797
+ }
798
+
799
+ /* Landscape orientation for mobile */
800
+ @media (max-width: 768px) and (orientation: landscape) {
801
+ .header-content {
802
+ padding: 15px;
803
+ }
804
+
805
+ .logo-icon {
806
+ font-size: 2rem;
807
+ margin-bottom: 5px;
808
+ }
809
+
810
+ .header h1 {
811
+ font-size: 1.8rem;
812
+ margin-bottom: 5px;
813
+ }
814
+
815
+ .header p {
816
+ font-size: 0.9rem;
817
+ margin-bottom: 10px;
818
+ }
819
+
820
+ .main-content {
821
+ padding: 15px;
822
+ }
823
+
824
+ .upload-methods {
825
+ flex-direction: row;
826
+ justify-content: center;
827
+ gap: 20px;
828
+ }
829
+
830
+ .method-card {
831
+ max-width: 200px;
832
+ padding: 12px;
833
+ }
834
+
835
+ .upload-area {
836
+ padding: 25px 15px;
837
+ }
838
+
839
+ #video {
840
+ max-height: 200px;
841
+ }
842
+
843
+ .image-preview img,
844
+ .analyzed-img {
845
+ max-height: 200px;
846
+ }
847
+ }
848
+
849
+ /* Print styles for results */
850
+ @media print {
851
+ .header,
852
+ .upload-methods,
853
+ .upload-section,
854
+ .camera-section,
855
+ .image-preview,
856
+ .loading-overlay,
857
+ .result-actions,
858
+ .footer {
859
+ display: none !important;
860
+ }
861
+
862
+ .main-content {
863
+ box-shadow: none;
864
+ border: 1px solid #000;
865
+ }
866
+
867
+ .results-section {
868
+ display: block !important;
869
+ }
870
+
871
+ body {
872
+ background: white;
873
+ color: black;
874
+ }
875
+ }
876
+
877
+ /* Responsive Design */
878
+
879
+ /* Large tablets and small desktops */
880
+ @media (max-width: 1024px) {
881
+ .container {
882
+ padding: 15px;
883
+ }
884
+
885
+ .header-content {
886
+ padding: 25px;
887
+ }
888
+
889
+ .main-content {
890
+ padding: 30px;
891
+ }
892
+
893
+ .upload-methods {
894
+ gap: 15px;
895
+ }
896
+
897
+ .method-card {
898
+ padding: 20px;
899
+ }
900
+ }
901
+
902
+ /* Tablets */
903
+ @media (max-width: 768px) {
904
+ .container {
905
+ padding: 10px;
906
+ }
907
+
908
+ .header h1 {
909
+ font-size: 2rem;
910
+ }
911
+
912
+ .header p {
913
+ font-size: 1rem;
914
+ }
915
+
916
+ .logo-icon {
917
+ font-size: 3rem;
918
+ }
919
+
920
+ .header-content {
921
+ padding: 20px;
922
+ }
923
+
924
+ .main-content {
925
+ padding: 20px;
926
+ margin-bottom: 20px;
927
+ }
928
+
929
+ .upload-methods {
930
+ flex-direction: column;
931
+ align-items: center;
932
+ gap: 15px;
933
+ }
934
+
935
+ .method-card {
936
+ max-width: 280px;
937
+ width: 100%;
938
+ }
939
+
940
+ .method-card i {
941
+ font-size: 2rem;
942
+ }
943
+
944
+ .upload-area {
945
+ padding: 40px 15px;
946
+ }
947
+
948
+ .upload-icon {
949
+ font-size: 3rem;
950
+ }
951
+
952
+ .upload-content h3 {
953
+ font-size: 1.3rem;
954
+ }
955
+
956
+ .camera-container {
957
+ padding: 15px;
958
+ }
959
+
960
+ #video {
961
+ max-height: 300px;
962
+ }
963
+
964
+ .image-preview img {
965
+ max-height: 300px;
966
+ }
967
+
968
+ .prediction-header {
969
+ flex-direction: column;
970
+ text-align: center;
971
+ gap: 10px;
972
+ }
973
+
974
+ .prediction-result {
975
+ flex-direction: column;
976
+ text-align: center;
977
+ gap: 15px;
978
+ }
979
+
980
+ .disease-icon {
981
+ font-size: 2.5rem;
982
+ padding: 15px;
983
+ align-self: center;
984
+ }
985
+
986
+ .disease-info h4 {
987
+ font-size: 1.5rem;
988
+ }
989
+
990
+ .probability-item {
991
+ flex-direction: column;
992
+ gap: 10px;
993
+ text-align: center;
994
+ padding: 12px;
995
+ }
996
+
997
+ .probability-bar {
998
+ width: 100%;
999
+ margin: 0;
1000
+ height: 6px;
1001
+ }
1002
+
1003
+ .analyzed-img {
1004
+ max-height: 250px;
1005
+ }
1006
+
1007
+ .detailed-analysis,
1008
+ .recommendations {
1009
+ padding: 20px;
1010
+ }
1011
+
1012
+ .recommendation-item {
1013
+ padding: 12px;
1014
+ flex-direction: column;
1015
+ text-align: center;
1016
+ gap: 8px;
1017
+ }
1018
+
1019
+ .camera-controls {
1020
+ flex-direction: column;
1021
+ align-items: center;
1022
+ gap: 10px;
1023
+ }
1024
+
1025
+ .image-actions,
1026
+ .result-actions {
1027
+ flex-direction: column;
1028
+ align-items: center;
1029
+ gap: 10px;
1030
+ }
1031
+
1032
+ .btn {
1033
+ width: 100%;
1034
+ max-width: 250px;
1035
+ padding: 12px 20px;
1036
+ font-size: 0.95rem;
1037
+ }
1038
+
1039
+ .footer {
1040
+ flex-direction: column;
1041
+ text-align: center;
1042
+ gap: 10px;
1043
+ padding: 15px;
1044
+ }
1045
+ }
1046
+
1047
+ /* Mobile phones */
1048
+ @media (max-width: 480px) {
1049
+ .container {
1050
+ padding: 8px;
1051
+ }
1052
+
1053
+ .header h1 {
1054
+ font-size: 1.8rem;
1055
+ margin-bottom: 8px;
1056
+ }
1057
+
1058
+ .header p {
1059
+ font-size: 0.95rem;
1060
+ margin-bottom: 10px;
1061
+ }
1062
+
1063
+ .logo-icon {
1064
+ font-size: 2.5rem;
1065
+ margin-bottom: 10px;
1066
+ }
1067
+
1068
+ .header-content {
1069
+ padding: 15px;
1070
+ margin-bottom: 20px;
1071
+ }
1072
+
1073
+ .main-content {
1074
+ padding: 15px;
1075
+ border-radius: 15px;
1076
+ }
1077
+
1078
+ .upload-methods {
1079
+ gap: 12px;
1080
+ }
1081
+
1082
+ .method-card {
1083
+ max-width: 100%;
1084
+ padding: 15px;
1085
+ }
1086
+
1087
+ .method-card h3 {
1088
+ font-size: 1.1rem;
1089
+ }
1090
+
1091
+ .method-card p {
1092
+ font-size: 0.85rem;
1093
+ }
1094
+
1095
+ .method-card i {
1096
+ font-size: 1.8rem;
1097
+ margin-bottom: 8px;
1098
+ }
1099
+
1100
+ .upload-area {
1101
+ padding: 30px 10px;
1102
+ border-radius: 12px;
1103
+ }
1104
+
1105
+ .upload-icon {
1106
+ font-size: 2.5rem;
1107
+ margin-bottom: 15px;
1108
+ }
1109
+
1110
+ .upload-content h3 {
1111
+ font-size: 1.2rem;
1112
+ margin-bottom: 8px;
1113
+ }
1114
+
1115
+ .upload-content p {
1116
+ font-size: 0.9rem;
1117
+ }
1118
+
1119
+ .supported-formats {
1120
+ font-size: 0.8rem;
1121
+ }
1122
+
1123
+ .camera-container {
1124
+ padding: 12px;
1125
+ }
1126
+
1127
+ #video {
1128
+ max-height: 250px;
1129
+ border-radius: 8px;
1130
+ }
1131
+
1132
+ .image-preview img {
1133
+ max-height: 250px;
1134
+ }
1135
+
1136
+ .results-section h2 {
1137
+ font-size: 1.6rem;
1138
+ margin-bottom: 20px;
1139
+ }
1140
+
1141
+ .prediction-card,
1142
+ .detailed-analysis,
1143
+ .recommendations {
1144
+ padding: 15px;
1145
+ margin-bottom: 20px;
1146
+ border-radius: 12px;
1147
+ }
1148
+
1149
+ .prediction-header h3 {
1150
+ font-size: 1.1rem;
1151
+ }
1152
+
1153
+ .confidence-badge {
1154
+ padding: 6px 15px;
1155
+ font-size: 1rem;
1156
+ border-radius: 15px;
1157
+ }
1158
+
1159
+ .disease-icon {
1160
+ font-size: 2rem;
1161
+ padding: 12px;
1162
+ }
1163
+
1164
+ .disease-info h4 {
1165
+ font-size: 1.3rem;
1166
+ margin-bottom: 8px;
1167
+ }
1168
+
1169
+ .disease-info p {
1170
+ font-size: 1rem;
1171
+ }
1172
+
1173
+ .detailed-analysis h3,
1174
+ .recommendations h3 {
1175
+ font-size: 1.2rem;
1176
+ margin-bottom: 15px;
1177
+ }
1178
+
1179
+ .probability-item {
1180
+ padding: 10px;
1181
+ border-radius: 8px;
1182
+ margin-bottom: 10px;
1183
+ }
1184
+
1185
+ .probability-label {
1186
+ font-size: 0.9rem;
1187
+ min-width: auto;
1188
+ }
1189
+
1190
+ .probability-bar {
1191
+ height: 5px;
1192
+ min-width: 80px;
1193
+ }
1194
+
1195
+ .probability-value {
1196
+ font-size: 0.9rem;
1197
+ min-width: 40px;
1198
+ }
1199
+
1200
+ .analyzed-image {
1201
+ padding: 15px;
1202
+ margin-bottom: 20px;
1203
+ }
1204
+
1205
+ .analyzed-image h3 {
1206
+ font-size: 1.2rem;
1207
+ margin-bottom: 15px;
1208
+ }
1209
+
1210
+ .analyzed-img {
1211
+ max-height: 200px;
1212
+ border-width: 2px;
1213
+ }
1214
+
1215
+ .recommendation-item {
1216
+ padding: 10px;
1217
+ border-radius: 8px;
1218
+ margin-bottom: 8px;
1219
+ font-size: 0.9rem;
1220
+ }
1221
+
1222
+ .recommendation-item i {
1223
+ font-size: 0.9rem;
1224
+ }
1225
+
1226
+ .btn {
1227
+ padding: 10px 15px;
1228
+ font-size: 0.9rem;
1229
+ border-radius: 6px;
1230
+ }
1231
+
1232
+ .camera-controls {
1233
+ gap: 8px;
1234
+ }
1235
+
1236
+ .image-actions,
1237
+ .result-actions {
1238
+ gap: 8px;
1239
+ }
1240
+
1241
+ .loading-overlay {
1242
+ border-radius: 15px;
1243
+ }
1244
+
1245
+ .spinner {
1246
+ width: 40px;
1247
+ height: 40px;
1248
+ margin-bottom: 15px;
1249
+ }
1250
+
1251
+ .loading-content p {
1252
+ font-size: 1rem;
1253
+ margin-bottom: 5px;
1254
+ }
1255
+
1256
+ .loading-content small {
1257
+ font-size: 0.85rem;
1258
+ }
1259
+
1260
+ .footer {
1261
+ padding: 12px;
1262
+ font-size: 0.85rem;
1263
+ }
1264
+
1265
+ .status-indicator {
1266
+ font-size: 0.8rem;
1267
+ }
1268
+ }
1269
+
1270
+ /* Very small screens */
1271
+ @media (max-width: 320px) {
1272
+ .container {
1273
+ padding: 5px;
1274
+ }
1275
+
1276
+ .header h1 {
1277
+ font-size: 1.6rem;
1278
+ }
1279
+
1280
+ .logo-icon {
1281
+ font-size: 2rem;
1282
+ }
1283
+
1284
+ .main-content {
1285
+ padding: 12px;
1286
+ }
1287
+
1288
+ .upload-area {
1289
+ padding: 20px 8px;
1290
+ }
1291
+
1292
+ .upload-content h3 {
1293
+ font-size: 1.1rem;
1294
+ }
1295
+
1296
+ .btn {
1297
+ padding: 8px 12px;
1298
+ font-size: 0.85rem;
1299
+ max-width: 200px;
1300
+ }
1301
+
1302
+ .prediction-card,
1303
+ .detailed-analysis,
1304
+ .recommendations,
1305
+ .analyzed-image {
1306
+ padding: 12px;
1307
+ }
1308
+
1309
+ .disease-info h4 {
1310
+ font-size: 1.2rem;
1311
+ }
1312
+
1313
+ .analyzed-img {
1314
+ max-height: 180px;
1315
+ }
1316
+ }
1317
+
1318
+ /* Utility Classes */
1319
+ .fade-in {
1320
+ animation: fadeIn 0.5s ease-in;
1321
+ }
1322
+
1323
+ @keyframes fadeIn {
1324
+ from { opacity: 0; transform: translateY(20px); }
1325
+ to { opacity: 1; transform: translateY(0); }
1326
+ }
1327
+
1328
+ .text-center {
1329
+ text-align: center;
1330
+ }
1331
+
1332
+ .hidden {
1333
+ display: none !important;
1334
+ }
static/js/script.js ADDED
@@ -0,0 +1,988 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class PotatoDiseaseDetector {
2
+ constructor() {
3
+ this.currentMethod = 'upload';
4
+ this.stream = null;
5
+ this.selectedFile = null;
6
+ this.initializeElements();
7
+ this.checkBrowserCompatibility();
8
+ this.bindEvents();
9
+ }
10
+
11
+ checkBrowserCompatibility() {
12
+ // Check for File System Access API support
13
+ this.folderSelectionSupported = 'showSaveFilePicker' in window;
14
+
15
+ // Update download help text based on browser compatibility
16
+ const downloadHelp = document.getElementById('downloadHelp');
17
+ if (downloadHelp) {
18
+ if (this.folderSelectionSupported) {
19
+ downloadHelp.innerHTML = '✅ Folder selection supported - Choose where to save your PDF!';
20
+ downloadHelp.style.color = '#28a745';
21
+ } else {
22
+ downloadHelp.innerHTML = '📁 Will download to your default Downloads folder';
23
+ downloadHelp.style.color = '#6c757d';
24
+ }
25
+ }
26
+
27
+ // Update button text based on compatibility
28
+ const downloadBtn = document.getElementById('downloadResultBtn');
29
+ if (downloadBtn && this.folderSelectionSupported) {
30
+ downloadBtn.innerHTML = '<i class="fas fa-folder-open"></i> Choose Folder & Download PDF';
31
+ } else if (downloadBtn) {
32
+ downloadBtn.innerHTML = '<i class="fas fa-download"></i> Download PDF Report';
33
+ }
34
+
35
+ console.log('Browser compatibility:', {
36
+ folderSelection: this.folderSelectionSupported,
37
+ userAgent: navigator.userAgent
38
+ });
39
+ }
40
+
41
+ initializeElements() {
42
+ // Method cards
43
+ this.uploadCard = document.getElementById('uploadCard');
44
+ this.cameraCard = document.getElementById('cameraCard');
45
+
46
+ // Sections
47
+ this.uploadSection = document.getElementById('uploadSection');
48
+ this.cameraSection = document.getElementById('cameraSection');
49
+
50
+ // Upload elements
51
+ this.uploadArea = document.getElementById('uploadArea');
52
+ this.fileInput = document.getElementById('fileInput');
53
+
54
+ // Camera elements
55
+ this.video = document.getElementById('video');
56
+ this.canvas = document.getElementById('canvas');
57
+ this.startCameraBtn = document.getElementById('startCamera');
58
+ this.captureBtn = document.getElementById('captureBtn');
59
+ this.stopCameraBtn = document.getElementById('stopCamera');
60
+
61
+ // Preview and actions
62
+ this.imagePreview = document.getElementById('imagePreview');
63
+ this.previewImg = document.getElementById('previewImg');
64
+ this.predictBtn = document.getElementById('predictBtn');
65
+ this.clearBtn = document.getElementById('clearBtn');
66
+
67
+ // Results
68
+ this.resultsSection = document.getElementById('resultsSection');
69
+ this.loadingOverlay = document.getElementById('loadingOverlay');
70
+ this.newAnalysisBtn = document.getElementById('newAnalysisBtn');
71
+ this.downloadResultBtn = document.getElementById('downloadResultBtn');
72
+
73
+ // Analyzed image display
74
+ this.analyzedImageSection = document.getElementById('analyzedImageSection');
75
+ this.analyzedImage = document.getElementById('analyzedImage');
76
+
77
+ // Result elements
78
+ this.diseaseName = document.getElementById('diseaseName');
79
+ this.diseaseDescription = document.getElementById('diseaseDescription');
80
+ this.confidenceValue = document.getElementById('confidenceValue');
81
+ this.confidenceBadge = document.getElementById('confidenceBadge');
82
+ this.diseaseIcon = document.getElementById('diseaseIcon');
83
+ this.timestamp = document.getElementById('timestamp');
84
+ this.probabilities = document.getElementById('probabilities');
85
+ this.recommendationList = document.getElementById('recommendationList');
86
+ }
87
+
88
+ bindEvents() {
89
+ // Method switching
90
+ this.uploadCard.addEventListener('click', () => this.switchMethod('upload'));
91
+ this.cameraCard.addEventListener('click', () => this.switchMethod('camera'));
92
+
93
+ // Upload events with touch support
94
+ this.uploadArea.addEventListener('click', (e) => {
95
+ console.log('Upload area clicked');
96
+ this.fileInput.click();
97
+ });
98
+
99
+ // Touch events for mobile
100
+ this.uploadArea.addEventListener('touchend', (e) => {
101
+ e.preventDefault();
102
+ console.log('Upload area touched');
103
+ this.fileInput.click();
104
+ });
105
+
106
+ // Specific handler for browse text
107
+ const browseText = document.querySelector('.browse-text');
108
+ if (browseText) {
109
+ browseText.addEventListener('click', (e) => {
110
+ e.stopPropagation();
111
+ console.log('Browse text clicked');
112
+ this.fileInput.click();
113
+ });
114
+
115
+ browseText.addEventListener('touchend', (e) => {
116
+ e.preventDefault();
117
+ e.stopPropagation();
118
+ console.log('Browse text touched');
119
+ this.fileInput.click();
120
+ });
121
+ }
122
+
123
+ this.uploadArea.addEventListener('dragover', this.handleDragOver.bind(this));
124
+ this.uploadArea.addEventListener('dragleave', this.handleDragLeave.bind(this));
125
+ this.uploadArea.addEventListener('drop', this.handleDrop.bind(this));
126
+ this.fileInput.addEventListener('change', (e) => {
127
+ console.log('File input changed:', e.target.files);
128
+ this.handleFileSelect(e);
129
+ });
130
+
131
+ // Camera events
132
+ this.startCameraBtn.addEventListener('click', this.startCamera.bind(this));
133
+ this.captureBtn.addEventListener('click', this.capturePhoto.bind(this));
134
+ this.stopCameraBtn.addEventListener('click', this.stopCamera.bind(this));
135
+
136
+ // Action buttons
137
+ this.predictBtn.addEventListener('click', this.makePrediction.bind(this));
138
+ this.clearBtn.addEventListener('click', this.clearSelection.bind(this));
139
+ this.newAnalysisBtn.addEventListener('click', this.newAnalysis.bind(this));
140
+ this.downloadResultBtn.addEventListener('click', this.downloadReport.bind(this));
141
+ }
142
+
143
+ switchMethod(method) {
144
+ this.currentMethod = method;
145
+
146
+ // Update card states
147
+ this.uploadCard.classList.toggle('active', method === 'upload');
148
+ this.cameraCard.classList.toggle('active', method === 'camera');
149
+
150
+ // Show/hide sections
151
+ this.uploadSection.style.display = method === 'upload' ? 'block' : 'none';
152
+ this.cameraSection.style.display = method === 'camera' ? 'block' : 'none';
153
+
154
+ // Stop camera if switching away
155
+ if (method !== 'camera') {
156
+ this.stopCamera();
157
+ }
158
+
159
+ // Clear any existing selections
160
+ this.clearSelection();
161
+ }
162
+
163
+ // Upload handling
164
+ handleDragOver(e) {
165
+ e.preventDefault();
166
+ this.uploadArea.classList.add('dragover');
167
+ }
168
+
169
+ handleDragLeave(e) {
170
+ e.preventDefault();
171
+ this.uploadArea.classList.remove('dragover');
172
+ }
173
+
174
+ handleDrop(e) {
175
+ e.preventDefault();
176
+ this.uploadArea.classList.remove('dragover');
177
+
178
+ const files = e.dataTransfer.files;
179
+ if (files.length > 0) {
180
+ this.processFile(files[0]);
181
+ }
182
+ }
183
+
184
+ handleFileSelect(e) {
185
+ const file = e.target.files[0];
186
+ if (file) {
187
+ this.processFile(file);
188
+ }
189
+ }
190
+
191
+ processFile(file) {
192
+ console.log('Processing file:', file.name, file.type, file.size);
193
+
194
+ if (!this.isValidImageFile(file)) {
195
+ this.showError('Please select a valid image file (PNG, JPG, JPEG)');
196
+ return;
197
+ }
198
+
199
+ if (file.size > 16 * 1024 * 1024) {
200
+ this.showError('File size must be less than 16MB');
201
+ return;
202
+ }
203
+
204
+ this.selectedFile = file;
205
+ console.log('File selected successfully:', file.name);
206
+ this.displayImagePreview(file);
207
+ }
208
+
209
+ isValidImageFile(file) {
210
+ const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
211
+ return validTypes.includes(file.type);
212
+ }
213
+
214
+ displayImagePreview(file) {
215
+ const reader = new FileReader();
216
+ reader.onload = (e) => {
217
+ this.previewImg.src = e.target.result;
218
+ this.imagePreview.style.display = 'block';
219
+ this.imagePreview.classList.add('fade-in');
220
+ this.hideResults();
221
+ };
222
+ reader.readAsDataURL(file);
223
+ }
224
+
225
+ // Camera handling
226
+ async startCamera() {
227
+ try {
228
+ // Enhanced camera constraints for mobile devices
229
+ const constraints = {
230
+ video: {
231
+ facingMode: 'environment', // Use back camera on mobile
232
+ width: { ideal: 1280, max: 1920 },
233
+ height: { ideal: 720, max: 1080 },
234
+ aspectRatio: { ideal: 16/9 }
235
+ }
236
+ };
237
+
238
+ // Fallback for devices that don't support environment camera
239
+ try {
240
+ this.stream = await navigator.mediaDevices.getUserMedia(constraints);
241
+ } catch (envError) {
242
+ console.log('Environment camera not available, trying default camera');
243
+ const fallbackConstraints = {
244
+ video: {
245
+ width: { ideal: 1280, max: 1920 },
246
+ height: { ideal: 720, max: 1080 }
247
+ }
248
+ };
249
+ this.stream = await navigator.mediaDevices.getUserMedia(fallbackConstraints);
250
+ }
251
+
252
+ this.video.srcObject = this.stream;
253
+ this.video.style.display = 'block';
254
+
255
+ this.startCameraBtn.style.display = 'none';
256
+ this.captureBtn.style.display = 'inline-flex';
257
+ this.stopCameraBtn.style.display = 'inline-flex';
258
+
259
+ } catch (error) {
260
+ console.error('Error accessing camera:', error);
261
+ this.showError('Could not access camera. Please check permissions.');
262
+ }
263
+ }
264
+
265
+ capturePhoto() {
266
+ const context = this.canvas.getContext('2d');
267
+ this.canvas.width = this.video.videoWidth;
268
+ this.canvas.height = this.video.videoHeight;
269
+
270
+ context.drawImage(this.video, 0, 0);
271
+
272
+ this.canvas.toBlob((blob) => {
273
+ this.selectedFile = blob;
274
+ this.previewImg.src = this.canvas.toDataURL();
275
+ this.imagePreview.style.display = 'block';
276
+ this.imagePreview.classList.add('fade-in');
277
+ this.hideResults();
278
+ }, 'image/png');
279
+ }
280
+
281
+ stopCamera() {
282
+ if (this.stream) {
283
+ this.stream.getTracks().forEach(track => track.stop());
284
+ this.stream = null;
285
+ }
286
+
287
+ this.video.style.display = 'none';
288
+ this.startCameraBtn.style.display = 'inline-flex';
289
+ this.captureBtn.style.display = 'none';
290
+ this.stopCameraBtn.style.display = 'none';
291
+ }
292
+
293
+ // Prediction
294
+ async makePrediction() {
295
+ console.log('Making prediction...');
296
+ console.log('Current method:', this.currentMethod);
297
+ console.log('Selected file:', this.selectedFile);
298
+
299
+ if (!this.selectedFile) {
300
+ this.showError('Please select an image first');
301
+ return;
302
+ }
303
+
304
+ this.showLoading(true);
305
+
306
+ try {
307
+ let response;
308
+
309
+ if (this.currentMethod === 'camera') {
310
+ console.log('Using camera prediction endpoint');
311
+ // Send base64 image for camera
312
+ const imageData = this.canvas.toDataURL();
313
+ response = await fetch('/predict_camera', {
314
+ method: 'POST',
315
+ headers: {
316
+ 'Content-Type': 'application/json',
317
+ },
318
+ body: JSON.stringify({ image: imageData })
319
+ });
320
+ } else {
321
+ console.log('Using upload prediction endpoint');
322
+ // Send file for upload
323
+ const formData = new FormData();
324
+ formData.append('file', this.selectedFile);
325
+
326
+ console.log('FormData created with file:', this.selectedFile.name);
327
+
328
+ response = await fetch('/predict', {
329
+ method: 'POST',
330
+ body: formData
331
+ });
332
+ }
333
+
334
+ console.log('Response status:', response.status);
335
+
336
+ if (!response.ok) {
337
+ const errorText = await response.text();
338
+ console.error('Response error:', errorText);
339
+ throw new Error(`HTTP error! status: ${response.status}`);
340
+ }
341
+
342
+ const result = await response.json();
343
+ console.log('Prediction result:', result);
344
+
345
+ if (result.error) {
346
+ throw new Error(result.error);
347
+ }
348
+
349
+ this.displayResults(result);
350
+
351
+ } catch (error) {
352
+ console.error('Prediction error:', error);
353
+ this.showError(`Prediction failed: ${error.message}`);
354
+ } finally {
355
+ this.showLoading(false);
356
+ }
357
+ }
358
+
359
+ displayResults(result) {
360
+ // Store current prediction data for PDF generation
361
+ this.currentPredictionData = result;
362
+
363
+ // Display the analyzed image if available
364
+ if (result.image_url) {
365
+ this.analyzedImage.src = result.image_url;
366
+ this.analyzedImageSection.style.display = 'block';
367
+ }
368
+
369
+ // Update main prediction
370
+ this.diseaseName.textContent = result.predicted_class;
371
+ this.diseaseDescription.textContent = result.description;
372
+ this.confidenceValue.textContent = `${result.confidence}%`;
373
+ this.timestamp.textContent = `Analysis completed: ${result.timestamp}`;
374
+
375
+ // Update confidence badge color
376
+ this.updateConfidenceBadge(result.confidence);
377
+
378
+ // Update disease icon
379
+ this.updateDiseaseIcon(result.predicted_class);
380
+
381
+ // Display probabilities
382
+ this.displayProbabilities(result.all_predictions);
383
+
384
+ // Display recommendations
385
+ this.displayRecommendations(result.recommendations);
386
+
387
+ // Show results
388
+ this.resultsSection.style.display = 'block';
389
+ this.resultsSection.classList.add('fade-in');
390
+ this.resultsSection.scrollIntoView({ behavior: 'smooth' });
391
+ }
392
+
393
+ updateConfidenceBadge(confidence) {
394
+ if (confidence >= 90) {
395
+ this.confidenceBadge.style.background = 'linear-gradient(135deg, #22c55e, #16a34a)';
396
+ } else if (confidence >= 70) {
397
+ this.confidenceBadge.style.background = 'linear-gradient(135deg, #f59e0b, #d97706)';
398
+ } else {
399
+ this.confidenceBadge.style.background = 'linear-gradient(135deg, #ef4444, #dc2626)';
400
+ }
401
+ }
402
+
403
+ updateDiseaseIcon(diseaseName) {
404
+ const iconMap = {
405
+ 'Early Blight': { icon: 'fas fa-exclamation-triangle', color: '#f59e0b' },
406
+ 'Late Blight': { icon: 'fas fa-skull-crossbones', color: '#ef4444' },
407
+ 'Healthy': { icon: 'fas fa-check-circle', color: '#22c55e' }
408
+ };
409
+
410
+ const iconInfo = iconMap[diseaseName] || { icon: 'fas fa-leaf', color: '#667eea' };
411
+ this.diseaseIcon.innerHTML = `<i class="${iconInfo.icon}"></i>`;
412
+ this.diseaseIcon.style.color = iconInfo.color;
413
+ }
414
+
415
+ displayProbabilities(allPredictions) {
416
+ this.probabilities.innerHTML = '';
417
+
418
+ Object.entries(allPredictions).forEach(([disease, data]) => {
419
+ const probability = data.probability;
420
+ const item = document.createElement('div');
421
+ item.className = 'probability-item';
422
+
423
+ const color = this.getProbabilityColor(probability);
424
+
425
+ item.innerHTML = `
426
+ <div class="probability-label">${disease}</div>
427
+ <div class="probability-bar">
428
+ <div class="probability-fill" style="width: ${probability}%; background: ${color};"></div>
429
+ </div>
430
+ <div class="probability-value">${probability}%</div>
431
+ `;
432
+
433
+ this.probabilities.appendChild(item);
434
+ });
435
+ }
436
+
437
+ getProbabilityColor(probability) {
438
+ if (probability >= 70) return 'linear-gradient(90deg, #22c55e, #16a34a)';
439
+ if (probability >= 40) return 'linear-gradient(90deg, #f59e0b, #d97706)';
440
+ return 'linear-gradient(90deg, #ef4444, #dc2626)';
441
+ }
442
+
443
+ displayRecommendations(recommendations) {
444
+ this.recommendationList.innerHTML = '';
445
+
446
+ recommendations.forEach((rec, index) => {
447
+ const item = document.createElement('div');
448
+ item.className = 'recommendation-item';
449
+ item.innerHTML = `
450
+ <i class="fas fa-check-circle"></i>
451
+ <span>${rec}</span>
452
+ `;
453
+ this.recommendationList.appendChild(item);
454
+ });
455
+ }
456
+
457
+ // Utility methods
458
+ clearSelection() {
459
+ this.selectedFile = null;
460
+ this.fileInput.value = '';
461
+ this.imagePreview.style.display = 'none';
462
+ this.hideResults();
463
+ }
464
+
465
+ newAnalysis() {
466
+ this.clearSelection();
467
+ this.stopCamera();
468
+ this.switchMethod('upload');
469
+ }
470
+
471
+ hideResults() {
472
+ this.resultsSection.style.display = 'none';
473
+ this.analyzedImageSection.style.display = 'none';
474
+ }
475
+
476
+ showLoading(show) {
477
+ this.loadingOverlay.style.display = show ? 'flex' : 'none';
478
+ }
479
+
480
+ showError(message) {
481
+ alert(`Error: ${message}`);
482
+ }
483
+
484
+ async downloadReport() {
485
+ try {
486
+ // Show loading state
487
+ this.downloadResultBtn.disabled = true;
488
+
489
+ if (this.folderSelectionSupported) {
490
+ this.downloadResultBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Preparing folder selection...';
491
+ } else {
492
+ this.downloadResultBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Generating PDF...';
493
+ }
494
+
495
+ // Gather all report data
496
+ const reportData = {
497
+ predicted_class: this.diseaseName.textContent,
498
+ confidence: parseFloat(this.confidenceValue.textContent.replace('%', '')),
499
+ description: this.diseaseDescription.textContent,
500
+ timestamp: this.timestamp.textContent,
501
+ image_url: this.analyzedImage.src || null,
502
+ all_predictions: this.currentPredictionData || {},
503
+ recommendations: this.getCurrentRecommendations()
504
+ };
505
+
506
+ // Try to generate PDF via backend
507
+ const response = await fetch('/generate-pdf-report', {
508
+ method: 'POST',
509
+ headers: {
510
+ 'Content-Type': 'application/json',
511
+ },
512
+ body: JSON.stringify(reportData)
513
+ });
514
+
515
+ if (response.ok) {
516
+ // Backend PDF generation successful
517
+ const blob = await response.blob();
518
+ const url = window.URL.createObjectURL(blob);
519
+
520
+ // Create download link with File System Access API for folder selection
521
+ if (this.folderSelectionSupported) {
522
+ this.downloadResultBtn.innerHTML = '<i class="fas fa-folder-open"></i> Choose save location...';
523
+
524
+ try {
525
+ // Modern browsers with File System Access API
526
+ const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:]/g, '');
527
+ const diseaseName = reportData.predicted_class.replace(/\s+/g, '_');
528
+ const filename = `potato_disease_report_${diseaseName}_${timestamp}.pdf`;
529
+
530
+ // Show folder picker dialog
531
+ const fileHandle = await window.showSaveFilePicker({
532
+ suggestedName: filename,
533
+ types: [
534
+ {
535
+ description: 'PDF Reports',
536
+ accept: {
537
+ 'application/pdf': ['.pdf'],
538
+ },
539
+ },
540
+ ],
541
+ excludeAcceptAllOption: true,
542
+ startIn: 'documents' // Suggest Documents folder
543
+ });
544
+
545
+ this.downloadResultBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Saving to selected folder...';
546
+
547
+ const writable = await fileHandle.createWritable();
548
+ await writable.write(blob);
549
+ await writable.close();
550
+
551
+ this.showSuccessMessage('📁 PDF report saved to your chosen folder successfully!');
552
+ } catch (err) {
553
+ if (err.name === 'AbortError') {
554
+ // User cancelled folder selection
555
+ this.showInfoMessage('📁 Folder selection cancelled. Try again to choose a save location.');
556
+ } else {
557
+ console.error('Folder save error:', err);
558
+ // Fallback to regular download
559
+ this.fallbackDownload(url, blob, reportData);
560
+ this.showWarningMessage('📁 Folder selection failed. Downloaded to default location instead.');
561
+ }
562
+ }
563
+ } else {
564
+ // Fallback for browsers without File System Access API
565
+ this.fallbackDownload(url, blob, reportData);
566
+ }
567
+
568
+ window.URL.revokeObjectURL(url);
569
+ } else {
570
+ // Backend failed, check if it's a server-side issue or ReportLab missing
571
+ let errorData;
572
+ try {
573
+ errorData = await response.json();
574
+ } catch (e) {
575
+ errorData = { error: 'Unknown server error' };
576
+ }
577
+
578
+ console.warn('Backend PDF generation failed:', errorData);
579
+
580
+ if (errorData.fallback === 'client' || response.status === 503) {
581
+ // Server suggests client-side fallback
582
+ console.log('Using client-side PDF generation fallback');
583
+ await this.generateClientSidePDF(reportData);
584
+ } else {
585
+ // Other server errors
586
+ throw new Error(errorData.message || errorData.error || 'Server PDF generation failed');
587
+ }
588
+ }
589
+
590
+ } catch (error) {
591
+ console.error('Error downloading report:', error);
592
+ // Final fallback to text report
593
+ this.generateTextReport();
594
+ this.showErrorMessage('PDF generation failed, downloaded as text file instead.');
595
+ } finally {
596
+ // Reset button state
597
+ this.downloadResultBtn.disabled = false;
598
+
599
+ if (this.folderSelectionSupported) {
600
+ this.downloadResultBtn.innerHTML = '<i class="fas fa-folder-open"></i> Choose Folder & Download PDF';
601
+ } else {
602
+ this.downloadResultBtn.innerHTML = '<i class="fas fa-download"></i> Download PDF Report';
603
+ }
604
+ }
605
+ }
606
+
607
+ fallbackDownload(url, blob, reportData) {
608
+ const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:]/g, '');
609
+ const diseaseName = reportData.predicted_class.replace(/\s+/g, '_');
610
+ const filename = `potato_disease_report_${diseaseName}_${timestamp}.pdf`;
611
+
612
+ const a = document.createElement('a');
613
+ a.href = url;
614
+ a.download = filename;
615
+ document.body.appendChild(a);
616
+ a.click();
617
+ document.body.removeChild(a);
618
+
619
+ this.showSuccessMessage('PDF report downloaded to default folder!');
620
+ }
621
+
622
+ async generateClientSidePDF(reportData) {
623
+ // Client-side PDF generation using jsPDF
624
+ try {
625
+ if (typeof jsPDF === 'undefined') {
626
+ throw new Error('jsPDF library not loaded');
627
+ }
628
+
629
+ console.log('📄 Generating client-side PDF...');
630
+ const { jsPDF } = window.jspdf;
631
+ const doc = new jsPDF();
632
+
633
+ // Add content to PDF
634
+ doc.setFontSize(20);
635
+ doc.text('🥔 POTATO DISEASE DETECTION REPORT', 20, 30);
636
+
637
+ doc.setFontSize(12);
638
+ doc.text(`Report Generated: ${reportData.timestamp}`, 20, 50);
639
+ doc.text(`Analysis Method: Deep Learning AI Classification`, 20, 60);
640
+ doc.text(`Model Version: TensorFlow/Keras CNN v1.0`, 20, 70);
641
+
642
+ // Main diagnosis
643
+ doc.setFontSize(16);
644
+ doc.text('🎯 DIAGNOSIS RESULTS', 20, 90);
645
+
646
+ doc.setFontSize(12);
647
+ doc.text(`Predicted Disease: ${reportData.predicted_class}`, 20, 105);
648
+ doc.text(`Confidence: ${reportData.confidence}%`, 20, 115);
649
+
650
+ // Risk assessment
651
+ let riskLevel = 'Unknown';
652
+ if (reportData.confidence >= 80) riskLevel = 'High Confidence';
653
+ else if (reportData.confidence >= 60) riskLevel = 'Medium Confidence';
654
+ else riskLevel = 'Low Confidence - Manual Verification Recommended';
655
+
656
+ doc.text(`Risk Assessment: ${riskLevel}`, 20, 125);
657
+
658
+ // Description
659
+ doc.setFontSize(16);
660
+ doc.text('📋 DESCRIPTION', 20, 145);
661
+
662
+ doc.setFontSize(10);
663
+ const splitDescription = doc.splitTextToSize(reportData.description, 170);
664
+ doc.text(splitDescription, 20, 160);
665
+
666
+ let yPos = 160 + (splitDescription.length * 5) + 15;
667
+
668
+ // Probability breakdown
669
+ doc.setFontSize(16);
670
+ doc.text('📊 PROBABILITY BREAKDOWN', 20, yPos);
671
+ yPos += 15;
672
+
673
+ doc.setFontSize(10);
674
+ if (reportData.all_predictions) {
675
+ for (const [disease, info] of Object.entries(reportData.all_predictions)) {
676
+ doc.text(`• ${disease}: ${info.probability}%`, 20, yPos);
677
+ yPos += 10;
678
+ }
679
+ }
680
+
681
+ yPos += 10;
682
+
683
+ // Recommendations
684
+ doc.setFontSize(16);
685
+ doc.text('💡 TREATMENT RECOMMENDATIONS', 20, yPos);
686
+ yPos += 15;
687
+
688
+ doc.setFontSize(10);
689
+ reportData.recommendations.forEach((rec, index) => {
690
+ const recText = `${index + 1}. ${rec}`;
691
+ const splitRec = doc.splitTextToSize(recText, 170);
692
+ doc.text(splitRec, 20, yPos);
693
+ yPos += splitRec.length * 5 + 3;
694
+
695
+ // Add new page if needed
696
+ if (yPos > 270) {
697
+ doc.addPage();
698
+ yPos = 20;
699
+ }
700
+ });
701
+
702
+ // Footer
703
+ yPos = Math.max(yPos + 20, 250);
704
+ doc.setFontSize(8);
705
+ doc.text('Generated by Potato Disease Detection System', 20, yPos);
706
+ doc.text('Powered by Flask & TensorFlow | Lucky Sharma', 20, yPos + 8);
707
+ doc.text('© 2025 All Rights Reserved', 20, yPos + 16);
708
+
709
+ // Save PDF
710
+ const timestamp = new Date().toISOString().slice(0, 19).replace(/[-:]/g, '');
711
+ const diseaseName = reportData.predicted_class.replace(/\s+/g, '_');
712
+ const filename = `potato_disease_report_${diseaseName}_${timestamp}.pdf`;
713
+
714
+ // Try to use File System Access API for folder selection
715
+ if ('showSaveFilePicker' in window) {
716
+ try {
717
+ const fileHandle = await window.showSaveFilePicker({
718
+ suggestedName: filename,
719
+ types: [
720
+ {
721
+ description: 'PDF files',
722
+ accept: {
723
+ 'application/pdf': ['.pdf'],
724
+ },
725
+ },
726
+ ],
727
+ });
728
+
729
+ const writable = await fileHandle.createWritable();
730
+ const pdfBlob = doc.output('blob');
731
+ await writable.write(pdfBlob);
732
+ await writable.close();
733
+
734
+ this.showSuccessMessage('PDF report saved successfully using client-side generation!');
735
+ } catch (err) {
736
+ if (err.name !== 'AbortError') {
737
+ // Fallback to regular download
738
+ doc.save(filename);
739
+ this.showSuccessMessage('PDF report generated successfully!');
740
+ }
741
+ }
742
+ } else {
743
+ // Regular download for older browsers
744
+ doc.save(filename);
745
+ this.showSuccessMessage('PDF report generated successfully!');
746
+ }
747
+
748
+ } catch (error) {
749
+ console.error('Client-side PDF generation failed:', error);
750
+ this.showErrorMessage('PDF generation failed. Falling back to text report.');
751
+ this.generateTextReport();
752
+ }
753
+ }
754
+
755
+ getCurrentRecommendations() {
756
+ const recommendations = [];
757
+ const recItems = this.recommendationList.querySelectorAll('.recommendation-item span');
758
+ recItems.forEach(item => {
759
+ recommendations.push(item.textContent);
760
+ });
761
+ return recommendations;
762
+ }
763
+
764
+ generateTextReport() {
765
+ // Fallback text report generation (original functionality)
766
+ const diseaseName = this.diseaseName.textContent;
767
+ const confidence = this.confidenceValue.textContent;
768
+ const description = this.diseaseDescription.textContent;
769
+ const timestamp = this.timestamp.textContent;
770
+
771
+ let report = `POTATO DISEASE DETECTION REPORT\n`;
772
+ report += `=====================================\n\n`;
773
+ report += `${timestamp}\n\n`;
774
+ report += `DIAGNOSIS: ${diseaseName}\n`;
775
+ report += `CONFIDENCE: ${confidence}\n\n`;
776
+ report += `DESCRIPTION:\n${description}\n\n`;
777
+ report += `RECOMMENDATIONS:\n`;
778
+
779
+ const recommendations = this.recommendationList.querySelectorAll('.recommendation-item span');
780
+ recommendations.forEach((rec, index) => {
781
+ report += `${index + 1}. ${rec.textContent}\n`;
782
+ });
783
+
784
+ report += `\n=====================================\n`;
785
+ report += `Generated by Potato Disease Detection System\n`;
786
+ report += `Powered by Flask & TensorFlow\n`;
787
+
788
+ // Download as text file
789
+ const blob = new Blob([report], { type: 'text/plain' });
790
+ const url = window.URL.createObjectURL(blob);
791
+ const a = document.createElement('a');
792
+ a.href = url;
793
+ a.download = `potato_disease_report_${Date.now()}.txt`;
794
+ document.body.appendChild(a);
795
+ a.click();
796
+ window.URL.revokeObjectURL(url);
797
+ document.body.removeChild(a);
798
+ }
799
+
800
+ showSuccessMessage(message) {
801
+ this.showMessage(message, 'success');
802
+ }
803
+
804
+ showInfoMessage(message) {
805
+ this.showMessage(message, 'info');
806
+ }
807
+
808
+ showWarningMessage(message) {
809
+ this.showMessage(message, 'warning');
810
+ }
811
+
812
+ showErrorMessage(message) {
813
+ this.showMessage(message, 'error');
814
+ }
815
+
816
+ showMessage(message, type = 'info') {
817
+ // Create or update message container
818
+ let messageContainer = document.getElementById('message-container');
819
+ if (!messageContainer) {
820
+ messageContainer = document.createElement('div');
821
+ messageContainer.id = 'message-container';
822
+ messageContainer.style.position = 'fixed';
823
+ messageContainer.style.top = '20px';
824
+ messageContainer.style.right = '20px';
825
+ messageContainer.style.zIndex = '10000';
826
+ messageContainer.style.maxWidth = '400px';
827
+ document.body.appendChild(messageContainer);
828
+ }
829
+
830
+ // Create message element
831
+ const messageEl = document.createElement('div');
832
+ messageEl.className = `message message-${type}`;
833
+ messageEl.innerHTML = `
834
+ <div style="
835
+ background: ${this.getMessageColor(type)};
836
+ color: white;
837
+ padding: 12px 16px;
838
+ border-radius: 8px;
839
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
840
+ margin-bottom: 10px;
841
+ display: flex;
842
+ align-items: center;
843
+ justify-content: space-between;
844
+ font-size: 14px;
845
+ animation: slideInRight 0.3s ease-out;
846
+ ">
847
+ <span>${message}</span>
848
+ <button onclick="this.parentElement.parentElement.remove()"
849
+ style="background: none; border: none; color: white; font-size: 18px; cursor: pointer; padding: 0; margin-left: 10px;">×</button>
850
+ </div>
851
+ `;
852
+
853
+ // Add CSS animation if not already added
854
+ if (!document.getElementById('message-styles')) {
855
+ const style = document.createElement('style');
856
+ style.id = 'message-styles';
857
+ style.textContent = `
858
+ @keyframes slideInRight {
859
+ from { transform: translateX(100%); opacity: 0; }
860
+ to { transform: translateX(0); opacity: 1; }
861
+ }
862
+ `;
863
+ document.head.appendChild(style);
864
+ }
865
+
866
+ messageContainer.appendChild(messageEl);
867
+
868
+ // Auto-remove after 5 seconds
869
+ setTimeout(() => {
870
+ if (messageEl.parentElement) {
871
+ messageEl.remove();
872
+ }
873
+ }, 5000);
874
+ }
875
+
876
+ getMessageColor(type) {
877
+ const colors = {
878
+ success: '#10b981', // green
879
+ info: '#3b82f6', // blue
880
+ warning: '#f59e0b', // amber
881
+ error: '#ef4444' // red
882
+ };
883
+ return colors[type] || colors.info;
884
+ }
885
+ }
886
+
887
+ // Initialize the application when DOM is loaded
888
+ document.addEventListener('DOMContentLoaded', () => {
889
+ new PotatoDiseaseDetector();
890
+ });
891
+
892
+ // Add some utility functions
893
+ function formatFileSize(bytes) {
894
+ if (bytes === 0) return '0 Bytes';
895
+ const k = 1024;
896
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
897
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
898
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
899
+ }
900
+
901
+ // Mobile device detection and utilities
902
+ function isMobileDevice() {
903
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
904
+ }
905
+
906
+ function isIOSDevice() {
907
+ return /iPad|iPhone|iPod/.test(navigator.userAgent);
908
+ }
909
+
910
+ function getOptimalImageSize() {
911
+ const isMobile = isMobileDevice();
912
+ if (isMobile) {
913
+ return {
914
+ maxWidth: window.innerWidth - 40,
915
+ maxHeight: Math.min(window.innerHeight * 0.4, 300)
916
+ };
917
+ }
918
+ return {
919
+ maxWidth: 400,
920
+ maxHeight: 400
921
+ };
922
+ }
923
+
924
+ // Prevent double-tap zoom on mobile
925
+ function preventDoubleTab() {
926
+ let lastTouchEnd = 0;
927
+ document.addEventListener('touchend', function (event) {
928
+ const now = (new Date()).getTime();
929
+ if (now - lastTouchEnd <= 300) {
930
+ event.preventDefault();
931
+ }
932
+ lastTouchEnd = now;
933
+ }, false);
934
+ }
935
+
936
+ // Initialize mobile optimizations
937
+ if (isMobileDevice()) {
938
+ preventDoubleTab();
939
+
940
+ // Add mobile class to body for CSS targeting
941
+ document.body.classList.add('mobile-device');
942
+
943
+ if (isIOSDevice()) {
944
+ document.body.classList.add('ios-device');
945
+ }
946
+
947
+ // Adjust viewport height for mobile browsers
948
+ function setVH() {
949
+ let vh = window.innerHeight * 0.01;
950
+ document.documentElement.style.setProperty('--vh', `${vh}px`);
951
+ }
952
+
953
+ setVH();
954
+ window.addEventListener('resize', setVH);
955
+ window.addEventListener('orientationchange', () => {
956
+ setTimeout(setVH, 100);
957
+ });
958
+ }
959
+
960
+ // Check browser compatibility
961
+ function checkBrowserSupport() {
962
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
963
+ console.warn('Camera functionality not supported in this browser');
964
+ const cameraCard = document.getElementById('cameraCard');
965
+ if (cameraCard) {
966
+ cameraCard.style.opacity = '0.5';
967
+ cameraCard.style.cursor = 'not-allowed';
968
+
969
+ // Add tooltip for unsupported browsers
970
+ const tooltip = document.createElement('div');
971
+ tooltip.className = 'tooltip';
972
+ tooltip.textContent = 'Camera not supported in this browser';
973
+ cameraCard.appendChild(tooltip);
974
+ }
975
+ }
976
+
977
+ // Check for file upload support
978
+ if (!window.File || !window.FileReader || !window.FileList || !window.Blob) {
979
+ console.warn('File upload not supported in this browser');
980
+ const uploadCard = document.getElementById('uploadCard');
981
+ if (uploadCard) {
982
+ uploadCard.style.opacity = '0.7';
983
+ }
984
+ }
985
+ }
986
+
987
+ // Run compatibility check
988
+ checkBrowserSupport();
static/uploads/20250711_012123_1cd053f6-0016-4680-a924-af15aecd7fb2___RS_LB_4414.JPG ADDED
static/uploads/20250711_012557_0eb24a67-a174-43db-86c7-cca8795942a2___RS_LB_4722.JPG ADDED
static/uploads/20250711_014017_2f81d148-c62f-4d3c-baf4-72b77abea41a___RS_Early.B_7493.JPG ADDED
static/uploads/20250711_015310_1e671694-5713-4568-b8ad-06f15688d25e___RS_Early.B_7659.JPG ADDED
static/uploads/20250711_015412_0a79700b-f834-41f5-ae51-6ceda6f67a48___RS_Early.B_8951.JPG ADDED
static/uploads/20250711_022739_414f6249-9f78-4af5-9593-9d5a7e7d979f___RS_HL_1918.JPG ADDED
static/uploads/20250711_234352_2f7b6898-a342-42a5-a0e5-a9f2bad7eaf1___RS_LB_2831.JPG ADDED
static/uploads/20250711_234419_0e7f0484-16eb-4183-b702-0a5b4f94d015___RS_LB_4000.JPG ADDED
static/uploads/20250711_234838_early1.jpeg ADDED
static/uploads/20250711_234852_healthy.jpeg ADDED