From fe67c0eeec2beb25428c6b8043f8255cc0144ac4 Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Tue, 28 Jan 2025 18:19:17 +0100 Subject: [PATCH 1/3] Add EnMAP dataset --- docs/api/datasets.rst | 5 + docs/api/datasets/geo_datasets.csv | 1 + ...0_20231221T134421Z-SPECTRAL_IMAGE_COG.tiff | Bin 0 -> 460677 bytes tests/data/enmap/data.py | 60 +++ tests/datasets/test_enmap.py | 70 ++++ torchgeo/datasets/__init__.py | 2 + torchgeo/datasets/enmap.py | 360 ++++++++++++++++++ 7 files changed, 498 insertions(+) create mode 100644 tests/data/enmap/ENMAP01-____L2A-DT0000001053_20220611T072305Z_002_V010400_20231221T134421Z-SPECTRAL_IMAGE_COG.tiff create mode 100755 tests/data/enmap/data.py create mode 100644 tests/datasets/test_enmap.py create mode 100644 torchgeo/datasets/enmap.py diff --git a/docs/api/datasets.rst b/docs/api/datasets.rst index d01a91dfe70..8147863f095 100644 --- a/docs/api/datasets.rst +++ b/docs/api/datasets.rst @@ -71,6 +71,11 @@ EDDMapS .. autoclass:: EDDMapS +EnMAP +^^^^^ + +.. autoclass:: EnMAP + EnviroAtlas ^^^^^^^^^^^ diff --git a/docs/api/datasets/geo_datasets.csv b/docs/api/datasets/geo_datasets.csv index 2bfafc39c39..8190c89eb54 100644 --- a/docs/api/datasets/geo_datasets.csv +++ b/docs/api/datasets/geo_datasets.csv @@ -8,6 +8,7 @@ Dataset,Type,Source,License,Size (px),Resolution (m) `Global Mangrove Distribution`_,Masks,"Remote Sensing, In Situ Measurements","public domain",-,3 `Cropland Data Layer`_,Masks,Landsat,"public domain",-,30 `EDDMapS`_,Points,Citizen Scientists,-,-,- +`EnMAP`_,Imagery,EnMAP,-,"1,200x1,200",30 `EnviroAtlas`_,"Imagery, Masks","NAIP, NLCD, OpenStreetMap","CC-BY-4.0",-,1 `Esri2020`_,Masks,Sentinel-2,"CC-BY-4.0",-,10 `EU-DEM`_,DEM,"Aster, SRTM, Russian Topomaps","CSCDA-ESA",-,25 diff --git a/tests/data/enmap/ENMAP01-____L2A-DT0000001053_20220611T072305Z_002_V010400_20231221T134421Z-SPECTRAL_IMAGE_COG.tiff b/tests/data/enmap/ENMAP01-____L2A-DT0000001053_20220611T072305Z_002_V010400_20231221T134421Z-SPECTRAL_IMAGE_COG.tiff new file mode 100644 index 0000000000000000000000000000000000000000..18671726c9fa1b0858d0a4ea228953a87206db3a GIT binary patch literal 460677 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 EnMAP: + root = os.path.join('tests', 'data', 'enmap') + transforms = nn.Identity() + return EnMAP(root, transforms=transforms) + + def test_getitem(self, dataset: EnMAP) -> None: + x = dataset[dataset.bounds] + assert isinstance(x, dict) + assert isinstance(x['crs'], CRS) + assert isinstance(x['image'], torch.Tensor) + + def test_len(self, dataset: EnMAP) -> None: + assert len(dataset) == 1 + + def test_and(self, dataset: EnMAP) -> None: + ds = dataset & dataset + assert isinstance(ds, IntersectionDataset) + + def test_or(self, dataset: EnMAP) -> None: + ds = dataset | dataset + assert isinstance(ds, UnionDataset) + + def test_plot(self, dataset: EnMAP) -> None: + x = dataset[dataset.bounds] + dataset.plot(x, suptitle='Test') + plt.close() + + def test_plot_wrong_bands(self, dataset: EnMAP) -> None: + bands = ('B1', 'B2', 'B3') + ds = EnMAP(dataset.paths, bands=bands) + x = dataset[dataset.bounds] + with pytest.raises( + RGBBandsMissingError, match='Dataset does not contain some of the RGB bands' + ): + ds.plot(x) + + def test_no_data(self, tmp_path: Path) -> None: + with pytest.raises(DatasetNotFoundError, match='Dataset not found'): + EnMAP(tmp_path) + + def test_invalid_query(self, dataset: EnMAP) -> None: + query = BoundingBox(0, 0, 0, 0, 0, 0) + with pytest.raises( + IndexError, match='query: .* not found in index with bounds:' + ): + dataset[query] diff --git a/torchgeo/datasets/__init__.py b/torchgeo/datasets/__init__.py index 8177120c2a7..1d644c6fc69 100644 --- a/torchgeo/datasets/__init__.py +++ b/torchgeo/datasets/__init__.py @@ -38,6 +38,7 @@ from .dfc2022 import DFC2022 from .digital_typhoon import DigitalTyphoon from .eddmaps import EDDMapS +from .enmap import EnMAP from .enviroatlas import EnviroAtlas from .errors import DatasetNotFoundError, DependencyNotFoundError, RGBBandsMissingError from .esri2020 import Esri2020 @@ -209,6 +210,7 @@ 'DependencyNotFoundError', 'DigitalTyphoon', 'EDDMapS', + 'EnMAP', 'EnviroAtlas', 'Esri2020', 'EuroCrops', diff --git a/torchgeo/datasets/enmap.py b/torchgeo/datasets/enmap.py new file mode 100644 index 00000000000..2c3034d8e4c --- /dev/null +++ b/torchgeo/datasets/enmap.py @@ -0,0 +1,360 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +"""EnMAP dataset.""" + +from collections.abc import Callable, Iterable, Sequence +from typing import Any, ClassVar + +import matplotlib.pyplot as plt +import numpy as np +from einops import rearrange +from matplotlib.figure import Figure +from rasterio.crs import CRS + +from .errors import RGBBandsMissingError +from .geo import RasterDataset +from .utils import Path, percentile_normalization + +ALL_BANDS = list(range(1, 225)) +# Remove bands strongly affected by water vapor absorption due to presence of nodata: +# * https://arxiv.org/abs/2306.00385 +# * https://arxiv.org/abs/2408.08447 +DEFAULT_BANDS = list(range(1, 127)) + list(range(142, 161)) + list(range(168, 225)) + + +class EnMAP(RasterDataset): + """`EnMAP `__ dataset. + + The Environmental Mapping and Analysis Program (EnMAP) is a German hyperspectral + satellite mission that monitors and characterizes Earth's environment on a global + scale. EnMAP measures geochemical, biochemical and biophysical variables providing + information on the status and evolution of terrestrial and aquatic ecosystems. + + Mission Outline: + + * Dedicated pushbroom hyperspectral imager mainly based on modified existing or + pre-developed technology + * Broad spectral range from 420 nm to 1000 nm (VNIR) and from 900 nm to 2450 nm + (SWIR) with high radiometric resolution and stability in both spectral ranges + * 30 km swath width at a spatial resolution of 30 x 30 m, nadir revisit time of + 27 days and off-nadir (30°) pointing feature for fast target revisit (4 days) + * Sufficient on-board memory to acquire 1,000 km swath length per orbit and a + total of 5,000 km per day. + + If you use this dataset in your research, please cite the following papers: + + * https://doi.org/10.1016/j.rse.2024.114379 + * https://doi.org/10.1016/j.rse.2023.113632 + + .. versionadded:: 0.7 + """ + + # https://www.enmap.org/data/doc/EN-PCV-ICD-2009-2_HSI_Product_Specification_Level1_Level2.pdf + filename_glob = 'ENMAP*SPECTRAL_IMAGE*' + filename_regex = r""" + ^ENMAP + (?P\d{2})- + (?P____L[12][ABC])- + (?PDT\d{10})_ + (?P\d{8}T\d{6})Z_ + (?P\d{3})_ + (?PV\d{6})_ + (?P\d{8}T\d{6})Z- + """ + date_format = '%Y%m%dT%H%M%S' + + all_bands = tuple(f'B{n}' for n in ALL_BANDS) + default_bands = tuple(f'B{n}' for n in DEFAULT_BANDS) + rgb_bands = ('B48', 'B30', 'B16') + + # Exact values vary from image to image, here are useful defaults: + # https://www.enmap.org/data/doc/EnMAP_Spectral_Bands_update.xlsx + wavelengths: ClassVar[dict[str, float]] = { + 'B1': 0.418416, + 'B2': 0.424043, + 'B3': 0.429457, + 'B4': 0.434686, + 'B5': 0.439758, + 'B6': 0.444699, + 'B7': 0.449539, + 'B8': 0.454306, + 'B9': 0.459031, + 'B10': 0.463730, + 'B11': 0.468411, + 'B12': 0.473080, + 'B13': 0.477744, + 'B14': 0.482411, + 'B15': 0.487087, + 'B16': 0.491780, + 'B17': 0.496497, + 'B18': 0.501243, + 'B19': 0.506020, + 'B20': 0.510829, + 'B21': 0.515672, + 'B22': 0.520551, + 'B23': 0.525467, + 'B24': 0.530424, + 'B25': 0.535422, + 'B26': 0.540463, + 'B27': 0.545551, + 'B28': 0.550687, + 'B29': 0.555873, + 'B30': 0.561112, + 'B31': 0.566405, + 'B32': 0.571756, + 'B33': 0.577166, + 'B34': 0.582636, + 'B35': 0.588171, + 'B36': 0.593773, + 'B37': 0.599446, + 'B38': 0.605193, + 'B39': 0.611017, + 'B40': 0.616923, + 'B41': 0.622921, + 'B42': 0.628987, + 'B43': 0.635112, + 'B44': 0.641294, + 'B45': 0.647537, + 'B46': 0.653841, + 'B47': 0.660207, + 'B48': 0.666637, + 'B49': 0.673131, + 'B50': 0.679691, + 'B51': 0.686319, + 'B52': 0.693014, + 'B53': 0.699780, + 'B54': 0.706617, + 'B55': 0.713524, + 'B56': 0.720501, + 'B57': 0.727545, + 'B58': 0.734654, + 'B59': 0.741826, + 'B60': 0.749060, + 'B61': 0.756353, + 'B62': 0.763703, + 'B63': 0.771108, + 'B64': 0.778567, + 'B65': 0.786078, + 'B66': 0.793639, + 'B67': 0.801249, + 'B68': 0.808905, + 'B69': 0.816608, + 'B70': 0.824355, + 'B71': 0.832145, + 'B72': 0.839976, + 'B73': 0.847847, + 'B74': 0.855757, + 'B75': 0.863703, + 'B76': 0.871683, + 'B77': 0.879693, + 'B78': 0.887729, + 'B79': 0.895789, + 'B80': 0.903870, + 'B81': 0.911968, + 'B82': 0.920081, + 'B83': 0.928204, + 'B84': 0.936335, + 'B85': 0.944470, + 'B86': 0.952608, + 'B87': 0.960748, + 'B88': 0.968892, + 'B89': 0.977037, + 'B90': 0.985186, + 'B91': 0.993338, + 'B92': 0.901961, + 'B93': 0.911571, + 'B94': 0.921320, + 'B95': 0.931203, + 'B96': 0.941218, + 'B97': 0.951360, + 'B98': 0.961628, + 'B99': 0.972016, + 'B100': 0.982523, + 'B101': 0.993144, + 'B102': 1.00388, + 'B103': 1.01472, + 'B104': 1.02566, + 'B105': 1.03670, + 'B106': 1.04784, + 'B107': 1.05907, + 'B108': 1.07039, + 'B109': 1.08178, + 'B110': 1.09326, + 'B111': 1.10481, + 'B112': 1.11643, + 'B113': 1.12810, + 'B114': 1.13984, + 'B115': 1.15162, + 'B116': 1.16344, + 'B117': 1.17530, + 'B118': 1.18720, + 'B119': 1.19911, + 'B120': 1.21105, + 'B121': 1.22300, + 'B122': 1.23497, + 'B123': 1.24694, + 'B124': 1.25893, + 'B125': 1.27092, + 'B126': 1.28292, + 'B127': 1.29491, + 'B128': 1.30690, + 'B129': 1.31888, + 'B130': 1.33085, + 'B131': 1.34282, + 'B132': 1.35476, + 'B133': 1.36669, + 'B134': 1.37860, + 'B135': 1.39048, + 'B136': 1.46110, + 'B137': 1.47274, + 'B138': 1.48434, + 'B139': 1.49589, + 'B140': 1.50740, + 'B141': 1.51887, + 'B142': 1.53029, + 'B143': 1.54167, + 'B144': 1.55301, + 'B145': 1.56430, + 'B146': 1.57555, + 'B147': 1.58676, + 'B148': 1.59791, + 'B149': 1.60902, + 'B150': 1.62009, + 'B151': 1.63111, + 'B152': 1.64207, + 'B153': 1.65300, + 'B154': 1.66387, + 'B155': 1.67470, + 'B156': 1.68547, + 'B157': 1.69620, + 'B158': 1.70687, + 'B159': 1.71750, + 'B160': 1.72808, + 'B161': 1.73860, + 'B162': 1.74908, + 'B163': 1.75951, + 'B164': 1.93914, + 'B165': 1.94869, + 'B166': 1.95820, + 'B167': 1.96766, + 'B168': 1.97708, + 'B169': 1.98645, + 'B170': 1.99579, + 'B171': 2.00508, + 'B172': 2.01433, + 'B173': 2.02354, + 'B174': 2.03270, + 'B175': 2.04183, + 'B176': 2.05092, + 'B177': 2.05996, + 'B178': 2.06897, + 'B179': 2.07793, + 'B180': 2.08686, + 'B181': 2.09574, + 'B182': 2.10459, + 'B183': 2.11340, + 'B184': 2.12217, + 'B185': 2.13090, + 'B186': 2.13960, + 'B187': 2.14826, + 'B188': 2.15688, + 'B189': 2.16547, + 'B190': 2.17402, + 'B191': 2.18253, + 'B192': 2.19101, + 'B193': 2.19945, + 'B194': 2.20786, + 'B195': 2.21624, + 'B196': 2.22458, + 'B197': 2.23289, + 'B198': 2.24116, + 'B199': 2.24940, + 'B200': 2.25761, + 'B201': 2.26579, + 'B202': 2.27393, + 'B203': 2.28204, + 'B204': 2.29012, + 'B205': 2.29817, + 'B206': 2.30619, + 'B207': 2.31417, + 'B208': 2.32213, + 'B209': 2.33005, + 'B210': 2.33794, + 'B211': 2.34581, + 'B212': 2.35364, + 'B213': 2.36144, + 'B214': 2.36921, + 'B215': 2.37695, + 'B216': 2.38466, + 'B217': 2.39234, + 'B218': 2.40000, + 'B219': 2.40762, + 'B220': 2.41521, + 'B221': 2.42278, + 'B222': 2.43032, + 'B223': 2.43782, + 'B224': 2.44530, + } + + def __init__( + self, + paths: Path | Iterable[Path] = 'data', + crs: CRS | None = None, + res: float | None = None, + bands: Sequence[str] | None = None, + transforms: Callable[[dict[str, Any]], dict[str, Any]] | None = None, + cache: bool = True, + ) -> None: + """Initialize a new EnMAP instance. + + Args: + paths: one or more root directories to search or files to load + crs: :term:`coordinate reference system (CRS)` to warp to + (defaults to the CRS of the first file found) + res: resolution of the dataset in units of CRS + (defaults to the resolution of the first file found) + bands: bands to return (defaults to all bands) + transforms: a function/transform that takes an input sample + and returns a transformed version + cache: if True, cache file handle to speed up repeated sampling + + Raises: + DatasetNotFoundError: If dataset is not found. + """ + bands = bands or self.default_bands + super().__init__(paths, crs, res, bands, transforms, cache) + + def plot(self, sample: dict[str, Any], suptitle: str | None = None) -> Figure: + """Plot a sample from the dataset. + + Args: + sample: A sample returned by :meth:`__getitem__`. + suptitle: optional string to use as a suptitle + + Returns: + A matplotlib Figure with the rendered sample. + + Raises: + RGBBandsMissingError: If *bands* does not include all RGB bands. + """ + rgb_indices = [] + for band in self.rgb_bands: + if band in self.bands: + rgb_indices.append(self.bands.index(band)) + else: + raise RGBBandsMissingError() + + image = sample['image'][rgb_indices].cpu().numpy() + image = np.clip(image, 0, None) + image = rearrange(image, 'c h w -> h w c') + image = percentile_normalization(image) + + fig, ax = plt.subplots() + ax.imshow(image) + ax.axis('off') + + if suptitle: + fig.suptitle(suptitle) + + return fig From e7d8764e27f9cc327ef684c335cbff3f420a7fd3 Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Tue, 28 Jan 2025 18:33:44 +0100 Subject: [PATCH 2/3] Fix doc link --- torchgeo/datasets/enmap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/torchgeo/datasets/enmap.py b/torchgeo/datasets/enmap.py index 2c3034d8e4c..1994c5ebb16 100644 --- a/torchgeo/datasets/enmap.py +++ b/torchgeo/datasets/enmap.py @@ -329,7 +329,7 @@ def plot(self, sample: dict[str, Any], suptitle: str | None = None) -> Figure: """Plot a sample from the dataset. Args: - sample: A sample returned by :meth:`__getitem__`. + sample: A sample returned by :meth:`RasterDataset.__getitem__`. suptitle: optional string to use as a suptitle Returns: From 9a22665623748b497b5bf0291b88706de515766d Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Mon, 3 Feb 2025 11:57:53 +0100 Subject: [PATCH 3/3] Add EnMAP license --- docs/api/datasets/geo_datasets.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api/datasets/geo_datasets.csv b/docs/api/datasets/geo_datasets.csv index 8190c89eb54..225c75dc277 100644 --- a/docs/api/datasets/geo_datasets.csv +++ b/docs/api/datasets/geo_datasets.csv @@ -8,7 +8,7 @@ Dataset,Type,Source,License,Size (px),Resolution (m) `Global Mangrove Distribution`_,Masks,"Remote Sensing, In Situ Measurements","public domain",-,3 `Cropland Data Layer`_,Masks,Landsat,"public domain",-,30 `EDDMapS`_,Points,Citizen Scientists,-,-,- -`EnMAP`_,Imagery,EnMAP,-,"1,200x1,200",30 +`EnMAP`_,Imagery,EnMAP,`EnMAP Data License `_,"1,200x1,200",30 `EnviroAtlas`_,"Imagery, Masks","NAIP, NLCD, OpenStreetMap","CC-BY-4.0",-,1 `Esri2020`_,Masks,Sentinel-2,"CC-BY-4.0",-,10 `EU-DEM`_,DEM,"Aster, SRTM, Russian Topomaps","CSCDA-ESA",-,25