99
103
self.steps = steps
100
104
self.format = format
101
105
self.label = None
103
def get_widgets(self, parent):
104
button = QPushButton(get_icon('edit.png'), _('Edit'), parent)
105
button.setToolTip(_("Edit fit parameter '%s' properties") % self.name)
106
QObject.connect(button, SIGNAL('clicked()'), self.edit_param)
107
self.checkbox = QCheckBox(_('Logarithmic scale'), parent)
108
self.update_checkbox_state()
109
QObject.connect(self.checkbox, SIGNAL('stateChanged(int)'), self.set_scale)
110
self.label = QLabel(parent)
111
self.slider = QSlider(parent)
109
self._size_offset = size_offset
112
"""Return a copy of this fitparam"""
113
return FitParam(self.name, self.value, self.min, self.max,
114
self.logscale, self.steps, self.format,
117
def create_widgets(self, parent):
118
self.label = QLabel()
119
font = self.label.font()
120
font.setPointSize(font.pointSize()+self._size_offset)
121
self.label.setFont(font)
122
self.button = QPushButton()
123
self.button.setIcon(get_icon('settings.png'))
124
self.button.setToolTip(
125
_("Edit '%s' fit parameter properties") % self.name)
126
QObject.connect(self.button, SIGNAL('clicked()'),
127
lambda: self.edit_param(parent))
128
self.lineedit = QLineEdit()
129
QObject.connect(self.lineedit, SIGNAL('editingFinished()'),
130
self.line_editing_finished)
131
self.slider = QSlider()
112
132
self.slider.setOrientation(Qt.Horizontal)
113
133
self.slider.setRange(0, self.steps-1)
114
134
QObject.connect(self.slider, SIGNAL("valueChanged(int)"),
115
135
self.slider_value_changed)
118
return button, self.checkbox, self.slider, self.label
138
return self.get_widgets()
140
def get_widgets(self):
141
return self.label, self.lineedit, self.slider, self.button
120
143
def set_scale(self, state):
121
144
self.logscale = state > 0
122
145
self.update_slider_value()
124
147
def set_text(self):
125
text = ('<b>%s</b> : '+self.format) % (self.name, self.value)
126
self.label.setText(text)
148
style = "<span style=\'color: #444444\'><b>%s</b></span>"
149
self.label.setText(style % self.name)
150
if self.value is None:
153
value_str = self.format % self.value
154
self.lineedit.setText(value_str)
156
def line_editing_finished(self):
158
self.value = float(self.lineedit.text())
161
self.update_slider_value()
128
163
def slider_value_changed(self, int_value):
129
164
if self.logscale:
130
min, max = np.log10(self.min), np.log10(self.max)
131
self.value = 10**(min+(max-min)*int_value/self.steps)
165
total_delta = np.log10(1+self.max-self.min)
166
self.value = self.min+10**(total_delta*int_value/(self.steps-1))-1
133
self.value = self.min+(self.max-self.min)*int_value/self.steps
168
total_delta = self.max-self.min
169
self.value = self.min+total_delta*int_value/(self.steps-1)
136
def _logscale_check(self):
138
self.min = self.max/10
140
self.max = self.mix*10
142
172
def update_slider_value(self):
144
self._logscale_check()
145
value, min, max = (np.log10(self.value), np.log10(self.min),
173
if self.value is None or self.min is None or self.max is None:
174
self.slider.setEnabled(False)
148
value, min, max = self.value, self.min, self.max
149
intval = int(self.steps*(value-min)/(max-min))
150
self.slider.setValue(intval)
152
def update_checkbox_state(self):
153
state = Qt.Checked if self.logscale else Qt.Unchecked
154
self.checkbox.setCheckState(state)
156
def edit_param(self):
159
self.update_checkbox_state()
176
self.slider.setEnabled(True)
178
value_delta = max([np.log10(1+self.value-self.min), 0.])
179
total_delta = np.log10(1+self.max-self.min)
181
value_delta = self.value-self.min
182
total_delta = self.max-self.min
183
intval = int(self.steps*value_delta/total_delta)
184
self.slider.blockSignals(True)
185
self.slider.setValue(intval)
186
self.slider.blockSignals(False)
188
def edit_param(self, parent):
189
if self.edit(parent=parent):
162
192
def update(self):
163
193
self.slider.setRange(0, self.steps-1)
164
194
self.update_slider_value()
168
class FitDialog(CurveDialog):
169
def __init__(self, x, y, fitfunc, fitparams, title=None):
171
title = _('Curve fitting')
196
# Force the QLineEdit to emit the editingFinished() signal
197
self.lineedit.emit(SIGNAL('editingFinished()'))
200
def add_fitparam_widgets_to(layout, fitparams, refresh_callback, param_cols=1):
204
for i, param in enumerate(fitparams):
205
widgets = param.create_widgets(layout.parent())
206
w_colums = len(widgets)+1
207
label, lineedit, slider, button = widgets
208
QObject.connect(slider, SIGNAL("valueChanged(int)"), refresh_callback)
209
QObject.connect(lineedit, SIGNAL("editingFinished()"), refresh_callback)
210
row_contents += [(label, row_nb, 0+col_nb*w_colums),
211
(slider, row_nb, 1+col_nb*w_colums),
212
(lineedit, row_nb, 2+col_nb*w_colums),
213
(button, row_nb, 3+col_nb*w_colums),]
215
if col_nb == param_cols:
218
for widget, row, col in row_contents:
219
layout.addWidget(widget, row, col)
221
for col_nb in range(param_cols):
222
layout.setColumnStretch(1+col_nb*w_colums, 5)
224
layout.setColumnStretch(col_nb*w_colums-1, 1)
226
class FitWidgetMixin(CurveWidgetMixin):
227
def __init__(self, wintitle="guiqwt plot", icon="guiqwt.png",
228
toolbar=False, options=None, panels=None, param_cols=1,
229
legend_anchor='TR', auto_fit=True):
231
wintitle = _('Curve fitting')
237
self.fitkwargs = None
238
self.fitparams = None
239
self.autofit_prm = None
241
self.data_curve = None
242
self.fit_curve = None
244
self.legend_anchor = legend_anchor
246
self.show_xrange = False
248
self.param_cols = param_cols
249
self.auto_fit_enabled = auto_fit
250
self.button_list = [] # list of buttons to be disabled at startup
252
self.fit_layout = None
253
self.params_layout = None
255
CurveWidgetMixin.__init__(self, wintitle=wintitle, icon=icon,
256
toolbar=toolbar, options=options,
261
# CurveWidgetMixin API -----------------------------------------------------
262
def setup_widget_layout(self):
263
self.fit_layout = QHBoxLayout()
264
self.params_layout = QGridLayout()
265
params_group = create_groupbox(self, _("Fit parameters"),
266
layout=self.params_layout)
267
if self.auto_fit_enabled:
268
auto_group = self.create_autofit_group()
269
self.fit_layout.addWidget(auto_group)
270
self.fit_layout.addWidget(params_group)
271
self.plot_layout.addLayout(self.fit_layout, 1, 0)
273
vlayout = QVBoxLayout(self)
274
vlayout.addWidget(self.toolbar)
275
vlayout.addLayout(self.plot_layout)
276
self.setLayout(vlayout)
278
def create_plot(self, options):
279
super(FitWidgetMixin, self).create_plot(options)
280
for plot in self.get_plots():
281
self.connect(plot, SIG_RANGE_CHANGED, self.range_changed)
283
# Public API ---------------------------------------------------------------
284
def set_data(self, x, y, fitfunc=None, fitparams=None,
285
fitargs=None, fitkwargs=None):
286
if self.fitparams is not None and fitparams is not None:
287
self.clear_params_layout()
174
self.fitfunc = fitfunc
175
self.fitparams = fitparams
176
super(FitDialog, self).__init__(wintitle=title, icon="guiqwt.png",
177
edit=True, toolbar=True, options=None)
290
if fitfunc is not None:
291
self.fitfunc = fitfunc
292
if fitparams is not None:
293
self.fitparams = fitparams
294
if fitargs is not None:
295
self.fitargs = fitargs
296
if fitkwargs is not None:
297
self.fitkwargs = fitkwargs
179
298
self.autofit_prm = AutoFitParam(title=_("Automatic fitting options"))
180
299
self.autofit_prm.xmin = x.min()
181
300
self.autofit_prm.xmax = x.max()
182
301
self.compute_imin_imax()
185
self.show_xrange = False
188
# CurveDialog API ----------------------------------------------------------
189
def install_button_layout(self):
190
auto_button = QPushButton(get_icon('apply.png'), _("Auto"), self)
302
if self.fitparams is not None and fitparams is not None:
303
self.populate_params_layout()
306
def set_fit_data(self, fitfunc, fitparams, fitargs=None, fitkwargs=None):
307
if self.fitparams is not None:
308
self.clear_params_layout()
309
self.fitfunc = fitfunc
310
self.fitparams = fitparams
311
self.fitargs = fitargs
312
self.fitkwargs = fitkwargs
313
self.populate_params_layout()
316
def clear_params_layout(self):
317
for i, param in enumerate(self.fitparams):
318
for widget in param.get_widgets():
319
if widget is not None:
320
self.params_layout.removeWidget(widget)
323
def populate_params_layout(self):
324
add_fitparam_widgets_to(self.params_layout, self.fitparams,
325
self.refresh, param_cols=self.param_cols)
327
def create_autofit_group(self):
328
auto_button = QPushButton(get_icon('apply.png'), _("Run"), self)
191
329
self.connect(auto_button, SIGNAL("clicked()"), self.autofit)
192
autoprm_button = QPushButton(get_icon('settings.png'),
193
_("Fit parameters..."), self)
330
autoprm_button = QPushButton(get_icon('settings.png'), _("Settings"),
194
332
self.connect(autoprm_button, SIGNAL("clicked()"), self.edit_parameters)
195
xrange_button = QPushButton(get_icon('xrange.png'), _("Fit bounds"),
333
xrange_button = QPushButton(get_icon('xrange.png'), _("Bounds"), self)
197
334
xrange_button.setCheckable(True)
198
335
self.connect(xrange_button, SIGNAL("toggled(bool)"), self.toggle_xrange)
200
self.button_layout.addWidget(auto_button)
201
self.button_layout.addWidget(autoprm_button)
202
self.button_layout.addWidget(xrange_button)
203
super(FitDialog, self).install_button_layout()
205
def create_plot(self, options):
206
super(FitDialog, self).create_plot(options)
207
for plot in self.get_plots():
208
self.connect(plot, SIG_RANGE_CHANGED, self.range_changed)
210
params_frame = QFrame(self)
211
params_frame.setFrameShape(QFrame.Box)
212
params_frame.setFrameShadow(QFrame.Sunken)
213
params_layout = QGridLayout()
214
params_frame.setLayout(params_layout)
215
for i, param in enumerate(self.fitparams):
216
button, checkbox, slider, label = param.get_widgets(self)
217
self.connect(slider, SIGNAL("valueChanged(int)"), self.refresh)
218
self.connect(checkbox, SIGNAL("stateChanged(int)"), self.refresh)
219
params_layout.addWidget(button, i, 0)
221
params_layout.addWidget(checkbox, i, 1)
222
params_layout.addWidget(slider, i, 2)
223
params_layout.addWidget(label, i, 3)
224
self.plot_layout.addWidget(params_frame, 1, 0)
226
# Public API ---------------------------------------------------------------
336
auto_layout = QVBoxLayout()
337
auto_layout.addWidget(auto_button)
338
auto_layout.addWidget(autoprm_button)
339
auto_layout.addWidget(xrange_button)
340
self.button_list += [auto_button, autoprm_button, xrange_button]
341
return create_groupbox(self, _("Automatic fit"), layout=auto_layout)
343
def get_fitfunc_arguments(self):
344
"""Return fitargs and fitkwargs"""
345
fitargs = self.fitargs
346
if self.fitargs is None:
348
fitkwargs = self.fitkwargs
349
if self.fitkwargs is None:
351
return fitargs, fitkwargs
353
def refresh(self, slider_value=None):
228
354
"""Refresh Fit Tool dialog box"""
229
yfit = self.fitfunc(self.x, [p.value for p in self.fitparams])
230
self.xrange = make.range(self.autofit_prm.xmin, self.autofit_prm.xmax)
355
# Update button states
356
enable = self.x is not None and self.y is not None \
357
and self.x.size > 0 and self.y.size > 0 \
358
and self.fitfunc is not None and self.fitparams is not None \
359
and len(self.fitparams) > 0
360
for btn in self.button_list:
361
btn.setEnabled(enable)
364
# Fit widget is not yet configured
367
fitargs, fitkwargs = self.get_fitfunc_arguments()
368
yfit = self.fitfunc(self.x, [p.value for p in self.fitparams],
369
*fitargs, **fitkwargs)
371
plot = self.get_plot()
373
if self.legend is None:
374
self.legend = make.legend(anchor=self.legend_anchor)
375
plot.add_item(self.legend)
377
if self.xrange is None:
378
self.xrange = make.range(0., 1.)
379
plot.add_item(self.xrange)
380
self.xrange.set_range(self.autofit_prm.xmin, self.autofit_prm.xmax)
231
381
self.xrange.setVisible(self.show_xrange)
232
items = [make.curve(self.x, self.y, _("Data"), color="b", linewidth=2),
233
make.curve(self.x, yfit, _("Fit"), color="r", linewidth=2),
234
make.legend(), self.xrange]
235
plot = self.get_plot()
383
if self.data_curve is None:
384
self.data_curve = make.curve([], [],
385
_("Data"), color="b", linewidth=2)
386
plot.add_item(self.data_curve)
387
self.data_curve.set_data(self.x, self.y)
389
if self.fit_curve is None:
390
self.fit_curve = make.curve([], [],
391
_("Fit"), color="r", linewidth=2)
392
plot.add_item(self.fit_curve)
393
self.fit_curve.set_data(self.x, yfit)
240
396
plot.disable_autoscale()
344
501
def get_values(self):
345
502
"""Convenience method to get fit parameter values"""
346
503
return [param.value for param in self.fitparams]
349
def guifit(x, y, fitfunc, fitparams, title=None):
506
class FitWidget(QWidget, FitWidgetMixin):
507
def __init__(self, wintitle=None, icon="guiqwt.png", toolbar=False,
508
options=None, parent=None, panels=None,
509
param_cols=1, legend_anchor='TR', auto_fit=False):
510
QWidget.__init__(self, parent)
511
FitWidgetMixin.__init__(self, wintitle, icon, toolbar, options, panels,
512
param_cols, legend_anchor, auto_fit)
515
class FitDialog(QDialog, FitWidgetMixin):
516
def __init__(self, wintitle=None, icon="guiqwt.png", edit=True,
517
toolbar=False, options=None, parent=None, panels=None,
518
param_cols=1, legend_anchor='TR', auto_fit=False):
519
QDialog.__init__(self, parent)
521
self.button_layout = None
522
FitWidgetMixin.__init__(self, wintitle, icon, toolbar, options, panels,
523
param_cols, legend_anchor, auto_fit)
524
self.setWindowFlags(Qt.Window)
526
def setup_widget_layout(self):
527
FitWidgetMixin.setup_widget_layout(self)
529
self.install_button_layout()
531
def install_button_layout(self):
532
bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
533
self.connect(bbox, SIGNAL("accepted()"), SLOT("accept()"))
534
self.connect(bbox, SIGNAL("rejected()"), SLOT("reject()"))
535
self.button_list += [bbox.button(QDialogButtonBox.Ok)]
537
self.button_layout = QHBoxLayout()
538
self.button_layout.addStretch()
539
self.button_layout.addWidget(bbox)
541
vlayout = self.layout()
542
vlayout.addSpacing(10)
543
vlayout.addLayout(self.button_layout)
546
def guifit(x, y, fitfunc, fitparams, fitargs=None, fitkwargs=None,
547
wintitle=None, title=None, xlabel=None, ylabel=None,
548
param_cols=1, auto_fit=True, winsize=None, winpos=None):
350
549
"""GUI-based curve fitting tool"""
351
550
_app = guidata.qapplication()
352
dlg = FitDialog(x, y, fitfunc, fitparams)
354
return dlg.get_values()
551
# win = FitWidget(wintitle=wintitle, toolbar=True,
552
# param_cols=param_cols, auto_fit=auto_fit,
553
# options=dict(title=title, xlabel=xlabel, ylabel=ylabel))
554
win = FitDialog(edit=True, wintitle=wintitle, toolbar=True,
555
param_cols=param_cols, auto_fit=auto_fit,
556
options=dict(title=title, xlabel=xlabel, ylabel=ylabel))
557
win.set_data(x, y, fitfunc, fitparams, fitargs, fitkwargs)
558
if winsize is not None:
560
if winpos is not None:
563
return win.get_values()
566
# return win.get_values()
357
569
if __name__ == "__main__":