-
Notifications
You must be signed in to change notification settings - Fork 284
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
Add NDVI-scaled hybrid green correction #2280
Changes from 11 commits
848d818
5903d93
134dcdc
9c159d8
0393031
a73c607
17ac671
3d6f2e8
4aeb090
caed5fb
c23a15e
7b282bb
34c7731
2f4b419
0f76c27
643e252
bf345dd
917250a
befb402
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -16,55 +16,164 @@ | |||||||||||||||
"""Composite classes for spectral adjustments.""" | ||||||||||||||||
|
||||||||||||||||
import logging | ||||||||||||||||
import warnings | ||||||||||||||||
|
||||||||||||||||
import dask.array as da | ||||||||||||||||
|
||||||||||||||||
from satpy.composites import GenericCompositor | ||||||||||||||||
from satpy.dataset import combine_metadata | ||||||||||||||||
|
||||||||||||||||
LOG = logging.getLogger(__name__) | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
class GreenCorrector(GenericCompositor): | ||||||||||||||||
class SpectralBlender(GenericCompositor): | ||||||||||||||||
"""Construct new channel by blending contributions from a set of channels. | ||||||||||||||||
|
||||||||||||||||
This class can be used to compute weighted average of different channels. | ||||||||||||||||
Primarily it's used to correct the green band of AHI and FCI in order to | ||||||||||||||||
allow for proper true color imagery. | ||||||||||||||||
|
||||||||||||||||
Below is an example used to generate a corrected green channel for AHI using a weighted average from | ||||||||||||||||
three channels, with 63% contribution from the native green channel (B02), 29% from the red channel (B03) | ||||||||||||||||
and 8% from the near-infrared channel (B04):: | ||||||||||||||||
|
||||||||||||||||
corrected_green: | ||||||||||||||||
compositor: !!python/name:satpy.composites.spectral.SpectralBlender | ||||||||||||||||
fractions: [0.63, 0.29, 0.08] | ||||||||||||||||
prerequisites: | ||||||||||||||||
- name: B02 | ||||||||||||||||
modifiers: [sunz_corrected, rayleigh_corrected] | ||||||||||||||||
- name: B03 | ||||||||||||||||
modifiers: [sunz_corrected, rayleigh_corrected] | ||||||||||||||||
- name: B04 | ||||||||||||||||
modifiers: [sunz_corrected, rayleigh_corrected] | ||||||||||||||||
standard_name: toa_bidirectional_reflectance | ||||||||||||||||
|
||||||||||||||||
Other examples can be found in the``ahi.yaml`` composite file in the satpy distribution. | ||||||||||||||||
""" | ||||||||||||||||
|
||||||||||||||||
def __init__(self, *args, fractions=(), **kwargs): | ||||||||||||||||
"""Set default keyword argument values.""" | ||||||||||||||||
self.fractions = fractions | ||||||||||||||||
super().__init__(*args, **kwargs) | ||||||||||||||||
|
||||||||||||||||
def __call__(self, projectables, optional_datasets=None, **attrs): | ||||||||||||||||
"""Blend channels in projectables using the weights in self.fractions.""" | ||||||||||||||||
if len(self.fractions) != len(projectables): | ||||||||||||||||
raise ValueError("fractions and projectables must have the same length.") | ||||||||||||||||
|
||||||||||||||||
projectables = self.match_data_arrays(projectables) | ||||||||||||||||
new_channel = sum(fraction * value for fraction, value in zip(self.fractions, projectables)) | ||||||||||||||||
new_channel.attrs = combine_metadata(*projectables) | ||||||||||||||||
return super().__call__((new_channel,), **attrs) | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
class HybridGreen(SpectralBlender): | ||||||||||||||||
"""Corrector of the FCI or AHI green band. | ||||||||||||||||
|
||||||||||||||||
The green band in FCI and AHI deliberately misses the chlorophyll peak | ||||||||||||||||
in order to focus on aerosol and ash rather than on vegetation. This | ||||||||||||||||
affects true colour RGBs, because vegetation looks brown rather than green. | ||||||||||||||||
To make vegetation look greener again, this corrector allows | ||||||||||||||||
to simulate the green band as a fraction of two or more other channels. | ||||||||||||||||
The green band in FCI and AHI (and other bands centered at 0.51 microns) deliberately | ||||||||||||||||
misses the chlorophyll spectral reflectance local maximum at 0.55 microns | ||||||||||||||||
in order to focus on aerosol and ash rather than on vegetation. This | ||||||||||||||||
affects true colour RGBs, because vegetation looks brown rather than green | ||||||||||||||||
and barren surface types typically gets a reddish hue. | ||||||||||||||||
|
||||||||||||||||
To be used, the composite takes two or more input channels and a parameter | ||||||||||||||||
``fractions`` that should be a list of floats with the same length as the | ||||||||||||||||
number of channels. | ||||||||||||||||
To correct for this the hybrid green approach proposed by Miller et al. (2016, :doi:`10.1175/BAMS-D-15-00154.2`) | ||||||||||||||||
is used. The basic idea is to include some contribution also from the 0.86 micron | ||||||||||||||||
channel, which is known for its sensitivity to vegetation. The formula used for this is: | ||||||||||||||||
|
||||||||||||||||
For example, to simulate an FCI corrected green composite, one could use | ||||||||||||||||
a combination of 93% from the green band (vis_05) and 7% from the | ||||||||||||||||
near-infrared 0.8 µm band (vis_08):: | ||||||||||||||||
hybrid_green = (1 - F) * R(0.51) + F * R(0.86) | ||||||||||||||||
|
||||||||||||||||
corrected_green: | ||||||||||||||||
compositor: !!python/name:satpy.composites.ahi.GreenCorrector | ||||||||||||||||
fractions: [0.93, 0.07] | ||||||||||||||||
where F is a constant value. The validation against VIIRS presented in the paper suggests | ||||||||||||||||
F = 0.07 as an optimal value to reconstruct a green band optimized for true color imagery | ||||||||||||||||
from AHI. This is the default value currently used for the hybrid green correction in Satpy. | ||||||||||||||||
|
||||||||||||||||
For example, to construct the hybrid green for AHI, one should use | ||||||||||||||||
a combination of 93% from the green band (B02) and 7% from the | ||||||||||||||||
near-infrared 0.85 µm band (B04):: | ||||||||||||||||
strandgren marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
|
||||||||||||||||
hybrid_green: | ||||||||||||||||
compositor: !!python/name:satpy.composites.spectral.HybridGreen | ||||||||||||||||
fraction: 0.07 | ||||||||||||||||
prerequisites: | ||||||||||||||||
- name: vis_05 | ||||||||||||||||
- name: B02 | ||||||||||||||||
modifiers: [sunz_corrected, rayleigh_corrected] | ||||||||||||||||
- name: vis_08 | ||||||||||||||||
- name: B04 | ||||||||||||||||
modifiers: [sunz_corrected, rayleigh_corrected] | ||||||||||||||||
standard_name: toa_bidirectional_reflectance | ||||||||||||||||
|
||||||||||||||||
Other examples can be found in the ``fci.yaml`` and ``ahi.yaml`` composite | ||||||||||||||||
files in the satpy distribution. | ||||||||||||||||
""" | ||||||||||||||||
|
||||||||||||||||
def __init__(self, *args, fractions=(0.85, 0.15), **kwargs): | ||||||||||||||||
def __init__(self, *args, fraction=0.15, **kwargs): | ||||||||||||||||
"""Set default keyword argument values.""" | ||||||||||||||||
# XXX: Should this be 0.93 and 0.07 | ||||||||||||||||
self.fractions = fractions | ||||||||||||||||
super(GreenCorrector, self).__init__(*args, **kwargs) | ||||||||||||||||
fractions = (1 - fraction, fraction) | ||||||||||||||||
super().__init__(fractions=fractions, *args, **kwargs) | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
class NDVIHybridGreen(SpectralBlender): | ||||||||||||||||
"""Construct a NDVI-weighted hybrid green channel. | ||||||||||||||||
|
||||||||||||||||
This green band correction follows the same approach as the HybridGreen compositor, but with a dynamic blend | ||||||||||||||||
factor `f` that depends on the pixel-level Normalized Differece Vegetation Index (NDVI). The higher the NDVI, the | ||||||||||||||||
smaller the contribution from the nir channel will be, following a liner relationship between the two ranges | ||||||||||||||||
[ndvi_min, ndvi_max] and `limits`. | ||||||||||||||||
|
||||||||||||||||
A new green channel using e.g. FCI data and the NDVIHybridGreen compositor can be defined like: | ||||||||||||||||
|
||||||||||||||||
ndvi_hybrid_green: | ||||||||||||||||
strandgren marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
compositor: !!python/name:satpy.composites.spectral.NDVIHybridGreen | ||||||||||||||||
limits: [0.15, 0.05] | ||||||||||||||||
prerequisites: | ||||||||||||||||
- name: vis_05 | ||||||||||||||||
modifiers: [sunz_corrected, rayleigh_corrected] | ||||||||||||||||
- name: vis_06 | ||||||||||||||||
modifiers: [sunz_corrected, rayleigh_corrected] | ||||||||||||||||
strandgren marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
- name: vis_08 | ||||||||||||||||
modifiers: [sunz_corrected ] | ||||||||||||||||
standard_name: toa_bidirectional_reflectance | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this whole thin should be indented one level and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks, that was the issue. Now it seems to work. |
||||||||||||||||
|
||||||||||||||||
In this example, pixels with NDVI=0.0 (default `ndvi_min`) will be a weighted average with 85% contribution from the | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this correct? I thought that for very green objects we wanted more of the 0.8 channel, not less - as we miss the chlorophyll peak in the FCI/AHI 0.5 micron band we need to boost it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well this is the interesting bit with the green band correction. The location of the AHI/FCI green bands has two effects:
The green color of vegetation can be boosted with a higher contribution from the NIR channel. Similarly, the reddishness of barren is reduced with an increasing contribution from the NIR channel. However, more contribution from NIR is required to get the proper color of barren, compared to vegetation. Hence, I came up with the solution of increasing the contribution from NIR with decreasing NDVI. Below are a few examples using the default hybrid green method, with decreasing contribution from the NIR channel. As you can see the color of Australia starts to get too red when the color of vegetation start to look realistic. I also included the result of the NDVIHybridGreen (with just qualitative tuning) and VIIRS RGB from NASA worldview as reference.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Your solution, does this mean that this is just for FCI so is "new" or is this being applied to AHI by some other group and documented in some paper? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is only internal developments in preparation for FCI. We are testing with AHI since we have the same scenario with sub-optimal position of the green band there. Hopefully we can publish it at some point though. |
||||||||||||||||
native green vis_05 channel and 15% from the near-infrared vis_08 channel, whereas pixels with an NDVI=1.0 (default | ||||||||||||||||
`ndvi_max`) will be a weighted average with 95% contribution from the native green vis_05 channel and 5% from the | ||||||||||||||||
near-infrared vis_08 channel. For other values of NDVI (within this range) a linear interpolation will be performed. | ||||||||||||||||
""" | ||||||||||||||||
|
||||||||||||||||
def __init__(self, *args, ndvi_min=0.0, ndvi_max=1.0, limits=(0.15, 0.05), **kwargs): | ||||||||||||||||
"""Initialize class and set the NDVI limits and the corresponding blending fraction limits.""" | ||||||||||||||||
self.ndvi_min = ndvi_min | ||||||||||||||||
self.ndvi_max = ndvi_max | ||||||||||||||||
self.limits = limits | ||||||||||||||||
super().__init__(*args, **kwargs) | ||||||||||||||||
|
||||||||||||||||
def __call__(self, projectables, optional_datasets=None, **attrs): | ||||||||||||||||
"""Boost vegetation effect thanks to NIR (0.8µm) band.""" | ||||||||||||||||
LOG.info('Boosting vegetation on green band') | ||||||||||||||||
"""Construct the hybrid green channel weighted by NDVI.""" | ||||||||||||||||
ndvi_input = self.match_data_arrays([projectables[1], projectables[2]]) | ||||||||||||||||
|
||||||||||||||||
projectables = self.match_data_arrays(projectables) | ||||||||||||||||
new_green = sum(fraction * value for fraction, value in zip(self.fractions, projectables)) | ||||||||||||||||
new_green.attrs = combine_metadata(*projectables) | ||||||||||||||||
return super(GreenCorrector, self).__call__((new_green,), **attrs) | ||||||||||||||||
ndvi = (ndvi_input[1] - ndvi_input[0]) / (ndvi_input[1] + ndvi_input[0]) | ||||||||||||||||
|
||||||||||||||||
ndvi.data = da.where(ndvi > self.ndvi_min, ndvi, self.ndvi_min) | ||||||||||||||||
ndvi.data = da.where(ndvi < self.ndvi_max, ndvi, self.ndvi_max) | ||||||||||||||||
|
||||||||||||||||
fraction = (ndvi - self.ndvi_min) / (self.ndvi_max - self.ndvi_min) * (self.limits[1] - self.limits[0]) \ | ||||||||||||||||
+ self.limits[0] | ||||||||||||||||
self.fractions = (1 - fraction, fraction) | ||||||||||||||||
|
||||||||||||||||
return super().__call__([projectables[0], projectables[2]], **attrs) | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
class GreenCorrector(SpectralBlender): | ||||||||||||||||
"""Previous class used to blend channels for green band corrections. | ||||||||||||||||
|
||||||||||||||||
This method has been refactored to make it more generic. The replacement class is 'SpectralBlender' wich computes | ||||||||||||||||
a weghted average based on N number of channels and N number fo corresponding weights/fractions. A new class | ||||||||||||||||
called 'HybridGreen' has been created, which performs a correction of green bands centered at 0.51 microns | ||||||||||||||||
following Miller et al. (2016, :doi:`10.1175/BAMS-D-15-00154.2`) in order to imrpove true color imagery. | ||||||||||||||||
strandgren marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||
""" | ||||||||||||||||
|
||||||||||||||||
def __init__(self, *args, fractions=(0.85, 0.15), **kwargs): | ||||||||||||||||
"""Set default keyword argument values.""" | ||||||||||||||||
warnings.warn( | ||||||||||||||||
"'GreenCorrector' is deprecated, use 'SpectralBlender' instead, or 'HybridGreen' for hybrid green" | ||||||||||||||||
" correction following Miller et al. (2016).", RuntimeWarning) | ||||||||||||||||
super().__init__(fractions=fractions, *args, **kwargs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe try making this a literal block (indent and put
::
on the line above) and see if that fixes the one error.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that was the issue. Now it seems to work.