Skip to content

Commit 26d63eb

Browse files
authored
Merge pull request #424 from sentinel-hub/fix/import-from-tiff-bbox
Fixed BBox setting in ImportFromTiffTask
2 parents 71d6579 + 02def8b commit 26d63eb

File tree

3 files changed

+51
-20
lines changed

3 files changed

+51
-20
lines changed

features/requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
eo-learn-core
22
numba>=0.53.0
33
numpy
4-
pillow>=9.0.0
4+
pillow>=9.1.0
55
python-dateutil
66
scikit-image>=0.19.0
77
scikit-learn

io/eolearn/io/local_io.py

+47-19
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import logging
1515
import warnings
1616
from abc import ABCMeta
17-
from typing import BinaryIO, Optional, Union
17+
from typing import BinaryIO, List, Optional, Tuple, Union
1818

1919
import dateutil
2020
import fs
@@ -421,7 +421,7 @@ def _get_session(self) -> AWSSession:
421421
aws_session_token=self.config.aws_session_token or None,
422422
)
423423

424-
def _load_from_image(self, path: str, filesystem: FS, bbox: Optional[BBox]) -> np.ndarray:
424+
def _load_from_image(self, path: str, filesystem: FS, bbox: Optional[BBox]) -> Tuple[np.ndarray, Optional[BBox]]:
425425
"""The method decides in what way data will be loaded the image.
426426
427427
The method always uses `rasterio.Env` to suppress any low-level warnings. In case of a local filesystem
@@ -445,39 +445,65 @@ def _load_from_image(self, path: str, filesystem: FS, bbox: Optional[BBox]) -> n
445445
with filesystem.openbin(path, "r") as file_handle:
446446
return self._read_image(file_handle, bbox)
447447

448-
def _read_image(self, file_object: Union[str, BinaryIO], bbox: Optional[BBox]) -> np.ndarray:
448+
def _read_image(self, file_object: Union[str, BinaryIO], bbox: Optional[BBox]) -> Tuple[np.ndarray, Optional[BBox]]:
449449
"""Reads data from the image."""
450450
with rasterio.open(file_object) as src:
451451
src: DatasetReader
452452

453-
read_window = self._get_reading_window(src, bbox)
453+
read_window, read_bbox = self._get_reading_window_and_bbox(src, bbox)
454454
boundless_reading = read_window is not None
455-
return src.read(window=read_window, boundless=boundless_reading, fill_value=self.no_data_value)
455+
return src.read(window=read_window, boundless=boundless_reading, fill_value=self.no_data_value), read_bbox
456456

457457
@staticmethod
458-
def _get_reading_window(reader: DatasetReader, bbox: Optional[BBox]) -> Optional[Window]:
458+
def _get_reading_window_and_bbox(
459+
reader: DatasetReader, bbox: Optional[BBox]
460+
) -> Tuple[Optional[Window], Optional[BBox]]:
459461
"""Provides a reading window for which data will be read from image. If it returns `None` this means that the
460462
whole image should be read. Those cases are when bbox is not defined, image is not geo-referenced, or
461-
bbox coordinates exactly match image coordinates."""
462-
if bbox is None:
463-
return None
464-
463+
bbox coordinates exactly match image coordinates. Additionally, it provides a bounding box of reading window
464+
if an image is geo-referenced."""
465465
image_crs = reader.crs
466466
image_transform = reader.transform
467467
image_bounds = reader.bounds
468468
if image_crs is None or image_transform is None or image_bounds is None:
469-
return None
469+
return None, bbox
470470

471-
if bbox.crs.epsg is not image_crs.to_epsg():
471+
image_bbox = BBox(list(image_bounds), crs=image_crs.to_epsg())
472+
if bbox is None:
473+
return None, image_bbox
474+
475+
original_bbox = bbox
476+
if bbox.crs is not image_bbox.crs:
472477
bbox = bbox.transform(image_crs.to_epsg())
473478

474-
bbox_coords = list(bbox)
475-
if bbox_coords == list(image_bounds):
476-
return None
479+
if bbox == image_bbox:
480+
return None, original_bbox
481+
482+
return from_bounds(*iter(bbox), transform=image_transform), original_bbox
477483

478-
return from_bounds(*bbox_coords, transform=image_transform)
484+
def _load_data(
485+
self, filename_paths: List[str], filesystem: FS, initial_bbox: Optional[BBox]
486+
) -> Tuple[np.ndarray, Optional[BBox]]:
487+
"""Load data from images, join them, and provide their bounding box."""
488+
data_per_path: List[np.ndarray] = []
489+
final_bbox: Optional[BBox] = None
479490

480-
def execute(self, eopatch=None, *, filename=None):
491+
for path in filename_paths:
492+
data, bbox = self._load_from_image(path, filesystem, initial_bbox)
493+
data_per_path.append(data)
494+
495+
if bbox is None:
496+
continue
497+
if final_bbox and bbox != final_bbox:
498+
raise RuntimeError(
499+
"Given images have different geo-references. They can't be imported into an EOPatch that"
500+
" doesn't have a bounding box."
501+
)
502+
final_bbox = bbox
503+
504+
return np.concatenate(data_per_path, axis=0), final_bbox
505+
506+
def execute(self, eopatch: Optional[EOPatch] = None, *, filename: Optional[str] = None) -> EOPatch:
481507
"""Execute method which adds a new feature to the EOPatch
482508
483509
:param eopatch: input EOPatch or None if a new EOPatch should be created
@@ -493,8 +519,10 @@ def execute(self, eopatch=None, *, filename=None):
493519

494520
filesystem, filename_paths = self._get_filesystem_and_paths(filename, eopatch.timestamp, create_paths=False)
495521

496-
data = [self._load_from_image(path, filesystem, eopatch.bbox) for path in filename_paths]
497-
data = np.concatenate(data, axis=0)
522+
data, bbox = self._load_data(filename_paths, filesystem, eopatch.bbox)
523+
524+
if eopatch.bbox is None:
525+
eopatch.bbox = bbox
498526

499527
if self.image_dtype is not None:
500528
data = data.astype(self.image_dtype)

io/eolearn/tests/test_local_io.py

+3
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@ def test_export_import(test_case, test_eopatch):
178178
expected_raster.dtype == new_eop[test_case.feature_type][test_case.name].dtype
179179
), "Tiff imported into new EOPatch has different dtype as expected"
180180

181+
assert new_eop.bbox == test_eopatch.bbox
182+
assert old_eop.bbox == test_eopatch.bbox
183+
181184

182185
def _execute_with_warning_control(
183186
export_task: ExportToTiffTask, warning: Optional[Type[Warning]], *args: Any, **kwargs: Any

0 commit comments

Comments
 (0)