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

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Thu Nov 25 07:42:36 GMT 2021



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


Commits:
8b0c0e6a by Antonio Valentino at 2021-11-25T07:31:28+00:00
New upstream version 1.16.1
- - - - -


6 changed files:

- CHANGELOG.md
- rtd_requirements.txt
- trollimage/image.py
- trollimage/tests/test_image.py
- trollimage/version.py
- trollimage/xrimage.py


Changes:

=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,15 @@
+## Version 1.16.1 (2021/11/17)
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 96](https://github.com/pytroll/trollimage/pull/96) - Fix XRImage reopening geotiff with the incorrect mode
+* [PR 95](https://github.com/pytroll/trollimage/pull/95) - Fix image format checking in special cases
+
+In this release 2 pull requests were closed.
+
+
 ## Version 1.16.0 (2021/10/12)
 
 ### Issues Closed


=====================================
rtd_requirements.txt
=====================================
@@ -1,4 +1,5 @@
 pillow
 rasterio
 xarray
-dask[array]
\ No newline at end of file
+dask[array]
+docutils<0.18


=====================================
trollimage/image.py
=====================================
@@ -1,28 +1,23 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-
-# Copyright (c) 2009-2017
-
-# Author(s):
-
-#   Martin Raspaud <martin.raspaud at smhi.se>
-#   Adam Dybbroe <adam.dybbroe at smhi.se>
-#   Esben S. Nielsen <esn at dmi.dk>
-
+#
+# Copyright (c) 2009-2021 Trollimage Developers
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
-
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
-
+#
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
+"""The main trollimage Image class.
 
-"""This module defines the image class. It overlaps largely the PIL library,
+It overlaps largely the PIL library,
 but has the advantage of using masked arrays as pixel arrays, so that data
 arrays containing invalid values may be properly handled.
 """
@@ -31,6 +26,7 @@ import logging
 import os
 import re
 from copy import deepcopy
+from functools import lru_cache
 
 import numpy as np
 from PIL import Image as Pil
@@ -42,35 +38,30 @@ except ImportError:
 
 logger = logging.getLogger(__name__)
 
-PIL_IMAGE_FORMATS = Pil.registered_extensions()
 
-
-def _pprint_pil_formats():
-    res = ''
-    row = []
-    for i in PIL_IMAGE_FORMATS:
-        if len(row) > 12:
-            res = res + ", ".join(row) + ",\n"
-            row = []
-        row.append(i)
-    return res + ", ".join(row)
+ at lru_cache(1)
+def get_pillow_image_formats():
+    """Get mapping from file extension to PIL format plugin."""
+    Pil.init()
+    return Pil.registered_extensions()
 
 
-PIL_IMAGE_FORMATS_STR = _pprint_pil_formats()
+def _pprint_pil_formats():
+    """Group format extensions into rows of 12."""
+    format_exts = list(get_pillow_image_formats().keys())
+    format_rows = [", ".join(format_exts[idx:idx + 12]) for idx in range(0, len(format_exts), 12)]
+    return ",\n".join(format_rows)
 
 
 def ensure_dir(filename):
-    """Checks if the dir of f exists, otherwise create it.
-    """
+    """Check if the dir of f exists, otherwise create it."""
     directory = os.path.dirname(filename)
     if len(directory) and not os.path.isdir(directory):
         os.makedirs(directory)
 
 
 class UnknownImageFormat(Exception):
-
-    """Exception to be raised when image format is unknown to pytroll-image"""
-    pass
+    """Exception to be raised when image format is unknown to pytroll-image."""
 
 
 def check_image_format(fformat):
@@ -80,17 +71,18 @@ def check_image_format(fformat):
     """
     fformat = fformat.lower()
     try:
-        fformat = PIL_IMAGE_FORMATS["." + fformat]
+        fformat = get_pillow_image_formats()["." + fformat]
     except KeyError:
         raise UnknownImageFormat(
-            "Unknown image format '%s'.  Supported formats for 'simple_image' writer are:\n%s" %
-            (fformat, PIL_IMAGE_FORMATS_STR))
+            "Unknown image format '%s'. Supported formats for 'simple_image' writer are:\n%s" %
+            (fformat, _pprint_pil_formats()))
     return fformat
 
 
 class Image(object):
+    """Generic masked-array based image.
 
-    """This class defines images. As such, it contains data of the different
+    This class defines images. As such, it contains data of the different
     *channels* of the image (red, green, and blue for example). The *mode*
     tells if the channels define a black and white image ("L"), an rgb image
     ("RGB"), an YCbCr image ("YCbCr"), or an indexed image ("P"), in which case
@@ -113,7 +105,7 @@ class Image(object):
 
     def __init__(self, channels=None, mode="L", color_range=None,
                  fill_value=None, palette=None, copy=True):
-
+        """Initialize basic image metadata and data storage."""
         self.channels = None
         self.mode = None
         self.width = 0
@@ -221,8 +213,7 @@ class Image(object):
             self.height = 0
 
     def _add_channel(self, chn, color_min, color_max):
-        """Adds a channel to the image object
-        """
+        """Add a channel to the image object."""
         if isinstance(chn, np.ma.core.MaskedArray):
             chn_data = chn.data
             chn_mask = chn.mask
@@ -234,9 +225,10 @@ class Image(object):
         self.channels.append(np.ma.array(scaled, mask=chn_mask))
 
     def _finalize(self, dtype=np.uint8):
-        """Finalize the image, that is put it in RGB mode, and set the channels
-        in unsigned 8bit format ([0,255] range) (if the *dtype* doesn't say
-        otherwise).
+        """Finalize the image.
+
+        That is, put it in RGB mode, and set the channels in unsigned 8bit
+        format ([0,255] range) (if the *dtype* doesn't say otherwise).
         """
         channels = []
         if self.mode == "P":
@@ -263,21 +255,18 @@ class Image(object):
         return channels, fill_value
 
     def is_empty(self):
-        """Checks for an empty image.
-        """
+        """Check for an empty image."""
         if(((self.channels == []) and (not self.shape == (0, 0))) or
            ((not self.channels == []) and (self.shape == (0, 0)))):
             raise RuntimeError("Channels-shape mismatch.")
         return self.channels == [] and self.shape == (0, 0)
 
     def show(self):
-        """Display the image on screen.
-        """
+        """Display the image on screen."""
         self.pil_image().show()
 
     def pil_image(self):
-        """Return a PIL image from the current image.
-        """
+        """Return a PIL image from the current image."""
         channels, fill_value = self._finalize()
 
         if self.is_empty():
@@ -360,7 +349,9 @@ class Image(object):
 
     def save(self, filename, compression=6, fformat=None,
              thumbnail_name=None, thumbnail_size=None):
-        """Save the image to the given *filename*. For some formats like jpg
+        """Save the image to the given *filename*.
+
+        For some formats like jpg
         and png, the work is delegated to :meth:`pil_save`, which doesn't
         support the *compression* option.
         """
@@ -407,7 +398,7 @@ class Image(object):
             img.save(thumbnail_name, fformat, **params)
 
     def _pngmeta(self):
-        """It will return GeoImage.tags as a PNG metadata object.
+        """Return GeoImage.tags as a PNG metadata object.
 
         Inspired by:
         public domain, Nick Galbreath
@@ -432,9 +423,7 @@ class Image(object):
         return meta
 
     def putalpha(self, alpha):
-        """Adds an *alpha* channel to the current image, or replaces it with
-        *alpha* if it already exists.
-        """
+        """Add an *alpha* channel to the current image, or replaces it with *alpha* if it already exists."""
         alpha = np.ma.array(alpha)
         if(not (alpha.shape[0] == 0 and
                 self.shape[0] == 0) and
@@ -448,7 +437,6 @@ class Image(object):
 
     def _rgb2ycbcr(self, mode):
         """Convert the image from RGB mode to YCbCr."""
-
         self._check_modes(("RGB", "RGBA"))
 
         (self.channels[0], self.channels[1], self.channels[2]) = \
@@ -464,9 +452,7 @@ class Image(object):
         self.mode = mode
 
     def _ycbcr2rgb(self, mode):
-        """Convert the image from YCbCr mode to RGB.
-        """
-
+        """Convert the image from YCbCr mode to RGB."""
         self._check_modes(("YCbCr", "YCbCrA"))
 
         (self.channels[0], self.channels[1], self.channels[2]) = \
@@ -482,9 +468,7 @@ class Image(object):
         self.mode = mode
 
     def _to_p(self, mode):
-        """Convert the image to P or PA mode.
-        """
-
+        """Convert the image to P or PA mode."""
         if self.mode.endswith("A"):
             chans = self.channels[:-1]
             alpha = self.channels[-1]
@@ -542,9 +526,7 @@ class Image(object):
         self.mode = mode
 
     def _from_p(self, mode):
-        """Convert the image from P or PA mode.
-        """
-
+        """Convert the image from P or PA mode."""
         self._check_modes(("P", "PA"))
 
         if self.mode.endswith("A"):
@@ -584,17 +566,14 @@ class Image(object):
         self.convert(mode)
 
     def _check_modes(self, modes):
-        """Check that the image is in on of the given *modes*, raise an
-        exception otherwise.
-        """
+        """Check that the image is in on of the given *modes*, raise an exception otherwise."""
         if not isinstance(modes, (tuple, list, set)):
             modes = [modes]
         if self.mode not in modes:
             raise ValueError("Image not in suitable mode: %s" % modes)
 
     def _l2rgb(self, mode):
-        """Convert from L (black and white) to RGB.
-        """
+        """Convert from L (black and white) to RGB."""
         self._check_modes(("L", "LA"))
         self.channels.append(self.channels[0].copy())
         self.channels.append(self.channels[0].copy())
@@ -606,8 +585,7 @@ class Image(object):
         self.mode = mode
 
     def _rgb2l(self, mode):
-        """Convert from RGB to monochrome L.
-        """
+        """Convert from RGB to monochrome L."""
         self._check_modes(("RGB", "RGBA"))
 
         kb_ = 0.114
@@ -630,8 +608,7 @@ class Image(object):
         self.mode = mode
 
     def _ycbcr2l(self, mode):
-        """Convert from YCbCr to L.
-        """
+        """Convert from YCbCr to L."""
         self._check_modes(("YCbCr", "YCbCrA"))
 
         self.channels = [self.channels[0]] + self.channels[3:]
@@ -640,8 +617,7 @@ class Image(object):
         self.mode = mode
 
     def _l2ycbcr(self, mode):
-        """Convert from L to YCbCr.
-        """
+        """Convert from L to YCbCr."""
         self._check_modes(("L", "LA"))
 
         luma = self.channels[0]
@@ -656,8 +632,9 @@ class Image(object):
         self.mode = mode
 
     def convert(self, mode):
-        """Convert the current image to the given *mode*. See :class:`Image`
-        for a list of available modes.
+        """Convert the current image to the given *mode*.
+
+        See :class:`Image` for a list of available modes.
         """
         if mode == self.mode:
             return
@@ -723,8 +700,10 @@ class Image(object):
                                  % (self.mode, mode))
 
     def clip(self, channels=True):
-        """Limit the values of the array to the default [0,1] range. *channels*
-        says which channels should be clipped."""
+        """Limit the values of the array to the default [0,1] range.
+
+        *channels* says which channels should be clipped.
+        """
         if not isinstance(channels, (tuple, list)):
             channels = [channels] * len(self.channels)
 
@@ -733,7 +712,9 @@ class Image(object):
                 self.channels[i] = np.ma.clip(self.channels[i], 0.0, 1.0)
 
     def resize(self, shape):
-        """Resize the image to the given *shape* tuple, in place. For zooming,
+        """Resize the image to the given *shape* tuple, in place.
+
+        For zooming,
         nearest neighbour method is used, while for shrinking, decimation is
         used. Therefore, *shape* must be a multiple or a divisor of the image
         shape.
@@ -786,9 +767,10 @@ class Image(object):
         self.shape = self.channels[0].shape
 
     def replace_luminance(self, luminance):
-        """Replace the Y channel of the image by the array *luminance*. If the
-        image is not in YCbCr mode, it is converted automatically to and
-        from that mode.
+        """Replace the Y channel of the image by the array *luminance*.
+
+        If the image is not in YCbCr mode, it is converted automatically to
+        and from that mode.
         """
         if self.is_empty():
             return
@@ -815,7 +797,9 @@ class Image(object):
 
     def enhance(self, inverse=False, gamma=1.0, stretch="no",
                 stretch_parameters=None, **kwargs):
-        """Image enhancement function. It applies **in this order** inversion,
+        """Image enhancement function.
+
+        It applies **in this order** inversion,
         gamma correction, and stretching to the current image, with parameters
         *inverse* (see :meth:`Image.invert`), *gamma* (see
         :meth:`Image.gamma`), and *stretch* (see :meth:`Image.stretch`).
@@ -829,14 +813,15 @@ class Image(object):
         self.gamma(gamma)
 
     def gamma(self, gamma=1.0):
-        """Apply gamma correction to the channels of the image. If *gamma* is a
+        """Apply gamma correction to the channels of the image.
+
+        If *gamma* is a
         tuple, then it should have as many elements as the channels of the
         image, and the gamma correction is applied elementwise. If *gamma* is a
         number, the same gamma correction is applied on every channel, if there
         are several channels in the image. The behaviour of :func:`gamma` is
         undefined outside the normal [0,1] range of the channels.
         """
-
         if(isinstance(gamma, (list, tuple, set)) and
            len(gamma) != len(self.channels)):
             raise ValueError("Number of channels and gamma components differ.")
@@ -872,7 +857,9 @@ class Image(object):
                                             self.channels[i])
 
     def stretch(self, stretch="crude", **kwargs):
-        """Apply stretching to the current image. The value of *stretch* sets
+        """Apply stretching to the current image.
+
+        The value of *stretch* sets
         the type of stretching applied. The values "histogram", "linear",
         "crude" (or "crude-stretch") perform respectively histogram
         equalization, contrast stretching (with 5% cutoff on both sides), and
@@ -882,7 +869,6 @@ class Image(object):
         with the values as cutoff. These values should be normalized in the
         range [0.0,1.0].
         """
-
         logger.debug("Applying stretch %s with parameters %s",
                      stretch, str(kwargs))
 
@@ -918,10 +904,12 @@ class Image(object):
             raise TypeError("Stretch parameter must be a string or a tuple.")
 
     def invert(self, invert=True):
-        """Inverts all the channels of a image according to *invert*. If invert is a tuple or a list, elementwise
-        invertion is performed, otherwise all channels are inverted if *invert* is true (default).
+        """Inverts all the channels of a image according to *invert*.
 
-        Note: 'Inverting' means that black becomes white, and vice-versa, not that the values are negated !
+        If invert is a tuple or a list, elementwise invertion is performed,
+        otherwise all channels are inverted if *invert* is true (default).
+
+        Note: 'Inverting' means that black becomes white, and vice-versa, not that the values are negated!
         """
         if(isinstance(invert, (tuple, list)) and
            len(self.channels) != len(invert)):
@@ -938,9 +926,7 @@ class Image(object):
                 self.channels[i] = 1 - chn
 
     def stretch_hist_equalize(self, ch_nb):
-        """Stretch the current image's colors by performing histogram
-        equalization on channel *ch_nb*.
-        """
+        """Stretch the current image's colors by performing histogram equalization on channel *ch_nb*."""
         logger.info("Perform a histogram equalized contrast stretch.")
 
         if(self.channels[ch_nb].size ==
@@ -966,9 +952,7 @@ class Image(object):
         self.channels[ch_nb] = res
 
     def stretch_logarithmic(self, ch_nb, factor=100.):
-        """Move data into range [1:factor] and do a normalized logarithmic
-        enhancement.
-        """
+        """Move data into range [1:factor] and do a normalized logarithmic enhancement."""
         logger.debug("Perform a logarithmic contrast stretch.")
         if ((self.channels[ch_nb].size ==
              np.ma.count_masked(self.channels[ch_nb])) or
@@ -987,8 +971,10 @@ class Image(object):
         self.channels[ch_nb] = arr
 
     def stretch_linear(self, ch_nb, cutoffs=(0.005, 0.005)):
-        """Stretch linearly the contrast of the current image on channel
-        *ch_nb*, using *cutoffs* for left and right trimming.
+        """Stretch linearly the contrast of the current image for a specific channel.
+
+        Channel *ch_nb* is the 0-based index.
+        Stretching is based on *cutoffs* fractions for left and right trimming.
         """
         logger.debug("Perform a linear contrast stretch.")
 
@@ -1019,9 +1005,10 @@ class Image(object):
             logger.warning("Unable to make a contrast stretch!")
 
     def crude_stretch(self, ch_nb, min_stretch=None, max_stretch=None):
-        """Perform simple linear stretching (without any cutoff) on the channel
-        *ch_nb* of the current image and normalize to the [0,1] range."""
+        """Perform simple linear stretching (without any cutoff) for a specific channel.
 
+        Channel *ch_nb* is the 0-based index. The image is normalized to the [0,1] range.
+        """
         if min_stretch is None:
             min_stretch = self.channels[ch_nb].min()
         if max_stretch is None:
@@ -1044,9 +1031,7 @@ class Image(object):
             logger.warning("Nothing to stretch !")
 
     def merge(self, img):
-        """Use the provided image as background for the current *img* image,
-        that is if the current image has missing data.
-        """
+        """Use provided image as a background where the current image has missing data."""
         if self.is_empty():
             raise ValueError("Cannot merge an empty image.")
 
@@ -1065,10 +1050,10 @@ class Image(object):
                                                    img.channels[i].mask)
 
     def colorize(self, colormap):
-        """Colorize the current image using
-        *colormap*. Works only on"L" or "LA" images.
-        """
+        """Colorize the current image using *colormap*.
 
+        Works only on"L" or "LA" images.
+        """
         if self.mode not in ("L", "LA"):
             raise ValueError("Image should be grayscale to colorize")
         if self.mode == "LA":
@@ -1083,10 +1068,10 @@ class Image(object):
             self.mode = "RGB"
 
     def palettize(self, colormap):
-        """Palettize the current image using
-        *colormap*. Works only on"L" or "LA" images.
-        """
+        """Palettize the current image using *colormap*.
 
+        Works only on"L" or "LA" images.
+        """
         if self.mode not in ("L", "LA"):
             raise ValueError("Image should be grayscale to colorize")
         self.channels[0], self.palette = colormap.palettize(self.channels[0])
@@ -1096,8 +1081,7 @@ class Image(object):
             self.mode = "PA"
 
     def blend(self, other):
-        """Alpha blend *other* on top of the current image.
-        """
+        """Alpha blend *other* on top of the current image."""
         if self.mode != "RGBA" or other.mode != "RGBA":
             raise ValueError("Images must be in RGBA")
         src = other
@@ -1118,14 +1102,12 @@ class Image(object):
 
 
 def _areinstances(the_list, types):
-    """Check if all the elements of the list are of given type.
-    """
+    """Check if all the elements of the list are of given type."""
     return all([isinstance(item, types) for item in the_list])
 
 
 def _is_pair(item):
-    """Check if an item is a pair (tuple of size 2).
-    """
+    """Check if an item is a pair (tuple of size 2)."""
     return (isinstance(item, (list, tuple, set)) and
             len(item) == 2 and
             not isinstance(item[0], (list, tuple, set)) and
@@ -1133,15 +1115,12 @@ def _is_pair(item):
 
 
 def _is_list_of_pairs(the_list):
-    """Check if a list contains only pairs.
-    """
+    """Check if a list contains only pairs."""
     return all([_is_pair(item) for item in the_list])
 
 
 def ycbcr2rgb(y__, cb_, cr_):
-    """Convert the three YCbCr channels to RGB channels.
-    """
-
+    """Convert the three YCbCr channels to RGB channels."""
     kb_ = 0.114
     kr_ = 0.299
 
@@ -1154,7 +1133,6 @@ def ycbcr2rgb(y__, cb_, cr_):
 
 def rgb2ycbcr(r__, g__, b__):
     """Convert the three RGB channels to YCbCr."""
-
     kb_ = 0.114
     kr_ = 0.299
 


=====================================
trollimage/tests/test_image.py
=====================================
@@ -1275,6 +1275,40 @@ 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")
+    def test_save_geotiff_closed_file(self):
+        """Test saving geotiffs when the geotiff file has been closed.
+
+        This is to mimic a situation where garbage collection would cause the
+        file handler to close the underlying geotiff file that will be written
+        to.
+
+        """
+        import xarray as xr
+        import dask.array as da
+        from trollimage import xrimage
+        import rasterio as rio
+
+        # numpy array image
+        data = xr.DataArray(np.arange(75).reshape(5, 5, 3), dims=[
+            'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
+        img = xrimage.XRImage(data)
+        assert np.issubdtype(img.data.dtype, np.integer)
+        with NamedTemporaryFile(suffix='.tif') as tmp:
+            results = img.save(tmp.name, compute=False)
+            results[1].close()  # mimic garbage collection
+            da.store(results[0], results[1])
+            results[1].close()  # required to flush writes to disk
+            with rio.open(tmp.name) as f:
+                file_data = f.read()
+            assert file_data.shape == (4, 5, 5)  # alpha band added
+            exp = np.arange(75).reshape(5, 5, 3)
+            np.testing.assert_allclose(file_data[0], exp[:, :, 0])
+            np.testing.assert_allclose(file_data[1], exp[:, :, 1])
+            np.testing.assert_allclose(file_data[2], exp[:, :, 2])
+            np.testing.assert_allclose(file_data[3], 255)
+
     @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."""


=====================================
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.16.0)"
-    git_full = "844ca2a2a005fe3016bda9dca3db294b0b71f0c1"
-    git_date = "2021-10-12 19:58:23 -0500"
+    git_refnames = " (tag: v1.16.1)"
+    git_full = "5c23f01c984be3dc685eae5750ee227aff41d0e7"
+    git_date = "2021-11-17 15:32:46 -0600"
     keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
     return keywords
 


=====================================
trollimage/xrimage.py
=====================================
@@ -133,13 +133,13 @@ class RIOFile(object):
     def write(self, *args, **kwargs):
         """Write to the file."""
         with self.lock:
-            self.open('a')
+            self.open('r+')
             return self.rfile.write(*args, **kwargs)
 
     def build_overviews(self, *args, **kwargs):
         """Write overviews."""
         with self.lock:
-            self.open('a')
+            self.open('r+')
             return self.rfile.build_overviews(*args, **kwargs)
 
     def update_tags(self, *args, **kwargs):
@@ -395,7 +395,11 @@ class XRImage(object):
                             ``(source, target)`` to be passed to
                             `dask.array.store`.
             keep_palette (bool): Saves the palettized version of the image if
-                                 set to True. False by default.
+                                 set to True. False by default.  Warning: this
+                                 does not automatically write the colormap
+                                 (palette) to the file.  To write the colormap
+                                 to the file, one should additionally pass the
+                                 colormap with the ``cmap`` keyword argument.
             cmap (Colormap or dict): Colormap to be applied to the image when
                                      saving with rasterio, used with
                                      keep_palette=True. Should be uint8.
@@ -1375,7 +1379,17 @@ class XRImage(object):
                                                    img.channels[i].mask)
 
     def colorize(self, colormap):
-        """Colorize the current image using `colormap`.
+        """Colorize the current image using ``colormap``.
+
+        Convert a greyscale image (mode "L" or "LA") to a color image (mode
+        "RGB" or "RGBA") by applying a colormap.
+
+        To create a color image in mode "P" or "PA", use
+        :meth:`~XRImage.palettize`.
+
+        Args:
+            colormap (:class:`~trollimage.colormap.Colormap`):
+                Colormap to be applied to the image.
 
         .. note::
 
@@ -1410,7 +1424,21 @@ class XRImage(object):
         self.data = xr.DataArray(new_data, coords=coords, attrs=attrs, dims=dims)
 
     def palettize(self, colormap):
-        """Palettize the current image using `colormap`.
+        """Palettize the current image using ``colormap``.
+
+        Convert a mode "L" (or "LA") grayscale image to a mode "P" (or "PA")
+        palette image and store the palette in the ``palette`` attribute.
+        To store this image in mode "P", call :meth:`~XRImage.save` with
+        ``keep_palette=True``.  To include color information in the output
+        format (if supported), call :meth:`~XRImage.save` with
+        ``keep_palette=True`` *and* ``cmap=colormap``.
+
+        To (directly) get an image in mode "RGB" or "RGBA", use
+        :meth:`~XRImage.colorize`.
+
+        Args:
+            colormap (:class:`~trollimage.colormap.Colormap`):
+                Colormap to be applied to the image.
 
         .. note::
 



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

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/-/commit/8b0c0e6a28bec2e2a581b594ac354b9be0549c87
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/20211125/b3df98af/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list