Spaces:
Running
Running
github-actions[bot] commited on
Commit Β·
26e5b8d
0
Parent(s):
Sync to Hugging Face Space (model and log images excluded)
Browse files- .gitattributes +3 -0
- .github/workflows/sync_to_hf.yml +53 -0
- README.md +73 -0
- app.py +538 -0
- code/infer.py +501 -0
- code/training.ipynb +1132 -0
- data/data_info.txt +70 -0
- logs/training_logs.txt +74 -0
- requirements.txt +8 -0
- systemworkflow.jpeg +0 -0
- usecasediagram.jpeg +0 -0
.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 |
+
[](https://onnx.ai) [](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 |
+

|
| 15 |
+
|
| 16 |
+
### Use Case Diagram
|
| 17 |
+

|
| 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/fXXAAA3NzecPXsW69atg4eHh1LHYQJPREREROJWjj3wUqn0nRP2f6tatSqqVKmCunXrKqx3cXFBRESE0sdhCw0RERER0Qegra2Npk2b4saNGwrrb968CRsbG6WPwwo8EREREYmbGj3IKSsrC7du3ZK/Tk5ORmxsLMzMzFCrVi1MnDgRffv2Rdu2beHl5YXQ0FD89ttvCAsLU/ocrMCrmZSUFEgkEsTGxpb5vZ6enhg3blyZ31faQwaIiIiIREGNHuR0+fJluLu7w93dHQAQFBQEd3d3zJw5EwDQo0cPrFu3DgsXLkSDBg2wceNG7Nu3D61bt1b6HEzgP7CAgABIJBL5Ym5uDh8fH8THxwMAatasibS0NNSvX1/gSKlVI3vsXT4Mt499h9yYVfDzdFXYnhuzqsTl64HtBYpYfHbt3AHfju3Q1L0BBnz2Ka78/98DKhuOo+pwLFWHY6kaHEfx8fT0hEwmK7Zs3bpVvs/gwYORmJiI3NxcxMbGolu3bmU6BxN4Afj4+CAtLQ1paWk4efIkqlSpgi5dugAANDU1YWVlhSpVSu5ukslkKCgo+JDhIj8//4OeT13o60px5ebfGBe8u8Ttth2mKCxDZ/2MoqIiHDgZ+2EDFanQI4exeGEwho0chV2/HoCTkzNGDAtEZmam0KGJCsdRdTiWqsOxVA2OYxlIJOW3qCEm8AKQSqWwsrKClZUV3Nzc8M033+Du3bt4+PBhsRaasLAwSCQSHDlyBI0bN4ZUKkVERASys7MxcOBAGBgYwNraGkuWLFHq3ImJiWjbti10dHRQt25dHD9+XGH7m/Pv3r0bHh4e0NHRwY4dO1BUVIS5c+fio48+glQqhZubG0JDQ4u9b9euXWjZsiV0dHRQv379Ms1pqm6ORV7DnDW/I+R0ydWOB5kvFBY/zwY4cykRKX/zB6sytm/bgp69+6B7j16wd3DA9FlzoKOjg4P79wkdmqhwHFWHY6k6HEvV4DhSaZjACywrKws///wzHBwcYG5uXup+33zzDRYsWICEhAS4urpi4sSJOHPmDA4dOoRjx44hLCwM0dHRbz1XUVERevbsCW1tbVy4cAHr1q3D5MmTSz3f2LFjkZCQAG9vb6xYsQJLlizB4sWLER8fD29vb3Tt2hWJiYkK75s4cSLGjx+PmJgYtGjRAn5+fpWiUmBpZgif1vWx7eA5oUMRhfxXr5Bw7Sqat2gpX6ehoYHmzVsiPi5GwMjEheOoOhxL1eFYqgbHsYzUqAf+Q+AsNAL4/fffYWBgAADIzs6GtbU1fv/9d2holP4hmTt3Ljp27AjgddK/adMm/Pzzz2jf/nW/9bZt2/DRRx+99bwnTpzA9evXcfToUVSvXh0AMH/+fPj6+hbbd9y4cejZs6f89eLFizF58mR89tlnAIDvv/8ep0+fxvLly7F69Wr5fl999RV69eoFAFi7di1CQ0OxadMmTJo0qcSY8vLyij2OWFZUCImG5luvRd187vcxXuS8xMFTsUKHIgpPnj5BYWFhsS+t5ubmSE6+LVBU4sNxVB2OpepwLFWD40hvo55fKyo4Ly8vxMbGIjY2FhcvXoS3tzd8fX1x586dUt/TpEkT+Z+TkpLw6tUrfPzxx/J1ZmZmcHJykr+eP38+DAwM5EtqaioSEhJQs2ZNefIOAC1atPjP8z1//hz3799Hq1atFPZp1aoVEhISFNb983hVqlRBkyZNiu3zTyU9nrjgQVSp+6urgd2aY/eRy8h79WHvTyAiIiKwB57Kn76+PhwcHODg4ICmTZti48aNyM7Oxo8//vjW95TF8OHD5V8SYmNjFZJ2ZWP8EKZMmYJnz54pLFWqNf4g51aVVu72cKpthS0HzgodimiYmphCU1OzWHtVZmYmqlatKlBU4sNxVB2OpepwLFWD40hvwwReDUgkEmhoaCA3N1ep/e3t7aGlpYULFy7I1z158gQ3b96UvzYzM5N/SXBwcECVKlXg4uKCu3fvIi0tTb7f+fPn//N8RkZGqF69OiIjIxXWR0ZGFnsU8D+PV1BQgKioKLi4uJR6bKlUCiMjI4VFbO0z/t1bIOpaKq7c/FvoUERDS1sbLnXr4cL5/90zUFRUhAsXzsG1obuAkYkLx1F1OJaqw7FUDY5jGbEHnspbXl4e0tPTAbxOvFetWoWsrCz4+fkp9X4DAwMEBgZi4sSJMDc3h6WlJaZNm/bWHnoA6NChA+rUqQN/f38sWrQIz58/x7Rp05Q658SJEzFr1izY29vDzc0NW7ZsQWxsLHbs2KGw3+rVq+Ho6AgXFxcsW7YMT548weDBg5U6h7rR19WGfU0L+WvbGuZwrVMDT57n4G76EwCAob4OenZ0xzdLDwgVpmh94T8IM6ZORr169VG/gSt+3r4Nubm56N6j53+/meQ4jqrDsVQdjqVqcBzLQE1bXcoLE3gBhIaGwtraGgBgaGgIZ2dn/Prrr/D09ERKSopSx1i0aJE86Tc0NMT48ePx7Nmzt75HQ0MDBw4cQGBgIJo1awZbW1usXLkSPj4+/3m+MWPG4NmzZxg/fjwyMjJQt25dhISEwNHRUWG/BQsWYMGCBYiNjYWDgwNCQkJE+6u+RnVtcGzjWPnrhRNe35y7PeQ8hs76GQDwqXdjSCDBntDLgsQoZj6+nfHk8WOsWbUSjx49hJOzC9as3whzkX5ehMJxVB2OpepwLFWD40ilkchkMpnQQZD4paSkoHbt2oiJiYGbm9t7HUvX/SvVBEV4cmmV0CEQEVEloSNgWVi384pyO3bu4bH/vdMHpp6NPUREREREVCK20BARERGRuLEHnqjsbG1twW4sIiIiovLHBJ6IiIiIxE1Np3ssL5XraomIiIiIRI4VeCIiIiISt0pWgWcCT0RERETiVsluYq1cX1eIiIiIiESOFXgiIiIiErdK1kJTua6WiIiIiEjkWIEnIiIiInFjDzwREREREakrVuCJiIiISNzYA09EREREROqKFXgiIiIiErdK1gPPBJ6IiIiIRE1SyRJ4ttAQEREREYkIK/BEREREJGqswBMRERERkdpiBZ6IiIiIxK1yFeBZgSciIiIiEhNW4ImIiIhI1NgDT0REREREaosVeCIiIiIStcpWgWcCT0RERESiVtkSeLbQEBERERGJCCvwRERERCRqrMATEREREZHaYgWeiIiIiMStchXgWYEnIiIiIhITVuCJiIiISNTYA09ERERERGqLFXgiIiIiErXKVoFnAk9q58mlVUKHUGGYdvxW6BAqhLu/TRE6BKJiDHT4TzjRG+qUwIeHh2PRokWIiopCWloaDhw4gO7du5e47/Dhw7F+/XosW7YM48aNU/ocbKEhIiIiIlKR7OxsNGzYEKtXr37rfgcOHMD58+dRvXr1Mp+DX9+JiIiISNTUqQLv6+sLX1/ft+7z999/Y/To0Th69Cg++eSTMp+DCTwRERERUSny8vKQl5ensE4qlUIqlb7T8YqKivDFF19g4sSJqFev3jsdgy00RERERCRukvJbgoODYWxsrLAEBwe/c6jff/89qlSpgjFjxrzzMViBJyIiIiIqxZQpUxAUFKSw7l2r71FRUVixYgWio6Pfq+2HCTwRERERiVp59sC/T7vMv/3555/IyMhArVq15OsKCwsxfvx4LF++HCkpKUodhwk8EREREdEH8MUXX6BDhw4K67y9vfHFF19g0KBBSh+HCTwRERERiZo6zUKTlZWFW7duyV8nJycjNjYWZmZmqFWrFszNzRX219LSgpWVFZycnJQ+BxN4IiIiIhI1dUrgL1++DC8vL/nrN/3z/v7+2Lp1q0rOwQSeiIiIiEhFPD09IZPJlN5f2b73f2ICT0RERETipj4F+A+C88ATEREREYkIK/BEREREJGrq1AP/IbACT0REREQkIqzAExEREZGosQJPRERERERqixV4IiIiIhK1ylaBZwJPRERERKJW2RJ4ttAQEREREYkIK/BEREREJG6VqwDPCjwRERERkZiwAk9EREREosYeeCIiIiIiUluswBMRERGRqLECT0REREREaosVeCIiIiISNVbgiYiIiIhIbbECT0RERETiVrkK8EzgiYiIiEjc2EJDRERERERqixV4IiIiIhI1VuCp0pBIJDh48KDQYRARERFRGTCBr8DS09MxevRo2NnZQSqVombNmvDz88PJkyeFDk3Udu3cAd+O7dDUvQEGfPYprsTHCx2S2mvlWgt7v+uL27+OQ+7pGfBr5VRsH6daVfHrvL5I/20iHh2ejIi1gahpaSRAtOISG30Zk8aNRFdvT7RqXA/hp/n3+11xLFWLPytVg+OoHIlEUm6LOmICX0GlpKSgcePGOHXqFBYtWoQrV64gNDQUXl5eGDVqlNDhiVbokcNYvDAYw0aOwq5fD8DJyRkjhgUiMzNT6NDUmr6OFq4kPcC4FUdK3F67uilOrvTHzbuP4P31djQdsgHB2//Ey1cFHzhS8cnNzYVDHSeMnzxd6FBEj2OpOvxZqRocRyoNE/gKauTIkZBIJLh48SJ69eqFOnXqoF69eggKCsL58+dLfM/du3fRp08fmJiYwMzMDN26dUNKSop8+6VLl9CxY0dUrVoVxsbG8PDwQHR0tMIxJBIJNm7ciB49ekBPTw+Ojo4ICQkpz0v9oLZv24Kevfuge49esHdwwPRZc6Cjo4OD+/cJHZpaO3YxCXM2hyEk4kaJ2+cEeuHohVuYtv4k4m6lI/n+E/xx9iYePs35wJGKT4tWbTB05Fh4tOsgdCiix7FUHf6sVA2Oo/JYgSfRe/z4MUJDQzFq1Cjo6+sX225iYlJsXX5+Pry9vWFoaIg///wTkZGRMDAwgI+PD169egUAePHiBfz9/REREYHz58/D0dERnTt3xosXLxSONWfOHPTp0wfx8fHo3LkzBgwYgMePH5fLtX5I+a9eIeHaVTRv0VK+TkNDA82bt0R8XIyAkYmbRAL4NHdA4r3HCFnYH3f2ByF8zeAS22yISP3xZ6VqcBzpbZjAV0C3bt2CTCaDs7Oz0u/ZvXs3ioqKsHHjRjRo0AAuLi7YsmULUlNTERYWBgBo164dPv/8czg7O8PFxQUbNmxATk4Ozpw5o3CsgIAA9OvXDw4ODpg/fz6ysrJw8eLFEs+bl5eH58+fKyx5eXnvfO3l6cnTJygsLIS5ubnCenNzczx69EigqMTP0kQfhnpSTOjXEscvJsFv4g6E/Hkdu+Z+itYNawkdHhGVEX9WqgbHsYwk5bioISbwFZBMJivze+Li4nDr1i0YGhrCwMAABgYGMDMzw8uXL5GUlAQAePDgAb788ks4OjrC2NgYRkZGyMrKQmpqqsKxXF1d5X/W19eHkZERMjIySjxvcHAwjI2NFZZF3weXOX4SLw2N1z8dfz97Ez/svYD4pAdY/MtZHD6XiC/9GgscHRERiUFla6HhPPAVkKOjIyQSCa5fv670e7KystC4cWPs2LGj2DYLCwsAgL+/PzIzM7FixQrY2NhAKpWiRYsW8habN7S0tBReSyQSFBUVlXjeKVOmICgoSGGdTFOqdNwfkqmJKTQ1NYvdPJSZmYmqVasKFJX4PXqWg/yCQiSkPFRYfyP1EVo2qClQVET0rvizUjU4jvQ2rMBXQGZmZvD29sbq1auRnZ1dbPvTp0+LrWvUqBESExNhaWkJBwcHhcXY2BgAEBkZiTFjxqBz586oV68epFLpe/8aTyqVwsjISGGRStUzgdfS1oZL3Xq4cP6cfF1RUREuXDgH14buAkYmbvkFRYi6fh91air+mtjxIzOkPngmUFRE9K74s1I1OI5lU9kq8EzgK6jVq1ejsLAQzZo1w759+5CYmIiEhASsXLkSLVq0KLb/gAEDULVqVXTr1g1//vknkpOTERYWhjFjxuDevXsAXlf2t2/fjoSEBFy4cAEDBgyArq7uh740QX3hPwj79+5ByMEDuJ2UhHlzZyM3Nxfde/QUOjS1pq+jBVf7anC1rwYAsLU2gat9Nfk878t2n0Nvr3oY9Ik77KqbYnj3Jujcsg42HLwsZNiikJOTjZs3EnDzRgIA4P79e7h5IwHpafcFjkx8OJaqw5+VqsFxpNKwhaaCsrOzQ3R0NL777juMHz8eaWlpsLCwQOPGjbF27dpi++vp6SE8PByTJ09Gz5498eLFC9SoUQPt27eHkdHrJGvTpk0YOnQoGjVqhJo1a2L+/PmYMGHCh740Qfn4dsaTx4+xZtVKPHr0EE7OLlizfiPM+evMt2rkVB3Hlg+Uv144qhMAYHtoHIZ+H4KQiBsYvewPTOzfCktGe+Pm3Uz0m/Urzv51V6iQReP6tasYPWyQ/PUPSxcCAHy7dMP0OfOFCkuUOJaqw5+VqsFxVJ6aFsrLjUT2Lnc8EpWjl3x2j8qYdvxW6BAqhLu/TRE6BKJiDHRYgyP1IuRH0mFCyQ8KVIVbi33L7djvin/7iYiIiEjU1LVXvbywB56IiIiISERYgSciIiIiUatkBXgm8EREREQkbmyhISIiIiIitcUKPBERERGJWiUrwLMCT0REREQkJqzAExEREZGoaWhUrhI8K/BERERERCLCCjwRERERiRp74ImIiIiISG0xgSciIiIiUZNIJOW2lFV4eDj8/PxQvXp1SCQSHDx4UL4tPz8fkydPRoMGDaCvr4/q1atj4MCBuH//fpnOwQSeiIiIiERNIim/payys7PRsGFDrF69uti2nJwcREdHY8aMGYiOjsb+/ftx48YNdO3atUznYA88EREREZGK+Pr6wtfXt8RtxsbGOH78uMK6VatWoVmzZkhNTUWtWrWUOgcTeCIiIiIStXdpdVFWXl4e8vLyFNZJpVJIpVKVHP/Zs2eQSCQwMTFR+j1soSEiIiIiKkVwcDCMjY0VluDgYJUc++XLl5g8eTL69esHIyMjpd/HCjwRERERiVp5VuCnTJmCoKAghXWqqL7n5+ejT58+kMlkWLt2bZneywSeiIiIiKgUqmyXeeNN8n7nzh2cOnWqTNV3gAk8EREREYmcmB7k9CZ5T0xMxOnTp2Fubl7mYzCBJyIiIiJSkaysLNy6dUv+Ojk5GbGxsTAzM4O1tTV69+6N6Oho/P777ygsLER6ejoAwMzMDNra2kqdgwk8EREREYlaefbAl9Xly5fh5eUlf/2mf97f3x+zZ89GSEgIAMDNzU3hfadPn4anp6dS52ACT0RERESipkb5Ozw9PSGTyUrd/rZtyuI0kkREREREIsIKPBERERGJmjq10HwIrMATEREREYkIK/BEREREJGqVrADPCjwRERERkZiwAk9EREREosYeeCIiIiIiUluswBMRERGRqFWyAjwTeCIiIiISN7bQEBERERGR2mIFnoiIiIhErZIV4JnAk/rJelkgdAgVRsLeiUKHUCE0nnpE6BAqjBtL/YQOgYhI9JjAExEREZGosQeeiIiIiIjUFivwRERERCRqlawAzwo8EREREZGYsAJPRERERKJW2XrgmcATERERkahVsvydLTRERERERGLCCjwRERERiVpla6FhBZ6IiIiISERYgSciIiIiUWMFnoiIiIiI1BYr8EREREQkapWsAM8KPBERERGRmLACT0RERESiVtl64JnAExEREZGoVbL8nS00RERERERiwgo8EREREYlaZWuhYQWeiIiIiEhEWIEnIiIiIlGrZAV4VuCJiIiIiMSEFXgiIiIiEjWNSlaCZwWeiIiIiEhEWIEnIiIiIlGrZAV4VuCJiIiIiMSEFXgiIiIiErXKNg88E3giIiIiEjWNypW/s4WGiIiIiEhMWIEnIiIiIlGrbC00rMATEREREYkIK/BEREREJGqVrADPCjwRERERkZgwgSciIiIiUZOU439lFR4eDj8/P1SvXh0SiQQHDx5U2C6TyTBz5kxYW1tDV1cXHTp0QGJiYpnOwQS+ggkICIBEIoFEIoGWlhaqVauGjh07YvPmzSgqKlLJOVJSUiCRSBAbG6uS44lJbPRlTBo3El29PdGqcT2Enz4pdEiitOunTRg9uD+6d2iBPp09MXvyONy9kyJ0WKLQzN4Mm4Y2xcVvO+LOSj90amClsH2cbx2cnOaFhEW+iF/gjR2jmsPNxkSYYEVo184d8O3YDk3dG2DAZ5/iSny80CGJFsdSNTiO4pOdnY2GDRti9erVJW5fuHAhVq5ciXXr1uHChQvQ19eHt7c3Xr58qfQ5mMBXQD4+PkhLS0NKSgqOHDkCLy8vjB07Fl26dEFBQYHQ4Ylabm4uHOo4Yfzk6UKHImrxMZfh16svlm/YjuAV61FYUICp44bjZW6O0KGpPT3tKkj4+zlm/HqlxO3JGdmY+esVdFpwBr2WR+Le4xxsH9kcZgbaHzhS8Qk9chiLFwZj2MhR2PXrATg5OWPEsEBkZmYKHZrocCxVg+OoPA1J+S1l5evri3nz5qFHjx7FtslkMixfvhzTp09Ht27d4Orqip9++gn3798vVql/6/WWPSxSd1KpFFZWVqhRowYaNWqEqVOn4tChQzhy5Ai2bt1aYgX96dOnkEgkCAsLAwA8efIEAwYMgIWFBXR1deHo6IgtW7YAAGrXrg0AcHd3h0QigaenJ8LDw6GlpYX09HSFWMaNG4c2bdp8kOv+EFq0aoOhI8fCo10HoUMRtfnL1qLTJ91ga+cAe0cnjJ8+FxkP0pB4PUHo0NReWEIGFv9xA0fj00vcfijqb0TefIS7mTlITM/CtweuwUhXCy7VjT5wpOKzfdsW9OzdB9179IK9gwOmz5oDHR0dHNy/T+jQRIdjqRocR+W96T4ojyUvLw/Pnz9XWPLy8t4pzuTkZKSnp6NDh//lEcbGxvj4449x7tw5pY/DBL6SaNeuHRo2bIj9+/crtf+MGTNw7do1HDlyBAkJCVi7di2qVq0KALh48SIA4MSJE0hLS8P+/fvRtm1b2NnZYfv27fJj5OfnY8eOHRg8eLDqL4gqlOzsLACAoRGTTFXS0pSgf8taeJaTj2t/Pxc6HLWW/+oVEq5dRfMWLeXrNDQ00Lx5S8THxQgYmfhwLFWD46g+goODYWxsrLAEBwe/07HeFDqrVaumsL5atWrFiqBvw2kkKxFnZ2fEK9k7l5qaCnd3dzRp0gQAYGtrK99mYWEBADA3N4eV1f/6bwMDA7FlyxZMnDgRAPDbb7/h5cuX6NOnT6nnycvLK/YtNi9fE1KpVKk4SfyKioqwbvlC1HN1g629o9DhVAjt6lliVUBj6GppIuP5S3y+5hyeZL8SOiy19uTpExQWFsLc3Fxhvbm5OZKTbwsUlThxLFWD41g25TmN5JQpUxAUFKSwTug8hRX4SkQmkyn9pLIRI0Zg165dcHNzw6RJk3D27Nn/fE9AQABu3bqF8+fPAwC2bt2KPn36QF9fv9T3lPStdsWS75W7IKoQVi2Zjzu3kzBl7kKhQ6kwziVmwvf7M+i5PAJnEh5izaAmMGcPPBHRO5FKpTAyMlJY3jWBf1P4fPDggcL6Bw8eKBRF/wsT+EokISEBtWvXhobG6//tMplMvi0/P19hX19fX9y5cwdff/017t+/j/bt22PChAlvPb6lpSX8/PywZcsWPHjwAEeOHPnP9pkpU6bg2bNnCsvY8ZPf8QpJbFYtmY8LkeFYuOpHWFhW++83kFJyXxXizqMcxKQ8xaRf4lBQWIS+LWoJHZZaMzUxhaamZrGbAzMzM+Xtg6QcjqVqcBzLRkMiKbdFlWrXrg0rKyucPPm/WeyeP3+OCxcuoEWLFspfr0qjIrV16tQpXLlyBb169ZK3wKSlpcm3lzQlpIWFBfz9/fHzzz9j+fLl2LBhAwBAW/t1Ja+wsLDYe4YMGYLdu3djw4YNsLe3R6tWrd4alyq/1ZJ4yGQyrFoyH2fPnMLCH36EVfWPhA6pQtPQkEC7Cn/cv42WtjZc6tbDhfP/u4msqKgIFy6cg2tDdwEjEx+OpWpwHMUrKysLsbGx8twqOTkZsbGxSE1NhUQiwbhx4zBv3jyEhITgypUrGDhwIKpXr47u3bsrfQ72wFdAeXl5SE9PR2FhIR48eIDQ0FAEBwejS5cuGDhwIDQ1NdG8eXMsWLAAtWvXRkZGBqZPV5wWcebMmWjcuDHq1auHvLw8/P7773BxcQHwutKuq6uL0NBQfPTRR9DR0YGxsTEAwNvbG0ZGRpg3bx7mzp37wa+9vOXkZOPe3VT56/v37+HmjQQYGRnDyrq6gJGJy6rF83H6+BHM/n45dPX08TjzEQBA38AAUqmOwNGpNz1tTdha/K8traa5HurWMMLTnHw8yX6Frzo54sRf6ch4lgdTA234t7FFNWMd/BFzX8CoxeEL/0GYMXUy6tWrj/oNXPHz9m3Izc1F9x49hQ5NdDiWqsFxVF559sCX1eXLl+Hl5SV//aZ/3t/fH1u3bsWkSZOQnZ2NoUOH4unTp2jdujVCQ0Oho6P8v39M4Cug0NBQWFtbo0qVKjA1NUXDhg2xcuVK+Pv7y9tnNm/ejMDAQDRu3BhOTk5YuHAhOnXqJD+GtrY2pkyZgpSUFOjq6qJNmzbYtWsXAKBKlSpYuXIl5s6di5kzZ6JNmzby6Sc1NDQQEBCA+fPnY+DAgR/82svb9WtXMXrYIPnrH5a+7tv27dIN0+fMFyos0fn9wB4AwMRRgQrrx0+bi06fdBMiJNFwrWWC3WP+NyvFzJ71AAC/XriLabvj4VDNAL2bNYGpgTaeZucjLvUpPl0RicT0LKFCFg0f38548vgx1qxaiUePHsLJ2QVr1m+EOdsVyoxjqRocR3Hy9PRUaFP+N4lEgrlz575XoVMie9sZiN5BYGAgHj58iJCQkHd6/6MsPmxKVbLyOJaq4DHnuNAhVBg3lvoJHQIRlRMdAcvCvbdEl9ux9w5qVG7HfleswJPKPHv2DFeuXMHOnTvfOXknIiIiKit1aqH5EJjAk8p069YNFy9exPDhw9GxY0ehwyEiIiKqkJjAk8q86YMnIiIi+pBUPd2juuO8YkREREREIsIKPBERERGJWuWqv7MCT0REREQkKqzAExEREZGoSdgDT0RERERE6ooVeCIiIiISNY3KVYBnAk9ERERE4sYWGiIiIiIiUluswBMRERGRqFWyAjwr8EREREREYsIKPBERERGJWmXrgVcqgQ8JCVH6gF27dn3nYIiIiIiI6O2USuC7d++u1MEkEgkKCwvfJx4iIiIiojLhNJIlKCoqKu84iIiIiIhICeyBJyIiIiJRYw+8ErKzs3HmzBmkpqbi1atXCtvGjBmjksCIiIiIiJRRudL3d0jgY2Ji0LlzZ+Tk5CA7OxtmZmZ49OgR9PT0YGlpyQSeiIiIiKgclXke+K+//hp+fn548uQJdHV1cf78edy5cweNGzfG4sWLyyNGIiIiIqJSaUgk5baoozIn8LGxsRg/fjw0NDSgqamJvLw81KxZEwsXLsTUqVPLI0YiIiIiIvp/ZU7gtbS0oKHx+m2WlpZITU0FABgbG+Pu3buqjY6IiIiI6D9IJOW3qKMy98C7u7vj0qVLcHR0hIeHB2bOnIlHjx5h+/btqF+/fnnESERERERE/6/MFfj58+fD2toaAPDdd9/B1NQUI0aMwMOHD7FhwwaVB0hERERE9DYSiaTcFnVU5gp8kyZN5H+2tLREaGioSgMiIiIiIqLS8UFORERERCRqalooLzdlTuBr16791l8n3L59+70CIiIiIiIqC3Wd7rG8lDmBHzdunMLr/Px8xMTEIDQ0FBMnTlRVXEREREREVIIyJ/Bjx44tcf3q1atx+fLl9w6IiIiIiKgsKlkBvuyz0JTG19cX+/btU9XhiIiIiIioBCq7iXXv3r0wMzNT1eGIiIiIiJSirtM9lpd3epDTPwdJJpMhPT0dDx8+xJo1a1QaHBERERERKSpzAt+tWzeFBF5DQwMWFhbw9PSEs7OzSoOjyslAh7Obknq5sdRP6BAqjGZzTwgdQoVxcWYHoUMgUhsq6wkXiTJnSrNnzy6HMIiIiIiISBll/sKiqamJjIyMYuszMzOhqampkqCIiIiIiJQlkUjKbVFHZa7Ay2SyEtfn5eVBW1v7vQMiIiIiIioLDfXMs8uN0gn8ypUrAbz+hrNx40YYGBjItxUWFiI8PJw98ERERERE5UzpBH7ZsmUAXlfg161bp9Auo62tDVtbW6xbt071ERIRERERvQUr8KVITk4GAHh5eWH//v0wNTUtt6CIiIiIiKhkZe6BP336dHnEQURERET0TtT1ZtPyUuZZaHr16oXvv/++2PqFCxfi008/VUlQRERERERUsjIn8OHh4ejcuXOx9b6+vggPD1dJUEREREREytKQlN9SFoWFhZgxYwZq164NXV1d2Nvb49tvvy11Fsd3VeYWmqysrBKni9TS0sLz589VEhQRERERkdh8//33WLt2LbZt24Z69erh8uXLGDRoEIyNjTFmzBiVnafMFfgGDRpg9+7dxdbv2rULdevWVUlQRERERETKkkjKbymLs2fPolu3bvjkk09ga2uL3r17o1OnTrh48aJKr7fMFfgZM2agZ8+eSEpKQrt27QAAJ0+exM6dO7F3716VBkdERERE9F80yvEm1ry8POTl5Smsk0qlkEqlxfZt2bIlNmzYgJs3b6JOnTqIi4tDREQEli5dqtKYylyB9/Pzw8GDB3Hr1i2MHDkS48ePx99//41Tp07BwcFBpcEREREREQkpODgYxsbGCktwcHCJ+37zzTf47LPP4OzsDC0tLbi7u2PcuHEYMGCASmMqcwUeAD755BN88sknAIDnz5/jl19+wYQJExAVFYXCwkKVBkhERERE9DZlrkiXwZQpUxAUFKSwrqTqOwDs2bMHO3bswM6dO1GvXj3ExsZi3LhxqF69Ovz9/VUW0zsl8MDr2Wg2bdqEffv2oXr16ujZsydWr16tssCIiIiIiIRWWrtMSSZOnCivwgOv7x29c+cOgoODhUvg09PTsXXrVmzatAnPnz9Hnz59kJeXh4MHD/IGViIiIiIShLo8xyknJwcaGoq/D9DU1ERRUZFKz6P0bxz8/Pzg5OSE+Ph4LF++HPfv38cPP/yg0mCIiIiIiMTKz88P3333Hf744w+kpKTgwIEDWLp0KXr06KHS8yhdgT9y5AjGjBmDESNGwNHRUaVBEBERERG9q/KchaYsfvjhB8yYMQMjR45ERkYGqlevjmHDhmHmzJkqPY/SFfiIiAi8ePECjRs3xscff4xVq1bh0aNHKg2GiIiIiEisDA0NsXz5cty5cwe5ublISkrCvHnzSnwI6vtQOoFv3rw5fvzxR6SlpWHYsGHYtWsXqlevjqKiIhw/fhwvXrxQaWBERERERMpQlwc5fShlnnVHX18fgwcPRkREBK5cuYLx48djwYIFsLS0RNeuXcsjRiIiIiIi+n/vNW2mk5MTFi5ciHv37uGXX35RVUxERERERErTkJTfoo7eeR74f9LU1ET37t3RvXt3VRyOiIiIiEhp6nIT64dSng+uIiIiIiIiFVNJBZ6IiIiISCiVrADPCjwRERERkZiwAk9EREREoqauN5uWF1bgiYiIiIhEhBV4IiIiIhI1CSpXCZ4VeCIiIiIiEWEFnoiIiIhEjT3wakQikeDgwYOCnNvT0xPjxo1T2fECAgLe6UFXtra2WL58ucriICIiIqpoKtuTWAVN4AMCAiCRSIotPj4+5XK+snwh2L9/P7799ttyiYPEbdfOHfDt2A5N3RtgwGef4kp8vNAhiU5s9GVMGjcSXb090apxPYSfPil0SKLGz2TZNbYxwQ8DGuLEhDaIn9sBXs4W8m1VNCQY19EB+0Y1x4XpXjgxoQ2+61kPFobaAkYsPvxcqgbHkUoieAXex8cHaWlpCssvv/wiWDyvXr0CAJiZmcHQ0FCwOEg9hR45jMULgzFs5Cjs+vUAnJycMWJYIDIzM4UOTVRyc3PhUMcJ4ydPFzoU0eNn8t3oamviRnoW5v9xvdg2HS0NuFQ3xPqw2+i79gKCdsXBtqoeVvZ3+/CBihQ/l6rBcVReSQVhVS3qSPAEXiqVwsrKSmExNTUtcd+7d++iT58+MDExgZmZGbp164aUlBSFfTZv3ox69epBKpXC2toaX331FYDXrSgA0KNHD0gkEvnr2bNnw83NDRs3bkTt2rWho6MDoHgLTV5eHiZPnoyaNWtCKpXCwcEBmzZtAgAUFhYiMDAQtWvXhq6uLpycnLBixYoyj0VGRgb8/Pygq6uL2rVrY8eOHcX2Wbp0KRo0aAB9fX3UrFkTI0eORFZWlnz71q1bYWJigt9//x1OTk7Q09ND7969kZOTg23btsHW1hampqYYM2YMCgsL5e/bvn07mjRpAkNDQ1hZWaF///7IyMhQOHdISAgcHR2ho6MDLy8vbNu2DRKJBE+fPpXvExERgTZt2kBXVxc1a9bEmDFjkJ2dXeaxUFfbt21Bz9590L1HL9g7OGD6rDnQ0dHBwf37hA5NVFq0aoOhI8fCo10HoUMRPX4m301EYiZWnUzCqYSHxbZl5RVi2LYYHLuagZTMHMTfe475v99AvRpGsDKWChCt+PBzqRocRyqN4Am8svLz8+Ht7Q1DQ0P8+eefiIyMhIGBAXx8fORV87Vr12LUqFEYOnQorly5gpCQEDg4OAAALl26BADYsmUL0tLS5K8B4NatW9i3bx/279+P2NjYEs8/cOBA/PLLL1i5ciUSEhKwfv16GBgYAACKiorw0Ucf4ddff8W1a9cwc+ZMTJ06FXv27CnTNQYEBODu3bs4ffo09u7dizVr1hRLojU0NLBy5UpcvXoV27Ztw6lTpzBp0iSFfXJycrBy5Urs2rULoaGhCAsLQ48ePXD48GEcPnwY27dvx/r167F3716F8f32228RFxeHgwcPIiUlBQEBAfLtycnJ6N27N7p37464uDgMGzYM06ZNUzhvUlISfHx80KtXL8THx2P37t2IiIiQf4kSu/xXr5Bw7Sqat2gpX6ehoYHmzVsiPi5GwMiosuJn8sMx0KmCoiIZXrwsEDoUtcfPpWpwHMumsvXACz4Lze+//y5PhN+YOnUqpk6dqrBu9+7dKCoqwsaNG+W/ztiyZQtMTEwQFhaGTp06Yd68eRg/fjzGjh0rf1/Tpk0BABYWr/sbTUxMYGVlpXDsV69e4aeffpLv8283b97Enj17cPz4cXTo8LpiaGdnJ9+upaWFOXPmyF/Xrl0b586dw549e9CnTx+lxuHmzZs4cuQILl68KI9506ZNcHFxUdjvn78VsLW1xbx58zB8+HCsWbNGvj4/Px9r166Fvb09AKB3797Yvn07Hjx4AAMDA9StWxdeXl44ffo0+vbtCwAYPHiw/P12dnZYuXIlmjZtiqysLBgYGGD9+vVwcnLCokWLAABOTk7466+/8N1338nfFxwcjAEDBshjdHR0xMqVK+Hh4YG1a9fKf7vxT3l5ecjLy1NYJ9OUQipVvyrXk6dPUFhYCHNzc4X15ubmSE6+LVBUVJnxM/lhaFfRwNedHHDkSjqy8wr/+w2VHD+XqsFxpLcRvALv5eWF2NhYhWX48OHF9ouLi8OtW7dgaGgIAwMDGBgYwMzMDC9fvkRSUhIyMjJw//59tG/fvswx2NjYlJq8A0BsbCw0NTXh4eFR6j6rV69G48aNYWFhAQMDA2zYsAGpqakl7rtjxw75NRgYGODPP/9EQkICqlSpgsaNG8v3c3Z2homJicJ7T5w4gfbt26NGjRowNDTEF198gczMTOTk5Mj30dPTkyfvAFCtWjXY2toqfFGqVq2aQnU/KioKfn5+qFWrFgwNDeXX+uYabty4If9i8UazZs0UXsfFxWHr1q0K1+bt7Y2ioiIkJyeXOBbBwcEwNjZWWBZ9H1zivkREH1oVDQkW92kACYB5vxfvlyci9SCRlN+ijgSvwOvr68vbXN4mKysLjRs3LrEv3MLCAhoa7/5dRF9f/63bdXV137p9165dmDBhApYsWYIWLVrA0NAQixYtwoULF0rcv2vXrvj444/lr2vUqIFjx479Z5wpKSno0qULRowYge+++w5mZmaIiIhAYGAgXr16BT09PQCvfyPwTxKJpMR1RUVFAIDs7Gx4e3vD29sbO3bsgIWFBVJTU+Ht7S1vT1JGVlYWhg0bhjFjxhTbVqtWrRLfM2XKFAQFBSmsk2mqX/UdAExNTKGpqVns5qHMzExUrVpVoKioMuNnsnxV0ZBgUZ8GsDbRwZAt0ay+K4mfS9XgONLbCF6BV1ajRo2QmJgIS0tLODg4KCzGxsYwNDSEra0tTp4sfTo6LS0thRs3ldWgQQMUFRXhzJkzJW6PjIxEy5YtMXLkSLi7u8PBwQFJSUmlHs/Q0FAhfl1dXTg7O6OgoABRUVHy/W7cuKFwg2hUVBSKioqwZMkSNG/eHHXq1MH9+/fLfD3/dv36dWRmZmLBggVo06YNnJ2di/XeOzk54fLlywrr/nkfAfD6/9G1a9eK/f9xcHCAtnbJ069JpVIYGRkpLOrYPgMAWtracKlbDxfOn5OvKyoqwoUL5+Da0F3AyKiy4mey/LxJ3m3M9TB0azSe5eYLHZJo8HOpGhzHstGQSMptUUeCJ/B5eXlIT09XWB49elRsvwEDBqBq1aro1q0b/vzzTyQnJyMsLAxjxozBvXv3ALyeUWbJkiVYuXIlEhMTER0djR9++EF+jDcJfnp6Op48eaJ0jLa2tvD398fgwYNx8OBB+bnf3KTq6OiIy5cv4+jRo7h58yZmzJhRLLn9L05OTvDx8cGwYcNw4cIFREVFYciQIQrVfwcHB+Tn5+OHH37A7du3sX37dqxbt65M5ylJrVq1oK2tLT9uSEhIsTnwhw0bhuvXr2Py5MnyewK2bt0KAPJ7EiZPnoyzZ8/iq6++QmxsLBITE3Ho0KEKcxMrAHzhPwj79+5ByMEDuJ2UhHlzZyM3Nxfde/QUOjRRycnJxs0bCbh5IwEAcP/+Pdy8kYD0tPf/QlrZ8DP5bnS1NeFkZQAnq9ethTVMdeFkZQArYymqaEiwpK8r6tUwwjd7/4KGhgTmBtowN9BGFU31/Mdc3fBzqRocR+XxJtYPLDQ0FNbW1grrnJyccP26Yq+hnp4ewsPDMXnyZPTs2RMvXrxAjRo10L59exgZGQEA/P398fLlSyxbtgwTJkxA1apV0bt3b/kxlixZgqCgIPz444+oUaNGsSko32bt2rWYOnUqRo4ciczMTNSqVUt+o+2wYcMQExODvn37QiKRoF+/fhg5ciSOHDlSprHYsmULhgwZAg8PD1SrVg3z5s3DjBkz5NsbNmyIpUuX4vvvv8eUKVPQtm1bBAcHY+DAgWU6z79ZWFhg69atmDp1KlauXIlGjRph8eLF6Nq1q3yf2rVrY+/evRg/fjxWrFiBFi1aYNq0aRgxYoS8Yu7q6oozZ85g2rRpaNOmDWQyGezt7eU3ylYEPr6d8eTxY6xZtRKPHj2Ek7ML1qzfCHP+OrNMrl+7itHDBslf/7B0IQDAt0s3TJ8zX6iwRImfyXdTr7oRNg/+3z1Hk3zrAAAOxdzH2tO34eXy+r6ovaOaK7xv8OYoXE5RvgBUWfFzqRocRyqNRCaTyYQOgsTpu+++w7p163D37l2VHpeztKlOFgdTJQx0BK91VBjN5p4QOoQK4+JMPkeB1IuQPyp/iCx5sgxVGN2qdrkd+13xXyVS2po1a9C0aVOYm5sjMjISixYtqlDtMURERERiwASelJaYmIh58+bh8ePHqFWrFsaPH48pU6YIHRYRERFVchpQ02b1csIEnpS2bNkyLFu2TOgwiIiIiCo1JvBEREREJGpqOttjuRF8GkkiIiIiIlIeK/BEREREJGrqOl97eWECT0RERESipq5PTC0vbKEhIiIiIhIRVuCJiIiISNQqWQGeFXgiIiIiIjFhBZ6IiIiIRI098EREREREpLZYgSciIiIiUatkBXhW4ImIiIiIxIQVeCIiIiIStcpWkWYCT0RERESiJqlkPTSV7QsLEREREZGosQJPRERERKJWuervrMATEREREYkKK/BEREREJGp8kBMREREREb2Tv//+G59//jnMzc2hq6uLBg0a4PLlyyo9ByvwRERERCRq6lJ/f/LkCVq1agUvLy8cOXIEFhYWSExMhKmpqUrPwwSeiIiIiEgFvv/+e9SsWRNbtmyRr6tdu7bKz8MWGiIiIiISNYmk/Ja8vDw8f/5cYcnLyysxjpCQEDRp0gSffvopLC0t4e7ujh9//FHl18sEnoiIiIhETSKRlNsSHBwMY2NjhSU4OLjEOG7fvo21a9fC0dERR48exYgRIzBmzBhs27ZNtdcrk8lkKj0i0Xt6WSB0BBVHFgdTJQx02G2oKs3mnhA6hArj4swOQodApEDIH5W/xPxdbsfuWbdqsYq7VCqFVCottq+2tjaaNGmCs2fPyteNGTMGly5dwrlz51QWE/9VIiIiIiJRK8+WktKS9ZJYW1ujbt26CutcXFywb98+lcbEFhoiIiIiIhVo1aoVbty4obDu5s2bsLGxUel5WIEnIiIiIlGTqMmDnL7++mu0bNkS8+fPR58+fXDx4kVs2LABGzZsUOl5WIEnIiIiIlKBpk2b4sCBA/jll19Qv359fPvtt1i+fDkGDBig0vOwAk9EREREoqYe9ffXunTpgi5dupTrOViBJyIiIiISEVbgiYiIiEjU1KUH/kNhAk9UgXH+clI3nLtcdUx7q/amuMrqyd6hQodAKlDZWkoq2/USEREREYkay3NEREREJGqVrYWGFXgiIiIiIhFhBZ6IiIiIRK1y1d9ZgSciIiIiEhVW4ImIiIhI1CpZCzwr8EREREREYsIKPBERERGJmkYl64JnAk9EREREosYWGiIiIiIiUluswBMRERGRqEkqWQsNK/BERERERCLCCjwRERERiRp74ImIiIiISG2xAk9EREREolbZppFkBZ6IiIiISERYgSciIiIiUatsPfBM4ImIiIhI1CpbAs8WGiIiIiIiEWEFnoiIiIhEjQ9yIiIiIiIitcUKPBERERGJmkblKsCzAk9EREREJCaswBMRERGRqLEHnoiIiIiI1BYr8EREREQkapwHnoiIiIiI1BYr8EREREQkapWtB54JPBERERGJGqeRJCIiIiIitcUKPBERERGJWmVroRFdBX7r1q0wMTEpt+OHhYVBIpHg6dOnKjleSkoKJBIJYmNjVXI8IiIiIqrc1DKBDwgIgEQigUQigba2NhwcHDB37lwUFBSU+7lbtmyJtLQ0GBsbl/u53njzpUEikUBDQwPGxsZwd3fHpEmTkJaW9sHiIOXs2rkDvh3boal7Awz47FNciY8XOiTR4liqBsdRdTiWZdeqrhX2TvPG7c0DkHtwKPw+tlHYvmGMB3IPDlVYDs30FSha8eFnUjkSSfkt6kgtE3gA8PHxQVpaGhITEzF+/HjMnj0bixYtKvfzamtrw8rKChIB/o/duHED9+/fx6VLlzB58mScOHEC9evXx5UrVz54LFSy0COHsXhhMIaNHIVdvx6Ak5MzRgwLRGZmptChiQ7HUjU4jqrDsXw3+jpauJKciXHrI0vd52hUKmwDtssX/yUnP2CE4sXPJJVGbRN4qVQKKysr2NjYYMSIEejQoQNCQkLk248ePQoXFxcYGBjIk30ACA8Ph5aWFtLT0xWON27cOLRp0wYAcOfOHfj5+cHU1BT6+vqoV68eDh8+DKDkFprIyEh4enpCT08Ppqam8Pb2xpMnTwAAoaGhaN26NUxMTGBubo4uXbogKSnpna7Z0tISVlZWqFOnDj777DNERkbCwsICI0aMkO9z6dIldOzYEVWrVoWxsTE8PDwQHR2tcByJRIL169ejS5cu0NPTg4uLC86dO4dbt27B09MT+vr6aNmypUKcSUlJ6NatG6pVqwYDAwM0bdoUJ06cUDhuWloaPvnkE+jq6qJ27drYuXMnbG1tsXz5cvk+T58+xZAhQ2BhYQEjIyO0a9cOcXFx7zQe6mj7ti3o2bsPuvfoBXsHB0yfNQc6Ojo4uH+f0KGJDsdSNTiOqsOxfDfHou9izs7LCLmQUuo+rwqK8OBprnx5mv3qwwUoYvxMKk9Sjos6UtsE/t90dXXx6tXrv/A5OTlYvHgxtm/fjvDwcKSmpmLChAkAgLZt28LOzg7bt2+Xvzc/Px87duzA4MGDAQCjRo1CXl4ewsPDceXKFXz//fcwMDAo8byxsbFo37496tati3PnziEiIgJ+fn4oLCwEAGRnZyMoKAiXL1/GyZMnoaGhgR49eqCoqEgl1zx8+HBERkYiIyMDAPDixQv4+/sjIiIC58+fh6OjIzp37owXL14ovPfbb7/FwIEDERsbC2dnZ/Tv3x/Dhg3DlClTcPnyZchkMnz11Vfy/bOystC5c2ecPHkSMTEx8PHxgZ+fH1JTU+X7DBw4EPfv30dYWBj27duHDRs2yON649NPP0VGRgaOHDmCqKgoNGrUCO3bt8fjx4/fezyElv/qFRKuXUXzFi3l6zQ0NNC8eUvEx8UIGJn4cCxVg+OoOhzL8tWmvjXubP0Ccav7YMWw1jAzlAodktrjZ5LeRu1noZHJZDh58iSOHj2K0aNHA3idkK9btw729vYAgK+++gpz586VvycwMBBbtmzBxIkTAQC//fYbXr58iT59+gAAUlNT0atXLzRo0AAAYGdnV+r5Fy5ciCZNmmDNmjXydfXq1ZP/uVevXgr7b968GRYWFrh27Rrq16//PpcOAHB2dgbw+mZYS0tLtGvXTmH7hg0bYGJigjNnzqBLly7y9YMGDZJf7+TJk9GiRQvMmDED3t7eAICxY8di0KBB8v0bNmyIhg0byl9/++23OHDgAEJCQvDVV1/h+vXrOHHiBC5duoQmTZoAADZu3AhHR0f5eyIiInDx4kVkZGRAKn39w3nx4sU4ePAg9u7di6FDhxa7vry8POTl5Smsk2lK5e9XJ0+ePkFhYSHMzc0V1pubmyM5+bZAUYkTx1I1OI6qw7EsP8ej7+HQuRSkZDyHnZUR5nzeDIdm+MLjm0MoKpIJHZ7a4meybDTUtVm9nKhtBf7333+HgYEBdHR04Ovri759+2L27NkAAD09PXnyDgDW1tYKleCAgADcunUL58+fB/B65po+ffpAX18fADBmzBjMmzcPrVq1wqxZsxD/lhtC3lTgS5OYmIh+/frBzs4ORkZGsLW1BQCFyvU/1atXDwYGBjAwMICv73/fxCOTvf7h9qYn/8GDB/jyyy/h6OgIY2NjGBkZISsrq9j5XF1d5X+uVq0aAMi/sLxZ9/LlSzx//hzA6wr8hAkT4OLiAhMTExgYGCAhIUF+3Bs3bqBKlSpo1KiR/BgODg4wNTWVv46Li0NWVhbMzc3l12hgYIDk5ORS24qCg4NhbGyssCz6Pvg/x4WIiMTh14gk/HHpDq7eeYLfLtxBz3mhaFLHEm3rWwsdGlUgla2FRm0r8F5eXli7di20tbVRvXp1VKnyv1C1tLQU9pVIJPJEF3jdS+7n54ctW7agdu3aOHLkCMLCwuTbhwwZAm9vb/zxxx84duwYgoODsWTJEnmF/590dXXfGqefnx9sbGzw448/onr16igqKkL9+vXl7T7/dvjwYeTn5yt1bABISEgAAPkXA39/f2RmZmLFihWwsbGBVCpFixYtip3vn2P0Jvkvad2bVp8JEybg+PHjWLx4MRwcHKCrq4vevXuXeh0lycrKgrW1tcJYv1Ha1J9TpkxBUFCQwjqZpvpV3wHA1MQUmpqaxW4eyszMRNWqVQWKSpw4lqrBcVQdjuWHk/LgBR4+y4W9lTHC4u8LHY7a4meS3kZtK/D6+vpwcHBArVq1FJJ3ZQ0ZMgS7d+/Ghg0bYG9vj1atWilsr1mzJoYPH479+/dj/Pjx+PHHH0s8jqurK06eLPlu+czMTNy4cQPTp09H+/bt4eLiIr+5tTQ2NjZwcHCAg4MDatSo8dZ9c3NzsWHDBrRt2xYWFhYAXt9QO2bMGHTu3Bn16tWDVCrFo0eP3nocZURGRiIgIAA9evRAgwYNYGVlhZSUFPl2JycnFBQUICbmf313t27dUrjeRo0aIT09HVWqVJFf45ultB82UqkURkZGCos6ts8AgJa2Nlzq1sOF8+fk64qKinDhwjm4NnQXMDLx4ViqBsdRdTiWH04Nc32YG+og/UmO0KGoNX4my6iSleDVNoF/X97e3jAyMsK8efMUer2B1zPSHD16FMnJyYiOjsbp06fh4uJS4nGmTJmCS5cuYeTIkYiPj8f169exdu1aPHr0CKampjA3N8eGDRtw69YtnDp1qlg1uSwyMjKQnp6OxMRE7Nq1C61atcKjR4+wdu1a+T6Ojo7Yvn07EhIScOHCBQwYMECpSv5/cXR0xP79+xEbG4u4uDj0799f4UZcZ2dndOjQAUOHDsXFixcRExODoUOHQldXV17N79ChA1q0aIHu3bvj2LFjSElJwdmzZzFt2jRcvnz5vWNUB1/4D8L+vXsQcvAAbiclYd7c2cjNzUX3Hj2FDk10OJaqwXFUHY7lu9HXqQLX2uZwrf26V9vW0giutc1Rs6o+9HWqYL7/x2hWxxK1LA3g6Vode6Z2QlLaMxyPuStw5OqPn0nxW7BgASQSCcaNG6fS46ptC8370tDQQEBAAObPn4+BAwcqbCssLMSoUaNw7949GBkZwcfHB8uWLSvxOHXq1MGxY8cwdepUNGvWDLq6uvj444/Rr18/aGhoYNeuXRgzZgzq168PJycnrFy5Ep6enu8Us5OTEyQSCQwMDGBnZ4dOnTohKCgIVlZW8n02bdqEoUOHolGjRqhZsybmz58vn4HnfSxduhSDBw9Gy5YtUbVqVUyePFneH//GTz/9hMDAQLRt2xZWVlYIDg7G1atXoaOjA+B1W87hw4cxbdo0DBo0CA8fPoSVlRXatm0r78MXOx/fznjy+DHWrFqJR48ewsnZBWvWb4Q5f51ZZhxL1eA4qg7H8t00crDAsXl+8tcLA1sAALafuoEx6yJQ39YMA7zqwERfG2lPcnAi9h7m7riMVwXvP1tbRcfPpPIkalgqv3TpEtavX69wX6KqSGT/bB6vYAIDA/Hw4UOF+eNJde7du4eaNWvixIkTb73Rt6xelv8Dd4mIRM+09wahQ6gQnuwtPkMavRsdAcvCF5KelduxP7Y3LvN7srKy0KhRI6xZswbz5s2Dm5ubwnNz3leFrMA/e/YMV65cwc6dO5m8q9CpU6eQlZWFBg0aIC0tDZMmTYKtrS3atm0rdGhERERUiZXnLJIlTXktlb59yutRo0bhk08+QYcOHTBv3jyVx1Qhe+C7deuGTp06Yfjw4ejYsaPQ4VQY+fn5mDp1KurVq4cePXrAwsICYWFhxWYFIiIiIqooSpryOji49Cmvd+3ahejo6Lfu874qdAsNiRNbaIiI/htbaFSDLTSqI2QLzaXb5ddC41pDR+kK/N27d9GkSRMcP35c3vvu6enJFhoiIiIiIgXl2ELzX+0y/xQVFYWMjAyFB18WFhYiPDwcq1atQl5eHjQ1Nd87JibwREREREQq0L59e1y5ckVh3aBBg+Ds7IzJkyerJHkHmMATERERkcipyzSShoaGqF+/vsI6fX19mJubF1v/PirkTaxERERERBUVK/BEREREJGrlOY3k+woLC1P5MVmBJyIiIiISEVbgiYiIiEjU1LgAXy5YgSciIiIiEhFW4ImIiIhI3CpZCZ4JPBERERGJmrpMI/mhsIWGiIiIiEhEWIEnIiIiIlFT52kkywMr8EREREREIsIKPBERERGJWiUrwLMCT0REREQkJqzAExEREZG4VbISPCvwREREREQiwgo8EREREYlaZZsHngk8EREREYkap5EkIiIiIiK1xQo8EREREYlaJSvAswJPRERERCQmrMATERERkbhVshI8K/BERERERCLCCjwRERERiVplm0aSFXgiIiIiIhFhBZ6IiIiIRK2yzQPPBJ6IiIiIRK2S5e9soSEiIiIiEhNW4ImIiIhI3CpZCV4ik8lkQgdB9E8vC4SOgIiIKgvTjt8KHUKFkXt6hmDnTkjLLrdju1jrl9ux3xUr8EREREQkapxGkoiIiIiI1BYr8EREREQkapVtGklW4ImIiIiIRIQVeCIiIiIStUpWgGcCT0REREQiV8kyeLbQEBERERGJCCvwRERERCRqnEaSiIiIiIjUFivwRERERCRqnEaSiIiIiIjUFivwRERERCRqlawAzwo8EREREZGYsAJPREREROJWyUrwTOCJiIiISNQ4jSQREREREaktVuCJiIiISNQ4jSQREREREZVZcHAwmjZtCkNDQ1haWqJ79+64ceOGys/DBJ6IiIiIRE1SjktZnDlzBqNGjcL58+dx/Phx5Ofno1OnTsjOzn7PK1TEFhoiIiIiIhUIDQ1VeL1161ZYWloiKioKbdu2Vdl5mMATERERkbiVYw98Xl4e8vLyFNZJpVJIpdL/fO+zZ88AAGZmZiqNiS00RERERESlCA4OhrGxscISHBz8n+8rKirCuHHj0KpVK9SvX1+lMbECT0RERESiVp7zwE+ZMgVBQUEK65Spvo8aNQp//fUXIiIiVB4TE3giIiIiolIo2y7zT1999RV+//13hIeH46OPPlJ5TEzgiYiIiEjU1GUeeJlMhtGjR+PAgQMICwtD7dq1y+U8TOCJiIiISNTUJH/HqFGjsHPnThw6dAiGhoZIT08HABgbG0NXV1dl5+FNrEREREREKrB27Vo8e/YMnp6esLa2li+7d+9W6XlYgSciIiIiUVOnFpoPgRV4IiIiIiIRYQWeiIiIiEROTUrwHwgr8EREREREIsIEnsrV7Nmz4ebmJnQYREREVIFJJOW3qKNKkcA/fPgQI0aMQK1atSCVSmFlZQVvb29ERkYKHZqCV69eYeHChWjYsCH09PRQtWpVtGrVClu2bEF+fr7Q4dH/27VzB3w7tkNT9wYY8NmnuBIfL3RIosWxVA2Oo+pwLFWHY1l2rVxrYe93fXH713HIPT0Dfq2ciu3jVKsqfp3XF+m/TcSjw5MRsTYQNS2NBIiWhFQpEvhevXohJiYG27Ztw82bNxESEgJPT09kZmYKHZrcq1ev4O3tjQULFmDo0KE4e/YsLl68iFGjRuGHH37A1atXBY2NXgs9chiLFwZj2MhR2PXrATg5OWPEsEC1+iyJBcdSNTiOqsOxVB2O5bvR19HClaQHGLfiSInba1c3xcmV/rh59xG8v96OpkM2IHj7n3j5quADR6p+JOW4qKMKn8A/ffoUf/75J77//nt4eXnBxsYGzZo1w5QpU9C1a1cAgEQiwcaNG9GjRw/o6enB0dERISEhCsc5c+YMmjVrBqlUCmtra3zzzTcoKHj9F+b333+HiYkJCgsLAQCxsbGQSCT45ptv5O8fMmQIPv/881LjXL58OcLDw3Hy5EmMGjUKbm5usLOzQ//+/XHhwgU4OjoCAPLy8jBmzBhYWlpCR0cHrVu3xqVLlwAARUVF+Oijj7B27VqFY8fExEBDQwN37tyRj8mQIUNgYWEBIyMjtGvXDnFxcfL937S9bNy4EbVr14aOjo5S7wOABQsWoFq1ajA0NERgYCBevnyp5P8pcdi+bQt69u6D7j16wd7BAdNnzYGOjg4O7t8ndGiiw7FUDY6j6nAsVYdj+W6OXUzCnM1hCIm4UeL2OYFeOHrhFqatP4m4W+lIvv8Ef5y9iYdPcz5wpOqHLTQVjIGBAQwMDHDw4EHk5eWVut+cOXPQp08fxMfHo3PnzhgwYAAeP34MAPj777/RuXNnNG3aFHFxcVi7di02bdqEefPmAQDatGmDFy9eICYmBsDrZL9q1aoICwuTH//MmTPw9PQs9fw7duxAhw4d4O7uXmyblpYW9PX1AQCTJk3Cvn37sG3bNkRHR8PBwQHe3t54/PgxNDQ00K9fP+zcubPYsVu1agUbGxsAwKeffoqMjAwcOXIEUVFRaNSoEdq3by+/XgC4desW9u3bh/379yM2Nlap9+3ZswezZ8/G/PnzcfnyZVhbW2PNmjWlXrPY5L96hYRrV9G8RUv5Og0NDTRv3hLxcTECRiY+HEvV4DiqDsdSdTiW5UMiAXyaOyDx3mOELOyPO/uDEL5mcIltNlTxVfgEvkqVKti6dSu2bdsGExMTtGrVClOnTkX8v3rxAgIC0K9fPzg4OGD+/PnIysrCxYsXAQBr1qxBzZo1sWrVKjg7O6N79+6YM2cOlixZgqKiIhgbG8PNzU2esIeFheHrr79GTEwMsrKy8Pfff+PWrVvw8PAoNc7ExEQ4Ozu/9Vqys7Oxdu1aLFq0CL6+vqhbty5+/PFH6OrqYtOmTQCAAQMGIDIyEqmpqQBeV+V37dqFAQMGAAAiIiJw8eJF/Prrr2jSpAkcHR2xePFimJiYYO/evfJzvXr1Cj/99BPc3d3h6uqq1PuWL1+OwMBABAYGwsnJCfPmzUPdunXfek15eXl4/vy5wvK2L1pCevL0CQoLC2Fubq6w3tzcHI8ePRIoKnHiWKoGx1F1OJaqw7EsH5Ym+jDUk2JCv5Y4fjEJfhN3IOTP69g191O0blhL6PAEJynH/9RRhU/ggdc98Pfv30dISAh8fHwQFhaGRo0aYevWrfJ9XF1d5X/W19eHkZERMjIyAAAJCQlo0aIFJP/4PUqrVq2QlZWFe/fuAQA8PDwQFhYGmUyGP//8Ez179oSLiwsiIiJw5swZVK9eXd4G8+a3AgYGBhg+fDgA5Z7clZSUhPz8fLRq1Uq+TktLC82aNUNCQgIAwM3NDS4uLvIq/JkzZ5CRkYFPP/0UABAXF4esrCyYm5srxJGcnIykpCT5cW1sbGBhYSF/rcz7EhIS8PHHHyvE3KJFi7deU3BwMIyNjRWWRd8H/+dYEBERVSYaGq9zkN/P3sQPey8gPukBFv9yFofPJeJLv8YCR0cfWqV5kJOOjg46duyIjh07YsaMGRgyZAhmzZqFgIAAAK8T4X+SSCQoKipS+vienp7YvHkz4uLioKWlBWdnZ3h6eiIsLAxPnjxRqL6/aUkBACOj13eO16lTB9evX3/3C/yHAQMGYOfOnfjmm2+wc+dO+Pj4yCshWVlZsLa2VmjvecPExET+5zctO28o+76ymjJlCoKCghTWyTSl73y88mRqYgpNTc1iN2FlZmaiatWqAkUlThxL1eA4qg7HUnU4luXj0bMc5BcUIiHlocL6G6mP0LJBTYGiUiPqWSgvN5WiAl+SunXrIjs7W6l9XVxccO7cOYUqeWRkJAwNDfHRRx8B+F8f/LJly+TJ+psEPiwsTKH/3cHBQb5YWloCAPr3748TJ07I++j/KT8/H9nZ2bC3t4e2trbC9Jf5+fm4dOmSQqtK//798ddffyEqKgp79+6Vt88AQKNGjZCeno4qVaooxOHg4PDWH6zKvM/FxQUXLlxQeN/58+ffOrZSqRRGRkYKi1Sqngm8lrY2XOrWw4Xz5+TrioqKcOHCObg2LH7vApWOY6kaHEfV4ViqDseyfOQXFCHq+n3UqanYmuT4kRlSHzwTKCoSSoVP4DMzM9GuXTv8/PPPiI+PR3JyMn799VcsXLgQ3bp1U+oYI0eOxN27dzF69Ghcv34dhw4dwqxZsxAUFAQNjddDaGpqCldXV+zYsUOerLdt2xbR0dG4efPmW/vfAWDcuHFo1aoV2rdvj9WrVyMuLg63b9/Gnj170Lx5cyQmJkJfXx8jRozAxIkTERoaimvXruHLL79ETk4OAgMD5ceytbVFy5YtERgYiMLCQvlsOwDQoUMHtGjRAt27d8exY8eQkpKCs2fPYtq0abh8+XKp8SnzvrFjx2Lz5s3YsmULbt68iVmzZgk6/WV5+MJ/EPbv3YOQgwdwOykJ8+bORm5uLrr36Cl0aKLDsVQNjqPqcCxVh2P5bvR1tOBqXw2u9tUAALbWJnC1ryaf533Z7nPo7VUPgz5xh111Uwzv3gSdW9bBhoOl//tdWVS2aSQrfAuNgYEBPv74YyxbtkzeQ16zZk18+eWXmDp1qlLHqFGjBg4fPoyJEyeiYcOGMDMzQ2BgIKZPn66wn4eHB2JjY+UJvJmZGerWrYsHDx7Ayentd4lLpVIcP34cy5Ytw/r16zFhwgTo6enBxcUFY8aMQf369QG8nqaxqKgIX3zxBV68eIEmTZrg6NGjMDU1VTjegAEDMHLkSAwcOBC6urry9RKJBIcPH8a0adMwaNAgPHz4EFZWVmjbti2qVatWanzKvK9v375ISkrCpEmT8PLlS/Tq1QsjRozA0aNHlRpnMfDx7Ywnjx9jzaqVePToIZycXbBm/UaY89fCZcaxVA2Oo+pwLFWHY/luGjlVx7HlA+WvF47qBADYHhqHod+HICTiBkYv+wMT+7fCktHeuHk3E/1m/Yqzf90VKmQSiESmzN2TRB/QSz6PgoiIPhDTjt8KHUKFkXt6hmDnznhRfk+stzTU+u+dPrAKX4EnIiIioopNXad7LC8VvgeeiIiIiKgiYQWeiIiIiMStchXgWYEnIiIiIhITVuCJiIiISNQqWQGeFXgiIiIiIjFhBZ6IiIiIRE1SyUrwrMATEREREYkIK/BEREREJGqVbR54JvBEREREJGpsoSEiIiIiIrXFBJ6IiIiISESYwBMRERERiQh74ImIiIhI1NgDT0REREREaosVeCIiIiIStco2jSQr8EREREREIsIKPBERERGJWmXrgWcCT0RERESiVsnyd7bQEBERERGJCSvwRERERCRulawEzwo8EREREZGIsAJPRERERKLGaSSJiIiIiEhtsQJPRERERKJW2aaRZAWeiIiIiEhEWIEnIiIiIlGrZAV4JvBEREREJHKVLINnCw0RERERkYgwgSciIiIiUZOU43/vYvXq1bC1tYWOjg4+/vhjXLx4UaXXywSeiIiIiEhFdu/ejaCgIMyaNQvR0dFo2LAhvL29kZGRobJzMIEnIiIiIlGTSMpvKaulS5fiyy+/xKBBg1C3bl2sW7cOenp62Lx5s8qulwk8EREREVEp8vLy8Pz5c4UlLy+vxH1fvXqFqKgodOjQQb5OQ0MDHTp0wLlz51QWE2ehIbWjo+afyry8PAQHB2PKlCmQSqVChyNqHEvV4ViqBsdRdcQylrmnZwgdwn8Sy1gKqTxzh9nzgjFnzhyFdbNmzcLs2bOL7fvo0SMUFhaiWrVqCuurVauG69evqywmiUwmk6nsaESVwPPnz2FsbIxnz57ByMhI6HBEjWOpOhxL1eA4qg7HUnU4lsLKy8srVnGXSqUlfpm6f/8+atSogbNnz6JFixby9ZMmTcKZM2dw4cIFlcSk5rVOIiIiIiLhlJasl6Rq1arQ1NTEgwcPFNY/ePAAVlZWKouJPfBERERERCqgra2Nxo0b4+TJk/J1RUVFOHnypEJF/n2xAk9EREREpCJBQUHw9/dHkyZN0KxZMyxfvhzZ2dkYNGiQys7BBJ6ojKRSKWbNmsUbiVSAY6k6HEvV4DiqDsdSdTiW4tK3b188fPgQM2fORHp6Otzc3BAaGlrsxtb3wZtYiYiIiIhEhD3wREREREQiwgSeiIiIiEhEmMATEREREYkIE3giIiIiIhFhAk9EREREJCJM4ImIiIiIRITzwBPRB/f06VNcvHgRGRkZKCoqUtg2cOBAgaISHw8PDwQGBuLTTz+Frq6u0OFUCK9evUJycjLs7e1RpQr/iXxXT58+xd69e5GUlISJEyfCzMwM0dHRqFatGmrUqCF0eGrL3d0dEolEqX2jo6PLORpSZ/zpRFQGT548waZNm5CQkAAAcHFxweDBg2FmZiZwZOLx22+/YcCAAcjKyoKRkZHCP1YSiYQJfBm4u7tjwoQJGD16NPr06YPAwEA0b95c6LBEKScnB6NHj8a2bdsAADdv3oSdnR1Gjx6NGjVq4JtvvhE4QvGIj49Hhw4dYGxsjJSUFHz55ZcwMzPD/v37kZqaip9++knoENVW9+7d5X9++fIl1qxZg7p166JFixYAgPPnz+Pq1asYOXKkQBGSuuCDnIiUFB4ejq5du8LIyAhNmjQBAERFReHp06f47bff0LZtW4EjFIc6deqgc+fOmD9/PvT09IQOR/QKCgoQEhKCbdu24ciRI3BwcMDgwYPxxRdfqPSpfxXd2LFjERkZieXLl8PHxwfx8fGws7PDoUOHMHv2bMTExAgdomh06NABjRo1wsKFC2FoaIi4uDjY2dnh7Nmz6N+/P1JSUoQOURSGDBkCa2trfPvttwrrZ82ahbt372Lz5s0CRUbqgAk8kZIaNGiAFi1aYO3atdDU1AQAFBYWYuTIkTh79iyuXLkicITioK+vjytXrsDOzk7oUCqcjIwMbNiwAd999x0KCwvRuXNnjBkzBu3atRM6NLVnY2OD3bt3o3nz5gpJ561bt9CoUSM8f/5c6BBFw9jYGNHR0bC3t1cYyzt37sDJyQkvX74UOkRRMDY2xuXLl+Ho6KiwPjExEU2aNMGzZ88EiozUAW9iJVLSrVu3MH78eHnyDgCampoICgrCrVu3BIxMXLy9vXH58mWhw6hwLl68iFmzZmHJkiWwtLTElClTULVqVXTp0gUTJkwQOjy19/DhQ1haWhZbn52drXRPMr0mlUpL/MJz8+ZNWFhYCBCROOnq6iIyMrLY+sjISOjo6AgQEakT9sATKalRo0ZISEiAk5OTwvqEhAQ0bNhQoKjE55NPPsHEiRNx7do1NGjQAFpaWgrbu3btKlBk4pORkYHt27djy5YtSExMhJ+fH3755Rd4e3vLk86AgAD4+Phg8eLFAker3po0aYI//vgDo0ePBgD5+G3cuFHef0zK6dq1K+bOnYs9e/YAeD2WqampmDx5Mnr16iVwdOIxbtw4jBgxAtHR0WjWrBkA4MKFC9i8eTNmzJghcHQkNLbQEClp9+7dmDRpEkaPHi2/UfD8+fNYvXo1FixYABcXF/m+rq6uQoWp9jQ0Sv/Fn0QiQWFh4QeMRty0tbVhb2+PwYMHIyAgoMTq5vPnz9GtWzecPn1agAjFIyIiAr6+vvj888+xdetWDBs2DNeuXcPZs2dx5swZNG7cWOgQRePZs2fo3bs3Ll++jBcvXqB69epIT09HixYtcPjwYejr6wsdomjs2bMHK1asUJg4YezYsejTp4/AkZHQmMATKeltiSfwOvmUyWRMQumD+fPPP9GmTRuhw6gwkpKSsGDBAsTFxSErKwuNGjXC5MmT0aBBA6FDE6WIiAjEx8fLx7JDhw5Ch0RUYTCBJ1LSnTt3lN7XxsamHCMheq1du3bYv38/TExMFNY/f/4c3bt3x6lTp4QJjIhU4s18+rdv38aECRM4nz7JMYEnog8uOzsbZ86cQWpqKl69eqWwbcyYMQJFJT6amppIS0srdvNlRkYGatSogfz8fIEiE5/SZpmRSCSQSqXQ1tb+wBGJ18qVK0tcL5FIoKOjAwcHB7Rt21ZhQgAq7t/z6d+4cQN2dnaYPn0659Mn3sRKVBZJSUlYvny5vB+xbt26GDt2LOzt7QWOTDxiYmLQuXNn5OTkIDs7G2ZmZnj06BH09PRgaWnJBF4J8fHxAACZTIZr164hPT1dvq2wsBChoaGszpWRiYnJW2eb+eijjxAQEIBZs2b9ZztdZbds2TI8fPgQOTk5MDU1BfD6IXh6enowMDBARkYG7OzscPr0adSsWVPgaNVXUFAQAgIC5PPpv9G5c2f0799fwMhIHfCnEJGSjh49irp16+LixYtwdXWFq6srLly4gHr16uH48eNChycaX3/9Nfz8/PDkyRPo6uri/PnzuHPnDho3bsyZUpTk5uYmf+R6u3bt4ObmJl8aN26MefPmYebMmUKHKSpbt25F9erVMXXqVBw8eBAHDx7E1KlTUaNGDaxduxZDhw7FypUrsWDBAqFDVXvz589H06ZNkZiYiMzMTGRmZuLmzZv4+OOPsWLFCqSmpsLKygpff/210KGqtUuXLmHYsGHF1teoUUPhSztVUjIiUoqbm5ts8uTJxdZPnjxZ5u7uLkBE4mRsbCy7fv26/M/Xrl2TyWQy2fnz52VOTk5ChiYaKSkpsuTkZJlEIpFdunRJlpKSIl/u378vKygoEDpE0WnXrp1s9+7dxdbv3r1b1q5dO5lMJpP99NNP/Iwqwc7OThYTE1NsfXR0tKx27doymUwmi4yMlFlZWX3gyMTFwsJCFh0dLZPJZDIDAwNZUlKSTCaTyY4dOyb76KOPhAyN1AAr8ERKSkhIQGBgYLH1gwcPxrVr1wSISJy0tLTkLQiWlpZITU0F8Pqpg3fv3hUyNNGwsbGBra0tioqK0KRJE9jY2MgXa2tr9ha/g7Nnz8Ld3b3Yend3d5w7dw4A0Lp1a/nnlUqXlpaGgoKCYusLCgrklePq1avjxYsXHzo0UXkzn/6be1k4nz79ExN4IiVZWFggNja22PrY2NgSn+BIJXN3d8elS5cAAB4eHpg5cyZ27NiBcePGoX79+gJHJy7btm3DH3/8IX89adIkmJiYoGXLlmWaNYmAmjVrYtOmTcXWb9q0Sd6nnZmZKe/pptJ5eXlh2LBhiImJka+LiYnBiBEj0K5dOwDAlStXULt2baFCFIUlS5YgKysLlpaWyM3NhYeHBxwcHGBoaIjvvvtO6PBIYJyFhkhJc+fOxbJly/DNN9+gZcuWAF4/0vr7779HUFAQn4ynpDcPd/Hy8kJGRgYGDhyIs2fPwtHREZs3b+ZTbcvAyckJa9euRbt27XDu3Dm0b98ey5cvx++//44qVapg//79QocoGiEhIfj000/h7OyMpk2bAnj9Wb1+/Tr27t2LLl26YO3atUhMTMTSpUsFjla9paen44svvsDJkyflT1ouKChA+/btsX37dlSrVg2nT59Gfn4+OnXqJHC06i8yMlLh2QScT58AJvBESpPJZFi+fDmWLFmC+/fvA3j9a+CJEydizJgxb53Bgqg86Onp4fr166hVqxYmT56MtLQ0/PTTT7h69So8PT3x8OFDoUMUlZSUFKxfvx43btwA8PoL0rBhw2BraytsYCJ1/fp13Lx5E8DrsXRychI4IvHIz8+Hrq4uYmNj+ZtJKhGnkSRSQkFBAXbu3In+/fvj66+/lvdu/nNqL1JeQUEBwsLCkJSUhP79+8PQ0BD379+HkZERDAwMhA5PNAwMDJCZmYlatWrh2LFjCAoKAgDo6OggNzdX4OjEx9bWFsHBwUKHUWE4OzvD2dlZ6DBESUtLC7Vq1eJTvalUrMATKUlPTw8JCQl8yup7unPnDnx8fJCamoq8vDzcvHkTdnZ2GDt2LPLy8rBu3TqhQxSNAQMG4Pr163B3d8cvv/yC1NRUmJubIyQkBFOnTsVff/0ldIiik5OTU+IDxlxdXQWKSJzu3buHkJCQEseSLUjK2bRpE/bv34/t27fDzMxM6HBIzbACT6SkZs2aISYmhgn8exo7diyaNGmCuLg4mJuby9f36NEDX375pYCRic/q1asxffp03L17F/v27ZOPZ1RUFPr16ydwdOLy8OFDDBo0CEeOHClxOyuhyjt58iS6du0KOzs7XL9+HfXr10dKSgpkMhkaNWokdHiisWrVKty6dQvVq1eHjY0N9PX1FbZHR0cLFBmpAybwREoaOXIkxo8fj3v37qFx48bFfpiyQqecP//8E2fPni32aHpbW1v8/fffAkUlTiYmJli1alWx9XPmzBEgGnEbN24cnj59igsXLsDT0xMHDhzAgwcPMG/ePCxZskTo8ERlypQpmDBhAubMmQNDQ0Ps27cPlpaWGDBgAHx8fIQOTzS6d+8udAikxthCQ6Skkh6fLpFIIJPJIJFIWKFTkqmpKSIjI1G3bl0YGhoiLi4OdnZ2iIiIQK9evfDgwQOhQxSN8PDwt25v27btB4pE/KytrXHo0CE0a9YMRkZGuHz5MurUqYOQkBAsXLgQERERQocoGoaGhoiNjYW9vT1MTU0RERGBevXqIS4uDt26dUNKSorQIRKJHivwREpKTk4WOoQKoVOnTli+fDk2bNgA4PWXoKysLMyaNQudO3cWODpx8fT0LLbun7Mh8Uul8rKzs+XPczA1NcXDhw9Rp04dNGjQgK0KZaSvry/ve7e2tkZSUhLq1asHAHj06JGQoYnO06dPsXfvXiQlJWHixIkwMzNDdHQ0qlWrhho1aggdHgmICTyRktj7rhpLliyBt7c36tati5cvX6J///5ITExE1apV8csvvwgdnqg8efJE4XV+fj5iYmIwY8YMPuiljJycnHDjxg3Y2tqiYcOGWL9+PWxtbbFu3TpYW1sLHZ6oNG/eHBEREXBxcUHnzp0xfvx4XLlyBfv370fz5s2FDk804uPj0aFDBxgbGyMlJQVffvklzMzMsH//fqSmpuKnn34SOkQSEFtoiN4iJCRE6X27du1ajpFULAUFBdi1axfi4+PlDycZMGAAdHV1hQ6tQjhz5gyCgoIQFRUldCii8fPPP6OgoAABAQGIioqCj48PHj9+DG1tbWzduhV9+/YVOkTRuH37NrKysuDq6ors7GyMHz9e/rC2pUuXshiipA4dOqBRo0ZYuHChQrvh2bNn0b9/f7YiVXJM4Ine4t9972963v/5+g22K5C6uH79Opo0aYKsrCyhQxGtnJwc+UOyqlatKnQ4VAkZGxsjOjoa9vb2Cgn8nTt34OTkhJcvXwodIgmILTREb1FUVCT/84kTJzB58mTMnz8fLVq0AACcO3cO06dPx/z584UKURT4m4zyER8fr/BaJpMhLS0NCxYsgJubmzBBVRB6enqc8lAFsrKyFH6OAoCRkZFA0YiLVCrF8+fPi62/efMmLCwsBIiI1Akr8ERKql+/PtatW4fWrVsrrP/zzz8xdOhQJCQkCBSZ+itpBp+ScDafstHQ0Cj2WyHgdQ/y5s2b+RTMMpDJZNi7dy9Onz6NjIyMYknn/v37BYpMfJKTk/HVV18hLCxMoUrMGbvKZsiQIcjMzMSePXtgZmaG+Ph4aGpqonv37mjbti2WL18udIgkIFbgiZSUlJQEExOTYuvf3GBEpft3MkSq8e+ZkTQ0NGBhYQEdHR2BIhKvcePGYf369fDy8kK1atUU2uOobD7//HPIZDJs3ryZY/kelixZgt69e8PS0hK5ubnw8PBAeno6WrRowZvUiRV4ImW1bdsWOjo62L59O6pVqwYAePDgAQYOHIiXL1/izJkzAkeo3k6dOoWvvvoK58+fL/Yr9GfPnqFly5ZYt24d2rRpI1CE4lJUVIStW7di//79SElJgUQiQe3atdG7d2988cUXTJrKyMzMDD///DOnMlUBAwMDREVFwcnJSehQKoSIiAiFG/47dOggdEikBliBJ1LS5s2b0aNHD9SqVQs1a9YEANy9exeOjo44ePCgsMGJwPLly/Hll1+W2P9qbGyMYcOGYenSpUzglSCTydC1a1ccPnwYDRs2RIMGDSCTyZCQkICAgADs37+fn8kyMjY2hp2dndBhVAhNmzbF3bt3mcC/p7t376JmzZpo3bp1sdZNIlbgicpAJpPh+PHjuH79OgDAxcUFHTp0YLVTCTY2NggNDYWLi0uJ269fv45OnTohNTX1A0cmPlu2bMHYsWNx6NAheHl5KWw7deoUunfvjlWrVmHgwIECRSg+27ZtQ2hoKDZv3szpTN9TUlIShg8fjs8//xz169eHlpaWwnZXV1eBIhMXTU1NtG7dGp9//jl69+4NU1NToUMiNcIEnugd3Lt3D9bW1tDU1BQ6FNHQ0dHBX3/9BQcHhxK337p1Cw0aNEBubu4Hjkx8OnXqhHbt2uGbb74pcfv8+fNx5swZHD169ANHJl65ubno0aMHIiMjYWtrWyzp5NNYlXf+/Pli85S/udmaN7EqLyYmBjt37sSuXbvw8OFD+Pj44PPPP4efnx+kUqnQ4ZHA2EJD9A7q1q2L2NhY/sq9DGrUqPHWBD4+Pp5PvFRSfHw8Fi5cWOp2X19frFy58gNGJH7+/v6IiorC559/zhsv39PgwYPh7u6OX375hWP5Htzd3eHu7o6FCxciLCwMO3fuxNChQ1FUVISePXti8+bNQodIAmIFnugd/POhGqSc0aNHIywsDJcuXSo2S0pubi6aNWsGLy8vJp5K0NbWxp07d0r9wnP//n3Url0beXl5Hzgy8dLX18fRo0fZa6wC+vr6iIuLK/XLOr276OhoBAYGIj4+nr/JqORYgSeiD2L69OnYv38/6tSpg6+++kp+g9v169exevVqFBYWYtq0aQJHKQ6FhYWoUqX0H9+ampooKCj4gBGJX82aNfmAIRVp164dE3gVunfvHnbu3ImdO3fir7/+QosWLbB69WqhwyKBMYEnegdTp06FmZmZ0GGISrVq1XD27FmMGDECU6ZMkT98SCKRwNvbG6tXr5ZPz0lvJ5PJEBAQUGofLCvvZbdkyRJMmjQJ69atg62trdDhiJqfnx++/vprXLlyBQ0aNCh2PwGftqyc9evXY+fOnYiMjISzszMGDBiAQ4cOwcbGRujQSA2whYaIPrgnT57g1q1bkMlkcHR05OwKZTRo0CCl9tuyZUs5R1JxmJqaIicnBwUFBdDT0yuWdD5+/FigyMTnbU9e5k2syqtZsyb69euHAQMGoGHDhkKHQ2qGCTzRWwQFBSm979KlS8sxEiIqT9u2bXvrdn9//w8UCdFrb2btISoJE3iit/j3HNvR0dEoKCiQ92/fvHkTmpqaaNy4MU6dOiVEiEREVEE9ffoUmzZtQkJCAoDXM6AFBgbC2NhY4MhIaEzgiZS0dOlShIWFYdu2bfKWjydPnmDQoEFo06YNxo8fL3CERKQKL1++xKtXrxTW8QbXssnOzsaZM2eQmppabCzHjBkjUFTicvnyZXh7e0NXVxfNmjUDAFy6dAm5ubk4duwYGjVqJHCEJCQm8ERKqlGjBo4dO4Z69eoprP/rr7/QqVMn3L9/X6DIiOh9ZWdnY/LkydizZw8yMzOLbWfftvJiYmLQuXNn5OTkIDs7G2ZmZnj06BH09PRgaWmJ27dvCx2iKLRp0wYODg748ccf5bNOFRQUYMiQIbh9+zbCw8MFjpCEVPqdJkSk4Pnz53j48GGx9Q8fPsSLFy8EiIiIVGXSpEk4deoU1q5dC6lUio0bN2LOnDmoXr06fvrpJ6HDE5Wvv/4afn5+ePLkCXR1dXH+/HncuXMHjRs3xuLFi4UOTzQuX76MyZMnK0wZW6VKFUyaNAmXL18WMDJSB0zgiZTUo0cPDBo0CPv378e9e/dw79497Nu3D4GBgejZs6fQ4RHRe/jtt9+wZs0a9OrVC1WqVEGbNm0wffp0zJ8/Hzt27BA6PFGJjY3F+PHjoaGhAU1NTeTl5aFmzZpYuHAhpk6dKnR4omFkZITU1NRi6+/evQtDQ0MBIiJ1wgSeSEnr1q2Dr68v+vfvDxsbG9jY2KB///7w8fHBmjVrhA6PiN7D48eP5U9WNjIykk8b2bp1a7YqlJGWlpZ8KklLS0t5EmpsbIy7d+8KGZqo9O3bF4GBgdi9ezfu3r2Lu3fvYteuXRgyZAj69esndHgkMD7IiUgJhYWFuHz5Mr777jssWrQISUlJAAB7e3vo6+sLHB0RvS87OzskJyejVq1acHZ2xp49e9CsWTP89ttvMDExETo8UXF3d8elS5fg6OgIDw8PzJw5E48ePcL27dtRv359ocMTjcWLF0MikWDgwIHyJytraWlhxIgRWLBggcDRkdB4EyuRknR0dJCQkIDatWsLHQoRqdiyZcugqamJMWPG4MSJE/Dz84NMJkN+fj6WLl2KsWPHCh2iaFy+fBkvXryAl5cXMjIyMHDgQJw9exaOjo7YvHkzH0pURjk5OQpFIz09PYEjInXABJ5ISU2aNMH333+P9u3bCx0KEZWzO3fuICoqCg4ODnB1dRU6HKpECgsLcfXqVTg6OkJXV1dhW25uLhITE1G/fv23PvGWKj4m8ERKCg0NxZQpU/Dtt9+icePGxVpnOE80EdFrjx49QkpKCiQSCWxtbWFubi50SKKxdetWrFq1ChcuXICmpqbCtoKCAjRv3hzjxo3D559/LlCEpA6YwBMp6Z/Vjn8+3vrN4645TzSRuKxcuVLpffnwIeVcvXoVI0aMQGRkpMJ6Dw8PrFmzBs7OzgJFJh5t2rTBqFGj8Nlnn5W4fc+ePVi1ahVvrq7kmMATKenMmTNv3e7h4fGBIiEiVfj3/SwPHz5ETk6O/KbVp0+f8uFDZZCeno769evDwsICw4cPh7OzM2QyGa5du4Yff/wRmZmZ+Ouvv2BpaSl0qGrN0tISFy9ehK2tbYnbk5OT0axZsxKfS0KVB2ehIVISE3SiiiU5OVn+5507d2LNmjXYtGkTnJycAAA3btzAl19+iWHDhgkVoqgsW7YMNjY2iIyMhI6Ojny9j48PRowYgdatW2PZsmUIDg4WMEr1l52djefPn5e6/cWLF8jJyfmAEZE6YgWe6C3i4+PlNwvFx8e/dV/e6EYkXvb29ti7dy/c3d0V1kdFRaF3794KyT6VrFGjRvjmm2/Qp0+fErfv2rULCxcuRHR09AeOTFzc3NwwfPhwDB8+vMTta9aswYYNGxAbG/thAyO1wgo80Vu4ubkhPT0dlpaWcHNzg0QiQUnfedkDTyRuaWlp8rm2/6mwsBAPHjwQICLxuX37Nho1alTq9iZNmrAVSQn9+/fH9OnT0bJly2KFobi4OMycOROTJk0SKDpSF6zAE73FnTt3UKtWLUgkEty5c+et+9rY2HygqIhI1fz8/PD3339j48aN8iQ0KioKQ4cORY0aNRASEiJwhOpPU1MTaWlppfa4P3jwADVq1CjxixL9T35+Pjp16oSIiAh06NBBfuPv9evXceLECbRq1QrHjx+HlpaWwJGSkJjAEykpMzNTPhXa3bt38eOPPyI3Nxddu3ZFmzZtBI6OiN7Hw4cP4e/vj9DQUHliVFBQAG9vb2zdupU3XipBU1MTN2/ehIWFRYnbHzx4AGdnZ/62Ugn5+flYtmwZdu7cicTERMhkMtSpUwf9+/fHuHHjoK2tLXSIJDAm8ET/4cqVK/Dz88Pdu3fh6OiIXbt2wcfHB9nZ2dDQ0EB2djb27t2L7t27Cx0qEb2nxMREJCQkAACcnZ1Rp04dgSMSDw0NDYUpdv+NU+4SqQ4TeKL/4OvriypVquCbb77B9u3b8fvvv8Pb2xs//vgjAGD06NGIiorC+fPnBY6UiFQhMjISTZo0gVQqFToUUfmvqXbf4IxeZTdy5EjMnTsXVatWFToUUhNM4In+Q9WqVXHq1Cm4uroiKysLRkZGuHTpEho3bgzgdV9i8+bN8fTpU2EDJSKVMDIyQmxsLOzs7IQOhQgAP5NUnMZ/70JUuT1+/BhWVlYAAAMDA+jr68PU1FS+3dTUFC9evBAqPCJSMda1VOeTTz5BWlqa0GGIHj+T9G9M4ImU8O++zrf1eRIR0Wvh4eHIzc0VOgyiCofzwBMpISAgQN4P+/LlSwwfPhz6+voAgLy8PCFDIyIVW79+PapVqyZ0GERy/C0v/Rt74In+w6BBg5Tab8uWLeUcCRGRuNSvXx9HjhxBzZo1hQ5FdAYOHAgvLy+0bdsW9vb2QodDaoYJPBERVUo9e/ZUet/9+/eXYyRExQ0ZMgTh4eG4desWatSoAQ8PD3h6esLDwwOOjo5Ch0cCYwJPRESVkrK/XQP4G7b/Eh8fr/S+rq6u5RhJxfP3338jPDwcZ86cwZkzZ3Dz5k1YW1vj3r17QodGAmIPPBERVUpMylXHzc0NEomk1NlS3mzjg5zKztTUFObm5jA1NYWJiQmqVKlS6tNuqfJgBZ6IiIjey507d5Te18bGphwjqTimTp2KsLAwxMTEwMXFRd5C07ZtW4WpjKlyYgJPREQEYO/evdizZw9SU1Px6tUrhW3R0dECRUWVlYaGBiwsLPD111+jZ8+eqFOnjtAhkRphCw0REVV6K1euxLRp0xAQEIBDhw5h0KBBSEpKwqVLlzBq1CihwxOla9eulfhlqGvXrgJFJC4xMTE4c+YMwsLCsGTJEmhra8ur8J6enkzoKzlW4ImIqNJzdnbGrFmz0K9fPxgaGiIuLg52dnaYOXMmHj9+jFWrVgkdomjcvn0bPXr0wJUrVxT64t88AI898O8mLi4Oy5Ytw44dO1BUVMRxrOT4JFYiIqr0UlNT0bJlSwCArq6u/ME5X3zxBX755RchQxOdsWPHonbt2sjIyICenh6uXr2K8PBwNGnSBGFhYUKHJxoymQzR0dFYunQpunbtCi8vL/z8889o0KABxowZI3R4JDC20BARUaVnZWWFx48fw8bGBrVq1cL58+fRsGFDJCcnlzqzCpXs3LlzOHXqFKpWrQoNDQ1oaGigdevWCA4OxpgxYxATEyN0iKJgZmaGrKwsNGzYEB4eHvjyyy/Rpk0bmJiYCB0aqQEm8EREVOm1a9cOISEhcHd3x6BBg/D1119j7969/9fevQdVVbdtHP9u5OAWNnkKCwUEKcWRTNNxrAxRUwJPOdVUppCHNDUspJKpRoxHzYoyDwMeyY5makamUZIaWmlFqJ1AHA1NC0fSQpPT5v3Dp/0+O9S01MViXZ8ZZ1i/tdj7co8z3vy417348ssvL+iBT3K6RcbhcADQsmVLDh06RPv27QkJCaGwsNDgdObx2muv0atXL/z9/Y2OIvWQCngREbG8RYsW4XQ6AZg4cSItWrTg008/ZfDgwYwbN87gdObSqVMndu7cSWhoKD169ODZZ5/F29ubRYsWERYWZnQ804iLi3N9/edDm9q0aWNUHKlndBOriIiIXDQ5OTmcOHGCYcOGUVxczMCBAykqKqJFixa89dZb9OnTx+iIpuB0OvnPf/5Deno65eXlADgcDqZMmcITTzyBh4duY7QyFfAiImJ5WVlZ+Pn5ceedd7qtv/3225w8eZL4+HiDkjUMZWVlNGvWzDWJRv5eSkoKS5cuZfr06dx0000AbN26ldTUVMaOHcuMGTMMTihGUgEvIiKWd+2117Jw4UKio6Pd1rds2cIDDzyg3u0LcPz4cWpqamjevLnbellZGZ6enurpPk+BgYFkZmbWmZv/7rvvMmHCBH766SeDkkl9oN+/iIiI5ZWUlBAaGlpnPSQkhJKSEgMSmdfdd9/NihUr6qyvXLmSu+++24BE5lRWVkaHDh3qrHfo0IGysjIDEkl9ogJeREQsLyAggF27dtVZ37lzJy1atDAgkXlt3769zm8yAHr37s327dsNSGROnTt3PuMDxObPn0/nzp0NSCT1iabQiIiI5d1zzz0kJibicDi45ZZbgNPtM5MnT9au8QWqqKigurq6znpVVRV//PGHAYnM6dlnnyUuLo6NGzfSs2dP4PSM/QMHDrB+/XqD04nR1AMvIiKWV1lZyYgRI3j77bfx9Dy9t+V0Ohk5ciSZmZl4e3sbnNA8oqOj6dSpE/PmzXNbnzhxIrt27SIvL8+gZOZz6NAhFixYwA8//ABAREQEEyZMIDAw0OBkYjQV8CIiIv9VVFTEzp07sdvtREZGEhISYnQk09m2bRv9+vWje/fu9O3bF4Dc3Fy++OILPvzwQ3r16mVwQhHzUwEvIiIiF1VBQQHPPfccBQUF2O12rrvuOlJSUrjmmmuMjmYqx44dY8eOHZSWlroeNPankSNHGpRK6gMV8CIiYklJSUmkpaXh6+tLUlLSOa994YUXLlMqkdPee+89hg8fTnl5Of7+/m4z9G02mybRWJxuYhUREUv6+uuvqaqqAiA/P/+sDxnSw4f+3m+//eaa7/7bb7+d81rNgT8/U6ZMYdSoUcycOZMmTZoYHUfqGe3Ai4iIyL/SqFEjDh8+TEBAAB4eHmf8oae2thabzUZNTY0BCc3H19eX3bt3ExYWZnQUqYe0Ay8iIpZWVVWF3W6noKCATp06GR3HlD7++GPXk1c3bdpkcJqGYcCAAXz55Zcq4OWMVMCLiIileXl5ERwcrJ3hfyEqKuqMX8s/FxcXx6OPPsp3331HZGQkXl5ebucHDx5sUDKpD9RCIyIilrd06VLWrFnDq6++6tpJln/mgw8+wM/Pj5tvvhmABQsWsHjxYjp27MiCBQto1qyZwQnNwcPD46zn1IokKuBFRMTyunTpQnFxMVVVVYSEhODr6+t2Pj8/36Bk5hMZGcns2bOJjY1l9+7ddOvWjSlTprBp0yY6dOhAVlaW0RFFTE8tNCIiYnlDhgzRtJmLZN++fXTs2BGA1atXM2jQIGbOnEl+fj6xsbEGp6v/PvvsM44ePcrAgQNda6+88grTpk3jxIkTDB06lHnz5uHj42NgSjGaCngREbG81NRUoyM0GN7e3pw8eRKAjRs3uh441Lx5878dMSnw9NNP07t3b1cBv3v3bkaPHk1CQgIRERE899xzBAYG6t+sxZ29wUpERMQiwsLCOHr0aJ31Y8eOaQrIBbr55ptdD8nasWMHcXFxABQVFdGmTRuD09V/BQUF9O3b13W8YsUKevToweLFi0lKSmLu3LmsXLnSwIRSH6iAFxERy9u/f/8ZbwqsqKjg4MGDBiQyr/nz5+Pp6cmqVavIyMigdevWAGzYsIGYmBiD09V/v/76K61atXIdb9myhdtuu8113L17dw4cOGBENKlH1EIjIiKWlZ2d7fo6JyeHK664wnVcU1NDbm4uoaGhRkQzreDgYNatW1dn/cUXXzQgjfm0atWKffv2ERQURGVlJfn5+UyfPt11/vfff68zUlKsRwW8iIhY1tChQ4HTY/ni4+Pdznl5edG2bVvS09MNSGZeUVFRjB49mjvvvBO73W50HNOJjY1l6tSpzJ49m7Vr19KkSRN69erlOr9r1y7atWtnYEKpD9RCIyIiluV0OnE6nQQHB1NaWuo6djqdVFRUUFhY6DYNRP5ely5dSE5O5qqrrmLs2LF8/vnnRkcylbS0NDw9PYmKimLx4sUsXrwYb29v1/lly5bRv39/AxNKfaA58CIiInJRVVdXk52dzfLly9mwYQPh4eGMGjWKESNGuPV3y9kdP34cPz8/GjVq5LZeVlaGn5+fW1Ev1qMCXkRELC8xMZHw8HASExPd1ufPn09xcTFz5swxJlgDUFpayqJFi5gxYwY1NTXExsaSmJhInz59jI4mYlpqoREREctbvXo1N910U531G2+8kVWrVhmQqGHYsWMH06ZNIz09nYCAAFJSUmjZsiUDBw4kOTnZ6HgipqUdeBERsbzGjRvzzTffEB4e7rZeXFxMp06dOHXqlEHJzKe0tJRXX32VrKws9uzZw6BBgxgzZgwDBgxwPe1269atxMTEUF5ebnBaEXPSFBoREbG88PBwPvjgAyZNmuS2vmHDBj3I6QK1adOGdu3aMWrUKBISErjyyivrXHPdddfRvXt3A9KJNAwq4EVExPKSkpKYNGkSR44ccfVm5+bmkp6erv73C5Sbm+s29vBM/P392bRp02VKJNLwqIVGREQEyMjIYMaMGRw6dAiAtm3bkpqaysiRIw1OJiLiTgW8iIjI/zhy5Ah2ux0/Pz+jo5jSL7/8QnJyMrm5uZSWlvLXMqOmpsagZCINh1poREREOD27fPPmzezdu5d7770XgEOHDuHv769i/gIkJCRQUlLCU089xdVXX+26cVVELh7twIuIiOX9+OOPxMTEUFJSQkVFBUVFRYSFhTF58mQqKirIzMw0OqJpOBwO8vLyuP76642OItJgaQ68iIhY3uTJk+nWrRu//vordrvdtX777beTm5trYDLzCQoKqtM2IyIXlwp4ERGxvLy8PJ588sk6j6dv27YtP/30k0GpzGnOnDlMnTqV/fv3Gx1FpMFSD7yIiFie0+k8482VBw8exOFwGJDIXJo1a+bW637ixAnatWtHkyZN8PLycru2rKzscscTaXBUwIuIiOX179+fOXPmsGjRIgBsNhvl5eVMmzaN2NhYg9PVf5qVL3J56SZWERGxvIMHDzJgwABqa2vZs2cP3bp1Y8+ePbRs2ZJPPvmEgIAAoyPWezU1NTz//PNkZ2dTWVlJ3759mTZtmts9BSJycaiAFxER4fQYyRUrVrBr1y7Ky8vp2rUrw4cPVwF6ntLS0khNTaVfv37Y7XZycnK45557WLZsmdHRRBocFfAiIiLyr11zzTUkJyczbtw4ADZu3EhcXBx//PEHHh6amSFyMamAFxERS8rOzj7vawcPHnwJkzQMPj4+FBcXExQU5Fpr3LgxxcXFtGnTxsBkIg2PbmIVERFLGjp06HldZ7PZzjihRtxVV1fTuHFjtzUvLy+qqqoMSiTScKmAFxERS3I6nUZHaFBqa2tJSEjAx8fHtXbq1CnGjx+Pr6+va23NmjVGxBNpUNSUJiIilhUbG8vx48ddx8888wzHjh1zHR89epSOHTsakMx84uPjCQgI4IorrnD9ue+++wgMDHRbE5F/Tz3wIiJiWR4eHvz888+uMZH+/v4UFBQQFhYGwC+//EJgYKBaaESkXtEOvIiIyH9pT0tEzEAFvIiIiIiIiaiAFxERy7LZbNhstjprIiL1mabQiIiIZf11cspfp6ZUVFQYGU9E5Ix0E6uIiFjW/ffff17XZWVlXeIkIiLnTwW8iIiIiIiJqAdeRERERMREVMCLiIiIiJiICngRERERERNRAS8iIpdEQkICQ4cOdR337t2bhx9++LLn2Lx5MzabjWPHjl329xYRuRRUwIuIWExCQoJr/rm3tzfh4eE8/fTTVFdXX9L3XbNmDWlpaed1rYpuEZGz0xx4ERELiomJISsri4qKCtavX8/EiRPx8vIiJSXF7brKykq8vb0vyns2b978oryOiIjVaQdeRMSCfHx8uOqqqwgJCeHBBx+kX79+ZGdnu9peZsyYQWBgIO3btwfgwIED3HXXXTRt2pTmzZszZMgQ9u/f73q9mpoakpKSaNq0KS1atOCxxx7jr1OK/9pCU1FRweOPP05QUBA+Pj6Eh4ezdOlS9u/fT3R0NADNmjXDZrORkJAAgNPpZNasWYSGhmK32+ncuTOrVq1ye5/169dz7bXXYrfbiY6OdsspItIQqIAXERHsdjuVlZUA5ObmUlhYyEcffcS6deuoqqpiwIABOBwO8vLy2LZtG35+fsTExLi+Jz09nZdffplly5axdetWysrKeOedd875niNHjuTNN99k7ty5fP/99yxcuBA/Pz+CgoJYvXo1AIWFhRw+fJiXXnoJgFmzZvHKK6+QmZnJt99+yyOPPMJ9993Hli1bgNM/aAwbNoxBgwZRUFDAmDFjmDp16qX62EREDKEWGhERC6utrSU3N5ecnBweeughjhw5gq+vL0uWLHG1zrz22ms4nU6WLFmCzWYDTj+ZtGnTpmzevJn+/fszZ84cUlJSGDZsGACZmZnk5OSc9X2LiopYuXIlH330Ef369QMgLCzMdf7PdpuAgACaNm0KnN6xnzlzJhs3bqRnz56u79m6dSsLFy4kKiqKjIwM2rVrR3p6OgDt27dn9+7dzJ49+yJ+aiIixlIBLyJiQevWrcPPz4+qqiqcTif33nsvqampTJw4kcjISLe+9507d1JcXIzD4XB7jVOnTrF3716OHz/O4cOH6dGjh+ucp6cn3bp1q9NG86eCggIaNWpEVFTUeWcuLi7m5MmT3HrrrW7rlZWVdOnSBYDvv//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
|