source: orange/Orange/OrangeWidgets/Data/OWPreprocess.py @ 11096:cf7d2ae9d22b

Revision 11096:cf7d2ae9d22b, 31.5 KB checked in by Ales Erjavec <ales.erjavec@…>, 19 months ago (diff)

Added new svg icons for the widgets/categories.

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