Skip to content

Commit 458dcf6

Browse files
committed
ENH: keep track of nsamples in timeavg flag
1 parent 0405f94 commit 458dcf6

File tree

4 files changed

+137
-56
lines changed

4 files changed

+137
-56
lines changed

src/pyrad_proc/pyrad/proc/process_intercomp.py

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,7 +1053,13 @@ def process_weighted_time_avg(procstatus, dscfg, radar_list=None):
10531053
def process_time_avg_flag(procstatus, dscfg, radar_list=None):
10541054
"""
10551055
computes a flag field describing the conditions of the data used while
1056-
averaging
1056+
averaging. The flag is an integer that tracks up to 999 occurrences of the number of samples as well as
1057+
of three conditions during data accumulation: 𝜙𝑑𝑝 exceeding a threshold (𝜙𝑑𝑝ₘₐₓ), clutter,
1058+
and non-rain precipitation. It is a packed representation of 4 numbers into one large integer.
1059+
Flags are encoded by adding +1 for nsamples, +1E3 for 𝜙𝑑𝑝ₘₐₓ exceedance, + 1E6 for clutter,
1060+
and +1E9 for non-rain.
1061+
Inputs include the 𝜙𝑑𝑝 field, a dynamic clutter map, and either a hydrometeor
1062+
classification or temperature field to identify precipitation phase.
10571063
10581064
Parameters
10591065
----------
@@ -1126,25 +1132,27 @@ def process_time_avg_flag(procstatus, dscfg, radar_list=None):
11261132
time_avg_flag = pyart.config.get_metadata("time_avg_flag")
11271133
time_avg_flag["data"] = np.ma.zeros((radar.nrays, radar.ngates), dtype=int)
11281134

1135+
time_avg_flag["data"] += 1 # number of samples
1136+
11291137
if phidp_name not in radar.fields:
11301138
warn("Missing PhiDP data")
1131-
time_avg_flag["data"] += 1
1139+
time_avg_flag["data"] += int(1e3)
11321140
else:
11331141
phidp_field = radar.fields[phidp_name]
1134-
time_avg_flag["data"][phidp_field["data"] > phidpmax] += 1
1142+
time_avg_flag["data"][phidp_field["data"] > phidpmax] += int(1e3)
11351143

11361144
if echo_name is not None:
11371145
if echo_name not in radar.fields:
11381146
warn("Missing echo ID data")
1139-
time_avg_flag["data"] += 100
1147+
time_avg_flag["data"] += int(1e6)
11401148
else:
11411149
echo_field = radar.fields[echo_name]
1142-
time_avg_flag["data"][echo_field["data"] == 2] += 100
1150+
time_avg_flag["data"][echo_field["data"] == 2] += int(1e6)
11431151

11441152
if hydro_name is not None and echo_name is not None:
11451153
if (hydro_name not in radar.fields) or (echo_name not in radar.fields):
11461154
warn("Missing hydrometeor classification data")
1147-
time_avg_flag["data"] += 10000
1155+
time_avg_flag["data"] += int(1e9)
11481156
else:
11491157
hydro_field = radar.fields[hydro_name]
11501158
# check where is no rain
@@ -1153,11 +1161,11 @@ def process_time_avg_flag(procstatus, dscfg, radar_list=None):
11531161
)
11541162
# where is no rain should be precip
11551163
is_not_rain = np.logical_and(is_not_rain, echo_field["data"] == 3)
1156-
time_avg_flag["data"][is_not_rain] += 10000
1164+
time_avg_flag["data"][is_not_rain] += int(1e9)
11571165
elif temp_name is not None:
11581166
if temp_name not in radar.fields:
11591167
warn("Missing temperature data")
1160-
time_avg_flag["data"] += 10000
1168+
time_avg_flag["data"] += int(1e9)
11611169
else:
11621170
beamwidth = dscfg.get("beamwidth", None)
11631171
if beamwidth is None:
@@ -1185,11 +1193,11 @@ def process_time_avg_flag(procstatus, dscfg, radar_list=None):
11851193
iso0_field=iso0_name,
11861194
temp_ref="temperature",
11871195
)
1188-
time_avg_flag["data"][mask_fzl] += 10000
1196+
time_avg_flag["data"][mask_fzl] += int(1e6)
11891197
elif iso0_name is not None:
11901198
if iso0_name not in radar.fields:
11911199
warn("Missing height relative to iso0 data")
1192-
time_avg_flag["data"] += 10000
1200+
time_avg_flag["data"] += int(1e6)
11931201
else:
11941202
beamwidth = dscfg.get("beamwidth", None)
11951203
if beamwidth is None:
@@ -1217,7 +1225,7 @@ def process_time_avg_flag(procstatus, dscfg, radar_list=None):
12171225
iso0_field=iso0_name,
12181226
temp_ref="height_over_iso0",
12191227
)
1220-
time_avg_flag["data"][mask_fzl] += 10000
1228+
time_avg_flag["data"][mask_fzl] += int(1e9)
12211229

12221230
radar_aux = deepcopy(radar)
12231231
radar_aux.fields = dict()
@@ -1882,7 +1890,6 @@ def process_intercomp(procstatus, dscfg, radar_list=None):
18821890

18831891
fname = savedir + fname[0]
18841892
coloc_data = read_colocated_data(fname)
1885-
18861893
intercomp_dict = {
18871894
"rad1_name": dscfg["global_data"]["rad1_name"],
18881895
"rad1_time": coloc_data[0],
@@ -1947,13 +1954,13 @@ def process_intercomp_time_avg(procstatus, dscfg, radar_list=None):
19471954
rng_tol : float. Dataset keyword
19481955
range tolerance between the two radars. Default 50 m
19491956
clt_max : int. Dataset keyword
1950-
maximum number of samples that can be clutter contaminated.
1951-
Default 100 i.e. all
1957+
maximum percentage of samples that can be clutter contaminated.
1958+
Default 100%& i.e. all
19521959
phi_excess_max : int. Dataset keyword
1953-
maximum number of samples that can have excess instantaneous
1954-
PhiDP. Default 100 i.e. all
1960+
maximum percentage of samples that can have excess instantaneous
1961+
PhiDP. Default 100% i.e. all
19551962
non_rain_max : int. Dataset keyword
1956-
maximum number of samples that can be no rain. Default 100 i.e. all
1963+
maximum percentage of samples that can be no rain. Default 100% i.e. all
19571964
phi_avg_max : float. Dataset keyword
19581965
maximum average PhiDP allowed. Default 600 deg i.e. any
19591966
@@ -2212,16 +2219,15 @@ def process_intercomp_time_avg(procstatus, dscfg, radar_list=None):
22122219

22132220
rad1_flag = flag1[rad1_ray_ind[i], ind_rng]
22142221

2215-
rad1_excess_phi = rad1_flag % 100
2216-
rad1_clt = ((rad1_flag - rad1_excess_phi) % 10000) / 100
2217-
rad1_prec = (
2218-
(rad1_flag - rad1_clt * 100 - rad1_excess_phi) % 1000000
2219-
) / 10000
2220-
2221-
flag1_vec[i] = int(
2222-
10000 * np.max(rad1_prec)
2223-
+ 100 * np.max(rad1_clt)
2224-
+ np.max(rad1_excess_phi)
2222+
rad1_nsamples = rad1_flag % int(1e3)
2223+
rad1_excess_phi = (rad1_flag // int(1e3)) % int(1e3)
2224+
rad1_clt = (rad1_flag // int(1e6)) % int(1e3)
2225+
rad1_prec = (rad1_flag // int(1e9)) % int(1e3)
2226+
flag1_vec[i] = (
2227+
int(1e9) * np.max(rad1_prec)
2228+
+ int(1e6) * np.max(rad1_clt)
2229+
+ int(1e3) * np.max(rad1_excess_phi)
2230+
+ np.max(rad1_nsamples)
22252231
)
22262232
is_valid_avg[i] = True
22272233

@@ -2270,16 +2276,16 @@ def process_intercomp_time_avg(procstatus, dscfg, radar_list=None):
22702276

22712277
rad2_flag = flag2[rad2_ray_ind[i], ind_rng]
22722278

2273-
rad2_excess_phi = rad2_flag % 100
2274-
rad2_clt = ((rad2_flag - rad2_excess_phi) % 10000) / 100
2275-
rad2_prec = (
2276-
(rad2_flag - rad2_clt * 100 - rad2_excess_phi) % 1000000
2277-
) / 10000
2279+
rad2_nsamples = rad1_flag % int(1e3)
2280+
rad2_excess_phi = (rad1_flag // int(1e3)) % int(1e3)
2281+
rad2_clt = (rad1_flag // int(1e6)) % int(1e3)
2282+
rad2_prec = (rad1_flag // int(1e9)) % int(1e3)
22782283

22792284
flag2_vec[i] = int(
2280-
10000 * np.max(rad2_prec)
2281-
+ 100 * np.max(rad2_clt)
2282-
+ np.max(rad2_excess_phi)
2285+
int(1e9) * np.max(rad2_prec)
2286+
+ int(1e6) * np.max(rad2_clt)
2287+
+ int(1e3) * np.max(rad2_excess_phi)
2288+
+ rad2_nsamples
22832289
)
22842290
is_valid_avg[i] = True
22852291

@@ -2396,17 +2402,15 @@ def process_intercomp_time_avg(procstatus, dscfg, radar_list=None):
23962402
if rad1_time is None:
23972403
return None, None
23982404

2399-
rad1_excess_phi = (rad1_flag % 100).astype(int)
2400-
rad2_excess_phi = (rad2_flag % 100).astype(int)
2401-
rad1_clt = (((rad1_flag - rad1_excess_phi) % 10000) / 100).astype(int)
2402-
rad2_clt = (((rad2_flag - rad2_excess_phi) % 10000) / 100).astype(int)
2405+
rad1_nsamples = rad1_flag % int(1e3)
2406+
rad1_excess_phi = (rad1_flag // int(1e3)) % int(1e3)
2407+
rad1_clt = (rad1_flag // int(1e6)) % int(1e3)
2408+
rad1_non_rain = (rad1_flag // int(1e9)) % int(1e3)
24032409

2404-
rad1_non_rain = (
2405-
((rad1_flag - rad1_clt * 100 - rad1_excess_phi) % 1000000) / 10000
2406-
).astype(int)
2407-
rad2_non_rain = (
2408-
((rad2_flag - rad2_clt * 100 - rad2_excess_phi) % 1000000) / 10000
2409-
).astype(int)
2410+
rad2_nsamples = rad2_flag % int(1e3)
2411+
rad2_excess_phi = (rad2_flag // int(1e3)) % int(1e3)
2412+
rad2_clt = (rad2_flag // int(1e6)) % int(1e3)
2413+
rad2_non_rain = (rad2_flag // int(1e9)) % int(1e3)
24102414

24112415
clt_max = dscfg.get("clt_max", 100)
24122416
phi_excess_max = dscfg.get("phi_excess_max", 100)
@@ -2417,14 +2421,14 @@ def process_intercomp_time_avg(procstatus, dscfg, radar_list=None):
24172421
ind_val = np.where(
24182422
np.logical_and.reduce(
24192423
(
2420-
rad1_clt <= clt_max,
2421-
rad2_clt <= clt_max,
2422-
rad1_excess_phi <= phi_excess_max,
2423-
rad2_excess_phi <= phi_excess_max,
2424-
rad1_non_rain <= non_rain_max,
2425-
rad2_non_rain <= non_rain_max,
2426-
rad1_phi <= phi_avg_max,
2427-
rad2_phi <= phi_avg_max,
2424+
rad1_clt / rad2_nsamples <= clt_max,
2425+
rad2_clt / rad2_nsamples <= clt_max,
2426+
rad1_excess_phi / rad2_nsamples <= phi_excess_max,
2427+
rad2_excess_phi / rad2_nsamples <= phi_excess_max,
2428+
rad1_non_rain / rad1_nsamples <= non_rain_max,
2429+
rad2_non_rain / rad1_nsamples <= non_rain_max,
2430+
rad1_phi / rad1_nsamples <= phi_avg_max,
2431+
rad2_phi / rad1_nsamples <= phi_avg_max,
24282432
)
24292433
)
24302434
)[0]

src/pyrad_proc/pyrad/prod/process_intercomp_products.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,6 @@ def generate_intercomp_products(dataset, prdcfg):
153153
)
154154

155155
fname = savedir + fname[0]
156-
157156
write_colocated_data(dataset["intercomp_dict"], fname)
158157

159158
print("saved colocated data file: " + fname)
@@ -194,6 +193,11 @@ def generate_intercomp_products(dataset, prdcfg):
194193
timeformat = "%Y%m%d"
195194
if scatter_type == "instant":
196195
timeformat = "%Y%m%d%H%M%S"
196+
colname = "dBZavg"
197+
else:
198+
colname = "val"
199+
if f"rad1_{colname}" not in dataset["intercomp_dict"]:
200+
return
197201

198202
field_name = get_fieldname_pyart(prdcfg["voltype"])
199203
savedir = get_save_dir(
@@ -247,8 +251,8 @@ def generate_intercomp_products(dataset, prdcfg):
247251
continue
248252

249253
hist_2d, bin_edges1, bin_edges2, stats = compute_2d_stats(
250-
np.ma.asarray(dataset["intercomp_dict"]["rad1_val"][selection]),
251-
np.ma.asarray(dataset["intercomp_dict"]["rad2_val"][selection]),
254+
np.ma.asarray(dataset["intercomp_dict"][f"rad1_{colname}"][selection]),
255+
np.ma.asarray(dataset["intercomp_dict"][f"rad2_{colname}"][selection]),
252256
field_name,
253257
field_name,
254258
step1=step,

src/pyrad_proc/pyrad/util/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,5 +98,7 @@
9898
from .data_retrieval_utils import retrieve_CPCCV # noqa
9999
from .data_retrieval_utils import retrieve_AQC_XLS # noqa
100100

101+
from .math_utils import bit_pack # noqa
102+
from .math_utils import bit_unpack # noqa
101103

102104
__all__ = [s for s in dir() if not s.startswith("_")]
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import math
2+
3+
4+
def bit_pack(values, max_value, num_values):
5+
"""
6+
Pack a list of integers into a single integer using bit manipulation.
7+
8+
Parameters
9+
----------
10+
values : list of int
11+
The values to pack. Each value must be <= `max_value`.
12+
If fewer than `num_values` are given, values are right-padded with 0s.
13+
If more than `num_values` are given, a ValueError is raised.
14+
max_value : int
15+
The maximum possible value any item in `values` can take.
16+
Determines how many bits are allocated per value.
17+
num_values : int
18+
The exact number of values to pack. Controls total bit length.
19+
20+
Returns
21+
-------
22+
int
23+
A single integer encoding all values.
24+
25+
Raises
26+
------
27+
ValueError
28+
If any value in `values` exceeds `max_value`, or if more than `num_values`
29+
are provided.
30+
31+
"""
32+
bits = math.ceil(math.log2(max_value + 1))
33+
if len(values) > num_values:
34+
raise ValueError("Too many values provided")
35+
padded_values = values + [0] * (num_values - len(values))
36+
37+
packed = 0
38+
for v in padded_values:
39+
if v > max_value:
40+
raise ValueError(f"Value {v} exceeds max allowed {max_value}")
41+
packed = (packed << bits) | v
42+
return packed
43+
44+
45+
def bit_unpack(packed, max_value, num_values):
46+
"""
47+
Unpack a single integer into a list of integers using bit manipulation.
48+
49+
Parameters
50+
----------
51+
packed : int
52+
The packed integer to decode.
53+
max_value : int
54+
The maximum possible value any original item could have taken.
55+
Determines how many bits were allocated per value.
56+
num_values : int
57+
The number of values to extract.
58+
59+
Returns
60+
-------
61+
list of int
62+
The unpacked values in their original order.
63+
64+
"""
65+
bits = math.ceil(math.log2(max_value + 1))
66+
mask = (1 << bits) - 1
67+
values = []
68+
for _ in range(num_values):
69+
values.append(packed & mask)
70+
packed >>= bits
71+
return list(reversed(values))

0 commit comments

Comments
 (0)