[Git][debian-gis-team/pyninjotiff][upstream] New upstream version 0.4.0

Antonio Valentino (@antonio.valentino) gitlab at salsa.debian.org
Thu Nov 11 09:35:09 GMT 2021



Antonio Valentino pushed to branch upstream at Debian GIS Project / pyninjotiff


Commits:
35b2fe9e by Antonio Valentino at 2021-11-11T07:10:49+00:00
New upstream version 0.4.0
- - - - -


6 changed files:

- + .github/workflows/ci.yaml
- + .github/workflows/deploy-sdist.yaml
- 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 }}


=====================================
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/-/commit/35b2fe9e50058b973783e0ffb292fae812dd8208

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/pyninjotiff/-/commit/35b2fe9e50058b973783e0ffb292fae812dd8208
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/ca6f403e/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list