Skip to content

Commit fa3414b

Browse files
committed
Data Projection: Retain embedding if metas change
1 parent df63250 commit fa3414b

File tree

5 files changed

+67
-58
lines changed

5 files changed

+67
-58
lines changed

Orange/widgets/tests/base.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -894,13 +894,22 @@ def test_none_data(self):
894894
"""Test widget for empty dataset"""
895895
self.send_signal(self.widget.Inputs.data, self.data[:0])
896896

897-
def test_subset_data(self, timeout=DEFAULT_TIMEOUT):
898-
"""Test widget for subset data"""
897+
def test_plot_once(self, timeout=DEFAULT_TIMEOUT):
898+
"""Test if data is plotted only once but committed on every input change"""
899+
self.widget.setup_plot = Mock()
900+
self.widget.commit = Mock()
899901
self.send_signal(self.widget.Inputs.data, self.data)
902+
self.widget.setup_plot.assert_called_once()
903+
self.widget.commit.assert_called_once()
904+
900905
if self.widget.isBlocking():
901906
spy = QSignalSpy(self.widget.blockingStateChanged)
902907
self.assertTrue(spy.wait(timeout))
908+
909+
self.widget.commit.reset_mock()
903910
self.send_signal(self.widget.Inputs.data_subset, self.data[::10])
911+
self.widget.setup_plot.assert_called_once()
912+
self.widget.commit.assert_called_once()
904913

905914
def test_class_density(self, timeout=DEFAULT_TIMEOUT):
906915
"""Check class density update"""
@@ -932,6 +941,24 @@ def test_sparse_data(self, timeout=DEFAULT_TIMEOUT):
932941
self.send_signal(self.widget.Inputs.data_subset, table[::30])
933942
self.assertEqual(len(self.widget.subset_indices), 5)
934943

944+
def test_invalidated_embedding(self, timeout=DEFAULT_TIMEOUT):
945+
"""Check if graph has been replotted when sending same data"""
946+
self.widget.graph.update_coordinates = Mock()
947+
self.widget.graph.update_point_props = Mock()
948+
self.send_signal(self.widget.Inputs.data, self.data)
949+
self.widget.graph.update_coordinates.assert_called_once()
950+
self.widget.graph.update_point_props.assert_called_once()
951+
952+
if self.widget.isBlocking():
953+
spy = QSignalSpy(self.widget.blockingStateChanged)
954+
self.assertTrue(spy.wait(timeout))
955+
956+
self.widget.graph.update_coordinates.reset_mock()
957+
self.widget.graph.update_point_props.reset_mock()
958+
self.send_signal(self.widget.Inputs.data, self.data)
959+
self.widget.graph.update_coordinates.assert_not_called()
960+
self.widget.graph.update_point_props.assert_called_once()
961+
935962
def test_send_report(self, timeout=DEFAULT_TIMEOUT):
936963
"""Test report """
937964
self.send_signal(self.widget.Inputs.data, self.data)

Orange/widgets/unsupervised/owtsne.py

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,6 @@ def __init__(self):
6767
super().__init__()
6868
self.pca_data = None
6969
self.projection = None
70-
self.__invalidated = True
7170
self.__update_loop = None
7271
# timer for scheduling updates
7372
self.__timer = QTimer(self, singleShot=True, interval=1,
@@ -113,11 +112,6 @@ def _add_controls_start_box(self):
113112
gui.hSlider(box, self, "pca_components", label="PCA components:",
114113
minValue=2, maxValue=50, step=1)
115114

116-
def set_data(self, data):
117-
self.__invalidated = not (self.data and data and
118-
np.array_equal(self.data.X, data.X))
119-
super().set_data(data)
120-
121115
def check_data(self):
122116
def error(err):
123117
err()
@@ -252,14 +246,9 @@ def __next_step(self):
252246

253247
self.__in_next_step = False
254248

255-
def handleNewSignals(self):
256-
if self.__invalidated:
257-
self.__invalidated = False
258-
self.setup_plot()
259-
self.start()
260-
else:
261-
self.graph.update_point_props()
262-
self.commit()
249+
def setup_plot(self):
250+
super().setup_plot()
251+
self.start()
263252

264253
def commit(self):
265254
super().commit()
@@ -282,12 +271,11 @@ def send_preprocessor(self):
282271
self.Outputs.preprocessor.send(prep)
283272

284273
def clear(self):
285-
if self.__invalidated:
286-
super().clear()
287-
self.__set_update_loop(None)
288-
self.__state = OWtSNE.Waiting
289-
self.pca_data = None
290-
self.projection = None
274+
super().clear()
275+
self.__set_update_loop(None)
276+
self.__state = OWtSNE.Waiting
277+
self.pca_data = None
278+
self.projection = None
291279

292280
@classmethod
293281
def migrate_settings(cls, settings, version):

Orange/widgets/unsupervised/tests/test_owtsne.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import unittest
22
import numpy as np
33

4-
from AnyQt.QtTest import QSignalSpy
5-
64
from Orange.data import DiscreteVariable, ContinuousVariable, Domain, Table
75
from Orange.preprocess import Preprocess
86
from Orange.widgets.tests.base import (
@@ -98,23 +96,6 @@ def test_output_preprocessor(self):
9896
self.assertEqual([a.name for a in transformed.domain.attributes],
9997
[m.name for m in output.domain.metas[:2]])
10098

101-
def test_invalidated_embedding(self):
102-
self.widget.graph.update_coordinates = unittest.mock.Mock()
103-
self.widget.graph.update_point_props = unittest.mock.Mock()
104-
self.send_signal(self.widget.Inputs.data, self.data)
105-
self.widget.graph.update_coordinates.assert_called_once()
106-
self.widget.graph.update_point_props.assert_called_once()
107-
108-
if self.widget.isBlocking():
109-
spy = QSignalSpy(self.widget.blockingStateChanged)
110-
self.assertTrue(spy.wait(5000))
111-
112-
self.widget.graph.update_coordinates.reset_mock()
113-
self.widget.graph.update_point_props.reset_mock()
114-
self.send_signal(self.widget.Inputs.data, self.data)
115-
self.widget.graph.update_coordinates.assert_not_called()
116-
self.widget.graph.update_point_props.assert_called_once()
117-
11899

119100
if __name__ == '__main__':
120101
unittest.main()

Orange/widgets/visualize/owscatterplot.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,10 @@ def _add_controls_sampling(self):
262262
callback=self.switch_sampling, commit=lambda: self.add_data(1))
263263
self.sampling.setVisible(False)
264264

265+
@property
266+
def effective_variables(self):
267+
return [self.attr_x, self.attr_y]
268+
265269
def _vizrank_color_change(self):
266270
self.vizrank.initialize()
267271
is_enabled = self.data is not None and not self.data.is_sparse() and \

Orange/widgets/visualize/utils/widget.py

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ def __init__(self):
373373
self.subset_data = None
374374
self.subset_indices = None
375375
self.__pending_selection = self.selection
376+
self.__invalidated = True
376377
self.setup_gui()
377378

378379
# GUI
@@ -395,19 +396,35 @@ def _add_controls(self):
395396
gui.auto_commit(self.controlArea, self, "auto_commit",
396397
"Send Selection", "Send Automatically")
397398

399+
@property
400+
def effective_variables(self):
401+
return self.data.domain.attributes
402+
403+
@property
404+
def effective_data(self):
405+
return self.data.transform(Domain(self.effective_variables,
406+
self.data.domain.class_vars,
407+
self.data.domain.metas))
408+
398409
# Input
399410
@Inputs.data
400411
@check_sql_input
401412
def set_data(self, data):
402-
same_domain = (self.data and data and
413+
data_exists = self.data is not None
414+
effective_data = self.effective_data if data_exists else None
415+
same_domain = (data_exists and data and
403416
data.domain.checksum() == self.data.domain.checksum())
404417
self.closeContext()
405-
self.clear()
406418
self.data = data
407419
self.check_data()
408420
if not same_domain:
409421
self.init_attr_values()
410422
self.openContext(self.data)
423+
self.__invalidated = not (data_exists and self.data and
424+
np.array_equal(effective_data.X,
425+
self.effective_data.X))
426+
if self.__invalidated:
427+
self.clear()
411428
self.cb_class_density.setEnabled(self.can_draw_density())
412429

413430
def check_data(self):
@@ -422,7 +439,11 @@ def set_subset_data(self, subset):
422439
self.controls.graph.alpha_value.setEnabled(subset is None)
423440

424441
def handleNewSignals(self):
425-
self.setup_plot()
442+
if self.__invalidated:
443+
self.__invalidated = False
444+
self.setup_plot()
445+
else:
446+
self.graph.update_point_props()
426447
self.commit()
427448

428449
def get_subset_mask(self):
@@ -439,7 +460,7 @@ def get_embedding(self):
439460
should return embedding for all data (valid and invalid). Invalid
440461
data embedding coordinates should be set to 0 (in some cases to Nan).
441462
442-
The method should also sets self.valid_data.
463+
The method should also set self.valid_data.
443464
444465
Returns:
445466
np.array: Array of embedding coordinates with shape
@@ -552,8 +573,6 @@ def sizeHint(self):
552573
return QSize(1132, 708)
553574

554575
def clear(self):
555-
self.data = None
556-
self.valid_data = None
557576
self.selection = None
558577
self.graph.selection = None
559578

@@ -587,16 +606,6 @@ def __init__(self):
587606
self.graph.view_box.moved.connect(self._manual_move)
588607
self.graph.view_box.finished.connect(self._manual_move_finish)
589608

590-
@property
591-
def effective_variables(self):
592-
return self.data.domain.attributes
593-
594-
@property
595-
def effective_data(self):
596-
return self.data.transform(Domain(self.effective_variables,
597-
self.data.domain.class_vars,
598-
self.data.domain.metas))
599-
600609
def check_data(self):
601610
def error(err):
602611
err()
@@ -615,7 +624,7 @@ def error(err):
615624

616625
def init_projection(self):
617626
self.projection = None
618-
if not len(self.effective_variables):
627+
if not self.effective_variables:
619628
return
620629
try:
621630
self.projection = self.projector(self.effective_data)

0 commit comments

Comments
 (0)