kopia lustrzana https://github.com/OpenDroneMap/ODM
odm_orthophoto module refactoring, orthophoto merge POC working
rodzic
5a8284c5cc
commit
99c8356d24
|
@ -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)
|
||||
|
|
|
@ -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))
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue