Skip to content

Commit 99bc425

Browse files
Azadeh GholoubiAzadeh Gholoubi
Azadeh Gholoubi
authored and
Azadeh Gholoubi
committed
[End- to End Test code sprint] Add SEVIRI METEOSAT-8 and METEOSAT-11 (#765)
End-to-end testing for Spinning Enhanced Visible and InfraRed Imager (SEVIRI) from METEOSAT-8 and to METEOSAT-11 new files include: parm/ioda/bufr2ioda//bufr2ioda_sevcsr.json : JSON containing data format, sensor/bufr2ioda_sevcsr.json, and satellite information ush/ioda/bufr2ioda/bufr2ioda_sevcsr.py: bufr2ioda code for extracting SEVIRI METEOSAT-8 and METEOSAT-11 from BUFR
1 parent 2dd725f commit 99bc425

File tree

2 files changed

+393
-0
lines changed

2 files changed

+393
-0
lines changed
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"data_format" : "bufr_d",
3+
"data_type" : "sevcsr",
4+
"cycle_type" : "{{ RUN }}",
5+
"cycle_datetime" : "{{ current_cycle | to_YMDH }}",
6+
"dump_directory" : "{{ DMPDIR }}",
7+
"ioda_directory" : "{{ COM_OBS }}",
8+
"subsets" : [ ],
9+
"data_description" : "NC021043 SEVIRI, Meteosat-11; NC021043 SEVIRI, Meteosat-08",
10+
"data_provider" : "U.S. NOAA/NESDIS",
11+
"sensor_info" : { "sensor_name": "SEVIRI", "sensor_full_name": "Spinning Enhanced Visible Infra-Red Imager", "sensor_id": 617 },
12+
"satellite_info" : [
13+
{ "satellite_name": "m11", "satellite_full_name": "Meteosat-11", "satellite_id": 70, "launch time": "20150715" },
14+
{ "satellite_name": "m08", "satellite_full_name": "Meteosat-08", "satellite_id": 55, "launch time": "20020828" } ]
15+
}
+378
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,378 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import numpy as np
4+
import numpy.ma as ma
5+
from pyiodaconv import bufr
6+
import calendar
7+
import json
8+
import time
9+
import math
10+
import datetime
11+
import os
12+
from datetime import datetime
13+
from pyioda import ioda_obs_space as ioda_ospace
14+
from wxflow import Logger
15+
16+
# Define and initialize global variables
17+
global float32_fill_value
18+
global int32_fill_value
19+
global int64_fill_value
20+
21+
float32_fill_value = np.float32(0)
22+
int32_fill_value = np.int32(0)
23+
int64_fill_value = np.int64(0)
24+
25+
26+
def bufr_to_ioda(config, logger):
27+
28+
subsets = config["subsets"]
29+
logger.debug(f"Checking subsets = {subsets}")
30+
31+
# Get parameters from configuration
32+
subsets = config["subsets"]
33+
data_format = config["data_format"]
34+
data_type = config["data_type"]
35+
data_description = config["data_description"]
36+
data_provider = config["data_provider"]
37+
cycle_type = config["cycle_type"]
38+
dump_dir = config["dump_directory"]
39+
ioda_dir = config["ioda_directory"]
40+
cycle = config["cycle_datetime"]
41+
yyyymmdd = cycle[0:8]
42+
hh = cycle[8:10]
43+
44+
satellite_info_array = config["satellite_info"]
45+
sensor_name = config["sensor_info"]["sensor_name"]
46+
sensor_full_name = config["sensor_info"]["sensor_full_name"]
47+
sensor_id = config["sensor_info"]["sensor_id"]
48+
49+
# Get derived parameters
50+
yyyymmdd = cycle[0:8]
51+
hh = cycle[8:10]
52+
reference_time = datetime.strptime(cycle, "%Y%m%d%H")
53+
reference_time = reference_time.strftime("%Y-%m-%dT%H:%M:%SZ")
54+
55+
# General informaton
56+
converter = 'BUFR to IODA Converter'
57+
process_level = 'Level-2'
58+
platform_description = 'NOAA Series of Geostationary Operational Environmental Satellites - 3rd generation since 2016'
59+
sensor_description = '16 channels, balaned visible, near IR, short-wave IR, mid-wave IR, and thermal IR; \
60+
central wavelentgh ranges from 470 nm to 13.3 micron'
61+
62+
logger.info(f'sensor_name = {sensor_name}')
63+
logger.info(f'sensor_full_name = {sensor_full_name}')
64+
logger.info(f'sensor_id = {sensor_id}')
65+
logger.info(f'reference_time = {reference_time}')
66+
67+
bufrfile = f"{cycle_type}.t{hh}z.{data_type}.tm00.{data_format}"
68+
DATA_PATH = os.path.join(dump_dir, f"{cycle_type}.{yyyymmdd}", str(hh), 'atmos', bufrfile)
69+
70+
# ============================================
71+
# Make the QuerySet for all the data we want
72+
# ============================================
73+
start_time = time.time()
74+
75+
logger.info('Making QuerySet')
76+
q = bufr.QuerySet(subsets)
77+
78+
# MetaData
79+
q.add('latitude', '*/CLATH')
80+
q.add('longitude', '*/CLONH')
81+
q.add('satelliteId', '*/SAID')
82+
q.add('year', '*/YEAR')
83+
q.add('month', '*/MNTH')
84+
q.add('day', '*/DAYS')
85+
q.add('hour', '*/HOUR')
86+
q.add('minute', '*/MINU')
87+
q.add('second', '*/SECO')
88+
#azadeh added
89+
q.add('sensorId', '*/SIID[1]')
90+
q.add('sensorZenithAngle', '*/SAZA')
91+
q.add('sensorCentralFrequency', '*/SCCF')
92+
93+
# azadeh added:
94+
q.add('solarZenithAngle', '*/SOZA')
95+
q.add('cloudFree', '*/NCLDMNT')
96+
# ObsValue
97+
q.add('brightnessTemperature', '*/TMBRST')
98+
# ClearSkyStdDev
99+
q.add('ClearSkyStdDev', '*/SDTB/TMBRST')
100+
101+
102+
end_time = time.time()
103+
running_time = end_time - start_time
104+
logger.debug(f'Processing time for making QuerySet : {running_time} seconds')
105+
106+
# ==============================================================
107+
# Open the BUFR file and execute the QuerySet to get ResultSet
108+
# Use the ResultSet returned to get numpy arrays of the data
109+
# ==============================================================
110+
start_time = time.time()
111+
112+
logger.info('Executing QuerySet to get ResultSet')
113+
with bufr.File(DATA_PATH) as f:
114+
r = f.execute(q)
115+
116+
# MetaData
117+
satid = r.get('satelliteId')
118+
instid = r.get('sensorId')
119+
year = r.get('year')
120+
month = r.get('month')
121+
day = r.get('day')
122+
hour = r.get('hour')
123+
minute = r.get('minute')
124+
second = r.get('second')
125+
lat = r.get('latitude')
126+
lon = r.get('longitude')
127+
satzenang = r.get('sensorZenithAngle')
128+
chanfreq = r.get('sensorCentralFrequency', type='float')
129+
BT= r.get('brightnessTemperature')
130+
clrStdDev= r.get('ClearSkyStdDev')
131+
cldFree= r.get('cloudFree')
132+
solzenang=r.get('solarZenithAngle')
133+
# DateTime: seconds since Epoch time
134+
# IODA has no support for numpy datetime arrays dtype=datetime64[s]
135+
timestamp = r.get_datetime('year', 'month', 'day', 'hour', 'minute', 'second').astype(np.int64)
136+
137+
# Check BUFR variable generic dimension and type
138+
139+
# Global variables declaration
140+
# Set global fill values
141+
float32_fill_value = satzenang.fill_value
142+
int32_fill_value = satid.fill_value
143+
int64_fill_value = timestamp.fill_value.astype(np.int64)
144+
145+
end_time = time.time()
146+
running_time = end_time - start_time
147+
logger.info(f'Processing time for executing QuerySet to get ResultSet : {running_time} seconds')
148+
149+
# =========================
150+
# Create derived variables
151+
# =========================
152+
start_time = time.time()
153+
#azadeh added
154+
if np.ma.is_masked(satzenang[0]):
155+
# Handle the masked value
156+
scanpos = np.array([int32_fill_value], dtype=np.int64)
157+
158+
else:
159+
# Convert the non-masked value to an integer
160+
scanpos = np.array([int(satzenang[0]) + 1], dtype=np.int32)
161+
162+
cloudAmount=1-cldFree
163+
channum=12
164+
165+
logger.info('Creating derived variables')
166+
167+
end_time = time.time()
168+
running_time = end_time - start_time
169+
logger.info(f'Processing time for creating derived variables : {running_time} seconds')
170+
171+
# =====================================
172+
# Split output based on satellite id
173+
# Create IODA ObsSpace
174+
# Write IODA output
175+
# =====================================
176+
logger.info('Create IODA ObsSpace and Write IODA output based on satellite ID')
177+
178+
# Find unique satellite identifiers in data to process
179+
unique_satids = np.unique(satid)
180+
logger.info(f'Number of Unique satellite identifiers: {len(unique_satids)}')
181+
logger.info(f'Unique satellite identifiers: {unique_satids}')
182+
183+
logger.debug(f'Loop through unique satellite identifier {unique_satids}')
184+
total_ob_processed = 0
185+
for sat in unique_satids.tolist():
186+
start_time = time.time()
187+
188+
matched = False
189+
for satellite_info in satellite_info_array:
190+
if (satellite_info["satellite_id"] == sat):
191+
matched = True
192+
satellite_id = satellite_info["satellite_id"]
193+
satellite_name = satellite_info["satellite_name"]
194+
satinst = sensor_name.lower()+'_'+satellite_name.lower()
195+
logger.debug(f'Split data for {satinst} satid = {sat}')
196+
197+
if matched:
198+
199+
# Define a boolean mask to subset data from the original data object
200+
mask = satid == sat
201+
# MetaData
202+
lon2 = lon[mask]
203+
lat2 = lat[mask]
204+
timestamp2 = timestamp[mask]
205+
satid2 = satid[mask]
206+
instid2 = instid[mask]
207+
satzenang2 = satzenang[mask]
208+
chanfreq2 = chanfreq[mask]
209+
# obstype2 = obstype[mask]
210+
#azadeh added
211+
solzenang2 = solzenang[mask]
212+
cldFree2 = cldFree[mask]
213+
cloudAmount2 = cloudAmount[mask]
214+
BT2= BT[mask]
215+
clrStdDev2= clrStdDev[mask]
216+
217+
# Timestamp Range
218+
timestamp2_min = datetime.fromtimestamp(timestamp2.min())
219+
timestamp2_max = datetime.fromtimestamp(timestamp2.max())
220+
221+
# Check unique observation time
222+
unique_timestamp2 = np.unique(timestamp2)
223+
logger.debug(f'Processing output for satid {sat}')
224+
225+
# Define Channel dimension for channels 4 to 11 since the other channel values are missing.( AZADEH)
226+
channel_start = 4
227+
channel_end = 11
228+
229+
# Create the dimensions
230+
dims = {
231+
'Location': np.arange(0, BT2.shape[0]),
232+
'Channel' : np.arange(channel_start, channel_end + 1)}
233+
234+
# Create IODA ObsSpace
235+
iodafile = f"{cycle_type}.t{hh}z.{satinst}.tm00.nc"
236+
OUTPUT_PATH = os.path.join(ioda_dir, iodafile)
237+
logger.info(f'Create output file : {OUTPUT_PATH}')
238+
obsspace = ioda_ospace.ObsSpace(OUTPUT_PATH, mode='w', dim_dict=dims)
239+
240+
# Create Global attributes
241+
logger.debug('Write global attributes')
242+
obsspace.write_attr('Converter', converter)
243+
obsspace.write_attr('sourceFiles', bufrfile)
244+
# obsspace.write_attr('dataProviderOrigin', data_provider)
245+
obsspace.write_attr('description', data_description)
246+
obsspace.write_attr('datetimeReference', reference_time)
247+
obsspace.write_attr('datetimeRange', [str(timestamp2_min), str(timestamp2_max)])
248+
obsspace.write_attr('sensor', sensor_id)
249+
obsspace.write_attr('platform', satellite_id)
250+
obsspace.write_attr('platformCommonName', satellite_name)
251+
obsspace.write_attr('sensorCommonName', sensor_name)
252+
obsspace.write_attr('processingLevel', process_level)
253+
obsspace.write_attr('platformLongDescription', platform_description)
254+
obsspace.write_attr('sensorLongDescription', sensor_description)
255+
256+
# Create IODA variables
257+
logger.debug('Write variables: name, type, units, and attributes')
258+
# Longitude
259+
obsspace.create_var('MetaData/longitude', dtype=lon2.dtype, fillval=lon2.fill_value) \
260+
.write_attr('units', 'degrees_east') \
261+
.write_attr('valid_range', np.array([-180, 180], dtype=np.float32)) \
262+
.write_attr('long_name', 'Longitude') \
263+
.write_data(lon2)
264+
265+
266+
# Latitude
267+
obsspace.create_var('MetaData/latitude', dtype=lat.dtype, fillval=lat2.fill_value) \
268+
.write_attr('units', 'degrees_north') \
269+
.write_attr('valid_range', np.array([-90, 90], dtype=np.float32)) \
270+
.write_attr('long_name', 'Latitude') \
271+
.write_data(lat2)
272+
273+
# Datetime
274+
obsspace.create_var('MetaData/dateTime', dtype=np.int64, fillval=int64_fill_value) \
275+
.write_attr('units', 'seconds since 1970-01-01T00:00:00Z') \
276+
.write_attr('long_name', 'Datetime') \
277+
.write_data(timestamp2)
278+
279+
# Satellite Identifier
280+
obsspace.create_var('MetaData/satelliteIdentifier', dtype=satid2.dtype, fillval=satid2.fill_value) \
281+
.write_attr('long_name', 'Satellite Identifier') \
282+
.write_data(satid2)
283+
284+
# Instrument Identifier
285+
obsspace.create_var('MetaData/instrumentIdentifier', dtype=instid2.dtype, fillval=instid2.fill_value) \
286+
.write_attr('long_name', 'Satellite Instrument Identifier') \
287+
.write_data(instid2)
288+
289+
290+
# Scan Position (derived variable, need to specified fill value explicitly)Azadeh
291+
obsspace.create_var('MetaData/sensorScanPosition', dtype=scanpos.dtype, fillval=int32_fill_value) \
292+
.write_attr('long_name', 'Sensor Scan Position') \
293+
.write_data(scanpos)
294+
295+
296+
# Sensor Zenith Angle
297+
obsspace.create_var('MetaData/sensorZenithAngle', dtype=satzenang2.dtype, fillval=satzenang2.fill_value) \
298+
.write_attr('units', 'degree') \
299+
.write_attr('valid_range', np.array([0, 90], dtype=np.float32)) \
300+
.write_attr('long_name', 'Sensor Zenith Angle') \
301+
.write_data(satzenang2)
302+
303+
304+
# Sensor Centrall Frequency
305+
obsspace.create_var('MetaData/sensorCentralFrequency', dtype=chanfreq2.dtype, fillval=chanfreq2.fill_value) \
306+
.write_attr('units', 'Hz') \
307+
.write_attr('long_name', 'Satellite Channel Center Frequency') \
308+
.write_data(chanfreq2)
309+
310+
# Solar Zenith Angle azadeh
311+
obsspace.create_var('MetaData/solarZenithAngle', dtype=solzenang2.dtype, fillval=solzenang2.fill_value) \
312+
.write_attr('units', 'degree') \
313+
.write_attr('valid_range', np.array([0, 180], dtype=np.float32)) \
314+
.write_attr('long_name', 'Solar Zenith Angle') \
315+
.write_data(solzenang2)
316+
317+
# Cloud free Azadeh
318+
obsspace.create_var('MetaData/cloudFree', dtype=cldFree2.dtype, fillval=cldFree2.fill_value) \
319+
.write_attr('units', '1') \
320+
.write_attr('valid_range', np.array([0, 1], dtype=np.float32)) \
321+
.write_attr('long_name', 'Amount Segment Cloud Free') \
322+
.write_data(cldFree2)
323+
324+
325+
# Cloud amount based on computation Azadeh
326+
obsspace.create_var('MetaData/cloudAmount', dtype=cloudAmount2.dtype, fillval=cloudAmount2.fill_value) \
327+
.write_attr('units', '1') \
328+
.write_attr('valid_range', np.array([0, 1], dtype=np.float32)) \
329+
.write_attr('long_name', 'Amount of cloud coverage in layer') \
330+
.write_data(cloudAmount2)
331+
332+
# ObsType based on computation method/spectral band
333+
obsspace.create_var('ObsValue/brightnessTemperature', dim_list=['Location','Channel'],dtype=np.float32, fillval=float32_fill_value) \
334+
.write_attr('units', 'k') \
335+
.write_attr('long_name', 'Brightness Temperature') \
336+
.write_data(BT2)
337+
338+
339+
obsspace.create_var('ClearSkyStdDev/brightnessTemperature', dim_list=['Location','Channel'],dtype=np.float32, fillval=float32_fill_value) \
340+
.write_attr('long_name', 'Standard Deviation Brightness Temperature') \
341+
.write_data(clrStdDev2)
342+
343+
344+
345+
end_time = time.time()
346+
running_time = end_time - start_time
347+
total_ob_processed += len(satid2)
348+
logger.debug(f'Number of observation processed : {len(satid2)}')
349+
logger.debug(f'Processing time for splitting and output IODA for {satinst} : {running_time} seconds')
350+
351+
else:
352+
logger.info(f"Do not find this satellite id in the configuration: satid = {sat}")
353+
354+
logger.info("All Done!")
355+
logger.info(f'Total number of observation processed : {total_ob_processed}')
356+
357+
358+
if __name__ == '__main__':
359+
360+
start_time = time.time()
361+
362+
parser = argparse.ArgumentParser()
363+
parser.add_argument('-c', '--config', type=str, help='Input JSON configuration', required=True)
364+
parser.add_argument('-v', '--verbose', help='print debug logging information',
365+
action='store_true')
366+
args = parser.parse_args()
367+
368+
log_level = 'DEBUG' if args.verbose else 'INFO'
369+
logger = Logger('bufr2ioda_sevcsr.py', level=log_level, colored_log=True)
370+
371+
with open(args.config, "r") as json_file:
372+
config = json.load(json_file)
373+
374+
bufr_to_ioda(config, logger)
375+
376+
end_time = time.time()
377+
running_time = end_time - start_time
378+
logger.info(f"Total running time: {running_time} seconds")

0 commit comments

Comments
 (0)