<kaggle_start><data_title>Danish Golf Courses Orthophotos<data_description>## Context:
This dataset contains 1123 orthophotos of Danish golf courses during spring with a scale of 1:1000 and a resolution of 1600x900 pixels. The orthophotos are captured from 107 different Danish golf courses, where each orthophoto captures a broad portion of the physical layout and features of the golf course. The images can contain between 1 to 4 golf holes each with some remaining parts of other converging holes.

### Content:
The data has been annotated to form the ground truth for semantic segmentation using CVAT.
The different classes annotated are:

- Background
- Fairway
- Green
- Tee
- Bunker
- Water

This dataset is divided into 3 folders:
1. Orthophotos: RGB images 
2. Segmentation masks: annotated images
3. Class masks: annotated images where each pixel value is between 0 to 5 depending on the class<data_name>danish-golf-courses-orthophotos
<code>import os
import numpy as np
import pytorch_lightning as pl
import torch
import torchmetrics
from torch import nn
import torch.nn.functional as F
from torch.utils.data import random_split, DataLoader, Dataset
from torchvision import transforms
from torchvision.io import read_image
import torchvision.transforms as T
import torchvision.transforms.functional as TF

# Plotting images
from PIL import Image
import matplotlib.pyplot as plt

# Hyperparameters
BATCH_SIZE = 16
IMAGE_SIZE = (256, 256)  # Images get resized to a smaller resolution
IN_CHANNELS = 3  # There are 3 channels for RGB
IMAGES_DIR = "/kaggle/input/danish-golf-courses-orthophotos/1. orthophotos/"
SEGMASKS_DIR = "/kaggle/input/danish-golf-courses-orthophotos/2. segmentation masks/"
LABELMASKS_DIR = "/kaggle/input/danish-golf-courses-orthophotos/3. class masks/"


# A Dataset class is created to load
class GolfDataset(Dataset):
    def __init__(self, images_dir, labelmasks_dir, segmasks_dir):
        self.images_dir = images_dir
        self.labelmasks_dir = labelmasks_dir
        self.images_dir_list = os.listdir(
            images_dir
        )  # We create a list PATHs to every file in the orthophotos directory.

    def __len__(self):
        # return 10
        return len(self.images_dir_list)

    def __getitem__(self, idx):
        image_path = os.path.join(self.images_dir, self.images_dir_list[idx])
        image = read_image(image_path)
        image = image.float()
        labelmask_path = os.path.join(
            self.labelmasks_dir, self.images_dir_list[idx]
        ).replace(
            ".jpg", ".png"
        )  # The class masks are png instead of jpg
        labelmask = read_image(labelmask_path)
        # Apply transformations to the images
        image = TF.resize(image, IMAGE_SIZE)  # Apply resize transform
        labelmask = TF.resize(labelmask, IMAGE_SIZE)  # Apply resize transform
        labelmask = TF.rgb_to_grayscale(
            labelmask
        )  # Apply grayscaling to go from 3->1 channels.
        labelmask = labelmask.float()
        return image, labelmask


# Loading the data
orthophoto_list = os.listdir(IMAGES_DIR)
# Load image with index of 5 (I prefer this image as it shows all the classes)
idx = 5
golf_image = Image.open(os.path.join(IMAGES_DIR, orthophoto_list[idx]))
golf_segmask = Image.open(
    os.path.join(SEGMASKS_DIR, orthophoto_list[idx].replace(".jpg", ".png"))
)  # The class masks are png instead of jpg
# Plot using matplotlib
fig, axes = plt.subplots(1, 2)
axes[0].set_title("Orthophoto")
axes[1].set_title("Segmentation Mask")
axes[0].imshow(golf_image)
axes[1].imshow(golf_segmask)


class GolfDataModule(pl.LightningDataModule):
    def __init__(self, batch_size):
        super().__init__()
        self.batch_size = batch_size
        self.all_images = []

    def prepare_data(self):
        # We don't use this function for loading the data as
        # WARNING
        # prepare_data is called from a single GPU. Do not use it to assign state (self.x = y).
        pass

    def setup(self, stage=None):
        # Data is loaded from the image and mask directories
        self.all_images = GolfDataset(IMAGES_DIR, LABELMASKS_DIR, SEGMASKS_DIR)
        # The data is split into train, val and test with a 70/20/10 split
        self.train_data, self.val_data, self.test_data = random_split(
            self.all_images, [0.7, 0.2, 0.1]
        )

    def train_dataloader(self):
        return DataLoader(self.train_data, batch_size=self.batch_size)

    def val_dataloader(self):
        return DataLoader(self.val_data, batch_size=self.batch_size)

    def test_dataloader(self):
        return DataLoader(self.test_data, batch_size=self.batch_size)


# Visualize the data in the golf dataset.
# A random orthophoto is shown along with its mask.
class UNetModel(pl.LightningModule):
    def __init__(self):
        super().__init__()

        def double_conv(in_channels, out_channels):
            return nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
                nn.ReLU(),
            )

        def down(in_channels, out_channels):
            return nn.Sequential(
                nn.MaxPool2d(kernel_size=2), double_conv(in_channels, out_channels)
            )

        def up(in_channels, out_channels):
            return nn.Sequential(
                nn.Upsample(scale_factor=2, mode="bilinear", align_corners=True),
                double_conv(in_channels, out_channels),
            )

        # The encoder part of UNet
        self.l1 = double_conv(3, 64)
        self.l2 = down(64, 128)
        self.l3 = down(128, 256)
        self.l4 = down(256, 512)
        self.l5 = down(512, 1024)
        # The decoder part of UNet
        self.l6 = up(1024, 512)
        self.l7 = up(512, 256)
        self.l8 = up(256, 128)
        self.l9 = up(128, 64)
        self.l10 = double_conv(64, 64)
        # Final (1x1) convolution to go from 64 -> 6 channels as we have 6 classes
        self.final = nn.Conv2d(64, 6, kernel_size=1)
        self.loss_fn = nn.CrossEntropyLoss()
        self.softmax = nn.Softmax2d()
        self.train_acc = torchmetrics.Accuracy(task="multiclass", num_classes=6)
        self.valid_acc = torchmetrics.Accuracy(task="multiclass", num_classes=6)

    def prepare_prediction_tensor(self, pred):
        pred = self.softmax(pred)
        pred = torch.argmax(pred, dim=1)
        pred = pred.float()
        pred = pred.unsqueeze(1)
        pred.requires_grad_()
        return pred

    def forward(self, x):
        x1 = self.l1(x)
        x2 = self.l2(x1)
        x3 = self.l3(x2)
        x4 = self.l4(x3)
        x5 = self.l5(x4)
        x6 = self.l6(x5)
        x7 = self.l7(x6)
        x8 = self.l8(x7)
        x9 = self.l9(x8)
        x10 = self.l10(x9)
        return self.final(x10)

    def training_step(self, batch, batch_idx):
        x, y = batch
        y_pred = self.forward(x)
        y_pred = self.prepare_prediction_tensor(y_pred)
        loss = self.loss_fn(y_pred, y)
        self.train_acc(y_pred, y)
        self.log("train_acc", self.train_acc, on_step=True, on_epoch=False)
        return loss

    def training_epoch_end(self, outs):
        pass

    def validation_step(self, batch, batch_idx):
        x, y = batch
        y_pred = self.forward(x)
        y_pred = self.prepare_prediction_tensor(y_pred)
        loss = self.loss_fn(y_pred, y)
        self.valid_acc(y_pred, y)
        self.log("valid_acc", self.valid_acc, on_step=True, on_epoch=True)
        return loss

    def save_predictions_as_imgs(
        self, x, y, preds, folder="/kaggle/working/", counter=0, names=[]
    ):
        x = x.to(self.device)
        y = y.to(self.device)
        preds = preds.to(self.device)
        class_to_color = [
            torch.tensor([0.0, 0.0, 0.0], device=self.device),
            torch.tensor([0.0, 140.0 / 255, 0.0], device=self.device),
            torch.tensor([0.0, 1.0, 0.0], device=self.device),
            torch.tensor([1.0, 0.0, 0.0], device=self.device),
            torch.tensor([217.0 / 255, 230.0 / 255, 122.0 / 255], device=self.device),
            torch.tensor([7.0 / 255, 15.0 / 255, 247.0 / 255], device=self.device),
        ]
        output = torch.zeros(
            preds.shape[0], 3, preds.size(-2), preds.size(-1), dtype=torch.float
        )  # Output size is set to preds.shape[0] as the size automatically changes to fit the remaining batch_size.
        for class_idx, color in enumerate(class_to_color):
            mask = preds[:, class_idx, :, :] == torch.max(preds, dim=1)[0]
            mask = mask.unsqueeze(1)
            curr_color = color.reshape(1, 3, 1, 1)
            segment = mask * curr_color
            output += segment
        y_output = torch.zeros(
            y.shape[0],
            3,
            preds.size(-2),
            preds.size(-1),
            dtype=torch.float,
            device=self.device,
        )
        for class_idx, color in enumerate(class_to_color):
            mask = y[:, :, :] == class_idx
            mask = mask.unsqueeze(1)
            # print("mask shape", mask.shape)
            curr_color = color.reshape(1, 3, 1, 1)
            segment = mask * curr_color
            y_output += segment
        # Save images to our saved_images folder
        torchvision.utils.save_image(output, f"{folder}/{idx+1}_prediction.png")
        # if counter == 0: #Only save original and grountruth on the first tine (no reason to re-save it if they dont change)
        torchvision.utils.save_image(y_output, f"{folder}/{idx+1}_groundtruth.png")
        torchvision.utils.save_image(x, f"{folder}/{idx+1}_figure_.png")

    def configure_optimizers(self):
        return torch.optim.AdamW(self.parameters(), lr=0.001)


train_loader = GolfDataModule(BATCH_SIZE)
trainer = pl.Trainer(max_epochs=100, accelerator="gpu", devices=2)
model = UNetModel()
trainer.fit(model, train_loader)
<|endoftext|>
[{"Id": 121873780, "ScriptId": 35882420, "ParentScriptVersionId": NaN, "ScriptLanguageId": 9, "AuthorUserId": 12923738, "CreationDate": "03/12/2023 14:06:11", "VersionNumber": 6.0, "Title": "HEJsa eftggfs", "EvaluationDate": "03/12/2023", "IsChange": false, "TotalLines": 250.0, "LinesInsertedFromPrevious": 0.0, "LinesChangedFromPrevious": 0.0, "LinesUnchangedFromPrevious": 250.0, "LinesInsertedFromFork": NaN, "LinesDeletedFromFork": NaN, "LinesChangedFromFork": NaN, "LinesUnchangedFromFork": NaN, "TotalVotes": 0}]
[{"Id": 174360921, "KernelVersionId": 121873780, "SourceDatasetVersionId": 4727518}]
[{"Id": 4727518, "DatasetId": 2735624, "DatasourceVersionId": 4790362, "CreatorUserId": 6407631, "LicenseName": "Database: Open Database, Contents: \u00a9 Original Authors", "CreationDate": "12/15/2022 13:54:25", "VersionNumber": 1.0, "Title": "Danish Golf Courses Orthophotos", "Slug": "danish-golf-courses-orthophotos", "Subtitle": "1123 orthophotos of Danish golf courses", "Description": "## Context:\nThis dataset contains 1123 orthophotos of Danish golf courses during spring with a scale of 1:1000 and a resolution of 1600x900 pixels. The orthophotos are captured from 107 different Danish golf courses, where each orthophoto captures a broad portion of the physical layout and features of the golf course. The images can contain between 1 to 4 golf holes each with some remaining parts of other converging holes.\n\n### Content:\nThe data has been annotated to form the ground truth for semantic segmentation using CVAT.\nThe different classes annotated are:\n\n- Background\n- Fairway\n- Green\n- Tee\n- Bunker\n- Water\n\nThis dataset is divided into 3 folders:\n1. Orthophotos: RGB images \n2. Segmentation masks: annotated images\n3. Class masks: annotated images where each pixel value is between 0 to 5 depending on the class\n\n### Acknowledgements:\nThis data was collected from the Danish national database \u201dDataforsyningen\".", "VersionNotes": "Initial release", "TotalCompressedBytes": 0.0, "TotalUncompressedBytes": 0.0}]
[{"Id": 2735624, "CreatorUserId": 6407631, "OwnerUserId": 6407631.0, "OwnerOrganizationId": NaN, "CurrentDatasetVersionId": 4727518.0, "CurrentDatasourceVersionId": 4790362.0, "ForumId": 2768924, "Type": 2, "CreationDate": "12/15/2022 13:54:25", "LastActivityDate": "12/15/2022", "TotalViews": 1667, "TotalDownloads": 79, "TotalVotes": 23, "TotalKernels": 2}]
import os
import numpy as np
import pytorch_lightning as pl
import torch
import torchmetrics
from torch import nn
import torch.nn.functional as F
from torch.utils.data import random_split, DataLoader, Dataset
from torchvision import transforms
from torchvision.io import read_image
import torchvision.transforms as T
import torchvision.transforms.functional as TF

# Plotting images
from PIL import Image
import matplotlib.pyplot as plt

# Hyperparameters
BATCH_SIZE = 16
IMAGE_SIZE = (256, 256)  # Images get resized to a smaller resolution
IN_CHANNELS = 3  # There are 3 channels for RGB
IMAGES_DIR = "/kaggle/input/danish-golf-courses-orthophotos/1. orthophotos/"
SEGMASKS_DIR = "/kaggle/input/danish-golf-courses-orthophotos/2. segmentation masks/"
LABELMASKS_DIR = "/kaggle/input/danish-golf-courses-orthophotos/3. class masks/"


# A Dataset class is created to load
class GolfDataset(Dataset):
    def __init__(self, images_dir, labelmasks_dir, segmasks_dir):
        self.images_dir = images_dir
        self.labelmasks_dir = labelmasks_dir
        self.images_dir_list = os.listdir(
            images_dir
        )  # We create a list PATHs to every file in the orthophotos directory.

    def __len__(self):
        # return 10
        return len(self.images_dir_list)

    def __getitem__(self, idx):
        image_path = os.path.join(self.images_dir, self.images_dir_list[idx])
        image = read_image(image_path)
        image = image.float()
        labelmask_path = os.path.join(
            self.labelmasks_dir, self.images_dir_list[idx]
        ).replace(
            ".jpg", ".png"
        )  # The class masks are png instead of jpg
        labelmask = read_image(labelmask_path)
        # Apply transformations to the images
        image = TF.resize(image, IMAGE_SIZE)  # Apply resize transform
        labelmask = TF.resize(labelmask, IMAGE_SIZE)  # Apply resize transform
        labelmask = TF.rgb_to_grayscale(
            labelmask
        )  # Apply grayscaling to go from 3->1 channels.
        labelmask = labelmask.float()
        return image, labelmask


# Loading the data
orthophoto_list = os.listdir(IMAGES_DIR)
# Load image with index of 5 (I prefer this image as it shows all the classes)
idx = 5
golf_image = Image.open(os.path.join(IMAGES_DIR, orthophoto_list[idx]))
golf_segmask = Image.open(
    os.path.join(SEGMASKS_DIR, orthophoto_list[idx].replace(".jpg", ".png"))
)  # The class masks are png instead of jpg
# Plot using matplotlib
fig, axes = plt.subplots(1, 2)
axes[0].set_title("Orthophoto")
axes[1].set_title("Segmentation Mask")
axes[0].imshow(golf_image)
axes[1].imshow(golf_segmask)


class GolfDataModule(pl.LightningDataModule):
    def __init__(self, batch_size):
        super().__init__()
        self.batch_size = batch_size
        self.all_images = []

    def prepare_data(self):
        # We don't use this function for loading the data as
        # WARNING
        # prepare_data is called from a single GPU. Do not use it to assign state (self.x = y).
        pass

    def setup(self, stage=None):
        # Data is loaded from the image and mask directories
        self.all_images = GolfDataset(IMAGES_DIR, LABELMASKS_DIR, SEGMASKS_DIR)
        # The data is split into train, val and test with a 70/20/10 split
        self.train_data, self.val_data, self.test_data = random_split(
            self.all_images, [0.7, 0.2, 0.1]
        )

    def train_dataloader(self):
        return DataLoader(self.train_data, batch_size=self.batch_size)

    def val_dataloader(self):
        return DataLoader(self.val_data, batch_size=self.batch_size)

    def test_dataloader(self):
        return DataLoader(self.test_data, batch_size=self.batch_size)


# Visualize the data in the golf dataset.
# A random orthophoto is shown along with its mask.
class UNetModel(pl.LightningModule):
    def __init__(self):
        super().__init__()

        def double_conv(in_channels, out_channels):
            return nn.Sequential(
                nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1),
                nn.ReLU(),
                nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1),
                nn.ReLU(),
            )

        def down(in_channels, out_channels):
            return nn.Sequential(
                nn.MaxPool2d(kernel_size=2), double_conv(in_channels, out_channels)
            )

        def up(in_channels, out_channels):
            return nn.Sequential(
                nn.Upsample(scale_factor=2, mode="bilinear", align_corners=True),
                double_conv(in_channels, out_channels),
            )

        # The encoder part of UNet
        self.l1 = double_conv(3, 64)
        self.l2 = down(64, 128)
        self.l3 = down(128, 256)
        self.l4 = down(256, 512)
        self.l5 = down(512, 1024)
        # The decoder part of UNet
        self.l6 = up(1024, 512)
        self.l7 = up(512, 256)
        self.l8 = up(256, 128)
        self.l9 = up(128, 64)
        self.l10 = double_conv(64, 64)
        # Final (1x1) convolution to go from 64 -> 6 channels as we have 6 classes
        self.final = nn.Conv2d(64, 6, kernel_size=1)
        self.loss_fn = nn.CrossEntropyLoss()
        self.softmax = nn.Softmax2d()
        self.train_acc = torchmetrics.Accuracy(task="multiclass", num_classes=6)
        self.valid_acc = torchmetrics.Accuracy(task="multiclass", num_classes=6)

    def prepare_prediction_tensor(self, pred):
        pred = self.softmax(pred)
        pred = torch.argmax(pred, dim=1)
        pred = pred.float()
        pred = pred.unsqueeze(1)
        pred.requires_grad_()
        return pred

    def forward(self, x):
        x1 = self.l1(x)
        x2 = self.l2(x1)
        x3 = self.l3(x2)
        x4 = self.l4(x3)
        x5 = self.l5(x4)
        x6 = self.l6(x5)
        x7 = self.l7(x6)
        x8 = self.l8(x7)
        x9 = self.l9(x8)
        x10 = self.l10(x9)
        return self.final(x10)

    def training_step(self, batch, batch_idx):
        x, y = batch
        y_pred = self.forward(x)
        y_pred = self.prepare_prediction_tensor(y_pred)
        loss = self.loss_fn(y_pred, y)
        self.train_acc(y_pred, y)
        self.log("train_acc", self.train_acc, on_step=True, on_epoch=False)
        return loss

    def training_epoch_end(self, outs):
        pass

    def validation_step(self, batch, batch_idx):
        x, y = batch
        y_pred = self.forward(x)
        y_pred = self.prepare_prediction_tensor(y_pred)
        loss = self.loss_fn(y_pred, y)
        self.valid_acc(y_pred, y)
        self.log("valid_acc", self.valid_acc, on_step=True, on_epoch=True)
        return loss

    def save_predictions_as_imgs(
        self, x, y, preds, folder="/kaggle/working/", counter=0, names=[]
    ):
        x = x.to(self.device)
        y = y.to(self.device)
        preds = preds.to(self.device)
        class_to_color = [
            torch.tensor([0.0, 0.0, 0.0], device=self.device),
            torch.tensor([0.0, 140.0 / 255, 0.0], device=self.device),
            torch.tensor([0.0, 1.0, 0.0], device=self.device),
            torch.tensor([1.0, 0.0, 0.0], device=self.device),
            torch.tensor([217.0 / 255, 230.0 / 255, 122.0 / 255], device=self.device),
            torch.tensor([7.0 / 255, 15.0 / 255, 247.0 / 255], device=self.device),
        ]
        output = torch.zeros(
            preds.shape[0], 3, preds.size(-2), preds.size(-1), dtype=torch.float
        )  # Output size is set to preds.shape[0] as the size automatically changes to fit the remaining batch_size.
        for class_idx, color in enumerate(class_to_color):
            mask = preds[:, class_idx, :, :] == torch.max(preds, dim=1)[0]
            mask = mask.unsqueeze(1)
            curr_color = color.reshape(1, 3, 1, 1)
            segment = mask * curr_color
            output += segment
        y_output = torch.zeros(
            y.shape[0],
            3,
            preds.size(-2),
            preds.size(-1),
            dtype=torch.float,
            device=self.device,
        )
        for class_idx, color in enumerate(class_to_color):
            mask = y[:, :, :] == class_idx
            mask = mask.unsqueeze(1)
            # print("mask shape", mask.shape)
            curr_color = color.reshape(1, 3, 1, 1)
            segment = mask * curr_color
            y_output += segment
        # Save images to our saved_images folder
        torchvision.utils.save_image(output, f"{folder}/{idx+1}_prediction.png")
        # if counter == 0: #Only save original and grountruth on the first tine (no reason to re-save it if they dont change)
        torchvision.utils.save_image(y_output, f"{folder}/{idx+1}_groundtruth.png")
        torchvision.utils.save_image(x, f"{folder}/{idx+1}_figure_.png")

    def configure_optimizers(self):
        return torch.optim.AdamW(self.parameters(), lr=0.001)


train_loader = GolfDataModule(BATCH_SIZE)
trainer = pl.Trainer(max_epochs=100, accelerator="gpu", devices=2)
model = UNetModel()
trainer.fit(model, train_loader)
