source: orange/docs/extend-widgets/rst/basics.rst @ 11439:2a63a9963207

Revision 11439:2a63a9963207, 14.0 KB checked in by Ales Erjavec <ales.erjavec@…>, 12 months ago (diff)

Small fixes to widget development documentation.

Line 
1###############
2Getting Started
3###############
4
5
6The tutorial on these pages is meant for those who are interested in
7developing widgets in Orange. Orange Widgets are components in
8Orange's visual programming environment. They are wrappers around some
9data analysis code that provide graphical user interface
10(GUI). Widgets communicate, and pass tokens through communication
11channels to interact with other widgets. While simplest widgets
12consist of even less than 100 lines of code, those more complex that
13often implement some fancy graphical display of data and allow for
14some really nice interaction may be over 1000 lines long.
15
16When we have started to write this tutorial, we have been working
17on widgets for quite a while. There are now (now being in the very
18time this page has been crafted) about 50 widgets available, and we
19have pretty much defined how widgets and their interfaces should look
20like. We have also made some libraries that help set up GUI with only
21a few lines of code, and some mechanisms that one may found useful and
22user friendly, like progress bars and alike.
23
24On this page, we will start with some simple essentials, and then
25show how to build a simple widget that will be ready to run within
26Orange Canvas, our visual programming environment.
27
28*************
29Prerequisites
30*************
31
32Each Orange widget belongs to a category and within a
33category has an associated priority. Opening Orange Canvas, a visual
34programming environment that comes with Orange, widgets are listed in
35toolbox on the top of the window:
36
37.. image:: widgettoolbox.png
38
39By default, Orange is installed in site-packages directory of
40Python libraries. Widgets are all put in the subdirectories of
41OrangeWidget directory; these subdirectories define widget
42categories. For instance, under windows and default settings, a
43directory that stores all the widgets displayed in the Evaluate pane is
44*C:\\Python23\\Lib\\site-packages\\Orange\\OrangeWidgets\\Evaluate*. Figure
45above shows that at the time of writing of this text there were five
46widgets for evaluation of classifiers, and this is how my Evaluate
47directory looked like:
48
49.. image:: explorer.png
50
51Notice that there are a number of files in Evaluate directory, so
52how does Orange Canvas distinguish those that define widgets? Well,
53widgets are Python script files that start with a header. Here is a
54header for OWTestLearners.py::
55
56    <name>Test Learners</name>
57    <description>Estimates the predictive performance of learners on a data set.</description>
58    <icon>icons/TestLearners.png</icon>
59    <priority>200</priority>
60
61OWTestLearners is a Python script, so the header information we
62show about lies within the comment block, with triple quote opening
63and closing the comment. Header defines the name of the widget, its
64description, the name of the picture file the widget will use for an
65icon, and a number expressing the priority of the widget. The name of
66the widget as given in the header will be the one that will be used
67throughout in Orange Canvas. As for naming, the actual file name of
68the widget is not important. The description of the widget is shown
69once mouse rests on an toolbar icon representing the widget. And for
70the priority: this determines the order in which widgets appear in the
71toolbox. The one shown above for Evaluate groups has widget named Test
72Learners with priority 200, Classifications with 300, ROC Analysis
73with 1010, Lift Curve with 1020 and Calibration Plot with 1030. Notice
74that every time the priority number crosses a multiplier of a 1000,
75there is a gap in the toolbox between the widgets; in this way, a
76subgroups of the widgets within the same group can be imposed.
77
78Widgets communicate. They use typed channels, and exchange
79tokens. Each widget would define its input and output channels in
80something like::
81
82    self.inputs = [("Test Data Set", ExampleTable, self.cdata),
83                   ("Learner", orange.Learner, self.learner, 0)]
84    self.outputs = [("Evaluation Results", orngTest.ExperimentResults)]
85
86Above two lines are for Test Learners widget, so hovering with your
87mouse over its icon in the widget toolbox would yield:
88
89.. image:: mouseoverwidgetintoolbox.png
90
91We will go over the syntax of channel definitions later, but for
92now the following is important:
93
94   - Widgets are defined in a Python files.
95   - For Orange and Orange canvas to find them, they reside in subdirectories
96     in OrangeWidgets directory of Orange installation. The name of the
97     subdirectory matters, as this is the name of the widget category. Widgets
98     in the same directory will be grouped in the same pane of widget toolbox
99     in Orange Canvas.
100   - A file describing a widget starts with a header. This, given in sort of
101     XMLish style, tells about the name, short description, location of an
102     icon and priority of the widget.
103   - The sole role of priority is to specify the placement (order) of widgets
104     in the Orange Canvas toolbox.
105   - Somewhere in the code (where we will learn later) there are two lines
106     which tell which channels the widgets uses for communication. These,
107     together with the header information, completely specify the widget as it
108     is seen from the outside.
109
110Oh, by the way. Orange caches widget descriptions to achieve a faster
111startup, but this cache is automatically refreshed at startup if any change
112is detected in widgets' files.
113
114***********
115Let's Start
116***********
117
118Now that we went through some of the more boring stuff, let us now
119have some fun and write a widget. We will start with a very simple
120one, that will receive a data set on the input and will output a data
121set with 10% of the data instances. Not to mess with other widgets, we
122will create a Test directory within OrangeWidgets directory, and write
123the widget in a file called `OWDataSamplerA.py`: OW for Orange Widget,
124DataSampler since this is what widget will be doing, and A since we
125prototype a number of this widgets in our tutorial.
126
127The script defining the OWDataSamplerA widget starts with a follwing header::
128
129    <name>Data Sampler</name>
130    <description>Randomly selects a subset of instances from the data set</description>
131    <icon>icons/DataSamplerA.png</icon>
132    <priority>10</priority>
133
134This should all be clear now, perhaps just a remark on an icon. We
135can put any name here, and if Orange Canvas won't find the
136corresponding file, it will use a file called Unknown.png (an icon
137with a question mark).
138
139Orange Widgets are all derived from the class OWWidget. The name of
140the class should be match the file name, so the lines following the
141header in our Data Sampler widget should look something like::
142
143    from OWWidget import *
144    import OWGUI
145
146    class OWDataSamplerA(OWWidget):
147
148        def __init__(self, parent=None, signalManager=None):
149            OWWidget.__init__(self, parent, signalManager, 'SampleDataA')
150
151            self.inputs = [("Data", ExampleTable, self.data)]
152            self.outputs = [("Sampled Data", ExampleTable)]
153
154            # GUI
155            box = OWGUI.widgetBox(self.controlArea, "Info")
156            self.infoa = OWGUI.widgetLabel(box, 'No data on input yet, waiting to get something.')
157            self.infob = OWGUI.widgetLabel(box, '')
158            self.resize(100,50)
159
160In initialization, the widget calls the :obj:`__init__` function
161of a base class, passing the name 'SampleData' which will,
162essentially, be used for nothing else than a stem of a file for saving
163the parameters of the widgets (we will regress on these somehow
164latter in tutorial). Widget then defines inputs and outputs. For
165input, widget defines a "Data" channel, accepting tokens of the type
166orange.ExampleTable and specifying that :obj:`data` function will
167be used to handle them. For now, we will use a single output channel
168called "Sampled Data", which will be of the same type
169(orange.ExampleTable).
170
171Notice that the types of the channels are
172specified by a class name; you can use any classes here, but if your
173widgets need to talk with other widgets in Orange, you will need to
174check which classes are used there. Luckily, and as one of the main
175design principles, there are just a few channel types that current
176Orange widgets are using.
177
178The next four lines specify the GUI of our widget. This will be
179simple, and will include only two lines of text of which, if nothing
180will happen, the first line will report on "no data yet", and second
181line will be empty. By (another) design principles, in an interface
182Orange widgets are most often split to control and main area. Control
183area appears on the left and should include any controls for settings
184or options that your widget will use. Main area would most often
185include a graph, table or some drawing that will be based on the
186inputs to the widget and current options/setting in the control
187area. OWWidget make these two areas available through its attributes
188:obj:`self.controlArea` and :obj:`self.mainArea`. Notice
189that while it would be nice for all widgets to have this common visual
190look, you can use these areas in any way you want to, even disregarding one
191and composing your widget completely unlike the others in Orange.
192
193As our widget won't display anything apart from some info, we will
194place the two labels in the control area and surround it with the box
195"Info".
196
197In order to complete our widget, we now need to define how will it
198handle the input data. This is done in a function called
199:obj:`data` (remember, we did introduce this name in the
200specification of the input channel)::
201
202    def data(self, dataset):
203        if dataset:
204            self.infoa.setText('%d instances in input data set' % len(dataset))
205            indices = orange.MakeRandomIndices2(p0=0.1)
206            ind = indices(dataset)
207            sample = dataset.select(ind, 0)
208            self.infob.setText('%d sampled instances' % len(sample))
209            self.send("Sampled Data", sample)
210        else:
211            self.infoa.setText('No data on input yet, waiting to get something.')
212            self.infob.setText('')
213            self.send("Sampled Data", None)
214
215The function is defined within a class definition, so its first
216argument has to be :obj:`self`. The second argument called
217:obj:`dataset` is the token sent through the input channel which
218our function needs to handle.
219
220To handle the non-empty token, the widget updates the interface
221reporting on number of data items on the input, then does the data
222sampling using Orange's routines for these, and updates the
223interface reporting on the number of sampled instances. Finally, the
224sampled data is sent as a token to the output channel with a name
225"Sampled Data".
226
227Notice that the token can be empty (``dataset is None``),
228resulting from either the sending widget to which we have connected
229intentionally emptying the channel, or when the link between the two
230widgets is removed. In any case, it is important that we always write
231token handlers that appropriately handle the empty tokens. In our
232implementation, we took care of empty input data set by appropriately
233setting the GUI of a widget and sending an empty token to the
234output channel.
235
236..
237   Although our widget is now ready to test, for a final touch, let's
238   design an icon for our widget. As specified in the widget header, we
239   will call it :download:`DataSamplerA.png <DataSamplerA.png>` and will
240   put it in icons subdirectory of OrangeWidgets directory (together with
241   all other icons of other widgets).
242
243For a test, we now open Orange Canvas. There should be a new pane in a
244widget toolbox called Test (this is the name of the directory we have
245used to put in our widget). If we click on this pane, it displays an
246icon of our widget. Try to hoover on it to see if the header and
247channel info was processed correctly:
248
249.. image:: samplewidgetontoolbox.png
250
251Now for the real test. We put the File widget on the schema (from
252Data pane), read iris.tab data set. We also put our Data Sampler widget on the pane and
253open it (double click on the icon, or right-click and choose
254Open):
255
256.. image:: datasamplerAempty.png
257
258Drag this window off the window with the widget schema of Orange
259Canvas, and connect File and Data Sampler widget (click on an ouput
260connector - green box - of the File widget, and drag the line to the
261input connector of the Data Sampler). If everything is ok, as soon as
262you release the mouse the connection is established and, the token
263that was waiting on the output of the file widget was sent to the Data
264Sampler widget, which in turn updated its window:
265
266.. image:: datasamplerAupdated.png
267
268To see if the Data Sampler indeed sent some data to the output,
269connect it to the Data Table widget:
270
271.. image:: schemawithdatatable.png
272
273Try opening different data files (the change should propagate
274through your widgets and with Data Table window open, you should
275immediately see the result of sampling). Try also removing the
276connection between File and Data Sampler (right click on the
277connection, choose Remove). What happens to the data displayed in the
278Data Table?
279
280*****************************************
281Testing Your Widget Outside Orange Canvas
282*****************************************
283
284When prototyping a single widget, for a fast test I often get
285bored of running Orange Canvas, setting the schema and clicking on
286icons to get widget windows. There are two options to bypass this. The
287first one is to add a testing script at the end of your widget. To do
288this, we finished Data Sampler with::
289
290    if __name__=="__main__":
291        appl = QApplication(sys.argv)
292        ow = OWDataSamplerA()
293        ow.show()
294        dataset = orange.ExampleTable('iris.tab')
295        ow.data(dataset)
296        appl.exec_()
297
298These are essentially some calls to Qt routines that run GUI for our
299widgets. At the core, however, notice that instead of sending the
300token to the input channel, we directly called the routine for token
301handling (:obj:`data`).
302
303To test your widget in more complex environment, that for instance
304requires to set a complex schema in which your widget collaborates,
305use Orange Canvas to set the schema and then either 1) save the schema
306to be opened every time you run Orange Canvas, or 2) save this schema
307(File menu) as an application within a single file you will need to
308run each time you will test your widget.
Note: See TracBrowser for help on using the repository browser.