Skip to content

Commit 10f2412

Browse files
authored
Merge pull request #25 from AdaptiveParticles/float_sliders
Update sliders to allow non-integer values
2 parents edb9ac8 + fcb36f2 commit 10f2412

File tree

6 files changed

+96
-57
lines changed

6 files changed

+96
-57
lines changed

demo/get_apr_by_block_interactive_demo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def main():
4343
par.dz = 1
4444

4545
# Interactively set the threshold parameters using the partial image
46-
par = pyapr.converter.find_parameters_interactive(img, params=par, verbose=True)
46+
par = pyapr.converter.find_parameters_interactive(img, params=par, verbose=True, slider_decimals=1)
4747

4848
del img # Parameters found, we don't need the partial image anymore
4949

demo/get_apr_interactive_demo.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import os
22
import pyapr
33
from skimage import io as skio
4-
import numpy as np
54

65

76
def main():
@@ -36,7 +35,7 @@ def main():
3635
par.dz = 1
3736

3837
# Compute APR and sample particle values
39-
apr, parts = pyapr.converter.get_apr_interactive(img, params=par, verbose=True)
38+
apr, parts = pyapr.converter.get_apr_interactive(img, params=par, verbose=True, slider_decimals=1)
4039

4140
# Display the APR
4241
pyapr.viewer.parts_viewer(apr, parts)

pyapr/converter/converter_methods.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def get_apr(image, rel_error=0.1, gradient_smoothing=2, verbose=True, params=Non
4545
return apr, parts
4646

4747

48-
def get_apr_interactive(image, rel_error=0.1, gradient_smoothing=2, verbose=True, params=None):
48+
def get_apr_interactive(image, rel_error=0.1, gradient_smoothing=2, verbose=True, params=None, slider_decimals=1):
4949

5050
# check that the image array is c-contiguous
5151
if not image.flags['C_CONTIGUOUS']:
@@ -86,7 +86,7 @@ def get_apr_interactive(image, rel_error=0.1, gradient_smoothing=2, verbose=True
8686
converter.set_verbose(verbose)
8787

8888
# launch interactive APR converter
89-
io_int.interactive_apr(converter, apr, image)
89+
io_int.interactive_apr(converter, apr, image, slider_decimals=slider_decimals)
9090

9191
if verbose:
9292
print("Total number of particles: {}".format(apr.total_number_particles()))
@@ -100,7 +100,7 @@ def get_apr_interactive(image, rel_error=0.1, gradient_smoothing=2, verbose=True
100100
return apr, parts
101101

102102

103-
def find_parameters_interactive(image, rel_error=0.1, gradient_smoothing=0, verbose=True, params=None):
103+
def find_parameters_interactive(image, rel_error=0.1, gradient_smoothing=0, verbose=True, params=None, slider_decimals=1):
104104

105105
# check that the image array is c-contiguous
106106
if not image.flags['C_CONTIGUOUS']:
@@ -137,7 +137,7 @@ def find_parameters_interactive(image, rel_error=0.1, gradient_smoothing=0, verb
137137
converter.set_verbose(verbose)
138138

139139
# launch interactive APR converter
140-
par = io_int.find_parameters_interactive(converter, apr, image)
140+
par = io_int.find_parameters_interactive(converter, apr, image, slider_decimals=slider_decimals)
141141

142142
if verbose:
143143
print("---------------------------------")

pyapr/filegui.py

Lines changed: 75 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,60 @@
11
import pyqtgraph.Qt as Qt
22
import pyqtgraph as pg
3-
import pyapr
43
import matplotlib.pyplot as plt
54
import numpy as np
65

76

8-
class customSlider():
9-
def __init__(self, window, label_name):
7+
class DoubleSlider(Qt.QtWidgets.QSlider):
8+
"""
9+
Extends QSlider to allow floating-point values
10+
11+
Adapted from Stack Overflow answer https://stackoverflow.com/a/50300848
12+
by user bfris (https://stackoverflow.com/users/9705687/bfris)
13+
"""
14+
15+
# create a signal that we can connect to if necessary
16+
doubleValueChanged = Qt.QtCore.pyqtSignal(float)
17+
18+
def __init__(self, decimals=2, *args, **kwargs):
19+
super(DoubleSlider, self).__init__(*args, **kwargs)
20+
self._multi = 10 ** decimals
21+
self.valueChanged.connect(self.emitDoubleValueChanged)
22+
23+
def emitDoubleValueChanged(self):
24+
value = float(super(DoubleSlider, self).value()) / self._multi
25+
self.doubleValueChanged.emit(value)
26+
27+
def value(self):
28+
return float(super(DoubleSlider, self).value()) / self._multi
29+
30+
def setValue(self, value):
31+
super(DoubleSlider, self).setValue(int(value * self._multi))
32+
33+
def setMinimum(self, value):
34+
return super(DoubleSlider, self).setMinimum(value * self._multi)
35+
36+
def setMaximum(self, value):
37+
return super(DoubleSlider, self).setMaximum(value * self._multi)
38+
39+
def setSingleStep(self, value):
40+
return super(DoubleSlider, self).setSingleStep(value * self._multi)
41+
42+
def singleStep(self):
43+
return float(super(DoubleSlider, self).singleStep()) / self._multi
44+
45+
46+
class CustomSlider:
47+
def __init__(self, window, label_name, decimals=0):
48+
49+
if decimals < 0 or not isinstance(decimals, int):
50+
raise ValueError('CustomSlider initialized with \'decimals\'={}. Only non-negative integers are allowed.'.format(decimals))
51+
52+
self.decimals = decimals
53+
54+
self.slider = DoubleSlider(decimals, Qt.QtCore.Qt.Horizontal, window)
55+
self.maxBox = Qt.QtWidgets.QDoubleSpinBox(window, decimals=self.decimals)
1056

11-
self.slider = Qt.QtWidgets.QSlider(Qt.QtCore.Qt.Horizontal, window)
1257
self.label = Qt.QtWidgets.QLabel(window)
13-
self.maxBox = Qt.QtWidgets.QSpinBox(window)
1458

1559
self.maxBox.setMaximum(64000)
1660
self.maxBox.setValue(300)
@@ -24,9 +68,9 @@ def __init__(self, window, label_name):
2468
self.slider.setValue(1)
2569
self.slider.setMaximum(self.maxBox.value())
2670

27-
sz_label = 100
28-
sz_slider = 200
29-
sz_box = 75
71+
self.sz_label = 200
72+
self.sz_slider = 200
73+
self.sz_box = 90
3074

3175
def move(self, loc1, loc2):
3276

@@ -39,21 +83,20 @@ def move(self, loc1, loc2):
3983
self.maxBox.setFixedWidth(self.sz_box)
4084

4185
def updateRange(self):
42-
max = self.maxBox.value()
43-
self.slider.setMaximum(max)
44-
self.slider.setTickInterval(1)
86+
max_val = self.maxBox.value()
87+
self.slider.setMaximum(max_val)
4588

4689
def connectSlider(self, function):
4790
self.slider.valueChanged.connect(function)
4891

4992
def updateText(self):
50-
text_str = self.label_name + ": " + str(self.slider.value())
93+
val_str = '{:.{prec}f}'.format(self.slider.value(), prec=self.decimals)
94+
text_str = self.label_name + ': ' + val_str
5195
self.label.setText(text_str)
5296

5397

5498
class MainWindowImage(Qt.QtGui.QWidget):
55-
56-
def __init__(self):
99+
def __init__(self, slider_decimals=0):
57100
super(MainWindowImage, self).__init__()
58101

59102
self.setMouseTracking(True)
@@ -88,15 +131,15 @@ def __init__(self):
88131
# add a QLabel giving information on the current slice and the APR
89132
self.slice_info = Qt.QtGui.QLabel(self)
90133

91-
self.slice_info.move(10, 20)
92-
self.slice_info.setFixedWidth(200)
134+
self.slice_info.move(20, 20)
135+
self.slice_info.setFixedWidth(250)
93136

94137
# add a label for the current cursor position
95138

96139
self.cursor = Qt.QtGui.QLabel(self)
97140

98141
self.cursor.move(20, 40)
99-
self.cursor.setFixedWidth(200)
142+
self.cursor.setFixedWidth(250)
100143

101144
# add parameter tuning
102145

@@ -108,17 +151,17 @@ def __init__(self):
108151

109152
self.max_label = Qt.QtWidgets.QLabel(self)
110153
self.max_label.setText("Slider Max")
111-
self.max_label.move(505, 50)
154+
self.max_label.move(610, 50)
112155

113-
self.slider_grad = customSlider(self, "grad_th")
156+
self.slider_grad = CustomSlider(self, "gradient threshold", decimals=slider_decimals)
114157
self.slider_grad.move(200, 80)
115158
self.slider_grad.connectSlider(self.valuechangeGrad)
116159

117-
self.slider_sigma = customSlider(self, "sigma_th")
160+
self.slider_sigma = CustomSlider(self, "sigma threshold", decimals=slider_decimals)
118161
self.slider_sigma.move(200, 110)
119162
self.slider_sigma.connectSlider(self.valuechangeSigma)
120163

121-
self.slider_Ith = customSlider(self, "Ip_th")
164+
self.slider_Ith = CustomSlider(self, "intensity threshold", decimals=slider_decimals)
122165
self.slider_Ith.move(200, 140)
123166
self.slider_Ith.connectSlider(self.valuechangeIth)
124167

@@ -130,7 +173,6 @@ def __init__(self):
130173
img_ref = 0
131174
par_ref = 0
132175

133-
134176
x_num = 0
135177
z_num = 0
136178
y_num = 0
@@ -149,7 +191,6 @@ def __init__(self):
149191

150192
#parameters to be played with
151193
grad_th = 0
152-
153194
app_ref = 0
154195

155196
def imageHoverEvent(self, event):
@@ -167,17 +208,14 @@ def imageHoverEvent(self, event):
167208
j = int(np.clip(j, 0, data.shape[1] - 1))
168209
val = data[i, j]
169210

170-
text_string = "(y: " + str(i) + ",x: " + str(j) + ") val; " + str(val) + "\n"
171-
211+
text_string = 'x={}, y={}, z={}, value={}\n'.format(j, i, self.current_view, val)
172212
self.cursor.setText(text_string)
173213

174214
def exitPressed(self):
175215
self.app_ref.exit()
176216

177-
def updateSliceText(self, slice):
178-
179-
text_string = 'Slice: ' + str(slice) + '/' + str(self.z_num) + ", " + str(self.y_num) + 'x' + str(self.x_num) + '\n'
180-
217+
def updateSliceText(self, z):
218+
text_string = 'Slice: {}/{}, {}x{}\n'.format(z+1, self.z_num, self.y_num, self.x_num)
181219
self.slice_info.setText(text_string)
182220

183221
def updatedLUT(self):
@@ -223,7 +261,6 @@ def setLUT(self, string):
223261

224262
self.img_I_ds.setImage(None, levels=(self.apr_ref.level_max()-2, self.apr_ref.level_max()), opacity=0.5)
225263

226-
227264
def update_slice(self, new_view):
228265

229266
if (new_view >= 0) & (new_view < self.z_num):
@@ -268,7 +305,6 @@ def valuechangeIth(self):
268305
self.par_ref.Ip_th = size
269306
self.update_slice(self.current_view)
270307

271-
272308
def histogram_updated(self):
273309

274310
hist_range = self.hist.item.getLevels()
@@ -278,7 +314,6 @@ def histogram_updated(self):
278314

279315
#self.img_I.setLevels([self.hist_min, self.hist_max], True)
280316

281-
282317
def set_image(self, img, converter):
283318

284319
self.img_I = pg.ImageItem(img[0, :, :])
@@ -313,7 +348,7 @@ def set_image(self, img, converter):
313348
self.img_I_ds.setRect(Qt.QtCore.QRectF(self.min_x, self.min_y, self.x_num_ds*2, self.y_num_ds*2))
314349
self.img_I.setRect(Qt.QtCore.QRectF(self.min_x, self.min_y, self.x_num, self.y_num))
315350

316-
## Set up the slide
351+
## Set up the z slider
317352
self.slider.setMinimum(0)
318353
self.slider.setMaximum(self.z_num - 1)
319354
self.slider.setTickPosition(Qt.QtWidgets.QSlider.TicksBothSides)
@@ -323,16 +358,14 @@ def set_image(self, img, converter):
323358

324359
## Image hover event
325360
self.img_I.hoverEvent = self.imageHoverEvent
326-
327361
self.update_slice(int(self.z_num/2))
328362

329363
def closeEvent(self, event):
330364
self.pg_win.close()
331365

332366

333-
class InteractiveIO():
367+
class InteractiveIO:
334368
def __init__(self):
335-
336369
# class methods require a QApplication instance - this helps to avoid multiple instances...
337370
self.app = Qt.QtGui.QApplication.instance()
338371
if self.app is None:
@@ -360,7 +393,7 @@ def save_tiff_file_name(default_name='output.tif'):
360393
file_name = Qt.QtGui.QFileDialog.getSaveFileName(None, "Save TIFF", default_name, "(*.tif *.tiff)")
361394
return file_name[0]
362395

363-
def interactive_apr(self, converter, apr, img):
396+
def interactive_apr(self, converter, apr, img, slider_decimals=2):
364397

365398
converter.get_apr_step1(apr, img)
366399

@@ -369,7 +402,7 @@ def interactive_apr(self, converter, apr, img):
369402
pg.setConfigOption('imageAxisOrder', 'row-major')
370403

371404
# Create window with GraphicsView widget
372-
win = MainWindowImage()
405+
win = MainWindowImage(slider_decimals=slider_decimals)
373406
win.show()
374407
win.apr_ref = apr
375408
win.app_ref = self.app
@@ -389,14 +422,16 @@ def interactive_apr(self, converter, apr, img):
389422
converter.get_apr_step2(apr, win.par_ref)
390423
return None
391424

392-
def find_parameters_interactive(self, converter, apr, img):
425+
def find_parameters_interactive(self, converter, apr, img, slider_decimals=2):
426+
393427
converter.get_apr_step1(apr, img)
428+
394429
pg.setConfigOption('background', 'w')
395430
pg.setConfigOption('foreground', 'k')
396431
pg.setConfigOption('imageAxisOrder', 'row-major')
397432

398433
# Create window with GraphicsView widget
399-
win = MainWindowImage()
434+
win = MainWindowImage(slider_decimals=slider_decimals)
400435
win.show()
401436
win.apr_ref = apr
402437
win.app_ref = self.app

pyapr/viewer/partsViewer.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,20 @@ def __init__(self):
6161

6262
self.slice_info.move(130, 20)
6363
self.slice_info.setFixedWidth(200)
64+
self.slice_info.setFixedHeight(45)
6465

6566
# add a label for the current cursor position
6667

6768
self.cursor = QtGui.QLabel(self)
6869

6970
self.cursor.move(330, 20)
70-
self.cursor.setFixedWidth(200)
71+
self.cursor.setFixedWidth(260)
7172
self.cursor.setFixedHeight(45)
7273

7374
def add_level_toggle(self):
7475
self.level_toggle = QtWidgets.QCheckBox(self)
7576
self.level_toggle.setText("View Level")
76-
self.level_toggle.move(605, 20)
77+
self.level_toggle.move(625, 20)
7778

7879
self.level_toggle.setChecked(False)
7980

@@ -127,10 +128,10 @@ def toggleLevel(self):
127128

128129
hist_on = True
129130

130-
def updateSliceText(self, slice):
131-
132-
text_string = 'Slice: ' + str(slice) + '/' + str(self.z_num) + ", " + str(self.y_num) + 'x' + str(self.x_num) + '\n'
133-
text_string += 'level_min: ' + str(self.level_min) + ', level_max: ' + str(self.level_max) + '\n'
131+
def updateSliceText(self, z):
132+
text_string = 'Slice: {}/{}, {}x{}\n' \
133+
'level_min: {}, level_max: {}\n'.format(z+1, self.z_num, self.y_num, self.x_num,
134+
self.level_min, self.level_max)
134135

135136
self.slice_info.setText(text_string)
136137

@@ -334,17 +335,21 @@ def imageHoverEvent(self, event):
334335
j = int(np.clip(j, 0, data.shape[1] - 1))
335336
val = data[i, j]
336337

338+
k = self.current_view
339+
337340
i_l = i
338341
j_l = j
342+
k_l = k
339343

340344
while (val == 0) & (current_level > self.level_min):
341345
current_level -= 1
342346
i_l = int(i_l/2)
343347
j_l = int(j_l/2)
348+
k_l = int(k_l/2)
344349
val = self.array_list[current_level][i_l, j_l]
345350

346-
text_string = "(y: " + str(i) + ",x: " + str(j) + ") val: " + str(val) + ")" + "\n"
347-
text_string += "(y_l: " + str(i_l) + ",x_l: " + str(j_l) + ",l: " + str(current_level) + ")" + "\n"
351+
text_string = 'x={}, y={}, z={}, value={}\n' \
352+
'x_l={}, y_l={}, z_l={}, level={}\n'.format(j, i, k, val, j_l, i_l, k_l, current_level)
348353

349354
self.cursor.setText(text_string)
350355

0 commit comments

Comments
 (0)