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

Add low level moisture composite #2164

Merged
merged 17 commits into from Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion doc/source/conf.py
Expand Up @@ -86,7 +86,8 @@ def __getattr__(cls, name):
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage',
'sphinx.ext.doctest', 'sphinx.ext.napoleon', 'sphinx.ext.autosummary', 'doi_role',
'sphinx.ext.viewcode', 'sphinxcontrib.apidoc']
'sphinx.ext.viewcode', 'sphinxcontrib.apidoc',
'sphinx.ext.mathjax']

# API docs
apidoc_module_dir = "../../satpy"
Expand Down
107 changes: 107 additions & 0 deletions satpy/enhancements/atmosphere.py
@@ -0,0 +1,107 @@
# Copyright (c) 2022- Satpy developers
#
# This file is part of satpy.
#
# satpy is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# satpy is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# satpy. If not, see <http://www.gnu.org/licenses/>.
"""Enhancements related to visualising atmospheric phenomena."""

import datetime

import dask.array as da
import xarray as xr


def essl_moisture(img, low=1.1, high=1.6) -> None:
r"""Low level moisture by European Severe Storms Laboratory (ESSL).

Expects a mode L image with data corresponding to the ratio of the
calibrated reflectancys for the 0.86 µm and 0.906 µm channel.
gerritholl marked this conversation as resolved.
Show resolved Hide resolved

This composite and its colorisation was developed by ESSL.

Ratio values are scaled from the range ``[low, high]``, which is by default
between 1.1 and 1.6, but might be tuned based on region or sensor,
to ``[0, 1]``. Values outside this range are clipped. Color values
for red, green, and blue are calculated as follows, where ``x`` is the
ratio between the 0.86 µm and 0.905 µm channels:

.. math::

R = \max(1.375 - 2.67 x, -0.75 + x) \\
G = 1 - \frac{8x}{7} \\
B = \max(0.75 - 1.5 x, 0.25 - (x - 0.75)^2) \\

The value of ``img.data`` is modified in-place.

A color interpretation guide is pending further adjustments to the
parameters for current and future sensors.

Args:
img: XRImage containing the relevant composite
low: optional, low end for scaling, defaults to 1.1
high: optional, high end for scaling, defaults to 1.6
"""
ratio = img.data
if _is_fci_test_data(img.data):
# Due to a bug in the FCI pre-launch simulated test data,
# the 0.86 µm channel is too bright. To correct for this, its
# reflectances should be multiplied by 0.8.
ratio *= 0.8

with xr.set_options(keep_attrs=True):
ratio = _scale_and_clip(ratio, low, high)
red = _calc_essl_red(ratio)
green = _calc_essl_green(ratio)
blue = _calc_essl_blue(ratio)
data = xr.concat([red, green, blue], dim="bands")
data.attrs["mode"] = "RGB"
data["bands"] = ["R", "G", "B"]
img.data = data


def _scale_and_clip(ratio, low, high):
"""Scale ratio values to [0, 1] and clip values outside this range."""
scaled = (ratio - low) / (high - low)
scaled.data = da.clip(scaled.data, 0, 1)
return scaled


def _calc_essl_red(ratio):
"""Calculate values for red based on scaled and clipped ratio."""
red_a = 1.375 - 2.67 * ratio
red_b = -0.75 + ratio
red = xr.where(red_a > red_b, red_a, red_b)
red.data = da.clip(red.data, 0, 1)
return red


def _calc_essl_green(ratio):
"""Calculate values for green based on scaled and clipped ratio."""
green = 1 - (8/7) * ratio
green.data = da.clip(green.data, 0, 1)
return green


def _calc_essl_blue(ratio):
"""Calculate values for blue based on scaled and clipped ratio."""
blue_a = 0.75 - 1.5 * ratio
blue_b = 0.25 - (ratio - 0.75)**2
blue = xr.where(blue_a > blue_b, blue_a, blue_b)
blue.data = da.clip(blue.data, 0, 1)
return blue


def _is_fci_test_data(data):
"""Check if we are working with FCI test data."""
return (data.attrs["sensor"] == "fci" and
data.attrs["start_time"] < datetime.datetime(2022, 11, 30))
8 changes: 8 additions & 0 deletions satpy/etc/composites/modis.yaml
Expand Up @@ -154,3 +154,11 @@ composites:
- 3.75
- 10.8
standard_name: night_fog

low_level_moisture:
description: Based on work by Pieter Groenemeijer and Hans-Peter Roesli
compositor: !!python/name:satpy.composites.RatioCompositor
prerequisites:
- name: '17'
- name: '2'
gerritholl marked this conversation as resolved.
Show resolved Hide resolved
standard_name: low_level_moisture
44 changes: 44 additions & 0 deletions satpy/etc/composites/visir.yaml
Expand Up @@ -505,3 +505,47 @@ composites:
- wavelength: 0.64
- wavelength: 1.61
standard_name: cimss_cloud_type

essl_low_level_moisture:
description: >
Greyscale low level moisture using the ratio between the
0.91 µm and the 0.86 µm channels. Developed by the
European Severe Storms Laboratory (ESSL). For a color version,
see essl_colorized_low_level_moisture.
compositor: !!python/name:satpy.composites.RatioCompositor
prerequisites:
- wavelength: 0.905
- wavelength: 0.86
standard_name: essl_low_level_moisture

day_essl_low_level_moisture:
description: >
Daytime only version of essl_low_level_moisture.
Nighttime part of the scene will be masked out.
compositor: !!python/name:satpy.composites.DayNightCompositor
day_night: day_only
prerequisites:
- name: essl_low_level_moisture
standard_name: essl_low_level_moisture

essl_colorized_low_level_moisture:
description: >
Colorized low level moisture using the ratio between the
0.91 µm and the 0.86 µm channels. Developed by the
European Severe Storms Laboratory (ESSL). The colorization
is still under development and may be subject to change.
compositor: !!python/name:satpy.composites.RatioCompositor
prerequisites:
- wavelength: 0.86
- wavelength: 0.905
standard_name: essl_colorized_low_level_moisture

day_essl_colorized_low_level_moisture:
description: >
Daytime only version of essl_colorized_low_level_moisture.
Nighttime part of the scene will be masked out.
compositor: !!python/name:satpy.composites.DayNightCompositor
day_night: day_only
prerequisites:
- name: essl_colorized_low_level_moisture
standard_name: essl_colorized_low_level_moisture
13 changes: 13 additions & 0 deletions satpy/etc/enhancements/generic.yaml
Expand Up @@ -954,3 +954,16 @@ enhancements:
stretch: crude
min_stretch: [ 0, 0, 0]
max_stretch: [50, 50, 100]

gerritholl marked this conversation as resolved.
Show resolved Hide resolved
essl_low_level_moisture:
name: essl_low_level_moisture
operations:
- name: linear_stretch
method: !!python/name:satpy.enhancements.stretch
kwargs: {stretch: 'crude', min_stretch: 0.35, max_stretch: 0.85}

essl_colorized_low_level_moisture:
name: essl_colorized_low_level_moisture
operations:
- name: essl_moisture
method: !!python/name:satpy.enhancements.atmosphere.essl_moisture
39 changes: 39 additions & 0 deletions satpy/tests/enhancement_tests/test_atmosphere.py
@@ -0,0 +1,39 @@
# Copyright (c) 2022- Satpy developers
#
# This file is part of satpy.
#
# satpy is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# satpy is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# satpy. If not, see <http://www.gnu.org/licenses/>.
"""Tests for enhancements in enhancements/atmosphere.py."""

import dask.array as da
import numpy as np
import xarray as xr
from trollimage.xrimage import XRImage


def test_essl_moisture():
"""Test ESSL moisture compositor."""
from satpy.enhancements.atmosphere import essl_moisture

ratio = xr.DataArray(
da.from_array(np.full((5, 5), 0.5), chunks=5),
dims=("y", "x"),
attrs={"name": "ratio",
"calibration": "reflectance",
"units": "%",
"mode": "L"})
im = XRImage(ratio)

essl_moisture(im)
assert im.data.attrs["mode"] == "RGB"
np.testing.assert_array_equal(im.data["bands"], ["R", "G", "B"])