@@ -420,6 +420,19 @@ def __init__(self,
420
420
mask , class_qual_area_bad & classification_qual > 0 ,
421
421
geo_qual_wse_bad & geolocation_qual > 0 ])
422
422
423
+ # Remove degraded class_qual where there is no prior water
424
+ PIXC_CLASS_QUAL_NO_PRIOR = 2 ** 2
425
+ degraded_class_mask = class_qual_area_degraded \
426
+ & classification_qual > 0
427
+ no_prior_water_mask = classification_qual \
428
+ & PIXC_CLASS_QUAL_NO_PRIOR > 0
429
+ degraded_and_no_prior_water = degraded_class_mask & no_prior_water_mask
430
+ degraded_and_prior_water = degraded_class_mask & ~ no_prior_water_mask
431
+ # uncomment to make make degraded class_qual good anywhere there is
432
+ # prior water
433
+ # degraded_class_mask[degraded_and_prior_water] = False
434
+ mask = np .logical_or (mask , degraded_and_no_prior_water )
435
+
423
436
# HACK in avoidance of bad water_frac values
424
437
water_frac = self .get (fractional_inundation_kwd )
425
438
mask = np .logical_or (mask , water_frac .mask )
@@ -429,7 +442,6 @@ def __init__(self,
429
442
mask = np .logical_or (mask , bright_land_flag > 0 )
430
443
431
444
# skip NaNs in dheight_dphase
432
-
433
445
good = ~ mask
434
446
if good .sum () == 0 :
435
447
LOGGER .warning ("No usable pixels found in input PIXC file" )
@@ -491,15 +503,18 @@ def __init__(self,
491
503
# later in node aggregation.
492
504
PIXC_GEO_QUAL_XOVR_SUSPECT = 2 ** 6
493
505
PIXC_GEO_QUAL_XOVR_BAD = 2 ** 23
506
+ PIXC_CLASS_QUAL_RINGING = 2 ** 19
494
507
495
508
if self .classification_qual is not None :
496
- self .is_area_degraded = (
497
- class_qual_area_degraded & self .classification_qual > 0 )
509
+ self .is_area_degraded = degraded_class_mask [good ]
498
510
self .is_area_suspect = (
499
- class_qual_area_suspect & self .classification_qual > 0 )
511
+ class_qual_area_suspect & self .classification_qual > 0 )
512
+ self .sring_flg = (
513
+ self .classification_qual & PIXC_CLASS_QUAL_RINGING ) > 0
500
514
else :
501
515
self .is_area_degraded = np .zeros (self .lat .shape , dtype = 'bool' )
502
516
self .is_area_suspect = np .zeros (self .lat .shape , dtype = 'bool' )
517
+ self .sring_flg = np .zeros (self .lat .shape , dtype = 'bool' )
503
518
504
519
if self .geolocation_qual is not None :
505
520
self .is_wse_degraded = (
@@ -1265,7 +1280,7 @@ def process_node(self,
1265
1280
# self.river_obs.
1266
1281
dsets = [
1267
1282
'h_noise' , 'h_flg' , 'wse_class_flg' , 'area_flg' , 'sig0_flg' , 'lon' ,
1268
- 'lat' , 'inundated_area' , 'klass' , 'pixel_area' ,
1283
+ 'lat' , 'inundated_area' , 'klass' , 'pixel_area' , 'sring_flg' ,
1269
1284
'xtrack' , 'sig0' , 'sig0_uncert' , 'water_frac' , 'water_frac_uncert' ,
1270
1285
'ifgram' , 'power1' , 'power2' , 'phase_noise_std' , 'dh_dphi' ,
1271
1286
'dlat_dphi' , 'dlon_dphi' , 'num_rare_looks' , 'num_med_looks' ,
@@ -1398,6 +1413,10 @@ def process_node(self,
1398
1413
self .river_obs .get_node_stat (
1399
1414
'sum' , 'inundated_area' , goodvar = 'good' )
1400
1415
)[~ mask_good_sus_area ]
1416
+ # compute the area of all pixels flagged specular ringing
1417
+ area_sring = np .asarray (
1418
+ self .river_obs .get_node_stat ('sum' , 'inundated_area' ,
1419
+ goodvar = 'sring_flg' ))
1401
1420
1402
1421
# area of pixels used to compute heights
1403
1422
with warnings .catch_warnings ():
@@ -1461,7 +1480,7 @@ def process_node(self,
1461
1480
area_u = node_aggs ['area_u' ]
1462
1481
area_det = node_aggs ['area_det' ]
1463
1482
area_det_u = node_aggs ['area_det_u' ]
1464
- area_of_ht = node_aggs ['area ' ]
1483
+ area_of_ht = node_aggs ['area_of_ht ' ]
1465
1484
1466
1485
# Use degraded pix as well when not enough good/suspect pix
1467
1486
width_area [~ mask_good_sus_area ] = node_aggs_w_degraded [
@@ -1477,7 +1496,7 @@ def process_node(self,
1477
1496
area_det_u [~ mask_good_sus_area ] = node_aggs_w_degraded [
1478
1497
'area_det_u' ][~ mask_good_sus_area ]
1479
1498
area_of_ht [~ mask_good_sus_area ] = node_aggs_w_degraded [
1480
- 'area ' ][~ mask_good_sus_area ]
1499
+ 'area_of_ht ' ][~ mask_good_sus_area ]
1481
1500
1482
1501
if self .height_agg_method != 'orig' :
1483
1502
wse = node_aggs ['h' ]
@@ -1494,8 +1513,6 @@ def process_node(self,
1494
1513
wse_r_u [~ mask_good_sus_wse ] = node_aggs_w_degraded [
1495
1514
'h_u' ][~ mask_good_sus_wse ]
1496
1515
1497
- area_of_ht = area
1498
-
1499
1516
# geoid heights and tide corrections weighted by height uncertainty
1500
1517
try :
1501
1518
geoid_hght = np .asarray (
@@ -1624,6 +1641,9 @@ def process_node(self,
1624
1641
dark_frac = MISSING_VALUE_FLT * np .ones (area .shape )
1625
1642
dark_frac [area > 0 ] = 1 - area_det [area > 0 ] / area [area > 0 ]
1626
1643
dark_frac [np .logical_and (dark_frac > 1 , area > 0 )] = 1 # clip to <= 1
1644
+ sring_frac = MISSING_VALUE_FLT * np .ones (area .shape )
1645
+ sring_frac [area > 0 ] = area_sring [area > 0 ] / area [area > 0 ]
1646
+ sring_frac [np .logical_and (sring_frac > 1 , area > 0 )] = 1
1627
1647
1628
1648
# Compute flow direction relative to along-track
1629
1649
tangent = self .river_obs .centerline .tangent [
@@ -1677,6 +1697,12 @@ def process_node(self,
1677
1697
n_pix_area_degraded = np .array (self .river_obs .get_node_stat (
1678
1698
'sum' , 'is_area_degraded' ))
1679
1699
1700
+ # create empty arrays for the reconst WSE & uncertainty (for later)
1701
+ w_opt = np .ones (wse .shape ,
1702
+ dtype = np .float64 ) * self .river_obs .missing_value
1703
+ w_opt_r_u = np .ones (wse .shape ,
1704
+ dtype = np .float64 ) * self .river_obs .missing_value
1705
+
1680
1706
# Create node_q_b quality bitwise flag
1681
1707
node_q_b = np .zeros (lat_median .shape , dtype = 'i4' )
1682
1708
@@ -1822,9 +1848,12 @@ def process_node(self,
1822
1848
'area_det' : area_det .astype ('float64' ),
1823
1849
'area_det_u' : area_det_u .astype ('float64' ),
1824
1850
'area_of_ht' : area_of_ht .astype ('float64' ),
1851
+ 'area_sring' : area_sring .astype ('float64' ),
1825
1852
'wse' : wse .astype ('float64' ),
1826
1853
'wse_std' : wse_std .astype ('float64' ),
1827
1854
'wse_r_u' : wse_r_u .astype ('float64' ),
1855
+ 'w_opt' : w_opt .astype ('float64' ),
1856
+ 'w_opt_r_u' : w_opt_r_u .astype ('float64' ),
1828
1857
'nobs' : nobs .astype ('int32' ),
1829
1858
'nobs_h' : nobs_h .astype ('int32' ),
1830
1859
'n_good_pix' : n_pix_wse .astype ('int32' ),
@@ -1842,6 +1871,7 @@ def process_node(self,
1842
1871
'pole_tide' : pole_tide .astype ('float64' ),
1843
1872
'node_blocked' : is_blocked .astype ('uint8' ),
1844
1873
'dark_frac' : dark_frac ,
1874
+ 'sring_frac' : sring_frac ,
1845
1875
'x_prior' : x_prior .astype ('float64' ),
1846
1876
'y_prior' : y_prior .astype ('float64' ),
1847
1877
'lon_prior' : lon_prior .astype ('float64' ),
@@ -1939,9 +1969,17 @@ def process_reach(self, river_reach_collection, river_reach, reach,
1939
1969
1940
1970
reach_stats ['area_of_ht' ] = np .sum (river_reach .area_of_ht )
1941
1971
1942
- reach_stats ['width' ] = np .sum (river_reach .area )/ reach_stats ['length' ]
1943
- reach_stats ['width_u' ] = np .sqrt (np .sum (
1944
- river_reach .area_u ** 2 )) / reach_stats ['length' ]
1972
+ pct_unmasked = 100 * np .sum (river_reach .mask ) / len (river_reach .mask )
1973
+ if pct_unmasked >= self .reach_pct_good_sus_thresh :
1974
+ reach_stats ['width' ] = np .sum (river_reach .area [river_reach .mask ])\
1975
+ / np .sum (river_reach .p_length [river_reach .mask ])
1976
+ reach_stats ['width_u' ] = np .sqrt (
1977
+ np .sum (river_reach .area_u [river_reach .mask ]** 2 )) / np .sum (
1978
+ river_reach .p_length [river_reach .mask ])
1979
+ else :
1980
+ reach_stats ['width' ] = reach_stats ['area' ] / reach_stats ['length' ]
1981
+ reach_stats ['width_u' ] = np .sqrt (
1982
+ np .sum (river_reach .area_u ** 2 ))/ reach_stats ['length' ]
1945
1983
reach_stats ['layovr_val' ] = np .sqrt (np .sum (
1946
1984
river_reach .layovr_val [river_reach .mask ]** 2 ))
1947
1985
@@ -2020,20 +2058,28 @@ def process_reach(self, river_reach_collection, river_reach, reach,
2020
2058
hh_opt , wse_r_u_opt , mask_opt , ss_opt = \
2021
2059
hh , wse_r_u , mask , ss
2022
2060
# get the optimal reconstruction (Bayes estimate)
2023
- wse_opt , height_u , slope_u = self .optimal_reconstruct (
2024
- river_reach_collection ,
2025
- river_reach , reach_id ,
2026
- ss_opt , hh_opt ,
2027
- wse_r_u_opt , mask_opt ,
2028
- min_fit_points ,
2029
- method = 'Bayes' ,
2030
- )
2061
+ wse_opt , wse_opt_r_u , height_u , slope_u , = \
2062
+ self .optimal_reconstruct (
2063
+ river_reach_collection ,
2064
+ river_reach , reach_id ,
2065
+ ss_opt , hh_opt ,
2066
+ wse_r_u_opt , mask_opt ,
2067
+ min_fit_points ,
2068
+ method = 'Bayes' ,
2069
+ )
2070
+ # Store the reconstructed node heights in river reach object.
2071
+ # Currently, only store populated nodes (node_indx clipped to
2072
+ # nodes with minobs observations as specified by L2_HR_Param
2073
+ # file).
2074
+ river_reach .w_opt = wse_opt [self .river_obs .populated_nodes ]
2075
+ river_reach .w_opt_r_u = wse_opt_r_u [
2076
+ self .river_obs .populated_nodes ]
2031
2077
# Use reconstruction height and slope for reach outputs
2032
2078
dx = ss_opt [0 ] - ss_opt [- 1 ] # along-reach dist
2033
2079
reach_stats ['slope' ] = (wse_opt [0 ] - wse_opt [- 1 ]) / dx
2034
2080
reach_stats ['height' ] = np .mean (wse_opt )
2035
- reach_stats ['slope_r_u' ] = slope_u * 0.0001 # m/m
2036
- reach_stats ['height_r_u' ] = height_u * 0.01 # m
2081
+ reach_stats ['slope_r_u' ] = slope_u
2082
+ reach_stats ['height_r_u' ] = height_u
2037
2083
reach_stats ['slope_u' ] = np .sqrt (
2038
2084
SLOPE_SYS_UNCERT ** 2 + reach_stats ['slope_r_u' ]** 2 )
2039
2085
reach_stats ['height_u' ] = np .sqrt (
@@ -2101,6 +2147,9 @@ def process_reach(self, river_reach_collection, river_reach, reach,
2101
2147
dark_frac = (
2102
2148
1 - np .sum (river_reach .area_det )/ np .sum (river_reach .area ))
2103
2149
reach_stats ['dark_frac' ] = min (dark_frac , 1 ) # clip to <= 1
2150
+ sring_frac = (
2151
+ np .sum (river_reach .area_sring )/ np .sum (river_reach .area ))
2152
+ reach_stats ['sring_frac' ] = min (sring_frac , 1 ) # clip to <= 1
2104
2153
2105
2154
reach_stats ['n_reach_up' ] = (reach_stats ['rch_id_up' ] > 0 ).sum ()
2106
2155
reach_stats ['n_reach_dn' ] = (reach_stats ['rch_id_dn' ] > 0 ).sum ()
@@ -2529,7 +2578,12 @@ def get_reach_mask(self, ss, hh, ww, node_q, min_fit_points,
2529
2578
mask = np .zeros (len (hh ), dtype = bool )
2530
2579
2531
2580
wse_outlier_mask = mask .copy ()
2532
- pct_good_sus = 100 * (node_q [mask ] < 2 ).sum () / mask .sum ()
2581
+
2582
+ if mask .sum () > 0 :
2583
+ pct_good_sus = 100 * (node_q [mask ] < 2 ).sum () / mask .sum ()
2584
+ else :
2585
+ pct_good_sus = 0
2586
+
2533
2587
if pct_good_sus > self .reach_pct_good_sus_thresh :
2534
2588
mask [node_q >= 2 ] = False
2535
2589
@@ -2904,9 +2958,11 @@ def optimal_reconstruct(
2904
2958
# define vectors b and c for uncertainty estimates later
2905
2959
this_reach_mask_b = np .zeros_like (ss )
2906
2960
this_reach_mask_b [first_node :first_node + this_len ] = 1
2961
+ this_reach_mask_b = this_reach_mask_b / np .sum (this_reach_mask_b )
2907
2962
first_and_last_node_c = np .zeros_like (ss )
2908
- first_and_last_node_c [first_node ] = - 1
2909
- first_and_last_node_c [first_node + this_len - 1 ] = 1
2963
+ reach_length = ss [first_node + this_len - 1 ] - ss [first_node ]
2964
+ first_and_last_node_c [first_node ] = - 1 / reach_length
2965
+ first_and_last_node_c [first_node + this_len - 1 ] = 1 / reach_length
2910
2966
2911
2967
# create a wse prior if flagged
2912
2968
if self .prior_wse_method == 'fit' :
@@ -2983,15 +3039,15 @@ def optimal_reconstruct(
2983
3039
wse_out0 = np .matmul (K , wse_reg )
2984
3040
# apply the prior term
2985
3041
wse_out = wse_out0 + np .matmul (K_bar , prior_wse )
2986
- height_u = this_reach_mask_b @ A_inv @ np . atleast_2d (
2987
- this_reach_mask_b ). T
2988
- slope_u = first_and_last_node_c @ A_inv @ np .atleast_2d (
2989
- first_and_last_node_c ).T
3042
+ wse_out_std = np . sqrt ( np . diag ( A_inv )) # node-level height uncertainty
3043
+ height_u = np . sqrt ( this_reach_mask_b @ A_inv @ this_reach_mask_b . T )
3044
+ slope_u = np . sqrt ( first_and_last_node_c @ A_inv @ np .atleast_2d (
3045
+ first_and_last_node_c ).T )
2990
3046
if self .use_multiple_reaches :
2991
3047
wse_out = wse_out [first_node :first_node + this_len ]
2992
-
2993
- # Return height_u and slope_u as scalars
2994
- return wse_out , height_u .item (), slope_u .item ()
3048
+ wse_out_std = wse_out_std [ first_node : first_node + this_len ]
3049
+ # Return node WSEs & stdevs, and reach height_u and slope_u as scalars
3050
+ return wse_out , wse_out_std , height_u .item (), slope_u .item ()
2995
3051
2996
3052
@staticmethod
2997
3053
def compute_bayes_estimator (Ry , Rv , H ):
0 commit comments