Skip to content

Commit

Permalink
Merge pull request #2211 from ales-erjavec/fixes/mds-schedule-policy
Browse files Browse the repository at this point in the history
[FIX] FIX MDS widget update scheduling
  • Loading branch information
astaric authored Apr 21, 2017
2 parents 29835e2 + e34b931 commit 98e22fc
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 47 deletions.
19 changes: 11 additions & 8 deletions Orange/widgets/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import numpy as np
from AnyQt.QtCore import Qt
from AnyQt.QtTest import QTest
from AnyQt.QtTest import QTest, QSignalSpy
from AnyQt.QtWidgets import (
QApplication, QComboBox, QSpinBox, QDoubleSpinBox, QSlider
)
Expand Down Expand Up @@ -180,7 +180,7 @@ def show(self, widget=None):
widget.show()
app.exec()

def send_signal(self, input_name, value, *args, widget=None):
def send_signal(self, input_name, value, *args, widget=None, wait=-1):
""" Send signal to widget by calling appropriate triggers.
Parameters
Expand All @@ -191,6 +191,8 @@ def send_signal(self, input_name, value, *args, widget=None):
channel id, used for inputs with flag Multiple
widget : Optional[OWWidget]
widget to send signal to. If not set, self.widget is used
wait : int
The amount of time to wait for the widget to complete.
"""
if widget is None:
widget = self.widget
Expand All @@ -202,6 +204,9 @@ def send_signal(self, input_name, value, *args, widget=None):
raise ValueError("'{}' is not an input name for widget {}"
.format(input_name, type(widget).__name__))
widget.handleNewSignals()
if wait >= 0 and widget.isBlocking():
spy = QSignalSpy(widget.blockingStateChanged)
self.assertTrue(spy.wait(timeout=wait))

def get_output(self, output_name, widget=None):
"""Return the last output that has been sent from the widget.
Expand Down Expand Up @@ -628,14 +633,12 @@ def init(self):
self.data = Table("iris")
self.same_input_output_domain = True

def test_outputs(self):
def test_outputs(self, timeout=5000):
self.send_signal(self.signal_name, self.signal_data)

# only needed in TestOWMDS
if type(self).__name__ == "TestOWMDS":
from AnyQt.QtCore import QEvent
self.widget.customEvent(QEvent(QEvent.User))
self.widget.commit()
if self.widget.isBlocking():
spy = QSignalSpy(self.widget.blockingStateChanged)
self.assertTrue(spy.wait(timeout))

# check selected data output
self.assertIsNone(self.get_output("Selected Data"))
Expand Down
47 changes: 21 additions & 26 deletions Orange/widgets/unsupervised/owmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
import scipy.spatial.distance

from AnyQt.QtWidgets import (
QFormLayout, QHBoxLayout, QGroupBox, QToolButton, QActionGroup, QAction, QApplication,
QGraphicsLineItem
QFormLayout, QHBoxLayout, QGroupBox, QToolButton, QActionGroup, QAction,
QApplication, QGraphicsLineItem
)
from AnyQt.QtGui import QColor, QPen, QBrush, QPainter, QKeySequence, QCursor, QIcon
from AnyQt.QtCore import Qt, QEvent
from AnyQt.QtGui import (
QColor, QPen, QBrush, QPainter, QKeySequence, QCursor, QIcon
)
from AnyQt.QtCore import Qt, QTimer

import pyqtgraph as pg
import pyqtgraph.graphicsItems.ScatterPlotItem
Expand Down Expand Up @@ -165,6 +167,9 @@ def __init__(self):
self._effective_matrix = None

self.__update_loop = None
# timer for scheduling updates
self.__timer = QTimer(self, singleShot=True, interval=0)
self.__timer.timeout.connect(self.__next_step)
self.__state = OWMDS.Waiting
self.__in_next_step = False
self.__draw_similar_pairs = False
Expand Down Expand Up @@ -592,8 +597,8 @@ def __set_update_loop(self, loop):
MDS points, `stress` is the current stress and `progress` a float
ratio (0 <= progress <= 1)
If an existing update loop is already in palace it is interrupted
(closed).
If an existing update coroutine loop is already in palace it is
interrupted (i.e. closed).
.. note::
The `loop` must not explicitly yield control flow to the event
Expand All @@ -608,20 +613,26 @@ def __set_update_loop(self, loop):
self.__update_loop = loop

if loop is not None:
self.setBlocking(True)
self.progressBarInit(processEvents=None)
self.setStatusMessage("Running")
self.runbutton.setText("Stop")
self.__state = OWMDS.Running
QApplication.postEvent(self, QEvent(QEvent.User))
self.__timer.start()
else:
self.setBlocking(False)
self.setStatusMessage("")
self.runbutton.setText("Start")
self.__state = OWMDS.Finished
self.__timer.stop()

def __next_step(self):
if self.__update_loop is None:
return

assert not self.__in_next_step
self.__in_next_step = True

loop = self.__update_loop
self.Error.out_of_memory.clear()
try:
Expand All @@ -647,25 +658,9 @@ def __next_step(self):
self._update_plot()
self.plot.autoRange(padding=0.1, items=[self._scatter_item])
# schedule next update
QApplication.postEvent(
self, QEvent(QEvent.User), Qt.LowEventPriority)

def customEvent(self, event):
if event.type() == QEvent.User and self.__update_loop is not None:
if not self.__in_next_step:
self.__in_next_step = True
try:
self.__next_step()
finally:
self.__in_next_step = False
else:
warnings.warn(
"Re-entry in update loop detected. "
"A rogue `proccessEvents` is on the loose.",
RuntimeWarning)
# re-schedule the update iteration.
QApplication.postEvent(self, QEvent(QEvent.User))
return super().customEvent(event)
self.__timer.start()

self.__in_next_step = False

def __invalidate_embedding(self):
# reset/invalidate the MDS embedding, to the default initialization
Expand Down
26 changes: 14 additions & 12 deletions Orange/widgets/unsupervised/tests/test_owmds.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ def setUpClass(cls):
cls.same_input_output_domain = False

def setUp(self):
self.widget = self.create_widget(OWMDS) # type: OWMDS
self.widget = self.create_widget(
OWMDS, stored_settings={
"max_iter": 10,
"initialization": OWMDS.PCA,
}
) # type: OWMDS

def _select_data(self):
random.seed(42)
Expand All @@ -35,18 +40,15 @@ def _select_data(self):
return sorted(points)

def test_pca_init(self):
self.send_signal(self.signal_name, self.signal_data)
self.widget.customEvent(QEvent(QEvent.User))
self.widget.commit()
self.send_signal(self.signal_name, self.signal_data, wait=1000)
output = self.get_output(ANNOTATED_DATA_SIGNAL_NAME)
np.testing.assert_array_almost_equal(
output.X[0, 4:], np.array([-2.6928912, 0.32603512]))
np.testing.assert_array_almost_equal(
output.X[1, 4:], np.array([-2.72432089, -0.21129957]))
np.testing.assert_array_almost_equal(
output.X[2, 4:], np.array([-2.90231621, -0.13535431]))
np.testing.assert_array_almost_equal(
output.X[3, 4:], np.array([-2.75269913, -0.33885988]))
expected = np.array(
[[-2.69304803, 0.32676458],
[-2.7246721, -0.20921726],
[-2.90244761, -0.13630526],
[-2.75281107, -0.33854819]]
)
np.testing.assert_array_almost_equal(output.X[:4, 4:], expected)

def test_nan_plot(self):
data = datasets.missing_data_1()
Expand Down
2 changes: 1 addition & 1 deletion requirements-gui.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# PyQt4/PyQt5 compatibility
AnyQt>=0.0.6
AnyQt>=0.0.8

pyqtgraph>=0.10.0

Expand Down

0 comments on commit 98e22fc

Please sign in to comment.