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.
- beratools/__init__.py +9 -0
- beratools/core/__init__.py +0 -0
- beratools/core/algo_centerline.py +351 -0
- beratools/core/constants.py +86 -0
- beratools/core/dijkstra_algorithm.py +460 -0
- beratools/core/logger.py +85 -0
- beratools/core/tool_base.py +133 -0
- beratools/gui/__init__.py +15 -0
- beratools/gui/batch_processing_dlg.py +463 -0
- beratools/gui/beratools.json +2300 -0
- beratools/gui/bt_data.py +487 -0
- beratools/gui/bt_gui_main.py +691 -0
- beratools/gui/cli.py +18 -0
- beratools/gui/gui.json +8 -0
- beratools/gui/img/BERALogo.png +0 -0
- beratools/gui/img/closed.gif +0 -0
- beratools/gui/img/closed.png +0 -0
- beratools/gui/img/open.gif +0 -0
- beratools/gui/img/open.png +0 -0
- beratools/gui/img/tool.gif +0 -0
- beratools/gui/img/tool.png +0 -0
- beratools/gui/map_window.py +146 -0
- beratools/gui/tool_widgets.py +493 -0
- beratools/gui_tk/ASCII Banners.txt +248 -0
- beratools/gui_tk/__init__.py +20 -0
- beratools/gui_tk/beratools_main.py +515 -0
- beratools/gui_tk/bt_widgets.py +442 -0
- beratools/gui_tk/cli.py +18 -0
- beratools/gui_tk/gui.json +8 -0
- 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 +14 -0
- beratools/gui_tk/map_window.py +144 -0
- beratools/gui_tk/runner.py +1481 -0
- beratools/gui_tk/tooltip.py +55 -0
- beratools/third_party/pyqtlet2/__init__.py +9 -0
- beratools/third_party/pyqtlet2/leaflet/__init__.py +26 -0
- beratools/third_party/pyqtlet2/leaflet/control/__init__.py +6 -0
- beratools/third_party/pyqtlet2/leaflet/control/control.py +59 -0
- beratools/third_party/pyqtlet2/leaflet/control/draw.py +52 -0
- beratools/third_party/pyqtlet2/leaflet/control/layers.py +20 -0
- beratools/third_party/pyqtlet2/leaflet/core/Parser.py +24 -0
- beratools/third_party/pyqtlet2/leaflet/core/__init__.py +2 -0
- beratools/third_party/pyqtlet2/leaflet/core/evented.py +180 -0
- beratools/third_party/pyqtlet2/leaflet/layer/__init__.py +5 -0
- beratools/third_party/pyqtlet2/leaflet/layer/featuregroup.py +34 -0
- beratools/third_party/pyqtlet2/leaflet/layer/icon/__init__.py +1 -0
- beratools/third_party/pyqtlet2/leaflet/layer/icon/icon.py +30 -0
- beratools/third_party/pyqtlet2/leaflet/layer/imageoverlay.py +18 -0
- beratools/third_party/pyqtlet2/leaflet/layer/layer.py +105 -0
- beratools/third_party/pyqtlet2/leaflet/layer/layergroup.py +45 -0
- beratools/third_party/pyqtlet2/leaflet/layer/marker/__init__.py +1 -0
- beratools/third_party/pyqtlet2/leaflet/layer/marker/marker.py +91 -0
- beratools/third_party/pyqtlet2/leaflet/layer/tile/__init__.py +2 -0
- beratools/third_party/pyqtlet2/leaflet/layer/tile/gridlayer.py +4 -0
- beratools/third_party/pyqtlet2/leaflet/layer/tile/tilelayer.py +16 -0
- beratools/third_party/pyqtlet2/leaflet/layer/vector/__init__.py +5 -0
- beratools/third_party/pyqtlet2/leaflet/layer/vector/circle.py +15 -0
- beratools/third_party/pyqtlet2/leaflet/layer/vector/circlemarker.py +18 -0
- beratools/third_party/pyqtlet2/leaflet/layer/vector/path.py +5 -0
- beratools/third_party/pyqtlet2/leaflet/layer/vector/polygon.py +14 -0
- beratools/third_party/pyqtlet2/leaflet/layer/vector/polyline.py +18 -0
- beratools/third_party/pyqtlet2/leaflet/layer/vector/rectangle.py +14 -0
- beratools/third_party/pyqtlet2/leaflet/map/__init__.py +1 -0
- beratools/third_party/pyqtlet2/leaflet/map/map.py +220 -0
- beratools/third_party/pyqtlet2/mapwidget.py +45 -0
- beratools/third_party/pyqtlet2/web/custom.js +43 -0
- beratools/third_party/pyqtlet2/web/map.html +23 -0
- 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 +656 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_193/leaflet.js +6 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.codeclimate.yml +14 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.editorconfig +4 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.gitattributes +22 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/.travis.yml +43 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/LICENSE +20 -0
- 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 +156 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.css +10 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_draw_414/leaflet.draw.js +10 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/LICENSE +22 -0
- beratools/third_party/pyqtlet2/web/modules/leaflet_rotatedMarker_020/leaflet.rotatedMarker.js +57 -0
- beratools/tools/Beratools_r_script.r +1120 -0
- beratools/tools/Ht_metrics.py +116 -0
- beratools/tools/__init__.py +7 -0
- beratools/tools/batch_processing.py +132 -0
- beratools/tools/canopy_threshold_relative.py +670 -0
- beratools/tools/canopycostraster.py +222 -0
- beratools/tools/centerline.py +176 -0
- beratools/tools/common.py +885 -0
- beratools/tools/fl_regen_csf.py +428 -0
- beratools/tools/forest_line_attributes.py +408 -0
- beratools/tools/forest_line_ecosite.py +216 -0
- beratools/tools/lapis_all.py +103 -0
- beratools/tools/least_cost_path_from_chm.py +152 -0
- beratools/tools/line_footprint_absolute.py +363 -0
- beratools/tools/line_footprint_fixed.py +282 -0
- beratools/tools/line_footprint_functions.py +720 -0
- beratools/tools/line_footprint_relative.py +64 -0
- beratools/tools/ln_relative_metrics.py +615 -0
- beratools/tools/r_cal_lpi_elai.r +25 -0
- beratools/tools/r_generate_pd_focalraster.r +101 -0
- beratools/tools/r_interface.py +80 -0
- beratools/tools/r_point_density.r +9 -0
- beratools/tools/rpy_chm2trees.py +86 -0
- beratools/tools/rpy_dsm_chm_by.py +81 -0
- beratools/tools/rpy_dtm_by.py +63 -0
- beratools/tools/rpy_find_cellsize.py +43 -0
- beratools/tools/rpy_gnd_csf.py +74 -0
- beratools/tools/rpy_hummock_hollow.py +85 -0
- beratools/tools/rpy_hummock_hollow_raster.py +71 -0
- beratools/tools/rpy_las_info.py +51 -0
- beratools/tools/rpy_laz2las.py +40 -0
- beratools/tools/rpy_lpi_elai_lascat.py +466 -0
- beratools/tools/rpy_normalized_lidar_by.py +56 -0
- beratools/tools/rpy_percent_above_dbh.py +80 -0
- beratools/tools/rpy_points2trees.py +88 -0
- beratools/tools/rpy_vegcoverage.py +94 -0
- beratools/tools/tiler.py +206 -0
- beratools/tools/tool_template.py +54 -0
- beratools/tools/vertex_optimization.py +620 -0
- beratools/tools/zonal_threshold.py +144 -0
- beratools-0.2.0.dist-info/METADATA +63 -0
- beratools-0.2.0.dist-info/RECORD +142 -0
- beratools-0.2.0.dist-info/WHEEL +4 -0
- beratools-0.2.0.dist-info/entry_points.txt +2 -0
- 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
|