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

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Wed Aug 18 09:30:00 BST 2021



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


Commits:
443fe8c5 by Antonio Valentino at 2021-08-18T08:18:42+00:00
New upstream version 1.15.1
- - - - -


7 changed files:

- CHANGELOG.md
- README.rst
- RELEASING.md
- trollimage/colormap.py
- trollimage/tests/test_colormap.py
- trollimage/tests/test_image.py
- trollimage/version.py


Changes:

=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,14 @@
+## Version 1.15.1 (2021/07/20)
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 81](https://github.com/pytroll/trollimage/pull/81) - Fix Colormap not being able to merge RGB and RGBA colormaps ([1658](https://github.com/pytroll/satpy/issues/1658))
+
+In this release 1 pull request was closed.
+
+
 ## Version 1.15.0 (2021/03/12)
 
 ### Issues Closed


=====================================
README.rst
=====================================
@@ -9,12 +9,12 @@ Trollimage
    :target: https://anaconda.org/conda-forge/trollimage/
    :alt: Conda-forge
 
-.. image:: https://github.com/pytroll/trollimage/workflows/CI/badge.svg?branch=master
+.. image:: https://github.com/pytroll/trollimage/workflows/CI/badge.svg?branch=main
     :target: https://github.com/pytroll/trollimage/actions?query=workflow%3A%22CI%22
     :alt: GitHub Actions
 
-.. image:: https://coveralls.io/repos/pytroll/trollimage/badge.png?branch=master
-    :target: https://coveralls.io/r/pytroll/trollimage?branch=master
+.. image:: https://coveralls.io/repos/pytroll/trollimage/badge.png?branch=main
+    :target: https://coveralls.io/r/pytroll/trollimage?branch=main
     :alt: Coverage
 
 Imaging package for pytroll packages like


=====================================
RELEASING.md
=====================================
@@ -1,23 +1,46 @@
 # Releasing trollimage
 
-1. checkout master
+1. checkout main branch
 2. pull from repo
 3. run the unittests
 4. run `loghub` and update the `CHANGELOG.md` file:
 
-```
-loghub pytroll/trollimage -u <username> -st v0.8.0 -plg bug "Bugs fixed" -plg enhancement "Features added" -plg documentation "Documentation changes" -plg backwards-incompatibility "Backwards incompatible changes"
-```
+   ```
+   loghub pytroll/trollimage --token $LOGHUB_GITHUB_TOKEN -st v<previous version> -plg bug "Bugs fixed" -plg enhancement "Features added" -plg documentation "Documentation changes" -plg backwards-incompatibility "Backward incompatible changes" -plg refactor "Refactoring"
+   ```
 
-Don't forget to commit!
+   This uses a `LOGHUB_GITHUB_TOKEN` environment variable. This must be created
+   on GitHub and it is recommended that you add it to your `.bashrc` or
+   `.bash_profile` or equivalent.
+
+   This command will create a CHANGELOG.temp file which needs to be added
+   to the top of the CHANGLOG.md file.  The same content is also printed
+   to terminal, so that can be copy-pasted, too.  Remember to update also
+   the version number to the same given in step 5. Don't forget to commit
+   CHANGELOG.md!
+   ```
 
 5. Create a tag with the new version number, starting with a 'v', eg:
 
-```
-git tag -a v0.22.45 -m "Version 0.22.45"
-```
+   ```
+   git tag -a v0.22.45 -m "Version 0.22.45"
+   ```
+
+   For example if the previous tag was `v0.9.0` and the new release is a
+   patch release, do:
+
+   ```
+   git tag -a v0.9.1 -m "Version 0.9.1"
+   ```
 
-See [semver.org](http://semver.org/) on how to write a version number.
+   See [semver.org](http://semver.org/) on how to write a version number.
 
 6. push changes to github `git push --follow-tags`
-7. Verify travis tests passed and deployed sdist and wheel to PyPI
+7. Verify github action unittests passed.
+8. Create a "Release" on GitHub by going to
+   https://github.com/pytroll/trollimage/releases and clicking "Draft a new release".
+   On the next page enter the newly created tag in the "Tag version" field,
+   "Version X.Y.Z" in the "Release title" field, and paste the markdown from
+   the changelog (the portion under the version section header) in the
+   "Describe this release" box. Finally click "Publish release".
+9. Verify the GitHub actions for deployment succeed and the release is on PyPI.


=====================================
trollimage/colormap.py
=====================================
@@ -22,6 +22,8 @@
 
 """A simple colormap module."""
 
+import warnings
+
 import numpy as np
 from trollimage.colorspaces import rgb2hcl, hcl2rgb
 
@@ -155,6 +157,18 @@ def _digitize_array(arr, values):
 class Colormap(object):
     """The colormap object.
 
+    Args:
+        *args: Series of (value, color) tuples. These positional arguments
+            are only used if the ``values`` and ``colors`` keyword arguments
+            aren't provided.
+        values: One dimensional array-like of control points where
+            each corresponding color is applied. Must be the same number of
+            elements as colors and must be monotonically increasing.
+        colors: Two dimensional array-like of RGB or RGBA colors where each
+            color is applied to a specific control point. Must be the same
+            number of colors as control points (values). Colors should be
+            floating point numbers between 0 and 1.
+
     Initialize with tuples of (value, (colors)), like this::
 
       Colormap((-75.0, (1.0, 1.0, 0.0)),
@@ -162,7 +176,6 @@ class Colormap(object):
                (-40.0, (1, 1, 1)),
                (30.0, (0, 0, 0)))
 
-
     You can also concatenate colormaps together, try::
 
       cm = cm1 + cm2
@@ -174,11 +187,26 @@ class Colormap(object):
         if 'colors' in kwargs and 'values' in kwargs:
             values = kwargs['values']
             colors = kwargs['colors']
+        elif 'colors' in kwargs or 'values' in kwargs:
+            raise ValueError("Both 'colors' and 'values' must be provided.")
         else:
             values = [a for (a, b) in tuples]
             colors = [b for (a, b) in tuples]
         self.values = np.array(values)
-        self.colors = np.array(colors)
+        self.colors = self._validate_colors(colors)
+        if self.values.shape[0] != self.colors.shape[0]:
+            raise ValueError("'values' and 'colors' should have the same "
+                             "number of elements. Got "
+                             f"{self.values.shape[0]} and {self.colors.shape[0]}.")
+
+    def _validate_colors(self, colors):
+        colors = np.array(colors)
+        if colors.ndim != 2 or colors.shape[-1] not in (3, 4):
+            raise ValueError("Colormap 'colors' must be RGB or RGBA. Got unexpected shape: {}".format(colors.shape))
+        if not np.issubdtype(colors.dtype, np.floating):
+            warnings.warn("Colormap 'colors' should be flotaing point numbers between 0 and 1.")
+            colors = colors.astype(np.float64)
+        return colors
 
     def colorize(self, data):
         """Colorize a monochromatic array *data*, based on the current colormap."""
@@ -188,12 +216,62 @@ class Colormap(object):
         """Palettize a monochromatic array *data* based on the current colormap."""
         return palettize(data, self.colors, self.values)
 
+    def to_rgb(self):
+        """Return colormap with RGB colors.
+
+        If already RGB then the same instance is returned.
+        If an Alpha channel exists in the colormap, it is dropped.
+
+        """
+        if self.colors.shape[-1] == 3:
+            return self
+
+        values = self.values.copy()
+        colors = self.colors.copy()
+        return Colormap(
+            values=values,
+            colors=colors[:, :3]
+        )
+
+    def to_rgba(self):
+        """Return colormap with RGBA colors.
+
+        If already RGBA then the same instance is returned.
+        If not already RGBA, a completely opaque (1.0) color
+
+        """
+        if self.colors.shape[-1] == 4:
+            return self
+
+        values = self.values.copy()
+        colors = np.empty((self.colors.shape[0], 4), dtype=self.colors.dtype)
+        colors[:, :3] = self.colors
+        colors[:, 3] = 1.0
+        return Colormap(
+            values=values,
+            colors=colors
+        )
+
     def __add__(self, other):
         """Append colormap together."""
-        new = Colormap()
-        new.values = np.concatenate((self.values, other.values))
-        new.colors = np.concatenate((self.colors, other.colors))
-        return new
+        old, other = self._normalize_color_arrays(self, other)
+        values = np.concatenate((old.values, other.values))
+        if not (np.diff(values) > 0).all():
+            raise ValueError("Merged colormap 'values' are not monotonically "
+                             "increasing.")
+        colors = np.concatenate((old.colors, other.colors))
+        return Colormap(
+            values=values,
+            colors=colors,
+        )
+
+    @staticmethod
+    def _normalize_color_arrays(cmap1, cmap2):
+        num_bands1 = cmap1.colors.shape[-1]
+        num_bands2 = cmap2.colors.shape[-1]
+        if num_bands1 == num_bands2:
+            return cmap1, cmap2
+        return cmap1.to_rgba(), cmap2.to_rgba()
 
     def reverse(self):
         """Reverse the current colormap in place."""
@@ -209,9 +287,9 @@ class Colormap(object):
 
     def to_rio(self):
         """Convert the colormap to a rasterio colormap."""
-        self.colors = (((self.colors * 1.0 - self.colors.min()) /
-                        (self.colors.max() - self.colors.min())) * 255)
-        return dict(zip(self.values, tuple(map(tuple, self.colors))))
+        colors = (((self.colors * 1.0 - self.colors.min()) /
+                   (self.colors.max() - self.colors.min())) * 255)
+        return dict(zip(self.values, tuple(map(tuple, colors))))
 
 
 # matlab jet "#00007F", "blue", "#007FFF", "cyan", "#7FFF7F", "yellow",


=====================================
trollimage/tests/test_colormap.py
=====================================
@@ -25,6 +25,8 @@ import unittest
 from trollimage import colormap
 import numpy as np
 
+import pytest
+
 
 class TestColormapClass(unittest.TestCase):
     """Test case for the colormap object."""
@@ -165,8 +167,8 @@ class TestColormapClass(unittest.TestCase):
 
         cm1 = colormap.Colormap((1, (1.0, 1.0, 0.0)),
                                 (2, (0.0, 1.0, 1.0)))
-        cm2 = colormap.Colormap((3, (1, 1, 1)),
-                                (4, (0, 0, 0)))
+        cm2 = colormap.Colormap((3, (1.0, 1.0, 1.0)),
+                                (4, (0.0, 0.0, 0.0)))
 
         cm3 = cm1 + cm2
 
@@ -175,10 +177,10 @@ class TestColormapClass(unittest.TestCase):
 
     def test_colorbar(self):
         """Test colorbar."""
-        cm_ = colormap.Colormap((1, (1, 1, 0)),
-                                (2, (0, 1, 1)),
-                                (3, (1, 1, 1)),
-                                (4, (0, 0, 0)))
+        cm_ = colormap.Colormap((1, (1.0, 1.0, 0.0)),
+                                (2, (0.0, 1.0, 1.0)),
+                                (3, (1.0, 1.0, 1.0)),
+                                (4, (0.0, 0.0, 0.0)))
 
         channels = colormap.colorbar(1, 4, cm_)
         for i in range(3):
@@ -188,10 +190,10 @@ class TestColormapClass(unittest.TestCase):
 
     def test_palettebar(self):
         """Test colorbar."""
-        cm_ = colormap.Colormap((1, (1, 1, 0)),
-                                (2, (0, 1, 1)),
-                                (3, (1, 1, 1)),
-                                (4, (0, 0, 0)))
+        cm_ = colormap.Colormap((1, (1.0, 1.0, 0.0)),
+                                (2, (0.0, 1.0, 1.0)),
+                                (3, (1.0, 1.0, 1.0)),
+                                (4, (0.0, 0.0, 0.0)))
 
         channel, palette = colormap.palettebar(1, 4, cm_)
 
@@ -200,13 +202,158 @@ class TestColormapClass(unittest.TestCase):
 
     def test_to_rio(self):
         """Test conversion to rasterio colormap."""
-        cm_ = colormap.Colormap((1, (1, 1, 0)),
-                                (2, (0, 1, 1)),
-                                (3, (1, 1, 1)),
-                                (4, (0, 0, 0)))
+        cm_ = colormap.Colormap((1, (1.0, 1.0, 0.0)),
+                                (2, (0.0, 1.0, 1.0)),
+                                (3, (1.0, 1.0, 1.0)),
+                                (4, (0.0, 0.0, 0.0)))
+        orig_colors = cm_.colors.copy()
 
         d = cm_.to_rio()
         exp = {1: (255, 255, 0), 2: (0, 255, 255),
                3: (255, 255, 255), 4: (0, 0, 0)}
 
         self.assertEqual(d, exp)
+        # assert original colormap information hasn't changed
+        np.testing.assert_allclose(orig_colors, cm_.colors)
+
+
+COLORS_RGB1 = np.array([
+    [0.0, 0.0, 0.0],
+    [0.2, 0.2, 0.0],
+    [0.0, 0.2, 0.2],
+    [0.0, 0.2, 0.0],
+])
+
+COLORS_RGBA1 = np.array([
+    [0.0, 0.0, 0.0, 1.0],
+    [0.2, 0.2, 0.0, 0.5],
+    [0.0, 0.2, 0.2, 0.0],
+    [0.0, 0.2, 0.0, 1.0],
+])
+
+
+class TestColormap:
+    """Pytest tests for colormap objects."""
+
+    def test_bad_color_dims(self):
+        """Test passing colors that aren't RGB or RGBA."""
+        # Nonsense
+        with pytest.raises(ValueError, match=r".*colors.*shape.*"):
+            colormap.Colormap(
+                colors=np.arange(5 * 5, dtype=np.float64).reshape((5, 5)),
+                values=np.linspace(0, 1, 5 * 5),
+            )
+        # LA
+        with pytest.raises(ValueError, match=r".*colors.*shape.*"):
+            colormap.Colormap(
+                colors=np.arange(5 * 2, dtype=np.float64).reshape((5, 2)),
+                values=np.linspace(0, 1, 5 * 2),
+            )
+
+    def test_only_colors_only_values(self):
+        """Test passing only colors or only values keyword arguments."""
+        with pytest.raises(ValueError, match=r"Both 'colors' and 'values'.*"):
+            colormap.Colormap(
+                colors=np.arange(5 * 3, dtype=np.float64).reshape((5, 3)),
+            )
+        with pytest.raises(ValueError, match=r"Both 'colors' and 'values'.*"):
+            colormap.Colormap(
+                values=np.linspace(0, 1, 5 * 3),
+            )
+
+    def test_diff_colors_values(self):
+        """Test failure when colors and values have different number of elements."""
+        with pytest.raises(ValueError, match=r".*same number.*"):
+            colormap.Colormap(
+                colors=np.arange(5 * 3, dtype=np.float64).reshape((5, 3)),
+                values=np.linspace(0, 1, 6),
+            )
+
+    def test_nonfloat_colors(self):
+        """Pass integer colors to colormap."""
+        colormap.Colormap(
+            colors=np.arange(5 * 3, dtype=np.uint8).reshape((5, 3)),
+            values=np.linspace(0, 1, 5),
+        )
+
+    def test_merge_nonmonotonic(self):
+        """Test that merged colormaps must have monotonic values."""
+        cmap1 = colormap.Colormap(
+            colors=np.arange(5 * 3).reshape((5, 3)),
+            values=np.linspace(2, 3, 5),
+        )
+        cmap2 = colormap.Colormap(
+            colors=np.arange(5 * 3).reshape((5, 3)),
+            values=np.linspace(0, 1, 5),
+        )
+        with pytest.raises(ValueError, match=r".*monotonic.*"):
+            cmap1 + cmap2
+        # this should succeed
+        cmap2 + cmap1
+
+    @pytest.mark.parametrize(
+        'colors',
+        [
+            COLORS_RGB1,
+            COLORS_RGBA1
+        ]
+    )
+    def test_to_rgb(self, colors):
+        """Test 'to_rgb' method."""
+        cmap = colormap.Colormap(
+            values=np.linspace(0.2, 0.5, colors.shape[0]),
+            colors=colors.copy(),
+        )
+        rgb_cmap = cmap.to_rgb()
+        assert rgb_cmap.colors.shape[-1] == 3
+        if colors.shape[-1] == 3:
+            assert rgb_cmap is cmap
+        else:
+            assert rgb_cmap is not cmap
+
+    @pytest.mark.parametrize(
+        'colors',
+        [
+            COLORS_RGB1,
+            COLORS_RGBA1
+        ]
+    )
+    def test_to_rgba(self, colors):
+        """Test 'to_rgba' method."""
+        cmap = colormap.Colormap(
+            values=np.linspace(0.2, 0.5, colors.shape[0]),
+            colors=colors.copy(),
+        )
+        rgb_cmap = cmap.to_rgba()
+        assert rgb_cmap.colors.shape[-1] == 4
+        if colors.shape[-1] == 4:
+            assert rgb_cmap is cmap
+        else:
+            assert rgb_cmap is not cmap
+
+    @pytest.mark.parametrize(
+        'colors1',
+        [
+            COLORS_RGB1,
+            COLORS_RGBA1
+        ]
+    )
+    @pytest.mark.parametrize(
+        'colors2',
+        [
+            COLORS_RGB1,
+            COLORS_RGBA1
+        ]
+    )
+    def test_merge_rgb_rgba(self, colors1, colors2):
+        """Test that two colormaps with RGB or RGBA colors can be merged."""
+        cmap1 = colormap.Colormap(
+            values=np.linspace(0.2, 0.5, colors1.shape[0]),
+            colors=colors1,
+        )
+        cmap2 = colormap.Colormap(
+            values=np.linspace(0.51, 0.8, colors2.shape[0]),
+            colors=colors2,
+        )
+        new_cmap = cmap1 + cmap2
+        assert new_cmap.values.shape[0] == colors1.shape[0] + colors2.shape[0]


=====================================
trollimage/tests/test_image.py
=====================================
@@ -1206,7 +1206,7 @@ class TestXRImage:
 
         # with trollimage colormap provided
         from trollimage.colormap import Colormap
-        t_cmap = Colormap(*tuple((i, (i, i, i)) for i in range(20)))
+        t_cmap = Colormap(*tuple((i, (i / 20, i / 20, i / 20)) for i in range(20)))
         exp_cmap = {i: (int(i * 255 / 19), int(i * 255 / 19), int(i * 255 / 19), 255) for i in range(20)}
         exp_cmap.update({i: (0, 0, 0, 255) for i in range(20, 256)})
         data = xr.DataArray(da.from_array(np.arange(81).reshape(9, 9, 1), chunks=9),


=====================================
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.15.0)"
-    git_full = "8f23f7413b9baf58b0d237e62b9eef73cf409a73"
-    git_date = "2021-03-12 13:57:27 +0100"
+    git_refnames = " (HEAD -> main, tag: v1.15.1)"
+    git_full = "c160f34f1c773a927c9e944f727003cbd185dee5"
+    git_date = "2021-07-20 09:06:09 -0500"
     keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
     return keywords
 



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

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/-/commit/443fe8c5c22fb233964e0bc090bdae92dd3b5ade
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/20210818/b42e26cb/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list