[Git][debian-gis-team/pyninjotiff][master] 8 commits: New upstream version 0.4.0
Antonio Valentino (@antonio.valentino)
gitlab at salsa.debian.org
Thu Nov 11 09:34:58 GMT 2021
Antonio Valentino pushed to branch master at Debian GIS Project / pyninjotiff
Commits:
35b2fe9e by Antonio Valentino at 2021-11-11T07:10:49+00:00
New upstream version 0.4.0
- - - - -
82224352 by Antonio Valentino at 2021-11-11T07:10:50+00:00
Update upstream source from tag 'upstream/0.4.0'
Update to upstream version '0.4.0'
with Debian dir f0446cc122a0ff54b4856a0d82d30354fbf491ef
- - - - -
8cd6b933 by Antonio Valentino at 2021-11-11T07:11:28+00:00
New usptream release
- - - - -
aa6cf5c9 by Antonio Valentino at 2021-11-11T07:21:32+00:00
New 0001-Use-setup.py-generated-by-poetry2setup.patch
- - - - -
3ace01e6 by Antonio Valentino at 2021-11-11T08:34:19+00:00
Drop command to copy test files in d/rules
- - - - -
2b4079c0 by Antonio Valentino at 2021-11-11T09:11:36+00:00
Do not install example files
- - - - -
1f16773d by Antonio Valentino at 2021-11-11T09:26:10+00:00
Add debian/tests
- - - - -
f91a9ff6 by Antonio Valentino at 2021-11-11T09:26:43+00:00
Set distribution to unstable
- - - - -
12 changed files:
- + .github/workflows/ci.yaml
- + .github/workflows/deploy-sdist.yaml
- debian/changelog
- + debian/patches/0001-Use-setup.py-generated-by-poetry2setup.patch
- + debian/patches/series
- debian/rules
- + debian/tests/control
- + debian/tests/python3
- pyninjotiff/ninjotiff.py
- pyninjotiff/tests/test_ninjotiff.py
- + pyproject.toml
- − setup.py
Changes:
=====================================
.github/workflows/ci.yaml
=====================================
@@ -0,0 +1,43 @@
+name: CI
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: true
+ matrix:
+ os: ["ubuntu-latest"]
+ python-version: ["3.7", "3.8", "3.9"]
+
+ env:
+ PYTHON_VERSION: ${{ matrix.python-version }}
+ OS: ${{ matrix.os }}
+ ACTIONS_ALLOW_UNSECURE_COMMANDS: true
+
+ steps:
+ - name: Checkout source
+ uses: actions/checkout at v2
+
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python at v2
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Install Poetry
+ uses: abatilo/actions-poetry at v2.0.0
+
+ - name: Install dependencies
+ run: poetry install
+
+ - name: Run pytest
+ run: poetry run pytest --cov=pyninjotiff pyninjotiff/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
+
=====================================
.github/workflows/deploy-sdist.yaml
=====================================
@@ -0,0 +1,27 @@
+name: Deploy sdist
+
+on:
+ release:
+ types:
+ - published
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout source
+ uses: actions/checkout at v2
+
+ - name: Install Poetry
+ uses: abatilo/actions-poetry at v2.0.0
+
+ - name: Create sdist and wheel
+ run: poetry build
+
+ - 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 }}
=====================================
debian/changelog
=====================================
@@ -1,3 +1,16 @@
+pyninjotiff (0.4.0-1) unstable; urgency=medium
+
+ * New upstream release.
+ * debian/patches:
+ - new 0001-Use-setup.py-generated-by-poetry2setup.patch,
+ (upstream switched to poetry).
+ * debian/rules:
+ - drop command to copy tets files (no longer needed)
+ - do not install example files.
+ * Add debian/tests.
+
+ -- Antonio Valentino <antonio.valentino at tiscali.it> Thu, 11 Nov 2021 09:26:24 +0000
+
pyninjotiff (0.3.0-2) unstable; urgency=medium
[ Bas Couwenberg ]
=====================================
debian/patches/0001-Use-setup.py-generated-by-poetry2setup.patch
=====================================
@@ -0,0 +1,53 @@
+From: Antonio Valentino <antonio.valentino at tiscali.it>
+Date: Thu, 11 Nov 2021 07:19:37 +0000
+Subject: Use setup.py generated by poetry2setup
+
+Forwarded: not-needed
+---
+ setup.py | 37 +++++++++++++++++++++++++++++++++++++
+ 1 file changed, 37 insertions(+)
+ create mode 100644 setup.py
+
+diff --git a/setup.py b/setup.py
+new file mode 100644
+index 0000000..27b7a5d
+--- /dev/null
++++ b/setup.py
+@@ -0,0 +1,37 @@
++# -*- coding: utf-8 -*-
++# Automatically generated with poetry2setup
++from setuptools import setup
++
++packages = \
++['pyninjotiff', 'pyninjotiff.tests']
++
++package_data = \
++{'': ['*']}
++
++install_requires = \
++['dask[array]>=2021.9.1,<2022.0.0',
++ 'numpy>=1.6',
++ 'pyproj>=3.2.1,<4.0.0',
++ 'pyresample>=1.21.1,<2.0.0',
++ 'trollimage>=1.15.1,<2.0.0',
++ 'xarray>=0.19.0,<0.20.0']
++
++setup_kwargs = {
++ 'name': 'pyninjotiff',
++ 'version': '0.4.0',
++ 'description': 'Python Ninjo TIFF writing library',
++ 'long_description': None,
++ 'author': 'Martin Raspaud',
++ 'author_email': 'martin.raspaud at smhi.se',
++ 'maintainer': None,
++ 'maintainer_email': None,
++ 'url': None,
++ 'packages': packages,
++ 'package_data': package_data,
++ 'install_requires': install_requires,
++ 'python_requires': '>=3.7,<4.0',
++}
++
++
++setup(**setup_kwargs)
++
=====================================
debian/patches/series
=====================================
@@ -0,0 +1 @@
+0001-Use-setup.py-generated-by-poetry2setup.patch
=====================================
debian/rules
=====================================
@@ -5,7 +5,7 @@
#export DH_VERBOSE=1
export PYBUILD_NAME=pyninjotiff
-export PYBUILD_BEFORE_TEST=cp -r {dir}/pyninjotiff/tests {build_dir}
+export PYBUILD_AFTER_INSTALL=rm -rf "{destdir}"/usr/lib/python3*/*-packages/pyninjotiff/*ninjotif*example*
%:
dh $@ --with python3 --buildsystem=pybuild
=====================================
debian/tests/control
=====================================
@@ -0,0 +1,2 @@
+Tests: python3
+Depends: @, python3-pytest
=====================================
debian/tests/python3
=====================================
@@ -0,0 +1,12 @@
+#!/bin/sh
+set -efu
+
+PYS=${PYS:-"$(py3versions -r 2>/dev/null)"}
+TESTPKG=${TESTPKG:-pyninjotiff}
+
+cd "$AUTOPKGTEST_TMP"
+
+for py in $PYS; do
+ echo "=== $py ==="
+ $py -m pytest --pyargs ${TESTPKG}
+done
=====================================
pyninjotiff/ninjotiff.py
=====================================
@@ -44,6 +44,7 @@ import numpy as np
from dask import delayed
import dask.array as da
import xarray as xr
+from contextlib import suppress
from pyproj import Proj
from pyresample.utils import proj4_radius_parameters
@@ -199,10 +200,7 @@ class ProductConfigs(object):
"""Call the product config."""
if force_read:
self.read_config(config_filename)
- if product_name in self._products:
- return self._products[product_name]
- else:
- return {}
+ return self._products.get(product_name, {})
@property
def product_names(self):
@@ -244,12 +242,12 @@ class ProductConfigs(object):
name_ = os.path.abspath(os.path.expanduser(fname))
if os.path.isfile(name_):
return name_
- else:
- home_ = os.path.dirname(os.path.abspath(__file__))
- penv_ = os.environ.get('PPP_CONFIG_DIR', '')
- for fname_ in [os.path.join(x, name_) for x in (home_, penv_)]:
- if os.path.isfile(fname_):
- return fname_
+
+ home_ = os.path.dirname(os.path.abspath(__file__))
+ penv_ = os.environ.get('PPP_CONFIG_DIR', '')
+ for fname_ in [os.path.join(x, name_) for x in (home_, penv_)]:
+ if os.path.isfile(fname_):
+ return fname_
# raise ValueError("Could not find a Ninjo tiff config file")
@@ -262,14 +260,14 @@ def _get_physic_value(physic_unit):
# return Ninjo's physics unit and value.
if physic_unit.upper() in ('K', 'KELVIN'):
return 'Kelvin', 'T'
- elif physic_unit.upper() in ('C', 'CELSIUS'):
+ if physic_unit.upper() in ('C', 'CELSIUS'):
return 'Celsius', 'T'
- elif physic_unit == '%':
+ if physic_unit == '%':
return physic_unit, 'Reflectance'
- elif physic_unit.upper() in ('MW M-2 SR-1 (CM-1)-1',):
+ if physic_unit.upper() in ('MW M-2 SR-1 (CM-1)-1',):
return physic_unit, 'Radiance'
- else:
- return physic_unit, 'Unknown'
+
+ return physic_unit, 'Unknown'
def _get_projection_name(area_def):
@@ -277,14 +275,14 @@ def _get_projection_name(area_def):
proj_name = area_def.proj_dict['proj']
if proj_name in ('eqc',):
return 'PLAT'
- elif proj_name in ('merc',):
+ if proj_name in ('merc',):
return 'MERC'
- elif proj_name in ('stere',):
+ if proj_name in ('stere',):
lat_0 = area_def.proj_dict['lat_0']
if lat_0 < 0:
return 'SPOL'
- else:
- return 'NPOL'
+
+ return 'NPOL'
# FIXME: this feels like a hack
return area_def.proj_id.split('_')[-1]
@@ -366,19 +364,8 @@ def _finalize(img, dtype=np.uint8, value_range_measurement_unit=None,
if isinstance(img, np.ma.MaskedArray):
data = img.channels[0]
else:
- # TODO: check what is the correct fill value for NinJo!
- if fill_value is not None:
- log.debug("Forcing fill value to %s", fill_value)
- # Go back to the masked_array for compatibility
- # with the following part of the code.
- if (np.issubdtype(img.data.dtype, np.integer)
- and '_FillValue' in img.data.attrs):
- nodata_value = img.data.attrs['_FillValue']
- if fill_value is None:
- fill_value = nodata_value
- data = img.data.squeeze()
- else:
- data = img.data.squeeze()
+ fill_value = _get_fill_value_from_arg_and_image(fill_value, img)
+ data = img.data.squeeze()
fill_value = fill_value if fill_value is not None else np.iinfo(dtype).min
@@ -438,7 +425,7 @@ def _finalize(img, dtype=np.uint8, value_range_measurement_unit=None,
return data, scale, offset, fill_value
- elif img.mode == 'RGB':
+ if img.mode == 'RGB':
if isinstance(img, np.ma.MaskedArray):
channels, fill_value = img._finalize(dtype)
else:
@@ -449,20 +436,30 @@ def _finalize(img, dtype=np.uint8, value_range_measurement_unit=None,
return data, 1.0, 0.0, fill_value
- elif img.mode == 'RGBA':
+ if img.mode == 'RGBA':
if not isinstance(img, np.ma.MaskedArray):
- raise NotImplementedError("The 'RGBA' case has not been updated to xarray")
- channels, fill_value = img._finalize(dtype)
- fill_value = fill_value or (0, 0, 0, 0)
- data = np.dstack((channels[0].filled(fill_value[0]),
- channels[1].filled(fill_value[1]),
- channels[2].filled(fill_value[2]),
- channels[3].filled(fill_value[3])))
- return data, 1.0, 0.0, fill_value[0]
-
- elif img.mode == 'P':
+ data, mode = img.finalize(fill_value=fill_value, dtype=dtype)
+ data = data.transpose('y', 'x', 'bands')
+ fill_value = fill_value or 0
+ else:
+ channels, fill_value = img._finalize(dtype)
+ fill_value = fill_value or (0, 0, 0, 0)
+ data = np.dstack((channels[0].filled(fill_value[0]),
+ channels[1].filled(fill_value[1]),
+ channels[2].filled(fill_value[2]),
+ channels[3].filled(fill_value[3])))
+ fill_value = fill_value[0]
+ return data, 1.0, 0.0, fill_value
+
+ if img.mode == 'P':
if not isinstance(img, np.ma.MaskedArray):
- raise NotImplementedError("The 'P' case has not been updated to xarray")
+ data = img.data.squeeze()
+ fill_value = _get_fill_value_from_arg_and_image(fill_value, img)
+ fill_value = fill_value if fill_value is not None else np.iinfo(dtype).min
+ data = data.where(data.notnull(), fill_value)
+
+ return data, 1.0, 0.0, 0
+ # Numpy masked array
fill_value = 0
data = img.channels[0]
if isinstance(data, np.ma.core.MaskedArray):
@@ -472,9 +469,17 @@ def _finalize(img, dtype=np.uint8, value_range_measurement_unit=None,
(data.min(), data.mean(), data.max()))
return data, 1.0, 0.0, fill_value
- else:
- raise ValueError("Don't know how to handle image mode '%s'" %
- str(img.mode))
+ raise ValueError("Don't know how to handle image mode '%s'" %
+ str(img.mode))
+
+
+def _get_fill_value_from_arg_and_image(fill_value, img):
+ if fill_value is not None:
+ log.debug("Forcing fill value to %s", fill_value)
+ elif (np.issubdtype(img.data.dtype, np.integer)
+ and '_FillValue' in img.data.attrs):
+ fill_value = img.data.attrs['_FillValue']
+ return fill_value
def save(img, filename, ninjo_product_name=None, writer_options=None, data_is_scaled_01=True,
@@ -548,18 +553,27 @@ def save(img, filename, ninjo_product_name=None, writer_options=None, data_is_sc
kwargs['image_dt'] = time_slot
kwargs['is_calibrated'] = True
if img.mode == 'P' and 'cmap' not in kwargs:
- r, g, b = zip(*img.palette)
- r = list((np.array(r) * 255).astype(np.uint8))
- g = list((np.array(g) * 255).astype(np.uint8))
- b = list((np.array(b) * 255).astype(np.uint8))
- if len(r) < 256:
- r += [0] * (256 - len(r))
- g += [0] * (256 - len(g))
- b += [0] * (256 - len(b))
- kwargs['cmap'] = r, g, b
+ kwargs['cmap'] = make_palette(img)
return write(data, filename, area_def, ninjo_product_name, **kwargs)
+def make_palette(img):
+ """Make a tiff palette from the image's palette."""
+ try:
+ r, g, b = zip(*img.palette)
+ except ValueError:
+ r, g, b, a = zip(*img.palette)
+ log.warning("Ignoring palette's alpha.")
+ r = list((np.array(r) * 255).astype(np.uint8))
+ g = list((np.array(g) * 255).astype(np.uint8))
+ b = list((np.array(b) * 255).astype(np.uint8))
+ if len(r) < 256:
+ r += [0] * (256 - len(r))
+ g += [0] * (256 - len(g))
+ b += [0] * (256 - len(b))
+ return r, g, b
+
+
def ninjo_nav_parameters(options, area_def):
"""Fill options with the navigation parameter in Ninjo format."""
# TODO: add altitude if available
@@ -610,7 +624,7 @@ def write(image_data, output_fn, area_def, product_name=None, **kwargs):
overwrite config file.
:Parameters:
- image_data : 2D numpy array
+ image_data : 2D or 3D xr.DataArray
Satellite image data to be put into the NinJo compatible tiff
output_fn : str
The name of the TIFF file to be created
@@ -658,9 +672,10 @@ def write(image_data, output_fn, area_def, product_name=None, **kwargs):
options = {}
options.update(kwargs) # Update/overwrite with passed arguments
- if len(image_data.sizes) == 2:
- options['min_gray_val'] = image_data.data.min().astype(int)
- options['max_gray_val'] = image_data.data.max().astype(int)
+ with suppress(ValueError):
+ if image_data.coords["bands"] in ["L", "P"]:
+ options['min_gray_val'] = image_data.data.min().astype(int)
+ options['max_gray_val'] = image_data.data.max().astype(int)
ninjo_nav_parameters(options, area_def)
@@ -790,10 +805,10 @@ def _write(image_data, output_fn, write_rgb=False, **kwargs):
if reverse:
return [[x for x in range(65535, -1, -1)]] * 3
return [[x for x in range(65536)]] * 3
- else:
- if reverse:
- return [[x * 256 for x in range(255, -1, -1)]] * 3
- return [[x * 256 for x in range(256)]] * 3
+
+ if reverse:
+ return [[x * 256 for x in range(255, -1, -1)]] * 3
+ return [[x * 256 for x in range(256)]] * 3
def _eval_or_none(key, eval_func):
try:
@@ -858,10 +873,8 @@ def _write(image_data, output_fn, write_rgb=False, **kwargs):
# Handle colormap or not.
min_is_white = False
if not write_rgb and not cmap:
- if physic_value == 'T' and inv_def_temperature_cmap:
- reverse = True
- else:
- reverse = False
+ reverse = (physic_value == 'T' and inv_def_temperature_cmap)
+
if np.iinfo(image_data.dtype).bits == 8:
# Always generate colormap for 8 bit gray scale.
cmap = _default_colormap(reverse)
@@ -1060,8 +1073,8 @@ def _write(image_data, output_fn, write_rgb=False, **kwargs):
factor **= 2
if kwargs.get('compute', True):
return tiffwrite(output_fn, image_data, args, tifargs, factors)
- else:
- return delayed(tiffwrite)(output_fn, image_data, args, tifargs, factors)
+
+ return delayed(tiffwrite)(output_fn, image_data, args, tifargs, factors)
def tiffwrite(output_fn, image_data, args, tifargs, ovw_factors):
=====================================
pyninjotiff/tests/test_ninjotiff.py
=====================================
@@ -22,13 +22,17 @@
"""Test the ninjotiff writing."""
-import numpy as np
+import colorsys
import datetime
import tempfile
-import xarray as xr
+
import dask.array as da
-import colorsys
+import numpy as np
import pytest
+import xarray as xr
+from pyninjotiff.ninjotiff import save
+from pyninjotiff.tifffile import TiffFile
+from trollimage.xrimage import XRImage
TIME = datetime.datetime.utcnow()
DELETE_FILES = True
@@ -53,6 +57,11 @@ class FakeImage(object):
res = self.data
return [res.astype(dtype)]
+ @property
+ def palette(self):
+ """Return the palette of the image."""
+ return self.data.attrs['palette']
+
class FakeArea(object):
"""Fake area class."""
@@ -66,17 +75,17 @@ class FakeArea(object):
self.pixel_size_y = (extent[3] - extent[1]) / y_size
+STEREOGRAPHIC_AREA = FakeArea({'ellps': 'WGS84', 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 0.0, 'proj': 'stere'},
+ (-1000000.0, -4500000.0, 2072000.0, -1428000.0),
+ 1024, 1024)
+
+
def test_write_bw():
"""Test saving a BW image.
Reflectances.
"""
- from pyninjotiff.ninjotiff import save
- from pyninjotiff.tifffile import TiffFile
-
- area = FakeArea({'ellps': 'WGS84', 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 0.0, 'proj': 'stere'},
- (-1000000.0, -4500000.0, 2072000.0, -1428000.0),
- 1024, 1024)
+ area = STEREOGRAPHIC_AREA
scale = 1.0 / 120
offset = 0.0
attrs = dict([('resolution', 1050),
@@ -122,12 +131,7 @@ def test_write_bw():
def test_write_bw_inverted_ir():
"""Test saving a BW image."""
- from pyninjotiff.ninjotiff import save
- from pyninjotiff.tifffile import TiffFile
-
- area = FakeArea({'ellps': 'WGS84', 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 0.0, 'proj': 'stere'},
- (-1000000.0, -4500000.0, 2072000.0, -1428000.0),
- 1024, 1024)
+ area = STEREOGRAPHIC_AREA
scale = 1.0 / 120
offset = 70.0 / 120
attrs = dict([('resolution', 1050),
@@ -173,12 +177,7 @@ def test_write_bw_inverted_ir():
def test_write_bw_fill():
"""Test saving a BW image with transparency."""
- from pyninjotiff.ninjotiff import save
- from pyninjotiff.tifffile import TiffFile
-
- area = FakeArea({'ellps': 'WGS84', 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 0.0, 'proj': 'stere'},
- (-1000000.0, -4500000.0, 2072000.0, -1428000.0),
- 1024, 1024)
+ area = STEREOGRAPHIC_AREA
scale = 1.0 / 120
offset = 0.0
attrs = dict([('resolution', 1050),
@@ -229,12 +228,7 @@ def test_write_bw_fill():
def test_write_bw_inverted_ir_fill():
"""Test saving a BW image with transparency."""
- from pyninjotiff.ninjotiff import save
- from pyninjotiff.tifffile import TiffFile
-
- area = FakeArea({'ellps': 'WGS84', 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 0.0, 'proj': 'stere'},
- (-1000000.0, -4500000.0, 2072000.0, -1428000.0),
- 1024, 1024)
+ area = STEREOGRAPHIC_AREA
scale = 1.0 / 120
offset = 70.0 / 120
attrs = dict([('resolution', 1050),
@@ -285,27 +279,10 @@ def test_write_bw_inverted_ir_fill():
def test_write_rgb():
"""Test saving a non-trasparent RGB."""
- from pyninjotiff.ninjotiff import save
- from pyninjotiff.tifffile import TiffFile
+ area = STEREOGRAPHIC_AREA
- area = FakeArea({'ellps': 'WGS84', 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 0.0, 'proj': 'stere'},
- (-1000000.0, -4500000.0, 2072000.0, -1428000.0),
- 1024, 1024)
-
- x_size, y_size = 1024, 1024
- arr = np.zeros((3, y_size, x_size))
- radius = min(x_size, y_size) / 2.0
- centre = x_size / 2, y_size / 2
-
- for x in range(x_size):
- for y in range(y_size):
- rx = x - centre[0]
- ry = y - centre[1]
- s = ((x - centre[0])**2.0 + (y - centre[1])**2.0)**0.5 / radius
- if s <= 1.0:
- h = ((np.arctan2(ry, rx) / np.pi) + 1.0) / 2.0
- rgb = colorsys.hsv_to_rgb(h, s, 1.0)
- arr[:, y, x] = np.array(rgb)
+ fill_value = 0.0
+ arr = create_hsv_color_disk(fill_value)
attrs = dict([('platform_name', 'NOAA-18'),
('resolution', 1050),
@@ -336,7 +313,6 @@ def test_write_rgb():
data = xr.DataArray(data, coords={'bands': ['R', 'G', 'B']}, dims=[
'bands', 'y', 'x'], attrs=attrs)
- from trollimage.xrimage import XRImage
img = XRImage(data)
with tempfile.NamedTemporaryFile(delete=DELETE_FILES) as tmpfile:
@@ -351,31 +327,30 @@ def test_write_rgb():
arr[idx, :, :] * 255).astype(np.uint8))
-def test_write_rgb_with_a():
- """Test saving a transparent RGB."""
- from pyninjotiff.ninjotiff import save
- from pyninjotiff.tifffile import TiffFile
-
- area = FakeArea({'ellps': 'WGS84', 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 0.0, 'proj': 'stere'},
- (-1000000.0, -4500000.0, 2072000.0, -1428000.0),
- 1024, 1024)
-
+def create_hsv_color_disk(fill_value):
+ """Create an HSV colordisk."""
x_size, y_size = 1024, 1024
- arr = np.zeros((3, y_size, x_size))
+ arr = np.full((3, y_size, x_size), fill_value)
radius = min(x_size, y_size) / 2.0
centre = x_size / 2, y_size / 2
-
for x in range(x_size):
for y in range(y_size):
rx = x - centre[0]
ry = y - centre[1]
- s = ((x - centre[0])**2.0 + (y - centre[1])**2.0)**0.5 / radius
+ s = ((x - centre[0]) ** 2.0 + (y - centre[1]) ** 2.0) ** 0.5 / radius
if s <= 1.0:
h = ((np.arctan2(ry, rx) / np.pi) + 1.0) / 2.0
rgb = colorsys.hsv_to_rgb(h, s, 1.0)
arr[:, y, x] = np.array(rgb)
- else:
- arr[:, y, x] = np.nan
+ return arr
+
+
+def test_write_rgb_with_a():
+ """Test saving a transparent RGB."""
+ area = STEREOGRAPHIC_AREA
+
+ fill_value = np.nan
+ arr = create_hsv_color_disk(fill_value)
attrs = dict([('platform_name', 'NOAA-18'),
('resolution', 1050),
@@ -406,7 +381,7 @@ def test_write_rgb_with_a():
data = xr.DataArray(data, coords={'bands': ['R', 'G', 'B']}, dims=[
'bands', 'y', 'x'], attrs=attrs)
- from trollimage.xrimage import XRImage
+
img = XRImage(data)
with tempfile.NamedTemporaryFile(delete=DELETE_FILES) as tmpfile:
filename = tmpfile.name
@@ -423,27 +398,10 @@ def test_write_rgb_with_a():
def test_write_rgb_tb():
"""Test saving a non-trasparent RGB with thumbnails."""
- from pyninjotiff.ninjotiff import save
- from pyninjotiff.tifffile import TiffFile
+ area = STEREOGRAPHIC_AREA
- area = FakeArea({'ellps': 'WGS84', 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 0.0, 'proj': 'stere'},
- (-1000000.0, -4500000.0, 2072000.0, -1428000.0),
- 1024, 1024)
-
- x_size, y_size = 1024, 1024
- arr = np.zeros((3, y_size, x_size))
- radius = min(x_size, y_size) / 2.0
- centre = x_size / 2, y_size / 2
-
- for x in range(x_size):
- for y in range(y_size):
- rx = x - centre[0]
- ry = y - centre[1]
- s = ((x - centre[0])**2.0 + (y - centre[1])**2.0)**0.5 / radius
- if s <= 1.0:
- h = ((np.arctan2(ry, rx) / np.pi) + 1.0) / 2.0
- rgb = colorsys.hsv_to_rgb(h, s, 1.0)
- arr[:, y, x] = np.array(rgb)
+ fill_value = 0.0
+ arr = create_hsv_color_disk(fill_value)
attrs = dict([('platform_name', 'NOAA-18'),
('resolution', 1050),
@@ -475,7 +433,6 @@ def test_write_rgb_tb():
data = xr.DataArray(data, coords={'bands': ['R', 'G', 'B']}, dims=[
'bands', 'y', 'x'], attrs=attrs)
- from trollimage.xrimage import XRImage
img = XRImage(data)
with tempfile.NamedTemporaryFile(delete=DELETE_FILES) as tmpfile:
@@ -568,12 +525,7 @@ def test_write_rgb_tb():
@pytest.mark.skip(reason="this is no implemented yet.")
def test_write_rgb_classified():
"""Test saving a transparent RGB."""
- from pyninjotiff.ninjotiff import save
- from pyninjotiff.tifffile import TiffFile
-
- area = FakeArea({'ellps': 'WGS84', 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 0.0, 'proj': 'stere'},
- (-1000000.0, -4500000.0, 2072000.0, -1428000.0),
- 1024, 1024)
+ area = STEREOGRAPHIC_AREA
x_size, y_size = 1024, 1024
arr = np.zeros((3, y_size, x_size))
@@ -606,7 +558,6 @@ def test_write_rgb_classified():
data = da.concatenate((data1, datanan, data2), axis=1)
data = xr.DataArray(data, coords={'bands': ['P']}, dims=['bands', 'y', 'x'], attrs=attrs)
- from trollimage.xrimage import XRImage
img = XRImage(data)
with tempfile.NamedTemporaryFile(delete=DELETE_FILES) as tmpfile:
filename = tmpfile.name
@@ -621,6 +572,60 @@ def test_write_rgb_classified():
np.testing.assert_allclose(res[:, :, 3] == 0, np.isnan(arr[0, :, :]))
+def test_write_rgba():
+ """Test saving an RGBA image."""
+ area = STEREOGRAPHIC_AREA
+
+ fill_value = np.nan
+ arr = create_hsv_color_disk(fill_value)
+
+ attrs = dict([('platform_name', 'NOAA-18'),
+ ('resolution', 1050),
+ ('polarization', None),
+ ('start_time', TIME - datetime.timedelta(minutes=55)),
+ ('end_time', TIME - datetime.timedelta(minutes=50)),
+ ('level', None),
+ ('sensor', 'avhrr-3'),
+ ('ancillary_variables', []),
+ ('area', area),
+ ('wavelength', None),
+ ('optional_datasets', []),
+ ('standard_name', 'overview'),
+ ('name', 'overview'),
+ ('prerequisites', [0.6, 0.8, 10.8]),
+ ('optional_prerequisites', []),
+ ('calibration', None),
+ ('modifiers', None),
+ ('mode', 'RGBA'),
+ ('enhancement_history', [{'scale': np.array([1, 1, -1]), 'offset': np.array([0, 0, 1])},
+ {'scale': np.array([0.0266347, 0.03559078, 0.01329783]),
+ 'offset': np.array([-0.02524969, -0.01996642, 3.8918446])},
+ {'gamma': 1.6}])])
+
+ kwargs = {'compute': True, 'fill_value': None, 'sat_id': 6300014,
+ 'chan_id': 6500015, 'data_cat': 'PPRN', 'data_source': 'SMHI', 'nbits': 8}
+ alpha = np.where(np.isnan(arr[0, :, :]), 0, 1)
+ arr = np.nan_to_num(arr)
+ arr = np.vstack((arr, alpha[np.newaxis, :, :]))
+ data = da.from_array(arr.clip(0, 1), chunks=1024)
+
+ data = xr.DataArray(data, coords={'bands': ['R', 'G', 'B', 'A']}, dims=[
+ 'bands', 'y', 'x'], attrs=attrs)
+
+ img = XRImage(data)
+ with tempfile.NamedTemporaryFile(delete=DELETE_FILES) as tmpfile:
+ filename = tmpfile.name
+ if not DELETE_FILES:
+ print(filename)
+ save(img, filename, data_is_scaled_01=True, **kwargs)
+ tif = TiffFile(filename)
+ res = tif[0].asarray()
+ for idx in range(4):
+ np.testing.assert_allclose(res[:, :, idx], np.round(
+ np.nan_to_num(arr[idx, :, :]) * 255).astype(np.uint8))
+ np.testing.assert_allclose(res[:, :, 3] == 0, alpha == 0)
+
+
def test_write_bw_colormap():
"""Test saving a BW image with a colormap.
@@ -628,12 +633,7 @@ def test_write_bw_colormap():
Reflectances are 0, 29.76, 60, 90.24, 120.
"""
- from pyninjotiff.ninjotiff import save
- from pyninjotiff.tifffile import TiffFile
-
- area = FakeArea({'ellps': 'WGS84', 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 0.0, 'proj': 'stere'},
- (-1000000.0, -4500000.0, 2072000.0, -1428000.0),
- 1024, 1024)
+ area = STEREOGRAPHIC_AREA
scale = 1.0 / 120
offset = 0.0
attrs = dict([('resolution', 1050),
@@ -695,10 +695,7 @@ def test_write_bw_colormap():
if not DELETE_FILES:
print(filename)
save(img, filename, data_is_scaled_01=True, **kwargs)
- tif = TiffFile(filename)
- page = tif[0]
- res = page.asarray(colormapped=False).squeeze()
- colormap = page.tags['color_map'].value
+ colormap, res = _load_file_values_with_colormap(filename)
assert(len(colormap) == 768)
assert(np.allclose(colormap[:256], cm_vis))
@@ -707,6 +704,14 @@ def test_write_bw_colormap():
assert(np.allclose(res[0, ::205], np.array([1, 64, 128, 192, 255])))
+def _load_file_values_with_colormap(filename):
+ tif = TiffFile(filename)
+ page = tif[0]
+ res = page.asarray(colormapped=False).squeeze()
+ colormap = page.tags['color_map'].value
+ return colormap, res
+
+
def test_write_ir_colormap():
"""Test saving a IR image with a colormap.
@@ -714,12 +719,7 @@ def test_write_ir_colormap():
Temperatures are -70, -40.24, -10, 20.24, 50.
"""
- from pyninjotiff.ninjotiff import save
- from pyninjotiff.tifffile import TiffFile
-
- area = FakeArea({'ellps': 'WGS84', 'lat_0': 90.0, 'lat_ts': 60.0, 'lon_0': 0.0, 'proj': 'stere'},
- (-1000000.0, -4500000.0, 2072000.0, -1428000.0),
- 1024, 1024)
+ area = STEREOGRAPHIC_AREA
scale = 1.0 / 120
offset = 70.0 / 120
attrs = dict([('resolution', 1050),
@@ -787,13 +787,59 @@ def test_write_ir_colormap():
if not DELETE_FILES:
print(filename)
save(img, filename, data_is_scaled_01=True, **kwargs)
- tif = TiffFile(filename)
- page = tif[0]
- res = page.asarray(colormapped=False).squeeze()
- colormap = page.tags['color_map'].value
+ colormap, res = _load_file_values_with_colormap(filename)
assert(len(colormap) == 768)
assert(np.allclose(colormap[:256], ir_map))
assert(np.allclose(colormap[256:512], ir_map))
assert(np.allclose(colormap[512:], ir_map))
assert(np.allclose(res[0, ::205], np.array([1, 64, 128, 192, 255])))
+
+
+def test_write_p():
+ """Test saving an image in P mode.
+
+ Values are 0, 1, 2, 3, 4, Palette is black, red, green, blue, gray.
+ """
+ area = STEREOGRAPHIC_AREA
+
+ palette = [np.array((0, 0, 0, 1)),
+ np.array((1, 0, 0, 1)),
+ np.array((0, 1, 0, 1)),
+ np.array((0, 0, 1, 1)),
+ np.array((.5, .5, .5, 1)),
+ ]
+ attrs = dict([('resolution', 1050),
+ ('polarization', None),
+ ('platform_name', 'MSG'),
+ ('sensor', 'seviri'),
+ ("palette", palette),
+ ('name', 'msg_cloudtop_height'),
+ ('level', None),
+ ('modifiers', ()),
+ ('start_time', TIME - datetime.timedelta(minutes=85)),
+ ('end_time', TIME - datetime.timedelta(minutes=80)),
+ ('area', area),
+ ('ancillary_variables', [])])
+
+ data = da.tile(da.repeat(da.arange(5, chunks=1024, dtype=np.uint8), 205)[:-1],
+ 1024).reshape((1, 1024, 1024))[:, :1024]
+ data = xr.DataArray(data, coords={'bands': ['P']}, dims=[
+ 'bands', 'y', 'x'], attrs=attrs)
+ kwargs = {'compute': True, 'fill_value': None, 'sat_id': 9000014,
+ 'chan_id': 1900015, 'data_cat': 'GPRN', 'data_source': 'SMHI',
+ 'physic_unit': 'NONE', "physic_value": "NONE",
+ "description": "NWCSAF Cloud Top Height"}
+
+ img = FakeImage(data)
+ with tempfile.NamedTemporaryFile(delete=DELETE_FILES) as tmpfile:
+ filename = tmpfile.name
+ if not DELETE_FILES:
+ print(filename)
+ save(img, filename, data_is_scaled_01=True, **kwargs)
+ colormap, res = _load_file_values_with_colormap(filename)
+
+ np.testing.assert_array_equal(res[0, ::205], [0, 1, 2, 3, 4])
+ assert(len(colormap) == 768)
+ for i, line in enumerate(palette):
+ np.testing.assert_array_equal(colormap[i::256], (line[:3] * 255).astype(int))
=====================================
pyproject.toml
=====================================
@@ -0,0 +1,35 @@
+[tool.poetry]
+name = "pyninjotiff"
+version = "0.3.0"
+description = "Python Ninjo TIFF writing library"
+authors = ["Martin Raspaud <martin.raspaud at smhi.se>"]
+license = "GPLv3"
+classifiers = [
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Development Status :: 5 - Production/Stable",
+ "Intended Audience :: Science/Research",
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python",
+ "Topic :: Scientific/Engineering"
+]
+
+[tool.poetry.dependencies]
+python = "^3.7"
+numpy = ">=1.6"
+pyproj = "^3.2.1"
+pyresample = "^1.21.1"
+dask = {extras = ["array"], version = "^2021.9.1"}
+xarray = "^0.19.0"
+trollimage = "^1.15.1"
+
+[tool.poetry.dev-dependencies]
+pytest = "^5.2"
+pytest-cov = "^3.0.0"
+
+[build-system]
+requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.poetry-dynamic-versioning]
+enable = true
=====================================
setup.py deleted
=====================================
@@ -1,48 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2017 Martin Raspaud
-
-# Author(s):
-
-# Martin Raspaud <martin.raspaud at smhi.se>
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-"""Setup for pyninjotiff."""
-
-import imp
-
-from setuptools import setup
-
-version = imp.load_source('pyninjotiff.version', 'pyninjotiff/version.py')
-
-setup(name="pyninjotiff",
- version=version.__version__,
- description='Python Ninjo TIFF writing library',
- author='Martin Raspaud',
- author_email='martin.raspaud at smhi.se',
- classifiers=["Development Status :: 5 - Production/Stable",
- "Intended Audience :: Science/Research",
- "License :: OSI Approved :: GNU General Public License v3 " +
- "or later (GPLv3+)",
- "Operating System :: OS Independent",
- "Programming Language :: Python",
- "Topic :: Scientific/Engineering"],
- url="https://github.com/pytroll/pyninjotiff",
- packages=['pyninjotiff'],
- zip_safe=False,
- install_requires=['numpy >=1.6', 'six', 'pyproj', 'pyresample', 'dask[dataframe]', 'xarray'],
- # test_suite='pyninjotiff.tests.suite',
- )
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyninjotiff/-/compare/2f2e07d79a4aff6c95a6ea7d10f2f919fcfe27c1...f91a9ff6526133800127eb09e6e1a401780129d8
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyninjotiff/-/compare/2f2e07d79a4aff6c95a6ea7d10f2f919fcfe27c1...f91a9ff6526133800127eb09e6e1a401780129d8
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/20211111/8cc1bdf3/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list