[Git][debian-gis-team/trollimage][master] 4 commits: New upstream version 1.18.1

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Wed Mar 2 06:37:44 GMT 2022



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


Commits:
eb39bd9e by Antonio Valentino at 2022-03-01T07:08:35+00:00
New upstream version 1.18.1
- - - - -
aa49ce35 by Antonio Valentino at 2022-03-01T07:08:42+00:00
Update upstream source from tag 'upstream/1.18.1'

Update to upstream version '1.18.1'
with Debian dir c75636731d76e66afbb352ba7c74866a534036a8
- - - - -
d3817526 by Antonio Valentino at 2022-03-01T07:09:27+00:00
New upstream release

- - - - -
06d9d1f2 by Antonio Valentino at 2022-03-01T07:11:26+00:00
Set distribution to unstable

- - - - -


6 changed files:

- CHANGELOG.md
- debian/changelog
- trollimage/colormap.py
- trollimage/tests/test_image.py
- trollimage/version.py
- trollimage/xrimage.py


Changes:

=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,14 @@
+## Version 1.18.1 (2022/02/28)
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 102](https://github.com/pytroll/trollimage/pull/102) - Fix enhancement_history not working with keep_palette=True
+
+In this release 1 pull request was closed.
+
+
 ## Version 1.18.0 (2022/02/24)
 
 ### Pull Requests Merged


=====================================
debian/changelog
=====================================
@@ -1,3 +1,9 @@
+trollimage (1.18.1-1) unstable; urgency=medium
+
+  * New upstream release.
+
+ -- Antonio Valentino <antonio.valentino at tiscali.it>  Tue, 01 Mar 2022 07:11:08 +0000
+
 trollimage (1.18.0-1) unstable; urgency=medium
 
   * New upstream release.


=====================================
trollimage/colormap.py
=====================================
@@ -349,10 +349,18 @@ class Colormap(object):
         return cmap
 
     def to_rio(self):
-        """Convert the colormap to a rasterio colormap."""
+        """Convert the colormap to a rasterio colormap.
+
+        Note that rasterio requires color tables to have round integer value
+        control points. This method assumes that the range of this Colormap
+        is already in the desired output range and to avoid issues with
+        rasterio will round the values and convert them to unsigned integers.
+        """
         colors = (((self.colors * 1.0 - self.colors.min()) /
                    (self.colors.max() - self.colors.min())) * 255)
-        return dict(zip(self.values, tuple(map(tuple, colors))))
+        # rasterio doesn't allow non-integer colormap values
+        values = np.round(self.values).astype(np.uint)
+        return dict(zip(values, tuple(map(tuple, colors))))
 
     def to_csv(
             self,


=====================================
trollimage/tests/test_image.py
=====================================
@@ -26,6 +26,7 @@ from unittest import mock
 from collections import OrderedDict
 from tempfile import NamedTemporaryFile
 
+import dask.array as da
 import numpy as np
 import xarray as xr
 import rasterio as rio
@@ -33,28 +34,12 @@ import pytest
 
 from trollimage import image, xrimage
 from trollimage.colormap import Colormap, brbg
+from .utils import assert_maximum_dask_computes
 
 
 EPSILON = 0.0001
 
 
-class CustomScheduler(object):
-    """Custom dask scheduler that raises an exception if dask is computed too many times."""
-
-    def __init__(self, max_computes=1):
-        """Set starting and maximum compute counts."""
-        self.max_computes = max_computes
-        self.total_computes = 0
-
-    def __call__(self, dsk, keys, **kwargs):
-        """Compute dask task and keep track of number of times we do so."""
-        import dask
-        self.total_computes += 1
-        if self.total_computes > self.max_computes:
-            raise RuntimeError("Too many dask computations were scheduled: {}".format(self.total_computes))
-        return dask.get(dsk, keys, **kwargs)
-
-
 class TestEmptyImage(unittest.TestCase):
     """Class for testing the mpop.imageo.image module."""
 
@@ -720,7 +705,6 @@ class TestXRImage:
 
     def test_init(self):
         """Test object initialization."""
-        from trollimage import xrimage
         data = xr.DataArray([[0, 0.5, 0.5], [0.5, 0.25, 0.25]], dims=['y', 'x'])
         img = xrimage.XRImage(data)
         assert img.mode == 'L'
@@ -756,7 +740,6 @@ class TestXRImage:
         Xarray >0.15 makes data read-only after expand_dims.
 
         """
-        from trollimage import xrimage
         data = xr.DataArray([[0, 0.5, 0.5], [0.5, 0.25, 0.25]], dims=['y', 'x'])
         img = xrimage.XRImage(data)
         assert img.mode == 'L'
@@ -766,8 +749,6 @@ class TestXRImage:
 
     def test_regression_double_format_save(self):
         """Test that double format information isn't passed to save."""
-        from trollimage import xrimage
-
         data = xr.DataArray(np.arange(75).reshape(5, 5, 3) / 74., dims=[
             'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
         with mock.patch.object(xrimage.XRImage, 'pil_save') as pil_save:
@@ -781,7 +762,6 @@ class TestXRImage:
     def test_rgb_save(self):
         """Test saving RGB/A data to simple image formats."""
         from dask.delayed import Delayed
-        from trollimage import xrimage
 
         data = xr.DataArray(np.arange(75).reshape(5, 5, 3) / 74., dims=[
             'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
@@ -812,8 +792,6 @@ class TestXRImage:
                         reason="'NamedTemporaryFile' not supported on Windows")
     def test_save_single_band_jpeg(self):
         """Test saving single band to jpeg formats."""
-        from trollimage import xrimage
-
         # Single band image
         data = np.arange(75).reshape(15, 5, 1) / 74.
         data[-1, -1, 0] = np.nan
@@ -839,8 +817,6 @@ class TestXRImage:
                         reason="'NamedTemporaryFile' not supported on Windows")
     def test_save_single_band_png(self):
         """Test saving single band images to simple image formats."""
-        from trollimage import xrimage
-
         # Single band image
         data = np.arange(75).reshape(15, 5, 1) / 74.
         data[-1, -1, 0] = np.nan
@@ -884,8 +860,6 @@ class TestXRImage:
                         reason="'NamedTemporaryFile' not supported on Windows")
     def test_save_palettes(self):
         """Test saving paletted images to simple image formats."""
-        from trollimage import xrimage
-
         # Single band image palettized
         from trollimage.colormap import brbg, Colormap
         data = xr.DataArray(np.arange(75).reshape(15, 5, 1) / 74., dims=[
@@ -909,9 +883,6 @@ class TestXRImage:
                         reason="'NamedTemporaryFile' not supported on Windows")
     def test_save_geotiff_float(self):
         """Test saving geotiffs when input data is float."""
-        import dask.array as da
-        from trollimage import xrimage
-
         # numpy array image - scale to 0 to 1 first
         data = xr.DataArray(np.arange(75).reshape(5, 5, 3) / 75.,
                             dims=['y', 'x', 'bands'],
@@ -1079,8 +1050,6 @@ class TestXRImage:
                         reason="'NamedTemporaryFile' not supported on Windows")
     def test_save_geotiff_int(self):
         """Test saving geotiffs when input data is int."""
-        import dask.array as da
-        from trollimage import xrimage
         from rasterio.control import GroundControlPoint
 
         # numpy array image
@@ -1189,38 +1158,6 @@ class TestXRImage:
             np.testing.assert_allclose(file_data[0], exp[:, :, 0])
             assert cmap == exp_cmap
 
-        # with trollimage colormap provided
-        from trollimage.colormap import Colormap
-        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),
-                            dims=['y', 'x', 'bands'],
-                            coords={'bands': ['P']})
-        img = xrimage.XRImage(data)
-        with NamedTemporaryFile(suffix='.tif') as tmp:
-            img.save(tmp.name, keep_palette=True, cmap=t_cmap)
-            with rio.open(tmp.name) as f:
-                file_data = f.read()
-                cmap = f.colormap(1)
-            assert file_data.shape == (1, 9, 9)  # no alpha band
-            exp = np.arange(81).reshape(9, 9, 1)
-            np.testing.assert_allclose(file_data[0], exp[:, :, 0])
-            assert cmap == exp_cmap
-
-        # with bad colormap provided
-        bad_cmap = [[i, [i, i, i]] for i in range(256)]
-        data = xr.DataArray(da.from_array(np.arange(81).reshape(9, 9, 1), chunks=9),
-                            dims=['y', 'x', 'bands'],
-                            coords={'bands': ['P']})
-        img = xrimage.XRImage(data)
-        with NamedTemporaryFile(suffix='.tif') as tmp:
-            with pytest.raises(ValueError):
-                img.save(tmp.name, keep_palette=True, cmap=bad_cmap)
-            with pytest.raises(ValueError):
-                img.save(tmp.name, keep_palette=True, cmap=t_cmap,
-                         dtype='uint16')
-
         # with input fill value
         data = np.arange(75).reshape(5, 5, 3)
         # second pixel is all bad
@@ -1260,6 +1197,61 @@ class TestXRImage:
             np.testing.assert_allclose(file_data[2], exp[:, :, 2])
             np.testing.assert_allclose(file_data[3], exp_alpha)
 
+    @pytest.mark.skipif(sys.platform.startswith('win'),
+                        reason="'NamedTemporaryFile' not supported on Windows")
+    @pytest.mark.parametrize(
+        "cmap",
+        [
+            Colormap(*tuple((i, (i / 20, i / 20, i / 20)) for i in range(20))),
+            Colormap(*tuple((i + 0.00001, (i / 20, i / 20, i / 20)) for i in range(20))),
+            Colormap(*tuple((i if i != 2 else 2.00000001, (i / 20, i / 20, i / 20)) for i in range(20))),
+        ]
+    )
+    def test_save_geotiff_int_with_cmap(self, cmap):
+        """Test saving integer data to geotiff with a colormap.
+
+        Rasterio specifically can't handle colormaps that are not round
+        integers. Unfortunately it only warns when it finds a value in the
+        color table that it doesn't expect. For example if an unsigned 8-bit
+        color table is being filled with a trollimage Colormap where due to
+        floating point one of the values is 15.0000001 instead of 15.0,
+        rasterio will issue a warning and then not add a color for that value.
+        This test makes sure the colormap written is the colormap read back.
+
+        """
+        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),
+                            dims=['y', 'x', 'bands'],
+                            coords={'bands': ['P']})
+        img = xrimage.XRImage(data)
+        with NamedTemporaryFile(suffix='.tif') as tmp:
+            img.save(tmp.name, keep_palette=True, cmap=cmap)
+            with rio.open(tmp.name) as f:
+                file_data = f.read()
+                cmap = f.colormap(1)
+            assert file_data.shape == (1, 9, 9)  # no alpha band
+            exp = np.arange(81).reshape(9, 9, 1)
+            np.testing.assert_allclose(file_data[0], exp[:, :, 0])
+            assert cmap == exp_cmap
+
+    @pytest.mark.skipif(sys.platform.startswith('win'),
+                        reason="'NamedTemporaryFile' not supported on Windows")
+    def test_save_geotiff_int_with_bad_cmap(self):
+        """Test saving integer data to geotiff with a bad colormap."""
+        t_cmap = Colormap(*tuple((i, (i / 20, i / 20, i / 20)) for i in range(20)))
+        bad_cmap = [[i, [i, i, i]] for i in range(256)]
+        data = xr.DataArray(da.from_array(np.arange(81).reshape(9, 9, 1), chunks=9),
+                            dims=['y', 'x', 'bands'],
+                            coords={'bands': ['P']})
+        img = xrimage.XRImage(data)
+        with NamedTemporaryFile(suffix='.tif') as tmp:
+            with pytest.raises(ValueError):
+                img.save(tmp.name, keep_palette=True, cmap=bad_cmap)
+            with pytest.raises(ValueError):
+                img.save(tmp.name, keep_palette=True, cmap=t_cmap,
+                         dtype='uint16')
+
     @pytest.mark.skipif(sys.platform.startswith('win'),
                         reason="'NamedTemporaryFile' not supported on Windows")
     def test_save_geotiff_closed_file(self):
@@ -1270,9 +1262,6 @@ class TestXRImage:
         to.
 
         """
-        import dask.array as da
-        from trollimage import xrimage
-
         # numpy array image
         data = xr.DataArray(np.arange(75).reshape(5, 5, 3), dims=[
             'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
@@ -1295,8 +1284,6 @@ class TestXRImage:
     @pytest.mark.skipif(sys.platform.startswith('win'), reason="'NamedTemporaryFile' not supported on Windows")
     def test_save_jp2_int(self):
         """Test saving jp2000 when input data is int."""
-        from trollimage import xrimage
-
         # numpy array image
         data = xr.DataArray(np.arange(75).reshape(5, 5, 3), dims=[
             'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
@@ -1316,8 +1303,6 @@ class TestXRImage:
     @pytest.mark.skipif(sys.platform.startswith('win'), reason="'NamedTemporaryFile' not supported on Windows")
     def test_save_cloud_optimized_geotiff(self):
         """Test saving cloud optimized geotiffs."""
-        from trollimage import xrimage
-
         # trigger COG driver to create 2 overview levels
         # COG driver is only available in GDAL 3.1 or later
         if rio.__gdal_version__ >= '3.1':
@@ -1335,8 +1320,6 @@ class TestXRImage:
     @pytest.mark.skipif(sys.platform.startswith('win'), reason="'NamedTemporaryFile' not supported on Windows")
     def test_save_overviews(self):
         """Test saving geotiffs with overviews."""
-        from trollimage import xrimage
-
         # numpy array image
         data = xr.DataArray(np.arange(75).reshape(5, 5, 3), dims=[
             'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
@@ -1374,8 +1357,6 @@ class TestXRImage:
     @pytest.mark.skipif(sys.platform.startswith('win'), reason="'NamedTemporaryFile' not supported on Windows")
     def test_save_tags(self):
         """Test saving geotiffs with tags."""
-        from trollimage import xrimage
-
         # numpy array image
         data = xr.DataArray(np.arange(75).reshape(5, 5, 3), dims=[
             'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
@@ -1390,8 +1371,6 @@ class TestXRImage:
 
     def test_gamma(self):
         """Test gamma correction."""
-        from trollimage import xrimage
-
         arr = np.arange(75).reshape(5, 5, 3) / 75.
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
@@ -1406,8 +1385,6 @@ class TestXRImage:
 
     def test_crude_stretch(self):
         """Check crude stretching."""
-        from trollimage import xrimage
-
         arr = np.arange(75).reshape(5, 5, 3)
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
@@ -1434,8 +1411,6 @@ class TestXRImage:
 
     def test_invert(self):
         """Check inversion of the image."""
-        from trollimage import xrimage
-
         arr = np.arange(75).reshape(5, 5, 3) / 75.
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
@@ -1459,8 +1434,6 @@ class TestXRImage:
 
     def test_linear_stretch(self):
         """Test linear stretching with cutoffs."""
-        from trollimage import xrimage
-
         arr = np.arange(75).reshape(5, 5, 3) / 74.
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
@@ -1499,8 +1472,6 @@ class TestXRImage:
 
     def test_histogram_stretch(self):
         """Test histogram stretching."""
-        from trollimage import xrimage
-
         arr = np.arange(75).reshape(5, 5, 3) / 74.
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
@@ -1550,9 +1521,6 @@ class TestXRImage:
     @pytest.mark.parametrize("base", ["e", "10", "2"])
     def test_logarithmic_stretch(self, min_stretch, max_stretch, base):
         """Test logarithmic strecthing."""
-        from trollimage import xrimage
-        from .utils import assert_maximum_dask_computes
-
         arr = np.arange(75).reshape(5, 5, 3) / 74.
         data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
                             coords={'bands': ['R', 'G', 'B']})
@@ -1669,8 +1637,6 @@ class TestXRImage:
 
     def test_convert_modes(self):
         """Test modes convertions."""
-        import dask
-        from trollimage import xrimage
         from trollimage.colormap import brbg, Colormap
 
         # RGBA colormap
@@ -1697,7 +1663,7 @@ class TestXRImage:
         assert new_img.data is not img.data
 
         # L -> LA (int)
-        with dask.config.set(scheduler=CustomScheduler(max_computes=1)):
+        with assert_maximum_dask_computes(1):
             img = xrimage.XRImage((dataset1 * 150).astype(np.uint8))
             img.data.attrs['_FillValue'] = 0  # set fill value
             img = img.convert('LA')
@@ -1710,7 +1676,7 @@ class TestXRImage:
             np.testing.assert_allclose(alpha[1:], 255)
 
         # L -> LA (float)
-        with dask.config.set(scheduler=CustomScheduler(max_computes=1)):
+        with assert_maximum_dask_computes(1):
             img = xrimage.XRImage(dataset1)
             img = img.convert('LA')
             assert img.mode == 'LA'
@@ -1719,13 +1685,13 @@ class TestXRImage:
             np.testing.assert_allclose(img.data.sel(bands='A'), 1.)
 
         # LA -> L (float)
-        with dask.config.set(scheduler=CustomScheduler(max_computes=0)):
+        with assert_maximum_dask_computes(0):
             img = img.convert('L')
             assert img.mode == 'L'
             assert len(img.data.coords['bands']) == 1
 
         # L -> RGB (float)
-        with dask.config.set(scheduler=CustomScheduler(max_computes=1)):
+        with assert_maximum_dask_computes(1):
             img = img.convert('RGB')
             assert img.mode == 'RGB'
             assert len(img.data.coords['bands']) == 3
@@ -1735,7 +1701,7 @@ class TestXRImage:
             np.testing.assert_allclose(data.sel(bands=['B']), arr1)
 
         # RGB -> RGBA (float)
-        with dask.config.set(scheduler=CustomScheduler(max_computes=1)):
+        with assert_maximum_dask_computes(1):
             img = img.convert('RGBA')
             assert img.mode == 'RGBA'
             assert len(img.data.coords['bands']) == 4
@@ -1748,7 +1714,7 @@ class TestXRImage:
             np.testing.assert_allclose(data.sel(bands='A'), 1.)
 
         # RGB -> RGBA (int)
-        with dask.config.set(scheduler=CustomScheduler(max_computes=1)):
+        with assert_maximum_dask_computes(1):
             img = xrimage.XRImage((dataset1 * 150).astype(np.uint8))
             img = img.convert('RGB')  # L -> RGB
             assert np.issubdtype(img.data.dtype, np.integer)
@@ -1764,14 +1730,14 @@ class TestXRImage:
             np.testing.assert_allclose(data.sel(bands='A'), 255)
 
         # LA -> RGBA (float)
-        with dask.config.set(scheduler=CustomScheduler(max_computes=0)):
+        with assert_maximum_dask_computes(0):
             img = xrimage.XRImage(dataset2)
             img = img.convert('RGBA')
             assert img.mode == 'RGBA'
             assert len(img.data.coords['bands']) == 4
 
         # L -> palettize -> RGBA (float)
-        with dask.config.set(scheduler=CustomScheduler(max_computes=0)):
+        with assert_maximum_dask_computes(0):
             img = xrimage.XRImage(dataset1)
             img.palettize(brbg)
             pal = img.palette
@@ -1784,7 +1750,7 @@ class TestXRImage:
         # PA -> RGB (float)
         img = xrimage.XRImage(dataset3)
         img.palette = pal
-        with dask.config.set(scheduler=CustomScheduler(max_computes=0)):
+        with assert_maximum_dask_computes(0):
             img = img.convert('RGB')
             assert np.issubdtype(img.data.dtype, np.floating)
             assert img.mode == 'RGB'
@@ -1794,7 +1760,7 @@ class TestXRImage:
             img.convert('A')
 
         # L -> palettize -> RGBA (float) with RGBA colormap
-        with dask.config.set(scheduler=CustomScheduler(max_computes=0)):
+        with assert_maximum_dask_computes(0):
             img = xrimage.XRImage(dataset1)
             img.palettize(bw)
 
@@ -2150,18 +2116,15 @@ class TestXRImagePalettize:
     """Test the XRImage palettize method."""
 
     @pytest.mark.parametrize(
-        ("new_range", "input_scale", "input_offset"),
+        ("new_range", "input_scale", "input_offset", "expected_scale", "expected_offset"),
         [
-            ((0.0, 1.0), 1.0, 0.0),
-            ((0.0, 0.5), 1.0, 0.0),
-            ((2.0, 4.0), 2.0, 2.0),
+            ((0.0, 1.0), 1.0, 0.0, 1.0, 0.0),
+            ((0.0, 0.5), 1.0, 0.0, 2.0, 0.0),
+            ((2.0, 4.0), 2.0, 2.0, 0.5, -1.0),
         ],
     )
-    def test_palettize(self, new_range, input_scale, input_offset):
+    def test_palettize(self, new_range, input_scale, input_offset, expected_scale, expected_offset):
         """Test palettize with an RGB colormap."""
-        from trollimage import xrimage
-        from trollimage.colormap import brbg
-
         arr = np.arange(75).reshape(5, 15) / 74. * input_scale + input_offset
         data = xr.DataArray(arr.copy(), dims=['y', 'x'])
         img = xrimage.XRImage(data)
@@ -2183,8 +2146,8 @@ class TestXRImagePalettize:
             expected = expected2.reshape((1, 5, 15))
         np.testing.assert_allclose(values, expected)
         assert "enhancement_history" in img.data.attrs
-        assert img.data.attrs["enhancement_history"][-1]["scale"] == 0.1
-        assert img.data.attrs["enhancement_history"][-1]["offset"] == 0.0
+        assert img.data.attrs["enhancement_history"][-1]["scale"] == expected_scale
+        assert img.data.attrs["enhancement_history"][-1]["offset"] == expected_offset
 
     def test_palettize_rgba(self):
         """Test palettize with an RGBA colormap."""
@@ -2206,6 +2169,29 @@ class TestXRImagePalettize:
         assert (1, 5, 15) == values.shape
         assert (2, 4) == bw.colors.shape
 
+    @pytest.mark.parametrize("colormap_tag", [None, "colormap"])
+    @pytest.mark.parametrize("keep_palette", [False, True])
+    def test_palettize_geotiff_tag(self, tmp_path, colormap_tag, keep_palette):
+        """Test that a palettized image can be saved to a geotiff tag."""
+        new_range = (0.0, 0.5)
+        arr = np.arange(75).reshape(5, 15) / 74.
+        data = xr.DataArray(arr.copy(), dims=['y', 'x'])
+        new_brbg = brbg.set_range(*new_range, inplace=False)
+        img = xrimage.XRImage(data)
+        img.palettize(new_brbg)
+
+        dst = str(tmp_path / "test.tif")
+        img.save(dst, colormap_tag=colormap_tag, keep_palette=keep_palette)
+        with rio.open(dst, "r") as gtiff_file:
+            metadata = gtiff_file.tags()
+            if colormap_tag is None:
+                assert "colormap" not in metadata
+            else:
+                assert "colormap" in metadata
+                loaded_brbg = Colormap.from_file(metadata["colormap"])
+                np.testing.assert_allclose(new_brbg.values, loaded_brbg.values)
+                np.testing.assert_allclose(new_brbg.colors, loaded_brbg.colors)
+
 
 class TestXRImageSaveScaleOffset(unittest.TestCase):
     """Test case for saving an image with scale and offset tags."""
@@ -2259,7 +2245,6 @@ class TestXRImageSaveScaleOffset(unittest.TestCase):
 
 
 def _get_tags_after_writing_to_geotiff(data):
-    from trollimage import xrimage
     import rasterio as rio
 
     img = xrimage.XRImage(data)


=====================================
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 -> main, tag: v1.18.0)"
-    git_full = "b5e69f5a2055f3f5f4c9e23305cbdc91171ed7ec"
-    git_date = "2022-02-24 09:00:27 -0600"
+    git_refnames = " (HEAD -> main, tag: v1.18.1)"
+    git_full = "00cdfc459c7b24e512ae47d33e97b0c50c6288a8"
+    git_date = "2022-02-28 13:49:22 -0600"
     keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
     return keywords
 


=====================================
trollimage/xrimage.py
=====================================
@@ -595,8 +595,9 @@ class XRImage(object):
         elif driver == 'JPEG' and 'A' in mode:
             raise ValueError('JPEG does not support alpha')
 
-        if colormap_tag and "colormap" in data.attrs.get('enhancement_history', [{}])[-2]:
-            tags[colormap_tag] = data.attrs['enhancement_history'][-2]['colormap'].to_csv()
+        enhancement_colormap = self._get_colormap_from_enhancement_history(data)
+        if colormap_tag and enhancement_colormap is not None:
+            tags[colormap_tag] = enhancement_colormap.to_csv()
         if scale_offset_tags:
             scale_label, offset_label = scale_offset_tags
             scale, offset = self.get_scaling_from_history(data.attrs.get('enhancement_history', []))
@@ -655,6 +656,13 @@ class XRImage(object):
         # closing the file
         return to_store
 
+    @staticmethod
+    def _get_colormap_from_enhancement_history(data_arr):
+        for enhance_dict in reversed(data_arr.attrs.get('enhancement_history', [])):
+            if "colormap" in enhance_dict:
+                return enhance_dict["colormap"]
+        return None
+
     @staticmethod
     def _split_regular_vs_lazy_tags(tags, r_file):
         """Split tags into regular vs lazy (dask) tags."""
@@ -1536,10 +1544,42 @@ class XRImage(object):
             colormap (:class:`~trollimage.colormap.Colormap`):
                 Colormap to be applied to the image.
 
-        .. note::
-
+        Notes:
             Works only on "L" or "LA" images.
 
+            Similar to other enhancement methods (colorize, stretch, etc)
+            this method adds an ``enhancement_history`` list to the metadata
+            stored in the image ``DataArray``'s metadata (``.attrs``).
+            In other methods, however, the metadata directly translates to
+            the linear operations performed in that enhancement. The palettize
+            operation converts data values to indices into a colormap.
+            This result is based on the range of values defined in the Colormap
+            (``cmap.values``). To be most useful, the enhancement history scale
+            and offset values represent the range of the colormap as if scaling
+            the data to a 0-1 range. This means that once the data is saved to
+            a format as an RGB (the palette colors are applied) the scale and
+            offset can be used to determine the original range of the data
+            based on the min/max of the data type of the format (ex. uint8).
+            For example:
+
+            .. code-block:: python
+
+                dtype_min = 0
+                dtype_max = 255
+                scale = ...  # scale from geotiff
+                offset = ...  # offset from geotiff
+                data_min = offset
+                data_max = (dtype_max - dtype_min) * scale + offset
+
+            If a geotiff is saved with ``keep_palette=True`` then the data
+            saved to the geotiff are the palette indices and will not be
+            scaled to the data type of the format. There will also be a
+            standard geotiff color table in the geotiff to identify that
+            these are indices rather than some other type of image data. This
+            means in this case the scale and offset can be used to determine
+            the original range of the data starting from a 0-1 range
+            (``dtype_min`` is 0 and ``dtype_max`` is 1 in the code above).
+
         """
         if self.mode not in ("L", "LA"):
             raise ValueError("Image should be grayscale to colorize")
@@ -1555,11 +1595,11 @@ class XRImage(object):
 
         self.data.data = new_data
         self.data.coords['bands'] = list(mode)
-        # the data values are now indexes into the palette array of colors
-        # so it can't be more than the maximum index (len - 1)
-        palette_num_values = len(self.palette) - 1
-        scale_factor = 1.0 / palette_num_values
-        offset = 0
+        # See docstring notes above for how scale/offset should be used
+        cmap_min = colormap.values[0]
+        cmap_max = colormap.values[-1]
+        scale_factor = 1.0 / (cmap_max - cmap_min)
+        offset = -cmap_min * scale_factor
 
         self.data.attrs.setdefault('enhancement_history', []).append({
             'scale': scale_factor,



View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/-/compare/c5d6c6ec79f03febc985d54413788f2169e3ccbf...06d9d1f22c6bbc20c4462a34bebd6091fcf73a19

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/-/compare/c5d6c6ec79f03febc985d54413788f2169e3ccbf...06d9d1f22c6bbc20c4462a34bebd6091fcf73a19
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/20220302/2ca5f31b/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list