[Git][debian-gis-team/pyresample][upstream] New upstream version 1.21.1
Antonio Valentino (@antonio.valentino)
gitlab at salsa.debian.org
Sun Sep 26 07:45:53 BST 2021
Antonio Valentino pushed to branch upstream at Debian GIS Project / pyresample
Commits:
b20d9c3d by Antonio Valentino at 2021-09-26T06:44:09+00:00
New upstream version 1.21.1
- - - - -
7 changed files:
- pyresample/ewa/_ll2cr.pyx
- pyresample/kd_tree.py
- pyresample/test/test_ewa_ll2cr.py
- pyresample/test/test_kd_tree.py
- pyresample/test/utils.py
- pyresample/version.py
- setup.cfg
Changes:
=====================================
pyresample/ewa/_ll2cr.pyx
=====================================
@@ -232,7 +232,6 @@ def ll2cr_static(numpy.ndarray[cr_dtype, ndim=2] lon_arr, numpy.ndarray[cr_dtype
cdef tuple projected_tuple = p(lon_arr, lat_arr)
cdef cr_dtype [:, ::1] rows_out = projected_tuple[1]
cdef cr_dtype [:, ::1] cols_out = projected_tuple[0]
- cdef double proj_circum = projection_circumference(p)
# indexes
cdef unsigned int row
@@ -252,9 +251,6 @@ def ll2cr_static(numpy.ndarray[cr_dtype, ndim=2] lon_arr, numpy.ndarray[cr_dtype
lon_arr[row, col] = fill_in
lat_arr[row, col] = fill_in
continue
- elif proj_circum != 0 and abs(x_tmp - origin_x) >= (0.75 * proj_circum):
- # if x is more than 75% around the projection space, it is probably crossing the anti-meridian
- x_tmp += proj_circum
x_tmp = (x_tmp - origin_x) / cell_width
y_tmp = (y_tmp - origin_y) / cell_height
=====================================
pyresample/kd_tree.py
=====================================
@@ -1,7 +1,6 @@
# pyresample, Resampling of remote sensing image data in python
#
-# Copyright (C) 2010, 2014, 2015 Esben S. Nielsen
-# Adam.Dybbroe
+# Copyright (C) 2010-2021 Pyresample developers
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Lesser General Public License as published by the Free
@@ -53,7 +52,7 @@ if sys.version >= '3':
class EmptyResult(ValueError):
- pass
+ """No valid data is produced."""
def resample_nearest(source_geo_def,
@@ -99,7 +98,6 @@ def resample_nearest(source_geo_def,
data : numpy array
Source data resampled to target geometry
"""
-
return _resample(source_geo_def, data, target_geo_def, 'nn',
radius_of_influence, neighbours=1,
epsilon=epsilon, fill_value=fill_value,
@@ -234,7 +232,6 @@ def resample_custom(source_geo_def, data, target_geo_def,
Weighted standard devaition for all pixels having more than one source value
Counts of number of source values used in weighting per pixel
"""
-
if not isinstance(weight_funcs, (list, tuple)):
if not isinstance(weight_funcs, types.FunctionType):
raise TypeError('weight_func must be function object')
@@ -254,7 +251,6 @@ def _resample(source_geo_def, data, target_geo_def, resample_type,
radius_of_influence, neighbours=8, epsilon=0, weight_funcs=None,
fill_value=0, reduce_data=True, nprocs=1, segments=None, with_uncert=False):
"""Resamples swath using kd-tree approach."""
-
valid_input_index, valid_output_index, index_array, distance_array = \
get_neighbour_info(source_geo_def,
target_geo_def,
@@ -279,7 +275,7 @@ def _resample(source_geo_def, data, target_geo_def, resample_type,
def get_neighbour_info(source_geo_def, target_geo_def, radius_of_influence,
neighbours=8, epsilon=0, reduce_data=True,
nprocs=1, segments=None):
- """Returns neighbour info.
+ """Return neighbour info.
Parameters
----------
@@ -309,7 +305,6 @@ def get_neighbour_info(source_geo_def, target_geo_def, radius_of_influence,
index_array, distance_array) : tuple of numpy arrays
Neighbour resampling info
"""
-
if source_geo_def.size < neighbours:
warnings.warn('Searching for %s neighbours in %s data points' %
(neighbours, source_geo_def.size))
@@ -396,7 +391,6 @@ def _get_valid_input_index(source_geo_def,
radius_of_influence,
nprocs=1):
"""Find indices of reduced inputput data."""
-
source_lons, source_lats = source_geo_def.get_lonlats(nprocs=nprocs)
source_lons = np.asanyarray(source_lons).ravel()
source_lats = np.asanyarray(source_lats).ravel()
@@ -408,18 +402,15 @@ def _get_valid_input_index(source_geo_def,
raise ValueError('Mismatch between lons and lats')
# Remove illegal values
- valid_input_index = ((source_lons >= -180) & (source_lons <= 180) &
- (source_lats <= 90) & (source_lats >= -90))
+ valid_input_index = ((source_lons >= -180) & (source_lons <= 180) & (source_lats <= 90) & (source_lats >= -90))
if reduce_data:
# Reduce dataset
- if (isinstance(source_geo_def, geometry.CoordinateDefinition) and
- isinstance(target_geo_def, (geometry.GridDefinition,
- geometry.AreaDefinition))) or \
- (isinstance(source_geo_def, (geometry.GridDefinition,
- geometry.AreaDefinition)) and
- isinstance(target_geo_def, (geometry.GridDefinition,
- geometry.AreaDefinition))):
+ griddish_types = (geometry.GridDefinition, geometry.AreaDefinition)
+ source_is_griddish = isinstance(source_geo_def, griddish_types)
+ target_is_griddish = isinstance(target_geo_def, griddish_types)
+ source_is_coord = isinstance(source_geo_def, geometry.CoordinateDefinition)
+ if (source_is_coord or source_is_griddish) and target_is_griddish:
# Resampling from swath to grid or from grid to grid
lonlat_boundary = target_geo_def.get_boundary_lonlats()
@@ -441,7 +432,6 @@ def _get_valid_input_index(source_geo_def,
def _get_valid_output_index(source_geo_def, target_geo_def, target_lons,
target_lats, reduce_data, radius_of_influence):
"""Find indices of reduced output data."""
-
valid_output_index = np.ones(target_lons.size, dtype=bool)
if reduce_data:
@@ -460,8 +450,7 @@ def _get_valid_output_index(source_geo_def, target_geo_def, target_lons,
valid_output_index = valid_output_index.astype(bool)
# Remove illegal values
- valid_out = ((target_lons >= -180) & (target_lons <= 180) &
- (target_lats <= 90) & (target_lats >= -90))
+ valid_out = ((target_lons >= -180) & (target_lons <= 180) & (target_lats <= 90) & (target_lats >= -90))
# Combine reduced and legal values
valid_output_index = (valid_output_index & valid_out)
@@ -561,16 +550,13 @@ def _query_resample_kdtree(resample_kdtree,
def _create_empty_info(source_geo_def, target_geo_def, neighbours):
- """Creates dummy info for empty result set."""
-
+ """Create dummy info for empty result set."""
valid_output_index = np.ones(target_geo_def.size, dtype=bool)
if neighbours > 1:
- index_array = (np.ones((target_geo_def.size, neighbours),
- dtype=np.int32) * source_geo_def.size)
+ index_array = (np.ones((target_geo_def.size, neighbours), dtype=np.int32) * source_geo_def.size)
distance_array = np.ones((target_geo_def.size, neighbours))
else:
- index_array = (np.ones(target_geo_def.size, dtype=np.int32) *
- source_geo_def.size)
+ index_array = (np.ones(target_geo_def.size, dtype=np.int32) * source_geo_def.size)
distance_array = np.ones(target_geo_def.size)
return valid_output_index, index_array, distance_array
@@ -617,7 +603,6 @@ def get_sample_from_neighbour_info(resample_type, output_shape, data,
result : numpy array
Source data resampled to target geometry
"""
-
if data.ndim > 2 and data.shape[0] * data.shape[1] == valid_input_index.size:
data = data.reshape(data.shape[0] * data.shape[1], data.shape[2])
elif data.shape[0] != valid_input_index.size:
@@ -879,6 +864,7 @@ def get_sample_from_neighbour_info(resample_type, output_shape, data,
def lonlat2xyz(lons, lats):
+ """Convert lon/lat degrees to geocentric x/y/z coordinates."""
R = 6370997.0
x_coords = R * np.cos(np.deg2rad(lats)) * np.cos(np.deg2rad(lons))
y_coords = R * np.cos(np.deg2rad(lats)) * np.sin(np.deg2rad(lons))
@@ -934,7 +920,7 @@ def query_no_distance(target_lons, target_lats, valid_output_index,
def _my_index(index_arr, vii, data_arr, vii_slices=None, ia_slices=None,
fill_value=np.nan):
- """Helper function for 'get_sample_from_neighbour_info'."""
+ """Wrap index logic for 'get_sample_from_neighbour_info' to be used inside dask map_blocks."""
vii_slices = tuple(
x if x is not None else vii.ravel() for x in vii_slices)
mask_slices = tuple(
@@ -947,13 +933,15 @@ def _my_index(index_arr, vii, data_arr, vii_slices=None, ia_slices=None,
class XArrayResamplerNN(object):
+ """Resampler for Xarray DataArray objects with the nearest neighbor algorithm."""
+
def __init__(self,
source_geo_def,
target_geo_def,
radius_of_influence=None,
neighbours=1,
epsilon=0):
- """
+ """Resampler for xarray DataArrays using a nearest neighbor algorithm.
Parameters
----------
@@ -1016,8 +1004,7 @@ class XArrayResamplerNN(object):
"""Set up kd tree on input."""
source_lons, source_lats = self.source_geo_def.get_lonlats(
chunks=chunks)
- valid_input_idx = ((source_lons >= -180) & (source_lons <= 180) &
- (source_lats <= 90) & (source_lats >= -90))
+ valid_input_idx = ((source_lons >= -180) & (source_lons <= 180) & (source_lats <= 90) & (source_lats >= -90))
input_coords = lonlat2xyz(source_lons, source_lats)
input_coords = input_coords[valid_input_idx.ravel(), :]
@@ -1070,8 +1057,7 @@ class XArrayResamplerNN(object):
# TODO: Add 'chunks' keyword argument to this method and use it
target_lons, target_lats = self.target_geo_def.get_lonlats(chunks=CHUNK_SIZE)
- valid_output_idx = ((target_lons >= -180) & (target_lons <= 180) &
- (target_lats <= 90) & (target_lats >= -90))
+ valid_output_idx = ((target_lons >= -180) & (target_lons <= 180) & (target_lats <= 90) & (target_lats >= -90))
if mask is not None:
assert (mask.shape == self.source_geo_def.shape), \
@@ -1146,9 +1132,8 @@ class XArrayResamplerNN(object):
# verify that the dims are next to each other
first_dim_idx = data.dims.index(src_geo_dims[0])
num_dims = len(src_geo_dims)
- assert (data.dims[first_dim_idx:first_dim_idx + num_dims] ==
- data_geo_dims), "Data's geolocation dimensions are not " \
- "consecutive."
+ assert (data.dims[first_dim_idx:first_dim_idx + num_dims] == data_geo_dims),\
+ "Data's geolocation dimensions are not consecutive."
# FIXME: Can't include coordinates whose dimensions depend on the geo
# dims either
@@ -1158,8 +1143,8 @@ class XArrayResamplerNN(object):
coords = {c: c_var for c, c_var in data.coords.items()
if not contain_coords(c_var, src_geo_dims + dst_geo_dims)}
try:
- # TODO: Add 'chunks' kwarg
- coord_x, coord_y = self.target_geo_def.get_proj_vectors(chunks=CHUNK_SIZE)
+ # get these as numpy arrays because xarray is going to compute them anyway
+ coord_x, coord_y = self.target_geo_def.get_proj_vectors()
coords['y'] = coord_y
coords['x'] = coord_x
except AttributeError:
@@ -1241,7 +1226,6 @@ def _get_fill_mask_value(data_dtype):
def _remask_data(data, is_to_be_masked=True):
"""Interprets half the array as mask for the other half."""
-
channels = data.shape[-1]
if is_to_be_masked:
mask = data[..., (channels // 2):]
=====================================
pyresample/test/test_ewa_ll2cr.py
=====================================
@@ -50,6 +50,17 @@ static_lcc = {
"proj4_definition": "+proj=lcc +a=6371200 +b=6371200 +lat_0=25 +lat_1=25 +lon_0=-95 +units=m +no_defs",
}
+static_geo_whole_earth = {
+ "grid_name": "test_geo",
+ "origin_x": -180.0,
+ "origin_y": 40,
+ "width": 361,
+ "height": 181,
+ "cell_width": 1.0,
+ "cell_height": 1.0,
+ "proj4_definition": "+proj=longlat +datum=WGS84 +no_defs +type=crs",
+}
+
class TestLL2CRStatic(unittest.TestCase):
def test_lcc_basic1(self):
@@ -66,9 +77,32 @@ class TestLL2CRStatic(unittest.TestCase):
w = grid_info["width"]
h = grid_info["height"]
points_in_grid = _ll2cr.ll2cr_static(lon_arr, lat_arr, fill_in, proj_str,
- cw, ch, w, h, ox, oy)
+ cw, ch, w, h, ox, oy)
self.assertEqual(points_in_grid, lon_arr.size, "all these test points should fall in this grid")
+ def test_geo_antimeridian(self):
+ """ Ensure output for anti-meridian crossing input includes all points. """
+ from pyresample.ewa import _ll2cr
+ lon_arr = create_test_longitude(160.0, 200.0, (50, 100), dtype=np.float64)
+ lat_arr = create_test_latitude(40.0, 60.0, (50, 100), dtype=np.float64)
+
+ # wrap values in lon_arr so -180 ≤ longitude (degrees east) ≤ 180
+ lon_arr[lon_arr > 180] -= 360.0
+
+ grid_info = static_geo_whole_earth.copy()
+ fill_in = np.nan
+ proj_str = grid_info['proj4_definition']
+ cw = grid_info['cell_width']
+ ch = grid_info['cell_height']
+ ox = grid_info['origin_x']
+ oy = grid_info['origin_y']
+ w = grid_info['width']
+ h = grid_info['height']
+ points_in_grid = _ll2cr.ll2cr_static(lon_arr, lat_arr, fill_in, proj_str,
+ cw, ch, w, h, ox, oy)
+ self.assertEqual(points_in_grid, lon_arr.size,
+ 'all these test points should fall in this grid')
+
def test_lcc_fail1(self):
from pyresample.ewa import _ll2cr
lon_arr = create_test_longitude(-15.0, 15.0, (50, 100), dtype=np.float64)
=====================================
pyresample/test/test_kd_tree.py
=====================================
@@ -1,3 +1,21 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2021 Pyresample developers
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+"""Test kd_tree operations."""
import os
import numpy as np
@@ -9,6 +27,7 @@ from unittest import mock
class Test(unittest.TestCase):
+ """Test nearest neighbor resampling on numpy arrays."""
@classmethod
def setUpClass(cls):
@@ -329,8 +348,7 @@ class Test(unittest.TestCase):
expected_stddev = [0.44621800779801657, 0.44363137712896705,
0.43861019464274459]
expected_counts = 4934802.0
- self.assertTrue(res.shape == stddev.shape and stddev.shape ==
- counts.shape and counts.shape == (800, 800, 3))
+ self.assertTrue(res.shape == stddev.shape and stddev.shape == counts.shape and counts.shape == (800, 800, 3))
self.assertAlmostEqual(cross_sum, expected)
for i, e_stddev in enumerate(expected_stddev):
@@ -881,13 +899,15 @@ class TestXArrayResamplerNN(unittest.TestCase):
def test_nearest_area_2d_to_area_1n(self):
"""Test 2D area definition to 2D area definition; 1 neighbor."""
from pyresample.kd_tree import XArrayResamplerNN
+ from pyresample.test.utils import assert_maximum_dask_computes
import xarray as xr
import dask.array as da
data = self.data_2d
resampler = XArrayResamplerNN(self.src_area_2d, self.area_def,
radius_of_influence=50000,
neighbours=1)
- ninfo = resampler.get_neighbour_info()
+ with assert_maximum_dask_computes(0):
+ ninfo = resampler.get_neighbour_info()
for val in ninfo[:3]:
# vii, ia, voi
self.assertIsInstance(val, da.Array)
@@ -896,7 +916,8 @@ class TestXArrayResamplerNN(unittest.TestCase):
# rename data dimensions to match the expected area dimensions
data = data.rename({'my_dim_y': 'y', 'my_dim_x': 'x'})
- res = resampler.get_sample_from_neighbour_info(data)
+ with assert_maximum_dask_computes(0):
+ res = resampler.get_sample_from_neighbour_info(data)
self.assertIsInstance(res, xr.DataArray)
self.assertIsInstance(res.data, da.Array)
res = res.values
@@ -946,8 +967,7 @@ class TestXArrayResamplerNN(unittest.TestCase):
self.assertEqual(cross_sum, expected)
def test_nearest_area_2d_to_area_1n_3d_data(self):
- """Test 2D area definition to 2D area definition; 1 neighbor, 3d
- data."""
+ """Test 2D area definition to 2D area definition; 1 neighbor, 3d data."""
from pyresample.kd_tree import XArrayResamplerNN
import xarray as xr
import dask.array as da
=====================================
pyresample/test/utils.py
=====================================
@@ -24,6 +24,7 @@ tests.
import sys
import types
import warnings
+from contextlib import contextmanager
import numpy as np
@@ -39,10 +40,9 @@ AstropyPendingDeprecationWarning = None
def treat_deprecations_as_exceptions():
- """Turn all DeprecationWarnings (which indicate deprecated uses of Python
- itself or Numpy, but not within Astropy, where we use our own deprecation
- warning class) into exceptions so that we find out about them early.
+ """Turn all DeprecationWarnings into exceptions.
+ Deprecation warnings indicate deprecated uses of Python itself or Numpy.
This completely resets the warning filters and any "already seen"
warning state.
"""
@@ -124,8 +124,9 @@ def treat_deprecations_as_exceptions():
class catch_warnings(warnings.catch_warnings):
- """A high-powered version of warnings.catch_warnings to use for testing and
- to make sure that there is no dependence on the order in which the tests
+ """A high-powered version of warnings.catch_warnings to use for testing.
+
+ Makes sure that there is no dependence on the order in which the tests
are run.
This completely blitzes any memory of any warnings that have
@@ -140,11 +141,14 @@ class catch_warnings(warnings.catch_warnings):
do.something.bad()
assert len(w) > 0
"""
+
def __init__(self, *classes):
+ """Initialize the classes of warnings to catch."""
super(catch_warnings, self).__init__(record=True)
self.classes = classes
def __enter__(self):
+ """Catch any warnings during this context."""
warning_list = super(catch_warnings, self).__enter__()
treat_deprecations_as_exceptions()
if len(self.classes) == 0:
@@ -156,10 +160,12 @@ class catch_warnings(warnings.catch_warnings):
return warning_list
def __exit__(self, type, value, traceback):
+ """Raise any warnings as errors."""
treat_deprecations_as_exceptions()
def create_test_longitude(start, stop, shape, twist_factor=0.0, dtype=np.float32):
+ """Get basic sample of longitude data."""
if start > stop:
stop += 360.0
@@ -175,6 +181,7 @@ def create_test_longitude(start, stop, shape, twist_factor=0.0, dtype=np.float32
def create_test_latitude(start, stop, shape, twist_factor=0.0, dtype=np.float32):
+ """Get basic sample of latitude data."""
num_cols = 1 if len(shape) < 2 else shape[1]
lat_col = np.linspace(start, stop, num=shape[0]).astype(dtype).reshape((shape[0], 1))
twist_array = np.arange(num_cols) * twist_factor
@@ -201,6 +208,14 @@ class CustomScheduler(object):
return dask.get(dsk, keys, **kwargs)
+ at contextmanager
+def assert_maximum_dask_computes(max_computes=1):
+ """Context manager to make sure dask computations are not executed more than ``max_computes`` times."""
+ import dask
+ with dask.config.set(scheduler=CustomScheduler(max_computes=max_computes)) as new_config:
+ yield new_config
+
+
def friendly_crs_equal(expected, actual, keys=None, use_obj=True, use_wkt=True):
"""Test if two projection definitions are equal.
=====================================
pyresample/version.py
=====================================
@@ -23,9 +23,9 @@ def get_keywords():
# setup.py/versioneer.py will grep for the variable names, so they must
# each be defined on a line of their own. _version.py will just call
# get_keywords().
- git_refnames = " (HEAD -> main, tag: v1.21.0)"
- git_full = "031260c097ed2cb28f1a27c286147fa3f2ed536b"
- git_date = "2021-08-19 08:03:26 -0500"
+ git_refnames = " (tag: v1.21.1)"
+ git_full = "034a336b92ef0a66b3529a46def3cd369280fd07"
+ git_date = "2021-09-17 09:48:30 -0500"
keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
return keywords
=====================================
setup.cfg
=====================================
@@ -5,6 +5,8 @@ doc_files = docs/Makefile docs/source/*.rst
[flake8]
max-line-length = 120
+per-file-ignores:
+ pyresample/test/*.py:D102
[versioneer]
VCS = git
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyresample/-/commit/b20d9c3dc14144118f14397772cc3dcd3fc6b4ad
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyresample/-/commit/b20d9c3dc14144118f14397772cc3dcd3fc6b4ad
You're receiving this email because of your account on salsa.debian.org.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20210926/bc792791/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list