Use DSM's euclidean map for DTM merging, nearest neighbor interpolation in overlapping empty areas

pull/1049/head
Piero Toffanin 2019-11-04 21:29:43 +00:00
rodzic 84a6349760
commit a3f5aa4509
4 zmienionych plików z 37 dodań i 26 usunięć

Wyświetl plik

@ -1,5 +1,6 @@
import math import math
import numpy as np import numpy as np
from scipy import ndimage
import rasterio import rasterio
from rasterio.transform import Affine, rowcol from rasterio.transform import Affine, rowcol
from opendm import system from opendm import system
@ -8,7 +9,7 @@ from opendm import log
from opendm import io from opendm import io
import os import os
def euclidean_merge_dems(input_dems, output_dem, creation_options={}): def euclidean_merge_dems(input_dems, output_dem, creation_options={}, euclidean_map_source=None):
""" """
Based on https://github.com/mapbox/rio-merge-rgba Based on https://github.com/mapbox/rio-merge-rgba
and ideas from Anna Petrasova and ideas from Anna Petrasova
@ -40,7 +41,7 @@ def euclidean_merge_dems(input_dems, output_dem, creation_options={}):
profile = first.profile profile = first.profile
for dem in existing_dems: for dem in existing_dems:
eumap = compute_euclidean_map(dem, io.related_file_path(dem, postfix=".euclideand"), overwrite=False) eumap = compute_euclidean_map(dem, io.related_file_path(dem, postfix=".euclideand", replace_base=euclidean_map_source), overwrite=False)
if eumap and io.file_exists(eumap): if eumap and io.file_exists(eumap):
inputs.append((dem, eumap)) inputs.append((dem, eumap))
@ -57,9 +58,6 @@ def euclidean_merge_dems(input_dems, output_dem, creation_options={}):
xs = [] xs = []
ys = [] ys = []
for src_d, src_e in sources: for src_d, src_e in sources:
if not same_bounds(src_d, src_e):
raise ValueError("DEM and euclidean file must have the same bounds")
left, bottom, right, top = src_d.bounds left, bottom, right, top = src_d.bounds
xs.extend([left, right]) xs.extend([left, right])
ys.extend([bottom, top]) ys.extend([bottom, top])
@ -109,6 +107,7 @@ def euclidean_merge_dems(input_dems, output_dem, creation_options={}):
dstarr = np.zeros(dst_shape, dtype=dtype) dstarr = np.zeros(dst_shape, dtype=dtype)
distsum = np.zeros(dst_shape, dtype=dtype) distsum = np.zeros(dst_shape, dtype=dtype)
small_distance = 0.001953125
for src_d, src_e in sources: for src_d, src_e in sources:
# The full_cover behavior is problematic here as it includes # The full_cover behavior is problematic here as it includes
@ -124,20 +123,26 @@ def euclidean_merge_dems(input_dems, output_dem, creation_options={}):
nodata = src_d.nodatavals[0] nodata = src_d.nodatavals[0]
# Alternative, custom get_window using rounding # Alternative, custom get_window using rounding
src_window = tuple(zip(rowcol( src_window_d = tuple(zip(rowcol(
src_d.transform, left, top, op=round, precision=precision src_d.transform, left, top, op=round, precision=precision
), rowcol( ), rowcol(
src_d.transform, right, bottom, op=round, precision=precision src_d.transform, right, bottom, op=round, precision=precision
))) )))
src_window_e = tuple(zip(rowcol(
src_e.transform, left, top, op=round, precision=precision
), rowcol(
src_e.transform, right, bottom, op=round, precision=precision
)))
temp_d = np.zeros(dst_shape, dtype=dtype) temp_d = np.zeros(dst_shape, dtype=dtype)
temp_d = src_d.read( temp_d = src_d.read(
out=temp_d, window=src_window, boundless=True, masked=False out=temp_d, window=src_window_d, boundless=True, masked=False
) )
temp_e = np.zeros(dst_shape, dtype=dtype) temp_e = np.zeros(dst_shape, dtype=dtype)
temp_e = src_e.read( temp_e = src_e.read(
out=temp_e, window=src_window, boundless=True, masked=False out=temp_e, window=src_window_e, boundless=True, masked=False
) )
# Set NODATA areas in the euclidean map to a very low value # Set NODATA areas in the euclidean map to a very low value
@ -146,7 +151,7 @@ def euclidean_merge_dems(input_dems, output_dem, creation_options={}):
# are far away from NODATA areas # are far away from NODATA areas
# - Areas that have no overlap are included in the final result # - Areas that have no overlap are included in the final result
# even if they are very close to a NODATA cell # even if they are very close to a NODATA cell
temp_e[temp_e==0] = 0.001953125 temp_e[temp_e==0] = small_distance
temp_e[temp_d==nodata] = 0 temp_e[temp_d==nodata] = 0
np.multiply(temp_d, temp_e, out=temp_d) np.multiply(temp_d, temp_e, out=temp_d)
@ -154,22 +159,17 @@ def euclidean_merge_dems(input_dems, output_dem, creation_options={}):
np.add(distsum, temp_e, out=distsum) np.add(distsum, temp_e, out=distsum)
np.divide(dstarr, distsum, out=dstarr, where=distsum[0] != 0.0) np.divide(dstarr, distsum, out=dstarr, where=distsum[0] != 0.0)
# Perform nearest neighbor interpolation on areas where two or more rasters overlap
# but where both rasters have only interpolated data. This prevents the creation
# of artifacts that average areas of interpolation.
indices = ndimage.distance_transform_edt(np.logical_and(distsum < 1, distsum > small_distance),
return_distances=False,
return_indices=True)
dstarr = dstarr[tuple(indices)]
dstarr[dstarr == 0.0] = src_nodata dstarr[dstarr == 0.0] = src_nodata
dstrast.write(dstarr, window=dst_window) dstrast.write(dstarr, window=dst_window)
return output_dem return output_dem
def same_bounds(rast_a, rast_b, EPS = 1E-5):
"""
Compares two raster bounds and returns true if they are equal
(up to a float precision threshold)
"""
a = rast_a.bounds
b = rast_b.bounds
return (abs(a.bottom - b.bottom) < EPS) and \
(abs(a.top - b.top) < EPS) and \
(abs(a.left - b.left) < EPS) and \
(abs(a.right - b.right) < EPS)

Wyświetl plik

@ -58,7 +58,7 @@ def find(filename, folder):
return '/'.join((root, filename)) if filename in files else None return '/'.join((root, filename)) if filename in files else None
def related_file_path(input_file_path, prefix="", postfix=""): def related_file_path(input_file_path, prefix="", postfix="", replace_base=None):
""" """
For example: related_file_path("/path/to/file.ext", "a.", ".b") For example: related_file_path("/path/to/file.ext", "a.", ".b")
--> "/path/to/a.file.b.ext" --> "/path/to/a.file.b.ext"
@ -72,6 +72,9 @@ def related_file_path(input_file_path, prefix="", postfix=""):
# basename = file # basename = file
# ext = .ext # ext = .ext
if replace_base is not None:
basename = replace_base
return os.path.join(path, "{}{}{}{}".format(prefix, basename, postfix, ext)) return os.path.join(path, "{}{}{}{}".format(prefix, basename, postfix, ext))
def path_or_json_string_to_dict(string): def path_or_json_string_to_dict(string):

Wyświetl plik

@ -58,7 +58,8 @@ class ODMDEMStage(types.ODM_Stage):
self.rerun(): self.rerun():
products = [] products = []
if args.dsm: products.append('dsm')
if args.dsm or (args.dtm and args.dem_euclidean_map): products.append('dsm')
if args.dtm: products.append('dtm') if args.dtm: products.append('dtm')
resolution = gsd.cap_resolution(args.dem_resolution, tree.opensfm_reconstruction, gsd_error_estimate=-3, ignore_gsd=args.ignore_gsd) resolution = gsd.cap_resolution(args.dem_resolution, tree.opensfm_reconstruction, gsd_error_estimate=-3, ignore_gsd=args.ignore_gsd)

Wyświetl plik

@ -314,7 +314,14 @@ class ODMMergeStage(types.ODM_Stage):
# Merge # Merge
dem_vars = utils.get_dem_vars(args) dem_vars = utils.get_dem_vars(args)
euclidean_merge_dems(all_dems, dem_file, dem_vars) eu_map_source = None # Default
# Use DSM's euclidean map for DTMs
# (requires the DSM to be computed)
if human_name == "DTM":
eu_map_source = "dsm"
euclidean_merge_dems(all_dems, dem_file, dem_vars, euclidean_map_source=eu_map_source)
if io.file_exists(dem_file): if io.file_exists(dem_file):
# Crop # Crop