예제 #1
0
def _find_rain_from_radar_echo(z: np.ndarray,
                               time: np.ndarray,
                               time_buffer: int = 5) -> np.ndarray:
    """Find profiles affected by rain.

    Rain is present in such profiles where the radar echo in
    the third range gate is > 0 dB. To make sure we do not include any
    rainy profiles, we also flag a few profiles before and after
    detections as raining.

    Args:
        z: Radar echo.
        time: Time vector.
        time_buffer: Time in minutes.

    Returns:
        1D Boolean array denoting profiles with rain.

    """
    is_rain = ma.array(z[:, 3] > 0, dtype=bool).filled(False)
    is_rain = skimage.morphology.remove_small_objects(
        is_rain, 2, connectivity=1)  # Filter hot pixels
    n_profiles = len(time)
    n_steps = utils.n_elements(time, time_buffer, "time")
    for ind in np.where(is_rain)[0]:
        ind1 = max(0, ind - n_steps)
        ind2 = min(ind + n_steps, n_profiles)
        is_rain[ind1:ind2 + 1] = True
    return is_rain
예제 #2
0
def correct_liquid_top(obs: ClassData,
                       liquid: dict,
                       is_freezing: np.ndarray,
                       limit: float = 200) -> np.ndarray:
    """Corrects lidar detected liquid cloud top using radar data.

    Args:
        obs: The :class:`ClassData` instance.
        liquid: Dictionary about liquid clouds including `tops` and `presence`.
        is_freezing: 2-D boolean array of sub-zero temperature, derived from the model
            temperature and melting layer based on radar data.
        limit: The maximum correction distance (m) above liquid cloud top.

    Returns:
        Corrected liquid cloud array.

    References:
        Hogan R. and O'Connor E., 2004, https://bit.ly/2Yjz9DZ.

    """
    is_liquid_corrected = np.copy(liquid["presence"])
    top_above = utils.n_elements(obs.height, limit)
    for prof, top in zip(*np.where(liquid["tops"])):
        ind = _find_ind_above_top(is_freezing[prof, top:], top_above)
        rad = obs.z[prof, top:top + ind + 1]
        if not (rad.mask.all() or ~rad.mask.any()):
            first_masked = ma.where(rad.mask)[0][0]
            is_liquid_corrected[prof, top:top + first_masked] = True
    return is_liquid_corrected
예제 #3
0
def _find_rain(z: np.ndarray,
               time: np.ndarray,
               time_buffer: Optional[int] = 5) -> np.ndarray:
    """Find profiles affected by rain.

    Rain is present in such profiles where the radar echo in
    the third range gate is > 0 dB. To make sure we do not include any
    rainy profiles, we also flag a few profiles before and after
    detections as raining.

    Args:
        z: Radar echo.
        time: Time vector.
        time_buffer: Time in minutes.

    Returns:
        1D Boolean array denoting profiles with rain.

    """
    is_rain = ma.array(z[:, 3] > 0, dtype=bool).filled(False)
    n_profiles = len(time)
    n_steps = utils.n_elements(time, time_buffer, 'time')
    for ind in np.where(is_rain)[0]:
        ind1 = max(0, ind - n_steps)
        ind2 = min(ind + n_steps, n_profiles)
        is_rain[ind1:ind2 + 1] = True
    return is_rain
예제 #4
0
def correct_liquid_top(obs, liquid, is_freezing, limit=200):
    """Corrects lidar detected liquid cloud top using radar data.

    Args:
        obs (ClassData): The :class:`ClassData` instance.
        liquid (dict): Dictionary about liquid clouds including `tops` and
            `presence`.
        is_freezing (ndarray): 2-D boolean array of sub-zero temperature,
            derived from the model temperature and melting layer based
            on radar data.
        limit (float): The maximum correction distance (m) above liquid cloud top.

    Returns:
        ndarray: Corrected liquid cloud array.

    """
    is_liquid_corrected = np.copy(liquid['presence'])
    top_above = utils.n_elements(obs.height, limit)
    for prof, top in zip(*np.where(liquid['tops'])):
        ind = _find_ind_above_top(is_freezing[prof, top:], top_above)
        rad = obs.z[prof, top:top + ind + 1]
        if not (rad.mask.all() or ~rad.mask.any()):
            first_masked = ma.where(rad.mask)[0][0]
            is_liquid_corrected[prof, top:top + first_masked] = True
    return is_liquid_corrected
예제 #5
0
def find_freezing_region(obs: ClassData,
                         melting_layer: np.ndarray) -> np.ndarray:
    """Finds freezing region using the model temperature and melting layer.

    Every profile that contains melting layer, subzero region starts from
    the mean melting layer height. If there are (long) time windows where
    no melting layer is present, model temperature is used in the
    middle of the time window. Finally, the subzero altitudes are linearly
    interpolated for all profiles.

    Args:
        obs: The :class:`ClassData` instance.
        melting_layer: 2-D boolean array denoting melting layer.

    Returns:
        2-D boolean array denoting the sub-zero region.

    Notes:
        It is not clear how model temperature and melting layer should be
        ideally combined to determine the sub-zero region. This current
        method differs slightly from the original Matlab code and should
        be validated more carefully later.

    """
    is_freezing = np.zeros(obs.tw.shape, dtype=bool)
    t0_alt = _find_t0_alt(obs.tw, obs.height)
    mean_melting_alt = _find_mean_melting_alt(obs, melting_layer)

    if _is_all_freezing(mean_melting_alt, t0_alt, obs.height):
        logging.info(
            "All temperatures below freezing and no detected melting layer")
        return np.ones(obs.tw.shape, dtype=bool)

    freezing_alt = ma.copy(mean_melting_alt)

    for ind in (0, -1):
        freezing_alt[ind] = mean_melting_alt[ind] or t0_alt[ind]
    win = utils.n_elements(obs.time, 240, "time")  # 4h window
    mid_win = int(win / 2)
    for n in range(len(obs.time) - win):
        if mean_melting_alt[n:n + win].mask.all():
            freezing_alt[n + mid_win] = t0_alt[n + mid_win]
    ind = ~freezing_alt.mask
    f = interp1d(obs.time[ind], freezing_alt[ind])
    freezing_alt_interpolated = f(obs.time) - 1
    for ii, alt in enumerate(freezing_alt_interpolated):
        is_freezing[ii, obs.height > alt] = True
    return is_freezing
예제 #6
0
def find_freezing_region(obs, melting_layer):
    """Finds freezing region using the model temperature and melting layer.

    Every profile that contains melting layer, subzero region starts from
    the mean melting layer height. If there are (long) time windows where
    no melting layer is present, model temperature is used in the
    middle of the time window. Finally, the subzero altitudes are linearly
    interpolated for all profiles.

    Args:
        obs (ClassData): The :class:`ClassData` instance.
        melting_layer (ndarray): 2-D boolean array denoting melting layer.

    Returns:
        ndarray: 2-D boolean array denoting the sub-zero region.

    Notes:
        It is not clear how model temperature and melting layer should be
        ideally combined to determine the sub-zero region.

    """
    is_freezing = np.zeros(obs.tw.shape, dtype=bool)
    t0_alt = _find_t0_alt(obs.tw, obs.height)
    mean_melting_alt = _find_mean_melting_alt(obs, melting_layer)
    freezing_alt = ma.copy(mean_melting_alt)
    for ind in (0, -1):
        freezing_alt[ind] = mean_melting_alt[ind] or t0_alt[ind]
    win = utils.n_elements(obs.time, 240, 'time')  # 4h window
    mid_win = int(win / 2)
    for n in range(len(obs.time) - win):
        if mean_melting_alt[n:n + win].mask.all():
            freezing_alt[n + mid_win] = t0_alt[n + mid_win]
    ind = ~freezing_alt.mask
    f = interp1d(obs.time[ind], freezing_alt[ind])
    for ii, alt in enumerate(f(obs.time)):
        is_freezing[ii, obs.height > alt] = True
    return is_freezing
예제 #7
0
def test_n_elements_2(x, a, result):
    assert utils.n_elements(x, a, 'time') == result
예제 #8
0
def test_n_elements(x, a, result):
    assert utils.n_elements(x, a) == result
예제 #9
0
def find_liquid(
    obs: ClassData,
    peak_amp: float = 1e-6,
    max_width: float = 300,
    min_points: int = 3,
    min_top_der: float = 1e-7,
    min_lwp: float = 0,
    min_alt: float = 100,
) -> dict:
    """Estimate liquid layers from SNR-screened attenuated backscatter.

    Args:
        obs: The :class:`ClassData` instance.
        peak_amp: Minimum value of peak. Default is 1e-6.
        max_width: Maximum width of peak. Default is 300 (m).
        min_points: Minimum number of valid points in peak. Default is 3.
        min_top_der: Minimum derivative above peak, defined as
            (beta_peak-beta_top) / (alt_top-alt_peak). Default is 1e-7.
        min_lwp: Minimum value from linearly interpolated lwp measured by the mwr. Default is 0.
        min_alt: Minimum altitude of the peak from the ground. Default is 100 (m).

    Returns:
        Dict containing `presence`, `bases` and `tops`.

    References:
        The method is based on Tuononen, M. et.al, 2019,
        https://acp.copernicus.org/articles/19/1985/2019/.

    """
    def _is_proper_peak():
        conditions = (
            npoints >= min_points,
            peak_width < max_width,
            top_der > min_top_der,
            is_positive_lwp,
            peak_alt > min_alt,
        )
        return all(conditions)

    def _save_peak_position():
        is_liquid[n, base:top + 1] = True
        liquid_top[n, top] = True
        liquid_base[n, base] = True

    lwp_int = interpolate_lwp(obs)
    beta = ma.copy(obs.beta)
    height = obs.height

    is_liquid, liquid_top, liquid_base = utils.init(3,
                                                    beta.shape,
                                                    dtype=bool,
                                                    masked=False)
    base_below_peak = utils.n_elements(height, 200)
    top_above_peak = utils.n_elements(height, 150)
    difference = np.diff(beta, axis=1)
    assert isinstance(difference, ma.MaskedArray)
    beta_diff = difference.filled(0)
    beta = beta.filled(0)
    peak_indices = _find_strong_peaks(beta, peak_amp)

    for n, peak in zip(*peak_indices):
        lprof = beta[n, :]
        dprof = beta_diff[n, :]
        try:
            base = ind_base(dprof, peak, base_below_peak, 4)
            top = ind_top(dprof, peak, height.shape[0], top_above_peak, 4)
        except IndexError:
            continue
        npoints = np.count_nonzero(lprof[base:top + 1])
        peak_width = height[top] - height[base]
        peak_alt = height[peak] - height[0]
        top_der = (lprof[peak] - lprof[top]) / (height[top] - height[peak])
        is_positive_lwp = lwp_int[n] > min_lwp
        if _is_proper_peak():
            _save_peak_position()

    return {"presence": is_liquid, "bases": liquid_base, "tops": liquid_top}
예제 #10
0
def find_liquid(obs: ClassData,
                peak_amp: Optional[float] = 1e-6,
                max_width: Optional[float] = 300,
                min_points: Optional[int] = 3,
                min_top_der: Optional[float] = 1e-7,
                min_lwp: Optional[float] = 0) -> dict:
    """ Estimate liquid layers from SNR-screened attenuated backscatter.

    Args:
        obs: The :class:`ClassData` instance.
        peak_amp: Minimum value of peak. Default is 2e-5.
        max_width: Maximum width of peak. Default is 300 (m).
        min_points: Minimum number of valid points in peak. Default is 3.
        min_top_der: Minimum derivative above peak, defined as
            (beta_peak-beta_top) / (alt_top-alt_peak), which is always positive. Default is 2e-7.
        min_lwp: Minimum value from linearly interpolated lwp measured by the mwr. Default is 0.

    Returns:
        Dict containing `presence`, `bases` and `tops`.

    References:
        The method is based on Tuononen, M. et.al, 2019,
        https://acp.copernicus.org/articles/19/1985/2019/.

    """
    def _is_proper_peak():
        conditions = (npoints >= min_points,
                      peak_width < max_width,
                      top_der > min_top_der,
                      is_positive_lwp)
        return all(conditions)

    def _save_peak_position():
        is_liquid[n, base:top + 1] = True
        liquid_top[n, top] = True
        liquid_base[n, base] = True

    lwp_int = interpolate_lwp(obs)
    beta = ma.copy(obs.beta)

    # TODO: append zero-row into data instead of setting first values to zero.
    # This fix is because the peak can be the very first value
    # (thus there is no proper base in data)
    beta[:, 0] = 0
    height = obs.height

    is_liquid, liquid_top, liquid_base = utils.init(3, beta.shape, dtype=bool,
                                                    masked=False)
    base_below_peak = utils.n_elements(height, 200)
    top_above_peak = utils.n_elements(height, 150)
    beta_diff = np.diff(beta, axis=1).filled(0)
    beta = beta.filled(0)
    peak_indices = _find_strong_peaks(beta, peak_amp)

    for n, peak in zip(*peak_indices):
        lprof = beta[n, :]
        dprof = beta_diff[n, :]
        try:
            base = ind_base(dprof, peak, base_below_peak, 4)
            top = ind_top(dprof, peak, height.shape[0], top_above_peak, 4)
        except IndexError:
            continue
        npoints = np.count_nonzero(lprof[base:top+1])
        peak_width = height[top] - height[base]
        top_der = (lprof[peak] - lprof[top]) / (height[top] - height[peak])
        is_positive_lwp = lwp_int[n] > min_lwp
        if _is_proper_peak():
            _save_peak_position()

    return {'presence': is_liquid,
            'bases': liquid_base,
            'tops': liquid_top}