Source code for rivgraph.mask_utils

# -*- coding: utf-8 -*-
"""
Mask Filtering Utils (mask_utils.py)
====================================

Functions for filtering islands from channel masks. Currently in beta.
Also see :mod:`im_utils` for morphologic operations that could be useful.

Created on Mon Jul 6 18:29:23 2020

@author: Jon
"""
import numpy as np
import geopandas as gpd
from loguru import logger
import rivgraph.im_utils as iu
from scipy.ndimage import distance_transform_edt
from shapely.geometry import shape
from scipy import stats
from rasterio.features import shapes as rio_shapes
from affine import Affine
import rivgraph.im_utils as im
import networkx as nx




def _build_island_polygons_rasterio(Ilabeled, gt, connectivity=2):
    """Build island polygons by polygonizing the labeled raster."""
    affine = Affine.from_gdal(*gt) * Affine.translation(-1, -1)
    rio_connectivity = 8 if connectivity == 2 else 4

    poly_by_id = {}
    for geom, value in rio_shapes(
        Ilabeled.astype(np.int32),
        mask=Ilabeled > 0,
        connectivity=rio_connectivity,
        transform=affine,
    ):
        label = int(value)
        if label == 0:
            continue
        poly_by_id[label] = shape(geom)

    return poly_by_id


[docs] def get_island_properties(Imask, pixlen, pixarea, crs, gt, props, connectivity=2): """Get island properties using raster polygonization for island geometries.""" props = list(props) # maxwidth is an additional property if 'maxwidth' in props: props.remove('maxwidth') do_maxwidth = True else: do_maxwidth = False user_requested_label = 'label' in props # Need labels to map polygonized geometries back to island IDs. if 'label' not in props: props.append('label') # Pad by one pixel to help identify and remove the outer portion of # the channel netowrk Imaskpad = np.array(np.pad(Imask, 1, mode='constant'), dtype=bool) Imp_invert = np.invert(Imaskpad) rp_islands, Ilabeled = iu.regionprops(Imp_invert, props=props, connectivity=connectivity) poly_by_id = _build_island_polygons_rasterio(Ilabeled, gt, connectivity=connectivity) ids = [int(i) for i in rp_islands['label']] pgons = [poly_by_id[i] for i in ids] # Do maximum width if requested if do_maxwidth: Idist = distance_transform_edt(Imp_invert) maxwids = [] for i in ids: maxwids.append(np.max(Idist[Ilabeled == i])*2*pixlen) # Convert requested properties to proper units if 'area' in props: rp_islands['area'] = rp_islands['area'] * pixarea if 'axis_major_length' in rp_islands: rp_islands['axis_major_length'] = rp_islands['axis_major_length'] * pixlen if 'axis_minor_length' in rp_islands: rp_islands['axis_minor_length'] = rp_islands['axis_minor_length'] * pixlen if 'major_axis_length' in rp_islands: rp_islands['major_axis_length'] = rp_islands['major_axis_length'] * pixlen if 'minor_axis_length' in rp_islands: rp_islands['minor_axis_length'] = rp_islands['minor_axis_length'] * pixlen if 'perimeter' in rp_islands: rp_islands['perimeter'] = rp_islands['perimeter'] * pixlen if 'perim_len' in rp_islands: rp_islands['perim_len'] = rp_islands['perim_len'] * pixlen if 'area_convex' in rp_islands: rp_islands['area_convex'] = rp_islands['area_convex'] * pixarea if 'convex_area' in rp_islands: rp_islands['convex_area'] = rp_islands['convex_area'] * pixarea # Need to change 'area' key as it's a function in geopandas if 'area' in rp_islands: rp_islands['Area'] = rp_islands.pop('area') # Create islands geodataframe gdf_dict = {k: rp_islands[k] for k in rp_islands if k not in ['coords', 'boundary_coords', 'centroid']} if not user_requested_label: gdf_dict.pop('label', None) gdf_dict['geometry'] = pgons gdf_dict['id'] = ids if do_maxwidth: gdf_dict['maxwid'] = maxwids gdf = gpd.GeoDataFrame(gdf_dict) gdf.crs = crs # Identify and remove the border blob border_id = Ilabeled[0][0] Ilabeled[Ilabeled == border_id] = 0 gdf = gdf[gdf.id.values != border_id] # Put 'id' column in front colnames = [k for k in gdf.keys()] colnames.remove('id') colnames.insert(0, 'id') gdf = gdf[colnames] return gdf, Ilabeled[1:-1, 1:-1]
def thresholding_set1(islands, apex_width): # Thresholding remove = set() # Global thresholding -- islands smaller than 1/10 the apex_wid^2 area_thresh = (1/10 * apex_width)**2 remove.update(np.where(islands.Area.values < area_thresh)[0].tolist()) # Threshold islands whose major axis length is less than 1/4 of the apex width maj_axis_thresh = apex_width/4 major_axis = (islands.axis_major_length.values if 'axis_major_length' in islands else islands.major_axis_length.values) remove.update(np.where((major_axis < maj_axis_thresh))[0].tolist()) # Threshold island area/surrounding area area_rat_thresh = 0.01 remove.update(np.where(islands.Area.values/islands.sur_area.values < area_rat_thresh)[0].tolist()) # # Threshold average island width as a fraction of surrounding channel widths avgwid_ratio_thresh = 0.1 imal = major_axis.copy() imal[imal == 0] = np.nan avg_island_wid = islands.Area.values / imal remove.update(np.where(avg_island_wid/islands.sur_avg_wid.values < avgwid_ratio_thresh)[0].tolist()) # Keep islands with a major axis length greater than the apex width keep = set() keep.update(np.where(major_axis > apex_width)[0].tolist()) # Do the thresholding remove = remove - keep return remove