Source code for satpy.tests.reader_tests.test_insat3d_img_l1b_h5

"""Tests for the Insat3D reader."""

import datetime as dt
import os

import dask.array as da
import h5netcdf
import numpy as np
import pytest

from satpy import Scene
from satpy.readers.insat3d_img_l1b_h5 import (
    CHANNELS_BY_RESOLUTION,
    LUT_SUFFIXES,
    Insat3DIMGL1BH5FileHandler,
    get_lonlat_suffix,
    open_dataset,
    open_datatree,
)
from satpy.tests.utils import RANDOM_GEN, make_dataid

# NOTE:
# The following fixtures are not defined in this file, but are used and injected by Pytest:
# - tmp_path_factory

# real shape is 1, 11220, 11264
shape_1km = (1, 1122, 1126)
shape_4km = (1, 2816, 2805)
shape_8km = (1, 1408, 1402)
rad_units = "mW.cm-2.sr-1.micron-1"
alb_units = "%"
temp_units = "K"
chunks_1km = (1, 46, 1126)
values_1km = RANDOM_GEN.integers(0, 1000, shape_1km, dtype=np.uint16)
values_1km[0, 0, 0] = 0
values_4km = RANDOM_GEN.integers(0, 1000, shape_4km, dtype=np.uint16)
values_8km = RANDOM_GEN.integers(0, 1000, shape_8km, dtype=np.uint16)

values_by_resolution = {1000: values_1km,
                        4000: values_4km,
                        8000: values_8km}

lut_values_2 = np.arange(0, 1024 * 2, 2)
lut_values_3 = np.arange(0, 1024 * 3, 3)

dimensions = {"GeoX": shape_4km[2],
              "GeoY": shape_4km[1],
              "GeoX1": shape_8km[2],
              "GeoY1": shape_8km[1],
              "GeoX2": shape_1km[2],
              "GeoY2": shape_1km[1],
              "time": 1,
              "GreyCount": 1024,
              }
dimensions_by_resolution = {1000: ("GeoY2", "GeoX2"),
                            4000: ("GeoY", "GeoX"),
                            8000: ("GeoY1", "GeoX1")}

channel_names = {"vis": "Visible",
                 "mir": "Middle Infrared",
                 "swir": "Shortwave Infrared",
                 "tir1": "Thermal Infrared1",
                 "tir2": "Thermal Infrared2",
                 "wv": "Water Vapor"}

calibrated_names = {"": "Count",
                    "RADIANCE": "Radiance",
                    "ALBEDO": "Albedo",
                    "TEMP": "Brightness Temperature"}

calibrated_units = {"": "1",
                    "RADIANCE": "mW.cm-2.sr-1.micron-1",
                    "ALBEDO": "%",
                    "TEMP": "K"}

start_time = dt.datetime(2009, 6, 9, 9, 0)
end_time = dt.datetime(2009, 6, 9, 9, 30)
subsatellite_longitude = 82

time_pattern = "%d-%b-%YT%H:%M:%S"

global_attrs = {"Observed_Altitude(km)": 35778.490219,
                "Field_of_View(degrees)": 17.973925,
                "Acquisition_Start_Time": start_time.strftime(time_pattern),
                "Acquisition_End_Time": end_time.strftime(time_pattern),
                "Nominal_Central_Point_Coordinates(degrees)_Latitude_Longitude": [0.0, subsatellite_longitude],
                "Nominal_Altitude(km)": 36000.0,
                }


[docs] @pytest.fixture(scope="session") def insat_filename(tmp_path_factory): """Create a fake insat 3d l1b file.""" filename = tmp_path_factory.mktemp("data") / "3DIMG_25OCT2022_0400_L1B_STD_V01R00.h5" with h5netcdf.File(filename, mode="w") as h5f: h5f.dimensions = dimensions h5f.attrs.update(global_attrs) for resolution, channels in CHANNELS_BY_RESOLUTION.items(): _create_channels(channels, h5f, resolution) _create_lonlats(h5f, resolution) return filename
[docs] def mask_array(array): """Mask an array with nan instead of 0.""" return np.where(array == 0, np.nan, array)
[docs] def _create_channels(channels, h5f, resolution): for channel in channels: var_name = "IMG_" + channel.upper() var = h5f.create_variable(var_name, ("time",) + dimensions_by_resolution[resolution], np.uint16, chunks=chunks_1km) var[:] = values_by_resolution[resolution] var.attrs["_FillValue"] = 0 for suffix, lut_values in zip(LUT_SUFFIXES[channel], (lut_values_2, lut_values_3)): lut_name = "_".join((var_name, suffix)) var = h5f.create_variable(lut_name, ("GreyCount",), float) var[:] = lut_values var.attrs["units"] = bytes(calibrated_units[suffix], "ascii") var.attrs["long_name"] = " ".join((channel_names[channel], calibrated_names[suffix]))
[docs] def _create_lonlats(h5f, resolution): lonlat_suffix = get_lonlat_suffix(resolution) for var_name in ["Longitude" + lonlat_suffix, "Latitude" + lonlat_suffix]: var = h5f.create_variable(var_name, dimensions_by_resolution[resolution], np.uint16, chunks=chunks_1km[1:]) var[:] = values_by_resolution[resolution] var.attrs["scale_factor"] = 0.01 var.attrs["add_offset"] = 0.0
[docs] def test_insat3d_backend_has_1km_channels(insat_filename): """Test the insat3d backend.""" res = open_dataset(insat_filename, resolution=1000) assert res["IMG_VIS"].shape == shape_1km assert res["IMG_SWIR"].shape == shape_1km
[docs] @pytest.mark.parametrize(("resolution", "name", "shape", "expected_values", "expected_name", "expected_units"), [(1000, "IMG_VIS_RADIANCE", shape_1km, mask_array(values_1km * 2), "Visible Radiance", rad_units), (1000, "IMG_VIS_ALBEDO", shape_1km, mask_array(values_1km * 3), "Visible Albedo", alb_units), (4000, "IMG_MIR_RADIANCE", shape_4km, mask_array(values_4km * 2), "Middle Infrared Radiance", rad_units), (4000, "IMG_MIR_TEMP", shape_4km, mask_array(values_4km * 3), "Middle Infrared Brightness Temperature", temp_units), (4000, "IMG_TIR1_RADIANCE", shape_4km, mask_array(values_4km * 2), "Thermal Infrared1 Radiance", rad_units), (4000, "IMG_TIR2_RADIANCE", shape_4km, mask_array(values_4km * 2), "Thermal Infrared2 Radiance", rad_units), (8000, "IMG_WV_RADIANCE", shape_8km, mask_array(values_8km * 2), "Water Vapor Radiance", rad_units), ]) def test_insat3d_has_calibrated_arrays(insat_filename, resolution, name, shape, expected_values, expected_name, expected_units): """Check that calibration happens as expected.""" res = open_dataset(insat_filename, resolution=resolution) assert res[name].shape == shape np.testing.assert_allclose(res[name], expected_values) assert res[name].attrs["units"] == expected_units assert res[name].attrs["long_name"] == expected_name
[docs] def test_insat3d_has_dask_arrays(insat_filename): """Test that the backend uses dask.""" res = open_dataset(insat_filename, resolution=1000) assert isinstance(res["IMG_VIS_RADIANCE"].data, da.Array) assert res["IMG_VIS"].chunks is not None
[docs] def test_insat3d_only_has_3_resolutions(insat_filename): """Test that we only accept 1000, 4000, 8000.""" with pytest.raises(ValueError, match="Resolution 1024 not available. Available resolutions: 1000, 4000, 8000"): _ = open_dataset(insat_filename, resolution=1024)
[docs] @pytest.mark.parametrize("resolution", [1000, 4000, 8000, ]) def test_insat3d_returns_lonlat(insat_filename, resolution): """Test that lons and lats are loaded.""" res = open_dataset(insat_filename, resolution=resolution) expected = values_by_resolution[resolution].squeeze() / 100.0 assert isinstance(res["Latitude"].data, da.Array) np.testing.assert_allclose(res["Latitude"], expected) assert isinstance(res["Longitude"].data, da.Array) np.testing.assert_allclose(res["Longitude"], expected)
[docs] @pytest.mark.parametrize("resolution", [1000, 4000, 8000, ]) def test_insat3d_has_global_attributes(insat_filename, resolution): """Test that the backend supports global attributes.""" res = open_dataset(insat_filename, resolution=resolution) assert res.attrs.keys() >= global_attrs.keys()
[docs] @pytest.mark.parametrize("resolution", [1000, 4000, 8000, ]) def test_insat3d_opens_datatree(insat_filename, resolution): """Test that a datatree is produced.""" res = open_datatree(insat_filename) assert str(resolution) in res.keys()
[docs] def test_insat3d_datatree_has_global_attributes(insat_filename): """Test that the backend supports global attributes in the datatree.""" res = open_datatree(insat_filename) assert res.attrs.keys() >= global_attrs.keys()
[docs] @pytest.mark.parametrize(("calibration", "expected_values"), [("counts", values_1km), ("radiance", mask_array(values_1km * 2)), ("reflectance", mask_array(values_1km * 3))]) def test_filehandler_returns_data_array(insat_filehandler, calibration, expected_values): """Test that the filehandler can get dataarrays.""" fh = insat_filehandler ds_info = None ds_id = make_dataid(name="VIS", resolution=1000, calibration=calibration) darr = fh.get_dataset(ds_id, ds_info) np.testing.assert_allclose(darr, expected_values.squeeze()) assert darr.dims == ("y", "x")
[docs] def test_filehandler_returns_masked_data_in_space(insat_filehandler): """Test that the filehandler masks space pixels.""" fh = insat_filehandler ds_info = None ds_id = make_dataid(name="VIS", resolution=1000, calibration="reflectance") darr = fh.get_dataset(ds_id, ds_info) assert np.isnan(darr[0, 0])
[docs] def test_insat3d_has_orbital_parameters(insat_filehandler): """Test that the filehandler returns data with orbital parameter attributes.""" fh = insat_filehandler ds_info = None ds_id = make_dataid(name="VIS", resolution=1000, calibration="reflectance") darr = fh.get_dataset(ds_id, ds_info) assert "orbital_parameters" in darr.attrs assert "satellite_nominal_longitude" in darr.attrs["orbital_parameters"] assert darr.attrs["orbital_parameters"]["satellite_nominal_longitude"] == subsatellite_longitude assert "satellite_nominal_latitude" in darr.attrs["orbital_parameters"] assert "satellite_nominal_altitude" in darr.attrs["orbital_parameters"] assert "satellite_actual_altitude" in darr.attrs["orbital_parameters"] assert "platform_name" in darr.attrs assert "sensor" in darr.attrs
[docs] def test_filehandler_returns_coords(insat_filehandler): """Test that lon and lat can be loaded.""" fh = insat_filehandler ds_info = None lon_id = make_dataid(name="longitude", resolution=1000) darr = fh.get_dataset(lon_id, ds_info) np.testing.assert_allclose(darr, values_1km.squeeze() / 100)
[docs] @pytest.fixture(scope="session") def insat_filehandler(insat_filename): """Instantiate a Filehandler.""" fileinfo = {} filetype = None fh = Insat3DIMGL1BH5FileHandler(insat_filename, fileinfo, filetype) return fh
[docs] def test_filehandler_returns_area(insat_filehandler): """Test that filehandle returns an area.""" fh = insat_filehandler ds_id = make_dataid(name="MIR", resolution=4000, calibration="brightness_temperature") area_def = fh.get_area_def(ds_id) _ = area_def.get_lonlats(chunks=1000) assert subsatellite_longitude == area_def.crs.to_cf()["longitude_of_projection_origin"]
[docs] def test_filehandler_has_start_and_end_time(insat_filehandler): """Test that the filehandler handles start and end time.""" fh = insat_filehandler assert fh.start_time == start_time assert fh.end_time == end_time
[docs] def test_satpy_load_array(insat_filename): """Test that satpy can load the VIS array.""" scn = Scene(filenames=[os.fspath(insat_filename)], reader="insat3d_img_l1b_h5") scn.load(["VIS"]) expected = mask_array(values_1km * 3).squeeze() np.testing.assert_allclose(scn["VIS"], expected)
[docs] def test_satpy_load_two_arrays(insat_filename): """Test that satpy can load the VIS array.""" scn = Scene(filenames=[os.fspath(insat_filename)], reader="insat3d_img_l1b_h5") scn.load(["TIR1", "WV"]) expected = mask_array(values_4km * 3).squeeze() np.testing.assert_allclose(scn["TIR1"], expected)