[Git][debian-gis-team/trollimage][upstream] New upstream version 1.18.1
Antonio Valentino (@antonio.valentino)
gitlab at salsa.debian.org
Wed Mar 2 06:37:50 GMT 2022
Antonio Valentino pushed to branch upstream at Debian GIS Project / trollimage
Commits:
eb39bd9e by Antonio Valentino at 2022-03-01T07:08:35+00:00
New upstream version 1.18.1
- - - - -
5 changed files:
- CHANGELOG.md
- 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
=====================================
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/-/commit/eb39bd9e69bed8b7c01f3002c57d5cb28a3c1645
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/-/commit/eb39bd9e69bed8b7c01f3002c57d5cb28a3c1645
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/1bcd2c79/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list