source: orange/Orange/OrangeWidgets/Data/OWFile.py @ 11785:f21218ba8f91

Revision 11785:f21218ba8f91, 14.0 KB checked in by blaz <blaz.zupan@…>, 4 months ago (diff)

Updated documentation for File widget (use of stamper.py to render images).

Line 
1import os
2import sys
3import warnings
4import Orange
5from OWWidget import *
6import OWGUI
7
8NAME = "File"
9DESCRIPTION = "Reads data from an input file."
10ICON = "icons/File.svg"
11MAINTAINER = "Janez Demsar"
12MAINTAINER_EMAIL = "janez.demsar(@at@)fri.uni-lj.si"
13PRIORITY = 10
14CATEGORY = "Data"
15KEYWORDS = ["data", "file", "load", "read"]
16
17OUTPUTS = (
18    {"name": "Data",
19     "type": Orange.data.Table,
20     "doc": "Attribute-valued data set read from the input file.",
21     },
22)
23
24WIDGET_CLASS = "OWFile"
25
26
27def call(f, *args, **kwargs):
28    return f(*args, **kwargs)
29
30# Make any KernelWarning raise an error if called through the 'call' function
31# defined above.
32warnings.filterwarnings(
33    "error", ".*", Orange.core.KernelWarning,
34    __name__, call.func_code.co_firstlineno + 1
35)
36
37
38class FileNameContextHandler(ContextHandler):
39    def match(self, context, imperfect, filename):
40        return context.filename == filename and 2
41
42
43def addOrigin(examples, filename):
44    vars = examples.domain.variables + examples.domain.getmetas().values()
45    strings = [var for var in vars if isinstance(var, Orange.feature.String)]
46    dirname, basename = os.path.split(filename)
47    for var in strings:
48        if "type" in var.attributes and "origin" not in var.attributes:
49            var.attributes["origin"] = dirname
50
51
52class OWFile(OWWidget):
53    settingsList = ["recentFiles", "createNewOn", "showAdvanced"]
54    contextHandlers = {"": FileNameContextHandler()}
55
56    registeredFileTypes = [ft[:2] for ft in Orange.core.getRegisteredFileTypes()
57                           if len(ft) > 2 and ft[2]]
58    dlgFormats = (
59        'Tab-delimited files (*.tab *.txt)\n'
60        'C4.5 files (*.data)\n'
61        'Assistant files (*.dat)\n'
62        'Retis files (*.rda *.rdo)\n'
63        'Basket files (*.basket)\n' +
64        "\n".join("%s (%s)" % (ft[:2]) for ft in registeredFileTypes) +
65        "\nAll files(*.*)"
66    )
67
68    formats = {
69        ".tab": "Tab-delimited file",
70        ".txt": "Tab-delimited file",
71        ".data": "C4.5 file",
72        ".dat": "Assistant file",
73        ".rda": "Retis file",
74        ".rdo": "Retis file",
75        ".basket": "Basket file"
76    }
77    formats.update(dict((ext.lstrip("*."), name)
78                        for name, ext in registeredFileTypes))
79
80    def __init__(self, parent=None, signalManager=None):
81        OWWidget.__init__(self, parent, signalManager, "File", wantMainArea=0)
82
83        self.inputs = []
84        self.outputs = [("Data", ExampleTable)]
85
86        self.recentFiles = []
87        self.symbolDC = "?"
88        self.symbolDK = "~"
89        self.createNewOn = 1
90        self.domain = None
91        self.loadedFile = ""
92        self.showAdvanced = 0
93        self.loadSettings()
94
95        self.dataReport = None
96
97        box = OWGUI.widgetBox(self.controlArea, "Data File", addSpace=True,
98                              orientation="horizontal")
99        self.filecombo = QComboBox(box)
100        self.filecombo.setMinimumWidth(150)
101        self.filecombo.activated[int].connect(self.selectFile)
102
103        box.layout().addWidget(self.filecombo)
104        button = OWGUI.button(box, self, '...', callback=self.browse)
105        button.setIcon(self.style().standardIcon(QStyle.SP_DirOpenIcon))
106        button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
107
108        self.reloadBtn = OWGUI.button(
109            box, self, "Reload", callback=self.reload, default=True)
110
111        self.reloadBtn.setIcon(
112            self.style().standardIcon(QStyle.SP_BrowserReload)
113        )
114        self.reloadBtn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
115
116        box = OWGUI.widgetBox(self.controlArea, "Info", addSpace=True)
117        self.infoa = OWGUI.widgetLabel(box, 'No data loaded.')
118        self.infob = OWGUI.widgetLabel(box, ' ')
119        self.warnings = OWGUI.widgetLabel(box, ' ')
120
121        #Set word wrap so long warnings won't expand the widget
122        self.warnings.setWordWrap(True)
123        self.warnings.setSizePolicy(QSizePolicy.Ignored,
124                                    QSizePolicy.MinimumExpanding)
125
126        smallWidget = OWGUI.collapsableWidgetBox(
127            self.controlArea, "Advanced settings", self, "showAdvanced",
128            callback=self.adjustSize0)
129
130        box = QGroupBox("Missing Value Symbols")
131        form = QFormLayout(fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow)
132
133        form.addRow(
134            "Don't care:",
135            OWGUI.lineEdit(None, self, "symbolDC",
136                           tooltip="Default values: '~' or '*'")
137        )
138        form.addRow(
139            "Don't know:",
140            OWGUI.lineEdit(None, self, "symbolDK",
141                           tooltip="Default values: empty fields (space), "
142                                   "'?' or 'NA'")
143        )
144        box.setLayout(form)
145        smallWidget.layout().addWidget(box)
146        smallWidget.layout().addSpacing(8)
147
148        OWGUI.radioButtonsInBox(
149            smallWidget, self, "createNewOn", box="New Attributes",
150            label="Create a new attribute when existing attribute(s) ...",
151            btnLabels=["Have mismatching order of values",
152                       "Have no common values with the new (recommended)",
153                       "Miss some values of the new attribute",
154                       "... Always create a new attribute"]
155        )
156
157        OWGUI.rubber(smallWidget)
158        smallWidget.updateControls()
159
160        OWGUI.rubber(self.controlArea)
161
162        # remove missing data set names
163        def exists(path):
164            if not os.path.exists(path):
165                dirpath, basename = os.path.split(path)
166                return os.path.exists(os.path.join("./", basename))
167            else:
168                return True
169
170        self.recentFiles = filter(exists, self.recentFiles)
171        self.setFileList()
172
173        if len(self.recentFiles) > 0 and exists(self.recentFiles[0]):
174            self.openFile(self.recentFiles[0])
175
176    def adjustSize0(self):
177        qApp.processEvents()
178        QTimer.singleShot(50, self.adjustSize)
179
180    def setFileList(self):
181        self.filecombo.clear()
182        model = self.filecombo.model()
183        iconprovider = QFileIconProvider()
184        if not self.recentFiles:
185            item = QStandardItem("(none)")
186            item.setEnabled(False)
187            item.setSelectable(False)
188            model.appendRow([item])
189        else:
190            for fname in self.recentFiles:
191                item = QStandardItem(os.path.basename(fname))
192                item.setToolTip(fname)
193                item.setIcon(iconprovider.icon(QFileInfo(fname)))
194                model.appendRow(item)
195
196        self.filecombo.insertSeparator(self.filecombo.count())
197        self.filecombo.addItem("Browse documentation data sets...")
198        item = model.item(self.filecombo.count() - 1)
199        item.setEnabled(os.path.isdir(Orange.utils.environ.dataset_install_dir))
200
201    def reload(self):
202        if self.recentFiles:
203            return self.openFile(self.recentFiles[0])
204
205    def settingsFromWidgetCallback(self, handler, context):
206        context.filename = self.loadedFile
207        context.symbolDC, context.symbolDK = self.symbolDC, self.symbolDK
208
209    def settingsToWidgetCallback(self, handler, context):
210        self.symbolDC, self.symbolDK = context.symbolDC, context.symbolDK
211
212    def selectFile(self, n):
213        if n < len(self.recentFiles):
214            name = self.recentFiles[n]
215            self.recentFiles.remove(name)
216            self.recentFiles.insert(0, name)
217        elif n >= 0:
218            self.browseDocDatasets()
219
220        if len(self.recentFiles) > 0:
221            self.setFileList()
222            self.openFile(self.recentFiles[0])
223
224    def browseDocDatasets(self):
225        """
226        Display a FileDialog with the documentation datasets folder.
227        """
228        self.browse(Orange.utils.environ.dataset_install_dir)
229
230    def browse(self, startpath=None):
231        """
232        Display a FileDialog and select a file to open.
233        """
234        if startpath is None:
235            if len(self.recentFiles) == 0 or self.recentFiles[0] == "(none)":
236                startpath = os.path.expanduser("~/")
237            else:
238                startpath = self.recentFiles[0]
239
240        filename = QFileDialog.getOpenFileName(
241            self, 'Open Orange Data File', startpath, self.dlgFormats)
242
243        filename = unicode(filename)
244
245        if filename == "":
246            return
247
248        if filename in self.recentFiles:
249            self.recentFiles.remove(filename)
250        self.recentFiles.insert(0, filename)
251
252        self.setFileList()
253
254        self.openFile(filename)
255
256    # Open a file, create data from it and send it over the data channel
257    def openFile(self, fn):
258        self.error()
259        self.warning()
260        self.information()
261
262        if not os.path.exists(fn):
263            basename = os.path.basename(fn)
264            if os.path.exists(os.path.join("./", basename)):
265                fn = os.path.join("./", basename)
266                self.information("Loading '%s' from the current directory." %
267                                 basename)
268
269        self.closeContext()
270        self.loadedFile = ""
271
272        if fn == "(none)":
273            self.send("Data", None)
274            self.infoa.setText("No data loaded")
275            self.infob.setText("")
276            self.warnings.setText("")
277            return
278
279        self.openContext("", fn)
280
281        self.loadedFile = ""
282
283        argdict = {"createNewOn": 3 - self.createNewOn}
284        if self.symbolDK:
285            argdict["DK"] = str(self.symbolDK)
286        if self.symbolDC:
287            argdict["DC"] = str(self.symbolDC)
288
289        data = None
290        try:
291            data = call(Orange.data.Table, fn, **argdict)
292            self.loadedFile = fn
293        except Exception as ex:
294            if "is being loaded as" in str(ex):
295                try:
296                    data = Orange.data.Table(fn, **argdict)
297                    self.warning(0, str(ex))
298                except:
299                    pass
300
301            if data is None:
302                self.error(str(ex))
303                self.infoa.setText('Data was not loaded due to an error.')
304                self.infob.setText('Error:')
305                self.warnings.setText(str(ex))
306                return
307
308        self.infoa.setText("%d data instance%s, " %
309                           (len(data), 's' if len(data) > 1 else '') +
310                           "%d feature%s, " %
311                           (len(data.domain.attributes),
312                            's' if len(data.domain.attributes) > 1 else '') +
313                           '%d meta attribute%s.' %
314                           (len(data.domain.getmetas()),
315                            's' if len(data.domain.getmetas()) > 1 else ''))
316        cl = data.domain.class_var
317        if cl is not None:
318            if isinstance(cl, Orange.feature.Continuous):
319                self.infob.setText('Regression; Numerical class.')
320            elif isinstance(cl, Orange.feature.Discrete):
321                self.infob.setText(
322                    'Classification; Discrete class with %d value%s.' %
323                    (len(cl.values), 's' if len(cl.values) > 1 else '')
324                )
325            else:
326                self.infob.setText("Class is neither discrete nor continuous.")
327        else:
328            self.infob.setText("Data has no dependent variable.")
329
330        self.warnings.setText(
331            feature_load_status_report(data, self.createNewOn))
332
333        addOrigin(data, fn)
334        # make new data and send it
335        name = os.path.basename(fn)
336        name, _ = os.path.splitext(name)
337        data.name = name
338
339        self.dataReport = self.prepareDataReport(data)
340
341        self.send("Data", data)
342
343    def sendReport(self):
344        if self.dataReport:
345            _, ext = os.path.splitext(self.loadedFile)
346            format = self.formats.get(ext, "unknown format")
347            self.reportSettings(
348                "File", [("File name", self.loadedFile),
349                         ("Format", format)])
350            self.reportData(self.dataReport)
351
352
353def feature_load_status_report(data, create_new_on):
354    warnings = ""
355    metas = data.domain.getmetas()
356    attr_status = []
357    meta_status = {}
358
359    if hasattr(data, "attribute_load_status"):
360        attr_status = data.attribute_load_status
361    elif hasattr(data, "attributeLoadStatus"):
362        attr_status = data.attributeLoadStatus
363
364    if hasattr(data, "meta_attribute_load_status"):
365        meta_status = data.meta_attribute_load_status
366    elif hasattr(data, "metaAttributeLoadStatus"):
367        meta_status = data.metaAttributeLoadStatus
368
369    for status, message_used, message_not_used in STATUS_MESASGES:
370        if create_new_on > status:
371            message = message_used
372        else:
373            message = message_not_used
374        if not message:
375            continue
376
377        attrs = [attr.name for attr, stat in zip(data.domain, attr_status)
378                 if stat == status] + \
379                [attr.name for id, attr in metas.items()
380                 if meta_status.get(id, -99) == status]
381
382        if attrs:
383            jattrs = ", ".join(attrs)
384            if len(jattrs) > 80:
385                jattrs = jattrs[:80] + "..."
386            if len(jattrs) > 30:
387                warnings += "<li>%s:<br/> %s</li>" % (message, jattrs)
388            else:
389                warnings += "<li>%s: %s</li>" % (message, jattrs)
390    return warnings
391
392
393STATUS_MESASGES = [
394    (Orange.feature.Descriptor.MakeStatus.Incompatible,
395     "",
396     "The following attributes already existed but had a different order " +
397     "of values, so new attributes needed to be created"),
398    (Orange.feature.Descriptor.MakeStatus.NoRecognizedValues,
399     "The following attributes were reused although they share no " +
400     "common values with the existing attribute of the same names",
401     "The following attributes were not reused since they share no " +
402     "common values with the existing attribute of the same names"),
403    (Orange.feature.Descriptor.MakeStatus.MissingValues,
404     "The following attribute(s) were reused although some values " +
405     "needed to be added",
406     "The following attribute(s) were not reused since they miss some values")
407]
408
409
410if __name__ == "__main__":
411    a = QApplication(sys.argv)
412    ow = OWFile()
413    ow.show()
414    a.exec_()
415    ow.saveSettings()
Note: See TracBrowser for help on using the repository browser.