mirror of
https://github.com/rasterio/rasterio.git
synced 2025-12-08 17:36:12 +00:00
Refactor of features tests
This commit is contained in:
parent
e7143fc20e
commit
15772d8d33
@ -2,9 +2,9 @@
|
|||||||
plugins = Cython.Coverage
|
plugins = Cython.Coverage
|
||||||
source = rasterio
|
source = rasterio
|
||||||
omit = *.pxd
|
omit = *.pxd
|
||||||
|
branch = True
|
||||||
|
|
||||||
[report]
|
[report]
|
||||||
show_missing = True
|
|
||||||
exclude_lines =
|
exclude_lines =
|
||||||
pragma: no cover
|
pragma: no cover
|
||||||
def __repr__
|
def __repr__
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -42,6 +42,7 @@ htmlcov/
|
|||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*,cover
|
*,cover
|
||||||
|
rasterio/_*.html
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
|
|||||||
@ -12,7 +12,6 @@ cdef class GeomBuilder:
|
|||||||
cpdef _buildPolygon(self)
|
cpdef _buildPolygon(self)
|
||||||
cpdef _buildMultiPolygon(self)
|
cpdef _buildMultiPolygon(self)
|
||||||
cdef build(self, void *geom)
|
cdef build(self, void *geom)
|
||||||
cpdef build_wkb(self, object wkb)
|
|
||||||
|
|
||||||
|
|
||||||
cdef class OGRGeomBuilder:
|
cdef class OGRGeomBuilder:
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import json
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
cimport numpy as np
|
cimport numpy as np
|
||||||
from rasterio._io cimport InMemoryRaster
|
from rasterio._io cimport InMemoryRaster
|
||||||
@ -62,10 +61,16 @@ def _shapes(image, mask, connectivity, transform):
|
|||||||
cdef InMemoryRaster mem_ds = None
|
cdef InMemoryRaster mem_ds = None
|
||||||
cdef InMemoryRaster mask_ds = None
|
cdef InMemoryRaster mask_ds = None
|
||||||
cdef bint is_float = np.dtype(image.dtype).kind == 'f'
|
cdef bint is_float = np.dtype(image.dtype).kind == 'f'
|
||||||
cdef int fieldtp = 0
|
cdef int fieldtp = 2 if is_float else 0
|
||||||
|
|
||||||
if is_float:
|
valid_dtypes = ('int16', 'int32', 'uint8', 'uint16', 'float32')
|
||||||
fieldtp = 2
|
|
||||||
|
if np.dtype(image.dtype).name not in valid_dtypes:
|
||||||
|
raise ValueError('image dtype must be one of: %s'
|
||||||
|
% (', '.join(valid_dtypes)))
|
||||||
|
|
||||||
|
if connectivity not in (4, 8):
|
||||||
|
raise ValueError("Connectivity Option must be 4 or 8")
|
||||||
|
|
||||||
if dtypes.is_ndarray(image):
|
if dtypes.is_ndarray(image):
|
||||||
mem_ds = InMemoryRaster(image, transform)
|
mem_ds = InMemoryRaster(image, transform)
|
||||||
@ -76,17 +81,22 @@ def _shapes(image, mask, connectivity, transform):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Invalid source image")
|
raise ValueError("Invalid source image")
|
||||||
|
|
||||||
if dtypes.is_ndarray(mask):
|
if mask is not None:
|
||||||
# A boolean mask must be converted to uint8 for GDAL
|
|
||||||
mask_ds = InMemoryRaster(mask.astype('uint8'), transform)
|
|
||||||
hmaskband = mask_ds.band
|
|
||||||
elif isinstance(mask, tuple):
|
|
||||||
if mask.shape != image.shape:
|
if mask.shape != image.shape:
|
||||||
raise ValueError("Mask must have same shape as image")
|
raise ValueError("Mask must have same shape as image")
|
||||||
mrdr = mask.ds
|
|
||||||
hmaskband = mrdr.band(mask.bidx)
|
if np.dtype(mask.dtype).name not in ('bool', 'uint8'):
|
||||||
else:
|
raise ValueError("Mask must be dtype rasterio.bool_ or "
|
||||||
hmaskband = NULL
|
"rasterio.uint8")
|
||||||
|
|
||||||
|
if dtypes.is_ndarray(mask):
|
||||||
|
# A boolean mask must be converted to uint8 for GDAL
|
||||||
|
mask_ds = InMemoryRaster(mask.astype('uint8'), transform)
|
||||||
|
hmaskband = mask_ds.band
|
||||||
|
|
||||||
|
elif isinstance(mask, tuple):
|
||||||
|
mrdr = mask.ds
|
||||||
|
hmaskband = mrdr.band(mask.bidx)
|
||||||
|
|
||||||
# Create an in-memory feature store.
|
# Create an in-memory feature store.
|
||||||
hfdriver = _ogr.OGRGetDriverByName("Memory")
|
hfdriver = _ogr.OGRGetDriverByName("Memory")
|
||||||
@ -133,7 +143,7 @@ def _shapes(image, mask, connectivity, transform):
|
|||||||
_gdal.CSLDestroy(options)
|
_gdal.CSLDestroy(options)
|
||||||
|
|
||||||
|
|
||||||
def _sieve(image, size, output, mask, connectivity):
|
def _sieve(image, size, out, mask, connectivity):
|
||||||
"""
|
"""
|
||||||
Replaces small polygons in `image` with the value of their largest
|
Replaces small polygons in `image` with the value of their largest
|
||||||
neighbor. Polygons are found for each set of neighboring pixels of the
|
neighbor. Polygons are found for each set of neighboring pixels of the
|
||||||
@ -147,7 +157,7 @@ def _sieve(image, size, output, mask, connectivity):
|
|||||||
rasterio.uint16, or rasterio.float32.
|
rasterio.uint16, or rasterio.float32.
|
||||||
size : int
|
size : int
|
||||||
minimum polygon size (number of pixels) to retain.
|
minimum polygon size (number of pixels) to retain.
|
||||||
output : numpy ndarray
|
out : numpy ndarray
|
||||||
Array of same shape and data type as `image` in which to store results.
|
Array of same shape and data type as `image` in which to store results.
|
||||||
mask : numpy ndarray or rasterio Band object
|
mask : numpy ndarray or rasterio Band object
|
||||||
Values of False or 0 will be excluded from feature generation.
|
Values of False or 0 will be excluded from feature generation.
|
||||||
@ -168,6 +178,29 @@ def _sieve(image, size, output, mask, connectivity):
|
|||||||
cdef _io.RasterUpdater udr
|
cdef _io.RasterUpdater udr
|
||||||
cdef _io.RasterReader mask_reader
|
cdef _io.RasterReader mask_reader
|
||||||
|
|
||||||
|
valid_dtypes = ('int16', 'int32', 'uint8', 'uint16')
|
||||||
|
|
||||||
|
if np.dtype(image.dtype).name not in valid_dtypes:
|
||||||
|
valid_types_str = ', '.join(('rasterio.{0}'.format(t) for t
|
||||||
|
in valid_dtypes))
|
||||||
|
raise ValueError('image dtype must be one of: %s' % valid_types_str)
|
||||||
|
|
||||||
|
if size <= 0:
|
||||||
|
raise ValueError('size must be greater than 0')
|
||||||
|
elif type(size) == float:
|
||||||
|
raise ValueError('size must be an integer number of pixels')
|
||||||
|
elif size > (image.shape[0] * image.shape[1]):
|
||||||
|
raise ValueError('size must be smaller than size of image')
|
||||||
|
|
||||||
|
if connectivity not in (4, 8):
|
||||||
|
raise ValueError('connectivity must be 4 or 8')
|
||||||
|
|
||||||
|
if out.shape != image.shape:
|
||||||
|
raise ValueError('out raster shape must be same as image shape')
|
||||||
|
|
||||||
|
if np.dtype(image.dtype).name != np.dtype(out.dtype).name:
|
||||||
|
raise ValueError('out raster must match dtype of image')
|
||||||
|
|
||||||
if dtypes.is_ndarray(image):
|
if dtypes.is_ndarray(image):
|
||||||
in_mem_ds = InMemoryRaster(image)
|
in_mem_ds = InMemoryRaster(image)
|
||||||
in_band = in_mem_ds.band
|
in_band = in_mem_ds.band
|
||||||
@ -177,27 +210,32 @@ def _sieve(image, size, output, mask, connectivity):
|
|||||||
else:
|
else:
|
||||||
raise ValueError("Invalid source image")
|
raise ValueError("Invalid source image")
|
||||||
|
|
||||||
if dtypes.is_ndarray(output):
|
if dtypes.is_ndarray(out):
|
||||||
log.debug("Output array: %r", output)
|
log.debug("out array: %r", out)
|
||||||
out_mem_ds = InMemoryRaster(output)
|
out_mem_ds = InMemoryRaster(out)
|
||||||
out_band = out_mem_ds.band
|
out_band = out_mem_ds.band
|
||||||
elif isinstance(output, tuple):
|
elif isinstance(out, tuple):
|
||||||
udr = output.ds
|
udr = out.ds
|
||||||
out_band = udr.band(output.bidx)
|
out_band = udr.band(out.bidx)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid output image")
|
raise ValueError("Invalid out image")
|
||||||
|
|
||||||
if dtypes.is_ndarray(mask):
|
if mask is not None:
|
||||||
# A boolean mask must be converted to uint8 for GDAL
|
|
||||||
mask_mem_ds = InMemoryRaster(mask.astype('uint8'))
|
|
||||||
mask_band = mask_mem_ds.band
|
|
||||||
elif isinstance(mask, tuple):
|
|
||||||
if mask.shape != image.shape:
|
if mask.shape != image.shape:
|
||||||
raise ValueError("Mask must have same shape as image")
|
raise ValueError("Mask must have same shape as image")
|
||||||
mask_reader = mask.ds
|
|
||||||
mask_band = mask_reader.band(mask.bidx)
|
if np.dtype(mask.dtype) not in ('bool', 'uint8'):
|
||||||
else:
|
raise ValueError("Mask must be dtype rasterio.bool_ or "
|
||||||
mask_band = NULL
|
"rasterio.uint8")
|
||||||
|
|
||||||
|
if dtypes.is_ndarray(mask):
|
||||||
|
# A boolean mask must be converted to uint8 for GDAL
|
||||||
|
mask_mem_ds = InMemoryRaster(mask.astype('uint8'))
|
||||||
|
mask_band = mask_mem_ds.band
|
||||||
|
|
||||||
|
elif isinstance(mask, tuple):
|
||||||
|
mask_reader = mask.ds
|
||||||
|
mask_band = mask_reader.band(mask.bidx)
|
||||||
|
|
||||||
_gdal.GDALSieveFilter(
|
_gdal.GDALSieveFilter(
|
||||||
in_band,
|
in_band,
|
||||||
@ -210,8 +248,8 @@ def _sieve(image, size, output, mask, connectivity):
|
|||||||
NULL
|
NULL
|
||||||
)
|
)
|
||||||
|
|
||||||
# Read from out_band into output
|
# Read from out_band into out
|
||||||
_io.io_auto(output, out_band, False)
|
_io.io_auto(out, out_band, False)
|
||||||
|
|
||||||
if in_mem_ds is not None:
|
if in_mem_ds is not None:
|
||||||
in_mem_ds.close()
|
in_mem_ds.close()
|
||||||
@ -238,7 +276,7 @@ def _rasterize(shapes, image, transform, all_touched):
|
|||||||
all_touched : boolean, optional
|
all_touched : boolean, optional
|
||||||
If True, all pixels touched by geometries will be burned in.
|
If True, all pixels touched by geometries will be burned in.
|
||||||
If false, only pixels whose center is within the polygon or
|
If false, only pixels whose center is within the polygon or
|
||||||
that are selected by brezenhams line algorithm will be burned
|
that are selected by Bresenham's line algorithm will be burned
|
||||||
in.
|
in.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@ -310,24 +348,21 @@ def _bounds(geometry):
|
|||||||
TODO: add to Fiona.
|
TODO: add to Fiona.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
if 'features' in geometry:
|
||||||
if 'features' in geometry:
|
xmins = []
|
||||||
xmins = []
|
ymins = []
|
||||||
ymins = []
|
xmaxs = []
|
||||||
xmaxs = []
|
ymaxs = []
|
||||||
ymaxs = []
|
for feature in geometry['features']:
|
||||||
for feature in geometry['features']:
|
xmin, ymin, xmax, ymax = _bounds(feature['geometry'])
|
||||||
xmin, ymin, xmax, ymax = _bounds(feature['geometry'])
|
xmins.append(xmin)
|
||||||
xmins.append(xmin)
|
ymins.append(ymin)
|
||||||
ymins.append(ymin)
|
xmaxs.append(xmax)
|
||||||
xmaxs.append(xmax)
|
ymaxs.append(ymax)
|
||||||
ymaxs.append(ymax)
|
return min(xmins), min(ymins), max(xmaxs), max(ymaxs)
|
||||||
return min(xmins), min(ymins), max(xmaxs), max(ymaxs)
|
else:
|
||||||
else:
|
xyz = tuple(zip(*list(_explode(geometry['coordinates']))))
|
||||||
xyz = tuple(zip(*list(_explode(geometry['coordinates']))))
|
return min(xyz[0]), min(xyz[1]), max(xyz[0]), max(xyz[1])
|
||||||
return min(xyz[0]), min(xyz[1]), max(xyz[0]), max(xyz[1])
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
# Mapping of OGR integer geometry types to GeoJSON type names.
|
# Mapping of OGR integer geometry types to GeoJSON type names.
|
||||||
@ -359,16 +394,6 @@ GEOJSON2OGR_GEOMETRY_TYPES = dict(
|
|||||||
|
|
||||||
# Geometry related functions and classes follow.
|
# Geometry related functions and classes follow.
|
||||||
|
|
||||||
cdef void * _createOgrGeomFromWKB(object wkb) except NULL:
|
|
||||||
"""Make an OGR geometry from a WKB string"""
|
|
||||||
|
|
||||||
geom_type = bytearray(wkb)[1]
|
|
||||||
cdef unsigned char *buffer = wkb
|
|
||||||
cdef void *cogr_geometry = _ogr.OGR_G_CreateGeometry(geom_type)
|
|
||||||
if cogr_geometry != NULL:
|
|
||||||
_ogr.OGR_G_ImportFromWkb(cogr_geometry, buffer, len(wkb))
|
|
||||||
return cogr_geometry
|
|
||||||
|
|
||||||
|
|
||||||
cdef _deleteOgrGeom(void *cogr_geometry):
|
cdef _deleteOgrGeom(void *cogr_geometry):
|
||||||
"""Delete an OGR geometry"""
|
"""Delete an OGR geometry"""
|
||||||
@ -445,15 +470,6 @@ cdef class GeomBuilder:
|
|||||||
self.geom = geom
|
self.geom = geom
|
||||||
return getattr(self, '_build' + self.geomtypename)()
|
return getattr(self, '_build' + self.geomtypename)()
|
||||||
|
|
||||||
cpdef build_wkb(self, object wkb):
|
|
||||||
"""Builds a GeoJSON object from a Well-Known Binary format (WKB)."""
|
|
||||||
# The only other method anyone needs to call
|
|
||||||
cdef object data = wkb
|
|
||||||
cdef void *cogr_geometry = _createOgrGeomFromWKB(data)
|
|
||||||
result = self.build(cogr_geometry)
|
|
||||||
_deleteOgrGeom(cogr_geometry)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
cdef class OGRGeomBuilder:
|
cdef class OGRGeomBuilder:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -74,6 +74,7 @@ def _gdal_typename(dt):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return typename_fwd[dtype_rev[dt().dtype.name]]
|
return typename_fwd[dtype_rev[dt().dtype.name]]
|
||||||
|
|
||||||
|
|
||||||
def check_dtype(dt):
|
def check_dtype(dt):
|
||||||
if dt not in dtype_rev:
|
if dt not in dtype_rev:
|
||||||
try:
|
try:
|
||||||
@ -83,32 +84,105 @@ def check_dtype(dt):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def get_minimum_int_dtype(values):
|
def get_minimum_dtype(values):
|
||||||
"""
|
"""
|
||||||
Uses range checking to determine the minimum integer data type required
|
Uses range checking to determine the minimum integer or floating point
|
||||||
|
data type required
|
||||||
to represent values.
|
to represent values.
|
||||||
|
|
||||||
:param values: numpy array
|
Parameters
|
||||||
:return: named data type that can be later used to create a numpy dtype
|
----------
|
||||||
|
values: list-like
|
||||||
|
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
rasterio dtype string
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
if not is_ndarray(values):
|
||||||
|
values = numpy.array(values)
|
||||||
|
|
||||||
min_value = values.min()
|
min_value = values.min()
|
||||||
max_value = values.max()
|
max_value = values.max()
|
||||||
|
|
||||||
if min_value >= 0:
|
if values.dtype.kind == 'i':
|
||||||
if max_value <= 255:
|
if min_value >= 0:
|
||||||
return uint8
|
if max_value <= 255:
|
||||||
elif max_value <= 65535:
|
return uint8
|
||||||
return uint16
|
elif max_value <= 65535:
|
||||||
elif max_value <= 4294967295:
|
return uint16
|
||||||
return uint32
|
elif max_value <= 4294967295:
|
||||||
elif min_value >= -32768 and max_value <= 32767:
|
return uint32
|
||||||
return int16
|
elif min_value >= -32768 and max_value <= 32767:
|
||||||
elif min_value >= -2147483648 and max_value <= 2147483647:
|
return int16
|
||||||
return int32
|
elif min_value >= -2147483648 and max_value <= 2147483647:
|
||||||
|
return int32
|
||||||
|
|
||||||
|
else:
|
||||||
|
if min_value >= -3.4028235e+38 and max_value <= 3.4028235e+38:
|
||||||
|
return float32
|
||||||
|
return float64
|
||||||
|
|
||||||
|
|
||||||
def is_ndarray(array):
|
def is_ndarray(array):
|
||||||
import numpy
|
import numpy
|
||||||
|
|
||||||
return isinstance(array, numpy.ndarray) or hasattr(array, '__array__')
|
return isinstance(array, numpy.ndarray) or hasattr(array, '__array__')
|
||||||
|
|
||||||
|
|
||||||
|
def can_cast_dtype(values, dtype):
|
||||||
|
"""
|
||||||
|
Tests if values can be cast to dtype without loss of information.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
values: list-like
|
||||||
|
dtype: numpy dtype or string
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
boolean
|
||||||
|
True if values can be cast to data type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
if not is_ndarray(values):
|
||||||
|
values = numpy.array(values)
|
||||||
|
|
||||||
|
if values.dtype.name == numpy.dtype(dtype).name:
|
||||||
|
return True
|
||||||
|
|
||||||
|
elif values.dtype.kind == 'f':
|
||||||
|
return numpy.allclose(values, values.astype(dtype))
|
||||||
|
|
||||||
|
else:
|
||||||
|
return numpy.array_equal(values, values.astype(dtype))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_dtype(values, valid_dtypes):
|
||||||
|
"""
|
||||||
|
Tests if dtype of values is one of valid_dtypes.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
values: list-like
|
||||||
|
valid_dtypes: list-like
|
||||||
|
list of valid dtype strings, e.g., ('int16', 'int32')
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
boolean:
|
||||||
|
True if dtype of values is one of valid_dtypes
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
if not is_ndarray(values):
|
||||||
|
values = numpy.array(values)
|
||||||
|
|
||||||
|
return (values.dtype.name in valid_dtypes or
|
||||||
|
get_minimum_dtype(values) in valid_dtypes)
|
||||||
@ -10,7 +10,7 @@ import numpy as np
|
|||||||
import rasterio
|
import rasterio
|
||||||
from rasterio._features import _shapes, _sieve, _rasterize, _bounds
|
from rasterio._features import _shapes, _sieve, _rasterize, _bounds
|
||||||
from rasterio.transform import IDENTITY, guard_transform
|
from rasterio.transform import IDENTITY, guard_transform
|
||||||
from rasterio.dtypes import get_minimum_int_dtype
|
from rasterio.dtypes import validate_dtype, can_cast_dtype, get_minimum_dtype
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger('rasterio')
|
log = logging.getLogger('rasterio')
|
||||||
@ -104,18 +104,6 @@ def shapes(image, mask=None, connectivity=4, transform=IDENTITY):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
valid_dtypes = ('int16', 'int32', 'uint8', 'uint16', 'float32')
|
|
||||||
|
|
||||||
if np.dtype(image.dtype).name not in valid_dtypes:
|
|
||||||
raise ValueError('image dtype must be one of: %s'
|
|
||||||
% (', '.join(valid_dtypes)))
|
|
||||||
|
|
||||||
if mask is not None and np.dtype(mask.dtype).name not in ('bool', 'uint8'):
|
|
||||||
raise ValueError("Mask must be dtype rasterio.bool_ or rasterio.uint8")
|
|
||||||
|
|
||||||
if connectivity not in (4, 8):
|
|
||||||
raise ValueError("Connectivity Option must be 4 or 8")
|
|
||||||
|
|
||||||
transform = guard_transform(transform)
|
transform = guard_transform(transform)
|
||||||
|
|
||||||
with rasterio.drivers():
|
with rasterio.drivers():
|
||||||
@ -165,49 +153,18 @@ def sieve(image, size, out=None, output=None, mask=None, connectivity=4):
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
valid_dtypes = ('int16', 'int32', 'uint8', 'uint16')
|
|
||||||
|
|
||||||
if np.dtype(image.dtype).name not in valid_dtypes:
|
|
||||||
valid_types_str = ', '.join(('rasterio.{0}'.format(t) for t
|
|
||||||
in valid_dtypes))
|
|
||||||
raise ValueError('image dtype must be one of: %s' % valid_types_str)
|
|
||||||
|
|
||||||
if size <= 0:
|
|
||||||
raise ValueError('size must be greater than 0')
|
|
||||||
elif type(size) == float:
|
|
||||||
raise ValueError('size must be an integer number of pixels')
|
|
||||||
elif size > (image.shape[0] * image.shape[1]):
|
|
||||||
raise ValueError('size must be smaller than size of image')
|
|
||||||
|
|
||||||
if connectivity not in (4, 8):
|
|
||||||
raise ValueError('connectivity must be 4 or 8')
|
|
||||||
|
|
||||||
if mask is not None:
|
|
||||||
if np.dtype(mask.dtype) not in ('bool', 'uint8'):
|
|
||||||
raise ValueError('Mask must be dtype rasterio.bool_ or '
|
|
||||||
'rasterio.uint8')
|
|
||||||
elif mask.shape != image.shape:
|
|
||||||
raise ValueError('mask shape must be same as image shape')
|
|
||||||
|
|
||||||
# Start moving users over to 'out'.
|
# Start moving users over to 'out'.
|
||||||
if output is not None:
|
if output is not None:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"The 'output' keyword arg has been superceded by 'out' "
|
"The 'output' keyword arg has been superceded by 'out' "
|
||||||
"and will be removed before Rasterio 1.0.",
|
"and will be removed before Rasterio 1.0.",
|
||||||
FutureWarning,
|
FutureWarning,
|
||||||
stacklevel=2)
|
stacklevel=2) # pragma: no cover
|
||||||
|
|
||||||
out = out if out is not None else output
|
out = out if out is not None else output
|
||||||
|
|
||||||
if out is None:
|
if out is None:
|
||||||
if isinstance(image, tuple):
|
out = np.zeros(image.shape, image.dtype)
|
||||||
out = np.zeros(image.shape, image.dtype)
|
|
||||||
else:
|
|
||||||
out = np.zeros_like(image)
|
|
||||||
else:
|
|
||||||
if np.dtype(image.dtype).name != np.dtype(out.dtype).name:
|
|
||||||
raise ValueError('out raster must match dtype of image')
|
|
||||||
elif out.shape != image.shape:
|
|
||||||
raise ValueError('out raster shape must be same as image shape')
|
|
||||||
|
|
||||||
with rasterio.drivers():
|
with rasterio.drivers():
|
||||||
_sieve(image, size, out, mask, connectivity)
|
_sieve(image, size, out, mask, connectivity)
|
||||||
@ -268,97 +225,92 @@ def rasterize(
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
valid_dtypes = ('int16', 'int32', 'uint8', 'uint16', 'uint32', 'float32',
|
valid_dtypes = (
|
||||||
'float64')
|
'int16', 'int32', 'uint8', 'uint16', 'uint32', 'float32', 'float64'
|
||||||
|
)
|
||||||
|
|
||||||
def get_valid_dtype(values):
|
def format_invalid_dtype(param):
|
||||||
values_dtype = values.dtype
|
return '{0} dtype must be one of: {1}'.format(
|
||||||
if values_dtype.kind == 'i':
|
param, ', '.join(valid_dtypes)
|
||||||
values_dtype = np.dtype(get_minimum_int_dtype(values))
|
)
|
||||||
if values_dtype.name in valid_dtypes:
|
|
||||||
return values_dtype
|
def format_cast_error(param, dtype):
|
||||||
return None
|
return '{0} cannot be cast to specified dtype: {1}'.format(param, dtype)
|
||||||
|
|
||||||
def can_cast_dtype(values, dtype):
|
|
||||||
if values.dtype.name == np.dtype(dtype).name:
|
|
||||||
return True
|
|
||||||
elif values.dtype.kind == 'f':
|
|
||||||
return np.allclose(values, values.astype(dtype))
|
|
||||||
else:
|
|
||||||
return np.array_equal(values, values.astype(dtype))
|
|
||||||
|
|
||||||
if fill != 0:
|
if fill != 0:
|
||||||
fill_array = np.array([fill])
|
fill_array = np.array([fill])
|
||||||
if get_valid_dtype(fill_array) is None:
|
if not validate_dtype(fill_array, valid_dtypes):
|
||||||
raise ValueError('fill must be one of these types: %s'
|
raise ValueError(format_invalid_dtype('fill'))
|
||||||
% (', '.join(valid_dtypes)))
|
|
||||||
elif dtype is not None and not can_cast_dtype(fill_array, dtype):
|
if dtype is not None and not can_cast_dtype(fill_array, dtype):
|
||||||
raise ValueError('fill value cannot be cast to specified dtype')
|
raise ValueError(format_cast_error('fill', dtype))
|
||||||
|
|
||||||
if default_value != 1:
|
if default_value != 1:
|
||||||
default_value_array = np.array([default_value])
|
default_value_array = np.array([default_value])
|
||||||
if get_valid_dtype(default_value_array) is None:
|
if not validate_dtype(default_value_array, valid_dtypes):
|
||||||
raise ValueError('default_value must be one of these types: %s'
|
raise ValueError(format_invalid_dtype('default_value'))
|
||||||
% (', '.join(valid_dtypes)))
|
|
||||||
elif dtype is not None and not can_cast_dtype(default_value_array,
|
if dtype is not None and not can_cast_dtype(default_value_array, dtype):
|
||||||
dtype):
|
raise ValueError(format_cast_error('default_vaue', dtype))
|
||||||
raise ValueError('default_value cannot be cast to specified dtype')
|
|
||||||
|
if dtype is not None and np.dtype(dtype).name not in valid_dtypes:
|
||||||
|
raise ValueError(format_invalid_dtype('dtype'))
|
||||||
|
|
||||||
|
|
||||||
valid_shapes = []
|
valid_shapes = []
|
||||||
shape_values = []
|
shape_values = []
|
||||||
for index, item in enumerate(shapes):
|
for index, item in enumerate(shapes):
|
||||||
try:
|
if isinstance(item, (tuple, list)):
|
||||||
if isinstance(item, (tuple, list)):
|
geom, value = item
|
||||||
geom, value = item
|
else:
|
||||||
else:
|
geom = item
|
||||||
geom = item
|
value = default_value
|
||||||
value = default_value
|
geom = getattr(geom, '__geo_interface__', None) or geom
|
||||||
geom = getattr(geom, '__geo_interface__', None) or geom
|
|
||||||
if (not isinstance(geom, dict) or
|
#not isinstance(geom, dict) or
|
||||||
'type' not in geom or 'coordinates' not in geom):
|
if 'type' in geom or 'coordinates' in geom:
|
||||||
raise ValueError(
|
|
||||||
'Object %r at index %d is not a geometry object' %
|
|
||||||
(geom, index))
|
|
||||||
valid_shapes.append((geom, value))
|
valid_shapes.append((geom, value))
|
||||||
shape_values.append(value)
|
shape_values.append(value)
|
||||||
except Exception:
|
|
||||||
log.exception('Exception caught, skipping shape %d', index)
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
'Invalid geometry object at index {0}'.format(index)
|
||||||
|
)
|
||||||
|
|
||||||
if not valid_shapes:
|
if not valid_shapes:
|
||||||
raise ValueError('No valid shapes found for rasterize. Shapes must be '
|
raise ValueError('No valid geometry objects found for rasterize')
|
||||||
'valid geometry objects')
|
|
||||||
|
|
||||||
shape_values = np.array(shape_values)
|
shape_values = np.array(shape_values)
|
||||||
values_dtype = get_valid_dtype(shape_values)
|
|
||||||
if values_dtype is None:
|
if not validate_dtype(shape_values, valid_dtypes):
|
||||||
raise ValueError('shape values must be one of these dtypes: %s' %
|
raise ValueError(format_invalid_dtype('shape values'))
|
||||||
(', '.join(valid_dtypes)))
|
|
||||||
|
|
||||||
if dtype is None:
|
if dtype is None:
|
||||||
dtype = values_dtype
|
dtype = get_minimum_dtype(np.append(shape_values, fill))
|
||||||
elif np.dtype(dtype).name not in valid_dtypes:
|
|
||||||
raise ValueError('dtype must be one of: %s' % (', '.join(valid_dtypes)))
|
|
||||||
elif not can_cast_dtype(shape_values, dtype):
|
elif not can_cast_dtype(shape_values, dtype):
|
||||||
raise ValueError('shape values could not be cast to specified dtype')
|
raise ValueError(format_cast_error('shape values', dtype))
|
||||||
|
|
||||||
if output is not None:
|
if output is not None:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"The 'output' keyword arg has been superceded by 'out' "
|
"The 'output' keyword arg has been superceded by 'out' "
|
||||||
"and will be removed before Rasterio 1.0.",
|
"and will be removed before Rasterio 1.0.",
|
||||||
FutureWarning,
|
FutureWarning,
|
||||||
stacklevel=2)
|
stacklevel=2) # pragma: no cover
|
||||||
|
|
||||||
out = out if out is not None else output
|
out = out if out is not None else output
|
||||||
if out is not None:
|
if out is not None:
|
||||||
if np.dtype(out.dtype).name not in valid_dtypes:
|
if np.dtype(out.dtype).name not in valid_dtypes:
|
||||||
raise ValueError('Output image dtype must be one of: %s'
|
raise ValueError(format_invalid_dtype('out'))
|
||||||
% (', '.join(valid_dtypes)))
|
|
||||||
if not can_cast_dtype(shape_values, out.dtype):
|
if not can_cast_dtype(shape_values, out.dtype):
|
||||||
raise ValueError('shape values cannot be cast to dtype of output '
|
raise ValueError(format_cast_error('shape values', out.dtype.name))
|
||||||
'image')
|
|
||||||
|
|
||||||
elif out_shape is not None:
|
elif out_shape is not None:
|
||||||
out = np.empty(out_shape, dtype=dtype)
|
out = np.empty(out_shape, dtype=dtype)
|
||||||
out.fill(fill)
|
out.fill(fill)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError('Either an output shape or image must be provided')
|
raise ValueError('Either an output shape or image must be provided')
|
||||||
|
|
||||||
|
|||||||
@ -122,14 +122,26 @@ def mask(
|
|||||||
bounds = geojson.get('bbox', calculate_bounds(geojson))
|
bounds = geojson.get('bbox', calculate_bounds(geojson))
|
||||||
|
|
||||||
with rasterio.open(input) as src:
|
with rasterio.open(input) as src:
|
||||||
has_disjoint_bounds = disjoint_bounds(bounds, src.bounds)
|
# If y pixel value is positive, then invert y dimension in bounds
|
||||||
|
invert_y = src.affine.e > 0
|
||||||
|
|
||||||
|
src_bounds = src.bounds
|
||||||
|
if invert_y:
|
||||||
|
src_bounds = [src.bounds[0], src.bounds[3],
|
||||||
|
src.bounds[2], src.bounds[1]]
|
||||||
|
|
||||||
|
has_disjoint_bounds = disjoint_bounds(bounds, src_bounds)
|
||||||
|
|
||||||
if crop:
|
if crop:
|
||||||
if has_disjoint_bounds:
|
if has_disjoint_bounds:
|
||||||
|
|
||||||
raise click.BadParameter('not allowed for GeoJSON outside '
|
raise click.BadParameter('not allowed for GeoJSON outside '
|
||||||
'the extent of the input raster',
|
'the extent of the input raster',
|
||||||
param=crop, param_hint='--crop')
|
param=crop, param_hint='--crop')
|
||||||
|
|
||||||
|
if invert_y:
|
||||||
|
bounds = (bounds[0], bounds[3], bounds[2], bounds[1])
|
||||||
|
|
||||||
window = src.window(*bounds)
|
window = src.window(*bounds)
|
||||||
transform = src.window_transform(window)
|
transform = src.window_transform(window)
|
||||||
(r1, r2), (c1, c2) = window
|
(r1, r2), (c1, c2) = window
|
||||||
@ -256,6 +268,9 @@ def shapes(
|
|||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
with rasterio.open(input) as src:
|
with rasterio.open(input) as src:
|
||||||
|
if bidx is not None and bidx > src.count:
|
||||||
|
raise ValueError('bidx is out of range for raster')
|
||||||
|
|
||||||
img = None
|
img = None
|
||||||
msk = None
|
msk = None
|
||||||
|
|
||||||
@ -533,6 +548,12 @@ def rasterize(
|
|||||||
|
|
||||||
kwargs = template_ds.meta.copy()
|
kwargs = template_ds.meta.copy()
|
||||||
kwargs['count'] = 1
|
kwargs['count'] = 1
|
||||||
|
|
||||||
|
# DEPRECATED
|
||||||
|
# upgrade transform to affine object or we may get an invalid
|
||||||
|
# transform set on output
|
||||||
|
kwargs['transform'] = template_ds.affine
|
||||||
|
|
||||||
template_ds.close()
|
template_ds.close()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -7,6 +7,10 @@ import sys
|
|||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
import py
|
import py
|
||||||
import pytest
|
import pytest
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_SHAPE = (10, 10)
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info > (3,):
|
if sys.version_info > (3,):
|
||||||
@ -38,3 +42,163 @@ def data():
|
|||||||
for filename in test_files:
|
for filename in test_files:
|
||||||
shutil.copy(filename, str(tmpdir))
|
shutil.copy(filename, str(tmpdir))
|
||||||
return tmpdir
|
return tmpdir
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def basic_geometry():
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
|
||||||
|
dict: GeoJSON-style geometry object.
|
||||||
|
Coordinates are in grid coordinates (Affine.identity()).
|
||||||
|
"""
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': 'Polygon',
|
||||||
|
'coordinates': [[(2, 2), (2, 4.25), (4.25, 4.25), (4.25, 2), (2, 2)]]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def basic_feature(basic_geometry):
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
|
||||||
|
dict: GeoJSON object.
|
||||||
|
Coordinates are in grid coordinates (Affine.identity()).
|
||||||
|
"""
|
||||||
|
|
||||||
|
return {
|
||||||
|
'geometry': basic_geometry,
|
||||||
|
'properties': {
|
||||||
|
'val': 15
|
||||||
|
},
|
||||||
|
'type': 'Feature'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def basic_featurecollection(basic_feature):
|
||||||
|
"""
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
|
||||||
|
dict: GeoJSON FeatureCollection object.
|
||||||
|
Coordinates are in grid coordinates (Affine.identity()).
|
||||||
|
"""
|
||||||
|
|
||||||
|
return {
|
||||||
|
'features': [basic_feature],
|
||||||
|
'type': 'FeatureCollection'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def basic_image():
|
||||||
|
"""
|
||||||
|
A basic 10x10 array for testing sieve and shapes functions.
|
||||||
|
Contains a square feature 3x3 (size 9).
|
||||||
|
Equivalent to results of rasterizing basic_geometry with all_touched=True.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
|
||||||
|
numpy ndarray
|
||||||
|
"""
|
||||||
|
|
||||||
|
image = numpy.zeros(DEFAULT_SHAPE, dtype=numpy.uint8)
|
||||||
|
image[2:5, 2:5] = 1
|
||||||
|
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def basic_image_2x2():
|
||||||
|
"""
|
||||||
|
A basic 10x10 array for testing sieve and shapes functions.
|
||||||
|
Contains a square feature 2x2 (size 4).
|
||||||
|
Equivalent to results of rasterizing basic_geometry with all_touched=False.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
|
||||||
|
numpy ndarray
|
||||||
|
"""
|
||||||
|
|
||||||
|
image = numpy.zeros(DEFAULT_SHAPE, dtype=numpy.uint8)
|
||||||
|
image[2:4, 2:4] = 1
|
||||||
|
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def pixelated_image(basic_image):
|
||||||
|
"""
|
||||||
|
A basic 10x10 array for testing sieve functions. Contains a square feature
|
||||||
|
3x3 (size 9), with 2 isolated pixels.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
|
||||||
|
numpy ndarray
|
||||||
|
"""
|
||||||
|
|
||||||
|
image = basic_image.copy()
|
||||||
|
image [0, 0] = 1
|
||||||
|
image [8, 8] = 1
|
||||||
|
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def diagonal_image():
|
||||||
|
"""
|
||||||
|
A 10x10 array for testing sieve functions, with only one diagonal filled.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
|
||||||
|
numpy ndarray
|
||||||
|
"""
|
||||||
|
|
||||||
|
image = numpy.zeros(DEFAULT_SHAPE, dtype=numpy.uint8)
|
||||||
|
numpy.fill_diagonal(image, 1)
|
||||||
|
return image
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def pixelated_image_file(tmpdir, pixelated_image):
|
||||||
|
"""
|
||||||
|
A basic raster file with a 10x10 array for testing sieve functions.
|
||||||
|
Contains data from pixelated_image.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
|
||||||
|
string
|
||||||
|
Filename of test raster file
|
||||||
|
"""
|
||||||
|
|
||||||
|
from affine import Affine
|
||||||
|
import rasterio
|
||||||
|
|
||||||
|
image = pixelated_image
|
||||||
|
|
||||||
|
outfilename = str(tmpdir.join('pixelated_image.tif'))
|
||||||
|
kwargs = {
|
||||||
|
"crs": {'init': 'epsg:4326'},
|
||||||
|
"transform": Affine.identity(),
|
||||||
|
"count": 1,
|
||||||
|
"dtype": rasterio.uint8,
|
||||||
|
"driver": "GTiff",
|
||||||
|
"width": image.shape[1],
|
||||||
|
"height": image.shape[0],
|
||||||
|
"nodata": 255
|
||||||
|
}
|
||||||
|
with rasterio.drivers():
|
||||||
|
with rasterio.open(outfilename, 'w', **kwargs) as out:
|
||||||
|
out.write_band(1, image)
|
||||||
|
|
||||||
|
return outfilename
|
||||||
|
|||||||
@ -1,23 +1,58 @@
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from rasterio import dtypes, ubyte
|
from rasterio import (
|
||||||
|
ubyte, uint8, uint16, uint32, int16, int32, float32, float64)
|
||||||
|
from rasterio.dtypes import (
|
||||||
|
_gdal_typename, is_ndarray, check_dtype, get_minimum_dtype, can_cast_dtype,
|
||||||
|
validate_dtype
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_is_ndarray():
|
def test_is_ndarray():
|
||||||
assert dtypes.is_ndarray(np.zeros((1,)))
|
assert is_ndarray(np.zeros((1,)))
|
||||||
assert dtypes.is_ndarray([0]) == False
|
assert is_ndarray([0]) == False
|
||||||
assert dtypes.is_ndarray((0,)) == False
|
assert is_ndarray((0,)) == False
|
||||||
|
|
||||||
|
|
||||||
def test_np_dt_uint8():
|
def test_np_dt_uint8():
|
||||||
assert dtypes.check_dtype(np.uint8)
|
assert check_dtype(np.uint8)
|
||||||
|
|
||||||
|
|
||||||
def test_dt_ubyte():
|
def test_dt_ubyte():
|
||||||
assert dtypes.check_dtype(ubyte)
|
assert check_dtype(ubyte)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_dtype_invalid():
|
||||||
|
assert check_dtype('foo') == False
|
||||||
|
|
||||||
|
|
||||||
def test_gdal_name():
|
def test_gdal_name():
|
||||||
assert dtypes._gdal_typename(ubyte) == 'Byte'
|
assert _gdal_typename(ubyte) == 'Byte'
|
||||||
assert dtypes._gdal_typename(np.uint8) == 'Byte'
|
assert _gdal_typename(np.uint8) == 'Byte'
|
||||||
assert dtypes._gdal_typename(np.uint16) == 'UInt16'
|
assert _gdal_typename(np.uint16) == 'UInt16'
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_minimum_dtype():
|
||||||
|
assert get_minimum_dtype([0, 1]) == uint8
|
||||||
|
assert get_minimum_dtype([0, 1000]) == uint16
|
||||||
|
assert get_minimum_dtype([0, 100000]) == uint32
|
||||||
|
assert get_minimum_dtype([-1, 0, 1]) == int16
|
||||||
|
assert get_minimum_dtype([-1, 0, 100000]) == int32
|
||||||
|
assert get_minimum_dtype([-1.5, 0, 1.5]) == float32
|
||||||
|
assert get_minimum_dtype([-1.5e+100, 0, 1.5e+100]) == float64
|
||||||
|
|
||||||
|
|
||||||
|
def test_can_cast_dtype():
|
||||||
|
assert can_cast_dtype((1, 2, 3), np.uint8) == True
|
||||||
|
assert can_cast_dtype(np.array([1, 2, 3]), np.uint8) == True
|
||||||
|
assert can_cast_dtype(np.array([1, 2, 3], dtype=np.uint8), np.uint8) == True
|
||||||
|
assert can_cast_dtype(np.array([1, 2, 3]), np.float32) == True
|
||||||
|
assert can_cast_dtype(np.array([1.4, 2.1, 3.65]), np.float32) == True
|
||||||
|
assert can_cast_dtype(np.array([1.4, 2.1, 3.65]), np.uint8) == False
|
||||||
|
|
||||||
|
|
||||||
|
def test_validate_dtype():
|
||||||
|
assert validate_dtype([1, 2, 3], ('uint8', 'uint16')) == True
|
||||||
|
assert validate_dtype(np.array([1, 2, 3]), ('uint8', 'uint16')) == True
|
||||||
|
assert validate_dtype(np.array([1.4, 2.1, 3.65]), ('float32',)) == True
|
||||||
|
assert validate_dtype(np.array([1.4, 2.1, 3.65]),('uint8',)) == False
|
||||||
|
|||||||
728
tests/test_features.py
Normal file
728
tests/test_features.py
Normal file
@ -0,0 +1,728 @@
|
|||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
import numpy
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from affine import Affine
|
||||||
|
import rasterio
|
||||||
|
from rasterio.features import bounds, geometry_mask, rasterize, sieve, shapes
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_SHAPE = (10, 10)
|
||||||
|
|
||||||
|
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bounds_point():
|
||||||
|
g = {'type': 'Point', 'coordinates': [10, 10]}
|
||||||
|
assert bounds(g) == (10, 10, 10, 10)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bounds_line():
|
||||||
|
g = {'type': 'LineString', 'coordinates': [[0, 0], [10, 10]]}
|
||||||
|
assert bounds(g) == (0, 0, 10, 10)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bounds_polygon():
|
||||||
|
g = {'type': 'Polygon', 'coordinates': [[[0, 0], [10, 10], [10, 0]]]}
|
||||||
|
assert bounds(g) == (0, 0, 10, 10)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bounds_z():
|
||||||
|
g = {'type': 'Point', 'coordinates': [10, 10, 10]}
|
||||||
|
assert bounds(g) == (10, 10, 10, 10)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bounds_invalid_obj():
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
bounds({'type': 'bogus', 'not_coordinates': []})
|
||||||
|
|
||||||
|
|
||||||
|
def test_feature_collection(basic_featurecollection):
|
||||||
|
fc = basic_featurecollection
|
||||||
|
assert bounds(fc) == bounds(fc['features'][0]) == (2, 2, 4.25, 4.25)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bounds_existing_bbox(basic_featurecollection):
|
||||||
|
"""
|
||||||
|
Test with existing bbox in geojson, similar to that produced by
|
||||||
|
rasterio. Values specifically modified here for testing, bboxes are not
|
||||||
|
valid as written.
|
||||||
|
"""
|
||||||
|
|
||||||
|
fc = basic_featurecollection
|
||||||
|
fc['bbox'] = [0, 10, 10, 20]
|
||||||
|
fc['features'][0]['bbox'] = [0, 100, 10, 200]
|
||||||
|
|
||||||
|
assert bounds(fc['features'][0]) == (0, 100, 10, 200)
|
||||||
|
assert bounds(fc) == (0, 10, 10, 20)
|
||||||
|
|
||||||
|
|
||||||
|
def test_geometry_mask(basic_geometry, basic_image_2x2):
|
||||||
|
with rasterio.drivers():
|
||||||
|
assert numpy.array_equal(
|
||||||
|
basic_image_2x2 == 0,
|
||||||
|
geometry_mask(
|
||||||
|
[basic_geometry],
|
||||||
|
out_shape=DEFAULT_SHAPE,
|
||||||
|
transform=Affine.identity()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_geometry_mask_invert(basic_geometry, basic_image_2x2):
|
||||||
|
with rasterio.drivers():
|
||||||
|
assert numpy.array_equal(
|
||||||
|
basic_image_2x2,
|
||||||
|
geometry_mask(
|
||||||
|
[basic_geometry],
|
||||||
|
out_shape=DEFAULT_SHAPE,
|
||||||
|
transform=Affine.identity(),
|
||||||
|
invert=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize(basic_geometry, basic_image_2x2):
|
||||||
|
""" Rasterize operation should succeed for both an out_shape and out """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
assert numpy.array_equal(
|
||||||
|
basic_image_2x2,
|
||||||
|
rasterize([basic_geometry], out_shape=DEFAULT_SHAPE)
|
||||||
|
)
|
||||||
|
|
||||||
|
out = numpy.zeros(DEFAULT_SHAPE)
|
||||||
|
rasterize([basic_geometry], out=out)
|
||||||
|
assert numpy.array_equal(basic_image_2x2, out)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_invalid_out_dtype(basic_geometry):
|
||||||
|
""" A non-supported data type for out should raise an exception """
|
||||||
|
|
||||||
|
out = numpy.zeros(DEFAULT_SHAPE, dtype=numpy.int64)
|
||||||
|
with rasterio.drivers():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
rasterize([basic_geometry], out=out)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_shapes_out_dtype_mismatch(basic_geometry):
|
||||||
|
""" Shape values must be able to fit in data type for out """
|
||||||
|
|
||||||
|
out = numpy.zeros(DEFAULT_SHAPE, dtype=numpy.uint8)
|
||||||
|
with rasterio.drivers():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
rasterize([(basic_geometry, 10000000)], out=out)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_missing_out(basic_geometry):
|
||||||
|
""" If both out and out_shape are missing, should raise exception """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
rasterize([basic_geometry], out=None, out_shape=None)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_missing_shapes():
|
||||||
|
""" Shapes are required for this operation """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
with pytest.raises(ValueError) as ex:
|
||||||
|
rasterize([], out_shape=DEFAULT_SHAPE)
|
||||||
|
|
||||||
|
assert 'No valid geometry objects' in str(ex.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_invalid_shapes():
|
||||||
|
""" Invalid shapes should raise an exception rather than be skipped """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
with pytest.raises(ValueError) as ex:
|
||||||
|
rasterize([{'foo': 'bar'}], out_shape=DEFAULT_SHAPE)
|
||||||
|
|
||||||
|
assert 'Invalid geometry object' in str(ex.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_default_value(basic_geometry, basic_image_2x2):
|
||||||
|
""" All shapes should rasterize to the default value """
|
||||||
|
|
||||||
|
default_value = 2
|
||||||
|
truth = basic_image_2x2 * default_value
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
assert numpy.array_equal(
|
||||||
|
truth,
|
||||||
|
rasterize(
|
||||||
|
[basic_geometry], out_shape=DEFAULT_SHAPE,
|
||||||
|
default_value=default_value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_invalid_default_value(basic_geometry):
|
||||||
|
""" A default value that requires an int64 should raise an exception """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
rasterize(
|
||||||
|
[basic_geometry], out_shape=DEFAULT_SHAPE,
|
||||||
|
default_value=1000000000000
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_fill_value(basic_geometry, basic_image_2x2):
|
||||||
|
""" All pixels not covered by shapes should be given fill value """
|
||||||
|
|
||||||
|
default_value = 2
|
||||||
|
with rasterio.drivers():
|
||||||
|
assert numpy.array_equal(
|
||||||
|
basic_image_2x2 + 1,
|
||||||
|
rasterize(
|
||||||
|
[basic_geometry], out_shape=DEFAULT_SHAPE, fill=1,
|
||||||
|
default_value=default_value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_invalid_fill_value(basic_geometry):
|
||||||
|
""" A fill value that requires an int64 should raise an exception """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
rasterize(
|
||||||
|
[basic_geometry], out_shape=DEFAULT_SHAPE, fill=1000000000000,
|
||||||
|
default_value=2
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_fill_value_dtype_mismatch(basic_geometry):
|
||||||
|
""" A fill value that doesn't match dtype should fail """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
rasterize(
|
||||||
|
[basic_geometry], out_shape=DEFAULT_SHAPE, fill=1000000,
|
||||||
|
default_value=2, dtype=numpy.uint8
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_all_touched(basic_geometry, basic_image):
|
||||||
|
with rasterio.drivers():
|
||||||
|
assert numpy.array_equal(
|
||||||
|
basic_image,
|
||||||
|
rasterize(
|
||||||
|
[basic_geometry], out_shape=DEFAULT_SHAPE, all_touched=True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_value(basic_geometry, basic_image_2x2):
|
||||||
|
"""
|
||||||
|
All shapes should rasterize to the value passed in a tuple alongside
|
||||||
|
each shape
|
||||||
|
"""
|
||||||
|
|
||||||
|
value = 5
|
||||||
|
with rasterio.drivers():
|
||||||
|
assert numpy.array_equal(
|
||||||
|
basic_image_2x2 * value,
|
||||||
|
rasterize(
|
||||||
|
[(basic_geometry, value)], out_shape=DEFAULT_SHAPE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_invalid_value(basic_geometry):
|
||||||
|
""" A shape value that requires an int64 should raise an exception """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
with pytest.raises(ValueError) as ex:
|
||||||
|
rasterize(
|
||||||
|
[(basic_geometry, 1000000000000)], out_shape=DEFAULT_SHAPE
|
||||||
|
)
|
||||||
|
|
||||||
|
assert 'dtype must be one of' in str(ex.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_supported_dtype(basic_geometry):
|
||||||
|
""" Supported data types should return valid results """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
supported_types = (
|
||||||
|
('int16', -32768),
|
||||||
|
('int32', -2147483648),
|
||||||
|
('uint8', 255),
|
||||||
|
('uint16', 65535),
|
||||||
|
('uint32', 4294967295),
|
||||||
|
('float32', 1.434532),
|
||||||
|
('float64', -98332.133422114)
|
||||||
|
)
|
||||||
|
|
||||||
|
for dtype, default_value in supported_types:
|
||||||
|
truth = numpy.zeros(DEFAULT_SHAPE, dtype=dtype)
|
||||||
|
truth[2:4, 2:4] = default_value
|
||||||
|
|
||||||
|
result = rasterize(
|
||||||
|
[basic_geometry],
|
||||||
|
out_shape=DEFAULT_SHAPE,
|
||||||
|
default_value=default_value,
|
||||||
|
dtype=dtype
|
||||||
|
)
|
||||||
|
assert numpy.array_equal(result, truth)
|
||||||
|
assert numpy.dtype(result.dtype) == numpy.dtype(truth.dtype)
|
||||||
|
|
||||||
|
result = rasterize(
|
||||||
|
[(basic_geometry, default_value)],
|
||||||
|
out_shape=DEFAULT_SHAPE
|
||||||
|
)
|
||||||
|
if numpy.dtype(dtype).kind == 'f':
|
||||||
|
assert numpy.allclose(result, truth)
|
||||||
|
else:
|
||||||
|
assert numpy.array_equal(result, truth)
|
||||||
|
# Since dtype is auto-detected, it may not match due to upcasting
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_unsupported_dtype(basic_geometry):
|
||||||
|
""" Unsupported types should all raise exceptions """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
unsupported_types = (
|
||||||
|
('int8', -127),
|
||||||
|
('int64', 20439845334323),
|
||||||
|
('float16', -9343.232)
|
||||||
|
)
|
||||||
|
|
||||||
|
for dtype, default_value in unsupported_types:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
rasterize(
|
||||||
|
[basic_geometry],
|
||||||
|
out_shape=DEFAULT_SHAPE,
|
||||||
|
default_value=default_value,
|
||||||
|
dtype=dtype
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
rasterize(
|
||||||
|
[(basic_geometry, default_value)],
|
||||||
|
out_shape=DEFAULT_SHAPE,
|
||||||
|
dtype=dtype
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_mismatched_dtype(basic_geometry):
|
||||||
|
""" Mismatched values and dtypes should raise exceptions """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
mismatched_types = (('uint8', 3.2423), ('uint8', -2147483648))
|
||||||
|
for dtype, default_value in mismatched_types:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
rasterize(
|
||||||
|
[basic_geometry],
|
||||||
|
out_shape=DEFAULT_SHAPE,
|
||||||
|
default_value=default_value,
|
||||||
|
dtype=dtype
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
rasterize(
|
||||||
|
[(basic_geometry, default_value)],
|
||||||
|
out_shape=DEFAULT_SHAPE,
|
||||||
|
dtype=dtype
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_geometries_symmetric():
|
||||||
|
""" Make sure that rasterize is symmetric with shapes """
|
||||||
|
|
||||||
|
transform = (1.0, 0.0, 0.0, 0.0, -1.0, 0.0)
|
||||||
|
truth = numpy.zeros(DEFAULT_SHAPE, dtype=rasterio.ubyte)
|
||||||
|
truth[2:5, 2:5] = 1
|
||||||
|
with rasterio.drivers():
|
||||||
|
s = shapes(truth, transform=transform)
|
||||||
|
result = rasterize(s, out_shape=DEFAULT_SHAPE, transform=transform)
|
||||||
|
assert numpy.array_equal(result, truth)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_internal_driver_manager(basic_geometry):
|
||||||
|
""" Rasterize should work without explicitly calling driver manager """
|
||||||
|
|
||||||
|
assert rasterize([basic_geometry], out_shape=DEFAULT_SHAPE).sum() == 4
|
||||||
|
|
||||||
|
|
||||||
|
def test_shapes(basic_image):
|
||||||
|
""" Test creation of shapes from pixel values """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
results = list(shapes(basic_image))
|
||||||
|
|
||||||
|
assert len(results) == 2
|
||||||
|
|
||||||
|
shape, value = results[0]
|
||||||
|
assert shape == {
|
||||||
|
'coordinates': [
|
||||||
|
[(2, 2), (2, 5), (5, 5), (5, 2), (2, 2)]
|
||||||
|
],
|
||||||
|
'type': 'Polygon'
|
||||||
|
}
|
||||||
|
assert value == 1
|
||||||
|
|
||||||
|
shape, value = results[1]
|
||||||
|
assert shape == {
|
||||||
|
'coordinates': [
|
||||||
|
[(0, 0), (0, 10), (10, 10), (10, 0), (0, 0)],
|
||||||
|
[(2, 2), (5, 2), (5, 5), (2, 5), (2, 2)]
|
||||||
|
],
|
||||||
|
'type': 'Polygon'
|
||||||
|
}
|
||||||
|
assert value == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_shapes_band(pixelated_image, pixelated_image_file):
|
||||||
|
""" Shapes from a band should match shapes from an array """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
truth = list(shapes(pixelated_image))
|
||||||
|
|
||||||
|
with rasterio.open(pixelated_image_file) as src:
|
||||||
|
band = rasterio.band(src, 1)
|
||||||
|
assert truth == list(shapes(band))
|
||||||
|
|
||||||
|
# Mask band should function, but will mask out some results
|
||||||
|
assert truth[0] == list(shapes(band, mask=band))[0]
|
||||||
|
|
||||||
|
|
||||||
|
def test_shapes_connectivity_rook(diagonal_image):
|
||||||
|
"""
|
||||||
|
Diagonals are not connected, so there will be 1 feature per pixel plus
|
||||||
|
background.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
assert len(list(shapes(diagonal_image, connectivity=4))) == 12
|
||||||
|
|
||||||
|
|
||||||
|
def test_shapes_connectivity_queen(diagonal_image):
|
||||||
|
"""
|
||||||
|
Diagonals are connected, so there will be 1 feature for all pixels plus
|
||||||
|
background.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
assert len(list(shapes(diagonal_image, connectivity=8))) == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_shapes_connectivity_invalid(diagonal_image):
|
||||||
|
""" Invalid connectivity should raise exception """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
assert next(shapes(diagonal_image, connectivity=12))
|
||||||
|
|
||||||
|
|
||||||
|
def test_shapes_mask(basic_image):
|
||||||
|
""" Only pixels not masked out should be converted to features """
|
||||||
|
|
||||||
|
mask = numpy.ones(basic_image.shape, dtype=rasterio.bool_)
|
||||||
|
mask[4:5, 4:5] = False
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
results = list(shapes(basic_image, mask=mask))
|
||||||
|
|
||||||
|
assert len(results) == 2
|
||||||
|
|
||||||
|
shape, value = results[0]
|
||||||
|
assert shape == {
|
||||||
|
'coordinates': [
|
||||||
|
[(2, 2), (2, 5), (4, 5), (4, 4), (5, 4), (5, 2), (2, 2)]
|
||||||
|
],
|
||||||
|
'type': 'Polygon'
|
||||||
|
}
|
||||||
|
assert value == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_shapes_blank_mask(basic_image):
|
||||||
|
""" Mask is blank so results should mask shapes without mask """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
assert numpy.array_equal(
|
||||||
|
list(shapes(
|
||||||
|
basic_image,
|
||||||
|
mask=numpy.ones(basic_image.shape, dtype=rasterio.bool_))
|
||||||
|
),
|
||||||
|
list(shapes(basic_image))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_shapes_invalid_mask_shape(basic_image):
|
||||||
|
""" A mask that is the wrong shape should fail """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
next(shapes(
|
||||||
|
basic_image,
|
||||||
|
mask=numpy.ones(
|
||||||
|
(basic_image.shape[0] + 10, basic_image.shape[1] + 10),
|
||||||
|
dtype=rasterio.bool_
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
def test_shapes_invalid_mask_dtype(basic_image):
|
||||||
|
""" A mask that is the wrong dtype should fail """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
for dtype in ('int8', 'int16', 'int32'):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
next(shapes(
|
||||||
|
basic_image,
|
||||||
|
mask=numpy.ones(basic_image.shape, dtype=dtype)
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
def test_shapes_supported_dtypes(basic_image):
|
||||||
|
""" Supported data types should return valid results """
|
||||||
|
|
||||||
|
supported_types = (
|
||||||
|
('int16', -32768),
|
||||||
|
('int32', -2147483648),
|
||||||
|
('uint8', 255),
|
||||||
|
('uint16', 65535),
|
||||||
|
('float32', 1.434532)
|
||||||
|
)
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
for dtype, test_value in supported_types:
|
||||||
|
shape, value = next(shapes(basic_image.astype(dtype) * test_value))
|
||||||
|
assert numpy.allclose(value, test_value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_shapes_unsupported_dtypes(basic_image):
|
||||||
|
""" Unsupported data types should raise exceptions """
|
||||||
|
|
||||||
|
unsupported_types = (
|
||||||
|
('int8', -127),
|
||||||
|
('uint32', 4294967295),
|
||||||
|
('int64', 20439845334323),
|
||||||
|
('float16', -9343.232),
|
||||||
|
('float64', -98332.133422114)
|
||||||
|
)
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
for dtype, test_value in unsupported_types:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
next(shapes(basic_image.astype(dtype) * test_value))
|
||||||
|
|
||||||
|
|
||||||
|
def test_shapes_internal_driver_manager(basic_image):
|
||||||
|
""" Shapes should work without explicitly calling driver manager """
|
||||||
|
|
||||||
|
assert next(shapes(basic_image))[0]['type'] == 'Polygon'
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_small(basic_image, pixelated_image):
|
||||||
|
"""
|
||||||
|
Setting the size smaller than or equal to the size of the feature in the
|
||||||
|
image should not change the image.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
assert numpy.array_equal(
|
||||||
|
basic_image,
|
||||||
|
sieve(pixelated_image, basic_image.sum())
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_large(basic_image):
|
||||||
|
"""
|
||||||
|
Setting the size larger than size of feature should leave us an empty image.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
assert not numpy.any(sieve(basic_image, basic_image.sum() + 1))
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_invalid_size(basic_image):
|
||||||
|
with rasterio.drivers():
|
||||||
|
for invalid_size in (0, 45.1234, basic_image.size + 1):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
sieve(basic_image, invalid_size)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_connectivity_rook(diagonal_image):
|
||||||
|
""" Diagonals are not connected, so feature is removed """
|
||||||
|
|
||||||
|
assert not numpy.any(
|
||||||
|
sieve(diagonal_image, diagonal_image.sum(), connectivity=4)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_connectivity_queen(diagonal_image):
|
||||||
|
""" Diagonals are connected, so feature is retained """
|
||||||
|
|
||||||
|
assert numpy.array_equal(
|
||||||
|
diagonal_image,
|
||||||
|
sieve(diagonal_image, diagonal_image.sum(), connectivity=8)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_connectivity_invalid(basic_image):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
sieve(basic_image, 54, connectivity=12)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_out(basic_image):
|
||||||
|
""" Output array passed in should match the returned array """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
output = numpy.zeros_like(basic_image)
|
||||||
|
output[1:3, 1:3] = 5
|
||||||
|
sieved_image = sieve(basic_image, basic_image.sum(), out=output)
|
||||||
|
assert numpy.array_equal(basic_image, sieved_image)
|
||||||
|
assert numpy.array_equal(output, sieved_image)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_invalid_out(basic_image):
|
||||||
|
""" Output with different dtype or shape should fail """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
sieve(
|
||||||
|
basic_image, basic_image.sum(),
|
||||||
|
out=numpy.zeros(basic_image.shape, dtype=rasterio.int32)
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
sieve(
|
||||||
|
basic_image, basic_image.sum(),
|
||||||
|
out=numpy.zeros(
|
||||||
|
(basic_image.shape[0] + 10, basic_image.shape[1] + 10),
|
||||||
|
dtype=rasterio.ubyte
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_mask(basic_image):
|
||||||
|
"""
|
||||||
|
Only areas within the overlap of mask and input will be kept, so long
|
||||||
|
as mask is a bool or uint8 dtype.
|
||||||
|
"""
|
||||||
|
|
||||||
|
mask = numpy.ones(basic_image.shape, dtype=rasterio.bool_)
|
||||||
|
mask[4:5, 4:5] = False
|
||||||
|
truth = basic_image * numpy.invert(mask)
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
sieved_image = sieve(basic_image, basic_image.sum(), mask=mask)
|
||||||
|
assert sieved_image.sum() > 0
|
||||||
|
|
||||||
|
assert numpy.array_equal(
|
||||||
|
truth,
|
||||||
|
sieved_image
|
||||||
|
)
|
||||||
|
|
||||||
|
assert numpy.array_equal(
|
||||||
|
truth.astype(rasterio.uint8),
|
||||||
|
sieved_image
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_blank_mask(basic_image):
|
||||||
|
""" A blank mask should have no effect """
|
||||||
|
|
||||||
|
mask = numpy.ones(basic_image.shape, dtype=rasterio.bool_)
|
||||||
|
with rasterio.drivers():
|
||||||
|
assert numpy.array_equal(
|
||||||
|
basic_image,
|
||||||
|
sieve(basic_image, basic_image.sum(), mask=mask)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_invalid_mask_shape(basic_image):
|
||||||
|
""" A mask that is the wrong shape should fail """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
sieve(
|
||||||
|
basic_image, basic_image.sum(),
|
||||||
|
mask=numpy.ones(
|
||||||
|
(basic_image.shape[0] + 10, basic_image.shape[1] + 10),
|
||||||
|
dtype=rasterio.bool_
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_invalid_mask_dtype(basic_image):
|
||||||
|
""" A mask that is the wrong dtype should fail """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
for dtype in ('int8', 'int16', 'int32'):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
sieve(
|
||||||
|
basic_image, basic_image.sum(),
|
||||||
|
mask=numpy.ones(basic_image.shape, dtype=dtype)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_supported_dtypes(basic_image):
|
||||||
|
""" Supported data types should return valid results """
|
||||||
|
|
||||||
|
supported_types = (
|
||||||
|
('int16', -32768),
|
||||||
|
('int32', -2147483648),
|
||||||
|
('uint8', 255),
|
||||||
|
('uint16', 65535)
|
||||||
|
)
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
for dtype, test_value in supported_types:
|
||||||
|
truth = (basic_image).astype(dtype) * test_value
|
||||||
|
sieved_image = sieve(truth, basic_image.sum())
|
||||||
|
assert numpy.array_equal(truth, sieved_image)
|
||||||
|
assert numpy.dtype(sieved_image.dtype) == numpy.dtype(dtype)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_unsupported_dtypes(basic_image):
|
||||||
|
""" Unsupported data types should raise exceptions """
|
||||||
|
|
||||||
|
unsupported_types = (
|
||||||
|
('int8', -127),
|
||||||
|
('uint32', 4294967295),
|
||||||
|
('int64', 20439845334323),
|
||||||
|
('float16', -9343.232),
|
||||||
|
('float32', 1.434532),
|
||||||
|
('float64', -98332.133422114)
|
||||||
|
)
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
for dtype, test_value in unsupported_types:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
sieve(
|
||||||
|
(basic_image).astype(dtype) * test_value,
|
||||||
|
basic_image.sum()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_band(pixelated_image, pixelated_image_file):
|
||||||
|
""" Sieving a band from a raster file should match sieve of array """
|
||||||
|
|
||||||
|
with rasterio.drivers():
|
||||||
|
truth = sieve(pixelated_image, 9)
|
||||||
|
|
||||||
|
with rasterio.open(pixelated_image_file) as src:
|
||||||
|
band = rasterio.band(src, 1)
|
||||||
|
assert numpy.array_equal(truth, sieve(band, 9))
|
||||||
|
|
||||||
|
# Mask band should also work but will be a no-op
|
||||||
|
assert numpy.array_equal(
|
||||||
|
pixelated_image,
|
||||||
|
sieve(band, 9, mask=band)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sieve_internal_driver_manager(basic_image, pixelated_image):
|
||||||
|
""" Sieve should work without explicitly calling driver manager """
|
||||||
|
|
||||||
|
assert numpy.array_equal(
|
||||||
|
basic_image,
|
||||||
|
sieve(pixelated_image, basic_image.sum())
|
||||||
|
)
|
||||||
@ -1,65 +0,0 @@
|
|||||||
from rasterio.features import bounds
|
|
||||||
|
|
||||||
|
|
||||||
# Tests copied from Fiona 1.4.1
|
|
||||||
|
|
||||||
def test_bounds_point():
|
|
||||||
g = {'type': 'Point', 'coordinates': [10, 10]}
|
|
||||||
assert bounds(g) == (10, 10, 10, 10)
|
|
||||||
|
|
||||||
|
|
||||||
def test_bounds_line():
|
|
||||||
g = {'type': 'LineString', 'coordinates': [[0, 0], [10, 10]]}
|
|
||||||
assert bounds(g) == (0, 0, 10, 10)
|
|
||||||
|
|
||||||
|
|
||||||
def test_bounds_polygon():
|
|
||||||
g = {'type': 'Polygon', 'coordinates': [[[0, 0], [10, 10], [10, 0]]]}
|
|
||||||
assert bounds(g) == (0, 0, 10, 10)
|
|
||||||
|
|
||||||
|
|
||||||
def test_bounds_z():
|
|
||||||
g = {'type': 'Point', 'coordinates': [10, 10, 10]}
|
|
||||||
assert bounds(g) == (10, 10, 10, 10)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: add these to Fiona with update to bounds
|
|
||||||
def test_bounds_existing_bbox():
|
|
||||||
""" Test with existing bbox in geojson, similar to that produced by
|
|
||||||
rasterio. Values specifically modified here for testing, bboxes are not
|
|
||||||
valid as written.
|
|
||||||
"""
|
|
||||||
|
|
||||||
fc = {
|
|
||||||
'bbox': [-107, 40, -105, 41],
|
|
||||||
'features': [{
|
|
||||||
'bbox': [-107, 40, -104, 42],
|
|
||||||
'geometry': {
|
|
||||||
'coordinates': [
|
|
||||||
[[-107, 40], [-106, 40], [-106, 41], [-107, 41], [-107, 40]]
|
|
||||||
],
|
|
||||||
'type': 'Polygon'
|
|
||||||
},
|
|
||||||
'type': 'Feature'
|
|
||||||
}],
|
|
||||||
'type': 'FeatureCollection'
|
|
||||||
}
|
|
||||||
assert bounds(fc['features'][0]) == (-107, 40, -104, 42)
|
|
||||||
assert bounds(fc) == (-107, 40, -105, 41)
|
|
||||||
|
|
||||||
|
|
||||||
def test_feature_collection():
|
|
||||||
fc = {
|
|
||||||
'features': [{
|
|
||||||
'geometry': {
|
|
||||||
'coordinates': [
|
|
||||||
[[-107, 40], [-106, 40], [-106, 41], [-107, 41], [-107, 40]]
|
|
||||||
],
|
|
||||||
'type': 'Polygon'
|
|
||||||
},
|
|
||||||
'type': 'Feature'
|
|
||||||
}],
|
|
||||||
'type': 'FeatureCollection'
|
|
||||||
}
|
|
||||||
assert bounds(fc['features'][0]) == (-107, 40, -106, 41)
|
|
||||||
assert bounds(fc) == (-107, 40, -106, 41)
|
|
||||||
@ -1,181 +0,0 @@
|
|||||||
import logging
|
|
||||||
import sys
|
|
||||||
import numpy
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import rasterio
|
|
||||||
from rasterio.features import shapes, rasterize, geometry_mask
|
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
|
|
||||||
|
|
||||||
|
|
||||||
def test_rasterize_geometries():
|
|
||||||
"""
|
|
||||||
Make sure that geometries are correctly rasterized according to parameters
|
|
||||||
"""
|
|
||||||
|
|
||||||
rows = cols = 10
|
|
||||||
transform = (1.0, 0.0, 0.0, 0.0, 1.0, 0.0)
|
|
||||||
geometry = {
|
|
||||||
'type': 'Polygon',
|
|
||||||
'coordinates': [[(2, 2), (2, 4.25), (4.25, 4.25), (4.25, 2), (2, 2)]]
|
|
||||||
}
|
|
||||||
|
|
||||||
with rasterio.drivers():
|
|
||||||
# we expect a subset of the pixels using default mode
|
|
||||||
result = rasterize([geometry], out_shape=(rows, cols))
|
|
||||||
truth = numpy.zeros((rows, cols))
|
|
||||||
truth[2:4, 2:4] = 1
|
|
||||||
assert numpy.array_equal(result, truth)
|
|
||||||
|
|
||||||
out = numpy.zeros((rows, cols))
|
|
||||||
result = rasterize([geometry], out=out, default_value=1)
|
|
||||||
assert numpy.array_equal(out, truth)
|
|
||||||
|
|
||||||
# we expect all touched pixels
|
|
||||||
result = rasterize(
|
|
||||||
[geometry], out_shape=(rows, cols), all_touched=True
|
|
||||||
)
|
|
||||||
truth = numpy.zeros((rows, cols))
|
|
||||||
truth[2:5, 2:5] = 1
|
|
||||||
assert numpy.array_equal(result, truth)
|
|
||||||
|
|
||||||
# we expect the pixel value to match the one we pass in
|
|
||||||
value = 5
|
|
||||||
result = rasterize([(geometry, value)], out_shape=(rows, cols))
|
|
||||||
truth = numpy.zeros((rows, cols))
|
|
||||||
truth[2:4, 2:4] = value
|
|
||||||
assert numpy.array_equal(result, truth)
|
|
||||||
|
|
||||||
# Check the fill and default transform.
|
|
||||||
# we expect the pixel value to match the one we pass in
|
|
||||||
value = 5
|
|
||||||
result = rasterize(
|
|
||||||
[(geometry, value)],
|
|
||||||
out_shape=(rows, cols),
|
|
||||||
fill=1
|
|
||||||
)
|
|
||||||
truth = numpy.ones((rows, cols))
|
|
||||||
truth[2:4, 2:4] = value
|
|
||||||
assert numpy.array_equal(result, truth)
|
|
||||||
|
|
||||||
|
|
||||||
def test_rasterize_dtype():
|
|
||||||
"""Make sure that data types are handled correctly"""
|
|
||||||
|
|
||||||
rows = cols = 10
|
|
||||||
transform = (1.0, 0.0, 0.0, 0.0, 1.0, 0.0)
|
|
||||||
geometry = {
|
|
||||||
'type': 'Polygon',
|
|
||||||
'coordinates': [[(2, 2), (2, 4.25), (4.25, 4.25), (4.25, 2), (2, 2)]]
|
|
||||||
}
|
|
||||||
|
|
||||||
with rasterio.drivers():
|
|
||||||
# Supported types should all work properly
|
|
||||||
supported_types = (
|
|
||||||
('int16', -32768),
|
|
||||||
('int32', -2147483648),
|
|
||||||
('uint8', 255),
|
|
||||||
('uint16', 65535),
|
|
||||||
('uint32', 4294967295),
|
|
||||||
('float32', 1.434532),
|
|
||||||
('float64', -98332.133422114)
|
|
||||||
)
|
|
||||||
|
|
||||||
for dtype, default_value in supported_types:
|
|
||||||
truth = numpy.zeros((rows, cols), dtype=dtype)
|
|
||||||
truth[2:4, 2:4] = default_value
|
|
||||||
|
|
||||||
result = rasterize(
|
|
||||||
[geometry],
|
|
||||||
out_shape=(rows, cols),
|
|
||||||
default_value=default_value,
|
|
||||||
dtype=dtype
|
|
||||||
)
|
|
||||||
assert numpy.array_equal(result, truth)
|
|
||||||
assert numpy.dtype(result.dtype) == numpy.dtype(truth.dtype)
|
|
||||||
|
|
||||||
result = rasterize(
|
|
||||||
[(geometry, default_value)],
|
|
||||||
out_shape=(rows, cols)
|
|
||||||
)
|
|
||||||
if numpy.dtype(dtype).kind == 'f':
|
|
||||||
assert numpy.allclose(result, truth)
|
|
||||||
else:
|
|
||||||
assert numpy.array_equal(result, truth)
|
|
||||||
# Since dtype is auto-detected, it may not match due to upcasting
|
|
||||||
|
|
||||||
# Unsupported types should all raise exceptions
|
|
||||||
unsupported_types = (
|
|
||||||
('int8', -127),
|
|
||||||
('int64', 20439845334323),
|
|
||||||
('float16', -9343.232)
|
|
||||||
)
|
|
||||||
|
|
||||||
for dtype, default_value in unsupported_types:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
rasterize(
|
|
||||||
[geometry],
|
|
||||||
out_shape=(rows, cols),
|
|
||||||
default_value=default_value,
|
|
||||||
dtype=dtype
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
rasterize(
|
|
||||||
[(geometry, default_value)],
|
|
||||||
out_shape=(rows, cols),
|
|
||||||
dtype=dtype
|
|
||||||
)
|
|
||||||
|
|
||||||
# Mismatched values and dtypes should raise exceptions
|
|
||||||
mismatched_types = (('uint8', 3.2423), ('uint8', -2147483648))
|
|
||||||
for dtype, default_value in mismatched_types:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
rasterize(
|
|
||||||
[geometry],
|
|
||||||
out_shape=(rows, cols),
|
|
||||||
default_value=default_value,
|
|
||||||
dtype=dtype
|
|
||||||
)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
rasterize(
|
|
||||||
[(geometry, default_value)],
|
|
||||||
out_shape=(rows, cols),
|
|
||||||
dtype=dtype
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_rasterize_geometries_symmetric():
|
|
||||||
"""Make sure that rasterize is symmetric with shapes"""
|
|
||||||
|
|
||||||
rows = cols = 10
|
|
||||||
transform = (1.0, 0.0, 0.0, 0.0, -1.0, 0.0)
|
|
||||||
truth = numpy.zeros((rows, cols), dtype=rasterio.ubyte)
|
|
||||||
truth[2:5, 2:5] = 1
|
|
||||||
with rasterio.drivers():
|
|
||||||
s = shapes(truth, transform=transform)
|
|
||||||
result = rasterize(s, out_shape=(rows, cols), transform=transform)
|
|
||||||
assert numpy.array_equal(result, truth)
|
|
||||||
|
|
||||||
|
|
||||||
def test_geometry_mask():
|
|
||||||
rows = cols = 10
|
|
||||||
transform = (1.0, 0.0, 0.0, 0.0, -1.0, 0.0)
|
|
||||||
truth = numpy.zeros((rows, cols), dtype=rasterio.bool_)
|
|
||||||
truth[2:5, 2:5] = True
|
|
||||||
with rasterio.drivers():
|
|
||||||
s = shapes((truth * 10).astype(rasterio.ubyte), transform=transform)
|
|
||||||
# Strip out values returned from shapes, and only keep first shape
|
|
||||||
geoms = [next(s)[0]]
|
|
||||||
|
|
||||||
# Regular mask should be the inverse of truth raster
|
|
||||||
mask = geometry_mask(geoms, out_shape=(rows, cols), transform=transform)
|
|
||||||
assert numpy.array_equal(mask, numpy.invert(truth))
|
|
||||||
|
|
||||||
# Inverted mask should be the same as the truth raster
|
|
||||||
mask = geometry_mask(geoms, out_shape=(rows, cols), transform=transform,
|
|
||||||
invert=True)
|
|
||||||
assert numpy.array_equal(mask, truth)
|
|
||||||
@ -1,144 +0,0 @@
|
|||||||
import logging
|
|
||||||
import sys
|
|
||||||
import numpy
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import rasterio
|
|
||||||
import rasterio.features as ftrz
|
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
|
|
||||||
|
|
||||||
|
|
||||||
def test_shapes():
|
|
||||||
"""Test creation of shapes from pixel values"""
|
|
||||||
|
|
||||||
image = numpy.zeros((20, 20), dtype=rasterio.ubyte)
|
|
||||||
image[5:15, 5:15] = 127
|
|
||||||
with rasterio.drivers():
|
|
||||||
shapes = ftrz.shapes(image)
|
|
||||||
shape, val = next(shapes)
|
|
||||||
assert shape['type'] == 'Polygon'
|
|
||||||
assert len(shape['coordinates']) == 2 # exterior and hole
|
|
||||||
assert val == 0
|
|
||||||
shape, val = next(shapes)
|
|
||||||
assert shape['type'] == 'Polygon'
|
|
||||||
assert len(shape['coordinates']) == 1 # no hole
|
|
||||||
assert val == 127
|
|
||||||
try:
|
|
||||||
shape, val = next(shapes)
|
|
||||||
except StopIteration:
|
|
||||||
assert True
|
|
||||||
else:
|
|
||||||
assert False
|
|
||||||
|
|
||||||
|
|
||||||
def test_shapes_band_shortcut():
|
|
||||||
"""Test rasterio bands as input to shapes"""
|
|
||||||
|
|
||||||
with rasterio.drivers():
|
|
||||||
with rasterio.open('tests/data/shade.tif') as src:
|
|
||||||
shapes = ftrz.shapes(rasterio.band(src, 1))
|
|
||||||
shape, val = next(shapes)
|
|
||||||
assert shape['type'] == 'Polygon'
|
|
||||||
assert len(shape['coordinates']) == 1
|
|
||||||
assert val == 255
|
|
||||||
|
|
||||||
|
|
||||||
def test_shapes_internal_driver_manager():
|
|
||||||
"""Make sure this works if driver is managed outside this test"""
|
|
||||||
|
|
||||||
image = numpy.zeros((20, 20), dtype=rasterio.ubyte)
|
|
||||||
image[5:15, 5:15] = 127
|
|
||||||
shapes = ftrz.shapes(image)
|
|
||||||
shape, val = next(shapes)
|
|
||||||
assert shape['type'] == 'Polygon'
|
|
||||||
|
|
||||||
|
|
||||||
def test_shapes_connectivity():
|
|
||||||
"""Test connectivity options"""
|
|
||||||
|
|
||||||
image = numpy.zeros((20, 20), dtype=rasterio.ubyte)
|
|
||||||
image[5:11, 5:11] = 1
|
|
||||||
image[11, 11] = 1
|
|
||||||
|
|
||||||
shapes = ftrz.shapes(image, connectivity=4)
|
|
||||||
shape, val = next(shapes)
|
|
||||||
assert len(shape['coordinates'][0]) == 5
|
|
||||||
|
|
||||||
shapes = ftrz.shapes(image, connectivity=8)
|
|
||||||
shape, val = next(shapes)
|
|
||||||
assert len(shape['coordinates'][0]) == 9
|
|
||||||
# Note: geometry is not technically valid at this point, it has a self
|
|
||||||
# intersection at 11,11
|
|
||||||
|
|
||||||
# Test invalid connectivity
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
shapes = ftrz.shapes(image, connectivity=12)
|
|
||||||
next(shapes)
|
|
||||||
|
|
||||||
|
|
||||||
def test_shapes_dtype():
|
|
||||||
"""Test image data type handling"""
|
|
||||||
|
|
||||||
rows = cols = 10
|
|
||||||
with rasterio.drivers():
|
|
||||||
supported_types = (
|
|
||||||
('int16', -32768),
|
|
||||||
('int32', -2147483648),
|
|
||||||
('uint8', 255),
|
|
||||||
('uint16', 65535),
|
|
||||||
('float32', 1.434532)
|
|
||||||
)
|
|
||||||
|
|
||||||
for dtype, test_value in supported_types:
|
|
||||||
image = numpy.zeros((rows, cols), dtype=dtype)
|
|
||||||
image[2:5, 2:5] = test_value
|
|
||||||
|
|
||||||
shapes = ftrz.shapes(image)
|
|
||||||
shape, value = next(shapes)
|
|
||||||
if dtype == 'float32':
|
|
||||||
assert round(value, 6) == round(test_value, 6)
|
|
||||||
else:
|
|
||||||
assert value == test_value
|
|
||||||
|
|
||||||
# Unsupported types should all raise exceptions
|
|
||||||
unsupported_types = (
|
|
||||||
('int8', -127),
|
|
||||||
('uint32', 4294967295),
|
|
||||||
('int64', 20439845334323),
|
|
||||||
('float16', -9343.232),
|
|
||||||
('float64', -98332.133422114)
|
|
||||||
)
|
|
||||||
|
|
||||||
for dtype, test_value in unsupported_types:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
image = numpy.zeros((rows, cols), dtype=dtype)
|
|
||||||
image[2:5, 2:5] = test_value
|
|
||||||
shapes = ftrz.shapes(image)
|
|
||||||
next(shapes)
|
|
||||||
|
|
||||||
# Test mask types
|
|
||||||
image = numpy.zeros((rows, cols), dtype='uint8')
|
|
||||||
image.fill(255)
|
|
||||||
supported_mask_types = (
|
|
||||||
('bool', 1),
|
|
||||||
('uint8', 255)
|
|
||||||
)
|
|
||||||
for dtype, mask_value in supported_mask_types:
|
|
||||||
mask = numpy.zeros((rows, cols), dtype=dtype)
|
|
||||||
mask[2:5, 2:5] = mask_value
|
|
||||||
shapes = ftrz.shapes(image, mask=mask)
|
|
||||||
shape, value = next(shapes)
|
|
||||||
assert value == 255
|
|
||||||
|
|
||||||
unsupported_mask_types = (
|
|
||||||
('int8', -127),
|
|
||||||
('int16', -32768)
|
|
||||||
)
|
|
||||||
for dtype, mask_value in unsupported_mask_types:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
mask = numpy.zeros((rows, cols), dtype=dtype)
|
|
||||||
mask[2:5, 2:5] = mask_value
|
|
||||||
shapes = ftrz.shapes(image, mask=mask)
|
|
||||||
next(shapes)
|
|
||||||
@ -1,185 +0,0 @@
|
|||||||
import logging
|
|
||||||
import sys
|
|
||||||
import numpy
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
import rasterio
|
|
||||||
import rasterio.features as ftrz
|
|
||||||
|
|
||||||
|
|
||||||
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
|
|
||||||
|
|
||||||
|
|
||||||
def test_sieve():
|
|
||||||
"""Test sieving a 10x10 feature from an ndarray."""
|
|
||||||
|
|
||||||
image = numpy.zeros((20, 20), dtype=rasterio.ubyte)
|
|
||||||
image[5:15, 5:15] = 1
|
|
||||||
|
|
||||||
# An attempt to sieve out features smaller than 100 should not change the
|
|
||||||
# image.
|
|
||||||
with rasterio.drivers():
|
|
||||||
sieved_image = ftrz.sieve(image, 100)
|
|
||||||
assert numpy.array_equal(sieved_image, image)
|
|
||||||
|
|
||||||
# Setting the size to 100 should leave us an empty, False image.
|
|
||||||
with rasterio.drivers():
|
|
||||||
sieved_image = ftrz.sieve(image, 101)
|
|
||||||
assert not sieved_image.any()
|
|
||||||
|
|
||||||
# Invalid size value should fail
|
|
||||||
for invalid_size in (0, 45.1234, image.size + 1):
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
sieved_image = ftrz.sieve(image, invalid_size)
|
|
||||||
|
|
||||||
|
|
||||||
def test_sieve_connectivity():
|
|
||||||
"""Test proper behavior of connectivity"""
|
|
||||||
|
|
||||||
image = numpy.zeros((20, 20), dtype=rasterio.ubyte)
|
|
||||||
image[5:15:2, 5:15] = 1
|
|
||||||
image[6, 4] = 1
|
|
||||||
image[8, 15] = 1
|
|
||||||
image[10, 4] = 1
|
|
||||||
image[12, 15] = 1
|
|
||||||
|
|
||||||
# Diagonals not connected, all become small features that will be removed
|
|
||||||
sieved_image = ftrz.sieve(image, 54, connectivity=4)
|
|
||||||
assert not sieved_image.any()
|
|
||||||
|
|
||||||
# Diagonals connected, everything is retained
|
|
||||||
sieved_image = ftrz.sieve(image, 54, connectivity=8)
|
|
||||||
assert numpy.array_equal(sieved_image, image)
|
|
||||||
|
|
||||||
# Invalid connectivity value should fail
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
ftrz.sieve(image, 54, connectivity=12)
|
|
||||||
|
|
||||||
|
|
||||||
def test_sieve_output():
|
|
||||||
"""Test proper behavior of output image, if passed into sieve"""
|
|
||||||
|
|
||||||
with rasterio.drivers():
|
|
||||||
shape = (20, 20)
|
|
||||||
image = numpy.zeros(shape, dtype=rasterio.ubyte)
|
|
||||||
image[5:15, 5:15] = 1
|
|
||||||
|
|
||||||
# Output should match returned array
|
|
||||||
output = numpy.zeros_like(image)
|
|
||||||
output[1:3, 1:3] = 5
|
|
||||||
sieved_image = ftrz.sieve(image, 100, output=output)
|
|
||||||
assert numpy.array_equal(output, sieved_image)
|
|
||||||
|
|
||||||
# Output of different dtype should fail
|
|
||||||
output = numpy.zeros(shape, dtype=rasterio.int32)
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
ftrz.sieve(image, 100, output)
|
|
||||||
|
|
||||||
# Output of a different shape should fail
|
|
||||||
output = numpy.zeros((21, 21), dtype=rasterio.ubyte)
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
ftrz.sieve(image, 100, output)
|
|
||||||
|
|
||||||
|
|
||||||
def test_sieve_mask():
|
|
||||||
"""Test proper behavior of mask image, if passed int sieve"""
|
|
||||||
|
|
||||||
with rasterio.drivers():
|
|
||||||
shape = (20, 20)
|
|
||||||
image = numpy.zeros(shape, dtype=rasterio.ubyte)
|
|
||||||
image[5:15, 5:15] = 1
|
|
||||||
image[1:3, 1:3] = 2
|
|
||||||
|
|
||||||
# Blank mask has no effect, only areas smaller than size will be
|
|
||||||
# removed
|
|
||||||
mask = numpy.ones(shape, dtype=rasterio.bool_)
|
|
||||||
sieved_image = ftrz.sieve(image, 100, mask=mask)
|
|
||||||
truth = numpy.zeros_like(image)
|
|
||||||
truth[5:15, 5:15] = 1
|
|
||||||
assert numpy.array_equal(sieved_image, truth)
|
|
||||||
|
|
||||||
# Only areas within the overlap of the mask and values will be kept
|
|
||||||
mask = numpy.ones(shape, dtype=rasterio.bool_)
|
|
||||||
mask[7:10, 7:10] = False
|
|
||||||
sieved_image = ftrz.sieve(image, 100, mask=mask)
|
|
||||||
truth = numpy.zeros_like(image)
|
|
||||||
truth[7:10, 7:10] = 1
|
|
||||||
assert numpy.array_equal(sieved_image, truth)
|
|
||||||
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
mask = numpy.ones((21, 21), dtype=rasterio.bool_)
|
|
||||||
ftrz.sieve(image, 100, mask=mask)
|
|
||||||
|
|
||||||
|
|
||||||
def test_dtypes():
|
|
||||||
"""Test data type support for sieve"""
|
|
||||||
|
|
||||||
rows = cols = 10
|
|
||||||
with rasterio.drivers():
|
|
||||||
supported_types = (
|
|
||||||
('int16', -32768),
|
|
||||||
('int32', -2147483648),
|
|
||||||
('uint8', 255),
|
|
||||||
('uint16', 65535)
|
|
||||||
)
|
|
||||||
|
|
||||||
for dtype, test_value in supported_types:
|
|
||||||
image = numpy.zeros((rows, cols), dtype=dtype)
|
|
||||||
image[2:5, 2:5] = test_value
|
|
||||||
|
|
||||||
# Sieve should return the original image
|
|
||||||
sieved_image = ftrz.sieve(image, 2)
|
|
||||||
assert numpy.array_equal(image, sieved_image)
|
|
||||||
assert numpy.dtype(sieved_image.dtype).name == dtype
|
|
||||||
|
|
||||||
# Sieve should return a blank image
|
|
||||||
sieved_image = ftrz.sieve(image, 10)
|
|
||||||
assert numpy.array_equal(numpy.zeros_like(image), sieved_image)
|
|
||||||
assert numpy.dtype(sieved_image.dtype).name == dtype
|
|
||||||
|
|
||||||
# Unsupported types should all raise exceptions
|
|
||||||
unsupported_types = (
|
|
||||||
('int8', -127),
|
|
||||||
('uint32', 4294967295),
|
|
||||||
('int64', 20439845334323),
|
|
||||||
('float16', -9343.232),
|
|
||||||
('float32', 1.434532),
|
|
||||||
('float64', -98332.133422114)
|
|
||||||
)
|
|
||||||
|
|
||||||
for dtype, test_value in unsupported_types:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
image = numpy.zeros((rows, cols), dtype=dtype)
|
|
||||||
image[2:5, 2:5] = test_value
|
|
||||||
ftrz.sieve(image, 2)
|
|
||||||
|
|
||||||
# Test mask types
|
|
||||||
image = numpy.zeros((rows, cols), dtype='uint8')
|
|
||||||
image.fill(255)
|
|
||||||
|
|
||||||
supported_mask_types = (
|
|
||||||
('bool', 1),
|
|
||||||
('uint8', 255)
|
|
||||||
)
|
|
||||||
for dtype, mask_value in supported_mask_types:
|
|
||||||
mask = numpy.zeros((rows, cols), dtype=dtype)
|
|
||||||
mask[2:5, 2:5] = mask_value
|
|
||||||
sieved_image = ftrz.sieve(image, 2, mask=mask)
|
|
||||||
assert numpy.array_equal(image, sieved_image)
|
|
||||||
|
|
||||||
unsupported_mask_types = (
|
|
||||||
('int8', -127),
|
|
||||||
('int16', -32768)
|
|
||||||
)
|
|
||||||
for dtype, mask_value in unsupported_mask_types:
|
|
||||||
with pytest.raises(ValueError):
|
|
||||||
mask = numpy.zeros((rows, cols), dtype=dtype)
|
|
||||||
mask[2:5, 2:5] = mask_value
|
|
||||||
ftrz.sieve(image, 2, mask=mask)
|
|
||||||
|
|
||||||
|
|
||||||
def test_sieve_shade():
|
|
||||||
with rasterio.drivers():
|
|
||||||
with rasterio.open('tests/data/shade.tif') as src:
|
|
||||||
sieved_image = ftrz.sieve(rasterio.band(src, 1), 42)
|
|
||||||
assert sieved_image.shape == (1024, 1024)
|
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user