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 088a59f625..b0ba3df206 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/behave/features/image_comparison.feature b/satpy/tests/behave/features/image_comparison.feature
index 0497a96c93..686062462c 100755
--- a/satpy/tests/behave/features/image_comparison.feature
+++ b/satpy/tests/behave/features/image_comparison.feature
@@ -2,14 +2,15 @@ Feature: Image Comparison
Scenario Outline: Compare generated image with reference image
Given I have a reference image file from resampled to
- When I generate a new image file from with for with clipping
+ When I generate a new image file from case with for with clipping
Then the generated image should be the same as the reference image
Examples:
- |satellite |composite | reader | area | clip |
- |Meteosat-12 | cloudtop | fci_l1c_nc | sve | True |
- |Meteosat-12 | night_microphysics | fci_l1c_nc | sve | True |
- |GOES17 |airmass | abi_l1b | null | null |
- |GOES16 |airmass | abi_l1b | null | null |
- |GOES16 |ash | abi_l1b | null | null |
- |GOES17 |ash | abi_l1b | null | null |
+ |satellite | case | composite | reader | area | clip |
+ |Meteosat-12 | scan_night | cloudtop | fci_l1c_nc | sve | True |
+ |Meteosat-12 | scan_night | night_microphysics | fci_l1c_nc | sve | True |
+ |Meteosat-12 | mali_day | essl_colorized_low_level_moisture | fci_l1c_nc | mali | False |
+ |GOES17 | americas_night | airmass | abi_l1b | null | null |
+ |GOES16 | americas_night | airmass | abi_l1b | null | null |
+ |GOES16 | americas_night | ash | abi_l1b | null | null |
+ |GOES17 | americas_night | ash | abi_l1b | null | null |
diff --git a/satpy/tests/behave/features/steps/image_comparison.py b/satpy/tests/behave/features/steps/image_comparison.py
index 92c5fa0034..5e7135bc53 100644
--- a/satpy/tests/behave/features/steps/image_comparison.py
+++ b/satpy/tests/behave/features/steps/image_comparison.py
@@ -63,8 +63,9 @@ def step_given_reference_image(context, composite, satellite, area):
context.area = area
-@when("I generate a new {composite} image file from {satellite} with {reader} for {area} with clipping {clip}")
-def step_when_generate_image(context, composite, satellite, reader, area, clip):
+@when("I generate a new {composite} image file from {satellite} case {case} "
+ "with {reader} for {area} with clipping {clip}")
+def step_when_generate_image(context, composite, satellite, case, reader, area, clip):
"""Generate test images."""
os.environ["OMP_NUM_THREADS"] = os.environ["MKL_NUM_THREADS"] = "2"
os.environ["PYTROLL_CHUNK_SIZE"] = "1024"
@@ -72,7 +73,7 @@ def step_when_generate_image(context, composite, satellite, reader, area, clip):
dask.config.set(scheduler="threads", num_workers=4)
# Get the list of satellite files to open
- filenames = glob(f"{ext_data_path}/satellite_data/{satellite}/*.nc")
+ filenames = glob(f"{ext_data_path}/satellite_data/{satellite}/{case}/*.nc")
reader_kwargs = {}
if clip != "null":
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)
diff --git a/utils/create_reference.py b/utils/create_reference.py
index 5510054099..04bffdd9a3 100644
--- a/utils/create_reference.py
+++ b/utils/create_reference.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2024 Satpy developers
+# Copyright (c) 2024-2025 Satpy developers
#
# This file is part of satpy.
#
@@ -42,7 +42,8 @@ def generate_images(props):
props (namespace): Object with attributes corresponding to command line
arguments as defined by :func:get_parser.
"""
- filenames = (props.basedir / "satellite_data" / props.satellite).glob("*")
+ filenames = (props.basedir / "satellite_data" / props.satellite /
+ props.case).glob("*")
scn = Scene(reader=props.reader, filenames=filenames)
@@ -75,20 +76,18 @@ def get_parser():
"reader", action="store", type=str,
help="Reader name.")
+ parser.add_argument(
+ "case", help="case to generate", type=str)
+
parser.add_argument(
"-b", "--basedir", action="store", type=pathlib.Path,
default=pathlib.Path("."),
help="Base directory for reference data. "
"This must contain a subdirectories satellite_data and "
"reference_images. The directory satellite_data must contain "
- "input data in a subdirectory for the satellite. Output images "
+ "input data in a subdirectory for the satellite and case. Output images "
"will be written to the subdirectory reference_images.")
- parser.add_argument(
- "-o", "--outdir", action="store", type=pathlib.Path,
- default=pathlib.Path("."),
- help="Directory where to write resulting images.")
-
parser.add_argument(
"-c", "--composites", nargs="+", help="composites to generate",
type=str, default=["ash", "airmass"])