Skip to content

Commit 3990f37

Browse files
authored
Merge pull request #2895 from ClementLaplace/add_flash_age_compositor
Add flash age compositor for li instruments
2 parents 9a40616 + 01237e2 commit 3990f37

File tree

5 files changed

+383
-117
lines changed

5 files changed

+383
-117
lines changed

satpy/composites/lightning.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/usr/bin/env python
2+
# -*- coding: utf-8 -*-
3+
# Copyright (c) 2019 Satpy developers
4+
#
5+
# This file is part of satpy.
6+
#
7+
# satpy is free software: you can redistribute it and/or modify it under the
8+
# terms of the GNU General Public License as published by the Free Software
9+
# Foundation, either version 3 of the License, or (at your option) any later
10+
# version.
11+
#
12+
# satpy is distributed in the hope that it will be useful, but WITHOUT ANY
13+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
14+
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15+
#
16+
# You should have received a copy of the GNU General Public License along with
17+
# satpy. If not, see <http://www.gnu.org/licenses/>.
18+
"""Composite classes for the LI instrument."""
19+
20+
import logging
21+
22+
import numpy as np
23+
import xarray as xr
24+
25+
from satpy.composites import CompositeBase
26+
27+
LOG = logging.getLogger(__name__)
28+
29+
30+
class LightningTimeCompositor(CompositeBase):
31+
"""Class used to create the flash_age compositor usefull for lighting event visualisation.
32+
33+
The datas used are dates related to the lightning event that should be normalised between
34+
0 and 1. The value 1 corresponds to the latest lightning event and the value 0 corresponds
35+
to the latest lightning event - time_range. The time_range is defined in the satpy/etc/composites/li.yaml
36+
and is in minutes.
37+
"""
38+
def __init__(self, name, prerequisites=None, optional_prerequisites=None, **kwargs):
39+
"""Initialisation of the class."""
40+
super().__init__(name, prerequisites, optional_prerequisites, **kwargs)
41+
# Get the time_range which is in minute
42+
self.time_range = self.attrs["time_range"]
43+
self.standard_name = self.attrs["standard_name"]
44+
self.reference_time_attr = self.attrs["reference_time"]
45+
46+
47+
def _normalize_time(self, data:xr.DataArray, attrs:dict) -> xr.DataArray:
48+
"""Normalize the time in the range between [end_time, end_time - time_range].
49+
50+
The range of the normalised data is between 0 and 1 where 0 corresponds to the date end_time - time_range
51+
and 1 to the end_time. Where end_times represent the latest lightning event and time_range is the range of
52+
time you want to see the event.The dates that are earlier to end_time - time_range are removed.
53+
54+
Args:
55+
data (xr.DataArray): datas containing dates to be normalised
56+
attrs (dict): Attributes suited to the flash_age composite
57+
58+
Returns:
59+
xr.DataArray: Normalised time
60+
"""
61+
# Compute the maximum time value
62+
end_time = np.array(np.datetime64(data.attrs[self.reference_time_attr]))
63+
# Compute the minimum time value based on the time range
64+
begin_time = end_time - np.timedelta64(self.time_range, "m")
65+
# Drop values that are bellow begin_time
66+
condition_time = data >= begin_time
67+
condition_time_computed = condition_time.compute()
68+
data = data.where(condition_time_computed, drop=True)
69+
# exit if data is empty afer filtering
70+
if data.size == 0 :
71+
LOG.error(f"All the flash_age events happened before {begin_time}")
72+
raise ValueError(f"Invalid data: data size is zero. All flash_age "
73+
f"events occurred before the specified start time ({begin_time})."
74+
)
75+
# Normalize the time values
76+
normalized_data = (data - begin_time) / (end_time - begin_time)
77+
# Ensure the result is still an xarray.DataArray
78+
return xr.DataArray(normalized_data, dims=data.dims, coords=data.coords, attrs=attrs)
79+
80+
81+
@staticmethod
82+
def _update_missing_metadata(existing_attrs, new_attrs):
83+
for key, val in new_attrs.items():
84+
if key not in existing_attrs and val is not None:
85+
existing_attrs[key] = val
86+
87+
def _redefine_metadata(self,attrs:dict)->dict:
88+
"""Modify the standard_name and name metadatas.
89+
90+
Args:
91+
attrs (dict): data's attributes
92+
93+
Returns:
94+
dict: atualised attributes
95+
"""
96+
attrs["name"] = self.standard_name
97+
attrs["standard_name"] = self.standard_name
98+
# Attributes to describe the values range
99+
return attrs
100+
101+
102+
def __call__(self,projectables, nonprojectables=None, **attrs):
103+
"""Normalise the dates."""
104+
data = projectables[0]
105+
new_attrs = data.attrs.copy()
106+
self._update_missing_metadata(new_attrs, attrs)
107+
new_attrs = self._redefine_metadata(new_attrs)
108+
return self._normalize_time(data, new_attrs)

satpy/etc/composites/fci.yaml

Lines changed: 78 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
sensor_name: visir/fci
22

33
composites:
4-
### L2
4+
### L2
55
binary_cloud_mask:
66
# This will set all clear pixels to '0', all pixels with cloudy features (meteorological/dust/ash clouds) to '1' and
77
# missing/undefined pixels to 'nan'. This can be used for the official EUMETSAT cloud mask product (CLM).
88
compositor: !!python/name:satpy.composites.CategoricalDataCompositor
99
prerequisites:
10-
- name: 'cloud_state'
11-
lut: [ .nan, 0, 1, 1, 1, 1, 1, 1, 0, .nan ]
10+
- name: "cloud_state"
11+
lut: [.nan, 0, 1, 1, 1, 1, 1, 1, 0, .nan]
1212
standard_name: binary_cloud_mask
1313

14-
### Night Layers
14+
### Night Layers
1515
night_ir105:
1616
compositor: !!python/name:satpy.composites.SingleBandCompositor
1717
prerequisites:
@@ -41,7 +41,7 @@ composites:
4141
- night_ir_alpha
4242
- _night_background_hires
4343

44-
### Green Corrections
44+
### Green Corrections
4545
ndvi_hybrid_green:
4646
description: >
4747
The FCI green band at 0.51 µm deliberately misses the chlorophyll band, such that
@@ -58,7 +58,7 @@ composites:
5858
- name: vis_06
5959
modifiers: [sunz_corrected, rayleigh_corrected, sunz_reduced]
6060
- name: vis_08
61-
modifiers: [sunz_corrected, sunz_reduced ]
61+
modifiers: [sunz_corrected, sunz_reduced]
6262
standard_name: toa_bidirectional_reflectance
6363

6464
ndvi_hybrid_green_raw:
@@ -76,18 +76,18 @@ composites:
7676
ndvi_hybrid_green_fully_sunzencorrected:
7777
description: Same as ndvi_hybrid_green, but without Sun-zenith reduction
7878
compositor: !!python/name:satpy.composites.spectral.NDVIHybridGreen
79-
limits: [ 0.15, 0.05 ]
79+
limits: [0.15, 0.05]
8080
strength: 3.0
8181
prerequisites:
8282
- name: vis_05
83-
modifiers: [ sunz_corrected, rayleigh_corrected ]
83+
modifiers: [sunz_corrected, rayleigh_corrected]
8484
- name: vis_06
85-
modifiers: [ sunz_corrected, rayleigh_corrected ]
85+
modifiers: [sunz_corrected, rayleigh_corrected]
8686
- name: vis_08
87-
modifiers: [ sunz_corrected ]
87+
modifiers: [sunz_corrected]
8888
standard_name: toa_bidirectional_reflectance
8989

90-
### True Color
90+
### True Color
9191
true_color:
9292
compositor: !!python/name:satpy.composites.SelfSharpenedRGB
9393
description: >
@@ -190,7 +190,7 @@ composites:
190190
- name: vis_04
191191
standard_name: true_color_reproduction_color_stretch
192192

193-
### True Color with LI lightning
193+
### True Color with LI lightning
194194

195195
true_color_with_night_ir105_acc_flash:
196196
compositor: !!python/name:satpy.composites.BackgroundCompositor
@@ -227,74 +227,81 @@ composites:
227227
- group_radiance_alpha
228228
- true_color_with_night_ir105
229229

230-
### GeoColor
230+
true_color_with_night_ir105_flash_age:
231+
compositor: !!python/name:satpy.composites.BackgroundCompositor
232+
standard_name: imager_with_lightning
233+
prerequisites:
234+
- flash_age
235+
- true_color_with_night_ir105
236+
237+
### GeoColor
231238
geo_color:
232-
compositor: !!python/name:satpy.composites.DayNightCompositor
233-
description: >
234-
GeoColor is a multi-layer blended RGB composite where the day-time part of the image is represented by true
235-
color imagery and the nighttime part of the image by a three layer vertically blended stack composed of a
236-
high-level cloud layer (single IR window channel), a low-level cloud layer (IR split window) and a static
237-
surface terrain layer with city lights (NASA Black Marble).
238-
references:
239-
Research Article: https://journals.ametsoc.org/view/journals/atot/37/3/JTECH-D-19-0134.1.xml
240-
lim_low: 78
241-
lim_high: 88
242-
standard_name: geo_color_day_night_blend
243-
prerequisites:
244-
- true_color
245-
- geo_color_night
239+
compositor: !!python/name:satpy.composites.DayNightCompositor
240+
description: >
241+
GeoColor is a multi-layer blended RGB composite where the day-time part of the image is represented by true
242+
color imagery and the nighttime part of the image by a three layer vertically blended stack composed of a
243+
high-level cloud layer (single IR window channel), a low-level cloud layer (IR split window) and a static
244+
surface terrain layer with city lights (NASA Black Marble).
245+
references:
246+
Research Article: https://journals.ametsoc.org/view/journals/atot/37/3/JTECH-D-19-0134.1.xml
247+
lim_low: 78
248+
lim_high: 88
249+
standard_name: geo_color_day_night_blend
250+
prerequisites:
251+
- true_color
252+
- geo_color_night
246253

247254
geo_color_high_clouds:
248-
standard_name: geo_color_high_clouds
249-
compositor: !!python/name:satpy.composites.HighCloudCompositor
250-
prerequisites:
251-
- name: ir_105
255+
standard_name: geo_color_high_clouds
256+
compositor: !!python/name:satpy.composites.HighCloudCompositor
257+
prerequisites:
258+
- name: ir_105
252259

253260
geo_color_low_clouds:
254-
standard_name: geo_color_low_clouds
255-
compositor: !!python/name:satpy.composites.LowCloudCompositor
256-
values_water: 0
257-
values_land: 100
258-
range_water: [0.0, 4.0]
259-
range_land: [1.5, 4.0]
260-
prerequisites:
261-
- compositor: !!python/name:satpy.composites.DifferenceCompositor
262-
prerequisites:
263-
- name: ir_105
264-
- name: ir_38
265-
- name: ir_105
266-
- compositor: !!python/name:satpy.composites.StaticImageCompositor
267-
standard_name: land_water_mask
268-
url: "https://zenodo.org/records/10076199/files/gshhs_land_water_mask_3km_i.tif"
269-
known_hash: "sha256:96df83c57416217e191f95dde3d3c1ce0373a8fc220e929228873db246ca3569"
261+
standard_name: geo_color_low_clouds
262+
compositor: !!python/name:satpy.composites.LowCloudCompositor
263+
values_water: 0
264+
values_land: 100
265+
range_water: [0.0, 4.0]
266+
range_land: [1.5, 4.0]
267+
prerequisites:
268+
- compositor: !!python/name:satpy.composites.DifferenceCompositor
269+
prerequisites:
270+
- name: ir_105
271+
- name: ir_38
272+
- name: ir_105
273+
- compositor: !!python/name:satpy.composites.StaticImageCompositor
274+
standard_name: land_water_mask
275+
url: "https://zenodo.org/records/10076199/files/gshhs_land_water_mask_3km_i.tif"
276+
known_hash: "sha256:96df83c57416217e191f95dde3d3c1ce0373a8fc220e929228873db246ca3569"
270277

271278
geo_color_background_with_low_clouds:
272-
compositor: !!python/name:satpy.composites.BackgroundCompositor
273-
standard_name: night_ir_with_background
274-
prerequisites:
275-
- geo_color_low_clouds
276-
- _night_background_hires
279+
compositor: !!python/name:satpy.composites.BackgroundCompositor
280+
standard_name: night_ir_with_background
281+
prerequisites:
282+
- geo_color_low_clouds
283+
- _night_background_hires
277284

278285
geo_color_night:
279-
compositor: !!python/name:satpy.composites.BackgroundCompositor
280-
standard_name: night_ir_with_background
281-
prerequisites:
282-
- geo_color_high_clouds
283-
- geo_color_background_with_low_clouds
286+
compositor: !!python/name:satpy.composites.BackgroundCompositor
287+
standard_name: night_ir_with_background
288+
prerequisites:
289+
- geo_color_high_clouds
290+
- geo_color_background_with_low_clouds
284291

285-
### IR-Sandwich
292+
### IR-Sandwich
286293
ir_sandwich:
287294
compositor: !!python/name:satpy.composites.SandwichCompositor
288295
standard_name: ir_sandwich
289296
prerequisites:
290-
- name: 'vis_06'
291-
modifiers: [ sunz_corrected ]
297+
- name: "vis_06"
298+
modifiers: [sunz_corrected]
292299
- name: colorized_ir_clouds
293300

294301
colorized_ir_clouds:
295302
compositor: !!python/name:satpy.composites.SingleBandCompositor
296303
prerequisites:
297-
- name: 'ir_105'
304+
- name: "ir_105"
298305
standard_name: colorized_ir_clouds
299306

300307
ir_sandwich_with_night_colorized_ir_clouds:
@@ -306,7 +313,7 @@ composites:
306313
- ir_sandwich
307314
- colorized_ir_clouds
308315

309-
### other RGBs
316+
### other RGBs
310317
cloud_type:
311318
description: >
312319
Equal to cimss_cloud_type recipe, but with additional sunz_reducer modifier to avoid saturation at the terminator.
@@ -316,11 +323,11 @@ composites:
316323
compositor: !!python/name:satpy.composites.GenericCompositor
317324
prerequisites:
318325
- name: nir_13
319-
modifiers: [ sunz_corrected, sunz_reduced ]
326+
modifiers: [sunz_corrected, sunz_reduced]
320327
- name: vis_06
321-
modifiers: [ sunz_corrected, sunz_reduced ]
328+
modifiers: [sunz_corrected, sunz_reduced]
322329
- name: nir_16
323-
modifiers: [ sunz_corrected, sunz_reduced ]
330+
modifiers: [sunz_corrected, sunz_reduced]
324331
standard_name: cimss_cloud_type
325332

326333
cloud_type_with_night_ir105:
@@ -416,10 +423,10 @@ composites:
416423
Recipe: https://resources.eumetrain.org/RGBguide/recipes/RGB_recipes.pdf
417424
compositor: !!python/name:satpy.composites.GenericCompositor
418425
prerequisites:
419-
- name: vis_08
420-
modifiers: [sunz_corrected]
421-
- name: nir_16
422-
modifiers: [sunz_corrected]
423-
- name: ir_38
424-
modifiers: [nir_reflectance]
426+
- name: vis_08
427+
modifiers: [sunz_corrected]
428+
- name: nir_16
429+
modifiers: [sunz_corrected]
430+
- name: ir_38
431+
modifiers: [nir_reflectance]
425432
standard_name: snow

0 commit comments

Comments
 (0)