Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make it possible to set fill value when reading images #2940

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions satpy/readers/generic_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,15 @@
class GenericImageFileHandler(BaseFileHandler):
"""Handle reading of generic image files."""

def __init__(self, filename, filename_info, filetype_info):
def __init__(self, filename, filename_info, filetype_info, set_fill_value=None, nodata_handling=None):
"""Initialize filehandler."""
super(GenericImageFileHandler, self).__init__(
filename, filename_info, filetype_info)
self.finfo = filename_info
self.set_fill_value = set_fill_value
if self.set_fill_value is not None and not isinstance(self.set_fill_value, (list, tuple)):
self.set_fill_value = [self.set_fill_value]
self.nodata_handling = nodata_handling

Check warning on line 65 in satpy/readers/generic_image.py

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Excess Number of Function Arguments

GenericImageFileHandler.__init__ has 5 arguments, threshold = 4. This function has too many arguments, indicating a lack of encapsulation. Avoid adding more arguments.
try:
self.finfo["end_time"] = self.finfo["start_time"]
except KeyError:
Expand All @@ -82,13 +86,16 @@
# however, error is not explicit enough (see https://github.com/pydata/xarray/issues/7831)
data = xr.open_dataset(self.finfo["filename"], engine="rasterio",
chunks={"band": 1, "y": CHUNK_SIZE, "x": CHUNK_SIZE}, mask_and_scale=False)["band_data"]

if hasattr(dataset, "nodatavals"):
# The nodata values for the raster bands
# copied from https://github.com/pydata/xarray/blob/v2023.03.0/xarray/backends/rasterio_.py#L322-L326
nodatavals = tuple(
np.nan if nodataval is None else nodataval for nodataval in dataset.nodatavals
)
data.attrs["nodatavals"] = nodatavals
if self.set_fill_value:
data.attrs["nodatavals"] = self.set_fill_value

attrs = data.attrs.copy()

Expand Down Expand Up @@ -123,10 +130,12 @@
ds_name = self.dataset_name if self.dataset_name else key["name"]
logger.debug("Reading '%s.'", ds_name)
data = self.file_content[ds_name]
if self.nodata_handling is not None:
info["nodata_handling"] = self.nodata_handling

# Mask data if necessary
try:
data = _mask_image_data(data, info)
data = _mask_image_data(data, info, self.set_fill_value)
except ValueError as err:
logger.warning(err)

Expand All @@ -135,15 +144,15 @@
return data


def _mask_image_data(data, info):
def _mask_image_data(data, info, set_fill_value):
"""Mask image data if necessary.

Masking is done if alpha channel is present or
dataset 'nodata_handling' is set to 'nan_mask'.
In the latter case even integer data is converted
to float32 and masked with np.nan.
"""
if data.bands.size in (2, 4):
if data.bands.size in (2, 4) and not set_fill_value:
if not np.issubdtype(data.dtype, np.integer):
raise ValueError("Only integer datatypes can be used as a mask.")
mask = data.data[-1, :, :] == np.iinfo(data.dtype).min
Expand Down Expand Up @@ -174,4 +183,6 @@
if np.issubdtype(data.dtype, np.integer):
fill_value = int(fill_value)
data.attrs["_FillValue"] = fill_value
if "A" in data.bands:
data = data.drop_sel(bands="A")
return data
27 changes: 24 additions & 3 deletions satpy/tests/reader_tests/test_generic_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def test_image_l(tmp_path, random_image_channel_l):
attrs={"name": "test_l", "start_time": DATA_DATE})
dset["bands"] = ["L"]
fname = tmp_path / "test_l.png"
_save_image(dset, fname, "simple_image")
_save_image(dset, fname, "simple_image", 255)

return fname

Expand Down Expand Up @@ -162,7 +162,7 @@ def test_png_scene_l_mode(test_image_l):
with pytest.warns(NotGeoreferencedWarning, match=r"Dataset has no geotransform"):
scn = Scene(reader="generic_image", filenames=[test_image_l])
scn.load(["image"])
_assert_image_common(scn, 1, None, None, np.float32)
_assert_image_common(scn, 1, None, None, np.uint8)
assert "area" not in scn["image"].attrs


Expand All @@ -182,13 +182,23 @@ def test_png_scene_la_mode(test_image_la):
"""Test reading a PNG image with LA mode via satpy.Scene()."""
with pytest.warns(NotGeoreferencedWarning, match=r"Dataset has no geotransform"):
scn = Scene(reader="generic_image", filenames=[test_image_la])
scn.load(["image"])
scn.load(["image"], nodata_handling="fill_value")
data = da.compute(scn["image"].data)
assert np.sum(np.isnan(data)) == 100
assert "area" not in scn["image"].attrs
_assert_image_common(scn, 1, DATA_DATE, DATA_DATE, np.float32)


def test_png_scene_la_mode_set_fill(test_image_la):
"""Test reading a PNG image with L mode via satpy.Scene() setting input fill value."""
with pytest.warns(NotGeoreferencedWarning, match=r"Dataset has no geotransform"):
scn = Scene(reader="generic_image", filenames=[test_image_la],
reader_kwargs={"set_fill_value": 255, "nodata_handling": "fill_value"})
scn.load(["image"])
_assert_image_common(scn, 1, DATA_DATE, DATA_DATE, np.uint8)
assert "area" not in scn["image"].attrs


def test_geotiff_scene_rgb(test_image_rgb):
"""Test reading geotiff image in RGB mode via satpy.Scene()."""
scn = Scene(reader="generic_image", filenames=[test_image_rgb])
Expand All @@ -205,6 +215,15 @@ def test_geotiff_scene_rgba(test_image_rgba):
assert scn["image"].area == AREA_DEFINITION


def test_png_scene_rgba_mode_set_fill(test_image_rgba):
"""Test reading an image in RGBA mode via satpy.Scene() setting input fill value."""
scn = Scene(reader="generic_image", filenames=[test_image_rgba],
reader_kwargs={"set_fill_value": 255, "nodata_handling": "fill_value"})
scn.load(["image"])
_assert_image_common(scn, 3, None, None, np.uint8)
assert scn["image"].area == AREA_DEFINITION


def test_geotiff_scene_nan_fill_value(test_image_l_nan_fill_value):
"""Test reading geotiff image with fill value set via satpy.Scene()."""
scn = Scene(reader="generic_image", filenames=[test_image_l_nan_fill_value])
Expand Down Expand Up @@ -252,6 +271,8 @@ def __init__(self, filename, filename_info, filetype_info, file_content, **kwarg
super(GenericImageFileHandler, self).__init__(filename, filename_info, filetype_info)
self.file_content = file_content
self.dataset_name = None
self.nodata_handling = kwargs.pop("nodata_handling", None)
self.set_fill_value = kwargs.pop("set_fill_value", None)
self.file_content.update(kwargs)


Expand Down
Loading