Skip to content

Commit 2768723

Browse files
committed
Refactor
1 parent 12d4415 commit 2768723

File tree

5 files changed

+124
-134
lines changed

5 files changed

+124
-134
lines changed

Orange/widgets/tests/base.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,12 +1018,16 @@ def test_invalidated_embedding(self, timeout=DEFAULT_TIMEOUT):
10181018

10191019
def test_saved_selection(self, timeout=DEFAULT_TIMEOUT):
10201020
self.send_signal(self.widget.Inputs.data, self.data)
1021-
self.wait_until_stop_blocking()
1021+
if self.widget.isBlocking():
1022+
spy = QSignalSpy(self.widget.blockingStateChanged)
1023+
self.assertTrue(spy.wait(timeout))
10221024
self.widget.graph.select_by_indices(list(range(0, len(self.data), 10)))
10231025
settings = self.widget.settingsHandler.pack_data(self.widget)
10241026
w = self.create_widget(self.widget.__class__, stored_settings=settings)
10251027
self.send_signal(self.widget.Inputs.data, self.data, widget=w)
1026-
self.wait_until_stop_blocking(widget=w)
1028+
if w.isBlocking():
1029+
spy = QSignalSpy(w.blockingStateChanged)
1030+
self.assertTrue(spy.wait(timeout))
10271031
self.assertEqual(np.sum(w.graph.selection), 15)
10281032
np.testing.assert_equal(self.widget.graph.selection, w.graph.selection)
10291033

Orange/widgets/utils/concurrent.py

Lines changed: 30 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -813,34 +813,33 @@ def __init__(self, *args):
813813
self.partial_result_ready, Qt.QueuedConnection)
814814

815815
@property
816-
def future(self):
817-
# type: () -> Future
816+
def future(self) -> Future:
818817
return self.__future
819818

820-
def set_status(self, text):
819+
def set_status(self, text: str):
821820
self._p_status_changed.emit(text)
822821

823-
def set_progress_value(self, value):
822+
def set_progress_value(self, value: float):
824823
if round(value, 1) > round(self.__progress, 1):
825824
# Only emit progress when it has changed sufficiently
826825
self._p_progress_changed.emit(value)
827826
self.__progress = value
828827

829-
def set_partial_results(self, value):
828+
def set_partial_result(self, value: Any):
830829
self._p_partial_result_ready.emit(value)
831830

832-
def is_interruption_requested(self):
831+
def is_interruption_requested(self) -> bool:
833832
return self.__interruption_requested
834833

835-
def start(self, executor, func=None):
836-
# type: (concurrent.futures.Executor, Callable[[], Any]) -> Future
834+
def start(self, executor: concurrent.futures.Executor,
835+
func: Callable[[], Any]=None) -> Future:
837836
assert self.future is None
838837
assert not self.__interruption_requested
839838
self.__future = executor.submit(func)
840839
self.watcher.setFuture(self.future)
841840
return self.future
842841

843-
def cancel(self):
842+
def cancel(self) -> bool:
844843
assert not self.__interruption_requested
845844
self.__interruption_requested = True
846845
if self.future is not None:
@@ -861,22 +860,19 @@ def __init__(self):
861860
self.__task = None # type: Optional[TaskState]
862861

863862
@property
864-
def task(self):
863+
def task(self) -> TaskState:
865864
return self.__task
866865

867866
def _prepare_task(self, state: TaskState) -> Callable[[], Any]:
868867
raise NotImplementedError
869868

870-
def _set_partial_results(self, result: Any) -> None:
869+
def _on_partial_result(self, result: Any) -> None:
871870
raise NotImplementedError
872871

873-
def _set_results(self, results: Any) -> None:
874-
# NOTE: All of these have already been set by _set_partial_results,
872+
def _on_done(self, result: Any) -> None:
873+
# NOTE: All of these have already been set by _on_partial_result,
875874
# we double check that they are aliases
876-
for key in results.__dict__:
877-
value = getattr(results, key)
878-
if value is not None:
879-
setattr(self, key, value)
875+
raise NotImplementedError
880876

881877
def __set_state_ready(self):
882878
self.progressBarFinished()
@@ -891,27 +887,39 @@ def __start_task(self, task: Callable[[], Any], state: TaskState):
891887
assert self.__task is None
892888
state.status_changed.connect(self.setStatusMessage)
893889
state.progress_changed.connect(self.progressBarSet)
894-
state.partial_result_ready.connect(self._set_partial_results)
895-
state.watcher.done.connect(self.on_done)
890+
state.partial_result_ready.connect(self._on_partial_result)
891+
state.watcher.done.connect(self.__on_task_done)
896892
state.start(self.__executor, task)
897893
state.setParent(self)
898894
self.__task = state
899895

900-
def __cancel_task(self, wait=True):
896+
def __cancel_task(self, wait: bool=True):
901897
if self.__task is not None:
902898
state, self.__task = self.__task, None
903899
state.cancel()
904-
state.partial_result_ready.disconnect(self._set_partial_results)
900+
state.partial_result_ready.disconnect(self._on_partial_result)
905901
state.status_changed.disconnect(self.setStatusMessage)
906902
state.progress_changed.disconnect(self.progressBarSet)
907-
state.watcher.done.disconnect(self.on_done)
903+
state.watcher.done.disconnect(self.__on_task_done)
908904
if wait:
909905
concurrent.futures.wait([state.future])
910906
state.deleteLater()
911907
else:
912908
w = FutureWatcher(state.future, parent=state)
913909
w.done.connect(state.deleteLater)
914910

911+
def __on_task_done(self, future: Future):
912+
""" Invoked when task is done. """
913+
assert future.done()
914+
assert self.__task is not None
915+
assert self.__task.future is future
916+
assert self.__task.watcher.future() is future
917+
self.__task, task = None, self.__task
918+
task.deleteLater()
919+
self.__set_state_ready()
920+
result = future.result()
921+
self._on_done(result)
922+
915923
def start(self):
916924
""" Call to start the task. """
917925
self.__cancel_task(wait=False)
@@ -925,18 +933,6 @@ def start(self):
925933
self.__set_state_busy()
926934
self.__start_task(task, state)
927935

928-
def on_done(self, future: Future):
929-
""" Invoked when task is done. """
930-
assert future.done()
931-
assert self.__task is not None
932-
assert self.__task.future is future
933-
assert self.__task.watcher.future() is future
934-
self.__task, task = None, self.__task
935-
task.deleteLater()
936-
self.__set_state_ready()
937-
result = future.result()
938-
self._set_results(result)
939-
940936
def cancel(self):
941937
""" Call to stop the task. """
942938
self.__cancel_task(wait=False)

Orange/widgets/utils/tests/concurrent_example.py

Lines changed: 37 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -13,42 +13,40 @@
1313
from Orange.widgets.visualize.utils.widget import OWDataProjectionWidget
1414

1515

16-
class Results(namespace):
16+
class Result(namespace):
1717
embedding = None # type: Optional[np.ndarray]
1818

1919

20-
class Runner:
21-
@staticmethod
22-
def run(data: Table, embedding: Optional[np.ndarray], state: TaskState):
23-
res = Results(embedding=embedding)
24-
25-
# simulate wasteful calculation (increase 'steps')
26-
step, steps = 0, 10
27-
state.set_status("Calculating...")
28-
while step < steps:
29-
for _ in range(steps):
30-
x_data = np.array(np.mean(data.X, axis=1))
31-
if x_data.ndim == 2:
32-
x_data = x_data.ravel()
33-
y_data = np.ones(len(x_data))
34-
y_data[::2] = step % 2
35-
y_data = np.random.rand(len(x_data))
36-
# Needs a copy because projection should not be modified
37-
# inplace. If it is modified inplace, the widget and the thread
38-
# hold a reference to the same object. When the thread is
39-
# interrupted it is still modifying the object, but the widget
40-
# receives it (the modified object) with a delay.
41-
embedding = np.vstack((x_data, y_data)).T.copy()
42-
step += 1
43-
if step % (steps / 10) == 0:
44-
state.set_progress_value(100 * step / steps)
45-
46-
if state.is_interruption_requested():
47-
return res
48-
49-
res.embedding = embedding
50-
state.set_partial_results(res)
51-
return res
20+
def run(data: Table, embedding: Optional[np.ndarray], state: TaskState):
21+
res = Result(embedding=embedding)
22+
23+
# simulate wasteful calculation (increase 'steps')
24+
step, steps = 0, 10
25+
state.set_status("Calculating...")
26+
while step < steps:
27+
for _ in range(steps):
28+
x_data = np.array(np.mean(data.X, axis=1))
29+
if x_data.ndim == 2:
30+
x_data = x_data.ravel()
31+
y_data = np.ones(len(x_data))
32+
y_data[::2] = step % 2
33+
y_data = np.random.rand(len(x_data))
34+
# Needs a copy because projection should not be modified
35+
# inplace. If it is modified inplace, the widget and the thread
36+
# hold a reference to the same object. When the thread is
37+
# interrupted it is still modifying the object, but the widget
38+
# receives it (the modified object) with a delay.
39+
embedding = np.vstack((x_data, y_data)).T.copy()
40+
step += 1
41+
if step % (steps / 10) == 0:
42+
state.set_progress_value(100 * step / steps)
43+
44+
if state.is_interruption_requested():
45+
return res
46+
47+
res.embedding = embedding
48+
state.set_partial_result(res)
49+
return res
5250

5351

5452
class OWConcurrentWidget(OWDataProjectionWidget, ConcurrentWidgetMixin):
@@ -89,10 +87,9 @@ def _toggle_run(self):
8987

9088
# extend ConcurrentWidgetMixin
9189
def _prepare_task(self, state: TaskState):
92-
return partial(Runner.run, self.data,
93-
embedding=self.embedding, state=state)
90+
return partial(run, self.data, embedding=self.embedding, state=state)
9491

95-
def _set_partial_results(self, result: Results):
92+
def _on_partial_result(self, result: Result):
9693
assert isinstance(result.embedding, np.ndarray)
9794
assert len(result.embedding) == len(self.data)
9895
first_result = self.embedding is None
@@ -103,20 +100,17 @@ def _set_partial_results(self, result: Results):
103100
self.graph.update_coordinates()
104101
self.graph.update_density()
105102

106-
def _set_results(self, result: Results):
103+
def _on_done(self, result: Result):
107104
assert isinstance(result.embedding, np.ndarray)
108105
assert len(result.embedding) == len(self.data)
109-
super()._set_results(result)
106+
self.embedding = result.embedding
107+
self.run_button.setText("Start")
108+
self.commit()
110109

111110
def start(self):
112111
self.run_button.setText("Stop")
113112
super().start()
114113

115-
def on_done(self, future):
116-
super().on_done(future)
117-
self.run_button.setText("Start")
118-
self.commit()
119-
120114
# extend OWDataProjectionWidget
121115
def set_data(self, data: Table):
122116
super().set_data(data)

Orange/widgets/visualize/owfreeviz.py

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -21,41 +21,40 @@
2121
from Orange.widgets.visualize.utils.widget import OWAnchorProjectionWidget
2222

2323

24-
class Results(namespace):
24+
class Result(namespace):
2525
projector = None # type: FreeViz
2626
projection = None # type: FreeVizModel
2727

2828

29-
class Runner:
30-
MAX_ITERATIONS = 1000
29+
MAX_ITERATIONS = 1000
3130

32-
@staticmethod
33-
def run(data: Table, projector: FreeViz,
34-
projection: FreeVizModel, state: TaskState):
35-
res = Results(projector=projector,
36-
projection=projection)
37-
step, steps = 0, Runner.MAX_ITERATIONS
38-
initial = res.projector.components_.T
39-
state.set_status("Calculating...")
40-
while True:
41-
# Needs a copy because projection should not be modified inplace.
42-
# If it is modified inplace, the widget and the thread hold a
43-
# reference to the same object. When the thread is interrupted it
44-
# is still modifying the object, but the widget receives it
45-
# (the modified object) with a delay.
46-
res.projection = res.projector(data).copy()
47-
anchors = res.projector.components_.T
48-
res.projector.initial = anchors
49-
50-
state.set_partial_results(res)
51-
if np.allclose(initial, anchors, rtol=1e-5, atol=1e-4):
52-
return res
53-
initial = anchors
54-
55-
step += 1
56-
state.set_progress_value(100 * step / steps)
57-
if state.is_interruption_requested():
58-
return res
31+
32+
def run_freeviz(data: Table, projector: FreeViz,
33+
projection: FreeVizModel, state: TaskState):
34+
res = Result(projector=projector,
35+
projection=projection)
36+
step, steps = 0, MAX_ITERATIONS
37+
initial = res.projector.components_.T
38+
state.set_status("Calculating...")
39+
while True:
40+
# Needs a copy because projection should not be modified inplace.
41+
# If it is modified inplace, the widget and the thread hold a
42+
# reference to the same object. When the thread is interrupted it
43+
# is still modifying the object, but the widget receives it
44+
# (the modified object) with a delay.
45+
res.projection = res.projector(data).copy()
46+
anchors = res.projector.components_.T
47+
res.projector.initial = anchors
48+
49+
state.set_partial_result(res)
50+
if np.allclose(initial, anchors, rtol=1e-5, atol=1e-4):
51+
return res
52+
initial = anchors
53+
54+
step += 1
55+
state.set_progress_value(100 * step / steps)
56+
if state.is_interruption_requested():
57+
return res
5958

6059

6160
class OWFreeVizGraph(OWGraphWithAnchors):
@@ -127,7 +126,6 @@ def items():
127126

128127

129128
class OWFreeViz(OWAnchorProjectionWidget, ConcurrentWidgetMixin):
130-
MAX_ITERATIONS = 1000
131129
MAX_INSTANCES = 10000
132130

133131
name = "FreeViz"
@@ -205,35 +203,33 @@ def _toggle_run(self):
205203

206204
# extend ConcurrentWidgetMixin
207205
def _prepare_task(self, state: TaskState):
208-
return partial(Runner.run, self.effective_data,
206+
return partial(run_freeviz, self.effective_data,
209207
projector=self.projector,
210208
projection=self.projection,
211209
state=state)
212210

213-
def _set_partial_results(self, result: Results):
211+
def _on_partial_result(self, result: Result):
214212
assert isinstance(result.projector, FreeViz)
215-
self.projector = result.projector
216213
assert isinstance(result.projection, FreeVizModel)
214+
self.projector = result.projector
217215
self.projection = result.projection
218216
self.graph.update_coordinates()
219217
self.graph.update_density()
220218

221-
def _set_results(self, result: Results):
222-
assert isinstance(result.projector, FreeViz)
223-
assert isinstance(result.projection, FreeVizModel)
224-
super()._set_results(result)
219+
def _on_done(self, results: Result):
220+
assert isinstance(results.projector, FreeViz)
221+
assert isinstance(results.projection, FreeVizModel)
222+
self.projector = results.projector
223+
self.projection = results.projection
224+
self.graph.set_sample_size(None)
225+
self.run_button.setText("Start")
226+
self.commit()
225227

226228
def start(self):
227229
self.graph.set_sample_size(self.SAMPLE_SIZE)
228230
self.run_button.setText("Stop")
229231
super().start()
230232

231-
def on_done(self, future):
232-
super().on_done(future)
233-
self.graph.set_sample_size(None)
234-
self.run_button.setText("Start")
235-
self.commit()
236-
237233
def check_data(self):
238234
def error(err):
239235
err()

0 commit comments

Comments
 (0)