Skip to content
Commits on Source (5)
## Version 1.9.0 (2019/06/18)
### Pull Requests Merged
#### Bugs fixed
* [PR 51](https://github.com/pytroll/trollimage/pull/51) - Fix _FillValue not being respected when converting to alpha image
#### Features added
* [PR 49](https://github.com/pytroll/trollimage/pull/49) - Add a new method for image stacking.
In this release 2 pull requests were closed.
## Version 1.8.0 (2019/05/10)
### Issues Closed
* [Issue 45](https://github.com/pytroll/trollimage/issues/45) - img.stretch gives TypeError where img.data is xarray.DataArray and img.data.data is a dask.array
In this release 1 issue was closed.
### Pull Requests Merged
#### Bugs fixed
* [PR 47](https://github.com/pytroll/trollimage/pull/47) - Fix xrimage palettize and colorize delaying internal functions
#### Features added
* [PR 46](https://github.com/pytroll/trollimage/pull/46) - Implement blend method for XRImage class
In this release 2 pull requests were closed.
## Version 1.7.0 (2019/02/28)
### Issues Closed
......
trollimage (1.7.0-2) UNRELEASED; urgency=medium
trollimage (1.9.0-1) unstable; urgency=medium
* Team upload.
[ Bas Couwenberg ]
* Update gbp.conf to use --source-only-changes by default.
-- Bas Couwenberg <sebastic@debian.org> Sun, 07 Jul 2019 10:22:10 +0200
[ Antonio Valentino ]
* New upstream release.
* debian/pathces:
- refresh all patches
-- Antonio Valentino <antonio.valentino@tiscali.it> Tue, 09 Jul 2019 06:35:44 +0000
trollimage (1.7.0-1) unstable; urgency=medium
......
......@@ -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 816d4f2..a949c8f 100644
index 4e7f34e..1b8cc74 100644
--- a/trollimage/tests/test_image.py
+++ b/trollimage/tests/test_image.py
@@ -1790,6 +1790,7 @@ class TestXRImage(unittest.TestCase):
@@ -1859,6 +1859,7 @@ class TestXRImage(unittest.TestCase):
def test_putalpha(self):
pass
......
......@@ -1504,12 +1504,15 @@ class TestXRImage(unittest.TestCase):
# L -> LA (int)
with dask.config.set(scheduler=CustomScheduler(max_computes=1)):
img = xrimage.XRImage((dataset1 * 150).astype(np.uint8))
img.data.attrs['_FillValue'] = 0 # set fill value
img = img.convert('LA')
self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
self.assertTrue(img.mode == 'LA')
self.assertTrue(len(img.data.coords['bands']) == 2)
# make sure the alpha band is all opaque
np.testing.assert_allclose(img.data.sel(bands='A'), 255)
# make sure the alpha band is all opaque except the first pixel
alpha = img.data.sel(bands='A').values.ravel()
np.testing.assert_allclose(alpha[0], 0)
np.testing.assert_allclose(alpha[1:], 255)
# L -> LA (float)
with dask.config.set(scheduler=CustomScheduler(max_computes=1)):
......@@ -1778,11 +1781,77 @@ class TestXRImage(unittest.TestCase):
self.assertTupleEqual((1, 5, 15), values.shape)
self.assertTupleEqual((2, 4), bw.colors.shape)
def test_stack(self):
import xarray as xr
from trollimage import xrimage
# background image
arr1 = np.zeros((2, 2))
data1 = xr.DataArray(arr1, dims=['y', 'x'])
bkg = xrimage.XRImage(data1)
# image to be stacked
arr2 = np.full((2, 2), np.nan)
arr2[0] = 1
data2 = xr.DataArray(arr2, dims=['y', 'x'])
img = xrimage.XRImage(data2)
# expected result
arr3 = arr1.copy()
arr3[0] = 1
data3 = xr.DataArray(arr3, dims=['y', 'x'])
res = xrimage.XRImage(data3)
# stack image over the background
bkg.stack(img)
# check result
np.testing.assert_allclose(bkg.data, res.data, rtol=1e-05)
def test_merge(self):
pass
def test_blend(self):
pass
import xarray as xr
from trollimage import xrimage
core1 = np.arange(75).reshape(5, 5, 3) / 75.0
alpha1 = np.linspace(0, 1, 25).reshape(5, 5, 1)
arr1 = np.concatenate([core1, alpha1], 2)
data1 = xr.DataArray(arr1, dims=['y', 'x', 'bands'],
coords={'bands': ['R', 'G', 'B', 'A']})
img1 = xrimage.XRImage(data1)
core2 = np.arange(75, 0, -1).reshape(5, 5, 3) / 75.0
alpha2 = np.linspace(1, 0, 25).reshape(5, 5, 1)
arr2 = np.concatenate([core2, alpha2], 2)
data2 = xr.DataArray(arr2, dims=['y', 'x', 'bands'],
coords={'bands': ['R', 'G', 'B', 'A']})
img2 = xrimage.XRImage(data2)
img3 = img1.blend(img2)
np.testing.assert_allclose(
(alpha1 + alpha2 * (1 - alpha1)).squeeze(),
img3.data.sel(bands="A"))
np.testing.assert_allclose(
img3.data.sel(bands="R").values,
np.array(
[[1., 0.95833635, 0.9136842, 0.8666667, 0.8180645],
[0.768815, 0.72, 0.6728228, 0.62857145, 0.5885714],
[0.55412847, 0.5264665, 0.50666666, 0.495612, 0.49394494],
[0.5020408, 0.52, 0.5476586, 0.5846154, 0.63027024],
[0.683871, 0.7445614, 0.81142855, 0.8835443, 0.96]]))
with self.assertRaises(TypeError):
img1.blend("Salekhard")
wrongimg = xrimage.XRImage(
xr.DataArray(np.zeros((0, 0)), dims=("y", "x")))
with self.assertRaises(ValueError):
img1.blend(wrongimg)
def test_replace_luminance(self):
pass
......
......@@ -23,9 +23,9 @@ def get_keywords():
# setup.py/versioneer.py will grep for the variable names, so they must
# each be defined on a line of their own. _version.py will just call
# get_keywords().
git_refnames = " (HEAD -> master, tag: v1.7.0)"
git_full = "d35a7665ad475ff230e457085523e21f2cd3f454"
git_date = "2019-02-28 12:49:51 -0600"
git_refnames = " (tag: v1.9.0)"
git_full = "63fa32f2d40bb65ebc39c4be1fb1baf8f163db98"
git_date = "2019-06-18 06:13:40 -0500"
keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
return keywords
......
......@@ -449,11 +449,15 @@ class XRImage(object):
If ``data`` is an integer type then the alpha band will be scaled
to use the smallest (min) value as fully transparent and the largest
(max) value as fully opaque. For float types the alpha band spans
0 to 1.
(max) value as fully opaque. If a `_FillValue` attribute is found for
integer type data then it is used to identify null values in the data.
Otherwise xarray's `isnull` is used.
For float types the alpha band spans 0 to 1.
"""
null_mask = alpha if alpha is not None else self._create_alpha(data)
fill_value = data.attrs.get('_FillValue', None) # integer fill value
null_mask = alpha if alpha is not None else self._create_alpha(data, fill_value)
# if we are using integer data, then alpha needs to be min-int to max-int
# otherwise for floats we want 0 to 1
if np.issubdtype(data.dtype, np.integer):
......@@ -914,6 +918,16 @@ class XRImage(object):
self.data = self.data * scale + offset
self.data.attrs = attrs
def stack(self, img):
"""Stack the provided image on top of the current image.
"""
# TODO: Conversions between different modes with notification
# to the user, i.e. proper logging
if self.mode != img.mode:
raise NotImplementedError("Cannot stack images of different modes.")
self.data = self.data.where(img.data.isnull(), img.data)
def merge(self, img):
"""Use the provided image as background for the current *img* image,
that is if the current image has missing data.
......@@ -937,6 +951,13 @@ class XRImage(object):
self.channels[i].mask = np.logical_and(selfmask,
img.channels[i].mask)
@staticmethod
def _colorize(l_data, colormap):
# 'l_data' is (1, rows, cols)
# 'channels' will be a list of 3 (RGB) or 4 (RGBA) arrays
channels = colormap.colorize(l_data)
return np.concatenate(channels, axis=0)
def colorize(self, colormap):
"""Colorize the current image using `colormap`.
......@@ -955,14 +976,7 @@ class XRImage(object):
alpha = None
l_data = self.data.sel(bands=['L'])
def _colorize(l_data, colormap):
# 'l_data' is (1, rows, cols)
# 'channels' will be a list of 3 (RGB) or 4 (RGBA) arrays
channels = colormap.colorize(l_data)
return np.concatenate(channels, axis=0)
new_data = l_data.data.map_blocks(_colorize, colormap,
new_data = l_data.data.map_blocks(self._colorize, colormap,
chunks=(colormap.colors.shape[1],) + l_data.data.chunks[1:],
dtype=np.float64)
......@@ -981,6 +995,12 @@ class XRImage(object):
dims = self.data.dims
self.data = xr.DataArray(new_data, coords=coords, attrs=attrs, dims=dims)
@staticmethod
def _palettize(data, colormap):
"""Helper for dask-friendly palettize operation."""
# returns data and palette, only need data
return colormap.palettize(data)[0]
def palettize(self, colormap):
"""Palettize the current image using `colormap`.
......@@ -994,12 +1014,7 @@ class XRImage(object):
raise ValueError("Image should be grayscale to colorize")
l_data = self.data.sel(bands=['L'])
def _palettize(data):
# returns data and palette, only need data
return colormap.palettize(data)[0]
new_data = l_data.data.map_blocks(_palettize, dtype=l_data.dtype)
new_data = l_data.data.map_blocks(self._palettize, colormap, dtype=l_data.dtype)
self.palette = tuple(colormap.colors)
if self.mode == "L":
......@@ -1011,22 +1026,61 @@ class XRImage(object):
self.data.data = new_data
self.data.coords['bands'] = list(mode)
def blend(self, other):
"""Alpha blend *other* on top of the current image."""
raise NotImplementedError("This method has not be implemented for "
"xarray support.")
def blend(self, src):
r"""Alpha blend *src* on top of the current image.
Perform `alpha blending`_ of *src* on top of the current image.
Alpha blending is defined as:
if self.mode != "RGBA" or other.mode != "RGBA":
raise ValueError("Images must be in RGBA")
src = other
dst = self
outa = src.channels[3] + dst.channels[3] * (1 - src.channels[3])
for i in range(3):
dst.channels[i] = (src.channels[i] * src.channels[3] +
dst.channels[i] * dst.channels[3] *
(1 - src.channels[3])) / outa
dst.channels[i][outa == 0] = 0
dst.channels[3] = outa
.. math::
\begin{cases}
\mathrm{out}_A =
\mathrm{src}_A + \mathrm{dst}_A (1 - \mathrm{src}_A) \\
\mathrm{out}_{RGB} =
\bigl(\mathrm{src}_{RGB}\mathrm{src}_A
+ \mathrm{dst}_{RGB} \mathrm{dst}_A
\left(1 - \mathrm{src}_A \right) \bigr)
\div \mathrm{out}_A \\
\mathrm{out}_A = 0 \Rightarrow \mathrm{out}_{RGB} = 0
\end{cases}
Both images must have mode ``"RGBA"``.
Args:
src (:class:`XRImage` with mode ``"RGBA"``)
Image to be blended on top of current image.
.. _alpha blending: https://en.wikipedia.org/w/index.php?title=Alpha_compositing&oldid=891033105#Alpha_blending
Returns
XRImage with mode "RGBA", blended as described above
"""
# NB: docstring maths copy-pasta from enwiki
if self.mode != "RGBA":
raise ValueError(
"Expected self.mode='RGBA', got {md!s}".format(
md=self.mode))
elif not isinstance(src, XRImage):
raise TypeError("Expected XRImage, got {tp!s}".format(
tp=type(src)))
elif src.mode != "RGBA":
raise ValueError("Expected src.mode='RGBA', got {sm!s}".format(
sm=src.mode))
srca = src.data.sel(bands="A")
dsta = self.data.sel(bands="A")
outa = srca + dsta * (1-srca)
bi = {"bands": ["R", "G", "B"]}
rgb = ((src.data.loc[bi] * srca
+ self.data.loc[bi] * dsta * (1-srca))
/ outa).where(outa != 0, 0)
return self.__class__(
xr.concat(
[rgb, outa.expand_dims("bands")],
dim="bands"))
def show(self):
"""Display the image on screen."""
......