2019-04-22 19:14:39 +00:00
|
|
|
import os
|
2015-12-01 16:26:13 +00:00
|
|
|
|
|
|
|
from opendm import io
|
|
|
|
from opendm import log
|
|
|
|
from opendm import system
|
|
|
|
from opendm import context
|
2016-02-23 17:47:43 +00:00
|
|
|
from opendm import types
|
2018-08-08 19:41:08 +00:00
|
|
|
from opendm import gsd
|
2019-04-28 16:20:03 +00:00
|
|
|
from opendm import orthophoto
|
2018-12-12 19:24:27 +00:00
|
|
|
from opendm.concurrency import get_max_memory
|
2019-04-27 22:37:07 +00:00
|
|
|
from opendm.cutline import compute_cutline
|
2019-12-18 17:46:25 +00:00
|
|
|
from pipes import quote
|
2015-12-01 16:26:13 +00:00
|
|
|
|
2019-04-22 19:14:39 +00:00
|
|
|
class ODMOrthoPhotoStage(types.ODM_Stage):
|
|
|
|
def process(self, args, outputs):
|
|
|
|
tree = outputs['tree']
|
|
|
|
reconstruction = outputs['reconstruction']
|
2019-04-28 16:20:03 +00:00
|
|
|
verbose = '-verbose' if args.verbose else ''
|
2015-12-01 16:26:13 +00:00
|
|
|
|
|
|
|
# define paths and create working directories
|
2015-12-10 11:01:41 +00:00
|
|
|
system.mkdir_p(tree.odm_orthophoto)
|
2015-12-01 16:26:13 +00:00
|
|
|
|
2019-10-24 15:48:21 +00:00
|
|
|
if not io.file_exists(tree.odm_orthophoto_tif) or self.rerun():
|
2015-12-10 11:01:41 +00:00
|
|
|
|
2016-02-23 17:47:43 +00:00
|
|
|
# odm_orthophoto definitions
|
2015-12-10 11:01:41 +00:00
|
|
|
kwargs = {
|
|
|
|
'bin': context.odm_modules_path,
|
|
|
|
'log': tree.odm_orthophoto_log,
|
2019-10-24 15:48:21 +00:00
|
|
|
'ortho': tree.odm_orthophoto_render,
|
2015-12-10 11:01:41 +00:00
|
|
|
'corners': tree.odm_orthophoto_corners,
|
2019-04-28 16:20:03 +00:00
|
|
|
'res': 1.0 / (gsd.cap_resolution(args.orthophoto_resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd) / 100.0),
|
2016-12-11 22:16:11 +00:00
|
|
|
'verbose': verbose
|
2015-12-10 11:01:41 +00:00
|
|
|
}
|
2015-12-01 16:52:18 +00:00
|
|
|
|
2018-04-25 14:00:56 +00:00
|
|
|
# Check if the georef object is initialized
|
|
|
|
# (during a --rerun this might not be)
|
2019-06-20 19:39:49 +00:00
|
|
|
# TODO: this should be moved to a more central location?
|
|
|
|
if reconstruction.is_georeferenced() and not reconstruction.georef.valid_utm_offsets():
|
2019-02-15 17:15:01 +00:00
|
|
|
georeferencing_dir = tree.odm_georeferencing if args.use_3dmesh and not args.skip_3dmodel else tree.odm_25dgeoreferencing
|
|
|
|
odm_georeferencing_model_txt_geo_file = os.path.join(georeferencing_dir, tree.odm_georeferencing_model_txt_geo)
|
2018-04-25 14:00:56 +00:00
|
|
|
|
|
|
|
if io.file_exists(odm_georeferencing_model_txt_geo_file):
|
2019-06-20 19:39:49 +00:00
|
|
|
reconstruction.georef.extract_offsets(odm_georeferencing_model_txt_geo_file)
|
2018-04-25 14:00:56 +00:00
|
|
|
else:
|
|
|
|
log.ODM_WARNING('Cannot read UTM offset from {}. An orthophoto will not be generated.'.format(odm_georeferencing_model_txt_geo_file))
|
|
|
|
|
2019-12-18 17:46:25 +00:00
|
|
|
models = []
|
|
|
|
|
|
|
|
if args.use_3dmesh:
|
|
|
|
base_dir = tree.odm_texturing
|
|
|
|
else:
|
|
|
|
base_dir = tree.odm_25dtexturing
|
|
|
|
|
2019-06-20 19:39:49 +00:00
|
|
|
if reconstruction.is_georeferenced():
|
2019-12-18 17:46:25 +00:00
|
|
|
model_file = tree.odm_georeferencing_model_obj_geo
|
2018-07-03 16:37:39 +00:00
|
|
|
else:
|
2019-12-18 17:46:25 +00:00
|
|
|
model_file = tree.odm_textured_model_obj
|
|
|
|
|
|
|
|
if reconstruction.multi_camera:
|
|
|
|
for band in reconstruction.multi_camera:
|
|
|
|
primary = band == reconstruction.multi_camera[0]
|
|
|
|
subdir = ""
|
|
|
|
if not primary:
|
|
|
|
subdir = band['name'].lower()
|
|
|
|
models.append(os.path.join(base_dir, subdir, model_file))
|
|
|
|
else:
|
|
|
|
models.append(os.path.join(base_dir, model_file))
|
|
|
|
|
|
|
|
kwargs['models'] = ','.join(map(quote, models))
|
2016-12-20 15:10:14 +00:00
|
|
|
|
2016-02-23 17:47:43 +00:00
|
|
|
# run odm_orthophoto
|
2019-12-18 17:46:25 +00:00
|
|
|
system.run('{bin}/odm_orthophoto -inputFiles {models} '
|
2016-12-11 22:16:11 +00:00
|
|
|
'-logFile {log} -outputFile {ortho} -resolution {res} {verbose} '
|
2016-02-26 18:50:12 +00:00
|
|
|
'-outputCornerFile {corners}'.format(**kwargs))
|
2016-02-23 17:47:43 +00:00
|
|
|
|
2018-01-30 20:04:26 +00:00
|
|
|
# Create georeferenced GeoTiff
|
|
|
|
geotiffcreated = False
|
|
|
|
|
2019-06-20 19:39:49 +00:00
|
|
|
if reconstruction.is_georeferenced() and reconstruction.georef.valid_utm_offsets():
|
2018-01-30 20:04:26 +00:00
|
|
|
ulx = uly = lrx = lry = 0.0
|
|
|
|
with open(tree.odm_orthophoto_corners) as f:
|
|
|
|
for lineNumber, line in enumerate(f):
|
|
|
|
if lineNumber == 0:
|
|
|
|
tokens = line.split(' ')
|
|
|
|
if len(tokens) == 4:
|
|
|
|
ulx = float(tokens[0]) + \
|
2019-06-20 19:39:49 +00:00
|
|
|
float(reconstruction.georef.utm_east_offset)
|
2018-01-30 20:04:26 +00:00
|
|
|
lry = float(tokens[1]) + \
|
2019-06-20 19:39:49 +00:00
|
|
|
float(reconstruction.georef.utm_north_offset)
|
2018-01-30 20:04:26 +00:00
|
|
|
lrx = float(tokens[2]) + \
|
2019-06-20 19:39:49 +00:00
|
|
|
float(reconstruction.georef.utm_east_offset)
|
2018-01-30 20:04:26 +00:00
|
|
|
uly = float(tokens[3]) + \
|
2019-06-20 19:39:49 +00:00
|
|
|
float(reconstruction.georef.utm_north_offset)
|
2018-01-30 20:04:26 +00:00
|
|
|
log.ODM_INFO('Creating GeoTIFF')
|
|
|
|
|
2019-04-28 16:20:03 +00:00
|
|
|
orthophoto_vars = orthophoto.get_orthophoto_vars(args)
|
|
|
|
|
2018-01-30 20:04:26 +00:00
|
|
|
kwargs = {
|
|
|
|
'ulx': ulx,
|
|
|
|
'uly': uly,
|
|
|
|
'lrx': lrx,
|
|
|
|
'lry': lry,
|
2019-04-28 16:20:03 +00:00
|
|
|
'vars': ' '.join(['-co %s=%s' % (k, orthophoto_vars[k]) for k in orthophoto_vars]),
|
2019-06-20 19:39:49 +00:00
|
|
|
'proj': reconstruction.georef.proj4(),
|
2019-10-24 15:48:21 +00:00
|
|
|
'input': tree.odm_orthophoto_render,
|
|
|
|
'output': tree.odm_orthophoto_tif,
|
2018-03-19 13:08:22 +00:00
|
|
|
'log': tree.odm_orthophoto_tif_log,
|
2018-12-12 19:24:27 +00:00
|
|
|
'max_memory': get_max_memory(),
|
2018-01-30 20:04:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
system.run('gdal_translate -a_ullr {ulx} {uly} {lrx} {lry} '
|
2019-04-28 16:20:03 +00:00
|
|
|
'{vars} '
|
2018-01-30 20:04:26 +00:00
|
|
|
'-a_srs \"{proj}\" '
|
2018-03-27 13:36:08 +00:00
|
|
|
'--config GDAL_CACHEMAX {max_memory}% '
|
2019-10-24 20:07:19 +00:00
|
|
|
'--config GDAL_TIFF_INTERNAL_MASK YES '
|
2019-10-24 15:48:21 +00:00
|
|
|
'{input} {output} > {log}'.format(**kwargs))
|
2018-01-30 20:04:26 +00:00
|
|
|
|
2019-04-27 22:37:07 +00:00
|
|
|
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.
|
2019-04-29 21:35:12 +00:00
|
|
|
if args.orthophoto_cutline:
|
2019-10-28 18:40:40 +00:00
|
|
|
cutline_file = os.path.join(tree.odm_orthophoto, "cutline.gpkg")
|
|
|
|
|
2019-04-27 22:37:07 +00:00
|
|
|
compute_cutline(tree.odm_orthophoto_tif,
|
|
|
|
bounds_file_path,
|
2019-10-28 18:40:40 +00:00
|
|
|
cutline_file,
|
2019-05-01 12:46:53 +00:00
|
|
|
args.max_concurrency,
|
2019-05-30 12:53:25 +00:00
|
|
|
tmpdir=os.path.join(tree.odm_orthophoto, "grass_cutline_tmpdir"),
|
|
|
|
scale=0.25)
|
2019-04-27 22:37:07 +00:00
|
|
|
|
2019-10-28 18:40:40 +00:00
|
|
|
orthophoto.compute_mask_raster(tree.odm_orthophoto_tif, cutline_file,
|
|
|
|
os.path.join(tree.odm_orthophoto, "odm_orthophoto_cut.tif"),
|
|
|
|
blend_distance=20, only_max_coords_feature=True)
|
|
|
|
|
2019-10-24 20:07:19 +00:00
|
|
|
orthophoto.post_orthophoto_steps(args, bounds_file_path, tree.odm_orthophoto_tif)
|
2018-01-30 20:04:26 +00:00
|
|
|
|
|
|
|
geotiffcreated = True
|
|
|
|
if not geotiffcreated:
|
|
|
|
log.ODM_WARNING('No geo-referenced orthophoto created due '
|
|
|
|
'to missing geo-referencing or corner coordinates.')
|
2016-02-23 17:47:43 +00:00
|
|
|
|
2015-12-01 16:52:18 +00:00
|
|
|
else:
|
2019-10-24 15:48:21 +00:00
|
|
|
log.ODM_WARNING('Found a valid orthophoto in: %s' % tree.odm_orthophoto_tif)
|