[Git][debian-gis-team/trollimage][master] 6 commits: New upstream version 1.15.0
Antonio Valentino
gitlab at salsa.debian.org
Sun Mar 21 12:12:39 GMT 2021
Antonio Valentino pushed to branch master at Debian GIS Project / trollimage
Commits:
337c6a61 by Antonio Valentino at 2021-03-21T11:37:45+00:00
New upstream version 1.15.0
- - - - -
4b449866 by Antonio Valentino at 2021-03-21T11:37:50+00:00
Update upstream source from tag 'upstream/1.15.0'
Update to upstream version '1.15.0'
with Debian dir 076e1c7227bff6b7833e67b8ebae98231f5f412e
- - - - -
3b5e6857 by Antonio Valentino at 2021-03-21T11:39:25+00:00
New usptream release
- - - - -
d593e160 by Antonio Valentino at 2021-03-21T11:42:05+00:00
Refresh all patches
- - - - -
d81e662d by Antonio Valentino at 2021-03-21T11:57:48+00:00
New 0002-Install-tests.patch
- - - - -
df656050 by Antonio Valentino at 2021-03-21T13:08:29+01:00
Add autopkgtest
- - - - -
18 changed files:
- + .github/workflows/ci.yaml
- + .github/workflows/deploy-sdist.yaml
- .travis.yml
- CHANGELOG.md
- README.rst
- − appveyor.yml
- + continuous_integration/environment.yaml
- debian/changelog
- debian/control
- debian/patches/0001-No-display.patch
- + debian/patches/0002-Install-tests.patch
- debian/patches/series
- + debian/tests/control
- + debian/tests/python3
- doc/colormap.rst
- trollimage/tests/test_image.py
- trollimage/version.py
- trollimage/xrimage.py
Changes:
=====================================
.github/workflows/ci.yaml
=====================================
@@ -0,0 +1,74 @@
+name: CI
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ${{ matrix.os }}
+ continue-on-error: ${{ matrix.experimental }}
+ strategy:
+ fail-fast: true
+ matrix:
+ os: ["windows-latest", "ubuntu-latest", "macos-latest"]
+ python-version: ["3.7", "3.8"]
+ experimental: [false]
+ include:
+ - python-version: "3.8"
+ os: "ubuntu-latest"
+ experimental: true
+
+ env:
+ PYTHON_VERSION: ${{ matrix.python-version }}
+ OS: ${{ matrix.os }}
+ UNSTABLE: ${{ matrix.experimental }}
+ ACTIONS_ALLOW_UNSECURE_COMMANDS: true
+
+ steps:
+ - name: Checkout source
+ uses: actions/checkout at v2
+
+ - name: Setup Conda Environment
+ uses: conda-incubator/setup-miniconda at v2
+ with:
+ miniconda-version: "latest"
+ python-version: ${{ matrix.python-version }}
+ mamba-version: "*"
+ channels: conda-forge
+ channel-priority: strict
+ environment-file: continuous_integration/environment.yaml
+ activate-environment: test-environment
+
+ - name: Install unstable dependencies
+ if: matrix.experimental == true
+ shell: bash -l {0}
+ run: |
+ python -m pip install \
+ -f https://7933911d6844c6c53a7d-47bd50c35cd79bd838daf386af554a83.ssl.cf2.rackcdn.com \
+ --no-deps --pre --upgrade \
+ numpy \
+ pandas; \
+ python -m pip install \
+ --no-deps --upgrade \
+ git+https://github.com/dask/dask \
+ git+https://github.com/dask/distributed \
+ git+https://github.com/Unidata/cftime \
+ git+https://github.com/mapbox/rasterio \
+ git+https://github.com/pydata/bottleneck \
+ git+https://github.com/pydata/xarray;
+
+ - name: Install trollimage
+ shell: bash -l {0}
+ run: |
+ pip install --no-deps -e .
+
+ - name: Run unit tests
+ shell: bash -l {0}
+ run: |
+ pytest --cov=trollimage trollimage/tests --cov-report=xml
+
+ - name: Upload unittest coverage to Codecov
+ uses: codecov/codecov-action at v1
+ with:
+ flags: unittests
+ file: ./coverage.xml
+ env_vars: OS,PYTHON_VERSION,UNSTABLE
=====================================
.github/workflows/deploy-sdist.yaml
=====================================
@@ -0,0 +1,25 @@
+name: Deploy sdist
+
+on:
+ release:
+ types:
+ - published
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout source
+ uses: actions/checkout at v2
+
+ - name: Create sdist
+ shell: bash -l {0}
+ run: python setup.py sdist
+
+ - name: Publish package to PyPI
+ if: github.event.action == 'published'
+ uses: pypa/gh-action-pypi-publish at v1.4.1
+ with:
+ user: __token__
+ password: ${{ secrets.pypi_password }}
\ No newline at end of file
=====================================
.travis.yml
=====================================
@@ -47,17 +47,17 @@ install:
- pip install -e . --no-deps
script:
- pytest --cov=trollimage trollimage/tests
-after_success:
- - if [[ $PYTHON_VERSION == 3.8 ]]; then coveralls; fi
-deploy:
- - provider: pypi
- user: dhoese
- password:
- secure: "Cpo7kPgjbyWKNRjnzWDggxo5dqG8KdtcrBxYWwBpkkhgly/ngN9EkjIdscrwmp8sCqfjTd0RGyR811k6dzU7kKJVbc6V309+4mG8O0w4IvfHCn+NaHymAMrHleRIqyxbo5kvrBZoX+eB7YWOUppF6ofeohbrNWWgMQv+/d+Mufs="
- distributions: sdist bdist_wheel
- skip_existing: true
- on:
- repo: pytroll/trollimage
- tags: true
-notifications:
- slack: pytroll:96mNSYSI1dBjGyzVXkBT6qFt
+#after_success:
+# - if [[ $PYTHON_VERSION == 3.8 ]]; then coveralls; fi
+#deploy:
+# - provider: pypi
+# user: dhoese
+# password:
+# secure: "Cpo7kPgjbyWKNRjnzWDggxo5dqG8KdtcrBxYWwBpkkhgly/ngN9EkjIdscrwmp8sCqfjTd0RGyR811k6dzU7kKJVbc6V309+4mG8O0w4IvfHCn+NaHymAMrHleRIqyxbo5kvrBZoX+eB7YWOUppF6ofeohbrNWWgMQv+/d+Mufs="
+# distributions: sdist bdist_wheel
+# skip_existing: true
+# on:
+# repo: pytroll/trollimage
+# tags: true
+#notifications:
+# slack: pytroll:96mNSYSI1dBjGyzVXkBT6qFt
=====================================
CHANGELOG.md
=====================================
@@ -1,3 +1,27 @@
+## Version 1.15.0 (2021/03/12)
+
+### Issues Closed
+
+* [Issue 74](https://github.com/pytroll/trollimage/issues/74) - MNT: Stop using ci-helpers in appveyor.yml
+
+In this release 1 issue was closed.
+
+### Pull Requests Merged
+
+#### Bugs fixed
+
+* [PR 78](https://github.com/pytroll/trollimage/pull/78) - Fix list stretch tags
+* [PR 77](https://github.com/pytroll/trollimage/pull/77) - Remove defaults channel from ci conda environment and strict priority
+
+#### Features added
+
+* [PR 76](https://github.com/pytroll/trollimage/pull/76) - Add GitHub Actions for CI tests and sdist deployment
+* [PR 75](https://github.com/pytroll/trollimage/pull/75) - Change XRImage.save to keep fill_value separate from valid data
+* [PR 73](https://github.com/pytroll/trollimage/pull/73) - Refactor finalize method in XRImage class
+
+In this release 5 pull requests were closed.
+
+
## Version 1.14.0 (2020/09/18)
=====================================
README.rst
=====================================
@@ -5,13 +5,13 @@ Trollimage
:target: https://pypi.python.org/pypi/trollimage/
:alt: Version
-.. image:: https://travis-ci.org/pytroll/trollimage.svg?branch=master
- :target: https://travis-ci.org/pytroll/trollimage
- :alt: Travis CI
+.. image:: https://anaconda.org/conda-forge/trollimage/badges/version.svg
+ :target: https://anaconda.org/conda-forge/trollimage/
+ :alt: Conda-forge
-.. image:: https://ci.appveyor.com/api/projects/status/9ux7hgi8rry971fn/branch/master?svg=true
- :target: https://ci.appveyor.com/project/pytroll/trollimage
- :alt: Appveyor
+.. image:: https://github.com/pytroll/trollimage/workflows/CI/badge.svg?branch=master
+ :target: https://github.com/pytroll/trollimage/actions?query=workflow%3A%22CI%22
+ :alt: GitHub Actions
.. image:: https://coveralls.io/repos/pytroll/trollimage/badge.png?branch=master
:target: https://coveralls.io/r/pytroll/trollimage?branch=master
=====================================
appveyor.yml deleted
=====================================
@@ -1,43 +0,0 @@
-environment:
- global:
- PYTHON: "C:\\conda"
- MINICONDA_VERSION: "latest"
- CMD_IN_ENV: "cmd /E:ON /V:ON /C .\\ci-helpers\\appveyor\\windows_sdk.cmd"
- CONDA_DEPENDENCIES: "pillow gdal xarray dask coverage coveralls codecov rasterio pytest pytest-cov"
- CONDA_CHANNELS: "conda-forge"
- CONDA_CHANNEL_PRIORITY: "True"
-
- matrix:
- - PYTHON: "C:\\Python37_64"
- PYTHON_VERSION: "3.7"
- PYTHON_ARCH: "64"
- NUMPY_VERSION: "stable"
-
- - PYTHON: "C:\\Python38_64"
- PYTHON_VERSION: "3.8"
- PYTHON_ARCH: "64"
- NUMPY_VERSION: "stable"
-
-install:
- - "git clone --depth 1 git://github.com/astropy/ci-helpers.git"
- - "powershell ci-helpers/appveyor/install-miniconda.ps1"
- - "conda activate test"
- - "pip install -e ."
-
-build: false # Not a C# project, build stuff at the test step instead.
-
-test_script:
- - "%CMD_IN_ENV% pytest --cov=trollimage trollimage/tests"
-
-after_test:
- # If tests are successful, create a whl package for the project.
- - "%CMD_IN_ENV% python setup.py bdist_wheel bdist_wininst"
- - ps: "ls dist"
-
-artifacts:
- # Archive the generated wheel package in the ci.appveyor.com build report.
- - path: dist\*
-
-#on_success:
-# - TODO: upload the content of dist/*.whl to a public wheelhouse
-#
=====================================
continuous_integration/environment.yaml
=====================================
@@ -0,0 +1,20 @@
+name: test-environment
+channels:
+ - conda-forge
+dependencies:
+ - xarray
+ - dask
+ - distributed
+ - toolz
+ - Cython
+ - sphinx
+ - pillow
+ - coveralls
+ - coverage
+ - codecov
+ - rasterio
+ - libtiff
+ - pytest
+ - pytest-cov
+ - fsspec
+ - pip
=====================================
debian/changelog
=====================================
@@ -1,10 +1,17 @@
-trollimage (1.14.0-2) UNRELEASED; urgency=medium
+trollimage (1.15.0-1) UNRELEASED; urgency=medium
- * Team upload.
+ [ Bas Couwenberg ]
* Bump Standards-Version to 4.5.1, no changes.
* Update watch file for GitHub URL changes.
- -- Bas Couwenberg <sebastic at debian.org> Sat, 28 Nov 2020 14:27:00 +0100
+ [ Antonio Valentino ]
+ * New upstream release.
+ * debian/patches:
+ - refresh all patches
+ - new 0002-Install-tests.patch
+ * Add autopkgtests.
+
+ -- Antonio Valentino <antonio.valentino at tiscali.it> Sun, 21 Mar 2021 11:38:49 +0000
trollimage (1.14.0-1) unstable; urgency=medium
=====================================
debian/control
=====================================
@@ -11,6 +11,7 @@ Build-Depends: debhelper-compat (= 12),
python3-dask,
python3-numpy (>= 1:1.13),
python3-pil,
+ python3-pytest,
python3-rasterio,
python3-setuptools,
python3-xarray
=====================================
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 bb628a4..cedc688 100644
+index ff4b755..505a8d3 100644
--- a/trollimage/tests/test_image.py
+++ b/trollimage/tests/test_image.py
-@@ -1966,6 +1966,7 @@ class TestXRImage(unittest.TestCase):
+@@ -2036,6 +2036,7 @@ class TestXRImage:
"""Test putalpha."""
pass
=====================================
debian/patches/0002-Install-tests.patch
=====================================
@@ -0,0 +1,22 @@
+From: Antonio Valentino <antonio.valentino at tiscali.it>
+Date: Sun, 21 Mar 2021 11:55:23 +0000
+Subject: Install tests
+
+Forwarded: not-needed
+---
+ setup.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/setup.py b/setup.py
+index 69fa07f..4e17202 100644
+--- a/setup.py
++++ b/setup.py
+@@ -44,7 +44,7 @@ setup(name="trollimage",
+ "Programming Language :: Python",
+ "Topic :: Scientific/Engineering"],
+ url="https://github.com/pytroll/trollimage",
+- packages=['trollimage'],
++ packages=['trollimage', 'trollimage.tests'],
+ zip_safe=False,
+ install_requires=['numpy >=1.13', 'pillow'],
+ python_requires='>=3.6',
=====================================
debian/patches/series
=====================================
@@ -1 +1,2 @@
0001-No-display.patch
+0002-Install-tests.patch
=====================================
debian/tests/control
=====================================
@@ -0,0 +1,2 @@
+Tests: python3
+Depends: @, @builddeps@
=====================================
debian/tests/python3
=====================================
@@ -0,0 +1,12 @@
+#!/bin/sh
+set -efu
+
+PYS=${PYS:-"$(py3versions -r 2>/dev/null)"}
+TESTPKG=${TESTPKG:-trollimage}
+
+cd "$AUTOPKGTEST_TMP"
+
+for py in $PYS; do
+ echo "=== $py ==="
+ $py -m pytest --pyargs ${TESTPKG}
+done
=====================================
doc/colormap.rst
=====================================
@@ -11,10 +11,10 @@ A simple example of applying a colormap on data::
from trollimage.image import Image
img = Image(data, mode="L")
-
+
rdbu.set_range(-90 + 273.15, 30 + 273.15)
img.colorize(rdbu)
-
+
img.show()
.. image:: _static/hayan_simple.png
@@ -29,7 +29,7 @@ A more complex example, with a colormap build from greyscale on one end, and spe
from trollimage.image import Image
img = Image(data, mode="L")
-
+
greys.set_range(-40 + 273.15, 30 + 273.15)
spectral.set_range(-90 + 273.15, -40.00001 + 273.15)
my_cm = spectral + greys
@@ -46,10 +46,10 @@ Now applying a palette to the data, with sharp edges::
from trollimage.image import Image
img = Image(data, mode="L")
-
+
set3.set_range(-90 + 273.15, 30 + 273.15)
img.palettize(set3)
-
+
img.show()
.. image:: _static/phayan.png
@@ -274,9 +274,10 @@ pastel2
Rainbow Colormap
~~~~~~~~~~~~~~~~
-Don't use this one ! See here_ why
+Don't use this one ! See here_ and there_ why
-.. _here: http://data3.mprog.nl/course/15%20Readings/40%20Reading%204/Borland_Rainbow_Color_Map.pdf
+.. _here: https://www.nature.com/articles/s41467-020-19160-7
+.. _there: https://doi.org/10.1109/MCG.2007.323435
rainbow
=====================================
trollimage/tests/test_image.py
=====================================
@@ -32,6 +32,7 @@ from collections import OrderedDict
from tempfile import NamedTemporaryFile
import numpy as np
+import pytest
from trollimage import image
EPSILON = 0.0001
@@ -316,7 +317,7 @@ class TestImageCreation(unittest.TestCase):
class TestRegularImage(unittest.TestCase):
- """Class for testing the mpop.imageo.image module."""
+ """Class for testing the image module."""
def setUp(self):
"""Set up the test case."""
@@ -714,7 +715,7 @@ def random_string(length,
for dummy in range(length)])
-class TestXRImage(unittest.TestCase):
+class TestXRImage:
"""Test XRImage objects."""
def test_init(self):
@@ -723,32 +724,32 @@ class TestXRImage(unittest.TestCase):
from trollimage import xrimage
data = xr.DataArray([[0, 0.5, 0.5], [0.5, 0.25, 0.25]], dims=['y', 'x'])
img = xrimage.XRImage(data)
- self.assertEqual(img.mode, 'L')
+ assert img.mode == 'L'
data = xr.DataArray([[0, 0.5, 0.5], [0.5, 0.25, 0.25]])
img = xrimage.XRImage(data)
- self.assertEqual(img.mode, 'L')
- self.assertTupleEqual(img.data.dims, ('bands', 'y', 'x'))
+ assert img.mode == 'L'
+ assert img.data.dims == ('bands', 'y', 'x')
data = xr.DataArray([[0, 0.5, 0.5], [0.5, 0.25, 0.25]], dims=['x', 'y_2'])
img = xrimage.XRImage(data)
- self.assertEqual(img.mode, 'L')
- self.assertTupleEqual(img.data.dims, ('bands', 'x', 'y'))
+ assert img.mode == 'L'
+ assert img.data.dims == ('bands', 'x', 'y')
data = xr.DataArray([[0, 0.5, 0.5], [0.5, 0.25, 0.25]], dims=['x_2', 'y'])
img = xrimage.XRImage(data)
- self.assertEqual(img.mode, 'L')
- self.assertTupleEqual(img.data.dims, ('bands', 'x', 'y'))
+ assert img.mode == 'L'
+ assert img.data.dims == ('bands', 'x', 'y')
data = xr.DataArray(np.arange(75).reshape(5, 5, 3), dims=[
'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
- self.assertEqual(img.mode, 'RGB')
+ assert img.mode == 'RGB'
data = xr.DataArray(np.arange(100).reshape(5, 5, 4), dims=[
'y', 'x', 'bands'], coords={'bands': ['Y', 'Cb', 'Cr', 'A']})
img = xrimage.XRImage(data)
- self.assertEqual(img.mode, 'YCbCrA')
+ assert img.mode == 'YCbCrA'
def test_init_writability(self):
"""Test data is writable after init.
@@ -757,11 +758,10 @@ class TestXRImage(unittest.TestCase):
"""
import xarray as xr
- import numpy as np
from trollimage import xrimage
data = xr.DataArray([[0, 0.5, 0.5], [0.5, 0.25, 0.25]], dims=['y', 'x'])
img = xrimage.XRImage(data)
- self.assertEqual(img.mode, 'L')
+ assert img.mode == 'L'
n_arr = np.asarray(img.data)
# if this succeeds then its writable
n_arr[n_arr == 0.5] = 1
@@ -777,80 +777,146 @@ class TestXRImage(unittest.TestCase):
img = xrimage.XRImage(data)
img.save(filename='bla.png', fformat='png', format='png')
- self.assertNotIn('format', pil_save.call_args_list[0][1])
+ assert 'format' not in pil_save.call_args_list[0][1]
- @unittest.skipIf(sys.platform.startswith('win'),
- "'NamedTemporaryFile' not supported on Windows")
- def test_save(self):
- """Test saving."""
+ @pytest.mark.skipif(sys.platform.startswith('win'),
+ reason="'NamedTemporaryFile' not supported on Windows")
+ def test_rgb_save(self):
+ """Test saving RGB/A data to simple image formats."""
import xarray as xr
- import dask.array as da
from dask.delayed import Delayed
from trollimage import xrimage
- from trollimage.colormap import brbg, Colormap
-
- # RGBA colormap
- bw = Colormap(
- (0.0, (1.0, 1.0, 1.0, 1.0)),
- (1.0, (0.0, 0.0, 0.0, 0.5)),
- )
+ import rasterio as rio
data = xr.DataArray(np.arange(75).reshape(5, 5, 3) / 74., dims=[
'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
with NamedTemporaryFile(suffix='.png') as tmp:
img.save(tmp.name)
+ 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) / 74. * 255).round()
+ 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) # completely opaque
+
+ data = data.where(data > (10 / 74.0))
+ img = xrimage.XRImage(data)
+ with NamedTemporaryFile(suffix='.png') as tmp:
+ img.save(tmp.name)
+
+ # dask delayed save
+ with NamedTemporaryFile(suffix='.png') as tmp:
+ delay = img.save(tmp.name, compute=False)
+ assert isinstance(delay, Delayed)
+ delay.compute()
+
+ @pytest.mark.skipif(sys.platform.startswith('win'),
+ reason="'NamedTemporaryFile' not supported on Windows")
+ def test_save_single_band_jpeg(self):
+ """Test saving single band to jpeg formats."""
+ import xarray as xr
+ from trollimage import xrimage
+ import rasterio as rio
# Single band image
- data = xr.DataArray(np.arange(75).reshape(15, 5, 1) / 74., dims=[
+ data = np.arange(75).reshape(15, 5, 1) / 74.
+ data[-1, -1, 0] = np.nan
+ data = xr.DataArray(data, dims=[
'y', 'x', 'bands'], coords={'bands': ['L']})
# Single band image to JPEG
img = xrimage.XRImage(data)
with NamedTemporaryFile(suffix='.jpg') as tmp:
img.save(tmp.name, fill_value=0)
+ with rio.open(tmp.name) as f:
+ file_data = f.read()
+ assert file_data.shape == (1, 15, 5)
+ # can't check data accuracy because jpeg compression will
+ # change the values
+
# Jpeg fails without fill value (no alpha handling)
with NamedTemporaryFile(suffix='.jpg') as tmp:
# make sure fill_value is mentioned in the error message
- self.assertRaisesRegex(OSError, "fill_value", img.save, tmp.name)
- # As PNG that support alpha channel
- img = xrimage.XRImage(data)
- with NamedTemporaryFile(suffix='.png') as tmp:
- img.save(tmp.name)
+ with pytest.raises(OSError, match=r".*fill_value.*"):
+ img.save(tmp.name)
- # Single band image palettized
- data = xr.DataArray(np.arange(75).reshape(15, 5, 1) / 74., dims=[
+ @pytest.mark.skipif(sys.platform.startswith('win'),
+ reason="'NamedTemporaryFile' not supported on Windows")
+ def test_save_single_band_png(self):
+ """Test saving single band images to simple image formats."""
+ import xarray as xr
+ from trollimage import xrimage
+ import rasterio as rio
+
+ # Single band image
+ data = np.arange(75).reshape(15, 5, 1) / 74.
+ data[-1, -1, 0] = np.nan
+ data = xr.DataArray(data, dims=[
'y', 'x', 'bands'], coords={'bands': ['L']})
# Single band image to JPEG
img = xrimage.XRImage(data)
- img.palettize(brbg)
+
+ # Single band image to PNG - min fill (check fill value scaling)
with NamedTemporaryFile(suffix='.png') as tmp:
- img.save(tmp.name)
- # RGBA colormap
- img = xrimage.XRImage(data)
- img.palettize(bw)
+ img.save(tmp.name, fill_value=0)
+ with rio.open(tmp.name) as f:
+ file_data = f.read()
+ assert file_data.shape == (1, 15, 5)
+ exp = (np.arange(75.).reshape(1, 15, 5) / 74. * 254 + 1).round()
+ exp[0, -1, -1] = 0
+ np.testing.assert_allclose(file_data, exp)
+
+ # Single band image to PNG - max fill (check fill value scaling)
with NamedTemporaryFile(suffix='.png') as tmp:
- img.save(tmp.name)
+ img.save(tmp.name, fill_value=255)
+ with rio.open(tmp.name) as f:
+ file_data = f.read()
+ assert file_data.shape == (1, 15, 5)
+ exp = (np.arange(75.).reshape(1, 15, 5) / 74. * 254).round()
+ exp[0, -1, -1] = 255
+ np.testing.assert_allclose(file_data, exp)
- data = xr.DataArray(da.from_array(np.arange(75).reshape(5, 5, 3) / 74.,
- chunks=5),
- dims=['y', 'x', 'bands'],
- coords={'bands': ['R', 'G', 'B']})
- img = xrimage.XRImage(data)
+ # As PNG that support alpha channel
with NamedTemporaryFile(suffix='.png') as tmp:
img.save(tmp.name)
+ with rio.open(tmp.name) as f:
+ file_data = f.read()
+ assert file_data.shape == (2, 15, 5)
+ # bad value should be transparent in alpha channel
+ assert file_data[1, -1, -1] == 0
+ # all other pixels should be opaque
+ assert file_data[1, 0, 0] == 255
+
+ @pytest.mark.skipif(sys.platform.startswith('win'),
+ reason="'NamedTemporaryFile' not supported on Windows")
+ def test_save_palettes(self):
+ """Test saving paletted images to simple image formats."""
+ import xarray as xr
+ from trollimage import xrimage
- data = data.where(data > (10 / 74.0))
+ # Single band image palettized
+ from trollimage.colormap import brbg, Colormap
+ data = xr.DataArray(np.arange(75).reshape(15, 5, 1) / 74., dims=[
+ 'y', 'x', 'bands'], coords={'bands': ['L']})
img = xrimage.XRImage(data)
+ img.palettize(brbg)
with NamedTemporaryFile(suffix='.png') as tmp:
img.save(tmp.name)
+ img = xrimage.XRImage(data)
+ # RGBA colormap
+ bw = Colormap(
+ (0.0, (1.0, 1.0, 1.0, 1.0)),
+ (1.0, (0.0, 0.0, 0.0, 0.5)),
+ )
- # dask delayed save
+ img.palettize(bw)
with NamedTemporaryFile(suffix='.png') as tmp:
- delay = img.save(tmp.name, compute=False)
- self.assertIsInstance(delay, Delayed)
- delay.compute()
+ img.save(tmp.name)
- @unittest.skipIf(sys.platform.startswith('win'), "'NamedTemporaryFile' not supported on Windows")
+ @pytest.mark.skipif(sys.platform.startswith('win'),
+ reason="'NamedTemporaryFile' not supported on Windows")
def test_save_geotiff_float(self):
"""Test saving geotiffs when input data is float."""
import xarray as xr
@@ -867,7 +933,7 @@ class TestXRImage(unittest.TestCase):
img.save(tmp.name)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (4, 5, 5)) # alpha band added
+ assert file_data.shape == (4, 5, 5) # alpha band added
exp = (np.arange(75.).reshape(5, 5, 3) / 75. * 255).round()
np.testing.assert_allclose(file_data[0], exp[:, :, 0])
np.testing.assert_allclose(file_data[1], exp[:, :, 1])
@@ -883,7 +949,7 @@ class TestXRImage(unittest.TestCase):
img.save(tmp.name)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (4, 5, 5)) # alpha band added
+ assert file_data.shape == (4, 5, 5) # alpha band added
exp = (np.arange(75.).reshape(5, 5, 3) / 75. * 255).round()
np.testing.assert_allclose(file_data[0], exp[:, :, 0])
np.testing.assert_allclose(file_data[1], exp[:, :, 1])
@@ -897,7 +963,7 @@ class TestXRImage(unittest.TestCase):
img.save(tmp.name)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (4, 5, 5)) # alpha band added
+ assert file_data.shape == (4, 5, 5) # alpha band added
exp = np.arange(75.).reshape(5, 5, 3) / 75.
exp[exp <= 10. / 75.] = 0 # numpy converts NaNs to 0s
exp = (exp * 255).round()
@@ -913,7 +979,7 @@ class TestXRImage(unittest.TestCase):
img.save(tmp.name, fill_value=128)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (3, 5, 5)) # no alpha band
+ assert file_data.shape == (3, 5, 5) # no alpha band
exp = np.arange(75.).reshape(5, 5, 3) / 75.
exp2 = (exp * 255).round()
exp2[exp <= 10. / 75.] = 128
@@ -926,7 +992,7 @@ class TestXRImage(unittest.TestCase):
img.save(tmp.name, dtype=np.float32)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (3, 5, 5)) # no alpha band
+ assert file_data.shape == (3, 5, 5) # no alpha band
exp = np.arange(75.).reshape(5, 5, 3) / 75.
# fill value is forced to 0
exp[exp <= 10. / 75.] = 0
@@ -939,7 +1005,7 @@ class TestXRImage(unittest.TestCase):
img.save(tmp.name, dtype=np.float32, fill_value=np.nan)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (3, 5, 5)) # no alpha band
+ assert file_data.shape == (3, 5, 5) # no alpha band
exp = np.arange(75.).reshape(5, 5, 3) / 75.
exp[exp <= 10. / 75.] = np.nan
np.testing.assert_allclose(file_data[0], exp[:, :, 0])
@@ -951,7 +1017,7 @@ class TestXRImage(unittest.TestCase):
img.save(tmp.name, dtype=np.float32, fill_value=128)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (3, 5, 5)) # no alpha band
+ assert file_data.shape == (3, 5, 5) # no alpha band
exp = np.arange(75.).reshape(5, 5, 3) / 75.
exp[exp <= 10. / 75.] = 128
np.testing.assert_allclose(file_data[0], exp[:, :, 0])
@@ -963,7 +1029,7 @@ class TestXRImage(unittest.TestCase):
img.save(tmp.name, dtype=np.int16, fill_value=-128)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (3, 5, 5)) # no alpha band
+ assert file_data.shape == (3, 5, 5) # no alpha band
exp = np.arange(75.).reshape(5, 5, 3) / 75.
exp2 = (exp * (2 ** 16 - 1) - (2 ** 15)).round()
exp2[exp <= 10. / 75.] = -128.
@@ -974,9 +1040,9 @@ class TestXRImage(unittest.TestCase):
# dask delayed save
with NamedTemporaryFile(suffix='.tif') as tmp:
delay = img.save(tmp.name, compute=False)
- self.assertIsInstance(delay, tuple)
- self.assertIsInstance(delay[0], da.Array)
- self.assertIsInstance(delay[1], xrimage.RIODataset)
+ assert isinstance(delay, tuple)
+ assert isinstance(delay[0], da.Array)
+ assert isinstance(delay[1], xrimage.RIODataset)
da.store(*delay)
delay[1].close()
@@ -991,7 +1057,7 @@ class TestXRImage(unittest.TestCase):
img.save(tmp.name)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (4, 5, 5)) # alpha band already existed
+ assert file_data.shape == (4, 5, 5) # alpha band already existed
exp = np.arange(75.).reshape(5, 5, 3) / 75.
exp[exp <= 10. / 75.] = 0 # numpy converts NaNs to 0s
exp = (exp * 255.).round()
@@ -1002,7 +1068,8 @@ class TestXRImage(unittest.TestCase):
np.testing.assert_allclose(file_data[3][not_null], 255) # completely opaque
np.testing.assert_allclose(file_data[3][~not_null], 0) # completely transparent
- @unittest.skipIf(sys.platform.startswith('win'), "'NamedTemporaryFile' not supported on Windows")
+ @pytest.mark.skipif(sys.platform.startswith('win'),
+ reason="'NamedTemporaryFile' not supported on Windows")
def test_save_geotiff_datetime(self):
"""Test saving geotiffs when start_time is in the attributes."""
import xarray as xr
@@ -1021,7 +1088,8 @@ class TestXRImage(unittest.TestCase):
tags = _get_tags_after_writing_to_geotiff(data)
assert "TIFFTAG_DATETIME" in tags
- @unittest.skipIf(sys.platform.startswith('win'), "'NamedTemporaryFile' not supported on Windows")
+ @pytest.mark.skipif(sys.platform.startswith('win'),
+ reason="'NamedTemporaryFile' not supported on Windows")
def test_save_geotiff_int(self):
"""Test saving geotiffs when input data is int."""
import xarray as xr
@@ -1034,12 +1102,12 @@ class TestXRImage(unittest.TestCase):
data = xr.DataArray(np.arange(75).reshape(5, 5, 3), dims=[
'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
- self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
+ assert np.issubdtype(img.data.dtype, np.integer)
with NamedTemporaryFile(suffix='.tif') as tmp:
img.save(tmp.name)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (4, 5, 5)) # alpha band added
+ 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])
@@ -1050,7 +1118,7 @@ class TestXRImage(unittest.TestCase):
img.save(tmp.name)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (4, 5, 5)) # alpha band added
+ 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])
@@ -1061,13 +1129,13 @@ class TestXRImage(unittest.TestCase):
dims=['y', 'x', 'bands'],
coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
- self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
+ assert np.issubdtype(img.data.dtype, np.integer)
# Regular default save
with NamedTemporaryFile(suffix='.tif') as tmp:
img.save(tmp.name)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (4, 5, 5)) # alpha band added
+ 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])
@@ -1077,9 +1145,9 @@ class TestXRImage(unittest.TestCase):
# dask delayed save
with NamedTemporaryFile(suffix='.tif') as tmp:
delay = img.save(tmp.name, compute=False)
- self.assertIsInstance(delay, tuple)
- self.assertIsInstance(delay[0], da.Array)
- self.assertIsInstance(delay[1], xrimage.RIODataset)
+ assert isinstance(delay, tuple)
+ assert isinstance(delay[0], da.Array)
+ assert isinstance(delay[1], xrimage.RIODataset)
da.store(*delay)
delay[1].close()
@@ -1113,12 +1181,12 @@ class TestXRImage(unittest.TestCase):
with rio.open(tmp.name) as f:
fgcps, fcrs = f.gcps
for ref, val in zip(gcps, fgcps):
- self.assertEqual(ref.col, val.col)
- self.assertEqual(ref.row, val.row)
- self.assertEqual(ref.x, val.x)
- self.assertEqual(ref.y, val.y)
- self.assertEqual(ref.z, val.z)
- self.assertEqual(crs, fcrs)
+ assert ref.col == val.col
+ assert ref.row == val.row
+ assert ref.x == val.x
+ assert ref.y == val.y
+ assert ref.z == val.z
+ assert crs == fcrs
# with rasterio colormap provided
exp_cmap = {i: (i, 255 - i, i, 255) for i in range(256)}
@@ -1131,10 +1199,10 @@ class TestXRImage(unittest.TestCase):
with rio.open(tmp.name) as f:
file_data = f.read()
cmap = f.colormap(1)
- self.assertEqual(file_data.shape, (1, 9, 9)) # no alpha band
+ assert file_data.shape == (1, 9, 9) # no alpha band
exp = np.arange(81).reshape(9, 9, 1)
np.testing.assert_allclose(file_data[0], exp[:, :, 0])
- self.assertEqual(cmap, exp_cmap)
+ assert cmap == exp_cmap
# with trollimage colormap provided
from trollimage.colormap import Colormap
@@ -1150,10 +1218,10 @@ class TestXRImage(unittest.TestCase):
with rio.open(tmp.name) as f:
file_data = f.read()
cmap = f.colormap(1)
- self.assertEqual(file_data.shape, (1, 9, 9)) # no alpha band
+ assert file_data.shape == (1, 9, 9) # no alpha band
exp = np.arange(81).reshape(9, 9, 1)
np.testing.assert_allclose(file_data[0], exp[:, :, 0])
- self.assertEqual(cmap, exp_cmap)
+ assert cmap == exp_cmap
# with bad colormap provided
bad_cmap = [[i, [i, i, i]] for i in range(256)]
@@ -1162,10 +1230,11 @@ class TestXRImage(unittest.TestCase):
coords={'bands': ['P']})
img = xrimage.XRImage(data)
with NamedTemporaryFile(suffix='.tif') as tmp:
- self.assertRaises(ValueError, img.save, tmp.name,
- keep_palette=True, cmap=bad_cmap)
- self.assertRaises(ValueError, img.save, tmp.name,
- keep_palette=True, cmap=t_cmap, dtype='uint16')
+ with pytest.raises(ValueError):
+ img.save(tmp.name, keep_palette=True, cmap=bad_cmap)
+ with pytest.raises(ValueError):
+ img.save(tmp.name, keep_palette=True, cmap=t_cmap,
+ dtype='uint16')
# with input fill value
data = np.arange(75).reshape(5, 5, 3)
@@ -1177,12 +1246,12 @@ class TestXRImage(unittest.TestCase):
attrs={'_FillValue': 5},
coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
- self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
+ assert np.issubdtype(img.data.dtype, np.integer)
with NamedTemporaryFile(suffix='.tif') as tmp:
img.save(tmp.name, fill_value=128)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (3, 5, 5)) # no alpha band
+ assert file_data.shape == (3, 5, 5) # no alpha band
exp = np.arange(75).reshape(5, 5, 3)
exp[0, 1, :] = 128
exp[0, 1, 1] = 128
@@ -1195,7 +1264,7 @@ class TestXRImage(unittest.TestCase):
img.save(tmp.name)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (4, 5, 5)) # no alpha band
+ assert file_data.shape == (4, 5, 5) # no alpha band
exp = np.arange(75).reshape(5, 5, 3)
exp[0, 1, :] = 5
exp[0, 1, 1] = 5
@@ -1206,7 +1275,7 @@ class TestXRImage(unittest.TestCase):
np.testing.assert_allclose(file_data[2], exp[:, :, 2])
np.testing.assert_allclose(file_data[3], exp_alpha)
- @unittest.skipIf(sys.platform.startswith('win'), "'NamedTemporaryFile' not supported on Windows")
+ @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."""
import xarray as xr
@@ -1217,19 +1286,19 @@ class TestXRImage(unittest.TestCase):
data = xr.DataArray(np.arange(75).reshape(5, 5, 3), dims=[
'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
- self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
+ assert np.issubdtype(img.data.dtype, np.integer)
with NamedTemporaryFile(suffix='.jp2') as tmp:
img.save(tmp.name, quality=100, reversible=True)
with rio.open(tmp.name) as f:
file_data = f.read()
- self.assertEqual(file_data.shape, (4, 5, 5)) # alpha band added
+ 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)
- @unittest.skipIf(sys.platform.startswith('win'), "'NamedTemporaryFile' not supported on Windows")
+ @pytest.mark.skipif(sys.platform.startswith('win'), reason="'NamedTemporaryFile' not supported on Windows")
def test_save_overviews(self):
"""Test saving geotiffs with overviews."""
import xarray as xr
@@ -1240,37 +1309,37 @@ class TestXRImage(unittest.TestCase):
data = xr.DataArray(np.arange(75).reshape(5, 5, 3), dims=[
'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
- self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
+ assert np.issubdtype(img.data.dtype, np.integer)
with NamedTemporaryFile(suffix='.tif') as tmp:
img.save(tmp.name, overviews=[2, 4])
with rio.open(tmp.name) as f:
- self.assertEqual(len(f.overviews(1)), 2)
+ assert len(f.overviews(1)) == 2
# auto-levels
data = np.zeros(25*25*3, dtype=np.uint8).reshape(25, 25, 3)
data = xr.DataArray(data, dims=[
'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
- self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
+ assert np.issubdtype(img.data.dtype, np.integer)
with NamedTemporaryFile(suffix='.tif') as tmp:
img.save(tmp.name, overviews=[], overviews_minsize=2)
with rio.open(tmp.name) as f:
- self.assertEqual(len(f.overviews(1)), 4)
+ assert len(f.overviews(1)) == 4
# auto-levels and resampling
data = np.zeros(25*25*3, dtype=np.uint8).reshape(25, 25, 3)
data = xr.DataArray(data, dims=[
'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
- self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
+ assert np.issubdtype(img.data.dtype, np.integer)
with NamedTemporaryFile(suffix='.tif') as tmp:
img.save(tmp.name, overviews=[], overviews_minsize=2,
overviews_resampling='average')
with rio.open(tmp.name) as f:
# no way to check resampling method from the file
- self.assertEqual(len(f.overviews(1)), 4)
+ assert len(f.overviews(1)) == 4
- @unittest.skipIf(sys.platform.startswith('win'), "'NamedTemporaryFile' not supported on Windows")
+ @pytest.mark.skipif(sys.platform.startswith('win'), reason="'NamedTemporaryFile' not supported on Windows")
def test_save_tags(self):
"""Test saving geotiffs with tags."""
import xarray as xr
@@ -1282,31 +1351,12 @@ class TestXRImage(unittest.TestCase):
'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
tags = {'avg': img.data.mean(), 'current_song': 'disco inferno'}
- self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
+ assert np.issubdtype(img.data.dtype, np.integer)
with NamedTemporaryFile(suffix='.tif') as tmp:
img.save(tmp.name, tags=tags)
tags['avg'] = '37.0'
with rio.open(tmp.name) as f:
- self.assertEqual(f.tags(), tags)
-
- @unittest.skipIf(sys.platform.startswith('win'), "'NamedTemporaryFile' not supported on Windows")
- def test_save_scale_offset(self):
- """Test saving geotiffs with tags."""
- import xarray as xr
- from trollimage import xrimage
- import rasterio as rio
-
- data = xr.DataArray(np.arange(25).reshape(5, 5, 1), dims=[
- 'y', 'x', 'bands'], coords={'bands': ['L']})
- img = xrimage.XRImage(data)
- img.stretch()
- with NamedTemporaryFile(suffix='.tif') as tmp:
- img.save(tmp.name, include_scale_offset_tags=True)
- tags = {'scale': 24.0 / 255, 'offset': 0}
- with rio.open(tmp.name) as f:
- ftags = f.tags()
- for key, val in tags.items():
- self.assertAlmostEqual(float(ftags[key]), val)
+ assert f.tags() == tags
def test_gamma(self):
"""Test gamma correction."""
@@ -1318,12 +1368,12 @@ class TestXRImage(unittest.TestCase):
coords={'bands': ['R', 'G', 'B']})
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})
+ assert np.allclose(img.data.values, arr ** 2)
+ assert 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))
+ assert len(img.data.attrs['enhancement_history']) == 2
+ assert np.allclose(img.data.values, arr)
def test_crude_stretch(self):
"""Check crude stretching."""
@@ -1366,8 +1416,8 @@ class TestXRImage(unittest.TestCase):
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))
+ assert enhs == {'scale': -1, 'offset': 1}
+ assert np.allclose(img.data.values, 1 - arr)
data = xr.DataArray(arr.copy(), dims=['y', 'x', 'bands'],
coords={'bands': ['R', 'G', 'B']})
@@ -1378,7 +1428,7 @@ class TestXRImage(unittest.TestCase):
coords={'bands': ['R', 'G', 'B']})
scale = xr.DataArray(np.array([-1, 1, -1]), dims=['bands'],
coords={'bands': ['R', 'G', 'B']})
- self.assertTrue(np.allclose(img.data.values, (data * scale + offset).values))
+ np.testing.assert_allclose(img.data.values, (data * scale + offset).values)
def test_linear_stretch(self):
"""Test linear stretching with cutoffs."""
@@ -1419,7 +1469,7 @@ class TestXRImage(unittest.TestCase):
[0.962963, 0.962963, 0.962963],
[1.005051, 1.005051, 1.005051]]])
- self.assertTrue(np.allclose(img.data.values, res, atol=1.e-6))
+ np.testing.assert_allclose(img.data.values, res, atol=1.e-6)
def test_histogram_stretch(self):
"""Test histogram stretching."""
@@ -1432,7 +1482,7 @@ class TestXRImage(unittest.TestCase):
img = xrimage.XRImage(data)
img.stretch('histogram')
enhs = img.data.attrs['enhancement_history'][0]
- self.assertDictEqual(enhs, {'hist_equalize': True})
+ assert enhs == {'hist_equalize': True}
res = np.array([[[0., 0., 0.],
[0.04166667, 0.04166667, 0.04166667],
[0.08333333, 0.08333333, 0.08333333],
@@ -1463,7 +1513,7 @@ class TestXRImage(unittest.TestCase):
[0.95833333, 0.95833333, 0.95833333],
[0.99951172, 0.99951172, 0.99951172]]])
- self.assertTrue(np.allclose(img.data.values, res, atol=1.e-6))
+ np.testing.assert_allclose(img.data.values, res, atol=1.e-6)
def test_logarithmic_stretch(self):
"""Test logarithmic strecthing."""
@@ -1476,7 +1526,7 @@ class TestXRImage(unittest.TestCase):
img = xrimage.XRImage(data)
img.stretch(stretch='logarithmic')
enhs = img.data.attrs['enhancement_history'][0]
- self.assertDictEqual(enhs, {'log_factor': 100.0})
+ assert enhs == {'log_factor': 100.0}
res = np.array([[[0., 0., 0.],
[0.35484693, 0.35484693, 0.35484693],
[0.48307087, 0.48307087, 0.48307087],
@@ -1507,7 +1557,7 @@ class TestXRImage(unittest.TestCase):
[0.99085269, 0.99085269, 0.99085269],
[1., 1., 1.]]])
- self.assertTrue(np.allclose(img.data.values, res, atol=1.e-6))
+ np.testing.assert_allclose(img.data.values, res, atol=1.e-6)
def test_weber_fechner_stretch(self):
"""Test applying S=2.3klog10I+C to the data."""
@@ -1520,7 +1570,7 @@ class TestXRImage(unittest.TestCase):
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)})
+ assert 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],
@@ -1551,7 +1601,7 @@ class TestXRImage(unittest.TestCase):
[3.84869831, 3.88467015, 3.92013174],
[3.95509735, 3.98958065, 4.02359478]]])
- self.assertTrue(np.allclose(img.data.values, res, atol=1.e-6))
+ np.testing.assert_allclose(img.data.values, res, atol=1.e-6)
def test_jpeg_save(self):
"""Test saving to jpeg."""
@@ -1606,19 +1656,19 @@ class TestXRImage(unittest.TestCase):
img = xrimage.XRImage(dataset1)
new_img = img.convert(img.mode)
- self.assertIsNotNone(new_img)
+ assert new_img is not None
# make sure it is a copy
- self.assertIsNot(new_img, img)
- self.assertIsNot(new_img.data, img.data)
+ assert new_img is not img
+ assert new_img.data is not img.data
# 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)
+ assert np.issubdtype(img.data.dtype, np.integer)
+ assert img.mode == 'LA'
+ assert len(img.data.coords['bands']) == 2
# 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)
@@ -1628,22 +1678,22 @@ class TestXRImage(unittest.TestCase):
with dask.config.set(scheduler=CustomScheduler(max_computes=1)):
img = xrimage.XRImage(dataset1)
img = img.convert('LA')
- self.assertTrue(img.mode == 'LA')
- self.assertTrue(len(img.data.coords['bands']) == 2)
+ assert img.mode == 'LA'
+ assert len(img.data.coords['bands']) == 2
# make sure the alpha band is all opaque
np.testing.assert_allclose(img.data.sel(bands='A'), 1.)
# LA -> L (float)
with dask.config.set(scheduler=CustomScheduler(max_computes=0)):
img = img.convert('L')
- self.assertTrue(img.mode == 'L')
- self.assertTrue(len(img.data.coords['bands']) == 1)
+ assert img.mode == 'L'
+ assert len(img.data.coords['bands']) == 1
# L -> RGB (float)
with dask.config.set(scheduler=CustomScheduler(max_computes=1)):
img = img.convert('RGB')
- self.assertTrue(img.mode == 'RGB')
- self.assertTrue(len(img.data.coords['bands']) == 3)
+ assert img.mode == 'RGB'
+ assert len(img.data.coords['bands']) == 3
data = img.data.compute()
np.testing.assert_allclose(data.sel(bands=['R']), arr1)
np.testing.assert_allclose(data.sel(bands=['G']), arr1)
@@ -1652,9 +1702,9 @@ class TestXRImage(unittest.TestCase):
# RGB -> RGBA (float)
with dask.config.set(scheduler=CustomScheduler(max_computes=1)):
img = img.convert('RGBA')
- self.assertTrue(img.mode == 'RGBA')
- self.assertTrue(len(img.data.coords['bands']) == 4)
- self.assertTrue(np.issubdtype(img.data.dtype, np.floating))
+ assert img.mode == 'RGBA'
+ assert len(img.data.coords['bands']) == 4
+ assert np.issubdtype(img.data.dtype, np.floating)
data = img.data.compute()
np.testing.assert_allclose(data.sel(bands=['R']), arr1)
np.testing.assert_allclose(data.sel(bands=['G']), arr1)
@@ -1666,11 +1716,11 @@ class TestXRImage(unittest.TestCase):
with dask.config.set(scheduler=CustomScheduler(max_computes=1)):
img = xrimage.XRImage((dataset1 * 150).astype(np.uint8))
img = img.convert('RGB') # L -> RGB
- self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
+ assert np.issubdtype(img.data.dtype, np.integer)
img = img.convert('RGBA')
- self.assertTrue(img.mode == 'RGBA')
- self.assertTrue(len(img.data.coords['bands']) == 4)
- self.assertTrue(np.issubdtype(img.data.dtype, np.integer))
+ assert img.mode == 'RGBA'
+ assert len(img.data.coords['bands']) == 4
+ assert np.issubdtype(img.data.dtype, np.integer)
data = img.data.compute()
np.testing.assert_allclose(data.sel(bands=['R']), (arr1 * 150).astype(np.uint8))
np.testing.assert_allclose(data.sel(bands=['G']), (arr1 * 150).astype(np.uint8))
@@ -1682,8 +1732,8 @@ class TestXRImage(unittest.TestCase):
with dask.config.set(scheduler=CustomScheduler(max_computes=0)):
img = xrimage.XRImage(dataset2)
img = img.convert('RGBA')
- self.assertTrue(img.mode == 'RGBA')
- self.assertTrue(len(img.data.coords['bands']) == 4)
+ assert img.mode == 'RGBA'
+ assert len(img.data.coords['bands']) == 4
# L -> palettize -> RGBA (float)
with dask.config.set(scheduler=CustomScheduler(max_computes=0)):
@@ -1692,20 +1742,21 @@ class TestXRImage(unittest.TestCase):
pal = img.palette
img2 = img.convert('RGBA')
- self.assertTrue(np.issubdtype(img2.data.dtype, np.floating))
- self.assertTrue(img2.mode == 'RGBA')
- self.assertTrue(len(img2.data.coords['bands']) == 4)
+ assert np.issubdtype(img2.data.dtype, np.floating)
+ assert img2.mode == 'RGBA'
+ assert len(img2.data.coords['bands']) == 4
# PA -> RGB (float)
img = xrimage.XRImage(dataset3)
img.palette = pal
with dask.config.set(scheduler=CustomScheduler(max_computes=0)):
img = img.convert('RGB')
- self.assertTrue(np.issubdtype(img.data.dtype, np.floating))
- self.assertTrue(img.mode == 'RGB')
- self.assertTrue(len(img.data.coords['bands']) == 3)
+ assert np.issubdtype(img.data.dtype, np.floating)
+ assert img.mode == 'RGB'
+ assert len(img.data.coords['bands']) == 3
- self.assertRaises(ValueError, img.convert, 'A')
+ with pytest.raises(ValueError):
+ img.convert('A')
# L -> palettize -> RGBA (float) with RGBA colormap
with dask.config.set(scheduler=CustomScheduler(max_computes=0)):
@@ -1713,14 +1764,14 @@ class TestXRImage(unittest.TestCase):
img.palettize(bw)
img2 = img.convert('RGBA')
- self.assertTrue(np.issubdtype(img2.data.dtype, np.floating))
- self.assertTrue(img2.mode == 'RGBA')
- self.assertTrue(len(img2.data.coords['bands']) == 4)
+ assert np.issubdtype(img2.data.dtype, np.floating)
+ assert img2.mode == 'RGBA'
+ assert len(img2.data.coords['bands']) == 4
# convert to RGB, use RGBA from colormap regardless
img2 = img.convert('RGB')
- self.assertTrue(np.issubdtype(img2.data.dtype, np.floating))
- self.assertTrue(img2.mode == 'RGBA')
- self.assertTrue(len(img2.data.coords['bands']) == 4)
+ assert np.issubdtype(img2.data.dtype, np.floating)
+ assert img2.mode == 'RGBA'
+ assert len(img2.data.coords['bands']) == 4
def test_final_mode(self):
"""Test final_mode."""
@@ -1731,8 +1782,8 @@ class TestXRImage(unittest.TestCase):
data = xr.DataArray(np.arange(75).reshape(5, 5, 3), dims=[
'y', 'x', 'bands'], coords={'bands': ['R', 'G', 'B']})
img = xrimage.XRImage(data)
- self.assertEqual(img.final_mode(None), 'RGBA')
- self.assertEqual(img.final_mode(0), 'RGB')
+ assert img.final_mode(None) == 'RGBA'
+ assert img.final_mode(0) == 'RGB'
def test_colorize(self):
"""Test colorize with an RGB colormap."""
@@ -1858,7 +1909,7 @@ class TestXRImage(unittest.TestCase):
img = xrimage.XRImage(data)
img.colorize(bw)
values = img.data.compute()
- self.assertTupleEqual((4, 5, 15), values.shape)
+ assert (4, 5, 15) == values.shape
np.testing.assert_allclose(values[:, 0, 0], [1.0, 1.0, 1.0, 1.0], rtol=1e-03)
np.testing.assert_allclose(values[:, -1, -1], [0.0, 0.0, 0.0, 0.5])
@@ -1900,8 +1951,8 @@ class TestXRImage(unittest.TestCase):
img.palettize(bw)
values = img.data.values
- self.assertTupleEqual((1, 5, 15), values.shape)
- self.assertTupleEqual((2, 4), bw.colors.shape)
+ assert (1, 5, 15) == values.shape
+ assert (2, 4) == bw.colors.shape
def test_stack(self):
"""Test stack."""
@@ -1969,12 +2020,12 @@ class TestXRImage(unittest.TestCase):
[0.5020408, 0.52, 0.5476586, 0.5846154, 0.63027024],
[0.683871, 0.7445614, 0.81142855, 0.8835443, 0.96]]))
- with self.assertRaises(TypeError):
+ with pytest.raises(TypeError):
img1.blend("Salekhard")
wrongimg = xrimage.XRImage(
xr.DataArray(np.zeros((0, 0)), dims=("y", "x")))
- with self.assertRaises(ValueError):
+ with pytest.raises(ValueError):
img1.blend(wrongimg)
def test_replace_luminance(self):
@@ -2031,9 +2082,9 @@ class TestXRImage(unittest.TestCase):
res = img.apply_pil(dummy_fun, 'RGB',
fun_args=('Hey', 'Jude'),
fun_kwargs={'chorus': "La lala lalalala"})
- self.assertEqual(dummy_args, [({}, ), {}])
+ assert dummy_args == [({}, ), {}]
res.data.data.compute()
- self.assertEqual(dummy_args, [(OrderedDict(), 'Hey', 'Jude'), {'chorus': "La lala lalalala"}])
+ assert dummy_args == [(OrderedDict(), 'Hey', 'Jude'), {'chorus': "La lala lalalala"}]
# Test HACK for _burn_overlay
dummy_args = [(OrderedDict(), ), {}]
@@ -2055,6 +2106,44 @@ class TestXRImage(unittest.TestCase):
pil_img.convert.assert_called_with('RGB')
+class TestXRImageSaveScaleOffset(unittest.TestCase):
+ """Test case for saving an image with scale and offset tags."""
+
+ def setUp(self) -> None:
+ """Set up the test case."""
+ import xarray as xr
+ from trollimage import xrimage
+ data = xr.DataArray(np.arange(25).reshape(5, 5, 1), dims=[
+ 'y', 'x', 'bands'], coords={'bands': ['L']})
+ self.img = xrimage.XRImage(data)
+
+ @pytest.mark.skipif(sys.platform.startswith('win'), reason="'NamedTemporaryFile' not supported on Windows")
+ def test_save_scale_offset(self):
+ """Test saving geotiffs with tags."""
+ expected_tags = {'scale': 24.0 / 255, 'offset': 0}
+
+ self.img.stretch()
+ self._save_and_check_tags(expected_tags)
+
+ def _save_and_check_tags(self, expected_tags):
+ with NamedTemporaryFile(suffix='.tif') as tmp:
+ self.img.save(tmp.name, include_scale_offset_tags=True)
+
+ import rasterio as rio
+ with rio.open(tmp.name) as f:
+ ftags = f.tags()
+ for key, val in expected_tags.items():
+ np.testing.assert_almost_equal(float(ftags[key]), val)
+
+ @pytest.mark.skipif(sys.platform.startswith('win'), reason="'NamedTemporaryFile' not supported on Windows")
+ def test_save_scale_offset_from_lists(self):
+ """Test saving geotiffs with tags that come from lists."""
+ expected_tags = {'scale': 23.0 / 255, 'offset': 1}
+
+ self.img.crude_stretch([1], [24])
+ self._save_and_check_tags(expected_tags)
+
+
def _get_tags_after_writing_to_geotiff(data):
from trollimage import xrimage
import rasterio as rio
=====================================
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 -> master, tag: v1.14.0)"
- git_full = "301c1a9dc5721bda9547d0acec112902a5800484"
- git_date = "2020-09-18 11:34:04 +0200"
+ git_refnames = " (HEAD -> master, tag: v1.15.0)"
+ git_full = "8f23f7413b9baf58b0d237e62b9eef73cf409a73"
+ git_date = "2021-03-12 13:57:27 +0100"
keywords = {"refnames": git_refnames, "full": git_full, "date": git_date}
return keywords
=====================================
trollimage/xrimage.py
=====================================
@@ -35,6 +35,7 @@ chunks can be saved in parallel.
import logging
import os
import threading
+import warnings
from contextlib import suppress
import dask
@@ -414,13 +415,13 @@ class XRImage(object):
kwformat = format_kwargs.pop('format', None)
fformat = fformat or kwformat or os.path.splitext(filename)[1][1:]
if fformat in ('tif', 'tiff', 'jp2') and rasterio:
+
return self.rio_save(filename, fformat=fformat,
fill_value=fill_value, compute=compute,
keep_palette=keep_palette, cmap=cmap,
**format_kwargs)
- else:
- return self.pil_save(filename, fformat, fill_value,
- compute=compute, **format_kwargs)
+ return self.pil_save(filename, fformat, fill_value,
+ compute=compute, **format_kwargs)
def rio_save(self, filename, fformat=None, fill_value=None,
dtype=np.uint8, compute=True, tags=None,
@@ -483,7 +484,7 @@ class XRImage(object):
tags = {}
data, mode = self.finalize(fill_value, dtype=dtype,
- keep_palette=keep_palette, cmap=cmap)
+ keep_palette=keep_palette)
data = data.transpose('bands', 'y', 'x')
crs = None
@@ -564,14 +565,7 @@ class XRImage(object):
except AttributeError:
raise ValueError("Colormap is not formatted correctly")
- da_tags = []
- for key, val in list(tags.items()):
- try:
- if isinstance(val.data, da.Array):
- da_tags.append((val.data, RIOTag(r_file, key)))
- tags.pop(key)
- except AttributeError:
- continue
+ tags, da_tags = self._split_regular_vs_lazy_tags(tags, r_file)
r_file.rfile.update_tags(**tags)
r_dataset = RIODataset(r_file, overviews,
@@ -596,6 +590,21 @@ class XRImage(object):
# closing the file
return to_store
+ @staticmethod
+ def _split_regular_vs_lazy_tags(tags, r_file):
+ """Split tags into regular vs lazy (dask) tags."""
+ da_tags = []
+ for key, val in list(tags.items()):
+ try:
+ if isinstance(val.data, da.Array):
+ da_tags.append((val.data, RIOTag(r_file, key)))
+ tags.pop(key)
+ else:
+ tags[key] = val.item()
+ except AttributeError:
+ continue
+ return tags, da_tags
+
def pil_save(self, filename, fformat=None, fill_value=None,
compute=True, **format_kwargs):
"""Save the image to the given *filename* using PIL.
@@ -746,7 +755,9 @@ class XRImage(object):
null_mask = null_mask.any(dim='bands')
null_mask = null_mask.expand_dims('bands')
null_mask['bands'] = ['A']
- # match data dtype
+ # changes to null_mask attrs should not effect the original attrs
+ # XRImage never uses them either
+ null_mask.attrs = {}
return null_mask
def _add_alpha(self, data, alpha=None):
@@ -773,7 +784,27 @@ class XRImage(object):
data.attrs = attrs
return data
- def _scale_to_dtype(self, data, dtype):
+ def _get_dtype_scale_offset(self, dtype, fill_value):
+ dinfo = np.iinfo(dtype)
+ scale = dinfo.max - dinfo.min
+ offset = dinfo.min
+ if fill_value is not None:
+ if fill_value == dinfo.min:
+ # leave the lowest value for fill value only
+ offset = offset + 1
+ scale = scale - 1
+ elif fill_value == dinfo.max:
+ # leave the top value for fill value only
+ scale = scale - 1
+ else:
+ warnings.warn(
+ "Specified fill value will overlap with valid "
+ "data. To avoid this warning specify a fill_value "
+ "that is the minimum or maximum for the data type "
+ "being saved to.")
+ return scale, offset
+
+ def _scale_to_dtype(self, data, dtype, fill_value=None):
"""Scale provided data to dtype range assuming a 0-1 range.
Float input data is assumed to be normalized to a 0 to 1 range.
@@ -789,9 +820,8 @@ class XRImage(object):
data = data.clip(np.iinfo(dtype).min, np.iinfo(dtype).max)
else:
# scale float data (assumed to be 0 to 1) to full integer space
- dinfo = np.iinfo(dtype)
- scale = dinfo.max - dinfo.min
- offset = dinfo.min
+ # leave room for fill value if needed
+ scale, offset = self._get_dtype_scale_offset(dtype, fill_value)
data = data.clip(0, 1) * scale + offset
attrs.setdefault('enhancement_history', list()).append({'scale': scale, 'offset': offset})
data = data.round()
@@ -899,21 +929,54 @@ class XRImage(object):
new_img.palette = self.palette
return new_img
- def _finalize(self, fill_value=None, dtype=np.uint8, keep_palette=False, cmap=None):
- """Wrap around 'finalize' method for backwards compatibility."""
- import warnings
- warnings.warn("'_finalize' is deprecated, use 'finalize' instead.",
- DeprecationWarning)
- return self.finalize(fill_value, dtype, keep_palette, cmap)
-
def final_mode(self, fill_value=None):
"""Get the mode of the finalized image when provided this fill_value."""
if fill_value is None and not self.mode.endswith('A'):
return self.mode + 'A'
- else:
- return self.mode
+ return self.mode
+
+ def _add_alpha_and_scale(self, data, ifill, dtype):
+ alpha = self._create_alpha(data, fill_value=ifill)
+ data = self._scale_to_dtype(data, dtype)
+ data = data.astype(dtype)
+ data = self._add_alpha(data, alpha=alpha)
+ return data
+
+ def _replace_fill_value(self, data, ifill, fill_value, dtype):
+ # Add fill_value after all other calculations have been done to
+ # make sure it is not scaled for the data type
+ if ifill is not None and fill_value is not None:
+ # cast fill value to output type so we don't change data type
+ fill_value = dtype(fill_value)
+ # integer fields have special fill values
+ data = data.where(data != ifill, dtype(fill_value))
+ elif fill_value is not None:
+ data = data.fillna(dtype(fill_value))
- def finalize(self, fill_value=None, dtype=np.uint8, keep_palette=False, cmap=None):
+ return data
+
+ def _get_input_fill_value(self, data):
+ # if the data are integers then this fill value will be used to check for invalid values
+ if np.issubdtype(data, np.integer):
+ return data.attrs.get('_FillValue')
+ return None
+
+ def _scale_and_replace_fill_value(self, data, input_fill_value, fill_value, dtype):
+ # scale float data to the proper dtype
+ # this method doesn't cast yet so that we can keep track of NULL values
+ data = self._scale_to_dtype(data, dtype, fill_value)
+ data = self._replace_fill_value(data, input_fill_value, fill_value, dtype)
+ return data
+
+ def _scale_alpha_or_fill_data(self, data, fill_value, dtype):
+ input_fill_value = self._get_input_fill_value(data)
+ needs_alpha = fill_value is None and not self.mode.endswith('A')
+ if needs_alpha:
+ # We don't have a fill value or an alpha, let's add an alpha
+ return self._add_alpha_and_scale(data, input_fill_value, dtype)
+ return self._scale_and_replace_fill_value(data, input_fill_value, fill_value, dtype)
+
+ def finalize(self, fill_value=None, dtype=np.uint8, keep_palette=False):
"""Finalize the image to be written to an output file.
This adds an alpha band or fills data with a fill_value (if
@@ -925,17 +988,44 @@ class XRImage(object):
determined by a special ``_FillValue`` attribute in the
``DataArray`` ``.attrs`` dictionary.
+ Args:
+ fill_value (int or float or None): Output value to use to
+ represent invalid or missing pixels. By default this is
+ `None` meaning an Alpha channel will be used to represent
+ the invalid values; transparent for invalid, opaque
+ otherwise. Some output formats do not support alpha channels
+ so a ``fill_value`` must be provided. This is determined by
+ the underlying library doing the writing (pillow or rasterio).
+ If specified, it should be the minimum or maximum of the
+ ``dtype`` (ex. 0 or 255 for uint8). Floating point image data
+ is then scaled to fit the remainder of the data type space.
+ Integer image data will **not** be scaled. For example, a
+ ``dtype`` of ``numpy.uint8`` and a ``fill_value`` of 0 will
+ result in floating-point data being scaled linearly from 1 to 255.
+ dtype (numpy.dtype): Output data type to convert the current image
+ data to. Default is unsigned 8-bit integer
+ (:class:`numpy.uint8`).
+ keep_palette (bool): Whether to convert a paletted image to RGB/A
+ or not. If ``False`` (default) then ``P`` mode images will be
+ converted to ``RGB`` and ``PA`` will be converted to ``RGBA``.
+ If ``True``, images with mode ``P`` or ``PA`` are kept as is
+ and will not be scaled in order for their index values into a
+ palette to be maintained. This flag should always be ``False``
+ for non-paletted images.
+
"""
if keep_palette and not self.mode.startswith('P'):
keep_palette = False
if not keep_palette:
+ finalize_kwargs = dict(
+ fill_value=fill_value, dtype=dtype,
+ keep_palette=keep_palette,
+ )
if self.mode == "P":
- return self.convert("RGB").finalize(fill_value=fill_value, dtype=dtype,
- keep_palette=keep_palette, cmap=cmap)
+ return self.convert("RGB").finalize(**finalize_kwargs)
if self.mode == "PA":
- return self.convert("RGBA").finalize(fill_value=fill_value, dtype=dtype,
- keep_palette=keep_palette, cmap=cmap)
+ return self.convert("RGBA").finalize(**finalize_kwargs)
if np.issubdtype(dtype, np.floating) and fill_value is None:
logger.warning("Image with floats cannot be transparent, so "
@@ -947,34 +1037,10 @@ class XRImage(object):
final_data.attrs['enhancement_history'] = list(self.data.attrs['enhancement_history'])
except KeyError:
pass
- attrs = final_data.attrs
- # if the data are integers then this fill value will be used to check for invalid values
with xr.set_options(keep_attrs=True):
- ifill = final_data.attrs.get('_FillValue') if np.issubdtype(final_data, np.integer) else None
+ attrs = final_data.attrs
if not keep_palette:
- if fill_value is None and not self.mode.endswith('A'):
- # We don't have a fill value or an alpha, let's add an alpha
- alpha = self._create_alpha(final_data, fill_value=ifill)
- final_data = self._scale_to_dtype(final_data, dtype)
- attrs = final_data.attrs
- final_data = final_data.astype(dtype)
- final_data = self._add_alpha(final_data, alpha=alpha)
- final_data.attrs = attrs
- else:
- # scale float data to the proper dtype
- # this method doesn't cast yet so that we can keep track of NULL values
- final_data = self._scale_to_dtype(final_data, dtype)
- attrs = final_data.attrs
- # Add fill_value after all other calculations have been done to
- # make sure it is not scaled for the data type
- if ifill is not None and fill_value is not None:
- # cast fill value to output type so we don't change data type
- fill_value = dtype(fill_value)
- # integer fields have special fill values
- final_data = final_data.where(final_data != ifill, dtype(fill_value))
- elif fill_value is not None:
- final_data = final_data.fillna(dtype(fill_value))
-
+ final_data = self._scale_alpha_or_fill_data(final_data, fill_value, dtype)
final_data = final_data.astype(dtype)
final_data.attrs = attrs
@@ -1394,10 +1460,10 @@ class XRImage(object):
raise ValueError(
"Expected self.mode='RGBA', got {md!s}".format(
md=self.mode))
- elif not isinstance(src, XRImage):
+ if not isinstance(src, XRImage):
raise TypeError("Expected XRImage, got {tp!s}".format(
tp=type(src)))
- elif src.mode != "RGBA":
+ if src.mode != "RGBA":
raise ValueError("Expected src.mode='RGBA', got {sm!s}".format(
sm=src.mode))
View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/-/compare/e1e62101563c25aca11a916380a75269235ba751...df6560502fed532e9c7914f07e81d184c9189149
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/trollimage/-/compare/e1e62101563c25aca11a916380a75269235ba751...df6560502fed532e9c7914f07e81d184c9189149
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/20210321/e54f2de4/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list