From 982a31c81c1701271b3ccfae7e80ee15d9f1fc35 Mon Sep 17 00:00:00 2001 From: Gerrit Holl Date: Wed, 18 Dec 2024 16:52:20 +0100 Subject: [PATCH] Change ESSL colorisation approach Change the approach for ESSL colorisation. Remove the dedicated class and instead define a colourmap to be used with the colorize enhancement. Fix the ratio to be the right way around. Fixes and closes 3020. --- satpy/enhancements/atmosphere.py | 110 ----------------- satpy/etc/composites/visir.yaml | 4 +- satpy/etc/enhancements/generic.yaml | 114 +++++++++++++++++- .../enhancement_tests/test_atmosphere.py | 61 ---------- 4 files changed, 110 insertions(+), 179 deletions(-) delete mode 100644 satpy/enhancements/atmosphere.py delete mode 100644 satpy/tests/enhancement_tests/test_atmosphere.py diff --git a/satpy/enhancements/atmosphere.py b/satpy/enhancements/atmosphere.py deleted file mode 100644 index bbc4bc3a86..0000000000 --- a/satpy/enhancements/atmosphere.py +++ /dev/null @@ -1,110 +0,0 @@ -# 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 . -"""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 reflectances for the 0.86 µm and 0.906 µm channel. - - This composite and its colorisation were 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 ("sensor" in data.attrs and - "start_time" in data.attrs and - data.attrs["sensor"] == "fci" and - isinstance(data.attrs["start_time"], datetime.datetime) and - data.attrs["start_time"] < datetime.datetime(2022, 11, 30)) diff --git a/satpy/etc/composites/visir.yaml b/satpy/etc/composites/visir.yaml index ffe3be4183..fa774e26da 100644 --- a/satpy/etc/composites/visir.yaml +++ b/satpy/etc/composites/visir.yaml @@ -626,8 +626,8 @@ composites: is still under development and may be subject to change. compositor: !!python/name:satpy.composites.RatioCompositor prerequisites: - - wavelength: 0.86 - wavelength: 0.905 + - wavelength: 0.86 standard_name: essl_colorized_low_level_moisture day_essl_colorized_low_level_moisture: @@ -638,7 +638,7 @@ composites: day_night: day_only prerequisites: - name: essl_colorized_low_level_moisture - standard_name: day_essl_colorized_low_level_moisture + standard_name: image_ready rocket_plume_day: description: > diff --git a/satpy/etc/enhancements/generic.yaml b/satpy/etc/enhancements/generic.yaml index cdfb7851ad..01668aaf5e 100644 --- a/satpy/etc/enhancements/generic.yaml +++ b/satpy/etc/enhancements/generic.yaml @@ -1243,12 +1243,114 @@ enhancements: essl_colorized_low_level_moisture: name: essl_colorized_low_level_moisture operations: - - name: essl_moisture - method: !!python/name:satpy.enhancements.atmosphere.essl_moisture - - day_essl_colorized_low_level_moisture: - standard_name: day_essl_colorized_low_level_moisture - operations: [] + - name: colorize + method: !!python/name:satpy.enhancements.colorize + kwargs: + palettes: + - min_value: 0.625 + max_value: 0.91 + values: + - 0.6250 + - 0.6290 + - 0.6331 + - 0.6372 + - 0.6414 + - 0.6456 + - 0.6499 + - 0.6542 + - 0.6586 + - 0.6631 + - 0.6676 + - 0.6722 + - 0.6768 + - 0.6815 + - 0.6863 + - 0.6911 + - 0.6960 + - 0.7010 + - 0.7061 + - 0.7112 + - 0.7164 + - 0.7216 + - 0.7270 + - 0.7324 + - 0.7380 + - 0.7436 + - 0.7492 + - 0.7550 + - 0.7609 + - 0.7668 + - 0.7729 + - 0.7790 + - 0.7853 + - 0.7916 + - 0.7980 + - 0.8046 + - 0.8113 + - 0.8180 + - 0.8249 + - 0.8319 + - 0.8390 + - 0.8463 + - 0.8537 + - 0.8612 + - 0.8688 + - 0.8766 + - 0.8845 + - 0.8925 + - 0.9007 + - 0.9091 + colors: + - [63, 0, 47] + - [58, 0, 50] + - [53, 0, 52] + - [48, 0, 54] + - [42, 0, 56] + - [37, 0, 58] + - [32, 0, 59] + - [27, 5, 60] + - [22, 11, 61] + - [16, 17, 62] + - [11, 23, 63] + - [6, 28, 63] + - [1, 34, 63] + - [0, 40, 63] + - [0, 46, 63] + - [0, 52, 62] + - [0, 58, 62] + - [0, 64, 61] + - [0, 70, 60] + - [0, 76, 58] + - [0, 82, 57] + - [0, 88, 55] + - [0, 94, 53] + - [0, 100, 51] + - [3, 106, 49] + - [17, 112, 46] + - [31, 118, 43] + - [44, 124, 40] + - [58, 130, 37] + - [72, 136, 35] + - [86, 141, 42] + - [100, 147, 50] + - [114, 153, 58] + - [128, 159, 66] + - [142, 165, 74] + - [156, 171, 81] + - [169, 177, 89] + - [183, 183, 97] + - [197, 189, 105] + - [211, 195, 113] + - [225, 201, 120] + - [239, 207, 128] + - [253, 213, 136] + - [255, 219, 144] + - [255, 225, 152] + - [255, 231, 160] + - [255, 237, 167] + - [255, 243, 175] + - [255, 249, 183] + - [255, 255, 191] rocket_plume: standard_name: rocket_plume diff --git a/satpy/tests/enhancement_tests/test_atmosphere.py b/satpy/tests/enhancement_tests/test_atmosphere.py deleted file mode 100644 index 42e25af0c6..0000000000 --- a/satpy/tests/enhancement_tests/test_atmosphere.py +++ /dev/null @@ -1,61 +0,0 @@ -# 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 . -"""Tests for enhancements in enhancements/atmosphere.py.""" - -import datetime - -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.linspace(1.0, 1.7, 25, chunks=5).reshape((5, 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"]) - assert im.data.sel(bands="R")[0, 0] == 1 - np.testing.assert_allclose(im.data.sel(bands="R")[2, 2], 0.04, rtol=1e-4) - np.testing.assert_allclose(im.data.sel(bands="G")[2, 2], 0.42857, rtol=1e-4) - np.testing.assert_allclose(im.data.sel(bands="B")[2, 2], 0.1875, rtol=1e-4) - - # test FCI test data correction - ratio = xr.DataArray( - da.linspace(1.0, 1.7, 25, chunks=5).reshape((5, 5)), - dims=("y", "x"), - attrs={"name": "ratio", - "calibration": "reflectance", - "units": "%", - "mode": "L", - "sensor": "fci", - "start_time": datetime.datetime(1999, 1, 1)}) - im = XRImage(ratio) - essl_moisture(im) - np.testing.assert_allclose(im.data.sel(bands="R")[3, 3], 0.7342, rtol=1e-4) - np.testing.assert_allclose(im.data.sel(bands="G")[3, 3], 0.7257, rtol=1e-4) - np.testing.assert_allclose(im.data.sel(bands="B")[3, 3], 0.39, rtol=1e-4)