We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Handle edge case: reading a window that overflows the RasterSource's extent that has been artificially cropped using extent_crop can cause data leakage
If, for example, there are two `RasterSource`'s with cropped extents such that they both respectively read from the left and right halves of the same underlying image, there will be instances at the boundary where windows straddle both halves. This is not an issue if the two halves are both in the training split or both in the validation split, but if they are in different splits, there will be data leakage.

**Possible solution**
`RasterSource` should fill the overflowing regions in the window with NODATA values if `extent_crop` is set. This can be a configurable option so that it can be enabled only when the image is part of scenes from different data splits.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `rastervision_core/rastervision/core/data/raster_source/rasterio_source.py`
Content:
```
1 import logging
2 import math
3 import os
4 from pyproj import Transformer
5 import subprocess
6 from decimal import Decimal
7 import tempfile
8 from typing import Optional
9 
10 import numpy as np
11 import rasterio
12 from rasterio.enums import (ColorInterp, MaskFlags)
13 
14 from rastervision.pipeline.file_system import download_if_needed
15 from rastervision.core.box import Box
16 from rastervision.core.data.crs_transformer import RasterioCRSTransformer
17 from rastervision.core.data.raster_source import (RasterSource, CropOffsets)
18 from rastervision.core.data import (ActivateMixin, ActivationError)
19 
20 log = logging.getLogger(__name__)
21 wgs84 = 'epsg:4326'
22 meters_per_degree = 111319.5
23 
24 
25 def build_vrt(vrt_path, image_paths):
26     """Build a VRT for a set of TIFF files."""
27     cmd = ['gdalbuildvrt', vrt_path]
28     cmd.extend(image_paths)
29     subprocess.run(cmd)
30 
31 
32 def download_and_build_vrt(image_uris, tmp_dir):
33     log.info('Building VRT...')
34     image_paths = [download_if_needed(uri, tmp_dir) for uri in image_uris]
35     image_path = os.path.join(tmp_dir, 'index.vrt')
36     build_vrt(image_path, image_paths)
37     return image_path
38 
39 
40 def stream_and_build_vrt(images_uris, tmp_dir):
41     log.info('Building VRT...')
42     image_paths = images_uris
43     image_path = os.path.join(tmp_dir, 'index.vrt')
44     build_vrt(image_path, image_paths)
45     return image_path
46 
47 
48 def load_window(image_dataset, window=None, is_masked=False):
49     """Load a window of an image using Rasterio.
50 
51     Args:
52         image_dataset: a Rasterio dataset
53         window: ((row_start, row_stop), (col_start, col_stop)) or
54         ((y_min, y_max), (x_min, x_max))
55         is_masked: If True, read a masked array from rasterio
56 
57     Returns:
58         np.ndarray of shape (height, width, channels) where channels is the number of
59             channels in the image_dataset.
60     """
61     if is_masked:
62         im = image_dataset.read(window=window, boundless=True, masked=True)
63         im = np.ma.filled(im, fill_value=0)
64     else:
65         im = image_dataset.read(window=window, boundless=True)
66 
67     # Handle non-zero NODATA values by setting the data to 0.
68     for channel, nodata in enumerate(image_dataset.nodatavals):
69         if nodata is not None and nodata != 0:
70             im[channel, im[channel] == nodata] = 0
71 
72     im = np.transpose(im, axes=[1, 2, 0])
73     return im
74 
75 
76 class RasterioSource(ActivateMixin, RasterSource):
77     def __init__(self,
78                  uris,
79                  raster_transformers,
80                  tmp_dir,
81                  allow_streaming=False,
82                  channel_order=None,
83                  x_shift=0.0,
84                  y_shift=0.0,
85                  extent_crop: Optional[CropOffsets] = None):
86         """Constructor.
87 
88         This RasterSource can read any file that can be opened by Rasterio/GDAL
89         including georeferenced formats such as GeoTIFF and non-georeferenced formats
90         such as JPG. See https://www.gdal.org/formats_list.html for more details.
91 
92         If channel_order is None, then use non-alpha channels. This also sets any
93         masked or NODATA pixel values to be zeros.
94 
95         Args:
96             channel_order: list of indices of channels to extract from raw imagery
97             extent_crop (CropOffsets, optional): Relative
98                 offsets (top, left, bottom, right) for cropping the extent.
99                 Useful for using splitting a scene into different datasets.
100                 Defaults to None i.e. no cropping.
101         """
102         self.uris = uris
103         self.tmp_dir = tmp_dir
104         self.image_tmp_dir = None
105         self.image_dataset = None
106         self.x_shift = x_shift
107         self.y_shift = y_shift
108         self.do_shift = self.x_shift != 0.0 or self.y_shift != 0.0
109         self.allow_streaming = allow_streaming
110         self.extent_crop = extent_crop
111 
112         num_channels = None
113 
114         # Activate in order to get information out of the raster
115         with self.activate():
116             num_channels = self.image_dataset.count
117             if channel_order is None:
118                 colorinterp = self.image_dataset.colorinterp
119                 if colorinterp:
120                     channel_order = [
121                         i for i, color_interp in enumerate(colorinterp)
122                         if color_interp != ColorInterp.alpha
123                     ]
124                 else:
125                     channel_order = list(range(0, num_channels))
126             self.validate_channel_order(channel_order, num_channels)
127 
128             mask_flags = self.image_dataset.mask_flag_enums
129             self.is_masked = any(
130                 [m for m in mask_flags if m != MaskFlags.all_valid])
131 
132             self.height = self.image_dataset.height
133             self.width = self.image_dataset.width
134 
135             # Get 1x1 chip and apply raster transformers to test dtype.
136             test_chip = self.get_raw_chip(Box.make_square(0, 0, 1))
137             test_chip = test_chip[:, :, channel_order]
138             for transformer in raster_transformers:
139                 test_chip = transformer.transform(test_chip, channel_order)
140             self.dtype = test_chip.dtype
141 
142             self._set_crs_transformer()
143 
144         super().__init__(channel_order, num_channels, raster_transformers)
145 
146     def _download_data(self, tmp_dir):
147         """Download any data needed for this Raster Source.
148 
149         Return a single local path representing the image or a VRT of the data.
150         """
151         if len(self.uris) == 1:
152             if self.allow_streaming:
153                 return self.uris[0]
154             else:
155                 return download_if_needed(self.uris[0], tmp_dir)
156         else:
157             if self.allow_streaming:
158                 return stream_and_build_vrt(self.uris, tmp_dir)
159             else:
160                 return download_and_build_vrt(self.uris, tmp_dir)
161 
162     def get_crs_transformer(self):
163         return self.crs_transformer
164 
165     def get_extent(self):
166         h, w = self.height, self.width
167         if self.extent_crop is not None:
168             skip_top, skip_left, skip_bottom, skip_right = self.extent_crop
169             ymin, xmin = int(h * skip_top), int(w * skip_left)
170             ymax, xmax = h - int(h * skip_bottom), w - int(w * skip_right)
171             return Box(ymin, xmin, ymax, xmax)
172         return Box(0, 0, h, w)
173 
174     def get_dtype(self):
175         """Return the numpy.dtype of this scene"""
176         return self.dtype
177 
178     def _get_chip(self, window):
179         if self.image_dataset is None:
180             raise ActivationError('RasterSource must be activated before use')
181         shifted_window = self._get_shifted_window(window)
182         return load_window(
183             self.image_dataset,
184             window=shifted_window.rasterio_format(),
185             is_masked=self.is_masked)
186 
187     def _activate(self):
188         # Download images to temporary directory and delete them when done.
189         self.image_tmp_dir = tempfile.TemporaryDirectory(dir=self.tmp_dir)
190         self.imagery_path = self._download_data(self.image_tmp_dir.name)
191         self.image_dataset = rasterio.open(self.imagery_path)
192         self._set_crs_transformer()
193 
194     def _set_crs_transformer(self):
195         self.crs_transformer = RasterioCRSTransformer.from_dataset(
196             self.image_dataset)
197         crs = self.image_dataset.crs
198         self.to_wgs84 = None
199         self.from_wgs84 = None
200         if crs and self.do_shift:
201             self.to_wgs84 = Transformer.from_crs(
202                 crs.wkt, wgs84, always_xy=True)
203             self.from_wgs84 = Transformer.from_crs(
204                 wgs84, crs.wkt, always_xy=True)
205 
206     def _deactivate(self):
207         self.image_dataset.close()
208         self.image_dataset = None
209         self.image_tmp_dir.cleanup()
210         self.image_tmp_dir = None
211 
212     def _get_shifted_window(self, window):
213         do_shift = self.x_shift != 0.0 or self.y_shift != 0.0
214         if do_shift:
215             ymin, xmin, ymax, xmax = window.tuple_format()
216             width = window.get_width()
217             height = window.get_height()
218 
219             # Transform image coordinates into world coordinates
220             transform = self.image_dataset.transform
221             xmin2, ymin2 = transform * (xmin, ymin)
222 
223             # Transform from world coordinates to WGS84
224             if self.to_wgs84:
225                 lon, lat = self.to_wgs84.transform(xmin2, ymin2)
226             else:
227                 lon, lat = xmin2, ymin2
228 
229             # Shift.  This is performed by computing the shifts in
230             # meters to shifts in degrees.  Those shifts are then
231             # applied to the WGS84 coordinate.
232             #
233             # Courtesy of https://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters  # noqa
234             lat_radians = math.pi * lat / 180.0
235             dlon = Decimal(self.x_shift) / Decimal(
236                 meters_per_degree * math.cos(lat_radians))
237             dlat = Decimal(self.y_shift) / Decimal(meters_per_degree)
238             lon = float(Decimal(lon) + dlon)
239             lat = float(Decimal(lat) + dlat)
240 
241             # Transform from WGS84 to world coordinates
242             if self.from_wgs84:
243                 xmin3, ymin3 = self.from_wgs84.transform(lon, lat)
244             else:
245                 xmin3, ymin3 = lon, lat
246 
247             # Trasnform from world coordinates back into image coordinates
248             xmin4, ymin4 = ~transform * (xmin3, ymin3)
249 
250             window = Box(ymin4, xmin4, ymin4 + height, xmin4 + width)
251         return window
252
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/rastervision_core/rastervision/core/data/raster_source/rasterio_source.py b/rastervision_core/rastervision/core/data/raster_source/rasterio_source.py
--- a/rastervision_core/rastervision/core/data/raster_source/rasterio_source.py
+++ b/rastervision_core/rastervision/core/data/raster_source/rasterio_source.py
@@ -73,6 +73,26 @@
     return im
 
 
+def fill_overflow(extent: Box,
+                  window: Box,
+                  arr: np.ndarray,
+                  fill_value: int = 0) -> np.ndarray:
+    """Given a window and corresponding array of values, if the window
+    overflows the extent, fill the overflowing regions with fill_value.
+    """
+    top_overflow = max(0, extent.ymin - window.ymin)
+    bottom_overflow = max(0, window.ymax - extent.ymax)
+    left_overflow = max(0, extent.xmin - window.xmin)
+    right_overflow = max(0, window.xmax - extent.xmax)
+
+    h, w = arr.shape[:2]
+    arr[:top_overflow] = fill_value
+    arr[h - bottom_overflow:] = fill_value
+    arr[:, :left_overflow] = fill_value
+    arr[:, w - right_overflow:] = fill_value
+    return arr
+
+
 class RasterioSource(ActivateMixin, RasterSource):
     def __init__(self,
                  uris,
@@ -175,14 +195,17 @@
         """Return the numpy.dtype of this scene"""
         return self.dtype
 
-    def _get_chip(self, window):
+    def _get_chip(self, window: Box) -> np.ndarray:
         if self.image_dataset is None:
             raise ActivationError('RasterSource must be activated before use')
         shifted_window = self._get_shifted_window(window)
-        return load_window(
+        chip = load_window(
             self.image_dataset,
             window=shifted_window.rasterio_format(),
             is_masked=self.is_masked)
+        if self.extent_crop is not None:
+            chip = fill_overflow(self.get_extent(), window, chip)
+        return chip
 
     def _activate(self):
         # Download images to temporary directory and delete them when done.

{"golden_diff": "diff --git a/rastervision_core/rastervision/core/data/raster_source/rasterio_source.py b/rastervision_core/rastervision/core/data/raster_source/rasterio_source.py\n--- a/rastervision_core/rastervision/core/data/raster_source/rasterio_source.py\n+++ b/rastervision_core/rastervision/core/data/raster_source/rasterio_source.py\n@@ -73,6 +73,26 @@\n     return im\n \n \n+def fill_overflow(extent: Box,\n+                  window: Box,\n+                  arr: np.ndarray,\n+                  fill_value: int = 0) -> np.ndarray:\n+    \"\"\"Given a window and corresponding array of values, if the window\n+    overflows the extent, fill the overflowing regions with fill_value.\n+    \"\"\"\n+    top_overflow = max(0, extent.ymin - window.ymin)\n+    bottom_overflow = max(0, window.ymax - extent.ymax)\n+    left_overflow = max(0, extent.xmin - window.xmin)\n+    right_overflow = max(0, window.xmax - extent.xmax)\n+\n+    h, w = arr.shape[:2]\n+    arr[:top_overflow] = fill_value\n+    arr[h - bottom_overflow:] = fill_value\n+    arr[:, :left_overflow] = fill_value\n+    arr[:, w - right_overflow:] = fill_value\n+    return arr\n+\n+\n class RasterioSource(ActivateMixin, RasterSource):\n     def __init__(self,\n                  uris,\n@@ -175,14 +195,17 @@\n         \"\"\"Return the numpy.dtype of this scene\"\"\"\n         return self.dtype\n \n-    def _get_chip(self, window):\n+    def _get_chip(self, window: Box) -> np.ndarray:\n         if self.image_dataset is None:\n             raise ActivationError('RasterSource must be activated before use')\n         shifted_window = self._get_shifted_window(window)\n-        return load_window(\n+        chip = load_window(\n             self.image_dataset,\n             window=shifted_window.rasterio_format(),\n             is_masked=self.is_masked)\n+        if self.extent_crop is not None:\n+            chip = fill_overflow(self.get_extent(), window, chip)\n+        return chip\n \n     def _activate(self):\n         # Download images to temporary directory and delete them when done.\n", "issue": "Handle edge case: reading a window that overflows the RasterSource's extent that has been artificially cropped using extent_crop can cause data leakage\nIf, for example, there are two `RasterSource`'s with cropped extents such that they both respectively read from the left and right halves of the same underlying image, there will be instances at the boundary where windows straddle both halves. This is not an issue if the two halves are both in the training split or both in the validation split, but if they are in different splits, there will be data leakage.\r\n\r\n**Possible solution**\r\n`RasterSource` should fill the overflowing regions in the window with NODATA values if `extent_crop` is set. This can be a configurable option so that it can be enabled only when the image is part of scenes from different data splits.\n", "before_files": [{"content": "import logging\nimport math\nimport os\nfrom pyproj import Transformer\nimport subprocess\nfrom decimal import Decimal\nimport tempfile\nfrom typing import Optional\n\nimport numpy as np\nimport rasterio\nfrom rasterio.enums import (ColorInterp, MaskFlags)\n\nfrom rastervision.pipeline.file_system import download_if_needed\nfrom rastervision.core.box import Box\nfrom rastervision.core.data.crs_transformer import RasterioCRSTransformer\nfrom rastervision.core.data.raster_source import (RasterSource, CropOffsets)\nfrom rastervision.core.data import (ActivateMixin, ActivationError)\n\nlog = logging.getLogger(__name__)\nwgs84 = 'epsg:4326'\nmeters_per_degree = 111319.5\n\n\ndef build_vrt(vrt_path, image_paths):\n    \"\"\"Build a VRT for a set of TIFF files.\"\"\"\n    cmd = ['gdalbuildvrt', vrt_path]\n    cmd.extend(image_paths)\n    subprocess.run(cmd)\n\n\ndef download_and_build_vrt(image_uris, tmp_dir):\n    log.info('Building VRT...')\n    image_paths = [download_if_needed(uri, tmp_dir) for uri in image_uris]\n    image_path = os.path.join(tmp_dir, 'index.vrt')\n    build_vrt(image_path, image_paths)\n    return image_path\n\n\ndef stream_and_build_vrt(images_uris, tmp_dir):\n    log.info('Building VRT...')\n    image_paths = images_uris\n    image_path = os.path.join(tmp_dir, 'index.vrt')\n    build_vrt(image_path, image_paths)\n    return image_path\n\n\ndef load_window(image_dataset, window=None, is_masked=False):\n    \"\"\"Load a window of an image using Rasterio.\n\n    Args:\n        image_dataset: a Rasterio dataset\n        window: ((row_start, row_stop), (col_start, col_stop)) or\n        ((y_min, y_max), (x_min, x_max))\n        is_masked: If True, read a masked array from rasterio\n\n    Returns:\n        np.ndarray of shape (height, width, channels) where channels is the number of\n            channels in the image_dataset.\n    \"\"\"\n    if is_masked:\n        im = image_dataset.read(window=window, boundless=True, masked=True)\n        im = np.ma.filled(im, fill_value=0)\n    else:\n        im = image_dataset.read(window=window, boundless=True)\n\n    # Handle non-zero NODATA values by setting the data to 0.\n    for channel, nodata in enumerate(image_dataset.nodatavals):\n        if nodata is not None and nodata != 0:\n            im[channel, im[channel] == nodata] = 0\n\n    im = np.transpose(im, axes=[1, 2, 0])\n    return im\n\n\nclass RasterioSource(ActivateMixin, RasterSource):\n    def __init__(self,\n                 uris,\n                 raster_transformers,\n                 tmp_dir,\n                 allow_streaming=False,\n                 channel_order=None,\n                 x_shift=0.0,\n                 y_shift=0.0,\n                 extent_crop: Optional[CropOffsets] = None):\n        \"\"\"Constructor.\n\n        This RasterSource can read any file that can be opened by Rasterio/GDAL\n        including georeferenced formats such as GeoTIFF and non-georeferenced formats\n        such as JPG. See https://www.gdal.org/formats_list.html for more details.\n\n        If channel_order is None, then use non-alpha channels. This also sets any\n        masked or NODATA pixel values to be zeros.\n\n        Args:\n            channel_order: list of indices of channels to extract from raw imagery\n            extent_crop (CropOffsets, optional): Relative\n                offsets (top, left, bottom, right) for cropping the extent.\n                Useful for using splitting a scene into different datasets.\n                Defaults to None i.e. no cropping.\n        \"\"\"\n        self.uris = uris\n        self.tmp_dir = tmp_dir\n        self.image_tmp_dir = None\n        self.image_dataset = None\n        self.x_shift = x_shift\n        self.y_shift = y_shift\n        self.do_shift = self.x_shift != 0.0 or self.y_shift != 0.0\n        self.allow_streaming = allow_streaming\n        self.extent_crop = extent_crop\n\n        num_channels = None\n\n        # Activate in order to get information out of the raster\n        with self.activate():\n            num_channels = self.image_dataset.count\n            if channel_order is None:\n                colorinterp = self.image_dataset.colorinterp\n                if colorinterp:\n                    channel_order = [\n                        i for i, color_interp in enumerate(colorinterp)\n                        if color_interp != ColorInterp.alpha\n                    ]\n                else:\n                    channel_order = list(range(0, num_channels))\n            self.validate_channel_order(channel_order, num_channels)\n\n            mask_flags = self.image_dataset.mask_flag_enums\n            self.is_masked = any(\n                [m for m in mask_flags if m != MaskFlags.all_valid])\n\n            self.height = self.image_dataset.height\n            self.width = self.image_dataset.width\n\n            # Get 1x1 chip and apply raster transformers to test dtype.\n            test_chip = self.get_raw_chip(Box.make_square(0, 0, 1))\n            test_chip = test_chip[:, :, channel_order]\n            for transformer in raster_transformers:\n                test_chip = transformer.transform(test_chip, channel_order)\n            self.dtype = test_chip.dtype\n\n            self._set_crs_transformer()\n\n        super().__init__(channel_order, num_channels, raster_transformers)\n\n    def _download_data(self, tmp_dir):\n        \"\"\"Download any data needed for this Raster Source.\n\n        Return a single local path representing the image or a VRT of the data.\n        \"\"\"\n        if len(self.uris) == 1:\n            if self.allow_streaming:\n                return self.uris[0]\n            else:\n                return download_if_needed(self.uris[0], tmp_dir)\n        else:\n            if self.allow_streaming:\n                return stream_and_build_vrt(self.uris, tmp_dir)\n            else:\n                return download_and_build_vrt(self.uris, tmp_dir)\n\n    def get_crs_transformer(self):\n        return self.crs_transformer\n\n    def get_extent(self):\n        h, w = self.height, self.width\n        if self.extent_crop is not None:\n            skip_top, skip_left, skip_bottom, skip_right = self.extent_crop\n            ymin, xmin = int(h * skip_top), int(w * skip_left)\n            ymax, xmax = h - int(h * skip_bottom), w - int(w * skip_right)\n            return Box(ymin, xmin, ymax, xmax)\n        return Box(0, 0, h, w)\n\n    def get_dtype(self):\n        \"\"\"Return the numpy.dtype of this scene\"\"\"\n        return self.dtype\n\n    def _get_chip(self, window):\n        if self.image_dataset is None:\n            raise ActivationError('RasterSource must be activated before use')\n        shifted_window = self._get_shifted_window(window)\n        return load_window(\n            self.image_dataset,\n            window=shifted_window.rasterio_format(),\n            is_masked=self.is_masked)\n\n    def _activate(self):\n        # Download images to temporary directory and delete them when done.\n        self.image_tmp_dir = tempfile.TemporaryDirectory(dir=self.tmp_dir)\n        self.imagery_path = self._download_data(self.image_tmp_dir.name)\n        self.image_dataset = rasterio.open(self.imagery_path)\n        self._set_crs_transformer()\n\n    def _set_crs_transformer(self):\n        self.crs_transformer = RasterioCRSTransformer.from_dataset(\n            self.image_dataset)\n        crs = self.image_dataset.crs\n        self.to_wgs84 = None\n        self.from_wgs84 = None\n        if crs and self.do_shift:\n            self.to_wgs84 = Transformer.from_crs(\n                crs.wkt, wgs84, always_xy=True)\n            self.from_wgs84 = Transformer.from_crs(\n                wgs84, crs.wkt, always_xy=True)\n\n    def _deactivate(self):\n        self.image_dataset.close()\n        self.image_dataset = None\n        self.image_tmp_dir.cleanup()\n        self.image_tmp_dir = None\n\n    def _get_shifted_window(self, window):\n        do_shift = self.x_shift != 0.0 or self.y_shift != 0.0\n        if do_shift:\n            ymin, xmin, ymax, xmax = window.tuple_format()\n            width = window.get_width()\n            height = window.get_height()\n\n            # Transform image coordinates into world coordinates\n            transform = self.image_dataset.transform\n            xmin2, ymin2 = transform * (xmin, ymin)\n\n            # Transform from world coordinates to WGS84\n            if self.to_wgs84:\n                lon, lat = self.to_wgs84.transform(xmin2, ymin2)\n            else:\n                lon, lat = xmin2, ymin2\n\n            # Shift.  This is performed by computing the shifts in\n            # meters to shifts in degrees.  Those shifts are then\n            # applied to the WGS84 coordinate.\n            #\n            # Courtesy of https://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters  # noqa\n            lat_radians = math.pi * lat / 180.0\n            dlon = Decimal(self.x_shift) / Decimal(\n                meters_per_degree * math.cos(lat_radians))\n            dlat = Decimal(self.y_shift) / Decimal(meters_per_degree)\n            lon = float(Decimal(lon) + dlon)\n            lat = float(Decimal(lat) + dlat)\n\n            # Transform from WGS84 to world coordinates\n            if self.from_wgs84:\n                xmin3, ymin3 = self.from_wgs84.transform(lon, lat)\n            else:\n                xmin3, ymin3 = lon, lat\n\n            # Trasnform from world coordinates back into image coordinates\n            xmin4, ymin4 = ~transform * (xmin3, ymin3)\n\n            window = Box(ymin4, xmin4, ymin4 + height, xmin4 + width)\n        return window\n", "path": "rastervision_core/rastervision/core/data/raster_source/rasterio_source.py"}], "after_files": [{"content": "import logging\nimport math\nimport os\nfrom pyproj import Transformer\nimport subprocess\nfrom decimal import Decimal\nimport tempfile\nfrom typing import Optional\n\nimport numpy as np\nimport rasterio\nfrom rasterio.enums import (ColorInterp, MaskFlags)\n\nfrom rastervision.pipeline.file_system import download_if_needed\nfrom rastervision.core.box import Box\nfrom rastervision.core.data.crs_transformer import RasterioCRSTransformer\nfrom rastervision.core.data.raster_source import (RasterSource, CropOffsets)\nfrom rastervision.core.data import (ActivateMixin, ActivationError)\n\nlog = logging.getLogger(__name__)\nwgs84 = 'epsg:4326'\nmeters_per_degree = 111319.5\n\n\ndef build_vrt(vrt_path, image_paths):\n    \"\"\"Build a VRT for a set of TIFF files.\"\"\"\n    cmd = ['gdalbuildvrt', vrt_path]\n    cmd.extend(image_paths)\n    subprocess.run(cmd)\n\n\ndef download_and_build_vrt(image_uris, tmp_dir):\n    log.info('Building VRT...')\n    image_paths = [download_if_needed(uri, tmp_dir) for uri in image_uris]\n    image_path = os.path.join(tmp_dir, 'index.vrt')\n    build_vrt(image_path, image_paths)\n    return image_path\n\n\ndef stream_and_build_vrt(images_uris, tmp_dir):\n    log.info('Building VRT...')\n    image_paths = images_uris\n    image_path = os.path.join(tmp_dir, 'index.vrt')\n    build_vrt(image_path, image_paths)\n    return image_path\n\n\ndef load_window(image_dataset, window=None, is_masked=False):\n    \"\"\"Load a window of an image using Rasterio.\n\n    Args:\n        image_dataset: a Rasterio dataset\n        window: ((row_start, row_stop), (col_start, col_stop)) or\n        ((y_min, y_max), (x_min, x_max))\n        is_masked: If True, read a masked array from rasterio\n\n    Returns:\n        np.ndarray of shape (height, width, channels) where channels is the number of\n            channels in the image_dataset.\n    \"\"\"\n    if is_masked:\n        im = image_dataset.read(window=window, boundless=True, masked=True)\n        im = np.ma.filled(im, fill_value=0)\n    else:\n        im = image_dataset.read(window=window, boundless=True)\n\n    # Handle non-zero NODATA values by setting the data to 0.\n    for channel, nodata in enumerate(image_dataset.nodatavals):\n        if nodata is not None and nodata != 0:\n            im[channel, im[channel] == nodata] = 0\n\n    im = np.transpose(im, axes=[1, 2, 0])\n    return im\n\n\ndef fill_overflow(extent: Box,\n                  window: Box,\n                  arr: np.ndarray,\n                  fill_value: int = 0) -> np.ndarray:\n    \"\"\"Given a window and corresponding array of values, if the window\n    overflows the extent, fill the overflowing regions with fill_value.\n    \"\"\"\n    top_overflow = max(0, extent.ymin - window.ymin)\n    bottom_overflow = max(0, window.ymax - extent.ymax)\n    left_overflow = max(0, extent.xmin - window.xmin)\n    right_overflow = max(0, window.xmax - extent.xmax)\n\n    h, w = arr.shape[:2]\n    arr[:top_overflow] = fill_value\n    arr[h - bottom_overflow:] = fill_value\n    arr[:, :left_overflow] = fill_value\n    arr[:, w - right_overflow:] = fill_value\n    return arr\n\n\nclass RasterioSource(ActivateMixin, RasterSource):\n    def __init__(self,\n                 uris,\n                 raster_transformers,\n                 tmp_dir,\n                 allow_streaming=False,\n                 channel_order=None,\n                 x_shift=0.0,\n                 y_shift=0.0,\n                 extent_crop: Optional[CropOffsets] = None):\n        \"\"\"Constructor.\n\n        This RasterSource can read any file that can be opened by Rasterio/GDAL\n        including georeferenced formats such as GeoTIFF and non-georeferenced formats\n        such as JPG. See https://www.gdal.org/formats_list.html for more details.\n\n        If channel_order is None, then use non-alpha channels. This also sets any\n        masked or NODATA pixel values to be zeros.\n\n        Args:\n            channel_order: list of indices of channels to extract from raw imagery\n            extent_crop (CropOffsets, optional): Relative\n                offsets (top, left, bottom, right) for cropping the extent.\n                Useful for using splitting a scene into different datasets.\n                Defaults to None i.e. no cropping.\n        \"\"\"\n        self.uris = uris\n        self.tmp_dir = tmp_dir\n        self.image_tmp_dir = None\n        self.image_dataset = None\n        self.x_shift = x_shift\n        self.y_shift = y_shift\n        self.do_shift = self.x_shift != 0.0 or self.y_shift != 0.0\n        self.allow_streaming = allow_streaming\n        self.extent_crop = extent_crop\n\n        num_channels = None\n\n        # Activate in order to get information out of the raster\n        with self.activate():\n            num_channels = self.image_dataset.count\n            if channel_order is None:\n                colorinterp = self.image_dataset.colorinterp\n                if colorinterp:\n                    channel_order = [\n                        i for i, color_interp in enumerate(colorinterp)\n                        if color_interp != ColorInterp.alpha\n                    ]\n                else:\n                    channel_order = list(range(0, num_channels))\n            self.validate_channel_order(channel_order, num_channels)\n\n            mask_flags = self.image_dataset.mask_flag_enums\n            self.is_masked = any(\n                [m for m in mask_flags if m != MaskFlags.all_valid])\n\n            self.height = self.image_dataset.height\n            self.width = self.image_dataset.width\n\n            # Get 1x1 chip and apply raster transformers to test dtype.\n            test_chip = self.get_raw_chip(Box.make_square(0, 0, 1))\n            test_chip = test_chip[:, :, channel_order]\n            for transformer in raster_transformers:\n                test_chip = transformer.transform(test_chip, channel_order)\n            self.dtype = test_chip.dtype\n\n            self._set_crs_transformer()\n\n        super().__init__(channel_order, num_channels, raster_transformers)\n\n    def _download_data(self, tmp_dir):\n        \"\"\"Download any data needed for this Raster Source.\n\n        Return a single local path representing the image or a VRT of the data.\n        \"\"\"\n        if len(self.uris) == 1:\n            if self.allow_streaming:\n                return self.uris[0]\n            else:\n                return download_if_needed(self.uris[0], tmp_dir)\n        else:\n            if self.allow_streaming:\n                return stream_and_build_vrt(self.uris, tmp_dir)\n            else:\n                return download_and_build_vrt(self.uris, tmp_dir)\n\n    def get_crs_transformer(self):\n        return self.crs_transformer\n\n    def get_extent(self):\n        h, w = self.height, self.width\n        if self.extent_crop is not None:\n            skip_top, skip_left, skip_bottom, skip_right = self.extent_crop\n            ymin, xmin = int(h * skip_top), int(w * skip_left)\n            ymax, xmax = h - int(h * skip_bottom), w - int(w * skip_right)\n            return Box(ymin, xmin, ymax, xmax)\n        return Box(0, 0, h, w)\n\n    def get_dtype(self):\n        \"\"\"Return the numpy.dtype of this scene\"\"\"\n        return self.dtype\n\n    def _get_chip(self, window: Box) -> np.ndarray:\n        if self.image_dataset is None:\n            raise ActivationError('RasterSource must be activated before use')\n        shifted_window = self._get_shifted_window(window)\n        chip = load_window(\n            self.image_dataset,\n            window=shifted_window.rasterio_format(),\n            is_masked=self.is_masked)\n        if self.extent_crop is not None:\n            chip = fill_overflow(self.get_extent(), window, chip)\n        return chip\n\n    def _activate(self):\n        # Download images to temporary directory and delete them when done.\n        self.image_tmp_dir = tempfile.TemporaryDirectory(dir=self.tmp_dir)\n        self.imagery_path = self._download_data(self.image_tmp_dir.name)\n        self.image_dataset = rasterio.open(self.imagery_path)\n        self._set_crs_transformer()\n\n    def _set_crs_transformer(self):\n        self.crs_transformer = RasterioCRSTransformer.from_dataset(\n            self.image_dataset)\n        crs = self.image_dataset.crs\n        self.to_wgs84 = None\n        self.from_wgs84 = None\n        if crs and self.do_shift:\n            self.to_wgs84 = Transformer.from_crs(\n                crs.wkt, wgs84, always_xy=True)\n            self.from_wgs84 = Transformer.from_crs(\n                wgs84, crs.wkt, always_xy=True)\n\n    def _deactivate(self):\n        self.image_dataset.close()\n        self.image_dataset = None\n        self.image_tmp_dir.cleanup()\n        self.image_tmp_dir = None\n\n    def _get_shifted_window(self, window):\n        do_shift = self.x_shift != 0.0 or self.y_shift != 0.0\n        if do_shift:\n            ymin, xmin, ymax, xmax = window.tuple_format()\n            width = window.get_width()\n            height = window.get_height()\n\n            # Transform image coordinates into world coordinates\n            transform = self.image_dataset.transform\n            xmin2, ymin2 = transform * (xmin, ymin)\n\n            # Transform from world coordinates to WGS84\n            if self.to_wgs84:\n                lon, lat = self.to_wgs84.transform(xmin2, ymin2)\n            else:\n                lon, lat = xmin2, ymin2\n\n            # Shift.  This is performed by computing the shifts in\n            # meters to shifts in degrees.  Those shifts are then\n            # applied to the WGS84 coordinate.\n            #\n            # Courtesy of https://gis.stackexchange.com/questions/2951/algorithm-for-offsetting-a-latitude-longitude-by-some-amount-of-meters  # noqa\n            lat_radians = math.pi * lat / 180.0\n            dlon = Decimal(self.x_shift) / Decimal(\n                meters_per_degree * math.cos(lat_radians))\n            dlat = Decimal(self.y_shift) / Decimal(meters_per_degree)\n            lon = float(Decimal(lon) + dlon)\n            lat = float(Decimal(lat) + dlat)\n\n            # Transform from WGS84 to world coordinates\n            if self.from_wgs84:\n                xmin3, ymin3 = self.from_wgs84.transform(lon, lat)\n            else:\n                xmin3, ymin3 = lon, lat\n\n            # Trasnform from world coordinates back into image coordinates\n            xmin4, ymin4 = ~transform * (xmin3, ymin3)\n\n            window = Box(ymin4, xmin4, ymin4 + height, xmin4 + width)\n        return window\n", "path": "rastervision_core/rastervision/core/data/raster_source/rasterio_source.py"}]}