Created
April 21, 2021 21:47
-
-
Save emlys/17a32ad2496829914a99f59ada0ea933 to your computer and use it in GitHub Desktop.
comparing two methods for "is near" algorithm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy | |
import math | |
from osgeo import gdal, osr | |
import pygeoprocessing | |
import timeit | |
FLOAT_NODATA = -1 | |
UINT8_NODATA = 255 | |
def is_near1(input_path, radius, distance_path, out_path): | |
# Calculate the distance from each pixel to the nearest '1' pixel | |
pygeoprocessing.distance_transform_edt( | |
(input_path, 1), | |
distance_path | |
) | |
def lte_threshold_op(array, threshold): | |
out = numpy.full(array.shape, 0, dtype=numpy.uint8) | |
valid_mask = ~numpy.isclose(array, FLOAT_NODATA) | |
out[valid_mask] = array[valid_mask] <= threshold | |
return out | |
# Threshold that to a binary array so '1' means it's within the radius | |
pygeoprocessing.raster_calculator( | |
[(distance_path, 1), (radius, 'raw')], | |
lte_threshold_op, | |
out_path, | |
gdal.GDT_Byte, | |
UINT8_NODATA) | |
def is_near2(input_path, radius, kernel_path, count_path, output_path): | |
""" | |
Args: | |
input_path: | |
radius | |
""" | |
search_kernel = make_search_kernel(input_path, radius) | |
# create and open the output raster and save the kernel to it | |
# set up a spatial reference and raster properties | |
srs = osr.SpatialReference() | |
srs.ImportFromEPSG(3857) | |
projection_wkt = srs.ExportToWkt() | |
pygeoprocessing.numpy_array_to_raster( | |
search_kernel, | |
UINT8_NODATA, | |
(20, -20), | |
(-10300000, 5610000), | |
projection_wkt, | |
kernel_path) | |
pygeoprocessing.convolve_2d( | |
(input_path, 1), | |
(kernel_path, 1), | |
count_path, | |
# pixels with nodata or off the edge of the raster won't count | |
ignore_nodata_and_edges=True, | |
# output will have nodata where ratio_path has nodata | |
mask_nodata=True, | |
target_datatype=gdal.GDT_UInt16, | |
target_nodata=FLOAT_NODATA) | |
def gte_threshold_op(array, threshold): | |
out = numpy.full(array.shape, 0, dtype=numpy.uint8) | |
valid_mask = ~numpy.isclose(array, FLOAT_NODATA) | |
out[valid_mask] = array[valid_mask] >= threshold | |
return out | |
pygeoprocessing.raster_calculator( | |
[(count_path, 1), (1, 'raw')], | |
gte_threshold_op, | |
output_path, | |
gdal.GDT_Byte, | |
UINT8_NODATA) | |
def make_search_kernel(raster_path, radius): | |
"""Make a search kernel for a raster that marks pixels within a radius. | |
Save the search kernel to a raster for use with pygeoprocessing.convolve_2d | |
Args: | |
raster_path (str): path to a raster to make kernel for | |
radius (float): distance around each pixel's centerpoint to search | |
in raster coordinate system units | |
Returns: | |
2D boolean numpy.ndarray. '1' pixels are within ``radius`` of the | |
center pixel, measured centerpoint-to-centerpoint. '0' pixels are | |
outside the radius. The array dimensions are as small as possible | |
while still including the entire radius. | |
""" | |
raster_info = pygeoprocessing.get_raster_info(raster_path) | |
pixel_radius = radius / raster_info['pixel_size'][0] | |
pixel_margin = math.floor(pixel_radius) | |
# the search kernel is just large enough to contain all pixels that | |
# *could* be within the radius of the center pixel | |
search_kernel_shape = tuple([pixel_margin * 2 + 1] * 2) | |
# arrays of the column index and row index of each pixel | |
col_indices, row_indices = numpy.indices(search_kernel_shape) | |
# adjust them so that (0, 0) is the center pixel | |
col_indices -= pixel_margin | |
row_indices -= pixel_margin | |
# hypotenuse_i = sqrt(col_indices_i**2 + row_indices_i**2) for each pixel i | |
hypotenuse = numpy.hypot(col_indices, row_indices) | |
# boolean kernel where 1=pixel centerpoint is within the radius of the | |
# center pixel's centerpoint | |
search_kernel = numpy.array(hypotenuse <= pixel_radius, dtype=numpy.uint8) | |
return search_kernel | |
radius = 30 | |
input_array = numpy.random.choice([0, 1], (1000, 1000), p=[0.9, 0.1]).astype(numpy.uint8) | |
input_path = '/Users/emily/Documents/test_is_near_input.tif' | |
distance_path = '/Users/emily/Documents/test_is_near_distance.tif' | |
kernel_path = '/Users/emily/Documents/test_is_near_kernel.tif' | |
count_path = '/Users/emily/Documents/test_is_near_count.tif' | |
output_path_1 = '/Users/emily/Documents/test_is_near_output1.tif' | |
output_path_2 = '/Users/emily/Documents/test_is_near_output2.tif' | |
# set up a spatial reference and raster properties | |
srs = osr.SpatialReference() | |
srs.ImportFromEPSG(3857) | |
projection_wkt = srs.ExportToWkt() | |
origin = (-10300000, 5610000) | |
pixel_size = (20, -20) | |
pygeoprocessing.numpy_array_to_raster(input_array, UINT8_NODATA, pixel_size, | |
origin, projection_wkt, input_path) | |
is_near1(input_path, radius / pixel_size[0], distance_path, output_path_1) | |
is_near2(input_path, radius, kernel_path, count_path, output_path_2) | |
result_1 = pygeoprocessing.raster_to_numpy_array(output_path_1) | |
result_2 = pygeoprocessing.raster_to_numpy_array(output_path_2) | |
numpy.testing.assert_equal(result_1, result_2) | |
def call_is_near1(): | |
is_near1( | |
input_path, | |
radius / pixel_size[0], | |
distance_path, | |
output_path_1) | |
def call_is_near2(): | |
is_near2( | |
input_path, | |
radius, | |
kernel_path, | |
count_path, | |
output_path_2) | |
duration = timeit.timeit(call_is_near1, number=1000) | |
print('Method 1:', duration) | |
duration = timeit.timeit(call_is_near2, number=1000) | |
print('Method 2:', duration) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment