From 59e65e24d15daa3e8d0e8298a36a75ac570eb070 Mon Sep 17 00:00:00 2001 From: Adeel Hassan Date: Thu, 10 Oct 2024 13:26:22 -0400 Subject: [PATCH] handle non-deterministic pyproj failure For some transformations, pyproj attempts to download transformation grids from the internet for improved accuracy when Transformer.transform() is called. If it fails to connect to the internet, it silently returns (inf, inf) and silently modifies its behavior to not access the internet on subsequent calls, causing them to succeed (though possibly with a loss of accuracy). See https://github.com/pyproj4/pyproj/issues/705 for details. This workaround forces an error to be raised by setting errcheck=True and ignoring the first error. --- .../rasterio_crs_transformer.py | 56 +++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/rastervision_core/rastervision/core/data/crs_transformer/rasterio_crs_transformer.py b/rastervision_core/rastervision/core/data/crs_transformer/rasterio_crs_transformer.py index 14263b8dc..303549d30 100644 --- a/rastervision_core/rastervision/core/data/crs_transformer/rasterio_crs_transformer.py +++ b/rastervision_core/rastervision/core/data/crs_transformer/rasterio_crs_transformer.py @@ -1,6 +1,8 @@ from typing import TYPE_CHECKING, Any -from pyproj import Transformer +import logging +from pyproj import Transformer +from pyproj.exceptions import ProjError import numpy as np import rasterio as rio from rasterio.transform import (rowcol, xy) @@ -12,6 +14,8 @@ if TYPE_CHECKING: from typing import Self +log = logging.getLogger(__name__) + class RasterioCRSTransformer(CRSTransformer): """Transformer for a RasterioRasterSource.""" @@ -61,7 +65,7 @@ def __repr__(self) -> str: out = f"""{cls_name}( image_crs="{image_crs_str}", map_crs="{map_crs_str}", - round_pixels="{self.round_pixels}", + round_pixels={self.round_pixels}, transform={transform_str}) """ return out @@ -78,7 +82,29 @@ def _map_to_pixel( Returns: (x, y) tuple in pixel coordinates """ - image_point = self.map2image(*map_point) + # For some transformations, pyproj attempts to download transformation + # grids from the internet for improved accuracy when + # Transformer.transform() is called. If it fails to connect to the + # internet, it silently returns (inf, inf) and silently modifies its + # behavior to not access the internet on subsequent calls, causing + # them to succeed (though possibly with a loss of accuracy). See + # https://github.com/pyproj4/pyproj/issues/705 for details. + # + # The below workaround forces an error to be raised by setting + # errcheck=True and ignoring the first error. + try: + image_point = self.map2image(*map_point, errcheck=True) + except ProjError as e: + log.debug(f'pyproj: {e}') + if 'network' in str(e).lower(): + log.warning( + 'pyproj tried and failed to connect to the internet to ' + 'download transformation grids for the transformation from\n' + f'{self.map_crs}\nto\n{self.image_crs}.\nSee ' + 'https://github.com/pyproj4/pyproj/issues/705 for details.' + ) + image_point = self.map2image(*map_point, errcheck=True) + x, y = image_point if self.round_pixels: row, col = rowcol(self.transform, x, y) @@ -103,7 +129,29 @@ def _pixel_to_map( col = col.astype(int) if isinstance(col, np.ndarray) else int(col) row = row.astype(int) if isinstance(row, np.ndarray) else int(row) image_point = xy(self.transform, row, col, offset='center') - map_point = self.image2map(*image_point) + + # For some transformations, pyproj attempts to download transformation + # grids from the internet for improved accuracy when + # Transformer.transform() is called. If it fails to connect to the + # internet, it silently returns (inf, inf) and silently modifies its + # behavior to not access the internet on subsequent calls, causing + # them to succeed (though possibly with a loss of accuracy). See + # https://github.com/pyproj4/pyproj/issues/705 for details. + # + # The below workaround forces an error to be raised by setting + # errcheck=True and ignoring the first error. + try: + map_point = self.image2map(*image_point, errcheck=True) + except ProjError as e: + log.debug(f'pyproj: {e}') + if 'network' in str(e).lower(): + log.warning( + 'pyproj tried and failed to connect to the internet to ' + 'download transformation grids for the transformation from' + f'\n{self.image_crs}\nto\n{self.map_crs}.\nSee ' + 'https://github.com/pyproj4/pyproj/issues/705 for details.' + ) + map_point = self.image2map(*image_point, errcheck=True) return map_point @classmethod