-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.py
254 lines (201 loc) · 9.36 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
import os
import sys
import droplets
from PyQt5 import QtWidgets
os.environ["PATH"] = ''
os.environ["OUT"] = ''
class SettingsMenu(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.title = 'Detect Droplets - Settings'
self.grid = QtWidgets.QGridLayout()
self.calibBox = QtWidgets.QLineEdit(self) # For calibration value
self.curRow = 1 # Keeps track of current row for adding rows
self.headerNames = ['Min Radius (pix)', 'Max Radius (pix)', 'Canny Edge Threshold', 'Accumulator Threshold']
self.settingsValues = [] # Used to access typed values
self.delButtons = [] # Stores delete row buttons
self.rowIndices = {} # Keep track of row indices of grid:settingsValues/delButtons
self.initUI()
def addRow(self):
self.curRow += 1
row = self.curRow
settingsRow = []
positions = [(self.curRow, i) for i in range(len(self.headerNames))]
# Add text boxes for new row
for position in positions:
textBox = QtWidgets.QLineEdit(self)
settingsRow.append(textBox) # Build the row of text boxes
self.grid.addWidget(textBox, *position)
# Add delete row button
delRowButton = QtWidgets.QPushButton('Delete row')
delRowButton.clicked.connect(lambda: self.delRow(row))
self.delButtons.append(delRowButton)
self.grid.addWidget(delRowButton, self.curRow, len(self.headerNames))
# Set new row indices
if len(self.rowIndices) > 0:
self.rowIndices[row] = self.rowIndices[max(self.rowIndices)] + 1
else:
self.rowIndices[row] = row
self.settingsValues.append(settingsRow) # Add new row of text boxes to settingsValues
def delRow(self, row):
# Hide delete button
self.delButtons[self.rowIndices[row] - 2].hide()
self.delButtons.pop(self.rowIndices[row] - 2)
# Hide text fields
for box in self.settingsValues.pop(self.rowIndices[row] - 2):
box.hide()
# Adjust indices
curRowFlag = True # Checks if last row was deleted
self.rowIndices.pop(row) # Remove entry of deleted row
for index in self.rowIndices:
if index > row:
curRowFlag = False
self.rowIndices[index] += -1 # Shift greater indices back 1 due to deletion
# Adjust current grid row for grid layout
if len(self.rowIndices) > 0:
# Current grid row is set to next greatest grid row when the last row is deleted
self.curRow = max(self.rowIndices) if curRowFlag else self.curRow
else:
self.curRow = 1 # When all rows are deleted
def runAction(self):
scriptSettings = [] # Stores settings entered by user
errorFlag = False
# First check that all settings fields are valid, and build settings list
try:
if float(self.calibBox.text()) <= 0:
raise ValueError('Negative or 0 value detected.')
except ValueError:
errorFlag = True
QtWidgets.QMessageBox.about(self, 'Error', 'Invalid calibration value detected.')
if len(self.settingsValues) == 0:
errorFlag = True
QtWidgets.QMessageBox.about(self, 'Error', 'No settings detected.')
for row in self.settingsValues:
rowSettings = [] # Store settings from each column on the row
if errorFlag is True:
break
for textBox in row:
try:
val = int(textBox.text())
if val <= 0:
raise ValueError('Negative or 0 value detected.')
rowSettings.append(val)
except ValueError:
errorFlag = True
QtWidgets.QMessageBox.about(self, 'Error', 'Invalid settings value detected.')
break
scriptSettings.append(rowSettings) # Store settings from the entire row
# Run script if no errors detected and paths are selected
if errorFlag is True:
pass
elif os.environ["PATH"] != '' and os.environ["OUT"] != '':
droplets.main(scriptSettings, float(self.calibBox.text()))
QtWidgets.QMessageBox.about(self, "Detect Droplets", "Processing done. Output is at {}".format(os.environ["OUT"]))
else:
QtWidgets.QMessageBox.about(self, 'Error', 'Missing selected directory.')
def initUI(self):
self.setLayout(self.grid)
positions = [(self.curRow, i) for i in range(len(self.headerNames))]
addRowButton = QtWidgets.QPushButton('Add Row')
addRowButton.clicked.connect(self.addRow)
self.grid.addWidget(addRowButton, 0, 0)
calibLabel = QtWidgets.QLabel('Calibration (microns/pix):')
self.grid.addWidget(calibLabel, 0, 1)
self.grid.addWidget(self.calibBox, 0, 2)
runButton = QtWidgets.QPushButton('Run')
runButton.clicked.connect(self.runAction)
self.grid.addWidget(runButton, 0, len(self.headerNames) - 1)
# Create header row
for position, name in zip(positions, self.headerNames):
label = QtWidgets.QLabel(name, self)
self.grid.addWidget(label, *position)
self.addRow()
self.setWindowTitle(self.title)
class SettingsButton(QtWidgets.QPushButton):
def __init__(self, title, parent):
super().__init__(title, parent)
self.dialog = SettingsMenu()
self.clicked.connect(self.onClick)
def onClick(self):
self.dialog.show()
class PathWidgets(QtWidgets.QWidget):
def __init__(self, title, envVar, parent):
super().__init__(parent)
self.dirLabel = QtWidgets.QLineEdit(self) # Text box for directory
self.dirButton = QtWidgets.QPushButton(title)
self.envVar = envVar
self.selectedDir = ''
self.initUI()
def onClick(self):
# On click, ask to choose directory then edit text box with the path selected directory
self.selectedDir = str(QtWidgets.QFileDialog.getExistingDirectory(self, 'Select Directory'))
if self.selectedDir != '':
self.dirLabel.setText(self.selectedDir)
os.environ[self.envVar] = self.selectedDir
def initUI(self):
self.dirButton.clicked.connect(self.onClick)
self.dirLabel.setReadOnly(True)
# Put text box and button side by side horizontally
hBox = QtWidgets.QHBoxLayout()
hBox.addWidget(self.dirLabel)
hBox.addWidget(self.dirButton)
self.setLayout(hBox)
class AddButtons(QtWidgets.QWidget):
def __init__(self, parent):
super().__init__(parent)
self.photoPath = PathWidgets('Choose Image Directory', 'PATH', self)
self.outPath = PathWidgets('Choose Output Directory', 'OUT', self)
self.setButton = SettingsButton('Settings/Run', self)
# Format with box layout methods to stack the above widgets vertically
hBox = QtWidgets.QHBoxLayout()
hBox.addStretch()
hBox.addWidget(self.setButton)
hBox.addStretch()
vBox = QtWidgets.QVBoxLayout()
vBox.addStretch()
vBox.addWidget(self.photoPath)
vBox.addWidget(self.outPath)
vBox.addLayout(hBox)
vBox.addStretch()
self.setLayout(vBox)
class App(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.title = 'Detect Droplets'
self.left = 300
self.top = 300
self.width = 500
self.height = 150
self.initUI()
def tipsWindow(self):
message = ('All settings should be positive integers except for the calibration value, which does not need to be an integer. \n\n'
'Make sure your ranges of radii do not overlap with each other.\n\n'
'The Canny Edge Threshold is the upper threshold for the Canny edge detector.\n\n'
'Accumulator Threshold is the threshold for center detection. Smaller values can return false circles')
QtWidgets.QMessageBox.about(self, 'Settings Tips', message)
def aboutWindow(self):
message = ('Detect Droplets by Alex Wu')
QtWidgets.QMessageBox.about(self, 'About', message)
def initUI(self):
exitAct = QtWidgets.QAction('Exit', self)
exitAct.setShortcut('Ctrl+Q')
exitAct.triggered.connect(self.close)
tipsAct = QtWidgets.QAction('Settings Tips', self)
tipsAct.triggered.connect(self.tipsWindow)
aboutAct = QtWidgets.QAction('About', self)
aboutAct.triggered.connect(self.aboutWindow)
menu = self.menuBar()
fileMenu = menu.addMenu('File')
fileMenu.addAction(exitAct)
helpMenu = menu.addMenu('Help')
helpMenu.addAction(tipsAct)
helpMenu.addAction(aboutAct)
self.buttons = AddButtons(self)
self.setCentralWidget(self.buttons)
self.setGeometry(self.left, self.top, self.width, self.height)
self.setWindowTitle(self.title)
self.show()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())