Added --compute-cutline param, close to working

pull/979/head
Piero Toffanin 2019-04-27 18:37:07 -04:00
rodzic 078a6a0678
commit e887296d13
8 zmienionych plików z 142 dodań i 83 usunięć

Wyświetl plik

@ -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

32
opendm/cutline.py 100644
Wyświetl plik

@ -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))

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -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'),

Wyświetl plik

@ -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.")