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