From 2264eab709b418354ded6c0cebab2154c15b4ffe Mon Sep 17 00:00:00 2001 From: Nils Lehmann Date: Mon, 15 May 2023 14:03:28 +0200 Subject: [PATCH 1/4] outline downstream dataset --- .../ssl4eo_downstream_landsat/l7-l1/data.py | 0 .../ssl4eo_downstream_landsat/l7-l2/data.py | 0 .../ssl4eo_downstream_landsat/l8-2/data.py | 0 .../ssl4eo_downstream_landsat/l8-l1/data.py | 0 .../test_ssl4eo_downstream_landsat.py | 71 +++++++ torchgeo/datasets/__init__.py | 2 + .../datasets/ssl4eo_downstream_landsat.py | 179 ++++++++++++++++++ 7 files changed, 252 insertions(+) create mode 100644 tests/data/ssl4eo_downstream_landsat/l7-l1/data.py create mode 100644 tests/data/ssl4eo_downstream_landsat/l7-l2/data.py create mode 100644 tests/data/ssl4eo_downstream_landsat/l8-2/data.py create mode 100644 tests/data/ssl4eo_downstream_landsat/l8-l1/data.py create mode 100644 tests/datasets/test_ssl4eo_downstream_landsat.py create mode 100644 torchgeo/datasets/ssl4eo_downstream_landsat.py diff --git a/tests/data/ssl4eo_downstream_landsat/l7-l1/data.py b/tests/data/ssl4eo_downstream_landsat/l7-l1/data.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/data/ssl4eo_downstream_landsat/l7-l2/data.py b/tests/data/ssl4eo_downstream_landsat/l7-l2/data.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/data/ssl4eo_downstream_landsat/l8-2/data.py b/tests/data/ssl4eo_downstream_landsat/l8-2/data.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/data/ssl4eo_downstream_landsat/l8-l1/data.py b/tests/data/ssl4eo_downstream_landsat/l8-l1/data.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/datasets/test_ssl4eo_downstream_landsat.py b/tests/datasets/test_ssl4eo_downstream_landsat.py new file mode 100644 index 00000000000..a8a35f5de10 --- /dev/null +++ b/tests/datasets/test_ssl4eo_downstream_landsat.py @@ -0,0 +1,71 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os +import shutil +from itertools import product +from pathlib import Path + +import matplotlib.pyplot as plt +import pytest +import torch +import torch.nn as nn +from _pytest.fixtures import SubRequest +from pytest import MonkeyPatch +from torch.utils.data import ConcatDataset + +import torchgeo.datasets.utils +from torchgeo.datasets import SSL4EODownstream + + +def download_url(url: str, root: str, *args: str, **kwargs: str) -> None: + shutil.copy(url, root) + + +class TestSSL4EODownstream: + @pytest.fixture( + params=product( + ["l7-l1", "l7-l2", "l8-l1", "l8-l2"], + ["cdl", "nlcd"], + ["train", "val", "test"], + ) + ) + def dataset( + self, monkeypatch: MonkeyPatch, tmp_path: Path, request: SubRequest + ) -> SSL4EODownstream: + input_sensor, mask_product, split = request.param + monkeypatch.setattr(torchgeo.datasets.eurosat, "download_url", download_url) + + root = str(tmp_path) + transforms = nn.Identity() + return SSL4EODownstream( + root=root, + input_sensor=input_sensor, + mask_product=mask_product, + split=split, + download=True, + checksum=True, + transforms=transforms, + ) + + def test_getitem(self, dataset: SSL4EODownstream) -> None: + x = dataset[0] + assert isinstance(x, dict) + assert isinstance(x["image"], torch.Tensor) + assert isinstance(x["mask"], torch.Tensor) + + def test_invalid_split(self) -> None: + with pytest.raises(AssertionError): + SSL4EODownstream(split="foo") + + def test_invalid_input_sensor(self) -> None: + with pytest.raises(AssertionError): + SSL4EODownstream(split="foo") + + def test_invalid_mask_product(self) -> None: + with pytest.raises(AssertionError): + SSL4EODownstream(split="foo") + + def test_add(self, dataset: SSL4EODownstream) -> None: + ds = dataset + dataset + assert isinstance(ds, ConcatDataset) diff --git a/torchgeo/datasets/__init__.py b/torchgeo/datasets/__init__.py index 92734304a48..4f12d46978a 100644 --- a/torchgeo/datasets/__init__.py +++ b/torchgeo/datasets/__init__.py @@ -105,6 +105,7 @@ time_series_split, ) from .ssl4eo import SSL4EO, SSL4EOL, SSL4EOS12 +from .ssl4eo_downstream_landsat import SSL4EODownstream from .sustainbench_crop_yield import SustainBenchCropYield from .ucmerced import UCMerced from .usavars import USAVars @@ -210,6 +211,7 @@ "SpaceNet6", "SpaceNet7", "SSL4EO", + "SSL4EODownstream", "SSL4EOL", "SSL4EOS12", "SustainBenchCropYield", diff --git a/torchgeo/datasets/ssl4eo_downstream_landsat.py b/torchgeo/datasets/ssl4eo_downstream_landsat.py new file mode 100644 index 00000000000..9340c9fc595 --- /dev/null +++ b/torchgeo/datasets/ssl4eo_downstream_landsat.py @@ -0,0 +1,179 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +"""Self-Supervised Learning for Earth Observation Downstream Evaluation.""" + +import glob +import os +import random +from typing import Callable, Optional + +import matplotlib.pyplot as plt +import numpy as np +import rasterio +import torch +from torch import Tensor + +from .geo import NonGeoDataset +from .utils import check_integrity, extract_archive + + +class SSL4EODownstream(NonGeoDataset): + """SSL4EO Downstream Evaluation Dataset. + + Dataset is intended to be used for evaluation of SSL techniques. + + Dataset format: + + * input landsat image and single channel mask + + Each patch has the following properties: + + * 264 x 264 pixels + * Resampled to 30 m resolution (7920 x 7920 m) + * Single multispectral GeoTIFF file + + """ + + valid_input_sensors = ["l7-l1", "l7-l2", "l8-l1", "l8-l2"] + valid_mask_products = ["cdl", "ncdl"] + valid_splits = ["train", "val", "test"] + + data_root = "ssl4eo-*-conus" + + def __init__( + self, + root: str = "data", + input_sensor: str = "l7-l1", + mask_product: str = "cdl", + split: str = "train", + transforms: Optional[Callable[[dict[str, Tensor]], dict[str, Tensor]]] = None, + download: bool = False, + checksum: bool = False, + ) -> None: + """Initialize a new SSL4EODownstream instance. + + Args: + root: root directory where dataset can be found + input_sensor: input sensor source, one of ['l7-l1', 'l7-l2', 'l8-l1, 'l8-l2'] + mask_product: mask target matched to input_sensor + transforms: a function/transform that takes input sample and its target as + entry and returns a transformed version + download: if True, download dataset and store it in the root directory + checksum: if True, check the MD5 after downloading files (may be slow) + + Raises: + AssertionError: if ``input_sensor`` argument is invalid + AssertionError: if ``mask_product`` argument is invalid + AssertionError: if ``split`` argument is invalid + """ + assert ( + input_sensor in self.valid_input_sensors + ), f"Only supports one of {self.valid_input_sensors}, but found {input_sensor}." + self.input_sensor = input_sensor + assert ( + mask_product in self.valid_mask_products + ), f"Only supports one of {self.valid_mask_products}, but found {mask_product}." + self.mask_product = mask_product + assert ( + split in self.valid_splits + ), f"Only supports one of {self.valid_splits}, but found {split}." + self.split = split + + self.root = root + self.transforms = transforms + self.download = download + self.checksum = checksum + + self._verify() + + self.sample_collection = self.retrieve_sample_collection() + + def __getitem__(self, index: int) -> dict[str, Tensor]: + """Return an index within the dataset. + + Args: + index: index to return + + Returns: + image and sample + """ + img_path, mask_path = self.sample_collection[index] + + sample = { + "image": self._load_image(img_path), + "mask": self._load_mask(mask_path), + } + + if self.transforms is not None: + sample = self.transforms(sample) + + return sample + + def __len__(self) -> int: + """Return the number of data points in the dataset. + + Returns: + length of the dataset + """ + return len(self.sample_collection) + + def _verify(self) -> None: + """Verify the integrity of the dataset. + + Raises: + RuntimeError: if dataset is missing or checksum fails + """ + pass + + def _download(self) -> None: + """Download the dataset.""" + pass + + def _extract(self) -> None: + """Extract the dataset.""" + pass + + def retrieve_sample_collection(self) -> tuple[str]: + """Retrieve paths to samples in data directory.""" + data_dir = self.data_root.replace("*", self.input_sensor) + img_paths = sorted( + glob.glob( + os.path.join(self.root, data_dir, "imgs", "**", "**", "all_bands.tif"), + recursive=True, + ) + ) + sample_collection = [ + ( + img_path, + img_path.replace("imgs", "masks").replace("all_bands.tif", "mask.tif"), + ) + for img_path in img_paths + ] + return sample_collection + + def _load_image(self, path: str) -> Tensor: + """Load the input image. + + Args: + path: path to input image + + Returns: + image + """ + with rasterio.open(path) as src: + image = src.read().astype(np.float32) + return torch.from_numpy(image) + + def _load_mask(self, path: str) -> Tensor: + """Load the mask. + + Args: + path: path to mask + + Retuns: + mask + """ + with rasterio.open(path) as src: + image = src.read() + return torch.from_numpy(image).long() From f65654445e1d595719be8c23e61b51757d737e3c Mon Sep 17 00:00:00 2001 From: Nils Lehmann Date: Mon, 15 May 2023 14:08:54 +0200 Subject: [PATCH 2/4] revert commit --- .../ssl4eo_downstream_landsat/l7-l1/data.py | 0 .../ssl4eo_downstream_landsat/l7-l2/data.py | 0 .../ssl4eo_downstream_landsat/l8-2/data.py | 0 .../ssl4eo_downstream_landsat/l8-l1/data.py | 0 .../test_ssl4eo_downstream_landsat.py | 71 ------- torchgeo/datasets/__init__.py | 2 - .../datasets/ssl4eo_downstream_landsat.py | 179 ------------------ 7 files changed, 252 deletions(-) delete mode 100644 tests/data/ssl4eo_downstream_landsat/l7-l1/data.py delete mode 100644 tests/data/ssl4eo_downstream_landsat/l7-l2/data.py delete mode 100644 tests/data/ssl4eo_downstream_landsat/l8-2/data.py delete mode 100644 tests/data/ssl4eo_downstream_landsat/l8-l1/data.py delete mode 100644 tests/datasets/test_ssl4eo_downstream_landsat.py delete mode 100644 torchgeo/datasets/ssl4eo_downstream_landsat.py diff --git a/tests/data/ssl4eo_downstream_landsat/l7-l1/data.py b/tests/data/ssl4eo_downstream_landsat/l7-l1/data.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/data/ssl4eo_downstream_landsat/l7-l2/data.py b/tests/data/ssl4eo_downstream_landsat/l7-l2/data.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/data/ssl4eo_downstream_landsat/l8-2/data.py b/tests/data/ssl4eo_downstream_landsat/l8-2/data.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/data/ssl4eo_downstream_landsat/l8-l1/data.py b/tests/data/ssl4eo_downstream_landsat/l8-l1/data.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/tests/datasets/test_ssl4eo_downstream_landsat.py b/tests/datasets/test_ssl4eo_downstream_landsat.py deleted file mode 100644 index a8a35f5de10..00000000000 --- a/tests/datasets/test_ssl4eo_downstream_landsat.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import os -import shutil -from itertools import product -from pathlib import Path - -import matplotlib.pyplot as plt -import pytest -import torch -import torch.nn as nn -from _pytest.fixtures import SubRequest -from pytest import MonkeyPatch -from torch.utils.data import ConcatDataset - -import torchgeo.datasets.utils -from torchgeo.datasets import SSL4EODownstream - - -def download_url(url: str, root: str, *args: str, **kwargs: str) -> None: - shutil.copy(url, root) - - -class TestSSL4EODownstream: - @pytest.fixture( - params=product( - ["l7-l1", "l7-l2", "l8-l1", "l8-l2"], - ["cdl", "nlcd"], - ["train", "val", "test"], - ) - ) - def dataset( - self, monkeypatch: MonkeyPatch, tmp_path: Path, request: SubRequest - ) -> SSL4EODownstream: - input_sensor, mask_product, split = request.param - monkeypatch.setattr(torchgeo.datasets.eurosat, "download_url", download_url) - - root = str(tmp_path) - transforms = nn.Identity() - return SSL4EODownstream( - root=root, - input_sensor=input_sensor, - mask_product=mask_product, - split=split, - download=True, - checksum=True, - transforms=transforms, - ) - - def test_getitem(self, dataset: SSL4EODownstream) -> None: - x = dataset[0] - assert isinstance(x, dict) - assert isinstance(x["image"], torch.Tensor) - assert isinstance(x["mask"], torch.Tensor) - - def test_invalid_split(self) -> None: - with pytest.raises(AssertionError): - SSL4EODownstream(split="foo") - - def test_invalid_input_sensor(self) -> None: - with pytest.raises(AssertionError): - SSL4EODownstream(split="foo") - - def test_invalid_mask_product(self) -> None: - with pytest.raises(AssertionError): - SSL4EODownstream(split="foo") - - def test_add(self, dataset: SSL4EODownstream) -> None: - ds = dataset + dataset - assert isinstance(ds, ConcatDataset) diff --git a/torchgeo/datasets/__init__.py b/torchgeo/datasets/__init__.py index 4f12d46978a..92734304a48 100644 --- a/torchgeo/datasets/__init__.py +++ b/torchgeo/datasets/__init__.py @@ -105,7 +105,6 @@ time_series_split, ) from .ssl4eo import SSL4EO, SSL4EOL, SSL4EOS12 -from .ssl4eo_downstream_landsat import SSL4EODownstream from .sustainbench_crop_yield import SustainBenchCropYield from .ucmerced import UCMerced from .usavars import USAVars @@ -211,7 +210,6 @@ "SpaceNet6", "SpaceNet7", "SSL4EO", - "SSL4EODownstream", "SSL4EOL", "SSL4EOS12", "SustainBenchCropYield", diff --git a/torchgeo/datasets/ssl4eo_downstream_landsat.py b/torchgeo/datasets/ssl4eo_downstream_landsat.py deleted file mode 100644 index 9340c9fc595..00000000000 --- a/torchgeo/datasets/ssl4eo_downstream_landsat.py +++ /dev/null @@ -1,179 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -"""Self-Supervised Learning for Earth Observation Downstream Evaluation.""" - -import glob -import os -import random -from typing import Callable, Optional - -import matplotlib.pyplot as plt -import numpy as np -import rasterio -import torch -from torch import Tensor - -from .geo import NonGeoDataset -from .utils import check_integrity, extract_archive - - -class SSL4EODownstream(NonGeoDataset): - """SSL4EO Downstream Evaluation Dataset. - - Dataset is intended to be used for evaluation of SSL techniques. - - Dataset format: - - * input landsat image and single channel mask - - Each patch has the following properties: - - * 264 x 264 pixels - * Resampled to 30 m resolution (7920 x 7920 m) - * Single multispectral GeoTIFF file - - """ - - valid_input_sensors = ["l7-l1", "l7-l2", "l8-l1", "l8-l2"] - valid_mask_products = ["cdl", "ncdl"] - valid_splits = ["train", "val", "test"] - - data_root = "ssl4eo-*-conus" - - def __init__( - self, - root: str = "data", - input_sensor: str = "l7-l1", - mask_product: str = "cdl", - split: str = "train", - transforms: Optional[Callable[[dict[str, Tensor]], dict[str, Tensor]]] = None, - download: bool = False, - checksum: bool = False, - ) -> None: - """Initialize a new SSL4EODownstream instance. - - Args: - root: root directory where dataset can be found - input_sensor: input sensor source, one of ['l7-l1', 'l7-l2', 'l8-l1, 'l8-l2'] - mask_product: mask target matched to input_sensor - transforms: a function/transform that takes input sample and its target as - entry and returns a transformed version - download: if True, download dataset and store it in the root directory - checksum: if True, check the MD5 after downloading files (may be slow) - - Raises: - AssertionError: if ``input_sensor`` argument is invalid - AssertionError: if ``mask_product`` argument is invalid - AssertionError: if ``split`` argument is invalid - """ - assert ( - input_sensor in self.valid_input_sensors - ), f"Only supports one of {self.valid_input_sensors}, but found {input_sensor}." - self.input_sensor = input_sensor - assert ( - mask_product in self.valid_mask_products - ), f"Only supports one of {self.valid_mask_products}, but found {mask_product}." - self.mask_product = mask_product - assert ( - split in self.valid_splits - ), f"Only supports one of {self.valid_splits}, but found {split}." - self.split = split - - self.root = root - self.transforms = transforms - self.download = download - self.checksum = checksum - - self._verify() - - self.sample_collection = self.retrieve_sample_collection() - - def __getitem__(self, index: int) -> dict[str, Tensor]: - """Return an index within the dataset. - - Args: - index: index to return - - Returns: - image and sample - """ - img_path, mask_path = self.sample_collection[index] - - sample = { - "image": self._load_image(img_path), - "mask": self._load_mask(mask_path), - } - - if self.transforms is not None: - sample = self.transforms(sample) - - return sample - - def __len__(self) -> int: - """Return the number of data points in the dataset. - - Returns: - length of the dataset - """ - return len(self.sample_collection) - - def _verify(self) -> None: - """Verify the integrity of the dataset. - - Raises: - RuntimeError: if dataset is missing or checksum fails - """ - pass - - def _download(self) -> None: - """Download the dataset.""" - pass - - def _extract(self) -> None: - """Extract the dataset.""" - pass - - def retrieve_sample_collection(self) -> tuple[str]: - """Retrieve paths to samples in data directory.""" - data_dir = self.data_root.replace("*", self.input_sensor) - img_paths = sorted( - glob.glob( - os.path.join(self.root, data_dir, "imgs", "**", "**", "all_bands.tif"), - recursive=True, - ) - ) - sample_collection = [ - ( - img_path, - img_path.replace("imgs", "masks").replace("all_bands.tif", "mask.tif"), - ) - for img_path in img_paths - ] - return sample_collection - - def _load_image(self, path: str) -> Tensor: - """Load the input image. - - Args: - path: path to input image - - Returns: - image - """ - with rasterio.open(path) as src: - image = src.read().astype(np.float32) - return torch.from_numpy(image) - - def _load_mask(self, path: str) -> Tensor: - """Load the mask. - - Args: - path: path to mask - - Retuns: - mask - """ - with rasterio.open(path) as src: - image = src.read() - return torch.from_numpy(image).long() From fbf871a7a25851c41cbfb7f5627047d8b1f1c32c Mon Sep 17 00:00:00 2001 From: Nils Lehmann Date: Wed, 24 May 2023 17:39:14 +0200 Subject: [PATCH 3/4] add ordinal label mapping to cdl --- torchgeo/datasets/cdl.py | 164 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 1 deletion(-) diff --git a/torchgeo/datasets/cdl.py b/torchgeo/datasets/cdl.py index 6c2c46f977d..31f0b8addde 100644 --- a/torchgeo/datasets/cdl.py +++ b/torchgeo/datasets/cdl.py @@ -12,7 +12,7 @@ from rasterio.crs import CRS from .geo import RasterDataset -from .utils import download_url, extract_archive +from .utils import BoundingBox, download_url, extract_archive class CDL(RasterDataset): @@ -28,6 +28,9 @@ class CDL(RasterDataset): annually using moderate resolution satellite imagery and extensive agricultural ground truth. + The dataset contains 133 classes, for a description of the classes see the + xls file at the top of `this page `_. + If you use this dataset in your research, please cite it using the following format: * https://www.nass.usda.gov/Research_and_Science/Cropland/sarsfaqs2.php#Section1_14.0 @@ -320,6 +323,143 @@ class CDL(RasterDataset): 255: (0, 0, 0, 255), } + ordinal_label_map = { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 10: 7, + 11: 8, + 12: 9, + 13: 10, + 14: 11, + 21: 12, + 22: 13, + 23: 14, + 24: 15, + 25: 16, + 26: 17, + 27: 18, + 28: 19, + 29: 20, + 30: 21, + 31: 22, + 32: 23, + 33: 24, + 34: 25, + 35: 26, + 36: 27, + 37: 28, + 38: 29, + 39: 30, + 41: 31, + 42: 32, + 43: 33, + 44: 34, + 45: 35, + 46: 36, + 47: 37, + 48: 38, + 49: 39, + 50: 40, + 51: 41, + 52: 42, + 53: 43, + 54: 44, + 55: 45, + 56: 46, + 57: 47, + 58: 48, + 59: 49, + 60: 50, + 61: 51, + 63: 52, + 64: 53, + 65: 54, + 66: 55, + 67: 56, + 68: 57, + 69: 58, + 70: 59, + 71: 60, + 72: 61, + 74: 62, + 75: 63, + 76: 64, + 77: 65, + 81: 66, + 82: 67, + 83: 68, + 87: 69, + 88: 70, + 92: 71, + 111: 72, + 112: 73, + 121: 74, + 122: 75, + 123: 76, + 124: 77, + 131: 78, + 141: 79, + 142: 80, + 143: 81, + 152: 82, + 176: 83, + 190: 84, + 195: 85, + 204: 86, + 205: 87, + 206: 88, + 207: 89, + 208: 90, + 209: 91, + 210: 92, + 211: 93, + 212: 94, + 213: 95, + 214: 96, + 215: 97, + 216: 98, + 217: 99, + 218: 100, + 219: 101, + 220: 102, + 221: 103, + 222: 104, + 223: 105, + 224: 106, + 225: 107, + 226: 108, + 227: 109, + 228: 110, + 229: 111, + 230: 112, + 231: 113, + 232: 114, + 233: 115, + 234: 116, + 235: 117, + 236: 118, + 237: 119, + 238: 120, + 239: 121, + 240: 122, + 241: 123, + 242: 124, + 243: 125, + 244: 126, + 245: 127, + 246: 128, + 247: 129, + 248: 130, + 249: 131, + 250: 132, + 254: 133, + } + def __init__( self, root: str = "data", @@ -356,6 +496,28 @@ def __init__( super().__init__(root, crs, res, transforms=transforms, cache=cache) + def __getitem__(self, query: BoundingBox) -> dict[str, Any]: + """Retrieve mask and metadata indexed by query. + + Args: + query: (minx, maxx, miny, maxy, mint, maxt) coordinates to index + + Returns: + sample of mask and metadata at that index + + Raises: + IndexError: if query is not found in the index + """ + sample = super().__getitem__(query) + + mask = sample["mask"] + for k, v in self.ordinal_label_map.items(): + mask[mask == k] = v + + sample["mask"] = mask + + return sample + def _verify(self) -> None: """Verify the integrity of the dataset. From 0e91dc95da13cc605d295643ef01c69b1f7c6c57 Mon Sep 17 00:00:00 2001 From: Nils Lehmann Date: Wed, 24 May 2023 17:52:52 +0200 Subject: [PATCH 4/4] change docstring --- torchgeo/datasets/cdl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/torchgeo/datasets/cdl.py b/torchgeo/datasets/cdl.py index 31f0b8addde..156df07143b 100644 --- a/torchgeo/datasets/cdl.py +++ b/torchgeo/datasets/cdl.py @@ -28,8 +28,9 @@ class CDL(RasterDataset): annually using moderate resolution satellite imagery and extensive agricultural ground truth. - The dataset contains 133 classes, for a description of the classes see the - xls file at the top of `this page `_. + The dataset contains 134 classes, for a description of the classes see the + xls file at the top of + `this page `_. If you use this dataset in your research, please cite it using the following format: