kopia lustrzana https://github.com/OpenDroneMap/ODM
Added --compute-cutline param, close to working
rodzic
078a6a0678
commit
e887296d13
|
@ -253,6 +253,15 @@ def config():
|
|||
'Use 0 to disable cropping. '
|
||||
'Default: %(default)s'))
|
||||
|
||||
parser.add_argument('--compute-cutline',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Generates a polygon around the cropping area '
|
||||
'that cuts the orthophoto around the edges of features. This polygon '
|
||||
'can be useful for stitching seamless mosaics with multiple overlapping orthophotos. '
|
||||
'Default: '
|
||||
'%(default)s')
|
||||
|
||||
parser.add_argument('--pc-classify',
|
||||
action='store_true',
|
||||
default=False,
|
||||
|
@ -528,4 +537,8 @@ def config():
|
|||
log.ODM_WARNING('--skip-3dmodel is set, but so is --use-3dmesh. --use_3dmesh will be ignored.')
|
||||
args.use_3dmesh = False
|
||||
|
||||
if args.compute_cutline and not args.crop:
|
||||
log.ODM_WARNING("--compute-cutline is set, but --crop is not. --crop will be set to 0.01")
|
||||
args.crop = 0.01
|
||||
|
||||
return args
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
import os
|
||||
import shutil
|
||||
from opendm import log
|
||||
from opendm import io
|
||||
from opendm import concurrency
|
||||
|
||||
|
||||
def compute_cutline(orthophoto_file, crop_area_file, destination, max_concurrency=1):
|
||||
if io.file_exists(orthophoto_file) and io.file_exists(crop_area_file):
|
||||
from opendm.grass_engine import grass
|
||||
log.ODM_DEBUG("Computing cutline")
|
||||
|
||||
gctx = grass.create_context({'auto_cleanup' : False})
|
||||
gctx.add_param('orthophoto_file', orthophoto_file)
|
||||
gctx.add_param('crop_area_file', crop_area_file)
|
||||
gctx.add_param('max_concurrency', max_concurrency)
|
||||
gctx.add_param('memory', int(concurrency.get_max_memory_mb(300)))
|
||||
gctx.set_location(orthophoto_file)
|
||||
|
||||
cutline_file = gctx.execute(os.path.join("opendm", "grass", "compute_cutline.grass"))
|
||||
if cutline_file != 'error':
|
||||
if io.file_exists(cutline_file):
|
||||
shutil.move(cutline_file, destination)
|
||||
log.ODM_INFO("Generated cutline file: %s --> %s" % (cutline_file, destination))
|
||||
# gctx.cleanup()
|
||||
return destination
|
||||
else:
|
||||
log.ODM_WARNING("Unexpected script result: %s. No cutline file has been generated." % cutline_file)
|
||||
else:
|
||||
log.ODM_WARNING("Could not generate orthophoto cutline. An error occured when running GRASS. No orthophoto will be generated.")
|
||||
else:
|
||||
log.ODM_WARNING("We've been asked to compute cutline, but either %s or %s is missing. Skipping..." % (orthophoto_file, crop_area_file))
|
|
@ -0,0 +1,37 @@
|
|||
# orthophoto_file: input GeoTIFF raster file
|
||||
# crop_area_file: input vector polygon file delimiting the safe area for processing
|
||||
# max_concurrency: maximum number of parallel processes to use
|
||||
# memory: maximum MB of memory to use
|
||||
# ------
|
||||
# output: If successful, prints the full path to the cutlines file. Otherwise it prints "error"
|
||||
|
||||
# Import orthophoto (green band only)
|
||||
r.external band=2 input="${orthophoto_file}" output=ortho --overwrite
|
||||
|
||||
# Import crop area
|
||||
v.in.ogr input="${crop_area_file}" output=crop_area --overwrite
|
||||
|
||||
g.region vector=crop_area
|
||||
|
||||
# Generate cutlines
|
||||
i.cutlines.py --overwrite input=ortho output=cutline number_lines=16 edge_detection=zc no_edge_friction=10 lane_border_multiplier=100 tile_width=1024 tile_height=1024 overlap=20 processes=${max_concurrency} memory=${memory}
|
||||
|
||||
#v.out.ogr input=cutline output="cutline_raw.gpkg" format=GPKG
|
||||
|
||||
# Select cutlines that are within crop area
|
||||
v.select ainput=cutline binput=crop_area output=result operator=within
|
||||
|
||||
# Export
|
||||
v.out.ogr input=result output="result.gpkg" format=GPKG
|
||||
|
||||
# Merge all geometries, select only the largest one (remove islands)
|
||||
ogr2ogr -f GPKG -overwrite -explodecollections -dialect SQLite -sql "SELECT ST_Union(geom) FROM result ORDER BY ST_AREA(geom) DESC LIMIT 1" cutline.gpkg result.gpkg
|
||||
|
||||
# Add new line output in case the last command didn't.
|
||||
echo ""
|
||||
|
||||
if [ -e "cutline.gpkg" ]; then
|
||||
echo "$$(pwd)/cutline.gpkg"
|
||||
else
|
||||
echo "error"
|
||||
fi
|
|
@ -1,58 +0,0 @@
|
|||
# orthophoto_files: comma-separated GeoTIFF file paths
|
||||
# max_concurrency: maximum number of parallel processes to use
|
||||
# memory: maximum MB of memory to use
|
||||
# ------
|
||||
# output: If successful, prints the full path to the cutlines file. Otherwise it prints "error"
|
||||
|
||||
# Split string using ',' separator
|
||||
IFS=',' read -ra DST <<< "${orthophoto_files}"
|
||||
ORTHOPHOTO_FILES=("$${DST[@]}")
|
||||
|
||||
i=0
|
||||
existing_cutlines=""
|
||||
current_rasters=""
|
||||
|
||||
for orthophoto_file in "$${ORTHOPHOTO_FILES[@]}"; do
|
||||
|
||||
# Import orthophoto (green band only)
|
||||
r.external band=2 input="$$orthophoto_file" output=ortho$$i --overwrite
|
||||
|
||||
# Generate polygon area
|
||||
gdal_polygonize.py -b 4 -f GeoPKG
|
||||
|
||||
# Set nodata
|
||||
r.null map=ortho$$i setnull=0
|
||||
|
||||
current_rasters="ortho$$i,$$current_rasters"
|
||||
g.region raster="$$current_rasters"
|
||||
|
||||
# Generate cutlines
|
||||
i.cutlines.py --overwrite input=ortho$$i output=cutline$$i number_lines=4 edge_detection=zc existing_cutlines=$$existing_cutlines processes=${max_concurrency} memory=${memory}
|
||||
|
||||
# TODO: use below for values (canny, etc.)
|
||||
#i.cutlines.py --overwrite input=ortho2.blue@PERMANENT output=cutline number_lines=16 edge_detection=canny no_edge_friction=10 lane_border_multiplier=100 processes=1 memory=300
|
||||
|
||||
|
||||
# TODO select only polygons within safe area
|
||||
#v.select ainput=cutline -binput=area -output=result operator=within
|
||||
|
||||
# TODO add these too
|
||||
# GRASS commands for dissolve don't seem to work as expected
|
||||
#ogr2ogr -f GPKG -overwrite -explodecollections -dialect SQLite -sql "SELECT ST_Union(geom) FROM result" -nln dissolved dissolved.gpkg result.gpkg
|
||||
#ogr2ogr -f GPKG -overwrite -dialect SQLITE -sql "SELECT * FROM dissolved ORDER BY ST_AREA(geom) DESC LIMIT 1" -nln cutline cutline.gpkg dissolved.gpkg
|
||||
|
||||
# Export
|
||||
v.out.ogr input="cutline$$i" output="cutline$$i.gpkg" format=GPKG
|
||||
|
||||
# Prepend cutline to list of cutlines
|
||||
existing_cutlines="cutline$$i,$$existing_cutlines"
|
||||
|
||||
# Next
|
||||
i=$$[i+1]
|
||||
done
|
||||
|
||||
if [ -e "cutline0.gpkg" ]; then
|
||||
echo "$$(pwd)/cutline0.gpkg"
|
||||
else
|
||||
echo "error"
|
||||
fi
|
|
@ -152,8 +152,9 @@ def get_submodel_argv(args, submodels_path, submodel_name):
|
|||
"""
|
||||
:return the same as argv, but removing references to --split,
|
||||
setting/replacing --project-path and name
|
||||
setting/replacing --crop to 0.01 (always crop on submodels)
|
||||
setting/replacing --crop (always crop on submodels)
|
||||
removing --rerun-from, --rerun, --rerun-all
|
||||
adding --compute-cutline
|
||||
"""
|
||||
argv = sys.argv
|
||||
|
||||
|
@ -162,6 +163,7 @@ def get_submodel_argv(args, submodels_path, submodel_name):
|
|||
project_path_found = False
|
||||
project_name_added = False
|
||||
crop_found = False
|
||||
compute_cutline_found = False
|
||||
|
||||
# TODO: what about GCP paths?
|
||||
|
||||
|
@ -184,9 +186,13 @@ def get_submodel_argv(args, submodels_path, submodel_name):
|
|||
i += 2
|
||||
elif arg == '--crop':
|
||||
result.append(arg)
|
||||
result.append("0.01")
|
||||
result.append(argv[i + 1])
|
||||
crop_found = True
|
||||
i += 2
|
||||
elif arg == '--compute-cutline':
|
||||
result.append(arg)
|
||||
compute_cutline_found = True
|
||||
i += 1
|
||||
elif arg == '--split':
|
||||
i += 2
|
||||
elif arg == '--rerun-from':
|
||||
|
@ -205,10 +211,13 @@ def get_submodel_argv(args, submodels_path, submodel_name):
|
|||
|
||||
if not crop_found:
|
||||
result.append('--crop')
|
||||
result.append('0.01')
|
||||
result.append('3')
|
||||
|
||||
if not project_name_added:
|
||||
result.append(submodel_name)
|
||||
|
||||
if not compute_cutline_found:
|
||||
result.append("--compute-cutline")
|
||||
|
||||
return result
|
||||
|
||||
|
@ -226,4 +235,29 @@ def get_submodel_paths(submodels_path, *paths):
|
|||
else:
|
||||
log.ODM_WARNING("Missing %s from submodel %s" % (p, f))
|
||||
|
||||
return result
|
||||
|
||||
def get_all_submodel_paths(submodels_path, *all_paths):
|
||||
"""
|
||||
:return Existing, multiple paths for all submodels as a nested list (all or nothing for each submodel)
|
||||
if a single file is missing from the submodule, no files are returned for that submodel.
|
||||
|
||||
(i.e. get_multi_submodel_paths("path/", "odm_orthophoto.tif", "dem.tif")) -->
|
||||
[["path/submodel_0000/odm_orthophoto.tif", "path/submodel_0000/dem.tif"],
|
||||
["path/submodel_0001/odm_orthophoto.tif", "path/submodel_0001/dem.tif"]]
|
||||
"""
|
||||
result = []
|
||||
for f in os.listdir(submodels_path):
|
||||
if f.startswith('submodel'):
|
||||
all_found = True
|
||||
|
||||
for ap in all_paths:
|
||||
p = os.path.join(submodels_path, f, ap)
|
||||
if not os.path.exists(p):
|
||||
log.ODM_WARNING("Missing %s from submodel %s" % (p, f))
|
||||
all_found = False
|
||||
|
||||
if all_found:
|
||||
result.append([os.path.join(submodels_path, f, ap) for ap in all_paths])
|
||||
|
||||
return result
|
|
@ -26,6 +26,8 @@ def exit_gracefully(signum, frame):
|
|||
for sp in running_subprocesses:
|
||||
log.ODM_WARNING("Sending TERM signal to PID %s..." % sp.pid)
|
||||
os.killpg(os.getpgid(sp.pid), signal.SIGTERM)
|
||||
|
||||
exit(1)
|
||||
|
||||
signal.signal(signal.SIGINT, exit_gracefully)
|
||||
signal.signal(signal.SIGTERM, exit_gracefully)
|
||||
|
|
|
@ -8,6 +8,7 @@ from opendm import types
|
|||
from opendm import gsd
|
||||
from opendm.concurrency import get_max_memory
|
||||
from opendm.cropper import Cropper
|
||||
from opendm.cutline import compute_cutline
|
||||
|
||||
|
||||
class ODMOrthoPhotoStage(types.ODM_Stage):
|
||||
|
@ -114,8 +115,17 @@ class ODMOrthoPhotoStage(types.ODM_Stage):
|
|||
'--config GDAL_CACHEMAX {max_memory}% '
|
||||
'{png} {tiff} > {log}'.format(**kwargs))
|
||||
|
||||
bounds_file_path = os.path.join(tree.odm_georeferencing, 'odm_georeferenced_model.bounds.gpkg')
|
||||
|
||||
# Cutline computation, before cropping
|
||||
# We want to use the full orthophoto, not the cropped one.
|
||||
if args.compute_cutline:
|
||||
compute_cutline(tree.odm_orthophoto_tif,
|
||||
bounds_file_path,
|
||||
os.path.join(tree.odm_orthophoto, "cutline.gpkg"),
|
||||
self.params.get('max_concurrency'))
|
||||
|
||||
if args.crop > 0:
|
||||
bounds_file_path = os.path.join(tree.odm_georeferencing, 'odm_georeferenced_model.bounds.gpkg')
|
||||
Cropper.crop(bounds_file_path, tree.odm_orthophoto_tif, {
|
||||
'TILED': 'NO' if self.params.get('no_tiled') else 'YES',
|
||||
'COMPRESS': self.params.get('compress'),
|
||||
|
|
|
@ -99,7 +99,7 @@ class ODMSplitStage(types.ODM_Stage):
|
|||
if io.file_exists(main_recon):
|
||||
os.remove(main_recon)
|
||||
|
||||
os.rename(aligned_recon, main_recon)
|
||||
shutil.move(aligned_recon, main_recon)
|
||||
log.ODM_DEBUG("%s is now %s" % (aligned_recon, main_recon))
|
||||
|
||||
log.ODM_INFO("========================")
|
||||
|
@ -121,8 +121,6 @@ class ODMSplitStage(types.ODM_Stage):
|
|||
|
||||
class ODMMergeStage(types.ODM_Stage):
|
||||
def process(self, args, outputs):
|
||||
from opendm.grass_engine import grass
|
||||
|
||||
tree = outputs['tree']
|
||||
reconstruction = outputs['reconstruction']
|
||||
|
||||
|
@ -131,28 +129,19 @@ class ODMMergeStage(types.ODM_Stage):
|
|||
# all_point_clouds = get_submodel_paths(tree.submodels_path, "odm_georeferencing", "odm_georeferenced_model.laz")
|
||||
# pdal.merge_point_clouds(all_point_clouds, tree.odm_georeferencing_model_laz, args.verbose)
|
||||
|
||||
#
|
||||
|
||||
# Merge orthophotos
|
||||
all_orthophotos = get_submodel_paths(tree.submodels_path, "odm_orthophoto", "odm_orthophoto.tif")
|
||||
if len(all_orthophotos) > 1:
|
||||
gctx = grass.create_context({'auto_cleanup' : False})
|
||||
all_orthos_and_cutlines = get_all_submodel_paths(tree.submodels_path,
|
||||
os.path.join("odm_orthophoto", "odm_orthophoto.tif"),
|
||||
os.path.join("odm_orthophoto", "cutline.gpkg"),
|
||||
)
|
||||
|
||||
gctx.add_param('orthophoto_files', ",".join(all_orthophotos))
|
||||
gctx.add_param('max_concurrency', args.max_concurrency)
|
||||
gctx.add_param('memory', int(concurrency.get_max_memory_mb(300)))
|
||||
gctx.set_location(all_orthophotos[0])
|
||||
|
||||
cutline_file = gctx.execute(os.path.join("opendm", "grass", "generate_cutlines.grass"))
|
||||
if cutline_file != 'error':
|
||||
log.ODM_INFO("YAY")
|
||||
log.ODM_INFO(cutline_file)
|
||||
else:
|
||||
log.ODM_WARNING("Could not generate orthophoto cutlines. An error occured when running GRASS. No orthophoto will be generated.")
|
||||
elif len(all_orthophotos) == 1:
|
||||
if len(all_orthos_and_cutlines) > 1:
|
||||
pass
|
||||
|
||||
elif len(all_orthos_and_cutlines) == 1:
|
||||
# Simply copy
|
||||
log.ODM_WARNING("A single orthophoto was found between all submodels.")
|
||||
shutil.copyfile(all_orthophotos[0], tree.odm_orthophoto_tif)
|
||||
shutil.copyfile(all_orthos_and_cutlines[0][0], tree.odm_orthophoto_tif)
|
||||
else:
|
||||
log.ODM_WARNING("No orthophotos were found in any of the submodels. No orthophoto will be generated.")
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue