BERATools 0.1.0__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.
Files changed (44) hide show
  1. beratools/__init__.py +3 -0
  2. beratools/core/__init__.py +0 -0
  3. beratools/core/algo_centerline.py +476 -0
  4. beratools/core/algo_common.py +489 -0
  5. beratools/core/algo_cost.py +185 -0
  6. beratools/core/algo_dijkstra.py +492 -0
  7. beratools/core/algo_footprint_rel.py +693 -0
  8. beratools/core/algo_line_grouping.py +941 -0
  9. beratools/core/algo_merge_lines.py +255 -0
  10. beratools/core/algo_split_with_lines.py +296 -0
  11. beratools/core/algo_vertex_optimization.py +451 -0
  12. beratools/core/constants.py +56 -0
  13. beratools/core/logger.py +92 -0
  14. beratools/core/tool_base.py +126 -0
  15. beratools/gui/__init__.py +11 -0
  16. beratools/gui/assets/BERALogo.png +0 -0
  17. beratools/gui/assets/beratools.json +471 -0
  18. beratools/gui/assets/closed.gif +0 -0
  19. beratools/gui/assets/closed.png +0 -0
  20. beratools/gui/assets/gui.json +8 -0
  21. beratools/gui/assets/open.gif +0 -0
  22. beratools/gui/assets/open.png +0 -0
  23. beratools/gui/assets/tool.gif +0 -0
  24. beratools/gui/assets/tool.png +0 -0
  25. beratools/gui/bt_data.py +485 -0
  26. beratools/gui/bt_gui_main.py +700 -0
  27. beratools/gui/main.py +27 -0
  28. beratools/gui/tool_widgets.py +730 -0
  29. beratools/tools/__init__.py +7 -0
  30. beratools/tools/canopy_threshold_relative.py +769 -0
  31. beratools/tools/centerline.py +127 -0
  32. beratools/tools/check_seed_line.py +48 -0
  33. beratools/tools/common.py +622 -0
  34. beratools/tools/line_footprint_absolute.py +203 -0
  35. beratools/tools/line_footprint_fixed.py +480 -0
  36. beratools/tools/line_footprint_functions.py +884 -0
  37. beratools/tools/line_footprint_relative.py +75 -0
  38. beratools/tools/tool_template.py +72 -0
  39. beratools/tools/vertex_optimization.py +57 -0
  40. beratools-0.1.0.dist-info/METADATA +134 -0
  41. beratools-0.1.0.dist-info/RECORD +44 -0
  42. beratools-0.1.0.dist-info/WHEEL +4 -0
  43. beratools-0.1.0.dist-info/entry_points.txt +2 -0
  44. beratools-0.1.0.dist-info/licenses/LICENSE +22 -0
@@ -0,0 +1,485 @@
1
+ """
2
+ Copyright (C) 2025 Applied Geospatial Research Group.
3
+
4
+ This script is licensed under the GNU General Public License v3.0.
5
+ See <https://gnu.org/licenses/gpl-3.0> for full license details.
6
+
7
+ Author: Richard Zeng
8
+
9
+ Description:
10
+ This script is part of the BERA Tools.
11
+ Webpage: https://github.com/appliedgrg/beratools
12
+
13
+ The purpose of this script is to provide main interface for GUI related settings.
14
+ """
15
+
16
+ import json
17
+ import os
18
+ import platform
19
+ from collections import OrderedDict
20
+ from pathlib import Path
21
+
22
+ import beratools.core.constants as bt_const
23
+
24
+ running_windows = platform.system() == "Windows"
25
+ BT_SHOW_ADVANCED_OPTIONS = False
26
+
27
+
28
+ def default_callback(value):
29
+ """
30
+ Define default callback that outputs using the print function.
31
+
32
+ When tools are called without providing a custom callback, this function
33
+ will be used to print to standard output.
34
+ """
35
+ print(value)
36
+
37
+
38
+ class BTData(object):
39
+ """An object for interfacing with the BERA Tools executable."""
40
+
41
+ def __init__(self):
42
+ if running_windows:
43
+ self.ext = ".exe"
44
+ else:
45
+ self.ext = ""
46
+ self.current_file_path = Path(__file__).resolve().parent
47
+
48
+ self.work_dir = ""
49
+ self.user_folder = Path("")
50
+ self.data_folder = Path("")
51
+ self.verbose = True
52
+ self.show_advanced = BT_SHOW_ADVANCED_OPTIONS
53
+ self.max_procs = -1
54
+ self.recent_tool = None
55
+ self.ascii_art = None
56
+ self.get_working_dir()
57
+ self.get_user_folder()
58
+
59
+ # set maximum available cpu core for tools
60
+ self.max_cpu_cores = os.cpu_count()
61
+
62
+ # load bera tools
63
+ self.tool_history = []
64
+ self.settings = {}
65
+ self.bera_tools = None
66
+ self.tools_list = []
67
+ self.sorted_tools = []
68
+ self.upper_toolboxes = []
69
+ self.lower_toolboxes = []
70
+ self.toolbox_list = []
71
+ self.get_bera_tools()
72
+ self.get_bera_tool_list()
73
+ self.get_bera_toolboxes()
74
+ self.sort_toolboxes()
75
+
76
+ self.setting_file = None
77
+ self.get_data_folder()
78
+ self.get_setting_file()
79
+ self.gui_setting_file = Path(self.current_file_path).joinpath(bt_const.ASSETS_PATH, r"gui.json")
80
+
81
+ self.load_saved_tool_info()
82
+ self.load_gui_data()
83
+ self.get_tool_history()
84
+
85
+ self.default_callback = default_callback
86
+ self.start_minimized = False
87
+
88
+ def set_bera_dir(self, path_str):
89
+ """Set the directory to the BERA Tools executable file."""
90
+ self.current_file_path = path_str
91
+
92
+ def add_tool_history(self, tool, params):
93
+ if "tool_history" not in self.settings:
94
+ self.settings["tool_history"] = OrderedDict()
95
+
96
+ self.settings["tool_history"][tool] = params
97
+ self.settings["tool_history"].move_to_end(tool, last=False)
98
+
99
+ def remove_tool_history_item(self, index):
100
+ key = list(self.settings["tool_history"].keys())[index]
101
+ self.settings["tool_history"].pop(key)
102
+ self.save_tool_info()
103
+
104
+ def remove_tool_history_all(self):
105
+ self.settings.pop("tool_history")
106
+ self.save_tool_info()
107
+
108
+ def save_tool_info(self):
109
+ if self.recent_tool:
110
+ if "gui_parameters" not in self.settings.keys():
111
+ self.settings["gui_parameters"] = {}
112
+
113
+ self.settings["gui_parameters"]["recent_tool"] = self.recent_tool
114
+
115
+ with open(self.setting_file, "w") as file_setting:
116
+ try:
117
+ json.dump(self.settings, file_setting, indent=4)
118
+ except json.decoder.JSONDecodeError:
119
+ pass
120
+
121
+ def save_setting(self, key, value):
122
+ # check setting directory existence
123
+ data_path = Path(self.setting_file).resolve().parent
124
+ if not data_path.exists():
125
+ data_path.mkdir()
126
+
127
+ self.load_saved_tool_info()
128
+
129
+ if value is not None:
130
+ if "gui_parameters" not in self.settings.keys():
131
+ self.settings["gui_parameters"] = {}
132
+
133
+ self.settings["gui_parameters"][key] = value
134
+
135
+ with open(self.setting_file, "w") as write_settings_file:
136
+ json.dump(self.settings, write_settings_file, indent=4)
137
+
138
+ def get_working_dir(self):
139
+ current_file = Path(__file__).resolve()
140
+ btool_dir = current_file.parents[1]
141
+ self.work_dir = btool_dir
142
+
143
+ def get_user_folder(self):
144
+ self.user_folder = Path.home().joinpath(".beratools")
145
+ if not self.user_folder.exists():
146
+ self.user_folder.mkdir()
147
+
148
+ def get_data_folder(self):
149
+ self.data_folder = self.user_folder.joinpath(".data")
150
+ if not self.data_folder.exists():
151
+ self.data_folder.mkdir()
152
+
153
+ def get_logger_file_name(self, name):
154
+ if not name:
155
+ name = "beratools"
156
+
157
+ logger_file_name = self.user_folder.joinpath(name).with_suffix(".log")
158
+ return logger_file_name.as_posix()
159
+
160
+ def get_setting_file(self):
161
+ self.setting_file = self.data_folder.joinpath("saved_tool_parameters.json")
162
+
163
+ def set_max_procs(self, val=-1):
164
+ """Set the maximum cores to use."""
165
+ self.max_procs = val
166
+ self.save_setting("max_procs", val)
167
+
168
+ def get_max_procs(self):
169
+ return self.max_procs
170
+
171
+ def get_max_cpu_cores(self):
172
+ return self.max_cpu_cores
173
+
174
+ def run_tool(self, tool_api, args, callback=None):
175
+ """
176
+ Run a tool and specifies tool arguments.
177
+
178
+ Returns 0 if completes without error.
179
+ Returns 1 if error encountered (details are sent to callback).
180
+ Returns 2 if process is cancelled by user.
181
+ """
182
+ try:
183
+ if callback is None:
184
+ callback = self.default_callback
185
+ except Exception as err:
186
+ callback(str(err))
187
+ return 1
188
+
189
+ # Call script using new process to make GUI responsive
190
+ try:
191
+ # convert to valid json string
192
+ args_string = str(args).replace("'", '"')
193
+ args_string = args_string.replace("True", "true")
194
+ args_string = args_string.replace("False", "false")
195
+
196
+ tool_name = self.get_bera_tool_name(tool_api)
197
+ tool_type = self.get_bera_tool_type(tool_name)
198
+ tool_args = None
199
+
200
+ if tool_type == "python":
201
+ tool_args = [
202
+ self.work_dir.joinpath(f"tools/{tool_api}.py").as_posix(),
203
+ "-i",
204
+ args_string,
205
+ "-p",
206
+ str(self.get_max_procs()),
207
+ "-v",
208
+ str(self.verbose),
209
+ ]
210
+ elif tool_type == "executable":
211
+ print(globals().get(tool_api))
212
+ tool_args = globals()[tool_api](args_string)
213
+ lapis_path = self.work_dir.joinpath("./third_party/Lapis_0_8")
214
+ os.chdir(lapis_path.as_posix())
215
+ except Exception as err:
216
+ callback(str(err))
217
+ return 1
218
+
219
+ return tool_type, tool_args
220
+
221
+ def about(self):
222
+ """Retrieve the description for BERA Tools."""
223
+ try:
224
+ about_text = "BERA Tools provide a series of tools developed by AppliedGRG lab.\n\n"
225
+ about_text += self.ascii_art
226
+ return about_text
227
+ except (OSError, ValueError) as err:
228
+ return err
229
+
230
+ def license(self):
231
+ """Retrieve the license information for BERA Tools."""
232
+ try:
233
+ with open(Path(self.current_file_path).joinpath(r"..\..\LICENSE.txt"), "r") as f:
234
+ ret = f.read()
235
+
236
+ return ret
237
+ except (OSError, ValueError) as err:
238
+ return err
239
+
240
+ def load_saved_tool_info(self):
241
+ data_path = Path(self.setting_file).parent
242
+ if not data_path.exists():
243
+ data_path.mkdir()
244
+
245
+ saved_parameters = {}
246
+ json_file = Path(self.setting_file)
247
+ if not json_file.exists():
248
+ return
249
+
250
+ with open(json_file) as open_file:
251
+ try:
252
+ saved_parameters = json.load(open_file, object_pairs_hook=OrderedDict)
253
+ except json.decoder.JSONDecodeError:
254
+ pass
255
+
256
+ self.settings = saved_parameters
257
+
258
+ # parse file
259
+ if "gui_parameters" in self.settings.keys():
260
+ gui_settings = self.settings["gui_parameters"]
261
+
262
+ if "max_procs" in gui_settings.keys():
263
+ self.max_procs = gui_settings["max_procs"]
264
+
265
+ if "recent_tool" in gui_settings.keys():
266
+ self.recent_tool = gui_settings["recent_tool"]
267
+ if not self.get_bera_tool_api(self.recent_tool):
268
+ self.recent_tool = None
269
+
270
+ def load_gui_data(self):
271
+ gui_settings = {}
272
+ if not self.gui_setting_file.exists():
273
+ print("gui.json not exist.")
274
+ else:
275
+ # read the settings.json file if it exists
276
+ with open(self.gui_setting_file, "r") as file_gui:
277
+ try:
278
+ gui_settings = json.load(file_gui)
279
+ except json.decoder.JSONDecodeError:
280
+ pass
281
+
282
+ # parse file
283
+ if "ascii_art" in gui_settings.keys():
284
+ bera_art = ""
285
+ for line_of_art in gui_settings["ascii_art"]:
286
+ bera_art += line_of_art
287
+ self.ascii_art = bera_art
288
+
289
+ def get_tool_history(self):
290
+ tool_history = []
291
+ self.load_saved_tool_info()
292
+ if self.settings:
293
+ if "tool_history" in self.settings:
294
+ tool_history = self.settings["tool_history"]
295
+
296
+ if tool_history:
297
+ self.tool_history = []
298
+ for item in tool_history:
299
+ item = self.get_bera_tool_name(item)
300
+ self.tool_history.append(item)
301
+
302
+ def get_saved_tool_params(self, tool_api, variable=None):
303
+ self.load_saved_tool_info()
304
+
305
+ if "tool_history" in self.settings:
306
+ if tool_api in list(self.settings["tool_history"]):
307
+ tool_params = self.settings["tool_history"][tool_api]
308
+ if tool_params:
309
+ if variable:
310
+ if variable in tool_params.keys():
311
+ saved_value = tool_params[variable]
312
+ return saved_value
313
+ else: # return all params
314
+ return tool_params
315
+
316
+ return None
317
+
318
+ def get_bera_tools(self):
319
+ tool_json = Path(self.current_file_path).joinpath(bt_const.ASSETS_PATH, r"beratools.json")
320
+ if tool_json.exists():
321
+ tool_json = open(Path(self.current_file_path).joinpath(bt_const.ASSETS_PATH, r"beratools.json"))
322
+ self.bera_tools = json.load(tool_json)
323
+ else:
324
+ print("Tool configuration file not exists")
325
+
326
+ def get_bera_tool_list(self):
327
+ self.tools_list = []
328
+ self.sorted_tools = []
329
+
330
+ for toolbox in self.bera_tools["toolbox"]:
331
+ category = []
332
+ for item in toolbox["tools"]:
333
+ if item["name"]:
334
+ category.append(item["name"])
335
+ self.tools_list.append(item["name"]) # add tool to list
336
+
337
+ self.sorted_tools.append(category)
338
+
339
+ def sort_toolboxes(self):
340
+ for toolbox in self.toolbox_list:
341
+ # Does not contain a sub toolbox, i.e. does not contain '/'
342
+ if toolbox.find("/") == (-1):
343
+ # add to both upper toolbox list and lower toolbox list
344
+ self.upper_toolboxes.append(toolbox)
345
+ self.lower_toolboxes.append(toolbox)
346
+ else: # Contains a sub toolbox
347
+ self.lower_toolboxes.append(toolbox) # add to the lower toolbox list
348
+
349
+ def get_bera_toolboxes(self):
350
+ toolboxes = []
351
+ for toolbox in self.bera_tools["toolbox"]:
352
+ tb = toolbox["category"]
353
+ toolboxes.append(tb)
354
+
355
+ self.toolbox_list = toolboxes
356
+
357
+ def get_bera_tool_params(self, tool_name):
358
+ new_param_whole = {"parameters": []}
359
+ tool = {}
360
+ for toolbox in self.bera_tools["toolbox"]:
361
+ for single_tool in toolbox["tools"]:
362
+ if tool_name == single_tool["name"]:
363
+ tool = single_tool
364
+
365
+ for key, value in tool.items():
366
+ if key != "parameters":
367
+ new_param_whole[key] = value
368
+
369
+ # convert json format for parameters
370
+ if "parameters" not in tool.keys():
371
+ print("issue")
372
+
373
+ for param in tool["parameters"]:
374
+ single_param = {"name": param["label"]}
375
+ if "variable" in param.keys():
376
+ single_param["flag"] = param["variable"]
377
+ # restore saved parameters
378
+ saved_value = self.get_saved_tool_params(tool["tool_api"], param["variable"])
379
+ if saved_value is not None:
380
+ single_param["saved_value"] = saved_value
381
+ else:
382
+ single_param["flag"] = "FIXME"
383
+
384
+ single_param["output"] = param["output"]
385
+ if not param["output"]:
386
+ if param["type"] == "list":
387
+ if tool_name == "Batch Processing":
388
+ single_param["parameter_type"] = {"OptionList": batch_tool_list}
389
+ single_param["data_type"] = "String"
390
+ else:
391
+ single_param["parameter_type"] = {"OptionList": param["data"]}
392
+ single_param["data_type"] = "String"
393
+ if param["subtype"] == "text":
394
+ single_param["data_type"] = "String"
395
+ elif param["subtype"] == "int":
396
+ single_param["data_type"] = "Integer"
397
+ elif param["subtype"] == "float":
398
+ single_param["data_type"] = "Float"
399
+ elif param["subtype"] == "bool":
400
+ single_param["data_type"] = "Boolean"
401
+ elif param["type"] == "text":
402
+ single_param["parameter_type"] = "String"
403
+ elif param["type"] == "number":
404
+ if param["subtype"] == "int":
405
+ single_param["parameter_type"] = "Integer"
406
+ else:
407
+ single_param["parameter_type"] = "Float"
408
+ elif param["type"] == "file":
409
+ single_param["parameter_type"] = {"ExistingFile": [param["subtype"]]}
410
+ else:
411
+ single_param["parameter_type"] = {"ExistingFile": ""}
412
+ else:
413
+ single_param["parameter_type"] = {"NewFile": [param["subtype"]]}
414
+
415
+ single_param["description"] = param["description"]
416
+
417
+ if param["type"] == "raster":
418
+ for i in single_param["parameter_type"].keys():
419
+ single_param["parameter_type"][i] = "Raster"
420
+ elif param["type"] == "lidar":
421
+ for i in single_param["parameter_type"].keys():
422
+ single_param["parameter_type"][i] = "Lidar"
423
+ elif param["type"] == "vector":
424
+ for i in single_param["parameter_type"].keys():
425
+ single_param["parameter_type"][i] = "Vector"
426
+ if "layer" in param.keys():
427
+ layer_value = self.get_saved_tool_params(tool["tool_api"], param["layer"])
428
+ single_param["layer"] = {
429
+ "layer_name": param["layer"],
430
+ "layer_value": layer_value,
431
+ }
432
+
433
+ elif param["type"] == "directory":
434
+ single_param["parameter_type"] = {"directory": [param["subtype"]]}
435
+
436
+ single_param["default_value"] = param["default"]
437
+ if "optional" in param.keys():
438
+ single_param["optional"] = param["optional"]
439
+ else:
440
+ single_param["optional"] = False
441
+
442
+ new_param_whole["parameters"].append(single_param)
443
+
444
+ return new_param_whole
445
+
446
+ def get_bera_tool_parameters_list(self, tool_name):
447
+ params = self.get_bera_tool_params(tool_name)
448
+ param_list = {}
449
+ for item in params["parameters"]:
450
+ param_list[item["flag"]] = item["default_value"]
451
+
452
+ return param_list
453
+
454
+ def get_bera_tool_args(self, tool_name):
455
+ params = self.get_bera_tool_params(tool_name)
456
+ tool_args = params["parameters"]
457
+
458
+ return tool_args
459
+
460
+ def get_bera_tool_name(self, tool_api):
461
+ tool_name = None
462
+ for toolbox in self.bera_tools["toolbox"]:
463
+ for tool in toolbox["tools"]:
464
+ if tool_api == tool["tool_api"]:
465
+ tool_name = tool["name"]
466
+
467
+ return tool_name
468
+
469
+ def get_bera_tool_api(self, tool_name):
470
+ tool_api = None
471
+ for toolbox in self.bera_tools["toolbox"]:
472
+ for tool in toolbox["tools"]:
473
+ if tool_name == tool["name"]:
474
+ tool_api = tool["tool_api"]
475
+
476
+ return tool_api
477
+
478
+ def get_bera_tool_type(self, tool_name):
479
+ tool_type = None
480
+ for toolbox in self.bera_tools["toolbox"]:
481
+ for tool in toolbox["tools"]:
482
+ if tool_name == tool["name"]:
483
+ tool_type = tool["tool_type"]
484
+
485
+ return tool_type