[Git][debian-gis-team/trollimage][upstream] New upstream version 1.13.0
Antonio Valentino
gitlab at salsa.debian.org
Sat Jun 13 07:26:21 BST 2020
Antonio Valentino pushed to branch upstream at Debian GIS Project / trollimage
Commits:
6e0164b9 by Antonio Valentino at 2020-06-13T06:07:04+00:00
New upstream version 1.13.0
- - - - -
4 changed files:
- doc/conf.py
- trollimage/tests/test_image.py
- trollimage/version.py
- trollimage/xrimage.py
Changes:
=====================================
doc/conf.py
=====================================
@@ -251,4 +251,5 @@ intersphinx_mapping = {
'scipy': ('https://docs.scipy.org/doc/scipy/reference', None),
'xarray': ('https://xarray.pydata.org/en/stable', None),
'dask': ('https://dask.pydata.org/en/latest', None),
+ 'rasterio': ('https://rasterio.readthedocs.io/en/latest', None),
}
=====================================
trollimage/tests/test_image.py
=====================================
@@ -808,6 +808,10 @@ class TestXRImage(unittest.TestCase):
img = xrimage.XRImage(data)
with NamedTemporaryFile(suffix='.jpg') as tmp:
img.save(tmp.name, fill_value=0)
+ # Jpeg fails without fill value (no alpha handling)
+ with NamedTemporaryFile(suffix='.jpg') as tmp:
+ # make sure fill_value is mentioned in the error message
+ self.assertRaisesRegex(OSError, "fill_value", img.save, tmp.name)
# As PNG that support alpha channel
img = xrimage.XRImage(data)
with NamedTemporaryFile(suffix='.png') as tmp:
@@ -1022,6 +1026,17 @@ class TestXRImage(unittest.TestCase):
np.testing.assert_allclose(file_data[1], exp[:, :, 1])
np.testing.assert_allclose(file_data[2], exp[:, :, 2])
np.testing.assert_allclose(file_data[3], 255)
+ # test .tiff too
+ with NamedTemporaryFile(suffix='.tiff') as tmp:
+ img.save(tmp.name)
+ with rio.open(tmp.name) as f:
+ file_data = f.read()
+ self.assertEqual(file_data.shape, (4, 5, 5)) # alpha band added
+ exp = np.arange(75).reshape(5, 5, 3)
+ np.testing.assert_allclose(file_data[0], exp[:, :, 0])
+ np.testing.assert_allclose(file_data[1], exp[:, :, 1])
+ np.testing.assert_allclose(file_data[2], exp[:, :, 2])
+ np.testing.assert_allclose(file_data[3], 255)
data = xr.DataArray(da.from_array(np.arange(75).reshape(5, 5, 3), chunks=5),
dims=['y', 'x', 'bands'],
@@ -1212,6 +1227,30 @@ class TestXRImage(unittest.TestCase):
with rio.open(tmp.name) as f:
self.assertEqual(len(f.overviews(1)), 2)
+ # auto-levels
+ data = np.zeros(25*25*3, dtype=np.uint8).reshape(25, 25, 3)
+ data = xr.DataArray(data, dims=[
+ 'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
+ img = xrimage.XRImage(data)
+ self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
+ with NamedTemporaryFile(suffix='.tif') as tmp:
+ img.save(tmp.name, overviews=[], overviews_minsize=2)
+ with rio.open(tmp.name) as f:
+ self.assertEqual(len(f.overviews(1)), 4)
+
+ # auto-levels and resampling
+ data = np.zeros(25*25*3, dtype=np.uint8).reshape(25, 25, 3)
+ data = xr.DataArray(data, dims=[
+ 'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
+ img = xrimage.XRImage(data)
+ self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
+ with NamedTemporaryFile(suffix='.tif') as tmp:
+ img.save(tmp.name, overviews=[], overviews_minsize=2,
+ overviews_resampling='average')
+ with rio.open(tmp.name) as f:
+ # no way to check resampling method from the file
+ self.assertEqual(len(f.overviews(1)), 4)
+
@unittest.skipIf(sys.platform.startswith('win'), "'NamedTemporaryFile' not supported on Windows")
def test_save_tags(self):
"""Test saving geotiffs with tags."""
@@ -1956,21 +1995,42 @@ class TestXRImage(unittest.TestCase):
return pil_obj
img = xrimage.XRImage(data)
- pi = mock.MagicMock()
- img.pil_image = pi
- res = img.apply_pil(dummy_fun, 'RGB')
- # check that the pil image generation is delayed
- pi.assert_not_called()
- # make it happen
- res.data.data.compute()
- pi.return_value.convert.assert_called_with('RGB')
+ with mock.patch.object(xrimage, "PILImage") as pi:
+ pil_img = mock.MagicMock()
+ pi.fromarray = mock.Mock(wraps=lambda *args, **kwargs: pil_img)
+ res = img.apply_pil(dummy_fun, 'RGB')
+ # check that the pil image generation is delayed
+ pi.fromarray.assert_not_called()
+ # make it happen
+ res.data.data.compute()
+ pil_img.convert.assert_called_with('RGB')
+
+ img = xrimage.XRImage(data)
+ with mock.patch.object(xrimage, "PILImage") as pi:
+ pil_img = mock.MagicMock()
+ pi.fromarray = mock.Mock(wraps=lambda *args, **kwargs: pil_img)
+ res = img.apply_pil(dummy_fun, 'RGB',
+ fun_args=('Hey', 'Jude'),
+ fun_kwargs={'chorus': "La lala lalalala"})
+ self.assertEqual(dummy_args, [({}, ), {}])
+ res.data.data.compute()
+ self.assertEqual(dummy_args, [(OrderedDict(), 'Hey', 'Jude'), {'chorus': "La lala lalalala"}])
+
+ # Test HACK for _burn_overlay
+ dummy_args = [(OrderedDict(), ), {}]
+
+ def _burn_overlay(pil_obj, *args, **kwargs):
+ dummy_args[0] = args
+ dummy_args[1] = kwargs
+ return pil_obj
img = xrimage.XRImage(data)
- pi = mock.MagicMock()
- img.pil_image = pi
- res = img.apply_pil(dummy_fun, 'RGB',
- fun_args=('Hey', 'Jude'),
- fun_kwargs={'chorus': "La lala lalalala"})
- self.assertEqual(dummy_args, [({}, ), {}])
- res.data.data.compute()
- self.assertEqual(dummy_args, [(OrderedDict(), 'Hey', 'Jude'), {'chorus': "La lala lalalala"}])
+ with mock.patch.object(xrimage, "PILImage") as pi:
+ pil_img = mock.MagicMock()
+ pi.fromarray = mock.Mock(wraps=lambda *args, **kwargs: pil_img)
+ res = img.apply_pil(_burn_overlay, 'RGB')
+ # check that the pil image generation is delayed
+ pi.fromarray.assert_not_called()
+ # make it happen
+ res.data.data.compute()
+ pil_img.convert.assert_called_with('RGB')
=====================================
trollimage/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 -> master, tag: v1.12.0)"
- git_full = "19eb077dce183fafc5be452efd5022c1378ccb48"
- git_date = "2020-03-02 13:11:54 -0600"
+ git_refnames = " (HEAD -> master, tag: v1.13.0)"
+ git_full = "1f63856486d83faccc1aafea2bea4585a5ab1530"
+ git_date = "2020-06-03 10:04:09 -0500"
keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
return keywords
=====================================
trollimage/xrimage.py
=====================================
@@ -47,6 +47,7 @@ from trollimage.image import check_image_format
try:
import rasterio
+ from rasterio.enums import Resampling
except ImportError:
rasterio = None
@@ -74,6 +75,16 @@ class RIOFile(object):
self.rfile = None
self.lock = threading.Lock()
+ @property
+ def width(self):
+ """Width of the band images."""
+ return self.kwargs['width']
+
+ @property
+ def height(self):
+ """Height of the band images."""
+ return self.kwargs['height']
+
@property
def closed(self):
"""Check if the file is closed."""
@@ -160,10 +171,15 @@ class RIOTag:
class RIODataset:
"""A wrapper for a rasterio dataset."""
- def __init__(self, rfile, overviews=None):
+ def __init__(self, rfile, overviews=None, overviews_resampling=None,
+ overviews_minsize=256):
"""Init the rasterio dataset."""
self.rfile = rfile
self.overviews = overviews
+ if overviews_resampling is None:
+ overviews_resampling = 'nearest'
+ self.overviews_resampling = Resampling[overviews_resampling]
+ self.overviews_minsize = overviews_minsize
def __setitem__(self, key, item):
"""Put the data chunk in the image."""
@@ -190,9 +206,19 @@ class RIODataset:
def close(self):
"""Close the file."""
- if self.overviews:
- logger.debug('Building overviews %s', str(self.overviews))
- self.rfile.build_overviews(self.overviews)
+ if self.overviews is not None:
+ overviews = self.overviews
+ # it's an empty list
+ if len(overviews) == 0:
+ from rasterio.rio.overview import get_maximum_overview_level
+ width = self.rfile.width
+ height = self.rfile.height
+ max_level = get_maximum_overview_level(
+ width, height, self.overviews_minsize)
+ overviews = [2 ** j for j in range(1, max_level + 1)]
+ logger.debug('Building overviews %s with %s resampling',
+ str(overviews), self.overviews_resampling.name)
+ self.rfile.build_overviews(overviews, resampling=self.overviews_resampling)
return self.rfile.close()
@@ -263,6 +289,25 @@ def invert_scale_offset(scale, offset):
return 1 / scale, -offset / scale
+ at delayed(nout=1, pure=True)
+def delayed_pil_save(img, *args, **kwargs):
+ """Dask delayed saving of PIL Image object.
+
+ Special wrapper to handle `fill_value` try/except catch and provide a
+ more useful error message.
+
+ """
+ try:
+ img.save(*args, **kwargs)
+ except OSError as e:
+ # ex: cannot write mode LA as JPEG
+ if "A as JPEG" in str(e):
+ new_msg = ("Image mode not supported for this format. Specify "
+ "`fill_value=0` to set invalid values to black.")
+ raise OSError(new_msg) from e
+ raise
+
+
class XRImage(object):
"""Image class using an :class:`xarray.DataArray` as internal storage.
@@ -370,7 +415,7 @@ class XRImage(object):
"""
kwformat = format_kwargs.pop('format', None)
fformat = fformat or kwformat or os.path.splitext(filename)[1][1:]
- if fformat in ('tif', 'jp2') and rasterio:
+ if fformat in ('tif', 'tiff', 'jp2') and rasterio:
return self.rio_save(filename, fformat=fformat,
fill_value=fill_value, compute=compute,
keep_palette=keep_palette, cmap=cmap,
@@ -382,6 +427,7 @@ class XRImage(object):
def rio_save(self, filename, fformat=None, fill_value=None,
dtype=np.uint8, compute=True, tags=None,
keep_palette=False, cmap=None, overviews=None,
+ overviews_minsize=256, overviews_resampling=None,
include_scale_offset_tags=False,
**format_kwargs):
"""Save the image using rasterio.
@@ -405,6 +451,20 @@ class XRImage(object):
img.rio_save('myfile.tif', overviews=[2, 4, 8, 16])
+ If provided as an empty list, then levels will be
+ computed as powers of two until the last level has less
+ pixels than `overviews_minsize`.
+ Default is to not add overviews.
+ overviews_minsize (int): Minimum number of pixels for the smallest
+ overview size generated when `overviews` is auto-generated.
+ Defaults to 256.
+ overviews_resampling (str): Resampling method
+ to use when generating overviews. This must be the name of an
+ enum value from :class:`rasterio.enums.Resampling` and
+ only takes effect if the `overviews` keyword argument is
+ provided. Common values include `nearest` (default),
+ `bilinear`, `average`, and many others. See the rasterio
+ documentation for more information.
include_scale_offset_tags (bool): Whether or not (default) to
include a ``scale`` and an ``offset`` tag in the data that would
help retrieving original data values from pixel values.
@@ -417,6 +477,7 @@ class XRImage(object):
drivers = {'jpg': 'JPEG',
'png': 'PNG',
'tif': 'GTiff',
+ 'tiff': 'GTiff',
'jp2': 'JP2OpenJPEG'}
driver = drivers.get(fformat, fformat)
@@ -515,7 +576,9 @@ class XRImage(object):
continue
r_file.rfile.update_tags(**tags)
- r_dataset = RIODataset(r_file, overviews)
+ r_dataset = RIODataset(r_file, overviews,
+ overviews_resampling=overviews_resampling,
+ overviews_minsize=overviews_minsize)
to_store = (data.data, r_dataset)
if da_tags:
@@ -551,7 +614,7 @@ class XRImage(object):
format_kwargs['pnginfo'] = self._pngmeta()
img = self.pil_image(fill_value, compute=False)
- delay = img.save(filename, fformat, **format_kwargs)
+ delay = delayed_pil_save(img, filename, fformat, **format_kwargs)
if compute:
return delay.compute()
return delay
@@ -571,19 +634,15 @@ class XRImage(object):
return combine_scales_offsets(*scaling)
@delayed(nout=1, pure=True)
- def _delayed_apply_pil(self, fun, pil_args, pil_kwargs, fun_args, fun_kwargs,
+ def _delayed_apply_pil(self, fun, pil_image, fun_args, fun_kwargs,
image_metadata=None, output_mode=None):
- if pil_args is None:
- pil_args = tuple()
- if pil_kwargs is None:
- pil_kwargs = dict()
if fun_args is None:
fun_args = tuple()
if fun_kwargs is None:
fun_kwargs = dict()
if image_metadata is None:
image_metadata = dict()
- new_img = fun(self.pil_image(*pil_args, **pil_kwargs), image_metadata, *fun_args, **fun_kwargs)
+ new_img = fun(pil_image, image_metadata, *fun_args, **fun_kwargs)
if output_mode is not None:
new_img = new_img.convert(output_mode)
return np.array(new_img) / self.data.dtype.type(255.0)
@@ -599,8 +658,39 @@ class XRImage(object):
The pil_args and pil_kwargs are passed to the `pil_image` method of the XRImage instance.
"""
- new_array = self._delayed_apply_pil(fun, pil_args, pil_kwargs, fun_args, fun_kwargs,
- self.data.attrs, output_mode)
+ if pil_args is None:
+ pil_args = tuple()
+ if pil_kwargs is None:
+ pil_kwargs = dict()
+ pil_image = self.pil_image(*pil_args, compute=False, **pil_kwargs)
+
+ # HACK: aggdraw.Font objects cause segmentation fault in dask tokenize
+ # Remove this when aggdraw is either updated to allow type(font_obj)
+ # or pycoast is updated to not accept Font objects
+ # See https://github.com/pytroll/pycoast/issues/43
+ # The last positional argument to the _burn_overlay function in Satpy
+ # is the 'overlay' dict. This could include aggdraw.Font objects so we
+ # completely remove it.
+ delayed_kwargs = {}
+ if fun.__name__ == "_burn_overlay":
+ from dask.base import tokenize
+ from dask.utils import funcname
+ func = self._delayed_apply_pil
+ if fun_args is None:
+ fun_args = tuple()
+ if fun_kwargs is None:
+ fun_kwargs = dict()
+ tokenize_args = (fun, pil_image, fun_args[:-1], fun_kwargs,
+ self.data.attrs, output_mode)
+ dask_key_name = "%s-%s" % (
+ funcname(func),
+ tokenize(func.key, *tokenize_args, pure=True),
+ )
+ delayed_kwargs['dask_key_name'] = dask_key_name
+
+ new_array = self._delayed_apply_pil(fun, pil_image, fun_args, fun_kwargs,
+ self.data.attrs, output_mode,
+ **delayed_kwargs)
bands = len(output_mode)
arr = da.from_delayed(new_array, dtype=self.data.dtype,
shape=(self.data.sizes['y'], self.data.sizes['x'], bands))
View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/-/commit/6e0164b9436ebdb116d2d68b98eeca29de482b06
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/-/commit/6e0164b9436ebdb116d2d68b98eeca29de482b06
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/20200613/187f8e74/attachment-0001.html>
More information about the Pkg-grass-devel
mailing list