[Git][debian-gis-team/trollimage][master] 6 commits: New upstream version 1.10.0
Antonio Valentino
gitlab at salsa.debian.org
Sun Sep 22 08:18:29 BST 2019
Antonio Valentino pushed to branch master at Debian GIS Project / trollimage
Commits:
b2240f1c by Antonio Valentino at 2019-09-22T06:57:07Z
New upstream version 1.10.0
- - - - -
e237c503 by Antonio Valentino at 2019-09-22T06:57:10Z
Update upstream source from tag 'upstream/1.10.0'
Update to upstream version '1.10.0'
with Debian dir 4dd150bf59c33418444e99ebb99acdf6657ab925
- - - - -
d73135bf by Antonio Valentino at 2019-09-22T06:58:53Z
New upstream release
- - - - -
1092646e by Antonio Valentino at 2019-09-22T07:02:56Z
Refresh all patches
- - - - -
03fadfaa by Antonio Valentino at 2019-09-22T07:13:37Z
Explicit specification of Rules-Requires-Root in d/control
- - - - -
e5d9fe50 by Antonio Valentino at 2019-09-22T07:14:02Z
Set distribution to unstable
- - - - -
9 changed files:
- + .pre-commit-config.yaml
- CHANGELOG.md
- debian/changelog
- debian/control
- debian/patches/0001-No-display.patch
- trollimage/colormap.py
- trollimage/tests/test_image.py
- trollimage/version.py
- trollimage/xrimage.py
Changes:
=====================================
.pre-commit-config.yaml
=====================================
@@ -0,0 +1,8 @@
+exclude: '^$'
+fail_fast: false
+repos:
+- repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v1.2.3
+ hooks:
+ - id: flake8
+ additional_dependencies: [flake8-docstrings, flake8-debugger, flake8-bugbear]
=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,20 @@
+## Version 1.10.0 (2019/09/20)
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 53](https://github.com/pytroll/trollimage/pull/53) - Fix double format passing in saving functions
+
+#### Features added
+
+* [PR 55](https://github.com/pytroll/trollimage/pull/55) - Add enhancement-history to the image
+* [PR 54](https://github.com/pytroll/trollimage/pull/54) - Add ability to use AreaDefinitions new "crs" property
+* [PR 52](https://github.com/pytroll/trollimage/pull/52) - Add 'colors' and 'values' keyword arguments to Colormap
+
+In this release 4 pull requests were closed.
+
+
## Version 1.9.0 (2019/06/18)
### Pull Requests Merged
=====================================
debian/changelog
=====================================
@@ -1,3 +1,13 @@
+trollimage (1.10.0-1) unstable; urgency=medium
+
+ * New upstream release.
+ * debian/patches:
+ - refresh all patches
+ * debian/control:
+ - explicit specification of Rules-Requires-Root
+
+ -- Antonio Valentino <antonio.valentino at tiscali.it> Sun, 22 Sep 2019 07:13:42 +0000
+
trollimage (1.9.0-2) unstable; urgency=medium
* Bump Standards-Version to 4.4.0, no changes.
=====================================
debian/control
=====================================
@@ -3,6 +3,7 @@ Maintainer: Debian GIS Project <pkg-grass-devel at lists.alioth.debian.org>
Uploaders: Antonio Valentino <antonio.valentino at tiscali.it>
Section: python
Testsuite: autopkgtest-pkg-python
+Rules-Requires-Root: no
Priority: optional
Build-Depends: debhelper-compat (= 12),
dh-python,
=====================================
debian/patches/0001-No-display.patch
=====================================
@@ -8,14 +8,14 @@ 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 4e7f34e..1b8cc74 100644
+index 06f5fd0..890bd85 100644
--- a/trollimage/tests/test_image.py
+++ b/trollimage/tests/test_image.py
-@@ -1859,6 +1859,7 @@ class TestXRImage(unittest.TestCase):
- def test_putalpha(self):
+@@ -1863,6 +1863,7 @@ class TestXRImage(unittest.TestCase):
+ """Test putalpha."""
pass
+ @unittest.skip("no display")
def test_show(self):
- """Test that the show commands calls PIL.show"""
+ """Test that the show commands calls PIL.show."""
import xarray as xr
=====================================
trollimage/colormap.py
=====================================
@@ -88,9 +88,13 @@ class Colormap(object):
"""
- def __init__(self, *tuples):
- values = [a for (a, b) in tuples]
- colors = [b for (a, b) in tuples]
+ def __init__(self, *tuples, **kwargs):
+ if 'colors' in kwargs and 'values' in kwargs:
+ values = kwargs['values']
+ colors = kwargs['colors']
+ else:
+ values = [a for (a, b) in tuples]
+ colors = [b for (a, b) in tuples]
self.values = np.array(values)
self.colors = np.array(colors)
=====================================
trollimage/tests/test_image.py
=====================================
@@ -21,8 +21,7 @@
# You should have received a copy of the GNU General Public License
# along with mpop. If not, see <http://www.gnu.org/licenses/>.
-"""Module for testing the imageo.image module.
-"""
+"""Module for testing the image and xrimage modules."""
import os
import sys
import random
@@ -58,18 +57,15 @@ class CustomScheduler(object):
class TestEmptyImage(unittest.TestCase):
- """Class for testing the mpop.imageo.image module
- """
+ """Class for testing the mpop.imageo.image module."""
def setUp(self):
- """Setup the test.
- """
+ """Set up the test case."""
self.img = image.Image()
self.modes = ["L", "LA", "RGB", "RGBA", "YCbCr", "YCbCrA", "P", "PA"]
def test_shape(self):
- """Shape of an empty image.
- """
+ """Shape of an empty image."""
oldmode = self.img.mode
for mode in self.modes:
self.img.convert(mode)
@@ -77,13 +73,11 @@ class TestEmptyImage(unittest.TestCase):
self.img.convert(oldmode)
def test_is_empty(self):
- """Test if an image is empty.
- """
+ """Test if an image is empty."""
self.assertEqual(self.img.is_empty(), True)
def test_clip(self):
- """Clip an empty image.
- """
+ """Clip an empty image."""
oldmode = self.img.mode
for mode in self.modes:
self.img.convert(mode)
@@ -91,8 +85,7 @@ class TestEmptyImage(unittest.TestCase):
self.img.convert(oldmode)
def test_convert(self):
- """Convert an empty image.
- """
+ """Convert an empty image."""
for mode1 in self.modes:
for mode2 in self.modes:
self.img.convert(mode1)
@@ -108,8 +101,7 @@ class TestEmptyImage(unittest.TestCase):
self.assertRaises(ValueError, self.img.convert, randstr)
def test_stretch(self):
- """Stretch an empty image
- """
+ """Stretch an empty image."""
oldmode = self.img.mode
for mode in self.modes:
self.img.convert(mode)
@@ -136,8 +128,7 @@ class TestEmptyImage(unittest.TestCase):
self.img.convert(oldmode)
def test_gamma(self):
- """Gamma correction on an empty image.
- """
+ """Gamma correction on an empty image."""
oldmode = self.img.mode
for mode in self.modes:
self.img.convert(mode)
@@ -158,8 +149,7 @@ class TestEmptyImage(unittest.TestCase):
self.img.convert(oldmode)
def test_invert(self):
- """Invert an empty image.
- """
+ """Invert an empty image."""
oldmode = self.img.mode
for mode in self.modes:
self.img.convert(mode)
@@ -174,8 +164,7 @@ class TestEmptyImage(unittest.TestCase):
self.img.convert(oldmode)
def test_pil_image(self):
- """Return an empty PIL image.
- """
+ """Return an empty PIL image."""
oldmode = self.img.mode
for mode in self.modes:
self.img.convert(mode)
@@ -189,8 +178,7 @@ class TestEmptyImage(unittest.TestCase):
self.img.convert(oldmode)
def test_putalpha(self):
- """Add an alpha channel to en empty image
- """
+ """Add an alpha channel to en empty image."""
# Putting alpha channel to an empty image should not do anything except
# change the mode if necessary.
oldmode = self.img.mode
@@ -212,8 +200,7 @@ class TestEmptyImage(unittest.TestCase):
self.img.convert(oldmode)
def test_save(self):
- """Save an empty image.
- """
+ """Save an empty image."""
oldmode = self.img.mode
for mode in self.modes:
self.img.convert(mode)
@@ -222,8 +209,7 @@ class TestEmptyImage(unittest.TestCase):
self.img.convert(oldmode)
def test_replace_luminance(self):
- """Replace luminance in an empty image.
- """
+ """Replace luminance in an empty image."""
oldmode = self.img.mode
for mode in self.modes:
self.img.convert(mode)
@@ -234,13 +220,11 @@ class TestEmptyImage(unittest.TestCase):
self.img.convert(oldmode)
def test_resize(self):
- """Resize an empty image.
- """
+ """Resize an empty image."""
self.assertRaises(ValueError, self.img.resize, (10, 10))
def test_merge(self):
- """Merging of an empty image with another.
- """
+ """Merging of an empty image with another."""
newimg = image.Image()
self.assertRaises(ValueError, self.img.merge, newimg)
newimg = image.Image(np.array([[1, 2], [3, 4]]))
@@ -250,20 +234,16 @@ class TestEmptyImage(unittest.TestCase):
class TestImageCreation(unittest.TestCase):
- """Class for testing the mpop.imageo.image module
- """
+ """Class for testing the mpop.imageo.image module."""
def setUp(self):
- """Setup the test.
- """
+ """Set up the test case."""
self.img = {}
self.modes = ["L", "LA", "RGB", "RGBA", "YCbCr", "YCbCrA", "P", "PA"]
self.modes_len = [1, 2, 3, 4, 3, 4, 1, 2]
def test_creation(self):
- """Creation of an image.
- """
-
+ """Test creation of an image."""
self.assertRaises(TypeError, image.Image,
channels=random.randint(1, 1000))
self.assertRaises(TypeError, image.Image,
@@ -338,12 +318,10 @@ class TestImageCreation(unittest.TestCase):
class TestRegularImage(unittest.TestCase):
- """Class for testing the mpop.imageo.image module
- """
+ """Class for testing the mpop.imageo.image module."""
def setUp(self):
- """Setup the test.
- """
+ """Set up the test case."""
one_channel = np.random.rand(random.randint(1, 10),
random.randint(1, 10))
self.rand_img = image.Image(channels=[one_channel] * 3,
@@ -370,8 +348,7 @@ class TestRegularImage(unittest.TestCase):
os.chmod(self.tempdir, 0o444)
def test_shape(self):
- """Shape of an image.
- """
+ """Shape of an image."""
oldmode = self.img.mode
for mode in self.modes:
if mode == "P" or mode == "PA":
@@ -381,13 +358,11 @@ class TestRegularImage(unittest.TestCase):
self.img.convert(oldmode)
def test_is_empty(self):
- """Test if an image is empty.
- """
+ """Test if an image is empty."""
self.assertEqual(self.img.is_empty(), False)
def test_clip(self):
- """Clip an image.
- """
+ """Clip an image."""
oldmode = self.img.mode
for mode in self.modes:
if mode == "P" or mode == "PA":
@@ -399,8 +374,7 @@ class TestRegularImage(unittest.TestCase):
self.img.convert(oldmode)
def test_convert(self):
- """Convert an image.
- """
+ """Convert an image."""
i = 0
for mode1 in self.modes:
j = 0
@@ -437,8 +411,7 @@ class TestRegularImage(unittest.TestCase):
self.assertRaises(ValueError, self.img.convert, randstr)
def test_stretch(self):
- """Stretch an image.
- """
+ """Stretch an image."""
oldmode = self.img.mode
for mode in "L":
@@ -479,8 +452,7 @@ class TestRegularImage(unittest.TestCase):
self.img.convert(oldmode)
def test_gamma(self):
- """Gamma correction on an image.
- """
+ """Gamma correction on an image."""
oldmode = self.img.mode
for mode in self.modes:
if mode == "P" or mode == "PA":
@@ -519,8 +491,7 @@ class TestRegularImage(unittest.TestCase):
self.img.convert(oldmode)
def test_invert(self):
- """Invert an image.
- """
+ """Invert an image."""
oldmode = self.img.mode
for mode in self.modes:
if mode == "P" or mode == "PA":
@@ -543,11 +514,8 @@ class TestRegularImage(unittest.TestCase):
self.img.convert(oldmode)
def test_pil_image(self):
- """Return an PIL image.
- """
-
+ """Return an PIL image."""
# FIXME: Should test on palette images
-
oldmode = self.img.mode
for mode in self.modes:
if (mode == "YCbCr" or
@@ -561,8 +529,7 @@ class TestRegularImage(unittest.TestCase):
self.img.convert(oldmode)
def test_putalpha(self):
- """Add an alpha channel.
- """
+ """Add an alpha channel."""
# Putting alpha channel to an image should not do anything except
# change the mode if necessary.
oldmode = self.img.mode
@@ -590,8 +557,7 @@ class TestRegularImage(unittest.TestCase):
@unittest.skipIf(sys.platform.startswith('win'),
"Read-only tmp dir not working under Windows")
def test_save(self):
- """Save an image.
- """
+ """Save an image."""
oldmode = self.img.mode
for mode in self.modes:
if (mode == "YCbCr" or
@@ -614,8 +580,7 @@ class TestRegularImage(unittest.TestCase):
@unittest.skipIf(sys.platform.startswith('win'),
"Read-only tmp dir not working under Windows")
def test_save_jpeg(self):
- """Save a jpeg image.
- """
+ """Save a jpeg image."""
oldmode = self.img.mode
self.img.convert('L')
self.img.save("test.jpg")
@@ -630,8 +595,7 @@ class TestRegularImage(unittest.TestCase):
self.img.convert(oldmode)
def test_replace_luminance(self):
- """Replace luminance in an image.
- """
+ """Replace luminance in an image."""
oldmode = self.img.mode
for mode in self.modes:
if (mode == "P" or
@@ -651,8 +615,7 @@ class TestRegularImage(unittest.TestCase):
self.img.convert(oldmode)
def test_resize(self):
- """Resize an image.
- """
+ """Resize an image."""
self.img.resize((6, 6))
res = np.array([[0, 0, 0.5, 0.5, 0.5, 0.5],
[0, 0, 0.5, 0.5, 0.5, 0.5],
@@ -667,8 +630,7 @@ class TestRegularImage(unittest.TestCase):
self.assertTrue(np.all(res == self.img.channels[0]))
def test_merge(self):
- """Merging of an image with another.
- """
+ """Merging of an image with another."""
newimg = image.Image()
self.assertRaises(ValueError, self.img.merge, newimg)
newimg = image.Image(np.array([[1, 2], [3, 4]]))
@@ -686,17 +648,16 @@ class TestRegularImage(unittest.TestCase):
EPSILON))
def tearDown(self):
- """Clean up the mess.
- """
+ """Clean up the mess."""
os.chmod(self.tempdir, 0o777)
os.rmdir(self.tempdir)
class TestFlatImage(unittest.TestCase):
- """Test a flat image, ie an image where min == max.
- """
+ """Test a flat image, ie an image where min == max."""
def setUp(self):
+ """Set up the test case."""
channel = np.ma.array([[0, 0.5, 0.5], [0.5, 0.25, 0.25]],
mask=[[1, 1, 1], [1, 1, 0]])
self.img = image.Image(channels=[channel] * 3,
@@ -704,8 +665,7 @@ class TestFlatImage(unittest.TestCase):
self.modes = ["L", "LA", "RGB", "RGBA", "YCbCr", "YCbCrA", "P", "PA"]
def test_stretch(self):
- """Stretch a flat image.
- """
+ """Stretch a flat image."""
self.img.stretch()
self.assertTrue(self.img.channels[0].shape == (2, 3) and
np.ma.count_masked(self.img.channels[0]) == 5)
@@ -724,10 +684,10 @@ class TestFlatImage(unittest.TestCase):
class TestNoDataImage(unittest.TestCase):
- """Test an image filled with no data.
- """
+ """Test an image filled with no data."""
def setUp(self):
+ """Set up the test case."""
channel = np.ma.array([[0, 0.5, 0.5], [0.5, 0.25, 0.25]],
mask=[[1, 1, 1], [1, 1, 1]])
self.img = image.Image(channels=[channel] * 3,
@@ -735,8 +695,7 @@ class TestNoDataImage(unittest.TestCase):
self.modes = ["L", "LA", "RGB", "RGBA", "YCbCr", "YCbCrA", "P", "PA"]
def test_stretch(self):
- """Stretch a no data image.
- """
+ """Stretch a no data image."""
self.img.stretch()
self.assertTrue(self.img.channels[0].shape == (2, 3))
self.img.stretch("crude")
@@ -752,16 +711,16 @@ class TestNoDataImage(unittest.TestCase):
def random_string(length,
choices="abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"):
- """Generates a random string with elements from *set* of the specified
- *length*.
- """
+ """Generate a random string with elements from *set* of the specified *length*."""
return "".join([random.choice(choices)
for dummy in range(length)])
class TestXRImage(unittest.TestCase):
+ """Test XRImage objects."""
def test_init(self):
+ """Test object initialization."""
import xarray as xr
from trollimage import xrimage
data = xr.DataArray([[0, 0.5, 0.5], [0.5, 0.25, 0.25]], dims=['y', 'x'])
@@ -793,9 +752,23 @@ class TestXRImage(unittest.TestCase):
img = xrimage.XRImage(data)
self.assertEqual(img.mode, 'YCbCrA')
+ def test_regression_double_format_save(self):
+ """Test that double format information isn't passed to save."""
+ import xarray as xr
+ 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:
+ img = xrimage.XRImage(data)
+
+ img.save(filename='bla.png', fformat='png', format='png')
+ self.assertNotIn('format', pil_save.call_args_list[0][1])
+
@unittest.skipIf(sys.platform.startswith('win'),
"'NamedTemporaryFile' not supported on Windows")
def test_save(self):
+ """Test saving."""
import xarray as xr
import dask.array as da
from dask.delayed import Delayed
@@ -1236,8 +1209,10 @@ class TestXRImage(unittest.TestCase):
img = xrimage.XRImage(data)
img.gamma(.5)
self.assertTrue(np.allclose(img.data.values, arr ** 2))
+ self.assertDictEqual(img.data.attrs['enhancement_history'][0], {'gamma': 0.5})
img.gamma([2., 2., 2.])
+ self.assertEqual(len(img.data.attrs['enhancement_history']), 2)
self.assertTrue(np.allclose(img.data.values, arr))
def test_crude_stretch(self):
@@ -1253,6 +1228,11 @@ class TestXRImage(unittest.TestCase):
red = img.data.sel(bands='R')
green = img.data.sel(bands='G')
blue = img.data.sel(bands='B')
+ enhs = img.data.attrs['enhancement_history'][0]
+ scale_expected = np.array([0.01388889, 0.01388889, 0.01388889])
+ offset_expected = np.array([0., -0.01388889, -0.02777778])
+ np.testing.assert_allclose(enhs['scale'].values, scale_expected)
+ np.testing.assert_allclose(enhs['offset'].values, offset_expected)
np.testing.assert_allclose(red, arr[:, :, 0] / 72.)
np.testing.assert_allclose(green, (arr[:, :, 1] - 1.) / (73. - 1.))
np.testing.assert_allclose(blue, (arr[:, :, 2] - 2.) / (74. - 2.))
@@ -1275,7 +1255,8 @@ class TestXRImage(unittest.TestCase):
img = xrimage.XRImage(data)
img.invert(True)
-
+ enhs = img.data.attrs['enhancement_history'][0]
+ self.assertDictEqual(enhs, {'scale': -1, 'offset': 1})
self.assertTrue(np.allclose(img.data.values, 1 - arr))
data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
@@ -1299,6 +1280,9 @@ class TestXRImage(unittest.TestCase):
coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
img.stretch_linear()
+ enhs = img.data.attrs['enhancement_history'][0]
+ np.testing.assert_allclose(enhs['scale'].values, np.array([1.03815937, 1.03815937, 1.03815937]))
+ np.testing.assert_allclose(enhs['offset'].values, np.array([-0.00505051, -0.01907969, -0.03310887]), atol=1e-8)
res = np.array([[[-0.005051, -0.005051, -0.005051],
[0.037037, 0.037037, 0.037037],
[0.079125, 0.079125, 0.079125],
@@ -1328,6 +1312,7 @@ class TestXRImage(unittest.TestCase):
self.assertTrue(np.allclose(img.data.values, res, atol=1.e-6))
def test_histogram_stretch(self):
+ """Test histogram stretching."""
import xarray as xr
from trollimage import xrimage
@@ -1336,6 +1321,8 @@ class TestXRImage(unittest.TestCase):
coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
img.stretch('histogram')
+ enhs = img.data.attrs['enhancement_history'][0]
+ self.assertDictEqual(enhs, {'hist_equalize': True})
res = np.array([[[0., 0., 0.],
[0.04166667, 0.04166667, 0.04166667],
[0.08333333, 0.08333333, 0.08333333],
@@ -1369,6 +1356,7 @@ class TestXRImage(unittest.TestCase):
self.assertTrue(np.allclose(img.data.values, res, atol=1.e-6))
def test_logarithmic_stretch(self):
+ """Test logarithmic strecthing."""
import xarray as xr
from trollimage import xrimage
@@ -1377,6 +1365,8 @@ class TestXRImage(unittest.TestCase):
coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
img.stretch(stretch='logarithmic')
+ enhs = img.data.attrs['enhancement_history'][0]
+ self.assertDictEqual(enhs, {'log_factor': 100.0})
res = np.array([[[0., 0., 0.],
[0.35484693, 0.35484693, 0.35484693],
[0.48307087, 0.48307087, 0.48307087],
@@ -1410,7 +1400,7 @@ class TestXRImage(unittest.TestCase):
self.assertTrue(np.allclose(img.data.values, res, atol=1.e-6))
def test_weber_fechner_stretch(self):
- """S=2.3klog10I+C """
+ """Test applying S=2.3klog10I+C to the data."""
import xarray as xr
from trollimage import xrimage
@@ -1419,6 +1409,8 @@ class TestXRImage(unittest.TestCase):
coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
img.stretch_weber_fechner(2.5, 0.2)
+ enhs = img.data.attrs['enhancement_history'][0]
+ self.assertDictEqual(enhs, {'weber_fechner': (2.5, 0.2)})
res = np.array([[[-np.inf, -6.73656795, -5.0037],
[-3.99003723, -3.27083205, -2.71297317],
[-2.25716928, -1.87179258, -1.5379641],
@@ -1452,27 +1444,35 @@ class TestXRImage(unittest.TestCase):
self.assertTrue(np.allclose(img.data.values, res, atol=1.e-6))
def test_jpeg_save(self):
+ """Test saving to jpeg."""
pass
def test_gtiff_save(self):
+ """Test saving to geotiff."""
pass
def test_save_masked(self):
+ """Test saving masked data."""
pass
def test_LA_save(self):
+ """Test LA saving."""
pass
def test_L_save(self):
+ """Test L saving."""
pass
def test_P_save(self):
+ """Test P saving."""
pass
def test_PA_save(self):
+ """Test PA saving."""
pass
def test_convert_modes(self):
+ """Test modes convertions."""
import dask
import xarray as xr
from trollimage import xrimage
@@ -1782,7 +1782,7 @@ class TestXRImage(unittest.TestCase):
self.assertTupleEqual((2, 4), bw.colors.shape)
def test_stack(self):
-
+ """Test stack."""
import xarray as xr
from trollimage import xrimage
@@ -1810,9 +1810,11 @@ class TestXRImage(unittest.TestCase):
np.testing.assert_allclose(bkg.data, res.data, rtol=1e-05)
def test_merge(self):
+ """Test merge."""
pass
def test_blend(self):
+ """Test blend."""
import xarray as xr
from trollimage import xrimage
@@ -1854,13 +1856,15 @@ class TestXRImage(unittest.TestCase):
img1.blend(wrongimg)
def test_replace_luminance(self):
+ """Test luminance replacement."""
pass
def test_putalpha(self):
+ """Test putalpha."""
pass
def test_show(self):
- """Test that the show commands calls PIL.show"""
+ """Test that the show commands calls PIL.show."""
import xarray as xr
from trollimage import xrimage
@@ -1873,7 +1877,7 @@ class TestXRImage(unittest.TestCase):
def suite():
- """The suite for test_image."""
+ """Create the suite for test_image."""
loader = unittest.TestLoader()
mysuite = unittest.TestSuite()
mysuite.addTest(loader.loadTestsFromTestCase(TestEmptyImage))
=====================================
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 = " (tag: v1.9.0)"
- git_full = "63fa32f2d40bb65ebc39c4be1fb1baf8f163db98"
- git_date = "2019-06-18 06:13:40 -0500"
+ git_refnames = " (HEAD -> master, tag: v1.10.0)"
+ git_full = "b1fb06cbf6ef8b23e5816c423df1eeaf8e76d606"
+ git_date = "2019-09-20 09:41:02 +0200"
keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
return keywords
=====================================
trollimage/xrimage.py
=====================================
@@ -21,12 +21,14 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-"""This module defines the XRImage class. It overlaps largely with the PIL
-library, but has the advantage of using :class:`~xarray.DataArray` objects
-backed by :class:`dask arrays <dask.array.Array>` as pixel arrays. This
-allows for invalid values to be tracked, metadata to be assigned, and
-stretching to be lazy evaluated. With the optional ``rasterio`` library
-installed dask array chunks can be saved in parallel.
+"""This module defines the XRImage class.
+
+It overlaps largely with the PIL library, but has the advantage of using
+:class:`~xarray.DataArray` objects backed by :class:`dask arrays
+<dask.array.Array>` as pixel arrays. This allows for invalid values to
+be tracked, metadata to be assigned, and stretching to be lazy
+evaluated. With the optional ``rasterio`` library installed dask array
+chunks can be saved in parallel.
"""
@@ -96,12 +98,14 @@ class RIOFile(object):
indexes=indexes)
def open(self, mode=None):
+ """Open the file."""
mode = mode or self.mode
if self._closed:
self.rfile = rasterio.open(self.path, mode, **self.kwargs)
self._closed = False
def close(self):
+ """Close the file."""
if not self._closed:
if self.overviews:
logger.debug('Building overviews %s', str(self.overviews))
@@ -119,6 +123,7 @@ class RIOFile(object):
self.close()
def __del__(self):
+ """Delete the instance."""
try:
self.close()
except (IOError, OSError):
@@ -168,8 +173,13 @@ def color_interp(data):
class XRImage(object):
"""Image class using an :class:`xarray.DataArray` as internal storage.
- It can be saved to a variety of image formats, but if Rasterio is installed,
- it can save to geotiff and jpeg2000 with geographical information.
+ It can be saved to a variety of image formats, but if Rasterio is
+ installed, it can save to geotiff and jpeg2000 with geographical
+ information.
+
+ The enhancements functions are recording some parameters in the image's
+ data attribute called `enhancement_history`.
+
"""
def __init__(self, data):
@@ -252,7 +262,8 @@ class XRImage(object):
saving with rasterio, used with
keep_palette=True. Should be uint8.
format_kwargs: Additional format options to pass to `rasterio`
- or `PIL` saving methods.
+ or `PIL` saving methods. Any format argument passed
+ at this stage would be superseeded by `fformat`.
Returns:
Either `None` if `compute` is True or a `dask.Delayed` object or
@@ -263,7 +274,8 @@ class XRImage(object):
the caller.
"""
- fformat = fformat or os.path.splitext(filename)[1][1:4]
+ kwformat = format_kwargs.pop('format', None)
+ fformat = fformat or kwformat or os.path.splitext(filename)[1][1:]
if fformat in ('tif', 'jp2') and rasterio:
return self.rio_save(filename, fformat=fformat,
fill_value=fill_value, compute=compute,
@@ -284,7 +296,7 @@ class XRImage(object):
img.rio_save('myfile.tif', overviews=[2, 4, 8, 16])
"""
- fformat = fformat or os.path.splitext(filename)[1][1:4]
+ fformat = fformat or os.path.splitext(filename)[1][1:]
drivers = {'jpg': 'JPEG',
'png': 'PNG',
'tif': 'GTiff',
@@ -318,7 +330,11 @@ class XRImage(object):
photometric_map[mode.upper()])
try:
- crs = rasterio.crs.CRS(data.attrs['area'].proj_dict)
+ area = data.attrs['area']
+ if hasattr(area, 'crs'):
+ crs = rasterio.crs.CRS.from_wkt(area.crs.to_wkt())
+ else:
+ crs = rasterio.crs.CRS(data.attrs['area'].proj_dict)
west, south, east, north = data.attrs['area'].area_extent
height, width = data.sizes['y'], data.sizes['x']
transform = rasterio.transform.from_bounds(west, south,
@@ -380,10 +396,11 @@ class XRImage(object):
compute=True, **format_kwargs):
"""Save the image to the given *filename* using PIL.
- For now, the compression level [0-9] is ignored, due to PIL's lack of
- support. See also :meth:`save`.
+ For now, the compression level [0-9] is ignored, due to PIL's
+ lack of support. See also :meth:`save`.
+
"""
- fformat = fformat or os.path.splitext(filename)[1][1:4]
+ fformat = fformat or os.path.splitext(filename)[1][1:]
fformat = check_image_format(fformat)
if fformat == 'png':
@@ -402,6 +419,7 @@ class XRImage(object):
Inspired by:
public domain, Nick Galbreath
http://blog.modp.com/2007/08/python-pil-and-png-metadata-take-2.html
+
"""
reserved = ('interlace', 'gamma', 'dpi', 'transparency', 'aspect')
@@ -531,8 +549,7 @@ class XRImage(object):
return data
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"))
bands = ["L"] * 3
@@ -543,6 +560,7 @@ class XRImage(object):
return data
def convert(self, mode):
+ """Convert image to *mode*."""
if mode == self.mode:
return self.__class__(self.data)
@@ -588,7 +606,7 @@ class XRImage(object):
return new_img
def _finalize(self, fill_value=None, dtype=np.uint8, keep_palette=False, cmap=None):
- """Wrapper around 'finalize' method for backwards compatibility."""
+ """Wrap around 'finalize' method for backwards compatibility."""
import warnings
warnings.warn("'_finalize' is deprecated, use 'finalize' instead.",
DeprecationWarning)
@@ -597,13 +615,14 @@ class XRImage(object):
def finalize(self, fill_value=None, dtype=np.uint8, keep_palette=False, cmap=None):
"""Finalize the image to be written to an output file.
- This adds an alpha band or fills data with a fill_value (if specified).
- It also scales float data to the output range of the data type (0-255
- for uint8, default). For integer input data this method assumes the
- data is already scaled to the proper desired range. It will still fill
- in invalid values and add an alpha band if needed. Integer input
- data's fill value is determined by a special ``_FillValue`` attribute
- in the ``DataArray`` ``.attrs`` dictionary.
+ This adds an alpha band or fills data with a fill_value (if
+ specified). It also scales float data to the output range of the
+ data type (0-255 for uint8, default). For integer input data
+ this method assumes the data is already scaled to the proper
+ desired range. It will still fill in invalid values and add an
+ alpha band if needed. Integer input data's fill value is
+ determined by a special ``_FillValue`` attribute in the
+ ``DataArray`` ``.attrs`` dictionary.
"""
if keep_palette and not self.mode.startswith('P'):
@@ -674,19 +693,20 @@ class XRImage(object):
dims=['bands'],
coords={'bands': self.data['bands']})
- def gamma(self, gamma=1.0):
+ def gamma(self, gamma=None):
"""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 *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)):
gamma = self.xrify_tuples(gamma)
- elif gamma == 1.0:
+ elif gamma is None or gamma == 1.0:
return
logger.debug("Applying gamma %s", str(gamma))
@@ -694,18 +714,21 @@ class XRImage(object):
self.data = self.data.clip(min=0)
self.data **= 1.0 / gamma
self.data.attrs = attrs
+ self.data.attrs.setdefault('enhancement_history', []).append({'gamma': gamma})
def stretch(self, stretch="crude", **kwargs):
"""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 contrast stretching without cutoff. The value "logarithmic"
- or "log" will do a logarithmic enhancement towards white. If a tuple or
- a list of two values is given as input, then a contrast stretching is
- performed with the values as cutoff. These values should be normalized
- in the range [0.0,1.0].
+ 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 contrast stretching without
+ cutoff. The value "logarithmic" or "log" will do a logarithmic
+ enhancement towards white. If a tuple or a list of two values is
+ given as input, then a contrast stretching is performed 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))
@@ -735,7 +758,7 @@ class XRImage(object):
@staticmethod
def _compute_quantile(data, dims, cutoffs):
- """Helper method for stretch_linear.
+ """Compute quantile for stretch_linear.
Dask delayed functions need to be non-internal functions (created
inside a function) to be serializable on a multi-process scheduler.
@@ -756,6 +779,7 @@ class XRImage(object):
"""Stretch linearly the contrast of the current image.
Use *cutoffs* for left and right trimming.
+
"""
logger.debug("Perform a linear contrast stretch.")
@@ -786,8 +810,9 @@ class XRImage(object):
def crude_stretch(self, min_stretch=None, max_stretch=None):
"""Perform simple linear stretching.
- This is done without any cutoff on the current image and normalizes to
- the [0,1] range.
+ This is done without any cutoff on the current image and
+ normalizes to the [0,1] range.
+
"""
if min_stretch is None:
non_band_dims = tuple(x for x in self.data.dims if x != 'bands')
@@ -808,9 +833,12 @@ class XRImage(object):
else:
scale_factor = 1.0 / delta
attrs = self.data.attrs
- self.data -= min_stretch
+ offset = -min_stretch * scale_factor
self.data *= scale_factor
+ self.data += offset
self.data.attrs = attrs
+ self.data.attrs.setdefault('enhancement_history', []).append({'scale': scale_factor,
+ 'offset': offset})
def stretch_hist_equalize(self, approximate=False):
"""Stretch the current image's colors through histogram equalization.
@@ -858,6 +886,7 @@ class XRImage(object):
band_results.append(self.data.sel(bands='A'))
self.data.data = da.stack(band_results,
axis=self.data.dims.index('bands'))
+ self.data.attrs.setdefault('enhancement_history', []).append({'hist_equalize': True})
def stretch_logarithmic(self, factor=100.):
"""Move data into range [1:factor] through normalized logarithm."""
@@ -885,6 +914,7 @@ class XRImage(object):
band_results.append(self.data.sel(bands='A'))
self.data.data = da.stack(band_results,
axis=self.data.dims.index('bands'))
+ self.data.attrs.setdefault('enhancement_history', []).append({'log_factor': factor})
def stretch_weber_fechner(self, k, s0):
"""Stretch according to the Weber-Fechner law.
@@ -892,10 +922,12 @@ class XRImage(object):
p = k.ln(S/S0)
p is perception, S is the stimulus, S0 is the stimulus threshold (the
highest unpercieved stimulus), and k is the factor.
+
"""
attrs = self.data.attrs
self.data = k * xu.log(self.data / s0)
self.data.attrs = attrs
+ self.data.attrs.setdefault('enhancement_history', []).append({'weber_fechner': (k, s0)})
def invert(self, invert=True):
"""Inverts all the channels of a image according to *invert*.
@@ -905,6 +937,7 @@ class XRImage(object):
Note: 'Inverting' means that black becomes white, and vice-versa, not
that the values are negated !
+
"""
logger.debug("Applying invert with parameters %s", str(invert))
if isinstance(invert, (tuple, list)):
@@ -917,10 +950,11 @@ class XRImage(object):
attrs = self.data.attrs
self.data = self.data * scale + offset
self.data.attrs = attrs
+ self.data.attrs.setdefault('enhancement_history', []).append({'scale': scale,
+ 'offset': offset})
def stack(self, img):
- """Stack the provided image on top of the current image.
- """
+ """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:
@@ -929,8 +963,10 @@ class XRImage(object):
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.
+ """Use the provided image as background for the current *img* image.
+
+ That is if the current image has missing data.
+
"""
raise NotImplementedError("This method has not be implemented for "
"xarray support.")
@@ -966,7 +1002,6 @@ class XRImage(object):
Works only on "L" or "LA" images.
"""
-
if self.mode not in ("L", "LA"):
raise ValueError("Image should be grayscale to colorize")
@@ -997,7 +1032,7 @@ class XRImage(object):
@staticmethod
def _palettize(data, colormap):
- """Helper for dask-friendly palettize operation."""
+ """Operate in a dask-friendly manner."""
# returns data and palette, only need data
return colormap.palettize(data)[0]
@@ -1009,7 +1044,6 @@ class XRImage(object):
Works only on "L" or "LA" images.
"""
-
if self.mode not in ("L", "LA"):
raise ValueError("Image should be grayscale to colorize")
View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/compare/57d3249a227f615b25b3dce3f2895222e5026410...e5d9fe50bab42c71a3c76ce1409589efee173fb8
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/compare/57d3249a227f615b25b3dce3f2895222e5026410...e5d9fe50bab42c71a3c76ce1409589efee173fb8
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/20190922/4786b04e/attachment-0001.html>
More information about the Pkg-grass-devel
mailing list