Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add validateCOG method to GDALFileTileSource #835

Merged
merged 9 commits into from
Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions large_image/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ class TileSourceXYZRangeError(TileSourceError):
pass


class TileSourceInefficientError(TileSourceError):
pass


class TileSourceFileNotFoundError(TileSourceError, FileNotFoundError):
def __init__(self, *args, **kwargs):
return super().__init__(errno.ENOENT, *args, **kwargs)
Expand Down
42 changes: 41 additions & 1 deletion sources/gdal/large_image_source_gdal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@
from large_image.constants import (TILE_FORMAT_IMAGE, TILE_FORMAT_NUMPY,
TILE_FORMAT_PIL, SourcePriority,
TileInputUnits, TileOutputMimeTypes)
from large_image.exceptions import TileSourceError, TileSourceFileNotFoundError
from large_image.exceptions import (TileSourceError,
TileSourceFileNotFoundError,
TileSourceInefficientError)
from large_image.tilesource import FileTileSource
from large_image.tilesource.utilities import getPaletteColors

Expand Down Expand Up @@ -1185,6 +1187,44 @@ def getRegion(self, format=(TILE_FORMAT_IMAGE, ), **kwargs):
raise exc
return pathlib.Path(outputPath), TileOutputMimeTypes['TILED']

def validateCOG(self, check_tiled=True, full_check=False, strict=True, warn=True):
"""Check if this image is a valid Cloud Optimized GeoTiff.

This will raise a :class:`large_image.exceptions.TileSourceInefficientError`
if not a valid Cloud Optimized GeoTiff. Otherwise, returns True.

Requires the ``osgeo_utils`` package.

Parameters
----------
check_tiled : bool
Set to False to ignore missing tiling.
full_check : bool
Set to True to check tile/strip leader/trailer bytes.
Might be slow on remote files
strict : bool
Enforce warnings as exceptions. Set to False to only warn and not
raise exceptions.
warn : bool
Log any warnings

"""
from osgeo_utils.samples.validate_cloud_optimized_geotiff import validate

warnings, errors, details = validate(
self._largeImagePath,
check_tiled=check_tiled,
full_check=full_check
)
if errors:
raise TileSourceInefficientError(errors)
if strict and warnings:
raise TileSourceInefficientError(warnings)
if warn:
for warning in warnings:
self.logger.warning(warning)
return True


def open(*args, **kwargs):
"""
Expand Down
5 changes: 5 additions & 0 deletions test/datastore.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@
# Multi source file using different sources
# Source: manually generated.
'multi_source.yml': 'sha512:81d7768b06eca6903082daa5b91706beaac8557ba4cede7f826524303df69a33478d6bb205c56af7ee2b45cd7d75897cc4b5704f743ddbf71bb3537ed3b9e8a8', # noqa
# Geospatial tiff - not cloud optimized
'TC_NG_SFBay_US_Geo.tif': 'sha512:da2e66528f77a5e10af5de9e496074b77277c3da81dafc69790189510e5a7e18dba9e966329d36c979f1b547f0d36a82fbc4cfccc65ae9ef9e2747b5a9ee77b0', # noqa
# Geospatial tiff - cloud optimized
'TC_NG_SFBay_US_Geo_COG.tif': 'sha512:5e56cdb8fb1a02615698a153862c10d5292b1ad42836a6e8bce5627e93a387dc0d3c9b6cfbd539796500bc2d3e23eafd07550f8c214e9348880bbbc6b3b0ea0c', # noqa

}


Expand Down
8 changes: 4 additions & 4 deletions test/test_source_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@
'openjpeg': {'read': r'\.(jp2)$'},
'openslide': {
'read': r'\.(ptif|svs|tif.*)$',
'noread': r'(oahu|DDX58_AXL|huron\.image2_jpeg2k|landcover_sample|d042-353\.crop)',
'noread': r'(oahu|DDX58_AXL|huron\.image2_jpeg2k|landcover_sample|d042-353\.crop|US_Geo\.)',
'skipTiles': r'one_layer_missing'},
'pil': {
'read': r'\.(jpeg|png|tif.*)$',
'noread': r'(G10-3|JK-kidney|d042-353|huron|one_layer_missing)'},
'noread': r'(G10-3|JK-kidney|d042-353|huron|one_layer_missing|US_Geo)'},
'test': {'any': True, 'skipTiles': r''},
'tiff': {
'read': r'\.(ptif|scn|svs|tif.*)$',
'noread': r'(oahu|DDX58_AXL|G10-3_pelvis_crop|'
r'd042-353\.crop\.small\.float|landcover_sample)',
r'd042-353\.crop\.small\.float|landcover_sample|US_Geo\.)',
'skipTiles': r'(sample_image\.ptif|one_layer_missing_tiles)'},
'vips': {
'read': r'',
Expand All @@ -66,7 +66,7 @@
# Python 3.6 has an older version of PIL that won't read some of the
# ome.tif files.
SourceAndFiles['pil']['noread'] = \
r'(G10-3|JK-kidney|d042-353|huron|sample.*ome|one_layer_missing)'
r'(G10-3|JK-kidney|d042-353|huron|sample.*ome|one_layer_missing|US_Geo)'


def testNearPowerOfTwo():
Expand Down
14 changes: 13 additions & 1 deletion test/test_source_gdal.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import pytest

from large_image import constants
from large_image.exceptions import TileSourceError
from large_image.exceptions import TileSourceError, TileSourceInefficientError

from . import utilities
from .datastore import datastore
Expand Down Expand Up @@ -550,3 +550,15 @@ def testHttpVfsPath():
assert tileMetadata['bounds']['ymin'] == pytest.approx(4876273, 1)
assert tileMetadata['bounds']['srs'] == 'epsg:3857'
assert tileMetadata['geospatial']


def testVfsCogValidation():
imagePath = datastore.get_url('TC_NG_SFBay_US_Geo_COG.tif')
source = large_image_source_gdal.open(
imagePath, projection='EPSG:3857', encoding='PNG')
assert source.validateCOG()
imagePath = datastore.get_url('TC_NG_SFBay_US_Geo.tif')
source = large_image_source_gdal.open(
imagePath, projection='EPSG:3857', encoding='PNG')
with pytest.raises(TileSourceInefficientError):
source.validateCOG()