def _significant_features(radar, field, gatefilter=None, min_size=None, size_bins=75, size_limits=(0, 300), structure=None, remove_size_field=True, fill_value=None, size_field=None, debug=False, verbose=False): """ """ # Parse fill value if fill_value is None: fill_value = get_fillvalue() # Parse field names if size_field is None: size_field = '{}_feature_size'.format(field) # Parse gate filter if gatefilter is None: gatefilter = GateFilter(radar, exclude_based=False) # Parse binary structuring element if structure is None: structure = ndimage.generate_binary_structure(2, 1) # Initialize echo feature size array size_data = np.zeros_like(radar.fields[field]['data'], subok=False, dtype=np.int32) # Loop over all sweeps feature_sizes = [] for sweep in radar.iter_slice(): # Parse radar sweep data and define only valid gates is_valid_gate = ~radar.fields[field]['data'][sweep].mask # Label the connected features in radar sweep data and create index # array which defines each unique label (feature) labels, nlabels = ndimage.label(is_valid_gate, structure=structure, output=None) index = np.arange(1, nlabels + 1, 1) if debug: print 'Number of unique features for {}: {}'.format(sweep, nlabels) # Compute the size (in radar gates) of each echo feature # Check for case where no echo features are found, e.g., no data in # sweep if nlabels > 0: sweep_sizes = ndimage.labeled_comprehension( is_valid_gate, labels, index, np.count_nonzero, np.int32, 0) feature_sizes.append(sweep_sizes) # Set each label (feature) to its total size (in radar gates) for label, size in zip(index, sweep_sizes): size_data[sweep][labels == label] = size # Stack sweep echo feature sizes feature_sizes = np.hstack(feature_sizes) # Compute histogram of echo feature sizes, bin centers and bin # width counts, bin_edges = np.histogram(feature_sizes, bins=size_bins, range=size_limits, normed=False, weights=None, density=False) bin_centers = bin_edges[:-1] + np.diff(bin_edges) / 2.0 bin_width = np.diff(bin_edges).mean() if debug: print 'Bin width: {} gate(s)'.format(bin_width) # Compute the peak of the echo feature size distribution # We expect the peak of the echo feature size distribution to be close to 1 # radar gate peak_size = bin_centers[counts.argmax()] - bin_width / 2.0 if debug: print 'Feature size at peak: {} gate(s)'.format(peak_size) # Determine the first instance when the count (sample size) for an echo # feature size bin reaches 0 after the distribution peak # This will define the minimum echo feature size is_zero_size = np.logical_and(bin_centers > peak_size, np.isclose(counts, 0, atol=1.0e-5)) min_size = bin_centers[is_zero_size].min() - bin_width / 2.0 if debug: _range = [0.0, min_size] print 'Insignificant feature size range: {} gates'.format(_range) # Mask invalid feature sizes, e.g., zero-size features size_data = np.ma.masked_equal(size_data, 0, copy=False) size_data.set_fill_value(fill_value) # Add echo feature size field to radar size_dict = { 'data': size_data.astype(np.int32), 'standard_name': size_field, 'long_name': '', '_FillValue': size_data.fill_value, 'units': 'unitless', } radar.add_field(size_field, size_dict, replace_existing=True) # Update gate filter gatefilter.include_above(size_field, min_size, op='and', inclusive=False) # Remove eacho feature size field if remove_size_field: radar.fields.pop(size_field, None) return gatefilter
def significant_detection(radar, gatefilter=None, remove_small_features=True, size_bins=75, size_limits=(0, 300), fill_holes=False, dilate=False, structure=None, iterations=1, rays_wrap_around=False, min_ncp=None, ncp_field=None, detect_field=None, debug=False, verbose=False): """ Determine the significant detection of a radar. Note that significant detection can still include other non-meteorological echoes that the user may still have to remove further down the processing chain. Parameters ---------- radar : Radar Radar object used to determine the appropriate GateFilter. gatefilter : GateFilter, optional If None, all radar gates will initially be assumed valid. remove_small_features : bool, optional True to remove insignificant echo features (e.g., salt and pepper noise) from significant detection mask. size_bins : int, optional Number of bins used to bin echo feature sizes and thus define its distribution. size_limits : list or tuple, optional Limits of the echo feature size distribution. The upper limit needs to be large enough to include the minimum feature size. fill_holes : bool, optional Fill any holes in the significant detection mask. For most radar volumes this should not be used since the default structuring element will automatically fill any sized hole. dilate : bool, optional Use binary dilation to fill in edges of the significant detection mask. structure : array_like, optional The binary structuring element used for all morphology routines. See SciPy's ndimage documentation for more information. iterations : int, optional The number of iterations to repeat binary dilation. If iterations is less than 1, binary dilation is repeated until the result does not change anymore. rays_wrap_around : bool, optional Whether the rays at the beginning and end of a sweep are connected (e.g., PPI VCP). min_ncp : float, optional Minimum normalized coherent power (signal quality) value used to indicate a significant echo. ncp_field : str, optional Minimum normalized coherent power (signal quality) field name. The default uses the Py-ART configuation file. detect_field : str, optional Radar significant detection mask field name. debug : bool, optional True to print debugging information, False to suppress. verbose : bool, optional True to print progress information, False to suppress. Returns ------- gatefilter : GateFilter Py-ART GateFilter object indicating which radar gates are valid and invalid. """ # Parse field names if ncp_field is None: ncp_field = get_field_name('normalized_coherent_power') if detect_field is None: detect_field = 'significant_detection_mask' # Parse gate filter if gatefilter is None: gatefilter = GateFilter(radar, exclude_based=False) # Exclude gates with poor signal quality if min_ncp is not None and ncp_field in radar.fields: gatefilter.include_above(ncp_field, min_ncp, op='and', inclusive=True) detect_dict = { 'data': gatefilter.gate_included.astype(np.int8), 'long_name': 'Radar significant detection mask', 'standard_name': 'significant_detection_mask', 'valid_min': 0, 'valid_max': 1, '_FillValue': None, 'units': 'unitless', 'comment': '0 = no significant detection, 1 = significant detection', } radar.add_field(detect_field, detect_dict, replace_existing=True) # Remove insignificant features from significant detection mask if remove_small_features: basic_fixes._binary_significant_features(radar, detect_field, size_bins=size_bins, size_limits=size_limits, structure=structure, debug=debug, verbose=verbose) # Fill holes in significant detection mask if fill_holes: basic_fixes._binary_fill(radar, detect_field, structure=structure) # Dilate significant detection mask if dilate: basic_fixes._binary_dilation(radar, detect_field, structure=structure, iterations=iterations, debug=debug, verbose=verbose) # Update gate filter gatefilter.include_equal(detect_field, 1, op='new') return gatefilter
def significant_detection( radar, gatefilter=None, remove_small_features=True, size_bins=75, size_limits=(0, 300), fill_holes=False, dilate=False, structure=None, iterations=1, rays_wrap_around=False, min_ncp=None, ncp_field=None, detect_field=None, debug=False, verbose=False): """ Determine the significant detection of a radar. Note that significant detection can still include other non-meteorological echoes that the user may still have to remove further down the processing chain. Parameters ---------- radar : Radar Radar object used to determine the appropriate GateFilter. gatefilter : GateFilter, optional If None, all radar gates will initially be assumed valid. remove_small_features : bool, optional True to remove insignificant echo features (e.g., salt and pepper noise) from significant detection mask. size_bins : int, optional Number of bins used to bin echo feature sizes and thus define its distribution. size_limits : list or tuple, optional Limits of the echo feature size distribution. The upper limit needs to be large enough to include the minimum feature size. fill_holes : bool, optional Fill any holes in the significant detection mask. For most radar volumes this should not be used since the default structuring element will automatically fill any sized hole. dilate : bool, optional Use binary dilation to fill in edges of the significant detection mask. structure : array_like, optional The binary structuring element used for all morphology routines. See SciPy's ndimage documentation for more information. iterations : int, optional The number of iterations to repeat binary dilation. If iterations is less than 1, binary dilation is repeated until the result does not change anymore. rays_wrap_around : bool, optional Whether the rays at the beginning and end of a sweep are connected (e.g., PPI VCP). min_ncp : float, optional Minimum normalized coherent power (signal quality) value used to indicate a significant echo. ncp_field : str, optional Minimum normalized coherent power (signal quality) field name. The default uses the Py-ART configuation file. detect_field : str, optional Radar significant detection mask field name. debug : bool, optional True to print debugging information, False to suppress. verbose : bool, optional True to print progress information, False to suppress. Returns ------- gatefilter : GateFilter Py-ART GateFilter object indicating which radar gates are valid and invalid. """ # Parse field names if ncp_field is None: ncp_field = get_field_name('normalized_coherent_power') if detect_field is None: detect_field = 'significant_detection_mask' # Parse gate filter if gatefilter is None: gatefilter = GateFilter(radar, exclude_based=False) # Exclude gates with poor signal quality if min_ncp is not None and ncp_field in radar.fields: gatefilter.include_above(ncp_field, min_ncp, op='and', inclusive=True) detect_dict = { 'data': gatefilter.gate_included.astype(np.int8), 'long_name': 'Radar significant detection mask', 'standard_name': 'significant_detection_mask', 'valid_min': 0, 'valid_max': 1, '_FillValue': None, 'units': 'unitless', 'comment': '0 = no significant detection, 1 = significant detection', } radar.add_field(detect_field, detect_dict, replace_existing=True) # Remove insignificant features from significant detection mask if remove_small_features: basic_fixes._binary_significant_features( radar, detect_field, size_bins=size_bins, size_limits=size_limits, structure=structure, debug=debug, verbose=verbose) # Fill holes in significant detection mask if fill_holes: basic_fixes._binary_fill(radar, detect_field, structure=structure) # Dilate significant detection mask if dilate: basic_fixes._binary_dilation( radar, detect_field, structure=structure, iterations=iterations, debug=debug, verbose=verbose) # Update gate filter gatefilter.include_equal(detect_field, 1, op='new') return gatefilter
def _significant_features( radar, field, gatefilter=None, min_size=None, size_bins=75, size_limits=(0, 300), structure=None, remove_size_field=True, fill_value=None, size_field=None, debug=False, verbose=False): """ """ # Parse fill value if fill_value is None: fill_value = get_fillvalue() # Parse field names if size_field is None: size_field = '{}_feature_size'.format(field) # Parse gate filter if gatefilter is None: gatefilter = GateFilter(radar, exclude_based=False) # Parse binary structuring element if structure is None: structure = ndimage.generate_binary_structure(2, 1) # Initialize echo feature size array size_data = np.zeros_like( radar.fields[field]['data'], subok=False, dtype=np.int32) # Loop over all sweeps feature_sizes = [] for sweep in radar.iter_slice(): # Parse radar sweep data and define only valid gates is_valid_gate = ~radar.fields[field]['data'][sweep].mask # Label the connected features in radar sweep data and create index # array which defines each unique label (feature) labels, nlabels = ndimage.label( is_valid_gate, structure=structure, output=None) index = np.arange(1, nlabels + 1, 1) if debug: print 'Number of unique features for {}: {}'.format(sweep, nlabels) # Compute the size (in radar gates) of each echo feature # Check for case where no echo features are found, e.g., no data in # sweep if nlabels > 0: sweep_sizes = ndimage.labeled_comprehension( is_valid_gate, labels, index, np.count_nonzero, np.int32, 0) feature_sizes.append(sweep_sizes) # Set each label (feature) to its total size (in radar gates) for label, size in zip(index, sweep_sizes): size_data[sweep][labels == label] = size # Stack sweep echo feature sizes feature_sizes = np.hstack(feature_sizes) # Compute histogram of echo feature sizes, bin centers and bin # width counts, bin_edges = np.histogram( feature_sizes, bins=size_bins, range=size_limits, normed=False, weights=None, density=False) bin_centers = bin_edges[:-1] + np.diff(bin_edges) / 2.0 bin_width = np.diff(bin_edges).mean() if debug: print 'Bin width: {} gate(s)'.format(bin_width) # Compute the peak of the echo feature size distribution # We expect the peak of the echo feature size distribution to be close to 1 # radar gate peak_size = bin_centers[counts.argmax()] - bin_width / 2.0 if debug: print 'Feature size at peak: {} gate(s)'.format(peak_size) # Determine the first instance when the count (sample size) for an echo # feature size bin reaches 0 after the distribution peak # This will define the minimum echo feature size is_zero_size = np.logical_and( bin_centers > peak_size, np.isclose(counts, 0, atol=1.0e-5)) min_size = bin_centers[is_zero_size].min() - bin_width / 2.0 if debug: _range = [0.0, min_size] print 'Insignificant feature size range: {} gates'.format(_range) # Mask invalid feature sizes, e.g., zero-size features size_data = np.ma.masked_equal(size_data, 0, copy=False) size_data.set_fill_value(fill_value) # Add echo feature size field to radar size_dict = { 'data': size_data.astype(np.int32), 'standard_name': size_field, 'long_name': '', '_FillValue': size_data.fill_value, 'units': 'unitless', } radar.add_field(size_field, size_dict, replace_existing=True) # Update gate filter gatefilter.include_above(size_field, min_size, op='and', inclusive=False) # Remove eacho feature size field if remove_size_field: radar.fields.pop(size_field, None) return gatefilter
def significant_features( radar, fields, gatefilter=None, size_bins=100, size_limits=(0, 400), structure=None, save_size_field=False, fill_value=None, debug=False, verbose=False): """ Determine significant radar echo features on a sweep by sweep basis by computing the size each echo feature. Here an echo feature is defined as multiple connected radar gates with valid data, where the connection structure is defined by the user. Parameters ---------- radar : Radar Py-ART Radar containing fields : str or list or tuple Radar fields to be used to identify significant echo featues. gatefilter : GateFilter Py-ART GateFilter instance. size_bins : int, optional Number of size bins used to bin feature size distribution. size_limits : list or tuple, optional Lower and upper limits of the feature size distribution. This together with size_bins defines the bin width of the feature size distribution. structure : array_like, optional Binary structuring element used to define connected radar gates. The default defines a structuring element in which diagonal radar gates are not considered connected. save_size_field : bool, optional True to save size fields in the radar object, False to discard. debug : bool, optional True to print debugging information, False to suppress. Returns ------- gf : GateFilter Py-ART GateFilter. """ # Parse fill value if fill_value is None: fill_value = get_fillvalue() # Parse radar fields if isinstance(fields, str): fields = [fields] # Parse gate filter if gatefilter is None: gf = GateFilter(radar, exclude_based=False) # Parse binary structuring element if structure is None: structure = ndimage.generate_binary_structure(2, 1) for field in fields: if verbose: print 'Processing echo features: {}'.format(field) # Initialize echo feature size array size_data = np.zeros_like( radar.fields[field]['data'], subok=False, dtype=np.int32) feature_sizes = [] for sweep, _slice in enumerate(radar.iter_slice()): # Parse radar sweep data and define only valid gates data = radar.get_field(sweep, field, copy=False) is_valid_gate = ~np.ma.getmaskarray(data) # Label the connected features in the radar sweep data and create # index array which defines each unique label (feature) labels, nlabels = ndimage.label( is_valid_gate, structure=structure, output=None) index = np.arange(1, nlabels + 1, 1) if debug: print 'Unique features in sweep {}: {}'.format(sweep, nlabels) # Compute the size (in radar gates) of each echo feature # Check for case where no echo features are found, e.g., no data in # sweep if nlabels > 0: sweep_sizes = ndimage.labeled_comprehension( is_valid_gate, labels, index, np.count_nonzero, np.int32, 0) feature_sizes.append(sweep_sizes) # Set each label (feature) to its total size (in radar gates) for label, size in zip(index, sweep_sizes): size_data[_slice][labels == label] = size # Stack sweep echo feature sizes feature_sizes = np.hstack(feature_sizes) # Bin and compute feature size occurrences counts, bin_edges = np.histogram( feature_sizes, bins=size_bins, range=size_limits, normed=False, weights=None, density=False) bin_centers = bin_edges[:-1] + np.diff(bin_edges) / 2.0 bin_width = np.diff(bin_edges).mean() if debug: print 'Bin width: {} gate(s)'.format(bin_width) # Compute the peak of the echo feature size distribution. We expect the # peak of the echo feature size distribution to be close to 1 radar # gate peak_size = bin_centers[counts.argmax()] - bin_width / 2.0 if debug: print 'Feature size at peak: {} gate(s)'.format(peak_size) # Determine the first instance when the count (sample size) for an echo # feature size bin reaches 0 after the distribution peak. This will # define the minimum echo feature size is_zero_size = np.logical_and( bin_centers > peak_size, np.isclose(counts, 0, atol=1.0e-1)) min_size = bin_centers[is_zero_size].min() - bin_width / 2.0 if debug: _range = [0.0, min_size] print 'Insignificant feature size range: {} gates'.format(_range) # Mask invalid feature sizes, e.g., zero-size features size_data = np.ma.masked_equal(size_data, 0, copy=False) size_data.set_fill_value(fill_value) # Parse echo feature size field name size_field = '{}_feature_size'.format(field) # Add echo feature size field to radar size_dict = { 'data': size_data.astype(np.int32), 'long_name': 'Echo feature size in number of radar gates', '_FillValue': size_data.fill_value, 'units': 'unitless', 'comment': None, } radar.add_field(size_field, size_dict, replace_existing=True) # Update gate filter gf.include_above(size_field, min_size, op='and', inclusive=False) # Remove eacho feature size field if specified if not save_size_field: radar.fields.pop(size_field, None) return gf