source: orange/Orange/OrangeWidgets/Data/OWFile.py @ 11252:e50ca88b9854

Revision 11252:e50ca88b9854, 15.3 KB checked in by Ales Erjavec <ales.erjavec@…>, 16 months ago (diff)

Changes to widget descriptors for better documentation/help.

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