28
28
import os
29
29
import time
30
30
from satpy .scene import Scene
31
+ from sbafs_ann .convert_vgac import convert_to_vgac_with_nn
31
32
from level1c4pps import (get_encoding , compose_filename ,
32
33
set_header_and_band_attrs_defaults ,
33
34
rename_latitude_longitude ,
38
39
import logging
39
40
import numpy as np
40
41
41
- # Example:
42
-
43
42
logger = logging .getLogger ("vgac2pps" )
44
43
45
44
# Order of BANDNAMES decides order of channels in file. Not important
66
65
67
66
MBAND_PPS = ["M05" , "M07" , "M09" , "M10" , "M11" , "M12" , "M14" , "M15" , "M16" ]
68
67
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" ]
70
70
71
71
MBAND_DEFAULT = ["M05" , "M07" , "M09" , "M10" , "M11" , "M12" , "M14" , "M15" , "M16" ]
72
72
323
323
"slope" : 1.002 ,
324
324
"offset" : - 0.69 ,
325
325
"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"
326
441
}
327
442
}
328
- }
329
443
330
444
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 } ' )
333
457
334
- logger .info (f"Using SBAF_{ sbaf_version } " )
335
458
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 ():
337
463
viirs_channel = scaling ["viirs_channel" ]
338
464
offset = scaling ["offset" ]
339
465
comment = scaling ["comment" ]
@@ -343,17 +469,83 @@ def convert_to_noaa19(scene, sbaf_version):
343
469
filt = filt & (scene ["sunzenith" ].values >= scaling ["min_sunzenith" ])
344
470
if "max_sunzenith" in scaling :
345
471
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
+ )
351
476
logger .info (f"{ avhhr_chan :<13} = { slope :<6} *{ viirs_channel :<3} +{ offset :<5} ({ comment } )" )
352
477
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
+
353
541
if "npp" in scene .attrs ["platform" ].lower ():
354
542
scene .attrs ["platform" ] = "vgacsnpp"
355
543
scene .attrs ["platform" ] = scene .attrs ["platform" ].replace ("noaa" , "vgac" )
356
544
545
+ # These are only needed for the NN, but they must be removed before saving
546
+ del scene ["M10" ]
547
+ del scene ["M14" ]
548
+
357
549
358
550
def get_encoding_viirs (scene ):
359
551
"""Get netcdf encoding for all datasets."""
0 commit comments