diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f1077a43..891b67df 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -5,7 +5,7 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.number }}-${{ github.event.type }} cancel-in-progress: true -on: [push, pull_request] +on: [push, pull_request, workflow_dispatch] env: CACHE_NUMBER: 1 diff --git a/build_environment.yml b/build_environment.yml index dc410973..a171f263 100644 --- a/build_environment.yml +++ b/build_environment.yml @@ -37,7 +37,7 @@ dependencies: - requests - setuptools - six - - trollimage>=1.21.0 + - trollimage>=1.22.2 - trollsift>=0.5.0 - scipy - zarr diff --git a/polar2grid/add_coastlines.py b/polar2grid/add_coastlines.py index 562893a0..60509863 100644 --- a/polar2grid/add_coastlines.py +++ b/polar2grid/add_coastlines.py @@ -34,12 +34,12 @@ import logging import os import sys +from pathlib import Path import numpy as np import rasterio from aggdraw import Font from PIL import Image, ImageFont -from pkg_resources import resource_filename as get_resource_filename from pycoast import ContourWriterAGG from pydecorate import DecoratorAGG from pyresample.utils import get_area_def_from_raster @@ -485,10 +485,10 @@ def find_font(font_name, size): font = ImageFont.truetype(font_name, size) return font.path except IOError as err: - font_path = get_resource_filename("polar2grid.fonts", font_name) - if not os.path.exists(font_path): - raise ValueError("Font path does not exist: {}".format(font_path)) from err - return font_path + font_path = Path(__file__).parent / "fonts" / font_name + if not font_path.is_file(): + raise ValueError(f"Font path does not exist: {font_path}") from err + return str(font_path) def _process_one_image( diff --git a/polar2grid/filters/_utils.py b/polar2grid/filters/_utils.py index b317fc29..682e6098 100644 --- a/polar2grid/filters/_utils.py +++ b/polar2grid/filters/_utils.py @@ -36,7 +36,7 @@ from typing import Union from pyresample.boundary import AreaBoundary, AreaDefBoundary, Boundary -from pyresample.geometry import AreaDefinition, SwathDefinition, get_geostationary_bounding_box +from pyresample.geometry import AreaDefinition, SwathDefinition, get_geostationary_bounding_box_in_lonlats from pyresample.spherical import SphPolygon logger = logging.getLogger(__name__) @@ -47,11 +47,12 @@ def boundary_for_area(area_def: PRGeometry) -> Boundary: """Create Boundary object representing the provided area.""" if getattr(area_def, "is_geostationary", False): - adp = Boundary(*get_geostationary_bounding_box(area_def, nb_points=100)) + adp = Boundary(*get_geostationary_bounding_box_in_lonlats(area_def, nb_points=100)) else: freq_fraction = 0.30 if isinstance(area_def, AreaDefinition) else 0.05 try: - adp = AreaDefBoundary(area_def, frequency=int(area_def.shape[0] * freq_fraction)) + adp = area_def.boundary() + adp.decimate(int(freq_fraction * area_def.shape[0])) except ValueError: if not isinstance(area_def, SwathDefinition): logger.error("Unable to generate bounding geolocation polygon") diff --git a/polar2grid/tests/test_add_coastlines.py b/polar2grid/tests/test_add_coastlines.py index 902b2b62..802156b0 100644 --- a/polar2grid/tests/test_add_coastlines.py +++ b/polar2grid/tests/test_add_coastlines.py @@ -171,8 +171,10 @@ def test_add_coastlines_basic( passed_cmap = add_scale_mock.call_args.kwargs["colormap"] _check_used_colormap(passed_cmap, has_colors, include_cmap_tag, include_scale_offset) - img = Image.open(output_fp) - arr = np.asarray(img) + with Image.open(output_fp) as img: + img.load() + arr = np.asarray(img) + out_tags = dict(img.tag_v2) if output_fp.endswith(".tif") else {} # bottom of the image is a colorbar image_arr = arr[:940] _check_exp_image_colors(image_arr, colormap, 0, has_colors) @@ -181,9 +183,9 @@ def test_add_coastlines_basic( assert (arr[940:] != 0).any() if output_fp.endswith(".tif"): - out_tags = dict(img.tag_v2) - in_img = Image.open(fp) - in_tags = dict(in_img.tag_v2) + with Image.open(fp) as in_img: + in_img.load() + in_tags = dict(in_img.tag_v2) assert len(out_tags) >= 14 for key, val in out_tags.items(): if key < 30000: diff --git a/polar2grid/tests/test_compare.py b/polar2grid/tests/test_compare.py index 1f44d8b0..0cb2aacd 100644 --- a/polar2grid/tests/test_compare.py +++ b/polar2grid/tests/test_compare.py @@ -21,13 +21,14 @@ # input into another program. # Documentation: http://www.ssec.wisc.edu/software/polar2grid/ """Tests for the compare.py script.""" - import os from glob import glob import numpy as np import pytest +from polar2grid.utils.warnings import ignore_no_georef + SHAPE1 = (200, 100) SHAPE2 = (200, 101) IMAGE1_L_UINT8_ZEROS = np.zeros(SHAPE1, dtype=np.uint8) @@ -52,19 +53,20 @@ def _create_geotiffs(base_dir, img_data): for idx, img_arr in enumerate(img_data): band_count = 1 if img_arr.ndim == 2 else img_arr.shape[0] gtiff_fn = os.path.join(base_dir, f"test{idx}.tif") - with rasterio.open( - gtiff_fn, - "w", - driver="GTiff", - count=band_count, - height=img_arr.shape[-2], - width=img_arr.shape[-1], - dtype=img_arr.dtype, - ) as gtiff_file: - if img_arr.ndim == 2: - img_arr = img_arr[None, :, :] - for band_idx, band_arr in enumerate(img_arr): - gtiff_file.write(band_arr, band_idx + 1) + with ignore_no_georef(): + with rasterio.open( + gtiff_fn, + "w", + driver="GTiff", + count=band_count, + height=img_arr.shape[-2], + width=img_arr.shape[-1], + dtype=img_arr.dtype, + ) as gtiff_file: + if img_arr.ndim == 2: + img_arr = img_arr[None, :, :] + for band_idx, band_arr in enumerate(img_arr): + gtiff_file.write(band_arr, band_idx + 1) def _create_hdf5(base_dir, img_data): @@ -196,7 +198,8 @@ def test_basic_compare( if include_html: args.extend(["--html", str(html_file)]) - num_diff_files = main(args) + with ignore_no_georef(): + num_diff_files = main(args) exp_num_png_files = _get_exp_num_png_files(actual_data, expected_data, expected_file_func) _check_num_diff_files(num_diff_files, exp_num_diff, expected_file_func, actual_file_func) _check_html_output(include_html, html_file, exp_num_png_files, expected_file_func, actual_file_func) diff --git a/polar2grid/tests/test_configs.py b/polar2grid/tests/test_configs.py index ef57862d..02278387 100644 --- a/polar2grid/tests/test_configs.py +++ b/polar2grid/tests/test_configs.py @@ -35,9 +35,10 @@ def pytest_generate_tests(metafunc): """ if "yaml_config_file" in metafunc.fixturenames: - root_dir = os.path.join(os.path.dirname(__file__), "..", "..") + root_dir = os.path.join(os.path.dirname(__file__), "..") glob_pat = os.path.join(root_dir, "etc", "**", "*.yaml") p2g_yaml_files = sorted(glob(glob_pat, recursive=True)) + assert len(p2g_yaml_files) != 0 metafunc.parametrize("yaml_config_file", p2g_yaml_files) diff --git a/polar2grid/tests/test_glue.py b/polar2grid/tests/test_glue.py index 79550de3..a666ac7a 100644 --- a/polar2grid/tests/test_glue.py +++ b/polar2grid/tests/test_glue.py @@ -35,6 +35,7 @@ from satpy.tests.utils import CustomScheduler from polar2grid.utils.config import get_polar2grid_etc +from polar2grid.utils.warnings import ignore_no_georef @contextlib.contextmanager @@ -294,7 +295,8 @@ def test_viirs_sdr_scene(self, scene_fixture, product_names, num_outputs, extra_ args.extend(product_names) if extra_flags: args.extend(extra_flags) - ret = main(args) + with ignore_no_georef(): + ret = main(args) output_files = glob(str(chtmpdir / "*.tif")) assert len(output_files) == num_outputs assert ret == 0 diff --git a/polar2grid/utils/warnings.py b/polar2grid/utils/warnings.py new file mode 100644 index 00000000..f6b5dae3 --- /dev/null +++ b/polar2grid/utils/warnings.py @@ -0,0 +1,38 @@ +"""Warnings or utilities for dealing with warnings.""" +from __future__ import annotations + +import contextlib +import warnings + + +@contextlib.contextmanager +def ignore_no_georef(): + """Wrap operations that we know will produce a rasterio geolocation warning.""" + from rasterio.errors import NotGeoreferencedWarning + + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "Dataset has no geotransform", + NotGeoreferencedWarning, + ) + yield + + +@contextlib.contextmanager +def ignore_pyproj_proj_warnings(): + """Wrap operations that we know will produce a PROJ.4 precision warning. + + Only to be used internally to Pyresample when we have no other choice but + to use PROJ.4 strings/dicts. For example, serialization to YAML or other + human-readable formats or testing the methods that produce the PROJ.4 + versions of the CRS. + + """ + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + "You will likely lose important projection information", + UserWarning, + ) + yield diff --git a/polar2grid/writers/hdf5.py b/polar2grid/writers/hdf5.py index 9109915c..f892144b 100644 --- a/polar2grid/writers/hdf5.py +++ b/polar2grid/writers/hdf5.py @@ -53,6 +53,7 @@ from satpy.writers import Writer, compute_writer_results, split_results from polar2grid.utils.legacy_compat import convert_p2g_pattern_to_satpy +from polar2grid.utils.warnings import ignore_pyproj_proj_warnings from polar2grid.writers.geotiff import NUMPY_DTYPE_STRS, NumpyDtypeList, str_to_dtype LOG = logging.getLogger(__name__) @@ -163,7 +164,8 @@ def create_proj_group(filename: str, parent: TextIO, area_def): group.attrs["height"], group.attrs["width"] = area_def.shape group.attrs["description"] = "No projection: native format" else: - group.attrs["proj4_definition"] = area_def.proj4_string + with ignore_pyproj_proj_warnings(): + group.attrs["proj4_definition"] = area_def.crs.to_string() for a in ["height", "width"]: ds_attr = getattr(area_def, a, None) if ds_attr is None: diff --git a/pyproject.toml b/pyproject.toml index 4dacc5af..cc6307f1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,18 @@ include-package-data = true [tool.setuptools.packages] find = {} +[tool.pytest.ini_options] +minversion = 6.0 +addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"] +xfail_strict = true +log_cli_level = "info" +testpaths = ["polar2grid/tests"] +filterwarnings = [ + "error", + "ignore:numpy.ndarray size changed, may indicate binary incompatibility:RuntimeWarning", + "ignore:The `frequency` argument is pending deprecation:PendingDeprecationWarning" +] + [tool.coverage.run] relative_files = true