Skip to content

Add a compositor to mask visible night #3102

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

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
120 changes: 120 additions & 0 deletions satpy/composites/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2015-2023 Satpy developers

Check warning on line 1 in satpy/composites/__init__.py

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Complex Conditional

MaskVisibleNightCompositor._weight_data has 1 complex conditionals with 2 branches, threshold = 2. A complex conditional is an expression inside a branch (e.g. if, for, while) which consists of multiple, logical operators such as AND/OR. The more logical operators in an expression, the more severe the code smell.
#
# This file is part of satpy.
#
Expand Down Expand Up @@ -691,6 +691,126 @@
return channels


class MaskVisibleNightCompositor(GenericCompositor):
"""A compositor masks night data on visible data.

Mask the night part of an image which is composed of visible channels only.
"""

def __init__(self, name, lim_low=85., lim_high=88., include_alpha=True, **kwargs): # noqa: D417
"""Collect custom configuration values.

Args:
lim_low (float): lower limit of Sun zenith angle for the
blending of the given channels

"""
self.lim_low = lim_low
self._has_sza = False
super().__init__(name, **kwargs)

Check warning on line 710 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L708-L710

Added lines #L708 - L710 were not covered by tests

def __call__(
self,
datasets: Sequence[xr.DataArray],
optional_datasets: Optional[Sequence[xr.DataArray]] = None,
**attrs
) -> xr.DataArray:
"""Generate the composite."""
datasets = self.match_data_arrays(datasets)

Check warning on line 719 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L719

Added line #L719 was not covered by tests
# At least one composite is requested.
foreground_data = datasets[0]
weights = self._get_coszen_blending_weights(datasets)

Check warning on line 722 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L721-L722

Added lines #L721 - L722 were not covered by tests
# Apply enhancements to the foreground data
foreground_data = enhance2dataset(foreground_data)

Check warning on line 724 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L724

Added line #L724 was not covered by tests

fg_attrs = foreground_data.attrs.copy()
day_data, night_data, weights = self._get_data_for_single_side_product(foreground_data, weights)

Check warning on line 727 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L726-L727

Added lines #L726 - L727 were not covered by tests

# The computed coszen is for the full area, so it needs to be masked for missing and off-swath data
if not self._has_sza:
weights = self._mask_weights_with_data(weights, day_data, night_data)

Check warning on line 731 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L730-L731

Added lines #L730 - L731 were not covered by tests

data = self._weight_data(day_data, night_data, weights, fg_attrs)

Check warning on line 733 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L733

Added line #L733 was not covered by tests

return super().__call__(

Check warning on line 735 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L735

Added line #L735 was not covered by tests
data,
optional_datasets=optional_datasets,
**attrs
)

def _get_coszen_blending_weights(
self,
projectables: Sequence[xr.DataArray],
) -> xr.DataArray:
lim_low = float(np.cos(np.deg2rad(self.lim_low)))
lim_high = float(np.cos(np.deg2rad(self.lim_low + 1)))
try:
coszen = np.cos(np.deg2rad(projectables[1]))
self._has_sza = True
except IndexError:
from satpy.modifiers.angles import get_cos_sza
LOG.debug("Computing sun zenith angles.")

Check warning on line 752 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L745-L752

Added lines #L745 - L752 were not covered by tests
# Get chunking that matches the data
coszen = get_cos_sza(projectables[0])

Check warning on line 754 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L754

Added line #L754 was not covered by tests
# Calculate blending weights
lum = xr.ufuncs.maximum(projectables[0].sel(bands="G"), projectables[0].sel(bands="B")) / 500
lum_zen = xr.ufuncs.maximum(lum, coszen)
coszen = xr.ones_like(coszen).where(coszen > lim_low, lum_zen)
coszen -= min(lim_high, lim_low)
coszen /= abs(lim_low - lim_high)
return coszen.clip(0, 1)

Check warning on line 761 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L756-L761

Added lines #L756 - L761 were not covered by tests

def _get_data_for_single_side_product(
self,
foreground_data: xr.DataArray,
weights: xr.DataArray,
) -> tuple[xr.DataArray, xr.DataArray, xr.DataArray]:
# Only one portion (day or night) is selected. One composite is requested.
# Add alpha band to single L/RGB composite to make the masked-out portion transparent when needed
# L -> LA
# RGB -> RGBA
foreground_data = add_alpha_bands(foreground_data)

Check warning on line 772 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L772

Added line #L772 was not covered by tests

night_data = self._get_night_fill(foreground_data)
return foreground_data, night_data, weights

Check warning on line 775 in satpy/composites/__init__.py

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Code Duplication

The module contains 7 functions with similar structure: DayNightCompositor._get_data_for_single_side_product,DayNightCompositor._weight_data,DifferenceCompositor.__call__,MaskVisibleNightCompositor._get_data_for_single_side_product and 3 more functions. Avoid duplicated, aka copy-pasted, code inside the module. More duplication lowers the code health.

Check warning on line 775 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L774-L775

Added lines #L774 - L775 were not covered by tests

def _get_night_fill(self, foreground_data):
return foreground_data.dtype.type(0)

Check warning on line 778 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L778

Added line #L778 was not covered by tests

def _mask_weights_with_data(
self,
weights: xr.DataArray,
day_data: xr.DataArray,
night_data: xr.DataArray,
) -> xr.DataArray:
data_a = _get_single_channel(day_data)
data_b = _get_single_channel(night_data)
mask = _get_weight_mask_for_single_side_product(data_a, data_b)

Check warning on line 788 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L786-L788

Added lines #L786 - L788 were not covered by tests

return weights.where(mask, np.nan)

Check warning on line 790 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L790

Added line #L790 was not covered by tests

def _weight_data(
self,
day_data: xr.DataArray,
night_data: xr.DataArray,
weights: xr.DataArray,
attrs: dict,
) -> list[xr.DataArray]:
data = []
for b in _get_band_names(day_data, night_data):
day_band = _get_single_band_data(day_data, b)
night_band = _get_single_band_data(night_data, b)

Check warning on line 802 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L799-L802

Added lines #L799 - L802 were not covered by tests
# For day-only and night-only products only the alpha channel is weighted
# If there's no alpha band, weight the actual data
if b == "A":
day_band = day_band * weights
night_band = night_band * (1 - weights)
band = day_band + night_band
band.attrs = attrs
data.append(band)
return data

Check warning on line 811 in satpy/composites/__init__.py

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Bumpy Road Ahead

MaskVisibleNightCompositor._weight_data has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is one single, nested block per function. The Bumpy Road code smell is a function that contains multiple chunks of nested conditional logic. The deeper the nesting and the more bumps, the lower the code health.

Check warning on line 811 in satpy/composites/__init__.py

View check run for this annotation

Codecov / codecov/patch

satpy/composites/__init__.py#L805-L811

Added lines #L805 - L811 were not covered by tests


class DayNightCompositor(GenericCompositor):
"""A compositor that blends day data with night data.

Expand Down
Loading