[Git][debian-gis-team/trollimage][upstream] New upstream version 1.9.0

Antonio Valentino gitlab at salsa.debian.org
Tue Jul 9 07:47:07 BST 2019



Antonio Valentino pushed to branch upstream at Debian GIS Project / trollimage


Commits:
d801eda1 by Antonio Valentino at 2019-07-09T06:32:32Z
New upstream version 1.9.0
- - - - -


4 changed files:

- CHANGELOG.md
- trollimage/tests/test_image.py
- trollimage/version.py
- trollimage/xrimage.py


Changes:

=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,39 @@
+## Version 1.9.0 (2019/06/18)
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 51](https://github.com/pytroll/trollimage/pull/51) - Fix _FillValue not being respected when converting to alpha image
+
+#### Features added
+
+* [PR 49](https://github.com/pytroll/trollimage/pull/49) - Add a new method for image stacking.
+
+In this release 2 pull requests were closed.
+
+
+## Version 1.8.0 (2019/05/10)
+
+### Issues Closed
+
+* [Issue 45](https://github.com/pytroll/trollimage/issues/45) - img.stretch gives TypeError where img.data is xarray.DataArray and img.data.data is a dask.array
+
+In this release 1 issue was closed.
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 47](https://github.com/pytroll/trollimage/pull/47) - Fix xrimage palettize and colorize delaying internal functions
+
+#### Features added
+
+* [PR 46](https://github.com/pytroll/trollimage/pull/46) - Implement blend method for XRImage class
+
+In this release 2 pull requests were closed.
+
+
 ## Version 1.7.0 (2019/02/28)
 
 ### Issues Closed


=====================================
trollimage/tests/test_image.py
=====================================
@@ -1504,12 +1504,15 @@ class TestXRImage(unittest.TestCase):
         # L -> LA (int)
         with dask.config.set(scheduler=CustomScheduler(max_computes=1)):
             img = xrimage.XRImage((dataset1 * 150).astype(np.uint8))
+            img.data.attrs['_FillValue'] = 0  # set fill value
             img = img.convert('LA')
             self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
             self.assertTrue(img.mode == 'LA')
             self.assertTrue(len(img.data.coords['bands']) == 2)
-            # make sure the alpha band is all opaque
-            np.testing.assert_allclose(img.data.sel(bands='A'), 255)
+            # make sure the alpha band is all opaque except the first pixel
+            alpha = img.data.sel(bands='A').values.ravel()
+            np.testing.assert_allclose(alpha[0], 0)
+            np.testing.assert_allclose(alpha[1:], 255)
 
         # L -> LA (float)
         with dask.config.set(scheduler=CustomScheduler(max_computes=1)):
@@ -1778,11 +1781,77 @@ class TestXRImage(unittest.TestCase):
         self.assertTupleEqual((1, 5, 15), values.shape)
         self.assertTupleEqual((2, 4), bw.colors.shape)
 
+    def test_stack(self):
+
+        import xarray as xr
+        from trollimage import xrimage
+
+        # background image
+        arr1 = np.zeros((2, 2))
+        data1 = xr.DataArray(arr1, dims=['y', 'x'])
+        bkg = xrimage.XRImage(data1)
+
+        # image to be stacked
+        arr2 = np.full((2, 2), np.nan)
+        arr2[0] = 1
+        data2 = xr.DataArray(arr2, dims=['y', 'x'])
+        img = xrimage.XRImage(data2)
+
+        # expected result
+        arr3 = arr1.copy()
+        arr3[0] = 1
+        data3 = xr.DataArray(arr3, dims=['y', 'x'])
+        res = xrimage.XRImage(data3)
+
+        # stack image over the background
+        bkg.stack(img)
+
+        # check result
+        np.testing.assert_allclose(bkg.data, res.data, rtol=1e-05)
+
     def test_merge(self):
         pass
 
     def test_blend(self):
-        pass
+        import xarray as xr
+        from trollimage import xrimage
+
+        core1 = np.arange(75).reshape(5, 5, 3) / 75.0
+        alpha1 = np.linspace(0, 1, 25).reshape(5, 5, 1)
+        arr1 = np.concatenate([core1, alpha1], 2)
+        data1 = xr.DataArray(arr1, dims=['y', 'x', 'bands'],
+                             coords={'bands': ['R', 'G', 'B', 'A']})
+        img1 = xrimage.XRImage(data1)
+
+        core2 = np.arange(75, 0, -1).reshape(5, 5, 3) / 75.0
+        alpha2 = np.linspace(1, 0, 25).reshape(5, 5, 1)
+        arr2 = np.concatenate([core2, alpha2], 2)
+        data2 = xr.DataArray(arr2, dims=['y', 'x', 'bands'],
+                             coords={'bands': ['R', 'G', 'B', 'A']})
+        img2 = xrimage.XRImage(data2)
+
+        img3 = img1.blend(img2)
+
+        np.testing.assert_allclose(
+                (alpha1 + alpha2 * (1 - alpha1)).squeeze(),
+                img3.data.sel(bands="A"))
+
+        np.testing.assert_allclose(
+            img3.data.sel(bands="R").values,
+            np.array(
+                [[1.,           0.95833635, 0.9136842,  0.8666667,  0.8180645],
+                 [0.768815,     0.72,       0.6728228,  0.62857145, 0.5885714],
+                 [0.55412847,   0.5264665,  0.50666666, 0.495612,   0.49394494],
+                 [0.5020408,    0.52,       0.5476586,  0.5846154,  0.63027024],
+                 [0.683871,     0.7445614,  0.81142855, 0.8835443,  0.96]]))
+
+        with self.assertRaises(TypeError):
+            img1.blend("Salekhard")
+
+        wrongimg = xrimage.XRImage(
+                xr.DataArray(np.zeros((0, 0)), dims=("y", "x")))
+        with self.assertRaises(ValueError):
+            img1.blend(wrongimg)
 
     def test_replace_luminance(self):
         pass


=====================================
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.7.0)"
-    git_full = "d35a7665ad475ff230e457085523e21f2cd3f454"
-    git_date = "2019-02-28 12:49:51 -0600"
+    git_refnames = " (tag: v1.9.0)"
+    git_full = "63fa32f2d40bb65ebc39c4be1fb1baf8f163db98"
+    git_date = "2019-06-18 06:13:40 -0500"
     keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
     return keywords
 


=====================================
trollimage/xrimage.py
=====================================
@@ -449,11 +449,15 @@ class XRImage(object):
 
         If ``data`` is an integer type then the alpha band will be scaled
         to use the smallest (min) value as fully transparent and the largest
-        (max) value as fully opaque. For float types the alpha band spans
-        0 to 1.
+        (max) value as fully opaque. If a `_FillValue` attribute is found for
+        integer type data then it is used to identify null values in the data.
+        Otherwise xarray's `isnull` is used.
+
+        For float types the alpha band spans 0 to 1.
 
         """
-        null_mask = alpha if alpha is not None else self._create_alpha(data)
+        fill_value = data.attrs.get('_FillValue', None)  # integer fill value
+        null_mask = alpha if alpha is not None else self._create_alpha(data, fill_value)
         # if we are using integer data, then alpha needs to be min-int to max-int
         # otherwise for floats we want 0 to 1
         if np.issubdtype(data.dtype, np.integer):
@@ -914,6 +918,16 @@ class XRImage(object):
         self.data = self.data * scale + offset
         self.data.attrs = attrs
 
+    def stack(self, img):
+        """Stack the provided image on top of the current image.
+        """
+        # TODO: Conversions between different modes with notification
+        # to the user, i.e. proper logging
+        if self.mode != img.mode:
+            raise NotImplementedError("Cannot stack images of different modes.")
+
+        self.data = self.data.where(img.data.isnull(), img.data)
+
     def merge(self, img):
         """Use the provided image as background for the current *img* image,
         that is if the current image has missing data.
@@ -937,6 +951,13 @@ class XRImage(object):
             self.channels[i].mask = np.logical_and(selfmask,
                                                    img.channels[i].mask)
 
+    @staticmethod
+    def _colorize(l_data, colormap):
+        # 'l_data' is (1, rows, cols)
+        # 'channels' will be a list of 3 (RGB) or 4 (RGBA) arrays
+        channels = colormap.colorize(l_data)
+        return np.concatenate(channels, axis=0)
+
     def colorize(self, colormap):
         """Colorize the current image using `colormap`.
 
@@ -955,14 +976,7 @@ class XRImage(object):
             alpha = None
 
         l_data = self.data.sel(bands=['L'])
-
-        def _colorize(l_data, colormap):
-            # 'l_data' is (1, rows, cols)
-            # 'channels' will be a list of 3 (RGB) or 4 (RGBA) arrays
-            channels = colormap.colorize(l_data)
-            return np.concatenate(channels, axis=0)
-
-        new_data = l_data.data.map_blocks(_colorize, colormap,
+        new_data = l_data.data.map_blocks(self._colorize, colormap,
                                           chunks=(colormap.colors.shape[1],) + l_data.data.chunks[1:],
                                           dtype=np.float64)
 
@@ -981,6 +995,12 @@ class XRImage(object):
         dims = self.data.dims
         self.data = xr.DataArray(new_data, coords=coords, attrs=attrs, dims=dims)
 
+    @staticmethod
+    def _palettize(data, colormap):
+        """Helper for dask-friendly palettize operation."""
+        # returns data and palette, only need data
+        return colormap.palettize(data)[0]
+
     def palettize(self, colormap):
         """Palettize the current image using `colormap`.
 
@@ -994,12 +1014,7 @@ class XRImage(object):
             raise ValueError("Image should be grayscale to colorize")
 
         l_data = self.data.sel(bands=['L'])
-
-        def _palettize(data):
-            # returns data and palette, only need data
-            return colormap.palettize(data)[0]
-
-        new_data = l_data.data.map_blocks(_palettize, dtype=l_data.dtype)
+        new_data = l_data.data.map_blocks(self._palettize, colormap, dtype=l_data.dtype)
         self.palette = tuple(colormap.colors)
 
         if self.mode == "L":
@@ -1011,22 +1026,61 @@ class XRImage(object):
         self.data.data = new_data
         self.data.coords['bands'] = list(mode)
 
-    def blend(self, other):
-        """Alpha blend *other* on top of the current image."""
-        raise NotImplementedError("This method has not be implemented for "
-                                  "xarray support.")
+    def blend(self, src):
+        r"""Alpha blend *src* on top of the current image.
+
+        Perform `alpha blending`_ of *src* on top of the current image.
+        Alpha blending is defined as:
+
+        .. math::
+
+           \begin{cases}
+            \mathrm{out}_A =
+             \mathrm{src}_A + \mathrm{dst}_A (1 - \mathrm{src}_A) \\
+            \mathrm{out}_{RGB} =
+             \bigl(\mathrm{src}_{RGB}\mathrm{src}_A
+                 + \mathrm{dst}_{RGB} \mathrm{dst}_A
+                   \left(1 - \mathrm{src}_A \right) \bigr)
+             \div \mathrm{out}_A \\
+            \mathrm{out}_A = 0 \Rightarrow \mathrm{out}_{RGB} = 0
+           \end{cases}
 
-        if self.mode != "RGBA" or other.mode != "RGBA":
-            raise ValueError("Images must be in RGBA")
-        src = other
-        dst = self
-        outa = src.channels[3] + dst.channels[3] * (1 - src.channels[3])
-        for i in range(3):
-            dst.channels[i] = (src.channels[i] * src.channels[3] +
-                               dst.channels[i] * dst.channels[3] *
-                               (1 - src.channels[3])) / outa
-            dst.channels[i][outa == 0] = 0
-        dst.channels[3] = outa
+        Both images must have mode ``"RGBA"``.
+
+        Args:
+            src (:class:`XRImage` with mode ``"RGBA"``)
+                Image to be blended on top of current image.
+
+        .. _alpha blending: https://en.wikipedia.org/w/index.php?title=Alpha_compositing&oldid=891033105#Alpha_blending
+
+        Returns
+            XRImage with mode "RGBA", blended as described above
+
+        """
+        # NB: docstring maths copy-pasta from enwiki
+
+        if self.mode != "RGBA":
+            raise ValueError(
+                    "Expected self.mode='RGBA', got {md!s}".format(
+                        md=self.mode))
+        elif not isinstance(src, XRImage):
+            raise TypeError("Expected XRImage, got {tp!s}".format(
+                tp=type(src)))
+        elif src.mode != "RGBA":
+            raise ValueError("Expected src.mode='RGBA', got {sm!s}".format(
+                sm=src.mode))
+
+        srca = src.data.sel(bands="A")
+        dsta = self.data.sel(bands="A")
+        outa = srca + dsta * (1-srca)
+        bi = {"bands": ["R", "G", "B"]}
+        rgb = ((src.data.loc[bi] * srca
+               + self.data.loc[bi] * dsta * (1-srca))
+               / outa).where(outa != 0, 0)
+        return self.__class__(
+                xr.concat(
+                    [rgb, outa.expand_dims("bands")],
+                    dim="bands"))
 
     def show(self):
         """Display the image on screen."""



View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/commit/d801eda139bd9577b5b0cbda31fee0e9f666f548

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/commit/d801eda139bd9577b5b0cbda31fee0e9f666f548
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/20190709/10e0b2fb/attachment-0001.html>


More information about the Pkg-grass-devel mailing list