Skip to content

Commit 82c31bf

Browse files
committed
OWConcurrentProjectionWidget: Add an example
1 parent 8901f54 commit 82c31bf

File tree

2 files changed

+193
-0
lines changed

2 files changed

+193
-0
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
from typing import Any, Tuple, Optional
2+
from types import SimpleNamespace as namespace
3+
from functools import partial
4+
5+
import numpy as np
6+
7+
from Orange.data import Table
8+
from Orange.widgets import gui
9+
from Orange.widgets.settings import Setting
10+
from Orange.widgets.utils.concurrent import TaskState, ConcurrentWidgetMixin
11+
from Orange.widgets.utils.widgetpreview import WidgetPreview
12+
from Orange.widgets.visualize.utils.widget import OWDataProjectionWidget
13+
14+
15+
class Results(namespace):
16+
embedding = None # type: Optional[np.ndarray]
17+
18+
19+
class Runner:
20+
@staticmethod
21+
def run(data: Table, embedding: Optional[np.ndarray], state: TaskState):
22+
res = Results(embedding=embedding)
23+
24+
# simulate wasteful calculation (increase 'steps')
25+
step, steps = 0, 10
26+
state.set_status("Calculating...")
27+
while step < steps:
28+
for i in range(steps):
29+
x_data = np.array(np.mean(data.X, axis=1))
30+
if x_data.ndim == 2:
31+
x_data = x_data.ravel()
32+
y_data = np.ones(len(x_data))
33+
y_data[::2] = step % 2
34+
y_data = np.random.rand(len(x_data))
35+
embedding = np.vstack((x_data, y_data)).T
36+
step += 1
37+
if step % (steps / 10) == 0:
38+
state.set_progress_value(100 * step / steps)
39+
40+
if state.is_interruption_requested():
41+
return res
42+
43+
res.embedding = embedding
44+
state.set_partial_results(("embedding", res.embedding))
45+
return res
46+
47+
48+
class OWConcurrentProjectionWidget(OWDataProjectionWidget,
49+
ConcurrentWidgetMixin):
50+
name = "Projection"
51+
param = Setting(0)
52+
53+
def __init__(self):
54+
OWDataProjectionWidget.__init__(self)
55+
ConcurrentWidgetMixin.__init__(self)
56+
self.embedding = None # type: Optional[np.ndarray]
57+
58+
# GUI
59+
def _add_controls(self):
60+
box = gui.vBox(self.controlArea, True)
61+
gui.comboBox(
62+
box, self, "param", label="Parameter:",
63+
items=["Param A", "Param B"], labelWidth=80,
64+
callback=self.__param_combo_changed
65+
)
66+
self.run_button = gui.button(box, self, "Start", self._toggle_run)
67+
super()._add_controls()
68+
69+
def __param_combo_changed(self):
70+
super().start()
71+
72+
def _toggle_run(self):
73+
if self.data is None:
74+
return
75+
76+
# Pause task
77+
if self.task is not None:
78+
self.cancel()
79+
self.run_button.setText("Resume")
80+
self.commit()
81+
# Resume task
82+
else:
83+
self.start()
84+
85+
# extend ConcurrentWidgetMixin
86+
def _start_partial(self, state: TaskState):
87+
return partial(Runner.run, self.data,
88+
embedding=self.embedding, state=state)
89+
90+
def _set_partial_results(self, result: Tuple[str, Any]):
91+
which, res = result
92+
if which == "embedding":
93+
assert isinstance(res, np.ndarray) and len(res) == len(self.data)
94+
first_result = self.embedding is None
95+
self.embedding = res
96+
if first_result:
97+
self.setup_plot()
98+
else:
99+
self.graph.update_coordinates()
100+
self.graph.update_density()
101+
else:
102+
assert False, which
103+
104+
def start(self):
105+
self.run_button.setText("Stop")
106+
super().start()
107+
108+
def finish(self, future):
109+
super().finish(future)
110+
self.run_button.setText("Start")
111+
self.commit()
112+
113+
# extend OWDataProjectionWidget
114+
def set_data(self, data: Table):
115+
super().set_data(data)
116+
if self._invalidated:
117+
self.init_projection()
118+
self.start()
119+
120+
def init_projection(self):
121+
if self.data is None:
122+
return
123+
x_data = np.array(np.mean(self.data.X, axis=1))
124+
if x_data.ndim == 2:
125+
x_data = x_data.ravel()
126+
y_data = np.ones(len(x_data))
127+
self.embedding = np.vstack((x_data, y_data)).T
128+
129+
def get_embedding(self):
130+
if self.embedding is None:
131+
self.valid_data = None
132+
return None
133+
134+
self.valid_data = np.all(np.isfinite(self.embedding), 1)
135+
return self.embedding
136+
137+
def clear(self):
138+
super().clear()
139+
self.cancel_task_no_wait()
140+
self.embedding = None
141+
142+
def onDeleteWidget(self):
143+
self.on_delete_widget()
144+
super().onDeleteWidget()
145+
146+
147+
if __name__ == "__main__":
148+
table = Table("iris")
149+
WidgetPreview(OWConcurrentProjectionWidget).run(
150+
set_data=table, set_subset_data=table[::10])
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Test methods with long descriptive names can omit docstrings
2+
# pylint: disable=missing-docstring
3+
import unittest
4+
from Orange.widgets.tests.base import (
5+
WidgetTest, WidgetOutputsTestMixin, ProjectionWidgetTestMixin
6+
)
7+
from Orange.widgets.utils.tests.concurrent_example import (
8+
OWConcurrentProjectionWidget
9+
)
10+
11+
12+
class TestOWConcurrentProjectionWidget(WidgetTest, ProjectionWidgetTestMixin,
13+
WidgetOutputsTestMixin):
14+
@classmethod
15+
def setUpClass(cls):
16+
super().setUpClass()
17+
WidgetOutputsTestMixin.init(cls)
18+
19+
cls.signal_name = "Data"
20+
cls.signal_data = cls.data
21+
cls.same_input_output_domain = False
22+
23+
def setUp(self):
24+
self.widget = self.create_widget(OWConcurrentProjectionWidget)
25+
26+
def test_button_no_data(self):
27+
self.widget.run_button.click()
28+
self.assertEqual(self.widget.run_button.text(), "Start")
29+
30+
def test_button_with_data(self):
31+
self.send_signal(self.widget.Inputs.data, self.data)
32+
self.assertEqual(self.widget.run_button.text(), "Stop")
33+
self.wait_until_stop_blocking()
34+
self.assertEqual(self.widget.run_button.text(), "Start")
35+
36+
def test_button_toggle(self):
37+
self.send_signal(self.widget.Inputs.data, self.data)
38+
self.widget.run_button.click()
39+
self.assertEqual(self.widget.run_button.text(), "Resume")
40+
41+
42+
if __name__ == "__main__":
43+
unittest.main()

0 commit comments

Comments
 (0)