diff --git a/SuperBuild/cmake/External-OpenSfM.cmake b/SuperBuild/cmake/External-OpenSfM.cmake index eb03802f..d5ffe330 100644 --- a/SuperBuild/cmake/External-OpenSfM.cmake +++ b/SuperBuild/cmake/External-OpenSfM.cmake @@ -9,7 +9,7 @@ ExternalProject_Add(${_proj_name} #--Download step-------------- DOWNLOAD_DIR ${SB_DOWNLOAD_DIR} GIT_REPOSITORY https://github.com/OpenDroneMap/OpenSfM/ - GIT_TAG 092 + GIT_TAG 098 #--Update/Patch step---------- UPDATE_COMMAND git submodule update --init --recursive #--Configure step------------- diff --git a/index.html b/index.html deleted file mode 100644 index 333535bd..00000000 --- a/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - OpenDroneMap - - - The project has moved to https://opendronemap.org! - - diff --git a/opendm/orthophoto.py b/opendm/orthophoto.py index 0de26c80..756142b7 100644 --- a/opendm/orthophoto.py +++ b/opendm/orthophoto.py @@ -62,6 +62,8 @@ def compute_mask_raster(input_raster, vector_mask, output_raster, blend_distance log.ODM_WARNING("Cannot mask raster, %s does not exist" % vector_mask) return + log.ODM_INFO("Computing mask raster: %s" % output_raster) + with rasterio.open(input_raster, 'r') as rast: with fiona.open(vector_mask) as src: burn_features = src @@ -98,6 +100,30 @@ def compute_mask_raster(input_raster, vector_mask, output_raster, blend_distance return output_raster +def feather_raster(input_raster, output_raster, blend_distance=20): + if not os.path.exists(input_raster): + log.ODM_WARNING("Cannot feather raster, %s does not exist" % input_raster) + return + + log.ODM_INFO("Computing feather raster: %s" % output_raster) + + with rasterio.open(input_raster, 'r') as rast: + out_image = rast.read() + if blend_distance > 0: + if out_image.shape[0] >= 4: + alpha_band = out_image[-1] + dist_t = ndimage.distance_transform_edt(alpha_band) + dist_t[dist_t <= blend_distance] /= blend_distance + dist_t[dist_t > blend_distance] = 1 + np.multiply(alpha_band, dist_t, out=alpha_band, casting="unsafe") + else: + log.ODM_WARNING("%s does not have an alpha band, cannot blend cutline!" % input_raster) + + with rasterio.open(output_raster, 'w', **rast.profile) as dst: + dst.colorinterp = rast.colorinterp + dst.write(out_image) + + return output_raster def merge(input_ortho_and_ortho_cuts, output_orthophoto, orthophoto_vars={}): """ @@ -181,9 +207,9 @@ def merge(input_ortho_and_ortho_cuts, output_orthophoto, orthophoto_vars={}): dst_count = first.count dst_shape = (dst_count, dst_rows, dst_cols) - # First pass, write all rasters naively dstarr = np.zeros(dst_shape, dtype=dtype) + # First pass, write all rasters naively without blending for src, _ in sources: src_window = tuple(zip(rowcol( src.transform, left, top, op=round, precision=precision @@ -206,7 +232,32 @@ def merge(input_ortho_and_ortho_cuts, output_orthophoto, orthophoto_vars={}): if np.count_nonzero(dstarr[-1]) == blocksize: break - # Second pass, write cut rasters + # Second pass, write all feathered rasters + # blending the edges + for src, _ in sources: + src_window = tuple(zip(rowcol( + src.transform, left, top, op=round, precision=precision + ), rowcol( + src.transform, right, bottom, op=round, precision=precision + ))) + + temp = np.zeros(dst_shape, dtype=dtype) + temp = src.read( + out=temp, window=src_window, boundless=True, masked=False + ) + + where = temp[-1] != 0 + for b in range(0, num_bands): + blended = temp[-1] / 255.0 * temp[b] + (1 - temp[-1] / 255.0) * dstarr[b] + np.copyto(dstarr[b], blended, casting='unsafe', where=where) + dstarr[-1][where] = 255.0 + + # check if dest has any nodata pixels available + if np.count_nonzero(dstarr[-1]) == blocksize: + break + + # Third pass, write cut rasters + # blending the cutlines for _, cut in sources: src_window = tuple(zip(rowcol( cut.transform, left, top, op=round, precision=precision diff --git a/opendm/remote.py b/opendm/remote.py index 9a6966bf..177ea2fa 100644 --- a/opendm/remote.py +++ b/opendm/remote.py @@ -502,6 +502,7 @@ class ToolchainTask(Task): outputs=["odm_orthophoto/odm_orthophoto.tif", "odm_orthophoto/cutline.gpkg", "odm_orthophoto/odm_orthophoto_cut.tif", + "odm_orthophoto/odm_orthophoto_feathered.tif", "odm_dem", "odm_georeferencing", "odm_georeferencing_25d"]) diff --git a/stages/odm_orthophoto.py b/stages/odm_orthophoto.py index d5b8ed34..a89d2e4f 100644 --- a/stages/odm_orthophoto.py +++ b/stages/odm_orthophoto.py @@ -137,6 +137,13 @@ class ODMOrthoPhotoStage(types.ODM_Stage): orthophoto.post_orthophoto_steps(args, bounds_file_path, tree.odm_orthophoto_tif) + # Generate feathered orthophoto also + if args.orthophoto_cutline: + orthophoto.feather_raster(tree.odm_orthophoto_tif, + os.path.join(tree.odm_orthophoto, "odm_orthophoto_feathered.tif"), + blend_distance=20 + ) + geotiffcreated = True if not geotiffcreated: log.ODM_WARNING('No geo-referenced orthophoto created due ' diff --git a/stages/splitmerge.py b/stages/splitmerge.py index ec7cf6df..40a9067e 100644 --- a/stages/splitmerge.py +++ b/stages/splitmerge.py @@ -209,7 +209,7 @@ class ODMMergeStage(types.ODM_Stage): if not io.file_exists(tree.odm_orthophoto_tif) or self.rerun(): all_orthos_and_ortho_cuts = get_all_submodel_paths(tree.submodels_path, - os.path.join("odm_orthophoto", "odm_orthophoto.tif"), + os.path.join("odm_orthophoto", "odm_orthophoto_feathered.tif"), os.path.join("odm_orthophoto", "odm_orthophoto_cut.tif"), )