"""
Things to consider adding:
choice of las or laz output
set default values for lasground_new params
clip structures step
lasclassify params to identify buildings
use veg polygon (if given) instead of inverse ground polygon to clip veg points
"""
from file_functions import *
import os
import sys
import shutil
import numpy as np
import logging
##########################
# first let's define some functions that will be helpful
[docs]def las_files(directory):
"""returns list of all .las/.laz files in directory (at top level)"""
l = []
for name in os.listdir(directory):
if name.endswith('.las') or name.endswith('.laz'):
l.append(directory + name)
return l
# input working directory for LAStools and directory containing .las/.laz files
# creates a .txt file for LAStools containing list of .las/.laz file names
# returns the name of the .txt file.
[docs]def lof_text(pwd, src):
"""creates a .txt file in pwd (LAStools bin) containing a list of .las/.laz filenames from src directory"""
filename = pwd + 'file_list.txt'
f = open(filename, 'w+')
if type(src) == str:
for i in las_files(src):
f.write('%s\n' % i)
else:
# this is the case when there are multiple source folders
for i in [name for source in src for name in las_files(source)]:
f.write('%s\n' % i)
f.close()
return filename
# input .las/.laz filename, outputs point density (after running lasinfo)
[docs]def pd(filename):
"""returns point density from lasinfo output .txt file"""
# name of txt output file from lasinfo
filename = filename[:-4] + '.txt'
f = open(filename, 'r')
text = f.readlines()
for line in text:
if line.startswith('point density:'):
d = line.split(' ')
d = d[d.index('returns') + 1]
return float(d)
[docs]def get_largest(directory):
"""returns name of largest file in directory"""
largest_so_far = 0
filename = ''
for name in os.listdir(directory):
size = os.path.getsize(os.path.join(directory, name))
if size > largest_so_far:
largest_so_far = size
filename = name
return os.path.join(directory, filename)
[docs]def pts(filename, lastoolsdir):
"""returns number of points in las file"""
# call lasinfo on the file
cmd('%slasinfo.exe -i %s -otxt -histo number_of_returns 1' % (lastoolsdir, filename))
# name of txt output file from lasinfo
txt = filename[:-4] + '.txt'
f = open(txt, 'r')
text = f.readlines()
for line in text:
if line.endswith('element(s)\n'):
d = line.split(' ')
d = d[d.index('for') + 1]
return int(d)
# the main function that runs when 'run' button is clicked
@err_info
def process_lidar(lastoolsdir,
lidardir,
ground_poly,
cores,
units_code,
keep_orig_pts,
coarse_step,
coarse_bulge,
coarse_spike,
coarse_down_spike,
coarse_offset,
fine_step,
fine_bulge,
fine_spike,
fine_down_spike,
fine_offset
):
"""Executes main LAStools processing workflow. See readme for more info."""
classes = ['01-Default',
'02-Ground',
'05-Vegetation',
'06-Building'
]
if (ground_poly != '') and (keep_orig_pts == True):
# run on coarse and fine settings, need to clip and remove duplicates after merging
outdirs = ['00_separated',
'00_declassified',
'01_tiled',
'02a_lasground_new_coarse',
'02b_lasground_new_fine',
'03a_lasheight_coarse',
'03b_lasheight_fine',
'04a_lasclassify_coarse',
'04b_lasclassify_fine',
'05a_lastile_rm_buffer_coarse',
'05b_lastile_rm_buffer_fine',
'06a_separated_coarse',
'06b_separated_fine',
'07a_ground_clipped_coarse',
'07b_ground_clipped_fine',
'08_ground_merged',
'09_ground_rm_duplicates',
'10_veg_new_merged',
'11_veg_new_clipped',
'12_veg_merged',
'13_veg_rm_duplicates'
]
elif (ground_poly == '') and (keep_orig_pts == True):
# only classify with coarse settings, no clipping, but need to remove duplicates
outdirs = ['00_separated',
'00_declassified',
'01_tiled',
'02_lasground_new',
'03_lasheight',
'04_lasclassify',
'05_lastile_rm_buffer',
'06_separated',
'08_ground_merged',
'09_ground_rm_duplicates',
'12_veg_merged',
'13_veg_rm_duplicates'
]
elif (ground_poly == '') and (keep_orig_pts == False):
# only classify with coarse setting, no clipping or removing duplicates necessary
outdirs = ['00_separated',
'00_declassified',
'01_tiled',
'02_lasground_new',
'03_lasheight',
'04_lasclassify',
'05_lastile_rm_buffer',
'06_separated'
]
elif (ground_poly != '') and (keep_orig_pts == False):
# run on coarse and fine settings, clip, but no removing duplicates needed
outdirs = ['00_separated',
'00_declassified',
'01_tiled',
'02a_lasground_new_coarse',
'02b_lasground_new_fine',
'03a_lasheight_coarse',
'03b_lasheight_fine',
'04a_lasclassify_coarse',
'04b_lasclassify_fine',
'05a_lastile_rm_buffer_coarse',
'05b_lastile_rm_buffer_fine',
'06a_separated_coarse',
'06b_separated_fine',
'07a_ground_clipped_coarse',
'07b_ground_clipped_fine',
'08_ground_merged',
'10_veg_new_merged',
'11_veg_new_clipped',
'12_veg_merged'
]
# make new directories for output from each step in processing
for outdir in outdirs:
if os.path.isdir(lidardir + outdir) == False:
os.mkdir(lidardir + outdir)
if len(os.listdir(lidardir + outdirs[0])) != 0:
msg = 'Output directories must initially be empty. Move or delete the data currently in output directories.'
logging.error(msg)
raise Exception(msg)
# in each 'separated' folder, create subdirs for each class type
if ground_poly != '':
sepdirs = [lidardir + '00_separated',
lidardir + '06a_separated_coarse',
lidardir + '06b_separated_fine'
]
else:
sepdirs = [lidardir + '00_separated',
lidardir + '06_separated'
]
for sepdir in sepdirs:
for class_type in classes:
class_dir = sepdir + '/' + class_type
if os.path.isdir(class_dir) == False:
os.mkdir(class_dir)
logging.info('Created directories for output data')
##################################
# create declassified points
logging.info('Declassifying copy of original point cloud...')
# get list of filenames for original LiDAR data (all .las and .laz files in lidardir)
lidar_files = []
for path, subdirs, files in os.walk(lidardir):
for name in files:
if name.endswith('.las') or name.endswith('.laz'):
lidar_files.append(path + '/' + name)
if lidar_files == []:
msg = 'No .las or .laz files in %s or its subdirectories' % lidardir
logging.error(msg)
raise Exception(msg)
# copy original files into '00_declassified' folder
for name in lidar_files:
shutil.copyfile(name, lidardir + '00_declassified/' + os.path.basename(name))
# make list of files for LASTools to process
lof = lof_text(lastoolsdir, lidardir + '00_declassified/')
# call LAStools command to declassify points and get point density
cmd('%slasinfo.exe -lof %s -set_classification 1 -otxt -cd' % (lastoolsdir, lof))
logging.info('OK')
# separate original data by class type
logging.info('Separating original data by class type...')
filename = lastoolsdir + 'file_list.txt'
f = open(filename, 'w+')
for i in lidar_files:
f.write('%s\n' % i)
f.close()
lof = filename
for class_type in classes:
odir = lidardir + '00_separated' + '/' + class_type + '/'
class_code = int(class_type.split('-')[0])
cmd('%slas2las.exe -lof %s -cores %i -keep_classification %i -odir %s -olas' % (
lastoolsdir, lof, cores, class_code, odir))
logging.info('OK')
########################
# create tiling (max 1.5M pts per tile)
logging.info('Creating tiling...')
# get point density for each .las file
ds = []
for filename in las_files(lidardir + '00_declassified/'):
ds.append(pd(filename))
# use max point density out of all files to determine tile size
max_d = max(ds)
# width of square tile so we have max of 1.5M pts per tile (assuming same number of points per tile)
# throw in another factor of 0.5 to make sure tiles will be small enough, round to nearest 10
tile_size = round(0.5 * np.sqrt((1.5 * 10 ** 6) / max_d), -1)
logging.info('Using tile size of %i' % tile_size)
odir = lidardir + '01_tiled/'
# call LAStools command to create tiling
cmd('%slastile.exe -lof %s -cores %i -o tile.las -tile_size %i -buffer 5 -faf -odir %s -olas' % (
lastoolsdir, lof, cores, tile_size, odir))
logging.info('OK')
# check to make sure tiles are small enough
logging.info('Checking if largest file has < 1.5M pts (to avoid licensing restrictions)...')
largest_file = get_largest(odir)
num = pts(largest_file, lastoolsdir)
if num < 1500000:
logging.info('Largest file has %i points, tiles small enough.' % num)
else:
logging.info('Tile size not small enough. Retrying with a smaller tile size...')
while num >= 1500000:
# delete original set of tiles
folder = odir
for the_file in os.listdir(folder):
file_path = os.path.join(folder, the_file)
try:
if os.path.isfile(file_path):
os.unlink(file_path)
except:
logging.warning('Couldn\'nt delete %s' % file_path)
# redo tiling
tile_size = int(tile_size * num * 1.0 / 1500000)
logging.info('Using tile size of %i' % tile_size)
cmd('%slastile.exe -lof %s -cores %i -o tile.las -tile_size %i -buffer 5 -faf -odir %s -olas' % (
lastoolsdir, lof, cores, tile_size, odir))
# recheck largest tile number of points
logging.info('Checking if largest file has < 1.5M pts (to avoid licensing restrictions)...')
largest_file = get_largest(odir)
num = pts(largest_file, lastoolsdir)
if num >= 1500000:
logging.info('Tile size not small enough. Retrying with a smaller tile size...')
logging.info('OK')
########################
# run lasground_new on coarse and fine settings
logging.info('Running ground classification on coarse setting...')
lof = lof_text(lastoolsdir, lidardir + '01_tiled/')
if ground_poly != '':
odir = lidardir + '02a_lasground_new_coarse/'
else:
odir = lidardir + '02_lasground_new'
cmd(
'%slasground_new.exe -lof %s -cores %i %s -step %s -bulge %s -spike %s -down_spike %s -offset %s -hyper_fine -odir %s -olas' % (
lastoolsdir,
lof,
cores,
units_code,
coarse_step,
coarse_bulge,
coarse_spike,
coarse_down_spike,
coarse_offset,
odir
)
)
logging.info('OK')
if ground_poly != '':
logging.info('Running ground classification on fine setting...')
odir = lidardir + '02b_lasground_new_fine/'
cmd(
'%slasground_new.exe -lof %s -cores %i %s -step %s -bulge %s -spike %s -down_spike %s -offset %s -hyper_fine -odir %s -olas' % (
lastoolsdir,
lof,
cores,
units_code,
fine_step,
fine_bulge,
fine_spike,
fine_down_spike,
fine_offset,
odir
)
)
logging.info('OK')
##########################
# run lasheight on each data set
logging.info('Measuring height above ground for non-ground points...')
if ground_poly != '':
lof = lof_text(lastoolsdir, lidardir + '02a_lasground_new_coarse/')
odir = lidardir + '03a_lasheight_coarse/'
else:
lof = lof_text(lastoolsdir, lidardir + '02_lasground_new/')
odir = lidardir + '03_lasheight/'
cmd('%slasheight.exe -lof %s -cores %i -odir %s -olas' % (lastoolsdir, lof, cores, odir))
if ground_poly != '':
lof = lof_text(lastoolsdir, lidardir + '02b_lasground_new_fine/')
odir = lidardir + '03b_lasheight_fine/'
cmd('%slasheight.exe -lof %s -cores %i -odir %s -olas' % (lastoolsdir, lof, cores, odir))
logging.info('OK')
##########################
# run lasclassify on each data set
logging.info('Classifying non-ground points on coarse setting...')
if ground_poly != '':
lof = lof_text(lastoolsdir, lidardir + '03a_lasheight_coarse/')
odir = lidardir + '04a_lasclassify_coarse/'
else:
lof = lof_text(lastoolsdir, lidardir + '03_lasheight/')
odir = lidardir + '04_lasclassify/'
cmd('%slasclassify.exe -lof %s -cores %i %s -odir %s -olas' % (lastoolsdir, lof, cores, units_code, odir))
logging.info('OK')
if ground_poly != '':
logging.info('Classifying non-ground points on fine setting...')
lof = lof_text(lastoolsdir, lidardir + '03b_lasheight_fine/')
odir = lidardir + '04b_lasclassify_fine/'
cmd('%slasclassify.exe -lof %s -cores %i %s -odir %s -olas' % (lastoolsdir, lof, cores, units_code, odir))
logging.info('OK')
##########################
# remove tile buffers on each data set
logging.info('Removing tile buffers...')
if ground_poly != '':
lof = lof_text(lastoolsdir, lidardir + '04a_lasclassify_coarse/')
odir = lidardir + '05a_lastile_rm_buffer_coarse/'
else:
lof = lof_text(lastoolsdir, lidardir + '04_lasclassify/')
odir = lidardir + '05_lastile_rm_buffer/'
cmd('%slastile.exe -lof %s -cores %i -remove_buffer -odir %s -olas' % (lastoolsdir, lof, cores, odir))
if ground_poly != '':
lof = lof_text(lastoolsdir, lidardir + '04b_lasclassify_fine/')
odir = lidardir + '05b_lastile_rm_buffer_fine/'
cmd('%slastile.exe -lof %s -cores %i -remove_buffer -odir %s -olas' % (lastoolsdir, lof, cores, odir))
logging.info('OK')
##########################
# separate into files for each class type
logging.info('Separating points by class type on coarse setting...')
# coarse
if ground_poly != '':
lof = lof_text(lastoolsdir, lidardir + '05a_lastile_rm_buffer_coarse/')
podir = lidardir + '06a_separated_coarse'
else:
lof = lof_text(lastoolsdir, lidardir + '05_lastile_rm_buffer/')
podir = lidardir + '06_separated'
for class_type in classes:
odir = podir + '/' + class_type + '/'
class_code = int(class_type.split('-')[0])
cmd('%slas2las.exe -lof %s -cores %i -keep_classification %i -odir %s -olas' % (
lastoolsdir, lof, cores, class_code, odir))
logging.info('OK')
if ground_poly == '' and (keep_orig_pts == False):
ground_results = podir + '/' + '02-Ground' + '/'
veg_results = podir + '/' + '05-Vegetation' + '/'
if ground_poly != '':
logging.info('Separating points by class type on fine setting...')
# fine
lof = lof_text(lastoolsdir, lidardir + '05b_lastile_rm_buffer_fine/')
for class_type in classes:
odir = lidardir + '06b_separated_fine' + '/' + class_type + '/'
class_code = int(class_type.split('-')[0])
cmd('%slas2las.exe -lof %s -cores %i -keep_classification %i -odir %s -olas' % (
lastoolsdir, lof, cores, class_code, odir))
logging.info('OK')
##########################
# clip ground data sets with ground polygon
if ground_poly != '':
logging.info('Clipping ground points to inverse ground polygon on coarse setting...')
# keep points outside ground polygon for coarse setting (-interior flag)
lof = lof_text(lastoolsdir, lidardir + '06a_separated_coarse' + '/' + '02-Ground' + '/')
odir = lidardir + '07a_ground_clipped_coarse/'
cmd('%slasclip.exe -lof %s -cores %i -poly %s -interior -donuts -odir %s -olas' % (
lastoolsdir, lof, cores, ground_poly, odir))
logging.info('OK')
logging.info('Clipping ground points to ground polygon on fine setting...')
# keep points inside ground polygon for fine setting
lof = lof_text(lastoolsdir, lidardir + '06b_separated_fine' + '/' + '02-Ground' + '/')
odir = lidardir + '07b_ground_clipped_fine/'
cmd('%slasclip.exe -lof %s -cores %i -poly %s -donuts -odir %s -olas' % (
lastoolsdir, lof, cores, ground_poly, odir))
logging.info('OK')
##########################
# merge
# merge processed ground points with original data set ground points
if keep_orig_pts == True:
logging.info('Merging new and original ground points...')
if ground_poly != '':
sources = [lidardir + '07a_ground_clipped_coarse/', lidardir + '07b_ground_clipped_fine/',
lidardir + '00_separated' + '/' + '02-Ground' + '/']
else:
sources = [lidardir + '06_separated' + '/' + '02-Ground' + '/',
lidardir + '00_separated' + '/' + '02-Ground' + '/']
# just use new points
elif ground_poly != '':
logging.info('Merging new ground points...')
sources = [lidardir + '07a_ground_clipped_coarse/', lidardir + '07b_ground_clipped_fine/']
if (keep_orig_pts == True) or (ground_poly != ''):
lof = lof_text(lastoolsdir, sources)
odir = lidardir + '08_ground_merged/'
ground_results = odir # will be overwritten if rm_duplicates block runs
cmd('%slastile.exe -lof %s -cores %i -o tile.las -tile_size %i -faf -odir %s -olas' % (
lastoolsdir, lof, cores, tile_size, odir))
logging.info('OK')
##########################
# remove duplicate ground points
if (keep_orig_pts == True):
logging.info('Removing duplicate ground points...')
lof = lof_text(lastoolsdir, lidardir + '08_ground_merged/')
odir = lidardir + '09_ground_rm_duplicates/'
ground_results = odir
cmd('%slasduplicate.exe -lof %s -cores %i -lowest_z -odir %s -olas' % (lastoolsdir, lof, cores, odir))
logging.info('OK')
##########################
# merge new veg points
if ground_poly != '':
logging.info('Merging new vegetation points from coarse and fine run...')
sources = [lidardir + '06a_separated_coarse' + '/' + '05-Vegetation' + '/',
lidardir + '06b_separated_fine' + '/' + '05-Vegetation' + '/']
lof = lof_text(lastoolsdir, sources)
odir = lidardir + '10_veg_new_merged/'
cmd('%slastile.exe -lof %s -cores %i -o tile.las -tile_size %i -faf -odir %s -olas' % (
lastoolsdir, lof, cores, tile_size, odir))
logging.info('OK')
#########################
# clip new veg points
# keeping points outside the ground polygon
logging.info('Clipping new vegetation points...')
lof = lof_text(lastoolsdir, lidardir + '10_veg_new_merged/')
odir = lidardir + '11_veg_new_clipped/'
cmd('%slasclip.exe -lof %s -cores %i -poly %s -interior -donuts -odir %s -olas' % (
lastoolsdir, lof, cores, ground_poly, odir))
logging.info('OK')
#########################
# merge with original veg points
if (keep_orig_pts == True):
logging.info('Merging new and original vegetation points...')
if ground_poly != '':
sources = [lidardir + '11_veg_new_clipped/', lidardir + '00_separated' + '/' + '05-Vegetation' + '/']
else:
sources = [lidardir + '06_separated' + '/' + '05-Vegetation' + '/',
lidardir + '00_separated' + '/' + '05-Vegetation' + '/']
elif ground_poly != '':
logging.info('Retiling new vegetation points...')
sources = [lidardir + '11_veg_new_clipped/']
if (keep_orig_pts == True) or (ground_poly != ''):
lof = lof_text(lastoolsdir, sources)
odir = lidardir + '12_veg_merged/'
veg_results = odir # will be overwritten if rm_duplicates block runs
cmd('%slastile.exe -lof %s -cores %i -o tile.las -tile_size %i -faf -odir %s -olas' % (
lastoolsdir, lof, cores, tile_size, odir))
logging.info('OK')
#########################
# remove duplicate veg points
if keep_orig_pts:
logging.info('Removing duplicate vegetation points...')
lof = lof_text(lastoolsdir, lidardir + '12_veg_merged/')
odir = lidardir + '13_veg_rm_duplicates/'
veg_results = odir
cmd('%slasduplicate.exe -lof %s -cores %i -lowest_z -odir %s -olas' % (lastoolsdir, lof, cores, odir))
logging.info('OK')
logging.info('Processing finished.')
logging.info('Outputs in:')
logging.info('%s\n%s' % (ground_results, veg_results))
return
#####################################################################
if __name__ == '__main__':
# initialize the logger
init_logger(__file__)
# make the GUI window
root = tk.Tk()
root.wm_title('LiDAR Reprocessing App (based on LAStools)')
# specify relevant directories/files
L1 = tk.Label(root, text='LAStools /bin/ directory:')
L1.grid(sticky=tk.E, row=0, column=1)
E1 = tk.Entry(root, bd=5)
E1.insert(tk.END, 'C:\\LAStools\\bin\\')
E1.grid(row=0, column=2)
b1 = tk.Button(root, text='Browse', command=lambda: browse(root, E1, select='folder'))
b1.grid(sticky=tk.W, row=0, column=3)
L2 = tk.Label(root, text='LiDAR data directory:')
L2.grid(sticky=tk.E, row=1, column=1)
E2 = tk.Entry(root, bd=5)
E2.insert(tk.END, sys.path[0])
E2.grid(row=1, column=2)
b2 = tk.Button(root, text='Browse', command=lambda: browse(root, E2, select='folder'))
b2.grid(sticky=tk.W, row=1, column=3)
L3 = tk.Label(root, text='Ground area .shp file (optional):')
shp_var = tk.StringVar()
L3.grid(sticky=tk.E, row=2, column=1)
E3 = tk.Entry(root, bd=5, textvariable=shp_var)
E3.grid(row=2, column=2)
b3 = tk.Button(root, text='Browse', command=lambda: browse(root, E3, select='file', ftypes=[('Shapefile', '*.shp'),
('All files', '*')]
)
)
b3.grid(sticky=tk.W, row=2, column=3)
# if no ground shapefile is provided, disable the fine setting and just run on "coarse"
def trace_choice(*args):
if shp_var.get() == '':
for widget in [E1b, E2b, E3b, E4b, E5b]:
widget.config(state=tk.DISABLED)
else:
for widget in [E1b, E2b, E3b, E4b, E5b]:
widget.config(state='normal')
shp_var.trace('w', trace_choice)
# specify lasground_new parameters
root.grid_rowconfigure(5, minsize=80)
LC1 = tk.Label(root, text='standard/coarse classification parameters:')
LC1.grid(row=5, column=0, columnspan=2)
L1a = tk.Label(root, text='step size:')
L1a.grid(sticky=tk.E, row=6)
E1a = tk.Entry(root, bd=5)
E1a.grid(row=6, column=1)
L2a = tk.Label(root, text='bulge:')
L2a.grid(sticky=tk.E, row=7)
E2a = tk.Entry(root, bd=5)
E2a.grid(row=7, column=1)
L3a = tk.Label(root, text='spike:')
L3a.grid(sticky=tk.E, row=8)
E3a = tk.Entry(root, bd=5)
E3a.grid(row=8, column=1)
L4a = tk.Label(root, text='down spike:')
L4a.grid(sticky=tk.E, row=9)
E4a = tk.Entry(root, bd=5)
E4a.grid(row=9, column=1)
L5a = tk.Label(root, text='offset:')
L5a.grid(sticky=tk.E, row=10)
E5a = tk.Entry(root, bd=5)
E5a.grid(row=10, column=1)
LC2 = tk.Label(root, text='fine classification parameters (in ground area):')
LC2.grid(row=5, column=2, columnspan=2)
L1b = tk.Label(root, text='step size:')
L1b.grid(sticky=tk.E, row=6, column=2)
E1b = tk.Entry(root, bd=5, state=tk.DISABLED)
E1b.grid(row=6, column=3)
L2b = tk.Label(root, text='bulge:')
L2b.grid(sticky=tk.E, row=7, column=2)
E2b = tk.Entry(root, bd=5, state=tk.DISABLED)
E2b.grid(row=7, column=3)
L3b = tk.Label(root, text='spike:')
L3b.grid(sticky=tk.E, row=8, column=2)
E3b = tk.Entry(root, bd=5, state=tk.DISABLED)
E3b.grid(row=8, column=3)
L4b = tk.Label(root, text='down spike:')
L4b.grid(sticky=tk.E, row=9, column=2)
E4b = tk.Entry(root, bd=5, state=tk.DISABLED)
E4b.grid(row=9, column=3)
L5b = tk.Label(root, text='offset:')
L5b.grid(sticky=tk.E, row=10, column=2)
E5b = tk.Entry(root, bd=5, state=tk.DISABLED)
E5b.grid(row=10, column=3)
# specify units
L5 = tk.Label(root, text='Units')
L5.grid(sticky=tk.W, row=11, column=2)
root.grid_rowconfigure(11, minsize=80)
unit_var = tk.StringVar()
R5m = tk.Radiobutton(root, text='Meters', variable=unit_var, value=' ')
R5m.grid(sticky=tk.E, row=12, column=1)
R5f = tk.Radiobutton(root, text='US Feet', variable=unit_var, value=' -feet -elevation_feet ')
R5f.grid(row=12, column=2)
unit_var.set(' ')
# specify number of cores
L4 = tk.Label(root, text='Number of cores for processing')
L4.grid(sticky=tk.E, row=13, column=1, columnspan=2)
root.grid_rowconfigure(13, minsize=80)
core_num = tk.IntVar()
R1 = tk.Radiobutton(root, text='1', variable=core_num, value=1)
R1.grid(sticky=tk.E, row=14, column=1)
R2 = tk.Radiobutton(root, text='2', variable=core_num, value=2)
R2.grid(row=14, column=2)
R4 = tk.Radiobutton(root, text='4', variable=core_num, value=4)
R4.grid(sticky=tk.W, row=14, column=3)
R8 = tk.Radiobutton(root, text='8', variable=core_num, value=8)
R8.grid(sticky=tk.E, row=15, column=1)
R16 = tk.Radiobutton(root, text='16', variable=core_num, value=16)
R16.grid(row=15, column=2)
R32 = tk.Radiobutton(root, text='32', variable=core_num, value=32)
R32.grid(sticky=tk.W, row=15, column=3)
core_num.set(16)
L5 = tk.Label(root, text='Keep original ground/veg points: ')
L5.grid(sticky=tk.E, row=16, column=1)
keep_originals = tk.BooleanVar()
C1 = tk.Checkbutton(root, variable=keep_originals)
C1.grid(sticky=tk.W, row=16, column=2)
keep_originals.set(True)
# make 'Run' button in GUI to call the process_lidar() function
b = tk.Button(root, text=' Run ', command=lambda: process_lidar(lastoolsdir=E1.get(),
lidardir=E2.get(),
ground_poly=E3.get(),
cores=core_num.get(),
units_code=unit_var.get()[1:-1],
keep_orig_pts=keep_originals.get(),
coarse_step=E1a.get(),
coarse_bulge=E2a.get(),
coarse_spike=E3a.get(),
coarse_down_spike=E4a.get(),
coarse_offset=E5a.get(),
fine_step=E1b.get(),
fine_bulge=E2b.get(),
fine_spike=E3b.get(),
fine_down_spike=E4b.get(),
fine_offset=E5b.get()
)
)
b.grid(sticky=tk.W, row=17, column=2)
root.grid_rowconfigure(17, minsize=80)
root.mainloop()