BERATools 0.2.0__py3-none-any.whl → 0.2.1__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.1.dist-info/METADATA +109 -0
- beratools-0.2.1.dist-info/RECORD +74 -0
- {beratools-0.2.0.dist-info → beratools-0.2.1.dist-info}/WHEEL +1 -1
- {beratools-0.2.0.dist-info → beratools-0.2.1.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.1.dist-info}/entry_points.txt +0 -0
|
@@ -1,222 +1,222 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import sys
|
|
3
|
-
import time
|
|
4
|
-
import numpy as np
|
|
5
|
-
from numpy.lib.stride_tricks import as_strided
|
|
6
|
-
|
|
7
|
-
import rasterio
|
|
8
|
-
import xarray as xr
|
|
9
|
-
from xrspatial import convolution, focal
|
|
10
|
-
from scipy import ndimage
|
|
11
|
-
|
|
12
|
-
from common import *
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
# TODO: Rolling Statistics for grid data... an alternative
|
|
16
|
-
# by Dan Patterson
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def _check(a, r_c, subok=False):
|
|
20
|
-
"""Performs the array checks necessary for stride and block.
|
|
21
|
-
: in_array - Array or list.
|
|
22
|
-
: r_c - tuple/list/array of rows x cols.
|
|
23
|
-
: subok - from numpy 1.12 added, keep for now
|
|
24
|
-
:Returns:
|
|
25
|
-
:------
|
|
26
|
-
:Attempts will be made to ...
|
|
27
|
-
: produce a shape at least (1*c). For a scalar, the
|
|
28
|
-
: minimum shape will be (1*r) for 1D array or (1*c) for 2D
|
|
29
|
-
: array if r<c. Be aware
|
|
30
|
-
"""
|
|
31
|
-
if isinstance(r_c, (int, float)):
|
|
32
|
-
r_c = (1, int(r_c))
|
|
33
|
-
|
|
34
|
-
r, c = r_c
|
|
35
|
-
if a.ndim == 1:
|
|
36
|
-
a = np.atleast_2d(a)
|
|
37
|
-
|
|
38
|
-
r, c = r_c = (min(r, a.shape[0]), min(c, a.shape[1]))
|
|
39
|
-
a = np.array(a, copy=False, subok=subok)
|
|
40
|
-
return a, r, c, tuple(r_c)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _pad(in_array, kernel):
|
|
44
|
-
"""Pad a sliding array to allow for stats"""
|
|
45
|
-
pad_x = int(kernel.shape[0] / 2)
|
|
46
|
-
pad_y = int(kernel.shape[0] / 2)
|
|
47
|
-
result = np.pad(in_array, pad_width=(pad_x, pad_y), mode="constant", constant_values=(np.NaN, np.NaN))
|
|
48
|
-
|
|
49
|
-
return result
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def stride(a, r_c):
|
|
53
|
-
"""Provide a 2D sliding/moving view of an array.
|
|
54
|
-
: There is no edge correction for outputs.
|
|
55
|
-
:
|
|
56
|
-
:Requires:
|
|
57
|
-
:--------
|
|
58
|
-
: _check(a, r_c) ... Runs the checks on the inputs.
|
|
59
|
-
: a - array or list, usually a 2D array. Assumes rows is >=1,
|
|
60
|
-
: it is corrected as is the number of columns.
|
|
61
|
-
: r_c - tuple/list/array of rows x cols. Attempts to
|
|
62
|
-
: produce a shape at least (1*c). For a scalar, the
|
|
63
|
-
: minimum shape will be (1*r) for 1D array or 2D
|
|
64
|
-
: array if r<c. Be aware
|
|
65
|
-
"""
|
|
66
|
-
a, r, c, r_c = _check(a, r_c)
|
|
67
|
-
shape = (a.shape[0] - r + 1, a.shape[1] - c + 1) + r_c
|
|
68
|
-
strides = a.strides * 2
|
|
69
|
-
a_s = (as_strided(a, shape=shape, strides=strides)).squeeze()
|
|
70
|
-
return a_s
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def normalize_chm(raster):
|
|
74
|
-
n_raster = np.where(raster >= 0, raster, 0)
|
|
75
|
-
return n_raster
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def np_cc_map(out_canopy_r, chm, in_array, min_ht):
|
|
79
|
-
print('Generating Canopy Closure Raster ...')
|
|
80
|
-
|
|
81
|
-
# canopy_ndarray = np.where(in_array >= min_ht, 1., 0.).astype(float)
|
|
82
|
-
canopy_ndarray = np.ma.where(in_array > min_ht, 1., 0.).astype(float)
|
|
83
|
-
canopy_ndarray = np.ma.filled(canopy_ndarray, chm.nodata)
|
|
84
|
-
try:
|
|
85
|
-
write_canopy = rasterio.open(out_canopy_r, 'w', **chm.profile)
|
|
86
|
-
write_canopy.write(canopy_ndarray, 1)
|
|
87
|
-
write_canopy.close()
|
|
88
|
-
print('Generating Canopy Closure (CC) Raster ... Done')
|
|
89
|
-
except Exception as e:
|
|
90
|
-
print(sys.exc_info())
|
|
91
|
-
del in_array
|
|
92
|
-
return canopy_ndarray
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def fs_raster(in_ndarray, kernel):
|
|
96
|
-
print('Generating Canopy Closure Focal Statistic ...')
|
|
97
|
-
padded_array = _pad(in_ndarray, kernel)
|
|
98
|
-
a_s = stride(padded_array, kernel.shape)
|
|
99
|
-
|
|
100
|
-
# TODO: np.where on large ndarray fail (allocate memory error)
|
|
101
|
-
a_s_masked = np.where(kernel == 1, a_s, np.NaN)
|
|
102
|
-
print("Calculating Canopy Closure's Focal Statistic-Mean ...")
|
|
103
|
-
mean_result = np.nanmean(a_s_masked, axis=(2, 3))
|
|
104
|
-
print("Calculating Canopy Closure's Focal Statistic-Stand Deviation Raster ...")
|
|
105
|
-
stdev_result = np.nanstd(a_s_masked, axis=(2, 3))
|
|
106
|
-
del a_s
|
|
107
|
-
return mean_result, stdev_result
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def fs_raster_stdmean(in_ndarray, kernel, nodata):
|
|
111
|
-
# This function uses xrspatial whcih can handle large data but slow
|
|
112
|
-
in_ndarray[in_ndarray == nodata] = np.nan
|
|
113
|
-
result_ndarray = focal.focal_stats(xr.DataArray(in_ndarray), kernel, stats_funcs=['std', 'mean'])
|
|
114
|
-
|
|
115
|
-
# Flattening the array
|
|
116
|
-
flatten_std_result_ndarray = result_ndarray[0].data.reshape(-1)
|
|
117
|
-
flatten_mean_result_ndarray = result_ndarray[1].data.reshape(-1)
|
|
118
|
-
|
|
119
|
-
# Re-shaping the array
|
|
120
|
-
reshape_std_ndarray = flatten_std_result_ndarray.reshape(in_ndarray.shape[0], in_ndarray.shape[1])
|
|
121
|
-
reshape_mean_ndarray = flatten_mean_result_ndarray.reshape(in_ndarray.shape[0], in_ndarray.shape[1])
|
|
122
|
-
return reshape_std_ndarray, reshape_mean_ndarray
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def smooth_cost(in_raster, search_dist, sampling):
|
|
126
|
-
print('Generating Cost Raster ...')
|
|
127
|
-
from tempfile import mkdtemp
|
|
128
|
-
import os.path as path
|
|
129
|
-
import shutil
|
|
130
|
-
|
|
131
|
-
euc_dist_array = None
|
|
132
|
-
row, col = in_raster.shape
|
|
133
|
-
if row * col >= 30000 * 30000:
|
|
134
|
-
filename = path.join(mkdtemp(), 'tempmmemap.dat')
|
|
135
|
-
a_in_mat = np.memmap(filename, in_raster.dtype, 'w+', shape=in_raster.shape)
|
|
136
|
-
a_in_mat[:] = in_raster[:]
|
|
137
|
-
a_in_mat.flush()
|
|
138
|
-
euc_dist_array = ndimage.distance_transform_edt(np.logical_not(a_in_mat), sampling=sampling)
|
|
139
|
-
del a_in_mat, in_raster
|
|
140
|
-
shutil.rmtree(path.dirname(filename))
|
|
141
|
-
else:
|
|
142
|
-
euc_dist_array = ndimage.distance_transform_edt(np.logical_not(in_raster), sampling=sampling)
|
|
143
|
-
|
|
144
|
-
smooth1 = float(search_dist) - euc_dist_array
|
|
145
|
-
smooth1[smooth1 <= 0.0] = 0.0
|
|
146
|
-
smooth_cost_array = smooth1 / float(search_dist)
|
|
147
|
-
|
|
148
|
-
return smooth_cost_array
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def np_cost_raster(canopy_ndarray, cc_mean, cc_std, cc_smooth, chm, avoidance, cost_raster_exponent, out_cost_r):
|
|
152
|
-
print('Generating Smoothed Cost Raster ...')
|
|
153
|
-
aM1a = (cc_mean - cc_std)
|
|
154
|
-
aM1b = (cc_mean + cc_std)
|
|
155
|
-
aM1 = np.divide(aM1a, aM1b, where=aM1b != 0, out=np.zeros(aM1a.shape, dtype=float))
|
|
156
|
-
aM = (1 + aM1) / 2
|
|
157
|
-
aaM = (cc_mean + cc_std)
|
|
158
|
-
bM = np.where(aaM <= 0, 0, aM)
|
|
159
|
-
cM = bM * (1 - avoidance) + (cc_smooth * avoidance)
|
|
160
|
-
dM = np.where(canopy_ndarray == 1, 1, cM)
|
|
161
|
-
eM = np.exp(dM)
|
|
162
|
-
result = np.power(eM, float(cost_raster_exponent))
|
|
163
|
-
write_cost = rasterio.open(out_cost_r, 'w+', driver='GTiff', height=chm.shape[0], width=chm.shape[1],
|
|
164
|
-
count=1, dtype=chm.read(1).dtype, crs=chm.crs, transform=chm.transform)
|
|
165
|
-
write_cost.write(result, 1)
|
|
166
|
-
write_cost.close()
|
|
167
|
-
print('Generating Smoothed Cost Raster ... Done')
|
|
168
|
-
return
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
# TODO: deal with NODATA
|
|
172
|
-
def canopy_cost_raster(callback, in_chm, canopy_ht_threshold, tree_radius, max_line_dist,
|
|
173
|
-
canopy_avoidance, exponent, out_canopy, out_cost, processes, verbose):
|
|
174
|
-
canopy_ht_threshold = float(canopy_ht_threshold)
|
|
175
|
-
tree_radius = float(tree_radius)
|
|
176
|
-
max_line_dist = float(max_line_dist)
|
|
177
|
-
canopy_avoidance = float(canopy_avoidance)
|
|
178
|
-
cost_raster_exponent = float(exponent)
|
|
179
|
-
|
|
180
|
-
print('In CHM: ' + in_chm)
|
|
181
|
-
chm = rasterio.open(in_chm)
|
|
182
|
-
(cell_x, cell_y) = chm.res
|
|
183
|
-
|
|
184
|
-
print('Loading CHM ...')
|
|
185
|
-
band1_ndarray = chm.read(1, masked=True)
|
|
186
|
-
print('%{}'.format(10))
|
|
187
|
-
|
|
188
|
-
print('Preparing Kernel window ...')
|
|
189
|
-
kernel = convolution.circle_kernel(cell_x, cell_y, tree_radius)
|
|
190
|
-
print('%{}'.format(20))
|
|
191
|
-
|
|
192
|
-
# Generate Canopy Raster and return the Canopy array
|
|
193
|
-
canopy_ndarray = np_cc_map(out_canopy, chm, band1_ndarray, canopy_ht_threshold)
|
|
194
|
-
print('%{}'.format(40))
|
|
195
|
-
print('Apply focal statistic on raster ...')
|
|
196
|
-
|
|
197
|
-
# Calculating focal statistic from canopy raster
|
|
198
|
-
# Alternative: (only work on large cell size
|
|
199
|
-
if cell_y > 1 and cell_x > 1:
|
|
200
|
-
cc_mean, cc_std = fs_raster(canopy_ndarray, kernel)
|
|
201
|
-
else:
|
|
202
|
-
cc_std, cc_mean = fs_raster_stdmean(canopy_ndarray, kernel, chm.nodata)
|
|
203
|
-
print('%{}'.format(60))
|
|
204
|
-
print('Apply focal statistic on raster ... Done')
|
|
205
|
-
|
|
206
|
-
# Smoothing raster
|
|
207
|
-
cc_smooth = smooth_cost(canopy_ndarray, max_line_dist, [cell_x, cell_y])
|
|
208
|
-
avoidance = max(min(float(canopy_avoidance), 1), 0)
|
|
209
|
-
np_cost_raster(canopy_ndarray, cc_mean, cc_std, cc_smooth, chm, avoidance, cost_raster_exponent, out_cost)
|
|
210
|
-
print('%{}'.format(100))
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if __name__ == '__main__':
|
|
214
|
-
in_args, in_verbose = check_arguments()
|
|
215
|
-
|
|
216
|
-
start_time = time.time()
|
|
217
|
-
print('Starting Canopy and Cost Raster processing\n @ {}'
|
|
218
|
-
.format(time.strftime("%d %b %Y %H:%M:%S", time.localtime())))
|
|
219
|
-
|
|
220
|
-
canopy_cost_raster(print, **in_args.input, processes=int(in_args.processes), verbose=in_verbose)
|
|
221
|
-
print('Canopy and Cost Raster processing is done in {} seconds)'
|
|
222
|
-
.format(round(time.time() - start_time, 5)))
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import numpy as np
|
|
5
|
+
from numpy.lib.stride_tricks import as_strided
|
|
6
|
+
|
|
7
|
+
import rasterio
|
|
8
|
+
import xarray as xr
|
|
9
|
+
from xrspatial import convolution, focal
|
|
10
|
+
from scipy import ndimage
|
|
11
|
+
|
|
12
|
+
from common import *
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# TODO: Rolling Statistics for grid data... an alternative
|
|
16
|
+
# by Dan Patterson
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _check(a, r_c, subok=False):
|
|
20
|
+
"""Performs the array checks necessary for stride and block.
|
|
21
|
+
: in_array - Array or list.
|
|
22
|
+
: r_c - tuple/list/array of rows x cols.
|
|
23
|
+
: subok - from numpy 1.12 added, keep for now
|
|
24
|
+
:Returns:
|
|
25
|
+
:------
|
|
26
|
+
:Attempts will be made to ...
|
|
27
|
+
: produce a shape at least (1*c). For a scalar, the
|
|
28
|
+
: minimum shape will be (1*r) for 1D array or (1*c) for 2D
|
|
29
|
+
: array if r<c. Be aware
|
|
30
|
+
"""
|
|
31
|
+
if isinstance(r_c, (int, float)):
|
|
32
|
+
r_c = (1, int(r_c))
|
|
33
|
+
|
|
34
|
+
r, c = r_c
|
|
35
|
+
if a.ndim == 1:
|
|
36
|
+
a = np.atleast_2d(a)
|
|
37
|
+
|
|
38
|
+
r, c = r_c = (min(r, a.shape[0]), min(c, a.shape[1]))
|
|
39
|
+
a = np.array(a, copy=False, subok=subok)
|
|
40
|
+
return a, r, c, tuple(r_c)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _pad(in_array, kernel):
|
|
44
|
+
"""Pad a sliding array to allow for stats"""
|
|
45
|
+
pad_x = int(kernel.shape[0] / 2)
|
|
46
|
+
pad_y = int(kernel.shape[0] / 2)
|
|
47
|
+
result = np.pad(in_array, pad_width=(pad_x, pad_y), mode="constant", constant_values=(np.NaN, np.NaN))
|
|
48
|
+
|
|
49
|
+
return result
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def stride(a, r_c):
|
|
53
|
+
"""Provide a 2D sliding/moving view of an array.
|
|
54
|
+
: There is no edge correction for outputs.
|
|
55
|
+
:
|
|
56
|
+
:Requires:
|
|
57
|
+
:--------
|
|
58
|
+
: _check(a, r_c) ... Runs the checks on the inputs.
|
|
59
|
+
: a - array or list, usually a 2D array. Assumes rows is >=1,
|
|
60
|
+
: it is corrected as is the number of columns.
|
|
61
|
+
: r_c - tuple/list/array of rows x cols. Attempts to
|
|
62
|
+
: produce a shape at least (1*c). For a scalar, the
|
|
63
|
+
: minimum shape will be (1*r) for 1D array or 2D
|
|
64
|
+
: array if r<c. Be aware
|
|
65
|
+
"""
|
|
66
|
+
a, r, c, r_c = _check(a, r_c)
|
|
67
|
+
shape = (a.shape[0] - r + 1, a.shape[1] - c + 1) + r_c
|
|
68
|
+
strides = a.strides * 2
|
|
69
|
+
a_s = (as_strided(a, shape=shape, strides=strides)).squeeze()
|
|
70
|
+
return a_s
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def normalize_chm(raster):
|
|
74
|
+
n_raster = np.where(raster >= 0, raster, 0)
|
|
75
|
+
return n_raster
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def np_cc_map(out_canopy_r, chm, in_array, min_ht):
|
|
79
|
+
print('Generating Canopy Closure Raster ...')
|
|
80
|
+
|
|
81
|
+
# canopy_ndarray = np.where(in_array >= min_ht, 1., 0.).astype(float)
|
|
82
|
+
canopy_ndarray = np.ma.where(in_array > min_ht, 1., 0.).astype(float)
|
|
83
|
+
canopy_ndarray = np.ma.filled(canopy_ndarray, chm.nodata)
|
|
84
|
+
try:
|
|
85
|
+
write_canopy = rasterio.open(out_canopy_r, 'w', **chm.profile)
|
|
86
|
+
write_canopy.write(canopy_ndarray, 1)
|
|
87
|
+
write_canopy.close()
|
|
88
|
+
print('Generating Canopy Closure (CC) Raster ... Done')
|
|
89
|
+
except Exception as e:
|
|
90
|
+
print(sys.exc_info())
|
|
91
|
+
del in_array
|
|
92
|
+
return canopy_ndarray
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def fs_raster(in_ndarray, kernel):
|
|
96
|
+
print('Generating Canopy Closure Focal Statistic ...')
|
|
97
|
+
padded_array = _pad(in_ndarray, kernel)
|
|
98
|
+
a_s = stride(padded_array, kernel.shape)
|
|
99
|
+
|
|
100
|
+
# TODO: np.where on large ndarray fail (allocate memory error)
|
|
101
|
+
a_s_masked = np.where(kernel == 1, a_s, np.NaN)
|
|
102
|
+
print("Calculating Canopy Closure's Focal Statistic-Mean ...")
|
|
103
|
+
mean_result = np.nanmean(a_s_masked, axis=(2, 3))
|
|
104
|
+
print("Calculating Canopy Closure's Focal Statistic-Stand Deviation Raster ...")
|
|
105
|
+
stdev_result = np.nanstd(a_s_masked, axis=(2, 3))
|
|
106
|
+
del a_s
|
|
107
|
+
return mean_result, stdev_result
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def fs_raster_stdmean(in_ndarray, kernel, nodata):
|
|
111
|
+
# This function uses xrspatial whcih can handle large data but slow
|
|
112
|
+
in_ndarray[in_ndarray == nodata] = np.nan
|
|
113
|
+
result_ndarray = focal.focal_stats(xr.DataArray(in_ndarray), kernel, stats_funcs=['std', 'mean'])
|
|
114
|
+
|
|
115
|
+
# Flattening the array
|
|
116
|
+
flatten_std_result_ndarray = result_ndarray[0].data.reshape(-1)
|
|
117
|
+
flatten_mean_result_ndarray = result_ndarray[1].data.reshape(-1)
|
|
118
|
+
|
|
119
|
+
# Re-shaping the array
|
|
120
|
+
reshape_std_ndarray = flatten_std_result_ndarray.reshape(in_ndarray.shape[0], in_ndarray.shape[1])
|
|
121
|
+
reshape_mean_ndarray = flatten_mean_result_ndarray.reshape(in_ndarray.shape[0], in_ndarray.shape[1])
|
|
122
|
+
return reshape_std_ndarray, reshape_mean_ndarray
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def smooth_cost(in_raster, search_dist, sampling):
|
|
126
|
+
print('Generating Cost Raster ...')
|
|
127
|
+
from tempfile import mkdtemp
|
|
128
|
+
import os.path as path
|
|
129
|
+
import shutil
|
|
130
|
+
|
|
131
|
+
euc_dist_array = None
|
|
132
|
+
row, col = in_raster.shape
|
|
133
|
+
if row * col >= 30000 * 30000:
|
|
134
|
+
filename = path.join(mkdtemp(), 'tempmmemap.dat')
|
|
135
|
+
a_in_mat = np.memmap(filename, in_raster.dtype, 'w+', shape=in_raster.shape)
|
|
136
|
+
a_in_mat[:] = in_raster[:]
|
|
137
|
+
a_in_mat.flush()
|
|
138
|
+
euc_dist_array = ndimage.distance_transform_edt(np.logical_not(a_in_mat), sampling=sampling)
|
|
139
|
+
del a_in_mat, in_raster
|
|
140
|
+
shutil.rmtree(path.dirname(filename))
|
|
141
|
+
else:
|
|
142
|
+
euc_dist_array = ndimage.distance_transform_edt(np.logical_not(in_raster), sampling=sampling)
|
|
143
|
+
|
|
144
|
+
smooth1 = float(search_dist) - euc_dist_array
|
|
145
|
+
smooth1[smooth1 <= 0.0] = 0.0
|
|
146
|
+
smooth_cost_array = smooth1 / float(search_dist)
|
|
147
|
+
|
|
148
|
+
return smooth_cost_array
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def np_cost_raster(canopy_ndarray, cc_mean, cc_std, cc_smooth, chm, avoidance, cost_raster_exponent, out_cost_r):
|
|
152
|
+
print('Generating Smoothed Cost Raster ...')
|
|
153
|
+
aM1a = (cc_mean - cc_std)
|
|
154
|
+
aM1b = (cc_mean + cc_std)
|
|
155
|
+
aM1 = np.divide(aM1a, aM1b, where=aM1b != 0, out=np.zeros(aM1a.shape, dtype=float))
|
|
156
|
+
aM = (1 + aM1) / 2
|
|
157
|
+
aaM = (cc_mean + cc_std)
|
|
158
|
+
bM = np.where(aaM <= 0, 0, aM)
|
|
159
|
+
cM = bM * (1 - avoidance) + (cc_smooth * avoidance)
|
|
160
|
+
dM = np.where(canopy_ndarray == 1, 1, cM)
|
|
161
|
+
eM = np.exp(dM)
|
|
162
|
+
result = np.power(eM, float(cost_raster_exponent))
|
|
163
|
+
write_cost = rasterio.open(out_cost_r, 'w+', driver='GTiff', height=chm.shape[0], width=chm.shape[1],
|
|
164
|
+
count=1, dtype=chm.read(1).dtype, crs=chm.crs, transform=chm.transform)
|
|
165
|
+
write_cost.write(result, 1)
|
|
166
|
+
write_cost.close()
|
|
167
|
+
print('Generating Smoothed Cost Raster ... Done')
|
|
168
|
+
return
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# TODO: deal with NODATA
|
|
172
|
+
def canopy_cost_raster(callback, in_chm, canopy_ht_threshold, tree_radius, max_line_dist,
|
|
173
|
+
canopy_avoidance, exponent, out_canopy, out_cost, processes, verbose):
|
|
174
|
+
canopy_ht_threshold = float(canopy_ht_threshold)
|
|
175
|
+
tree_radius = float(tree_radius)
|
|
176
|
+
max_line_dist = float(max_line_dist)
|
|
177
|
+
canopy_avoidance = float(canopy_avoidance)
|
|
178
|
+
cost_raster_exponent = float(exponent)
|
|
179
|
+
|
|
180
|
+
print('In CHM: ' + in_chm)
|
|
181
|
+
chm = rasterio.open(in_chm)
|
|
182
|
+
(cell_x, cell_y) = chm.res
|
|
183
|
+
|
|
184
|
+
print('Loading CHM ...')
|
|
185
|
+
band1_ndarray = chm.read(1, masked=True)
|
|
186
|
+
print('%{}'.format(10))
|
|
187
|
+
|
|
188
|
+
print('Preparing Kernel window ...')
|
|
189
|
+
kernel = convolution.circle_kernel(cell_x, cell_y, tree_radius)
|
|
190
|
+
print('%{}'.format(20))
|
|
191
|
+
|
|
192
|
+
# Generate Canopy Raster and return the Canopy array
|
|
193
|
+
canopy_ndarray = np_cc_map(out_canopy, chm, band1_ndarray, canopy_ht_threshold)
|
|
194
|
+
print('%{}'.format(40))
|
|
195
|
+
print('Apply focal statistic on raster ...')
|
|
196
|
+
|
|
197
|
+
# Calculating focal statistic from canopy raster
|
|
198
|
+
# Alternative: (only work on large cell size
|
|
199
|
+
if cell_y > 1 and cell_x > 1:
|
|
200
|
+
cc_mean, cc_std = fs_raster(canopy_ndarray, kernel)
|
|
201
|
+
else:
|
|
202
|
+
cc_std, cc_mean = fs_raster_stdmean(canopy_ndarray, kernel, chm.nodata)
|
|
203
|
+
print('%{}'.format(60))
|
|
204
|
+
print('Apply focal statistic on raster ... Done')
|
|
205
|
+
|
|
206
|
+
# Smoothing raster
|
|
207
|
+
cc_smooth = smooth_cost(canopy_ndarray, max_line_dist, [cell_x, cell_y])
|
|
208
|
+
avoidance = max(min(float(canopy_avoidance), 1), 0)
|
|
209
|
+
np_cost_raster(canopy_ndarray, cc_mean, cc_std, cc_smooth, chm, avoidance, cost_raster_exponent, out_cost)
|
|
210
|
+
print('%{}'.format(100))
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
if __name__ == '__main__':
|
|
214
|
+
in_args, in_verbose = check_arguments()
|
|
215
|
+
|
|
216
|
+
start_time = time.time()
|
|
217
|
+
print('Starting Canopy and Cost Raster processing\n @ {}'
|
|
218
|
+
.format(time.strftime("%d %b %Y %H:%M:%S", time.localtime())))
|
|
219
|
+
|
|
220
|
+
canopy_cost_raster(print, **in_args.input, processes=int(in_args.processes), verbose=in_verbose)
|
|
221
|
+
print('Canopy and Cost Raster processing is done in {} seconds)'
|
|
222
|
+
.format(round(time.time() - start_time, 5)))
|