コード例 #1
0
    def test_set_width(self):
        entity = gpd.read_file(self.rgi_file).iloc[0]

        gdir = oggm.GlacierDirectory(entity, base_dir=self.testdir)
        gis.define_glacier_region(gdir)
        gis.glacier_masks(gdir)
        centerlines.compute_centerlines(gdir)
        centerlines.initialize_flowlines(gdir)
        centerlines.compute_downstream_line(gdir)
        centerlines.compute_downstream_bedshape(gdir)
        centerlines.catchment_area(gdir)
        centerlines.catchment_intersections(gdir)
        centerlines.catchment_width_geom(gdir)
        centerlines.catchment_width_correction(gdir)

        # Test that area and area-altitude elev is fine
        with utils.ncDataset(gdir.get_filepath('gridded_data')) as nc:
            mask = nc.variables['glacier_mask'][:]
            topo = nc.variables['topo_smoothed'][:]
        rhgt = topo[np.where(mask)][:]

        fls = gdir.read_pickle('inversion_flowlines')
        hgt, widths = gdir.get_inversion_flowline_hw()

        bs = 100
        bins = np.arange(utils.nicenumber(np.min(hgt), bs, lower=True),
                         utils.nicenumber(np.max(hgt), bs) + 1,
                         bs)
        h1, b = np.histogram(hgt, weights=widths, density=True, bins=bins)
        h2, b = np.histogram(rhgt, density=True, bins=bins)
        h1 = h1 / np.sum(h1)
        h2 = h2 / np.sum(h2)
        assert utils.rmsd(h1, h2) < 0.02  # less than 2% error
        new_area = np.sum(widths * fls[-1].dx * gdir.grid.dx)
        np.testing.assert_allclose(new_area, gdir.rgi_area_m2)

        centerlines.terminus_width_correction(gdir, new_width=714)

        fls = gdir.read_pickle('inversion_flowlines')
        hgt, widths = gdir.get_inversion_flowline_hw()

        # Check that the width is ok
        np.testing.assert_allclose(fls[-1].widths[-1] * gdir.grid.dx, 714)

        # Check for area distrib
        bins = np.arange(utils.nicenumber(np.min(hgt), bs, lower=True),
                         utils.nicenumber(np.max(hgt), bs) + 1,
                         bs)
        h1, b = np.histogram(hgt, weights=widths, density=True, bins=bins)
        h2, b = np.histogram(rhgt, density=True, bins=bins)
        h1 = h1 / np.sum(h1)
        h2 = h2 / np.sum(h2)
        assert utils.rmsd(h1, h2) < 0.02  # less than 2% error
        new_area = np.sum(widths * fls[-1].dx * gdir.grid.dx)
        np.testing.assert_allclose(new_area, gdir.rgi_area_m2)
コード例 #2
0
ファイル: test_benchmarks.py プロジェクト: bearecinos/oggm
    def test_set_width(self):
        entity = gpd.read_file(self.rgi_file).iloc[0]

        gdir = oggm.GlacierDirectory(entity, base_dir=self.testdir)
        gis.define_glacier_region(gdir, entity=entity)
        gis.glacier_masks(gdir)
        centerlines.compute_centerlines(gdir)
        centerlines.initialize_flowlines(gdir)
        centerlines.compute_downstream_line(gdir)
        centerlines.compute_downstream_bedshape(gdir)
        centerlines.catchment_area(gdir)
        centerlines.catchment_intersections(gdir)
        centerlines.catchment_width_geom(gdir)
        centerlines.catchment_width_correction(gdir)

        # Test that area and area-altitude elev is fine
        with utils.ncDataset(gdir.get_filepath('gridded_data')) as nc:
            mask = nc.variables['glacier_mask'][:]
            topo = nc.variables['topo_smoothed'][:]
        rhgt = topo[np.where(mask)][:]

        fls = gdir.read_pickle('inversion_flowlines')
        hgt, widths = gdir.get_inversion_flowline_hw()

        bs = 100
        bins = np.arange(utils.nicenumber(np.min(hgt), bs, lower=True),
                         utils.nicenumber(np.max(hgt), bs) + 1,
                         bs)
        h1, b = np.histogram(hgt, weights=widths, density=True, bins=bins)
        h2, b = np.histogram(rhgt, density=True, bins=bins)
        h1 = h1 / np.sum(h1)
        h2 = h2 / np.sum(h2)
        assert utils.rmsd(h1, h2) < 0.02  # less than 2% error
        new_area = np.sum(widths * fls[-1].dx * gdir.grid.dx)
        np.testing.assert_allclose(new_area, gdir.rgi_area_m2)

        centerlines.terminus_width_correction(gdir, new_width=714)

        fls = gdir.read_pickle('inversion_flowlines')
        hgt, widths = gdir.get_inversion_flowline_hw()

        # Check that the width is ok
        np.testing.assert_allclose(fls[-1].widths[-1] * gdir.grid.dx, 714)

        # Check for area distrib
        bins = np.arange(utils.nicenumber(np.min(hgt), bs, lower=True),
                         utils.nicenumber(np.max(hgt), bs) + 1,
                         bs)
        h1, b = np.histogram(hgt, weights=widths, density=True, bins=bins)
        h2, b = np.histogram(rhgt, density=True, bins=bins)
        h1 = h1 / np.sum(h1)
        h2 = h2 / np.sum(h2)
        assert utils.rmsd(h1, h2) < 0.02  # less than 2% error
        new_area = np.sum(widths * fls[-1].dx * gdir.grid.dx)
        np.testing.assert_allclose(new_area, gdir.rgi_area_m2)
コード例 #3
0
    def test_width(self):

        hef_file = get_demo_file('Hintereisferner.shp')
        rgidf = gpd.GeoDataFrame.from_file(hef_file)

        # loop because for some reason indexing wont work
        for index, entity in rgidf.iterrows():
            gdir = oggm.GlacierDirectory(entity, base_dir=self.testdir)
            gis.define_glacier_region(gdir, entity=entity)
            gis.glacier_masks(gdir)
            centerlines.compute_centerlines(gdir)
            geometry.initialize_flowlines(gdir)
            geometry.catchment_area(gdir)
            geometry.catchment_width_geom(gdir)
            geometry.catchment_width_correction(gdir)

        area = 0.
        otherarea = 0.
        hgt = []
        harea = []
        for i in gdir.divide_ids:
            cls = gdir.read_pickle('inversion_flowlines', div_id=i)
            for cl in cls:
                harea.extend(list(cl.widths * cl.dx))
                hgt.extend(list(cl.surface_h))
                area += np.sum(cl.widths * cl.dx)
            nc = netCDF4.Dataset(gdir.get_filepath('gridded_data', div_id=i))
            otherarea += np.sum(nc.variables['glacier_mask'][:])
            nc.close()

        nc = netCDF4.Dataset(gdir.get_filepath('gridded_data', div_id=0))
        mask = nc.variables['glacier_mask'][:]
        topo = nc.variables['topo_smoothed'][:]
        nc.close()
        rhgt = topo[np.where(mask)][:]

        tdf = gpd.GeoDataFrame.from_file(gdir.get_filepath('outlines'))
        np.testing.assert_allclose(area, otherarea, rtol=0.1)
        area *= (gdir.grid.dx)**2
        otherarea *= (gdir.grid.dx)**2
        np.testing.assert_allclose(area * 10**-6,
                                   np.float(tdf['AREA']),
                                   rtol=1e-4)

        # Check for area distrib
        bins = np.arange(utils.nicenumber(np.min(hgt), 50, lower=True),
                         utils.nicenumber(np.max(hgt), 50) + 1, 50.)
        h1, b = np.histogram(hgt, weights=harea, density=True, bins=bins)
        h2, b = np.histogram(rhgt, density=True, bins=bins)
        self.assertTrue(utils.rmsd(h1 * 100 * 50, h2 * 100 * 50) < 1)
コード例 #4
0
ファイル: test_prepro.py プロジェクト: the-uli/oggm
    def test_width(self):

        hef_file = get_demo_file('Hintereisferner.shp')
        rgidf = gpd.GeoDataFrame.from_file(hef_file)

        # loop because for some reason indexing wont work
        for index, entity in rgidf.iterrows():
            gdir = cfg.GlacierDir(entity, base_dir=self.testdir)
            gis.define_glacier_region(gdir, entity)
            gis.glacier_masks(gdir)
            centerlines.compute_centerlines(gdir)
            geometry.initialize_flowlines(gdir)
            geometry.catchment_area(gdir)
            geometry.catchment_width_geom(gdir)
            geometry.catchment_width_correction(gdir)

        area = 0.
        otherarea = 0.
        hgt = []
        harea = []
        for i in gdir.divide_ids:
            cls = gdir.read_pickle('inversion_flowlines', div_id=i)
            for cl in cls:
                harea.extend(list(cl.widths * cl.dx))
                hgt.extend(list(cl.surface_h))
                area += np.sum(cl.widths * cl.dx)
            nc = netCDF4.Dataset(gdir.get_filepath('grids', div_id=i))
            otherarea += np.sum(nc.variables['glacier_mask'][:])
            nc.close()

        nc = netCDF4.Dataset(gdir.get_filepath('grids', div_id=0))
        mask = nc.variables['glacier_mask'][:]
        topo = nc.variables['topo_smoothed'][:]
        nc.close()
        rhgt = topo[np.where(mask)][:]

        tdf = gpd.GeoDataFrame.from_file(gdir.get_filepath('outlines'))
        np.testing.assert_allclose(area, otherarea, rtol=0.1)
        area *= (gdir.grid.dx) ** 2
        otherarea *= (gdir.grid.dx) ** 2
        np.testing.assert_allclose(area * 10**-6, np.float(tdf['AREA']), rtol=1e-4)

        # Check for area distrib
        bins = np.arange(utils.nicenumber(np.min(hgt), 50, lower=True),
                         utils.nicenumber(np.max(hgt), 50)+1,
                         50.)
        h1, b = np.histogram(hgt, weights=harea, density=True, bins=bins)
        h2, b = np.histogram(rhgt, density=True, bins=bins)
        self.assertTrue(utils.rmsd(h1*100*50, h2*100*50) < 1)
コード例 #5
0
ファイル: itmix.py プロジェクト: Enaith/oggm
def correct_dem(gdir, glacier_mask, dem, smoothed_dem):
    """Compare with Huss and stuff."""

    dem_glac = dem[np.nonzero(glacier_mask)]

    # Read RGI hypso for compa
    tosearch = '{:02d}'.format(np.int(gdir.rgi_region))
    tosearch = os.path.join(RGI_DIR, '*', tosearch + '*_hypso.csv')
    for fh in glob.glob(tosearch):
        pass
    df = pd.read_csv(fh)
    df.columns = [c.strip() for c in df.columns]
    df = df.loc[df.RGIId.isin([gdir.rgi_id])]
    df = df[df.columns[3:]].T
    df.columns = ['RGI (Huss)']
    hs = np.asarray(df.index.values, np.int)
    bins = utils.nicenumber(hs, 50, lower=True)
    bins = np.append(bins, bins[-1] + 50)
    myhist, _ = np.histogram(dem_glac, bins=bins)
    myhist = myhist / np.sum(myhist) * 1000
    df['OGGM'] = myhist
    df = df / 10
    df.index.rename('Alt (m)', inplace=True)
    df.plot()
    plt.ylabel('Freq (%)')
    plt.savefig('/home/mowglie/hypso_' + gdir.rgi_id + '.png')

    minz = None
    if gdir.rgi_id == 'RGI50-06.00424': minz = 800
    if gdir.rgi_id == 'RGI50-06.00443': minz = 600

    return dem, smoothed_dem
コード例 #6
0
ファイル: test_prepro.py プロジェクト: OGGM/oggm
    def test_width(self):

        hef_file = get_demo_file("Hintereisferner.shp")
        entity = gpd.GeoDataFrame.from_file(hef_file).iloc[0]

        gdir = oggm.GlacierDirectory(entity, base_dir=self.testdir)
        gis.define_glacier_region(gdir, entity=entity)
        gis.glacier_masks(gdir)
        centerlines.compute_centerlines(gdir)
        geometry.initialize_flowlines(gdir)
        geometry.catchment_area(gdir)
        geometry.catchment_width_geom(gdir)
        geometry.catchment_width_correction(gdir)

        area = 0.0
        otherarea = 0.0
        hgt = []
        harea = []
        for i in gdir.divide_ids:
            cls = gdir.read_pickle("inversion_flowlines", div_id=i)
            for cl in cls:
                harea.extend(list(cl.widths * cl.dx))
                hgt.extend(list(cl.surface_h))
                area += np.sum(cl.widths * cl.dx)
            with netCDF4.Dataset(gdir.get_filepath("gridded_data", div_id=i)) as nc:
                otherarea += np.sum(nc.variables["glacier_mask"][:])

        with netCDF4.Dataset(gdir.get_filepath("gridded_data", div_id=0)) as nc:
            mask = nc.variables["glacier_mask"][:]
            topo = nc.variables["topo_smoothed"][:]
        rhgt = topo[np.where(mask)][:]

        tdf = gpd.GeoDataFrame.from_file(gdir.get_filepath("outlines"))
        np.testing.assert_allclose(area, otherarea, rtol=0.1)
        area *= (gdir.grid.dx) ** 2
        otherarea *= (gdir.grid.dx) ** 2
        np.testing.assert_allclose(area * 10 ** -6, np.float(tdf["AREA"]), rtol=1e-4)

        # Check for area distrib
        bins = np.arange(utils.nicenumber(np.min(hgt), 50, lower=True), utils.nicenumber(np.max(hgt), 50) + 1, 50.0)
        h1, b = np.histogram(hgt, weights=harea, density=True, bins=bins)
        h2, b = np.histogram(rhgt, density=True, bins=bins)
        self.assertTrue(utils.rmsd(h1 * 100 * 50, h2 * 100 * 50) < 1)
コード例 #7
0
ファイル: geometry.py プロジェクト: JohannesUIBK/oggm
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
コード例 #8
0
ファイル: gis.py プロジェクト: bearecinos/oggm
def simple_glacier_masks(gdir):
    """Compute glacier masks based on much simpler rules than OGGM's default.

    This is therefore more robust: we use this function to compute glacier
    hypsometries.

    Parameters
    ----------
    gdir : :py:class:`oggm.GlacierDirectory`
        where to write the data
    """

    # open srtm tif-file:
    dem_dr = rasterio.open(gdir.get_filepath('dem'), 'r', driver='GTiff')
    dem = dem_dr.read(1).astype(rasterio.float32)

    # Grid
    nx = dem_dr.width
    ny = dem_dr.height
    assert nx == gdir.grid.nx
    assert ny == gdir.grid.ny

    # Correct the DEM
    # Currently we just do a linear interp -- filling is totally shit anyway
    min_z = -999.
    dem[dem <= min_z] = np.NaN
    isfinite = np.isfinite(dem)
    if np.all(~isfinite):
        raise InvalidDEMError('Not a single valid grid point in DEM')
    if np.any(~isfinite):
        xx, yy = gdir.grid.ij_coordinates
        pnan = np.nonzero(~isfinite)
        pok = np.nonzero(isfinite)
        points = np.array((np.ravel(yy[pok]), np.ravel(xx[pok]))).T
        inter = np.array((np.ravel(yy[pnan]), np.ravel(xx[pnan]))).T
        try:
            dem[pnan] = griddata(points, np.ravel(dem[pok]), inter,
                                 method='linear')
        except ValueError:
            raise InvalidDEMError('DEM interpolation not possible.')
        log.warning(gdir.rgi_id + ': DEM needed interpolation.')
        gdir.add_to_diagnostics('dem_needed_interpolation', True)
        gdir.add_to_diagnostics('dem_invalid_perc', len(pnan[0]) / (nx*ny))

    isfinite = np.isfinite(dem)
    if np.any(~isfinite):
        # this happens when extrapolation is needed
        # see how many percent of the dem
        if np.sum(~isfinite) > (0.5 * nx * ny):
            log.warning('({}) many NaNs in DEM'.format(gdir.rgi_id))
        xx, yy = gdir.grid.ij_coordinates
        pnan = np.nonzero(~isfinite)
        pok = np.nonzero(isfinite)
        points = np.array((np.ravel(yy[pok]), np.ravel(xx[pok]))).T
        inter = np.array((np.ravel(yy[pnan]), np.ravel(xx[pnan]))).T
        try:
            dem[pnan] = griddata(points, np.ravel(dem[pok]), inter,
                                 method='nearest')
        except ValueError:
            raise InvalidDEMError('DEM extrapolation not possible.')
        log.warning(gdir.rgi_id + ': DEM needed extrapolation.')
        gdir.add_to_diagnostics('dem_needed_extrapolation', True)
        gdir.add_to_diagnostics('dem_extrapol_perc', len(pnan[0]) / (nx*ny))

    if np.min(dem) == np.max(dem):
        raise InvalidDEMError('({}) min equal max in the DEM.'
                              .format(gdir.rgi_id))

    # Proj
    if LooseVersion(rasterio.__version__) >= LooseVersion('1.0'):
        transf = dem_dr.transform
    else:
        raise ImportError('This task needs rasterio >= 1.0 to work properly')
    x0 = transf[2]  # UL corner
    y0 = transf[5]  # UL corner
    dx = transf[0]
    dy = transf[4]  # Negative
    assert dx == -dy
    assert dx == gdir.grid.dx
    assert y0 == gdir.grid.corner_grid.y0
    assert x0 == gdir.grid.corner_grid.x0

    profile = dem_dr.profile
    dem_dr.close()

    # Clip topography to 0 m a.s.l.
    dem = dem.clip(0)

    # Smooth DEM?
    if cfg.PARAMS['smooth_window'] > 0.:
        gsize = np.rint(cfg.PARAMS['smooth_window'] / dx)
        smoothed_dem = gaussian_blur(dem, np.int(gsize))
    else:
        smoothed_dem = dem.copy()

    if not np.all(np.isfinite(smoothed_dem)):
        raise InvalidDEMError('({}) NaN in smoothed DEM'.format(gdir.rgi_id))

    # Geometries
    geometry = gdir.read_shapefile('outlines').geometry[0]

    # simple trick to correct invalid polys:
    # http://stackoverflow.com/questions/20833344/
    # fix-invalid-polygon-python-shapely
    geometry = geometry.buffer(0)
    if not geometry.is_valid:
        raise InvalidDEMError('This glacier geometry is not valid.')

    # Compute the glacier mask using rasterio
    # Small detour as mask only accepts DataReader objects
    with rasterio.io.MemoryFile() as memfile:
        with memfile.open(**profile) as dataset:
            dataset.write(dem.astype(np.int16)[np.newaxis, ...])
        dem_data = rasterio.open(memfile.name)
        masked_dem, _ = riomask(dem_data, [shpg.mapping(geometry)],
                                filled=False)
    glacier_mask = ~masked_dem[0, ...].mask

    # Smame without nunataks
    with rasterio.io.MemoryFile() as memfile:
        with memfile.open(**profile) as dataset:
            dataset.write(dem.astype(np.int16)[np.newaxis, ...])
        dem_data = rasterio.open(memfile.name)
        poly = shpg.mapping(shpg.Polygon(geometry.exterior))
        masked_dem, _ = riomask(dem_data, [poly],
                                filled=False)
    glacier_mask_nonuna = ~masked_dem[0, ...].mask

    # Glacier exterior excluding nunataks
    erode = binary_erosion(glacier_mask_nonuna)
    glacier_ext = glacier_mask_nonuna ^ erode
    glacier_ext = np.where(glacier_mask_nonuna, glacier_ext, 0)

    # Last sanity check based on the masked dem
    tmp_max = np.max(dem[glacier_mask])
    tmp_min = np.min(dem[glacier_mask])
    if tmp_max < (tmp_min + 1):
        raise InvalidDEMError('({}) min equal max in the masked DEM.'
                              .format(gdir.rgi_id))

    # hypsometry
    bsize = 50.
    dem_on_ice = dem[glacier_mask]
    bins = np.arange(nicenumber(dem_on_ice.min(), bsize, lower=True),
                     nicenumber(dem_on_ice.max(), bsize) + 0.01, bsize)

    h, _ = np.histogram(dem_on_ice, bins)
    h = h / np.sum(h) * 1000  # in permil

    # We want to convert the bins to ints but preserve their sum to 1000
    # Start with everything rounded down, then round up the numbers with the
    # highest fractional parts until the desired sum is reached.
    hi = np.floor(h).astype(np.int)
    hup = np.ceil(h).astype(np.int)
    aso = np.argsort(hup - h)
    for i in aso:
        hi[i] = hup[i]
        if np.sum(hi) == 1000:
            break

    # slope
    sy, sx = np.gradient(dem, dx)
    aspect = np.arctan2(np.mean(-sx[glacier_mask]), np.mean(sy[glacier_mask]))
    aspect = np.rad2deg(aspect)
    if aspect < 0:
        aspect += 360
    slope = np.arctan(np.sqrt(sx ** 2 + sy ** 2))
    avg_slope = np.rad2deg(np.mean(slope[glacier_mask]))

    # write
    df = pd.DataFrame()
    df['RGIId'] = [gdir.rgi_id]
    df['GLIMSId'] = [gdir.glims_id]
    df['Zmin'] = [dem_on_ice.min()]
    df['Zmax'] = [dem_on_ice.max()]
    df['Zmed'] = [np.median(dem_on_ice)]
    df['Area'] = [gdir.rgi_area_km2]
    df['Slope'] = [avg_slope]
    df['Aspect'] = [aspect]
    for b, bs in zip(hi, (bins[1:] + bins[:-1])/2):
        df['{}'.format(np.round(bs).astype(int))] = [b]
    df.to_csv(gdir.get_filepath('hypsometry'), index=False)

    # write out the grids in the netcdf file
    nc = gdir.create_gridded_ncdf_file('gridded_data')

    v = nc.createVariable('topo', 'f4', ('y', 'x', ), zlib=True)
    v.units = 'm'
    v.long_name = 'DEM topography'
    v[:] = dem

    v = nc.createVariable('topo_smoothed', 'f4', ('y', 'x', ), zlib=True)
    v.units = 'm'
    v.long_name = ('DEM topography smoothed '
                   'with radius: {:.1} m'.format(cfg.PARAMS['smooth_window']))
    v[:] = smoothed_dem

    v = nc.createVariable('glacier_mask', 'i1', ('y', 'x', ), zlib=True)
    v.units = '-'
    v.long_name = 'Glacier mask'
    v[:] = glacier_mask

    v = nc.createVariable('glacier_ext', 'i1', ('y', 'x', ), zlib=True)
    v.units = '-'
    v.long_name = 'Glacier external boundaries'
    v[:] = glacier_ext

    # add some meta stats and close
    nc.max_h_dem = np.max(dem)
    nc.min_h_dem = np.min(dem)
    dem_on_g = dem[np.where(glacier_mask)]
    nc.max_h_glacier = np.max(dem_on_g)
    nc.min_h_glacier = np.min(dem_on_g)
    nc.close()
コード例 #9
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)
            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'])

    # 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
                # # TODO: smooth them ?
                # widths = utils.smooth1d(widths)
                # if np.nanmax(_width_change_factor(new_widths)[:-5]) < 2.:
                #     break
            bsize += 5

            # Add a security for infinite loops
            if bsize > 250:
                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')

        # Write it
        fl.widths = new_widths

    return flowlines
コード例 #10
0
tasks.compute_centerlines(gdir)
tasks.initialize_flowlines(gdir)
tasks.compute_downstream_line(gdir)
tasks.catchment_area(gdir)
tasks.catchment_intersections(gdir)
tasks.catchment_width_geom(gdir)
tasks.catchment_width_correction(gdir)

with netCDF4.Dataset(gdir.get_filepath('gridded_data')) as nc:
    mask = nc.variables['glacier_mask'][:]
    topo = nc.variables['topo_smoothed'][:]
rhgt = topo[np.where(mask)][:]
hgt, harea = gdir.get_inversion_flowline_hw()

# Check for area distrib
bins = np.arange(nicenumber(np.min(hgt), 150, lower=True),
                 nicenumber(np.max(hgt), 150) + 1, 150.)
h1, b = np.histogram(hgt, weights=harea, density=True, bins=bins)
h2, b = np.histogram(rhgt, density=True, bins=bins)

h1 = h1 / np.sum(h1)
h2 = h2 / np.sum(h2)

LCMAP = plt.get_cmap('Purples')(np.linspace(0.4, 1, 5)[::-1])
MCMAP = graphics.truncate_colormap(plt.get_cmap('Blues'), 0.2, 0.8, 255)

f, axs = plt.subplots(2, 2, figsize=(8.5, 7))
axs = np.asarray(axs).flatten()

llkw = {'interval': 0}
letkm = dict(color='black',
コード例 #11
0
def simple_glacier_masks(gdir):
    """Compute glacier masks based on much simpler rules than OGGM's default.

    This is therefore more robust: we use this function to compute glacier
    hypsometries.

    Parameters
    ----------
    gdir : :py:class:`oggm.GlacierDirectory`
        where to write the data
    """

    # open srtm tif-file:
    dem_dr = rasterio.open(gdir.get_filepath('dem'), 'r', driver='GTiff')
    dem = dem_dr.read(1).astype(rasterio.float32)

    # Grid
    nx = dem_dr.width
    ny = dem_dr.height
    assert nx == gdir.grid.nx
    assert ny == gdir.grid.ny

    # Correct the DEM
    # Currently we just do a linear interp -- filling is totally shit anyway
    min_z = -999.
    dem[dem <= min_z] = np.NaN
    isfinite = np.isfinite(dem)
    if np.all(~isfinite):
        raise InvalidDEMError('Not a single valid grid point in DEM')
    if np.any(~isfinite):
        xx, yy = gdir.grid.ij_coordinates
        pnan = np.nonzero(~isfinite)
        pok = np.nonzero(isfinite)
        points = np.array((np.ravel(yy[pok]), np.ravel(xx[pok]))).T
        inter = np.array((np.ravel(yy[pnan]), np.ravel(xx[pnan]))).T
        try:
            dem[pnan] = griddata(points,
                                 np.ravel(dem[pok]),
                                 inter,
                                 method='linear')
        except ValueError:
            raise InvalidDEMError('DEM interpolation not possible.')
        log.warning(gdir.rgi_id + ': DEM needed interpolation.')
        gdir.add_to_diagnostics('dem_needed_interpolation', True)
        gdir.add_to_diagnostics('dem_invalid_perc', len(pnan[0]) / (nx * ny))

    isfinite = np.isfinite(dem)
    if np.any(~isfinite):
        # this happens when extrapolation is needed
        # see how many percent of the dem
        if np.sum(~isfinite) > (0.5 * nx * ny):
            log.warning('({}) many NaNs in DEM'.format(gdir.rgi_id))
        xx, yy = gdir.grid.ij_coordinates
        pnan = np.nonzero(~isfinite)
        pok = np.nonzero(isfinite)
        points = np.array((np.ravel(yy[pok]), np.ravel(xx[pok]))).T
        inter = np.array((np.ravel(yy[pnan]), np.ravel(xx[pnan]))).T
        try:
            dem[pnan] = griddata(points,
                                 np.ravel(dem[pok]),
                                 inter,
                                 method='nearest')
        except ValueError:
            raise InvalidDEMError('DEM extrapolation not possible.')
        log.warning(gdir.rgi_id + ': DEM needed extrapolation.')
        gdir.add_to_diagnostics('dem_needed_extrapolation', True)
        gdir.add_to_diagnostics('dem_extrapol_perc', len(pnan[0]) / (nx * ny))

    if np.min(dem) == np.max(dem):
        raise InvalidDEMError('({}) min equal max in the DEM.'.format(
            gdir.rgi_id))

    # Proj
    if LooseVersion(rasterio.__version__) >= LooseVersion('1.0'):
        transf = dem_dr.transform
    else:
        raise ImportError('This task needs rasterio >= 1.0 to work properly')
    x0 = transf[2]  # UL corner
    y0 = transf[5]  # UL corner
    dx = transf[0]
    dy = transf[4]  # Negative
    assert dx == -dy
    assert dx == gdir.grid.dx
    assert y0 == gdir.grid.corner_grid.y0
    assert x0 == gdir.grid.corner_grid.x0

    profile = dem_dr.profile
    dem_dr.close()

    # Clip topography to 0 m a.s.l.
    dem = dem.clip(0)

    # Smooth DEM?
    if cfg.PARAMS['smooth_window'] > 0.:
        gsize = np.rint(cfg.PARAMS['smooth_window'] / dx)
        smoothed_dem = gaussian_blur(dem, np.int(gsize))
    else:
        smoothed_dem = dem.copy()

    if not np.all(np.isfinite(smoothed_dem)):
        raise InvalidDEMError('({}) NaN in smoothed DEM'.format(gdir.rgi_id))

    # Geometries
    geometry = gdir.read_shapefile('outlines').geometry[0]

    # simple trick to correct invalid polys:
    # http://stackoverflow.com/questions/20833344/
    # fix-invalid-polygon-python-shapely
    geometry = geometry.buffer(0)
    if not geometry.is_valid:
        raise InvalidDEMError('This glacier geometry is not valid.')

    # Compute the glacier mask using rasterio
    # Small detour as mask only accepts DataReader objects
    with rasterio.io.MemoryFile() as memfile:
        with memfile.open(**profile) as dataset:
            dataset.write(dem.astype(np.int16)[np.newaxis, ...])
        dem_data = rasterio.open(memfile.name)
        masked_dem, _ = riomask(dem_data, [shpg.mapping(geometry)],
                                filled=False)
    glacier_mask = ~masked_dem[0, ...].mask

    # Smame without nunataks
    with rasterio.io.MemoryFile() as memfile:
        with memfile.open(**profile) as dataset:
            dataset.write(dem.astype(np.int16)[np.newaxis, ...])
        dem_data = rasterio.open(memfile.name)
        poly = shpg.mapping(shpg.Polygon(geometry.exterior))
        masked_dem, _ = riomask(dem_data, [poly], filled=False)
    glacier_mask_nonuna = ~masked_dem[0, ...].mask

    # Glacier exterior excluding nunataks
    erode = binary_erosion(glacier_mask_nonuna)
    glacier_ext = glacier_mask_nonuna ^ erode
    glacier_ext = np.where(glacier_mask_nonuna, glacier_ext, 0)

    # Last sanity check based on the masked dem
    tmp_max = np.max(dem[glacier_mask])
    tmp_min = np.min(dem[glacier_mask])
    if tmp_max < (tmp_min + 1):
        raise InvalidDEMError('({}) min equal max in the masked DEM.'.format(
            gdir.rgi_id))

    # hypsometry
    bsize = 50.
    dem_on_ice = dem[glacier_mask]
    bins = np.arange(nicenumber(dem_on_ice.min(), bsize, lower=True),
                     nicenumber(dem_on_ice.max(), bsize) + 0.01, bsize)

    h, _ = np.histogram(dem_on_ice, bins)
    h = h / np.sum(h) * 1000  # in permil

    # We want to convert the bins to ints but preserve their sum to 1000
    # Start with everything rounded down, then round up the numbers with the
    # highest fractional parts until the desired sum is reached.
    hi = np.floor(h).astype(np.int)
    hup = np.ceil(h).astype(np.int)
    aso = np.argsort(hup - h)
    for i in aso:
        hi[i] = hup[i]
        if np.sum(hi) == 1000:
            break

    # slope
    sy, sx = np.gradient(dem, dx)
    aspect = np.arctan2(np.mean(-sx[glacier_mask]), np.mean(sy[glacier_mask]))
    aspect = np.rad2deg(aspect)
    if aspect < 0:
        aspect += 360
    slope = np.arctan(np.sqrt(sx**2 + sy**2))
    avg_slope = np.rad2deg(np.mean(slope[glacier_mask]))

    # write
    df = pd.DataFrame()
    df['RGIId'] = [gdir.rgi_id]
    df['GLIMSId'] = [gdir.glims_id]
    df['Zmin'] = [dem_on_ice.min()]
    df['Zmax'] = [dem_on_ice.max()]
    df['Zmed'] = [np.median(dem_on_ice)]
    df['Area'] = [gdir.rgi_area_km2]
    df['Slope'] = [avg_slope]
    df['Aspect'] = [aspect]
    for b, bs in zip(hi, (bins[1:] + bins[:-1]) / 2):
        df['{}'.format(np.round(bs).astype(int))] = [b]
    df.to_csv(gdir.get_filepath('hypsometry'), index=False)

    # write out the grids in the netcdf file
    nc = gdir.create_gridded_ncdf_file('gridded_data')

    v = nc.createVariable('topo', 'f4', (
        'y',
        'x',
    ), zlib=True)
    v.units = 'm'
    v.long_name = 'DEM topography'
    v[:] = dem

    v = nc.createVariable('topo_smoothed', 'f4', (
        'y',
        'x',
    ), zlib=True)
    v.units = 'm'
    v.long_name = ('DEM topography smoothed '
                   'with radius: {:.1} m'.format(cfg.PARAMS['smooth_window']))
    v[:] = smoothed_dem

    v = nc.createVariable('glacier_mask', 'i1', (
        'y',
        'x',
    ), zlib=True)
    v.units = '-'
    v.long_name = 'Glacier mask'
    v[:] = glacier_mask

    v = nc.createVariable('glacier_ext', 'i1', (
        'y',
        'x',
    ), zlib=True)
    v.units = '-'
    v.long_name = 'Glacier external boundaries'
    v[:] = glacier_ext

    # add some meta stats and close
    nc.max_h_dem = np.max(dem)
    nc.min_h_dem = np.min(dem)
    dem_on_g = dem[np.where(glacier_mask)]
    nc.max_h_glacier = np.max(dem_on_g)
    nc.min_h_glacier = np.min(dem_on_g)
    nc.close()
コード例 #12
0
def simple_glacier_masks(gdir):
    """Compute glacier masks based on much simpler rules than OGGM's default.

    This is therefore more robust: we use this function to compute glacier
    hypsometries.

    Parameters
    ----------
    gdir : :py:class:`oggm.GlacierDirectory`
        where to write the data
    """

    if not os.path.exists(gdir.get_filepath('gridded_data')):
        # In a possible future, we might actually want to raise a
        # deprecation warning here
        process_dem(gdir)

    # Geometries
    geometry = gdir.read_shapefile('outlines').geometry[0]

    # rio metadata
    with rasterio.open(gdir.get_filepath('dem'), 'r', driver='GTiff') as ds:
        data = ds.read(1).astype(rasterio.float32)
        profile = ds.profile

    # simple trick to correct invalid polys:
    # http://stackoverflow.com/questions/20833344/
    # fix-invalid-polygon-python-shapely
    geometry = geometry.buffer(0)
    if not geometry.is_valid:
        raise InvalidDEMError('This glacier geometry is not valid.')

    # Compute the glacier mask using rasterio
    # Small detour as mask only accepts DataReader objects
    with rasterio.io.MemoryFile() as memfile:
        with memfile.open(**profile) as dataset:
            dataset.write(data.astype(np.int16)[np.newaxis, ...])
        dem_data = rasterio.open(memfile.name)
        masked_dem, _ = riomask(dem_data, [shpg.mapping(geometry)],
                                filled=False)
    glacier_mask = ~masked_dem[0, ...].mask

    # Same without nunataks
    with rasterio.io.MemoryFile() as memfile:
        with memfile.open(**profile) as dataset:
            dataset.write(data.astype(np.int16)[np.newaxis, ...])
        dem_data = rasterio.open(memfile.name)
        poly = shpg.mapping(shpg.Polygon(geometry.exterior))
        masked_dem, _ = riomask(dem_data, [poly],
                                filled=False)
    glacier_mask_nonuna = ~masked_dem[0, ...].mask

    # Glacier exterior excluding nunataks
    erode = binary_erosion(glacier_mask_nonuna)
    glacier_ext = glacier_mask_nonuna ^ erode
    glacier_ext = np.where(glacier_mask_nonuna, glacier_ext, 0)

    dem = read_geotiff_dem(gdir)

    # Last sanity check based on the masked dem
    tmp_max = np.max(dem[glacier_mask])
    tmp_min = np.min(dem[glacier_mask])
    if tmp_max < (tmp_min + 1):
        raise InvalidDEMError('({}) min equal max in the masked DEM.'
                              .format(gdir.rgi_id))

    # hypsometry
    bsize = 50.
    dem_on_ice = dem[glacier_mask]
    bins = np.arange(nicenumber(dem_on_ice.min(), bsize, lower=True),
                     nicenumber(dem_on_ice.max(), bsize) + 0.01, bsize)

    h, _ = np.histogram(dem_on_ice, bins)
    h = h / np.sum(h) * 1000  # in permil

    # We want to convert the bins to ints but preserve their sum to 1000
    # Start with everything rounded down, then round up the numbers with the
    # highest fractional parts until the desired sum is reached.
    hi = np.floor(h).astype(np.int)
    hup = np.ceil(h).astype(np.int)
    aso = np.argsort(hup - h)
    for i in aso:
        hi[i] = hup[i]
        if np.sum(hi) == 1000:
            break

    # slope
    sy, sx = np.gradient(dem, gdir.grid.dx)
    aspect = np.arctan2(np.mean(-sx[glacier_mask]), np.mean(sy[glacier_mask]))
    aspect = np.rad2deg(aspect)
    if aspect < 0:
        aspect += 360
    slope = np.arctan(np.sqrt(sx ** 2 + sy ** 2))
    avg_slope = np.rad2deg(np.mean(slope[glacier_mask]))

    # write
    df = pd.DataFrame()
    df['RGIId'] = [gdir.rgi_id]
    df['GLIMSId'] = [gdir.glims_id]
    df['Zmin'] = [dem_on_ice.min()]
    df['Zmax'] = [dem_on_ice.max()]
    df['Zmed'] = [np.median(dem_on_ice)]
    df['Area'] = [gdir.rgi_area_km2]
    df['Slope'] = [avg_slope]
    df['Aspect'] = [aspect]
    for b, bs in zip(hi, (bins[1:] + bins[:-1])/2):
        df['{}'.format(np.round(bs).astype(int))] = [b]
    df.to_csv(gdir.get_filepath('hypsometry'), index=False)

    # write out the grids in the netcdf file
    with GriddedNcdfFile(gdir) as nc:

        if 'glacier_mask' not in nc.variables:
            v = nc.createVariable('glacier_mask', 'i1', ('y', 'x', ),
                                  zlib=True)
            v.units = '-'
            v.long_name = 'Glacier mask'
        else:
            v = nc.variables['glacier_mask']
        v[:] = glacier_mask

        if 'glacier_ext' not in nc.variables:
            v = nc.createVariable('glacier_ext', 'i1', ('y', 'x', ),
                                  zlib=True)
            v.units = '-'
            v.long_name = 'Glacier external boundaries'
        else:
            v = nc.variables['glacier_ext']
        v[:] = glacier_ext

        # Log DEM that needed processing within the glacier mask
        valid_mask = nc.variables['topo_valid_mask'][:]
        if gdir.get_diagnostics().get('dem_needed_interpolation', False):
            pnan = (valid_mask == 0) & glacier_mask
            gdir.add_to_diagnostics('dem_invalid_perc_in_mask',
                                    np.sum(pnan) / np.sum(glacier_mask))

        # add some meta stats and close
        nc.max_h_dem = np.max(dem)
        nc.min_h_dem = np.min(dem)
        dem_on_g = dem[np.where(glacier_mask)]
        nc.max_h_glacier = np.max(dem_on_g)
        nc.min_h_glacier = np.min(dem_on_g)