2015-12-01 16:25:14 +00:00
|
|
|
import ecto
|
2016-02-25 19:51:03 +00:00
|
|
|
import csv
|
2016-12-14 18:36:23 +00:00
|
|
|
import os
|
2018-06-04 01:01:04 +00:00
|
|
|
import struct
|
2015-12-01 16:25:14 +00:00
|
|
|
|
|
|
|
from opendm import io
|
|
|
|
from opendm import log
|
2015-12-10 17:17:39 +00:00
|
|
|
from opendm import types
|
2015-12-01 16:25:14 +00:00
|
|
|
from opendm import system
|
|
|
|
from opendm import context
|
2018-01-04 17:08:59 +00:00
|
|
|
from opendm.cropper import Cropper
|
2015-12-01 16:25:14 +00:00
|
|
|
|
2016-02-25 20:02:48 +00:00
|
|
|
|
2015-12-01 16:25:14 +00:00
|
|
|
class ODMGeoreferencingCell(ecto.Cell):
|
2015-12-10 11:01:41 +00:00
|
|
|
def declare_params(self, params):
|
|
|
|
params.declare("gcp_file", 'path to the file containing the ground control '
|
|
|
|
'points used for georeferencing.The file needs to '
|
|
|
|
'be on the following line format: \neasting '
|
|
|
|
'northing height pixelrow pixelcol imagename', 'gcp_list.txt')
|
2016-12-21 20:56:27 +00:00
|
|
|
params.declare("use_exif", 'use exif', False)
|
2016-12-11 22:16:11 +00:00
|
|
|
params.declare("verbose", 'print additional messages to console', False)
|
2015-12-01 16:25:14 +00:00
|
|
|
|
|
|
|
def declare_io(self, params, inputs, outputs):
|
2015-12-10 11:01:41 +00:00
|
|
|
inputs.declare("tree", "Struct with paths", [])
|
2015-12-01 16:25:14 +00:00
|
|
|
inputs.declare("args", "The application arguments.", {})
|
2015-12-10 11:01:41 +00:00
|
|
|
inputs.declare("reconstruction", "list of ODMReconstructions", [])
|
|
|
|
outputs.declare("reconstruction", "list of ODMReconstructions", [])
|
2015-12-01 16:25:14 +00:00
|
|
|
|
|
|
|
def process(self, inputs, outputs):
|
2016-12-14 18:36:23 +00:00
|
|
|
|
2016-02-29 14:45:00 +00:00
|
|
|
# Benchmarking
|
|
|
|
start_time = system.now_raw()
|
2015-12-01 16:25:14 +00:00
|
|
|
|
2016-03-08 18:26:58 +00:00
|
|
|
log.ODM_INFO('Running ODM Georeferencing Cell')
|
2015-12-01 16:25:14 +00:00
|
|
|
|
|
|
|
# get inputs
|
2018-01-26 19:38:26 +00:00
|
|
|
args = inputs.args
|
|
|
|
tree = inputs.tree
|
|
|
|
reconstruction = inputs.reconstruction
|
|
|
|
gcpfile = tree.odm_georeferencing_gcp
|
2018-01-02 20:02:33 +00:00
|
|
|
doPointCloudGeo = True
|
2018-07-03 16:37:39 +00:00
|
|
|
transformPointCloud = True
|
2016-12-11 22:16:11 +00:00
|
|
|
verbose = '-verbose' if self.params.verbose else ''
|
2015-12-01 16:25:14 +00:00
|
|
|
|
2015-12-01 16:52:18 +00:00
|
|
|
# check if we rerun cell or not
|
2016-03-08 18:26:58 +00:00
|
|
|
rerun_cell = (args.rerun is not None and
|
|
|
|
args.rerun == 'odm_georeferencing') or \
|
|
|
|
(args.rerun_all) or \
|
|
|
|
(args.rerun_from is not None and
|
|
|
|
'odm_georeferencing' in args.rerun_from)
|
2015-12-01 16:52:18 +00:00
|
|
|
|
2017-04-05 17:56:48 +00:00
|
|
|
runs = [{
|
2017-04-05 18:27:52 +00:00
|
|
|
'georeferencing_dir': tree.odm_georeferencing,
|
|
|
|
'texturing_dir': tree.odm_texturing,
|
2017-04-05 17:56:48 +00:00
|
|
|
'model': os.path.join(tree.odm_texturing, tree.odm_textured_model_obj)
|
|
|
|
}]
|
2018-01-02 17:38:15 +00:00
|
|
|
|
2018-07-03 16:37:39 +00:00
|
|
|
if args.skip_3dmodel:
|
2018-01-02 17:38:15 +00:00
|
|
|
runs = []
|
|
|
|
|
2018-07-03 16:37:39 +00:00
|
|
|
if not args.use_3dmesh:
|
2017-04-05 17:56:48 +00:00
|
|
|
runs += [{
|
2017-04-05 18:27:52 +00:00
|
|
|
'georeferencing_dir': tree.odm_25dgeoreferencing,
|
|
|
|
'texturing_dir': tree.odm_25dtexturing,
|
2017-04-05 17:56:48 +00:00
|
|
|
'model': os.path.join(tree.odm_25dtexturing, tree.odm_textured_model_obj)
|
|
|
|
}]
|
|
|
|
|
|
|
|
for r in runs:
|
2017-04-05 18:27:52 +00:00
|
|
|
odm_georeferencing_model_obj_geo = os.path.join(r['texturing_dir'], tree.odm_georeferencing_model_obj_geo)
|
|
|
|
odm_georeferencing_model_ply_geo = os.path.join(r['georeferencing_dir'], tree.odm_georeferencing_model_ply_geo)
|
|
|
|
odm_georeferencing_log = os.path.join(r['georeferencing_dir'], tree.odm_georeferencing_log)
|
2017-06-12 10:23:32 +00:00
|
|
|
odm_georeferencing_transform_file = os.path.join(r['georeferencing_dir'], tree.odm_georeferencing_transform_file)
|
2018-04-25 14:00:56 +00:00
|
|
|
odm_georeferencing_model_txt_geo_file = os.path.join(r['georeferencing_dir'], tree.odm_georeferencing_model_txt_geo)
|
2017-04-05 17:56:48 +00:00
|
|
|
|
|
|
|
if not io.file_exists(odm_georeferencing_model_obj_geo) or \
|
|
|
|
not io.file_exists(odm_georeferencing_model_ply_geo) or rerun_cell:
|
|
|
|
|
|
|
|
# odm_georeference definitions
|
|
|
|
kwargs = {
|
|
|
|
'bin': context.odm_modules_path,
|
|
|
|
'bundle': tree.opensfm_bundle,
|
2017-08-24 19:19:51 +00:00
|
|
|
'imgs': tree.dataset_raw,
|
2017-04-05 17:56:48 +00:00
|
|
|
'imgs_list': tree.opensfm_bundle_list,
|
|
|
|
'model': r['model'],
|
|
|
|
'log': odm_georeferencing_log,
|
2018-01-26 19:38:26 +00:00
|
|
|
'input_trans_file': tree.opensfm_transformation,
|
2017-06-12 10:23:32 +00:00
|
|
|
'transform_file': odm_georeferencing_transform_file,
|
2017-04-05 17:56:48 +00:00
|
|
|
'coords': tree.odm_georeferencing_coords,
|
|
|
|
'pc_geo': odm_georeferencing_model_ply_geo,
|
2018-04-25 14:00:56 +00:00
|
|
|
'geo_sys': odm_georeferencing_model_txt_geo_file,
|
2017-04-05 17:56:48 +00:00
|
|
|
'model_geo': odm_georeferencing_model_obj_geo,
|
|
|
|
'gcp': gcpfile,
|
|
|
|
'verbose': verbose
|
|
|
|
|
|
|
|
}
|
2018-07-01 21:28:55 +00:00
|
|
|
|
|
|
|
if args.fast_orthophoto:
|
|
|
|
kwargs['pc'] = os.path.join(tree.opensfm, 'reconstruction.ply')
|
2018-07-01 22:49:53 +00:00
|
|
|
elif args.use_opensfm_dense:
|
2018-07-01 21:28:55 +00:00
|
|
|
kwargs['pc'] = tree.opensfm_model
|
2018-07-01 22:49:53 +00:00
|
|
|
else:
|
2018-12-02 17:45:26 +00:00
|
|
|
kwargs['pc'] = tree.mve_model
|
2017-04-05 17:56:48 +00:00
|
|
|
|
2018-07-03 16:37:39 +00:00
|
|
|
if transformPointCloud:
|
|
|
|
kwargs['pc_params'] = '-inputPointCloudFile {pc} -outputPointCloudFile {pc_geo}'.format(**kwargs)
|
2018-07-04 19:33:11 +00:00
|
|
|
else:
|
|
|
|
kwargs['pc_params'] = ''
|
|
|
|
|
2017-04-05 17:56:48 +00:00
|
|
|
# Check to see if the GCP file exists
|
|
|
|
|
2018-04-25 14:00:56 +00:00
|
|
|
if not self.params.use_exif and (self.params.gcp_file or tree.odm_georeferencing_gcp):
|
|
|
|
log.ODM_INFO('Found %s' % gcpfile)
|
|
|
|
try:
|
|
|
|
system.run('{bin}/odm_georef -bundleFile {bundle} -imagesPath {imgs} -imagesListPath {imgs_list} '
|
|
|
|
'-inputFile {model} -outputFile {model_geo} '
|
2018-07-03 16:37:39 +00:00
|
|
|
'{pc_params} {verbose} '
|
2018-04-25 14:00:56 +00:00
|
|
|
'-logFile {log} -outputTransformFile {transform_file} -georefFileOutputPath {geo_sys} -gcpFile {gcp} '
|
|
|
|
'-outputCoordFile {coords}'.format(**kwargs))
|
|
|
|
except Exception:
|
|
|
|
log.ODM_EXCEPTION('Georeferencing failed. ')
|
|
|
|
return ecto.QUIT
|
|
|
|
elif io.file_exists(tree.opensfm_transformation) and io.file_exists(tree.odm_georeferencing_coords):
|
2018-01-26 19:38:26 +00:00
|
|
|
log.ODM_INFO('Running georeferencing with OpenSfM transformation matrix')
|
2018-04-25 14:00:56 +00:00
|
|
|
system.run('{bin}/odm_georef -bundleFile {bundle} -inputTransformFile {input_trans_file} -inputCoordFile {coords} '
|
2018-01-26 19:38:26 +00:00
|
|
|
'-inputFile {model} -outputFile {model_geo} '
|
2018-07-03 16:37:39 +00:00
|
|
|
'{pc_params} {verbose} '
|
2018-01-26 19:38:26 +00:00
|
|
|
'-logFile {log} -outputTransformFile {transform_file} -georefFileOutputPath {geo_sys}'.format(**kwargs))
|
2017-04-05 17:56:48 +00:00
|
|
|
elif io.file_exists(tree.odm_georeferencing_coords):
|
|
|
|
log.ODM_INFO('Running georeferencing with generated coords file.')
|
|
|
|
system.run('{bin}/odm_georef -bundleFile {bundle} -inputCoordFile {coords} '
|
|
|
|
'-inputFile {model} -outputFile {model_geo} '
|
2018-07-03 16:37:39 +00:00
|
|
|
'{pc_params} {verbose} '
|
2017-06-12 11:57:37 +00:00
|
|
|
'-logFile {log} -outputTransformFile {transform_file} -georefFileOutputPath {geo_sys}'.format(**kwargs))
|
2017-04-05 17:56:48 +00:00
|
|
|
else:
|
|
|
|
log.ODM_WARNING('Georeferencing failed. Make sure your '
|
|
|
|
'photos have geotags in the EXIF or you have '
|
|
|
|
'provided a GCP file. ')
|
2018-01-02 20:02:33 +00:00
|
|
|
doPointCloudGeo = False # skip the rest of the georeferencing
|
2017-04-05 17:56:48 +00:00
|
|
|
|
2018-01-02 20:02:33 +00:00
|
|
|
if doPointCloudGeo:
|
2018-01-26 19:38:26 +00:00
|
|
|
# update images metadata
|
|
|
|
geo_ref = reconstruction.georef
|
2018-04-25 14:00:56 +00:00
|
|
|
geo_ref.extract_offsets(odm_georeferencing_model_txt_geo_file)
|
2017-10-26 17:14:17 +00:00
|
|
|
|
|
|
|
# convert ply model to LAS reference system
|
|
|
|
geo_ref.convert_to_las(odm_georeferencing_model_ply_geo,
|
2018-06-18 13:57:20 +00:00
|
|
|
tree.odm_georeferencing_model_laz,
|
2017-10-26 17:14:17 +00:00
|
|
|
tree.odm_georeferencing_las_json)
|
|
|
|
|
2018-01-26 19:38:26 +00:00
|
|
|
reconstruction.georef = geo_ref
|
|
|
|
|
2017-10-26 17:14:17 +00:00
|
|
|
# XYZ point cloud output
|
2018-06-04 01:01:04 +00:00
|
|
|
if args.pc_csv:
|
|
|
|
log.ODM_INFO("Creating geo-referenced CSV file (XYZ format)")
|
|
|
|
with open(tree.odm_georeferencing_xyz_file, "wb") as csvfile:
|
|
|
|
csvfile_writer = csv.writer(csvfile, delimiter=",")
|
|
|
|
with open(odm_georeferencing_model_ply_geo) as f:
|
|
|
|
endianess = '<' # little endian
|
|
|
|
while True:
|
|
|
|
line = f.readline()
|
|
|
|
if "binary_big_endian" in line:
|
|
|
|
endianess = '>'
|
|
|
|
if line.startswith("end_header"):
|
|
|
|
break
|
|
|
|
|
2018-06-04 01:15:27 +00:00
|
|
|
fmt = '{}dddBBB'.format(endianess)
|
2018-06-04 01:01:04 +00:00
|
|
|
while True:
|
|
|
|
chunk = f.read(27) # 3 doubles, 3 uints
|
|
|
|
if len(chunk) < 27:
|
|
|
|
break
|
2018-06-04 01:15:27 +00:00
|
|
|
tokens = struct.unpack(fmt, chunk)
|
2018-02-05 17:46:27 +00:00
|
|
|
csv_line = [float(tokens[0]),
|
|
|
|
float(tokens[1]),
|
2017-10-26 17:14:17 +00:00
|
|
|
tokens[2]]
|
|
|
|
csvfile_writer.writerow(csv_line)
|
2015-12-10 11:01:41 +00:00
|
|
|
|
2018-01-04 17:08:59 +00:00
|
|
|
if args.crop > 0:
|
|
|
|
log.ODM_INFO("Calculating cropping area and generating bounds shapefile from point cloud")
|
|
|
|
cropper = Cropper(tree.odm_georeferencing, 'odm_georeferenced_model')
|
2018-07-02 21:29:22 +00:00
|
|
|
cropper.create_bounds_shapefile(tree.odm_georeferencing_model_laz, args.crop,
|
2018-07-03 00:39:36 +00:00
|
|
|
decimation_step=40 if args.fast_orthophoto or args.use_opensfm_dense else 90,
|
2018-07-02 21:29:22 +00:00
|
|
|
outlier_radius=20 if args.fast_orthophoto else 2)
|
2018-01-04 17:08:59 +00:00
|
|
|
|
2018-01-02 20:02:33 +00:00
|
|
|
# Do not execute a second time, since
|
2018-01-26 19:38:26 +00:00
|
|
|
# We might be doing georeferencing for
|
2018-01-02 20:02:33 +00:00
|
|
|
# multiple models (3D, 2.5D, ...)
|
|
|
|
doPointCloudGeo = False
|
2018-07-03 16:37:39 +00:00
|
|
|
transformPointCloud = False
|
2018-04-25 14:00:56 +00:00
|
|
|
else:
|
|
|
|
log.ODM_WARNING('Found a valid georeferenced model in: %s'
|
|
|
|
% odm_georeferencing_model_ply_geo)
|
2015-12-10 17:17:39 +00:00
|
|
|
|
2018-01-26 19:38:26 +00:00
|
|
|
outputs.reconstruction = reconstruction
|
|
|
|
|
2016-03-08 18:26:58 +00:00
|
|
|
if args.time:
|
2016-02-29 14:45:00 +00:00
|
|
|
system.benchmark(start_time, tree.benchmarking, 'Georeferencing')
|
|
|
|
|
2016-03-08 18:26:58 +00:00
|
|
|
log.ODM_INFO('Running ODM Georeferencing Cell - Finished')
|
|
|
|
return ecto.OK if args.end_with != 'odm_georeferencing' else ecto.QUIT
|