source: orange/Orange/OrangeWidgets/Data/OWFile.py @ 11719:d26fa6fe99b2

Revision 11719:d26fa6fe99b2, 15.4 KB checked in by blaz <blaz.zupan@…>, 7 months ago (diff)

Updated DESCRIPTION fields.

Line 
1"""
2<name>File</name>
3<description>Reads data from a file.</description>
4<icon>icons/File.svg</icon>
5<contact>Janez Demsar (janez.demsar(@at@)fri.uni-lj.si)</contact>
6<priority>10</priority>
7"""
8
9# Don't move this - the line number of the call is important
10def call(f,*args,**keyargs):
11    return f(*args, **keyargs)
12
13from OWWidget import *
14import OWGUI, string, os, sys, warnings
15import orngIO
16
17NAME = "File"
18ID = "orange.widgets.data.file"
19
20DESCRIPTION = """
21Reads data from an input file.
22"""
23
24LONG_DESCRIPTION = ""
25"""
26This is the widget you will probably use at the start of every schema to read
27the input data file (data table with examples). The widget maintains a
28history of most recently used data files. For convenience, the history
29also includes a directory with the sample data sets that come with Orange.
30"""
31
32ICON = "icons/File.svg"
33AUTHOR = "Janez Demsar"
34MAINTAINER_EMAIL = "janez.demsar(@at@)fri.uni-lj.si"
35PRIORITY = 10
36CATEGORY = "Data"
37
38KEYWORDS = ["data", "file", "load", "read"]
39
40OUTPUTS = (
41    {"name": "Data",
42     "type": orange.ExampleTable,
43     "doc": "Attribute-valued data set read from the input file.",
44    },
45)
46
47WIDGET_CLASS = "OWFile"
48
49# This is why the 'call''s line number is important. Actually you can
50# move it but you need to make sure the following filter's lineno is updated
51warnings.filterwarnings("error", ".*", orange.KernelWarning, "OWFile", 11)
52
53
54class FileNameContextHandler(ContextHandler):
55    def match(self, context, imperfect, filename):
56        return context.filename == filename and 2
57
58
59def addOrigin(examples, filename):
60    vars = examples.domain.variables + examples.domain.getmetas().values()
61    strings = [var for var in vars if isinstance(var, orange.StringVariable)]
62    dirname, basename = os.path.split(filename)
63    for var in strings:
64        if "type" in var.attributes and "origin" not in var.attributes:
65            var.attributes["origin"] = dirname
66
67
68class OWFile(OWWidget):
69    settingsList=["recentFiles", "createNewOn", "showAdvanced"]
70    contextHandlers = {"": FileNameContextHandler()}
71
72    registeredFileTypes = [ft for ft in orange.getRegisteredFileTypes() if len(ft)>2 and ft[2]]
73    dlgFormats = 'Tab-delimited files (*.tab *.txt)\nC4.5 files (*.data)\nAssistant files (*.dat)\nRetis files (*.rda *.rdo)\nBasket files (*.basket)\n' \
74                 + "\n".join("%s (%s)" % (ft[:2]) for ft in registeredFileTypes) \
75                 + "\nAll files(*.*)"
76                 
77    formats = {".tab": "Tab-delimited file", ".txt": "Tab-delimited file", ".data": "C4.5 file",
78               ".dat": "Assistant file", ".rda": "Retis file", ".rdo": "Retis file",
79               ".basket": "Basket file"}
80    formats.update(dict((ft[1][2:], ft[0]) for ft in registeredFileTypes))
81     
82    def __init__(self, parent=None, signalManager = None):
83        OWWidget.__init__(self, parent, signalManager, "File", wantMainArea = 0, resizingEnabled = 1)
84
85        self.inputs = []
86        self.outputs = [("Data", ExampleTable)]
87
88        self.recentFiles=["(none)"]
89        self.symbolDC = "?"
90        self.symbolDK = "~"
91        self.createNewOn = 1
92        self.domain = None
93        self.loadedFile = ""
94        self.showAdvanced = 0
95        self.loadSettings()
96
97        box = OWGUI.widgetBox(self.controlArea, "Data File", addSpace = True, orientation=0)
98        self.filecombo = QComboBox(box)
99        self.filecombo.setMinimumWidth(150)
100        box.layout().addWidget(self.filecombo)
101        button = OWGUI.button(box, self, '...', callback = self.browseFile, disabled=0)
102        button.setIcon(self.style().standardIcon(QStyle.SP_DirOpenIcon))
103        button.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
104       
105        self.reloadBtn = OWGUI.button(box, self, "Reload", callback = self.reload, default=True)
106        self.reloadBtn.setIcon(self.style().standardIcon(QStyle.SP_BrowserReload))
107        self.reloadBtn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
108       
109        box = OWGUI.widgetBox(self.controlArea, "Info", addSpace = True)
110        self.infoa = OWGUI.widgetLabel(box, 'No data loaded.')
111        self.infob = OWGUI.widgetLabel(box, ' ')
112        self.warnings = OWGUI.widgetLabel(box, ' ')
113       
114        #Set word wrap so long warnings won't expand the widget
115        self.warnings.setWordWrap(True)
116        self.warnings.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.MinimumExpanding)
117       
118        smallWidget = OWGUI.collapsableWidgetBox(self.controlArea, "Advanced settings", self, "showAdvanced", callback=self.adjustSize0)
119       
120        box = OWGUI.widgetBox(smallWidget, "Missing Value Symbols")
121#        OWGUI.widgetLabel(box, "Symbols for missing values in tab-delimited files (besides default ones)")
122       
123        hbox = OWGUI.indentedBox(box)
124        OWGUI.lineEdit(hbox, self, "symbolDC", "Don't care:", labelWidth=80, orientation="horizontal", tooltip="Default values: '~' or '*'")
125        OWGUI.lineEdit(hbox, self, "symbolDK", "Don't know:", labelWidth=80, orientation="horizontal", tooltip="Default values: empty fields (space), '?' or 'NA'")
126
127        smallWidget.layout().addSpacing(8)
128        OWGUI.radioButtonsInBox(smallWidget, self, "createNewOn", box="New Attributes",
129                       label = "Create a new attribute when existing attribute(s) ...",
130                       btnLabels = ["Have mismatching order of values",
131                                    "Have no common values with the new (recommended)",
132                                    "Miss some values of the new attribute",
133                                    "... Always create a new attribute"
134                               ])
135       
136        OWGUI.rubber(smallWidget)
137        smallWidget.updateControls()
138       
139        OWGUI.rubber(self.controlArea)
140       
141        # remove missing data set names
142        def exists(path):
143            if not os.path.exists(path):
144                dirpath, basename = os.path.split(path)
145                return os.path.exists(os.path.join("./", basename))
146            else:
147                return True
148        self.recentFiles = filter(exists, self.recentFiles)
149        self.setFileList()
150
151        if len(self.recentFiles) > 0 and exists(self.recentFiles[0]):
152            self.openFile(self.recentFiles[0], 0, self.symbolDK, self.symbolDC)
153
154        self.connect(self.filecombo, SIGNAL('activated(int)'), self.selectFile)
155
156    def adjustSize0(self):
157        qApp.processEvents()
158        QTimer.singleShot(50, self.adjustSize)
159
160    def setFileList(self):
161        self.filecombo.clear()
162        if not self.recentFiles:
163            self.filecombo.addItem("(none)")
164        for file in self.recentFiles:
165            if file == "(none)":
166                self.filecombo.addItem("(none)")
167            else:
168                self.filecombo.addItem(os.path.split(file)[1])
169        self.filecombo.addItem("Browse documentation data sets...")
170       
171
172    def reload(self):
173        if self.recentFiles:
174            return self.openFile(self.recentFiles[0], 1, self.symbolDK, self.symbolDC)
175
176
177    def settingsFromWidgetCallback(self, handler, context):
178        context.filename = self.loadedFile
179        context.symbolDC, context.symbolDK = self.symbolDC, self.symbolDK
180
181    def settingsToWidgetCallback(self, handler, context):
182        self.symbolDC, self.symbolDK = context.symbolDC, context.symbolDK
183
184    def selectFile(self, n):
185        if n < len(self.recentFiles) :
186            name = self.recentFiles[n]
187            self.recentFiles.remove(name)
188            self.recentFiles.insert(0, name)
189        elif n:
190            self.browseFile(1)
191
192        if len(self.recentFiles) > 0:
193            self.setFileList()
194            self.openFile(self.recentFiles[0], 0, self.symbolDK, self.symbolDC)
195
196    def browseFile(self, inDemos=0):
197        "Display a FileDialog and select a file"
198        if inDemos:
199            try:
200                import orngConfiguration
201                startfile = orngConfiguration.datasetsPath
202            except:
203                startfile = ""
204               
205            if not startfile or not os.path.exists(startfile):
206                try:
207                    import win32api, win32con
208                    t = win32api.RegOpenKey(win32con.HKEY_LOCAL_MACHINE, "SOFTWARE\\Python\\PythonCore\\%i.%i\\PythonPath\\Orange" % sys.version_info[:2], 0, win32con.KEY_READ)
209                    t = win32api.RegQueryValueEx(t, "")[0]
210                    startfile = t[:t.find("orange")] + "orange\\doc\\datasets"
211                except:
212                    startfile = ""
213
214            if not startfile or not os.path.exists(startfile):
215                widgetsdir = os.path.dirname(OWGUI.__file__)
216                orangedir = os.path.dirname(widgetsdir)
217                startfile = os.path.join(orangedir, "doc", "datasets")
218
219            if not startfile or not os.path.exists(startfile):
220                d = os.getcwd()
221                if os.path.basename(d) == "OrangeCanvas":
222                    startfile = os.path.join(os.path.dirname(d), "doc", "datasets")
223                else:
224                    startfile = os.path.join(d, "doc", "datasets")
225
226            if not os.path.exists(startfile):
227                QMessageBox.information( None, "File", "Cannot find the directory with example data sets", QMessageBox.Ok + QMessageBox.Default)
228                return
229        else:
230            if len(self.recentFiles) == 0 or self.recentFiles[0] == "(none)":
231                startfile = os.path.expanduser("~/")
232            else:
233                startfile = self.recentFiles[0]
234
235        filename = QFileDialog.getOpenFileName(self, 'Open Orange Data File', startfile, self.dlgFormats)
236        filename = unicode(filename)
237       
238        if filename == "":
239            return
240       
241        if filename in self.recentFiles:
242            self.recentFiles.remove(filename)
243        self.recentFiles.insert(0, filename)
244        self.setFileList()
245
246        self.openFile(self.recentFiles[0], 0, self.symbolDK, self.symbolDC)
247
248
249    # Open a file, create data from it and send it over the data channel
250    def openFile(self, fn, throughReload, DK=None, DC=None):
251        if self.processingHandler: 
252            self.processingHandler(self, 1)    # focus on active widget
253        self.error()
254        self.warning()
255        self.information()
256       
257        if not os.path.exists(fn):
258            dirname, basename = os.path.split(fn)
259            if os.path.exists(os.path.join("./", basename)):
260                fn = os.path.join("./", basename)
261                self.information("Loading '%s' from the current directory." % basename)
262
263        self.closeContext()
264        self.loadedFile = ""
265       
266        if fn == "(none)":
267            self.send("Data", None)
268            self.infoa.setText("No data loaded")
269            self.infob.setText("")
270            self.warnings.setText("")
271            return
272           
273        self.symbolDK = self.symbolDC = ""
274        self.openContext("", fn)
275
276        self.loadedFile = ""
277
278        argdict = {"createNewOn": 3-self.createNewOn}
279        if DK:
280            argdict["DK"] = str(DK)
281        if DC:
282            argdict["DC"] = str(DC)
283
284        data = None
285        try:
286            data = call(orange.ExampleTable, fn, **argdict)
287            self.loadedFile = fn
288        except Exception, (errValue):
289            if "is being loaded as" in str(errValue):
290                try:
291                    data = orange.ExampleTable(fn, **argdict)
292                    self.warning(0, str(errValue))
293                except:
294                    pass
295            if data is None:
296                self.error(str(errValue))
297                self.dataDomain = None
298                self.infoa.setText('Data was not loaded due to an error.')
299                self.infob.setText('Error:')
300                self.warnings.setText(str(errValue))
301                if self.processingHandler: self.processingHandler(self, 0)    # remove focus from this widget
302                return
303                       
304        self.dataDomain = data.domain
305
306        self.infoa.setText('%d example(s), ' % len(data) + '%d attribute(s), ' % len(data.domain.attributes) + '%d meta attribute(s).' % len(data.domain.getmetas()))
307        cl = data.domain.classVar
308        if cl:
309            if cl.varType == orange.VarTypes.Continuous:
310                    self.infob.setText('Regression; Numerical class.')
311            elif cl.varType == orange.VarTypes.Discrete:
312                    self.infob.setText('Classification; Discrete class with %d value(s).' % len(cl.values))
313            else:
314                self.infob.setText("Class is neither discrete nor continuous.")
315        else:
316            self.infob.setText("Data has no dependent variable.")
317
318        warnings = ""
319        metas = data.domain.getmetas()
320        if hasattr(data, "attribute_load_status"):  # For some file formats, this is not populated
321            for status, messageUsed, messageNotUsed in [
322                                    (orange.Variable.MakeStatus.Incompatible,
323                                     "",
324                                     "The following attributes already existed but had a different order of values, so new attributes needed to be created"),
325                                    (orange.Variable.MakeStatus.NoRecognizedValues,
326                                     "The following attributes were reused although they share no common values with the existing attribute of the same names",
327                                     "The following attributes were not reused since they share no common values with the existing attribute of the same names"),
328                                    (orange.Variable.MakeStatus.MissingValues,
329                                     "The following attribute(s) were reused although some values needed to be added",
330                                     "The following attribute(s) were not reused since they miss some values")
331                                    ]:
332                if self.createNewOn > status:
333                    message = messageUsed
334                else:
335                    message = messageNotUsed
336                if not message:
337                    continue
338                attrs = [attr.name for attr, stat in zip(data.domain, data.attributeLoadStatus) if stat == status] \
339                      + [attr.name for id, attr in metas.items() if data.metaAttributeLoadStatus.get(id, -99) == status]
340                if attrs:
341                    jattrs = ", ".join(attrs)
342                    if len(jattrs) > 80:
343                        jattrs = jattrs[:80] + "..."
344                    if len(jattrs) > 30: 
345                        warnings += "<li>%s:<br/> %s</li>" % (message, jattrs)
346                    else:
347                        warnings += "<li>%s: %s</li>" % (message, jattrs)
348
349        self.warnings.setText(warnings)
350        #qApp.processEvents()
351        #self.adjustSize()
352
353        addOrigin(data, fn)
354        # make new data and send it
355        fName = os.path.split(fn)[1]
356        if "." in fName:
357            data.name = fName[:fName.rfind('.')]
358        else:
359            data.name = fName
360
361        self.dataReport = self.prepareDataReport(data)
362        self.send("Data", data)
363        if self.processingHandler: self.processingHandler(self, 0)    # remove focus from this widget
364
365    def sendReport(self):
366        if hasattr(self, "dataReport"):
367            self.reportSettings("File",
368                                [("File name", self.loadedFile),
369                                 ("Format", self.formats.get(os.path.splitext(self.loadedFile)[1], "unknown format"))])
370            self.reportData(self.dataReport)
371
372if __name__ == "__main__":
373    a = QApplication(sys.argv)
374    ow = OWFile()
375    ow.show()
376    a.exec_()
377    ow.saveSettings()
Note: See TracBrowser for help on using the repository browser.