Source code for act.retrievals.cbh

"""
Functions for calculated cloud base height that are instrument agnostic.

"""

import numpy as np
import xarray as xr
from scipy import ndimage


[docs]def generic_sobel_cbh( ds, variable=None, height_dim=None, var_thresh=None, fill_na=None, return_thresh=False, filter_type='uniform', edge_thresh=5.0, ): """ Function for calculating cloud base height from lidar/radar data using a basic sobel filter and thresholding. Note, this was not initially based on any published work, but a lit review indicates that there have been similar methods employed to detect boundary layer heights. NOTE: The returned variable now appends the field name of the data used to generate the CBH as part of the variable name. cbh_sobel_[varname] Parameters ---------- ds : ACT xarray.Dataset ACT xarray dataset where data are stored. variable : string Variable on which to process. height_dim : string Height variable to use for CBH values. var_thresh : float Thresholding for variable if needed. fill_na : float Value to fill nans with in DataArray if any. filter_type : string Currently the only option is for uniform filtering. uniform: Apply uniform filtering after the sobel filter? Applies a standard area of 3x3 filtering None: Excludes the filtering edge_thresh : float Threshold value for finding the edge after the sobel filtering. If the signal is not strong, this may need to be lowered Returns ------- new_ds : ACT xarray.Dataset ACT xarray dataset with cbh values included as variable. Examples -------- In testing on the ARM KAZR and MPL data, the following methods tended to work best for thresholding/corrections/etc. .. code-block:: python kazr = act.retrievals.cbh.generic_sobel_cbh( kazr, variable="reflectivity_copol", height_dim="range", var_thresh=-10.0 ) mpl = act.corrections.mpl.correct_mpl(mpl) mpl.range_bins.values = mpl.height.values[0, :] * 1000.0 mpl.range_bins.attrs["units"] = "m" mpl["signal_return_co_pol"].values[:, 0:10] = 0.0 mpl = act.retrievals.cbh.generic_sobel_cbh( mpl, variable="signal_return_co_pol", height_dim="range_bins", var_thresh=10.0, fill_na=0.0, ) ceil = act.retrievals.cbh.generic_sobel_cbh( ceil, variable="backscatter", height_dim="range", var_thresh=1000.0, fill_na=0, ) """ if variable is None: return if fill_na is None: fill_na = var_thresh # Pull data into Standalone DataArray da = ds[variable] # Apply thresholds if set if var_thresh is not None: da = da.where(da.values > var_thresh) # Fill with fill_na values da = da.fillna(fill_na) # If return_thresh is True, replace variable data with # thresholded data if return_thresh is True: ds[variable].values = da.values # Apply Sobel filter to data and smooth the results data = da.values.tolist() edge = ndimage.sobel(data) if filter_type == 'uniform': edge = ndimage.uniform_filter(edge, size=3, mode='nearest') # Create Data Array edge_da = xr.DataArray(edge, dims=ds[variable].dims) # Filter some of the resulting edge data to get defined edges edge_da = edge_da.where(edge_da > edge_thresh) edge_da = edge_da.fillna(fill_na) # Do a diff along the height dimension to define edge diff = edge_da.diff(dim=1).values # Get height variable to use for cbh height = ds[height_dim].values # Run through times and find the height cbh = [] for i in range(np.shape(diff)[0]): try: index = np.where(diff[i, :] > edge_thresh)[0] except ValueError(): index = [] if len(np.shape(height)) > 1: ht = height[i, :] else: ht = height if len(index) > 0: cbh.append(ht[index[0] + 1]) else: cbh.append(np.nan) # Create DataArray to add to the dataset var_name = 'cbh_sobel_' + variable da = xr.DataArray(cbh, dims=['time'], coords=[ds['time'].values]) ds[var_name] = da ds[var_name].attrs['long_name'] = ' '.join( ['CBH calculated from', variable, 'using sobel filter'] ) ds[var_name].attrs['units'] = ds[height_dim].attrs['units'] return ds