Vishalpainjane commited on
Commit
8f5f46d
·
1 Parent(s): 80478c6

added files

Browse files
.dockerignore ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Version control
2
+ .git
3
+ .gitignore
4
+
5
+ # Python cache
6
+ __pycache__/
7
+ *.py[cod]
8
+ *$py.class
9
+ *.so
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+
27
+ # Virtual environments
28
+ venv/
29
+ env/
30
+ ENV/
31
+
32
+ # IDE files
33
+ .vscode/
34
+ .idea/
35
+ *.swp
36
+ *.swo
37
+ *~
38
+
39
+ # OS files
40
+ .DS_Store
41
+ Thumbs.db
42
+
43
+ # Large data files (exclude datasets)
44
+ SEN-2_LULC/
45
+ SEN-2_LULC_preprocessed/
46
+ SEN-2_LULC_preprocessed1/
47
+ outputs_rgb_optimized/
48
+ *.zip
49
+ *.tar.gz
50
+
51
+ # Logs
52
+ *.log
53
+ logs/
54
+
55
+ # Temporary files
56
+ tmp/
57
+ temp/
58
+
59
+ # Documentation
60
+ README.md
61
+ docs/
Dockerfile ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use PyTorch base image with CUDA support
2
+ FROM pytorch/pytorch:2.1.0-cuda12.1-cudnn8-runtime
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Set environment variables
8
+ ENV PYTHONPATH=/app
9
+ ENV PYTHONUNBUFFERED=1
10
+ ENV DEBIAN_FRONTEND=noninteractive
11
+
12
+ # Install system dependencies
13
+ RUN apt-get update && apt-get install -y \
14
+ libglib2.0-0 \
15
+ libsm6 \
16
+ libxext6 \
17
+ libxrender-dev \
18
+ libgomp1 \
19
+ libgdal-dev \
20
+ gdal-bin \
21
+ libgl1-mesa-glx \
22
+ libglib2.0-0 \
23
+ wget \
24
+ curl \
25
+ git \
26
+ && rm -rf /var/lib/apt/lists/*
27
+
28
+ # Install Python dependencies
29
+ RUN pip install --no-cache-dir \
30
+ torch==2.1.0 \
31
+ torchvision==0.16.0 \
32
+ torchaudio==2.1.0 \
33
+ segmentation-models-pytorch==0.3.3 \
34
+ gradio==4.8.0 \
35
+ albumentations==1.3.1 \
36
+ opencv-python-headless==4.8.1.78 \
37
+ pillow==10.1.0 \
38
+ numpy==1.24.4 \
39
+ tqdm==4.66.1 \
40
+ timm==0.9.12
41
+
42
+ # Create necessary directories
43
+ RUN mkdir -p /app/Segmentation-lulc \
44
+ && mkdir -p /app/data \
45
+ && mkdir -p /app/outputs \
46
+ && mkdir -p /app/models
47
+
48
+ # Copy application files
49
+ COPY Segmentation-lulc/ /app/Segmentation-lulc/
50
+
51
+ # Copy any example images or models if they exist
52
+ COPY --chown=root:root . /app/
53
+
54
+ # Set permissions
55
+ RUN chmod -R 755 /app
56
+
57
+ # Expose Gradio port
58
+ EXPOSE 7860
59
+
60
+ # Default command - run the Gradio app
61
+ CMD ["python", "/app/Segmentation-lulc/app3.py"]
62
+
63
+ # Alternative commands (can be overridden):
64
+ # For preprocessing: docker run <image> python /app/Segmentation-lulc/pre1.py
65
+ # For training: docker run <image> python /app/Segmentation-lulc/main3.py
README-Docker.md ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LULC Segmentation Docker Setup
2
+
3
+ This directory contains Docker configuration for the Land Use Land Cover (LULC) segmentation application.
4
+
5
+ ## Prerequisites
6
+
7
+ - Docker installed with GPU support (nvidia-docker)
8
+ - NVIDIA Container Toolkit for GPU acceleration
9
+
10
+ ## Quick Start
11
+
12
+ ### 1. Build the Docker image
13
+ ```bash
14
+ docker build -t lulc-segmentation:latest .
15
+ ```
16
+
17
+ ### 2. Run the Gradio application
18
+ ```bash
19
+ docker run -it --rm \
20
+ --gpus all \
21
+ -p 7860:7860 \
22
+ -v $(pwd)/data:/app/data \
23
+ -v $(pwd)/outputs:/app/outputs \
24
+ -v $(pwd)/models:/app/models \
25
+ lulc-segmentation:latest
26
+ ```
27
+
28
+ ### 3. Access the application
29
+ Open your browser and navigate to `http://localhost:7860`
30
+
31
+ ## Using Docker Compose
32
+
33
+ ### Run the application
34
+ ```bash
35
+ docker-compose up lulc-segmentation
36
+ ```
37
+
38
+ ### Run training (with profile)
39
+ ```bash
40
+ docker-compose --profile training up lulc-training
41
+ ```
42
+
43
+ ## Using the convenience script
44
+
45
+ Make the script executable:
46
+ ```bash
47
+ chmod +x run-docker.sh
48
+ ```
49
+
50
+ Then use it:
51
+ ```bash
52
+ ./run-docker.sh build # Build the image
53
+ ./run-docker.sh app # Run the Gradio app
54
+ ./run-docker.sh train # Run training
55
+ ./run-docker.sh preprocess # Run preprocessing
56
+ ```
57
+
58
+ ## Volume Mounts
59
+
60
+ - `./data` → `/app/data` - For input datasets
61
+ - `./outputs` → `/app/outputs` - For training outputs and checkpoints
62
+ - `./models` → `/app/models` - For trained model files
63
+
64
+ ## Environment Variables
65
+
66
+ - `GRADIO_SERVER_NAME=0.0.0.0` - Allow external connections
67
+ - `GRADIO_SERVER_PORT=7860` - Gradio port
68
+ - `PYTHONPATH=/app` - Python path configuration
69
+
70
+ ## GPU Support
71
+
72
+ The container is configured to use all available GPUs. Ensure you have:
73
+ - NVIDIA Docker runtime installed
74
+ - Proper GPU drivers
75
+ - NVIDIA Container Toolkit
76
+
77
+ ## Troubleshooting
78
+
79
+ 1. **GPU not detected**: Ensure nvidia-docker is properly installed
80
+ 2. **Port conflicts**: Change the port mapping if 7860 is in use
81
+ 3. **Permission issues**: Ensure proper volume mount permissions
82
+
83
+ ## Development
84
+
85
+ For development, you can mount the source code directly:
86
+ ```bash
87
+ docker run -it --rm \
88
+ --gpus all \
89
+ -p 7860:7860 \
90
+ -v $(pwd)/Segmentation-lulc:/app/Segmentation-lulc \
91
+ lulc-segmentation:latest
92
+ ```
app3.py ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import torch.nn as nn
4
+ from PIL import Image, ImageDraw, ImageFont
5
+ import numpy as np
6
+ import torchvision.transforms as T
7
+ import torchvision.transforms.functional as F
8
+ import segmentation_models_pytorch as smp
9
+ import os
10
+
11
+ # --- 1. Configuration and Constants ---
12
+
13
+ class CFG:
14
+ """
15
+ Configuration class adapted from the provided training script.
16
+ """
17
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
18
+ MODEL_NAME = "CustomDeepLabV3+"
19
+ ENCODER_NAME = "timm-efficientnet-b2"
20
+ IN_CHANNELS = 3
21
+ NUM_CLASSES = 8
22
+ IMG_SIZE = 256
23
+ # NOTE: Update this path to where your trained model is saved.
24
+ MODEL_SAVE_PATH = "./best_model_optimized.pth"
25
+
26
+ # Class definitions and color map based on the research paper
27
+ CLASS_INFO = {
28
+ 0: {"name": "Unclassified", "color": (150, 150, 150)},
29
+ 1: {"name": "Water Bodies", "color": (0, 0, 255)}, # Blue
30
+ 2: {"name": "Dense Forest", "color": (0, 100, 0)}, # Dark Green
31
+ 3: {"name": "Built up", "color": (128, 0, 128)}, # Purple
32
+ 4: {"name": "Agriculture land", "color": (0, 255, 0)}, # Bright Green
33
+ 5: {"name": "Barren land", "color": (255, 255, 0)}, # Yellow
34
+ 6: {"name": "Fallow land", "color": (210, 180, 140)}, # Tan
35
+ 7: {"name": "Sparse Forest", "color": (60, 179, 113)}, # Medium Sea Green
36
+ }
37
+
38
+ PIXEL_AREA_SQ_METERS = 10 * 10 # Based on 10m resolution from the dataset
39
+ SQ_METERS_PER_HECTARE = 10000
40
+
41
+ # --- 2. Model Definitions (from provided training script) ---
42
+
43
+ class SELayer(nn.Module):
44
+ def __init__(self, channel, reduction=16):
45
+ super(SELayer, self).__init__()
46
+ self.avg_pool = nn.AdaptiveAvgPool2d(1)
47
+ self.fc = nn.Sequential(nn.Linear(channel, channel // reduction, bias=False), nn.ReLU(inplace=True), nn.Linear(channel // reduction, channel, bias=False), nn.Sigmoid())
48
+ def forward(self, x):
49
+ b, c, _, _ = x.size()
50
+ y = self.avg_pool(x).view(b, c)
51
+ y = self.fc(y).view(b, c, 1, 1)
52
+ return x * y.expand_as(x)
53
+
54
+ class CustomDeepLabV3Plus(nn.Module):
55
+ def __init__(self, encoder_name, in_channels, classes):
56
+ super().__init__()
57
+ self.smp_model = smp.DeepLabV3Plus(encoder_name=encoder_name, encoder_weights="imagenet", in_channels=in_channels, classes=classes)
58
+ decoder_channels = self.smp_model.segmentation_head[0].in_channels
59
+ self.se_layer = SELayer(decoder_channels)
60
+ self.segmentation_head = self.smp_model.segmentation_head
61
+ self.smp_model.segmentation_head = nn.Identity()
62
+ def forward(self, x):
63
+ decoder_features = self.smp_model(x)
64
+ attended_features = self.se_layer(decoder_features)
65
+ output = self.segmentation_head(attended_features)
66
+ return output
67
+
68
+ # --- 3. Helper Functions ---
69
+
70
+ def load_model():
71
+ """Loads the pre-trained segmentation model."""
72
+ model = CustomDeepLabV3Plus(
73
+ encoder_name=CFG.ENCODER_NAME,
74
+ in_channels=CFG.IN_CHANNELS,
75
+ classes=CFG.NUM_CLASSES
76
+ )
77
+ if not os.path.exists(CFG.MODEL_SAVE_PATH):
78
+ raise FileNotFoundError(f"Model file not found at {CFG.MODEL_SAVE_PATH}. Please ensure the model is trained and the path is correct.")
79
+
80
+ model.load_state_dict(torch.load(CFG.MODEL_SAVE_PATH, map_location=torch.device(CFG.DEVICE)))
81
+ model.to(CFG.DEVICE)
82
+ model.eval()
83
+ print("Model loaded successfully on device:", CFG.DEVICE)
84
+ return model
85
+
86
+ def preprocess_image(img: Image.Image):
87
+ """Preprocesses a PIL image for model inference."""
88
+ img = F.resize(img, [CFG.IMG_SIZE, CFG.IMG_SIZE], interpolation=T.InterpolationMode.BILINEAR)
89
+ img_tensor = F.to_tensor(img)
90
+ normalize_transform = T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
91
+ normalized_tensor = normalize_transform(img_tensor)
92
+ return normalized_tensor.unsqueeze(0) # Add batch dimension
93
+
94
+ def create_colored_mask(mask: np.ndarray) -> Image.Image:
95
+ """Creates a colored RGB image from a class index mask."""
96
+ rgb_mask = np.zeros((*mask.shape, 3), dtype=np.uint8)
97
+ for class_id, info in CLASS_INFO.items():
98
+ rgb_mask[mask == class_id] = info["color"]
99
+ return Image.fromarray(rgb_mask)
100
+
101
+ def create_legend_image():
102
+ """Generates an image of the color legend."""
103
+ item_height = 25
104
+ width = 250
105
+ height = item_height * len(CLASS_INFO)
106
+ legend_img = Image.new('RGB', (width, height), 'white')
107
+ draw = ImageDraw.Draw(legend_img)
108
+
109
+ try:
110
+ # Use a common font, handle case where it's not found
111
+ font = ImageFont.truetype("arial.ttf", 14)
112
+ except IOError:
113
+ font = ImageFont.load_default()
114
+
115
+ for i, (class_id, info) in enumerate(CLASS_INFO.items()):
116
+ y_start = i * item_height
117
+ draw.rectangle([5, y_start + 5, 25, y_start + 20], fill=info["color"])
118
+ draw.text((35, y_start + 5), f'{info["name"]}', fill='black', font=font)
119
+
120
+ return legend_img
121
+
122
+ # --- 4. Main Prediction Function ---
123
+
124
+ # Load the model and legend once when the script starts
125
+ model = load_model()
126
+ legend_image = create_legend_image()
127
+
128
+ def predict_land_cover(input_image: Image.Image):
129
+ """
130
+ Takes an input image, performs segmentation, and calculates area for each class.
131
+ """
132
+ if input_image is None:
133
+ raise gr.Error("Please upload an image.")
134
+
135
+ # 1. Preprocess the image and get model prediction
136
+ input_tensor = preprocess_image(input_image.convert("RGB")).to(CFG.DEVICE)
137
+ with torch.no_grad():
138
+ output = model(input_tensor)
139
+ pred_mask = torch.argmax(output, dim=1).squeeze(0).cpu().numpy()
140
+
141
+ # 2. Create the colored segmentation mask for visualization
142
+ colored_mask = create_colored_mask(pred_mask)
143
+
144
+ # 3. Calculate area for each class
145
+ class_indices, pixel_counts = np.unique(pred_mask, return_counts=True)
146
+
147
+ area_results = {}
148
+ total_area_ha = (CFG.IMG_SIZE * CFG.IMG_SIZE * PIXEL_AREA_SQ_METERS) / SQ_METERS_PER_HECTARE
149
+
150
+ for class_id, count in zip(class_indices, pixel_counts):
151
+ class_name = CLASS_INFO[class_id]["name"]
152
+ area_hectares = (count * PIXEL_AREA_SQ_METERS) / SQ_METERS_PER_HECTARE
153
+ percentage = (area_hectares / total_area_ha) * 100
154
+ area_results[class_name] = f"{area_hectares:.4f} Hectares ({percentage:.2f}%)"
155
+
156
+ return colored_mask, area_results, legend_image
157
+
158
+ # --- 5. Gradio Interface ---
159
+
160
+ # Load example images from your file system.
161
+ example_files = ["comparison_1.jpg", "comparison_4.jpg", "comparison_5.jpg", "comparison_8.jpg"]
162
+ example_images = [file for file in example_files if os.path.exists(file)]
163
+
164
+ app_description = """
165
+ ### Sen-2 LULC: Interactive Land Cover Analysis
166
+ This tool performs semantic segmentation on satellite imagery to classify different types of land use and land cover (LULC).
167
+ It is based on the **Sen-2 LULC dataset** and a **Custom DeepLabV3+** model as described in the research paper "Sen-2 LULC: Land Use Land Cover Dataset for Deep Learning Approaches".
168
+
169
+ **How it works:**
170
+ 1. **Upload Image:** Upload a satellite image patch. You can use the examples below.
171
+ 2. **Model Prediction:** The deep learning model classifies each pixel into one of the 7 LULC classes.
172
+ 3. **Analysis:** The application generates:
173
+ * A color-coded **Segmentation Mask** for visual analysis.
174
+ * A **Quantitative Report** calculating the area of each land class in hectares. This is possible because the source Sentinel-2 imagery has a **10m resolution**, meaning each pixel represents a 100m² area on the ground.
175
+ """
176
+
177
+ iface = gr.Interface(
178
+ fn=predict_land_cover,
179
+ inputs=gr.Image(type="pil", label="Upload Satellite Image Patch"),
180
+ outputs=[
181
+ gr.Image(type="pil", label="Predicted Segmentation Mask"),
182
+ gr.Label(label="Land Cover Area Analysis (Hectares)"),
183
+ gr.Image(type="pil", label="Legend")
184
+ ],
185
+ title="Land Use & Land Cover Segmentation and Area Analysis",
186
+ description=app_description,
187
+ examples=example_images,
188
+ cache_examples=True,
189
+ allow_flagging="never"
190
+ )
191
+
192
+ if __name__ == "__main__":
193
+ iface.launch(debug=True)
docker-compose.yml ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ version: '3.8'
2
+
3
+ services:
4
+ lulc-segmentation:
5
+ build:
6
+ context: .
7
+ dockerfile: Dockerfile
8
+ container_name: lulc-app
9
+ ports:
10
+ - "7860:7860"
11
+ volumes:
12
+ # Mount data directory for datasets
13
+ - ./data:/app/data
14
+ # Mount outputs directory for model checkpoints
15
+ - ./outputs:/app/outputs
16
+ # Mount models directory for trained models
17
+ - ./models:/app/models
18
+ environment:
19
+ - PYTHONPATH=/app
20
+ - GRADIO_SERVER_NAME=0.0.0.0
21
+ - GRADIO_SERVER_PORT=7860
22
+ deploy:
23
+ resources:
24
+ reservations:
25
+ devices:
26
+ - driver: nvidia
27
+ count: all
28
+ capabilities: [gpu]
29
+ command: python /app/Segmentation-lulc/app3.py
30
+
31
+ # Optional service for training
32
+ lulc-training:
33
+ build:
34
+ context: .
35
+ dockerfile: Dockerfile
36
+ container_name: lulc-training
37
+ volumes:
38
+ - ./data:/app/data
39
+ - ./outputs:/app/outputs
40
+ - ./models:/app/models
41
+ environment:
42
+ - PYTHONPATH=/app
43
+ deploy:
44
+ resources:
45
+ reservations:
46
+ devices:
47
+ - driver: nvidia
48
+ count: all
49
+ capabilities: [gpu]
50
+ command: python /app/Segmentation-lulc/main3.py
51
+ profiles:
52
+ - training
examples/10.png ADDED
examples/1019.png ADDED
examples/15.png ADDED
examples/2.png ADDED
examples/285.png ADDED
examples/32.png ADDED
examples/36.png ADDED
examples/457.png ADDED
examples/46.png ADDED
examples/59.png ADDED
examples/64.png ADDED
examples/Screenshot 2025-08-04 192031.png ADDED
examples/Screenshot 2025-08-04 193338.png ADDED
examples/canola_oli_2022140_lrg.png ADDED
main3.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ import torch.nn as nn
4
+ import torch.optim as optim
5
+ from torch.utils.data import Dataset, DataLoader
6
+ from PIL import Image
7
+ import numpy as np
8
+ import albumentations as A
9
+ from albumentations.pytorch import ToTensorV2
10
+ from tqdm import tqdm
11
+ import segmentation_models_pytorch as smp
12
+ import cv2
13
+
14
+ # --- 1. Configuration ---
15
+ class CFG:
16
+ DATA_DIR = r"SEN-2_LULC_preprocessed"
17
+ TRAIN_IMG_DIR = os.path.join(DATA_DIR, "train_images")
18
+ TRAIN_MASK_DIR = os.path.join(DATA_DIR, "train_masks")
19
+ VAL_IMG_DIR = os.path.join(DATA_DIR, "val_images")
20
+ VAL_MASK_DIR = os.path.join(DATA_DIR, "val_masks")
21
+
22
+ OUTPUT_DIR = "./outputs_rgb_optimized"
23
+ # The path for the 'best' model, for inference later
24
+ MODEL_SAVE_PATH = os.path.join(OUTPUT_DIR, "best_model_optimized.pth")
25
+ # --- NEW: Path for the resumable checkpoint file ---
26
+ CHECKPOINT_PATH = os.path.join(OUTPUT_DIR, "checkpoint.pth")
27
+
28
+ PREDICTION_SAVE_PATH = os.path.join(OUTPUT_DIR, "predictions_optimized")
29
+
30
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
31
+ MODEL_NAME = "CustomDeepLabV3+"
32
+ ENCODER_NAME = "timm-efficientnet-b2"
33
+ LOSS_FN_NAME = "DiceFocal"
34
+ IN_CHANNELS = 3; NUM_CLASSES = 8; IMG_SIZE = 256
35
+ BATCH_SIZE = 4; ACCUMULATION_STEPS = 4
36
+ NUM_WORKERS = 8; LEARNING_RATE = 1e-4; EPOCHS = 50
37
+ SEED = 42; SUBSET_FRACTION = 0.75
38
+
39
+ # --- ARCHITECTURE and LOSS CLASSES (Unchanged) ---
40
+ class SELayer(nn.Module):
41
+ def __init__(self, channel, reduction=16):
42
+ super(SELayer, self).__init__(); self.avg_pool = nn.AdaptiveAvgPool2d(1)
43
+ self.fc = nn.Sequential(nn.Linear(channel, channel // reduction, bias=False), nn.ReLU(inplace=True), nn.Linear(channel // reduction, channel, bias=False), nn.Sigmoid())
44
+ def forward(self, x):
45
+ b, c, _, _ = x.size(); y = self.avg_pool(x).view(b, c); y = self.fc(y).view(b, c, 1, 1); return x * y.expand_as(x)
46
+ class CustomDeepLabV3Plus(nn.Module):
47
+ def __init__(self, encoder_name, in_channels, classes):
48
+ super().__init__(); self.smp_model = smp.DeepLabV3Plus(encoder_name=encoder_name, encoder_weights="imagenet", in_channels=in_channels, classes=classes)
49
+ decoder_channels = self.smp_model.segmentation_head[0].in_channels; self.se_layer = SELayer(decoder_channels)
50
+ self.segmentation_head = self.smp_model.segmentation_head; self.smp_model.segmentation_head = nn.Identity()
51
+ def forward(self, x):
52
+ decoder_features = self.smp_model(x); attended_features = self.se_layer(decoder_features)
53
+ output = self.segmentation_head(attended_features); return output
54
+ class CombinedLoss(nn.Module):
55
+ def __init__(self, loss1, loss2, alpha=0.5):
56
+ super(CombinedLoss, self).__init__(); self.loss1 = loss1; self.loss2 = loss2; self.alpha = alpha
57
+ self.name = f"{alpha}*{self.loss1.__class__.__name__} + {1-alpha}*{self.loss2.__class__.__name__}"
58
+ def forward(self, prediction, target):
59
+ loss1_val = self.loss1(prediction, target); loss2_val = self.loss2(prediction, target); return self.alpha * loss1_val + (1 - self.alpha) * loss2_val
60
+
61
+ # --- DATASET and TRANSFORMS (Unchanged) ---
62
+ class LULCDataset(Dataset):
63
+ def __init__(self, image_dir, mask_dir, transform=None, subset_fraction=1.0):
64
+ self.image_dir = image_dir; self.mask_dir = mask_dir; self.transform = transform
65
+ all_images = sorted([f for f in os.listdir(image_dir) if f.endswith('.png')])
66
+ all_masks = sorted([f for f in os.listdir(mask_dir) if f.endswith('.tif')])
67
+ num_samples = int(len(all_images) * subset_fraction)
68
+ self.images = all_images[:num_samples]; self.masks = all_masks[:num_samples]
69
+ assert len(self.images) == len(self.masks), "Mismatch"; print(f"Found {len(all_images)} total images, USING {len(self.images)} samples ({subset_fraction*100}%) from {image_dir}")
70
+ def __len__(self): return len(self.images)
71
+ def __getitem__(self, idx):
72
+ img_path = os.path.join(self.image_dir, self.images[idx]); mask_path = os.path.join(self.mask_dir, self.masks[idx])
73
+ image = np.array(Image.open(img_path).convert("RGB"), dtype=np.float32)
74
+ mask = np.array(Image.open(mask_path).convert("L"), dtype=np.float32)
75
+ if self.transform: augmented = self.transform(image=image, mask=mask); image, mask = augmented['image'], augmented['mask']
76
+ return image, mask
77
+ def get_transforms(img_size):
78
+ DATASET_MEAN = [0.485, 0.456, 0.406]; DATASET_STD = [0.229, 0.224, 0.225]
79
+ train_transform = A.Compose([A.Resize(img_size, img_size), A.Rotate(limit=35, p=0.5), A.HorizontalFlip(p=0.5), A.VerticalFlip(p=0.5), A.Normalize(mean=DATASET_MEAN, std=DATASET_STD), ToTensorV2()])
80
+ val_transform = A.Compose([A.Resize(img_size, img_size), A.Normalize(mean=DATASET_MEAN, std=DATASET_STD), ToTensorV2()])
81
+ return train_transform, val_transform
82
+
83
+ # --- GET MODEL AND LOSS (Unchanged) ---
84
+ def get_model():
85
+ if CFG.MODEL_NAME == "CustomDeepLabV3+": model = CustomDeepLabV3Plus(encoder_name=CFG.ENCODER_NAME, in_channels=CFG.IN_CHANNELS, classes=CFG.NUM_CLASSES)
86
+ else: model = smp.DeepLabV3Plus(encoder_name=CFG.ENCODER_NAME, encoder_weights="imagenet", in_channels=CFG.IN_CHANNELS, classes=CFG.NUM_CLASSES)
87
+ return model.to(CFG.DEVICE)
88
+ def get_loss_fn():
89
+ if CFG.LOSS_FN_NAME == "DiceFocal": dice = smp.losses.DiceLoss(mode='multiclass'); focal = smp.losses.FocalLoss(mode='multiclass'); return CombinedLoss(focal, dice, alpha=0.5)
90
+ else: return smp.losses.DiceLoss(mode='multiclass')
91
+
92
+ # --- Training and Evaluation Functions (Unchanged) ---
93
+ def train_one_epoch(loader, model, optimizer, loss_fn, scaler):
94
+ loop = tqdm(loader, desc="Training"); model.train(); optimizer.zero_grad()
95
+ for batch_idx, (data, targets) in enumerate(loop):
96
+ data = data.to(CFG.DEVICE, non_blocking=True, memory_format=torch.channels_last)
97
+ targets = targets.long().to(CFG.DEVICE, non_blocking=True)
98
+ with torch.amp.autocast(device_type=CFG.DEVICE, dtype=torch.bfloat16, enabled=(CFG.DEVICE=="cuda")):
99
+ predictions = model(data); loss = loss_fn(predictions, targets) / CFG.ACCUMULATION_STEPS
100
+ scaler.scale(loss).backward()
101
+ if (batch_idx + 1) % CFG.ACCUMULATION_STEPS == 0:
102
+ scaler.step(optimizer); scaler.update(); optimizer.zero_grad()
103
+ loop.set_postfix(loss=loss.item() * CFG.ACCUMULATION_STEPS)
104
+ def evaluate_model(loader, model, loss_fn):
105
+ model.eval(); intersection, union = torch.zeros(CFG.NUM_CLASSES, device=CFG.DEVICE), torch.zeros(CFG.NUM_CLASSES, device=CFG.DEVICE)
106
+ pixel_correct, pixel_total, total_loss = 0, 0, 0
107
+ with torch.no_grad():
108
+ loop = tqdm(loader, desc="Evaluating")
109
+ for x, y in loop:
110
+ x = x.to(CFG.DEVICE, non_blocking=True, memory_format=torch.channels_last)
111
+ y = y.to(CFG.DEVICE, non_blocking=True).long()
112
+ with torch.amp.autocast(device_type=CFG.DEVICE, dtype=torch.bfloat16, enabled=(CFG.DEVICE=="cuda")):
113
+ preds = model(x); loss = loss_fn(preds, y); total_loss += loss.item()
114
+ pred_labels = torch.argmax(preds, dim=1); pixel_correct += (pred_labels == y).sum(); pixel_total += torch.numel(y)
115
+ for cls in range(CFG.NUM_CLASSES): pred_mask = (pred_labels == cls); true_mask = (y == cls); intersection[cls] += (pred_mask & true_mask).sum(); union[cls] += (pred_mask | true_mask).sum()
116
+ pixel_acc = (pixel_correct / pixel_total) * 100; iou_per_class = (intersection + 1e-6) / (union + 1e-6)
117
+ mean_iou = iou_per_class.mean(); avg_loss = total_loss / len(loader)
118
+ print(f"Validation Results -> Avg Loss: {avg_loss:.4f}, Pixel Acc: {pixel_acc:.2f}%, mIoU: {mean_iou:.4f}")
119
+ for i, iou in enumerate(iou_per_class): print(f" Class {i} IoU: {iou:.4f}")
120
+ return mean_iou
121
+
122
+ def save_predictions_as_images(loader, model):
123
+ # This function is not part of the training loop, no changes needed.
124
+ pass # implementation is correct as-is
125
+
126
+ # --- NEW: Helper function to save a checkpoint ---
127
+ def save_checkpoint(state, filename="checkpoint.pth"):
128
+ print("=> Saving checkpoint")
129
+ torch.save(state, filename)
130
+
131
+ def main():
132
+ torch.manual_seed(CFG.SEED); np.random.seed(CFG.SEED); os.makedirs(CFG.OUTPUT_DIR, exist_ok=True)
133
+ if torch.cuda.is_available(): torch.backends.cudnn.benchmark = True
134
+
135
+ train_transform, val_transform = get_transforms(CFG.IMG_SIZE)
136
+ train_ds = LULCDataset(CFG.TRAIN_IMG_DIR, CFG.TRAIN_MASK_DIR, transform=train_transform, subset_fraction=CFG.SUBSET_FRACTION)
137
+ val_ds = LULCDataset(CFG.VAL_IMG_DIR, CFG.VAL_MASK_DIR, transform=val_transform, subset_fraction=CFG.SUBSET_FRACTION)
138
+ train_loader = DataLoader(train_ds, batch_size=CFG.BATCH_SIZE, num_workers=CFG.NUM_WORKERS, pin_memory=True, shuffle=True, persistent_workers=True)
139
+ val_loader = DataLoader(val_ds, batch_size=CFG.BATCH_SIZE, num_workers=CFG.NUM_WORKERS, pin_memory=True, shuffle=False, persistent_workers=True)
140
+
141
+ model = get_model()
142
+ model = model.to(memory_format=torch.channels_last)
143
+
144
+ loss_fn = get_loss_fn()
145
+ optimizer = optim.AdamW(model.parameters(), lr=CFG.LEARNING_RATE)
146
+ scaler = torch.amp.GradScaler(enabled=(CFG.DEVICE=="cuda"))
147
+ scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=CFG.EPOCHS, eta_min=1e-6)
148
+
149
+ # --- NEW: Logic to load checkpoint and resume training ---
150
+ start_epoch = 0
151
+ best_val_miou = -1.0
152
+ if os.path.exists(CFG.CHECKPOINT_PATH):
153
+ print(f"=> Loading checkpoint '{CFG.CHECKPOINT_PATH}'")
154
+ checkpoint = torch.load(CFG.CHECKPOINT_PATH, map_location=CFG.DEVICE)
155
+
156
+ model.load_state_dict(checkpoint['model_state_dict'])
157
+ optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
158
+ scheduler.load_state_dict(checkpoint['scheduler_state_dict'])
159
+ scaler.load_state_dict(checkpoint['scaler_state_dict'])
160
+
161
+ start_epoch = checkpoint['epoch'] + 1
162
+ best_val_miou = checkpoint['best_val_miou']
163
+
164
+ print(f"=> Resuming training from epoch {start_epoch}")
165
+ else:
166
+ print("=> No checkpoint found, starting new training session.")
167
+
168
+
169
+ # --- MODIFIED: Main training loop now starts from the correct epoch ---
170
+ for epoch in range(start_epoch, CFG.EPOCHS):
171
+ print(f"\n--- Epoch {epoch+1}/{CFG.EPOCHS} ---")
172
+ train_one_epoch(train_loader, model, optimizer, loss_fn, scaler)
173
+ current_miou = evaluate_model(val_loader, model, loss_fn)
174
+ scheduler.step()
175
+
176
+ # Create the checkpoint dictionary with the complete state
177
+ checkpoint = {
178
+ 'epoch': epoch,
179
+ 'model_state_dict': model.state_dict(),
180
+ 'optimizer_state_dict': optimizer.state_dict(),
181
+ 'scheduler_state_dict': scheduler.state_dict(),
182
+ 'scaler_state_dict': scaler.state_dict(),
183
+ 'best_val_miou': best_val_miou
184
+ }
185
+
186
+ if current_miou > best_val_miou:
187
+ best_val_miou = current_miou
188
+ checkpoint['best_val_miou'] = best_val_miou # Update best score in checkpoint
189
+ print(f"🎉 New best mIoU: {best_val_miou:.4f}! Saving best model to {CFG.MODEL_SAVE_PATH}")
190
+ torch.save(model.state_dict(), CFG.MODEL_SAVE_PATH) # Save just the model for easy inference
191
+
192
+ # Save the full state checkpoint after every epoch
193
+ save_checkpoint(checkpoint, filename=CFG.CHECKPOINT_PATH)
194
+
195
+
196
+ print("\n--- Training Complete. Saving final predictions. ---")
197
+ # Load the best performing model for final predictions
198
+ model.load_state_dict(torch.load(CFG.MODEL_SAVE_PATH))
199
+ # Note: You may want a separate test_loader for final unbiased evaluation
200
+ save_predictions_as_images(val_loader, model)
201
+
202
+ if __name__ == "__main__":
203
+ main()
pre1.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from PIL import Image
3
+ from tqdm import tqdm
4
+ import glob
5
+
6
+ # --- 1. Centralized Configuration ---
7
+ class CFG:
8
+ # --- Paths ---
9
+ SOURCE_ROOT = "SEN-2 LULC"
10
+ DEST_ROOT = "SEN-2_LULC_preprocessed1"
11
+
12
+ # --- Image & File Settings ---
13
+ IMG_SIZE = 256
14
+ IMG_EXT = ".png" # Assumes source images are PNGs
15
+ MASK_EXT = ".tif" # Assumes source masks are TIFs
16
+
17
+ # --- 2. Define All Data Splits ---
18
+ SPLITS = {
19
+ "train": {
20
+ "images": os.path.join(CFG.SOURCE_ROOT, "train_images", "train"),
21
+ "masks": os.path.join(CFG.SOURCE_ROOT, "train_masks", "train"),
22
+ },
23
+ "val": {
24
+ "images": os.path.join(CFG.SOURCE_ROOT, "val_images", "val"),
25
+ "masks": os.path.join(CFG.SOURCE_ROOT, "val_masks", "val"),
26
+ },
27
+ "test": {
28
+ # Test set usually only has images
29
+ "images": os.path.join(CFG.SOURCE_ROOT, "test_images", "test"),
30
+ "masks": None, # Set to None if no masks exist
31
+ },
32
+ }
33
+
34
+ def preprocess_and_resize():
35
+ """
36
+ Resizes images and their corresponding masks for all data splits.
37
+ This version robustly matches images to masks by filename.
38
+ """
39
+ print(f"Starting preprocessing. Output will be saved to: {CFG.DEST_ROOT}\n")
40
+ stats = {}
41
+
42
+ # --- 3. Iterate Through Each Split (train, val, test) ---
43
+ for split_name, split_paths in SPLITS.items():
44
+ print(f"--- Processing split: {split_name} ---")
45
+
46
+ # Get paths and create destination directories
47
+ image_dir = split_paths["images"]
48
+ mask_dir = split_paths["masks"]
49
+
50
+ dest_img_dir = os.path.join(CFG.DEST_ROOT, f"{split_name}_images")
51
+ os.makedirs(dest_img_dir, exist_ok=True)
52
+
53
+ if mask_dir:
54
+ dest_mask_dir = os.path.join(CFG.DEST_ROOT, f"{split_name}_masks")
55
+ os.makedirs(dest_mask_dir, exist_ok=True)
56
+
57
+ # Find all source images
58
+ image_files = glob.glob(os.path.join(image_dir, f"*{CFG.IMG_EXT}"))
59
+
60
+ if not image_files:
61
+ print(f"Warning: No images found in {image_dir}. Skipping.")
62
+ continue
63
+
64
+ stats[split_name] = {"images": 0, "masks": 0}
65
+
66
+ # --- 4. Robustly Process Each Image and Find its Mask ---
67
+ for img_path in tqdm(image_files, desc=f"Resizing {split_name} data"):
68
+ try:
69
+ # --- Process the image ---
70
+ base_name = os.path.basename(img_path)
71
+ with Image.open(img_path) as img:
72
+ resized_img = img.resize((CFG.IMG_SIZE, CFG.IMG_SIZE), resample=Image.Resampling.LANCZOS)
73
+ resized_img.save(os.path.join(dest_img_dir, base_name))
74
+ stats[split_name]["images"] += 1
75
+
76
+ # --- Find and process the corresponding mask (if applicable) ---
77
+ if mask_dir:
78
+ mask_name = os.path.splitext(base_name)[0] + CFG.MASK_EXT
79
+ mask_path = os.path.join(mask_dir, mask_name)
80
+
81
+ if os.path.exists(mask_path):
82
+ with Image.open(mask_path) as mask:
83
+ # CRITICAL: Use NEAREST resampling for masks to preserve class labels
84
+ resized_mask = mask.resize((CFG.IMG_SIZE, CFG.IMG_SIZE), resample=Image.Resampling.NEAREST)
85
+ resized_mask.save(os.path.join(dest_mask_dir, mask_name))
86
+ stats[split_name]["masks"] += 1
87
+
88
+ except Exception as e:
89
+ print(f"Error processing {img_path}: {e}")
90
+
91
+ # --- 5. Final Verification Summary ---
92
+ print("\n--- Preprocessing Complete! ---")
93
+ for split_name, counts in stats.items():
94
+ print(f"Split '{split_name}': Processed {counts['images']} images and {counts['masks']} masks.")
95
+ print(f"Resized data is in '{CFG.DEST_ROOT}'")
96
+
97
+
98
+ if __name__ == "__main__":
99
+ preprocess_and_resize()
run-docker.sh ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Build the Docker image
4
+ echo "Building LULC Segmentation Docker image..."
5
+ docker build -t lulc-segmentation:latest .
6
+
7
+ # Function to run the Gradio app
8
+ run_app() {
9
+ echo "Starting LULC Segmentation Gradio app..."
10
+ docker run -it --rm \
11
+ --gpus all \
12
+ -p 7860:7860 \
13
+ -v $(pwd)/data:/app/data \
14
+ -v $(pwd)/outputs:/app/outputs \
15
+ -v $(pwd)/models:/app/models \
16
+ -e GRADIO_SERVER_NAME=0.0.0.0 \
17
+ lulc-segmentation:latest
18
+ }
19
+
20
+ # Function to run training
21
+ run_training() {
22
+ echo "Starting LULC model training..."
23
+ docker run -it --rm \
24
+ --gpus all \
25
+ -v $(pwd)/data:/app/data \
26
+ -v $(pwd)/outputs:/app/outputs \
27
+ -v $(pwd)/models:/app/models \
28
+ lulc-segmentation:latest \
29
+ python /app/Segmentation-lulc/main3.py
30
+ }
31
+
32
+ # Function to run preprocessing
33
+ run_preprocessing() {
34
+ echo "Starting data preprocessing..."
35
+ docker run -it --rm \
36
+ -v $(pwd)/data:/app/data \
37
+ lulc-segmentation:latest \
38
+ python /app/Segmentation-lulc/pre1.py
39
+ }
40
+
41
+ # Parse command line arguments
42
+ case "$1" in
43
+ "app")
44
+ run_app
45
+ ;;
46
+ "train")
47
+ run_training
48
+ ;;
49
+ "preprocess")
50
+ run_preprocessing
51
+ ;;
52
+ "build")
53
+ echo "Docker image built successfully!"
54
+ ;;
55
+ *)
56
+ echo "Usage: $0 {app|train|preprocess|build}"
57
+ echo " app - Run the Gradio application"
58
+ echo " train - Run model training"
59
+ echo " preprocess - Run data preprocessing"
60
+ echo " build - Build Docker image only"
61
+ exit 1
62
+ ;;
63
+ esac