[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