source: orange/Orange/OrangeWidgets/Data/OWPreprocess.py @ 11780:df916a1a0cb1

Revision 11780:df916a1a0cb1, 31.7 KB checked in by Ales Erjavec <ales.erjavec@…>, 5 months ago (diff)

Replaced hypens with unicode minus character in GUI button labels.

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