Fix last lingering window errors

This commit is contained in:
Sean Gillies 2017-06-28 15:05:16 +02:00
parent 2c1d7c5377
commit f0d3eb0bfb
24 changed files with 452 additions and 693 deletions

View File

@ -51,6 +51,6 @@ install:
- "pip list"
script:
- "if [[ $TRAVIS_PYTHON_VERSION == 3.5 && $GDALVERSION == 2.1.0 ]]; then python -m pytest --doctest-ignore-import-errors --doctest-glob='*.rst' docs/*.rst -k 'not index and not quickstart and not switch' ; fi"
- py.test --cov rasterio --cov-report term-missing
- python -Wall -m pytest --cov rasterio --cov-report term-missing
after_success:
- coveralls || echo "!! intermittent coveralls failure"

View File

@ -24,7 +24,7 @@ from rasterio.enums import (
from rasterio.env import Env
from rasterio.errors import (
RasterioIOError, CRSError, DriverRegistrationError,
NotGeoreferencedWarning)
NotGeoreferencedWarning, RasterioDeprecationWarning)
from rasterio.profiles import Profile
from rasterio.transform import Affine, guard_transform, tastes_like_gdal
from rasterio.vfs import parse_path, vsi_path
@ -425,7 +425,7 @@ cdef class DatasetBase(object):
def __get__(self):
warnings.warn(
"'mask_flags' is deprecated. Switch to 'mask_flag_enums'",
DeprecationWarning,
RasterioDeprecationWarning,
stacklevel=2)
return self._mask_flags
@ -535,7 +535,7 @@ cdef class DatasetBase(object):
col = i * w
width = min(w, self.width - col)
yield (j, i), windows.Window(
col_off=col, row_off=row, num_cols=width, num_rows=height)
col_off=col, row_off=row, width=width, height=height)
property bounds:
"""Returns the lower left and upper right bounds of the dataset
@ -703,7 +703,7 @@ cdef class DatasetBase(object):
"'src.affine' is deprecated. Please switch to "
"'src.transform'. See "
"https://github.com/mapbox/rasterio/issues/86 for details.",
DeprecationWarning,
RasterioDeprecationWarning,
stacklevel=2)
return Affine.from_gdal(*self.get_transform())

View File

@ -24,11 +24,11 @@ from rasterio import dtypes
from rasterio.enums import ColorInterp, MaskFlags, Resampling
from rasterio.errors import CRSError, DriverRegistrationError
from rasterio.errors import RasterioIOError
from rasterio.errors import NodataShadowWarning
from rasterio.errors import NodataShadowWarning, WindowError
from rasterio.sample import sample_gen
from rasterio.transform import Affine
from rasterio.vfs import parse_path, vsi_path
from rasterio import windows
from rasterio.windows import Window, intersection
from libc.stdio cimport FILE
cimport numpy as np
@ -121,7 +121,7 @@ cdef class DatasetReaderBase(DatasetBase):
Cannot combined with `out`.
window : a pair (tuple) of pairs of ints, optional
window : a pair (tuple) of pairs of ints or Window, optional
The optional `window` argument is a 2 item tuple. The first
item is a tuple containing the indexes of the rows at which
the window starts and stops and the second is a tuple
@ -225,15 +225,15 @@ cdef class DatasetReaderBase(DatasetBase):
if window:
if isinstance(window, tuple):
window = windows.coerce_and_warn(window)
window = Window.from_slices(
*window, height=self.height, width=self.width,
boundless=boundless)
if not boundless:
window = windows.crop(
windows.evaluate(window, self.height, self.width),
self.height, self.width)
window = window.crop(self.height, self.width)
int_window = window.round_shape()
win_shape += (int(int_window.num_rows), int(int_window.num_cols))
int_window = window.round_lengths()
win_shape += (int(int_window.height), int(int_window.width))
else:
win_shape += self.shape
@ -319,19 +319,23 @@ cdef class DatasetReaderBase(DatasetBase):
else:
# Compute the overlap between the dataset and the boundless window.
overlap = ((
max(min(window[0][0], self.height), 0),
max(min(window[0][1], self.height), 0)), (
max(min(window[1][0], self.width), 0),
max(min(window[1][1], self.width), 0)))
log.debug("Overlap: %r", overlap)
if overlap != ((0, 0), (0, 0)):
try:
overlap = intersection(
window, Window(0, 0, self.width, self.height))
except WindowError:
log.info("Dataset and window do not overlap")
data = None
if masked:
kwds = {'mask': True}
if len(set(nodatavals)) == 1:
if nodatavals[0] is not None:
kwds['fill_value'] = nodatavals[0]
out = np.ma.array(out, **kwds)
else:
# Prepare a buffer.
window_h, window_w = win_shape[-2:]
overlap_h = overlap[0][1] - overlap[0][0]
overlap_w = overlap[1][1] - overlap[1][0]
overlap_h = overlap.height
overlap_w = overlap.width
scaling_h = float(out.shape[-2:][0])/window_h
scaling_w = float(out.shape[-2:][1])/window_w
buffer_shape = (
@ -352,24 +356,15 @@ cdef class DatasetReaderBase(DatasetBase):
kwds['fill_value'] = nodatavals[0]
data = np.ma.array(data, **kwds)
else:
data = None
if masked:
kwds = {'mask': True}
if len(set(nodatavals)) == 1:
if nodatavals[0] is not None:
kwds['fill_value'] = nodatavals[0]
out = np.ma.array(out, **kwds)
if data is not None:
# Determine where to put the data in the output window.
data_h, data_w = buffer_shape
roff = 0
coff = 0
if window[0][0] < 0:
roff = int(-window[0][0] * scaling_h)
if window[1][0] < 0:
coff = int(-window[1][0] * scaling_w)
if window.row_off < 0:
roff = int(-window.row_off * scaling_h)
if window.col_off < 0:
coff = int(-window.col_off * scaling_w)
for dst, src in zip(
out if len(out.shape) == 3 else [out],
@ -422,7 +417,7 @@ cdef class DatasetReaderBase(DatasetBase):
Cannot combined with `out`.
window : a pair (tuple) of pairs of ints, optional
window : a pair (tuple) of pairs of ints or Window, optional
The optional `window` argument is a 2 item tuple. The first
item is a tuple containing the indexes of the rows at which
the window starts and stops and the second is a tuple
@ -463,15 +458,15 @@ cdef class DatasetReaderBase(DatasetBase):
if window:
if isinstance(window, tuple):
window = windows.coerce_and_warn(window)
window = Window.from_slices(
*window, height=self.height, width=self.width,
boundless=boundless)
if not boundless:
window = windows.crop(
windows.evaluate(window, self.height, self.width),
self.height, self.width)
window = window.crop(self.height, self.width)
int_window = window.round_shape()
win_shape += (int(int_window.num_rows), int(int_window.num_cols))
int_window = window.round_lengths()
win_shape += (int(int_window.height), int(int_window.width))
else:
win_shape += self.shape
@ -506,25 +501,23 @@ cdef class DatasetReaderBase(DatasetBase):
else:
# Compute the overlap between the dataset and the boundless window.
overlap = ((
max(min(window[0][0], self.height), 0),
max(min(window[0][1], self.height), 0)), (
max(min(window[1][0], self.width), 0),
max(min(window[1][1], self.width), 0)))
if overlap != ((0, 0), (0, 0)):
try:
overlap = intersection(
window, Window(0, 0, self.width, self.height))
except WindowError:
log.info("Window and dataset do not overlap")
data = None
else:
# Prepare a buffer.
window_h, window_w = win_shape[-2:]
overlap_h = overlap[0][1] - overlap[0][0]
overlap_w = overlap[1][1] - overlap[1][0]
overlap_h = overlap.height
overlap_w = overlap.width
scaling_h = float(out.shape[-2:][0])/window_h
scaling_w = float(out.shape[-2:][1])/window_w
buffer_shape = (int(overlap_h*scaling_h), int(overlap_w*scaling_w))
data = np.zeros(win_shape[:-2] + buffer_shape, 'uint8')
data = self._read(indexes, data, overlap, dtype, masks=True,
resampling=resampling)
else:
data = None
if data is not None:
# Determine where to put the data in the output window.
@ -575,27 +568,19 @@ cdef class DatasetReaderBase(DatasetBase):
dataset = self.handle()
# Turning the read window into GDAL offsets and lengths is
# the job of _read().
if window:
if isinstance(window, tuple):
window = windows.Window.from_ranges(*window)
window = windows.evaluate(window, self.height, self.width)
log.debug("Eval'd window: %r", window)
if not isinstance(window, Window):
raise WindowError("window must be an instance of Window")
yoff = window.row_off
xoff = window.col_off
height = window.num_rows
width = window.num_cols
# Now that we have floating point windows it's easy for
# the number of pixels to read to slip below 1 due to
# loss of floating point precision. Here we ensure that
# we're reading at least one pixel.
height = max(1.0, height)
width = max(1.0, width)
height = max(1.0, window.height)
width = max(1.0, window.width)
else:
xoff = yoff = <int>0
@ -685,46 +670,13 @@ cdef class DatasetReaderBase(DatasetBase):
"""Read the mask band into an `out` array if provided,
otherwise return a new array containing the dataset's
valid data mask.
The optional `window` argument takes a tuple like:
((row_start, row_stop), (col_start, col_stop))
specifying a raster subset to write into.
"""
cdef GDALRasterBandH band
cdef GDALRasterBandH mask
warnings.warn(
"read_mask() is deprecated and will be removed by Rasterio 1.0. "
"Please use read_masks() instead.",
DeprecationWarning,
stacklevel=2)
band = self.band(1)
mask = GDALGetMaskBand(band)
if mask == NULL:
return None
if out is None:
out_shape = (
window
and windows.shape(window, self.height, self.width)
or self.shape)
out = np.zeros(out_shape, np.uint8)
if window:
window = windows.evaluate(window, self.height, self.width)
yoff = window[0][0]
xoff = window[1][0]
height = window[0][1] - yoff
width = window[1][1] - xoff
else:
xoff = yoff = 0
width = self.width
height = self.height
io_band(mask, 0, xoff, yoff, width, height, out)
return out
return self.read_masks(1, out=out, window=window, boundless=boundless)
def sample(self, xy, indexes=None):
"""Get the values of a dataset at certain positions
@ -1315,11 +1267,12 @@ cdef class DatasetWriterBase(DatasetReaderBase):
# Prepare the IO window.
if window:
window = windows.evaluate(window, self.height, self.width)
yoff = <int>window[0][0]
xoff = <int>window[1][0]
height = <int>window[0][1] - yoff
width = <int>window[1][1] - xoff
if isinstance(window, tuple):
window = Window.from_slices(*window, self.height, self.width)
yoff = window.row_off
xoff = window.col_off
height = window.height
width = window.width
else:
xoff = yoff = <int>0
width = <int>self.width
@ -1497,11 +1450,12 @@ cdef class DatasetWriterBase(DatasetReaderBase):
raise RasterioIOError("Failed to get mask.")
if window:
window = windows.evaluate(window, self.height, self.width)
yoff = window[0][0]
xoff = window[1][0]
height = window[0][1] - yoff
width = window[1][1] - xoff
if isinstance(window, tuple):
window = Window.from_slices(*window, self.height, self.width)
yoff = window.row_off
xoff = window.col_off
height = window.height
width = window.width
else:
xoff = yoff = 0
width = self.width

View File

@ -3,12 +3,20 @@
from click import FileError
class RasterioError(Exception):
"""Root exception class"""
class WindowError(RasterioError):
"""Raised when errors occur during window operations"""
class CRSError(ValueError):
"""Raised when a CRS string or mapping is invalid or cannot serve
to define a coordinate transformation."""
class EnvError(Exception):
class EnvError(RasterioError):
"""Raised when the state of GDAL/AWS environment cannot be created
or modified."""
@ -48,8 +56,15 @@ class GDALBehaviorChangeException(RuntimeError):
example, antimeridian cutting is always on as of GDAL 2.2.0. Users
expecting it to be off will be presented with a MultiPolygon when the
rest of their code expects a Polygon.
# Raises an exception on GDAL >= 2.2.0
rasterio.warp.transform_geometry(
src_crs, dst_crs, antimeridian_cutting=False)
"""
class WindowEvaluationError(ValueError):
"""Raised when window evaluation fails"""
class RasterioDeprecationWarning(UserWarning):
"""Rasterio module deprecations"""

View File

@ -105,7 +105,7 @@ class WindowMethodsMixin(object):
properties: `transform`, `height` and `width`
"""
def window(self, left, bottom, right, top, boundless=False, precision=6):
def window(self, left, bottom, right, top, boundless=True, precision=6):
"""Get the window corresponding to the bounding coordinates.
Parameters
@ -127,10 +127,7 @@ class WindowMethodsMixin(object):
Returns
-------
window: tuple
((row_start, row_stop), (col_start, col_stop))
corresponding to the bounding coordinates
window: Window
"""
transform = guard_transform(self.transform)

View File

@ -104,7 +104,7 @@ def mask(raster, shapes, nodata=None, crop=False, all_touched=False,
# Get the window with integer height
# and width that contains the bounds window.
out_window = bounds_window.round_shape()
out_window = bounds_window.round_shape(op='ceil')
height = int(out_window.num_rows)
width = int(out_window.num_cols)

View File

@ -155,7 +155,7 @@ def merge(sources, bounds=None, res=None, nodata=None, precision=7):
# 4. Initialize temp array.
tcount = first.count
trows, tcols = (
int(round(dst_window.num_rows)), int(round(dst_window.num_cols)))
int(round(dst_window.height)), int(round(dst_window.width)))
temp_shape = (tcount, trows, tcols)

View File

@ -4,6 +4,7 @@ import warnings
from rasterio.compat import UserDict
from rasterio.dtypes import uint8
from rasterio.errors import RasterioDeprecationWarning
class Profile(UserDict):
@ -24,11 +25,11 @@ class Profile(UserDict):
if 'affine' in initdata and 'transform' in initdata:
warnings.warn("affine item is deprecated, use transform only",
DeprecationWarning)
RasterioDeprecationWarning)
del initdata['affine']
elif 'affine' in initdata:
warnings.warn("affine item is deprecated, use transform instead",
DeprecationWarning)
RasterioDeprecationWarning)
initdata['transform'] = initdata.pop('affine')
self.data.update(initdata)
@ -38,7 +39,7 @@ class Profile(UserDict):
if key == 'affine':
key = 'transform'
warnings.warn("affine item is deprecated, use transform instead",
DeprecationWarning)
RasterioDeprecationWarning)
return self.data[key]
def __setitem__(self, key, val):
@ -53,7 +54,7 @@ class Profile(UserDict):
DEPRECATED.
"""
warnings.warn("__call__() is deprecated, use mapping methods instead",
DeprecationWarning)
RasterioDeprecationWarning)
profile = self.data.copy()
profile.update(**kwds)
return profile

View File

@ -7,6 +7,7 @@ from .helpers import resolve_inout
from . import options
import rasterio
from rasterio.coords import disjoint_bounds
from rasterio.windows import Window
# Geographic (default), projected, or Mercator switch.
@ -100,13 +101,15 @@ def clip(ctx, files, output, bounds, like, driver, projection,
raise click.UsageError('--bounds or --like required')
bounds_window = src.window(*bounds)
bounds_window = bounds_window.intersection(
Window(0, 0, src.width, src.height))
# Get the window with integer height
# and width that contains the bounds window.
out_window = bounds_window.round_shape()
out_window = bounds_window.round_lengths(op='ceil')
height = int(out_window.num_rows)
width = int(out_window.num_cols)
height = int(out_window.height)
width = int(out_window.width)
out_kwargs = src.meta.copy()
out_kwargs.update({

View File

@ -12,7 +12,7 @@ def sample_gen(dataset, xy, indexes=None):
indexes = [indexes]
for x, y in xy:
r, c = index(x, y)
window = Window(c, r, 1, 1)
row_off, col_off = index(x, y)
window = Window(col_off, row_off, 1, 1)
data = read(indexes, window=window, masked=False, boundless=True)
yield data[:, 0, 0]

View File

@ -1,11 +1,20 @@
"""Window utilities and related functions.
A window is an instance of Window or a 2D ndarray indexer in the form
of a tuple:
A window is an instance of Window
Window(column_offset, row_offset, width, height)
or a 2D N-D array indexer in the form of a tuple.
((row_start, row_stop), (col_start, col_stop))
This latter form will be deprecated. Please change your usage.
The latter can be evaluated within the context of a given height and
width and a boolean flag specifying whether the evaluation is boundless
or not. If boundless=True, negative index values do not mean index from
the end of the array dimension as they do in the boundless=False case.
The newer float precision read-write window capabilities of Rasterio
require instances of Window to be used.
"""
from __future__ import division
@ -19,26 +28,11 @@ import attr
from affine import Affine
import numpy as np
from rasterio.errors import RasterioDeprecationWarning, WindowError
from rasterio.transform import rowcol
def warn_window_deprecation():
"""Standard warning about range tuple deprecation"""
warnings.warn(
"Range tuple window are deprecated. Please switch to Window class",
DeprecationWarning)
def coerce_and_warn(value):
"""Coerce ranges to Window and warn"""
if isinstance(value, Window):
return value
else:
warn_window_deprecation()
if not (len(value) == 2 and len(value[0]) == 2 and
len(value[1]) == 2):
raise ValueError("Not a valid pair of value")
return Window.from_ranges(*value)
PIXEL_PRECISION = 6
def iter_args(function):
@ -59,7 +53,6 @@ def toranges(window):
if isinstance(window, Window):
return window.toranges()
else:
warn_window_deprecation()
return window
@ -82,20 +75,21 @@ def get_data_window(arr, nodata=None):
num_dims = len(arr.shape)
if num_dims > 3:
raise ValueError('get_data_window input array must have no more than '
'3 dimensions')
raise WindowError(
"get_data_window input array must have no more than "
"3 dimensions")
if nodata is None:
if not hasattr(arr, 'mask'):
return Window.from_ranges((0, arr.shape[-2]), (0, arr.shape[-1]))
return Window.from_slices((0, arr.shape[-2]), (0, arr.shape[-1]))
else:
arr = np.ma.masked_array(arr, arr == nodata)
if num_dims == 2:
data_rows, data_cols = np.where(arr.mask == False)
data_rows, data_cols = np.where(np.equal(arr.mask, False))
else:
data_rows, data_cols = np.where(
np.any(np.rollaxis(arr.mask, 0, 3) == False, axis=2))
np.any(np.equal(np.rollaxis(arr.mask, 0, 3), False), axis=2))
if data_rows.size:
row_range = (data_rows.min(), data_rows.max() + 1)
@ -107,7 +101,7 @@ def get_data_window(arr, nodata=None):
else:
col_range = (0, 0)
return Window.from_ranges(row_range, col_range)
return Window.from_slices(row_range, col_range)
@iter_args
@ -125,7 +119,7 @@ def union(*windows):
Window
"""
stacked = np.dstack([toranges(w) for w in windows])
return Window.from_ranges(
return Window.from_slices(
(stacked[0, 0].min(), stacked[0, 1].max()),
(stacked[1, 0].min(), stacked[1, 1]. max()))
@ -134,7 +128,7 @@ def union(*windows):
def intersection(*windows):
"""Innermost extent of window intersections.
Will raise ValueError if windows do not intersect.
Will raise WindowError if windows do not intersect.
Parameters
----------
@ -146,10 +140,10 @@ def intersection(*windows):
Window
"""
if not intersect(windows):
raise ValueError('windows do not intersect')
raise WindowError("windows do not intersect")
stacked = np.dstack([toranges(w) for w in windows])
return Window.from_ranges(
return Window.from_slices(
(stacked[0, 0].max(), stacked[0, 1].min()),
(stacked[1, 0].max(), stacked[1, 1]. min()))
@ -185,7 +179,7 @@ def intersect(*windows):
def from_bounds(left, bottom, right, top, transform=None,
height=None, width=None, boundless=False, precision=6):
height=None, width=None, precision=6, **kwargs):
"""Get the window corresponding to the bounding coordinates.
Parameters
@ -197,9 +191,6 @@ def from_bounds(left, bottom, right, top, transform=None,
Affine transform matrix.
height, width : int
Number of rows and columns of the window.
boundless : boolean, optional
If True, the output window's size may exceed the given height
and width.
precision : int, optional
Number of decimal points of precision when computing inverse
transform.
@ -209,20 +200,19 @@ def from_bounds(left, bottom, right, top, transform=None,
Window
A new Window
"""
window_start = rowcol(
if 'boundless' in kwargs:
warnings.warn("boundless keyword should not be used",
RasterioDeprecationWarning)
row_start, col_start = rowcol(
transform, left, top, op=float, precision=precision)
window_stop = rowcol(
row_stop, col_stop = rowcol(
transform, right, bottom, op=float, precision=precision)
window = Window.from_ranges(*tuple(zip(window_start, window_stop)))
if boundless:
return window
else:
if None in (height, width):
raise ValueError("Must supply height and width unless boundless")
return crop(window, height, width)
return Window.from_slices(
(row_start, row_stop), (col_start, col_stop), height=height,
width=width, boundless=True)
def transform(window, transform):
@ -240,14 +230,14 @@ def transform(window, transform):
Affine
The affine transform matrix for the given window
"""
window = coerce_and_warn(window)
window = evaluate(window, height=0, width=0)
x, y = transform * (window.col_off or 0.0, window.row_off or 0.0)
return Affine.translation(
x - transform.c, y - transform.f) * transform
def bounds(window, transform):
def bounds(window, transform, height=0, width=0):
"""Get the spatial bounds of a window.
Parameters
@ -262,12 +252,12 @@ def bounds(window, transform):
left, bottom, right, top: float
A tuple of spatial coordinate bounding values.
"""
window = coerce_and_warn(window)
window = evaluate(window, height=height, width=width)
row_min = window.row_off
row_max = row_min + window.num_rows
row_max = row_min + window.height
col_min = window.col_off
col_max = col_min + window.num_cols
col_max = col_min + window.width
left, bottom = transform * (col_min, row_max)
right, top = transform * (col_max, row_min)
@ -289,17 +279,18 @@ def crop(window, height, width):
Window
A new Window object.
"""
window = coerce_and_warn(window)
window = evaluate(window, height=height, width=width)
row_start = min(max(window.row_off, 0), height)
col_start = min(max(window.col_off, 0), width)
row_stop = max(0, min(window.row_off + window.num_rows, height))
col_stop = max(0, min(window.col_off + window.num_cols, width))
row_stop = max(0, min(window.row_off + window.height, height))
col_stop = max(0, min(window.col_off + window.width, width))
return Window.from_ranges((row_start, row_stop), (col_start, col_stop))
return Window(col_start, row_start, col_stop - col_start,
row_stop - row_start)
def evaluate(window, height, width):
def evaluate(window, height, width, boundless=False):
"""Evaluates a window tuple that may contain relative index values.
The height and width of the array the window targets is the context
@ -307,9 +298,9 @@ def evaluate(window, height, width):
Parameters
----------
window : Window.
window: Window.
The input window.
height, width : int
height, width: int
The number of rows or columns in the array that the window
targets.
@ -318,43 +309,11 @@ def evaluate(window, height, width):
Window
A new Window object with absolute index values.
"""
window = coerce_and_warn(window)
r, c = window.toranges()
r_start = r[0] or 0
if r_start < 0:
if height < 0:
raise ValueError("invalid height: %d" % height)
r_start += height
r_stop = r[1] or height
if r_stop < 0:
if height < 0:
raise ValueError("invalid height: %d" % height)
r_stop += height
if not r_stop >= r_start:
raise ValueError(
"invalid window: row range (%d, %d)" % (r_start, r_stop))
c_start = c[0] or 0
if c_start < 0:
if width < 0:
raise ValueError("invalid width: %d" % width)
c_start += width
c_stop = c[1] or width
if c_stop < 0:
if width < 0:
raise ValueError("invalid width: %d" % width)
c_stop += width
if not c_stop >= c_start:
raise ValueError(
"invalid window: col range (%d, %d)" % (c_start, c_stop))
return Window.from_ranges((r_start, r_stop), (c_start, c_stop))
if isinstance(window, Window):
return window
else:
return Window.from_slices(window[0], window[1], height=height, width=width,
boundless=boundless)
def shape(window, height=-1, width=-1):
@ -377,10 +336,10 @@ def shape(window, height=-1, width=-1):
The number of rows and columns of the window.
"""
evaluated = evaluate(window, height, width)
return evaluated.num_rows, evaluated.num_cols
return evaluated.height, evaluated.width
def window_index(window):
def window_index(window, height=0, width=0):
"""Construct a pair of slice objects for ndarray indexing
Starting indexes are rounded down, Stopping indexes are rounded up.
@ -395,7 +354,7 @@ def window_index(window):
row_slice, col_slice: slice
A pair of slices in row, column order
"""
window = coerce_and_warn(window)
window = evaluate(window, height=height, width=width)
(row_start, row_stop), (col_start, col_stop) = window.toranges()
return (
@ -403,9 +362,8 @@ def window_index(window):
slice(int(math.floor(col_start)), int(math.ceil(col_stop))))
def round_window_to_full_blocks(window, block_shapes):
"""
Round window to include full expanse of intersecting tiles.
def round_window_to_full_blocks(window, block_shapes, height=0, width=0):
"""Round window to include full expanse of intersecting tiles.
Parameters
----------
@ -413,18 +371,19 @@ def round_window_to_full_blocks(window, block_shapes):
The input window.
block_shapes : tuple of block shapes
The input raster's block shape. All bands must have the same block/stripe structure
The input raster's block shape. All bands must have the same
block/stripe structure
Returns
-------
Window
"""
if len(set(block_shapes)) != 1: # pragma: no cover
raise ValueError(
raise WindowError(
"All bands must have the same block/stripe structure")
window = coerce_and_warn(window)
window = evaluate(window, height=height, width=width)
height_shape = block_shapes[0][0]
width_shape = block_shapes[0][1]
@ -438,7 +397,14 @@ def round_window_to_full_blocks(window, block_shapes):
col_max = int(col_stop // width_shape) * width_shape + \
(width_shape if col_stop % width_shape != 0 else 0)
return Window.from_ranges((row_min, row_max), (col_min, col_max))
return Window(col_min, row_min, col_max - col_min, row_max - row_min)
def validate_length_value(instance, attribute, value):
if value and value < 0:
raise ValueError("Number of columns or rows must be non-negative")
_default = attr.Factory(lambda x: 0.0 if x is None else float(x))
@attr.s(slots=True)
@ -450,54 +416,53 @@ class Window(object):
Attributes
----------
col_off
num_cols
row_off
num_rows
col_off, row_off: float
The offset for the window.
width, height: float
Lengths of the window.
Previously the lengths were called 'num_cols' and 'num_rows' but
this is a bit confusing in the new float precision world and the
attributes have been changed. The originals are deprecated.
"""
col_off = attr.ib(default=0.0)
row_off = attr.ib(default=0.0)
num_cols = attr.ib(default=0.0)
num_rows = attr.ib(default=0.0)
col_off = attr.ib(default=_default)
row_off = attr.ib(default=_default)
width = attr.ib(default=_default, validator=validate_length_value)
height = attr.ib(default=_default, validator=validate_length_value)
def __repr__(self):
"""Return a nicely formatted representation string"""
return (
"Window(col_off={self.col_off}, row_off={self.row_off}, "
"num_cols={self.num_cols}, num_rows={self.num_rows})").format(
"width={self.width}, height={self.height})").format(
self=self)
def __getnewargs__(self):
'Return self as a plain tuple. Used by copy and pickle.'
return self.flatten()
def flatten(self):
"""A flattened form of the window.
Returns
-------
col_off, row_off, num_cols, num_rows: int
col_off, row_off, width, height: float
Window offsets and lengths.
"""
return (self.col_off, self.row_off, self.num_cols, self.num_rows)
return (self.col_off, self.row_off, self.width, self.height)
def todict(self):
"""A mapping of field names and values.
"""A mapping of attribute names and values.
Returns
-------
dict
"""
return collections.OrderedDict(
col_off=self.col_off, row_off=self.row_off, num_cols=self.num_cols,
num_rows=self.num_rows)
col_off=self.col_off, row_off=self.row_off, width=self.width,
height=self.height)
def toranges(self):
"""Makes an equivalent pair of range tuples"""
row_stop = None if self.num_rows is None else self.row_off + self.num_rows
col_stop = None if self.num_cols is None else self.col_off + self.num_cols
return (
(self.row_off, row_stop), (self.col_off, col_stop))
(self.row_off, self.row_off + self.height),
(self.col_off, self.col_off + self.width))
def toslices(self):
"""Slice objects for use as an ndarray indexer.
@ -510,44 +475,109 @@ class Window(object):
return tuple(slice(*rng) for rng in self.toranges())
@property
def __array_interface__(self):
return {'shape': (2, 2), 'typestr': 'f', 'version': 3,
'data': np.array(
[[a, b] for a, b in self.toranges()], dtype='float')}
def num_cols(self):
warnings.warn("use 'width' attribute instead", RasterioDeprecationWarning)
return self.width
@property
def num_rows(self):
warnings.warn("use 'height' attribute instead",
RasterioDeprecationWarning, stacklevel=2)
return self.height
def __getitem__(self, index):
"""Provides backwards compatibility for clients using tuples"""
warnings.warn("This usage is deprecated", RasterioDeprecationWarning)
return self.toranges()[index]
@classmethod
def from_offlen(cls, col_off, row_off, num_cols, num_rows):
"""For backwards compatibility only"""
warnings.warn("Use the class constructor instead of this method",
DeprecationWarning)
RasterioDeprecationWarning)
return cls(col_off, row_off, num_cols, num_rows)
@classmethod
def from_ranges(cls, row_range, col_range):
"""Construct a Window from row and column range tuples.
def from_slices(cls, rows, cols, height=-1, width=-1, boundless=False):
"""Construct a Window from row and column slices or tuples.
Parameters
----------
row_range, col_range: tuple
2-tuples containing start, stop indexes.
rows, cols: slice or tuple
Slices or 2-tuples containing start, stop indexes.
height, width: float
A shape to resolve relative values against.
boundless: bool, optional
Whether the inputs are bounded or bot.
Returns
-------
Window
"""
col_off = col_range[0] or 0.0
row_off = row_range[0] or 0.0
num_cols = None if col_range[1] is None else col_range[1] - col_off
num_rows = None if row_range[1] is None else row_range[1] - row_off
# Convert the rows indexing obj to offset and height.
# Normalize to slices
if not isinstance(rows, (tuple, slice)):
raise WindowError("rows must be a tuple or slice")
else:
rows = slice(*rows) if isinstance(rows, tuple) else rows
# Resolve the window height.
# Fail if the stop value is relative or implicit and there
# is no height context.
if not boundless and (
(rows.start is not None and rows.start < 0) or
rows.stop is None or rows.stop < 0) and height < 0:
raise WindowError(
"A non-negative height is required")
row_off = rows.start or 0.0
if not boundless and row_off < 0:
row_off += height
row_stop = height if rows.stop is None else rows.stop
if not boundless and row_stop < 0:
row_stop += height
num_rows = row_stop - row_off
# Number of rows is never less than 0.
num_rows = max(num_rows, 0.0)
# Do the same for the cols indexing object.
if not isinstance(cols, (tuple, slice)):
raise WindowError("cols must be a tuple or slice")
else:
cols = slice(*cols) if isinstance(cols, tuple) else cols
if not boundless and (
(cols.start is not None and cols.start < 0) or
cols.stop is None or cols.stop < 0) and width < 0:
raise WindowError("A non-negative width is required")
col_off = cols.start or 0.0
if not boundless and col_off < 0:
col_off += width
col_stop = width if cols.stop is None else cols.stop
if not boundless and col_stop < 0:
col_stop += width
num_cols = col_stop - col_off
num_cols = max(num_cols, 0.0)
return cls(col_off, row_off, num_cols, num_rows)
def round_shape(self, op='ceil', pixel_precision=3):
"""Return a copy with column and row numbers rounded
Numbers are rounded to the nearest whole number. The offsets
@classmethod
def from_ranges(cls, rows, cols):
"""For backwards compatibility only"""
warnings.warn("Use the from_slices class method instead",
RasterioDeprecationWarning)
return cls.from_slices(rows, cols)
def round_lengths(self, op='floor', pixel_precision=3):
"""Return a copy with width and height rounded.
Lengths are rounded to the nearest whole number. The offsets
are not changed.
Parameters
@ -563,8 +593,54 @@ class Window(object):
"""
operator = getattr(math, op, None)
if not operator:
raise ValueError("operator must be 'ceil' or 'floor'")
return Window(self.col_off, self.row_off,
operator(round(self.num_cols, pixel_precision)),
operator(round(self.num_rows, pixel_precision)))
raise WindowError("operator must be 'ceil' or 'floor'")
else:
return Window(self.col_off, self.row_off,
operator(round(self.width, pixel_precision)),
operator(round(self.height, pixel_precision)))
round_shape = round_lengths
def round_offsets(self, op='floor', pixel_precision=3):
"""Return a copy with column and row offsets rounded.
Offsets are rounded to the nearest whole number. The lengths
are not changed.
Parameters
----------
op: str
'ceil' or 'floor'
pixel_precision: int
Number of places of rounding precision.
Returns
-------
Window
"""
operator = getattr(math, op, None)
if not operator:
raise WindowError("operator must be 'ceil' or 'floor'")
else:
return Window(operator(round(self.col_off, pixel_precision)),
operator(round(self.row_off, pixel_precision)),
self.width, self.height)
def crop(self, height, width):
"""Return a copy cropped to height and width"""
return crop(self, height, width)
def intersection(self, other):
"""Return the intersection of this window and another
Parameters
----------
other: Window
Another window
Returns
-------
Window
"""
return intersection([self, other])

View File

@ -7,10 +7,7 @@ import pytest
import rasterio
from rasterio.enums import MaskFlags
from rasterio.errors import NodataShadowWarning
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
from rasterio.errors import NodataShadowWarning, RasterioDeprecationWarning
@pytest.fixture(scope='function')
@ -46,8 +43,7 @@ def tiffs(tmpdir):
def test_mask_flags_deprecation():
"""mask_flags is deprecated"""
warnings.simplefilter('always')
with pytest.warns(DeprecationWarning):
with pytest.warns(RasterioDeprecationWarning):
with rasterio.open('tests/data/RGB.byte.tif') as src:
src.mask_flags

View File

@ -14,21 +14,8 @@ import rasterio
from rasterio import windows
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
class WindowTest(unittest.TestCase):
def test_window_shape_errors(self):
# Positive height and width are needed when stop is None.
self.assertRaises(
ValueError,
rasterio.window_shape,
(((10, 20), (10, None)),))
self.assertRaises(
ValueError,
rasterio.window_shape,
(((None, 10), (10, 20)),))
def test_window_shape_None_start(self):
self.assertEqual(
rasterio.window_shape(((None, 4), (None, 102))),
@ -131,7 +118,7 @@ class WindowWriteTest(unittest.TestCase):
s.write(a, indexes=1, window=windows.Window(10, 30, 50, 50))
# subprocess.call(["open", name])
info = subprocess.check_output(["gdalinfo", "-stats", name])
self.assert_(
self.assertTrue(
"Minimum=0.000, Maximum=127.000, "
"Mean=31.750, StdDev=54.993" in info.decode('utf-8'),
info)
@ -169,7 +156,8 @@ def test_block_windows_filtered_none(path_rgb_byte_tif):
"""Get no block windows using filter"""
with rasterio.open(path_rgb_byte_tif) as src:
w, s, e, n = src.bounds
focus_window = src.window(w - 100.0, n + 100.0, w - 1.0, n + 1.0)
focus_window = src.window(w - 100.0, n + 1.0, w - 1.0, n + 100.0,
boundless=True)
filter_func = partial(windows.intersect, focus_window)
itr = ((ij, win) for ij, win in src.block_windows() if filter_func(win))
with pytest.raises(StopIteration):

View File

@ -6,9 +6,9 @@ def test_bounds():
def test_ul():
with rasterio.open('tests/data/RGB.byte.tif') as src:
assert src.ul(0, 0) == (101985.0, 2826915.0)
assert src.ul(1, 0) == (101985.0, 2826614.95821727)
assert src.ul(src.height, src.width) == (339315.0, 2611485.0)
assert src.xy(0, 0, offset='ul') == (101985.0, 2826915.0)
assert src.xy(1, 0, offset='ul') == (101985.0, 2826614.95821727)
assert src.xy(src.height, src.width, offset='ul') == (339315.0, 2611485.0)
def test_res():
with rasterio.open('tests/data/RGB.byte.tif') as src:

View File

@ -25,4 +25,4 @@ class CopyTest(unittest.TestCase):
'tests/data/RGB.byte.tif',
name)
info = subprocess.check_output(["gdalinfo", name])
self.assert_("GTiff" in info.decode('utf-8'))
self.assertTrue("GTiff" in info.decode('utf-8'))

View File

@ -5,6 +5,7 @@ import affine
import pytest
import rasterio
from rasterio.errors import RasterioDeprecationWarning
def test_open_affine_and_transform(path_rgb_byte_tif):
@ -18,14 +19,13 @@ def test_open_affine_and_transform(path_rgb_byte_tif):
with rasterio.open(
path_rgb_byte_tif,
affine=rasterio,
transform=affine.Affine.identity()) as src:
transform=affine.Affine.identity()):
pass
assert len(record) == 2
assert "The 'affine' kwarg in rasterio.open() is deprecated" in str(record[0].message)
assert "choosing 'transform'" in str(record[1].message)
def test_open_transform_gdal_geotransform(path_rgb_byte_tif):
"""Passing a GDAL geotransform to rasterio.open(transform=...) should raise
an exception.
@ -33,7 +33,7 @@ def test_open_transform_gdal_geotransform(path_rgb_byte_tif):
with pytest.raises(TypeError):
with rasterio.open(
path_rgb_byte_tif,
transform=tuple(affine.Affine.identity())) as src:
transform=tuple(affine.Affine.identity())):
pass
@ -42,12 +42,12 @@ def test_open_affine_kwarg_warning(path_rgb_byte_tif):
with pytest.warns(DeprecationWarning):
with rasterio.open(
path_rgb_byte_tif,
affine=affine.Affine.identity()) as src:
affine=affine.Affine.identity()):
pass
def test_src_affine_warning(path_rgb_byte_tif):
"""Calling src.affine should raise a warning."""
with pytest.warns(DeprecationWarning):
with pytest.warns(RasterioDeprecationWarning):
with rasterio.open(path_rgb_byte_tif) as src:
assert src.affine == src.transform

View File

@ -6,13 +6,14 @@ import numpy as np
import pytest
import rasterio
from rasterio.errors import WindowError
from rasterio import windows
DATA_WINDOW = ((3, 5), (2, 6))
def assert_window_almost_equals(a, b, precision=6):
def assert_window_almost_equals(a, b, precision=3):
for pair_outer in zip(a, b):
for x, y in zip(*pair_outer):
assert round(x, precision) == round(y, precision)
@ -163,7 +164,7 @@ def test_window_intersection():
def test_window_intersection_disjunct():
with pytest.raises(ValueError):
with pytest.raises(WindowError):
windows.intersection(
((0, 6), (3, 6)),
((100, 200), (0, 12)),
@ -208,7 +209,7 @@ def test_3x3matrix():
arrangement = product(pairs, pairs)
for wins in combinations(arrangement, 2):
assert not windows.intersect(*wins)
with pytest.raises(ValueError):
with pytest.raises(WindowError):
windows.intersection(*wins)

View File

@ -3,11 +3,13 @@ import pytest
import rasterio
from rasterio.io import WindowMethodsMixin
from rasterio.windows import Window
EPS = 1.0e-8
def assert_window_almost_equals(a, b, precision=6):
def assert_window_almost_equals(a, b, precision=3):
for pair_outer in zip(a, b):
for x, y in zip(*pair_outer):
assert round(x, precision) == round(y, precision)
@ -35,13 +37,10 @@ def test_windows_mixin():
src.window(*src.bounds),
((0, src.height), (0, src.width)))
assert src.window_bounds(
((0, src.height),
(0, src.width))) == src.bounds
assert src.window_bounds(Window(0, 0, src.width, src.height)) == src.bounds
assert src.window_transform(
((0, src.height),
(0, src.width))) == src.transform
Window(0, 0, src.width, src.height)) == src.transform
def test_windows_mixin_fail():
@ -51,26 +50,26 @@ def test_windows_mixin_fail():
pass
src = MockDataset()
with pytest.raises(AttributeError):
assert src.window(0, 0, 1, 1, boundless=True)
with pytest.raises(AttributeError):
assert src.window_bounds(((0, 1), (0, 1)))
with pytest.raises(AttributeError):
assert src.window_transform(((0, 1), (0, 1)))
with pytest.raises(TypeError):
src.window()
with pytest.raises(TypeError):
src.window_bounds()
with pytest.raises(TypeError):
src.window_transform()
def test_window_transform_method():
with rasterio.open('tests/data/RGB.byte.tif') as src:
assert src.window_transform(((0, None), (0, None))) == src.transform
assert src.window_transform(((None, None), (None, None))) == src.transform
assert src.window_transform(Window(0, 0, None, None)) == src.transform
assert src.window_transform(Window(None, None, None, None)) == src.transform
assert src.window_transform(
((1, None), (1, None))).c == src.bounds.left + src.res[0]
Window(1, 1, None, None)).c == src.bounds.left + src.res[0]
assert src.window_transform(
((1, None), (1, None))).f == src.bounds.top - src.res[1]
((1, None), (1, None))).f == src.bounds.top - src.res[1]
assert src.window_transform(
((-1, None), (-1, None))).c == src.bounds.left - src.res[0]
((-1, None), (-1, None))).c == src.bounds.left - src.res[0]
assert src.window_transform(
((-1, None), (-1, None))).f == src.bounds.top + src.res[1]
((-1, None), (-1, None))).f == src.bounds.top + src.res[1]
def test_window_method():
@ -90,14 +89,8 @@ def test_window_method():
src.window(left, top - 2 * dy - EPS, left + 2 * dx - EPS, top),
((0, 2), (0, 2)))
# bounds cropped
assert_window_almost_equals(
src.window(left - 2 * dx, top - 2 * dy, left + 2 * dx, top + 2 * dy),
((0, 2), (0, 2)))
# boundless
assert_window_almost_equals(
src.window(left - 2 * dx, top - 2 * dy, left + 2 * dx, top + 2 * dy, boundless=True),
((-2, 2), (-2, 2)))

View File

@ -3,6 +3,7 @@ import warnings
import pytest
import rasterio
from rasterio.errors import RasterioDeprecationWarning
from rasterio.profiles import Profile, DefaultGTiffProfile
from rasterio.profiles import default_gtiff_profile
@ -15,12 +16,10 @@ def test_base_profile_kwarg():
assert Profile(foo='bar')['foo'] == 'bar'
def test_gtiff_profile_format(recwarn):
def test_gtiff_profile_format():
assert DefaultGTiffProfile()['driver'] == 'GTiff'
warnings.simplefilter('always')
assert DefaultGTiffProfile()()['driver'] == 'GTiff'
assert len(recwarn) == 1
assert recwarn.pop(DeprecationWarning)
with pytest.warns(RasterioDeprecationWarning):
assert DefaultGTiffProfile()()['driver'] == 'GTiff'
def test_gtiff_profile_interleave():
@ -116,33 +115,27 @@ def test_dataset_profile_creation_kwds(data):
assert src.profile['foo'] == 'bar'
def test_profile_affine_stashing(recwarn):
def test_profile_affine_stashing():
"""Passing affine sets transform, with a warning"""
warnings.simplefilter('always')
profile = Profile(affine='foo')
assert len(recwarn) == 1
assert recwarn.pop(DeprecationWarning)
assert 'affine' not in profile
assert profile['transform'] == 'foo'
with pytest.warns(RasterioDeprecationWarning):
profile = Profile(affine='foo')
assert 'affine' not in profile
assert profile['transform'] == 'foo'
def test_profile_mixed_error(recwarn):
def test_profile_mixed_error():
"""Warn if both affine and transform are passed"""
warnings.simplefilter('always')
profile = Profile(affine='foo', transform='bar')
assert len(recwarn) == 1
assert recwarn.pop(DeprecationWarning)
assert 'affine' not in profile
assert profile['transform'] == 'bar'
with pytest.warns(RasterioDeprecationWarning):
profile = Profile(affine='foo', transform='bar')
assert 'affine' not in profile
assert profile['transform'] == 'bar'
def test_profile_affine_alias(recwarn):
def test_profile_affine_alias():
"""affine is an alias for transform, with a warning"""
profile = Profile(transform='foo')
warnings.simplefilter('always')
assert profile['affine'] == 'foo'
assert len(recwarn) == 1
assert recwarn.pop(DeprecationWarning)
with pytest.warns(RasterioDeprecationWarning):
assert profile['affine'] == 'foo'
def test_profile_affine_set():

View File

@ -36,7 +36,7 @@ class ReaderContextTest(unittest.TestCase):
self.assertEqual(s.nodatavals, (0, 0, 0))
self.assertEqual(s.indexes, (1, 2, 3))
self.assertEqual(s.crs['init'], 'epsg:32618')
self.assert_(s.crs.wkt.startswith('PROJCS'), s.crs.wkt)
self.assertTrue(s.crs.wkt.startswith('PROJCS'), s.crs.wkt)
for i, v in enumerate((101985.0, 2611485.0, 339315.0, 2826915.0)):
self.assertAlmostEqual(s.bounds[i], v)
self.assertEqual(
@ -69,10 +69,10 @@ class ReaderContextTest(unittest.TestCase):
def test_derived_spatial(self):
with rasterio.open('tests/data/RGB.byte.tif') as s:
self.assert_(s.crs.wkt.startswith('PROJCS'), s.crs.wkt)
self.assertTrue(s.crs.wkt.startswith('PROJCS'), s.crs.wkt)
for i, v in enumerate((101985.0, 2611485.0, 339315.0, 2826915.0)):
self.assertAlmostEqual(s.bounds[i], v)
for a, b in zip(s.ul(0, 0), (101985.0, 2826915.0)):
for a, b in zip(s.xy(0, 0, offset='ul'), (101985.0, 2826915.0)):
self.assertAlmostEqual(a, b)
def test_read_ubyte(self):
@ -127,7 +127,7 @@ class ReaderContextTest(unittest.TestCase):
a = s.read(masked=True) # floating point values
self.assertEqual(a.ndim, 3)
self.assertEqual(a.shape, (1, 2, 3))
self.assert_(hasattr(a, 'mask'))
self.assertTrue(hasattr(a, 'mask'))
self.assertEqual(list(set(s.nodatavals)), [None])
self.assertEqual(a.dtype, rasterio.float64)
@ -169,8 +169,6 @@ class ReaderContextTest(unittest.TestCase):
def test_read_window(self):
with rasterio.open('tests/data/RGB.byte.tif') as s:
# correct format
self.assertRaises(ValueError, s.read, window=(300, 320, 320, 330))
# window with 1 nodata on band 3
a = s.read(window=((300, 320), (320, 330)), masked=True)
self.assertEqual(a.ndim, 3)

View File

@ -8,9 +8,6 @@ import rasterio
from rasterio.windows import Window
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
@pytest.fixture(scope='session')
def rgb_array(path_rgb_byte_tif):
with rasterio.open(path_rgb_byte_tif) as src:
@ -134,17 +131,3 @@ def test_read_boundless_noshift():
r2 = src.read(boundless=True,
window=((-1, src.shape[0] + 1), (100, 101)))[0, 0, 0:9]
assert np.array_equal(r1, r2)
def test_np_warning(recwarn, rgb_byte_tif_reader):
"""Ensure no deprecation warnings
On np 1.11 and previous versions of rasterio you might see:
VisibleDeprecationWarning: using a non-integer number
instead of an integer will result in an error in the future
"""
import warnings
warnings.simplefilter('always')
with rgb_byte_tif_reader as src:
window = Window(-10, -10, 110, 110)
src.read(1, window=window, boundless=True)
assert len(recwarn) == 0

View File

@ -1,4 +1,3 @@
import logging
import json
import os
import re
@ -17,8 +16,6 @@ from rasterio.rio.main import main_group
DEFAULT_SHAPE = (10, 10)
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
# Custom markers.
xfail_pixel_sensitive_gdal22 = pytest.mark.xfail(
@ -124,13 +121,13 @@ def test_mask_out_of_bounds(runner, tmpdir, basic_feature,
output = str(tmpdir.join('test.tif'))
with pytest.warns(UserWarning) as warnings:
with pytest.warns(UserWarning) as rec:
result = runner.invoke(
main_group,
['mask', pixelated_image_file, output, '--geojson-mask', '-'],
input=json.dumps(basic_feature))
assert result.exit_code == 0
assert any(['outside bounds' in w.message.args[0] for w in warnings])
assert any(['outside bounds' in w.message.args[0] for w in rec])
assert os.path.exists(output)
with rasterio.open(output) as out:
@ -277,8 +274,7 @@ def test_mask_crop_and_invert(runner, tmpdir, basic_feature, pixelated_image,
def test_shapes(runner, pixelated_image_file):
with warnings.catch_warnings():
warnings.simplefilter("ignore")
with pytest.warns(None):
result = runner.invoke(main_group, ['shapes', pixelated_image_file])
@ -303,8 +299,7 @@ def test_shapes_sequence(runner, pixelated_image_file):
--sequence option should produce 4 features in series rather than
inside a feature collection.
"""
with warnings.catch_warnings():
warnings.simplefilter("ignore")
with pytest.warns(None):
result = runner.invoke(
main_group, ['shapes', pixelated_image_file, '--sequence'])
@ -349,8 +344,7 @@ def test_shapes_indent(runner, pixelated_image_file):
"""
--indent option should produce lots of newlines and contiguous spaces
"""
with warnings.catch_warnings():
warnings.simplefilter("ignore")
with pytest.warns(None):
result = runner.invoke(
main_group, ['shapes', pixelated_image_file, '--indent', 2])
@ -363,8 +357,7 @@ def test_shapes_indent(runner, pixelated_image_file):
def test_shapes_compact(runner, pixelated_image_file):
with warnings.catch_warnings():
warnings.simplefilter("ignore")
with pytest.warns(None):
result = runner.invoke(
main_group, ['shapes', pixelated_image_file, '--compact'])
@ -408,8 +401,7 @@ def test_shapes_mask(runner, pixelated_image, pixelated_image_file):
with rasterio.open(pixelated_image_file, 'r+') as out:
out.write(pixelated_image, indexes=1)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
with pytest.warns(None):
result = runner.invoke(
main_group, ['shapes', pixelated_image_file, '--mask'])
assert result.exit_code == 0
@ -431,8 +423,8 @@ def test_shapes_mask_sampling(runner, pixelated_image, pixelated_image_file):
with rasterio.open(pixelated_image_file, 'r+') as out:
out.write(pixelated_image, indexes=1)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
with pytest.warns(None):
result = runner.invoke(
main_group,
['shapes', pixelated_image_file, '--mask', '--sampling', 5])
@ -457,8 +449,7 @@ def test_shapes_band1_as_mask(runner, pixelated_image, pixelated_image_file):
with rasterio.open(pixelated_image_file, 'r+') as out:
out.write(pixelated_image, indexes=1)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
with pytest.warns(None):
result = runner.invoke(
main_group,
['shapes', pixelated_image_file, '--band', '--bidx', '1', '--as-mask'])

View File

@ -11,18 +11,18 @@ def test_window_transform():
assert src.window_transform(((0, None), (0, None))) == src.transform
assert src.window_transform(((None, None), (None, None))) == src.transform
assert src.window_transform(
((1, None), (1, None))).c == src.bounds.left + src.res[0]
((1, None), (1, None))).c == src.bounds.left + src.res[0]
assert src.window_transform(
((1, None), (1, None))).f == src.bounds.top - src.res[1]
((1, None), (1, None))).f == src.bounds.top - src.res[1]
assert src.window_transform(
((-1, None), (-1, None))).c == src.bounds.left - src.res[0]
((-1, None), (-1, None))).c == src.bounds.left - src.res[0]
assert src.window_transform(
((-1, None), (-1, None))).f == src.bounds.top + src.res[1]
((-1, None), (-1, None))).f == src.bounds.top + src.res[1]
def test_from_origin():
with rasterio.open('tests/data/RGB.byte.tif') as src:
w, n = src.ul(0, 0)
w, n = src.xy(0, 0, offset='ul')
xs, ys = src.res
tr = transform.from_origin(w, n, xs, ys)
assert [round(v, 7) for v in tr] == [round(v, 7) for v in src.transform]
@ -58,37 +58,6 @@ def test_window_bounds():
assert ds_x_min <= w_x_min <= w_x_max <= ds_x_max
assert ds_y_min <= w_y_min <= w_y_max <= ds_y_max
# Test a small window in each corner, both in and slightly out of bounds
p = 10
for ranges in (
# In bounds (UL, UR, LL, LR)
((0, p), (0, p)),
((0, p), (cols - p, p)),
((rows - p, p), (0, p)),
((rows - p, p), (cols - p, p)),
# Out of bounds (UL, UR, LL, LR)
((-1, p), (-1, p)),
((-1, p), (cols - p, p + 1)),
((rows - p, p + 1), (-1, p)),
((rows - p, p + 1), (cols - p, p + 1))):
# Alternate formula
window = Window.from_ranges(*ranges)
(row_min, row_max), (col_min, col_max) = ranges
win_aff = src.window_transform(window)
x_min, y_max = win_aff.c, win_aff.f
x_max = win_aff.c + (src.res[0] * (col_max - col_min))
y_min = win_aff.f - (src.res[1] * (row_max - row_min))
expected = (x_min, y_min, x_max, y_max)
actual = src.window_bounds(window)
for e, a in zip(expected, actual):
assert round(e, 7) == round(a, 7)
def test_affine_roundtrip(tmpdir):
output = str(tmpdir.join('test.tif'))
@ -174,6 +143,11 @@ def test_xy():
xy(aff, 1, 0, offset='ur')
def test_bogus_offset():
with pytest.raises(ValueError):
xy(None, 1, 0, offset='bogus')
def test_guard_transform_gdal_TypeError(path_rgb_byte_tif):
"""As part of the 1.0 migration, guard_transform() should raise a TypeError
if a GDAL geotransform is encountered"""
@ -200,7 +174,7 @@ def test_rowcol():
assert rowcol(aff, right, bottom) == (src.height, src.width)
assert rowcol(aff, left, bottom) == (src.height, 0)
assert rowcol(aff, 101985.0, 2826915.0) == (0, 0)
assert rowcol(aff, 101985.0+400.0, 2826915.0) == (0, 1)
assert rowcol(aff, 101985.0 + 400.0, 2826915.0) == (0, 1)
def test_xy_rowcol_inverse():

View File

@ -2,6 +2,7 @@ from copy import copy
import logging
import math
import sys
import warnings
from affine import Affine
from hypothesis import given
@ -10,50 +11,19 @@ import numpy as np
import pytest
import rasterio
from rasterio.errors import RasterioDeprecationWarning, WindowError
from rasterio.windows import (
crop, from_bounds, bounds, transform, evaluate, window_index, shape, Window,
intersect, intersection, get_data_window, union, round_window_to_full_blocks,
toranges)
crop, from_bounds, bounds, transform, evaluate, window_index, shape,
Window, intersect, intersection, get_data_window, union,
round_window_to_full_blocks, toranges)
EPS = 1.0e-8
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
def assert_window_almost_equals(a, b, precision=3):
for pair_outer in zip(a, b):
for x, y in zip(*pair_outer):
assert round(x, precision) == round(y, precision)
@given(col_off=floats(min_value=-1.0e+7, max_value=1.0e+7),
row_off=floats(min_value=-1.0e+7, max_value=1.0e+7),
num_cols=floats(min_value=0.0, max_value=1.0e+7),
num_rows=floats(min_value=0.0, max_value=1.0e+7))
def test_window_ctor(col_off, row_off, num_cols, num_rows):
window = Window(col_off, row_off, num_cols, num_rows)
assert window.col_off == col_off
assert window.row_off == row_off
assert window.num_cols == num_cols
assert window.num_rows == num_rows
@given(col_off=floats(min_value=-1.0e+7, max_value=1.0e+7),
row_off=floats(min_value=-1.0e+7, max_value=1.0e+7),
num_cols=floats(min_value=0.0, max_value=1.0e+7),
num_rows=floats(min_value=0.0, max_value=1.0e+7))
def test_window_round_up(col_off, row_off, num_cols, num_rows):
window = Window(col_off, row_off, num_cols, num_rows).round_shape()
assert window.col_off == col_off
assert window.row_off == row_off
assert window.num_cols == math.ceil(round(num_cols, 3))
assert window.num_rows == math.ceil(round(num_rows, 3))
def test_round_shape_invalid_op():
with pytest.raises(ValueError):
Window(0, 0, 1, 1).round_shape(op='bogus')
def assert_window_almost_equals(a, b):
assert np.allclose(a.flatten(), b.flatten(), rtol=1e-3, atol=1e-4)
@given(col_off=floats(min_value=-1.0e+7, max_value=1.0e+7),
@ -66,23 +36,8 @@ def test_crop(col_off, row_off, num_cols, num_rows, height, width):
window = crop(Window(col_off, row_off, num_cols, num_rows), height, width)
assert 0.0 <= round(window.col_off, 3) <= width
assert 0.0 <= round(window.row_off, 3) <= height
assert round(window.num_cols, 3) <= round(width - window.col_off, 3)
assert round(window.num_rows, 3) <= round(height - window.row_off, 3)
def test_crop_coerce():
with pytest.warns(DeprecationWarning):
assert crop(((-10, 10), (-10, 10)), 5, 5).num_cols == 5
def test_transform_coerce():
with pytest.warns(DeprecationWarning):
assert transform(((-10, 10), (-10, 10)), Affine.identity()).a == 1.0
def test_window_index_coerce():
with pytest.warns(DeprecationWarning):
assert window_index(((-10, 10), (-10, 10)))[0].start == -10
assert round(window.width, 3) <= round(width - window.col_off, 3)
assert round(window.height, 3) <= round(height - window.row_off, 3)
def test_window_function():
@ -95,16 +50,19 @@ def test_window_function():
assert_window_almost_equals(from_bounds(
left + EPS, bottom + EPS, right - EPS, top - EPS, src.transform,
height, width), ((0, height), (0, width)))
height, width), Window.from_slices((0, height), (0, width)))
assert_window_almost_equals(from_bounds(
left, top - 2 * dy - EPS, left + 2 * dx - EPS, top, src.transform,
height, width), ((0, 2), (0, 2)))
height, width), Window.from_slices((0, 2), (0, 2)))
# boundless
assert_window_almost_equals(from_bounds(
left - 2 * dx, top - 2 * dy, left + 2 * dx, top + 2 * dy,
src.transform, boundless=True), ((-2, 2), (-2, 2)))
assert_window_almost_equals(
from_bounds(left - 2 * dx, top - 2 * dy, left + 2 * dx,
top + 2 * dy, src.transform, height=height,
width=width),
Window.from_slices((-2, 2), (-2, 2), boundless=True, height=height,
width=width))
def test_window_float():
@ -117,97 +75,21 @@ def test_window_float():
assert_window_almost_equals(from_bounds(
left, top - 400, left + 400, top, src.transform,
height, width), ((0, 400 / src.res[1]), (0, 400 / src.res[0])))
height, width), Window.from_slices((0, 400 / src.res[1]), (0, 400 / src.res[0])))
def test_window_bounds_south_up():
identity = Affine.identity()
assert_window_almost_equals(
from_bounds(0, 10, 10, 0, identity, 10, 10),
Window(0, 0, 10, 10),
precision=5)
def test_toranges():
assert Window(0, 0, 1, 1).toranges() == ((0, 1), (0, 1))
def test_toranges_warn():
with pytest.warns(DeprecationWarning):
toranges(((0, 1), (0, 1)))
def test_window_function():
# TODO: break this test up.
with rasterio.open('tests/data/RGB.byte.tif') as src:
left, bottom, right, top = src.bounds
dx, dy = src.res
height = src.height
width = src.width
assert_window_almost_equals(from_bounds(
left + EPS, bottom + EPS, right - EPS, top - EPS, src.transform,
height, width), ((0, height), (0, width)))
assert_window_almost_equals(from_bounds(
left, top - 2 * dy - EPS, left + 2 * dx - EPS, top, src.transform,
height, width), ((0, 2), (0, 2)))
# boundless
assert_window_almost_equals(from_bounds(
left - 2 * dx, top - 2 * dy, left + 2 * dx, top + 2 * dy,
src.transform, boundless=True), ((-2, 2), (-2, 2)))
def test_window_float():
"""Test window float values"""
with rasterio.open('tests/data/RGB.byte.tif') as src:
left, bottom, right, top = src.bounds
dx, dy = src.res
height = src.height
width = src.width
assert_window_almost_equals(from_bounds(
left, top - 400, left + 400, top, src.transform,
height, width), ((0, 400 / src.res[1]), (0, 400 / src.res[0])))
def test_window_bounds_south_up():
identity = Affine.identity()
assert_window_almost_equals(
from_bounds(0, 10, 10, 0, identity, 10, 10),
Window(0, 0, 10, 10),
precision=5)
def test_toranges():
assert Window(0, 0, 1, 1).toranges() == ((0, 1), (0, 1))
@given(col_off=floats(min_value=-1.0e+7, max_value=1.0e+7),
row_off=floats(min_value=-1.0e+7, max_value=1.0e+7),
num_cols=floats(min_value=0.0, max_value=1.0e+7),
num_rows=floats(min_value=0.0, max_value=1.0e+7))
def test_array_interface(col_off, row_off, num_cols, num_rows):
arr = np.array(Window(col_off, row_off, num_cols, num_rows))
assert arr.shape == (2, 2)
Window(0, 0, 10, 10))
def test_window_bounds_north_up():
transform = Affine.translation(0.0, 10.0) * Affine.scale(1.0, -1.0) * Affine.identity()
assert_window_almost_equals(
from_bounds(0, 0, 10, 10, transform, 10, 10),
Window(0, 0, 10, 10),
precision=5)
@pytest.mark.xfail(reason="feature eliminated")
def test_window_function_valuerror():
with rasterio.open('tests/data/RGB.byte.tif') as src:
left, bottom, right, top = src.bounds
with pytest.raises(ValueError):
# No height or width
from_bounds(left + EPS, bottom + EPS, right - EPS, top - EPS,
src.transform)
Window(0, 0, 10, 10))
def test_window_transform_function():
@ -231,51 +113,17 @@ def test_window_bounds_function():
assert bounds(((0, rows), (0, cols)), src.transform) == src.bounds
bad_value_windows = [
(1, 2, 3),
((1, 0), (2,))]
bad_type_windows = [
(1, 2),
((1, 0), 2)]
@pytest.mark.parametrize("window", bad_value_windows)
def test_eval_window_bad_value(window):
with pytest.raises(ValueError):
evaluate(window, 10, 10)
@pytest.mark.parametrize("window", bad_type_windows)
def test_eval_window_bad_type(window):
with pytest.raises(TypeError):
with pytest.raises(WindowError):
evaluate(window, 10, 10)
bad_params = (
(((-1, 10), (0, 10)), -1, 10),
(((1, -1), (0, 10)), -1, 10),
(((0, 10), (-1, 10)), 10, -1),
(((0, 10), (1, -1)), 10, -1),
(((10, 5), (0, 5)), 10, 10),
(((0, 5), (10, 5)), 10, 10))
@pytest.mark.parametrize("params", bad_params)
def test_eval_window_invalid_dims(params):
with pytest.raises(ValueError):
evaluate(*params)
@pytest.mark.parametrize("params,expected", [
([((2, 4), (2, 4)), 10, 10], ((2, 4), (2, 4))),
([((-10, None), (-10, None)), 100, 90], ((90, 100), (80, 90))),
([((None, -10), (None, -10)), 100, 90], ((0, 90), (0, 80))),
([((0, 256), (0, 256)), 7791, 7621], ((0, 256), (0, 256)))])
def test_windows_evaluate(params, expected):
assert evaluate(*params) == Window.from_ranges(*expected)
def test_window_index():
idx = window_index(((0, 4), (1, 12)))
assert len(idx) == 2
@ -290,12 +138,9 @@ def test_window_index():
def test_window_shape_errors():
# Positive height and width are needed when stop is None.
with pytest.raises(ValueError):
with pytest.raises(WindowError):
assert shape(((10, 20), (10, None)))
with pytest.raises(ValueError):
assert shape(((-1, 10), (10, 20)))
def test_window_shape_None_start():
assert shape(((None, 4), (None, 102))) == (4, 102)
@ -318,57 +163,6 @@ def test_shape_negative_start():
assert shape(((None, ~0), (None, ~0)), 100, 90) == (99, 89)
def test_window_class_constructor():
"""Construct a Window from offsets, height, and width"""
window = Window(row_off=0, col_off=1, num_rows=100, num_cols=200)
assert window == Window.from_ranges((0, 100), (1, 201))
def test_window_class_constructor_positional():
"""Construct a Window using positional parameters"""
window = Window(1, 0, 200, 100)
assert window == Window.from_ranges((0, 100), (1, 201))
def test_window_class_attrs():
"""Test Window attributes"""
window = Window(row_off=0, col_off=1, num_rows=100, num_cols=200)
assert window.col_off == 1
assert window.row_off == 0
assert window.num_cols == 200
assert window.num_rows == 100
def test_window_class_repr():
"""Test Window respresentation"""
window = Window(row_off=0, col_off=1, num_rows=100, num_cols=200)
assert repr(window) == 'Window(col_off=1, row_off=0, num_cols=200, num_rows=100)'
assert eval(repr(window)) == Window.from_ranges((0, 100), (1, 201))
def test_window_class_copy():
"""Test Window copying"""
window = Window(row_off=0, col_off=1, num_rows=100, num_cols=200)
assert copy(window) == Window.from_ranges((0, 100), (1, 201))
def test_window_class_todict():
"""Test Window.todict"""
window = Window(row_off=0, col_off=1, num_rows=100, num_cols=200)
assert window.todict() == {
'col_off': 1, 'num_cols': 200, 'num_rows': 100, 'row_off': 0}
def test_window_class_toslices():
"""Test Window.toslices"""
window = Window(row_off=0, col_off=1, num_rows=100, num_cols=200)
yslice, xslice = window.toslices()
assert yslice.start == 0
assert yslice.stop == 100
assert xslice.start == 1
assert xslice.stop == 201
def test_window_class_intersects():
"""Windows intersect"""
assert intersect(Window(0, 0, 10, 10), Window(8, 8, 10, 10))
@ -384,14 +178,15 @@ def test_window_class_nonintersects():
assert not intersect(Window(0, 0, 10, 10), Window(10, 10, 10, 10))
def test_window_from_ranges():
"""from_ranges classmethod works."""
assert Window.from_ranges((0, 1), (2, 3)) == Window.from_ranges((0, 1), (2, 3))
def test_window_from_slices():
"""from_slices classmethod works."""
assert Window.from_slices((0, 1), (2, 3)) == Window.from_slices((0, 1), (2, 3))
def test_window_from_offlen():
"""from_offlen classmethod works."""
assert Window.from_offlen(2, 0, 1, 1) == Window.from_ranges((0, 1), (2, 3))
with pytest.warns(RasterioDeprecationWarning):
assert Window.from_offlen(2, 0, 1, 1) == Window.from_slices((0, 1), (2, 3))
def test_read_with_window_class():
@ -404,7 +199,7 @@ def test_read_with_window_class():
def test_data_window_invalid_arr_dims():
"""An array of more than 3 dimensions is invalid."""
arr = np.ones((3, 3, 3, 3))
with pytest.raises(ValueError):
with pytest.raises(WindowError):
get_data_window(arr)
@ -412,7 +207,7 @@ def test_data_window_full():
"""Get window of entirely valid data array."""
arr = np.ones((3, 3))
window = get_data_window(arr)
assert window == Window.from_ranges((0, 3), (0, 3))
assert window == Window.from_slices((0, 3), (0, 3))
def test_data_window_nodata():
@ -420,7 +215,7 @@ def test_data_window_nodata():
arr = np.ones((3, 3))
arr[0, :] = 0
window = get_data_window(arr, nodata=0)
assert window == Window.from_ranges((1, 3), (0, 3))
assert window == Window.from_slices((1, 3), (0, 3))
def test_data_window_novalid():
@ -428,7 +223,7 @@ def test_data_window_novalid():
arr = np.ones((3, 3))
arr[:, :] = 0
window = get_data_window(arr, nodata=0)
assert window == Window.from_ranges((0, 0), (0, 0))
assert window == Window.from_slices((0, 0), (0, 0))
def test_data_window_maskedarray():
@ -437,7 +232,7 @@ def test_data_window_maskedarray():
arr[0, :] = 0
arr = np.ma.masked_array(arr, arr == 0)
window = get_data_window(arr)
assert window == Window.from_ranges((1, 3), (0, 3))
assert window == Window.from_slices((1, 3), (0, 3))
def test_data_window_nodata_3d():
@ -445,43 +240,43 @@ def test_data_window_nodata_3d():
arr = np.ones((3, 3, 3))
arr[:, 0, :] = 0
window = get_data_window(arr, nodata=0)
assert window == Window.from_ranges((1, 3), (0, 3))
assert window == Window.from_slices((1, 3), (0, 3))
def test_window_union():
"""Window union works."""
window = union(Window(0, 0, 1, 1), Window(1, 1, 2, 2))
assert window == Window.from_ranges((0, 3), (0, 3))
assert window == Window.from_slices((0, 3), (0, 3))
def test_no_intersection():
"""Non intersecting windows raises error."""
with pytest.raises(ValueError):
with pytest.raises(WindowError):
intersection(Window(0, 0, 1, 1), Window(1, 1, 2, 2))
def test_intersection():
"""Window intersection works."""
window = intersection(Window(0, 0, 10, 10), Window(8, 8, 12, 12))
assert window == Window.from_ranges((8, 10), (8, 10))
assert window == Window.from_slices((8, 10), (8, 10))
def test_round_window_to_full_blocks():
with rasterio.open('tests/data/alpha.tif') as src, pytest.warns(DeprecationWarning):
with rasterio.open('tests/data/alpha.tif') as src:
block_shapes = src.block_shapes
test_window = ((321, 548), (432, 765))
rounded_window = round_window_to_full_blocks(test_window, block_shapes)
block_shape = block_shapes[0]
height_shape = block_shape[0]
width_shape = block_shape[1]
assert rounded_window[0][0] % height_shape == 0
assert rounded_window[0][1] % height_shape == 0
assert rounded_window[1][0] % width_shape == 0
assert rounded_window[1][1] % width_shape == 0
assert rounded_window.row_off % height_shape == 0
assert rounded_window.height % height_shape == 0
assert rounded_window.col_off % width_shape == 0
assert rounded_window.width % width_shape == 0
def test_round_window_to_full_blocks_error():
with pytest.raises(ValueError):
with pytest.raises(WindowError):
round_window_to_full_blocks(
Window(0, 0, 10, 10), block_shapes=[(1, 1), (2, 2)])
@ -491,7 +286,8 @@ def test_round_window_already_at_edge():
block_shapes = src.block_shapes
test_window = ((256, 512), (512, 768))
rounded_window = round_window_to_full_blocks(test_window, block_shapes)
assert rounded_window == Window.from_ranges(*test_window)
assert rounded_window == Window.from_slices(*test_window)
def test_round_window_boundless():
with rasterio.open('tests/data/alpha.tif') as src:
@ -501,7 +297,7 @@ def test_round_window_boundless():
block_shape = block_shapes[0]
height_shape = block_shape[0]
width_shape = block_shape[1]
assert rounded_window[0][0] % height_shape == 0
assert rounded_window[0][1] % height_shape == 0
assert rounded_window[1][0] % width_shape == 0
assert rounded_window[1][1] % width_shape == 0
assert rounded_window.row_off % height_shape == 0
assert rounded_window.height % height_shape == 0
assert rounded_window.col_off % width_shape == 0
assert rounded_window.width % width_shape == 0