From b918d42961311e257d0cfbb9a2c577969ef68fcb Mon Sep 17 00:00:00 2001 From: Nils Lehmann Date: Thu, 23 Jan 2025 19:49:33 +0000 Subject: [PATCH 1/2] dataset --- docs/api/datasets.rst | 4 + docs/api/datasets/non_geo_datasets.csv | 1 + tests/data/ai4arctic_sea_ice/data.py | 247 +++++++++ tests/data/ai4arctic_sea_ice/metadata.csv | 6 + tests/data/ai4arctic_sea_ice/test.tar.gz | Bin 0 -> 16277 bytes .../test/20210104T000000_dmi_prep.nc | Bin 0 -> 19592 bytes .../20210104T000000_dmi_prep_reference.nc | Bin 0 -> 22604 bytes .../test/20210105T000000_cis_prep.nc | Bin 0 -> 19592 bytes .../20210105T000000_cis_prep_reference.nc | Bin 0 -> 22604 bytes tests/data/ai4arctic_sea_ice/train.tar.gzaa | Bin 0 -> 5540 bytes tests/data/ai4arctic_sea_ice/train.tar.gzab | Bin 0 -> 5541 bytes .../train/20210101T000000_dmi_prep.nc | Bin 0 -> 22584 bytes .../train/20210102T000000_cis_prep.nc | Bin 0 -> 22584 bytes .../train/20210103T000000_dmi_prep.nc | Bin 0 -> 22584 bytes tests/datasets/test_ai4arctic_sea_ice.py | 93 ++++ torchgeo/datasets/__init__.py | 2 + torchgeo/datasets/ai4arctic_sea_ice.py | 506 ++++++++++++++++++ 17 files changed, 859 insertions(+) create mode 100644 tests/data/ai4arctic_sea_ice/data.py create mode 100644 tests/data/ai4arctic_sea_ice/metadata.csv create mode 100644 tests/data/ai4arctic_sea_ice/test.tar.gz create mode 100644 tests/data/ai4arctic_sea_ice/test/20210104T000000_dmi_prep.nc create mode 100644 tests/data/ai4arctic_sea_ice/test/20210104T000000_dmi_prep_reference.nc create mode 100644 tests/data/ai4arctic_sea_ice/test/20210105T000000_cis_prep.nc create mode 100644 tests/data/ai4arctic_sea_ice/test/20210105T000000_cis_prep_reference.nc create mode 100644 tests/data/ai4arctic_sea_ice/train.tar.gzaa create mode 100644 tests/data/ai4arctic_sea_ice/train.tar.gzab create mode 100644 tests/data/ai4arctic_sea_ice/train/20210101T000000_dmi_prep.nc create mode 100644 tests/data/ai4arctic_sea_ice/train/20210102T000000_cis_prep.nc create mode 100644 tests/data/ai4arctic_sea_ice/train/20210103T000000_dmi_prep.nc create mode 100644 tests/datasets/test_ai4arctic_sea_ice.py create mode 100644 torchgeo/datasets/ai4arctic_sea_ice.py diff --git a/docs/api/datasets.rst b/docs/api/datasets.rst index c60b08f6666..c2eb9bab961 100644 --- a/docs/api/datasets.rst +++ b/docs/api/datasets.rst @@ -202,6 +202,10 @@ ADVANCE .. autoclass:: ADVANCE +AI4ArcticSeaIce + +.. autoclass:: AI4ArcticSeaIce + Benin Cashew Plantations ^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/api/datasets/non_geo_datasets.csv b/docs/api/datasets/non_geo_datasets.csv index f91f6b0e967..0f452ee265d 100644 --- a/docs/api/datasets/non_geo_datasets.csv +++ b/docs/api/datasets/non_geo_datasets.csv @@ -1,5 +1,6 @@ Dataset,Task,Source,License,# Samples,# Classes,Size (px),Resolution (m),Bands `ADVANCE`_,C,"Google Earth, Freesound","CC-BY-4.0","5,075",13,512x512,0.5,RGB +`AI4ArcticSeaIce`_,S,"Sentinel-1","CC-BY-4.0","520",2,"~5000x5000",80,"HH,HV" `Benin Cashew Plantations`_,S,Airbus Pléiades,"CC-BY-4.0",70,6,"1,122x1,186",10,MSI `BigEarthNet`_,C,Sentinel-1/2,"CDLA-Permissive-1.0","590,326",19--43,120x120,10,"SAR, MSI" `BioMassters`_,R,Sentinel-1/2 and Lidar,"CC-BY-4.0",,,256x256, 10, "SAR, MSI" diff --git a/tests/data/ai4arctic_sea_ice/data.py b/tests/data/ai4arctic_sea_ice/data.py new file mode 100644 index 00000000000..826e73e397c --- /dev/null +++ b/tests/data/ai4arctic_sea_ice/data.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python3 + +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os +import numpy as np +import xarray as xr +import pandas as pd +import tarfile +import hashlib +import shutil +from datetime import datetime, timedelta + + +def create_dummy_nc_file(filepath: str, is_reference: bool = False): + """Create dummy netCDF file matching original dataset structure.""" + + # Define dimensions + dims = { + 'sar_lines': 12, + 'sar_samples': 9, + 'sar_sample_2dgrid_points': 3, + 'sar_line_2dgrid_points': 4, + '2km_grid_lines': 5, + '2km_grid_samples': 6, + } + + # Create variables with realistic dummy data + data_vars = { + # SAR variables (full resolution) + 'nersc_sar_primary': ( + ('sar_lines', 'sar_samples'), + np.random.normal(-20, 5, (dims['sar_lines'], dims['sar_samples'])).astype( + np.float32 + ), + ), + 'nersc_sar_secondary': ( + ('sar_lines', 'sar_samples'), + np.random.normal(-25, 5, (dims['sar_lines'], dims['sar_samples'])).astype( + np.float32 + ), + ), + # Grid coordinates + 'sar_grid2d_latitude': ( + ('sar_sample_2dgrid_points', 'sar_line_2dgrid_points'), + np.random.uniform( + 60, + 80, + (dims['sar_sample_2dgrid_points'], dims['sar_line_2dgrid_points']), + ).astype(np.float64), + ), + 'sar_grid2d_longitude': ( + ('sar_sample_2dgrid_points', 'sar_line_2dgrid_points'), + np.random.uniform( + -60, + 0, + (dims['sar_sample_2dgrid_points'], dims['sar_line_2dgrid_points']), + ).astype(np.float64), + ), + # Weather variables (2km grid) + 'u10m_rotated': ( + ('2km_grid_lines', '2km_grid_samples'), + np.random.normal( + 0, 5, (dims['2km_grid_lines'], dims['2km_grid_samples']) + ).astype(np.float32), + ), + 'v10m_rotated': ( + ('2km_grid_lines', '2km_grid_samples'), + np.random.normal( + 0, 5, (dims['2km_grid_lines'], dims['2km_grid_samples']) + ).astype(np.float32), + ), + # AMSR2 variables (6.9, 7.3, 10.7, 23.8, 36.5, 89.0 GHz, h, v) + **{ + f'btemp_{freq}{pol}': ( + ('2km_grid_lines', '2km_grid_samples'), + np.random.normal( + 250, 20, (dims['2km_grid_lines'], dims['2km_grid_samples']) + ).astype(np.float32), + ) + for freq in ['6_9', '7_3'] + for pol in ['h', 'v'] + }, + # Add distance map + 'distance_map': ( + ('sar_lines', 'sar_samples'), + np.random.uniform(0, 10, (dims['sar_lines'], dims['sar_samples'])).astype( + np.float32 + ), + { + 'long_name': 'Distance to land zones numbered with ids ranging from 0 to N', + 'zonal_range_description': '\ndist_id; dist_range_km\n0; land\n1; 0 -> 0.5\n2; 0.5 -> 1\n3; 1 -> 2\n4; 2 -> 4\n5; 4 -> 8\n6; 8 -> 16\n7; 16 -> 32\n8; 32 -> 64\n9; 64 -> 128\n10; >128', + }, + ), + } + + # Add target variables if reference file + if is_reference: + data_vars.update( + { + 'SOD': ( + ('sar_lines', 'sar_samples'), + np.random.randint( + 0, 6, (dims['sar_lines'], dims['sar_samples']) + ).astype(np.uint8), + ), + 'SIC': ( + ('sar_lines', 'sar_samples'), + np.random.randint( + 0, 11, (dims['sar_lines'], dims['sar_samples']) + ).astype(np.uint8), + ), + 'FLOE': ( + ('sar_lines', 'sar_samples'), + np.random.randint( + 0, 7, (dims['sar_lines'], dims['sar_samples']) + ).astype(np.uint8), + ), + } + ) + + # Create dataset with correct attributes + ds = xr.Dataset( + data_vars=data_vars, + attrs={ + 'scene_id': os.path.basename(filepath), + 'original_id': f'S1A_EW_GRDM_1SDH_{os.path.basename(filepath)}', + 'ice_service': 'dmi' if 'dmi' in filepath else 'cis', + 'flip': 0, + 'pixel_spacing': 80, + }, + ) + + # Save to netCDF file + os.makedirs(os.path.dirname(filepath), exist_ok=True) + ds.to_netcdf(filepath) + + +def create_metadata_csv(root_dir: str, n_train: int = 3, n_test: int = 2): + """Create metadata CSV file.""" + records = [] + + # Generate dates + base_date = datetime(2021, 1, 1) + dates = [base_date + timedelta(days=i) for i in range(n_train + n_test)] + + # Create train records + for i in range(n_train): + date_str = dates[i].strftime('%Y%m%dT%H%M%S') + service = 'dmi' if i % 2 == 0 else 'cis' + path = f'train/{date_str}_{service}_prep.nc' + records.append( + { + 'input_path': path, + 'reference_path': None, + 'date': dates[i], + 'ice_service': service, + 'split': 'train', + 'region_id': 'SGRDIFOXE' if service == 'cis' else 'North_RIC', + } + ) + + # Create test records + for i in range(n_test): + date_str = dates[n_train + i].strftime('%Y%m%dT%H%M%S') + service = 'dmi' if i % 2 == 0 else 'cis' + input_path = f'test/{date_str}_{service}_prep.nc' + ref_path = f'test/{date_str}_{service}_prep_reference.nc' + records.append( + { + 'input_path': input_path, + 'reference_path': ref_path, + 'date': dates[n_train + i], + 'ice_service': service, + 'split': 'test', + 'region_id': 'SGRDIFOXE' if service == 'cis' else 'North_RIC', + } + ) + + # Create DataFrame and save + df = pd.DataFrame(records) + df.to_csv(os.path.join(root_dir, 'metadata.csv'), index=False) + return df + + +def main(): + """Create complete dummy dataset.""" + root_dir = '.' + n_train = 3 + n_test = 2 + + # Create metadata + df = create_metadata_csv(root_dir, n_train, n_test) + + # Create train files + train_files = df[df['split'] == 'train']['input_path'] + for f in train_files: + create_dummy_nc_file(os.path.join(root_dir, f), is_reference=True) + + # Create test files + test_files = df[df['split'] == 'test'] + for _, row in test_files.iterrows(): + create_dummy_nc_file( + os.path.join(root_dir, row['input_path']), is_reference=False + ) + create_dummy_nc_file( + os.path.join(root_dir, row['reference_path']), is_reference=True + ) + + # Create and split train tarball + shutil.make_archive('train', 'gztar', '.', 'train') + + with open('train.tar.gz', 'rb') as f: + content = f.read() + + # Split into two chunks + chunk1 = content[: len(content) // 2] + chunk2 = content[len(content) // 2 :] + + with open('train.tar.gzaa', 'wb') as g: + g.write(chunk1) + with open('train.tar.gzab', 'wb') as g: + g.write(chunk2) + + # Remove original tarball + os.remove('train.tar.gz') + + with tarfile.open('test.tar.gz', 'w:gz') as tar: + tar.add('test') + + # compute md5sum + def md5(fname: str) -> str: + hash_md5 = hashlib.md5() + with open(fname, 'rb') as f: + for chunk in iter(lambda: f.read(4096), b''): + hash_md5.update(chunk) + return hash_md5.hexdigest() + + print(f'MD5 checksum train.gzaa: {md5("train.tar.gzaa")}') + print(f'MD5 checksum train.gzab: {md5("train.tar.gzab")}') + print(f'MD5 checksum test.gz: {md5("test.tar.gz")}') + print(f'MD5 checksum metadata: {md5("metadata.csv")}') + + +if __name__ == '__main__': + main() diff --git a/tests/data/ai4arctic_sea_ice/metadata.csv b/tests/data/ai4arctic_sea_ice/metadata.csv new file mode 100644 index 00000000000..61b73f0dbb9 --- /dev/null +++ b/tests/data/ai4arctic_sea_ice/metadata.csv @@ -0,0 +1,6 @@ +input_path,reference_path,date,ice_service,split,region_id +train/20210101T000000_dmi_prep.nc,,2021-01-01,dmi,train,North_RIC +train/20210102T000000_cis_prep.nc,,2021-01-02,cis,train,SGRDIFOXE +train/20210103T000000_dmi_prep.nc,,2021-01-03,dmi,train,North_RIC +test/20210104T000000_dmi_prep.nc,test/20210104T000000_dmi_prep_reference.nc,2021-01-04,dmi,test,North_RIC +test/20210105T000000_cis_prep.nc,test/20210105T000000_cis_prep_reference.nc,2021-01-05,cis,test,SGRDIFOXE diff --git a/tests/data/ai4arctic_sea_ice/test.tar.gz b/tests/data/ai4arctic_sea_ice/test.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..970ed7e1f72ca6770222b7a9e562bc42f49e2aa9 GIT binary patch literal 16277 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 literal 0 HcmV?d00001 diff --git a/tests/data/ai4arctic_sea_ice/test/20210104T000000_dmi_prep_reference.nc b/tests/data/ai4arctic_sea_ice/test/20210104T000000_dmi_prep_reference.nc new file mode 100644 index 0000000000000000000000000000000000000000..35b206103efd05da314809bd30bfbe625fc3689e GIT binary patch literal 22604 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 Kx9IHq`u_k}vN@*! literal 0 HcmV?d00001 diff --git a/tests/data/ai4arctic_sea_ice/test/20210105T000000_cis_prep.nc b/tests/data/ai4arctic_sea_ice/test/20210105T000000_cis_prep.nc new file mode 100644 index 0000000000000000000000000000000000000000..ef79d0ac12177a4a6dabfdd9eb7678b8dff11a54 GIT binary patch literal 19592 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 literal 0 HcmV?d00001 diff --git a/tests/data/ai4arctic_sea_ice/test/20210105T000000_cis_prep_reference.nc b/tests/data/ai4arctic_sea_ice/test/20210105T000000_cis_prep_reference.nc new file mode 100644 index 0000000000000000000000000000000000000000..4d2aaec6e4992acef310296ede6283a2a9bdd2d3 GIT binary patch literal 22604 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 literal 0 HcmV?d00001 diff --git a/tests/data/ai4arctic_sea_ice/train/20210101T000000_dmi_prep.nc b/tests/data/ai4arctic_sea_ice/train/20210101T000000_dmi_prep.nc new file mode 100644 index 0000000000000000000000000000000000000000..3453ec06aa04853b1a41d5f169e50f8ccab289c6 GIT binary patch literal 22584 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 literal 0 HcmV?d00001 diff --git a/tests/data/ai4arctic_sea_ice/train/20210102T000000_cis_prep.nc b/tests/data/ai4arctic_sea_ice/train/20210102T000000_cis_prep.nc new file mode 100644 index 0000000000000000000000000000000000000000..1d3c8a072fddfda29e8966fd2afb3a24f6bdc8d7 GIT binary patch literal 22584 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 literal 0 HcmV?d00001 diff --git a/tests/data/ai4arctic_sea_ice/train/20210103T000000_dmi_prep.nc b/tests/data/ai4arctic_sea_ice/train/20210103T000000_dmi_prep.nc new file mode 100644 index 0000000000000000000000000000000000000000..85158f84acbe2cf5492d0bb00049dcdb2b991b5b GIT binary patch literal 22584 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 F{|9bkEwcas literal 0 HcmV?d00001 diff --git a/tests/datasets/test_ai4arctic_sea_ice.py b/tests/datasets/test_ai4arctic_sea_ice.py new file mode 100644 index 00000000000..8e321c1be9c --- /dev/null +++ b/tests/datasets/test_ai4arctic_sea_ice.py @@ -0,0 +1,93 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import shutil +import os +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 torchgeo.datasets import DatasetNotFoundError, AI4ArcticSeaIce + +pytest.importorskip('xarray', minversion='2023.9') +pytest.importorskip('netCDF4', minversion='1.5.4') + +valid_amsr2_vars = ('btemp_6_9h', 'btemp_6_9v', 'btemp_7_3h', 'btemp_7_3v') +valid_weather_vars = ('u10m_rotated', 'v10m_rotated') + + +class TestAI4ArcticSeaIce: + @pytest.fixture( + params=zip( + ['train', 'train', 'test', 'test'], + ['SOD', 'SIC', 'FLOE', 'SIC'], + [None, 'distance_map', None, 'distance_map'], + [valid_amsr2_vars, None, valid_amsr2_vars, None], + [valid_weather_vars, None, valid_weather_vars, None], + ) + ) + def dataset( + self, monkeypatch: MonkeyPatch, tmp_path: Path, request: SubRequest + ) -> AI4ArcticSeaIce: + url = os.path.join('tests', 'data', 'ai4arctic_sea_ice', '{}') + monkeypatch.setattr(AI4ArcticSeaIce, 'url', url) + files = [ + {'name': 'train.tar.gzaa', 'md5': '399952b2603d0d508a30909357e6956a'}, + {'name': 'train.tar.gzab', 'md5': 'a998c852a2f418394f97cb1f99716489'}, + {'name': 'test.tar.gz', 'md5': 'b81e53b4c402a64d53854f02f66ce938'}, + {'name': 'metadata.csv', 'md5': 'd1222877af76d3fe9620678c930d70f0'}, + ] + monkeypatch.setattr(AI4ArcticSeaIce, 'files', files) + + monkeypatch.setattr(AI4ArcticSeaIce, 'valid_amsr2_vars', valid_amsr2_vars) + + monkeypatch.setattr(AI4ArcticSeaIce, 'valid_weather_vars', valid_weather_vars) + root = tmp_path + split, target_var, geo_var, amsr2_var, weather_var = request.param + transforms = nn.Identity() + return AI4ArcticSeaIce( + root, + split=split, + target_var=target_var, + geo_var=geo_var, + amsr2_vars=amsr2_var, + weather_vars=weather_var, + transforms=transforms, + download=True, + checksum=False, + ) + + def test_getitem(self, dataset: AI4ArcticSeaIce) -> None: + x = dataset[0] + assert isinstance(x, dict) + + def test_len(self, dataset: AI4ArcticSeaIce) -> None: + if dataset.split == 'train': + assert len(dataset) == 3 + else: + assert len(dataset) == 2 + + def test_not_downloaded(self, tmp_path: Path) -> None: + with pytest.raises(DatasetNotFoundError, match='Dataset not found'): + AI4ArcticSeaIce(tmp_path) + + def test_already_downloaded_and_extracted(self, dataset: AI4ArcticSeaIce) -> None: + AI4ArcticSeaIce(root=dataset.root, download=False) + + def test_invalid_split(self) -> None: + with pytest.raises(AssertionError): + AI4ArcticSeaIce(split='foo') + + def test_plot(self, dataset: AI4ArcticSeaIce) -> None: + dataset.plot(dataset[0], suptitle='Test') + plt.close() + + sample = dataset[0] + sample['prediction'] = torch.clone(sample['mask']) + dataset.plot(sample, suptitle='Test with prediction') + plt.close() diff --git a/torchgeo/datasets/__init__.py b/torchgeo/datasets/__init__.py index 0e522c09976..ec2fc2d419c 100644 --- a/torchgeo/datasets/__init__.py +++ b/torchgeo/datasets/__init__.py @@ -6,6 +6,7 @@ from .advance import ADVANCE from .agb_live_woody_density import AbovegroundLiveWoodyBiomassDensity from .agrifieldnet import AgriFieldNet +from .ai4arctic_sea_ice import AI4ArcticSeaIce from .airphen import Airphen from .astergdem import AsterGDEM from .benin_cashews import BeninSmallHolderCashews @@ -151,6 +152,7 @@ __all__ = ( 'ADVANCE', + 'AI4ArcticSeaIce', 'CDL', 'COWC', 'DFC2022', diff --git a/torchgeo/datasets/ai4arctic_sea_ice.py b/torchgeo/datasets/ai4arctic_sea_ice.py new file mode 100644 index 00000000000..d5754378c1a --- /dev/null +++ b/torchgeo/datasets/ai4arctic_sea_ice.py @@ -0,0 +1,506 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +"""AI4Artic Sea Ice Dataset.""" + +import json +import os +import pandas as pd +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.figure import Figure +from matplotlib.patches import Patch +from collections.abc import Callable, Sequence +from datetime import datetime, timedelta +from typing import Any, ClassVar, cast + +import numpy as np +import torch +from torch import Tensor +import xarray as xr + +from .errors import DatasetNotFoundError +from .geo import NonGeoDataset +from .utils import Path, download_url, extract_archive, check_integrity + + +class AI4ArcticSeaIce(NonGeoDataset): + """AI4Artic Sea Ice Dataset. + + The Sea Ice Challenge Dataset contains Sentinel-1 SAR imagery, passive microwave radiometer observations + from AMSR2, and numerical weather prediction data from the ECMWF Reanalysis v5 (ERA5) dataset - all + gridded to match the Sentinel-1 SAR scenes geometrically. As label data, the dataset contains ice charts + manually produced by the ice analysts at the Greenland Ice Service and the Canadian Ice Service. + + Dataset features: + + * Dual-polarization SAR (HH, HV) imagery for each patch. + * Sea Ice Concentration (SIC): the percentage ratio of sea ice to open water for an area, + discretized into 11 10% bins ranging from 0% to 100%. + * Stage Of Development (SOD): type of sea ice, as proxy for ice thickness and + ease of traversing with 6 classes + * Floe size (FLOE): Classifying or segmenting distinct ice floes based on size, shape, + or other geometric properties. + + Dataset format: + + * each sample scene is stored in a separate .nc file + * pixel dimension of varying sizes up to ~5000pxx5000px + * 80m resolution + + Geographical variables: + + * distance-to-land layer (distance_map) + + SAR variables: + + * Sentinel-1 backscatter intensity (dB) in HH polarization (nersc_sar_primary) + * Sentinel-1 backscatter intensity (dB) in HV polarization (nersc_sar_secondary) + * Sentinel-1 incidence angle (sar_incidenceangle) + + Weather variables: + + * eastward wind component at 10m (u10m_rotated) + * northward wind component at 10m (v10m_rotated) + * ERA5 2m air temperature (t2m) + * ERA5 skin temperature (skt) + * ERA5 total column water vapor (tcwv) + * ERA5 total column liquid water (tclw) + + Advanced Microwave Scanning Radiometer 2 (AMSR2) variables: + + * 6.9 GHz Brightness Temperature (btemp_6_9h, btemp_6_9v) + * 7.3 GHz Brightness Temperature (btemp_7_3h, btemp_7_3v) + * 10.7 GHz Brightness Temperature (btemp_10_7h, btemp_10_7v) + * 18.7 GHz Brightness Temperature (btemp_18_7h, btemp_18_7v) + * 23.8 GHz Brightness Temperature (btemp_23_8h, btemp_23_8v) + * 36.5 GHz Brightness Temperature (btemp_36_5h, btemp_36_5v) + * 89.0 GHz Brightness Temperature (btemp_89_0h, btemp_89_0v) + + Sea Ice Concentration (SIC) classes: + + * 0: 0% + * 1: 0-10% + * 2: 10-20% + * 3: 20-30% + * 4: 30-40% + * 5: 40-50% + * 6: 50-60% + * 7: 60-70% + * 8: 70-80% + * 9: 80-90% + * 10: 90-100% + + Stage of Development (SOD) classes: + + * 0: Open-water + * 1: New ice + * 2: Young ice + * 3: Thin First-year ice + * 4: Thick First-year ice + * 5: Old ice (older than 1 year) + + Floe size (FLOE) classes: + + * 0: Open-water + * 1: Cake ice + * 2: Small floe + * 3: Medium floe + * 4: Big floe + * 5: Vast floe + * 6: Bergs (variants of icebergs and glacier ice) + + files: + Danish Meteorological Institute (DMI) and the Canadian Ice Service (CIS) + dmi_prep: data by DMI + cis_prep: data by CIS + dmi_prep_referece: contains SIC, SOD, FLOE + cis_prep_reference: contains SIC, SOD, FLOE + + Dataset format: + + * Dataset in separate .nc files + + If you use this dataset in your research, please cite the following paper: + + * https://data.dtu.dk/articles/dataset/Ready-To-Train_AI4Arctic_Sea_Ice_Challenge_Dataset/21316608 + + .. note:: + + This dataset requires the following additional libraries to be installed: + + * `xarray `_ + * `netcdf4 `_ + + .. versionadded:: 0.7 + + # Variables in the ASID3 challenge ready-to-train dataset + """ + + url = 'https://huggingface.co/datasets/torchgeo/ai4artic-sea-ice-challenge/resolve/main/{}' + + files = [ + {'name': 'metadata.csv', 'md5': '4b610118c2d182325ec7599434b37deb'}, + {'name': 'train.tar.gzaa', 'md5': '847ea12d0a5100f0a00af4bb110404b4'}, + {'name': 'train.tar.gzab', 'md5': '3f4770c586487dc681d1d216c7003f2c'}, + {'name': 'test.tar.gz', 'md5': 'bca98ec6734783aa6f005382549a0d21'}, + ] + + splits = ('train', 'test') + + # https://github.com/astokholm/AI4ArcticSeaIceChallenge/blob/4d5e3bc85e681f6c56821d96f2ebfcf4ed58b495/utils.py#L68 + SIC_GROUPS = { + 0: 0, + 1: 10, + 2: 20, + 3: 30, + 4: 40, + 5: 50, + 6: 60, + 7: 70, + 8: 80, + 9: 90, + 10: 100, + } + + SOD_GROUPS = { + 0: 'Open water', + 1: 'New Ice', + 2: 'Young ice', + 3: 'Thin FYI', + 4: 'Thick FYI', + 5: 'Old ice', + } + + FLOE_GROUPS = { + 0: 'Open water', + 1: 'Cake Ice', + 2: 'Small floe', + 3: 'Medium floe', + 4: 'Big floe', + 5: 'Vast floe', + 6: 'Bergs', + } + + valid_sar_vars = ('nersc_sar_primary', 'nersc_sar_secondary', 'sar_incidenceangle') + valid_geo_vars = ('distance_map',) + valid_amsr2_vars = ( + 'btemp_6_9h', + 'btemp_6_9v', + 'btemp_7_3h', + 'btemp_7_3v', + 'btemp_10_7h', + 'btemp_10_7v', + 'btemp_18_7h', + 'btemp_18_7v', + 'btemp_23_8h', + 'btemp_23_8v', + 'btemp_36_5h', + 'btemp_36_5v', + 'btemp_89_0h', + 'btemp_89_0v', + ) + valid_weather_vars = ('u10m_rotated', 'v10m_rotated', 't2m', 'skt', 'tcwv', 'tclw') + + valid_target_vars = ('SOD', 'SIC', 'FLOE') + + def __init__( + self, + root: Path = 'data', + split: str = 'train', + target_var: str = 'SOD', + geo_var: str | None = None, + amsr2_vars: Sequence[str] | None = None, + weather_vars: Sequence[str] | None = None, + transforms: Callable[[dict[str, Tensor]], dict[str, Tensor]] | None = None, + download: bool = False, + checksum: bool = False, + ) -> None: + """Initialize the AI4Artic Sea Ice dataset. + + Args: + root: root directory where the dataset can be found + split: The split of the dataset. Either 'train' or 'test'. + target_var: Target variable to be the label mask + geo_var: Geographical variables to include in the dataset, only option is 'distance_map' + amsr2_vars: AMSR2 channels to include in the dataset + weather_vars: Environmental variables to include in the dataset + transforms: a function/transform that takes input sample dictionary + and returns a transformed version + download: if True, download dataset and store it in the root directory + checksum: if True, check the MD5 of the downloaded files (may be slow) + + Raises: + AssertionError: If *split* is not one of 'train' or 'test', or if selected variables are not valid. + DatasetNotFoundError: If the dataset is not found and *download* is False. + DependencyNotFoundError: If xarray is not installed. + """ + assert target_var in self.valid_target_vars, ( + f'Invalid target variable selected. Must be one of {self.valid_target_vars}' + ) + if geo_var is not None: + assert geo_var == 'distance_map', ( + f"Invalid geographical variable selected. Only 'distance_map' is supported." + ) + + if amsr2_vars is not None: + assert all(var in self.valid_amsr2_vars for var in amsr2_vars), ( + f'Invalid AMSR2 variables selected. Must be a subset of {self.valid_amsr2_vars}' + ) + + if weather_vars is not None: + assert all(var in self.valid_weather_vars for var in weather_vars), ( + f'Invalid weather variables selected. Must be a subset of {self.valid_weather_vars}' + ) + + assert split in self.splits, ( + f"Split '{split}' not supported, must be one of {self.splits}" + ) + + self.target_var = target_var + self.geo_var = geo_var + self.amsr2_vars = amsr2_vars + self.weather_vars = weather_vars + + self.root = root + self.split = split + self.transforms = transforms + self.download = download + self.checksum = checksum + + self._verify() + + # metadata df + self.metadata_df = pd.read_csv(os.path.join(self.root, 'metadata.csv')) + self.metadata_df = self.metadata_df[ + self.metadata_df['split'] == self.split + ].reset_index(drop=True) + + def __len__(self) -> int: + """Return the number of samples in the dataset.""" + return len(self.metadata_df) + + def __getitem__(self, idx: int) -> dict[str, Tensor]: + """Get the sample at the given index. + + Args: + idx: index of the sample to return + + Returns: + A dictionary containing the sample data, split into the following keys by data type: + * 'image': SAR data stacked hh, hv in that order + * 'geo': Geographical data + * 'amsr2': AMSR2 data + * 'weather': Weather data + * 'mask': Chosen target data + """ + df_row = self.metadata_df.iloc[idx] + + # load data + sample = self._load_data(os.path.join(self.root, df_row['input_path'])) + + # load target + sample['mask'] = self._load_label(os.path.join(self.root, df_row['input_path'])) + if self.transforms is not None: + sample = self.transforms(sample) + + # crop bottom right corner of the image + # sample["image"] = sample["image"][:, -1024:, -1024:] + # sample["mask"] = sample["mask"][-1024:, -1024:] + # crop bottom left corner + # sample["image"] = sample["image"][:, -1024:, :1024] + # sample["mask"] = sample["mask"][-1024:, :1024] + + return sample + + def _load_data(self, path: str) -> dict[str, Tensor]: + """Load the data from the given path. + + Args: + input_path: path to the data file + + Returns: + A dictionary containing the data, split into the following keys followed by var name if specified: + * 'image': SAR data stacked hh, hv in that order + * 'geo': Geographical data + * 'amsr2': AMSR2 data + * 'weather': Weather data + """ + sample: dict[str, Tensor] = {} + + input_data = xr.open_dataset(path) + + # load s1 vars + hh = torch.from_numpy(input_data['nersc_sar_primary'].values) + hv = torch.from_numpy(input_data['nersc_sar_secondary'].values) + + # NaN values in SAR data have value 2 + sample['image'] = torch.stack([hh, hv], dim=0) + + if self.geo_var is not None: + sample['geo'] = torch.from_numpy(input_data[self.geo_var].values) + + if self.amsr2_vars is not None: + data = np.stack([input_data[var].values for var in self.amsr2_vars]) + sample['amsr2'] = torch.from_numpy(data) + + if self.weather_vars is not None: + data = np.stack([input_data[var].values for var in self.weather_vars]) + sample['weather'] = torch.from_numpy(data) + + input_data.close() + + return sample + + def _load_label(self, path: str) -> Tensor: + """Load the label from the given path. + + Args: + path: path to the label file + + Returns: + A tensor containing the label data + """ + # in test directory label is under a separate file + if self.split == 'test': + # append 'reference' to the input path to get the reference file + path = path.replace('.nc', '_reference.nc') + + target_data = xr.open_dataset(path) + # NaN values in target data have value 255 + tensor = torch.from_numpy(target_data[self.target_var].values).long() + target_data.close() + + return tensor + + def _verify(self) -> None: + """Verify integrity of the dataset.""" + # check if metadata file exists + exists = [] + if os.path.exists(os.path.join(self.root, 'metadata.csv')): + df = pd.read_csv(os.path.join(self.root, 'metadata.csv')) + for i, row in df.iterrows(): + exists.append( + os.path.exists(os.path.join(self.root, row['input_path'])) + ) + else: + exists.append(False) + + if all(exists): + return + + # check presence of tarball files + exists = [ + os.path.exists(os.path.join(self.root, file['name'])) for file in self.files + ] + if all(exists): + return + + if not self.download: + raise DatasetNotFoundError(self) + + self._download_data() + self._extract_data() + + def _download_data(self) -> None: + """Download data.""" + for file in self.files: + download_url( + self.url.format(file['name']), + self.root, + md5=file['md5'] if self.checksum else None, + ) + + def _extract_data(self) -> None: + """Extract the dataset.""" + # Concatenate the train tarballs together + chunk_size = 2**15 # same as torchvision + path = os.path.join(self.root, 'train.tar.gz') + with open(path, 'wb') as f: + for split in ['aa', 'ab']: + with open(os.path.join(self.root, f'train.tar.gz{split}'), 'rb') as g: + while chunk := g.read(chunk_size): + f.write(chunk) + extract_archive(path, self.root) + + # Extract test tarball + extract_archive(os.path.join(self.root, 'test.tar.gz'), self.root) + + def plot( + self, + sample: dict[str, Tensor], + show_titles: bool = True, + suptitle: str | None = None, + ) -> Figure: + """Plot a sample from the dataset. + + Args: + sample: a sample returned by :meth:`CaFFe.__getitem__` + show_titles: flag indicating whether to show titles above each panel + suptitle: optional string to use as a suptitle + + Returns: + a matplotlib Figure with the rendered sample + """ + if 'prediction' in sample: + ncols = 3 + else: + ncols = 2 + + class_mapping = getattr(self, f'{self.target_var}_GROUPS') + # add 255 for NaN values + class_mapping[255] = 'NaN' + + num_classes = len(class_mapping) + + fig, axs = plt.subplots(1, ncols, figsize=(15, 7)) + + # Plot SAR image (HH channel) with proper normalization + hh_image = sample['image'][0].numpy() + vmin, vmax = np.nanpercentile(hh_image, (2, 98)) # robust normalization + axs[0].imshow(hh_image, cmap='gray', vmin=vmin, vmax=vmax) + axs[0].axis('off') + if show_titles: + axs[0].set_title('SAR HH Channel') + + # Create colormap with transparent color for NaN + colors = plt.cm.tab20(np.linspace(0, 1, num_classes)) + # colors = np.vstack((colors, [1, 1, 1, 0])) # add transparent for NaN + cmap = plt.cm.colors.ListedColormap(colors) + + # Plot mask with proper handling of NaN values + # import pdb + # pdb.set_trace() + mask = sample['mask'].numpy() + # mask_ma = ma.masked_where(mask == 255, mask) # mask NaN values + axs[1].imshow(mask, cmap=cmap, vmin=0, vmax=num_classes) + if show_titles: + axs[1].set_title(f'{self.target_var} Mask') + axs[1].axis('off') + + if 'prediction' in sample: + prediction = sample['prediction'].numpy() + # pred_ma = ma.masked_where(prediction == 255, prediction) + axs[2].imshow(prediction, cmap=cmap) + if show_titles: + axs[2].set_title('Prediction Mask') + axs[2].axis('off') + + # create legend with class names + # import pdb + # pdb.set_trace() + legend_elements = [ + Patch(facecolor=colors[i], label=list(class_mapping.values())[i]) + for i in range(num_classes) + ] + fig.legend( + handles=legend_elements, + loc='center right', + bbox_to_anchor=(0.98, 0.5), + title=self.target_var, + ) + + if suptitle is not None: + fig.suptitle(suptitle) + + return fig From e3a3206f7b90c5c387824e71098227af7b94641b Mon Sep 17 00:00:00 2001 From: Nils Lehmann Date: Fri, 24 Jan 2025 08:26:38 +0000 Subject: [PATCH 2/2] add version 3 --- torchgeo/datasets/ai4arctic_sea_ice.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/torchgeo/datasets/ai4arctic_sea_ice.py b/torchgeo/datasets/ai4arctic_sea_ice.py index d5754378c1a..f3f157819df 100644 --- a/torchgeo/datasets/ai4arctic_sea_ice.py +++ b/torchgeo/datasets/ai4arctic_sea_ice.py @@ -27,10 +27,11 @@ class AI4ArcticSeaIce(NonGeoDataset): """AI4Artic Sea Ice Dataset. - The Sea Ice Challenge Dataset contains Sentinel-1 SAR imagery, passive microwave radiometer observations + The `AI4ArcticSea Ice Challenge Dataset `_ contains Sentinel-1 SAR imagery, passive microwave radiometer observations from AMSR2, and numerical weather prediction data from the ECMWF Reanalysis v5 (ERA5) dataset - all gridded to match the Sentinel-1 SAR scenes geometrically. As label data, the dataset contains ice charts - manually produced by the ice analysts at the Greenland Ice Service and the Canadian Ice Service. + manually produced by the ice analysts at the Greenland Ice Service and the Canadian Ice Service. This is the + "Ready-To-Train" version of the dataset (Version 3). Dataset features: