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"),
)