source: orange/orange/OrangeWidgets/Data/OWPreprocess.py @ 9546:2b6cc6f397fe

Revision 9546:2b6cc6f397fe, 31.5 KB checked in by ales_erjavec <ales.erjavec@…>, 2 years ago (diff)

Renamed widget channel names in line with the new naming rules/convention.
Added backwards compatibility in orngDoc loadDocument to enable loading of schemas saved before the change.

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