github-actions[bot] commited on
Commit
26e5b8d
Β·
0 Parent(s):

Sync to Hugging Face Space (model and log images excluded)

Browse files
.gitattributes ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ *.pt filter=lfs diff=lfs merge=lfs -text
2
+ *.bin filter=lfs diff=lfs merge=lfs -text
3
+ *.pkl filter=lfs diff=lfs merge=lfs -text
.github/workflows/sync_to_hf.yml ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync to Hugging Face Space
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ sync-to-hf:
11
+ name: Push to HF Space
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: read
15
+ steps:
16
+ - name: Checkout repository
17
+ uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0
20
+ lfs: true
21
+
22
+ - name: Configure Git
23
+ run: |
24
+ git config --global user.email "github-actions[bot]@users.noreply.github.com"
25
+ git config --global user.name "github-actions[bot]"
26
+
27
+ - name: Validate HF_TOKEN
28
+ env:
29
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
30
+ run: |
31
+ if [ -z "${HF_TOKEN}" ]; then
32
+ echo "Error: HF_TOKEN secret is not set."
33
+ echo "Please add a Hugging Face User Access Token as a repository secret named HF_TOKEN."
34
+ echo "See: https://huggingface.co/settings/tokens"
35
+ exit 1
36
+ fi
37
+
38
+ - name: Push to Hugging Face Space
39
+ env:
40
+ HF_TOKEN: ${{ secrets.HF_TOKEN }}
41
+ run: |
42
+ git remote add space https://Abs6187:${HF_TOKEN}@huggingface.co/spaces/Abs6187/TechTrident
43
+
44
+ # Build a clean deployment commit that excludes binary files that are
45
+ # already present on the HF Space or would be rejected by the pre-receive hook.
46
+ # - model/: files exceed the 10 MiB per-file limit.
47
+ # - logs/*.png: binary image files rejected by HF's binary-file policy.
48
+ # git checkout --orphan keeps all current files staged in the index.
49
+ git checkout --orphan hf-deploy
50
+ git rm -rf --cached model/
51
+ git rm -rf --cached logs/*.png
52
+ git commit -m "Sync to Hugging Face Space (model and log images excluded)"
53
+ git push --force space hf-deploy:main
README.md ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Solar Panel AI Diagnostics
2
+ **TechTrident** | **Wadla 4.0 2025** | **[HF Demo](https://huggingface.co/spaces/Abs6187/TechTrident)**
3
+
4
+ [![ONNX](https://img.shields.io/badge/ONNX-v1.15-blue)](https://onnx.ai) [![Status](https://img.shields.io/badge/Model_Locked-PASSED-green)](https://wadla.ai)
5
+
6
+ ## Problem Statement 3: Solar Panel Maintenance
7
+ **AI system detects defects/degradation** (cracks, hotspots, soiling) using tabular performance data + images. **ONNX export required**. Public datasets only.
8
+
9
+ **Classes**: `['Bird-drop', 'Clean', 'Dusty', 'Electrical-damage', 'Physical-Damage', 'Snow-Covered']`
10
+
11
+ ## Architecture
12
+
13
+ ### System Workflow
14
+ ![System Workflow Diagram](systemworkflow.jpeg)
15
+
16
+ ### Use Case Diagram
17
+ ![Use Case Diagram](usecasediagram.jpeg)
18
+
19
+ **Models**: ResNet18 + XGBoost + RandomForest | **ONNX v1.15**
20
+
21
+ ## Dependencies Installation
22
+ ```bash
23
+ pip install -r requirements.txt
24
+ ```
25
+
26
+ **Outputs**: `resnet18_solar.onnx`, `xgboost_degr.onnx`
27
+
28
+ **Output**: `{"priority": "HIGH", "30d_loss": "18.2%"}`
29
+
30
+ ## ONNX Inference
31
+ ```bash
32
+ cd code
33
+ python infer.py
34
+ ```
35
+
36
+ **Live Demo**: [HuggingFace Space](https://huggingface.co/spaces/Abs6187/TechTrident)
37
+
38
+ ## Quick Results
39
+ | Metric | Value |
40
+ |--------|-------|
41
+ | Defect Acc | **99.2%** |
42
+ | Degradation RMSE | **0.87%** |
43
+ | Inference | **15ms** |
44
+
45
+ ## Structure
46
+ ```text
47
+ /model
48
+ └── final_model.onnx
49
+ /code
50
+ β”œβ”€β”€ training.ipynb
51
+ └── infer.py
52
+ /data
53
+ └── dataset_info.txt
54
+ /logs
55
+ └── training_logs.txt
56
+ app.py ← Hugging Face Spaces entry point
57
+ README.md
58
+ requirements.txt
59
+ ```
60
+
61
+ ## Team TechTrident
62
+ - **Lead ML Engineer**: Dev Kumar Sharma (Kuch aur Train krna hai Model)
63
+ - **Computer Vision**: Abhay Gupta
64
+ - **Full-Stack Dev**: Aditya Patwa
65
+ - **BTech CS, Shri Ram IT, Jabalpur**
66
+
67
+ **Contact**: [contact2abhay@gmail.com](mailto:contact2abhay@gmail.com)
68
+
69
+ **Datasets**: [PV Panel Defect Dataset](https://www.kaggle.com/datasets/alicjalena/pv-panel-defect-dataset)
70
+ **TechTrident** | Wadla 4.0 Hackathon 2025
71
+
72
+
73
+
app.py ADDED
@@ -0,0 +1,538 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import onnxruntime as ort
2
+ import numpy as np
3
+ from PIL import Image
4
+ from torchvision import transforms
5
+ import gradio as gr
6
+ import cv2
7
+ from datetime import datetime, timedelta
8
+ import os
9
+ import html
10
+
11
+
12
+ # Path to ONNX model (relative to repo root for HF Spaces)
13
+ ONNX_PATH = "model/final_model.onnx"
14
+
15
+ # Load ONNX runtime session
16
+ ort_session = ort.InferenceSession(ONNX_PATH, providers=["CPUExecutionProvider"])
17
+
18
+ print("βœ… ONNX model loaded")
19
+
20
+ # Class labels (VERY IMPORTANT: order must match training)
21
+ CLASS_NAMES = [
22
+ 'Bird-drop',
23
+ 'Clean',
24
+ 'Dusty',
25
+ 'Electrical-damage',
26
+ 'Physical-Damage',
27
+ 'Snow-Covered'
28
+ ]
29
+
30
+ # Same preprocessing as training
31
+ IMG_SIZE = 224
32
+
33
+ preprocess = transforms.Compose([
34
+ transforms.Resize((IMG_SIZE, IMG_SIZE)),
35
+ transforms.Grayscale(num_output_channels=3),
36
+ transforms.ToTensor(),
37
+ transforms.Normalize(
38
+ mean=[0.485, 0.456, 0.406],
39
+ std=[0.229, 0.224, 0.225]
40
+ )
41
+ ])
42
+
43
+ # Maintenance recommendations database
44
+ MAINTENANCE_RECOMMENDATIONS = {
45
+ 'Bird-drop': {
46
+ 'severity': 'Medium',
47
+ 'severity_color': '🟑',
48
+ 'urgency': 'Schedule within 1-2 weeks',
49
+ 'impact': '5-15% efficiency loss',
50
+ 'actions': [
51
+ 'Clean affected panels with soft brush and water',
52
+ 'Install bird deterrents (spikes, netting, or reflective tape)',
53
+ 'Inspect for corrosion under droppings',
54
+ 'Apply protective coating if acid damage detected'
55
+ ],
56
+ 'frequency': 'Inspect monthly in areas with high bird activity',
57
+ 'degradation_rate': 0.8
58
+ },
59
+ 'Clean': {
60
+ 'severity': 'Low',
61
+ 'severity_color': '🟒',
62
+ 'urgency': 'Routine maintenance only',
63
+ 'impact': 'Optimal performance (0-2% below peak)',
64
+ 'actions': [
65
+ 'Continue regular monitoring schedule',
66
+ 'Quarterly visual inspections recommended',
67
+ 'Annual professional inspection',
68
+ 'Maintain vegetation clearance around panels'
69
+ ],
70
+ 'frequency': 'Quarterly inspections',
71
+ 'degradation_rate': 0.5
72
+ },
73
+ 'Dusty': {
74
+ 'severity': 'Medium',
75
+ 'severity_color': '🟑',
76
+ 'urgency': 'Schedule within 2-4 weeks',
77
+ 'impact': '10-25% efficiency loss depending on dust thickness',
78
+ 'actions': [
79
+ 'Clean panels with deionized water and soft microfiber cloth',
80
+ 'Consider automated cleaning system for frequent dust',
81
+ 'Apply anti-soiling nano-coating',
82
+ 'Schedule cleaning before monsoon/rain season'
83
+ ],
84
+ 'frequency': 'Clean every 2-6 months (varies by location)',
85
+ 'degradation_rate': 1.2
86
+ },
87
+ 'Electrical-damage': {
88
+ 'severity': 'High',
89
+ 'severity_color': 'πŸ”΄',
90
+ 'urgency': 'URGENT - Address within 24-48 hours',
91
+ 'impact': '30-100% efficiency loss, fire/safety risk',
92
+ 'actions': [
93
+ '⚠️ IMMEDIATELY disconnect affected panel circuit',
94
+ 'Call certified solar technician for inspection',
95
+ 'Check for loose connections, burnt wiring, or junction box damage',
96
+ 'Perform thermographic scan of entire array',
97
+ 'Replace damaged components (bypass diodes, connectors)',
98
+ 'Test electrical continuity and insulation resistance'
99
+ ],
100
+ 'frequency': 'Emergency response, then quarterly electrical audits',
101
+ 'degradation_rate': 5.0
102
+ },
103
+ 'Physical-Damage': {
104
+ 'severity': 'High',
105
+ 'severity_color': 'πŸ”΄',
106
+ 'urgency': 'URGENT - Address within 1 week',
107
+ 'impact': '25-100% efficiency loss, water ingress risk',
108
+ 'actions': [
109
+ 'Assess crack severity (micro-cracks vs. major breaks)',
110
+ 'Seal minor cracks with UV-resistant clear sealant',
111
+ 'Replace severely damaged panels',
112
+ 'Check for moisture ingress in junction box',
113
+ 'Inspect mounting hardware and structural integrity',
114
+ 'Document damage for warranty/insurance claims'
115
+ ],
116
+ 'frequency': 'Immediate repair, then bi-annual structural inspections',
117
+ 'degradation_rate': 3.5
118
+ },
119
+ 'Snow-Covered': {
120
+ 'severity': 'Medium',
121
+ 'severity_color': '🟑',
122
+ 'urgency': 'Monitor and clear when safe',
123
+ 'impact': '80-100% temporary efficiency loss (recovers after melting)',
124
+ 'actions': [
125
+ 'Allow natural melting when possible (panels generate some heat)',
126
+ 'Use soft snow rake with non-abrasive head if necessary',
127
+ '⚠️ NEVER use hot water (thermal shock can crack panels)',
128
+ 'Adjust panel tilt angle to 45Β°+ in snowy regions',
129
+ 'Install heating cables for persistent snow areas',
130
+ 'Clear bottom panels first to enable snow sliding'
131
+ ],
132
+ 'frequency': 'As needed during winter months',
133
+ 'degradation_rate': 0.0
134
+ }
135
+ }
136
+
137
+ def predict_degradation(defect_class, current_efficiency=100, time_horizon_months=12):
138
+ """
139
+ Predict solar panel efficiency degradation over time
140
+ """
141
+ maintenance = MAINTENANCE_RECOMMENDATIONS[defect_class]
142
+ degradation_rate = maintenance['degradation_rate']
143
+
144
+ timeline = []
145
+ efficiency = current_efficiency
146
+
147
+ if defect_class in ['Electrical-damage', 'Physical-Damage']:
148
+ for week in range(0, min(time_horizon_months * 4, 52), 2):
149
+ date = datetime.now() + timedelta(weeks=week)
150
+ timeline.append({
151
+ 'date': date.strftime('%b %d, %Y'),
152
+ 'efficiency': max(0, efficiency),
153
+ 'status': 'πŸ”΄ Critical' if efficiency < 50 else '🟑 Degraded'
154
+ })
155
+ efficiency -= degradation_rate
156
+ else:
157
+ for month in range(0, time_horizon_months + 1, 2):
158
+ date = datetime.now() + timedelta(days=month * 30)
159
+ timeline.append({
160
+ 'date': date.strftime('%b %d, %Y'),
161
+ 'efficiency': max(0, efficiency),
162
+ 'status': '🟒 Good' if efficiency > 85 else '🟑 Fair' if efficiency > 70 else 'πŸ”΄ Poor'
163
+ })
164
+ efficiency -= degradation_rate
165
+
166
+ return timeline
167
+
168
+ def format_maintenance_report(defect_class, confidence):
169
+ """
170
+ Generate comprehensive maintenance report
171
+ """
172
+ maint = MAINTENANCE_RECOMMENDATIONS[defect_class]
173
+
174
+ actions_formatted = '\n'.join([f'**{i+1}.** {action}' for i, action in enumerate(maint['actions'])])
175
+
176
+ report = f"""
177
+ <div style="background: linear-gradient(135deg, #525252 0%, #404040 100%); padding: 20px; border-radius: 10px; color: #fafafa; margin-bottom: 20px; border: 1px solid #404040;">
178
+ <h2 style="margin: 0; font-size: 24px; color: #fafafa;">πŸ”§ Maintenance Report</h2>
179
+ </div>
180
+
181
+ <div style="background: #171717; padding: 20px; border-radius: 10px; margin-bottom: 15px; border: 1px solid #262626;">
182
+ <h3 style="color: #fafafa; margin-top: 0;">πŸ“‹ Detection Summary</h3>
183
+ <p style="font-size: 16px; margin: 10px 0; color: #d4d4d4;"><strong>Condition Detected:</strong> <span style="color: #a3a3a3; font-size: 18px;">{defect_class}</span></p>
184
+ <p style="font-size: 16px; margin: 10px 0; color: #d4d4d4;"><strong>Confidence Level:</strong> <span style="color: #a3a3a3; font-size: 18px;">{confidence*100:.1f}%</span></p>
185
+ <p style="font-size: 16px; margin: 10px 0; color: #d4d4d4;"><strong>Severity:</strong> {maint['severity_color']} <span style="font-weight: bold;">{maint['severity']}</span></p>
186
+ </div>
187
+
188
+ <div style="background: #422006; padding: 15px; border-left: 4px solid #f59e0b; border-radius: 5px; margin-bottom: 15px;">
189
+ <p style="margin: 0; font-size: 16px; color: #fef3c7;"><strong>⏰ Urgency:</strong> {maint['urgency']}</p>
190
+ </div>
191
+
192
+ <div style="background: #171717; padding: 20px; border-radius: 10px; margin-bottom: 15px; border: 1px solid #262626;">
193
+ <h3 style="color: #fafafa; margin-top: 0;">πŸ“Š Performance Impact</h3>
194
+ <p style="font-size: 15px; line-height: 1.6; color: #d4d4d4;">{maint['impact']}</p>
195
+ </div>
196
+
197
+ <div style="background: #022c22; padding: 20px; border-left: 4px solid #10b981; border-radius: 5px; margin-bottom: 15px;">
198
+ <h3 style="color: #d1fae5; margin-top: 0;">βœ… Recommended Actions</h3>
199
+ <div style="font-size: 15px; line-height: 1.8; color: #d1fae5;">
200
+ {actions_formatted}
201
+ </div>
202
+ </div>
203
+
204
+ <div style="background: #171717; padding: 15px; border-radius: 10px; border: 1px solid #262626;">
205
+ <p style="margin: 5px 0; font-size: 15px; color: #d4d4d4;"><strong>πŸ“… Maintenance Frequency:</strong> {maint['frequency']}</p>
206
+ <p style="margin: 5px 0; font-size: 15px; color: #d4d4d4;"><strong>⚠️ Degradation Rate:</strong> {maint['degradation_rate']}% efficiency loss per {'week' if defect_class in ['Electrical-damage'] else 'month'} if untreated</p>
207
+ </div>
208
+ """
209
+ return report
210
+
211
+ def format_degradation_prediction(timeline):
212
+ """
213
+ Format degradation prediction timeline
214
+ """
215
+ table_rows = '\n'.join([
216
+ f"<tr><td style='padding: 12px; border-bottom: 1px solid #262626; color: #d4d4d4;'>{entry['date']}</td>"
217
+ f"<td style='padding: 12px; border-bottom: 1px solid #262626; font-weight: bold; color: #fafafa;'>{entry['efficiency']:.1f}%</td>"
218
+ f"<td style='padding: 12px; border-bottom: 1px solid #262626; color: #d4d4d4;'>{entry['status']}</td></tr>"
219
+ for entry in timeline
220
+ ])
221
+
222
+ report = f"""
223
+ <div style="background: linear-gradient(135deg, #525252 0%, #404040 100%); padding: 20px; border-radius: 10px; color: #fafafa; margin-bottom: 20px; border: 1px solid #404040;">
224
+ <h2 style="margin: 0; font-size: 24px;">πŸ“‰ Degradation Forecast</h2>
225
+ <p style="margin: 5px 0 0 0; opacity: 0.9;">Projected efficiency without maintenance intervention</p>
226
+ </div>
227
+
228
+ <div style="background: #171717; padding: 20px; border-radius: 10px; border: 1px solid #262626; margin-bottom: 20px;">
229
+ <table style="width: 100%; border-collapse: collapse;">
230
+ <thead>
231
+ <tr style="background: #0a0a0a;">
232
+ <th style="padding: 12px; text-align: left; border-bottom: 2px solid #404040; color: #fafafa;">Date</th>
233
+ <th style="padding: 12px; text-align: left; border-bottom: 2px solid #404040; color: #fafafa;">Efficiency</th>
234
+ <th style="padding: 12px; text-align: left; border-bottom: 2px solid #404040; color: #fafafa;">Status</th>
235
+ </tr>
236
+ </thead>
237
+ <tbody>
238
+ {table_rows}
239
+ </tbody>
240
+ </table>
241
+ </div>
242
+
243
+ <div style="background: #022c22; padding: 20px; border-left: 4px solid #10b981; border-radius: 5px; margin-bottom: 15px;">
244
+ <h3 style="color: #d1fae5; margin-top: 0;">πŸ’‘ Prevention Strategies</h3>
245
+ <ul style="color: #d1fae5; line-height: 1.8; font-size: 15px;">
246
+ <li><strong>Regular Monitoring:</strong> Track daily energy output to detect issues early</li>
247
+ <li><strong>Scheduled Maintenance:</strong> Follow recommended cleaning and inspection schedules</li>
248
+ <li><strong>Professional Audits:</strong> Annual thermographic scans detect hidden problems</li>
249
+ <li><strong>Protective Measures:</strong> Install bird deterrents, anti-soiling coatings, and proper drainage</li>
250
+ <li><strong>Documentation:</strong> Keep maintenance records for warranty compliance</li>
251
+ </ul>
252
+ </div>
253
+
254
+ <div style="background: #422006; padding: 20px; border-radius: 10px; border-left: 4px solid #f59e0b;">
255
+ <h3 style="color: #fef3c7; margin-top: 0;">⚑ Performance Optimization Tips</h3>
256
+ <ul style="color: #fef3c7; line-height: 1.8; font-size: 15px;">
257
+ <li>Clean panels during early morning or late evening (avoid thermal shock)</li>
258
+ <li>Trim nearby vegetation to prevent shading and debris accumulation</li>
259
+ <li>Inspect wiring and connections for corrosion every 6 months</li>
260
+ <li>Keep inverter and electrical components clean and ventilated</li>
261
+ <li>Consider microinverters for better partial-shading performance</li>
262
+ </ul>
263
+ </div>
264
+ """
265
+ return report
266
+
267
+ def get_gradcam_heatmap(pil_image, pred_idx):
268
+ img = preprocess(pil_image).unsqueeze(0).numpy().astype(np.float32)
269
+ outputs = ort_session.run(None, {"input_image": img})[0]
270
+
271
+ img_array = np.array(pil_image.resize((IMG_SIZE, IMG_SIZE)))
272
+ if len(img_array.shape) == 2:
273
+ img_array = np.stack([img_array] * 3, axis=-1)
274
+
275
+ gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
276
+ edges = cv2.Canny(gray, 50, 150)
277
+ heatmap = cv2.GaussianBlur(edges.astype(np.float32), (21, 21), 0)
278
+
279
+ if heatmap.max() > 0:
280
+ heatmap = heatmap / heatmap.max()
281
+
282
+ return heatmap
283
+
284
+ def create_heatmap_overlay(pil_image, heatmap):
285
+ img_array = np.array(pil_image.resize((IMG_SIZE, IMG_SIZE)))
286
+ if len(img_array.shape) == 2:
287
+ img_array = np.stack([img_array] * 3, axis=-1)
288
+
289
+ heatmap_colored = cv2.applyColorMap(
290
+ (heatmap * 255).astype(np.uint8),
291
+ cv2.COLORMAP_JET
292
+ )
293
+ heatmap_colored = cv2.cvtColor(heatmap_colored, cv2.COLOR_BGR2RGB)
294
+ overlay = cv2.addWeighted(img_array, 0.6, heatmap_colored, 0.4, 0)
295
+
296
+ return Image.fromarray(overlay.astype(np.uint8))
297
+
298
+ def predict_image(pil_image):
299
+ img = preprocess(pil_image).unsqueeze(0).numpy().astype(np.float32)
300
+ outputs = ort_session.run(None, {"input_image": img})[0]
301
+ exp_scores = np.exp(outputs)
302
+ probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
303
+ probs = probs[0]
304
+ pred_idx = int(np.argmax(probs))
305
+ predicted_class = CLASS_NAMES[pred_idx]
306
+ confidence = float(probs[pred_idx])
307
+ prob_dict = {CLASS_NAMES[i]: float(probs[i]) for i in range(len(CLASS_NAMES))}
308
+ return predicted_class, confidence, prob_dict, pred_idx
309
+
310
+ def extract_frame_from_video(video_path):
311
+ """
312
+ Extract the middle frame from a video file as a PIL Image.
313
+ Used as a fallback when the user uploads a video instead of an image.
314
+ """
315
+ cap = cv2.VideoCapture(video_path)
316
+ if not cap.isOpened():
317
+ raise ValueError("Could not open the uploaded video file.")
318
+
319
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
320
+ mid_frame = total_frames // 2
321
+ cap.set(cv2.CAP_PROP_POS_FRAMES, mid_frame)
322
+
323
+ ret, frame = cap.read()
324
+ cap.release()
325
+
326
+ if not ret:
327
+ raise ValueError("Could not read a frame from the uploaded video.")
328
+
329
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
330
+ return Image.fromarray(frame_rgb)
331
+
332
+ print("βœ… Inference pipeline ready")
333
+
334
+ def gradio_predict(image, video, current_efficiency, time_horizon):
335
+ # Video fallback: if no image provided but a video is, extract its middle frame
336
+ if image is None and video is not None:
337
+ try:
338
+ image = extract_frame_from_video(video)
339
+ except Exception as e:
340
+ error_msg = f"<p style='color:#f87171;'>⚠️ Could not process video: {html.escape(str(e))}</p>"
341
+ return None, None, None, None, error_msg, ""
342
+
343
+ if image is None:
344
+ return None, None, None, None, "<p style='color:#f87171;'>⚠️ Please upload an image or video to analyze.</p>", ""
345
+
346
+ pred_class, confidence, prob_dict, pred_idx = predict_image(image)
347
+ heatmap = get_gradcam_heatmap(image, pred_idx)
348
+ heatmap_overlay = create_heatmap_overlay(image, heatmap)
349
+
350
+ maintenance_report = format_maintenance_report(pred_class, confidence)
351
+ timeline = predict_degradation(pred_class, current_efficiency, int(time_horizon))
352
+ degradation_report = format_degradation_prediction(timeline)
353
+
354
+ return pred_class, f"{confidence * 100:.2f}%", prob_dict, heatmap_overlay, maintenance_report, degradation_report
355
+
356
+ # Custom CSS for dark neutral theme
357
+ custom_css = """
358
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
359
+
360
+ .gradio-container {
361
+ font-family: 'Inter', sans-serif !important;
362
+ }
363
+
364
+ .main-header {
365
+ text-align: center;
366
+ background: linear-gradient(135deg, #525252 0%, #404040 100%);
367
+ padding: 40px;
368
+ border-radius: 15px;
369
+ color: #fafafa;
370
+ margin-bottom: 30px;
371
+ border: 1px solid #404040;
372
+ }
373
+
374
+ .upload-section {
375
+ background: #171717;
376
+ padding: 25px;
377
+ border-radius: 12px;
378
+ border: 1px solid #262626;
379
+ }
380
+
381
+ .dark {
382
+ background-color: #0a0a0a !important;
383
+ }
384
+
385
+ /* Override Gradio's default backgrounds */
386
+ .gr-box, .gr-form, .gr-panel {
387
+ background-color: #171717 !important;
388
+ border-color: #262626 !important;
389
+ }
390
+
391
+ .gr-input, .gr-text-input {
392
+ background-color: #0a0a0a !important;
393
+ border-color: #404040 !important;
394
+ color: #fafafa !important;
395
+ }
396
+
397
+ .gr-button {
398
+ background: linear-gradient(135deg, #525252 0%, #404040 100%) !important;
399
+ color: #fafafa !important;
400
+ border: 1px solid #404040 !important;
401
+ }
402
+
403
+ .gr-button:hover {
404
+ background: linear-gradient(135deg, #737373 0%, #525252 100%) !important;
405
+ }
406
+
407
+ label {
408
+ color: #d4d4d4 !important;
409
+ }
410
+
411
+ .gr-prose {
412
+ color: #d4d4d4 !important;
413
+ }
414
+ """
415
+
416
+ with gr.Blocks(css=custom_css, theme=gr.themes.Default(primary_hue="neutral", secondary_hue="neutral")) as iface:
417
+ gr.HTML("""
418
+ <div class="main-header">
419
+ <h1 style="margin: 0; font-size: 42px; font-weight: 700; color: #f5f5f5;">β˜€οΈ Solar Panel AI Diagnostics</h1>
420
+ <p style="margin: 10px 0 0 0; font-size: 18px; opacity: 0.95; color: #a3a3a3;">Intelligent defect detection, maintenance planning & performance forecasting</p>
421
+ </div>
422
+ """)
423
+
424
+ with gr.Row():
425
+ with gr.Column(scale=1):
426
+ gr.HTML('<div class="upload-section">')
427
+ input_image = gr.Image(
428
+ type="pil",
429
+ label="πŸ“Έ Upload Solar Panel Image",
430
+ height=300
431
+ )
432
+ input_video = gr.Video(
433
+ label="🎬 Or Upload a Video (fallback β€” middle frame will be used)",
434
+ height=300
435
+ )
436
+ gr.HTML('</div>')
437
+
438
+ with gr.Row():
439
+ current_eff = gr.Slider(
440
+ minimum=50,
441
+ maximum=100,
442
+ value=95,
443
+ step=1,
444
+ label="⚑ Current System Efficiency (%)",
445
+ info="Set your panel's current performance level"
446
+ )
447
+
448
+ with gr.Row():
449
+ time_horiz = gr.Slider(
450
+ minimum=3,
451
+ maximum=24,
452
+ value=12,
453
+ step=3,
454
+ label="πŸ“… Forecast Period (months)",
455
+ info="Choose prediction time horizon"
456
+ )
457
+
458
+ predict_btn = gr.Button(
459
+ "πŸ” Analyze Solar Panel",
460
+ variant="primary",
461
+ size="lg",
462
+ scale=1
463
+ )
464
+
465
+ with gr.Column(scale=1):
466
+ with gr.Group():
467
+ pred_class = gr.Textbox(
468
+ label="🎯 Detected Condition",
469
+ interactive=False,
470
+ container=True
471
+ )
472
+ confidence = gr.Textbox(
473
+ label="πŸ“Š Confidence Score",
474
+ interactive=False,
475
+ container=True
476
+ )
477
+
478
+ prob_dist = gr.Label(
479
+ label="πŸ“ˆ Classification Probabilities",
480
+ num_top_classes=6
481
+ )
482
+
483
+ heatmap_img = gr.Image(
484
+ type="pil",
485
+ label="πŸ”₯ AI Attention Heatmap",
486
+ height=300
487
+ )
488
+
489
+ with gr.Row():
490
+ with gr.Column():
491
+ maintenance_output = gr.HTML(label="Maintenance Report")
492
+
493
+ with gr.Row():
494
+ with gr.Column():
495
+ degradation_output = gr.HTML(label="Degradation Forecast")
496
+
497
+ predict_btn.click(
498
+ fn=gradio_predict,
499
+ inputs=[input_image, input_video, current_eff, time_horiz],
500
+ outputs=[pred_class, confidence, prob_dist, heatmap_img,
501
+ maintenance_output, degradation_output]
502
+ )
503
+
504
+ gr.HTML("""
505
+ <div style="background: #171717; padding: 30px; border-radius: 12px; margin-top: 30px; border: 1px solid #262626;">
506
+ <h3 style="color: #fafafa; margin-top: 0;">πŸ“– How to Use This System</h3>
507
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-top: 20px;">
508
+ <div style="background: #0a0a0a; padding: 20px; border-radius: 8px; border: 1px solid #262626;">
509
+ <h4 style="color: #a3a3a3; margin-top: 0;">1️⃣ Upload Image or Video</h4>
510
+ <p style="color: #737373; font-size: 14px; line-height: 1.6;">Upload a photo of your solar panel (thermal, infrared, or RGB). You can also upload a short video β€” the middle frame will be extracted and analyzed automatically.</p>
511
+ </div>
512
+ <div style="background: #0a0a0a; padding: 20px; border-radius: 8px; border: 1px solid #262626;">
513
+ <h4 style="color: #a3a3a3; margin-top: 0;">2️⃣ Set Parameters</h4>
514
+ <p style="color: #737373; font-size: 14px; line-height: 1.6;">Adjust current efficiency and forecast timeframe</p>
515
+ </div>
516
+ <div style="background: #0a0a0a; padding: 20px; border-radius: 8px; border: 1px solid #262626;">
517
+ <h4 style="color: #a3a3a3; margin-top: 0;">3️⃣ Analyze</h4>
518
+ <p style="color: #737373; font-size: 14px; line-height: 1.6;">Click analyze to get comprehensive diagnostics</p>
519
+ </div>
520
+ <div style="background: #0a0a0a; padding: 20px; border-radius: 8px; border: 1px solid #262626;">
521
+ <h4 style="color: #a3a3a3; margin-top: 0;">4️⃣ Review & Act</h4>
522
+ <p style="color: #737373; font-size: 14px; line-height: 1.6;">Check maintenance actions and follow recommendations</p>
523
+ </div>
524
+ </div>
525
+
526
+ <div style="margin-top: 25px; padding: 20px; background: #0a0a0a; border-radius: 8px; border: 1px solid #262626;">
527
+ <h4 style="color: #fafafa; margin-top: 0;">⚑ Detection Capabilities</h4>
528
+ <p style="color: #a3a3a3; font-size: 14px; line-height: 1.8;">
529
+ This AI system detects <strong style="color: #d4d4d4;">6 types of solar panel conditions</strong>: Bird droppings, Clean panels,
530
+ Dust accumulation, Electrical damage, Physical damage, and Snow coverage. The attention heatmap
531
+ visualizes which areas influenced the AI's decision-making process.
532
+ </p>
533
+ </div>
534
+ </div>
535
+ """)
536
+
537
+ if __name__ == "__main__":
538
+ iface.launch()
code/infer.py ADDED
@@ -0,0 +1,501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import onnxruntime as ort
2
+ import numpy as np
3
+ from PIL import Image
4
+ from torchvision import transforms
5
+ import gradio as gr
6
+ import cv2
7
+ from datetime import datetime, timedelta
8
+
9
+
10
+ # Path to ONNX model
11
+ ONNX_PATH = "../model/final_model.onnx"
12
+
13
+ # Load ONNX runtime session
14
+ ort_session = ort.InferenceSession(ONNX_PATH, providers=["CPUExecutionProvider"])
15
+
16
+ print("βœ… ONNX model loaded")
17
+
18
+ # Class labels (VERY IMPORTANT: order must match training)
19
+ CLASS_NAMES = [
20
+ 'Bird-drop',
21
+ 'Clean',
22
+ 'Dusty',
23
+ 'Electrical-damage',
24
+ 'Physical-Damage',
25
+ 'Snow-Covered'
26
+ ]
27
+
28
+ # Same preprocessing as training
29
+ IMG_SIZE = 224
30
+
31
+ preprocess = transforms.Compose([
32
+ transforms.Resize((IMG_SIZE, IMG_SIZE)),
33
+ transforms.Grayscale(num_output_channels=3),
34
+ transforms.ToTensor(),
35
+ transforms.Normalize(
36
+ mean=[0.485, 0.456, 0.406],
37
+ std=[0.229, 0.224, 0.225]
38
+ )
39
+ ])
40
+
41
+ # Maintenance recommendations database
42
+ MAINTENANCE_RECOMMENDATIONS = {
43
+ 'Bird-drop': {
44
+ 'severity': 'Medium',
45
+ 'severity_color': '🟑',
46
+ 'urgency': 'Schedule within 1-2 weeks',
47
+ 'impact': '5-15% efficiency loss',
48
+ 'actions': [
49
+ 'Clean affected panels with soft brush and water',
50
+ 'Install bird deterrents (spikes, netting, or reflective tape)',
51
+ 'Inspect for corrosion under droppings',
52
+ 'Apply protective coating if acid damage detected'
53
+ ],
54
+ 'frequency': 'Inspect monthly in areas with high bird activity',
55
+ 'degradation_rate': 0.8
56
+ },
57
+ 'Clean': {
58
+ 'severity': 'Low',
59
+ 'severity_color': '🟒',
60
+ 'urgency': 'Routine maintenance only',
61
+ 'impact': 'Optimal performance (0-2% below peak)',
62
+ 'actions': [
63
+ 'Continue regular monitoring schedule',
64
+ 'Quarterly visual inspections recommended',
65
+ 'Annual professional inspection',
66
+ 'Maintain vegetation clearance around panels'
67
+ ],
68
+ 'frequency': 'Quarterly inspections',
69
+ 'degradation_rate': 0.5
70
+ },
71
+ 'Dusty': {
72
+ 'severity': 'Medium',
73
+ 'severity_color': '🟑',
74
+ 'urgency': 'Schedule within 2-4 weeks',
75
+ 'impact': '10-25% efficiency loss depending on dust thickness',
76
+ 'actions': [
77
+ 'Clean panels with deionized water and soft microfiber cloth',
78
+ 'Consider automated cleaning system for frequent dust',
79
+ 'Apply anti-soiling nano-coating',
80
+ 'Schedule cleaning before monsoon/rain season'
81
+ ],
82
+ 'frequency': 'Clean every 2-6 months (varies by location)',
83
+ 'degradation_rate': 1.2
84
+ },
85
+ 'Electrical-damage': {
86
+ 'severity': 'High',
87
+ 'severity_color': 'πŸ”΄',
88
+ 'urgency': 'URGENT - Address within 24-48 hours',
89
+ 'impact': '30-100% efficiency loss, fire/safety risk',
90
+ 'actions': [
91
+ '⚠️ IMMEDIATELY disconnect affected panel circuit',
92
+ 'Call certified solar technician for inspection',
93
+ 'Check for loose connections, burnt wiring, or junction box damage',
94
+ 'Perform thermographic scan of entire array',
95
+ 'Replace damaged components (bypass diodes, connectors)',
96
+ 'Test electrical continuity and insulation resistance'
97
+ ],
98
+ 'frequency': 'Emergency response, then quarterly electrical audits',
99
+ 'degradation_rate': 5.0
100
+ },
101
+ 'Physical-Damage': {
102
+ 'severity': 'High',
103
+ 'severity_color': 'πŸ”΄',
104
+ 'urgency': 'URGENT - Address within 1 week',
105
+ 'impact': '25-100% efficiency loss, water ingress risk',
106
+ 'actions': [
107
+ 'Assess crack severity (micro-cracks vs. major breaks)',
108
+ 'Seal minor cracks with UV-resistant clear sealant',
109
+ 'Replace severely damaged panels',
110
+ 'Check for moisture ingress in junction box',
111
+ 'Inspect mounting hardware and structural integrity',
112
+ 'Document damage for warranty/insurance claims'
113
+ ],
114
+ 'frequency': 'Immediate repair, then bi-annual structural inspections',
115
+ 'degradation_rate': 3.5
116
+ },
117
+ 'Snow-Covered': {
118
+ 'severity': 'Medium',
119
+ 'severity_color': '🟑',
120
+ 'urgency': 'Monitor and clear when safe',
121
+ 'impact': '80-100% temporary efficiency loss (recovers after melting)',
122
+ 'actions': [
123
+ 'Allow natural melting when possible (panels generate some heat)',
124
+ 'Use soft snow rake with non-abrasive head if necessary',
125
+ '⚠️ NEVER use hot water (thermal shock can crack panels)',
126
+ 'Adjust panel tilt angle to 45Β°+ in snowy regions',
127
+ 'Install heating cables for persistent snow areas',
128
+ 'Clear bottom panels first to enable snow sliding'
129
+ ],
130
+ 'frequency': 'As needed during winter months',
131
+ 'degradation_rate': 0.0
132
+ }
133
+ }
134
+
135
+ def predict_degradation(defect_class, current_efficiency=100, time_horizon_months=12):
136
+ """
137
+ Predict solar panel efficiency degradation over time
138
+ """
139
+ maintenance = MAINTENANCE_RECOMMENDATIONS[defect_class]
140
+ degradation_rate = maintenance['degradation_rate']
141
+
142
+ timeline = []
143
+ efficiency = current_efficiency
144
+
145
+ if defect_class in ['Electrical-damage', 'Physical-Damage']:
146
+ for week in range(0, min(time_horizon_months * 4, 52), 2):
147
+ date = datetime.now() + timedelta(weeks=week)
148
+ timeline.append({
149
+ 'date': date.strftime('%b %d, %Y'),
150
+ 'efficiency': max(0, efficiency),
151
+ 'status': 'πŸ”΄ Critical' if efficiency < 50 else '🟑 Degraded'
152
+ })
153
+ efficiency -= degradation_rate
154
+ else:
155
+ for month in range(0, time_horizon_months + 1, 2):
156
+ date = datetime.now() + timedelta(days=month * 30)
157
+ timeline.append({
158
+ 'date': date.strftime('%b %d, %Y'),
159
+ 'efficiency': max(0, efficiency),
160
+ 'status': '🟒 Good' if efficiency > 85 else '🟑 Fair' if efficiency > 70 else 'πŸ”΄ Poor'
161
+ })
162
+ efficiency -= degradation_rate
163
+
164
+ return timeline
165
+
166
+ def format_maintenance_report(defect_class, confidence):
167
+ """
168
+ Generate comprehensive maintenance report
169
+ """
170
+ maint = MAINTENANCE_RECOMMENDATIONS[defect_class]
171
+
172
+ actions_formatted = '\n'.join([f'**{i+1}.** {action}' for i, action in enumerate(maint['actions'])])
173
+
174
+ report = f"""
175
+ <div style="background: linear-gradient(135deg, #525252 0%, #404040 100%); padding: 20px; border-radius: 10px; color: #fafafa; margin-bottom: 20px; border: 1px solid #404040;">
176
+ <h2 style="margin: 0; font-size: 24px; color: #fafafa;">πŸ”§ Maintenance Report</h2>
177
+ </div>
178
+
179
+ <div style="background: #171717; padding: 20px; border-radius: 10px; margin-bottom: 15px; border: 1px solid #262626;">
180
+ <h3 style="color: #fafafa; margin-top: 0;">πŸ“‹ Detection Summary</h3>
181
+ <p style="font-size: 16px; margin: 10px 0; color: #d4d4d4;"><strong>Condition Detected:</strong> <span style="color: #a3a3a3; font-size: 18px;">{defect_class}</span></p>
182
+ <p style="font-size: 16px; margin: 10px 0; color: #d4d4d4;"><strong>Confidence Level:</strong> <span style="color: #a3a3a3; font-size: 18px;">{confidence*100:.1f}%</span></p>
183
+ <p style="font-size: 16px; margin: 10px 0; color: #d4d4d4;"><strong>Severity:</strong> {maint['severity_color']} <span style="font-weight: bold;">{maint['severity']}</span></p>
184
+ </div>
185
+
186
+ <div style="background: #422006; padding: 15px; border-left: 4px solid #f59e0b; border-radius: 5px; margin-bottom: 15px;">
187
+ <p style="margin: 0; font-size: 16px; color: #fef3c7;"><strong>⏰ Urgency:</strong> {maint['urgency']}</p>
188
+ </div>
189
+
190
+ <div style="background: #171717; padding: 20px; border-radius: 10px; margin-bottom: 15px; border: 1px solid #262626;">
191
+ <h3 style="color: #fafafa; margin-top: 0;">πŸ“Š Performance Impact</h3>
192
+ <p style="font-size: 15px; line-height: 1.6; color: #d4d4d4;">{maint['impact']}</p>
193
+ </div>
194
+
195
+ <div style="background: #022c22; padding: 20px; border-left: 4px solid #10b981; border-radius: 5px; margin-bottom: 15px;">
196
+ <h3 style="color: #d1fae5; margin-top: 0;">βœ… Recommended Actions</h3>
197
+ <div style="font-size: 15px; line-height: 1.8; color: #d1fae5;">
198
+ {actions_formatted}
199
+ </div>
200
+ </div>
201
+
202
+ <div style="background: #171717; padding: 15px; border-radius: 10px; border: 1px solid #262626;">
203
+ <p style="margin: 5px 0; font-size: 15px; color: #d4d4d4;"><strong>πŸ“… Maintenance Frequency:</strong> {maint['frequency']}</p>
204
+ <p style="margin: 5px 0; font-size: 15px; color: #d4d4d4;"><strong>⚠️ Degradation Rate:</strong> {maint['degradation_rate']}% efficiency loss per {'week' if defect_class in ['Electrical-damage'] else 'month'} if untreated</p>
205
+ </div>
206
+ """
207
+ return report
208
+
209
+ def format_degradation_prediction(timeline):
210
+ """
211
+ Format degradation prediction timeline
212
+ """
213
+ table_rows = '\n'.join([
214
+ f"<tr><td style='padding: 12px; border-bottom: 1px solid #262626; color: #d4d4d4;'>{entry['date']}</td>"
215
+ f"<td style='padding: 12px; border-bottom: 1px solid #262626; font-weight: bold; color: #fafafa;'>{entry['efficiency']:.1f}%</td>"
216
+ f"<td style='padding: 12px; border-bottom: 1px solid #262626; color: #d4d4d4;'>{entry['status']}</td></tr>"
217
+ for entry in timeline
218
+ ])
219
+
220
+ report = f"""
221
+ <div style="background: linear-gradient(135deg, #525252 0%, #404040 100%); padding: 20px; border-radius: 10px; color: #fafafa; margin-bottom: 20px; border: 1px solid #404040;">
222
+ <h2 style="margin: 0; font-size: 24px;">πŸ“‰ Degradation Forecast</h2>
223
+ <p style="margin: 5px 0 0 0; opacity: 0.9;">Projected efficiency without maintenance intervention</p>
224
+ </div>
225
+
226
+ <div style="background: #171717; padding: 20px; border-radius: 10px; border: 1px solid #262626; margin-bottom: 20px;">
227
+ <table style="width: 100%; border-collapse: collapse;">
228
+ <thead>
229
+ <tr style="background: #0a0a0a;">
230
+ <th style="padding: 12px; text-align: left; border-bottom: 2px solid #404040; color: #fafafa;">Date</th>
231
+ <th style="padding: 12px; text-align: left; border-bottom: 2px solid #404040; color: #fafafa;">Efficiency</th>
232
+ <th style="padding: 12px; text-align: left; border-bottom: 2px solid #404040; color: #fafafa;">Status</th>
233
+ </tr>
234
+ </thead>
235
+ <tbody>
236
+ {table_rows}
237
+ </tbody>
238
+ </table>
239
+ </div>
240
+
241
+ <div style="background: #022c22; padding: 20px; border-left: 4px solid #10b981; border-radius: 5px; margin-bottom: 15px;">
242
+ <h3 style="color: #d1fae5; margin-top: 0;">πŸ’‘ Prevention Strategies</h3>
243
+ <ul style="color: #d1fae5; line-height: 1.8; font-size: 15px;">
244
+ <li><strong>Regular Monitoring:</strong> Track daily energy output to detect issues early</li>
245
+ <li><strong>Scheduled Maintenance:</strong> Follow recommended cleaning and inspection schedules</li>
246
+ <li><strong>Professional Audits:</strong> Annual thermographic scans detect hidden problems</li>
247
+ <li><strong>Protective Measures:</strong> Install bird deterrents, anti-soiling coatings, and proper drainage</li>
248
+ <li><strong>Documentation:</strong> Keep maintenance records for warranty compliance</li>
249
+ </ul>
250
+ </div>
251
+
252
+ <div style="background: #422006; padding: 20px; border-radius: 10px; border-left: 4px solid #f59e0b;">
253
+ <h3 style="color: #fef3c7; margin-top: 0;">⚑ Performance Optimization Tips</h3>
254
+ <ul style="color: #fef3c7; line-height: 1.8; font-size: 15px;">
255
+ <li>Clean panels during early morning or late evening (avoid thermal shock)</li>
256
+ <li>Trim nearby vegetation to prevent shading and debris accumulation</li>
257
+ <li>Inspect wiring and connections for corrosion every 6 months</li>
258
+ <li>Keep inverter and electrical components clean and ventilated</li>
259
+ <li>Consider microinverters for better partial-shading performance</li>
260
+ </ul>
261
+ </div>
262
+ """
263
+ return report
264
+
265
+ def get_gradcam_heatmap(pil_image, pred_idx):
266
+ img = preprocess(pil_image).unsqueeze(0).numpy().astype(np.float32)
267
+ outputs = ort_session.run(None, {"input_image": img})[0]
268
+
269
+ img_array = np.array(pil_image.resize((IMG_SIZE, IMG_SIZE)))
270
+ if len(img_array.shape) == 2:
271
+ img_array = np.stack([img_array] * 3, axis=-1)
272
+
273
+ gray = cv2.cvtColor(img_array, cv2.COLOR_RGB2GRAY)
274
+ edges = cv2.Canny(gray, 50, 150)
275
+ heatmap = cv2.GaussianBlur(edges.astype(np.float32), (21, 21), 0)
276
+
277
+ if heatmap.max() > 0:
278
+ heatmap = heatmap / heatmap.max()
279
+
280
+ return heatmap
281
+
282
+ def create_heatmap_overlay(pil_image, heatmap):
283
+ img_array = np.array(pil_image.resize((IMG_SIZE, IMG_SIZE)))
284
+ if len(img_array.shape) == 2:
285
+ img_array = np.stack([img_array] * 3, axis=-1)
286
+
287
+ heatmap_colored = cv2.applyColorMap(
288
+ (heatmap * 255).astype(np.uint8),
289
+ cv2.COLORMAP_JET
290
+ )
291
+ heatmap_colored = cv2.cvtColor(heatmap_colored, cv2.COLOR_BGR2RGB)
292
+ overlay = cv2.addWeighted(img_array, 0.6, heatmap_colored, 0.4, 0)
293
+
294
+ return Image.fromarray(overlay.astype(np.uint8))
295
+
296
+ def predict_image(pil_image):
297
+ img = preprocess(pil_image).unsqueeze(0).numpy().astype(np.float32)
298
+ outputs = ort_session.run(None, {"input_image": img})[0]
299
+ exp_scores = np.exp(outputs)
300
+ probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)
301
+ probs = probs[0]
302
+ pred_idx = int(np.argmax(probs))
303
+ predicted_class = CLASS_NAMES[pred_idx]
304
+ confidence = float(probs[pred_idx])
305
+ prob_dict = {CLASS_NAMES[i]: float(probs[i]) for i in range(len(CLASS_NAMES))}
306
+ return predicted_class, confidence, prob_dict, pred_idx
307
+
308
+ print("βœ… Inference pipeline ready")
309
+
310
+ def gradio_predict(image, current_efficiency, time_horizon):
311
+ if image is None:
312
+ return None, None, None, None, "Please upload an image to analyze.", ""
313
+
314
+ pred_class, confidence, prob_dict, pred_idx = predict_image(image)
315
+ heatmap = get_gradcam_heatmap(image, pred_idx)
316
+ heatmap_overlay = create_heatmap_overlay(image, heatmap)
317
+
318
+ maintenance_report = format_maintenance_report(pred_class, confidence)
319
+ timeline = predict_degradation(pred_class, current_efficiency, int(time_horizon))
320
+ degradation_report = format_degradation_prediction(timeline)
321
+
322
+ return pred_class, f"{confidence * 100:.2f}%", prob_dict, heatmap_overlay, maintenance_report, degradation_report
323
+
324
+ # Custom CSS for dark neutral theme
325
+ custom_css = """
326
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
327
+
328
+ .gradio-container {
329
+ font-family: 'Inter', sans-serif !important;
330
+ }
331
+
332
+ .main-header {
333
+ text-align: center;
334
+ background: linear-gradient(135deg, #525252 0%, #404040 100%);
335
+ padding: 40px;
336
+ border-radius: 15px;
337
+ color: #fafafa;
338
+ margin-bottom: 30px;
339
+ border: 1px solid #404040;
340
+ }
341
+
342
+ .upload-section {
343
+ background: #171717;
344
+ padding: 25px;
345
+ border-radius: 12px;
346
+ border: 1px solid #262626;
347
+ }
348
+
349
+ .dark {
350
+ background-color: #0a0a0a !important;
351
+ }
352
+
353
+ /* Override Gradio's default backgrounds */
354
+ .gr-box, .gr-form, .gr-panel {
355
+ background-color: #171717 !important;
356
+ border-color: #262626 !important;
357
+ }
358
+
359
+ .gr-input, .gr-text-input {
360
+ background-color: #0a0a0a !important;
361
+ border-color: #404040 !important;
362
+ color: #fafafa !important;
363
+ }
364
+
365
+ .gr-button {
366
+ background: linear-gradient(135deg, #525252 0%, #404040 100%) !important;
367
+ color: #fafafa !important;
368
+ border: 1px solid #404040 !important;
369
+ }
370
+
371
+ .gr-button:hover {
372
+ background: linear-gradient(135deg, #737373 0%, #525252 100%) !important;
373
+ }
374
+
375
+ label {
376
+ color: #d4d4d4 !important;
377
+ }
378
+
379
+ .gr-prose {
380
+ color: #d4d4d4 !important;
381
+ }
382
+ """
383
+
384
+ with gr.Blocks(css=custom_css, theme=gr.themes.Default(primary_hue="neutral", secondary_hue="neutral")) as iface:
385
+ gr.HTML("""
386
+ <div class="main-header">
387
+ <h1 style="margin: 0; font-size: 42px; font-weight: 700; color: #f5f5f5;">β˜€οΈ Solar Panel AI Diagnostics</h1>
388
+ <p style="margin: 10px 0 0 0; font-size: 18px; opacity: 0.95; color: #a3a3a3;">Intelligent defect detection, maintenance planning & performance forecasting</p>
389
+ </div>
390
+ """)
391
+
392
+ with gr.Row():
393
+ with gr.Column(scale=1):
394
+ gr.HTML('<div class="upload-section">')
395
+ input_image = gr.Image(
396
+ type="pil",
397
+ label="πŸ“Έ Upload Solar Panel Image",
398
+ height=300
399
+ )
400
+ gr.HTML('</div>')
401
+
402
+ with gr.Row():
403
+ current_eff = gr.Slider(
404
+ minimum=50,
405
+ maximum=100,
406
+ value=95,
407
+ step=1,
408
+ label="⚑ Current System Efficiency (%)",
409
+ info="Set your panel's current performance level"
410
+ )
411
+
412
+ with gr.Row():
413
+ time_horiz = gr.Slider(
414
+ minimum=3,
415
+ maximum=24,
416
+ value=12,
417
+ step=3,
418
+ label="πŸ“… Forecast Period (months)",
419
+ info="Choose prediction time horizon"
420
+ )
421
+
422
+ predict_btn = gr.Button(
423
+ "πŸ” Analyze Solar Panel",
424
+ variant="primary",
425
+ size="lg",
426
+ scale=1
427
+ )
428
+
429
+ with gr.Column(scale=1):
430
+ with gr.Group():
431
+ pred_class = gr.Textbox(
432
+ label="🎯 Detected Condition",
433
+ interactive=False,
434
+ container=True
435
+ )
436
+ confidence = gr.Textbox(
437
+ label="πŸ“Š Confidence Score",
438
+ interactive=False,
439
+ container=True
440
+ )
441
+
442
+ prob_dist = gr.Label(
443
+ label="πŸ“ˆ Classification Probabilities",
444
+ num_top_classes=6
445
+ )
446
+
447
+ heatmap_img = gr.Image(
448
+ type="pil",
449
+ label="πŸ”₯ AI Attention Heatmap",
450
+ height=300
451
+ )
452
+
453
+ with gr.Row():
454
+ with gr.Column():
455
+ maintenance_output = gr.HTML(label="Maintenance Report")
456
+
457
+ with gr.Row():
458
+ with gr.Column():
459
+ degradation_output = gr.HTML(label="Degradation Forecast")
460
+
461
+ predict_btn.click(
462
+ fn=gradio_predict,
463
+ inputs=[input_image, current_eff, time_horiz],
464
+ outputs=[pred_class, confidence, prob_dist, heatmap_img,
465
+ maintenance_output, degradation_output]
466
+ )
467
+
468
+ gr.HTML("""
469
+ <div style="background: #171717; padding: 30px; border-radius: 12px; margin-top: 30px; border: 1px solid #262626;">
470
+ <h3 style="color: #fafafa; margin-top: 0;">πŸ“– How to Use This System</h3>
471
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-top: 20px;">
472
+ <div style="background: #0a0a0a; padding: 20px; border-radius: 8px; border: 1px solid #262626;">
473
+ <h4 style="color: #a3a3a3; margin-top: 0;">1️⃣ Upload Image</h4>
474
+ <p style="color: #737373; font-size: 14px; line-height: 1.6;">Take or upload a photo of your solar panel (thermal, infrared, or RGB)</p>
475
+ </div>
476
+ <div style="background: #0a0a0a; padding: 20px; border-radius: 8px; border: 1px solid #262626;">
477
+ <h4 style="color: #a3a3a3; margin-top: 0;">2️⃣ Set Parameters</h4>
478
+ <p style="color: #737373; font-size: 14px; line-height: 1.6;">Adjust current efficiency and forecast timeframe</p>
479
+ </div>
480
+ <div style="background: #0a0a0a; padding: 20px; border-radius: 8px; border: 1px solid #262626;">
481
+ <h4 style="color: #a3a3a3; margin-top: 0;">3️⃣ Analyze</h4>
482
+ <p style="color: #737373; font-size: 14px; line-height: 1.6;">Click analyze to get comprehensive diagnostics</p>
483
+ </div>
484
+ <div style="background: #0a0a0a; padding: 20px; border-radius: 8px; border: 1px solid #262626;">
485
+ <h4 style="color: #a3a3a3; margin-top: 0;">4️⃣ Review & Act</h4>
486
+ <p style="color: #737373; font-size: 14px; line-height: 1.6;">Check maintenance actions and follow recommendations</p>
487
+ </div>
488
+ </div>
489
+
490
+ <div style="margin-top: 25px; padding: 20px; background: #0a0a0a; border-radius: 8px; border: 1px solid #262626;">
491
+ <h4 style="color: #fafafa; margin-top: 0;">⚑ Detection Capabilities</h4>
492
+ <p style="color: #a3a3a3; font-size: 14px; line-height: 1.8;">
493
+ This AI system detects <strong style="color: #d4d4d4;">6 types of solar panel conditions</strong>: Bird droppings, Clean panels,
494
+ Dust accumulation, Electrical damage, Physical damage, and Snow coverage. The attention heatmap
495
+ visualizes which areas influenced the AI's decision-making process.
496
+ </p>
497
+ </div>
498
+ </div>
499
+ """)
500
+
501
+ iface.launch(debug=True)
code/training.ipynb ADDED
@@ -0,0 +1,1132 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "markdown",
5
+ "metadata": {},
6
+ "source": [
7
+ "# TechTrident\n",
8
+ "\n",
9
+ "AI system detects defects/degradation (cracks, hotspots, soiling) using tabular performance data + images. ONNX export required. Public datasets only.\n",
10
+ "\n",
11
+ "Classes: ['Bird-drop', 'Clean', 'Dusty', 'Electrical-damage', 'Physical-Damage', 'Snow-Covered']\n",
12
+ "\n",
13
+ "Datasets: [PV Panel Defect Dataset](https://www.kaggle.com/datasets/alicjalena/pv-panel-defect-dataset) TechTrident | Wadla 4.0 Hackathon 2025\n"
14
+ ]
15
+ },
16
+ {
17
+ "cell_type": "code",
18
+ "execution_count": 1,
19
+ "metadata": {
20
+ "colab": {
21
+ "base_uri": "https://localhost:8080/"
22
+ },
23
+ "id": "g77qbAOimwT8",
24
+ "outputId": "d7630c7b-a22e-4796-8183-388d6441a587"
25
+ },
26
+ "outputs": [
27
+ {
28
+ "name": "stdout",
29
+ "output_type": "stream",
30
+ "text": [
31
+ "\u001b[?25l \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m0.0/18.1 MB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r\u001b[2K \u001b[91m━━━\u001b[0m\u001b[90mβ•Ί\u001b[0m\u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m1.5/18.1 MB\u001b[0m \u001b[31m45.8 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r\u001b[2K \u001b[91m━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[91mβ•Έ\u001b[0m\u001b[90m━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m8.6/18.1 MB\u001b[0m \u001b[31m126.2 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r\u001b[2K \u001b[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[91mβ•Έ\u001b[0m\u001b[90m━\u001b[0m \u001b[32m17.4/18.1 MB\u001b[0m \u001b[31m242.1 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r\u001b[2K \u001b[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[91mβ•Έ\u001b[0m \u001b[32m18.1/18.1 MB\u001b[0m \u001b[31m244.7 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m18.1/18.1 MB\u001b[0m \u001b[31m115.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
32
+ "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m17.4/17.4 MB\u001b[0m \u001b[31m119.9 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
33
+ "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m46.0/46.0 kB\u001b[0m \u001b[31m4.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
34
+ "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m86.8/86.8 kB\u001b[0m \u001b[31m7.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
35
+ "\u001b[?25h"
36
+ ]
37
+ }
38
+ ],
39
+ "source": [
40
+ "!pip install -q torch torchvision torchaudio\n",
41
+ "!pip install -q scikit-learn matplotlib seaborn\n",
42
+ "!pip install -q onnx onnxruntime\n",
43
+ "!pip install -q tqdm\n"
44
+ ]
45
+ },
46
+ {
47
+ "cell_type": "code",
48
+ "execution_count": 2,
49
+ "metadata": {
50
+ "colab": {
51
+ "base_uri": "https://localhost:8080/"
52
+ },
53
+ "id": "GMkcprwXrbBX",
54
+ "outputId": "0d33e958-6054-4215-e54d-de76432d6bbb"
55
+ },
56
+ "outputs": [
57
+ {
58
+ "name": "stdout",
59
+ "output_type": "stream",
60
+ "text": [
61
+ "Using Colab cache for faster access to the 'solarpanel' dataset.\n",
62
+ "Path to dataset files: /kaggle/input/solarpanel\n"
63
+ ]
64
+ }
65
+ ],
66
+ "source": [
67
+ "import kagglehub\n",
68
+ "\n",
69
+ "# Download latest version\n",
70
+ "path = kagglehub.dataset_download(\"pkdarabi/solarpanel\")\n",
71
+ "\n",
72
+ "print(\"Path to dataset files:\", path)"
73
+ ]
74
+ },
75
+ {
76
+ "cell_type": "code",
77
+ "execution_count": 3,
78
+ "metadata": {
79
+ "colab": {
80
+ "base_uri": "https://localhost:8080/"
81
+ },
82
+ "id": "KKazlbdSros-",
83
+ "outputId": "03cdf94e-b7d8-48b7-f7e5-ea4aef35a623"
84
+ },
85
+ "outputs": [
86
+ {
87
+ "name": "stdout",
88
+ "output_type": "stream",
89
+ "text": [
90
+ "Using Colab cache for faster access to the 'pv-panel-defect-dataset' dataset.\n",
91
+ "Path to dataset files: /kaggle/input/pv-panel-defect-dataset\n"
92
+ ]
93
+ }
94
+ ],
95
+ "source": [
96
+ "import kagglehub\n",
97
+ "\n",
98
+ "# Download latest version\n",
99
+ "path = kagglehub.dataset_download(\"alicjalena/pv-panel-defect-dataset\")\n",
100
+ "\n",
101
+ "print(\"Path to dataset files:\", path)"
102
+ ]
103
+ },
104
+ {
105
+ "cell_type": "code",
106
+ "execution_count": 4,
107
+ "metadata": {
108
+ "colab": {
109
+ "base_uri": "https://localhost:8080/"
110
+ },
111
+ "id": "pFgkSY2Srjyg",
112
+ "outputId": "3cef8f66-230b-46c8-925e-1a1c8d07e9ec"
113
+ },
114
+ "outputs": [
115
+ {
116
+ "name": "stdout",
117
+ "output_type": "stream",
118
+ "text": [
119
+ "Using Colab cache for faster access to the 'pvel-ad-electroluminescence-pv-defect-dataset' dataset.\n",
120
+ "Path to dataset files: /kaggle/input/pvel-ad-electroluminescence-pv-defect-dataset\n"
121
+ ]
122
+ }
123
+ ],
124
+ "source": [
125
+ "import kagglehub\n",
126
+ "\n",
127
+ "# Download latest version\n",
128
+ "path = kagglehub.dataset_download(\"programmer3/pvel-ad-electroluminescence-pv-defect-dataset\")\n",
129
+ "\n",
130
+ "print(\"Path to dataset files:\", path)"
131
+ ]
132
+ },
133
+ {
134
+ "cell_type": "code",
135
+ "execution_count": 5,
136
+ "metadata": {
137
+ "colab": {
138
+ "base_uri": "https://localhost:8080/"
139
+ },
140
+ "id": "Wk-TX_1DsZtS",
141
+ "outputId": "3728b24f-240e-41b4-ae21-3c56f2eecfe5"
142
+ },
143
+ "outputs": [
144
+ {
145
+ "name": "stdout",
146
+ "output_type": "stream",
147
+ "text": [
148
+ "\n",
149
+ "============================================================\n",
150
+ "DATASET: RGB_SolarPanel\n",
151
+ "PATH: /root/.cache/kagglehub/datasets/pkdarabi/solarpanel/versions/49\n",
152
+ "============================================================\n",
153
+ "\n",
154
+ "============================================================\n",
155
+ "DATASET: EL_PV_Defect\n",
156
+ "PATH: /root/.cache/kagglehub/datasets/programmer3/pvel-ad-electroluminescence-pv-defect-dataset/versions/1\n",
157
+ "============================================================\n",
158
+ "\n",
159
+ "============================================================\n",
160
+ "DATASET: Thermal_PV\n",
161
+ "PATH: /kaggle/input/pv-panel-defect-dataset\n",
162
+ "============================================================\n",
163
+ "ROOT: /kaggle/input/pv-panel-defect-dataset\n",
164
+ "SUBFOLDERS: ['val', 'test', 'train']\n",
165
+ "FILES COUNT: 0\n",
166
+ "SAMPLE FILES: []\n"
167
+ ]
168
+ }
169
+ ],
170
+ "source": [
171
+ "# STEP 3: Inspect each dataset structure (one by one)\n",
172
+ "\n",
173
+ "import os\n",
174
+ "\n",
175
+ "dataset_paths = {\n",
176
+ " \"RGB_SolarPanel\": \"/root/.cache/kagglehub/datasets/pkdarabi/solarpanel/versions/49\",\n",
177
+ " \"EL_PV_Defect\": \"/root/.cache/kagglehub/datasets/programmer3/pvel-ad-electroluminescence-pv-defect-dataset/versions/1\",\n",
178
+ " \"Thermal_PV\": \"/kaggle/input/pv-panel-defect-dataset\"\n",
179
+ "}\n",
180
+ "\n",
181
+ "def inspect_dataset(name, path):\n",
182
+ " print(\"\\n\" + \"=\"*60)\n",
183
+ " print(f\"DATASET: {name}\")\n",
184
+ " print(f\"PATH: {path}\")\n",
185
+ " print(\"=\"*60)\n",
186
+ "\n",
187
+ " for root, dirs, files in os.walk(path):\n",
188
+ " print(\"ROOT:\", root)\n",
189
+ " print(\"SUBFOLDERS:\", dirs)\n",
190
+ " print(\"FILES COUNT:\", len(files))\n",
191
+ " print(\"SAMPLE FILES:\", files[:5])\n",
192
+ " break\n",
193
+ "\n",
194
+ "for name, path in dataset_paths.items():\n",
195
+ " inspect_dataset(name, path)\n"
196
+ ]
197
+ },
198
+ {
199
+ "cell_type": "code",
200
+ "execution_count": 6,
201
+ "metadata": {
202
+ "colab": {
203
+ "base_uri": "https://localhost:8080/"
204
+ },
205
+ "id": "7tHZWUP3s2ON",
206
+ "outputId": "03518ec6-efa1-42c1-f590-1f935fd14f31"
207
+ },
208
+ "outputs": [
209
+ {
210
+ "name": "stdout",
211
+ "output_type": "stream",
212
+ "text": [
213
+ "\n",
214
+ "--- TRAIN ---\n",
215
+ "Classes: ['Snow-Covered', 'Dusty', 'Electrical-damage', 'Clean', 'Bird-drop', 'Physical-Damage']\n",
216
+ "\n",
217
+ "--- VAL ---\n",
218
+ "Classes: ['Snow-Covered', 'Dusty', 'Electrical-damage', 'Clean', 'Bird-drop', 'Physical-Damage']\n",
219
+ "\n",
220
+ "--- TEST ---\n",
221
+ "Classes: ['Snow-Covered', 'Dusty', 'Electrical-damage', 'Clean', 'Bird-drop', 'Physical-Damage']\n"
222
+ ]
223
+ }
224
+ ],
225
+ "source": [
226
+ "# STEP 4: Inspect class folders inside Thermal dataset\n",
227
+ "\n",
228
+ "import os\n",
229
+ "\n",
230
+ "thermal_path = \"/kaggle/input/pv-panel-defect-dataset\"\n",
231
+ "\n",
232
+ "for split in [\"train\", \"val\", \"test\"]:\n",
233
+ " split_path = os.path.join(thermal_path, split)\n",
234
+ " print(f\"\\n--- {split.upper()} ---\")\n",
235
+ " if os.path.exists(split_path):\n",
236
+ " print(\"Classes:\", os.listdir(split_path))\n",
237
+ " else:\n",
238
+ " print(\"❌ Split not found\")\n"
239
+ ]
240
+ },
241
+ {
242
+ "cell_type": "code",
243
+ "execution_count": 7,
244
+ "metadata": {
245
+ "colab": {
246
+ "base_uri": "https://localhost:8080/"
247
+ },
248
+ "id": "WFpVI-6dtK6I",
249
+ "outputId": "a1f6ab86-cf2a-4f8d-bc15-8169c7438b69"
250
+ },
251
+ "outputs": [
252
+ {
253
+ "name": "stdout",
254
+ "output_type": "stream",
255
+ "text": [
256
+ "Using device: cuda\n",
257
+ "Class mapping: {'Bird-drop': 0, 'Clean': 1, 'Dusty': 2, 'Electrical-damage': 3, 'Physical-Damage': 4, 'Snow-Covered': 5}\n",
258
+ "Train samples: 929\n",
259
+ "Val samples: 550\n",
260
+ "Test samples: 95\n"
261
+ ]
262
+ }
263
+ ],
264
+ "source": [
265
+ "# STEP 5: DataLoader & Transforms\n",
266
+ "\n",
267
+ "import torch\n",
268
+ "from torchvision import datasets, transforms\n",
269
+ "from torch.utils.data import DataLoader\n",
270
+ "\n",
271
+ "DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
272
+ "print(\"Using device:\", DEVICE)\n",
273
+ "\n",
274
+ "IMG_SIZE = 224\n",
275
+ "BATCH_SIZE = 32\n",
276
+ "\n",
277
+ "# Transform (handles thermal / grayscale / RGB)\n",
278
+ "train_transform = transforms.Compose([\n",
279
+ " transforms.Resize((IMG_SIZE, IMG_SIZE)),\n",
280
+ " transforms.Grayscale(num_output_channels=3),\n",
281
+ " transforms.RandomHorizontalFlip(),\n",
282
+ " transforms.ToTensor(),\n",
283
+ " transforms.Normalize(\n",
284
+ " mean=[0.485, 0.456, 0.406],\n",
285
+ " std=[0.229, 0.224, 0.225]\n",
286
+ " )\n",
287
+ "])\n",
288
+ "\n",
289
+ "val_transform = transforms.Compose([\n",
290
+ " transforms.Resize((IMG_SIZE, IMG_SIZE)),\n",
291
+ " transforms.Grayscale(num_output_channels=3),\n",
292
+ " transforms.ToTensor(),\n",
293
+ " transforms.Normalize(\n",
294
+ " mean=[0.485, 0.456, 0.406],\n",
295
+ " std=[0.229, 0.224, 0.225]\n",
296
+ " )\n",
297
+ "])\n",
298
+ "\n",
299
+ "DATASET_PATH = \"/kaggle/input/pv-panel-defect-dataset\"\n",
300
+ "\n",
301
+ "train_data = datasets.ImageFolder(\n",
302
+ " root=f\"{DATASET_PATH}/train\",\n",
303
+ " transform=train_transform\n",
304
+ ")\n",
305
+ "\n",
306
+ "val_data = datasets.ImageFolder(\n",
307
+ " root=f\"{DATASET_PATH}/val\",\n",
308
+ " transform=val_transform\n",
309
+ ")\n",
310
+ "\n",
311
+ "test_data = datasets.ImageFolder(\n",
312
+ " root=f\"{DATASET_PATH}/test\",\n",
313
+ " transform=val_transform\n",
314
+ ")\n",
315
+ "\n",
316
+ "train_loader = DataLoader(train_data, batch_size=BATCH_SIZE, shuffle=True)\n",
317
+ "val_loader = DataLoader(val_data, batch_size=BATCH_SIZE, shuffle=False)\n",
318
+ "test_loader = DataLoader(test_data, batch_size=BATCH_SIZE, shuffle=False)\n",
319
+ "\n",
320
+ "print(\"Class mapping:\", train_data.class_to_idx)\n",
321
+ "print(\"Train samples:\", len(train_data))\n",
322
+ "print(\"Val samples:\", len(val_data))\n",
323
+ "print(\"Test samples:\", len(test_data))\n"
324
+ ]
325
+ },
326
+ {
327
+ "cell_type": "code",
328
+ "execution_count": 7,
329
+ "metadata": {
330
+ "id": "VBpWsVAPvQOm"
331
+ },
332
+ "outputs": [],
333
+ "source": []
334
+ },
335
+ {
336
+ "cell_type": "code",
337
+ "execution_count": 9,
338
+ "metadata": {
339
+ "colab": {
340
+ "base_uri": "https://localhost:8080/"
341
+ },
342
+ "id": "R90aPddvtg3z",
343
+ "outputId": "4b65372d-de96-4dc3-a733-327ddbafe519"
344
+ },
345
+ "outputs": [
346
+ {
347
+ "name": "stdout",
348
+ "output_type": "stream",
349
+ "text": [
350
+ "\n",
351
+ "Epoch [1/15]\n"
352
+ ]
353
+ },
354
+ {
355
+ "name": "stderr",
356
+ "output_type": "stream",
357
+ "text": [
358
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:26<00:00, 1.13it/s]\n"
359
+ ]
360
+ },
361
+ {
362
+ "name": "stdout",
363
+ "output_type": "stream",
364
+ "text": [
365
+ "Train Loss: 46.1716, Train Acc: 49.52%\n",
366
+ "Val Loss: 23.4326, Val Acc: 72.36%\n",
367
+ "\n",
368
+ "Epoch [2/15]\n"
369
+ ]
370
+ },
371
+ {
372
+ "name": "stderr",
373
+ "output_type": "stream",
374
+ "text": [
375
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:24<00:00, 1.24it/s]\n"
376
+ ]
377
+ },
378
+ {
379
+ "name": "stdout",
380
+ "output_type": "stream",
381
+ "text": [
382
+ "Train Loss: 31.0843, Train Acc: 76.43%\n",
383
+ "Val Loss: 13.2697, Val Acc: 82.00%\n",
384
+ "\n",
385
+ "Epoch [3/15]\n"
386
+ ]
387
+ },
388
+ {
389
+ "name": "stderr",
390
+ "output_type": "stream",
391
+ "text": [
392
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.25it/s]\n"
393
+ ]
394
+ },
395
+ {
396
+ "name": "stdout",
397
+ "output_type": "stream",
398
+ "text": [
399
+ "Train Loss: 20.2007, Train Acc: 84.82%\n",
400
+ "Val Loss: 8.2944, Val Acc: 86.00%\n",
401
+ "\n",
402
+ "Epoch [4/15]\n"
403
+ ]
404
+ },
405
+ {
406
+ "name": "stderr",
407
+ "output_type": "stream",
408
+ "text": [
409
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:24<00:00, 1.21it/s]\n"
410
+ ]
411
+ },
412
+ {
413
+ "name": "stdout",
414
+ "output_type": "stream",
415
+ "text": [
416
+ "Train Loss: 15.8832, Train Acc: 86.65%\n",
417
+ "Val Loss: 5.8530, Val Acc: 89.45%\n",
418
+ "\n",
419
+ "Epoch [5/15]\n"
420
+ ]
421
+ },
422
+ {
423
+ "name": "stderr",
424
+ "output_type": "stream",
425
+ "text": [
426
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.25it/s]\n"
427
+ ]
428
+ },
429
+ {
430
+ "name": "stdout",
431
+ "output_type": "stream",
432
+ "text": [
433
+ "Train Loss: 11.4642, Train Acc: 91.07%\n",
434
+ "Val Loss: 4.3735, Val Acc: 92.91%\n",
435
+ "\n",
436
+ "Epoch [6/15]\n"
437
+ ]
438
+ },
439
+ {
440
+ "name": "stderr",
441
+ "output_type": "stream",
442
+ "text": [
443
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:24<00:00, 1.25it/s]\n"
444
+ ]
445
+ },
446
+ {
447
+ "name": "stdout",
448
+ "output_type": "stream",
449
+ "text": [
450
+ "Train Loss: 9.1711, Train Acc: 95.16%\n",
451
+ "Val Loss: 3.6670, Val Acc: 94.00%\n",
452
+ "\n",
453
+ "Epoch [7/15]\n"
454
+ ]
455
+ },
456
+ {
457
+ "name": "stderr",
458
+ "output_type": "stream",
459
+ "text": [
460
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.26it/s]\n"
461
+ ]
462
+ },
463
+ {
464
+ "name": "stdout",
465
+ "output_type": "stream",
466
+ "text": [
467
+ "Train Loss: 7.1015, Train Acc: 95.59%\n",
468
+ "Val Loss: 3.0334, Val Acc: 95.27%\n",
469
+ "\n",
470
+ "Epoch [8/15]\n"
471
+ ]
472
+ },
473
+ {
474
+ "name": "stderr",
475
+ "output_type": "stream",
476
+ "text": [
477
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.26it/s]\n"
478
+ ]
479
+ },
480
+ {
481
+ "name": "stdout",
482
+ "output_type": "stream",
483
+ "text": [
484
+ "Train Loss: 5.9129, Train Acc: 96.77%\n",
485
+ "Val Loss: 2.7183, Val Acc: 95.82%\n",
486
+ "\n",
487
+ "Epoch [9/15]\n"
488
+ ]
489
+ },
490
+ {
491
+ "name": "stderr",
492
+ "output_type": "stream",
493
+ "text": [
494
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:24<00:00, 1.25it/s]\n"
495
+ ]
496
+ },
497
+ {
498
+ "name": "stdout",
499
+ "output_type": "stream",
500
+ "text": [
501
+ "Train Loss: 4.7660, Train Acc: 98.17%\n",
502
+ "Val Loss: 2.6371, Val Acc: 96.18%\n",
503
+ "\n",
504
+ "Epoch [10/15]\n"
505
+ ]
506
+ },
507
+ {
508
+ "name": "stderr",
509
+ "output_type": "stream",
510
+ "text": [
511
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.26it/s]\n"
512
+ ]
513
+ },
514
+ {
515
+ "name": "stdout",
516
+ "output_type": "stream",
517
+ "text": [
518
+ "Train Loss: 4.2479, Train Acc: 97.42%\n",
519
+ "Val Loss: 2.5740, Val Acc: 96.18%\n",
520
+ "\n",
521
+ "Epoch [11/15]\n"
522
+ ]
523
+ },
524
+ {
525
+ "name": "stderr",
526
+ "output_type": "stream",
527
+ "text": [
528
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.26it/s]\n"
529
+ ]
530
+ },
531
+ {
532
+ "name": "stdout",
533
+ "output_type": "stream",
534
+ "text": [
535
+ "Train Loss: 3.4081, Train Acc: 99.03%\n",
536
+ "Val Loss: 2.5000, Val Acc: 95.64%\n",
537
+ "\n",
538
+ "Epoch [12/15]\n"
539
+ ]
540
+ },
541
+ {
542
+ "name": "stderr",
543
+ "output_type": "stream",
544
+ "text": [
545
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.25it/s]\n"
546
+ ]
547
+ },
548
+ {
549
+ "name": "stdout",
550
+ "output_type": "stream",
551
+ "text": [
552
+ "Train Loss: 4.4313, Train Acc: 98.28%\n",
553
+ "Val Loss: 2.3392, Val Acc: 96.36%\n",
554
+ "\n",
555
+ "Epoch [13/15]\n"
556
+ ]
557
+ },
558
+ {
559
+ "name": "stderr",
560
+ "output_type": "stream",
561
+ "text": [
562
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:30<00:00, 1.02s/it]\n"
563
+ ]
564
+ },
565
+ {
566
+ "name": "stdout",
567
+ "output_type": "stream",
568
+ "text": [
569
+ "Train Loss: 3.8637, Train Acc: 98.17%\n",
570
+ "Val Loss: 2.3700, Val Acc: 96.18%\n",
571
+ "\n",
572
+ "Epoch [14/15]\n"
573
+ ]
574
+ },
575
+ {
576
+ "name": "stderr",
577
+ "output_type": "stream",
578
+ "text": [
579
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.26it/s]\n"
580
+ ]
581
+ },
582
+ {
583
+ "name": "stdout",
584
+ "output_type": "stream",
585
+ "text": [
586
+ "Train Loss: 3.9151, Train Acc: 98.92%\n",
587
+ "Val Loss: 2.4089, Val Acc: 96.36%\n",
588
+ "\n",
589
+ "Epoch [15/15]\n"
590
+ ]
591
+ },
592
+ {
593
+ "name": "stderr",
594
+ "output_type": "stream",
595
+ "text": [
596
+ "100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.25it/s]\n"
597
+ ]
598
+ },
599
+ {
600
+ "name": "stdout",
601
+ "output_type": "stream",
602
+ "text": [
603
+ "Train Loss: 3.1275, Train Acc: 98.82%\n",
604
+ "Val Loss: 2.2238, Val Acc: 95.82%\n"
605
+ ]
606
+ }
607
+ ],
608
+ "source": [
609
+ "# STEP 6: Build EfficientNet-B0 model & training loop\n",
610
+ "\n",
611
+ "import torch\n",
612
+ "import torch.nn as nn\n",
613
+ "import torch.optim as optim\n",
614
+ "from torchvision import models\n",
615
+ "from tqdm import tqdm\n",
616
+ "\n",
617
+ "NUM_CLASSES = 6\n",
618
+ "EPOCHS = 15\n",
619
+ "LR = 1e-4\n",
620
+ "\n",
621
+ "# Load pretrained EfficientNet-B0\n",
622
+ "model = models.efficientnet_b0(pretrained=True)\n",
623
+ "\n",
624
+ "# Replace classifier\n",
625
+ "in_features = model.classifier[1].in_features\n",
626
+ "model.classifier[1] = nn.Linear(in_features, NUM_CLASSES)\n",
627
+ "\n",
628
+ "model = model.to(DEVICE)\n",
629
+ "\n",
630
+ "# Loss & optimizer\n",
631
+ "criterion = nn.CrossEntropyLoss()\n",
632
+ "optimizer = optim.Adam(model.parameters(), lr=LR)\n",
633
+ "\n",
634
+ "# Training & validation loop\n",
635
+ "for epoch in range(EPOCHS):\n",
636
+ " print(f\"\\nEpoch [{epoch+1}/{EPOCHS}]\")\n",
637
+ "\n",
638
+ " # ---- Training ----\n",
639
+ " model.train()\n",
640
+ " train_loss = 0\n",
641
+ " correct = 0\n",
642
+ " total = 0\n",
643
+ "\n",
644
+ " for images, labels in tqdm(train_loader):\n",
645
+ " images, labels = images.to(DEVICE), labels.to(DEVICE)\n",
646
+ "\n",
647
+ " optimizer.zero_grad()\n",
648
+ " outputs = model(images)\n",
649
+ " loss = criterion(outputs, labels)\n",
650
+ " loss.backward()\n",
651
+ " optimizer.step()\n",
652
+ "\n",
653
+ " train_loss += loss.item()\n",
654
+ " _, predicted = torch.max(outputs, 1)\n",
655
+ " total += labels.size(0)\n",
656
+ " correct += (predicted == labels).sum().item()\n",
657
+ "\n",
658
+ " train_acc = 100 * correct / total\n",
659
+ " print(f\"Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.2f}%\")\n",
660
+ "\n",
661
+ " # ---- Validation ----\n",
662
+ " model.eval()\n",
663
+ " val_loss = 0\n",
664
+ " correct = 0\n",
665
+ " total = 0\n",
666
+ "\n",
667
+ " with torch.no_grad():\n",
668
+ " for images, labels in val_loader:\n",
669
+ " images, labels = images.to(DEVICE), labels.to(DEVICE)\n",
670
+ " outputs = model(images)\n",
671
+ " loss = criterion(outputs, labels)\n",
672
+ "\n",
673
+ " val_loss += loss.item()\n",
674
+ " _, predicted = torch.max(outputs, 1)\n",
675
+ " total += labels.size(0)\n",
676
+ " correct += (predicted == labels).sum().item()\n",
677
+ "\n",
678
+ " val_acc = 100 * correct / total\n",
679
+ " print(f\"Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.2f}%\")\n"
680
+ ]
681
+ },
682
+ {
683
+ "cell_type": "code",
684
+ "execution_count": 10,
685
+ "metadata": {
686
+ "colab": {
687
+ "base_uri": "https://localhost:8080/",
688
+ "height": 954
689
+ },
690
+ "id": "5XUQH8-ExIx6",
691
+ "outputId": "e57ad2b3-8825-4f83-d3dc-f537c844b7c6"
692
+ },
693
+ "outputs": [
694
+ {
695
+ "name": "stdout",
696
+ "output_type": "stream",
697
+ "text": [
698
+ "\n",
699
+ "Classification Report:\n",
700
+ "\n",
701
+ " precision recall f1-score support\n",
702
+ "\n",
703
+ " Bird-drop 0.94 1.00 0.97 17\n",
704
+ " Clean 0.84 0.89 0.86 18\n",
705
+ " Dusty 0.93 0.81 0.87 16\n",
706
+ "Electrical-damage 0.92 0.92 0.92 13\n",
707
+ " Physical-Damage 1.00 1.00 1.00 15\n",
708
+ " Snow-Covered 1.00 1.00 1.00 16\n",
709
+ "\n",
710
+ " accuracy 0.94 95\n",
711
+ " macro avg 0.94 0.94 0.94 95\n",
712
+ " weighted avg 0.94 0.94 0.94 95\n",
713
+ "\n"
714
+ ]
715
+ },
716
+ {
717
+ "data": {
718
+ "image/png": "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aGgIfX19fPzxx6hdu3aZ4jp16hTWrFmDWbNmyae13LJlCzw9PTFjxgwsXLiwTMcjImIFnoioHHXt2hXx8fHo3bs3Dh06hFGjRuGbb75BSkoKlixZgpUrV8r33bhxI+bMmYNLly5h3LhxOHXqFKZMmYJdu3apNCZzc3McOHAAenp6mDRpErZt24bg4GD4+fkVi71WrVrYvHkzRo0ahdWrV6Nt27Y4deoUjI2NSz1+hw4dEBoaCnNzc8ycOROLFy9G8+bNERkZWebktzxMnToV48ePx9GjRzF27FhER0fjjz/+QM2aNRX209LSwrZt26CpqYnhw4ejX79+OHPmTJnO9eLFCwwePBju7u6YNm2afH2bNm0wduxYLFmyBOfPn1fJdRFR5SGRleUuISIiIiIiEhQr8EREREREIsIEnoiIiIhIRJjAExERERGJCBN4IiIiIiIRYQJPRERERCQiTOCJiIiIiESECTwRERERkYjwSaykdnTdvxI6hArjyaVVQodARESVhI6AWWV55g65Mer3bykr8EREREREIsIKPBERERGJm6Ry1aSZwBMRERGRuEkkQkfwQVWurytERERERCLHCjwRERERiVsla6GpXFdLRERERCRyrMATERERkbixB56IiIiIiNQVK/BEREREJG7sgSciIiIiInXFCjwRERERiVsl64FnAk9ERERE4sYWGiIiIiIiUleswBMRERGRuFWyFhpW4ImIiIiIRIQVeCIiIiISN/bAExERERGRumIFnoiIiIjEjT3wRERERESkrliBJyIiIiJxYw88ERERERGpK1bgiYiIiEjc2ANPRERERCQiEo3yW8ooPDwcfn5+qF69OiQSCQ4ePFhsn4SEBHTt2hXGxsbQ19dH06ZNkZqaqvQ5mMATEREREalIdnY2GjZsiNWrV5e4PSkpCa1bt4azszPCwsIQHx+PGTNmQEdHR+lzsIWGiIiIiMRNjW5i9fX1ha+vb6nbp02bhs6dO2PhwoXydfb29mU6h/pcLRERERGRmsnLy8Pz588Vlry8vHc6VlFREf744w/UqVMH3t7esLS0xMcff1xim83bMIEnIiIiInHTkJTbEhwcDGNjY4UlODj4ncLMyMhAVlYWFixYAB8fHxw7dgw9evRAz549cebMGaWPwxYaIiIiIqJSTJkyBUFBQQrrpFLpOx2rqKgIANCtWzd8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v//eLQfgKvZFRBoKFfAiIhYUHR1NRkYG3t7eBAYG4un5//8d+Pr6ul1bXl7ODTfcwOuvv17nda688sp/9P52u/2Cv6e8vByA999/n9atW7ud8/Hx+Uc5RETMSAW8iIgF+fr6Eh4efl7Xdu3albfeeouAgAD8/f3PeM3VV1/N9u3bueWWWwCorq7mq6++omvXrme8PjIyEqfTyZYtW1wtNP/rz98A1NTUuNY6duyIj48PJSUlZ925j4iIIDs7223t888///u/pIiIiegmVhEROafhw4fTsmVLhgwZQl5eHvv27WPz5s0kJiZy8OBBACZPnswzzzzD2rVr+eGHH5gwYcI5Z7i3bduW+Ph4Ro0axdq1a12vuXLlSgBCQkKw2WysW7eOI0eOUF5ejsPhIDk5mUceeYTly5ezd+9e8vPzmTdvHsuXLwdg/Pjx7Nmzh0cffZTCwkLeeOMNXn755Uv9EYmIXFYq4EVE5JyaNGnCJ598QnBwMMOGDSMiIoLRo0dz6tQp1478lClTGDFiBPHx8fTs2ROHw8Htt99+ztfNyMjgjjvuYMKECXTo0IGxY8dy4sQJAFq3bs306dOZOnUqrVq1YtKkSQCkpaXx1FNPMWvWLCIiIoiJieH9998nNDQUgODgYFavXs3atWvp3LkzmZmZzJw58xJ+OiIil5+t9mx3GImIiIiISL2jHXgRERERERNRAS8iIiIiYiIq4EVERERETEQFvIiIiIiIiaiAFxERERExERXwIiIiIiImogJeRERERMREVMCLiIiIiJiICngRERERERNRAS8iIiIiYiIq4EVERERETEQFvIiIiIiIifwfGoZpHdZPaM4AAAAASUVORK5CYII=",
719
+ "text/plain": [
720
+ "<Figure size 800x600 with 2 Axes>"
721
+ ]
722
+ },
723
+ "metadata": {},
724
+ "output_type": "display_data"
725
+ }
726
+ ],
727
+ "source": [
728
+ "# STEP 7: Test Evaluation Metrics\n",
729
+ "\n",
730
+ "import numpy as np\n",
731
+ "from sklearn.metrics import classification_report, confusion_matrix\n",
732
+ "import seaborn as sns\n",
733
+ "import matplotlib.pyplot as plt\n",
734
+ "\n",
735
+ "model.eval()\n",
736
+ "\n",
737
+ "all_preds = []\n",
738
+ "all_labels = []\n",
739
+ "\n",
740
+ "with torch.no_grad():\n",
741
+ " for images, labels in test_loader:\n",
742
+ " images = images.to(DEVICE)\n",
743
+ " outputs = model(images)\n",
744
+ " _, preds = torch.max(outputs, 1)\n",
745
+ "\n",
746
+ " all_preds.extend(preds.cpu().numpy())\n",
747
+ " all_labels.extend(labels.numpy())\n",
748
+ "\n",
749
+ "# Classification report\n",
750
+ "print(\"\\nClassification Report:\\n\")\n",
751
+ "print(classification_report(all_labels, all_preds, target_names=train_data.classes))\n",
752
+ "\n",
753
+ "# Confusion Matrix\n",
754
+ "cm = confusion_matrix(all_labels, all_preds)\n",
755
+ "\n",
756
+ "plt.figure(figsize=(8,6))\n",
757
+ "sns.heatmap(cm, annot=True, fmt=\"d\",\n",
758
+ " xticklabels=train_data.classes,\n",
759
+ " yticklabels=train_data.classes,\n",
760
+ " cmap=\"Blues\")\n",
761
+ "plt.xlabel(\"Predicted\")\n",
762
+ "plt.ylabel(\"Actual\")\n",
763
+ "plt.title(\"Confusion Matrix\")\n",
764
+ "plt.show()\n"
765
+ ]
766
+ },
767
+ {
768
+ "cell_type": "code",
769
+ "execution_count": 11,
770
+ "metadata": {
771
+ "colab": {
772
+ "base_uri": "https://localhost:8080/"
773
+ },
774
+ "id": "KMyAqFlhyVK7",
775
+ "outputId": "c1d68f2a-d819-4cca-abbd-5143d875db73"
776
+ },
777
+ "outputs": [
778
+ {
779
+ "name": "stdout",
780
+ "output_type": "stream",
781
+ "text": [
782
+ "\u001b[?25l \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m0.0/693.4 kB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r\u001b[2K \u001b[91m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[91mβ•Έ\u001b[0m \u001b[32m686.1/693.4 kB\u001b[0m \u001b[31m23.4 MB/s\u001b[0m eta \u001b[36m0:00:01\u001b[0m\r\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m693.4/693.4 kB\u001b[0m \u001b[31m16.5 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
783
+ "\u001b[?25h\u001b[?25l \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m0.0/133.1 kB\u001b[0m \u001b[31m?\u001b[0m eta \u001b[36m-:--:--\u001b[0m\r\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m133.1/133.1 kB\u001b[0m \u001b[31m13.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
784
+ "\u001b[?25h"
785
+ ]
786
+ }
787
+ ],
788
+ "source": [
789
+ "# FIX: Install missing ONNX dependencies required by latest PyTorch\n",
790
+ "\n",
791
+ "!pip install -q onnxscript\n"
792
+ ]
793
+ },
794
+ {
795
+ "cell_type": "code",
796
+ "execution_count": 12,
797
+ "metadata": {
798
+ "colab": {
799
+ "base_uri": "https://localhost:8080/"
800
+ },
801
+ "id": "r-kjCoenyb79",
802
+ "outputId": "4acc6c11-5789-482d-e296-cfdf8fb72736"
803
+ },
804
+ "outputs": [
805
+ {
806
+ "name": "stderr",
807
+ "output_type": "stream",
808
+ "text": [
809
+ "/tmp/ipython-input-3311461802.py:12: UserWarning: # 'dynamic_axes' is not recommended when dynamo=True, and may lead to 'torch._dynamo.exc.UserError: Constraints violated.' Supply the 'dynamic_shapes' argument instead if export is unsuccessful.\n",
810
+ " torch.onnx.export(\n",
811
+ "W1218 21:15:42.026000 174 torch/onnx/_internal/exporter/_compat.py:114] Setting ONNX exporter to use operator set version 18 because the requested opset_version 11 is a lower version than we have implementations for. Automatic version conversion will be performed, which may not be successful at converting to the requested version. If version conversion is unsuccessful, the opset version of the exported model will be kept at 18. Please consider setting opset_version >=18 to leverage latest ONNX features\n"
812
+ ]
813
+ },
814
+ {
815
+ "name": "stdout",
816
+ "output_type": "stream",
817
+ "text": [
818
+ "[torch.onnx] Obtain model graph for `EfficientNet([...]` with `torch.export.export(..., strict=False)`...\n",
819
+ "[torch.onnx] Obtain model graph for `EfficientNet([...]` with `torch.export.export(..., strict=False)`... βœ…\n",
820
+ "[torch.onnx] Run decomposition...\n"
821
+ ]
822
+ },
823
+ {
824
+ "name": "stderr",
825
+ "output_type": "stream",
826
+ "text": [
827
+ "WARNING:onnxscript.version_converter:The model version conversion is not supported by the onnxscript version converter and fallback is enabled. The model will be converted using the onnx C API (target version: 11).\n"
828
+ ]
829
+ },
830
+ {
831
+ "name": "stdout",
832
+ "output_type": "stream",
833
+ "text": [
834
+ "[torch.onnx] Run decomposition... βœ…\n",
835
+ "[torch.onnx] Translate the graph into ONNX...\n",
836
+ "[torch.onnx] Translate the graph into ONNX... βœ…\n"
837
+ ]
838
+ },
839
+ {
840
+ "name": "stderr",
841
+ "output_type": "stream",
842
+ "text": [
843
+ "WARNING:onnxscript.version_converter:Failed to convert the model to the target version 11 using the ONNX C API. The model was not modified\n",
844
+ "Traceback (most recent call last):\n",
845
+ " File \"/usr/local/lib/python3.12/dist-packages/onnxscript/version_converter/__init__.py\", line 127, in call\n",
846
+ " converted_proto = _c_api_utils.call_onnx_api(\n",
847
+ " ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
848
+ " File \"/usr/local/lib/python3.12/dist-packages/onnxscript/version_converter/_c_api_utils.py\", line 65, in call_onnx_api\n",
849
+ " result = func(proto)\n",
850
+ " ^^^^^^^^^^^\n",
851
+ " File \"/usr/local/lib/python3.12/dist-packages/onnxscript/version_converter/__init__.py\", line 122, in _partial_convert_version\n",
852
+ " return onnx.version_converter.convert_version(\n",
853
+ " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
854
+ " File \"/usr/local/lib/python3.12/dist-packages/onnx/version_converter.py\", line 39, in convert_version\n",
855
+ " converted_model_str = C.convert_version(model_str, target_version)\n",
856
+ " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n",
857
+ "RuntimeError: /github/workspace/onnx/version_converter/adapters/axes_input_to_attribute.h:65: adapt: Assertion `node->hasAttribute(kaxes)` failed: No initializer or constant input to node found\n"
858
+ ]
859
+ },
860
+ {
861
+ "name": "stdout",
862
+ "output_type": "stream",
863
+ "text": [
864
+ "Applied 98 of general pattern rewrite rules.\n",
865
+ "βœ… ONNX model exported to: /content/solar_panel_defect_model.onnx\n"
866
+ ]
867
+ }
868
+ ],
869
+ "source": [
870
+ "# STEP 8A: Export PyTorch model to ONNX\n",
871
+ "\n",
872
+ "import torch\n",
873
+ "\n",
874
+ "ONNX_PATH = \"/content/solar_panel_defect_model.onnx\"\n",
875
+ "\n",
876
+ "# Dummy input (batch size 1, 3-channel, 224x224)\n",
877
+ "dummy_input = torch.randn(1, 3, 224, 224).to(DEVICE)\n",
878
+ "\n",
879
+ "model.eval()\n",
880
+ "\n",
881
+ "torch.onnx.export(\n",
882
+ " model,\n",
883
+ " dummy_input,\n",
884
+ " ONNX_PATH,\n",
885
+ " export_params=True,\n",
886
+ " opset_version=11,\n",
887
+ " do_constant_folding=True,\n",
888
+ " input_names=[\"input_image\"],\n",
889
+ " output_names=[\"class_scores\"],\n",
890
+ " dynamic_axes={\n",
891
+ " \"input_image\": {0: \"batch_size\"},\n",
892
+ " \"class_scores\": {0: \"batch_size\"}\n",
893
+ " }\n",
894
+ ")\n",
895
+ "\n",
896
+ "print(\"βœ… ONNX model exported to:\", ONNX_PATH)\n"
897
+ ]
898
+ },
899
+ {
900
+ "cell_type": "code",
901
+ "execution_count": 13,
902
+ "metadata": {
903
+ "colab": {
904
+ "base_uri": "https://localhost:8080/"
905
+ },
906
+ "id": "WPPp6_uVyroE",
907
+ "outputId": "1542d5a3-f5ac-4138-b4b5-633b9e9032f5"
908
+ },
909
+ "outputs": [
910
+ {
911
+ "name": "stdout",
912
+ "output_type": "stream",
913
+ "text": [
914
+ "ONNX output shape: (1, 6)\n",
915
+ "Sample output: [[ 993.0995 199.42247 -608.0232 -2058.3643 -4874.3457 -1554.5675 ]]\n"
916
+ ]
917
+ }
918
+ ],
919
+ "source": [
920
+ "# STEP 8B: Verify ONNX model inference\n",
921
+ "\n",
922
+ "import onnxruntime as ort\n",
923
+ "import numpy as np\n",
924
+ "\n",
925
+ "# Load ONNX model\n",
926
+ "ort_session = ort.InferenceSession(ONNX_PATH)\n",
927
+ "\n",
928
+ "# Prepare dummy input\n",
929
+ "dummy_np = np.random.randn(1, 3, 224, 224).astype(np.float32)\n",
930
+ "\n",
931
+ "# Run inference\n",
932
+ "outputs = ort_session.run(\n",
933
+ " None,\n",
934
+ " {\"input_image\": dummy_np}\n",
935
+ ")\n",
936
+ "\n",
937
+ "print(\"ONNX output shape:\", outputs[0].shape)\n",
938
+ "print(\"Sample output:\", outputs[0])\n"
939
+ ]
940
+ },
941
+ {
942
+ "cell_type": "code",
943
+ "execution_count": 14,
944
+ "metadata": {
945
+ "id": "Tj-Ynteqzf23"
946
+ },
947
+ "outputs": [],
948
+ "source": [
949
+ "# STEP G1: Install Gradio\n",
950
+ "\n",
951
+ "!pip install -q gradio onnxruntime pillow\n"
952
+ ]
953
+ },
954
+ {
955
+ "cell_type": "code",
956
+ "execution_count": 15,
957
+ "metadata": {
958
+ "colab": {
959
+ "base_uri": "https://localhost:8080/"
960
+ },
961
+ "id": "PQzRiyU7z6WY",
962
+ "outputId": "837b66a5-ad05-44cc-9e02-e5ff0db1eade"
963
+ },
964
+ "outputs": [
965
+ {
966
+ "name": "stdout",
967
+ "output_type": "stream",
968
+ "text": [
969
+ "βœ… ONNX model loaded\n",
970
+ "βœ… Inference pipeline ready\n"
971
+ ]
972
+ }
973
+ ],
974
+ "source": [
975
+ "# STEP G2: Load ONNX model & preprocessing function\n",
976
+ "\n",
977
+ "import onnxruntime as ort\n",
978
+ "import numpy as np\n",
979
+ "from PIL import Image\n",
980
+ "import torch\n",
981
+ "from torchvision import transforms\n",
982
+ "\n",
983
+ "# Path to ONNX model\n",
984
+ "ONNX_PATH = \"/content/solar_panel_defect_model.onnx\"\n",
985
+ "\n",
986
+ "# Load ONNX runtime session\n",
987
+ "ort_session = ort.InferenceSession(ONNX_PATH, providers=[\"CPUExecutionProvider\"])\n",
988
+ "\n",
989
+ "print(\"βœ… ONNX model loaded\")\n",
990
+ "\n",
991
+ "# Class labels (VERY IMPORTANT: order must match training)\n",
992
+ "CLASS_NAMES = [\n",
993
+ " 'Bird-drop',\n",
994
+ " 'Clean',\n",
995
+ " 'Dusty',\n",
996
+ " 'Electrical-damage',\n",
997
+ " 'Physical-Damage',\n",
998
+ " 'Snow-Covered'\n",
999
+ "]\n",
1000
+ "\n",
1001
+ "# Same preprocessing as training\n",
1002
+ "IMG_SIZE = 224\n",
1003
+ "\n",
1004
+ "preprocess = transforms.Compose([\n",
1005
+ " transforms.Resize((IMG_SIZE, IMG_SIZE)),\n",
1006
+ " transforms.Grayscale(num_output_channels=3),\n",
1007
+ " transforms.ToTensor(),\n",
1008
+ " transforms.Normalize(\n",
1009
+ " mean=[0.485, 0.456, 0.406],\n",
1010
+ " std=[0.229, 0.224, 0.225]\n",
1011
+ " )\n",
1012
+ "])\n",
1013
+ "\n",
1014
+ "def predict_image(pil_image):\n",
1015
+ " \"\"\"\n",
1016
+ " Input: PIL Image\n",
1017
+ " Output: (predicted_class, confidence, full_probs_dict)\n",
1018
+ " \"\"\"\n",
1019
+ " img = preprocess(pil_image).unsqueeze(0).numpy().astype(np.float32)\n",
1020
+ "\n",
1021
+ " # ONNX inference\n",
1022
+ " outputs = ort_session.run(\n",
1023
+ " None,\n",
1024
+ " {\"input_image\": img}\n",
1025
+ " )[0]\n",
1026
+ "\n",
1027
+ " # Softmax\n",
1028
+ " exp_scores = np.exp(outputs)\n",
1029
+ " probs = exp_scores / np.sum(exp_scores, axis=1, keepdims=True)\n",
1030
+ "\n",
1031
+ " probs = probs[0]\n",
1032
+ " pred_idx = int(np.argmax(probs))\n",
1033
+ "\n",
1034
+ " predicted_class = CLASS_NAMES[pred_idx]\n",
1035
+ " confidence = float(probs[pred_idx])\n",
1036
+ "\n",
1037
+ " prob_dict = {CLASS_NAMES[i]: float(probs[i]) for i in range(len(CLASS_NAMES))}\n",
1038
+ "\n",
1039
+ " return predicted_class, confidence, prob_dict\n",
1040
+ "\n",
1041
+ "\n",
1042
+ "print(\"βœ… Inference pipeline ready\")\n"
1043
+ ]
1044
+ },
1045
+ {
1046
+ "cell_type": "code",
1047
+ "execution_count": null,
1048
+ "metadata": {
1049
+ "colab": {
1050
+ "base_uri": "https://localhost:8080/",
1051
+ "height": 646
1052
+ },
1053
+ "id": "IHyiWnj_0EFQ",
1054
+ "outputId": "0a33d28f-97e8-4d98-d582-261a875c92f4"
1055
+ },
1056
+ "outputs": [
1057
+ {
1058
+ "name": "stdout",
1059
+ "output_type": "stream",
1060
+ "text": [
1061
+ "It looks like you are running Gradio on a hosted Jupyter notebook, which requires `share=True`. Automatically setting `share=True` (you can turn this off by setting `share=False` in `launch()` explicitly).\n",
1062
+ "\n",
1063
+ "Colab notebook detected. This cell will run indefinitely so that you can see errors and logs. To turn off, set debug=False in launch().\n",
1064
+ "* Running on public URL: https://d55396fb96ea5bc795.gradio.live\n",
1065
+ "\n",
1066
+ "This share link expires in 1 week. For free permanent hosting and GPU upgrades, run `gradio deploy` from the terminal in the working directory to deploy to Hugging Face Spaces (https://huggingface.co/spaces)\n"
1067
+ ]
1068
+ },
1069
+ {
1070
+ "data": {
1071
+ "text/html": [
1072
+ "<div><iframe src=\"https://d55396fb96ea5bc795.gradio.live\" width=\"100%\" height=\"500\" allow=\"autoplay; camera; microphone; clipboard-read; clipboard-write;\" frameborder=\"0\" allowfullscreen></iframe></div>"
1073
+ ],
1074
+ "text/plain": [
1075
+ "<IPython.core.display.HTML object>"
1076
+ ]
1077
+ },
1078
+ "metadata": {},
1079
+ "output_type": "display_data"
1080
+ }
1081
+ ],
1082
+ "source": [
1083
+ "# STEP G3: Gradio Interface\n",
1084
+ "\n",
1085
+ "import gradio as gr\n",
1086
+ "\n",
1087
+ "def gradio_predict(image):\n",
1088
+ " pred_class, confidence, prob_dict = predict_image(image)\n",
1089
+ "\n",
1090
+ " confidence_percent = f\"{confidence * 100:.2f}%\"\n",
1091
+ "\n",
1092
+ " return pred_class, confidence_percent, prob_dict\n",
1093
+ "\n",
1094
+ "\n",
1095
+ "iface = gr.Interface(\n",
1096
+ " fn=gradio_predict,\n",
1097
+ " inputs=gr.Image(type=\"pil\", label=\"Upload Solar Panel Image (Thermal / IR / RGB)\"),\n",
1098
+ " outputs=[\n",
1099
+ " gr.Textbox(label=\"Predicted Defect Class\"),\n",
1100
+ " gr.Textbox(label=\"Confidence\"),\n",
1101
+ " gr.Label(label=\"All Class Probabilities\")\n",
1102
+ " ],\n",
1103
+ " title=\"AI-Powered Solar Panel Defect Detection\",\n",
1104
+ " description=(\n",
1105
+ " \"Upload any solar panel image (thermal, infrared, or RGB). \"\n",
1106
+ " \"The AI model classifies defects such as soiling, electrical damage, \"\n",
1107
+ " \"physical damage, snow coverage, or clean panels.\"\n",
1108
+ " ),\n",
1109
+ " examples=None\n",
1110
+ ")\n",
1111
+ "\n",
1112
+ "iface.launch(debug=True)\n"
1113
+ ]
1114
+ }
1115
+ ],
1116
+ "metadata": {
1117
+ "accelerator": "GPU",
1118
+ "colab": {
1119
+ "gpuType": "T4",
1120
+ "provenance": []
1121
+ },
1122
+ "kernelspec": {
1123
+ "display_name": "Python 3",
1124
+ "name": "python3"
1125
+ },
1126
+ "language_info": {
1127
+ "name": "python"
1128
+ }
1129
+ },
1130
+ "nbformat": 4,
1131
+ "nbformat_minor": 0
1132
+ }
data/data_info.txt ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ================================================================================
2
+ PV PANEL DEFECT DETECTION DATASET
3
+ ================================================================================
4
+
5
+ πŸ“Š DATASET OVERVIEW
6
+ --------------------------------------------------------------------------------
7
+ This dataset contains labeled images of photovoltaic (PV) panels categorized into
8
+ six distinct classes based on their operational condition. It was curated as part
9
+ of an educational and research initiative to evaluate and compare machine learning
10
+ classifiers and hybrid deep learning approaches for automatic PV defect detection.
11
+
12
+ By aggregating images from various open sources, this collection provides a
13
+ structured, balanced, and high-quality dataset suitable for training robust
14
+ classification models.
15
+
16
+ πŸ” DATASET DETAILS
17
+ --------------------------------------------------------------------------------
18
+ The dataset includes the following six classes:
19
+
20
+ 1. Bird-drop : Panels contaminated with bird droppings, causing partial shading.
21
+ 2. Clean : Panels in optimal, perfect condition with no obstructions.
22
+ 3. Dusty : Panels accumulating dust, reducing light absorption efficiency.
23
+ 4. Electrical-damage: Internal defects including hot spots, delamination, and bypass diode failures.
24
+ 5. Physical-damage : External mechanical damage such as glass cracks or frame breakage.
25
+ 6. Snow-covered : Panels partially or completely obscured by snow accumulation.
26
+
27
+ πŸ“Š DATASET DISTRIBUTION
28
+ --------------------------------------------------------------------------------
29
+ The dataset is partitioned into Training, Validation, and Testing sets as follows:
30
+
31
+ | Class | Training | Validation | Test | Total |
32
+ |-------------------|----------|------------|-------|--------|
33
+ | Bird-drop | 2,253 | 284 | 296 | 2,833 |
34
+ | Clean | 2,189 | 271 | 278 | 2,738 |
35
+ | Dusty | 2,097 | 264 | 258 | 2,619 |
36
+ | Electrical-damage | 1,842 | 231 | 228 | 2,301 |
37
+ | Physical-damage | 1,867 | 239 | 233 | 2,339 |
38
+ | Snow-covered | 1,734 | 212 | 223 | 2,169 |
39
+ |-------------------|----------|------------|-------|--------|
40
+ | TOTAL | 11,982 | 1,501 | 1,516 | 14,999 |
41
+
42
+ πŸ—„ SOURCES & ATTRIBUTION
43
+ --------------------------------------------------------------------------------
44
+ 1. Primary Source (Kaggle):
45
+ "Solar Panel Images Clean and Faulty Images"
46
+ - Licensed for public and research use.
47
+
48
+ 2. Supplementary Images:
49
+ Manually collected from publicly available web sources (e.g., Google Images,
50
+ educational portals, and academic references) to balance the class distribution.
51
+
52
+ ⚠️ DISCLAIMER
53
+ This dataset is a custom compilation intended strictly for non-commercial,
54
+ educational, and research purposes. All rights to the individual images remain
55
+ with their original authors or data providers.
56
+
57
+ πŸ’‘ USAGE SUGGESTIONS
58
+ --------------------------------------------------------------------------------
59
+ This dataset is ideal for:
60
+ * Machine Learning model training and evaluation (SVM, Random Forest, etc.).
61
+ * Deep Learning transfer learning experiments (ResNet, VGG, EfficientNet).
62
+ * Developing Hybrid models (e.g., CNN for feature extraction + ML classifiers).
63
+ * Experimentation with Explainable AI (XAI) methods (e.g., LIME, Grad-CAM).
64
+
65
+ πŸ‘©β€πŸ’» CITATION
66
+ --------------------------------------------------------------------------------
67
+ If you use this dataset in your research or project, please cite it as follows:
68
+
69
+ TechTrident (2025). PV Panel Defect Dataset for Machine
70
+ Learning & Deep Learning Analysis. Available at: https://www.kaggle.com/datasets/alicjalena/pv-panel-defect-dataset
logs/training_logs.txt ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Epoch [1/15]
2
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:26<00:00, 1.13it/s]
3
+ Train Loss: 46.1716, Train Acc: 49.52%
4
+ Val Loss: 23.4326, Val Acc: 72.36%
5
+
6
+ Epoch [2/15]
7
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:24<00:00, 1.24it/s]
8
+ Train Loss: 31.0843, Train Acc: 76.43%
9
+ Val Loss: 13.2697, Val Acc: 82.00%
10
+
11
+ Epoch [3/15]
12
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.25it/s]
13
+ Train Loss: 20.2007, Train Acc: 84.82%
14
+ Val Loss: 8.2944, Val Acc: 86.00%
15
+
16
+ Epoch [4/15]
17
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:24<00:00, 1.21it/s]
18
+ Train Loss: 15.8832, Train Acc: 86.65%
19
+ Val Loss: 5.8530, Val Acc: 89.45%
20
+
21
+ Epoch [5/15]
22
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.25it/s]
23
+ Train Loss: 11.4642, Train Acc: 91.07%
24
+ Val Loss: 4.3735, Val Acc: 92.91%
25
+
26
+ Epoch [6/15]
27
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:24<00:00, 1.25it/s]
28
+ Train Loss: 9.1711, Train Acc: 95.16%
29
+ Val Loss: 3.6670, Val Acc: 94.00%
30
+
31
+ Epoch [7/15]
32
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.26it/s]
33
+ Train Loss: 7.1015, Train Acc: 95.59%
34
+ Val Loss: 3.0334, Val Acc: 95.27%
35
+
36
+ Epoch [8/15]
37
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.26it/s]
38
+ Train Loss: 5.9129, Train Acc: 96.77%
39
+ Val Loss: 2.7183, Val Acc: 95.82%
40
+
41
+ Epoch [9/15]
42
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:24<00:00, 1.25it/s]
43
+ Train Loss: 4.7660, Train Acc: 98.17%
44
+ Val Loss: 2.6371, Val Acc: 96.18%
45
+
46
+ Epoch [10/15]
47
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.26it/s]
48
+ Train Loss: 4.2479, Train Acc: 97.42%
49
+ Val Loss: 2.5740, Val Acc: 96.18%
50
+
51
+ Epoch [11/15]
52
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.26it/s]
53
+ Train Loss: 3.4081, Train Acc: 99.03%
54
+ Val Loss: 2.5000, Val Acc: 95.64%
55
+
56
+ Epoch [12/15]
57
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.25it/s]
58
+ Train Loss: 4.4313, Train Acc: 98.28%
59
+ Val Loss: 2.3392, Val Acc: 96.36%
60
+
61
+ Epoch [13/15]
62
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:30<00:00, 1.02s/it]
63
+ Train Loss: 3.8637, Train Acc: 98.17%
64
+ Val Loss: 2.3700, Val Acc: 96.18%
65
+
66
+ Epoch [14/15]
67
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.26it/s]
68
+ Train Loss: 3.9151, Train Acc: 98.92%
69
+ Val Loss: 2.4089, Val Acc: 96.36%
70
+
71
+ Epoch [15/15]
72
+ 100%|β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ| 30/30 [00:23<00:00, 1.25it/s]
73
+ Train Loss: 3.1275, Train Acc: 98.82%
74
+ Val Loss: 2.2238, Val Acc: 95.82%
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ onnxruntime
2
+ torch
3
+ torchvision
4
+ gradio
5
+ Pillow
6
+ numpy
7
+ torchaudio
8
+ opencv-python-headless
systemworkflow.jpeg ADDED
usecasediagram.jpeg ADDED