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,700 @@
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 GUI functions.
14
+ """
15
+
16
+ import json
17
+ import os
18
+ import sys
19
+ import webbrowser
20
+ from pathlib import Path
21
+ from re import compile
22
+
23
+ from PyQt5 import QtCore, QtGui, QtWidgets
24
+
25
+ import beratools.core.constants as bt_const
26
+ import beratools.tools.common as bt_common
27
+ from beratools.gui import bt_data
28
+ from beratools.gui.tool_widgets import ToolWidgets
29
+
30
+ # A regular expression, to extract the % complete.
31
+ progress_re = compile("Total complete: (\d+)%")
32
+ bt = bt_data.BTData()
33
+
34
+
35
+ def simple_percent_parser(output):
36
+ """
37
+ Match lines using the progress_re regex.
38
+
39
+ Return a single integer for the % progress.
40
+ """
41
+ m = progress_re.search(output)
42
+ if m:
43
+ pc_complete = m.group(1)
44
+ return int(pc_complete)
45
+
46
+
47
+ class _SearchProxyModel(QtCore.QSortFilterProxyModel):
48
+ def setFilterRegExp(self, pattern):
49
+ if isinstance(pattern, str):
50
+ pattern = QtCore.QRegExp(pattern, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.FixedString)
51
+ super(_SearchProxyModel, self).setFilterRegExp(pattern)
52
+
53
+ def _accept_index(self, idx):
54
+ if idx.isValid():
55
+ text = idx.data(QtCore.Qt.DisplayRole)
56
+ if self.filterRegExp().indexIn(text) >= 0:
57
+ return True
58
+ for row in range(idx.model().rowCount(idx)):
59
+ if self._accept_index(idx.model().index(row, 0, idx)):
60
+ return True
61
+ return False
62
+
63
+ def filterAcceptsRow(self, sourceRow, sourceParent):
64
+ idx = self.sourceModel().index(sourceRow, 0, sourceParent)
65
+ return self._accept_index(idx)
66
+
67
+
68
+ class BTTreeView(QtWidgets.QWidget):
69
+ """Tree view for BERA Tools GUI."""
70
+
71
+ tool_changed = QtCore.pyqtSignal(str) # tool selection changed
72
+
73
+ def __init__(self, parent=None):
74
+ super(BTTreeView, self).__init__(parent)
75
+
76
+ # controls
77
+ self.tool_search = QtWidgets.QLineEdit()
78
+ self.tool_search.setPlaceholderText("Search...")
79
+
80
+ self.tags_model = _SearchProxyModel()
81
+ self.tree_model = QtGui.QStandardItemModel()
82
+ self.tags_model.setSourceModel(self.tree_model)
83
+ # self.tags_model.setDynamicSortFilter(True)
84
+ self.tags_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
85
+
86
+ self.tree_view = QtWidgets.QTreeView()
87
+ self.tree_view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
88
+ self.tree_view.setHeaderHidden(True)
89
+ self.tree_view.setRootIsDecorated(True)
90
+ self.tree_view.setUniformRowHeights(True)
91
+ self.tree_view.setModel(self.tags_model)
92
+
93
+ # layout
94
+ main_layout = QtWidgets.QVBoxLayout()
95
+ main_layout.addWidget(self.tool_search)
96
+ main_layout.addWidget(self.tree_view)
97
+ self.setLayout(main_layout)
98
+
99
+ # signals
100
+ self.tool_search.textChanged.connect(self.search_text_changed)
101
+
102
+ # init
103
+ first_child = self.create_model()
104
+
105
+ self.tree_view.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
106
+ self.tree_view.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
107
+ self.tree_view.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
108
+ self.tree_view.setFirstColumnSpanned(0, self.tree_view.rootIndex(), True)
109
+ self.tree_view.setUniformRowHeights(True)
110
+
111
+ self.tree_model.setHorizontalHeaderLabels(["Tools"])
112
+ self.tree_sel_model = self.tree_view.selectionModel()
113
+ self.tree_sel_model.selectionChanged.connect(self.tree_view_selection_changed)
114
+
115
+ index = None
116
+ # select recent tool
117
+ if bt.recent_tool:
118
+ index = self.get_tool_index(bt.recent_tool)
119
+ else:
120
+ # index_set = self.tree_model.index(0, 0)
121
+ index = self.tree_model.indexFromItem(first_child)
122
+
123
+ self.select_tool_by_index(index)
124
+ self.tree_view.collapsed.connect(self.tree_item_collapsed)
125
+ self.tree_view.expanded.connect(self.tree_item_expanded)
126
+
127
+ def create_model(self):
128
+ first_child = self.add_tool_list_to_tree(bt.toolbox_list, bt.sorted_tools)
129
+
130
+ return first_child
131
+
132
+ def search_text_changed(self, text=None):
133
+ self.tags_model.setFilterRegExp(self.tool_search.text())
134
+
135
+ if len(self.tool_search.text()) >= 1 and self.tags_model.rowCount() > 0:
136
+ self.tree_view.expandAll()
137
+ else:
138
+ self.tree_view.collapseAll()
139
+
140
+ def add_tool_list_to_tree(self, toolbox_list, sorted_tools):
141
+ first_child = None
142
+ for i, toolbox in enumerate(toolbox_list):
143
+ parent = QtGui.QStandardItem(
144
+ QtGui.QIcon(os.path.join(bt_const.ASSETS_PATH, "close.gif")), toolbox
145
+ )
146
+ for j, tool in enumerate(sorted_tools[i]):
147
+ child = QtGui.QStandardItem(QtGui.QIcon(os.path.join(bt_const.ASSETS_PATH, "tool.gif")), tool)
148
+ if i == 0 and j == 0:
149
+ first_child = child
150
+
151
+ parent.appendRow([child])
152
+ self.tree_model.appendRow(parent)
153
+
154
+ return first_child
155
+
156
+ def tree_view_selection_changed(self, new, old):
157
+ if len(new.indexes()) == 0:
158
+ return
159
+
160
+ selected = new.indexes()[0]
161
+ source_index = self.tags_model.mapToSource(selected)
162
+ item = self.tree_model.itemFromIndex(source_index)
163
+ parent = item.parent()
164
+ if not parent:
165
+ return
166
+
167
+ tool = item.text()
168
+ self.tool_changed.emit(tool)
169
+
170
+ def tree_item_expanded(self, index):
171
+ source_index = self.tags_model.mapToSource(index)
172
+ item = self.tree_model.itemFromIndex(source_index)
173
+ if not item:
174
+ return
175
+
176
+ if item.hasChildren():
177
+ item.setIcon(QtGui.QIcon(os.path.join(bt_const.ASSETS_PATH, "open.gif")))
178
+
179
+ def tree_item_collapsed(self, index):
180
+ source_index = self.tags_model.mapToSource(index)
181
+ item = self.tree_model.itemFromIndex(source_index)
182
+ if not item:
183
+ return
184
+
185
+ if item.hasChildren():
186
+ item.setIcon(QtGui.QIcon(os.path.join(bt_const.ASSETS_PATH, "close.gif")))
187
+
188
+ def get_tool_index(self, tool_name):
189
+ item = self.tree_model.findItems(tool_name, QtCore.Qt.MatchExactly | QtCore.Qt.MatchRecursive)
190
+ if len(item) > 0:
191
+ item = item[0]
192
+
193
+ index = self.tree_model.indexFromItem(item)
194
+ return index
195
+
196
+ def select_tool_by_index(self, index):
197
+ proxy_index = self.tags_model.mapFromSource(index)
198
+ self.tree_sel_model.select(proxy_index, QtCore.QItemSelectionModel.ClearAndSelect)
199
+ self.tree_view.expand(proxy_index.parent())
200
+ self.tree_sel_model.setCurrentIndex(proxy_index, QtCore.QItemSelectionModel.Current)
201
+
202
+ def select_tool_by_name(self, name):
203
+ index = self.get_tool_index(name)
204
+ self.select_tool_by_index(index)
205
+
206
+
207
+ class ClickSlider(QtWidgets.QSlider):
208
+ """Custom slider for BERA Tools GUI."""
209
+
210
+ def mousePressEvent(self, event):
211
+ super(ClickSlider, self).mousePressEvent(event)
212
+ if event.button() == QtCore.Qt.LeftButton:
213
+ val = self.pixel_pos_to_range_value(event.pos())
214
+ self.setValue(val)
215
+ self.sliderMoved.emit(val)
216
+
217
+ def pixel_pos_to_range_value(self, pos):
218
+ opt = QtWidgets.QStyleOptionSlider()
219
+ self.initStyleOption(opt)
220
+ gr = self.style().subControlRect(
221
+ QtWidgets.QStyle.CC_Slider, opt, QtWidgets.QStyle.SC_SliderGroove, self
222
+ )
223
+ sr = self.style().subControlRect(
224
+ QtWidgets.QStyle.CC_Slider, opt, QtWidgets.QStyle.SC_SliderHandle, self
225
+ )
226
+
227
+ if self.orientation() == QtCore.Qt.Horizontal:
228
+ slider_length = sr.width()
229
+ slider_min = gr.x()
230
+ slider_max = gr.right() - slider_length + 1
231
+ else:
232
+ slider_length = sr.height()
233
+ slider_min = gr.y()
234
+ slider_max = gr.bottom() - slider_length + 1
235
+ pr = pos - sr.center() + sr.topLeft()
236
+ p = pr.x() if self.orientation() == QtCore.Qt.Horizontal else pr.y()
237
+ return QtWidgets.QStyle.sliderValueFromPosition(
238
+ self.minimum(),
239
+ self.maximum(),
240
+ p - slider_min,
241
+ slider_max - slider_min,
242
+ opt.upsideDown,
243
+ )
244
+
245
+
246
+ class BTSlider(QtWidgets.QWidget):
247
+ """Slider for BERA Tools GUI."""
248
+
249
+ def __init__(self, current, maximum, parent=None):
250
+ super(BTSlider, self).__init__(parent)
251
+
252
+ self.value = current
253
+ self.slider = ClickSlider(QtCore.Qt.Horizontal)
254
+ self.slider.setFixedWidth(120)
255
+ self.slider.setTickInterval(2)
256
+ self.slider.setTickPosition(QtWidgets.QSlider.TicksAbove)
257
+ self.slider.setRange(1, maximum)
258
+ self.slider.setValue(current)
259
+ self.label = QtWidgets.QLabel(self.generate_label_text(current))
260
+
261
+ layout = QtWidgets.QHBoxLayout()
262
+ layout.addWidget(self.label)
263
+ layout.addWidget(self.slider)
264
+ self.setLayout(layout)
265
+
266
+ self.slider.sliderMoved.connect(self.slider_moved)
267
+
268
+ def slider_moved(self, value):
269
+ bt.set_max_procs(value)
270
+ QtWidgets.QToolTip.showText(QtGui.QCursor.pos(), f"{value}")
271
+ self.label.setText(self.generate_label_text())
272
+
273
+ def generate_label_text(self, value=None):
274
+ if not value:
275
+ value = self.slider.value()
276
+
277
+ return f"Use CPU Cores: {value:3d}"
278
+
279
+
280
+ class BTListView(QtWidgets.QWidget):
281
+ """List view for BERA Tools GUI."""
282
+
283
+ tool_changed = QtCore.pyqtSignal(str)
284
+
285
+ def __init__(self, data_list=None, parent=None):
286
+ super(BTListView, self).__init__(parent)
287
+
288
+ delete_icon = QtWidgets.QStyle.SP_DialogCloseButton
289
+ delete_icon = self.style().standardIcon(delete_icon)
290
+ clear_icon = QtWidgets.QStyle.SP_DialogResetButton
291
+ clear_icon = self.style().standardIcon(clear_icon)
292
+ btn_delete = QtWidgets.QPushButton()
293
+ btn_clear = QtWidgets.QPushButton()
294
+ btn_delete.setIcon(delete_icon)
295
+ btn_clear.setIcon(clear_icon)
296
+ btn_delete.setToolTip("Delete selected tool history")
297
+ btn_clear.setToolTip("clear all tool history")
298
+ btn_delete.setFixedWidth(40)
299
+ btn_clear.setFixedWidth(40)
300
+
301
+ layout_h = QtWidgets.QHBoxLayout()
302
+ layout_h.addWidget(btn_delete)
303
+ layout_h.addWidget(btn_clear)
304
+ layout_h.addStretch(1)
305
+
306
+ self.list_view = QtWidgets.QListView()
307
+ self.list_view.setFlow(QtWidgets.QListView.TopToBottom)
308
+ self.list_view.setBatchSize(5)
309
+
310
+ self.list_model = QtCore.QStringListModel() # model
311
+ if data_list:
312
+ self.list_model.setStringList(data_list)
313
+
314
+ self.list_view.setModel(self.list_model) # set model
315
+ self.sel_model = self.list_view.selectionModel()
316
+
317
+ self.list_view.setLayoutMode(QtWidgets.QListView.SinglePass)
318
+ btn_delete.clicked.connect(self.delete_selected_item)
319
+ btn_clear.clicked.connect(self.clear_all_items)
320
+ self.sel_model.selectionChanged.connect(self.selection_changed)
321
+
322
+ layout = QtWidgets.QVBoxLayout()
323
+ layout.addLayout(layout_h)
324
+ layout.addWidget(self.list_view)
325
+ self.setLayout(layout)
326
+
327
+ def selection_changed(self, new, old):
328
+ indexes = new.indexes()
329
+ if len(indexes) == 0:
330
+ return
331
+
332
+ selection = new.indexes()[0]
333
+ tool = self.list_model.itemData(selection)[0]
334
+ self.tool_changed.emit(tool)
335
+
336
+ def set_data_list(self, data_list):
337
+ self.list_model.setStringList(data_list)
338
+
339
+ def delete_selected_item(self):
340
+ selection = self.sel_model.currentIndex()
341
+ self.list_model.removeRow(selection.row())
342
+ bt.remove_tool_history_item(selection.row())
343
+
344
+ def clear_all_items(self):
345
+ self.list_model.setStringList([])
346
+ bt.remove_tool_history_all()
347
+
348
+
349
+ class MainWindow(QtWidgets.QMainWindow):
350
+ """Main window for BERA Tools GUI."""
351
+
352
+ def __init__(self):
353
+ super().__init__()
354
+
355
+ self.script_dir = os.path.dirname(os.path.realpath(__file__))
356
+ self.title = "BERA Tools"
357
+ self.setWindowTitle(self.title)
358
+ self.working_dir = bt.work_dir
359
+ self.tool_api = None
360
+ self.tool_name = "Centerline"
361
+ self.recent_tool = bt.recent_tool
362
+ if self.recent_tool:
363
+ self.tool_name = self.recent_tool
364
+ self.tool_api = bt.get_bera_tool_api(self.tool_name)
365
+
366
+ self.update_procs(bt.get_max_cpu_cores())
367
+
368
+ # QProcess run tools
369
+ self.process = None
370
+ self.cancel_op = False
371
+
372
+ # BERA tool list
373
+ self.bera_tools = bt.bera_tools
374
+ self.tools_list = bt.tools_list
375
+ self.sorted_tools = bt.sorted_tools
376
+ self.toolbox_list = bt.toolbox_list
377
+ self.upper_toolboxes = bt.upper_toolboxes
378
+ self.lower_toolboxes = bt.lower_toolboxes
379
+
380
+ self.current_file_path = Path(__file__).resolve().parent
381
+ bt.set_bera_dir(self.current_file_path)
382
+
383
+ # Tree view
384
+ self.tree_view = BTTreeView()
385
+ self.tree_view.tool_changed.connect(self.set_tool)
386
+
387
+ # group box for tree view
388
+ tree_box = QtWidgets.QGroupBox()
389
+ tree_box.setTitle("Tools available")
390
+ tree_layout = QtWidgets.QVBoxLayout()
391
+ tree_layout.addWidget(self.tree_view)
392
+ tree_box.setLayout(tree_layout)
393
+
394
+ # QListWidget
395
+ self.tool_history = BTListView()
396
+ self.tool_history.set_data_list(bt.tool_history)
397
+ self.tool_history.tool_changed.connect(self.set_tool)
398
+
399
+ # group box
400
+ tool_history_box = QtWidgets.QGroupBox()
401
+ tool_history_layout = QtWidgets.QVBoxLayout()
402
+ tool_history_layout.addWidget(self.tool_history)
403
+ tool_history_box.setTitle("Tool history")
404
+ tool_history_box.setLayout(tool_history_layout)
405
+
406
+ # left layout
407
+ page_layout = QtWidgets.QHBoxLayout()
408
+ self.left_layout = QtWidgets.QVBoxLayout()
409
+ self.right_layout = QtWidgets.QVBoxLayout()
410
+
411
+ self.left_layout.addWidget(tree_box)
412
+ self.left_layout.addWidget(tool_history_box)
413
+
414
+ # top buttons
415
+ label = QtWidgets.QLabel(f"{self.tool_name}")
416
+ label.setFont(QtGui.QFont("Consolas", 14))
417
+ self.btn_advanced = QtWidgets.QPushButton("Show Advanced Options")
418
+ self.btn_advanced.setFixedWidth(180)
419
+ btn_help = QtWidgets.QPushButton("help")
420
+ btn_code = QtWidgets.QPushButton("Code")
421
+ btn_help.setFixedWidth(250)
422
+ btn_code.setFixedWidth(100)
423
+
424
+ self.btn_layout_top = QtWidgets.QHBoxLayout()
425
+ self.btn_layout_top.setAlignment(QtCore.Qt.AlignRight)
426
+ self.btn_layout_top.addWidget(label)
427
+ self.btn_layout_top.addStretch(1)
428
+ self.btn_layout_top.addWidget(self.btn_advanced)
429
+ self.btn_layout_top.addWidget(btn_code)
430
+
431
+ # ToolWidgets
432
+ tool_args = bt.get_bera_tool_args(self.tool_name)
433
+ self.tool_widget = ToolWidgets(self.recent_tool, tool_args, bt.show_advanced)
434
+
435
+ # bottom buttons
436
+ slider = BTSlider(bt.max_procs, bt.max_cpu_cores)
437
+ btn_default_args = QtWidgets.QPushButton("Load Default Arguments")
438
+ self.btn_run = QtWidgets.QPushButton("Run")
439
+ btn_cancel = QtWidgets.QPushButton("Cancel")
440
+ btn_default_args.setFixedWidth(150)
441
+ slider.setFixedWidth(250)
442
+ self.btn_run.setFixedWidth(120)
443
+ btn_cancel.setFixedWidth(120)
444
+
445
+ btn_layout_bottom = QtWidgets.QHBoxLayout()
446
+ btn_layout_bottom.setAlignment(QtCore.Qt.AlignRight)
447
+ btn_layout_bottom.addStretch(1)
448
+ btn_layout_bottom.addWidget(btn_default_args)
449
+ btn_layout_bottom.addWidget(slider)
450
+ btn_layout_bottom.addWidget(self.btn_run)
451
+ btn_layout_bottom.addWidget(btn_cancel)
452
+
453
+ self.top_right_layout = QtWidgets.QVBoxLayout()
454
+ self.top_right_layout.addLayout(self.btn_layout_top)
455
+ self.top_right_layout.addWidget(self.tool_widget)
456
+ self.top_right_layout.addLayout(btn_layout_bottom)
457
+ tool_widget_grp = QtWidgets.QGroupBox("Tool")
458
+ tool_widget_grp.setLayout(self.top_right_layout)
459
+
460
+ # Text widget
461
+ self.text_edit = QtWidgets.QPlainTextEdit()
462
+ self.text_edit.setFont(QtGui.QFont("Consolas", 9))
463
+ self.text_edit.setReadOnly(True)
464
+ self.print_about()
465
+
466
+ # progress bar
467
+ self.progress_label = QtWidgets.QLabel()
468
+ self.progress_bar = QtWidgets.QProgressBar(self)
469
+ self.progress_var = 0
470
+
471
+ # progress layout
472
+ progress_layout = QtWidgets.QHBoxLayout()
473
+ progress_layout.addWidget(self.progress_label)
474
+ progress_layout.addWidget(self.progress_bar)
475
+
476
+ self.right_layout.addWidget(tool_widget_grp)
477
+ self.right_layout.addWidget(self.text_edit)
478
+ self.right_layout.addLayout(progress_layout)
479
+
480
+ # main layouts
481
+ page_layout.addLayout(self.left_layout, 3)
482
+ page_layout.addLayout(self.right_layout, 7)
483
+
484
+ # signals and slots
485
+ self.btn_advanced.clicked.connect(self.show_advanced)
486
+ btn_help.clicked.connect(self.show_help)
487
+ btn_code.clicked.connect(self.view_code)
488
+ btn_default_args.clicked.connect(self.load_default_args)
489
+ self.btn_run.clicked.connect(self.start_process)
490
+ btn_cancel.clicked.connect(self.stop_process)
491
+
492
+ widget = QtWidgets.QWidget(self)
493
+ widget.setLayout(page_layout)
494
+ self.setCentralWidget(widget)
495
+
496
+ def set_tool(self, tool=None):
497
+ if tool:
498
+ self.tool_name = tool
499
+
500
+ # let tree view select the tool
501
+ self.tree_view.select_tool_by_name(self.tool_name)
502
+ self.tool_api = bt.get_bera_tool_api(self.tool_name)
503
+ tool_args = bt.get_bera_tool_args(self.tool_name)
504
+
505
+ # update tool label
506
+ self.btn_layout_top.itemAt(0).widget().setText(self.tool_name)
507
+
508
+ # update tool widget
509
+ self.tool_widget = ToolWidgets(self.tool_name, tool_args, bt.show_advanced)
510
+ widget = self.top_right_layout.itemAt(1).widget()
511
+ self.top_right_layout.removeWidget(widget)
512
+ self.top_right_layout.insertWidget(1, self.tool_widget)
513
+ self.top_right_layout.update()
514
+
515
+ def save_tool_parameter(self):
516
+ # Retrieve tool parameters from GUI
517
+ args = self.tool_widget.get_widgets_arguments()
518
+ # bt.load_saved_tool_info()
519
+ bt.add_tool_history(self.tool_api, args)
520
+ bt.save_tool_info()
521
+
522
+ # update tool history list
523
+ bt.get_tool_history()
524
+ self.tool_history.set_data_list(bt.tool_history)
525
+
526
+ def get_current_tool_parameters(self):
527
+ self.tool_api = bt.get_bera_tool_api(self.tool_name)
528
+ return bt.get_bera_tool_params(self.tool_name)
529
+
530
+ def show_help(self):
531
+ # open the user manual section for the current tool
532
+ webbrowser.open_new_tab(self.get_current_tool_parameters()["tech_link"])
533
+
534
+ def print_about(self):
535
+ self.text_edit.clear()
536
+ self.print_to_output(bt.about())
537
+
538
+ def print_license(self):
539
+ self.text_edit.clear()
540
+ self.print_to_output(bt.license())
541
+
542
+ def update_procs(self, value):
543
+ max_procs = int(value)
544
+ bt.set_max_procs(max_procs)
545
+
546
+ def print_to_output(self, text):
547
+ self.text_edit.moveCursor(QtGui.QTextCursor.End)
548
+ self.text_edit.insertPlainText(text)
549
+ self.text_edit.moveCursor(QtGui.QTextCursor.End)
550
+
551
+ def print_line_to_output(self, text, tag=None):
552
+ self.text_edit.moveCursor(QtGui.QTextCursor.End)
553
+ self.text_edit.insertPlainText(text + "\n")
554
+ self.text_edit.moveCursor(QtGui.QTextCursor.End)
555
+
556
+ def show_advanced(self):
557
+ if bt.show_advanced:
558
+ bt.show_advanced = False
559
+ self.btn_advanced.setText("Show Advanced Options")
560
+ else:
561
+ bt.show_advanced = True
562
+ self.btn_advanced.setText("Hide Advanced Options")
563
+
564
+ self.set_tool()
565
+
566
+ def view_code(self):
567
+ webbrowser.open_new_tab(self.get_current_tool_parameters()["tech_link"])
568
+
569
+ def custom_callback(self, value):
570
+ """Define custom callback that deals with tool output."""
571
+ value = str(value)
572
+ value.strip()
573
+ if value != "":
574
+ # remove esc string which origin is unknown
575
+ rm_str = "\x1b[0m"
576
+ if rm_str in value:
577
+ value = value.replace(rm_str, "")
578
+
579
+ if "%" in value:
580
+ try:
581
+ str_progress = bt_common.extract_string_from_printout(value, "%")
582
+
583
+ # remove progress string
584
+ value = value.replace(str_progress, "").strip()
585
+ progress = float(str_progress.replace("%", "").strip())
586
+ self.progress_bar.setValue(int(progress))
587
+ except ValueError as e:
588
+ print("custom_callback: Problem parsing data into number: ", e)
589
+ except Exception as e:
590
+ print(e)
591
+ elif "PROGRESS_LABEL" in value:
592
+ str_label = bt_common.extract_string_from_printout(value, "PROGRESS_LABEL")
593
+ value = value.replace(str_label, "").strip() # remove progress string
594
+ value = value.replace('"', "")
595
+ str_label = str_label.replace("PROGRESS_LABEL", "").strip()
596
+ self.progress_label.setText(str_label)
597
+
598
+ if value:
599
+ self.print_line_to_output(value)
600
+
601
+ def message(self, s):
602
+ self.text_edit.appendPlainText(s)
603
+
604
+ def load_default_args(self):
605
+ self.tool_widget.load_default_args()
606
+
607
+ def start_process(self):
608
+ args = self.tool_widget.get_widgets_arguments()
609
+ if not args:
610
+ print("Please check the parameters.")
611
+ return
612
+
613
+ self.print_line_to_output("")
614
+ self.print_line_to_output(f"Starting tool {self.tool_name} ... \n")
615
+ self.print_line_to_output("-----------------------------")
616
+ self.print_line_to_output("Tool arguments:")
617
+ self.print_line_to_output(json.dumps(args, indent=4))
618
+ self.print_line_to_output("")
619
+
620
+ bt.recent_tool = self.tool_name
621
+ self.save_tool_parameter()
622
+
623
+ # Run the tool and check the return value for an error
624
+ for key in args.keys():
625
+ if type(args[key]) is not str:
626
+ args[key] = str(args[key])
627
+
628
+ tool_type, tool_args = bt.run_tool(self.tool_api, args, self.custom_callback)
629
+
630
+ if self.process is None: # No process running.
631
+ self.print_line_to_output(f"Tool {self.tool_name} started")
632
+ self.print_line_to_output("-----------------------")
633
+ self.process = QtCore.QProcess() # Keep a reference to the QProcess
634
+ self.process.readyReadStandardOutput.connect(self.handle_stdout)
635
+ self.process.readyReadStandardError.connect(self.handle_stderr)
636
+ self.process.stateChanged.connect(self.handle_state)
637
+
638
+ # Clean up once complete.
639
+ self.process.finished.connect(self.process_finished)
640
+ self.process.start(tool_type, tool_args)
641
+
642
+ while self.process is not None:
643
+ sys.stdout.flush()
644
+ if self.cancel_op:
645
+ self.cancel_op = False
646
+ self.process.terminate()
647
+ else:
648
+ break
649
+
650
+ def stop_process(self):
651
+ self.cancel_op = True
652
+ if self.process:
653
+ self.print_line_to_output(f"Tool {self.tool_name} terminating ...")
654
+ self.process.kill()
655
+
656
+ def handle_stderr(self):
657
+ data = self.process.readAllStandardError()
658
+ stderr = bytes(data).decode("utf8")
659
+
660
+ # Extract progress if it is in the data.
661
+ progress = simple_percent_parser(stderr)
662
+ if progress:
663
+ self.progress_bar.setValue(progress)
664
+ self.message(stderr)
665
+
666
+ def handle_stdout(self):
667
+ line = self.process.readLine()
668
+ line = bytes(line).decode("utf8")
669
+
670
+ # process line output
671
+ self.custom_callback(line)
672
+ sys.stdout.flush()
673
+
674
+ def handle_state(self, state):
675
+ states = {
676
+ QtCore.QProcess.NotRunning: "Not running",
677
+ QtCore.QProcess.Starting: "Starting",
678
+ QtCore.QProcess.Running: "Running",
679
+ }
680
+ state_name = states[state]
681
+ if state_name == "Not running":
682
+ self.btn_run.setEnabled(True)
683
+ if self.cancel_op:
684
+ self.message("Tool operation canceled")
685
+ elif state_name == "Starting":
686
+ self.btn_run.setEnabled(False)
687
+
688
+ def process_finished(self):
689
+ self.message("Process finished.")
690
+ self.process = None
691
+ self.progress_bar.setValue(0)
692
+ self.progress_label.setText("")
693
+
694
+
695
+ def runner():
696
+ app = QtWidgets.QApplication(sys.argv)
697
+ window = MainWindow()
698
+ window.setMinimumSize(1024, 768)
699
+ window.show()
700
+ app.exec()