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
|
Custom fit configuration widgets
================================
The *silx* fit widget allows users to add custom fit theories.
A fit theory consists of several components, such as the model function
to be fitted, an estimation function...
One of these component is the optional custom configuration widget.
This is the dialog widget that is opened when a user clicks the *Configure*
button next to the drop-down list used to select the fit theory or the
background theory.
This tutorial explains how to define your own fit configuration widget
for your custom fit theories.
Prerequisites
--------------
This tutorial assumes that you are already familiar with
the standard features of :class:`silx.gui.fit.FitWidget`.
See the :ref:`fitwidget-tutorial` tutorial.
You should also be familiar with adding custom fit theories.
You will find documentation about these subjects by clicking the following links:
- :class:`silx.math.fit.fitmanager` (methods `addtheory` and `addbgtheories`)
- :class:`silx.math.fit.fittheory`
- :class:`silx.math.fit.fittheories`
- :class:`silx.math.fit.bgtheories`
The widgets we will create in this tutorial are based on the PyQt library.
Some knowledge of *PyQt* is desirable.
Basic concepts
--------------
Modal window
++++++++++++
A fit configuration widget should be a modal dialog window, so that
when a user opens the dialog to modify the configuration, the rest of
the program is frozen until all the configuration parameters are properly
defined. The program usually resumes when the user clicks the *Ok* or the
*Cancel* button in the dialog.
The widget must implement a number of methods and attributes to be used as a
dialog by FitWidget:
- standard *QDialog* methods:
- :meth:`show`: should cause the widget to become visible to the
user)
- :meth:`exec`: should run while the user is interacting with the
widget, interrupting the rest of the program. It should
typically end (*return*) when the user clicks an *OK*
or a *Cancel* button.
- :meth:`result`: must return ``True`` if the new configuration
is to be accepted (*OK* clicked) or ``False`` if it should be
rejected (*Cancel* clicked)
- an additional *output* attribute, a dictionary storing configuration parameters
to be interpreted by the corresponding fit theory.
- an optional *setDefault* method to initialize the
widget values with values in a dictionary passed as a parameter.
This will be executed first.
The first 3 methods can be automatically defined by inheriting :class:`QDialog`.
Associate a dialog to a theory
++++++++++++++++++++++++++++++
After defining a custom dialog widget, it must be initialized and associated
with a theory.
A fit theory in :class:`FitWidget` is defined by a name. For example,
one of the default theories is named *"Gaussians"*.
So if you define a configuration dialog :class:`MyGaussianConfigWidget` to define
configuration parameters understood by this theory, you can associate it the following
way.
.. code-block:: python
fw = FitWidget()
my_config_widget = MyGaussianConfigWidget(parent=fw)
fw.associateConfigDialog(theory_name="Gaussians",
config_widget=my_config_widget)
Example
-------
The following example defines a very basic configuration dialog widget
with a simple text entry in which the user can type in a floating point value.
The value is simply saved in a dictionary attribute
:attr:`CustomConfigWidget.output`. *FitWidget* will look-up this dictionary
and pass it to the theory's custom configuration function, :func:`fitconfig`.
The configuration function essentially updates the :const:`CONFIG` dictionary
used by our fit function to scale the *y* values.
.. code-block:: python
from silx.gui import qt
from silx.gui.fit import FitWidget
from silx.math.fit.fittheory import FitTheory
from silx.math.fit.fitmanager import FitManager
app = qt.QApplication([])
# default fit configuration
CONFIG = {"scale": 1.0}
# define custom fit config dialog
class CustomConfigWidget(qt.QDialog):
def __init__(self):
qt.QDialog.__init__(self)
self.setModal(True)
self.scalingFactorEdit = qt.QLineEdit(self)
self.scalingFactorEdit.setToolTip(
"Enter the scaling factor"
)
self.scalingFactorEdit.setValidator(qt.QDoubleValidator(self))
self.ok = qt.QPushButton("ok", self)
self.ok.clicked.connect(self.accept)
cancel = qt.QPushButton("cancel", self)
cancel.clicked.connect(self.reject)
layout = qt.QVBoxLayout(self)
layout.addWidget(self.scalingFactorEdit)
layout.addWidget(self.ok)
layout.addWidget(cancel)
self.old_scale = CONFIG["scale"]
self.output = {}
def accept(self):
self.output["scale"] = float(self.scalingFactorEdit.text())
qt.QDialog.accept(self)
def reject(self):
self.output["scale"] = self.old_scale
qt.QDialog.reject(self)
# our actual fit model function
def fitfun(x, a, b):
return CONFIG["scale"] * (a * x + b)
# fit configuration
def fitconfig(scale=None, **kw):
"""Update global config dict CONFIG"""
if scale is not None:
CONFIG["scale"] = scale
return CONFIG
# synthetic test data a=2, b=3
x = list(range(0, 100))
y = [fitfun(x_, 2, 3) for x_ in x]
# register our custom fit theory
fitmngr = FitManager()
fitmngr.setdata(x, y)
fitmngr.addtheory("scaled linear",
FitTheory(
function=fitfun,
parameters=["a", "b"],
configure=fitconfig))
# open a fitwidget and associate an instance of our custom
# configuration dialog to our custom theory
fw = FitWidget(fitmngr=fitmngr)
fw.associateConfigDialog("scaled linear", CustomConfigWidget())
fw.show()
app.exec()
.. |img0| image:: img/custom_config_scale1.0.png
:height: 300px
:align: middle
.. |img1| image:: img/custom_config_scale2.1.png
:height: 300px
:align: middle
.. |img2| image:: img/custom_config_scale0.5.png
:height: 300px
:align: middle
.. list-table::
:widths: 1 4
:header-rows: 1
* - Screenshot
- Description
* - |img0|
- If the default value of 1.0 is used, the fit finds *a=2* and *b=3*
as expected.
* - |img1|
- Setting a scaling factor of 2.1 causes the fit to find results that are
less than about half of the normal expected result.
* - |img2|
- A scaling factor of 0.5 causes the fit to find the values to be double
of the ones used for generating the synthetic data.
|