-
Notifications
You must be signed in to change notification settings - Fork 5
/
ml.py
2932 lines (2522 loc) · 108 KB
/
ml.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
pyart.retrieve.ml
=========================================
Routines to detect the ML from polarimetric RHI scans.
.. autosummary::
:toctree: generated/
melting_layer_mf
detect_ml
melting_layer_giangrande
melting_layer_hydroclass
compute_theoretical_profile
compute_apparent_profile
get_ml_rng_limits
find_best_profile
filter_ml
compare_rhohv
enough_data
mask_ml_top
get_iso0_val
get_flag_ml
get_pos_ml
compute_iso0
find_ml_field
interpol_field
_create_ml_obj
_prepare_radar
_get_ml_global
_get_target_azimuths
_find_ml_gates
_insert_ml_points
_find_ml_limits
_interpol_ml_limits
_get_res_vol_sides
_detect_ml_sweep
_process_map_ml
_process_map_ml_only_zh
_r_to_h
_remap_to_polar
_normalize_image
_gradient_2D
_convolve_with_nan
_mean_filter
_calc_sub_ind
"""
import datetime
from copy import deepcopy
from warnings import warn
import numpy as np
from scipy.interpolate import (
InterpolatedUnivariateSpline,
RegularGridInterpolator,
interp1d,
pchip,
)
from scipy.ndimage import convolve
from ..config import get_field_name, get_fillvalue, get_metadata
from ..core.transforms import antenna_to_cartesian, antenna_vectors_to_cartesian
from ..map.polar_to_cartesian import get_earth_radius, polar_to_cartesian
from ..util.datetime_utils import datetime_from_radar
from ..util.radar_utils import compute_antenna_diagram, compute_azimuthal_average
from ..util.sigmath import compute_corr, compute_nse
from ..util.xsect import cross_section_ppi, cross_section_rhi
# Parameters
# They shouldn not be changed ideally
MAXTHICKNESS_ML = 1000
MAXHEIGHT_ML = 6000.
MINHEIGHT_ML = 1000.
LOWMLBOUND = 0.7
UPMLBOUND = 1.3
SIZEFILT_M = 75
ZH_IM_BOUNDS = (10, 60)
RHOHV_IM_BOUNDS = (0.75, 1)
RHOHV_VALID_BOUNDS = (0.6, 1)
KE = 4 / 3. # Constant in the 4/3 earth radius model
def melting_layer_mf(radar, nvalid_min=180, ml_thickness_min=200.,
ml_thickness_max=1400., ml_thickness_step=100.,
iso0_max=4500., ml_top_diff_max=700., ml_top_step=100.,
rhohv_snow=0.99, rhohv_rain=0.99, rhohv_ml=0.93,
zh_snow=20., zh_rain=20., zh_ml=27., zv_snow=20.,
zv_rain=20., zv_ml=26., h_max=6000., h_res=1.,
beam_factor=2., npts_diagram=81, rng_bottom_max=200000.,
ns_factor=0.6, rhohv_corr_min=0.9, rhohv_nash_min=0.5,
ang_iso0=10., age_iso0=3., ml_thickness_iso0=700.,
ml_memory=None, rhohv_field_obs=None, temp_field=None,
iso0_field=None, rhohv_field_theo=None, ml_field=None,
ml_pos_field=None, temp_ref=None, get_iso0=True):
"""
Detects the melting layer following the approach implemented at
Meteo-France
Parameters
----------
radar : radar
radar object
Other Parameters
----------------
nvalid_min : int
Number of volume scans to aggregate
ml_thickness_min, ml_thickness_max, ml_thickness_step : float
Minimum, maximum and step of the melting layer thickness of the models
to explore [m]
iso0_max : maximum iso0 altitude [masl]
ml_top_diff_max, ml_top_step : float
maximum difference +- between iso-0 and top of the melting layer [m]
of the models to explore. Step
rhohv_snow, rhohv_rain, rhohv_ml : float
values of RhoHV above, below and at the peak of the melting layer used
to construct the model
zh_snow, zh_rain, zh_ml : float
values of horizontal reflectivity above, below and at the peak of the
melting layer used to construct the model
zv_snow, zv_rain, zv_ml : float
values of vertical reflectivity above, below and at the peak of the
melting layer used to construct the model
h_max : float
maximum altitude [masl] where to compute the model RhoHV profile
h_res : float
resolution of the model RhoHV profile
beam_factor : float
factor by which to multiply the antenna beamwidth. Used to select the
range of angles where the antenna pattern is going to be computed
rng_bottom_max : float
Maximum range up to which the bottom of the melting layer can be
placed in order to try to find a suitable model
ns_factor : float
multiplicative factor applied to the number of valid model gates when
comparing with the valid observations to decide whether the
observations and the model can be compared
rhohv_corr_min, rhohv_nash_min : float
minimum correlation and NSE to consider the comparison between model
and observations valid
ang_iso0 : float
the equivalent iso0 angle; Used for the computation of the weights
age_iso0 : float
the equivalent age of the iso0 (hours)
ml_thickness_iso0 : float
Default iso-0 thickness
ml_memory : dict or None
dictionary containing the memory of past retrievals
rhohv_field_obs, temp_field, iso0_field : str
name of the RhoHV observed field, temperature field and height over
iso0 field
rhohv_field_theo: str
name of the RhoHV modelled field
ml_field : str
Output. Field name which represents the melting layer field.
A value of None will use the default field name as defined in the
Py-ART configuration file.
ml_pos_field : str
Output. Field name which represents the melting layer top and bottom
height field. A value of None will use the default field name as
defined in the Py-ART configuration file.
temp_ref : str
the field use as reference for temperature. Can be temperature
or height_over_iso0.
get_iso0 : bool
returns height w.r.t. freezing level top for each gate in the radar
volume.
Returns
-------
ml_obj : radar-like object
A radar-like object containing the field melting layer height with
the bottom (at range position 0) and top (at range position one) of
the melting layer at each ray.
ml_dict : dict
A dictionary containg the position of the range gate respect to the
melting layer and metadata
iso0_dict : dict or None
A dictionary containing the distance respect to the melting layer
and metadata
ml_found_obj : radar-like object
A radar-like object containing the field melting layer height with
the bottom (at range position 0) and top (at range position one) of
the melting layer at each ray. This stores the instantaneous retrieval
i.e. the retrieval not averaged in time.
"""
# parse the field parameters
if rhohv_field_obs is None:
rhohv_field_obs = get_field_name('cross_correlation_ratio')
if temp_ref == 'temperature':
if temp_field is None:
temp_field = get_field_name('temperature')
temp_ref_field = temp_field
elif temp_ref == 'height_over_iso0':
if iso0_field is None:
iso0_field = get_field_name('height_over_iso0')
temp_ref_field = iso0_field
if rhohv_field_theo is None:
rhohv_field_theo = get_field_name(
'theoretical_cross_correlation_ratio')
if ml_field is None:
ml_field = get_field_name('melting_layer')
if ml_pos_field is None:
ml_pos_field = get_field_name('melting_layer_height')
# filter out temperature reference where there is no valid data
radar_aux = deepcopy(radar)
mask = np.ma.getmaskarray(radar.fields[rhohv_field_obs]['data'])
radar_aux.fields[temp_ref_field]['data'] = np.ma.masked_where(
mask, radar_aux.fields[temp_ref_field]['data'])
# get iso-0 reference (to use when data is insuficient)
if temp_ref == 'height_over_iso0':
iso0_ref = (
radar.gate_altitude['data'][0, 0]
- radar.fields[temp_ref_field]['data'][0, 0])
else:
ind = np.ma.where(
radar.fields[temp_ref_field]['data'][0, :] <= 0.)[0]
if ind.size == 0:
# all gates below the iso-0
iso0_ref = radar.gate_altitude['data'][0, -1]
else:
iso0_ref = radar.gate_altitude['data'][0, ind[0]]
print('iso0 ref:', iso0_ref)
# average RhoHV and temperature reference field
radar_rhi = compute_azimuthal_average(
radar_aux, [rhohv_field_obs, temp_ref_field], nvalid_min=nvalid_min)
iso0 = get_iso0_val(
radar_rhi, temp_ref_field=temp_ref_field, temp_ref=temp_ref,
iso0_ref=iso0_ref)
print('iso0:', iso0)
# get best instantaneous model by elevation angle
(best_ml_thickness, best_ml_bottom, best_rhohv_nash,
best_rhohv_nash_bottom) = find_best_profile(
radar_rhi, ml_thickness_min=ml_thickness_min,
ml_thickness_max=ml_thickness_max,
ml_thickness_step=ml_thickness_step, iso0=iso0, iso0_max=iso0_max,
ml_top_diff_max=ml_top_diff_max, ml_top_step=ml_top_step,
rhohv_snow=rhohv_snow, rhohv_rain=rhohv_rain, rhohv_ml=rhohv_ml,
zh_snow=zh_snow, zh_rain=zh_rain, zh_ml=zh_ml, zv_snow=zv_snow,
zv_rain=zv_rain, zv_ml=zv_ml, h_max=h_max, h_res=h_res,
beam_factor=beam_factor, npts_diagram=npts_diagram,
rng_bottom_max=rng_bottom_max, ns_factor=ns_factor,
rhohv_corr_min=rhohv_corr_min, rhohv_nash_min=rhohv_nash_min,
rhohv_field_obs=rhohv_field_obs, rhohv_field_theo=rhohv_field_theo)
print('\nelevations', radar_rhi.elevation['data'])
print('best_ml_thickness', best_ml_thickness)
print('best_ml_bottom', best_ml_bottom)
print('best_rhohv_nash', best_rhohv_nash)
print('best_rhohv_nash_bottom', best_rhohv_nash_bottom)
ml_found_obj = _create_ml_obj(radar_rhi, ml_pos_field)
ml_found_obj.fields[ml_pos_field]['data'][:, 0] = best_ml_bottom
ml_found_obj.fields[ml_pos_field]['data'][:, 1] = (
best_ml_bottom + best_ml_thickness)
ml_bottom, ml_thickness = filter_ml(
best_ml_thickness, best_ml_bottom, iso0, radar_rhi.elevation['data'],
ang_iso0=ang_iso0, age_iso0=age_iso0,
ml_thickness_iso0=ml_thickness_iso0, ml_memory=ml_memory)
print('best_ml_thickness', ml_thickness)
print('best_ml_bottom', ml_bottom)
# Create melting layer object containing top and bottom and metadata
# and melting layer field
ml_obj = _create_ml_obj(radar, ml_pos_field)
ml_obj.fields[ml_pos_field]['data'][:, 0] = ml_bottom
ml_obj.fields[ml_pos_field]['data'][:, 1] = ml_bottom + ml_thickness
# Find position of range gates respect to melting layer top and bottom
ml_dict = get_metadata(ml_field)
ml_dict.update({'_FillValue': 0})
ml_dict = find_ml_field(
radar, ml_obj, ml_pos_field=ml_pos_field, ml_field=ml_field)
# get the iso0
iso0_dict = None
if get_iso0:
iso0_dict = compute_iso0(
radar, ml_obj.fields[ml_pos_field]['data'][:, 1],
iso0_field=iso0_field)
return ml_obj, ml_dict, iso0_dict, ml_found_obj
def detect_ml(radar, gatefilter=None, fill_value=None, refl_field=None,
rhohv_field=None, ml_field=None, ml_pos_field=None,
iso0_field=None, max_range=20000,
detect_threshold=0.02, interp_holes=False, max_length_holes=250,
check_min_length=True, get_iso0=False):
'''
Detects the melting layer (ML) using the reflectivity and copolar
correlation coefficient. Internally it uses RHIs
Paremeters
----------
radar : Radar
A Radar class instance
gatefilter : GateFilter, optional
A GateFilter indicating radar gates that should be excluded
fill_value : float, optional
Value indicating missing or bad data in differential phase
field, if not specified, the default in the Py-ART
configuration file will be used
refl_field : str, optional
Reflectivity field. If None, the default field name must be
specified in the Py-ART configuration file.
rhohv_field : str, optional
Copolar correlation coefficient field. If None, the default
field name must be specified in the Py-ART configuration file.
ml_field : str, optional
Melting layer field. If None, the default field name must
be specified in the Py-ART configuration file.
ml_pos_field : str, optional
Melting layer height field. If None, the default field name must
be specified in the Py-ART configuration file.
iso0_field : str, optional
height respect to the iso0 field.
max_range : float, optional
the max. range from the radar to be used in the ML determination
detect_threshold : float, optional
the detection threshold (see paper), you can play around and
see how it affects the output. Lowering the value makes the
algorithm more sensitive but increases the number of
erroneous detections.
interp_holes : bool, optional
Flag to allow for interpolation of small holes in the detected ML
max_length_holes : float, optional
The maximum size of holes in the ML for them to be interpolated
check_min_length : bool, optional
If true, the length of the detected ML will
be compared with the length of the valid data and the
ML will be kept only if sufficiently long
get_iso0 : bool
returns height w.r.t. freezing level top for each gate in the
radar volume.
Returns
-------
ml_obj : radar-like object
A radar-like object containing the field melting layer height with
the bottom (at range position 0) and top (at range position one) of
the melting layer at each ray
ml_dict : dict
A dictionary containg the position of the range gate respect to the
melting layer and metadata
iso0_dict : dict or None
A dictionary containing the distance respect to the melting layer
and metadata
all_ml : dict
Dictionary containing internal parameters in polar and cartesian
coordinates
Reference:
----------
Wolfensberger, D. , Scipion, D. and Berne, A. (2016), Detection and
characterization of the melting layer based on polarimetric radar scans.
Q.J.R. Meteorol. Soc., 142: 108-124. doi:10.1002/qj.2672
'''
# parse fill value
if fill_value is None:
fill_value = get_fillvalue()
# parse field names
if refl_field is None:
refl_field = get_field_name('reflectivity')
if rhohv_field is None:
rhohv_field = get_field_name('copolar_correlation_coefficient')
if ml_field is None:
ml_field = get_field_name('melting_layer')
if ml_pos_field is None:
ml_pos_field = get_field_name('melting_layer_height')
# get radar with only relevant fields
radar_in = _prepare_radar(
radar, [refl_field, rhohv_field], temp_ref='no_field')
if radar_in is None:
warn('Unable to obtain melting layer information for this radar scan')
return None, None, None, None
# mask radar gates indicated by the gate filter
if gatefilter is not None:
radar_in.fields[refl_field]['data'] = np.ma.masked_where(
gatefilter.gate_excluded, radar_in.fields[refl_field]['data'])
radar_in.fields[rhohv_field]['data'] = np.ma.masked_where(
gatefilter.gate_excluded, radar_in.fields[rhohv_field]['data'])
# transform radar into rhi
if radar_in.scan_type == 'ppi':
target_azimuths, az_tol = _get_target_azimuths(radar_in)
radar_rhi = cross_section_ppi(
radar_in, target_azimuths, az_tol=az_tol)
elif radar_in.scan_type == 'rhi':
radar_rhi = radar_in
else:
warn('Error: unsupported scan type.')
return None, None, None, None
# get melting layer data
all_ml = []
ml_exists = []
for sweep in range(radar_rhi.nsweeps):
radar_sweep = radar_rhi.extract_sweeps([sweep])
out = _detect_ml_sweep(
radar_sweep, fill_value, refl_field, rhohv_field,
ml_field, max_range, detect_threshold, interp_holes,
max_length_holes, check_min_length)
all_ml.append(out)
ml_exists.append(out['ml_exists'])
# Check if melting layer has been found
if not any(ml_exists):
warn('Unable to obtain melting layer information for this radar scan')
return None, None, None, None
# Create melting layer object containing top and bottom and metadata
# and melting layer field
ml_dict = get_metadata(ml_field)
ml_dict.update({'_FillValue': 0})
ml_obj = _create_ml_obj(radar_rhi, ml_pos_field)
ml_data = np.ma.masked_all(
(radar_rhi.nrays, radar_rhi.ngates), dtype=np.uint8)
for sweep in range(radar_rhi.nsweeps):
sweep_start = radar_rhi.sweep_start_ray_index['data'][sweep]
sweep_end = radar_rhi.sweep_end_ray_index['data'][sweep]
ml_obj.fields[ml_pos_field]['data'][sweep_start:sweep_end + 1, 0] = (
all_ml[sweep]['ml_pol']['bottom_ml'])
ml_obj.fields[ml_pos_field]['data'][sweep_start:sweep_end + 1, 1] = (
all_ml[sweep]['ml_pol']['top_ml'])
ml_data[sweep_start:sweep_end + 1, :] = all_ml[sweep]['ml_pol']['data']
ml_dict['data'] = ml_data
valid_values = ml_obj.fields[ml_pos_field]['data'][:, 1].compressed()
print('Before PPI transformation ', valid_values[valid_values > 6000.])
# transform back into PPI volume
if radar_in.scan_type == 'ppi':
radar_rhi.add_field(ml_field, ml_dict)
radar_out = cross_section_rhi(radar_rhi, radar_in.fixed_angle['data'])
ml_dict['data'] = radar_out.fields[ml_field]['data']
ml_obj = cross_section_rhi(ml_obj, radar_in.fixed_angle['data'])
valid_values = ml_obj.fields[ml_pos_field]['data'][:, 1].compressed()
print('After PPI transformation ', valid_values[valid_values > 6000.])
# get the iso0
iso0_dict = None
if get_iso0:
iso0_dict = compute_iso0(
radar_in, ml_obj.fields[ml_pos_field]['data'][:, 1],
iso0_field=iso0_field)
return ml_obj, ml_dict, iso0_dict, all_ml
def melting_layer_giangrande(radar, nVol=3, maxh=6000., hres=50.,
rmin=1000., elmin=4., elmax=10., rhomin=0.75,
rhomax=0.94, zhmin=20., hwindow=500.,
mlzhmin=30., mlzhmax=50., mlzdrmin=1.,
mlzdrmax=5., htol=500., ml_bottom_diff_max=1000.,
time_accu_max=1800., nml_points_min=None,
wlength=20., percentile_bottom=0.3,
percentile_top=0.9, interpol=True,
time_nodata_allowed=3600., refl_field=None,
zdr_field=None, rhv_field=None, temp_field=None,
iso0_field=None, ml_field=None,
ml_pos_field=None, temp_ref=None,
get_iso0=False, ml_global=None):
"""
Detects the melting layer following the approach by Giangrande et al
(2008)
Parameters
----------
radar : radar
radar object
Other Parameters
----------------
nVol : int
Number of volume scans to aggregate
maxh : float
Maximum possible height of the melting layer [m MSL]
hres : float
Step of the height of the melting layer [m]
rmin : float
Minimum range from radar where to look for melting layer contaminated
range gates [m]
elmin, elmax : float
Minimum and maximum elevation angles where to look for melting layer
contaminated range gates [degree]
rhomin, rhomax : float
min and max rhohv to consider pixel potential melting layer pixel
zhmin : float
Minimum reflectivity level of a range gate to consider it a potential
melting layer gate [dBZ]
hwindow : float
Maximum distance (in range) from potential melting layer gate where to
look for a maximum [m]
mlzhmin, mlzhmax : float
Minimum and maximum values that a peak in reflectivity within the
melting layer may have to consider the range gate melting layer
contaminated [dBZ]
mlzdrmin, mlzdrmax : float
Minimum and maximum values that a peak in differential reflectivity
within the melting layer may have to consider the range gate melting
layer contaminated [dB]
htol : float
maximum distance from the iso0 coming from model allowed to consider
the range gate melting layer contaminated [m]
ml_bottom_dif_max : float
Maximum distance from the bottom of the melting layer computed in the
previous time step to consider a range gate melting layer contaminated
[m]
time_accu_max : float
Maximum time allowed to accumulate data from consecutive scans [s]
nml_points_min : int
minimum number of melting layer points to consider valid melting layer
detection
wlength : float
length of the window to select the azimuth angles used to compute the
melting layer limits at a particular azimuth [degree]
percentile_bottom, percentile_top : float [0,1]
percentile of ml points above which is considered that the bottom of
the melting layer starts and the top ends
interpol : bool
Whether to interpolate the obtained results in order to get a value
for each azimuth
time_nodata_allowed : float
The maximum time allowed for no data before considering the melting
layer not valid [s]
refl_field, zdr_field, rhv_field, temp_field, iso0_field : str
Inputs. Field names within the radar object which represent the
horizonal reflectivity, the differential reflectivity, the copolar
correlation coefficient, the temperature and the height respect to the
iso0 fields. A value of None for any of these parameters will use the
default field name as defined in the Py-ART configuration file.
ml_field : str
Output. Field name which represents the melting layer field.
A value of None will use the default field name as defined in the
Py-ART configuration file.
ml_pos_field : str
Output. Field name which represents the melting layer top and bottom
height field. A value of None will use the default field name as
defined in the Py-ART configuration file.
temp_ref : str
the field use as reference for temperature. Can be temperature
or height_over_iso0.
If None, it excludes model data from the algorithm.
get_iso0 : bool
returns height w.r.t. freezing level top for each gate in the radar
volume.
ml_global :
stack of previous volume data to introduce some time dependency. Its
max size is controlled by the nVol parameter. It is always in
(pseudo-)RHI mode.
Returns
-------
ml_obj : radar-like object
A radar-like object containing the field melting layer height with
the bottom (at range position 0) and top (at range position one) of
the melting layer at each ray
ml_dict : dict
A dictionary containg the position of the range gate respect to the
melting layer and metadata
iso0_dict : dict or None
A dictionary containing the distance respect to the melting layer
and metadata
ml_global : dict or None
stack of previous volume data to introduce some time dependency. Its
max size is controlled by the nVol parameter. It is always in
(pseudo-)RHI mode.
References
----------
Giangrande, S.E., Krause, J.M., Ryzhkov, A.V.: Automatic Designation of
the Melting Layer with a Polarimetric Prototype of the WSR-88D Radar,
J. of Applied Meteo. and Clim., 47, 1354-1364, doi:10.1175/2007JAMC1634.1,
2008
"""
# parse the field parameters
if refl_field is None:
refl_field = get_field_name('reflectivity')
if zdr_field is None:
zdr_field = get_field_name('differential_reflectivity')
if rhv_field is None:
rhv_field = get_field_name('cross_correlation_ratio')
if temp_ref == 'temperature':
if temp_field is None:
temp_field = get_field_name('temperature')
elif temp_ref == 'height_over_iso0':
if iso0_field is None:
iso0_field = get_field_name('height_over_iso0')
if ml_field is None:
ml_field = get_field_name('melting_layer')
if ml_pos_field is None:
ml_pos_field = get_field_name('melting_layer_height')
# prepare radar input (select relevant radar fields)
field_list = [refl_field, zdr_field, rhv_field]
if temp_ref == 'temperature':
field_list.append(temp_field)
elif temp_ref == 'height_over_iso0':
field_list.append(iso0_field)
radar_in = _prepare_radar(
radar, field_list, temp_ref=temp_ref, iso0_field=iso0_field,
temp_field=temp_field, lapse_rate=-6.5)
if radar_in is None:
warn('Unable to obtain melting layer information for this radar scan')
return None, None, None, ml_global
ml_global, is_valid = _get_ml_global(
radar_in, ml_global=ml_global, nVol=nVol, maxh=maxh, hres=hres)
if not is_valid:
warn('Unable to obtain melting layer information for this radar scan')
return None, None, None, ml_global
# Find gates suspected to belong to the melting layer
ml_points, nml_total = _find_ml_gates(
ml_global, refl_field=refl_field, zdr_field=zdr_field,
rhv_field=rhv_field, iso0_field=iso0_field, rmin=rmin, elmin=elmin,
elmax=elmax, rhomin=rhomin, rhomax=rhomax, zhmin=zhmin,
hwindow=hwindow, htol=htol, mlzhmin=mlzhmin, mlzhmax=mlzhmax,
mlzdrmin=mlzdrmin, mlzdrmax=mlzdrmax,
ml_bottom_diff_max=ml_bottom_diff_max)
now_time = datetime_from_radar(radar_in)
if nml_total > 0:
ml_global = _insert_ml_points(
ml_global, ml_points, now_time, time_accu_max=time_accu_max)
# Find melting layer limits using accumulated global data
ml_top, ml_bottom = _find_ml_limits(
ml_global, nml_points_min=nml_points_min, wlength=wlength,
percentile_top=percentile_top,
percentile_bottom=percentile_bottom, interpol=interpol)
if ml_top.all() is np.ma.masked:
if ml_global['time_nodata_start'] is None:
ml_global['time_nodata_start'] = deepcopy(now_time)
elif ((now_time - ml_global['time_nodata_start']).total_seconds() >
time_nodata_allowed):
warn('Invalid melting layer data')
return None, None, None, None
else:
ml_global['ml_top'] = ml_top
ml_global['ml_bottom'] = ml_bottom
ml_global['time_nodata_start'] = None
else:
if ml_global['time_nodata_start'] is None:
ml_global['time_nodata_start'] = deepcopy(now_time)
elif ((now_time - ml_global['time_nodata_start']).total_seconds() >
time_nodata_allowed):
warn('Invalid melting layer data')
return None, None, None, None
# check if valid melting layer limits are available
if ml_global['ml_top'].all() is np.ma.masked:
warn('Invalid melting layer data')
return None, None, None, ml_global
# Find melting layer top and bottom height of each ray in current radar
ml_obj = _interpol_ml_limits(
radar_in, ml_global['ml_top'], ml_global['ml_bottom'],
ml_global['azi_vec'], ml_pos_field=ml_pos_field)
# Find position of range gates respect to melting layer top and bottom
ml_dict = find_ml_field(
radar_in, ml_obj, ml_pos_field=ml_pos_field, ml_field=ml_field)
# get the iso0
iso0_dict = None
if get_iso0:
iso0_dict = compute_iso0(
radar_in, ml_obj.fields[ml_pos_field]['data'][:, 1],
iso0_field=iso0_field)
return ml_obj, ml_dict, iso0_dict, ml_global
def melting_layer_hydroclass(radar, hydro_field=None, ml_field=None,
ml_pos_field=None, iso0_field=None,
force_continuity=True, dist_max=350.,
get_iso0=False):
"""
Using the results of the hydrometeor classification by Besic et al.
estimates the position of the range gates respect to the melting layer,
the melting layer top and bottom height and the distance of the range
gate with respect to the freezing level.
Parameters
----------
radar : Radar
Radar object. Must have and hydrometeor classification field
hydro_field : str
Name of the hydrometeor classification field. A value
of None will use the default field name as defined in the Py-ART
configuration file.
ml_field, ml_pos_field, iso0_field : str
Name of the melting layer, melting layer heightand iso0 field.
A value of None for any of these parameters will use the default field
names as defined in the Py-ART configuration file.
force_continuity : Bool
If True, the melting layer is forced to be continuous in range
dist_max : float
The maximum distance between range gates flagged as inside the melting
layer to consider them as gates in the melting layer.
Returns
-------
ml_obj : radar-like object
A radar-like object containing the field melting layer height with
the bottom (at range position 0) and top (at range position one) of
the melting layer at each ray
ml_dict : dict
A dictionary containg the position of the range gate respect to the
melting layer and metadata
iso0_dict : dict or None
A dictionary containing the distance respect to the melting layer
and metadata
"""
# parse the field parameters
if hydro_field is None:
hydro_field = get_field_name('radar_echo_classification')
if ml_field is None:
ml_field = get_field_name('melting_layer')
if ml_pos_field is None:
ml_pos_field = get_field_name('melting_layer_height')
if iso0_field is None:
iso0_field = get_field_name('height_over_iso0')
# check if fieldnames exists
radar.check_field_exists(hydro_field)
# get the position of the range gates respect to the melting layer
ml_dict = get_flag_ml(
radar, hydro_field=hydro_field, ml_field=ml_field,
force_continuity=force_continuity, dist_max=dist_max)
# get the melting layer top and bottom
ml_obj = get_pos_ml(radar, ml_dict['data'], ml_pos_field=ml_pos_field)
# get the iso0
iso0_dict = None
if get_iso0:
iso0_dict = compute_iso0(
radar, ml_obj.fields[ml_pos_field]['data'][:, 1],
iso0_field=iso0_field)
return ml_obj, ml_dict, iso0_dict
def compute_theoretical_profile(ml_top=3000., ml_thickness=200.,
val_snow=0.99, val_rain=0.99, val_ml=0.93,
h_max=6000., h_res=1.):
"""
Computes an idealized vertical profile. The default values are those of
RhoHV
Parameters
----------
ml_top : float
melting layer top [m asl]
ml_thickness : float
melting layer thickness [m]
val_snow, val_rain, val_ml : float
values in snow, rain and in the peak of the melting layer
h_max : float
maximum altitude at which to compute the profile [m asl]
h_res : float
profile resolution [m]
Returns
-------
val_theo_dict : dict
A dictionary containg the value at each altitude, the reference
altitude and the top and bottom of the melting layer
"""
h = np.arange(0, h_max, h_res)
val_theo = np.ma.masked_all(h.size)
ml_bottom = ml_top - ml_thickness
ml_peak = ml_top - ml_thickness / 2.
val_theo[h < ml_bottom] = val_rain
val_theo[(h >= ml_bottom) & (h < ml_peak)] = (
val_rain - 2. * (val_rain - val_ml) / ml_thickness
* (h[(h >= ml_bottom) & (h < ml_peak)] - ml_bottom))
val_theo[(h >= ml_peak) & (h <= ml_top)] = (
val_ml + 2. * (val_snow - val_ml) / ml_thickness
* (h[(h >= ml_peak) & (h <= ml_top)] - ml_peak))
val_theo[h > ml_top] = val_snow
val_theo_dict = {
'value': val_theo,
'altitude': h,
'ml_top': ml_top,
'ml_bottom': ml_bottom
}
return val_theo_dict
def compute_apparent_profile(
radar,
ml_top=3000.,
ml_thickness=200.,
rhohv_snow=0.99,
rhohv_rain=0.99,
rhohv_ml=0.93,
zh_snow=20.,
zh_rain=20.,
zh_ml=27.,
zv_snow=20.,
zv_rain=20.,
zv_ml=26.,
h_max=6000.,
h_res=1.,
beam_factor=2.,
npts_diagram=81,
rng_bottom_max=200000.,
rhohv_field='theoretical_cross_correlation_ratio'):
"""
Computes the apparent profile of RhoHV
Parameters
----------
radar : radar object
the reference radar object
ml_top, ml_thickness : float
melting layer top [m asl] and thickness [m]
rhohv_snow, rhohv_rain, rhohv_ml : float
values of RhoHV in snow, rain and in the peak of the melting layer
zh_snow, zh_rain, zh_ml : float
values of horizontal reflectivity [dBZ] in snow, rain and in the peak
of the melting layer
zv_snow, zv_rain, zv_ml : float
values of vertical reflectivity [dBZ] in snow, rain and in the peak
of the melting layer
h_max : float
maximum altitude at which to compute the theoretical profiles [m asl]
h_res : float
profile resolution [m]
beam_factor : float
the factor by which the antenna beam width is multiplied
npts_diagram : int
The number of points that that the antenna diagram will have
rng_bottom_max: float
maximum range at which the bottom of the melting layer can be placed
rhohv_field: str
Name of the apparent RhoHV profile obtained
Returns
-------
radar_out : radar object or None
A radar object containing the apparent RhoHV profile. None if it could
not be computed
rhohv_theo_dict : dict or None
A dictionary containg the theoretical RhoHV profile. None if it could
not be computed
"""
ml_bottom = ml_top - ml_thickness
radar_out = deepcopy(radar)
radar_out.fields = {}
rhohv_dict = get_metadata(rhohv_field)
rhohv_dict['data'] = np.ma.masked_all((radar_out.nrays, radar_out.ngates))
radar_out.add_field(rhohv_field, rhohv_dict)
if ml_bottom < radar_out.altitude['data']:
return None, None
# get theoretical profiles as a function of altitude
rhohv_theo_dict = compute_theoretical_profile(
ml_top=ml_top, ml_thickness=ml_thickness, val_snow=rhohv_snow,
val_rain=rhohv_rain, val_ml=rhohv_ml, h_max=h_max, h_res=h_res)
zh_theo_dict = compute_theoretical_profile(
ml_top=ml_top, ml_thickness=ml_thickness, val_snow=zh_snow,
val_rain=zh_rain, val_ml=zh_ml, h_max=h_max, h_res=h_res)
zv_theo_dict = compute_theoretical_profile(
ml_top=ml_top, ml_thickness=ml_thickness, val_snow=zv_snow,
val_rain=zv_rain, val_ml=zv_ml, h_max=h_max, h_res=h_res)
alt_theo = rhohv_theo_dict['altitude']
rhohv_theo = rhohv_theo_dict['value']
zh_theo_lin = np.power(10., 0.1 * zh_theo_dict['value'])
zv_theo_lin = np.power(10., 0.1 * zv_theo_dict['value'])
rng = radar_out.range['data']
# range resolution of the radar resolution volume
rng_res = rng[1] - rng[0]
rng_left_km = (rng - rng_res / 2.) / 1000.
rng_right_km = (rng + rng_res / 2.) / 1000.
# angular resolution of the radar resolution volume defined as a factor
# of the antenna beam width
beam_width = (
radar_out.instrument_parameters['radar_beam_width_h']['data'][0])
ang_res = beam_factor * beam_width
ang_diag, weights_diag = compute_antenna_diagram(
npts_diagram=npts_diagram, beam_factor=beam_factor,
beam_width=beam_width)
f_rhohv = interp1d(
alt_theo, rhohv_theo, kind='nearest', bounds_error=False,
fill_value=np.nan)
f_zh = interp1d(
alt_theo, zh_theo_lin, kind='nearest', bounds_error=False,
fill_value=np.nan)
f_zv = interp1d(
alt_theo, zv_theo_lin, kind='nearest', bounds_error=False,
fill_value=np.nan)
for ind_ray, ang in enumerate(radar_out.elevation['data']):
rng_bottom, rng_top = get_ml_rng_limits(
rng_left_km, rng_right_km, rng, ang, ang_res,
radar_out.altitude['data'][0], ml_bottom, ml_top)
if rng_bottom > rng_bottom_max:
# the bottom of the area affected by the melting layer is too far
continue
i_rng_btm = np.where(rng >= rng_bottom)[0][0]
i_rng_top = np.where(rng >= rng_top)[0][0]
# maximum range where to define the apparent RhoHV profile
rng_max = rng_top + (rng_top - rng_bottom) / 2.
i_rng_max = np.where(rng >= rng_max)[0]
if i_rng_max.size == 0:
i_rng_max = rng.size - 1
else:
i_rng_max = i_rng_max[0]
# values above and below the melting layer affected area
radar_out.fields[rhohv_field]['data'][ind_ray, 0:i_rng_btm] = (
rhohv_rain)
radar_out.fields[rhohv_field]['data'][
ind_ray, i_rng_top + 1:i_rng_max + 1] = rhohv_snow
# values in the area affected by the melting layer
rng_ml_vals = rng[i_rng_btm:i_rng_top + 1] / 1000. # km
for i_rng, rng_ml in enumerate(rng_ml_vals):
# altitudes affected by the antenna diagram
_, _, z_diag = antenna_to_cartesian(rng_ml, 0., ang + ang_diag)
z_diag += radar_out.altitude['data']
rhohv_vals = f_rhohv(z_diag)
rhohv_vals = np.ma.masked_invalid(rhohv_vals)
zh_vals = f_zh(z_diag)
zh_vals = np.ma.masked_invalid(zh_vals)
zv_vals = f_zv(z_diag)
zv_vals = np.ma.masked_invalid(zh_vals)
radar_out.fields[rhohv_field]['data'][ind_ray, i_rng_btm +
i_rng] = (np.ma.sum(rhohv_vals *
np.ma.sqrt(zh_vals *
zv_vals) *
weights_diag) /
np.ma.sqrt(np.ma.sum(zh_vals *
weights_diag) *
np.ma.sum(zv_vals *
weights_diag)))
return radar_out, rhohv_theo_dict
def get_ml_rng_limits(rng_left_km, rng_right_km, rng, ang, ang_res,
radar_altitude, ml_bottom, ml_top):
"""
get the minimum and maximum range affected by the melting layer
Parameters
----------
rng_left_km, rng_right_km : array of floats
the left and right limits of each range resolution volume [km]
rng : array of floats
the radar range (center of the bin) [m]
ang : float
the elevation angle
ang_res : float
the angle resolution considered
radar_altitude : float
the radar altitude [masl]
ml_bottom, ml_top : float
the top and bottom of the melting layer [m msl]
Returns