Skip to content

Commit eccc8d9

Browse files
committed
adding support for handling merci3 data by renaming and extending the
merci2 script
1 parent 0cab9b7 commit eccc8d9

File tree

6 files changed

+79
-94
lines changed

6 files changed

+79
-94
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ agency specific level-1 formats. So far, supports
1010

1111
- EUMETSAT Meteosat Second Generation SEVIRI HRIT level-1.5
1212
- NOAA AVHRR GAC
13-
- MERSI-2 level-1
13+
- MERSI-2/3 level-1
1414
- MODIS level-1
1515
- AVHRR eps level-1b
1616
- AVHRR AAPP level-1b
1717
- VIIRS level-1b
1818
- SLSTR level-1b
1919
- EPS-SG MetImage level-1 test data
20-
- EUMETSAT AVHRR GAC FDR
20+
- EUMETSAT AVHRR GAC FDR

bin/mersi22pps.py bin/mersi2pps.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,17 @@
2525
"""Script to convert MERSI-2 level-1 to PPS level-1c format using Pytroll/Satpy."""
2626

2727
import argparse
28-
from level1c4pps.mersi22pps_lib import process_one_scene
28+
from level1c4pps.mersi2pps_lib import process_one_scene
2929

3030

3131
if __name__ == "__main__":
3232
""" Create PPS-format level1c data
33-
From a list of MERSI-2 level-1 files create a NWCSAF/PPS formatet level1c file for pps.
33+
From a list of MERSI-2/3 level-1 files create a NWCSAF/PPS formatet level1c file for pps.
3434
"""
3535
parser = argparse.ArgumentParser(
36-
description=('Script to produce a PPS-level1c file for a MERSI-2 level-1 scene'))
36+
description=('Script to produce a PPS-level1c file for a MERSI-2/3 level-1 scene'))
3737
parser.add_argument('files', metavar='fileN', type=str, nargs='+',
38-
help='List of MERSI-2 files to process')
38+
help='List of MERSI-2/3 files to process')
3939
parser.add_argument('-o', '--out_dir', type=str, nargs='?',
4040
required=False, default='.',
4141
help="Output directory where to store the level1c file")

level1c4pps/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ def convert_angles(scene, delete_azimuth=False):
184184
'sunazimuth': np.array([-18000, 18000], dtype='int16'),
185185
'satazimuth': np.array([-18000, 18000], dtype='int16'),
186186
},
187-
'mersi2_file_key': {
187+
'mersi_file_key': {
188188
'sunzenith': 'Geolocation/SolarZenithAngle',
189189
'satzenith': 'Geolocation/SensorZenithAngle',
190190
'azimuthdiff': 'Geolocation/SensorSolarAzimuthDifference',

level1c4pps/mersi22pps_lib.py level1c4pps/mersi2pps_lib.py

+53-52
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,22 @@
2222
# Nina Hakansson <nina.hakansson@smhi.se>
2323
# Adam.Dybbroe <adam.dybbroe@smhi.se>
2424

25-
"""Functions to convert MERSI-2 level-1 data to a NWCSAF/PPS level-1c formatet netCDF/CF file."""
25+
"""Functions to convert MERSI-2/3 level-1 data to a NWCSAF/PPS level-1c formated netCDF/CF file."""
2626

27+
import logging
2728
import os
2829
import time
30+
31+
import numpy as np
2932
from satpy.scene import Scene
30-
from level1c4pps import (get_encoding, compose_filename,
33+
from level1c4pps import (ANGLE_ATTRIBUTES,
34+
compose_filename,
35+
convert_angles,
36+
get_encoding,
37+
get_header_attrs,
38+
rename_latitude_longitude,
3139
set_header_and_band_attrs_defaults,
32-
ANGLE_ATTRIBUTES, rename_latitude_longitude,
33-
update_angle_attributes, get_header_attrs,
34-
convert_angles)
35-
import pyspectral # testing that pyspectral is available # noqa: F401
36-
import logging
40+
update_angle_attributes)
3741

3842
# Example:
3943
# tf2019234102243.FY3D-X_MERSI_GEOQK_L1B.HDF
@@ -42,10 +46,15 @@
4246
# tf2019234102243.FY3D-X_MERSI_0250M_L1B.HDF
4347
#
4448

45-
logger = logging.getLogger('mersi22pps')
49+
logger = logging.getLogger('mersi2pps')
50+
51+
SENSOR = {'FY3D': 'merci-2',
52+
'FY3F': 'merci-3',
53+
'FY3H': 'merci-3'}
54+
55+
SATPY_READER = {'merci-2': 'mersi2_l1b',
56+
'merci-3': 'mersi3_l1b'}
4657

47-
PLATFORM_SHORTNAMES = {'FY3D': 'FY-3D'}
48-
# BANDNAMES = ['%d' % (chn+1) for chn in range(25)]
4958
BANDNAMES = ['3', '4', '5', '6', '20', '22', '23', '24', '25']
5059

5160
REFL_BANDS = ['3', '4', '5', '6']
@@ -63,28 +72,22 @@
6372
'24': 'ch_tb11',
6473
'25': 'ch_tb12'}
6574

66-
MERSI2_LEVEL1_FILE_PATTERN = 'tf{start_time:%Y%j%H%M%S}.{platform_shortname:4s}-X_MERSI_{dataset}_L1B.HDF'
67-
68-
69-
def get_encoding_mersi2(scene):
70-
"""Get netcdf encoding for all datasets."""
71-
return get_encoding(scene,
72-
BANDNAMES,
73-
PPS_TAGNAMES,
74-
chunks=None)
75-
7675

7776
def set_header_and_band_attrs(scene, orbit_n=0):
7877
"""Set and delete some attributes."""
7978
irch = scene['24']
80-
nimg = set_header_and_band_attrs_defaults(scene, BANDNAMES, PPS_TAGNAMES, REFL_BANDS, irch, orbit_n=orbit_n)
81-
scene.attrs['source'] = "mersi22pps.py"
79+
nimg = set_header_and_band_attrs_defaults(scene,
80+
BANDNAMES,
81+
PPS_TAGNAMES,
82+
REFL_BANDS,
83+
irch,
84+
orbit_n=orbit_n)
85+
scene.attrs['source'] = "mersi2pps.py"
8286
return nimg
8387

8488

8589
def remove_broken_data(scene):
8690
"""Set bad data to nodata."""
87-
import numpy as np
8891
for band in BANDNAMES:
8992
if band in REFL_BANDS:
9093
continue
@@ -93,42 +96,40 @@ def remove_broken_data(scene):
9396
scene[band].values = scene[band].values + remove
9497

9598

99+
def get_sensor(scene_file):
100+
"""Get sensor associated to the scene file."""
101+
for platform, sensor in SENSOR.items():
102+
if platform in scene_file:
103+
return sensor
104+
logger.info("Failed to determine sensor associated to scene file: '%s'", scene_file)
105+
return None
106+
107+
96108
def process_one_scene(scene_files, out_path, engine='h5netcdf', orbit_n=0):
97109
"""Make level 1c files in PPS-format."""
98110
tic = time.time()
99-
scn_ = Scene(
100-
reader='mersi2_l1b',
101-
filenames=scene_files)
102-
103-
scn_.load(BANDNAMES + ['latitude', 'longitude'] + ANGLE_NAMES, resolution=1000)
104-
105-
# Remove bad data at first and last column
106-
remove_broken_data(scn_)
107-
108-
# one ir channel
109-
irch = scn_['24']
110-
111-
# Set header and band attributes
112-
set_header_and_band_attrs(scn_, orbit_n=orbit_n)
113-
114-
# Rename longitude, latitude to lon, lat.
115-
rename_latitude_longitude(scn_)
116-
117-
# Convert angles to PPS
118-
convert_angles(scn_, delete_azimuth=True)
119-
update_angle_attributes(scn_, irch)
111+
sensor = get_sensor(os.path.basename(scene_files[0]))
112+
reader = SATPY_READER[sensor]
113+
scene = Scene(reader=reader, filenames=scene_files)
114+
scene.load(BANDNAMES + ['latitude', 'longitude'] + ANGLE_NAMES, resolution=1000)
115+
remove_broken_data(scene)
116+
irch = scene['24'] # one ir channel
117+
set_header_and_band_attrs(scene, orbit_n=orbit_n)
118+
rename_latitude_longitude(scene)
119+
convert_angles(scene, delete_azimuth=True)
120+
update_angle_attributes(scene, irch)
120121
for angle in ['sunzenith', 'satzenith', 'azimuthdiff']:
121-
scn_[angle].attrs['file_key'] = ANGLE_ATTRIBUTES['mersi2_file_key'][angle]
122+
scene[angle].attrs['file_key'] = ANGLE_ATTRIBUTES['mersi_file_key'][angle]
122123

123-
filename = compose_filename(scn_, out_path, instrument='mersi2', band=irch)
124-
scn_.save_datasets(writer='cf',
124+
filename = compose_filename(scene, out_path, instrument=sensor.replace('-', ''), band=irch)
125+
encoding = get_encoding(scene, BANDNAMES, PPS_TAGNAMES, chunks=None)
126+
attrs = get_header_attrs(scene, band=irch, sensor=sensor)
127+
scene.save_datasets(writer='cf',
125128
filename=filename,
126-
header_attrs=get_header_attrs(scn_, band=irch, sensor='mersi-2'),
129+
header_attrs=attrs,
127130
engine=engine,
128131
include_lonlats=False,
129132
flatten_attrs=True,
130-
encoding=get_encoding_mersi2(scn_))
131-
print("Saved file {:s} after {:3.1f} seconds".format(
132-
os.path.basename(filename),
133-
time.time()-tic))
133+
encoding=encoding)
134+
print(f"Saved file {os.path.basename(filename)} after {time.time() - tic:3.1f} seconds")
134135
return filename

level1c4pps/tests/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
import unittest
2626

2727
from level1c4pps.tests import (test_angles, test_seviri2pps, test_gac2pps,
28-
test_mersi22pps, test_modis2pps, test_slstr2pps,
28+
test_mersi2pps, test_modis2pps, test_slstr2pps,
2929
test_viirs2pps, test_eumgacfdr2pps,
3030
test_avhrr2pps, test_init)
3131

@@ -37,7 +37,7 @@ def suite():
3737
mysuite.addTests(test_seviri2pps.suite())
3838
mysuite.addTests(test_gac2pps.suite())
3939
mysuite.addTests(test_eumgacfdr2pps.suite())
40-
mysuite.addTests(test_mersi22pps.suite())
40+
mysuite.addTests(test_mersi2pps.suite())
4141
mysuite.addTests(test_modis2pps.suite())
4242
mysuite.addTests(test_avhrr2pps.suite())
4343
mysuite.addTests(test_slstr2pps.suite())

level1c4pps/tests/test_mersi22pps.py level1c4pps/tests/test_mersi2pps.py

+17-33
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
# Stephan Finkensieper <stephan.finkensieper@dwd.de>
2222
# Nina Hakansson <nina.hakansson@smhi.se>
2323

24-
"""Unit tests for the gac2pps_lib module."""
24+
"""Unit tests for the merci2pps_lib module."""
2525

2626
import datetime as dt
2727
import unittest
@@ -31,15 +31,15 @@
3131
import mock
3232
from satpy import Scene
3333

34-
import level1c4pps.mersi22pps_lib as mersi22pps
34+
import level1c4pps.mersi2pps_lib as mersi2pps
3535

3636

37-
class TestMersi22PPS(unittest.TestCase):
38-
"""Test mersi22pps_lib."""
37+
class TestMersi2PPS(unittest.TestCase):
38+
"""Test mersi2pps_lib."""
3939

4040
def setUp(self):
4141
"""Create a test scene."""
42-
mersi22pps.BANDNAMES = ['3', '24']
42+
mersi2pps.BANDNAMES = ['3', '24']
4343
vis006 = mock.MagicMock(attrs={'name': 'image0',
4444
'wavelength': [1, 2, 3, 'um'],
4545
'id_tag': 'ch_r06'})
@@ -60,32 +60,6 @@ def setUp(self):
6060
self.scene[key].attrs['name'] = pps_name
6161
self.scene.attrs['sensor'] = ['mersi2']
6262

63-
def test_get_encoding(self):
64-
"""Test encoding for MERSI-2."""
65-
enc_exp_angles = {'dtype': 'int16',
66-
'scale_factor': 0.01,
67-
'zlib': True,
68-
'complevel': 4,
69-
'_FillValue': -32767,
70-
'add_offset': 0.0}
71-
encoding_exp = {
72-
'image0': {'dtype': 'int16',
73-
'scale_factor': 0.01,
74-
'zlib': True,
75-
'complevel': 4,
76-
'_FillValue': -32767,
77-
'add_offset': 0.0},
78-
'image1': {'dtype': 'int16',
79-
'scale_factor': 0.01,
80-
'_FillValue': -32767,
81-
'zlib': True,
82-
'complevel': 4,
83-
'add_offset': 273.15},
84-
'satzenith': enc_exp_angles
85-
}
86-
encoding = mersi22pps.get_encoding_mersi2(self.scene)
87-
self.assertDictEqual(encoding, encoding_exp)
88-
8963
def test_compose_filename(self):
9064
"""Test compose filename for MERSI-2."""
9165
start_time = dt.datetime(2009, 7, 1, 12, 15)
@@ -99,15 +73,25 @@ def test_compose_filename(self):
9973
band = mock.MagicMock(attrs={'start_time': start_time,
10074
'end_time': end_time})
10175
fname_exp = '/out/path/S_NWC_mersi2_noaa19_99999_20090701T1216000Z_20090701T1227000Z.nc'
102-
fname = mersi22pps.compose_filename(scene, '/out/path', 'mersi2', band=band)
76+
fname = mersi2pps.compose_filename(scene, '/out/path', 'mersi2', band=band)
10377
self.assertEqual(fname, fname_exp)
10478

10579
def test_set_header_and_band_attrs(self):
10680
"""Test to set header_and_band_attrs."""
107-
mersi22pps.set_header_and_band_attrs(self.scene, orbit_n='12345')
81+
mersi2pps.set_header_and_band_attrs(self.scene, orbit_n='12345')
10882
self.assertTrue(isinstance(self.scene.attrs['orbit_number'], int))
10983
self.assertEqual(self.scene.attrs['orbit_number'], 12345)
11084

85+
def test_get_sensor(self):
86+
"""Test get sensor."""
87+
sensor = mersi2pps.get_sensor('tf2019234102243.FY3D-X_MERSI_GEOQK_L1B.HDF')
88+
self.assertEqual(sensor, "merci-2")
89+
90+
def test_get_sensor_returns_none(self):
91+
"""Test get sensor returns none for not recognized file."""
92+
sensor = mersi2pps.get_sensor('not_recognized_file')
93+
self.assertEqual(sensor, None)
94+
11195

11296
def suite():
11397
"""Create the test suite for test_mersi22pps."""

0 commit comments

Comments
 (0)