diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 96fdb226f3c..900e9ea7a96 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -18,7 +18,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: ['3.10', '3.11', '3.12', '3.13'] + python-version: ['3.11', '3.12', '3.13'] steps: - name: Clone repo uses: actions/checkout@v4.2.2 @@ -59,7 +59,7 @@ jobs: id: setup-python uses: actions/setup-python@v5.3.0 with: - python-version: '3.10' + python-version: '3.11' - name: Cache dependencies uses: actions/cache@v4.2.0 id: cache diff --git a/pyproject.toml b/pyproject.toml index df0c51cd2b7..55f2134f07f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta" name = "torchgeo" description = "TorchGeo: datasets, samplers, transforms, and pre-trained models for geospatial data" readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.11" license = {file = "LICENSE"} authors = [ {name = "Adam J. Stewart", email = "ajstewart426@gmail.com"}, @@ -29,7 +29,6 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", @@ -39,8 +38,8 @@ classifiers = [ dependencies = [ # einops 0.3+ required for einops.repeat "einops>=0.3", - # fiona 1.8.21+ required for Python 3.10 wheels - "fiona>=1.8.21", + # fiona 1.8.22+ required for Python 3.11 wheels + "fiona>=1.8.22", # kornia 0.7.4+ required for AugmentationSequential support for unknown keys "kornia>=0.7.4", # lightly 1.4.5+ required for LARS optimizer @@ -51,53 +50,53 @@ dependencies = [ # lightning 2.3 contains known bugs related to YAML parsing # https://github.com/Lightning-AI/pytorch-lightning/issues/19977 "lightning[pytorch-extra]>=2,!=2.3.*,!=2.5.0", - # matplotlib 3.5+ required for Python 3.10 wheels - "matplotlib>=3.5", - # numpy 1.21.2+ required by Python 3.10 wheels - "numpy>=1.21.2", - # pandas 1.3.3+ required for Python 3.10 wheels - "pandas>=1.3.3", - # pillow 8.4+ required for Python 3.10 wheels - "pillow>=8.4", - # pyproj 3.3+ required for Python 3.10 wheels - "pyproj>=3.3", - # rasterio 1.3+ required for Python 3.10 wheels + # matplotlib 3.6+ required for Python 3.11 wheels + "matplotlib>=3.6", + # numpy 1.23.2+ required by Python 3.11 wheels + "numpy>=1.23.2", + # pandas 1.5+ required for Python 3.11 wheels + "pandas>=1.5", + # pillow 9.2+ required for Python 3.11 wheels + "pillow>=9.2", + # pyproj 3.4+ required for Python 3.11 wheels + "pyproj>=3.4", + # rasterio 1.3.3+ required for Python 3.11 wheels # rasterio 1.4.0-1.4.2 lack support for merging WarpedVRT objects # https://github.com/rasterio/rasterio/issues/3196 - "rasterio>=1.3,!=1.4.0,!=1.4.1,!=1.4.2", - # rtree 1+ required for Python 3.10 wheels - "rtree>=1", + "rasterio>=1.3.3,!=1.4.0,!=1.4.1,!=1.4.2", + # rtree 1.0.1+ required for Python 3.11 wheels + "rtree>=1.0.1", # segmentation-models-pytorch 0.2+ required for smp.losses module "segmentation-models-pytorch>=0.2", - # shapely 1.8+ required for Python 3.10 wheels - "shapely>=1.8", + # shapely 1.8.5+ required for Python 3.11 wheels + "shapely>=1.8.5", # timm 0.4.12 required by segmentation-models-pytorch "timm>=0.4.12", - # torch 1.13+ required by torchvision - "torch>=1.13", + # torch 2+ required for Python 3.11 wheels + "torch>=2", # torchmetrics 0.10+ required for binary/multiclass/multilabel classification metrics "torchmetrics>=0.10", - # torchvision 0.14+ required for torchvision.models.swin_v2_b - "torchvision>=0.14", + # torchvision 0.15.1+ required for Python 3.11 wheels + "torchvision>=0.15.1", ] dynamic = ["version"] [project.optional-dependencies] datasets = [ - # h5py 3.6+ required for Python 3.10 wheels - "h5py>=3.6", + # h5py 3.8+ required for Python 3.11 wheels + "h5py>=3.8", # laspy 2+ required for laspy.read "laspy>=2", - # opencv-python 4.5.4+ required for Python 3.10 wheels - "opencv-python>=4.5.4", + # opencv-python 4.5.5+ required for Python 3.11 wheels + "opencv-python>=4.5.5", # pandas 2+ required for parquet extra "pandas[parquet]>=2", - # pycocotools 2.0.7+ required for wheels + # pycocotools 2.0.7+ required for Python 3.11 wheels "pycocotools>=2.0.7", - # scikit-image 0.19+ required for Python 3.10 wheels - "scikit-image>=0.19", - # scipy 1.7.2+ required for Python 3.10 wheels - "scipy>=1.7.2", + # scikit-image 0.20+ required for Python 3.11 wheels + "scikit-image>=0.20", + # scipy 1.9.2+ required for Python 3.11 wheels + "scipy>=1.9.2", ] docs = [ # ipywidgets 7+ required by nbsphinx diff --git a/requirements/min-reqs.old b/requirements/min-reqs.old index e1058142ac6..755abf6f685 100644 --- a/requirements/min-reqs.old +++ b/requirements/min-reqs.old @@ -3,32 +3,32 @@ setuptools==61.0.0 # install einops==0.3.0 -fiona==1.8.21 +fiona==1.8.22 kornia==0.7.4 lightly==1.4.5 lightning[pytorch-extra]==2.0.0 -matplotlib==3.5.0 -numpy==1.21.2 -pandas==1.3.3 -pillow==8.4.0 -pyproj==3.3.0 -rasterio==1.3.0.post1 -rtree==1.0.0 +matplotlib==3.6.0 +numpy==1.23.2 +pandas==1.5.0 +pillow==9.2.0 +pyproj==3.4.0 +rasterio==1.3.3 +rtree==1.0.1 segmentation-models-pytorch==0.2.0 -shapely==1.8.0 +shapely==1.8.5 timm==0.4.12 -torch==1.13.0 +torch==2.0.0 torchmetrics==0.10.0 -torchvision==0.14.0 +torchvision==0.15.1 # datasets -h5py==3.6.0 +h5py==3.8.0 laspy==2.0.0 -opencv-python==4.5.4.58 +opencv-python==4.5.5.64 pycocotools==2.0.7 pyarrow==15.0.0 # Remove when we upgrade min version of pandas to `pandas[parquet]>=2` -scikit-image==0.19.0 -scipy==1.7.2 +scikit-image==0.20.0 +scipy==1.9.2 # tests pytest==7.3.0 diff --git a/tests/datasets/test_advance.py b/tests/datasets/test_advance.py index 12d20c0fd76..a2348f793a7 100644 --- a/tests/datasets/test_advance.py +++ b/tests/datasets/test_advance.py @@ -12,7 +12,7 @@ from torchgeo.datasets import ADVANCE, DatasetNotFoundError -pytest.importorskip('scipy', minversion='1.7.2') +pytest.importorskip('scipy', minversion='1.9.2') class TestADVANCE: diff --git a/tests/datasets/test_cabuar.py b/tests/datasets/test_cabuar.py index 967f43ee4d3..89eb3a8b33f 100644 --- a/tests/datasets/test_cabuar.py +++ b/tests/datasets/test_cabuar.py @@ -14,7 +14,7 @@ from torchgeo.datasets import CaBuAr, DatasetNotFoundError -pytest.importorskip('h5py', minversion='3.6') +pytest.importorskip('h5py', minversion='3.8') class TestCaBuAr: diff --git a/tests/datasets/test_chabud.py b/tests/datasets/test_chabud.py index fed0aed5087..cdd76709414 100644 --- a/tests/datasets/test_chabud.py +++ b/tests/datasets/test_chabud.py @@ -13,7 +13,7 @@ from torchgeo.datasets import ChaBuD, DatasetNotFoundError -pytest.importorskip('h5py', minversion='3.6') +pytest.importorskip('h5py', minversion='3.8') class TestChaBuD: diff --git a/tests/datasets/test_cropharvest.py b/tests/datasets/test_cropharvest.py index 3d77ac2b5fe..a8e4100d4a6 100644 --- a/tests/datasets/test_cropharvest.py +++ b/tests/datasets/test_cropharvest.py @@ -13,7 +13,7 @@ from torchgeo.datasets import CropHarvest, DatasetNotFoundError -pytest.importorskip('h5py', minversion='3.6') +pytest.importorskip('h5py', minversion='3.8') class TestCropHarvest: diff --git a/tests/datasets/test_digital_typhoon.py b/tests/datasets/test_digital_typhoon.py index c3df283ec35..b105145c021 100644 --- a/tests/datasets/test_digital_typhoon.py +++ b/tests/datasets/test_digital_typhoon.py @@ -14,7 +14,7 @@ from torchgeo.datasets import DatasetNotFoundError, DigitalTyphoon -pytest.importorskip('h5py', minversion='3.6') +pytest.importorskip('h5py', minversion='3.8') class TestDigitalTyphoon: diff --git a/tests/datasets/test_landcoverai.py b/tests/datasets/test_landcoverai.py index cda68604599..b64b1d5e090 100644 --- a/tests/datasets/test_landcoverai.py +++ b/tests/datasets/test_landcoverai.py @@ -72,7 +72,7 @@ def test_plot(self, dataset: LandCoverAIGeo) -> None: class TestLandCoverAI: - pytest.importorskip('cv2', minversion='4.5.4') + pytest.importorskip('cv2', minversion='4.5.5') @pytest.fixture( params=product([LandCoverAI100, LandCoverAI], ['train', 'val', 'test']) diff --git a/tests/datasets/test_mmearth.py b/tests/datasets/test_mmearth.py index c25c2a1dece..12117080e0e 100644 --- a/tests/datasets/test_mmearth.py +++ b/tests/datasets/test_mmearth.py @@ -12,7 +12,7 @@ from torchgeo.datasets import DatasetNotFoundError, MMEarth -pytest.importorskip('h5py', minversion='3.6') +pytest.importorskip('h5py', minversion='3.8') data_dir_dict = { 'MMEarth': os.path.join('tests', 'data', 'mmearth', 'data_1M_v001'), diff --git a/tests/datasets/test_quakeset.py b/tests/datasets/test_quakeset.py index fbb6ea29234..ade1b124866 100644 --- a/tests/datasets/test_quakeset.py +++ b/tests/datasets/test_quakeset.py @@ -13,7 +13,7 @@ from torchgeo.datasets import DatasetNotFoundError, QuakeSet -pytest.importorskip('h5py', minversion='3.6') +pytest.importorskip('h5py', minversion='3.8') class TestQuakeSet: diff --git a/tests/datasets/test_skippd.py b/tests/datasets/test_skippd.py index 68f4e889df8..dc4ca4fedfc 100644 --- a/tests/datasets/test_skippd.py +++ b/tests/datasets/test_skippd.py @@ -15,7 +15,7 @@ from torchgeo.datasets import SKIPPD, DatasetNotFoundError -pytest.importorskip('h5py', minversion='3.6') +pytest.importorskip('h5py', minversion='3.8') class TestSKIPPD: diff --git a/tests/datasets/test_so2sat.py b/tests/datasets/test_so2sat.py index bc88662c16a..11b608b3ec4 100644 --- a/tests/datasets/test_so2sat.py +++ b/tests/datasets/test_so2sat.py @@ -13,7 +13,7 @@ from torchgeo.datasets import DatasetNotFoundError, RGBBandsMissingError, So2Sat -pytest.importorskip('h5py', minversion='3.6') +pytest.importorskip('h5py', minversion='3.8') class TestSo2Sat: @@ -43,9 +43,7 @@ def test_len(self, dataset: So2Sat) -> None: assert len(dataset) == 2 def test_out_of_bounds(self, dataset: So2Sat) -> None: - # h5py at version 2.10.0 raises a ValueError instead of an IndexError so we - # check for both here - with pytest.raises((IndexError, ValueError)): + with pytest.raises(IndexError): dataset[2] def test_invalid_split(self) -> None: diff --git a/tests/datasets/test_vhr10.py b/tests/datasets/test_vhr10.py index aa0920d69e7..2e342d6846e 100644 --- a/tests/datasets/test_vhr10.py +++ b/tests/datasets/test_vhr10.py @@ -72,7 +72,7 @@ def test_not_downloaded(self, tmp_path: Path) -> None: VHR10(tmp_path) def test_plot(self, dataset: VHR10) -> None: - pytest.importorskip('skimage', minversion='0.19') + pytest.importorskip('skimage', minversion='0.20') x = dataset[1].copy() dataset.plot(x, suptitle='Test') plt.close() diff --git a/tests/datasets/test_zuericrop.py b/tests/datasets/test_zuericrop.py index 6d4cdc8844c..00b176c48d1 100644 --- a/tests/datasets/test_zuericrop.py +++ b/tests/datasets/test_zuericrop.py @@ -12,7 +12,7 @@ from torchgeo.datasets import DatasetNotFoundError, RGBBandsMissingError, ZueriCrop -pytest.importorskip('h5py', minversion='3.6') +pytest.importorskip('h5py', minversion='3.8') class TestZueriCrop: diff --git a/torchgeo/datasets/cyclone.py b/torchgeo/datasets/cyclone.py index 2a21832703a..76b1097f009 100644 --- a/torchgeo/datasets/cyclone.py +++ b/torchgeo/datasets/cyclone.py @@ -131,11 +131,7 @@ def _load_image(self, image_id: str) -> Tensor: filename = os.path.join(self.root, self.split, f'{image_id}.jpg') with Image.open(filename) as img: if img.height != self.size or img.width != self.size: - # Moved in PIL 9.1.0 - try: - resample = Image.Resampling.BILINEAR - except AttributeError: - resample = Image.BILINEAR # type: ignore[attr-defined] + resample = Image.Resampling.BILINEAR img = img.resize(size=(self.size, self.size), resample=resample) array: np.typing.NDArray[np.int_] = np.array(img.convert('RGB')) tensor = torch.from_numpy(array) diff --git a/torchgeo/datasets/satlas.py b/torchgeo/datasets/satlas.py index 2f6e79c6c81..8c1cff85800 100644 --- a/torchgeo/datasets/satlas.py +++ b/torchgeo/datasets/satlas.py @@ -639,12 +639,6 @@ def _load_image( row: Web Mercator row. directories: Directories that may contain the image. """ - # Moved in PIL 9.1.0 - try: - resample = Image.Resampling.BILINEAR - except AttributeError: - resample = Image.BILINEAR # type: ignore[attr-defined] - # Find directories that match image product good_directories: list[str] = [] for directory in directories: @@ -659,6 +653,7 @@ def _load_image( sample[f'time_{image}'] = torch.tensor(time) # Load all bands + resample = Image.Resampling.BILINEAR channels = [] for band in self.bands[image]: path = os.path.join(self.root, image, directory, band, f'{col}_{row}.png') diff --git a/torchgeo/datasets/seco.py b/torchgeo/datasets/seco.py index c67fecb9c8e..a16c0fb0877 100644 --- a/torchgeo/datasets/seco.py +++ b/torchgeo/datasets/seco.py @@ -171,11 +171,7 @@ def _load_patch(self, root: Path, subdir: Path) -> Tensor: # slowdown here from converting to/from a PIL Image just to resize. # https://gist.github.com/calebrob6/748045ac8d844154067b2eefa47de92f pil_image = Image.fromarray(band_data) - # Moved in PIL 9.1.0 - try: - resample = Image.Resampling.BILINEAR - except AttributeError: - resample = Image.BILINEAR # type: ignore[attr-defined] + resample = Image.Resampling.BILINEAR band_data = np.array( pil_image.resize((264, 264), resample=resample) ) diff --git a/torchgeo/trainers/moco.py b/torchgeo/trainers/moco.py index 2e7e6907e37..4105211d893 100644 --- a/torchgeo/trainers/moco.py +++ b/torchgeo/trainers/moco.py @@ -23,6 +23,7 @@ from torch.optim.lr_scheduler import ( CosineAnnealingLR, LinearLR, + LRScheduler, MultiStepLR, SequentialLR, ) @@ -34,11 +35,6 @@ from . import utils from .base import BaseTask -try: - from torch.optim.lr_scheduler import LRScheduler -except ImportError: - from torch.optim.lr_scheduler import _LRScheduler as LRScheduler - def moco_augmentations( version: int, size: int, weights: Tensor