odm_orthophoto module refactoring, orthophoto merge POC working

pull/979/head
Piero Toffanin 2019-04-28 12:20:03 -04:00
rodzic 5a8284c5cc
commit 99c8356d24
8 zmienionych plików z 152 dodań i 95 usunięć

Wyświetl plik

@ -72,17 +72,19 @@ class Cropper:
geomcol = ogr.Geometry(ogr.wkbGeometryCollection)
driver = ogr.GetDriverByName('GPKG')
srs = None
for input_bound_file in input_bound_files:
ds = driver.Open(input_bound_file, 0) # ready-only
layer = ds.GetLayer()
srs = layer.GetSpatialRef()
# Collect all Geometry
for feature in layer:
geomcol.AddGeometry(feature.GetGeometryRef())
# Close
ds = None
ds = None
# Calculate convex hull
convexhull = geomcol.ConvexHull()
@ -102,7 +104,7 @@ class Cropper:
driver.DeleteDataSource(output_bounds)
out_ds = driver.CreateDataSource(output_bounds)
layer = out_ds.CreateLayer("convexhull", geom_type=ogr.wkbPolygon)
layer = out_ds.CreateLayer("convexhull", srs=srs, geom_type=ogr.wkbPolygon)
feature_def = layer.GetLayerDefn()
feature = ogr.Feature(feature_def)
@ -113,7 +115,6 @@ class Cropper:
# Save and close output data source
out_ds = None
def create_bounds_geojson(self, pointcloud_path, buffer_distance = 0, decimation_step=40):
"""
Compute a buffered polygon around the data extents (not just a bounding box)

Wyświetl plik

@ -0,0 +1,23 @@
from opendm import log
from opendm import system
def get_orthophoto_vars(args):
return {
'TILED': 'NO' if args.orthophoto_no_tiled else 'YES',
'COMPRESS': args.orthophoto_compression,
'PREDICTOR': '2' if args.orthophoto_compression in ['LZW', 'DEFLATE'] else '1',
'BIGTIFF': args.orthophoto_bigtiff,
'BLOCKXSIZE': 512,
'BLOCKYSIZE': 512,
'NUM_THREADS': args.max_concurrency
}
def build_overviews(orthophoto_file):
log.ODM_DEBUG("Building Overviews")
kwargs = {'orthophoto': orthophoto_file}
# Run gdaladdo
system.run('gdaladdo -ro -r average '
'--config BIGTIFF_OVERVIEW IF_SAFER '
'--config COMPRESS_OVERVIEW JPEG '
'{orthophoto} 2 4 8 16'.format(**kwargs))

Wyświetl plik

@ -152,7 +152,6 @@ 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 (always crop on submodels)
removing --rerun-from, --rerun, --rerun-all
adding --compute-cutline
"""
@ -162,7 +161,6 @@ def get_submodel_argv(args, submodels_path, submodel_name):
i = 1
project_path_found = False
project_name_added = False
crop_found = False
compute_cutline_found = False
# TODO: what about GCP paths?
@ -184,11 +182,6 @@ def get_submodel_argv(args, submodels_path, submodel_name):
result.append(submodels_path)
project_path_found = True
i += 2
elif arg == '--crop':
result.append(arg)
result.append(argv[i + 1])
crop_found = True
i += 2
elif arg == '--compute-cutline':
result.append(arg)
compute_cutline_found = True
@ -209,10 +202,6 @@ def get_submodel_argv(args, submodels_path, submodel_name):
result.append('--project-path')
result.append(submodel_project_path)
if not crop_found:
result.append('--crop')
result.append('3')
if not project_name_added:
result.append(submodel_name)

Wyświetl plik

@ -312,7 +312,6 @@ class ODM_Tree(object):
self.odm_orthophoto_corners = io.join_paths(self.odm_orthophoto, 'odm_orthophoto_corners.txt')
self.odm_orthophoto_log = io.join_paths(self.odm_orthophoto, 'odm_orthophoto_log.txt')
self.odm_orthophoto_tif_log = io.join_paths(self.odm_orthophoto, 'gdal_translate_log.txt')
self.odm_orthophoto_gdaladdo_log = io.join_paths(self.odm_orthophoto, 'gdaladdo_log.txt')
# Split-merge
self.submodels_path = io.join_paths(self.root_path, 'submodels')

Wyświetl plik

@ -57,14 +57,7 @@ class ODMApp:
dem = ODMDEMStage('odm_dem', args,
max_concurrency=args.max_concurrency,
verbose=args.verbose)
orthophoto = ODMOrthoPhotoStage('odm_orthophoto', args,
resolution=args.orthophoto_resolution,
no_tiled=args.orthophoto_no_tiled,
compress=args.orthophoto_compression,
bigtiff=args.orthophoto_bigtiff,
build_overviews=args.build_overviews,
max_concurrency=args.max_concurrency,
verbose=args.verbose)
orthophoto = ODMOrthoPhotoStage('odm_orthophoto', args)
if not args.video:
# Normal pipeline

Wyświetl plik

@ -6,6 +6,7 @@ from opendm import system
from opendm import context
from opendm import types
from opendm import gsd
from opendm import orthophoto
from opendm.concurrency import get_max_memory
from opendm.cropper import Cropper
from opendm.cutline import compute_cutline
@ -15,7 +16,7 @@ class ODMOrthoPhotoStage(types.ODM_Stage):
def process(self, args, outputs):
tree = outputs['tree']
reconstruction = outputs['reconstruction']
verbose = '-verbose' if self.params.get('verbose') else ''
verbose = '-verbose' if args.verbose else ''
# define paths and create working directories
system.mkdir_p(tree.odm_orthophoto)
@ -28,7 +29,7 @@ class ODMOrthoPhotoStage(types.ODM_Stage):
'log': tree.odm_orthophoto_log,
'ortho': tree.odm_orthophoto_file,
'corners': tree.odm_orthophoto_corners,
'res': 1.0 / (gsd.cap_resolution(self.params.get('resolution'), tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd) / 100.0),
'res': 1.0 / (gsd.cap_resolution(args.orthophoto_resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd) / 100.0),
'verbose': verbose
}
@ -85,32 +86,23 @@ class ODMOrthoPhotoStage(types.ODM_Stage):
float(georef.utm_north_offset)
log.ODM_INFO('Creating GeoTIFF')
orthophoto_vars = orthophoto.get_orthophoto_vars(args)
kwargs = {
'ulx': ulx,
'uly': uly,
'lrx': lrx,
'lry': lry,
'tiled': '' if self.params.get('no_tiled') else '-co TILED=yes ',
'compress': self.params.get('compress'),
'predictor': '-co PREDICTOR=2 ' if self.params.get('compress') in
['LZW', 'DEFLATE'] else '',
'vars': ' '.join(['-co %s=%s' % (k, orthophoto_vars[k]) for k in orthophoto_vars]),
'proj': georef.projection.srs,
'bigtiff': self.params.get('bigtiff'),
'png': tree.odm_orthophoto_file,
'tiff': tree.odm_orthophoto_tif,
'log': tree.odm_orthophoto_tif_log,
'max_memory': get_max_memory(),
'threads': self.params.get('max_concurrency')
}
system.run('gdal_translate -a_ullr {ulx} {uly} {lrx} {lry} '
'{tiled} '
'-co BIGTIFF={bigtiff} '
'-co COMPRESS={compress} '
'{predictor} '
'-co BLOCKXSIZE=512 '
'-co BLOCKYSIZE=512 '
'-co NUM_THREADS={threads} '
'{vars} '
'-a_srs \"{proj}\" '
'--config GDAL_CACHEMAX {max_memory}% '
'{png} {tiff} > {log}'.format(**kwargs))
@ -123,30 +115,13 @@ class ODMOrthoPhotoStage(types.ODM_Stage):
compute_cutline(tree.odm_orthophoto_tif,
bounds_file_path,
os.path.join(tree.odm_orthophoto, "cutline.gpkg"),
self.params.get('max_concurrency'))
args.max_concurrency)
if args.crop > 0:
Cropper.crop(bounds_file_path, tree.odm_orthophoto_tif, {
'TILED': 'NO' if self.params.get('no_tiled') else 'YES',
'COMPRESS': self.params.get('compress'),
'PREDICTOR': '2' if self.params.get('compress') in ['LZW', 'DEFLATE'] else '1',
'BIGTIFF': self.params.get('bigtiff'),
'BLOCKXSIZE': 512,
'BLOCKYSIZE': 512,
'NUM_THREADS': self.params.get('max_concurrency')
})
Cropper.crop(bounds_file_path, tree.odm_orthophoto_tif, orthophoto_vars)
if self.params.get('build_overviews'):
log.ODM_DEBUG("Building Overviews")
kwargs = {
'orthophoto': tree.odm_orthophoto_tif,
'log': tree.odm_orthophoto_gdaladdo_log
}
# Run gdaladdo
system.run('gdaladdo -ro -r average '
'--config BIGTIFF_OVERVIEW IF_SAFER '
'--config COMPRESS_OVERVIEW JPEG '
'{orthophoto} 2 4 8 16 > {log}'.format(**kwargs))
if args.build_overviews:
orthophoto.build_overviews(tree.odm_orthophoto_tif)
geotiffcreated = True
if not geotiffcreated:

Wyświetl plik

@ -53,23 +53,23 @@ class ODMOpenSfMStage(types.ODM_Stage):
else:
log.ODM_WARNING("Found an undistorted directory in %s" % undistorted_images_path)
# Skip dense reconstruction if necessary and export
# sparse reconstruction instead
if args.fast_orthophoto:
if not io.file_exists(output_file) or self.rerun():
octx.run('export_ply --no-cameras' % image_scale)
else:
log.ODM_WARNING("Found a valid PLY reconstruction in %s" % output_file)
# Skip dense reconstruction if necessary and export
# sparse reconstruction instead
if args.fast_orthophoto:
if not io.file_exists(output_file) or self.rerun():
octx.run('export_ply --no-cameras' % image_scale)
else:
log.ODM_WARNING("Found a valid PLY reconstruction in %s" % output_file)
elif args.use_opensfm_dense:
# Undistort images at full scale in JPG
# (TODO: we could compare the size of the PNGs if they are < than depthmap_resolution
# and use those instead of re-exporting full resolution JPGs)
if not io.file_exists(output_file) or self.rerun():
octx.run('undistort')
octx.run('compute_depthmaps')
else:
log.ODM_WARNING("Found a valid dense reconstruction in %s" % output_file)
elif args.use_opensfm_dense:
# Undistort images at full scale in JPG
# (TODO: we could compare the size of the PNGs if they are < than depthmap_resolution
# and use those instead of re-exporting full resolution JPGs)
if not io.file_exists(output_file) or self.rerun():
octx.run('undistort')
octx.run('compute_depthmaps')
else:
log.ODM_WARNING("Found a valid dense reconstruction in %s" % output_file)
# check if reconstruction was exported to bundler before
octx.export_bundler(tree.opensfm_bundle_list, self.rerun())

Wyświetl plik

@ -1,13 +1,15 @@
import os
import shutil
from opendm import log
from opendm.osfm import OSFMContext, get_submodel_argv, get_submodel_paths
from opendm.osfm import OSFMContext, get_submodel_argv, get_submodel_paths, get_all_submodel_paths
from opendm import types
from opendm import io
from opendm import system
from opendm import orthophoto
from opendm.dem import pdal
from opensfm.large import metadataset
from opendm import concurrency
from opendm.cropper import Cropper
from opendm.concurrency import get_max_memory
from pipes import quote
class ODMSplitStage(types.ODM_Stage):
@ -126,26 +128,101 @@ class ODMMergeStage(types.ODM_Stage):
if outputs['large']:
# Merge point clouds
# 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)
if not io.file_exists(tree.odm_georeferencing_model_laz) or self.rerun():
pass
# 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)
else:
log.ODM_WARNING("Found merged point cloud in %s" % tree.odm_georeferencing_model_laz)
# Merge crop bounds
merged_bounds_file = os.path.join(tree.odm_georeferencing, 'odm_georeferenced_model.bounds.gpkg')
if not io.file_exists(merged_bounds_file) or self.rerun():
all_bounds = get_submodel_paths(tree.submodels_path, 'odm_georeferencing', 'odm_georeferenced_model.bounds.gpkg')
log.ODM_DEBUG("Merging all crop bounds: %s" % all_bounds)
if len(all_bounds) > 0:
# Calculate a new crop area
# based on the convex hull of all crop areas of all submodels
# (without a buffer, otherwise we are double-cropping)
Cropper.merge_bounds(all_bounds, merged_bounds_file, 0)
else:
log.ODM_WARNING("No bounds found for any submodel.")
# Merge orthophotos
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"),
)
if not io.file_exists(tree.odm_orthophoto_tif) or self.rerun():
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"),
)
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_orthos_and_cutlines[0][0], tree.odm_orthophoto_tif)
if len(all_orthos_and_cutlines) > 1:
log.ODM_DEBUG("Found %s submodels with valid orthophotos and cutlines" % len(all_orthos_and_cutlines))
merged_geotiff = os.path.join(tree.odm_orthophoto, "odm_orthophoto.merged.tif")
kwargs = {
'orthophoto_merged': merged_geotiff,
'input_files': ' '.join(map(lambda i: quote(i[0]), all_orthos_and_cutlines)),
}
# use bounds as cutlines (blending)
if io.file_exists(merged_geotiff):
os.remove(merged_geotiff)
system.run('gdal_merge.py -o {orthophoto_merged} '
'-createonly '
'-co "BIGTIFF=YES" '
'-co "BLOCKXSIZE=512" '
'-co "BLOCKYSIZE=512" '
'{input_files} '.format(**kwargs)
)
for ortho_cutline in all_orthos_and_cutlines:
kwargs['input_file'], kwargs['cutline'] = ortho_cutline
system.run('gdalwarp -cutline {cutline} '
#'-cblend 2 '
'-r lanczos -multi '
' {input_file} {orthophoto_merged}'.format(**kwargs)
)
# Apply orthophoto settings (compression, tiling, etc.)
orthophoto_vars = orthophoto.get_orthophoto_vars(args)
if io.file_exists(tree.odm_orthophoto_tif):
os.remove(tree.odm_orthophoto_tif)
kwargs = {
'vars': ' '.join(['-co %s=%s' % (k, orthophoto_vars[k]) for k in orthophoto_vars]),
'max_memory': get_max_memory(),
'merged': merged_geotiff,
'log': tree.odm_orthophoto_tif_log,
'orthophoto': tree.odm_orthophoto_tif,
}
system.run('gdal_translate '
'{vars} '
'--config GDAL_CACHEMAX {max_memory}% '
'{merged} {orthophoto} > {log}'.format(**kwargs))
os.remove(merged_geotiff)
# Crop
if args.crop > 0:
Cropper.crop(merged_bounds_file, tree.odm_orthophoto_tif, orthophoto_vars)
# Overviews
if args.build_overviews:
orthophoto.build_overviews(tree.odm_orthophoto_tif)
elif len(all_orthos_and_cutlines) == 1:
# Simply copy
log.ODM_WARNING("A single orthophoto/cutline pair was found between all submodels.")
shutil.copyfile(all_orthos_and_cutlines[0][0], tree.odm_orthophoto_tif)
else:
log.ODM_WARNING("No orthophoto/cutline pairs were found in any of the submodels. No orthophoto will be generated.")
else:
log.ODM_WARNING("No orthophotos were found in any of the submodels. No orthophoto will be generated.")
# TODO: crop ortho if necessary
log.ODM_WARNING("Found merged orthophoto in %s" % tree.odm_orthophoto_tif)
# Merge DEM