source: orange/Orange/OrangeWidgets/Data/OWPreprocess.py @ 11748:467f952c108d

Revision 11748:467f952c108d, 31.6 KB checked in by blaz <blaz.zupan@…>, 6 months ago (diff)

Changes in headers, widget descriptions text.

Line 
1from OWWidget import *
2from OWItemModels import PyListModel, ListSingleSelectionModel, ModelActionsWidget
3import OWGUI, OWGUIEx
4
5import orange
6import orngWrap
7import orngSVM
8
9import sys
10
11from Orange.data import preprocess
12
13NAME = "Preprocess"
14DESCRIPTION = """Supports construction of data preprocessors.
15Can apply them on input data sets or offer them to predictors."""
16LONG_DESCRIPTION = ""
17ICON = "icons/Preprocess.svg"
18PRIORITY = 2105
19AUTHOR = "Ales Erjavec"
20AUTHOR_EMAIL = "ales.erjavec(@at@)fri.uni-lj.si"
21INPUTS = [("Data", Orange.data.Table, "setData")]
22OUTPUTS = [("Preprocess", orngWrap.PreprocessedLearner, ),
23           ("Preprocessed Data", Orange.data.Table, )]
24
25
26def _gettype(obj):
27    """ Return type of obj. If obj is type return obj.
28    """
29    if isinstance(obj, type):
30        return obj
31    else:
32        return type(obj)
33
34
35def _pyqtProperty(type, **kwargs):
36    # check for Qt version, 4.4 supports only C++ classes
37    if qVersion() >= "4.5":
38        return pyqtProperty(type, **kwargs)
39    else:
40        if "user" in kwargs:
41            del kwargs["user"]
42        return property(**kwargs)
43
44
45## Preprocessor item editor widgets
46class BaseEditor(OWBaseWidget):
47    def __init__(self, parent=None):
48        OWBaseWidget.__init__(self, parent)
49        self.setLayout(QVBoxLayout())
50        self.layout().setContentsMargins(0,0,0,0)
51       
52    def keyPressEvent(self, event):
53        if event.key() == Qt.Key_Escape:
54            event.ignore()
55           
56class DiscretizeEditor(BaseEditor):
57    DISCRETIZERS = [("Entropy-MDL discretization", orange.EntropyDiscretization, {}),
58                    ("Equal frequency discretization", orange.EquiDistDiscretization, {"numberOfIntervals":3}),
59                    ("Equal width discretization", orange.EquiNDiscretization, {"numberOfIntervals":3}),
60                    ("Remove continuous attributes", type(None), {})]
61    def __init__(self, parent=None):
62        BaseEditor.__init__(self, parent)
63        self.discInd = 0
64        self.numberOfIntervals = 3
65#        box = OWGUI.widgetBox(self, "Discretize")
66        rb = OWGUI.radioButtonsInBox(self, self, "discInd", [], box="Discretize", callback=self.onChange)
67        for label, _, _ in self.DISCRETIZERS[:-1]:
68            OWGUI.appendRadioButton(rb, self, "discInd", label)
69        self.sliderBox = OWGUI.widgetBox(OWGUI.indentedBox(rb, sep=OWGUI.checkButtonOffsetHint(rb.buttons[-1])), "Num. of intervals (for equal width/frequency)")
70        OWGUI.hSlider(self.sliderBox, self, "numberOfIntervals", callback=self.onChange, minValue=1)
71        OWGUI.appendRadioButton(rb, self, "discInd", self.DISCRETIZERS[-1][0])
72        OWGUI.rubber(rb)
73       
74        self.updateSliderBox()
75       
76    def updateSliderBox(self):
77        self.sliderBox.setEnabled(self.discInd in [1, 2])
78       
79    def onChange(self):
80        self.updateSliderBox()
81        self.emit(SIGNAL("dataChanged"), self.data)
82       
83    def getDiscretizer(self):
84        if self.discInd == 0:
85            preprocessor = preprocess.DiscretizeEntropy(method=orange.EntropyDiscretization())
86        elif self.discInd in [1, 2]:
87            name, disc, kwds = self.DISCRETIZERS[self.discInd]
88            preprocessor = preprocess.Discretize(method=disc(**dict([(key, getattr(self, key, val)) for key,
89                                                                                              val in kwds.items()])))
90        elif self.discInd == 3:
91            preprocessor = preprocess.RemoveContinuous()
92        return preprocessor
93   
94    def setDiscretizer(self, discretizer):
95        disc = dict([(val, i) for i, (_, val, _) in enumerate(self.DISCRETIZERS)])
96        self.discInd = disc.get(_gettype(discretizer.method), 3)
97        _, d, kwargs = self.DISCRETIZERS[self.discInd]
98        for key, val in kwargs.items():
99            setattr(self, key, getattr(discretizer.method, key, val))
100           
101        self.updateSliderBox()
102       
103    data = _pyqtProperty(preprocess.Discretize,
104                        fget=getDiscretizer,
105                        fset=setDiscretizer,
106                        user=True)
107   
108class ContinuizeEditor(BaseEditor):
109    CONTINUIZERS = [("Most frequent is base", orange.DomainContinuizer.FrequentIsBase),
110                    ("One attribute per value", orange.DomainContinuizer.NValues),
111                    ("Ignore multinomial attributes", orange.DomainContinuizer.Ignore),
112                    ("Ignore all discrete attributes", None),
113                    ("Treat as ordinal", orange.DomainContinuizer.AsOrdinal),
114                    ("Divide by number of values",orange.DomainContinuizer.AsNormalizedOrdinal)]
115   
116    TREATMENT_TO_IND = dict([(val, i) for i, (_, val) in enumerate(CONTINUIZERS)])
117   
118    def __init__(self, parent=None):
119        BaseEditor.__init__(self, parent)
120        self.contInd = 0
121       
122        b = OWGUI.radioButtonsInBox(self, self, "contInd", [name for name, _ in self.CONTINUIZERS], box="Continuize", callback=self.onChange)
123        OWGUI.rubber(b)
124       
125    def onChange(self):
126        self.emit(SIGNAL("dataChanged"), self.data)
127       
128    def getContinuizer(self):
129        if self.contInd in [0, 1, 2, 4, 5]:
130            preprocessor = preprocess.Continuize(multinomialTreatment=self.CONTINUIZERS[self.contInd][1])
131        elif self.contInd == 3:
132            preprocessor = preprocess.RemoveDiscrete()
133        return preprocessor
134   
135    def setContinuizer(self, continuizer):
136        if isinstance(continuizer, preprocess.RemoveDiscrete):
137            self.contInd = 3 #Ignore all discrete
138        elif isinstance(continuizer,preprocess.Continuize):
139            self.contInd = self.TREATMENT_TO_IND.get(continuizer.multinomialTreatment, 3)
140   
141    data = _pyqtProperty(preprocess.Continuize,
142                        fget=getContinuizer,
143                        fset=setContinuizer,
144                        user=True)
145   
146class ImputeEditor(BaseEditor):
147    IMPUTERS = [("Average/Most frequent", orange.MajorityLearner),
148                ("Model-based imputer", orange.BayesLearner),
149                ("Random values", orange.RandomLearner),
150                ("Remove examples with missing values", None)]
151   
152    def __init__(self, parent):
153        BaseEditor.__init__(self, parent)
154       
155        self.methodInd = 0
156        b = OWGUI.radioButtonsInBox(self, self, "methodInd", [label for label, _ in self.IMPUTERS], box="Impute", callback=self.onChange)
157        OWGUI.rubber(b)
158       
159    def onChange(self):
160        self.emit(SIGNAL("dataChanged"), self.data)
161       
162    def getImputer(self):
163        if self.methodInd in [0, 1, 2]:
164            learner = self.IMPUTERS[self.methodInd][1]()
165            imputer = preprocess.ImputeByLearner(learner=learner)
166        elif self.methodInd == 3:
167            imputer = preprocess.DropMissing()
168        return imputer
169           
170   
171    def setImputer(self, imputer):
172        self.methodInd = 0
173        if isinstance(imputer, preprocess.ImputeByLearner):
174            learner = imputer.learner
175            dd = dict([(t, i) for i, (_, t) in enumerate(self.IMPUTERS)])
176            self.methodInd = dd.get(_gettype(learner), 0)
177        elif isinstance(imputer, preprocess.DropMissing):
178            self.methodInd = 3
179           
180    data = _pyqtProperty(preprocess.ImputeByLearner,
181                        fget=getImputer,
182                        fset=setImputer,
183                        user=True)
184   
185class FeatureSelectEditor(BaseEditor):
186    MEASURES = [("ReliefF", orange.MeasureAttribute_relief),
187                ("Information Gain", orange.MeasureAttribute_info),
188                ("Gain ratio", orange.MeasureAttribute_gainRatio),
189                ("Gini Gain", orange.MeasureAttribute_gini),
190                ("Log Odds Ratio", orange.MeasureAttribute_logOddsRatio),
191                ("Linear SVM weights", orngSVM.MeasureAttribute_SVMWeights)]
192   
193    FILTERS = [preprocess.FeatureSelection.bestN,
194               preprocess.FeatureSelection.bestP]
195   
196    def __init__(self, parent=None):
197        BaseEditor.__init__(self, parent)
198       
199        self.measureInd = 0
200        self.selectBy = 0
201        self.bestN = 10
202        self.bestP = 10
203       
204        box = OWGUI.radioButtonsInBox(self, self, "selectBy", [], "Feature selection", callback=self.onChange)
205       
206        OWGUI.comboBox(box, self, "measureInd",  items= [name for (name, _) in self.MEASURES], label="Measure", callback=self.onChange)
207       
208        hbox1 = OWGUI.widgetBox(box, orientation="horizontal", margin=0)
209        rb1 = OWGUI.appendRadioButton(box, self, "selectBy", "Best", insertInto=hbox1, callback=self.onChange)
210        self.spin1 = OWGUI.spin(OWGUI.widgetBox(hbox1), self, "bestN", 1, 10000, step=1, controlWidth=75, callback=self.onChange, posttext="features")
211        OWGUI.rubber(hbox1)
212       
213        hbox2 = OWGUI.widgetBox(box, orientation="horizontal", margin=0)
214        rb2 = OWGUI.appendRadioButton(box, self, "selectBy", "Best", insertInto=hbox2, callback=self.onChange)
215        self.spin2 = OWGUI.spin(OWGUI.widgetBox(hbox2), self, "bestP", 1, 100, step=1, controlWidth=75, callback=self.onChange, posttext="% features")
216        OWGUI.rubber(hbox2)
217       
218        self.updateSpinStates()
219       
220        OWGUI.rubber(box)
221       
222    def updateSpinStates(self):
223        self.spin1.setDisabled(bool(self.selectBy))
224        self.spin2.setDisabled(not bool(self.selectBy))
225       
226    def onChange(self):
227        self.updateSpinStates()
228        self.emit(SIGNAL("dataChanged"), self.data)
229       
230    def setFeatureSelection(self, fs):
231        select = dict([(filter, i) for i, filter in enumerate(self.FILTERS)])
232       
233        measures = dict([(measure, i) for i, (_, measure) in enumerate(self.MEASURES)])
234       
235        self.selectBy = select.get(fs.filter, 0)
236        self.measureInd = measures.get(fs.measure, 0)
237        if self.selectBy:
238            self.bestP = fs.limit
239        else:
240            self.bestN = fs.limit
241           
242        self.updateSpinStates()
243   
244    def getFeatureSelection(self):
245        return preprocess.FeatureSelection(measure=self.MEASURES[self.measureInd][1],
246                                             filter=self.FILTERS[self.selectBy],
247                                             limit=self.bestP if self.selectBy  else self.bestN)
248   
249    data = _pyqtProperty(preprocess.FeatureSelection,
250                        fget=getFeatureSelection,
251                        fset=setFeatureSelection,
252                        user=True)
253       
254class SampleEditor(BaseEditor):
255    FILTERS = [preprocess.Sample.selectNRandom,
256               preprocess.Sample.selectPRandom]
257    def __init__(self, parent=None):
258        BaseEditor.__init__(self, parent)
259        self.methodInd = 0
260        self.sampleN = 100
261        self.sampleP = 25
262       
263        box = OWGUI.radioButtonsInBox(self, self, "methodInd", [], box="Sample", callback=self.onChange)
264       
265        w1 = OWGUI.widgetBox(box, orientation="horizontal", margin=0)
266        rb1 = OWGUI.appendRadioButton(box, self, "methodInd", "Sample", insertInto=w1)
267        self.sb1 = OWGUI.spin(OWGUI.widgetBox(w1), self, "sampleN", min=1, max=100000, step=1, controlWidth=75, callback=self.onChange, posttext="data instances")
268        OWGUI.rubber(w1)
269       
270        w2 = OWGUI.widgetBox(box, orientation="horizontal", margin=0)
271        rb2 = OWGUI.appendRadioButton(box, self, "methodInd", "Sample", insertInto=w2)
272        self.sb2 = OWGUI.spin(OWGUI.widgetBox(w2), self, "sampleP", min=1, max=100, step=1, controlWidth=75, callback=self.onChange, posttext="% data instances")
273        OWGUI.rubber(w2)
274       
275        self.updateSpinStates()
276       
277        OWGUI.rubber(box)
278       
279    def updateSpinStates(self):
280        self.sb1.setEnabled(not self.methodInd)
281        self.sb2.setEnabled(self.methodInd)
282       
283    def onChange(self):
284        self.updateSpinStates()
285        self.emit(SIGNAL("dataChanged"), self.data)
286       
287    def getSampler(self):
288        return preprocess.Sample(filter=self.FILTERS[self.methodInd],
289                                   limit=self.sampleN if self.methodInd == 0 else self.sampleP)
290   
291    def setSampler(self, sampler):
292        filter = dict([(s, i) for i, s in enumerate(self.FILTERS)])
293        self.methodInd = filter.get(sampler.filter, 0)
294        if self.methodInd == 0:
295            self.sampleN = sampler.limit
296        else:
297            self.sampleP = sampler.limit
298           
299        self.updateSpinStates()
300           
301    data = _pyqtProperty(preprocess.Sample,
302                        fget=getSampler,
303                        fset=setSampler,
304                        user=True)
305   
306def _funcName(func):
307    return func.__name__
308   
309class PreprocessorItemDelegate(QStyledItemDelegate):
310       
311    #Preprocessor name replacement rules
312    REPLACE = {preprocess.Discretize: "Discretize ({0.method})",
313               preprocess.DiscretizeEntropy: "Discretize (entropy)",
314               preprocess.RemoveContinuous: "Discretize (remove continuous)",
315               preprocess.Continuize: "Continuize ({0.multinomialTreatment})",
316               preprocess.RemoveDiscrete: "Continuize (remove discrete)",
317               preprocess.Impute: "Impute ({0.model})",
318               preprocess.ImputeByLearner: "Impute ({0.learner})",
319               preprocess.DropMissing: "Remove missing",
320               preprocess.FeatureSelection: "Feature selection ({0.measure}, {0.filter}, {0.limit})",
321               preprocess.Sample: "Sample ({0.filter}, {0.limit})",
322               orange.EntropyDiscretization: "entropy",
323               orange.EquiNDiscretization: "freq, {0.numberOfIntervals}",
324               orange.EquiDistDiscretization: "width, {0.numberOfIntervals}",
325               orange.RandomLearner: "random", 
326               orange.BayesLearner: "bayes  model",
327               orange.MajorityLearner: "average",
328               orange.MeasureAttribute_relief: "ReliefF",
329               orange.MeasureAttribute_info: "Info gain",
330               orange.MeasureAttribute_gainRatio: "Gain ratio",
331               orange.MeasureAttribute_gini: "Gini",
332               orange.MeasureAttribute_logOddsRatio: "Log Odds",
333               orngSVM.MeasureAttribute_SVMWeights: "Linear SVM weights",
334               type(lambda : None): _funcName}
335   
336    import re
337    INSERT_RE = re.compile(r"{0\.(\w+)}")
338   
339    def __init__(self, parent=None):
340        QStyledItemDelegate.__init__(self, parent)
341       
342    def displayText(self, value, locale):
343        try:
344            p = value.toPyObject()
345            return self.format(p)
346        except Exception, ex:
347            return repr(ex)
348       
349    def format(self, obj):
350        def replace(match):
351            attr = match.groups()[0]
352            if hasattr(obj, attr):
353                return self.format(getattr(obj, attr))
354       
355        text = self.REPLACE.get(_gettype(obj), str(obj))
356        if hasattr(text, "__call__"):
357            return text(obj)
358        else:
359            return self.INSERT_RE.sub(replace, text)
360       
361class PreprocessorSchema(object):
362    """ Preprocessor schema holds a saved a named preprocessor list for display.
363    """
364    def __init__(self, name="New schema", preprocessors=[], selectedPreprocessor=0, modified=False):
365        self.name = name
366        self.preprocessors = preprocessors
367        self.selectedPreprocessor = selectedPreprocessor
368        self.modified = modified
369       
370class PreprocessorSchemaDelegate(QStyledItemDelegate):
371    @classmethod
372    def asSchema(cls, obj):
373        if isinstance(obj, PreprocessorSchema):
374            return obj
375        elif isinstance(obj, tuple):
376            return PreprocessorSchema(*obj)
377       
378    def displayText(self, value, locale):
379        schema = self.asSchema(value.toPyObject())
380        try:
381            if schema.modified:
382                return QString("*" + schema.name)
383            else:
384                return QString(schema.name)
385        except Exception:
386            return QString("Invalid schema")
387       
388    def paint(self, painter, option, index):
389        schema = self.asSchema(index.data(Qt.DisplayRole).toPyObject())
390        if getattr(schema, "modified", False):
391            option = QStyleOptionViewItemV4(option)
392            option.palette.setColor(QPalette.Text, QColor(Qt.red))
393            option.palette.setColor(QPalette.Highlight, QColor(Qt.darkRed))
394        QStyledItemDelegate.paint(self, painter, option, index)
395       
396    def createEditor(self, parent, option, index):
397        return QLineEdit(parent)
398   
399    def setEditorData(self, editor, index):
400        schema = self.asSchema(index.data().toPyObject())
401        editor.setText(schema.name)
402       
403    def setModelData(self, editor, model, index):
404        schema = self.asSchema(index.data().toPyObject())
405        schema.name = editor.text()
406        model.setData(index, QVariant(schema))
407       
408       
409class PySortFilterProxyModel(QSortFilterProxyModel):
410    def __init__(self, filter_fmt=None, sort_fmt=None, parent=None):
411        QSortFilterProxyModel.__init__(self, parent)
412        self.filter_fmt = filter_fmt
413        self.sort_fmt = sort_fmt
414       
415    if sys.version < "2.6":
416        import re
417        INSERT_RE = re.compile(r"{0\.(\w+)}")
418        def format(self, fmt, *args, **kwargs):
419            # a simple formating function for python 2.5
420            def replace(match):
421                attr = match.groups()[0]
422                if hasattr(args[0], attr):
423                    return str(getattr(args[0], attr))
424            return self.INSERT_RE.sub(replace, fmt)
425       
426    else:
427        def format(self, fmt, *args, **kwargs):
428            return fmt.format(*args, **kwargs)
429       
430    def lessThen(self, left, right):
431        left = self.sourceModel().data(left)
432        right = self.sourceModel().data(right)
433       
434        left, right = left.toPyObject(), right.toPyObject()
435       
436        if self.sort_fmt is not None:
437            left = self.format(self.sort_fmt, left)
438            right = self.format(self.sort_fmt, right)
439       
440        return left < right
441   
442    def filterAcceptsRow(self, sourceRow, sourceParent):
443        index = self.sourceModel().index(sourceRow, 0, sourceParent)
444       
445        value = index.data().toPyObject()
446        if self.filter_fmt:
447            value = self.format(self.filter_fmt, value)
448           
449        regexp = self.filterRegExp()
450        return regexp.indexIn(str(value)) >= 0
451
452class OWPreprocess(OWWidget):
453    contextHandlers = {"": PerfectDomainContextHandler("", [""])}
454    settingsList = ["allSchemas", "lastSelectedSchemaIndex"]
455   
456    # Default preprocessors
457    preprocessors =[("Discretize", preprocess.DiscretizeEntropy, {}),
458                    ("Continuize", preprocess.Continuize, {}),
459                    ("Impute", preprocess.Impute, {}),
460                    ("Feature selection", preprocess.FeatureSelection, {}),
461                    ("Sample", preprocess.Sample, {})]
462   
463    # Editor widgets for preprocessors
464    EDITORS = {preprocess.Discretize: DiscretizeEditor,
465               preprocess.DiscretizeEntropy: DiscretizeEditor,
466               preprocess.RemoveContinuous: DiscretizeEditor,
467               preprocess.Continuize: ContinuizeEditor,
468               preprocess.RemoveDiscrete: ContinuizeEditor,
469               preprocess.Impute: ImputeEditor,
470               preprocess.ImputeByLearner: ImputeEditor,
471               preprocess.DropMissing: ImputeEditor,
472               preprocess.FeatureSelection: FeatureSelectEditor,
473               preprocess.Sample: SampleEditor,
474               type(None): QWidget}
475   
476    def __init__(self, parent=None, signalManager=None, name="Preprocess"):
477        OWWidget.__init__(self, parent, signalManager, name)
478       
479        self.inputs = [("Data", Orange.data.Table, self.setData)]
480        self.outputs = [("Preprocess", orngWrap.PreprocessedLearner),
481                        ("Preprocessed Data", Orange.data.Table)]
482       
483        self.autoCommit = False
484        self.changedFlag = False
485       
486#        self.allSchemas = [PreprocessorSchema("Default" , [Preprocessor_discretize(method=orange.EntropyDiscretization()), Preprocessor_dropMissing()])]
487        self.allSchemas = [("Default" , [preprocess.DiscretizeEntropy(method=orange.EntropyDiscretization()),
488                                         preprocess.DropMissing()], 0)]
489       
490        self.lastSelectedSchemaIndex = 0
491       
492        self.preprocessorsList =  PyListModel([], self)
493       
494        box = OWGUI.widgetBox(self.controlArea, "Preprocessors", addSpace=True)
495        box.layout().setSpacing(1)
496       
497        self.setStyleSheet("QListView::item { margin: 1px;}")
498        self.preprocessorsListView = QListView()
499        self.preprocessorsListSelectionModel = ListSingleSelectionModel(self.preprocessorsList, self)
500        self.preprocessorsListView.setItemDelegate(PreprocessorItemDelegate(self))
501        self.preprocessorsListView.setModel(self.preprocessorsList)
502       
503        self.preprocessorsListView.setSelectionModel(self.preprocessorsListSelectionModel)
504        self.preprocessorsListView.setSelectionMode(QListView.SingleSelection)
505       
506        self.connect(self.preprocessorsListSelectionModel, SIGNAL("selectedIndexChanged(QModelIndex)"), self.onPreprocessorSelection)
507        self.connect(self.preprocessorsList, SIGNAL("dataChanged(QModelIndex, QModelIndex)"), lambda arg1, arg2: self.commitIf)
508       
509        box.layout().addWidget(self.preprocessorsListView)
510       
511        self.addPreprocessorAction = QAction("+",self)
512        self.addPreprocessorAction.pyqtConfigure(toolTip="Add a new preprocessor to the list")
513        self.removePreprocessorAction = QAction("-", self)
514        self.removePreprocessorAction.pyqtConfigure(toolTip="Remove selected preprocessor from the list")
515        self.removePreprocessorAction.setEnabled(False)
516       
517        self.connect(self.preprocessorsListSelectionModel, SIGNAL("selectedIndexChanged(QModelIndex)"), lambda index:self.removePreprocessorAction.setEnabled(index.isValid()))
518       
519        actionsWidget = ModelActionsWidget([self.addPreprocessorAction, self.removePreprocessorAction])
520        actionsWidget.layout().setSpacing(1)
521        actionsWidget.layout().addStretch(10)
522       
523        box.layout().addWidget(actionsWidget)
524       
525        self.connect(self.addPreprocessorAction, SIGNAL("triggered()"), self.onAddPreprocessor)
526        self.connect(self.removePreprocessorAction, SIGNAL("triggered()"), self.onRemovePreprocessor)
527       
528        box = OWGUI.widgetBox(self.controlArea, "Saved Schemas", addSpace=True)
529       
530        self.schemaFilterEdit = OWGUIEx.LineEditFilter(self)
531        box.layout().addWidget(self.schemaFilterEdit)
532       
533        self.schemaList = PyListModel([], self, flags=Qt.ItemIsSelectable | Qt.ItemIsEditable| Qt.ItemIsEnabled)
534        self.schemaListProxy = PySortFilterProxyModel(filter_fmt="{0.name}", parent=self)
535        self.schemaListProxy.setFilterCaseSensitivity(Qt.CaseInsensitive)
536        self.schemaListProxy.setSourceModel(self.schemaList)
537        self.schemaListView = QListView()
538        self.schemaListView.setItemDelegate(PreprocessorSchemaDelegate(self))
539#        self.schemaListView.setModel(self.schemaList)
540        self.schemaListView.setModel(self.schemaListProxy)
541        self.connect(self.schemaFilterEdit, SIGNAL("textEdited(QString)"), self.schemaListProxy.setFilterRegExp)
542        box.layout().addWidget(self.schemaListView)
543       
544        self.schemaListSelectionModel = ListSingleSelectionModel(self.schemaListProxy, self)
545        self.schemaListView.setSelectionMode(QListView.SingleSelection)
546        self.schemaListView.setSelectionModel(self.schemaListSelectionModel)
547       
548        self.connect(self.schemaListSelectionModel, SIGNAL("selectedIndexChanged(QModelIndex)"), self.onSchemaSelection)
549       
550        self.addSchemaAction = QAction("+", self)
551        self.addSchemaAction.pyqtConfigure(toolTip="Add a new preprocessor schema")
552        self.updateSchemaAction = QAction("Update", self)
553        self.updateSchemaAction.pyqtConfigure(toolTip="Save changes made in the current schema")
554        self.removeSchemaAction = QAction("-", self)
555        self.removeSchemaAction.pyqtConfigure(toolTip="Remove selected schema")
556       
557        self.updateSchemaAction.setEnabled(False)
558        self.removeSchemaAction.setEnabled(False)
559       
560        actionsWidget = ModelActionsWidget([])
561        actionsWidget.addAction(self.addSchemaAction)
562        actionsWidget.addAction(self.updateSchemaAction).setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
563        actionsWidget.addAction(self.removeSchemaAction)
564        actionsWidget.layout().setSpacing(1)
565       
566        box.layout().addWidget(actionsWidget)
567       
568        self.connect(self.addSchemaAction, SIGNAL("triggered()"), self.onAddSchema)
569        self.connect(self.updateSchemaAction, SIGNAL("triggered()"), self.onUpdateSchema)
570        self.connect(self.removeSchemaAction, SIGNAL("triggered()"), self.onRemoveSchema)
571       
572        self.addPreprocessorsMenuActions = actions = []
573        for name, pp, kwargs in self.preprocessors:
574            action = QAction(name, self)
575            self.connect(action, SIGNAL("triggered()"), lambda pp=pp, kwargs=kwargs:self.addPreprocessor(pp(**kwargs)))
576            actions.append(action)
577           
578        box = OWGUI.widgetBox(self.controlArea, "Output")
579        cb = OWGUI.checkBox(box, self, "autoCommit", "Commit on any change", callback=self.commitIf)
580        b = OWGUI.button(box, self, "Commit", callback=self.commit, default=True)
581        OWGUI.setStopper(self, b, cb, "changedFlag", callback=self.commitIf)
582       
583        self.mainAreaStack = QStackedLayout()
584        self.stackedEditorsCache = {}
585       
586        OWGUI.widgetBox(self.mainArea, orientation=self.mainAreaStack)
587       
588        self.data = None
589        self.learner = None
590       
591        self.loadSettings()
592        self.activateLoadedSettings()
593       
594    def activateLoadedSettings(self):
595        try:
596            self.allSchemas = [PreprocessorSchemaDelegate.asSchema(obj) for obj in self.allSchemas]
597            for s in self.allSchemas:
598                s.modified = False
599            self.schemaList.wrap(self.allSchemas)
600            self.schemaListSelectionModel.select(self.schemaList.index(min(self.lastSelectedSchemaIndex, len(self.schemaList) - 1)), QItemSelectionModel.ClearAndSelect)
601            self.commit()
602        except Exception, ex:
603            print repr(ex)
604           
605    def setData(self, data=None):
606        self.data = data
607#        self.commit()
608   
609    def setLearner(self, learner=None):
610        self.learner = learner
611#        self.commit()
612
613    def handleNewSignals(self):
614        self.commit()
615       
616    def selectedSchemaIndex(self):
617        rows = self.schemaListSelectionModel.selectedRows()
618        rows = [self.schemaListProxy.mapToSource(row) for row in rows]
619        if rows:
620            return rows[0]
621        else:
622            return QModelIndex()
623   
624    def addPreprocessor(self, prep):
625        self.preprocessorsList.append(prep)
626        self.preprocessorsListSelectionModel.select(self.preprocessorsList.index(len(self.preprocessorsList)-1),
627                                                    QItemSelectionModel.ClearAndSelect)
628        self.commitIf()
629        self.setSchemaModified(True)
630   
631    def onAddPreprocessor(self):
632        action = QMenu.exec_(self.addPreprocessorsMenuActions, QCursor.pos())
633   
634    def onRemovePreprocessor(self):
635        index = self.preprocessorsListSelectionModel.selectedRow()
636        if index.isValid():
637            row = index.row()
638            del self.preprocessorsList[row]
639            newrow = min(max(row - 1, 0), len(self.preprocessorsList) - 1)
640            if newrow > -1:
641                self.preprocessorsListSelectionModel.select(self.preprocessorsList.index(newrow), QItemSelectionModel.ClearAndSelect)
642            self.commitIf()
643            self.setSchemaModified(True)
644           
645    def onPreprocessorSelection(self, index):
646        if index.isValid():
647            pp = self.preprocessorsList[index.row()]
648            self.currentSelectedIndex = index.row()
649            self.showEditWidget(pp)
650        else:
651            self.showEditWidget(None)
652       
653    def onSchemaSelection(self, index):
654        self.updateSchemaAction.setEnabled(index.isValid())
655        self.removeSchemaAction.setEnabled(index.isValid())
656        if index.isValid():
657            self.lastSelectedSchemaIndex = index.row()
658            self.setActiveSchema(index.data().toPyObject())
659   
660    def onAddSchema(self):
661        schema = list(self.preprocessorsList)
662        self.schemaList.append(PreprocessorSchema("New schema", schema, self.preprocessorsListSelectionModel.selectedRow().row()))
663        index = self.schemaList.index(len(self.schemaList) - 1)
664        index = self.schemaListProxy.mapFromSource(index)
665        self.schemaListSelectionModel.setCurrentIndex(index, QItemSelectionModel.ClearAndSelect)
666        self.schemaListView.edit(index)
667   
668    def onUpdateSchema(self):
669#        index = self.schemaListSelectionModel.selectedRow()
670        index = self.selectedSchemaIndex()
671        if index.isValid():
672            row = index.row()
673            schema = self.schemaList[row]
674            self.schemaList[row] = PreprocessorSchema(schema.name, list(self.preprocessorsList),
675                                                      self.preprocessorsListSelectionModel.selectedRow().row())
676   
677    def onRemoveSchema(self):
678#        index = self.schemaListSelectionModel.selectedRow()
679        index = self.selectedSchemaIndex()
680        if index.isValid():
681            row = index.row()
682            del self.schemaList[row]
683            newrow = min(max(row - 1, 0), len(self.schemaList) - 1)
684            if newrow > -1:
685                self.schemaListSelectionModel.select(self.schemaListProxy.mapFromSource(self.schemaList.index(newrow)),
686                                                     QItemSelectionModel.ClearAndSelect)
687               
688    def setActiveSchema(self, schema):
689        if schema.modified and hasattr(schema, "_tmp_preprocessors"):
690            self.preprocessorsList[:] = list(schema._tmp_preprocessors)
691        else:
692            self.preprocessorsList[:] = list(schema.preprocessors)
693        self.preprocessorsListSelectionModel.select(schema.selectedPreprocessor, QItemSelectionModel.ClearAndSelect)
694        self.commitIf()
695       
696    def showEditWidget(self, pp):
697        w = self.stackedEditorsCache.get(type(pp), None)
698        if w is None:
699            w = self.EDITORS[type(pp)](self.mainArea)
700            self.stackedEditorsCache[type(pp)] = w
701            self.connect(w, SIGNAL("dataChanged"), self.setEditedPreprocessor)
702            self.mainAreaStack.addWidget(w)
703        self.mainAreaStack.setCurrentWidget(w)
704        w.data = pp
705        w.show()
706       
707    def setEditedPreprocessor(self, pp):
708        self.preprocessorsList[self.preprocessorsListSelectionModel.selectedRow().row()] = pp
709       
710        self.setSchemaModified(True)
711        self.commitIf()
712#        self.onUpdateSchema()
713       
714    def setSchemaModified(self, state):
715#        index = self.schemaListSelectionModel.selectedRow()
716        index = self.selectedSchemaIndex()
717        if index.isValid():
718            row = index.row()
719            self.schemaList[row].modified = True
720            self.schemaList[row]._tmp_preprocessors = list(self.preprocessorsList)
721            self.schemaList.emitDataChanged([row])
722       
723    def commitIf(self):
724        if self.autoCommit:
725            self.commit()
726        else:
727            self.changedFlag = True
728           
729    def commit(self):
730        wrap = orngWrap.PreprocessedLearner(list(self.preprocessorsList))
731        if self.data is not None:
732            data = wrap.processData(self.data)
733            self.send("Preprocessed Data", data)
734        self.send("Preprocess", wrap)
735           
736#        self.send("Preprocessor", Preprocessor_preprocessorList(list(self.preprocessorsList)))
737        self.changedFlag = False
738       
739
740if __name__ == "__main__":
741    app = QApplication(sys.argv)
742    w = OWPreprocess()
743    w.setData(orange.ExampleTable("../../doc/datasets/iris"))
744    w.show()
745    app.exec_()
746    w.saveSettings()
747   
Note: See TracBrowser for help on using the repository browser.