import os from opendm import io from opendm import log 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.cutline import compute_cutline from opendm.utils import double_quote from opendm import pseudogeo from opendm.multispectral import get_primary_band_name class ODMOrthoPhotoStage(types.ODM_Stage): def process(self, args, outputs): tree = outputs['tree'] reconstruction = outputs['reconstruction'] # define paths and create working directories system.mkdir_p(tree.odm_orthophoto) if args.skip_orthophoto: log.ODM_WARNING("--skip-orthophoto is set, no orthophoto will be generated") return if not io.file_exists(tree.odm_orthophoto_tif) or self.rerun(): resolution = 1.0 / (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) # odm_orthophoto definitions kwargs = { 'odm_ortho_bin': context.odm_orthophoto_path, 'log': tree.odm_orthophoto_log, 'ortho': tree.odm_orthophoto_render, 'corners': tree.odm_orthophoto_corners, 'res': resolution, 'bands': '', 'depth_idx': '' } 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([double_quote(b['name']) for b in reconstruction.multi_camera])) # If a RGB band is present, # use bit depth of the first non-RGB band depth_idx = None all_bands = [b['name'].lower() for b in reconstruction.multi_camera] for b in ['rgb', 'redgreenblue']: if b in all_bands: for idx in range(len(all_bands)): if all_bands[idx] != b: depth_idx = idx break break if depth_idx is not None: kwargs['depth_idx'] = '-outputDepthIdx %s' % depth_idx else: models.append(os.path.join(base_dir, model_file)) kwargs['models'] = ','.join(map(double_quote, models)) # run odm_orthophoto system.run('"{odm_ortho_bin}" -inputFiles {models} ' '-logFile "{log}" -outputFile "{ortho}" -resolution {res} -verbose ' '-outputCornerFile "{corners}" {bands} {depth_idx}'.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, 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.replace(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)