Exemple #1
0
 def test_smooth(self):
     a = np.array([1., 4, 7, 7, 4, 1])
     b = utils.smooth1d(a, 3, kernel='mean')
     assert_allclose(b, [3, 4, 6, 6, 4, 3])
     kernel = [0.60653066, 1., 0.60653066]
     b = utils.smooth1d(a, 3, kernel=kernel)
     c = utils.smooth1d(a, 3)
     assert_allclose(b, c)
Exemple #2
0
 def test_smooth(self):
     a = np.array([1., 4, 7, 7, 4, 1])
     b = utils.smooth1d(a, 3, kernel='mean')
     assert_allclose(b, [3, 4, 6, 6, 4, 3])
     kernel = [0.60653066,  1., 0.60653066]
     b = utils.smooth1d(a, 3, kernel=kernel)
     c = utils.smooth1d(a, 3)
     assert_allclose(b, c)
Exemple #3
0
def filter_inversion_output(gdir):
    """Filters the last few grid points after the physically-based inversion.

    For various reasons (but mostly: the equilibrium assumption), the last few
    grid points on a glacier flowline are often noisy and create unphysical
    depressions. Here we try to correct for that. It is not volume conserving,
    but area conserving.

    Parameters
    ----------
    gdir : :py:class:`oggm.GlacierDirectory`
        the glacier directory to process
    """

    if gdir.is_tidewater:
        # No need for filter in tidewater case
        return

    if not gdir.has_file('downstream_line'):
        raise InvalidWorkflowError('filter_inversion_output now needs a '
                                   'previous call to the '
                                   'compute_dowstream_line and '
                                   'compute_downstream_bedshape tasks')

    dic_ds = gdir.read_pickle('downstream_line')
    bs = np.average(dic_ds['bedshapes'][:3])

    n = -5

    cls = gdir.read_pickle('inversion_output')
    cl = cls[-1]

    # First guess thickness based on width
    w = cl['width'][n:]
    s = w**3 * bs / 6
    h = 3 / 2 * s / w

    # Smoothing things out a bit
    hts = np.append(np.append(cl['thick'][n - 3:n], h), 0)
    h = utils.smooth1d(hts, 3)[n - 1:-1]

    # Recompute bedshape based on that
    bs = utils.clip_min(4 * h / w**2, cfg.PARAMS['mixed_min_shape'])

    # OK, done
    s = w**3 * bs / 6

    cl['thick'][n:] = 3 / 2 * s / w
    cl['volume'][n:] = s * cl['dx']
    cl['is_trapezoid'][n:] = False
    cl['is_rectangular'][n:] = False

    gdir.write_pickle(cls, 'inversion_output')

    # output the volume here - this simplifies code for some downstream funcs
    return np.sum([np.sum(cl['volume']) for cl in cls])
Exemple #4
0
def catchment_width_correction(gdir, div_id=None):
    """Corrects for NaNs and inconsistencies in the geometrical widths.

    Interpolates missing values, ensures consistency of the
    surface-area distribution AND with the geometrical area of the glacier
    polygon, avoiding errors due to gridded representation.

    Updates the 'inversion_flowlines' save file.

    Parameters
    ----------
    gdir : oggm.GlacierDirectory
    """

    # The code below makes of this task a "special" divide task.
    # We keep it as is and remove the divide task decorator
    if div_id is None:
        # This is the original call
        # This time instead of just looping over the divides we add a test
        # to check for the conservation of the shapefile's area.
        area = 0.
        divides = []
        for i in gdir.divide_ids:
            log.info('%s: width correction, divide %d', gdir.rgi_id, i)
            fls = catchment_width_correction(gdir, div_id=i, reset=True)
            for fl in fls:
                area += np.sum(fl.widths) * fl.dx
            divides.append(fls)

        # Final correction - because of the raster, the gridded area of the
        # glacier is not that of the actual geometry. correct for that
        fac = gdir.rgi_area_km2 / (area * gdir.grid.dx**2 * 10**-6)
        log.debug('%s: corrected widths with a factor %.2f', gdir.rgi_id, fac)
        for i in gdir.divide_ids:
            fls = divides[i-1]
            for fl in fls:
                fl.widths *= fac
            # Overwrite centerlines
            gdir.write_pickle(fls, 'inversion_flowlines', div_id=i)
        return None

    # variables
    flowlines = gdir.read_pickle('inversion_flowlines', div_id=div_id)
    catchment_indices = gdir.read_pickle('catchment_indices', div_id=div_id)

    # Topography for altitude-area distribution
    # I take the non-smoothed topography and remove the borders
    fpath = gdir.get_filepath('gridded_data', div_id=div_id)
    with netCDF4.Dataset(fpath) as nc:
        topo = nc.variables['topo'][:]
        ext = nc.variables['glacier_ext'][:]
    topo[np.where(ext==1)] = np.NaN

    # Param
    nmin = int(cfg.PARAMS['min_n_per_bin'])
    smooth_ws = int(cfg.PARAMS['smooth_widths_window_size'])

    # Per flowline (important so that later, the indices can be moved)
    catchment_heights = []
    for ci in catchment_indices:
        _t = topo[tuple(ci.T)][:]
        catchment_heights.append(list(_t[np.isfinite(_t)]))

    # Loop over lines in a reverse order
    for fl, catch_h in zip(flowlines, catchment_heights):

        # Interpolate widths
        widths = utils.interp_nans(fl.widths)
        widths = np.clip(widths, 0.1, np.max(widths))

        # Get topo per catchment and per flowline point
        fhgt = fl.surface_h

        # Sometimes, the centerline does not reach as high as each pix on the
        # glacier. (e.g. RGI40-11.00006)
        catch_h = np.clip(catch_h, 0, np.max(fhgt))

        # Max and mins for the histogram
        maxh = np.max(fhgt)
        if fl.flows_to is None:
            minh = np.min(fhgt)
            catch_h = np.clip(catch_h, minh, np.max(catch_h))
        else:
            minh = np.min(fhgt)  # Min just for flowline (this has reasons)

        # Now decide on a binsize which ensures at least N element per bin
        bsize = cfg.PARAMS['base_binsize']
        while True:
            maxb = utils.nicenumber(maxh, 1)
            minb = utils.nicenumber(minh, 1, lower=True)
            bins = np.arange(minb, maxb+bsize+0.01, bsize)
            minb = np.min(bins)

            # Ignore the topo pixels below the last bin
            tmp_ght = catch_h[np.where(catch_h >= minb)]

            topo_digi = np.digitize(tmp_ght, bins) - 1  # I prefer the left
            fl_digi = np.digitize(fhgt, bins) - 1  # I prefer the left
            if nmin == 1:
                # No need for complicated count
                _c = set(topo_digi)
                _fl = set(fl_digi)
            else:
                # Keep indexes with at least n counts
                _c = Counter(topo_digi.tolist())
                _c = set([k for (k, v) in _c.items() if v >= nmin])
                _fl = Counter(fl_digi.tolist())
                _fl = set([k for (k, v) in _fl.items() if v >= nmin])

            ref_set = set(range(len(bins)-1))
            if (_c == ref_set) and (_fl == ref_set):
                # For each bin, the width(s) have to represent the "real" area
                new_widths = widths.copy()
                for bi in range(len(bins) - 1):
                    bintopoarea = len(np.where(topo_digi == bi)[0])
                    wherewiths = np.where(fl_digi == bi)
                    binflarea = np.sum(new_widths[wherewiths]) * fl.dx
                    new_widths[wherewiths] = (bintopoarea / binflarea) * \
                                             new_widths[wherewiths]
                break
            bsize += 5

            # Add a security for infinite loops
            if bsize > 500:
                nmin -= 1
                bsize = cfg.PARAMS['base_binsize']
                log.warning('%s: reduced min n per bin to %d', gdir.rgi_id,
                            nmin)
                if nmin == 0:
                    raise RuntimeError('NO binsize could be chosen for: '
                                       '{}'.format(gdir.rgi_id))
        if bsize > 150:
            log.warning('%s: chosen binsize %d', gdir.rgi_id, bsize)
        else:
            log.debug('%s: chosen binsize %d', gdir.rgi_id, bsize)

        # Now keep the good topo pixels and send the unattributed ones to the
        # next flowline
        tosend = list(catch_h[np.where(catch_h < minb)])
        if (len(tosend) > 0) and (fl.flows_to is not None):
            ide = flowlines.index(fl.flows_to)
            catchment_heights[ide] = np.append(catchment_heights[ide], tosend)
        if (len(tosend) > 0) and (fl.flows_to is None):
            raise RuntimeError('This should not happen')

        # Now we have a width which is the "best" representation of our
        # tributary according to the altitude area distribution.
        # This sometimes leads to abrupt changes in the widths from one
        # grid point to another. I think it's not too harmful to smooth them
        # here, at the cost of a less perfect altitude area distribution
        if smooth_ws != 0:
            if smooth_ws == 1:
                new_widths = utils.smooth1d(new_widths)
            else:
                new_widths = utils.smooth1d(new_widths, window_size=smooth_ws)

        # Write it
        fl.widths = new_widths

    return flowlines
Exemple #5
0
def catchment_width_correction(gdir, div_id=None):
    """Corrects for NaNs and inconsistencies in the geometrical widths.

    Interpolates missing values, ensures consistency of the
    surface-area distribution AND with the geometrical area of the glacier
    polygon, avoiding errors due to gridded representation.

    Updates the 'inversion_flowlines' save file.

    Parameters
    ----------
    gdir : oggm.GlacierDirectory
    """

    # The code below makes of this task a "special" divide task.
    # We keep it as is and remove the divide task decorator
    if div_id is None:
        # This is the original call
        # This time instead of just looping over the divides we add a test
        # to check for the conservation of the shapefile's area.
        area = 0.
        divides = []
        for i in gdir.divide_ids:
            log.info('%s: width correction, divide %d', gdir.rgi_id, i)
            fls = catchment_width_correction(gdir, div_id=i, reset=True)
            for fl in fls:
                area += np.sum(fl.widths) * fl.dx
            divides.append(fls)

        # Final correction - because of the raster, the gridded area of the
        # glacier is not that of the actual geometry. correct for that
        fac = gdir.rgi_area_km2 / (area * gdir.grid.dx**2 * 10**-6)
        log.debug('%s: corrected widths with a factor %.2f', gdir.rgi_id, fac)
        for i in gdir.divide_ids:
            fls = divides[i - 1]
            for fl in fls:
                fl.widths *= fac
            # Overwrite centerlines
            gdir.write_pickle(fls, 'inversion_flowlines', div_id=i)
        return None

    # variables
    flowlines = gdir.read_pickle('inversion_flowlines', div_id=div_id)
    catchment_indices = gdir.read_pickle('catchment_indices', div_id=div_id)

    # Topography for altitude-area distribution
    # I take the non-smoothed topography and remove the borders
    fpath = gdir.get_filepath('gridded_data', div_id=div_id)
    with netCDF4.Dataset(fpath) as nc:
        topo = nc.variables['topo'][:]
        ext = nc.variables['glacier_ext'][:]
    topo[np.where(ext == 1)] = np.NaN

    # Param
    nmin = int(cfg.PARAMS['min_n_per_bin'])
    smooth_ws = int(cfg.PARAMS['smooth_widths_window_size'])

    # Per flowline (important so that later, the indices can be moved)
    catchment_heights = []
    for ci in catchment_indices:
        _t = topo[tuple(ci.T)][:]
        catchment_heights.append(list(_t[np.isfinite(_t)]))

    # Loop over lines in a reverse order
    for fl, catch_h in zip(flowlines, catchment_heights):

        # Interpolate widths
        widths = utils.interp_nans(fl.widths)
        widths = np.clip(widths, 0.1, np.max(widths))

        # Get topo per catchment and per flowline point
        fhgt = fl.surface_h

        # Sometimes, the centerline does not reach as high as each pix on the
        # glacier. (e.g. RGI40-11.00006)
        catch_h = np.clip(catch_h, 0, np.max(fhgt))

        # Max and mins for the histogram
        maxh = np.max(fhgt)
        if fl.flows_to is None:
            minh = np.min(fhgt)
            catch_h = np.clip(catch_h, minh, np.max(catch_h))
        else:
            minh = np.min(fhgt)  # Min just for flowline (this has reasons)

        # Now decide on a binsize which ensures at least N element per bin
        bsize = cfg.PARAMS['base_binsize']
        while True:
            maxb = utils.nicenumber(maxh, 1)
            minb = utils.nicenumber(minh, 1, lower=True)
            bins = np.arange(minb, maxb + bsize + 0.01, bsize)
            minb = np.min(bins)

            # Ignore the topo pixels below the last bin
            tmp_ght = catch_h[np.where(catch_h >= minb)]

            topo_digi = np.digitize(tmp_ght, bins) - 1  # I prefer the left
            fl_digi = np.digitize(fhgt, bins) - 1  # I prefer the left
            if nmin == 1:
                # No need for complicated count
                _c = set(topo_digi)
                _fl = set(fl_digi)
            else:
                # Keep indexes with at least n counts
                _c = Counter(topo_digi.tolist())
                _c = set([k for (k, v) in _c.items() if v >= nmin])
                _fl = Counter(fl_digi.tolist())
                _fl = set([k for (k, v) in _fl.items() if v >= nmin])

            ref_set = set(range(len(bins) - 1))
            if (_c == ref_set) and (_fl == ref_set):
                # For each bin, the width(s) have to represent the "real" area
                new_widths = widths.copy()
                for bi in range(len(bins) - 1):
                    bintopoarea = len(np.where(topo_digi == bi)[0])
                    wherewiths = np.where(fl_digi == bi)
                    binflarea = np.sum(new_widths[wherewiths]) * fl.dx
                    new_widths[wherewiths] = (bintopoarea / binflarea) * \
                                             new_widths[wherewiths]
                break
            bsize += 5

            # Add a security for infinite loops
            if bsize > 500:
                nmin -= 1
                bsize = cfg.PARAMS['base_binsize']
                log.warning('%s: reduced min n per bin to %d', gdir.rgi_id,
                            nmin)
                if nmin == 0:
                    raise RuntimeError('NO binsize could be chosen for: '
                                       '{}'.format(gdir.rgi_id))
        if bsize > 150:
            log.warning('%s: chosen binsize %d', gdir.rgi_id, bsize)
        else:
            log.debug('%s: chosen binsize %d', gdir.rgi_id, bsize)

        # Now keep the good topo pixels and send the unattributed ones to the
        # next flowline
        tosend = list(catch_h[np.where(catch_h < minb)])
        if (len(tosend) > 0) and (fl.flows_to is not None):
            ide = flowlines.index(fl.flows_to)
            catchment_heights[ide] = np.append(catchment_heights[ide], tosend)
        if (len(tosend) > 0) and (fl.flows_to is None):
            raise RuntimeError('This should not happen')

        # Now we have a width which is the "best" representation of our
        # tributary according to the altitude area distribution.
        # This sometimes leads to abrupt changes in the widths from one
        # grid point to another. I think it's not too harmful to smooth them
        # here, at the cost of a less perfect altitude area distribution
        if smooth_ws != 0:
            if smooth_ws == 1:
                new_widths = utils.smooth1d(new_widths)
            else:
                new_widths = utils.smooth1d(new_widths, window_size=smooth_ws)

        # Write it
        fl.widths = new_widths

    return flowlines