Exemplo n.º 1
0
def create_25dmesh(inPointCloud,
                   outMesh,
                   dsm_radius=0.07,
                   dsm_resolution=0.05,
                   depth=8,
                   samples=1,
                   maxVertexCount=100000,
                   verbose=False,
                   available_cores=None,
                   method='gridded'):
    # Create DSM from point cloud

    # Create temporary directory
    mesh_directory = os.path.dirname(outMesh)
    tmp_directory = os.path.join(mesh_directory, 'tmp')
    if os.path.exists(tmp_directory):
        shutil.rmtree(tmp_directory)
    os.mkdir(tmp_directory)
    log.ODM_INFO('Created temporary directory: %s' % tmp_directory)

    radius_steps = [dsm_radius]

    log.ODM_INFO('Creating DSM for 2.5D mesh')

    commands.create_dem(inPointCloud,
                        'mesh_dsm',
                        output_type='max',
                        radiuses=map(str, radius_steps),
                        gapfill=True,
                        outdir=tmp_directory,
                        resolution=dsm_resolution,
                        verbose=verbose,
                        max_workers=available_cores)

    if method == 'gridded':
        mesh = dem_to_mesh_gridded(os.path.join(tmp_directory, 'mesh_dsm.tif'),
                                   outMesh, maxVertexCount, verbose)
    elif method == 'poisson':
        dsm_points = dem_to_points(
            os.path.join(tmp_directory, 'mesh_dsm.tif'),
            os.path.join(tmp_directory, 'dsm_points.ply'), verbose)
        mesh = screened_poisson_reconstruction(dsm_points,
                                               outMesh,
                                               depth=depth,
                                               samples=samples,
                                               maxVertexCount=maxVertexCount,
                                               threads=available_cores,
                                               verbose=verbose)
    else:
        raise 'Not a valid method: ' + method

    # Cleanup tmp
    if os.path.exists(tmp_directory):
        shutil.rmtree(tmp_directory)

    return mesh
Exemplo n.º 2
0
def create_25dmesh(inPointCloud, outMesh, dsm_radius=0.07, dsm_resolution=0.05, depth=8, samples=1, maxVertexCount=100000, verbose=False, available_cores=None, method='gridded'):
    # Create DSM from point cloud

    # Create temporary directory
    mesh_directory = os.path.dirname(outMesh)
    tmp_directory = os.path.join(mesh_directory, 'tmp')
    if os.path.exists(tmp_directory):
        shutil.rmtree(tmp_directory)
    os.mkdir(tmp_directory)
    log.ODM_INFO('Created temporary directory: %s' % tmp_directory)

    radius_steps = [dsm_radius]

    log.ODM_INFO('Creating DSM for 2.5D mesh')

    commands.create_dem(
            inPointCloud,
            'mesh_dsm',
            output_type='max',
            radiuses=map(str, radius_steps),
            gapfill=True,
            outdir=tmp_directory,
            resolution=dsm_resolution,
            verbose=verbose,
            max_workers=available_cores
        )

    if method == 'gridded':
        mesh = dem_to_mesh_gridded(os.path.join(tmp_directory, 'mesh_dsm.tif'), outMesh, maxVertexCount, verbose)
    elif method == 'poisson':
        dsm_points = dem_to_points(os.path.join(tmp_directory, 'mesh_dsm.tif'), os.path.join(tmp_directory, 'dsm_points.ply'), verbose)
        mesh = screened_poisson_reconstruction(dsm_points, outMesh, depth=depth, 
                                    samples=samples, 
                                    maxVertexCount=maxVertexCount, 
                                    threads=available_cores,
                                    verbose=verbose)
    else:
        raise 'Not a valid method: ' + method

    # Cleanup tmp
    if os.path.exists(tmp_directory):
        shutil.rmtree(tmp_directory)

    return mesh
Exemplo n.º 3
0
    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')
Exemplo n.º 4
0
                    default=3,
                    type=int,
                    help='Number of steps used to fill areas with gaps. Set to 0 to disable gap filling. '
                            'Starting with a radius equal to the output resolution, N different DEMs are generated with '
                            'progressively bigger radius using the inverse distance weighted (IDW) algorithm '
                            'and merged together. Remaining gaps are then merged using nearest neighbor interpolation. '
                            'Default: %(default)s')
args = parser.parse_args()

if not os.path.exists(args.point_cloud):
    print("%s does not exist" % args.point_cloud)
    exit(1)

outdir = os.path.dirname(args.point_cloud)

radius_steps = [args.resolution / 2.0]
for _ in range(args.gapfill_steps - 1):
    radius_steps.append(radius_steps[-1] * 2) # 2 is arbitrary, maybe there's a better value?

commands.create_dem(args.point_cloud,
                    args.type,
                    output_type='idw' if args.type == 'dtm' else 'max',
                    radiuses=list(map(str, radius_steps)),
                    gapfill=args.gapfill_steps > 0,
                    outdir=outdir,
                    resolution=args.resolution,
                    decimation=1,
                    verbose=True,
                    max_workers=multiprocessing.cpu_count(),
                    keep_unfilled_copy=False
                )
Exemplo n.º 5
0
    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')
Exemplo n.º 6
0
def create_25dmesh(inPointCloud,
                   outMesh,
                   dsm_radius=0.07,
                   dsm_resolution=0.05,
                   depth=8,
                   samples=1,
                   maxVertexCount=100000,
                   verbose=False,
                   available_cores=None,
                   method='gridded',
                   smooth_dsm=True):
    # Create DSM from point cloud

    # Create temporary directory
    mesh_directory = os.path.dirname(outMesh)
    tmp_directory = os.path.join(mesh_directory, 'tmp')
    if os.path.exists(tmp_directory):
        shutil.rmtree(tmp_directory)
    os.mkdir(tmp_directory)
    log.ODM_INFO('Created temporary directory: %s' % tmp_directory)

    radius_steps = [dsm_radius]
    for _ in range(2):
        radius_steps.append(radius_steps[-1] * 2)  # 2 is arbitrary

    log.ODM_INFO('Creating DSM for 2.5D mesh')

    commands.create_dem(inPointCloud,
                        'mesh_dsm',
                        output_type='max',
                        radiuses=list(map(str, radius_steps)),
                        gapfill=True,
                        outdir=tmp_directory,
                        resolution=dsm_resolution,
                        verbose=verbose,
                        max_workers=available_cores,
                        apply_smoothing=smooth_dsm)

    if method == 'gridded':
        mesh = dem_to_mesh_gridded(os.path.join(tmp_directory, 'mesh_dsm.tif'),
                                   outMesh,
                                   maxVertexCount,
                                   verbose,
                                   maxConcurrency=max(1, available_cores))
    elif method == 'poisson':
        dsm_points = dem_to_points(
            os.path.join(tmp_directory, 'mesh_dsm.tif'),
            os.path.join(tmp_directory, 'dsm_points.ply'), verbose)
        mesh = screened_poisson_reconstruction(
            dsm_points,
            outMesh,
            depth=depth,
            samples=samples,
            maxVertexCount=maxVertexCount,
            threads=max(
                1, available_cores - 1
            ),  # poissonrecon can get stuck on some machines if --threads == all cores
            verbose=verbose)
    else:
        raise 'Not a valid method: ' + method

    # Cleanup tmp
    if os.path.exists(tmp_directory):
        shutil.rmtree(tmp_directory)

    return mesh
Exemplo n.º 7
0
    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')
Exemplo n.º 8
0
    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