[Git][debian-gis-team/trollimage][master] 5 commits: New upstream version 1.16.1
Antonio Valentino (@antonio.valentino)
gitlab at salsa.debian.org
Thu Nov 25 07:42:26 GMT 2021
Antonio Valentino pushed to branch master at Debian GIS Project / trollimage
Commits:
8b0c0e6a by Antonio Valentino at 2021-11-25T07:31:28+00:00
New upstream version 1.16.1
- - - - -
8dd9bae2 by Antonio Valentino at 2021-11-25T07:31:36+00:00
Update upstream source from tag 'upstream/1.16.1'
Update to upstream version '1.16.1'
with Debian dir 47b4d50d863dde06c7456d297d043528f0db1c4a
- - - - -
030fd02b by Antonio Valentino at 2021-11-25T07:32:10+00:00
New upstream release
- - - - -
7944aba1 by Antonio Valentino at 2021-11-25T07:34:06+00:00
Update d/copyright file
- - - - -
1b4edb8a by Antonio Valentino at 2021-11-25T07:35:05+00:00
Refresh all patches
- - - - -
9 changed files:
- CHANGELOG.md
- debian/changelog
- debian/copyright
- debian/patches/0001-No-display.patch
- 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
=====================================
debian/changelog
=====================================
@@ -1,3 +1,12 @@
+trollimage (1.16.1-1) UNRELEASED; urgency=medium
+
+ * New upstream releae.
+ * Updated d/copyright file.
+ * debian/patches:
+ - refresh all patches.
+
+ -- Antonio Valentino <antonio.valentino at tiscali.it> Thu, 25 Nov 2021 07:31:41 +0000
+
trollimage (1.16.0-1) unstable; urgency=medium
[ Bas Couwenberg ]
=====================================
debian/copyright
=====================================
@@ -5,6 +5,7 @@ Source: https://github.com/pytroll/trollimage
Files: *
Copyright: 2009-2018 Martin Raspaud
+ 2009-2021 Trollimage Developers
License: GPL-3+
Files: versioneer.py
=====================================
debian/patches/0001-No-display.patch
=====================================
@@ -8,10 +8,10 @@ Skip tests that require display.
1 file changed, 1 insertion(+)
diff --git a/trollimage/tests/test_image.py b/trollimage/tests/test_image.py
-index 6469ced..691a49c 100644
+index daa81b9..b945293 100644
--- a/trollimage/tests/test_image.py
+++ b/trollimage/tests/test_image.py
-@@ -2036,6 +2036,7 @@ class TestXRImage:
+@@ -2070,6 +2070,7 @@ class TestXRImage:
"""Test putalpha."""
pass
=====================================
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/-/compare/dd7bf82caa4f36f0c68d4f9e1abccbb53a4d0e04...1b4edb8aec646c961f012e37c4be1594f7cfb76f
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/-/compare/dd7bf82caa4f36f0c68d4f9e1abccbb53a4d0e04...1b4edb8aec646c961f012e37c4be1594f7cfb76f
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/934f40d8/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list