BERATools 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- beratools/__init__.py +1 -7
- beratools/core/algo_centerline.py +491 -351
- beratools/core/algo_common.py +497 -0
- beratools/core/algo_cost.py +192 -0
- beratools/core/{dijkstra_algorithm.py → algo_dijkstra.py} +503 -460
- beratools/core/algo_footprint_rel.py +577 -0
- beratools/core/algo_line_grouping.py +944 -0
- beratools/core/algo_merge_lines.py +214 -0
- beratools/core/algo_split_with_lines.py +304 -0
- beratools/core/algo_tiler.py +428 -0
- beratools/core/algo_vertex_optimization.py +469 -0
- beratools/core/constants.py +52 -86
- beratools/core/logger.py +76 -85
- beratools/core/tool_base.py +196 -133
- beratools/gui/__init__.py +11 -15
- beratools/gui/{beratools.json → assets/beratools.json} +2185 -2300
- beratools/gui/batch_processing_dlg.py +513 -463
- beratools/gui/bt_data.py +481 -487
- beratools/gui/bt_gui_main.py +710 -691
- beratools/gui/main.py +26 -0
- beratools/gui/map_window.py +162 -146
- beratools/gui/tool_widgets.py +725 -493
- beratools/tools/Beratools_r_script.r +1120 -1120
- beratools/tools/Ht_metrics.py +116 -116
- beratools/tools/__init__.py +7 -7
- beratools/tools/batch_processing.py +136 -132
- beratools/tools/canopy_threshold_relative.py +672 -670
- beratools/tools/canopycostraster.py +222 -222
- beratools/tools/centerline.py +136 -176
- beratools/tools/common.py +857 -885
- beratools/tools/fl_regen_csf.py +428 -428
- beratools/tools/forest_line_attributes.py +408 -408
- beratools/tools/line_footprint_absolute.py +213 -363
- beratools/tools/line_footprint_fixed.py +436 -282
- beratools/tools/line_footprint_functions.py +733 -720
- beratools/tools/line_footprint_relative.py +73 -64
- beratools/tools/line_grouping.py +45 -0
- beratools/tools/ln_relative_metrics.py +615 -615
- beratools/tools/r_cal_lpi_elai.r +24 -24
- beratools/tools/r_generate_pd_focalraster.r +100 -100
- beratools/tools/r_interface.py +79 -79
- beratools/tools/r_point_density.r +8 -8
- beratools/tools/rpy_chm2trees.py +86 -86
- beratools/tools/rpy_dsm_chm_by.py +81 -81
- beratools/tools/rpy_dtm_by.py +63 -63
- beratools/tools/rpy_find_cellsize.py +43 -43
- beratools/tools/rpy_gnd_csf.py +74 -74
- beratools/tools/rpy_hummock_hollow.py +85 -85
- beratools/tools/rpy_hummock_hollow_raster.py +71 -71
- beratools/tools/rpy_las_info.py +51 -51
- beratools/tools/rpy_laz2las.py +40 -40
- beratools/tools/rpy_lpi_elai_lascat.py +466 -466
- beratools/tools/rpy_normalized_lidar_by.py +56 -56
- beratools/tools/rpy_percent_above_dbh.py +80 -80
- beratools/tools/rpy_points2trees.py +88 -88
- beratools/tools/rpy_vegcoverage.py +94 -94
- beratools/tools/tiler.py +48 -206
- beratools/tools/tool_template.py +69 -54
- beratools/tools/vertex_optimization.py +61 -620
- beratools/tools/zonal_threshold.py +144 -144
- beratools-0.2.2.dist-info/METADATA +108 -0
- beratools-0.2.2.dist-info/RECORD +74 -0
- {beratools-0.2.0.dist-info → beratools-0.2.2.dist-info}/WHEEL +1 -1
- {beratools-0.2.0.dist-info → beratools-0.2.2.dist-info}/licenses/LICENSE +22 -22
- beratools/gui/cli.py +0 -18
- beratools/gui/gui.json +0 -8
- beratools/gui_tk/ASCII Banners.txt +0 -248
- beratools/gui_tk/__init__.py +0 -20
- beratools/gui_tk/beratools_main.py +0 -515
- beratools/gui_tk/bt_widgets.py +0 -442
- beratools/gui_tk/cli.py +0 -18
- beratools/gui_tk/img/BERALogo.png +0 -0
- beratools/gui_tk/img/closed.gif +0 -0
- beratools/gui_tk/img/closed.png +0 -0
- beratools/gui_tk/img/open.gif +0 -0
- beratools/gui_tk/img/open.png +0 -0
- beratools/gui_tk/img/tool.gif +0 -0
- beratools/gui_tk/img/tool.png +0 -0
- beratools/gui_tk/main.py +0 -14
- beratools/gui_tk/map_window.py +0 -144
- beratools/gui_tk/runner.py +0 -1481
- beratools/gui_tk/tooltip.py +0 -55
- beratools/third_party/pyqtlet2/__init__.py +0 -9
- beratools/third_party/pyqtlet2/leaflet/__init__.py +0 -26
- beratools/third_party/pyqtlet2/leaflet/control/__init__.py +0 -6
- beratools/third_party/pyqtlet2/leaflet/control/control.py +0 -59
- beratools/third_party/pyqtlet2/leaflet/control/draw.py +0 -52
- beratools/third_party/pyqtlet2/leaflet/control/layers.py +0 -20
- beratools/third_party/pyqtlet2/leaflet/core/Parser.py +0 -24
- beratools/third_party/pyqtlet2/leaflet/core/__init__.py +0 -2
- beratools/third_party/pyqtlet2/leaflet/core/evented.py +0 -180
- beratools/third_party/pyqtlet2/leaflet/layer/__init__.py +0 -5
- beratools/third_party/pyqtlet2/leaflet/layer/featuregroup.py +0 -34
- beratools/third_party/pyqtlet2/leaflet/layer/icon/__init__.py +0 -1
- beratools/third_party/pyqtlet2/leaflet/layer/icon/icon.py +0 -30
- beratools/third_party/pyqtlet2/leaflet/layer/imageoverlay.py +0 -18
- beratools/third_party/pyqtlet2/leaflet/layer/layer.py +0 -105
- beratools/third_party/pyqtlet2/leaflet/layer/layergroup.py +0 -45
- beratools/third_party/pyqtlet2/leaflet/layer/marker/__init__.py +0 -1
- beratools/third_party/pyqtlet2/leaflet/layer/marker/marker.py +0 -91
- beratools/third_party/pyqtlet2/leaflet/layer/tile/__init__.py +0 -2
- beratools/third_party/pyqtlet2/leaflet/layer/tile/gridlayer.py +0 -4
- beratools/third_party/pyqtlet2/leaflet/layer/tile/tilelayer.py +0 -16
- beratools/third_party/pyqtlet2/leaflet/layer/vector/__init__.py +0 -5
- beratools/third_party/pyqtlet2/leaflet/layer/vector/circle.py +0 -15
- beratools/third_party/pyqtlet2/leaflet/layer/vector/circlemarker.py +0 -18
- beratools/third_party/pyqtlet2/leaflet/layer/vector/path.py +0 -5
- beratools/third_party/pyqtlet2/leaflet/layer/vector/polygon.py +0 -14
- beratools/third_party/pyqtlet2/leaflet/layer/vector/polyline.py +0 -18
- beratools/third_party/pyqtlet2/leaflet/layer/vector/rectangle.py +0 -14
- beratools/third_party/pyqtlet2/leaflet/map/__init__.py +0 -1
- beratools/third_party/pyqtlet2/leaflet/map/map.py +0 -220
- beratools/third_party/pyqtlet2/mapwidget.py +0 -45
- beratools/third_party/pyqtlet2/web/custom.js +0 -43
- beratools/third_party/pyqtlet2/web/map.html +0 -23
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/layers.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-icon.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/images/marker-shadow.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.css +0 -656
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.js +0 -6
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.codeclimate.yml +0 -14
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.editorconfig +0 -4
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.gitattributes +0 -22
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.travis.yml +0 -43
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/LICENSE +0 -20
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/layers.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-icon.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/marker-shadow.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet-2x.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.png +0 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/images/spritesheet.svg +0 -156
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.css +0 -10
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.js +0 -10
- beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/LICENSE +0 -22
- beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/leaflet.rotatedMarker.js +0 -57
- beratools/tools/forest_line_ecosite.py +0 -216
- beratools/tools/lapis_all.py +0 -103
- beratools/tools/least_cost_path_from_chm.py +0 -152
- beratools-0.2.0.dist-info/METADATA +0 -63
- beratools-0.2.0.dist-info/RECORD +0 -142
- /beratools/gui/{img → assets}/BERALogo.png +0 -0
- /beratools/gui/{img → assets}/closed.gif +0 -0
- /beratools/gui/{img → assets}/closed.png +0 -0
- /beratools/{gui_tk → gui/assets}/gui.json +0 -0
- /beratools/gui/{img → assets}/open.gif +0 -0
- /beratools/gui/{img → assets}/open.png +0 -0
- /beratools/gui/{img → assets}/tool.gif +0 -0
- /beratools/gui/{img → assets}/tool.png +0 -0
- {beratools-0.2.0.dist-info → beratools-0.2.2.dist-info}/entry_points.txt +0 -0
|
@@ -1,466 +1,466 @@
|
|
|
1
|
-
import math
|
|
2
|
-
import os
|
|
3
|
-
import winreg
|
|
4
|
-
import time
|
|
5
|
-
import geopandas
|
|
6
|
-
import sys
|
|
7
|
-
import numpy
|
|
8
|
-
import xrspatial.focal as focal
|
|
9
|
-
from xrspatial import convolution
|
|
10
|
-
import xarray as xr
|
|
11
|
-
|
|
12
|
-
from multiprocessing.pool import Pool
|
|
13
|
-
from beratools.tools.common import *
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class OperationCancelledException(Exception):
|
|
17
|
-
pass
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
try: # integrated R env
|
|
21
|
-
# check R language within env
|
|
22
|
-
current_env_path = os.environ['CONDA_PREFIX']
|
|
23
|
-
# if os.path.isdir(current_env_path):
|
|
24
|
-
os.environ['R_HOME'] = os.path.join(current_env_path, r"Lib\R")
|
|
25
|
-
os.environ['R_USER'] = os.path.expanduser('~')
|
|
26
|
-
os.environ['R_LIBS_USER'] = os.path.join(current_env_path, r"Lib\R\library")
|
|
27
|
-
|
|
28
|
-
except FileNotFoundError:
|
|
29
|
-
print("Warning: Please install R for this process!!")
|
|
30
|
-
exit()
|
|
31
|
-
|
|
32
|
-
import rpy2.robjects as robjects
|
|
33
|
-
from rpy2.robjects.packages import importr, data
|
|
34
|
-
from rpy2.robjects.vectors import StrVector
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def lpi_lai(arg):
|
|
38
|
-
pdTotal = arg[0]
|
|
39
|
-
pdGround = arg[1]
|
|
40
|
-
out_folder = arg[2]
|
|
41
|
-
filename = arg[3]
|
|
42
|
-
scan_angle = float(arg[4])
|
|
43
|
-
|
|
44
|
-
##variable for not calling R
|
|
45
|
-
cell_size = float(arg[5])
|
|
46
|
-
radius = float(arg[6])
|
|
47
|
-
tfocal_filename = filename + "_tfocal_py.tif"
|
|
48
|
-
gfocal_filename = filename + "_gfocal_py.tif"
|
|
49
|
-
# out_tfocal = os.path.join(out_folder, tfocal_filename)
|
|
50
|
-
# out_gfocal = os.path.join(out_folder, gfocal_filename)
|
|
51
|
-
|
|
52
|
-
## output files variables
|
|
53
|
-
out_lpi_fielname = filename + "_LPI_py.tif"
|
|
54
|
-
out_elai_fielname = filename + "_eLAI_py.tif"
|
|
55
|
-
LPI_folder = os.path.join(out_folder, "LPI")
|
|
56
|
-
eLAI_folder = os.path.join(out_folder, "eLAI")
|
|
57
|
-
out_lpi = os.path.join(LPI_folder, out_lpi_fielname)
|
|
58
|
-
out_elai = os.path.join(eLAI_folder, out_elai_fielname)
|
|
59
|
-
|
|
60
|
-
# Working out the searching radius
|
|
61
|
-
# with rasterio.open(chm) as image:
|
|
62
|
-
# ndarray = image.read(1)
|
|
63
|
-
# ndarray[ndarray==image.nodata]=numpy.NaN
|
|
64
|
-
# ndarray[ndarray <0.0] = numpy.NaN
|
|
65
|
-
# radius = math.ceil(numpy.nanmean(ndarray) * 2)
|
|
66
|
-
|
|
67
|
-
print("Calculating LPI and eLAI for {} ...".format(filename))
|
|
68
|
-
with rasterio.open(pdTotal) as pd_total:
|
|
69
|
-
with rasterio.open(pdGround) as pd_Ground:
|
|
70
|
-
raster_profile = pd_total.profile
|
|
71
|
-
pd_total_ndarray = pd_total.read(1, boundless=True)
|
|
72
|
-
nodata = pd_total.nodata
|
|
73
|
-
pd_total_ndarray[pd_total_ndarray == nodata] = numpy.nan
|
|
74
|
-
kernel = convolution.circle_kernel(cell_size, cell_size, radius)
|
|
75
|
-
total_focalsum = fs_raster_stdmean(pd_total_ndarray, kernel, nodata)
|
|
76
|
-
write_total_focalsum = rasterio.open(tfocal_filename, 'w', **raster_profile)
|
|
77
|
-
write_total_focalsum.write(total_focalsum, 1)
|
|
78
|
-
write_total_focalsum.close()
|
|
79
|
-
del write_total_focalsum
|
|
80
|
-
|
|
81
|
-
pd_ground_ndarray = pd_Ground.read(1, boundless=True)
|
|
82
|
-
nodata = pd_Ground.nodata
|
|
83
|
-
pd_ground_ndarray[pd_ground_ndarray == nodata] = numpy.nan
|
|
84
|
-
ground_focalsum = fs_raster_stdmean(pd_ground_ndarray, kernel, nodata)
|
|
85
|
-
write_ground_focalsum = rasterio.open(gfocal_filename, 'w', **raster_profile)
|
|
86
|
-
write_ground_focalsum.write(ground_focalsum, 1)
|
|
87
|
-
write_ground_focalsum.close()
|
|
88
|
-
del write_ground_focalsum
|
|
89
|
-
del pd_total
|
|
90
|
-
|
|
91
|
-
del pd_Ground
|
|
92
|
-
lpi_array = numpy.divide(pd_ground_ndarray, pd_total_ndarray, out=numpy.zeros_like(pd_ground_ndarray),
|
|
93
|
-
where=pd_total_ndarray != 0)
|
|
94
|
-
|
|
95
|
-
print("Calculating LPI: {} ...".format(filename))
|
|
96
|
-
write_lpi = rasterio.open(out_lpi, 'w', **raster_profile)
|
|
97
|
-
write_lpi.write(lpi_array, 1)
|
|
98
|
-
write_lpi.close()
|
|
99
|
-
del write_lpi
|
|
100
|
-
print('%{}'.format(80))
|
|
101
|
-
print("Calculating LPI: {} ...Done".format(filename))
|
|
102
|
-
|
|
103
|
-
print("Calculating eLAI: {} ...".format(filename))
|
|
104
|
-
elai_array = ((math.cos(((scan_angle / 2.0) / 180.0) * math.pi)) / 0.5) * (numpy.log(lpi_array)) * -1
|
|
105
|
-
|
|
106
|
-
write_elai = rasterio.open(out_elai, 'w', **raster_profile)
|
|
107
|
-
write_elai.write(elai_array, 1)
|
|
108
|
-
write_elai.close()
|
|
109
|
-
del write_elai
|
|
110
|
-
print("Calculating eLAI: {} ... Done".format(filename))
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def fs_raster_stdmean(in_ndarray, kernel, nodata):
|
|
114
|
-
# This function uses xrspatial whcih can handle large data but slow
|
|
115
|
-
in_ndarray[in_ndarray == nodata] = numpy.nan
|
|
116
|
-
result_ndarray = focal.focal_stats(xr.DataArray(in_ndarray), kernel, stats_funcs=['sum'])
|
|
117
|
-
|
|
118
|
-
# Flattening the array
|
|
119
|
-
flatten_sum_result_ndarray = result_ndarray.data.reshape(-1)
|
|
120
|
-
|
|
121
|
-
# Re-shaping the array
|
|
122
|
-
reshape_sum_ndarray = flatten_sum_result_ndarray.reshape(in_ndarray.shape[0], in_ndarray.shape[1])
|
|
123
|
-
return reshape_sum_ndarray
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def r_lpi_lai_with_focalR(arg):
|
|
127
|
-
r = robjects.r
|
|
128
|
-
pdTotal = arg[0]
|
|
129
|
-
pdGround = arg[1]
|
|
130
|
-
out_folder = arg[2]
|
|
131
|
-
filename = arg[3]
|
|
132
|
-
scan_angle = float(arg[4])
|
|
133
|
-
|
|
134
|
-
## output files variables
|
|
135
|
-
out_lpi_fielname = filename + "_LPI_r.tif"
|
|
136
|
-
out_elai_fielname = filename + "_eLAI_r.tif"
|
|
137
|
-
LPI_folder = os.path.join(out_folder, "LPI")
|
|
138
|
-
eLAI_folder = os.path.join(out_folder, "eLAI")
|
|
139
|
-
out_lpi = os.path.join(LPI_folder, out_lpi_fielname)
|
|
140
|
-
out_elai = os.path.join(eLAI_folder, out_elai_fielname)
|
|
141
|
-
|
|
142
|
-
radius = float(arg[6])
|
|
143
|
-
print("Calculating LPI and eLAI for {} ...".format(filename))
|
|
144
|
-
|
|
145
|
-
# assign R script file to local variable
|
|
146
|
-
rlpi_elai_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'r_cal_lpi_elai.r')
|
|
147
|
-
# Defining the R script and loading the instance in Python
|
|
148
|
-
r['source'](rlpi_elai_script)
|
|
149
|
-
# Loading the function defined in R script.
|
|
150
|
-
rlpi_elai = robjects.globalenv['rlpi_elai']
|
|
151
|
-
# Invoking the R function
|
|
152
|
-
rlpi_elai(pdTotal, pdGround, radius, scan_angle, out_lpi, out_elai)
|
|
153
|
-
|
|
154
|
-
# At this stage no process for CHM
|
|
155
|
-
|
|
156
|
-
print("Calculating LPI adn eLAI: {} ... Done".format(filename))
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def f_pulse_density(ctg, out_folder, rprocesses, verbose):
|
|
160
|
-
r = robjects.r
|
|
161
|
-
print('Calculate cell size from average point cloud density...')
|
|
162
|
-
cache_folder = os.path.join(out_folder, "Cache")
|
|
163
|
-
# assign R script file to local variable
|
|
164
|
-
beratools_r_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Beratools_r_script.r')
|
|
165
|
-
# Defining the R script and loading the instance in Python
|
|
166
|
-
r['source'](beratools_r_script)
|
|
167
|
-
# Loading the function defined in R script.
|
|
168
|
-
pd2cellsize = robjects.globalenv['pd2cellsize']
|
|
169
|
-
# Invoking the R function
|
|
170
|
-
cell_size = pd2cellsize(ctg, rprocesses)
|
|
171
|
-
|
|
172
|
-
return (cell_size)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def pd_raster(callback, in_polygon_file, in_las_folder, cut_ht, radius_fr_CHM, focal_radius, pulse_density,
|
|
176
|
-
cell_size, mean_scanning_angle, out_folder, processes, verbose):
|
|
177
|
-
r = robjects.r
|
|
178
|
-
import psutil
|
|
179
|
-
stats = psutil.virtual_memory()
|
|
180
|
-
available = getattr(stats, 'available') / 1024000000
|
|
181
|
-
if 2 < processes <= 8:
|
|
182
|
-
if available <= 50:
|
|
183
|
-
rprocesses = 2
|
|
184
|
-
elif 50 < available <= 150:
|
|
185
|
-
rprocesses = 4
|
|
186
|
-
elif 150 < available <= 250:
|
|
187
|
-
rprocesses = 8
|
|
188
|
-
else:
|
|
189
|
-
rprocesses = 8
|
|
190
|
-
|
|
191
|
-
cache_folder = os.path.join(out_folder, "Cache")
|
|
192
|
-
# dtm_folder=os.path.join(out_folder,"DTM")
|
|
193
|
-
# dsm_folder=os.path.join(out_folder,"DSM")
|
|
194
|
-
# chm_folder=os.path.join(out_folder,"CHM")
|
|
195
|
-
PD_folder = os.path.join(out_folder, "PD")
|
|
196
|
-
PD_Total_folder = os.path.join(PD_folder, "Total")
|
|
197
|
-
PD_Ground_folder = os.path.join(PD_folder, "Ground")
|
|
198
|
-
LPI_folder = os.path.join(out_folder, "LPI")
|
|
199
|
-
eLAI_folder = os.path.join(out_folder, "eLAI")
|
|
200
|
-
|
|
201
|
-
if not os.path.exists(cache_folder):
|
|
202
|
-
os.makedirs(cache_folder)
|
|
203
|
-
|
|
204
|
-
if not os.path.exists(PD_folder):
|
|
205
|
-
os.makedirs(PD_folder)
|
|
206
|
-
if not os.path.exists(PD_Total_folder):
|
|
207
|
-
os.makedirs(PD_Total_folder)
|
|
208
|
-
if not os.path.exists(PD_Ground_folder):
|
|
209
|
-
os.makedirs(PD_Ground_folder)
|
|
210
|
-
if not os.path.exists(LPI_folder):
|
|
211
|
-
os.makedirs(LPI_folder)
|
|
212
|
-
if not os.path.exists(eLAI_folder):
|
|
213
|
-
os.makedirs(eLAI_folder)
|
|
214
|
-
|
|
215
|
-
lascat = lidR.readLAScatalog(in_las_folder, filter="-drop_class 7")
|
|
216
|
-
cache_folder = cache_folder.replace("\\", "/")
|
|
217
|
-
# dtm_folder = dtm_folder.replace("\\", "/") + "/{*}_dtm"
|
|
218
|
-
# chm_folder = chm_folder.replace("\\","/") + "/{*}_chm"
|
|
219
|
-
PD_Total_folder = PD_folder.replace("\\", "/") + "/Total"
|
|
220
|
-
PD_Ground_folder = PD_folder.replace("\\", "/") + "/Ground"
|
|
221
|
-
LPI_folder = LPI_folder.replace("\\", "/")
|
|
222
|
-
eLAI_folder = eLAI_folder.replace("\\", "/")
|
|
223
|
-
|
|
224
|
-
if not in_polygon_file == "":
|
|
225
|
-
try:
|
|
226
|
-
r.vect(in_polygon_file)
|
|
227
|
-
except FileNotFoundError:
|
|
228
|
-
print("Could not locate shapefile, all area will be process")
|
|
229
|
-
|
|
230
|
-
if cell_size <= 0:
|
|
231
|
-
if pulse_density <= 0:
|
|
232
|
-
cell_size = f_pulse_density(lascat, out_folder, rprocesses, verbose)
|
|
233
|
-
|
|
234
|
-
# assign R script file to local variable
|
|
235
|
-
Beratools_R_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Beratools_r_script.r')
|
|
236
|
-
# Defining the R script and loading the instance in Python
|
|
237
|
-
r['source'](Beratools_R_script)
|
|
238
|
-
# Loading the function defined in R script.
|
|
239
|
-
generate_pd = robjects.globalenv['generate_pd']
|
|
240
|
-
# Invoking the R function
|
|
241
|
-
generate_pd(lascat, radius_fr_CHM, focal_radius, cell_size, cache_folder, cut_ht, PD_Ground_folder,
|
|
242
|
-
PD_Total_folder, rprocesses)
|
|
243
|
-
|
|
244
|
-
# At this stage no process for CHM
|
|
245
|
-
# locate the point density raster for generating eLAI and LPI
|
|
246
|
-
pd_total_filelist = []
|
|
247
|
-
pd_ground_filelist = []
|
|
248
|
-
|
|
249
|
-
# Get raster files lists
|
|
250
|
-
for root, dirs, files in sorted(os.walk(os.path.join(out_folder, "PD\\Total"))):
|
|
251
|
-
for file in files:
|
|
252
|
-
if file.endswith("_PD_Tfocalsum.tif"):
|
|
253
|
-
pd_total_filelist.append(os.path.join(root, file))
|
|
254
|
-
del root, dirs, files
|
|
255
|
-
for root, dirs, files in sorted(os.walk(os.path.join(out_folder, "PD\\Ground"))):
|
|
256
|
-
for file in files:
|
|
257
|
-
if file.endswith("_PD_Gfocalsum.tif"):
|
|
258
|
-
pd_ground_filelist.append(os.path.join(root, file))
|
|
259
|
-
del root, dirs, files
|
|
260
|
-
args_list = []
|
|
261
|
-
|
|
262
|
-
# At this stage no process for finding average cell size from all CHM
|
|
263
|
-
radius_fr_CHM = False
|
|
264
|
-
if radius_fr_CHM:
|
|
265
|
-
pass
|
|
266
|
-
# chm_filelist = []
|
|
267
|
-
# for root, dirs, files in sorted(os.walk(os.path.join(out_folder, "CHM"))):
|
|
268
|
-
# for file in files:
|
|
269
|
-
# if file.endswith("_chm.tif"):
|
|
270
|
-
# chm_filelist.append(os.path.join(root, file))
|
|
271
|
-
# del root, dirs, files
|
|
272
|
-
#
|
|
273
|
-
# if len(pd_total_filelist) == len(pd_ground_filelist) == len(chm_filelist):
|
|
274
|
-
# for i in range(0, len(pd_total_filelist)):
|
|
275
|
-
# chm_filename = os.path.splitext(os.path.split(chm_filelist[i])[1])[0]
|
|
276
|
-
# pdtotal_filename = os.path.splitext(os.path.split(pd_total_filelist[i])[1])[0]
|
|
277
|
-
# pdGround_filename = os.path.splitext(os.path.split(pd_ground_filelist[i])[1])[0]
|
|
278
|
-
# result_list = []
|
|
279
|
-
# if chm_filename[0:-4] == pdtotal_filename[0:-13] == pdGround_filename[2:-13]:
|
|
280
|
-
# result_list.append(chm_filelist[i])
|
|
281
|
-
# result_list.append(pd_total_filelist[i])
|
|
282
|
-
# result_list.append(pd_ground_filelist[i])
|
|
283
|
-
# result_list.append(out_folder)
|
|
284
|
-
# result_list.append(chm_filename[0:-4])
|
|
285
|
-
# result_list.append(mean_scanning_angle)
|
|
286
|
-
# result_list.append(cell_size)
|
|
287
|
-
# result_list.append(focal_radius)
|
|
288
|
-
# args_list.append(result_list)
|
|
289
|
-
#
|
|
290
|
-
# try:
|
|
291
|
-
# total_steps = len(args_list)
|
|
292
|
-
# features = []
|
|
293
|
-
# with Pool(processes=int(processes)) as pool:
|
|
294
|
-
# step = 0
|
|
295
|
-
# # execute tasks in order, process results out of order
|
|
296
|
-
# for result in pool.imap_unordered(lpi_lai, args_list):
|
|
297
|
-
# if BT_DEBUGGING:
|
|
298
|
-
# print('Got result: {}'.format(result), flush=True)
|
|
299
|
-
# features.append(result)
|
|
300
|
-
# step += 1
|
|
301
|
-
# print('%{}'.format(step / total_steps * 100))
|
|
302
|
-
#
|
|
303
|
-
# except OperationCancelledException:
|
|
304
|
-
# print("Operation cancelled")
|
|
305
|
-
# exit()
|
|
306
|
-
else:
|
|
307
|
-
# processing LPI_eLAI
|
|
308
|
-
# prepare arguments
|
|
309
|
-
if len(pd_total_filelist) == len(pd_ground_filelist):
|
|
310
|
-
for i in range(0, len(pd_total_filelist)):
|
|
311
|
-
|
|
312
|
-
pdtotal_filename = os.path.splitext(os.path.split(pd_total_filelist[i])[1])[0]
|
|
313
|
-
pdGround_filename = os.path.splitext(os.path.split(pd_ground_filelist[i])[1])[0]
|
|
314
|
-
result_list = []
|
|
315
|
-
if pdtotal_filename[0:-13] == pdGround_filename[2:-13]:
|
|
316
|
-
result_list.append(pd_total_filelist[i])
|
|
317
|
-
result_list.append(pd_ground_filelist[i])
|
|
318
|
-
result_list.append(out_folder)
|
|
319
|
-
result_list.append(pdtotal_filename[0:-13])
|
|
320
|
-
result_list.append(mean_scanning_angle)
|
|
321
|
-
result_list.append(cell_size)
|
|
322
|
-
result_list.append(focal_radius)
|
|
323
|
-
args_list.append(result_list)
|
|
324
|
-
|
|
325
|
-
# Multiprocessing eLAI and LPI raster using R package.
|
|
326
|
-
try:
|
|
327
|
-
total_steps = len(args_list)
|
|
328
|
-
if processes >= total_steps:
|
|
329
|
-
processes = total_steps
|
|
330
|
-
|
|
331
|
-
features = []
|
|
332
|
-
with Pool(processes=int(processes)) as pool:
|
|
333
|
-
step = 0
|
|
334
|
-
# execute tasks in order, process results out of order
|
|
335
|
-
for result in pool.imap_unordered(r_lpi_lai_with_focalR, args_list):
|
|
336
|
-
if BT_DEBUGGING:
|
|
337
|
-
print('Got result: {}'.format(result), flush=True)
|
|
338
|
-
features.append(result)
|
|
339
|
-
step += 1
|
|
340
|
-
print('%{}'.format(step / total_steps * 100))
|
|
341
|
-
|
|
342
|
-
except OperationCancelledException:
|
|
343
|
-
print("Operation cancelled")
|
|
344
|
-
exit()
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
if __name__ == '__main__':
|
|
348
|
-
start_time = time.time()
|
|
349
|
-
print('Starting generating LPA and eLAI raster processing\n @ {}'
|
|
350
|
-
.format(time.strftime("%d %b %Y %H:%M:%S", time.localtime())))
|
|
351
|
-
|
|
352
|
-
r = robjects.r
|
|
353
|
-
utils = importr('utils')
|
|
354
|
-
base = importr('base')
|
|
355
|
-
utils.chooseCRANmirror(ind=12) # select the 12th mirror in the list: Canada
|
|
356
|
-
print("Checking R packages ...")
|
|
357
|
-
|
|
358
|
-
CRANpacknames = ['lidR', 'rgrass', 'rlas', 'future', 'terra', 'na.tools', 'sf', 'sp'] # ,'fasterRaster']
|
|
359
|
-
CRANnames_to_install = [x for x in CRANpacknames if not robjects.packages.isinstalled(x)]
|
|
360
|
-
need_fasterRaster = False
|
|
361
|
-
if len(CRANnames_to_install) > 0:
|
|
362
|
-
if not 'fasterRaster' in CRANnames_to_install:
|
|
363
|
-
utils.install_packages(StrVector(CRANnames_to_install))
|
|
364
|
-
need_fasterRaster = False
|
|
365
|
-
else:
|
|
366
|
-
CRANnames_to_install.remove('fasterRaster')
|
|
367
|
-
need_fasterRaster = True
|
|
368
|
-
if len(CRANnames_to_install) > 0:
|
|
369
|
-
utils.install_packages(StrVector(CRANnames_to_install))
|
|
370
|
-
|
|
371
|
-
# if need_fasterRaster:
|
|
372
|
-
# devtools=importr('devtools')
|
|
373
|
-
# devtools.install_github("adamlilith/fasterRaster", dependencies=True)
|
|
374
|
-
|
|
375
|
-
del CRANpacknames, CRANnames_to_install
|
|
376
|
-
|
|
377
|
-
in_args, in_verbose = check_arguments()
|
|
378
|
-
# loading R packages
|
|
379
|
-
# utils = importr('utils')
|
|
380
|
-
# base = importr('base')
|
|
381
|
-
print("Loading R packages ...")
|
|
382
|
-
na = importr('na.tools')
|
|
383
|
-
terra = importr('terra')
|
|
384
|
-
lidR = importr('lidR')
|
|
385
|
-
sf = importr('sf')
|
|
386
|
-
sp = importr('sp')
|
|
387
|
-
future = importr('future')
|
|
388
|
-
|
|
389
|
-
print("Checking input parameters ...")
|
|
390
|
-
|
|
391
|
-
aoi_shapefile = in_args.input['in_polygon_file']
|
|
392
|
-
in_las_folder = in_args.input['in_las_folder']
|
|
393
|
-
cut_ht = float(in_args.input['cut_ht'])
|
|
394
|
-
radius_fr_CHM = in_args.input['radius_fr_CHM']
|
|
395
|
-
focal_radius = float(in_args.input['focal_radius'])
|
|
396
|
-
pulse_density = int(in_args.input['pulse_density'])
|
|
397
|
-
cell_size = float(in_args.input['cell_size'])
|
|
398
|
-
mean_scanning_angle = float(in_args.input['mean_scanning_angle'])
|
|
399
|
-
out_folder = in_args.input['out_folder']
|
|
400
|
-
|
|
401
|
-
# if optional shapefile is empty, then do nothing, else verify shapefile
|
|
402
|
-
if not aoi_shapefile == "":
|
|
403
|
-
if not os.path.exists(os.path.dirname(aoi_shapefile)):
|
|
404
|
-
print("Can't locate the input polygon folder. Please check.")
|
|
405
|
-
exit()
|
|
406
|
-
else:
|
|
407
|
-
if not isinstance(geopandas.GeoDataFrame.from_file(aoi_shapefile), geopandas.GeoDataFrame):
|
|
408
|
-
print("Error input file: Please check effective LiDAR data extend shapefile")
|
|
409
|
-
exit()
|
|
410
|
-
# check existence of input las/laz folder
|
|
411
|
-
if not os.path.exists(in_las_folder):
|
|
412
|
-
print("Error! Cannot locate LAS/LAZ folder, please check.")
|
|
413
|
-
exit()
|
|
414
|
-
else:
|
|
415
|
-
found = False
|
|
416
|
-
for files in os.listdir(in_las_folder):
|
|
417
|
-
if files.endswith(".las") or files.endswith(".laz"):
|
|
418
|
-
found = True
|
|
419
|
-
break
|
|
420
|
-
if not found:
|
|
421
|
-
print("Error! Cannot locate input LAS file(s), please check!")
|
|
422
|
-
exit()
|
|
423
|
-
|
|
424
|
-
# if doing focal radius divided from point cloud CHM
|
|
425
|
-
if radius_fr_CHM == True:
|
|
426
|
-
pass
|
|
427
|
-
# do nothing for now
|
|
428
|
-
else:
|
|
429
|
-
# check manual input for radius, check input
|
|
430
|
-
if not isinstance(focal_radius, float) or focal_radius <= 0.0:
|
|
431
|
-
print("Invalid search radius!!Default radius will be adopted (10m).")
|
|
432
|
-
in_args.input['focal_radius'] = 10.0
|
|
433
|
-
else:
|
|
434
|
-
in_args.input['focal_radius'] = focal_radius
|
|
435
|
-
# check manual input for cell size and pulse density
|
|
436
|
-
if not isinstance(cell_size, float) or cell_size <= 0.00:
|
|
437
|
-
if not isinstance(pulse_density, int) or pulse_density <= 0.00:
|
|
438
|
-
print("Invalid cell size and average pulse density provided.\n"
|
|
439
|
-
"Cell size will be calulated based on aveage point density.")
|
|
440
|
-
in_args.input['cell_size'] = 0.0
|
|
441
|
-
in_args.input['pulse_density'] = 0
|
|
442
|
-
else:
|
|
443
|
-
# mean_pd = (((math.pow(3 / pulse_density, 1 / 2)) + (math.pow(5 / pulse_density, 1 / 2))) / 2)
|
|
444
|
-
mean_pd = math.pow(3 / pulse_density, 1 / 2)
|
|
445
|
-
# mean_pd = math.pow(5 / pulse_density, 1 / 2)
|
|
446
|
-
in_args.input['cell_size'] = round(0.05 * round(mean_pd / 0.05), 2)
|
|
447
|
-
in_args.input['pulse_density'] = pulse_density
|
|
448
|
-
else:
|
|
449
|
-
in_args.input['cell_size'] = (cell_size)
|
|
450
|
-
in_args.input['pulse_density'] = pulse_density
|
|
451
|
-
|
|
452
|
-
# Check manual input for cutt off height
|
|
453
|
-
if not isinstance(cut_ht, float) and cut_ht > 0.0:
|
|
454
|
-
print("Invalid cut off height!! Default cut off height will be adopted (1m).")
|
|
455
|
-
in_args.input["cut_ht"] = 1.0
|
|
456
|
-
|
|
457
|
-
if not isinstance(mean_scanning_angle, float) and mean_scanning_angle > 0.00:
|
|
458
|
-
print("Invalid sensor scanning angle.\n Default sensor scanning angle will size be adopted (30 degree).")
|
|
459
|
-
in_args.input['mean_scanning_angle'] = 30.0
|
|
460
|
-
else:
|
|
461
|
-
in_args.input['mean_scanning_angle'] = mean_scanning_angle
|
|
462
|
-
|
|
463
|
-
print("Checking input parameters ... Done")
|
|
464
|
-
|
|
465
|
-
pd_raster(print, **in_args.input, processes=int(in_args.processes), verbose=in_verbose)
|
|
466
|
-
print('Task is done in {} seconds)'.format(round(time.time() - start_time, 5)))
|
|
1
|
+
import math
|
|
2
|
+
import os
|
|
3
|
+
import winreg
|
|
4
|
+
import time
|
|
5
|
+
import geopandas
|
|
6
|
+
import sys
|
|
7
|
+
import numpy
|
|
8
|
+
import xrspatial.focal as focal
|
|
9
|
+
from xrspatial import convolution
|
|
10
|
+
import xarray as xr
|
|
11
|
+
|
|
12
|
+
from multiprocessing.pool import Pool
|
|
13
|
+
from beratools.tools.common import *
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class OperationCancelledException(Exception):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
try: # integrated R env
|
|
21
|
+
# check R language within env
|
|
22
|
+
current_env_path = os.environ['CONDA_PREFIX']
|
|
23
|
+
# if os.path.isdir(current_env_path):
|
|
24
|
+
os.environ['R_HOME'] = os.path.join(current_env_path, r"Lib\R")
|
|
25
|
+
os.environ['R_USER'] = os.path.expanduser('~')
|
|
26
|
+
os.environ['R_LIBS_USER'] = os.path.join(current_env_path, r"Lib\R\library")
|
|
27
|
+
|
|
28
|
+
except FileNotFoundError:
|
|
29
|
+
print("Warning: Please install R for this process!!")
|
|
30
|
+
exit()
|
|
31
|
+
|
|
32
|
+
import rpy2.robjects as robjects
|
|
33
|
+
from rpy2.robjects.packages import importr, data
|
|
34
|
+
from rpy2.robjects.vectors import StrVector
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def lpi_lai(arg):
|
|
38
|
+
pdTotal = arg[0]
|
|
39
|
+
pdGround = arg[1]
|
|
40
|
+
out_folder = arg[2]
|
|
41
|
+
filename = arg[3]
|
|
42
|
+
scan_angle = float(arg[4])
|
|
43
|
+
|
|
44
|
+
##variable for not calling R
|
|
45
|
+
cell_size = float(arg[5])
|
|
46
|
+
radius = float(arg[6])
|
|
47
|
+
tfocal_filename = filename + "_tfocal_py.tif"
|
|
48
|
+
gfocal_filename = filename + "_gfocal_py.tif"
|
|
49
|
+
# out_tfocal = os.path.join(out_folder, tfocal_filename)
|
|
50
|
+
# out_gfocal = os.path.join(out_folder, gfocal_filename)
|
|
51
|
+
|
|
52
|
+
## output files variables
|
|
53
|
+
out_lpi_fielname = filename + "_LPI_py.tif"
|
|
54
|
+
out_elai_fielname = filename + "_eLAI_py.tif"
|
|
55
|
+
LPI_folder = os.path.join(out_folder, "LPI")
|
|
56
|
+
eLAI_folder = os.path.join(out_folder, "eLAI")
|
|
57
|
+
out_lpi = os.path.join(LPI_folder, out_lpi_fielname)
|
|
58
|
+
out_elai = os.path.join(eLAI_folder, out_elai_fielname)
|
|
59
|
+
|
|
60
|
+
# Working out the searching radius
|
|
61
|
+
# with rasterio.open(chm) as image:
|
|
62
|
+
# ndarray = image.read(1)
|
|
63
|
+
# ndarray[ndarray==image.nodata]=numpy.NaN
|
|
64
|
+
# ndarray[ndarray <0.0] = numpy.NaN
|
|
65
|
+
# radius = math.ceil(numpy.nanmean(ndarray) * 2)
|
|
66
|
+
|
|
67
|
+
print("Calculating LPI and eLAI for {} ...".format(filename))
|
|
68
|
+
with rasterio.open(pdTotal) as pd_total:
|
|
69
|
+
with rasterio.open(pdGround) as pd_Ground:
|
|
70
|
+
raster_profile = pd_total.profile
|
|
71
|
+
pd_total_ndarray = pd_total.read(1, boundless=True)
|
|
72
|
+
nodata = pd_total.nodata
|
|
73
|
+
pd_total_ndarray[pd_total_ndarray == nodata] = numpy.nan
|
|
74
|
+
kernel = convolution.circle_kernel(cell_size, cell_size, radius)
|
|
75
|
+
total_focalsum = fs_raster_stdmean(pd_total_ndarray, kernel, nodata)
|
|
76
|
+
write_total_focalsum = rasterio.open(tfocal_filename, 'w', **raster_profile)
|
|
77
|
+
write_total_focalsum.write(total_focalsum, 1)
|
|
78
|
+
write_total_focalsum.close()
|
|
79
|
+
del write_total_focalsum
|
|
80
|
+
|
|
81
|
+
pd_ground_ndarray = pd_Ground.read(1, boundless=True)
|
|
82
|
+
nodata = pd_Ground.nodata
|
|
83
|
+
pd_ground_ndarray[pd_ground_ndarray == nodata] = numpy.nan
|
|
84
|
+
ground_focalsum = fs_raster_stdmean(pd_ground_ndarray, kernel, nodata)
|
|
85
|
+
write_ground_focalsum = rasterio.open(gfocal_filename, 'w', **raster_profile)
|
|
86
|
+
write_ground_focalsum.write(ground_focalsum, 1)
|
|
87
|
+
write_ground_focalsum.close()
|
|
88
|
+
del write_ground_focalsum
|
|
89
|
+
del pd_total
|
|
90
|
+
|
|
91
|
+
del pd_Ground
|
|
92
|
+
lpi_array = numpy.divide(pd_ground_ndarray, pd_total_ndarray, out=numpy.zeros_like(pd_ground_ndarray),
|
|
93
|
+
where=pd_total_ndarray != 0)
|
|
94
|
+
|
|
95
|
+
print("Calculating LPI: {} ...".format(filename))
|
|
96
|
+
write_lpi = rasterio.open(out_lpi, 'w', **raster_profile)
|
|
97
|
+
write_lpi.write(lpi_array, 1)
|
|
98
|
+
write_lpi.close()
|
|
99
|
+
del write_lpi
|
|
100
|
+
print('%{}'.format(80))
|
|
101
|
+
print("Calculating LPI: {} ...Done".format(filename))
|
|
102
|
+
|
|
103
|
+
print("Calculating eLAI: {} ...".format(filename))
|
|
104
|
+
elai_array = ((math.cos(((scan_angle / 2.0) / 180.0) * math.pi)) / 0.5) * (numpy.log(lpi_array)) * -1
|
|
105
|
+
|
|
106
|
+
write_elai = rasterio.open(out_elai, 'w', **raster_profile)
|
|
107
|
+
write_elai.write(elai_array, 1)
|
|
108
|
+
write_elai.close()
|
|
109
|
+
del write_elai
|
|
110
|
+
print("Calculating eLAI: {} ... Done".format(filename))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def fs_raster_stdmean(in_ndarray, kernel, nodata):
|
|
114
|
+
# This function uses xrspatial whcih can handle large data but slow
|
|
115
|
+
in_ndarray[in_ndarray == nodata] = numpy.nan
|
|
116
|
+
result_ndarray = focal.focal_stats(xr.DataArray(in_ndarray), kernel, stats_funcs=['sum'])
|
|
117
|
+
|
|
118
|
+
# Flattening the array
|
|
119
|
+
flatten_sum_result_ndarray = result_ndarray.data.reshape(-1)
|
|
120
|
+
|
|
121
|
+
# Re-shaping the array
|
|
122
|
+
reshape_sum_ndarray = flatten_sum_result_ndarray.reshape(in_ndarray.shape[0], in_ndarray.shape[1])
|
|
123
|
+
return reshape_sum_ndarray
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def r_lpi_lai_with_focalR(arg):
|
|
127
|
+
r = robjects.r
|
|
128
|
+
pdTotal = arg[0]
|
|
129
|
+
pdGround = arg[1]
|
|
130
|
+
out_folder = arg[2]
|
|
131
|
+
filename = arg[3]
|
|
132
|
+
scan_angle = float(arg[4])
|
|
133
|
+
|
|
134
|
+
## output files variables
|
|
135
|
+
out_lpi_fielname = filename + "_LPI_r.tif"
|
|
136
|
+
out_elai_fielname = filename + "_eLAI_r.tif"
|
|
137
|
+
LPI_folder = os.path.join(out_folder, "LPI")
|
|
138
|
+
eLAI_folder = os.path.join(out_folder, "eLAI")
|
|
139
|
+
out_lpi = os.path.join(LPI_folder, out_lpi_fielname)
|
|
140
|
+
out_elai = os.path.join(eLAI_folder, out_elai_fielname)
|
|
141
|
+
|
|
142
|
+
radius = float(arg[6])
|
|
143
|
+
print("Calculating LPI and eLAI for {} ...".format(filename))
|
|
144
|
+
|
|
145
|
+
# assign R script file to local variable
|
|
146
|
+
rlpi_elai_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'r_cal_lpi_elai.r')
|
|
147
|
+
# Defining the R script and loading the instance in Python
|
|
148
|
+
r['source'](rlpi_elai_script)
|
|
149
|
+
# Loading the function defined in R script.
|
|
150
|
+
rlpi_elai = robjects.globalenv['rlpi_elai']
|
|
151
|
+
# Invoking the R function
|
|
152
|
+
rlpi_elai(pdTotal, pdGround, radius, scan_angle, out_lpi, out_elai)
|
|
153
|
+
|
|
154
|
+
# At this stage no process for CHM
|
|
155
|
+
|
|
156
|
+
print("Calculating LPI adn eLAI: {} ... Done".format(filename))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def f_pulse_density(ctg, out_folder, rprocesses, verbose):
|
|
160
|
+
r = robjects.r
|
|
161
|
+
print('Calculate cell size from average point cloud density...')
|
|
162
|
+
cache_folder = os.path.join(out_folder, "Cache")
|
|
163
|
+
# assign R script file to local variable
|
|
164
|
+
beratools_r_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Beratools_r_script.r')
|
|
165
|
+
# Defining the R script and loading the instance in Python
|
|
166
|
+
r['source'](beratools_r_script)
|
|
167
|
+
# Loading the function defined in R script.
|
|
168
|
+
pd2cellsize = robjects.globalenv['pd2cellsize']
|
|
169
|
+
# Invoking the R function
|
|
170
|
+
cell_size = pd2cellsize(ctg, rprocesses)
|
|
171
|
+
|
|
172
|
+
return (cell_size)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def pd_raster(callback, in_polygon_file, in_las_folder, cut_ht, radius_fr_CHM, focal_radius, pulse_density,
|
|
176
|
+
cell_size, mean_scanning_angle, out_folder, processes, verbose):
|
|
177
|
+
r = robjects.r
|
|
178
|
+
import psutil
|
|
179
|
+
stats = psutil.virtual_memory()
|
|
180
|
+
available = getattr(stats, 'available') / 1024000000
|
|
181
|
+
if 2 < processes <= 8:
|
|
182
|
+
if available <= 50:
|
|
183
|
+
rprocesses = 2
|
|
184
|
+
elif 50 < available <= 150:
|
|
185
|
+
rprocesses = 4
|
|
186
|
+
elif 150 < available <= 250:
|
|
187
|
+
rprocesses = 8
|
|
188
|
+
else:
|
|
189
|
+
rprocesses = 8
|
|
190
|
+
|
|
191
|
+
cache_folder = os.path.join(out_folder, "Cache")
|
|
192
|
+
# dtm_folder=os.path.join(out_folder,"DTM")
|
|
193
|
+
# dsm_folder=os.path.join(out_folder,"DSM")
|
|
194
|
+
# chm_folder=os.path.join(out_folder,"CHM")
|
|
195
|
+
PD_folder = os.path.join(out_folder, "PD")
|
|
196
|
+
PD_Total_folder = os.path.join(PD_folder, "Total")
|
|
197
|
+
PD_Ground_folder = os.path.join(PD_folder, "Ground")
|
|
198
|
+
LPI_folder = os.path.join(out_folder, "LPI")
|
|
199
|
+
eLAI_folder = os.path.join(out_folder, "eLAI")
|
|
200
|
+
|
|
201
|
+
if not os.path.exists(cache_folder):
|
|
202
|
+
os.makedirs(cache_folder)
|
|
203
|
+
|
|
204
|
+
if not os.path.exists(PD_folder):
|
|
205
|
+
os.makedirs(PD_folder)
|
|
206
|
+
if not os.path.exists(PD_Total_folder):
|
|
207
|
+
os.makedirs(PD_Total_folder)
|
|
208
|
+
if not os.path.exists(PD_Ground_folder):
|
|
209
|
+
os.makedirs(PD_Ground_folder)
|
|
210
|
+
if not os.path.exists(LPI_folder):
|
|
211
|
+
os.makedirs(LPI_folder)
|
|
212
|
+
if not os.path.exists(eLAI_folder):
|
|
213
|
+
os.makedirs(eLAI_folder)
|
|
214
|
+
|
|
215
|
+
lascat = lidR.readLAScatalog(in_las_folder, filter="-drop_class 7")
|
|
216
|
+
cache_folder = cache_folder.replace("\\", "/")
|
|
217
|
+
# dtm_folder = dtm_folder.replace("\\", "/") + "/{*}_dtm"
|
|
218
|
+
# chm_folder = chm_folder.replace("\\","/") + "/{*}_chm"
|
|
219
|
+
PD_Total_folder = PD_folder.replace("\\", "/") + "/Total"
|
|
220
|
+
PD_Ground_folder = PD_folder.replace("\\", "/") + "/Ground"
|
|
221
|
+
LPI_folder = LPI_folder.replace("\\", "/")
|
|
222
|
+
eLAI_folder = eLAI_folder.replace("\\", "/")
|
|
223
|
+
|
|
224
|
+
if not in_polygon_file == "":
|
|
225
|
+
try:
|
|
226
|
+
r.vect(in_polygon_file)
|
|
227
|
+
except FileNotFoundError:
|
|
228
|
+
print("Could not locate shapefile, all area will be process")
|
|
229
|
+
|
|
230
|
+
if cell_size <= 0:
|
|
231
|
+
if pulse_density <= 0:
|
|
232
|
+
cell_size = f_pulse_density(lascat, out_folder, rprocesses, verbose)
|
|
233
|
+
|
|
234
|
+
# assign R script file to local variable
|
|
235
|
+
Beratools_R_script = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'Beratools_r_script.r')
|
|
236
|
+
# Defining the R script and loading the instance in Python
|
|
237
|
+
r['source'](Beratools_R_script)
|
|
238
|
+
# Loading the function defined in R script.
|
|
239
|
+
generate_pd = robjects.globalenv['generate_pd']
|
|
240
|
+
# Invoking the R function
|
|
241
|
+
generate_pd(lascat, radius_fr_CHM, focal_radius, cell_size, cache_folder, cut_ht, PD_Ground_folder,
|
|
242
|
+
PD_Total_folder, rprocesses)
|
|
243
|
+
|
|
244
|
+
# At this stage no process for CHM
|
|
245
|
+
# locate the point density raster for generating eLAI and LPI
|
|
246
|
+
pd_total_filelist = []
|
|
247
|
+
pd_ground_filelist = []
|
|
248
|
+
|
|
249
|
+
# Get raster files lists
|
|
250
|
+
for root, dirs, files in sorted(os.walk(os.path.join(out_folder, "PD\\Total"))):
|
|
251
|
+
for file in files:
|
|
252
|
+
if file.endswith("_PD_Tfocalsum.tif"):
|
|
253
|
+
pd_total_filelist.append(os.path.join(root, file))
|
|
254
|
+
del root, dirs, files
|
|
255
|
+
for root, dirs, files in sorted(os.walk(os.path.join(out_folder, "PD\\Ground"))):
|
|
256
|
+
for file in files:
|
|
257
|
+
if file.endswith("_PD_Gfocalsum.tif"):
|
|
258
|
+
pd_ground_filelist.append(os.path.join(root, file))
|
|
259
|
+
del root, dirs, files
|
|
260
|
+
args_list = []
|
|
261
|
+
|
|
262
|
+
# At this stage no process for finding average cell size from all CHM
|
|
263
|
+
radius_fr_CHM = False
|
|
264
|
+
if radius_fr_CHM:
|
|
265
|
+
pass
|
|
266
|
+
# chm_filelist = []
|
|
267
|
+
# for root, dirs, files in sorted(os.walk(os.path.join(out_folder, "CHM"))):
|
|
268
|
+
# for file in files:
|
|
269
|
+
# if file.endswith("_chm.tif"):
|
|
270
|
+
# chm_filelist.append(os.path.join(root, file))
|
|
271
|
+
# del root, dirs, files
|
|
272
|
+
#
|
|
273
|
+
# if len(pd_total_filelist) == len(pd_ground_filelist) == len(chm_filelist):
|
|
274
|
+
# for i in range(0, len(pd_total_filelist)):
|
|
275
|
+
# chm_filename = os.path.splitext(os.path.split(chm_filelist[i])[1])[0]
|
|
276
|
+
# pdtotal_filename = os.path.splitext(os.path.split(pd_total_filelist[i])[1])[0]
|
|
277
|
+
# pdGround_filename = os.path.splitext(os.path.split(pd_ground_filelist[i])[1])[0]
|
|
278
|
+
# result_list = []
|
|
279
|
+
# if chm_filename[0:-4] == pdtotal_filename[0:-13] == pdGround_filename[2:-13]:
|
|
280
|
+
# result_list.append(chm_filelist[i])
|
|
281
|
+
# result_list.append(pd_total_filelist[i])
|
|
282
|
+
# result_list.append(pd_ground_filelist[i])
|
|
283
|
+
# result_list.append(out_folder)
|
|
284
|
+
# result_list.append(chm_filename[0:-4])
|
|
285
|
+
# result_list.append(mean_scanning_angle)
|
|
286
|
+
# result_list.append(cell_size)
|
|
287
|
+
# result_list.append(focal_radius)
|
|
288
|
+
# args_list.append(result_list)
|
|
289
|
+
#
|
|
290
|
+
# try:
|
|
291
|
+
# total_steps = len(args_list)
|
|
292
|
+
# features = []
|
|
293
|
+
# with Pool(processes=int(processes)) as pool:
|
|
294
|
+
# step = 0
|
|
295
|
+
# # execute tasks in order, process results out of order
|
|
296
|
+
# for result in pool.imap_unordered(lpi_lai, args_list):
|
|
297
|
+
# if BT_DEBUGGING:
|
|
298
|
+
# print('Got result: {}'.format(result), flush=True)
|
|
299
|
+
# features.append(result)
|
|
300
|
+
# step += 1
|
|
301
|
+
# print('%{}'.format(step / total_steps * 100))
|
|
302
|
+
#
|
|
303
|
+
# except OperationCancelledException:
|
|
304
|
+
# print("Operation cancelled")
|
|
305
|
+
# exit()
|
|
306
|
+
else:
|
|
307
|
+
# processing LPI_eLAI
|
|
308
|
+
# prepare arguments
|
|
309
|
+
if len(pd_total_filelist) == len(pd_ground_filelist):
|
|
310
|
+
for i in range(0, len(pd_total_filelist)):
|
|
311
|
+
|
|
312
|
+
pdtotal_filename = os.path.splitext(os.path.split(pd_total_filelist[i])[1])[0]
|
|
313
|
+
pdGround_filename = os.path.splitext(os.path.split(pd_ground_filelist[i])[1])[0]
|
|
314
|
+
result_list = []
|
|
315
|
+
if pdtotal_filename[0:-13] == pdGround_filename[2:-13]:
|
|
316
|
+
result_list.append(pd_total_filelist[i])
|
|
317
|
+
result_list.append(pd_ground_filelist[i])
|
|
318
|
+
result_list.append(out_folder)
|
|
319
|
+
result_list.append(pdtotal_filename[0:-13])
|
|
320
|
+
result_list.append(mean_scanning_angle)
|
|
321
|
+
result_list.append(cell_size)
|
|
322
|
+
result_list.append(focal_radius)
|
|
323
|
+
args_list.append(result_list)
|
|
324
|
+
|
|
325
|
+
# Multiprocessing eLAI and LPI raster using R package.
|
|
326
|
+
try:
|
|
327
|
+
total_steps = len(args_list)
|
|
328
|
+
if processes >= total_steps:
|
|
329
|
+
processes = total_steps
|
|
330
|
+
|
|
331
|
+
features = []
|
|
332
|
+
with Pool(processes=int(processes)) as pool:
|
|
333
|
+
step = 0
|
|
334
|
+
# execute tasks in order, process results out of order
|
|
335
|
+
for result in pool.imap_unordered(r_lpi_lai_with_focalR, args_list):
|
|
336
|
+
if BT_DEBUGGING:
|
|
337
|
+
print('Got result: {}'.format(result), flush=True)
|
|
338
|
+
features.append(result)
|
|
339
|
+
step += 1
|
|
340
|
+
print('%{}'.format(step / total_steps * 100))
|
|
341
|
+
|
|
342
|
+
except OperationCancelledException:
|
|
343
|
+
print("Operation cancelled")
|
|
344
|
+
exit()
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
if __name__ == '__main__':
|
|
348
|
+
start_time = time.time()
|
|
349
|
+
print('Starting generating LPA and eLAI raster processing\n @ {}'
|
|
350
|
+
.format(time.strftime("%d %b %Y %H:%M:%S", time.localtime())))
|
|
351
|
+
|
|
352
|
+
r = robjects.r
|
|
353
|
+
utils = importr('utils')
|
|
354
|
+
base = importr('base')
|
|
355
|
+
utils.chooseCRANmirror(ind=12) # select the 12th mirror in the list: Canada
|
|
356
|
+
print("Checking R packages ...")
|
|
357
|
+
|
|
358
|
+
CRANpacknames = ['lidR', 'rgrass', 'rlas', 'future', 'terra', 'na.tools', 'sf', 'sp'] # ,'fasterRaster']
|
|
359
|
+
CRANnames_to_install = [x for x in CRANpacknames if not robjects.packages.isinstalled(x)]
|
|
360
|
+
need_fasterRaster = False
|
|
361
|
+
if len(CRANnames_to_install) > 0:
|
|
362
|
+
if not 'fasterRaster' in CRANnames_to_install:
|
|
363
|
+
utils.install_packages(StrVector(CRANnames_to_install))
|
|
364
|
+
need_fasterRaster = False
|
|
365
|
+
else:
|
|
366
|
+
CRANnames_to_install.remove('fasterRaster')
|
|
367
|
+
need_fasterRaster = True
|
|
368
|
+
if len(CRANnames_to_install) > 0:
|
|
369
|
+
utils.install_packages(StrVector(CRANnames_to_install))
|
|
370
|
+
|
|
371
|
+
# if need_fasterRaster:
|
|
372
|
+
# devtools=importr('devtools')
|
|
373
|
+
# devtools.install_github("adamlilith/fasterRaster", dependencies=True)
|
|
374
|
+
|
|
375
|
+
del CRANpacknames, CRANnames_to_install
|
|
376
|
+
|
|
377
|
+
in_args, in_verbose = check_arguments()
|
|
378
|
+
# loading R packages
|
|
379
|
+
# utils = importr('utils')
|
|
380
|
+
# base = importr('base')
|
|
381
|
+
print("Loading R packages ...")
|
|
382
|
+
na = importr('na.tools')
|
|
383
|
+
terra = importr('terra')
|
|
384
|
+
lidR = importr('lidR')
|
|
385
|
+
sf = importr('sf')
|
|
386
|
+
sp = importr('sp')
|
|
387
|
+
future = importr('future')
|
|
388
|
+
|
|
389
|
+
print("Checking input parameters ...")
|
|
390
|
+
|
|
391
|
+
aoi_shapefile = in_args.input['in_polygon_file']
|
|
392
|
+
in_las_folder = in_args.input['in_las_folder']
|
|
393
|
+
cut_ht = float(in_args.input['cut_ht'])
|
|
394
|
+
radius_fr_CHM = in_args.input['radius_fr_CHM']
|
|
395
|
+
focal_radius = float(in_args.input['focal_radius'])
|
|
396
|
+
pulse_density = int(in_args.input['pulse_density'])
|
|
397
|
+
cell_size = float(in_args.input['cell_size'])
|
|
398
|
+
mean_scanning_angle = float(in_args.input['mean_scanning_angle'])
|
|
399
|
+
out_folder = in_args.input['out_folder']
|
|
400
|
+
|
|
401
|
+
# if optional shapefile is empty, then do nothing, else verify shapefile
|
|
402
|
+
if not aoi_shapefile == "":
|
|
403
|
+
if not os.path.exists(os.path.dirname(aoi_shapefile)):
|
|
404
|
+
print("Can't locate the input polygon folder. Please check.")
|
|
405
|
+
exit()
|
|
406
|
+
else:
|
|
407
|
+
if not isinstance(geopandas.GeoDataFrame.from_file(aoi_shapefile), geopandas.GeoDataFrame):
|
|
408
|
+
print("Error input file: Please check effective LiDAR data extend shapefile")
|
|
409
|
+
exit()
|
|
410
|
+
# check existence of input las/laz folder
|
|
411
|
+
if not os.path.exists(in_las_folder):
|
|
412
|
+
print("Error! Cannot locate LAS/LAZ folder, please check.")
|
|
413
|
+
exit()
|
|
414
|
+
else:
|
|
415
|
+
found = False
|
|
416
|
+
for files in os.listdir(in_las_folder):
|
|
417
|
+
if files.endswith(".las") or files.endswith(".laz"):
|
|
418
|
+
found = True
|
|
419
|
+
break
|
|
420
|
+
if not found:
|
|
421
|
+
print("Error! Cannot locate input LAS file(s), please check!")
|
|
422
|
+
exit()
|
|
423
|
+
|
|
424
|
+
# if doing focal radius divided from point cloud CHM
|
|
425
|
+
if radius_fr_CHM == True:
|
|
426
|
+
pass
|
|
427
|
+
# do nothing for now
|
|
428
|
+
else:
|
|
429
|
+
# check manual input for radius, check input
|
|
430
|
+
if not isinstance(focal_radius, float) or focal_radius <= 0.0:
|
|
431
|
+
print("Invalid search radius!!Default radius will be adopted (10m).")
|
|
432
|
+
in_args.input['focal_radius'] = 10.0
|
|
433
|
+
else:
|
|
434
|
+
in_args.input['focal_radius'] = focal_radius
|
|
435
|
+
# check manual input for cell size and pulse density
|
|
436
|
+
if not isinstance(cell_size, float) or cell_size <= 0.00:
|
|
437
|
+
if not isinstance(pulse_density, int) or pulse_density <= 0.00:
|
|
438
|
+
print("Invalid cell size and average pulse density provided.\n"
|
|
439
|
+
"Cell size will be calulated based on aveage point density.")
|
|
440
|
+
in_args.input['cell_size'] = 0.0
|
|
441
|
+
in_args.input['pulse_density'] = 0
|
|
442
|
+
else:
|
|
443
|
+
# mean_pd = (((math.pow(3 / pulse_density, 1 / 2)) + (math.pow(5 / pulse_density, 1 / 2))) / 2)
|
|
444
|
+
mean_pd = math.pow(3 / pulse_density, 1 / 2)
|
|
445
|
+
# mean_pd = math.pow(5 / pulse_density, 1 / 2)
|
|
446
|
+
in_args.input['cell_size'] = round(0.05 * round(mean_pd / 0.05), 2)
|
|
447
|
+
in_args.input['pulse_density'] = pulse_density
|
|
448
|
+
else:
|
|
449
|
+
in_args.input['cell_size'] = (cell_size)
|
|
450
|
+
in_args.input['pulse_density'] = pulse_density
|
|
451
|
+
|
|
452
|
+
# Check manual input for cutt off height
|
|
453
|
+
if not isinstance(cut_ht, float) and cut_ht > 0.0:
|
|
454
|
+
print("Invalid cut off height!! Default cut off height will be adopted (1m).")
|
|
455
|
+
in_args.input["cut_ht"] = 1.0
|
|
456
|
+
|
|
457
|
+
if not isinstance(mean_scanning_angle, float) and mean_scanning_angle > 0.00:
|
|
458
|
+
print("Invalid sensor scanning angle.\n Default sensor scanning angle will size be adopted (30 degree).")
|
|
459
|
+
in_args.input['mean_scanning_angle'] = 30.0
|
|
460
|
+
else:
|
|
461
|
+
in_args.input['mean_scanning_angle'] = mean_scanning_angle
|
|
462
|
+
|
|
463
|
+
print("Checking input parameters ... Done")
|
|
464
|
+
|
|
465
|
+
pd_raster(print, **in_args.input, processes=int(in_args.processes), verbose=in_verbose)
|
|
466
|
+
print('Task is done in {} seconds)'.format(round(time.time() - start_time, 5)))
|