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
import rivgraph.im_utils as iu
import rivgraph.geo_utils as gu
from scipy.ndimage import distance_transform_edt
from shapely.geometry import Polygon
from shapely.ops import cascaded_union
from scipy import stats
import rivgraph.im_utils as im
import networkx as nx


[docs]def pixagon(c_cent, r_cent, pixlen): """ Returns a shapely polygon of a provided center coordinate given the pixel resolution (pixlen). """ halflen = pixlen/2 c_corner = np.array([c_cent-halflen, c_cent+halflen, c_cent+halflen, c_cent-halflen, c_cent-halflen]) r_corner = np.array([r_cent+halflen, r_cent+halflen, r_cent-halflen, r_cent-halflen, r_cent+halflen]) pixgon = Polygon(zip(c_corner, r_corner)) return pixgon
[docs]def get_island_properties(Imask, pixlen, pixarea, crs, gt, props, connectivity=2): """Get island properties.""" # maxwidth is an additional property if 'maxwidth' in props: props.remove('maxwidth') do_maxwidth = True else: do_maxwidth = False # Need perimeter to make island polygons if 'perimeter' not in props: props.append('perimeter') # 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) # Make polygons of the island perimeters # Also get ids to match the labeled image pgons = [] ids = [] for ip, p in enumerate(rp_islands['perimeter']): ids.append(Ilabeled[p[0][0], p[0][1]]) # Store the index p = np.vstack((p, p[0])) # Close the polygon # Adjust for the single-pixel padding we added to the image cr = gu.xy_to_coords(p[:, 1] - 1, p[:, 0] - 1, gt) # Special cases: where the island is two pixels or less, we use the # corner coordinates rather than the center coordinates to define # the polygon. if len(cr[0]) <= 2: pixgon = [pixagon(cc, rc, pixlen) for cc, rc, in zip(cr[0], cr[1])] if len(pixgon) > 1: pixgon = cascaded_union(pixgon) pgons.append(pixgon) else: pgons.append(Polygon(zip(cr[0], cr[1]))) # 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 'major_axis_length' in props: rp_islands['major_axis_length'] = rp_islands['major_axis_length'] * pixlen if 'minor_axis_length' in props: rp_islands['minor_axis_length'] = rp_islands['minor_axis_length'] * pixlen if 'perim_len' in props: rp_islands['perim_len'] = rp_islands['perim_len'] * pixlen if 'convex_area' in props: 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', 'perimeter', 'centroid']} 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/3 of the apex width maj_axis_thresh = apex_width/4 remove.update(np.where((islands.major_axis_length.values < 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 = islands.major_axis_length.values 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(islands.major_axis_length.values > apex_width)[0].tolist()) # Do the thresholding remove = remove - keep return remove