Skip to content

Commit

Permalink
Merge pull request biolab#1805 from astaric/refactor-domain-edit
Browse files Browse the repository at this point in the history
[FIX] Store changed variables in File widget
  • Loading branch information
janezd authored and astaric committed Jan 4, 2017
1 parent 818d146 commit 9a67086
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 112 deletions.
99 changes: 28 additions & 71 deletions Orange/widgets/data/owfile.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import os
import logging
from itertools import chain, count
from warnings import catch_warnings

import numpy as np
Expand All @@ -11,12 +10,12 @@
from AnyQt.QtCore import Qt, QTimer, QSize

from Orange.canvas.gui.utils import OSX_NSURL_toLocalFile
from Orange.data import Domain, DiscreteVariable, StringVariable
from Orange.data import StringVariable
from Orange.data.table import Table, get_sample_datasets_dir
from Orange.data.io import FileFormat, UrlReader
from Orange.widgets import widget, gui
from Orange.widgets.settings import Setting, ContextHandler, ContextSetting, \
PerfectDomainContextHandler
from Orange.widgets.settings import Setting, ContextSetting, \
PerfectDomainContextHandler, SettingProvider
from Orange.widgets.utils.domaineditor import DomainEditor
from Orange.widgets.utils.itemmodels import PyListModel
from Orange.widgets.utils.filedialogs import RecentPathsWComboMixin
Expand Down Expand Up @@ -62,22 +61,6 @@ def add_name(self, url, name):
self.modelReset.emit()


class XlsContextHandler(ContextHandler):
def new_context(self, filename, sheet):
context = super().new_context()
context.filename = filename
return context

# noinspection PyMethodOverriding
def match(self, context, filename, sheets):
context_sheet = context.values.get("xls_sheet")
if context.filename == filename and context_sheet in sheets:
return ContextHandler.PERFECT_MATCH
if context_sheet in sheets:
return 1
return ContextHandler.NO_MATCH


class LineEditSelectOnFocus(QLineEdit):
def focusInEvent(self, event):
super().focusInEvent(event)
Expand Down Expand Up @@ -128,6 +111,8 @@ class OWFile(widget.OWWidget, RecentPathsWComboMixin):
for f in sorted(set(FileFormat.readers.values()),
key=list(FileFormat.readers.values()).index)))

domain_editor = SettingProvider(DomainEditor)

class Warning(widget.OWWidget.Warning):
file_too_big = widget.Msg("The file is too large to load automatically."
" Press Reload to load.")
Expand Down Expand Up @@ -171,7 +156,7 @@ def __init__(self):
self.sheet_box = gui.hBox(None, addToLayout=False, margin=0)
self.sheet_combo = gui.comboBox(None, self, "xls_sheet",
callback=self.select_sheet,
sendSelectedValue=True)
sendSelectedValue=True,)
self.sheet_combo.setSizePolicy(
Policy.MinimumExpanding, Policy.Fixed)
self.sheet_label = QLabel()
Expand Down Expand Up @@ -207,9 +192,9 @@ def __init__(self):
self.warnings = gui.widgetLabel(box, '')

box = gui.widgetBox(self.controlArea, "Columns (Double click to edit)")
domain_editor = DomainEditor(self.variables)
self.editor_model = domain_editor.model()
box.layout().addWidget(domain_editor)
self.domain_editor = DomainEditor(self)
self.editor_model = self.domain_editor.model()
box.layout().addWidget(self.domain_editor)

box = gui.hBox(self.controlArea)
gui.button(
Expand Down Expand Up @@ -283,7 +268,8 @@ def load_data(self):
# We need to catch any exception type since anything can happen in
# file readers
# pylint: disable=broad-except
self.editor_model.set_domain(None)
self.closeContext()
self.domain_editor.set_domain(None)
self.apply_button.setEnabled(False)
self.Warning.file_too_big.clear()

Expand Down Expand Up @@ -313,16 +299,15 @@ def load_data(self):
self.data = None
self.send("Data", None)
self.info.setText("An error occurred:\n{}".format(error))
self.editor_model.reset()
self.sheet_box.hide()
return

self.info.setText(self._describe(data))

add_origin(data, self.loaded_file or self.last_path())
self.send("Data", data)
self.editor_model.set_domain(data.domain)
self.data = data
self.openContext(data.domain)
self.apply_domain_edit() # sends data

def _get_reader(self):
"""
Expand Down Expand Up @@ -403,49 +388,21 @@ def retrieveSpecificSettings(self):
self.variables[:] = self.current_context.modified_variables

def apply_domain_edit(self):
attributes = []
class_vars = []
metas = []
places = [attributes, class_vars, metas]
X, y, m = [], [], []
cols = [X, y, m] # Xcols, Ycols, Mcols

def is_missing(x):
return str(x) in ("nan", "")

for column, (name, tpe, place, vals, is_con), (orig_var, orig_plc) in \
zip(count(), self.editor_model.variables,
chain([(at, 0) for at in self.data.domain.attributes],
[(cl, 1) for cl in self.data.domain.class_vars],
[(mt, 2) for mt in self.data.domain.metas])):
if place == 3:
continue
if orig_plc == 2:
col_data = list(chain(*self.data[:, orig_var].metas))
else:
col_data = list(chain(*self.data[:, orig_var]))
if name == orig_var.name and tpe == type(orig_var):
var = orig_var
elif tpe == DiscreteVariable:
values = list(str(i) for i in set(col_data) if not is_missing(i))
var = tpe(name, values)
col_data = [np.nan if is_missing(x) else values.index(str(x))
for x in col_data]
elif tpe == StringVariable and type(orig_var) == DiscreteVariable:
var = tpe(name)
col_data = [orig_var.repr_val(x) if not np.isnan(x) else ""
for x in col_data]
else:
var = tpe(name)
places[place].append(var)
cols[place].append(col_data)
domain = Domain(attributes, class_vars, metas)
X = np.array(X).T if len(X) else np.empty((len(self.data), 0))
y = np.array(y).T if len(y) else None
dtpe = object if any(isinstance(m, StringVariable)
for m in domain.metas) else float
m = np.array(m, dtype=dtpe).T if len(m) else None
table = Table.from_numpy(domain, X, y, m, self.data.W)
if self.data is not None:
domain, cols = self.domain_editor.get_domain(self.data.domain, self.data)
X, y, m = cols
X = np.array(X).T if len(X) else np.empty((len(self.data), 0))
y = np.array(y).T if len(y) else None
dtpe = object if any(isinstance(m, StringVariable)
for m in domain.metas) else float
m = np.array(m, dtype=dtpe).T if len(m) else None
table = Table.from_numpy(domain, X, y, m, self.data.W)
table.name = self.data.name
table.ids = np.array(self.data.ids)
table.attributes = getattr(self.data, 'attributes', {})
else:
table = self.data

self.send("Data", table)
self.apply_button.setEnabled(False)

Expand Down
24 changes: 24 additions & 0 deletions Orange/widgets/data/tests/test_owfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from AnyQt.QtGui import QDragEnterEvent, QDropEvent

import Orange
from Orange.data import FileFormat, dataset_dirs, StringVariable
from Orange.widgets.data.owfile import OWFile
from Orange.widgets.tests.base import WidgetTest

Expand Down Expand Up @@ -69,3 +70,26 @@ def test_check_file_size(self):
self.widget.SIZE_LIMIT = 4000
self.widget.__init__()
self.assertTrue(self.widget.Warning.file_too_big.is_shown())

def test_domain_changes_are_stored(self):
assert isinstance(self.widget, OWFile)

self.open_dataset("iris")
idx = self.widget.domain_editor.model().createIndex(4, 1)
self.widget.domain_editor.model().setData(idx, "string", Qt.EditRole)
self.widget.apply_button.click()
data = self.get_output("Data")
self.assertIsInstance(data.domain["iris"], StringVariable)

self.open_dataset("zoo")
data = self.get_output("Data")
self.assertEqual(data.name, "zoo")

self.open_dataset("iris")
data = self.get_output("Data")
self.assertIsInstance(data.domain["iris"], StringVariable)

def open_dataset(self, name):
filename = FileFormat.locate(name, dataset_dirs)
self.widget.add_path(filename)
self.widget.load_data()
8 changes: 7 additions & 1 deletion Orange/widgets/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -967,7 +967,13 @@ def settings_to_widget(self, widget, domain, *args):
setattr(instance, setting.selected, data[setting.selected])

if isinstance(value, list):
excluded |= set(value)
try:
excluded |= set(value)
except Exception:
# Some values in the list were not hashable so they will
# be included in the reservoir. Since no one uses
# reservoirs anyway, pretend nothing happened.
pass
else:
if setting.not_attribute:
excluded.add(value)
Expand Down
Loading

0 comments on commit 9a67086

Please sign in to comment.