Skip to content

Commit 68482cd

Browse files
Merge pull request #97 from salomoneliassonSMHI/SBAF_NN
Sbaf nn
2 parents deed643 + 3db4362 commit 68482cd

File tree

3 files changed

+210
-15
lines changed

3 files changed

+210
-15
lines changed

bin/vgac2pps.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,9 @@
4646
parser.add_argument('-all_ch', '--all_channels', action='store_true',
4747
help="Save all 21 channels to level1c4pps file.")
4848
parser.add_argument('-n19', '--as_noaa19',
49-
choices=[version for version in SBAF],
5049
default=None,
5150
help=("Save only the AVHRR (1,2, 3B, 4, 5) channels to level1c4pps file. "
52-
"And apply SBAFs to the channels."))
51+
"And apply SBAFs to the channels. "))
5352
parser.add_argument('-pps_ch', '--pps_channels', action='store_true',
5453
help="Save only the necessary (for PPS) channels to level1c4pps file.")
5554
parser.add_argument('-avhrr_ch', '--avhrr_channels', action='store_true',

continuous_integration/environment.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@ name: test-environment
22
channels:
33
- conda-forge
44
dependencies:
5+
- tensorflow
6+
- scikit-learn
57
- sphinx
68
- scipy
79
- h5py
810
- python-geotiepoints
11+
- matplotlib
912
- mock
1013
- numpy<2.0.0
1114
- satpy>0.41.1
@@ -18,3 +21,4 @@ dependencies:
1821
- pip:
1922
- trollsift
2023
- pygac
24+
- git+https://github.com/foua-pps/sbafs_ann@main

level1c4pps/vgac2pps_lib.py

+205-13
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import os
2929
import time
3030
from satpy.scene import Scene
31+
from sbafs_ann.convert_vgac import convert_to_vgac_with_nn
3132
from level1c4pps import (get_encoding, compose_filename,
3233
set_header_and_band_attrs_defaults,
3334
rename_latitude_longitude,
@@ -38,8 +39,6 @@
3839
import logging
3940
import numpy as np
4041

41-
# Example:
42-
4342
logger = logging.getLogger("vgac2pps")
4443

4544
# Order of BANDNAMES decides order of channels in file. Not important
@@ -66,7 +65,8 @@
6665

6766
MBAND_PPS = ["M05", "M07", "M09", "M10", "M11", "M12", "M14", "M15", "M16"]
6867

69-
MBAND_AVHRR = ["M05", "M07", "M12", "M15", "M16"]
68+
# "M10", "M14" are not AVHRR channels, but needed for NN SABAF
69+
MBAND_AVHRR = ["M05", "M07", "M12", "M15", "M16", "M10", "M14"]
7070

7171
MBAND_DEFAULT = ["M05", "M07", "M09", "M10", "M11", "M12", "M14", "M15", "M16"]
7272

@@ -323,17 +323,143 @@
323323
"slope": 1.002,
324324
"offset": -0.69,
325325
"comment": "Based on collocation data for VZA < 3 and SZA 0-180",
326+
},
327+
},
328+
"KNMI": {
329+
"r06": {
330+
"viirs_channel": "M05",
331+
"slope": 0.9401,
332+
"offset": 0.629,
333+
"comment": "VZA<60, SZA<60, Delta(VZA) < 5, Delta(Scat-angle) < 5",
334+
},
335+
"r09": {
336+
"viirs_channel": "M07",
337+
"slope": 0.9345,
338+
"offset": -1.304,
339+
"comment": "VZA<60, SZA<60, Delta(VZA) < 5, Delta(Scat-angle) < 5",
340+
},
341+
"tb37_night": {
342+
"viirs_channel": "M12",
343+
"slope": 0.9934,
344+
"offset": 1.659,
345+
"min_sunzenith": 89,
346+
"comment": " VZA<60, SZA>95, Delta(VZA) < 5",
347+
},
348+
"tb37_day": {
349+
"viirs_channel": "M12",
350+
"slope": 0.9572,
351+
"offset": 9.572,
352+
"max_sunzenith": 80,
353+
"comment": "VZA<60, SZA<60, Delta(VZA) < 5, Delta(Scat-angle) < 5",
354+
},
355+
"tb37_twilight": {
356+
"viirs_channel": "M12",
357+
"slope": 0.9753,
358+
"offset": 5.6155,
359+
"min_sunzenith": 80,
360+
"max_sunzenith": 89,
361+
"comment": "The linear average of the SBAFs for t37_day and t37_night. 80<= SZA <89",
362+
},
363+
"tb11": {
364+
"viirs_channel": "M15",
365+
"slope": 0.9986,
366+
"offset": 0.600,
367+
"comment": "",
368+
},
369+
"tb12": {
370+
"viirs_channel": "M16",
371+
"slope": 0.9943,
372+
"offset": 1.328,
373+
"comment": "",
374+
},
375+
},
376+
"KNMI_v2": {
377+
"r06": {
378+
"viirs_channel": "M05",
379+
"slope": 0.9401,
380+
"offset": 0.629,
381+
"comment": "VZA<60, SZA<60, Delta(VZA) < 5, Delta(Scat-angle) < 5",
382+
},
383+
"r09": {
384+
"viirs_channel": "M07",
385+
"slope": 0.9345,
386+
"offset": -1.304,
387+
"comment": "VZA<60, SZA<60, Delta(VZA) < 5, Delta(Scat-angle) < 5",
388+
},
389+
"tb37_day": {
390+
"viirs_channel": "M12",
391+
"slope": 0.9572,
392+
"offset": 9.572,
393+
"max_sunzenith": 70,
394+
"comment": "VZA<60, SZA<60, Delta(VZA) < 5, Delta(Scat-angle) < 5",
395+
},
396+
"tb37_night": {
397+
"viirs_channel": "M12",
398+
"slope": 0.9934,
399+
"offset": 1.659,
400+
"min_sunzenith": 85,
401+
"comment": "VZA<60, SZA>95, Delta(VZA) < 5",
402+
},
403+
"tb37_twilight": {
404+
"viirs_channel": "M12",
405+
"slope": None,
406+
"offset": None,
407+
"min_sunzenith": 70,
408+
"max_sunzenith": 85,
409+
"comment": "70 < SZA < 85: BT = (1-f)*BT(day) + f*BT(night), f=(SZA-70)/15",
410+
},
411+
"tb11": {
412+
"viirs_channel": "M15",
413+
"slope": 0.9986,
414+
"offset": 0.600,
415+
"comment": "VZA<60, SZA>95, Delta(VZA) < 5",
416+
},
417+
"tb12": {
418+
"viirs_channel": "M16",
419+
"slope": None,
420+
"offset": None,
421+
"comment": "VZA<60, SZA>95, Delta(VZA) < 5. Applies SBAF(tb11)-1.1646*(tb11-tb12)-0.235",
422+
},
423+
},
424+
"NN_v1": {
425+
"cfg_file_day": "ch7_SATZ_less_15_SUNZ_0_89_TD_1_min.yaml",
426+
"cfg_file_night": "ch4_SATZ_less_25_SUNZ_90_180_TD_5_min.yaml",
427+
"cfg_file_twilight": None,
428+
"comment": "NN based on AVHRR and VGAC matchups using all AVHRR heritage channels"
429+
},
430+
"NN_v2": {
431+
"cfg_file_day": "ch7_satz_max_25_SUNZ_0_80_tdiff_300_sec_20241031.yaml",
432+
"cfg_file_night": "ch4_satz_max_25_SUNZ_90_180_tdiff_300_sec_20241031.yaml",
433+
"cfg_file_twilight": "ch7_satz_max_25_SUNZ_80_89_tdiff_300_sec_20241031.yaml",
434+
"comment": "NN based on AVHRR and VGAC matchups using all AVHRR heritage channels"
435+
},
436+
"NN_v3": {
437+
"cfg_file_day": "ch7_satz_max_15_SUNZ_0_80_tdiff_120_sec_20241120.yaml",
438+
"cfg_file_night": "ch4_satz_max_15_SUNZ_90_180_tdiff_120_sec_20241120.yaml",
439+
"cfg_file_twilight": "ch7_satz_max_15_SUNZ_80_89_tdiff_120_sec_20241120.yaml",
440+
"comment": "NN based on AVHRR and VGAC matchups using all AVHRR heritage channels"
326441
}
327442
}
328-
}
329443

330444

331-
def convert_to_noaa19(scene, sbaf_version):
332-
""" Applies AVHRR SBAF to VGAC channels"""
445+
def convert_to_noaa19_neural_network(scene, sbaf_version):
446+
"""Applies AVHRR SBAF to VGAC channels using NN approach"""
447+
448+
if sbaf_version in ["NN_v1", "NN_v2", "NN_v3"]:
449+
day_cfg_file = SBAF[sbaf_version]['cfg_file_day']
450+
night_cfg_file = SBAF[sbaf_version]['cfg_file_night']
451+
twilight_cfg_file = SBAF[sbaf_version]['cfg_file_twilight']
452+
else:
453+
logger.exception(f"Unrecognized NN version, {sbaf_version}")
454+
scene = convert_to_vgac_with_nn(scene, day_cfg_file, night_cfg_file, twilight_cfg_file)
455+
456+
logger.info(f'Created NN version {sbaf_version}')
333457

334-
logger.info(f"Using SBAF_{sbaf_version}")
335458

336-
for avhhr_chan, scaling in SBAF[sbaf_version].items():
459+
def convert_to_noaa19_linear(scene, SBAF):
460+
""" Apply linear regression"""
461+
462+
for avhhr_chan, scaling in SBAF.items():
337463
viirs_channel = scaling["viirs_channel"]
338464
offset = scaling["offset"]
339465
comment = scaling["comment"]
@@ -343,17 +469,83 @@ def convert_to_noaa19(scene, sbaf_version):
343469
filt = filt & (scene["sunzenith"].values >= scaling["min_sunzenith"])
344470
if "max_sunzenith" in scaling:
345471
filt = filt & (scene["sunzenith"].values < scaling["max_sunzenith"])
346-
scene[viirs_channel].values = np.where(
347-
filt,
348-
slope * scene[viirs_channel].values + offset,
349-
scene[viirs_channel].values
350-
)
472+
scene[viirs_channel].values = np.where(filt,
473+
slope * scene[viirs_channel].values + offset,
474+
scene[viirs_channel].values
475+
)
351476
logger.info(f"{avhhr_chan:<13} = {slope:<6}*{viirs_channel:<3}+{offset:<5} ({comment})")
352477

478+
479+
def convert_to_noaa19_KNMI_v2(scene, sbaf_version):
480+
""" Apply 1 channel linear regression SBAF for KNMI version 2"""
481+
482+
# I need to save the t11 values before the SBAF adjustment as they are needed for the tb12 SBAF
483+
tb11_original = scene["M15"].values.copy()
484+
for avhrr_chan, scaling in SBAF[sbaf_version].items():
485+
viirs_channel = scaling["viirs_channel"]
486+
offset = scaling["offset"]
487+
comment = scaling["comment"]
488+
slope = scaling["slope"]
489+
filt = np.ones_like(scene[viirs_channel].values, dtype=bool)
490+
if "min_sunzenith" in scaling:
491+
filt = filt & (scene["sunzenith"].values >= scaling["min_sunzenith"])
492+
if "max_sunzenith" in scaling:
493+
filt = filt & (scene["sunzenith"].values < scaling["max_sunzenith"])
494+
495+
if slope is not None:
496+
# Then simple linear regression
497+
scene[viirs_channel].values = np.where(
498+
filt,
499+
slope * scene[viirs_channel].values + offset,
500+
scene[viirs_channel].values
501+
)
502+
logger.info(f"{avhrr_chan:<13} = {slope:<6}*{viirs_channel:<3}+{offset:<5} ({comment})")
503+
else:
504+
if avhrr_chan == "tb37_twilight":
505+
# 70 < SZA < 85: BT = (1-f)*BT(day) + f*BT(night), f=(SZA-70)/15
506+
507+
f = (scene["sunzenith"].values - scaling["min_sunzenith"])/15
508+
tb37_day_slope = scaling["tb37_day"]["slope"]
509+
tb37_day_offset = scaling["tb37_day"]["offset"]
510+
tb37_night_slope = scaling["tb37_night"]["slope"]
511+
tb37_night_offset = scaling["tb37_night"]["offset"]
512+
tb37_day = tb37_day_slope * scene[viirs_channel].values + tb37_day_offset
513+
tb37_night = tb37_night_slope * scene[viirs_channel].values + tb37_night_offset
514+
515+
scene[viirs_channel].values = np.where(
516+
filt,
517+
(1-f)*tb37_day + f*tb37_night,
518+
scene[viirs_channel].values
519+
)
520+
521+
elif avhrr_chan == "tb12":
522+
# BT(5) = BT(ch4)-1.1646*(BT(M15)-BT(M16))-0.235
523+
scene[viirs_channel].values = scene["M15"].values-1.1646*(tb11_original-scene["M16"].values)-0.235
524+
else:
525+
logger.exception(f'Unknown channel, {avhrr_chan}, or missing slope parameter')
526+
logger.info(f"{avhrr_chan:<13}: ({comment})")
527+
528+
529+
def convert_to_noaa19(scene, sbaf_version):
530+
""" Applies AVHRR SBAF to VGAC channels"""
531+
532+
logger.info(f"Using SBAF_{sbaf_version}")
533+
534+
if "NN" in sbaf_version:
535+
convert_to_noaa19_neural_network(scene, sbaf_version)
536+
elif sbaf_version == "KNMI_v2":
537+
convert_to_noaa19_KNMI_v2(scene, sbaf_version)
538+
else:
539+
convert_to_noaa19_linear(scene, SBAF[sbaf_version])
540+
353541
if "npp" in scene.attrs["platform"].lower():
354542
scene.attrs["platform"] = "vgacsnpp"
355543
scene.attrs["platform"] = scene.attrs["platform"].replace("noaa", "vgac")
356544

545+
# These are only needed for the NN, but they must be removed before saving
546+
del scene["M10"]
547+
del scene["M14"]
548+
357549

358550
def get_encoding_viirs(scene):
359551
"""Get netcdf encoding for all datasets."""

0 commit comments

Comments
 (0)