diff --git a/satpy/etc/composites/fci.yaml b/satpy/etc/composites/fci.yaml index 07ff48da8a..86e9ba3b46 100644 --- a/satpy/etc/composites/fci.yaml +++ b/satpy/etc/composites/fci.yaml @@ -430,3 +430,77 @@ composites: - name: ir_38 modifiers: [nir_reflectance] standard_name: snow + + masked_colorized_low_level_moisture: + description: > + Like essl_colorized_low_level_moisture, but with clouds masked out + according to the EUMETSAT FCI L2 CLM product. Note that due to the + categorical nature of the mask, resampling this composite should + only be done with nearest neighbour. The colormap for the cloudfree + part has been developed by the European Severe Storms Laboratory (ESSL). + compositor: !!python/name:satpy.composites.MaskingCompositor + standard_name: masked_essl_colorized_low_level_moisture + prerequisites: + - essl_colorized_low_level_moisture + - cloud_state + conditions: + - method: equal + value: Not processed (no or corrupt data) + transparency: 100 + - method: equal + value: Cloud free (no cloud, snow or ice) + transparency: 0 + - method: equal + value: Cloud contaminated (partial or semitransparent cloud) + transparency: 100 + - method: equal + value: Cloud filled (opaque cloud filled) + transparency: 100 + - method: equal + value: Dust contaminated + transparency: 100 + - method: equal + value: Dust filled (opaque) + transparency: 100 + - method: equal + value: Ash contaminated + transparency: 100 + - method: equal + value: Ash filled (opaque) + transparency: 100 + - method: equal + value: Snow or ice contaminated + transparency: 0 + - method: equal + value: Undefined + transparency: 0 + mode: LA + + colorized_low_level_moisture_with_vis06: + description: > + Like essl_colorized_low_level_moisture, but with clouds shown according + to the vis_06 channel (enhanced with effective_solar_pathlength_corrected). + Note that due to the categorical nature of the mask, resampling this + composite should only be done with nearest neighbour. The colormap + for the cloudfree part has been developed by the European Severe Storms + Laboratory (ESSL) + compositor: !!python/name:satpy.composites.BackgroundCompositor + standard_name: image_ready + prerequisites: + - masked_colorized_low_level_moisture + - name: vis_06 + modifiers: [effective_solar_pathlength_corrected] + + colorized_low_level_moisture_with_ir105: + description: > + Like essl_colorized_low_level_moisture, but with clouds shown according + to the in_105 channel (inverted so clouds are white). + Note that due to the categorical nature of the mask, resampling this + composite should only be done with nearest neighbour. The colormap + for the cloudfree part has been developed by the European Severe Storms + Laboratory (ESSL). + compositor: !!python/name:satpy.composites.BackgroundCompositor + standard_name: image_ready + prerequisites: + - masked_colorized_low_level_moisture + - night_ir105 diff --git a/satpy/etc/composites/visir.yaml b/satpy/etc/composites/visir.yaml index fa774e26da..7b6f6d4243 100644 --- a/satpy/etc/composites/visir.yaml +++ b/satpy/etc/composites/visir.yaml @@ -603,9 +603,11 @@ composites: 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 + prerequisites: &llm + - wavelength: 0.905 # workaround for https://github.com/pytroll/satpy/issues/1913 + calibration: reflectance + - wavelength: 0.86 + calibration: reflectance standard_name: essl_low_level_moisture day_essl_low_level_moisture: @@ -625,9 +627,7 @@ composites: 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.905 - - wavelength: 0.86 + prerequisites: *llm standard_name: essl_colorized_low_level_moisture day_essl_colorized_low_level_moisture: diff --git a/satpy/etc/enhancements/generic.yaml b/satpy/etc/enhancements/generic.yaml index b0ba3df206..47e21a5acf 100644 --- a/satpy/etc/enhancements/generic.yaml +++ b/satpy/etc/enhancements/generic.yaml @@ -1241,8 +1241,10 @@ enhancements: operations: [] essl_colorized_low_level_moisture: + # this enhancement is only found if using name but not standard_name + # The colormap was developed by the European Severe Storms Laboratory (ESSL). name: essl_colorized_low_level_moisture - operations: + operations: &masked_llm - name: colorize method: !!python/name:satpy.enhancements.colorize kwargs: @@ -1352,6 +1354,11 @@ enhancements: - [255, 249, 183] - [255, 255, 191] + masked_essl_colorized_low_level_moisture: + # this enhancement is only found if using standard_name but not name + standard_name: masked_essl_colorized_low_level_moisture + operations: *masked_llm + rocket_plume: standard_name: rocket_plume operations: diff --git a/satpy/tests/behave/features/image_comparison.feature b/satpy/tests/behave/features/image_comparison.feature index 686062462c..5fe2e1d383 100755 --- a/satpy/tests/behave/features/image_comparison.feature +++ b/satpy/tests/behave/features/image_comparison.feature @@ -2,15 +2,16 @@ 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 case with for with clipping + When I generate a new image file from case with for resampling with with clipping Then the generated image should be the same as the reference image Examples: - |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 | + |satellite | case | composite | reader | area | resampler | clip | + |Meteosat-12 | scan_night | cloudtop | fci_l1c_nc | sve | gradient_search | True | + |Meteosat-12 | scan_night | night_microphysics | fci_l1c_nc | sve | gradient_search | True | + |Meteosat-12 | mali_day | essl_colorized_low_level_moisture | fci_l1c_nc | mali | gradient_search | False | + |Meteosat-12 | spain_day | colorized_low_level_moisture_with_vis06 | fci_l1c_nc,fci_l2_nc | spain | nearest | False | + |GOES17 | americas_night | airmass | abi_l1b | null | null | null | + |GOES16 | americas_night | airmass | abi_l1b | null | null | null | + |GOES16 | americas_night | ash | abi_l1b | null | null | null | + |GOES17 | americas_night | ash | abi_l1b | null | null | null | diff --git a/satpy/tests/behave/features/steps/image_comparison.py b/satpy/tests/behave/features/steps/image_comparison.py index 5e7135bc53..fccd03650c 100644 --- a/satpy/tests/behave/features/steps/image_comparison.py +++ b/satpy/tests/behave/features/steps/image_comparison.py @@ -27,6 +27,7 @@ import numpy as np from behave import given, then, when +import satpy from satpy import Scene ext_data_path = "/app/ext_data" @@ -64,8 +65,8 @@ def step_given_reference_image(context, composite, satellite, area): @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): + "with {reader} for {area} resampling with {resampler} with clipping {clip}") +def step_when_generate_image(context, composite, satellite, case, reader, area, resampler, clip): """Generate test images.""" os.environ["OMP_NUM_THREADS"] = os.environ["MKL_NUM_THREADS"] = "2" os.environ["PYTROLL_CHUNK_SIZE"] = "1024" @@ -75,17 +76,17 @@ def step_when_generate_image(context, composite, satellite, case, reader, area, # Get the list of satellite files to open filenames = glob(f"{ext_data_path}/satellite_data/{satellite}/{case}/*.nc") - reader_kwargs = {} - if clip != "null": - reader_kwargs["clip_negative_radiances"] = clip - scn = Scene(reader=reader, filenames=filenames, reader_kwargs=reader_kwargs) - scn.load([composite]) + if "," in reader: + reader = reader.split(",") + with satpy.config.set({"readers.clip_negative_radiances": False if clip == "null" else clip}): + scn = Scene(reader=reader, filenames=filenames) + scn.load([composite]) if area == "null": ls = scn else: - ls = scn.resample(area, resampler="gradient_search") + ls = scn.resample(area, resampler=resampler) # Save the generated image in the generated folder generated_image_path = os.path.join(context.test_results_dir, "generated", diff --git a/satpy/tests/etc/readers/fake1.yaml b/satpy/tests/etc/readers/fake1.yaml index b34c7e1b34..746c2ae04c 100644 --- a/satpy/tests/etc/readers/fake1.yaml +++ b/satpy/tests/etc/readers/fake1.yaml @@ -60,6 +60,7 @@ datasets: ds8: name: ds8 wavelength: [0.7, 0.8, 0.9] + calibration: "reflectance" file_type: fake_file1 coordinates: [lons, lats] ds9_fail_load: @@ -70,6 +71,7 @@ datasets: ds10: name: ds10 wavelength: [0.75, 0.85, 0.95] + calibration: "reflectance" file_type: fake_file1 coordinates: [lons, lats] ds11: diff --git a/utils/create_reference.py b/utils/create_reference.py index 04bffdd9a3..6a63e5c0da 100644 --- a/utils/create_reference.py +++ b/utils/create_reference.py @@ -45,13 +45,19 @@ def generate_images(props): filenames = (props.basedir / "satellite_data" / props.satellite / props.case).glob("*") - scn = Scene(reader=props.reader, filenames=filenames) + if "," in props.reader: + reader = props.reader.split(",") + resampler = "nearest" # use nearest when combining with cloud mask + else: + reader = props.reader + resampler = "gradient_search" + scn = Scene(reader=reader, filenames=filenames) scn.load(props.composites) if props.area == "native": ls = scn.resample(resampler="native") elif props.area is not None: - ls = scn.resample(props.area, resampler="gradient_search") + ls = scn.resample(props.area, resampler=resampler) else: ls = scn @@ -74,7 +80,7 @@ def get_parser(): parser.add_argument( "reader", action="store", type=str, - help="Reader name.") + help="Reader name. Multiple readers (if needed) can be comma-seperated.") parser.add_argument( "case", help="case to generate", type=str)