source: orange-bioinformatics/_bioinformatics/widgets/OWQualityControl.py @ 1636:10d234fdadb9

Revision 1636:10d234fdadb9, 24.4 KB checked in by mitar, 2 years ago (diff)

Restructuring because we will not be using namespaces.

RevLine 
[1556]1"""
[1558]2<name>Quality Control</name>
[1562]3<description>Experiment quality control</description>
[1556]4
5"""
6
[1632]7from __future__ import absolute_import, with_statement
8
[1556]9import sys
[1558]10from collections import defaultdict
11from contextlib import contextmanager
[1556]12from pprint import pprint
13
[1632]14import numpy
15
16import Orange
17from Orange.OrangeWidgets import OWGUI
18from Orange.OrangeWidgets.OWWidget import *
19from Orange.OrangeWidgets.OWItemModels import PyListModel, safe_text
20from Orange.OrangeWidgets.OWGraphics import GraphicsSimpleTextLayoutItem
21
22from .. import obiExperiments as exp
23
24from .OWGenotypeDistances import SetContextHandler
25
[1558]26DEBUG = False
[1556]27
[1558]28@contextmanager
[1560]29def widget_disable(widget):
30    """A context to disable the widget (enabled property) 
31    """
[1558]32    widget.setEnabled(False)
[1567]33    try:
34        yield
35    finally:
36        widget.setEnabled(True)
[1558]37   
[1566]38   
[1558]39@contextmanager
40def disable_updates(widget):
[1560]41    """
42    A context that sets '_disable_updates' member to True, and
43    then restores it.
44   
45    """
[1558]46    widget._disable_updates = True
[1567]47    try:
48        yield
49    finally:
50        widget._disable_updates = False
[1558]51
[1566]52
[1558]53def group_label(splits, groups):
[1560]54    """Return group label.
[1556]55    """
[1573]56    labels = ["%s=%s" % (split, group) \
[1556]57              for split, group in zip(splits, groups)]
58    return " | ".join(labels)
59
60
[1558]61def sort_label(sort, attr):
[1560]62    """Return within group sorted items label for attribute.
[1558]63    """
64    items = [(key, attr.attributes.get(key, "?")) \
65             for key in sort]
[1573]66    labels = ["%s=%s" % tuple(item) for item in items]
[1558]67    return " | ".join(labels)
68
[1566]69
[1558]70def float_if_posible(val):
[1560]71    """Return val as float if possible otherwise return the value unchanged.
72   
73    """
[1558]74    try:
75        return float(val)
76    except ValueError:
77        return val
[1561]78   
[1566]79   
[1561]80def experiment_description(feature):
[1562]81    """Return experiment description from ``feature.attributes``.
82    """
[1561]83    text = ""
84    if feature.attributes:
85        items = feature.attributes.items()
86        items = [(safe_text(key), safe_text(value)) for key, value in items]
87        labels = map("%s = %s".__mod__, items)
[1563]88        text += "<b>%s</b><br/>" % safe_text(feature.name)
[1561]89        text += "<br/>".join(labels)
90    return text
[1556]91
[1566]92
[1556]93class OWQualityControl(OWWidget):
[1558]94    contextHandlers = {"": SetContextHandler("")}
[1567]95    settingsList = ["selected_distance_index"]
[1558]96   
[1560]97    DISTANCE_FUNCTIONS = [("Distance from Pearson correlation",
98                           exp.dist_pcorr),
99                          ("Euclidean distance", 
100                           exp.dist_eucl),
101                          ("Distance from Spearman correlation", 
102                           exp.dist_spearman)]
[1556]103
104    def __init__(self, parent=None, signalManager=None,
105                 title="Quality Control"):
106        OWWidget.__init__(self, parent, signalManager, title,
107                          wantGraph=True)
108
109        self.inputs = [("Experiment Data", Orange.data.Table, self.set_data)]
110
[1558]111        ## Settings
[1556]112        self.selected_distance_index = 0
113
114        ## Attributes
115        self.data = None
116        self.distances = None
117        self.groups = None
[1558]118        self.unique_pos = None
[1566]119        self.base_group_index = 0
[1556]120
121        ## GUI
122        box = OWGUI.widgetBox(self.controlArea, "Info")
123        self.info_box = OWGUI.widgetLabel(box, "\n")
124
[1566]125        ## Separate By box
126        box = OWGUI.widgetBox(self.controlArea, "Separate By")
[1556]127        self.split_by_model = PyListModel()
128        self.split_by_view = QListView()
[1558]129        self.split_by_view.setSelectionMode(QListView.ExtendedSelection)
[1556]130        self.split_by_view.setModel(self.split_by_model)
131        box.layout().addWidget(self.split_by_view)
132
[1558]133        self.connect(self.split_by_view.selectionModel(),
134                     SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
135                     self.on_split_key_changed)
136
[1556]137        ## Sort By box
138        box = OWGUI.widgetBox(self.controlArea, "Sort By")
139        self.sort_by_model = PyListModel()
140        self.sort_by_view = QListView()
[1558]141        self.sort_by_view.setSelectionMode(QListView.ExtendedSelection)
[1556]142        self.sort_by_view.setModel(self.sort_by_model)
143        box.layout().addWidget(self.sort_by_view)
[1558]144       
145        self.connect(self.sort_by_view.selectionModel(),
146                     SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
147                     self.on_sort_key_changed)
[1556]148
149        ## Distance box
150        box = OWGUI.widgetBox(self.controlArea, "Distance Measure")
151        OWGUI.comboBox(box, self, "selected_distance_index",
[1558]152                       items=[t[0] for t in self.DISTANCE_FUNCTIONS],
153                       callback=self.on_distance_measure_changed)
[1556]154
[1558]155        self.connect(self.graphButton,
156                     SIGNAL("clicked()"),
157                     self.save_graph)
158       
[1556]159        self.scene = QGraphicsScene()
[1558]160        self.scene_view = QualityGraphicsView(self.scene)
161        self.scene_view.setRenderHints(QPainter.Antialiasing)
[1581]162        self.scene_view.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
[1556]163        self.mainArea.layout().addWidget(self.scene_view)
[1558]164       
165        self.connect(self.scene_view,
166                     SIGNAL("view_size_changed(QSize)"),
167                     self.on_view_resize)
[1556]168
[1558]169        self._disable_updates = False
[1561]170        self._cached_distances = {}
171        self._base_index_hints = {}
[1558]172        self.main_widget = None
173       
[1556]174        self.resize(800, 600)
175
176    def clear(self):
177        """Clear the widget state.
178        """
179        self.data = None
180        self.distances = None
181        self.groups = None
[1558]182        self.unique_pos = None
183       
184        with disable_updates(self):
185            self.split_by_model[:] = []
186            self.sort_by_model[:] = []
[1581]187       
188        self.main_widget = None
[1556]189        self.scene.clear()
[1558]190        self.info_box.setText("\n")
[1561]191        self._cached_distances = {}
[1556]192
193    def set_data(self, data=None):
194        """Set input experiment data.
195        """
[1564]196        self.clear()
[1566]197       
198        self.error(0)
199        self.warning(0)
200       
201        if data is not None:
202            keys = self.get_suitable_keys(data)
203            if not keys:
204                self.error(0, "Data has no suitable feature labels.")
205                data = None
206               
[1556]207        self.data = data
208
209    def handleNewSignals(self):
210        """Called after all signals have been set.
211        """
212        if self.data:
213            self.on_new_data()
214        else:
[1558]215            self.closeContext("")
[1556]216            self.clear()
217
218    def update_label_candidates(self):
219        """Update the label candidates selection GUI
220        (Group/Sort By views).
221       
222        """
[1558]223        keys = self.get_suitable_keys(self.data)
224        with disable_updates(self):
225            self.split_by_model[:] = keys
226            self.sort_by_model[:] = keys
227       
228    def get_suitable_keys(self, data):
[1560]229        """ Return suitable attr label keys from the data where
230        the key has at least two unique values in the data.
[1558]231       
232        """
233        attrs = [attr.attributes.items() for attr in data.domain.attributes]
234        attrs  = reduce(list.__add__, attrs, [])
235        # in case someone put non string values in attributes dict
236        attrs = [(str(key), str(value)) for key, value in attrs]
237        attrs = set(attrs)
238        values = defaultdict(set)
239        for key, value in attrs:
240            values[key].add(value)
241        keys = [key for key in values if len(values[key]) > 1]
242        return keys
[1556]243
244    def selected_split_by_labels(self):
245        """Return the current selected split labels.
246        """
[1558]247        sel_m = self.split_by_view.selectionModel()
248        indices = [r.row() for r in sel_m.selectedRows()]
249        return [self.sort_by_model[i] for i in indices]
[1556]250
251    def selected_sort_by_labels(self):
252        """Return the current selected sort labels
253        """
[1558]254        sel_m = self.sort_by_view.selectionModel()
255        indices = [r.row() for r in sel_m.selectedRows()]
256        return [self.sort_by_model[i] for i in indices]
[1556]257
258    def selected_distance(self):
259        """Return the selected distance function.
260        """
261        return self.DISTANCE_FUNCTIONS[self.selected_distance_index][1]
[1558]262   
[1561]263    def selected_base_group_index(self):
264        """Return the selected base group index
[1560]265        """
[1566]266        return self.base_group_index
[1561]267   
268    def selected_base_indices(self, base_group_index=None):
269        indices = []
270        for g, ind in self.groups:
271            if base_group_index is None:
272                label = group_label(self.selected_split_by_labels(), g)
[1563]273                ind = [i for i in ind if i is not None]
[1561]274                i = self._base_index_hints.get(label, ind[0] if ind else None)
275            else:
276                i = ind[base_group_index]
277            indices.append(i)
278        return indices
[1556]279
280    def on_new_data(self):
281        """We have new data and need to recompute all.
282        """
[1558]283        self.closeContext("")
284       
[1556]285        self.update_label_candidates()
[1573]286        self.info_box.setText("%s genes \n%s experiments" % (
[1558]287                                len(self.data), 
288                                len(self.data.domain.attributes)
289                                )
290                              )
291       
[1567]292        self.base_group_index = 0
293       
[1558]294        keys = self.get_suitable_keys(self.data)
295        self.openContext("", keys)
296       
297        ## Restore saved context
298        context = self.currentContexts[""]
[1560]299        split_by_labels = getattr(context, "split_by_labels", set())
[1558]300        sort_by_labels = getattr(context, "sort_by_labels", set())
301       
302        def select(model, selection_model, selected_items):
[1560]303            """Select items in a Qt item model view
304            """
[1558]305            all_items = list(model)
306            try:
307                indices = [all_items.index(item) for item in selected_items]
308            except:
309                indices = []
310            for ind in indices:
[1560]311                selection_model.select(model.index(ind), 
312                                       QItemSelectionModel.Select)
[1558]313               
314        with disable_updates(self):
315            select(self.split_by_view.model(),
316                   self.split_by_view.selectionModel(),
317                   split_by_labels)
318           
319            select(self.sort_by_view.model(),
320                   self.sort_by_view.selectionModel(),
321                   sort_by_labels)
[1567]322       
323        with widget_disable(self):
324            self.split_and_update()
[1558]325       
326    def on_split_key_changed(self, *args):
[1560]327        """Split key has changed
328        """
329        with widget_disable(self):
[1558]330            if not self._disable_updates:
[1566]331                self.base_group_index = 0
[1558]332                context = self.currentContexts[""]
333                context.split_by_labels = self.selected_split_by_labels()
334                self.split_and_update()
335   
336    def on_sort_key_changed(self, *args):
[1560]337        """Sort key has changed
338        """
339        with widget_disable(self):
[1558]340            if not self._disable_updates:
[1566]341                self.base_group_index = 0
[1558]342                context = self.currentContexts[""]
343                context.sort_by_labels = self.selected_sort_by_labels()
344                self.split_and_update()
345       
346    def on_distance_measure_changed(self):
[1560]347        """Distance measure has changed
348        """
[1581]349        if self.data is not None:
350            with widget_disable(self):
351                self.update_distances()
352                self.replot_experiments()
[1558]353       
354    def on_view_resize(self, size):
[1560]355        """The view with the quality plot has changed
356        """
[1558]357        if self.main_widget:
358            current = self.main_widget.size()
[1581]359            self.main_widget.resize(size.width() - 6, 
[1558]360                                    current.height())
361           
362            self.scene.setSceneRect(self.scene.itemsBoundingRect())
363       
364    def on_rug_item_clicked(self, item):
[1560]365        """An ``item`` in the quality plot has been clicked.
366        """
[1561]367        update = False
368        sort_by_labels = self.selected_sort_by_labels()
369        if sort_by_labels and item.in_group:
370            ## The item is part of the group
[1566]371            if item.group_index != self.base_group_index:
372                self.base_group_index = item.group_index
[1561]373                update = True
374           
375        else:
376            if sort_by_labels:
377                # If the user clicked on an background item it
378                # invalidates the sorted labels selection
379                with disable_updates(self):
380                    self.sort_by_view.selectionModel().clear()
381                    update = True
382                   
383            index = item.index
384            group = item.group
385            label = group_label(self.selected_split_by_labels(), group)
386           
387            if self._base_index_hints.get(label, 0) != index:
388                self._base_index_hints[label] = index
389                update = True
390           
391        if update:
[1567]392            with widget_disable(self):
393                self.split_and_update()
[1558]394       
[1556]395    def split_and_update(self):
396        """
397        Split the data based on the selected sort/split labels
398        and update the quality plot.
399       
400        """
[1561]401        split_labels = self.selected_split_by_labels()
402        sort_labels = self.selected_sort_by_labels()
[1566]403       
404        self.warning(0)
405        if not split_labels:
406            self.warning(0, "No separate by label selected.")
407           
[1558]408        self.groups, self.unique_pos = \
[1561]409                exp.separate_by(self.data, split_labels,
410                                consider=sort_labels,
[1558]411                                add_empty=True)
412       
413       
414        self.groups = sorted(self.groups.items(),
415                             key=lambda t: map(float_if_posible, t[0]))
416        self.unique_pos = sorted(self.unique_pos.items(),
417                                 key=lambda t: map(float_if_posible, t[0]))
[1556]418       
[1563]419       
[1558]420        if self.groups:
[1561]421            if sort_labels:
422                group_base = self.selected_base_group_index()
423                base_indices = self.selected_base_indices(group_base)
424            else:
425                base_indices = self.selected_base_indices()
426            self.update_distances(base_indices)
[1558]427            self.replot_experiments()
[1556]428
[1561]429    def get_cached_distances(self, measure):
430        if measure not in self._cached_distances:
431            attrs = self.data.domain.attributes
432            mat = Orange.misc.SymMatrix(len(attrs))
433            self._cached_distances[measure] = \
434                (mat, set(zip(range(len(attrs)), range(len(attrs)))))
435           
436        return self._cached_distances[measure]
[1566]437       
[1561]438    def get_cached_distance(self, measure, i, j):
439        matrix, computed = self.get_cached_distances(measure)
440        key = (i, j) if i < j else (j, i) 
441        if key in computed:
442            return matrix[i, j]
443        else:
444            return None
445       
446    def get_distance(self, measure, i, j):
447        d = self.get_cached_distance(measure, i, j)
448        if d is None:
449            vec_i = exp.linearize(self.data, [i])
450            vec_j = exp.linearize(self.data, [j])
451            d = distance(vec_i, vec_j)
452            mat, computed = self.get_cached_distances(measure)
453            mat[i, j] = d
454            key = key = (i, j) if i < j else (j, i)
455            computed.add(key)
456        return d
457   
458    def store_distance(self, measure, i, j, dist):
459        matrix, computed = self.get_cached_distances(measure)
460        key = key = (i, j) if i < j else (j, i)
461        matrix[i, j] = dist
462        computed.add(key)
463       
464    def update_distances(self, base_indices=()):
[1556]465        """Recompute the experiment distances.
466        """
467        distance = self.selected_distance()
[1561]468        if base_indices == ():
469            base_group_index = self.selected_base_group_index()
470            base_indices = [ind[base_group_index] \
471                            for _, ind in self.groups]
[1563]472           
[1561]473        assert(len(base_indices) == len(self.groups)) 
474       
[1556]475        base_distances = []
[1561]476        attributes = self.data.domain.attributes
[1558]477        pb = OWGUI.ProgressBar(self, len(self.groups) * \
[1561]478                               len(attributes))
479       
480        cached_distances, filled_set = self.get_cached_distances(distance)
481       
482        for (group, indices), base_index in zip(self.groups, base_indices):
[1556]483            # Base column of the group
[1561]484            if base_index is not None:
485                base_vec = exp.linearize(self.data, [base_index])
[1558]486                distances = []
[1561]487                # Compute the distances between base column
[1558]488                # and all the rest data columns.
[1561]489                for i in range(len(attributes)):
490                    if i == base_index:
[1558]491                        distances.append(0.0)
[1561]492                    elif self.get_cached_distance(distance, i, base_index) is not None:
493                        distances.append(self.get_cached_distance(distance, i, base_index))
[1558]494                    else:
495                        vec_i = exp.linearize(self.data, [i])
[1561]496                        dist = distance(base_vec, vec_i)
497                        self.store_distance(distance, i, base_index, dist)
498                        distances.append(dist)
[1558]499                    pb.advance()
500                   
501                base_distances.append(distances)
502            else:
503                base_distances.append(None)
[1561]504               
[1558]505        pb.finish()
[1556]506        self.distances = base_distances
507
508    def replot_experiments(self):
[1561]509        """Replot the whole quality plot.
[1556]510        """
511        self.scene.clear()
512        labels = []
[1562]513       
[1558]514        max_dist = numpy.max(filter(None, self.distances))
[1556]515        rug_widgets = []
[1558]516       
517        group_pen = QPen(QColor(0, 0, 0))
518        group_pen.setWidth(2)
519        group_pen.setCapStyle(Qt.RoundCap)
520        background_pen = QPen(QColor(0, 0, 250, 150))
521        background_pen.setWidth(1)
522        background_pen.setCapStyle(Qt.RoundCap)
523       
524        main_widget = QualityControlWidget()
525        layout = QGraphicsGridLayout()
526        split_by = self.selected_split_by_labels()
527        sort_by = self.selected_sort_by_labels()
528        attributes = self.data.domain.attributes
[1556]529        if self.data:
530            for (group, indices), dist_vec in zip(self.groups, self.distances):
531                indices_set = set(indices)
532                rug_items = []
[1558]533                if dist_vec is not None:
534                    for i, attr in enumerate(attributes):
535                        # Is this a within group distance or background
536                        in_group = i in indices_set
537                        if in_group:
538                            rug_item = ClickableRugItem(dist_vec[i] / max_dist,
539                                           1.0, self.on_rug_item_clicked)
540                            rug_item.setPen(group_pen)
[1561]541                            tooltip = experiment_description(attr)
[1558]542                            rug_item.setToolTip(tooltip)
[1561]543                            rug_item.group_index = indices.index(i)
[1581]544                            rug_item.setZValue(rug_item.zValue() + 1)
[1558]545                        else:
546                            rug_item = ClickableRugItem(dist_vec[i] / max_dist,
[1561]547                                           0.85, self.on_rug_item_clicked)
548                            rug_item.setPen(background_pen)
549                            tooltip = experiment_description(attr)
[1558]550                            rug_item.setToolTip(tooltip)
[1561]551                           
552                        rug_item.group = group
553                        rug_item.index = i
554                        rug_item.in_group = in_group
555                       
[1558]556                        rug_items.append(rug_item)
[1556]557                   
558                rug_widget = RugGraphicsWidget()
559                rug_widget.set_rug(rug_items)
[1558]560               
[1556]561                rug_widgets.append(rug_widget)
562               
[1558]563                label = group_label(self.selected_split_by_labels(), group)
[1562]564                label_item = QGraphicsSimpleTextItem(label, main_widget)
[1558]565                label_item = GraphicsSimpleTextLayoutItem(label_item)
566                label_item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
567                labels.append(label_item)
568       
569        for i, (label, w) in enumerate(zip(labels, rug_widgets)):
570            layout.addItem(label, i, 0, Qt.AlignVCenter)
571            layout.addItem(w, i, 1)
572            layout.setRowMaximumHeight(i, 30)
[1556]573           
574        main_widget.setLayout(layout)
575        self.scene.addItem(main_widget)
[1558]576        main_widget.show()
577        self.main_widget = main_widget
578        self.rug_widgets = rug_widgets
579        self.labels = labels
580        self.on_view_resize(self.scene_view.size())
[1556]581       
[1558]582    def save_graph(self):
[1632]583        from Orange.OrangeWidgets.OWDlgs import OWChooseImageSizeDlg
[1558]584        dlg = OWChooseImageSizeDlg(self.scene, parent=self)
585        dlg.exec_()
[1556]586
587
588class RugGraphicsWidget(QGraphicsWidget):
589    def __init__(self, parent=None, rug=None):
590        QGraphicsWidget.__init__(self, parent)
591        self.rug_items = []
592        self.set_rug(rug)
[1558]593        self.setMaximumHeight(30)
594        self.setMinimumHeight(30)
595       
[1556]596    def clear(self):
597        """
598        Clear all rug items from this widget and remove them
599        from the scene.
600         
601        """
602        for item in self.rug_items:
603            item.setParent(None)
604            if self.scene() is not None:
605                self.scene().removeItem(item)
606
607    def set_rug(self, rug):
608        """
609        Set the rug items.
610       
611        ``rug`` must be a list of floats or already initialized
612        instances of RugItem. The widget takes ownership of all
613        items.
614         
615        """
616        rug = rug if rug is not None else []
617        self.clear()
618        self.add_rug(rug)
619
620    def add_rug(self, rug):
621        """
622        Add rug items.
623       
624        See :obj:`set_rug`
625       
626        """
627        items = []
628        for item in rug:
629            if isinstance(item, float):
630                item = RugItem(value=item)
631                items.append(item)
632            elif isinstance(item, RugItem):
633                items.append(item)
634
635        for item in items:
636            item.setParentItem(self)
637
638        self.rug_items.extend(items)
639
640        self.update_rug_geometry()
641       
642    def update_rug_geometry(self):
[1560]643        """Recompute the rug items positions within this widget.
644        """
[1556]645        size = self.size()
646        height = size.height()
647        width = size.width()
648       
649        for item in self.rug_items:
[1558]650            offset = (1.0 - item.height) * height / 2.0
[1556]651            item.setPos(width * item.value, 0)
[1558]652            item.setLine(0., offset, 0., height - offset)
[1556]653
654    def resizeEvent(self, event):
[1560]655        """Reimplemented from QGraphicsWidget
656        """
[1556]657        QGraphicsWidget.resizeEvent(self, event)
658        self.update_rug_geometry()
659
660    def setGeometry(self, geom):
[1560]661        """Reimplemented from QGraphicsWidget
662        """
[1556]663        QGraphicsWidget.setGeometry(self, geom)
664
665
666class RugItem(QGraphicsLineItem):
[1558]667    def __init__(self, value, height):
[1556]668        QGraphicsLineItem.__init__(self)
669        self.value = value
[1558]670        self.height = height
[1556]671
672    def set_height(self, height):
[1558]673        """Set the height of this item (in ratio of the rug height)
[1556]674        """
675        self.height = height
[1558]676       
677class ClickableRugItem(RugItem):
678    def __init__(self, value, height, on_pressed):
679        RugItem.__init__(self, value, height)
680        self.on_pressed = on_pressed
681        self.setAcceptedMouseButtons(Qt.LeftButton)
682        self.setAcceptHoverEvents(True)
683       
684    def mousePressEvent(self, event):
685        if event.button() == Qt.LeftButton and self.on_pressed:
686            self.on_pressed(self)
687           
688    def hoverEnterEvent(self, event):
689        pen = QPen(self.pen())
690        pen.setWidthF(3)
691        self.setPen(pen)
692        return RugItem.hoverEnterEvent(self, event)
693   
694    def hoverLeaveEvent(self, event):
695        pen = QPen(self.pen())
696        pen.setWidth(2)
697        self.setPen(pen)
698        return RugItem.hoverLeaveEvent(self, event)
[1556]699
700
[1558]701class QualityGraphicsView(QGraphicsView):
702    def resizeEvent(self, event):
703        QGraphicsView.resizeEvent(self, event)
704        self.emit(SIGNAL("view_size_changed(QSize)"),
705                  event.size())
[1556]706
[1558]707
708class QualityControlWidget(QGraphicsWidget):
709    if DEBUG:
710        def paint(self, painter, options, widget=0):
711            rect =  self.geometry()
712            rect.translate(-self.pos())
713            painter.drawRect(rect)
714           
[1556]715if __name__ == "__main__":
716    app = QApplication(sys.argv)
717    w = OWQualityControl()
[1558]718#    data = Orange.data.Table("doc:dicty-abc-sample.tab")
719    data = Orange.data.Table("doc:pipa.tab")
[1556]720
721    w.set_data(data)
722    w.show()
723    w.handleNewSignals()
724    app.exec_()
[1558]725    w.set_data(None)
726    w.handleNewSignals()
727    w.saveSettings()
Note: See TracBrowser for help on using the repository browser.