BERATools 0.2.2__py3-none-any.whl → 0.2.4__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 +8 -3
- beratools/core/{algo_footprint_rel.py → algo_canopy_footprint_exp.py} +176 -139
- beratools/core/algo_centerline.py +61 -77
- beratools/core/algo_common.py +48 -57
- beratools/core/algo_cost.py +18 -25
- beratools/core/algo_dijkstra.py +37 -45
- beratools/core/algo_line_grouping.py +100 -100
- beratools/core/algo_merge_lines.py +40 -8
- beratools/core/algo_split_with_lines.py +289 -304
- beratools/core/algo_vertex_optimization.py +25 -46
- beratools/core/canopy_threshold_relative.py +755 -0
- beratools/core/constants.py +8 -9
- beratools/{tools → core}/line_footprint_functions.py +411 -258
- beratools/core/logger.py +18 -2
- beratools/core/tool_base.py +17 -75
- beratools/gui/assets/BERALogo.ico +0 -0
- beratools/gui/assets/BERA_Splash.gif +0 -0
- beratools/gui/assets/BERA_WizardImage.png +0 -0
- beratools/gui/assets/beratools.json +475 -2171
- beratools/gui/bt_data.py +585 -234
- beratools/gui/bt_gui_main.py +129 -91
- beratools/gui/main.py +4 -7
- beratools/gui/tool_widgets.py +530 -354
- beratools/tools/__init__.py +0 -7
- beratools/tools/{line_footprint_absolute.py → canopy_footprint_absolute.py} +81 -56
- beratools/tools/canopy_footprint_exp.py +113 -0
- beratools/tools/centerline.py +30 -37
- beratools/tools/check_seed_line.py +127 -0
- beratools/tools/common.py +65 -586
- beratools/tools/{line_footprint_fixed.py → ground_footprint.py} +140 -117
- beratools/tools/line_footprint_relative.py +64 -35
- beratools/tools/tool_template.py +48 -40
- beratools/tools/vertex_optimization.py +20 -34
- beratools/utility/env_checks.py +53 -0
- beratools/utility/spatial_common.py +210 -0
- beratools/utility/tool_args.py +138 -0
- beratools-0.2.4.dist-info/METADATA +134 -0
- beratools-0.2.4.dist-info/RECORD +50 -0
- {beratools-0.2.2.dist-info → beratools-0.2.4.dist-info}/WHEEL +1 -1
- beratools-0.2.4.dist-info/entry_points.txt +3 -0
- beratools-0.2.4.dist-info/licenses/LICENSE +674 -0
- beratools/core/algo_tiler.py +0 -428
- beratools/gui/__init__.py +0 -11
- beratools/gui/batch_processing_dlg.py +0 -513
- beratools/gui/map_window.py +0 -162
- beratools/tools/Beratools_r_script.r +0 -1120
- beratools/tools/Ht_metrics.py +0 -116
- beratools/tools/batch_processing.py +0 -136
- beratools/tools/canopy_threshold_relative.py +0 -672
- beratools/tools/canopycostraster.py +0 -222
- beratools/tools/fl_regen_csf.py +0 -428
- beratools/tools/forest_line_attributes.py +0 -408
- beratools/tools/line_grouping.py +0 -45
- beratools/tools/ln_relative_metrics.py +0 -615
- beratools/tools/r_cal_lpi_elai.r +0 -25
- beratools/tools/r_generate_pd_focalraster.r +0 -101
- beratools/tools/r_interface.py +0 -80
- beratools/tools/r_point_density.r +0 -9
- beratools/tools/rpy_chm2trees.py +0 -86
- beratools/tools/rpy_dsm_chm_by.py +0 -81
- beratools/tools/rpy_dtm_by.py +0 -63
- beratools/tools/rpy_find_cellsize.py +0 -43
- beratools/tools/rpy_gnd_csf.py +0 -74
- beratools/tools/rpy_hummock_hollow.py +0 -85
- beratools/tools/rpy_hummock_hollow_raster.py +0 -71
- beratools/tools/rpy_las_info.py +0 -51
- beratools/tools/rpy_laz2las.py +0 -40
- beratools/tools/rpy_lpi_elai_lascat.py +0 -466
- beratools/tools/rpy_normalized_lidar_by.py +0 -56
- beratools/tools/rpy_percent_above_dbh.py +0 -80
- beratools/tools/rpy_points2trees.py +0 -88
- beratools/tools/rpy_vegcoverage.py +0 -94
- beratools/tools/tiler.py +0 -48
- beratools/tools/zonal_threshold.py +0 -144
- beratools-0.2.2.dist-info/METADATA +0 -108
- beratools-0.2.2.dist-info/RECORD +0 -74
- beratools-0.2.2.dist-info/entry_points.txt +0 -2
- beratools-0.2.2.dist-info/licenses/LICENSE +0 -22
beratools/core/algo_cost.py
CHANGED
|
@@ -12,6 +12,7 @@ Description:
|
|
|
12
12
|
|
|
13
13
|
This file hosts cost raster related functions.
|
|
14
14
|
"""
|
|
15
|
+
|
|
15
16
|
import numpy as np
|
|
16
17
|
import scipy
|
|
17
18
|
|
|
@@ -35,7 +36,7 @@ def cost_raster(
|
|
|
35
36
|
"""
|
|
36
37
|
if len(in_raster.shape) > 2:
|
|
37
38
|
in_raster = np.squeeze(in_raster, axis=0)
|
|
38
|
-
|
|
39
|
+
|
|
39
40
|
# regulate canopy_avoid between 0 and 1
|
|
40
41
|
avoidance = max(0, min(1, canopy_avoid))
|
|
41
42
|
cell_x, cell_y = meta["transform"][0], -meta["transform"][4]
|
|
@@ -45,9 +46,7 @@ def cost_raster(
|
|
|
45
46
|
dyn_canopy_ndarray = dyn_np_cc_map(in_raster, canopy_ht_threshold)
|
|
46
47
|
|
|
47
48
|
cc_std, cc_mean = cost_focal_stats(dyn_canopy_ndarray, kernel)
|
|
48
|
-
cc_smooth = cost_norm_dist_transform(
|
|
49
|
-
dyn_canopy_ndarray, max_line_dist, [cell_x, cell_y]
|
|
50
|
-
)
|
|
49
|
+
cc_smooth = cost_norm_dist_transform(dyn_canopy_ndarray, max_line_dist, [cell_x, cell_y])
|
|
51
50
|
|
|
52
51
|
cost_clip = dyn_np_cost_raster_refactor(
|
|
53
52
|
dyn_canopy_ndarray, cc_mean, cc_std, cc_smooth, avoidance, cost_raster_exponent
|
|
@@ -59,62 +58,56 @@ def cost_raster(
|
|
|
59
58
|
|
|
60
59
|
return cost_clip, dyn_canopy_ndarray
|
|
61
60
|
|
|
61
|
+
|
|
62
62
|
def remove_nan_from_array_refactor(matrix, replacement_value=bt_const.BT_NODATA_COST):
|
|
63
63
|
# Use boolean indexing to replace nan values
|
|
64
64
|
matrix[np.isnan(matrix)] = replacement_value
|
|
65
65
|
|
|
66
|
+
|
|
66
67
|
def dyn_np_cc_map(in_chm, canopy_ht_threshold):
|
|
67
68
|
"""
|
|
68
69
|
Create a new canopy raster.
|
|
69
70
|
|
|
70
|
-
MaskedArray based on the threshold comparison of in_chm (canopy height model)
|
|
71
|
-
with canopy_ht_threshold. It assigns 1.0 where the condition is True (canopy)
|
|
71
|
+
MaskedArray based on the threshold comparison of in_chm (canopy height model)
|
|
72
|
+
with canopy_ht_threshold. It assigns 1.0 where the condition is True (canopy)
|
|
72
73
|
and 0.0 where the condition is False (non-canopy).
|
|
73
74
|
|
|
74
75
|
"""
|
|
75
76
|
canopy_ndarray = np.ma.where(in_chm >= canopy_ht_threshold, 1.0, 0.0).astype(float)
|
|
76
77
|
return canopy_ndarray
|
|
77
78
|
|
|
79
|
+
|
|
78
80
|
def cost_focal_stats(canopy_ndarray, kernel):
|
|
79
81
|
mask = canopy_ndarray.mask
|
|
80
82
|
in_ndarray = np.ma.where(mask, np.nan, canopy_ndarray)
|
|
81
83
|
|
|
82
84
|
# Function to compute mean and standard deviation
|
|
83
85
|
def calc_mean(arr):
|
|
84
|
-
# Check if the array is empty or full of NaNs
|
|
85
|
-
if arr.size == 0 or np.all(np.isnan(arr)):
|
|
86
|
-
return np.nan # Or any other value you'd prefer for empty arrays
|
|
87
86
|
return np.nanmean(arr)
|
|
88
87
|
|
|
89
88
|
def calc_std(arr):
|
|
90
|
-
# Check if the array is empty or full of NaNs
|
|
91
|
-
if arr.size == 0 or np.all(np.isnan(arr)):
|
|
92
|
-
return np.nan # Or any other placeholder you prefer
|
|
93
89
|
return np.nanstd(arr)
|
|
94
90
|
|
|
95
91
|
# Apply the generic_filter function to compute mean and std
|
|
96
|
-
mean_array = scipy.ndimage.generic_filter(
|
|
97
|
-
|
|
98
|
-
)
|
|
99
|
-
std_array = scipy.ndimage.generic_filter(
|
|
100
|
-
in_ndarray, calc_std, footprint=kernel, mode="nearest"
|
|
101
|
-
)
|
|
92
|
+
mean_array = scipy.ndimage.generic_filter(in_ndarray, calc_mean, footprint=kernel, mode="nearest")
|
|
93
|
+
std_array = scipy.ndimage.generic_filter(in_ndarray, calc_std, footprint=kernel, mode="nearest")
|
|
102
94
|
|
|
103
95
|
return std_array, mean_array
|
|
104
96
|
|
|
97
|
+
|
|
105
98
|
def cost_norm_dist_transform(canopy_ndarray, max_line_dist, sampling):
|
|
106
99
|
"""Compute a distance-based cost map based on the proximity of valid data points."""
|
|
107
100
|
# Convert masked array to a regular array and fill the masked areas with np.nan
|
|
108
101
|
in_ndarray = canopy_ndarray.filled(np.nan)
|
|
109
|
-
|
|
102
|
+
|
|
110
103
|
# Compute the Euclidean distance transform (edt) where the valid values are
|
|
111
104
|
euc_dist_array = scipy.ndimage.distance_transform_edt(
|
|
112
105
|
np.logical_not(np.isnan(in_ndarray)), sampling=sampling
|
|
113
106
|
)
|
|
114
|
-
|
|
107
|
+
|
|
115
108
|
# Apply the mask back to set the distances to np.nan
|
|
116
109
|
euc_dist_array[canopy_ndarray.mask] = np.nan
|
|
117
|
-
|
|
110
|
+
|
|
118
111
|
# Calculate the smoothness (cost) array
|
|
119
112
|
normalized_cost = float(max_line_dist) - euc_dist_array
|
|
120
113
|
normalized_cost[normalized_cost <= 0.0] = 0.0
|
|
@@ -122,9 +115,8 @@ def cost_norm_dist_transform(canopy_ndarray, max_line_dist, sampling):
|
|
|
122
115
|
|
|
123
116
|
return smooth_cost_array
|
|
124
117
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
):
|
|
118
|
+
|
|
119
|
+
def dyn_np_cost_raster_refactor(canopy_ndarray, cc_mean, cc_std, cc_smooth, avoidance, cost_raster_exponent):
|
|
128
120
|
# Calculate the lower and upper bounds for canopy cover (mean ± std deviation)
|
|
129
121
|
lower_bound = cc_mean - cc_std
|
|
130
122
|
upper_bound = cc_mean + cc_std
|
|
@@ -158,6 +150,7 @@ def dyn_np_cost_raster_refactor(
|
|
|
158
150
|
|
|
159
151
|
return result_cost_raster
|
|
160
152
|
|
|
153
|
+
|
|
161
154
|
def circle_kernel_refactor(size, radius):
|
|
162
155
|
"""
|
|
163
156
|
Create a circular kernel using Scipy.
|
|
@@ -189,4 +182,4 @@ def circle_kernel_refactor(size, radius):
|
|
|
189
182
|
|
|
190
183
|
# Create a circular kernel
|
|
191
184
|
kernel = distance <= radius
|
|
192
|
-
return kernel.astype(float)
|
|
185
|
+
return kernel.astype(float)
|
beratools/core/algo_dijkstra.py
CHANGED
|
@@ -17,7 +17,7 @@ the Free Software Foundation; either version 2 of the License, or
|
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
# This will get replaced with a git SHA1 when you do a git archive
|
|
20
|
-
__revision__ =
|
|
20
|
+
__revision__ = "$Format:%H$"
|
|
21
21
|
|
|
22
22
|
import math
|
|
23
23
|
import queue
|
|
@@ -52,9 +52,7 @@ class MinCostPathHelper:
|
|
|
52
52
|
def create_points_from_path(ras_transform, min_cost_path, start_point, end_point):
|
|
53
53
|
path_points = list(
|
|
54
54
|
map(
|
|
55
|
-
lambda row_col: MinCostPathHelper._row_col_to_point(
|
|
56
|
-
row_col, ras_transform
|
|
57
|
-
),
|
|
55
|
+
lambda row_col: MinCostPathHelper._row_col_to_point(row_col, ras_transform),
|
|
58
56
|
min_cost_path,
|
|
59
57
|
)
|
|
60
58
|
)
|
|
@@ -71,7 +69,7 @@ class MinCostPathHelper:
|
|
|
71
69
|
@staticmethod
|
|
72
70
|
def block2matrix_numpy(block, nodata):
|
|
73
71
|
contains_negative = False
|
|
74
|
-
with np.nditer(block, flags=["refs_ok"], op_flags=[
|
|
72
|
+
with np.nditer(block, flags=["refs_ok"], op_flags=["readwrite"]) as it:
|
|
75
73
|
for x in it:
|
|
76
74
|
# TODO: this speeds up a lot, but need further inspection
|
|
77
75
|
# if np.isclose(x, nodata) or np.isnan(x):
|
|
@@ -90,8 +88,7 @@ class MinCostPathHelper:
|
|
|
90
88
|
matrix = [
|
|
91
89
|
[
|
|
92
90
|
None
|
|
93
|
-
if np.isclose(block[i][j], nodata)
|
|
94
|
-
or np.isclose(block[i][j], bt_const.BT_NODATA)
|
|
91
|
+
if np.isclose(block[i][j], nodata) or np.isclose(block[i][j], bt_const.BT_NODATA)
|
|
95
92
|
else block[i][j]
|
|
96
93
|
for j in range(height)
|
|
97
94
|
]
|
|
@@ -129,8 +126,16 @@ def dijkstra(start_tuple, end_tuples, block, find_nearest, feedback=None):
|
|
|
129
126
|
|
|
130
127
|
def neighbors(self, id):
|
|
131
128
|
x, y = id
|
|
132
|
-
results = [
|
|
133
|
-
|
|
129
|
+
results = [
|
|
130
|
+
(x + 1, y),
|
|
131
|
+
(x, y - 1),
|
|
132
|
+
(x - 1, y),
|
|
133
|
+
(x, y + 1),
|
|
134
|
+
(x + 1, y - 1),
|
|
135
|
+
(x + 1, y + 1),
|
|
136
|
+
(x - 1, y - 1),
|
|
137
|
+
(x - 1, y + 1),
|
|
138
|
+
]
|
|
134
139
|
results = list(filter(self.is_valid, results))
|
|
135
140
|
return results
|
|
136
141
|
|
|
@@ -142,22 +147,15 @@ def dijkstra(start_tuple, end_tuples, block, find_nearest, feedback=None):
|
|
|
142
147
|
|
|
143
148
|
@staticmethod
|
|
144
149
|
def min_manhattan(curr_node, end_nodes):
|
|
145
|
-
return min(
|
|
146
|
-
map(lambda node: Grid.manhattan_distance(curr_node, node), end_nodes)
|
|
147
|
-
)
|
|
150
|
+
return min(map(lambda node: Grid.manhattan_distance(curr_node, node), end_nodes))
|
|
148
151
|
|
|
149
152
|
@staticmethod
|
|
150
153
|
def max_manhattan(curr_node, end_nodes):
|
|
151
|
-
return max(
|
|
152
|
-
map(lambda node: Grid.manhattan_distance(curr_node, node), end_nodes)
|
|
153
|
-
)
|
|
154
|
+
return max(map(lambda node: Grid.manhattan_distance(curr_node, node), end_nodes))
|
|
154
155
|
|
|
155
156
|
@staticmethod
|
|
156
157
|
def all_manhattan(curr_node, end_nodes):
|
|
157
|
-
return {
|
|
158
|
-
end_node: Grid.manhattan_distance(curr_node, end_node)
|
|
159
|
-
for end_node in end_nodes
|
|
160
|
-
}
|
|
158
|
+
return {end_node: Grid.manhattan_distance(curr_node, end_node) for end_node in end_nodes}
|
|
161
159
|
|
|
162
160
|
def simple_cost(self, cur, nex):
|
|
163
161
|
cx, cy = cur
|
|
@@ -230,10 +228,7 @@ def dijkstra(start_tuple, end_tuples, block, find_nearest, feedback=None):
|
|
|
230
228
|
bound = curr_bound
|
|
231
229
|
if feedback:
|
|
232
230
|
feedback.setProgress(
|
|
233
|
-
1
|
|
234
|
-
+ 100
|
|
235
|
-
* (1 - bound / total_manhattan)
|
|
236
|
-
* (1 - bound / total_manhattan)
|
|
231
|
+
1 + 100 * (1 - bound / total_manhattan) * (1 - bound / total_manhattan)
|
|
237
232
|
)
|
|
238
233
|
|
|
239
234
|
# destination
|
|
@@ -336,6 +331,9 @@ def backtrack(initial_node, desired_node, distances):
|
|
|
336
331
|
if path[-1][0] == initial_node[0] and path[-1][1] == initial_node[1]:
|
|
337
332
|
break
|
|
338
333
|
|
|
334
|
+
if len(path) == 0 or path[-1][0] != initial_node[0] or path[-1][1] != initial_node[1]:
|
|
335
|
+
return None
|
|
336
|
+
|
|
339
337
|
return list(reversed(path))
|
|
340
338
|
|
|
341
339
|
|
|
@@ -371,11 +369,9 @@ def dijkstra_np(start_tuple, end_tuple, matrix):
|
|
|
371
369
|
return [(path, costs, end_tuple)]
|
|
372
370
|
|
|
373
371
|
|
|
374
|
-
def find_least_cost_path(
|
|
375
|
-
out_image, in_meta, line, find_nearest=True, output_linear_reference=False
|
|
376
|
-
):
|
|
372
|
+
def find_least_cost_path(out_image, in_meta, line, find_nearest=True, output_linear_reference=False):
|
|
377
373
|
default_return = None
|
|
378
|
-
ras_nodata = in_meta[
|
|
374
|
+
ras_nodata = in_meta["nodata"]
|
|
379
375
|
|
|
380
376
|
pt_start = line.coords[0]
|
|
381
377
|
pt_end = line.coords[-1]
|
|
@@ -385,24 +381,22 @@ def find_least_cost_path(
|
|
|
385
381
|
out_image = np.squeeze(out_image, axis=0)
|
|
386
382
|
|
|
387
383
|
if USE_NUMPY_FOR_DIJKSTRA:
|
|
388
|
-
matrix, contains_negative = MinCostPathHelper.block2matrix_numpy(
|
|
389
|
-
out_image, ras_nodata
|
|
390
|
-
)
|
|
384
|
+
matrix, contains_negative = MinCostPathHelper.block2matrix_numpy(out_image, ras_nodata)
|
|
391
385
|
else:
|
|
392
|
-
matrix, contains_negative = MinCostPathHelper.block2matrix(
|
|
393
|
-
out_image, ras_nodata
|
|
394
|
-
)
|
|
386
|
+
matrix, contains_negative = MinCostPathHelper.block2matrix(out_image, ras_nodata)
|
|
395
387
|
|
|
396
388
|
if contains_negative:
|
|
397
|
-
print(
|
|
389
|
+
print("ERROR: Raster has negative values.")
|
|
398
390
|
return default_return
|
|
399
391
|
|
|
400
|
-
transformer = rasterio.transform.AffineTransformer(in_meta[
|
|
392
|
+
transformer = rasterio.transform.AffineTransformer(in_meta["transform"])
|
|
401
393
|
|
|
402
|
-
if (
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
394
|
+
if (
|
|
395
|
+
type(pt_start[0]) is tuple
|
|
396
|
+
or type(pt_start[1]) is tuple
|
|
397
|
+
or type(pt_end[0]) is tuple
|
|
398
|
+
or type(pt_end[1]) is tuple
|
|
399
|
+
):
|
|
406
400
|
print("Point initialization error. Input is tuple.")
|
|
407
401
|
return default_return
|
|
408
402
|
|
|
@@ -446,7 +440,7 @@ def find_least_cost_path(
|
|
|
446
440
|
return default_return
|
|
447
441
|
|
|
448
442
|
if len(result) == 0:
|
|
449
|
-
print(
|
|
443
|
+
print("No result returned.")
|
|
450
444
|
return default_return
|
|
451
445
|
|
|
452
446
|
path_points = None
|
|
@@ -473,7 +467,7 @@ def find_least_cost_path_skimage(cost_clip, in_meta, seed_line):
|
|
|
473
467
|
if len(cost_clip.shape) > 2:
|
|
474
468
|
cost_clip = np.squeeze(cost_clip, axis=0)
|
|
475
469
|
|
|
476
|
-
out_transform = in_meta[
|
|
470
|
+
out_transform = in_meta["transform"]
|
|
477
471
|
transformer = rasterio.transform.AffineTransformer(out_transform)
|
|
478
472
|
|
|
479
473
|
x1, y1 = list(seed_line.coords)[0][:2]
|
|
@@ -482,9 +476,7 @@ def find_least_cost_path_skimage(cost_clip, in_meta, seed_line):
|
|
|
482
476
|
row2, col2 = transformer.rowcol(x2, y2)
|
|
483
477
|
|
|
484
478
|
try:
|
|
485
|
-
path_new = sk_graph.route_through_array(
|
|
486
|
-
cost_clip[0], [row1, col1], [row2, col2]
|
|
487
|
-
)
|
|
479
|
+
path_new = sk_graph.route_through_array(cost_clip[0], [row1, col1], [row2, col2])
|
|
488
480
|
except Exception as e:
|
|
489
481
|
print(f"find_least_cost_path_skimage: {e}")
|
|
490
482
|
return None
|
|
@@ -495,7 +487,7 @@ def find_least_cost_path_skimage(cost_clip, in_meta, seed_line):
|
|
|
495
487
|
lc_path_new.append((x, y))
|
|
496
488
|
|
|
497
489
|
if len(lc_path_new) < 2:
|
|
498
|
-
print(
|
|
490
|
+
print("No least cost path detected, pass.")
|
|
499
491
|
return None
|
|
500
492
|
else:
|
|
501
493
|
lc_path_new = sh_geom.LineString(lc_path_new)
|