Repository: Lightning-Universe/lightning-bolts

Issue:
Cityscapes DataModule does not work due to wrong usage of torchvision.datasets.cityscapes.Cityscapes
## 🐛 Bug

CityScapes DataModule does not work at all. It seems to be related to wrong usage of the underlying `torchvision.datasets.cityscapes.Cityscapes` api.

### To Reproduce

Please see the code sample to reproduce. It throws an error: `TypeError: __init__() got an unexpected keyword argument 'train'`
<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->


#### Code sample
<!-- Ideally attach a minimal code sample to reproduce the decried issue. 
Minimal means having the shorteest code but still preserving the bug. -->

```python
from pl_bolts.datamodules.cityscapes_datamodule import CityscapesDataModule

dm = CityscapesDataModule('./data', num_workers=0, batch_size=1)
loader = dm.train_dataloader()
batch = next(iter(loader))
```

### Expected behavior

<!-- A clear and concise description of what you expected to happen. -->
No crash and first batch returned.
### Environment

 - PyTorch Version: 1.6.0
 - OS: macOS
 - How you installed PyTorch: pip
 - Build command you used (if compiling from source): pip install `pip install https://github.com/PyTorchLightning/pytorch-lightning-bolts/archive/master.zip`
 - Python version: 3.8.5
 - CUDA/cuDNN version: irrelevant to this issue
 - GPU models and configuration: irrelevant to this issue
 - Any other relevant information:

### Additional context

<!-- Add any other context about the problem here. -->
I would be more than happy to submit a PR providing a fix for this issue.



Discussion:
Hi! thanks for your contribution!, great first issue!

Resolving patch:
diff --git a/pl_bolts/datamodules/__init__.py b/pl_bolts/datamodules/__init__.py
--- a/pl_bolts/datamodules/__init__.py
+++ b/pl_bolts/datamodules/__init__.py
@@ -83,6 +83,13 @@
 else:
     __all__ += ['VOCDetectionDataModule']
 
+try:
+    from pl_bolts.datamodules.cityscapes_datamodule import CityscapesDataModule
+except ModuleNotFoundError:  # pragma: no-cover
+    pass
+else:
+    __all__ += ['CityscapesDataModule']
+
 try:
     from pl_bolts.datasets.kitti_dataset import KittiDataset
 except ModuleNotFoundError:
diff --git a/pl_bolts/datamodules/cityscapes_datamodule.py b/pl_bolts/datamodules/cityscapes_datamodule.py
--- a/pl_bolts/datamodules/cityscapes_datamodule.py
+++ b/pl_bolts/datamodules/cityscapes_datamodule.py
@@ -23,9 +23,12 @@ class CityscapesDataModule(LightningDataModule):
 
     Standard Cityscapes, train, val, test splits and transforms
 
+    Note: You need to have downloaded the Cityscapes dataset first and provide the path to where it is saved.
+        You can download the dataset here: https://www.cityscapes-dataset.com/
+
     Specs:
         - 30 classes (road, person, sidewalk, etc...)
-        - (image, target) - image dims: (3 x 32 x 32), target dims: (3 x 32 x 32)
+        - (image, target) - image dims: (3 x 1024 x 2048), target dims: (1024 x 2048)
 
     Transforms::
 
@@ -53,6 +56,7 @@ class CityscapesDataModule(LightningDataModule):
         dm.train_transforms = ...
         dm.test_transforms = ...
         dm.val_transforms  = ...
+        dm.target_transforms = ...
     """
 
     name = 'Cityscapes'
@@ -60,18 +64,21 @@ class CityscapesDataModule(LightningDataModule):
 
     def __init__(
             self,
-            data_dir,
-            val_split=5000,
-            num_workers=16,
-            batch_size=32,
-            seed=42,
+            data_dir: str,
+            quality_mode: str = 'fine',
+            target_type: str = 'instance',
+            num_workers: int = 16,
+            batch_size: int = 32,
+            seed: int = 42,
             *args,
             **kwargs,
     ):
         """
         Args:
-            data_dir: where to save/load the data
-            val_split: how many of the training images to use for the validation split
+            data_dir: where to load the data from path, i.e. where directory leftImg8bit and gtFine or gtCoarse
+                are located
+            quality_mode: the quality mode to use, either 'fine' or 'coarse'
+            target_type: targets to use, either 'instance' or 'semantic'
             num_workers: how many workers to use for loading data
             batch_size: number of examples per training/eval step
         """
@@ -82,13 +89,17 @@ def __init__(
                 'You want to use CityScapes dataset loaded from `torchvision` which is not installed yet.'
             )
 
-        self.dims = (3, 32, 32)
-        self.DATASET = Cityscapes
+        if target_type not in ['instance', 'semantic']:
+            raise ValueError(f'Only "semantic" and "instance" target types are supported. Got {target_type}.')
+
+        self.dims = (3, 1024, 2048)
         self.data_dir = data_dir
-        self.val_split = val_split
+        self.quality_mode = quality_mode
+        self.target_type = target_type
         self.num_workers = num_workers
         self.batch_size = batch_size
         self.seed = seed
+        self.target_transforms = None
 
     @property
     def num_classes(self):
@@ -98,28 +109,23 @@ def num_classes(self):
         """
         return 30
 
-    def prepare_data(self):
-        """
-        Saves Cityscapes files to data_dir
-        """
-        self.DATASET(self.data_dir, train=True, download=True, transform=transform_lib.ToTensor(), **self.extra_args)
-        self.DATASET(self.data_dir, train=False, download=True, transform=transform_lib.ToTensor(), **self.extra_args)
-
     def train_dataloader(self):
         """
-        Cityscapes train set with removed subset to use for validation
+        Cityscapes train set
         """
-        transforms = self.default_transforms() if self.train_transforms is None else self.train_transforms
+        transforms = self.train_transforms or self.default_transforms()
+        target_transforms = self.target_transforms or self.default_target_transforms()
+
+        dataset = Cityscapes(self.data_dir,
+                             split='train',
+                             target_type=self.target_type,
+                             mode=self.quality_mode,
+                             transform=transforms,
+                             target_transform=target_transforms,
+                             **self.extra_args)
 
-        dataset = self.DATASET(self.data_dir, train=True, download=False, transform=transforms, **self.extra_args)
-        train_length = len(dataset)
-        dataset_train, _ = random_split(
-            dataset,
-            [train_length - self.val_split, self.val_split],
-            generator=torch.Generator().manual_seed(self.seed)
-        )
         loader = DataLoader(
-            dataset_train,
+            dataset,
             batch_size=self.batch_size,
             shuffle=True,
             num_workers=self.num_workers,
@@ -130,19 +136,21 @@ def train_dataloader(self):
 
     def val_dataloader(self):
         """
-        Cityscapes val set uses a subset of the training set for validation
+        Cityscapes val set
         """
-        transforms = self.default_transforms() if self.val_transforms is None else self.val_transforms
+        transforms = self.val_transforms or self.default_transforms()
+        target_transforms = self.target_transforms or self.default_target_transforms()
+
+        dataset = Cityscapes(self.data_dir,
+                             split='val',
+                             target_type=self.target_type,
+                             mode=self.quality_mode,
+                             transform=transforms,
+                             target_transform=target_transforms,
+                             **self.extra_args)
 
-        dataset = self.DATASET(self.data_dir, train=True, download=False, transform=transforms, **self.extra_args)
-        train_length = len(dataset)
-        _, dataset_val = random_split(
-            dataset,
-            [train_length - self.val_split, self.val_split],
-            generator=torch.Generator().manual_seed(self.seed)
-        )
         loader = DataLoader(
-            dataset_val,
+            dataset,
             batch_size=self.batch_size,
             shuffle=False,
             num_workers=self.num_workers,
@@ -153,11 +161,18 @@ def val_dataloader(self):
 
     def test_dataloader(self):
         """
-        Cityscapes test set uses the test split
+        Cityscapes test set
         """
-        transforms = self.default_transforms() if self.test_transforms is None else self.test_transforms
-
-        dataset = self.DATASET(self.data_dir, train=False, download=False, transform=transforms, **self.extra_args)
+        transforms = self.test_transforms or self.default_transforms()
+        target_transforms = self.target_transforms or self.default_target_transforms()
+
+        dataset = Cityscapes(self.data_dir,
+                             split='test',
+                             target_type=self.target_type,
+                             mode=self.quality_mode,
+                             transform=transforms,
+                             target_transform=target_transforms,
+                             **self.extra_args)
         loader = DataLoader(
             dataset,
             batch_size=self.batch_size,
@@ -177,3 +192,10 @@ def default_transforms(self):
             )
         ])
         return cityscapes_transforms
+
+    def default_target_transforms(self):
+        cityscapes_target_trasnforms = transform_lib.Compose([
+            transform_lib.ToTensor(),
+            transform_lib.Lambda(lambda t: t.squeeze())
+        ])
+        return cityscapes_target_trasnforms


Test patch:
diff --git a/tests/datamodules/test_datamodules.py b/tests/datamodules/test_datamodules.py
--- a/tests/datamodules/test_datamodules.py
+++ b/tests/datamodules/test_datamodules.py
@@ -1,8 +1,62 @@
+import torch
+import uuid
+from PIL import Image
+from pathlib import Path
 from pl_bolts.datasets.cifar10_dataset import CIFAR10
+from pl_bolts.datamodules import CityscapesDataModule
 
 
 def test_dev_datasets(tmpdir):
-
     ds = CIFAR10(tmpdir)
     for b in ds:
         pass
+
+
+def _create_synth_Cityscapes_dataset(path_dir):
+    """Create synthetic dataset with random images, just to simulate that the dataset have been already downloaded."""
+    non_existing_citites = ['dummy_city_1', 'dummy_city_2']
+    fine_labels_dir = Path(path_dir) / 'gtFine'
+    images_dir = Path(path_dir) / 'leftImg8bit'
+    dataset_splits = ['train', 'val', 'test']
+
+    for split in dataset_splits:
+        for city in non_existing_citites:
+            (images_dir / split / city).mkdir(parents=True)
+            (fine_labels_dir / split / city).mkdir(parents=True)
+            base_name = str(uuid.uuid4())
+            image_name = f'{base_name}_leftImg8bit.png'
+            instance_target_name = f'{base_name}_gtFine_instanceIds.png'
+            semantic_target_name = f'{base_name}_gtFine_labelIds.png'
+            Image.new('RGB', (2048, 1024)).save(
+                images_dir / split / city / image_name)
+            Image.new('L', (2048, 1024)).save(
+                fine_labels_dir / split / city / instance_target_name)
+            Image.new('L', (2048, 1024)).save(
+                fine_labels_dir / split / city / semantic_target_name)
+
+
+def test_cityscapes_datamodule(tmpdir):
+
+    _create_synth_Cityscapes_dataset(tmpdir)
+
+    batch_size = 1
+    target_types = ['semantic', 'instance']
+    for target_type in target_types:
+        dm = CityscapesDataModule(tmpdir,
+                                  num_workers=0,
+                                  batch_size=batch_size,
+                                  target_type=target_type)
+    loader = dm.train_dataloader()
+    img, mask = next(iter(loader))
+    assert img.size() == torch.Size([batch_size, 3, 1024, 2048])
+    assert mask.size() == torch.Size([batch_size, 1024, 2048])
+
+    loader = dm.val_dataloader()
+    img, mask = next(iter(loader))
+    assert img.size() == torch.Size([batch_size, 3, 1024, 2048])
+    assert mask.size() == torch.Size([batch_size, 1024, 2048])
+
+    loader = dm.test_dataloader()
+    img, mask = next(iter(loader))
+    assert img.size() == torch.Size([batch_size, 3, 1024, 2048])
+    assert mask.size() == torch.Size([batch_size, 1024, 2048])