def process(self, args, outputs): tree = outputs['tree'] las_model_found = io.file_exists(tree.odm_georeferencing_model_laz) log.ODM_INFO('Classify: ' + str(args.pc_classify)) log.ODM_INFO('Create DSM: ' + str(args.dsm)) log.ODM_INFO('Create DTM: ' + str(args.dtm)) log.ODM_INFO('DEM input file {0} found: {1}'.format(tree.odm_georeferencing_model_laz, str(las_model_found))) # define paths and create working directories odm_dem_root = tree.path('odm_dem') if not io.dir_exists(odm_dem_root): system.mkdir_p(odm_dem_root) if args.pc_classify and las_model_found: pc_classify_marker = os.path.join(odm_dem_root, 'pc_classify_done.txt') if not io.file_exists(pc_classify_marker) or self.rerun(): log.ODM_INFO("Classifying {} using Simple Morphological Filter".format(tree.odm_georeferencing_model_laz)) commands.classify(tree.odm_georeferencing_model_laz, args.smrf_scalar, args.smrf_slope, args.smrf_threshold, args.smrf_window, verbose=args.verbose ) with open(pc_classify_marker, 'w') as f: f.write('Classify: smrf\n') f.write('Scalar: {}\n'.format(args.smrf_scalar)) f.write('Slope: {}\n'.format(args.smrf_slope)) f.write('Threshold: {}\n'.format(args.smrf_threshold)) f.write('Window: {}\n'.format(args.smrf_window)) progress = 20 self.update_progress(progress) # Do we need to process anything here? if (args.dsm or args.dtm) and las_model_found: dsm_output_filename = os.path.join(odm_dem_root, 'dsm.tif') dtm_output_filename = os.path.join(odm_dem_root, 'dtm.tif') if (args.dtm and not io.file_exists(dtm_output_filename)) or \ (args.dsm and not io.file_exists(dsm_output_filename)) or \ self.rerun(): products = [] if args.dsm: products.append('dsm') if args.dtm: products.append('dtm') resolution = gsd.cap_resolution(args.dem_resolution, tree.opensfm_reconstruction, gsd_error_estimate=-3, ignore_gsd=args.ignore_gsd) radius_steps = [(resolution / 100.0) / 2.0] for _ in range(args.dem_gapfill_steps - 1): radius_steps.append(radius_steps[-1] * 2) # 2 is arbitrary, maybe there's a better value? for product in products: commands.create_dem( tree.odm_georeferencing_model_laz, product, output_type='idw' if product == 'dtm' else 'max', radiuses=map(str, radius_steps), gapfill=args.dem_gapfill_steps > 0, outdir=odm_dem_root, resolution=resolution / 100.0, decimation=args.dem_decimation, verbose=args.verbose, max_workers=args.max_concurrency, keep_unfilled_copy=args.dem_euclidean_map ) dem_geotiff_path = os.path.join(odm_dem_root, "{}.tif".format(product)) bounds_file_path = os.path.join(tree.odm_georeferencing, 'odm_georeferenced_model.bounds.gpkg') if args.crop > 0: # Crop DEM Cropper.crop(bounds_file_path, dem_geotiff_path, utils.get_dem_vars(args)) if args.dem_euclidean_map: unfilled_dem_path = io.related_file_path(dem_geotiff_path, postfix=".unfilled") if args.crop > 0: # Crop unfilled DEM Cropper.crop(bounds_file_path, unfilled_dem_path, utils.get_dem_vars(args)) commands.compute_euclidean_map(unfilled_dem_path, io.related_file_path(dem_geotiff_path, postfix=".euclideand"), overwrite=True) progress += 30 self.update_progress(progress) else: log.ODM_WARNING('Found existing outputs in: %s' % odm_dem_root) else: log.ODM_WARNING('DEM will not be generated')
def process(self, inputs, outputs): # Benchmarking start_time = system.now_raw() log.ODM_INFO('Running ODM Meshing Cell') # get inputs args = inputs.args tree = inputs.tree reconstruction = inputs.reconstruction # define paths and create working directories system.mkdir_p(tree.odm_meshing) # check if we rerun cell or not rerun_cell = (args.rerun is not None and args.rerun == 'odm_meshing') or \ (args.rerun_all) or \ (args.rerun_from is not None and 'odm_meshing' in args.rerun_from) infile = tree.smvs_model if args.fast_orthophoto: infile = os.path.join(tree.opensfm, 'reconstruction.ply') elif args.use_opensfm_dense: infile = tree.opensfm_model # Create full 3D model unless --skip-3dmodel is set if not args.skip_3dmodel: if not io.file_exists(tree.odm_mesh) or rerun_cell: log.ODM_DEBUG('Writing ODM Mesh file in: %s' % tree.odm_mesh) mesh.screened_poisson_reconstruction( infile, tree.odm_mesh, depth=self.params.oct_tree, samples=self.params.samples, maxVertexCount=self.params.max_vertex, pointWeight=self.params.point_weight, threads=self.params.max_concurrency, verbose=self.params.verbose) else: log.ODM_WARNING('Found a valid ODM Mesh file in: %s' % tree.odm_mesh) # Always generate a 2.5D mesh # unless --use-3dmesh is set. if not args.use_3dmesh: if not io.file_exists(tree.odm_25dmesh) or rerun_cell: log.ODM_DEBUG('Writing ODM 2.5D Mesh file in: %s' % tree.odm_25dmesh) dsm_resolution = gsd.cap_resolution( args.orthophoto_resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd) / 100.0 # Create reference DSM at half ortho resolution dsm_resolution *= 2 # Sparse point clouds benefits from using # a larger resolution value (more radius interolation, less holes) if args.fast_orthophoto: dsm_resolution *= 2 mesh.create_25dmesh(infile, tree.odm_25dmesh, dsm_resolution=dsm_resolution, depth=self.params.oct_tree, maxVertexCount=self.params.max_vertex, samples=self.params.samples, verbose=self.params.verbose, max_workers=args.max_concurrency) else: log.ODM_WARNING('Found a valid ODM 2.5D Mesh file in: %s' % tree.odm_25dmesh) outputs.reconstruction = reconstruction if args.time: system.benchmark(start_time, tree.benchmarking, 'Meshing') log.ODM_INFO('Running ODM Meshing Cell - Finished') return ecto.OK if args.end_with != 'odm_meshing' else ecto.QUIT
def process(self, inputs, outputs): # Benchmarking start_time = system.now_raw() log.ODM_INFO('Running ODM DEM Cell') # get inputs args = self.inputs.args tree = self.inputs.tree las_model_found = io.file_exists(tree.odm_georeferencing_model_laz) # check if we rerun cell or not rerun_cell = (args.rerun is not None and args.rerun == 'odm_dem') or \ (args.rerun_all) or \ (args.rerun_from is not None and 'odm_dem' in args.rerun_from) log.ODM_INFO('Classify: ' + str(args.pc_classify != "none")) log.ODM_INFO('Create DSM: ' + str(args.dsm)) log.ODM_INFO('Create DTM: ' + str(args.dtm)) log.ODM_INFO('DEM input file {0} found: {1}'.format( tree.odm_georeferencing_model_laz, str(las_model_found))) # Setup terrain parameters terrain_params_map = { 'flatnonforest': (1, 3), 'flatforest': (1, 2), 'complexnonforest': (5, 2), 'complexforest': (10, 2) } terrain_params = terrain_params_map[args.dem_terrain_type.lower()] slope, cellsize = terrain_params # define paths and create working directories odm_dem_root = tree.path('odm_dem') if not io.dir_exists(odm_dem_root): system.mkdir_p(odm_dem_root) if args.pc_classify != "none" and las_model_found: pc_classify_marker = os.path.join(odm_dem_root, 'pc_classify_done.txt') if not io.file_exists(pc_classify_marker) or rerun_cell: log.ODM_INFO("Classifying {} using {}".format( tree.odm_georeferencing_model_laz, args.pc_classify)) commands.classify(tree.odm_georeferencing_model_laz, args.pc_classify == "smrf", slope, cellsize, approximate=args.dem_approximate, initialDistance=args.dem_initial_distance, verbose=args.verbose) with open(pc_classify_marker, 'w') as f: f.write('Classify: {}\n'.format(args.pc_classify)) f.write('Slope: {}\n'.format(slope)) f.write('Cellsize: {}\n'.format(cellsize)) f.write('Approximate: {}\n'.format(args.dem_approximate)) f.write('InitialDistance: {}\n'.format( args.dem_initial_distance)) # Do we need to process anything here? if (args.dsm or args.dtm) and las_model_found: dsm_output_filename = os.path.join(odm_dem_root, 'dsm.tif') dtm_output_filename = os.path.join(odm_dem_root, 'dtm.tif') if (args.dtm and not io.file_exists(dtm_output_filename)) or \ (args.dsm and not io.file_exists(dsm_output_filename)) or \ rerun_cell: products = [] if args.dsm: products.append('dsm') if args.dtm: products.append('dtm') resolution = gsd.cap_resolution(args.dem_resolution, tree.opensfm_reconstruction, gsd_error_estimate=-3, ignore_gsd=args.ignore_gsd) radius_steps = [(resolution / 100.0) / 2.0] for _ in range(args.dem_gapfill_steps - 1): radius_steps.append( radius_steps[-1] * 2) # 2 is arbitrary, maybe there's a better value? for product in products: commands.create_dems( [tree.odm_georeferencing_model_laz], product, radius=map(str, radius_steps), gapfill=True, outdir=odm_dem_root, resolution=resolution / 100.0, maxsd=args.dem_maxsd, maxangle=args.dem_maxangle, decimation=args.dem_decimation, verbose=args.verbose, max_workers=get_max_concurrency_for_dem( args.max_concurrency, tree.odm_georeferencing_model_laz)) if args.crop > 0: bounds_shapefile_path = os.path.join( tree.odm_georeferencing, 'odm_georeferenced_model.bounds.shp') if os.path.exists(bounds_shapefile_path): Cropper.crop( bounds_shapefile_path, os.path.join(odm_dem_root, "{}.tif".format(product)), { 'TILED': 'YES', 'COMPRESS': 'LZW', 'BLOCKXSIZE': 512, 'BLOCKYSIZE': 512, 'NUM_THREADS': self.params.max_concurrency }) else: log.ODM_WARNING('Found existing outputs in: %s' % odm_dem_root) else: log.ODM_WARNING('DEM will not be generated') if args.time: system.benchmark(start_time, tree.benchmarking, 'Dem') log.ODM_INFO('Running ODM DEM Cell - Finished') return ecto.OK if args.end_with != 'odm_dem' else ecto.QUIT
def process(self, args, outputs): tree = outputs['tree'] reconstruction = outputs['reconstruction'] verbose = '-verbose' if args.verbose else '' # define paths and create working directories system.mkdir_p(tree.odm_orthophoto) if not io.file_exists(tree.odm_orthophoto_file) or self.rerun(): # odm_orthophoto definitions kwargs = { 'bin': context.odm_modules_path, 'log': tree.odm_orthophoto_log, 'ortho': tree.odm_orthophoto_file, 'corners': tree.odm_orthophoto_corners, 'res': 1.0 / (gsd.cap_resolution(args.orthophoto_resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd) / 100.0), 'verbose': verbose } # Have geo coordinates? georef = reconstruction.georef # Check if the georef object is initialized # (during a --rerun this might not be) # TODO: this should be moved to a more central location? if reconstruction.is_georeferenced() and not reconstruction.georef.valid_utm_offsets(): 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) if io.file_exists(odm_georeferencing_model_txt_geo_file): reconstruction.georef.extract_offsets(odm_georeferencing_model_txt_geo_file) else: log.ODM_WARNING('Cannot read UTM offset from {}. An orthophoto will not be generated.'.format(odm_georeferencing_model_txt_geo_file)) if reconstruction.is_georeferenced(): if args.use_3dmesh: kwargs['model_geo'] = os.path.join(tree.odm_texturing, tree.odm_georeferencing_model_obj_geo) else: kwargs['model_geo'] = os.path.join(tree.odm_25dtexturing, tree.odm_georeferencing_model_obj_geo) else: if args.use_3dmesh: kwargs['model_geo'] = os.path.join(tree.odm_texturing, tree.odm_textured_model_obj) else: kwargs['model_geo'] = os.path.join(tree.odm_25dtexturing, tree.odm_textured_model_obj) # run odm_orthophoto system.run('{bin}/odm_orthophoto -inputFile {model_geo} ' '-logFile {log} -outputFile {ortho} -resolution {res} {verbose} ' '-outputCornerFile {corners}'.format(**kwargs)) # Create georeferenced GeoTiff geotiffcreated = False if reconstruction.is_georeferenced() and reconstruction.georef.valid_utm_offsets(): 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]) + \ float(reconstruction.georef.utm_east_offset) lry = float(tokens[1]) + \ float(reconstruction.georef.utm_north_offset) lrx = float(tokens[2]) + \ float(reconstruction.georef.utm_east_offset) uly = float(tokens[3]) + \ float(reconstruction.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, 'vars': ' '.join(['-co %s=%s' % (k, orthophoto_vars[k]) for k in orthophoto_vars]), 'proj': reconstruction.georef.proj4(), 'png': tree.odm_orthophoto_file, 'tiff': tree.odm_orthophoto_tif, 'log': tree.odm_orthophoto_tif_log, 'max_memory': get_max_memory(), } system.run('gdal_translate -a_ullr {ulx} {uly} {lrx} {lry} ' '{vars} ' '-a_srs \"{proj}\" ' '--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.orthophoto_cutline: compute_cutline(tree.odm_orthophoto_tif, bounds_file_path, os.path.join(tree.odm_orthophoto, "cutline.gpkg"), args.max_concurrency, tmpdir=os.path.join(tree.odm_orthophoto, "grass_cutline_tmpdir"), scale=0.25) if args.crop > 0: Cropper.crop(bounds_file_path, tree.odm_orthophoto_tif, orthophoto_vars) if args.build_overviews: orthophoto.build_overviews(tree.odm_orthophoto_tif) geotiffcreated = True if not geotiffcreated: log.ODM_WARNING('No geo-referenced orthophoto created due ' 'to missing geo-referencing or corner coordinates.') else: log.ODM_WARNING('Found a valid orthophoto in: %s' % tree.odm_orthophoto_file)
def process(self, inputs, outputs): # Benchmarking start_time = system.now_raw() log.ODM_INFO('Running ODM Orthophoto Cell') # get inputs args = self.inputs.args tree = self.inputs.tree reconstruction = inputs.reconstruction verbose = '-verbose' if self.params.verbose else '' # define paths and create working directories system.mkdir_p(tree.odm_orthophoto) # check if we rerun cell or not rerun_cell = (args.rerun is not None and args.rerun == 'odm_orthophoto') or \ (args.rerun_all) or \ (args.rerun_from is not None and 'odm_orthophoto' in args.rerun_from) if not io.file_exists(tree.odm_orthophoto_file) or rerun_cell: # odm_orthophoto definitions kwargs = { 'bin': context.odm_modules_path, 'log': tree.odm_orthophoto_log, 'ortho': tree.odm_orthophoto_file, 'corners': tree.odm_orthophoto_corners, 'res': 1.0 / (gsd.cap_resolution(self.params.resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd) / 100.0), 'verbose': verbose } # Have geo coordinates? georef = reconstruction.georef # Check if the georef object is initialized # (during a --rerun this might not be) # TODO: we should move this to a more central # location (perhaps during the dataset initialization) if georef and not georef.utm_east_offset: 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) if io.file_exists(odm_georeferencing_model_txt_geo_file): georef.extract_offsets( odm_georeferencing_model_txt_geo_file) else: log.ODM_WARNING( 'Cannot read UTM offset from {}. An orthophoto will not be generated.' .format(odm_georeferencing_model_txt_geo_file)) if georef: if args.use_3dmesh: kwargs['model_geo'] = os.path.join( tree.odm_texturing, tree.odm_georeferencing_model_obj_geo) else: kwargs['model_geo'] = os.path.join( tree.odm_25dtexturing, tree.odm_georeferencing_model_obj_geo) else: if args.use_3dmesh: kwargs['model_geo'] = os.path.join( tree.odm_texturing, tree.odm_textured_model_obj) else: kwargs['model_geo'] = os.path.join( tree.odm_25dtexturing, tree.odm_textured_model_obj) # run odm_orthophoto system.run( '{bin}/odm_orthophoto -inputFile {model_geo} ' '-logFile {log} -outputFile {ortho} -resolution {res} {verbose} ' '-outputCornerFile {corners}'.format(**kwargs)) # Create georeferenced GeoTiff geotiffcreated = False if georef and georef.projection and georef.utm_east_offset and georef.utm_north_offset: 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]) + \ float(georef.utm_east_offset) lry = float(tokens[1]) + \ float(georef.utm_north_offset) lrx = float(tokens[2]) + \ float(georef.utm_east_offset) uly = float(tokens[3]) + \ float(georef.utm_north_offset) log.ODM_INFO('Creating GeoTIFF') kwargs = { 'ulx': ulx, 'uly': uly, 'lrx': lrx, 'lry': lry, 'tiled': '' if self.params.no_tiled else '-co TILED=yes ', 'compress': self.params.compress, 'predictor': '-co PREDICTOR=2 ' if self.params.compress in ['LZW', 'DEFLATE'] else '', 'proj': georef.projection.srs, 'bigtiff': self.params.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.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} ' '-a_srs \"{proj}\" ' '--config GDAL_CACHEMAX {max_memory}% ' '{png} {tiff} > {log}'.format(**kwargs)) if args.crop > 0: shapefile_path = os.path.join( tree.odm_georeferencing, 'odm_georeferenced_model.bounds.shp') Cropper.crop( shapefile_path, tree.odm_orthophoto_tif, { 'TILED': 'NO' if self.params.no_tiled else 'YES', 'COMPRESS': self.params.compress, 'PREDICTOR': '2' if self.params.compress in ['LZW', 'DEFLATE'] else '1', 'BIGTIFF': self.params.bigtiff, 'BLOCKXSIZE': 512, 'BLOCKYSIZE': 512, 'NUM_THREADS': self.params.max_concurrency }) if self.params.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)) geotiffcreated = True if not geotiffcreated: log.ODM_WARNING( 'No geo-referenced orthophoto created due ' 'to missing geo-referencing or corner coordinates.') else: log.ODM_WARNING('Found a valid orthophoto in: %s' % tree.odm_orthophoto_file) if args.time: system.benchmark(start_time, tree.benchmarking, 'Orthophoto') log.ODM_INFO('Running ODM OrthoPhoto Cell - Finished') return ecto.OK if args.end_with != 'odm_orthophoto' else ecto.QUIT
def process(self, args, outputs): tree = outputs['tree'] reconstruction = outputs['reconstruction'] # define paths and create working directories system.mkdir_p(tree.odm_meshing) # Create full 3D model unless --skip-3dmodel is set if not args.skip_3dmodel: if not io.file_exists(tree.odm_mesh) or self.rerun(): log.ODM_INFO('Writing ODM Mesh file in: %s' % tree.odm_mesh) mesh.screened_poisson_reconstruction( tree.filtered_point_cloud, tree.odm_mesh, depth=self.params.get('oct_tree'), samples=self.params.get('samples'), maxVertexCount=self.params.get('max_vertex'), pointWeight=self.params.get('point_weight'), threads=max( 1, self.params.get('max_concurrency') - 1 ), # poissonrecon can get stuck on some machines if --threads == all cores verbose=self.params.get('verbose')) else: log.ODM_WARNING('Found a valid ODM Mesh file in: %s' % tree.odm_mesh) self.update_progress(50) # Always generate a 2.5D mesh # unless --use-3dmesh is set. if not args.use_3dmesh: if not io.file_exists(tree.odm_25dmesh) or self.rerun(): log.ODM_INFO('Writing ODM 2.5D Mesh file in: %s' % tree.odm_25dmesh) ortho_resolution = gsd.cap_resolution( args.orthophoto_resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd, ignore_resolution=(not reconstruction.is_georeferenced()) and args.ignore_gsd, has_gcp=reconstruction.has_gcp()) / 100.0 dsm_multiplier = max( 1.0, gsd.rounded_gsd(tree.opensfm_reconstruction, default_value=4, ndigits=3, ignore_gsd=args.ignore_gsd)) # A good DSM size depends on the flight altitude. # Flights at low altitude need more details (higher resolution) # Flights at higher altitude benefit from smoother surfaces (lower resolution) dsm_resolution = ortho_resolution * dsm_multiplier dsm_radius = dsm_resolution * math.sqrt(2) if args.fast_orthophoto: dsm_radius *= 2 dsm_resolution *= 8 log.ODM_INFO('ODM 2.5D DSM resolution: %s' % dsm_resolution) mesh.create_25dmesh( tree.filtered_point_cloud, tree.odm_25dmesh, dsm_radius=dsm_radius, dsm_resolution=dsm_resolution, depth=self.params.get('oct_tree'), maxVertexCount=self.params.get('max_vertex'), samples=self.params.get('samples'), verbose=self.params.get('verbose'), available_cores=args.max_concurrency, method='poisson' if args.fast_orthophoto else 'gridded', smooth_dsm=True) else: log.ODM_WARNING('Found a valid ODM 2.5D Mesh file in: %s' % tree.odm_25dmesh)
def process(args, current_path, max_concurrency, reconstruction): #args = vars(args) orthophoto_cutline = True odm_orthophoto = io.join_paths(current_path, 'orthophoto') odm_orthophoto_path = odm_orthophoto odm_orthophoto_render = io.join_paths(odm_orthophoto_path, 'odm_orthophoto_render.tif') odm_orthophoto_tif = io.join_paths(odm_orthophoto_path, 'odm_orthophoto.tif') odm_orthophoto_corners = io.join_paths(odm_orthophoto_path, 'odm_orthophoto_corners.tif') odm_orthophoto_log = io.join_paths(odm_orthophoto_path, 'odm_orthophoto_log.tif') odm_orthophoto_tif_log = io.join_paths(odm_orthophoto_path, 'gdal_translate_log.txt') odm_25dgeoreferencing = io.join_paths(current_path, 'odm_georeferencing') odm_georeferencing = io.join_paths(current_path, 'odm_georeferencing') odm_georeferencing_coords = io.join_paths(odm_georeferencing, 'coords.txt') odm_georeferencing_gcp = io.find('gcp_list.txt', current_path) odm_georeferencing_gcp_utm = io.join_paths(odm_georeferencing, 'gcp_list_utm.txt') odm_georeferencing_utm_log = io.join_paths( odm_georeferencing, 'odm_georeferencing_utm_log.txt') odm_georeferencing_log = 'odm_georeferencing_log.txt' odm_georeferencing_transform_file = 'odm_georeferencing_transform.txt' odm_georeferencing_proj = 'proj.txt' odm_georeferencing_model_txt_geo = 'odm_georeferencing_model_geo.txt' odm_georeferencing_model_obj_geo = 'odm_textured_model_geo.obj' odm_georeferencing_xyz_file = io.join_paths(odm_georeferencing, 'odm_georeferenced_model.csv') odm_georeferencing_las_json = io.join_paths(odm_georeferencing, 'las.json') odm_georeferencing_model_laz = io.join_paths( odm_georeferencing, 'odm_georeferenced_model.laz') odm_georeferencing_model_las = io.join_paths( odm_georeferencing, 'odm_georeferenced_model.las') odm_georeferencing_dem = io.join_paths(odm_georeferencing, 'odm_georeferencing_model_dem.tif') opensfm_reconstruction = io.join_paths(current_path, 'reconstruction.json') odm_texturing = io.join_paths(current_path, 'mvs') odm_textured_model_obj = io.join_paths(odm_texturing, 'odm_textured_model.obj') images_dir = io.join_paths(current_path, 'images') reconstruction = reconstruction verbose = '' #"-verbose" # define paths and create working directories system.mkdir_p(odm_orthophoto) if not io.file_exists(odm_orthophoto_tif): gsd_error_estimate = 0.1 ignore_resolution = False if not reconstruction.is_georeferenced(): # Match DEMs gsd_error_estimate = -3 ignore_resolution = True orthophoto_resolution = 5 resolution = 1.0 / ( gsd.cap_resolution(orthophoto_resolution, opensfm_reconstruction, gsd_error_estimate=gsd_error_estimate, ignore_gsd=True, ignore_resolution=ignore_resolution, has_gcp=reconstruction.has_gcp()) / 100.0) # odm_orthophoto definitions kwargs = { 'bin': context.odm_modules_path, 'log': odm_orthophoto_log, 'ortho': odm_orthophoto_render, 'corners': odm_orthophoto_corners, 'res': resolution, 'bands': '', 'verbose': verbose } # Check if the georef object is initialized # (during a --rerun this might not be) # TODO: this should be moved to a more central location? if reconstruction.is_georeferenced( ) and not reconstruction.georef.valid_utm_offsets(): georeferencing_dir = odm_georeferencing #if args.use_3dmesh and not args.skip_3dmodel else odm_25dgeoreferencing odm_georeferencing_model_txt_geo_file = os.path.join( georeferencing_dir, odm_georeferencing_model_txt_geo) if io.file_exists(odm_georeferencing_model_txt_geo_file): reconstruction.georef.extract_offsets( odm_georeferencing_model_txt_geo_file) else: log.ODM_WARNING('Cannot read UTM offset from {}.'.format( odm_georeferencing_model_txt_geo_file)) models = [] base_dir = odm_texturing if reconstruction.is_georeferenced(): model_file = odm_georeferencing_model_obj_geo else: model_file = 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)) kwargs['bands'] = '-bands %s' % (','.join([ quote(b['name'].lower()) for b in reconstruction.multi_camera ])) else: models.append(os.path.join(base_dir, model_file)) kwargs['models'] = ','.join(map(quote, models)) # run odm_orthophoto system.run( '{bin}/odm_orthophoto -inputFiles {models} ' '-logFile {log} -outputFile {ortho} -resolution {res} {verbose} ' '-outputCornerFile {corners} {bands}'.format(**kwargs)) # Create georeferenced GeoTiff geotiffcreated = False if reconstruction.is_georeferenced( ) and reconstruction.georef.valid_utm_offsets(): ulx = uly = lrx = lry = 0.0 with open(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]) + \ float(reconstruction.georef.utm_east_offset) lry = float(tokens[1]) + \ float(reconstruction.georef.utm_north_offset) lrx = float(tokens[2]) + \ float(reconstruction.georef.utm_east_offset) uly = float(tokens[3]) + \ float(reconstruction.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, 'vars': ' '.join([ '-co %s=%s' % (k, orthophoto_vars[k]) for k in orthophoto_vars ]), 'proj': reconstruction.georef.proj4(), 'input': odm_orthophoto_render, 'output': odm_orthophoto_tif, 'log': odm_orthophoto_tif_log, 'max_memory': get_max_memory(), } system.run('gdal_translate -a_ullr {ulx} {uly} {lrx} {lry} ' '{vars} ' '-a_srs \"{proj}\" ' '--config GDAL_CACHEMAX {max_memory}% ' '--config GDAL_TIFF_INTERNAL_MASK YES ' '{input} {output} > {log}'.format(**kwargs)) bounds_file_path = os.path.join( odm_georeferencing, 'odm_georeferenced_model.bounds.gpkg') # Cutline computation, before cropping # We want to use the full orthophoto, not the cropped one. pio = True if pio: cutline_file = os.path.join(odm_orthophoto, "cutline.gpkg") compute_cutline(odm_orthophoto_tif, bounds_file_path, cutline_file, max_concurrency, tmpdir=os.path.join(odm_orthophoto, "grass_cutline_tmpdir"), scale=0.25) orthophoto.compute_mask_raster(odm_orthophoto_tif, cutline_file, os.path.join( odm_orthophoto, "odm_orthophoto_cut.tif"), blend_distance=20, only_max_coords_feature=True) orthophoto.post_orthophoto_steps(args, bounds_file_path, odm_orthophoto_tif) # Generate feathered orthophoto also if pio: orthophoto.feather_raster(odm_orthophoto_tif, os.path.join( odm_orthophoto, "odm_orthophoto_feathered.tif"), blend_distance=20) geotiffcreated = True if not geotiffcreated: if io.file_exists(odm_orthophoto_render): pseudogeo.add_pseudo_georeferencing(odm_orthophoto_render) log.ODM_INFO("Renaming %s --> %s" % (odm_orthophoto_render, odm_orthophoto_tif)) os.rename(odm_orthophoto_render, odm_orthophoto_tif) else: log.ODM_WARNING( "Could not generate an orthophoto (it did not render)") else: log.ODM_WARNING('Found a valid orthophoto in: %s' % odm_orthophoto_tif) #generate png orthophoto.generate_png(odm_orthophoto_tif)
def process(self, args, outputs): tree = outputs['tree'] reconstruction = outputs['reconstruction'] dem_input = tree.odm_georeferencing_model_laz pc_model_found = io.file_exists(dem_input) ignore_resolution = False pseudo_georeference = False if not reconstruction.is_georeferenced(): # Special case to clear previous run point cloud # (NodeODM will generate a fake georeferenced laz during postprocessing # with non-georeferenced datasets). odm_georeferencing_model_laz should # not be here! Perhaps we should improve this. if io.file_exists( tree.odm_georeferencing_model_laz) and self.rerun(): os.remove(tree.odm_georeferencing_model_laz) log.ODM_WARNING( "Not georeferenced, using ungeoreferenced point cloud...") dem_input = tree.path("odm_filterpoints", "point_cloud.ply") pc_model_found = io.file_exists(dem_input) ignore_resolution = True pseudo_georeference = True resolution = gsd.cap_resolution(args.dem_resolution, tree.opensfm_reconstruction, gsd_error_estimate=-3, ignore_gsd=args.ignore_gsd, ignore_resolution=ignore_resolution, has_gcp=reconstruction.has_gcp()) log.ODM_INFO('Classify: ' + str(args.pc_classify)) log.ODM_INFO('Create DSM: ' + str(args.dsm)) log.ODM_INFO('Create DTM: ' + str(args.dtm)) log.ODM_INFO('DEM input file {0} found: {1}'.format( dem_input, str(pc_model_found))) # define paths and create working directories odm_dem_root = tree.path('odm_dem') if not io.dir_exists(odm_dem_root): system.mkdir_p(odm_dem_root) if args.pc_classify and pc_model_found: pc_classify_marker = os.path.join(odm_dem_root, 'pc_classify_done.txt') if not io.file_exists(pc_classify_marker) or self.rerun(): log.ODM_INFO( "Classifying {} using Simple Morphological Filter".format( dem_input)) commands.classify(dem_input, args.smrf_scalar, args.smrf_slope, args.smrf_threshold, args.smrf_window, verbose=args.verbose) with open(pc_classify_marker, 'w') as f: f.write('Classify: smrf\n') f.write('Scalar: {}\n'.format(args.smrf_scalar)) f.write('Slope: {}\n'.format(args.smrf_slope)) f.write('Threshold: {}\n'.format(args.smrf_threshold)) f.write('Window: {}\n'.format(args.smrf_window)) progress = 20 self.update_progress(progress) if args.pc_rectify: commands.rectify(dem_input, args.debug) # Do we need to process anything here? if (args.dsm or args.dtm) and pc_model_found: dsm_output_filename = os.path.join(odm_dem_root, 'dsm.tif') dtm_output_filename = os.path.join(odm_dem_root, 'dtm.tif') if (args.dtm and not io.file_exists(dtm_output_filename)) or \ (args.dsm and not io.file_exists(dsm_output_filename)) or \ self.rerun(): products = [] if args.dsm or (args.dtm and args.dem_euclidean_map): products.append('dsm') if args.dtm: products.append('dtm') radius_steps = [(resolution / 100.0) / 2.0] for _ in range(args.dem_gapfill_steps - 1): radius_steps.append( radius_steps[-1] * 2) # 2 is arbitrary, maybe there's a better value? for product in products: commands.create_dem( dem_input, product, output_type='idw' if product == 'dtm' else 'max', radiuses=map(str, radius_steps), gapfill=args.dem_gapfill_steps > 0, outdir=odm_dem_root, resolution=resolution / 100.0, decimation=args.dem_decimation, verbose=args.verbose, max_workers=args.max_concurrency, keep_unfilled_copy=args.dem_euclidean_map) dem_geotiff_path = os.path.join(odm_dem_root, "{}.tif".format(product)) bounds_file_path = os.path.join( tree.odm_georeferencing, 'odm_georeferenced_model.bounds.gpkg') if args.crop > 0: # Crop DEM Cropper.crop(bounds_file_path, dem_geotiff_path, utils.get_dem_vars(args)) if args.dem_euclidean_map: unfilled_dem_path = io.related_file_path( dem_geotiff_path, postfix=".unfilled") if args.crop > 0: # Crop unfilled DEM Cropper.crop(bounds_file_path, unfilled_dem_path, utils.get_dem_vars(args)) commands.compute_euclidean_map( unfilled_dem_path, io.related_file_path(dem_geotiff_path, postfix=".euclideand"), overwrite=True) if pseudo_georeference: # 0.1 is arbitrary pseudogeo.add_pseudo_georeferencing( dem_geotiff_path, 0.1) progress += 30 self.update_progress(progress) else: log.ODM_WARNING('Found existing outputs in: %s' % odm_dem_root) else: log.ODM_WARNING('DEM will not be generated')
def mesh_3d(args, odm_mesh_folder, odm_mesh_ply, filter_point_cloud_path, max_concurrency, reconstruction, current_path): oct_tree = 10 samples = 1.0 max_vertex = 200000 point_weight = 4 verbose = False args['fast_orthophoto'] = True if args['use_3dmesh']: if not io.file_exists(odm_mesh_ply): log.ODM_INFO('Writing ODM Mesh file in: %s' % odm_mesh_ply) mesh.screened_poisson_reconstruction( filter_point_cloud_path, odm_mesh_ply, depth=oct_tree, samples=samples, maxVertexCount=max_vertex, pointWeight=point_weight, threads=max( 1, max_concurrency - 1 ), # poissonrecon can get stuck on some machines if --threads == all cores verbose=verbose) else: log.ODM_WARNING('Found a valid ODM Mesh file in: %s' % odm_mesh_ply) if not args['use_3dmesh']: if not io.file_exists(odm_mesh_ply): opensfm_reconstruction = io.join_paths(current_path, 'reconstruction.json') reconstruction = reconstruction log.ODM_INFO('Writing ODM 2.5D Mesh file in: %s' % odm_mesh_ply) ortho_resolution = gsd.cap_resolution( args['orthophoto_resolution'], opensfm_reconstruction, ignore_gsd=args['ignore_gsd'], ignore_resolution=not reconstruction.is_georeferenced(), has_gcp=reconstruction.has_gcp()) / 100.0 dsm_multiplier = max( 1.0, gsd.rounded_gsd(opensfm_reconstruction, default_value=4, ndigits=3, ignore_gsd=args['ignore_gsd'])) # A good DSM size depends on the flight altitude. # Flights at low altitude need more details (higher resolution) # Flights at higher altitude benefit from smoother surfaces (lower resolution) dsm_resolution = ortho_resolution * dsm_multiplier dsm_radius = dsm_resolution * math.sqrt(2) # Sparse point clouds benefits from using # a larger radius interolation --> less holes if args['fast_orthophoto']: dsm_radius *= 2 log.ODM_INFO('ODM 2.5D DSM resolution: %s' % dsm_resolution) mesh.create_25dmesh( filter_point_cloud_path, odm_mesh_ply, dsm_radius=dsm_radius, dsm_resolution=dsm_resolution, depth=oct_tree, maxVertexCount=max_vertex, samples=samples, verbose=verbose, available_cores=max_concurrency, method='poisson' if args['fast_orthophoto'] else 'gridded', smooth_dsm=not args['fast_orthophoto']) else: log.ODM_WARNING('Found a valid ODM 2.5D Mesh file in: %s' % odm_mesh_ply)
def process(self, args, outputs): tree = outputs['tree'] reconstruction = outputs['reconstruction'] verbose = '-verbose' if args.verbose else '' # define paths and create working directories system.mkdir_p(tree.odm_orthophoto) if not io.file_exists(tree.odm_orthophoto_tif) or self.rerun(): gsd_error_estimate = 0.1 ignore_resolution = False if not reconstruction.is_georeferenced(): # Match DEMs gsd_error_estimate = -3 ignore_resolution = True resolution = 1.0 / ( gsd.cap_resolution(args.orthophoto_resolution, tree.opensfm_reconstruction, gsd_error_estimate=gsd_error_estimate, ignore_gsd=args.ignore_gsd, ignore_resolution=ignore_resolution, has_gcp=reconstruction.has_gcp()) / 100.0) # odm_orthophoto definitions kwargs = { 'bin': context.odm_modules_path, 'log': tree.odm_orthophoto_log, 'ortho': tree.odm_orthophoto_render, 'corners': tree.odm_orthophoto_corners, 'res': resolution, 'bands': '', 'verbose': verbose } models = [] if args.use_3dmesh: base_dir = tree.odm_texturing else: base_dir = tree.odm_25dtexturing model_file = tree.odm_textured_model_obj if reconstruction.multi_camera: for band in reconstruction.multi_camera: primary = band['name'] == get_primary_band_name( reconstruction.multi_camera, args.primary_band) subdir = "" if not primary: subdir = band['name'].lower() models.append(os.path.join(base_dir, subdir, model_file)) kwargs['bands'] = '-bands %s' % (','.join( [quote(b['name']) for b in reconstruction.multi_camera])) else: models.append(os.path.join(base_dir, model_file)) kwargs['models'] = ','.join(map(quote, models)) # run odm_orthophoto system.run( '{bin}/odm_orthophoto -inputFiles {models} ' '-logFile {log} -outputFile {ortho} -resolution {res} {verbose} ' '-outputCornerFile {corners} {bands}'.format(**kwargs)) # Create georeferenced GeoTiff geotiffcreated = False if reconstruction.is_georeferenced(): 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]) + \ float(reconstruction.georef.utm_east_offset) lry = float(tokens[1]) + \ float(reconstruction.georef.utm_north_offset) lrx = float(tokens[2]) + \ float(reconstruction.georef.utm_east_offset) uly = float(tokens[3]) + \ float(reconstruction.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, 'vars': ' '.join([ '-co %s=%s' % (k, orthophoto_vars[k]) for k in orthophoto_vars ]), 'proj': reconstruction.georef.proj4(), 'input': tree.odm_orthophoto_render, 'output': tree.odm_orthophoto_tif, 'log': tree.odm_orthophoto_tif_log, 'max_memory': get_max_memory(), } system.run('gdal_translate -a_ullr {ulx} {uly} {lrx} {lry} ' '{vars} ' '-a_srs \"{proj}\" ' '--config GDAL_CACHEMAX {max_memory}% ' '--config GDAL_TIFF_INTERNAL_MASK YES ' '{input} {output} > {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.orthophoto_cutline: cutline_file = os.path.join(tree.odm_orthophoto, "cutline.gpkg") compute_cutline(tree.odm_orthophoto_tif, bounds_file_path, cutline_file, args.max_concurrency, tmpdir=os.path.join( tree.odm_orthophoto, "grass_cutline_tmpdir"), scale=0.25) 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) orthophoto.post_orthophoto_steps(args, bounds_file_path, tree.odm_orthophoto_tif, tree.orthophoto_tiles) # Generate feathered orthophoto also if args.orthophoto_cutline: orthophoto.feather_raster( tree.odm_orthophoto_tif, os.path.join(tree.odm_orthophoto, "odm_orthophoto_feathered.tif"), blend_distance=20) geotiffcreated = True if not geotiffcreated: if io.file_exists(tree.odm_orthophoto_render): pseudogeo.add_pseudo_georeferencing( tree.odm_orthophoto_render) log.ODM_INFO( "Renaming %s --> %s" % (tree.odm_orthophoto_render, tree.odm_orthophoto_tif)) os.rename(tree.odm_orthophoto_render, tree.odm_orthophoto_tif) else: log.ODM_WARNING( "Could not generate an orthophoto (it did not render)") else: log.ODM_WARNING('Found a valid orthophoto in: %s' % tree.odm_orthophoto_tif) if args.optimize_disk_space and io.file_exists( tree.odm_orthophoto_render): os.remove(tree.odm_orthophoto_render)
def process(self, inputs, outputs): # Benchmarking start_time = system.now_raw() log.ODM_INFO('Running ODM Meshing Cell') # get inputs args = inputs.args tree = inputs.tree reconstruction = inputs.reconstruction # define paths and create working directories system.mkdir_p(tree.odm_meshing) # check if we rerun cell or not rerun_cell = (args.rerun is not None and args.rerun == 'odm_meshing') or \ (args.rerun_all) or \ (args.rerun_from is not None and 'odm_meshing' in args.rerun_from) infile = tree.smvs_model if args.fast_orthophoto: infile = os.path.join(tree.opensfm, 'reconstruction.ply') elif args.use_opensfm_dense: infile = tree.opensfm_model # Create full 3D model unless --skip-3dmodel is set if not args.skip_3dmodel: if not io.file_exists(tree.odm_mesh) or rerun_cell: log.ODM_DEBUG('Writing ODM Mesh file in: %s' % tree.odm_mesh) mesh.screened_poisson_reconstruction( infile, tree.odm_mesh, depth=self.params.oct_tree, samples=self.params.samples, maxVertexCount=self.params.max_vertex, pointWeight=self.params.point_weight, threads=self.params.max_concurrency, verbose=self.params.verbose) else: log.ODM_WARNING('Found a valid ODM Mesh file in: %s' % tree.odm_mesh) # Always generate a 2.5D mesh # unless --use-3dmesh is set. if not args.use_3dmesh: if not io.file_exists(tree.odm_25dmesh) or rerun_cell: log.ODM_DEBUG('Writing ODM 2.5D Mesh file in: %s' % tree.odm_25dmesh) ortho_resolution = gsd.cap_resolution( args.orthophoto_resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd) / 100.0 dsm_multiplier = max( 1.0, gsd.rounded_gsd(tree.opensfm_reconstruction, default_value=4, ndigits=3, ignore_gsd=args.ignore_gsd)) # A good DSM size depends on the flight altitude. # Flights at low altitude need more details (higher resolution) # Flights at higher altitude benefit from smoother surfaces (lower resolution) dsm_resolution = ortho_resolution * dsm_multiplier dsm_radius = dsm_resolution * math.sqrt(2) # Sparse point clouds benefits from using # a larger radius interolation --> less holes if args.fast_orthophoto: dsm_radius *= 2 log.ODM_DEBUG('ODM 2.5D DSM resolution: %s' % dsm_resolution) mesh.create_25dmesh( infile, tree.odm_25dmesh, dsm_radius=dsm_radius, dsm_resolution=dsm_resolution, depth=self.params.oct_tree, maxVertexCount=self.params.max_vertex, samples=self.params.samples, verbose=self.params.verbose, max_workers=args.max_concurrency, method='poisson' if args.fast_orthophoto else 'gridded') else: log.ODM_WARNING('Found a valid ODM 2.5D Mesh file in: %s' % tree.odm_25dmesh) outputs.reconstruction = reconstruction if args.time: system.benchmark(start_time, tree.benchmarking, 'Meshing') log.ODM_INFO('Running ODM Meshing Cell - Finished') return ecto.OK if args.end_with != 'odm_meshing' else ecto.QUIT
def process(self, inputs, outputs): # Benchmarking start_time = system.now_raw() log.ODM_INFO('Running ODM Orthophoto Cell') # get inputs args = self.inputs.args tree = self.inputs.tree reconstruction = inputs.reconstruction verbose = '-verbose' if self.params.verbose else '' # define paths and create working directories system.mkdir_p(tree.odm_orthophoto) # check if we rerun cell or not rerun_cell = (args.rerun is not None and args.rerun == 'odm_orthophoto') or \ (args.rerun_all) or \ (args.rerun_from is not None and 'odm_orthophoto' in args.rerun_from) if not io.file_exists(tree.odm_orthophoto_file) or rerun_cell: # odm_orthophoto definitions kwargs = { 'bin': context.odm_modules_path, 'log': tree.odm_orthophoto_log, 'ortho': tree.odm_orthophoto_file, 'corners': tree.odm_orthophoto_corners, 'res': 1.0 / (gsd.cap_resolution(self.params.resolution, tree.opensfm_reconstruction, ignore_gsd=args.ignore_gsd) / 100.0), 'verbose': verbose } # Have geo coordinates? georef = reconstruction.georef # Check if the georef object is initialized # (during a --rerun this might not be) # TODO: we should move this to a more central # location (perhaps during the dataset initialization) if georef and not georef.utm_east_offset: 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) if io.file_exists(odm_georeferencing_model_txt_geo_file): georef.extract_offsets(odm_georeferencing_model_txt_geo_file) else: log.ODM_WARNING('Cannot read UTM offset from {}. An orthophoto will not be generated.'.format(odm_georeferencing_model_txt_geo_file)) if georef: if args.use_3dmesh: kwargs['model_geo'] = os.path.join(tree.odm_texturing, tree.odm_georeferencing_model_obj_geo) else: kwargs['model_geo'] = os.path.join(tree.odm_25dtexturing, tree.odm_georeferencing_model_obj_geo) else: if args.use_3dmesh: kwargs['model_geo'] = os.path.join(tree.odm_texturing, tree.odm_textured_model_obj) else: kwargs['model_geo'] = os.path.join(tree.odm_25dtexturing, tree.odm_textured_model_obj) # run odm_orthophoto system.run('{bin}/odm_orthophoto -inputFile {model_geo} ' '-logFile {log} -outputFile {ortho} -resolution {res} {verbose} ' '-outputCornerFile {corners}'.format(**kwargs)) # Create georeferenced GeoTiff geotiffcreated = False if georef and georef.projection and georef.utm_east_offset and georef.utm_north_offset: 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]) + \ float(georef.utm_east_offset) lry = float(tokens[1]) + \ float(georef.utm_north_offset) lrx = float(tokens[2]) + \ float(georef.utm_east_offset) uly = float(tokens[3]) + \ float(georef.utm_north_offset) log.ODM_INFO('Creating GeoTIFF') kwargs = { 'ulx': ulx, 'uly': uly, 'lrx': lrx, 'lry': lry, 'tiled': '' if self.params.no_tiled else '-co TILED=yes ', 'compress': self.params.compress, 'predictor': '-co PREDICTOR=2 ' if self.params.compress in ['LZW', 'DEFLATE'] else '', 'proj': georef.projection.srs, 'bigtiff': self.params.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.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} ' '-a_srs \"{proj}\" ' '--config GDAL_CACHEMAX {max_memory}% ' '{png} {tiff} > {log}'.format(**kwargs)) if args.crop > 0: shapefile_path = os.path.join(tree.odm_georeferencing, 'odm_georeferenced_model.bounds.shp') Cropper.crop(shapefile_path, tree.odm_orthophoto_tif, { 'TILED': 'NO' if self.params.no_tiled else 'YES', 'COMPRESS': self.params.compress, 'PREDICTOR': '2' if self.params.compress in ['LZW', 'DEFLATE'] else '1', 'BIGTIFF': self.params.bigtiff, 'BLOCKXSIZE': 512, 'BLOCKYSIZE': 512, 'NUM_THREADS': self.params.max_concurrency }) if self.params.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)) geotiffcreated = True if not geotiffcreated: log.ODM_WARNING('No geo-referenced orthophoto created due ' 'to missing geo-referencing or corner coordinates.') else: log.ODM_WARNING('Found a valid orthophoto in: %s' % tree.odm_orthophoto_file) if args.time: system.benchmark(start_time, tree.benchmarking, 'Orthophoto') log.ODM_INFO('Running ODM OrthoPhoto Cell - Finished') return ecto.OK if args.end_with != 'odm_orthophoto' else ecto.QUIT
def process(self, args, outputs): tree = outputs['tree'] reconstruction = outputs['reconstruction'] dem_input = tree.odm_georeferencing_model_laz pc_model_found = io.file_exists(dem_input) ignore_resolution = False pseudo_georeference = False if not reconstruction.is_georeferenced(): log.ODM_WARNING( "Not georeferenced, using ungeoreferenced point cloud...") ignore_resolution = True pseudo_georeference = True # It is probably not reasonable to have accurate DEMs a the same resolution as the source photos, so reduce it # by a factor! gsd_scaling = 2.0 resolution = gsd.cap_resolution(args.dem_resolution, tree.opensfm_reconstruction, gsd_scaling=gsd_scaling, ignore_gsd=args.ignore_gsd, ignore_resolution=ignore_resolution and args.ignore_gsd, has_gcp=reconstruction.has_gcp()) log.ODM_INFO('Classify: ' + str(args.pc_classify)) log.ODM_INFO('Create DSM: ' + str(args.dsm)) log.ODM_INFO('Create DTM: ' + str(args.dtm)) log.ODM_INFO('DEM input file {0} found: {1}'.format( dem_input, str(pc_model_found))) # define paths and create working directories odm_dem_root = tree.path('odm_dem') if not io.dir_exists(odm_dem_root): system.mkdir_p(odm_dem_root) if args.pc_classify and pc_model_found: pc_classify_marker = os.path.join(odm_dem_root, 'pc_classify_done.txt') if not io.file_exists(pc_classify_marker) or self.rerun(): log.ODM_INFO( "Classifying {} using Simple Morphological Filter".format( dem_input)) commands.classify(dem_input, args.smrf_scalar, args.smrf_slope, args.smrf_threshold, args.smrf_window, verbose=args.verbose) with open(pc_classify_marker, 'w') as f: f.write('Classify: smrf\n') f.write('Scalar: {}\n'.format(args.smrf_scalar)) f.write('Slope: {}\n'.format(args.smrf_slope)) f.write('Threshold: {}\n'.format(args.smrf_threshold)) f.write('Window: {}\n'.format(args.smrf_window)) progress = 20 self.update_progress(progress) if args.pc_rectify: commands.rectify(dem_input, args.debug) # Do we need to process anything here? if (args.dsm or args.dtm) and pc_model_found: dsm_output_filename = os.path.join(odm_dem_root, 'dsm.tif') dtm_output_filename = os.path.join(odm_dem_root, 'dtm.tif') if (args.dtm and not io.file_exists(dtm_output_filename)) or \ (args.dsm and not io.file_exists(dsm_output_filename)) or \ self.rerun(): products = [] if args.dsm or (args.dtm and args.dem_euclidean_map): products.append('dsm') if args.dtm: products.append('dtm') radius_steps = [(resolution / 100.0) / 2.0] for _ in range(args.dem_gapfill_steps - 1): radius_steps.append( radius_steps[-1] * 2) # 2 is arbitrary, maybe there's a better value? for product in products: commands.create_dem( dem_input, product, output_type='idw' if product == 'dtm' else 'max', radiuses=list(map(str, radius_steps)), gapfill=args.dem_gapfill_steps > 0, outdir=odm_dem_root, resolution=resolution / 100.0, decimation=args.dem_decimation, verbose=args.verbose, max_workers=args.max_concurrency, keep_unfilled_copy=args.dem_euclidean_map) dem_geotiff_path = os.path.join(odm_dem_root, "{}.tif".format(product)) bounds_file_path = os.path.join( tree.odm_georeferencing, 'odm_georeferenced_model.bounds.gpkg') if args.crop > 0 or args.boundary: # Crop DEM Cropper.crop( bounds_file_path, dem_geotiff_path, utils.get_dem_vars(args), keep_original=not args.optimize_disk_space) if args.dem_euclidean_map: unfilled_dem_path = io.related_file_path( dem_geotiff_path, postfix=".unfilled") if args.crop > 0 or args.boundary: # Crop unfilled DEM Cropper.crop( bounds_file_path, unfilled_dem_path, utils.get_dem_vars(args), keep_original=not args.optimize_disk_space) commands.compute_euclidean_map( unfilled_dem_path, io.related_file_path(dem_geotiff_path, postfix=".euclideand"), overwrite=True) if pseudo_georeference: pseudogeo.add_pseudo_georeferencing(dem_geotiff_path) if args.tiles: generate_dem_tiles(dem_geotiff_path, tree.path("%s_tiles" % product), args.max_concurrency) if args.cog: convert_to_cogeo(dem_geotiff_path, max_workers=args.max_concurrency) progress += 30 self.update_progress(progress) else: log.ODM_WARNING('Found existing outputs in: %s' % odm_dem_root) else: log.ODM_WARNING('DEM will not be generated')
def process(self, inputs, outputs): # Benchmarking start_time = system.now_raw() log.ODM_INFO('Running ODM DEM Cell') # get inputs args = self.inputs.args tree = self.inputs.tree las_model_found = io.file_exists(tree.odm_georeferencing_model_laz) # check if we rerun cell or not rerun_cell = (args.rerun is not None and args.rerun == 'odm_dem') or \ (args.rerun_all) or \ (args.rerun_from is not None and 'odm_dem' in args.rerun_from) log.ODM_INFO('Classify: ' + str(args.pc_classify)) log.ODM_INFO('Create DSM: ' + str(args.dsm)) log.ODM_INFO('Create DTM: ' + str(args.dtm)) log.ODM_INFO('DEM input file {0} found: {1}'.format( tree.odm_georeferencing_model_laz, str(las_model_found))) # define paths and create working directories odm_dem_root = tree.path('odm_dem') if not io.dir_exists(odm_dem_root): system.mkdir_p(odm_dem_root) if args.pc_classify and las_model_found: pc_classify_marker = os.path.join(odm_dem_root, 'pc_classify_done.txt') if not io.file_exists(pc_classify_marker) or rerun_cell: log.ODM_INFO( "Classifying {} using Simple Morphological Filter".format( tree.odm_georeferencing_model_laz)) commands.classify(tree.odm_georeferencing_model_laz, args.smrf_scalar, args.smrf_slope, args.smrf_threshold, args.smrf_window, verbose=args.verbose) with open(pc_classify_marker, 'w') as f: f.write('Classify: smrf\n') f.write('Scalar: {}\n'.format(args.smrf_scalar)) f.write('Slope: {}\n'.format(args.smrf_slope)) f.write('Threshold: {}\n'.format(args.smrf_threshold)) f.write('Window: {}\n'.format(args.smrf_window)) # Do we need to process anything here? if (args.dsm or args.dtm) and las_model_found: dsm_output_filename = os.path.join(odm_dem_root, 'dsm.tif') dtm_output_filename = os.path.join(odm_dem_root, 'dtm.tif') if (args.dtm and not io.file_exists(dtm_output_filename)) or \ (args.dsm and not io.file_exists(dsm_output_filename)) or \ rerun_cell: products = [] if args.dsm: products.append('dsm') if args.dtm: products.append('dtm') resolution = gsd.cap_resolution(args.dem_resolution, tree.opensfm_reconstruction, gsd_error_estimate=-3, ignore_gsd=args.ignore_gsd) radius_steps = [(resolution / 100.0) / 2.0] for _ in range(args.dem_gapfill_steps - 1): radius_steps.append( radius_steps[-1] * 2) # 2 is arbitrary, maybe there's a better value? for product in products: commands.create_dem( tree.odm_georeferencing_model_laz, product, output_type='idw' if product == 'dtm' else 'max', radiuses=map(str, radius_steps), gapfill=args.dem_gapfill_steps > 0, outdir=odm_dem_root, resolution=resolution / 100.0, decimation=args.dem_decimation, verbose=args.verbose, max_workers=args.max_concurrency) if args.crop > 0: bounds_shapefile_path = os.path.join( tree.odm_georeferencing, 'odm_georeferenced_model.bounds.shp') if os.path.exists(bounds_shapefile_path): Cropper.crop( bounds_shapefile_path, os.path.join(odm_dem_root, "{}.tif".format(product)), { 'TILED': 'YES', 'COMPRESS': 'LZW', 'BLOCKXSIZE': 512, 'BLOCKYSIZE': 512, 'NUM_THREADS': self.params.max_concurrency }) else: log.ODM_WARNING('Found existing outputs in: %s' % odm_dem_root) else: log.ODM_WARNING('DEM will not be generated') if args.time: system.benchmark(start_time, tree.benchmarking, 'Dem') log.ODM_INFO('Running ODM DEM Cell - Finished') return ecto.OK if args.end_with != 'odm_dem' else ecto.QUIT