Skip to content

Commit 071d442

Browse files
authored
Merge pull request jupyter-widgets#233 from vidartf/picker-example
Add a more extensive picker example
2 parents 03a509d + 44b4a4b commit 071d442

File tree

1 file changed

+336
-0
lines changed

1 file changed

+336
-0
lines changed

examples/Picker.ipynb

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Visualize iterative minima fininding algorithm"
8+
]
9+
},
10+
{
11+
"cell_type": "markdown",
12+
"metadata": {},
13+
"source": [
14+
"This notebook outlines a way of using pythreejs as a way of getting input from the user with a `Picker`, and then triggering kernel-side computations based on this, which in turn will update the visualization.\n",
15+
"\n",
16+
"The example case here is an iterative algorithm that minimizes a function of two variables. The visualization will be a grid-based surface plot of this function. By double-clicking the surface, the user will start the algorithm. The results of the alogrithm will be live-plotted as a line on top of the surface."
17+
]
18+
},
19+
{
20+
"cell_type": "markdown",
21+
"metadata": {},
22+
"source": [
23+
"## Importing dependencies\n",
24+
"\n",
25+
"First, we import the dependencies we will need."
26+
]
27+
},
28+
{
29+
"cell_type": "code",
30+
"execution_count": null,
31+
"metadata": {},
32+
"outputs": [],
33+
"source": [
34+
"from pythreejs import *\n",
35+
"import numpy as np\n",
36+
"from IPython.display import display\n",
37+
"from ipywidgets import HTML, Output, VBox, jslink\n",
38+
"\n",
39+
"view_width = 600\n",
40+
"view_height = 400"
41+
]
42+
},
43+
{
44+
"cell_type": "markdown",
45+
"metadata": {},
46+
"source": [
47+
"## Define our iterative algorithm\n",
48+
"\n",
49+
"Here is a simple random descent method for finding local minima, that yields its iterative values. There are many issues with this algorithm, and as such it has a lot of potential for improvement! (left as an exercise for the reader)"
50+
]
51+
},
52+
{
53+
"cell_type": "code",
54+
"execution_count": null,
55+
"metadata": {},
56+
"outputs": [],
57+
"source": [
58+
"def find_minima(f, start=(0, 0), xlim=None, ylim=None):\n",
59+
" rate = 0.1 # Learning rate\n",
60+
" max_iters = 200 # maximum number of iterations\n",
61+
" iters = 0 # iteration counter\n",
62+
" \n",
63+
" cur = np.array(start[:2])\n",
64+
" previous_step_size = 1 #\n",
65+
" cur_val = f(cur[0], cur[1]) \n",
66+
" \n",
67+
" while (iters < max_iters and\n",
68+
" xlim[0] <= cur[0] <= xlim[1] and ylim[0] <= cur[1] <= ylim[1]):\n",
69+
" iters = iters + 1\n",
70+
" candidate = cur - rate * (np.random.rand(2) - 0.5)\n",
71+
" candidate_val = f(candidate[0], candidate[1])\n",
72+
" if candidate_val >= cur_val:\n",
73+
" continue # Bad guess, try again\n",
74+
" prev = cur\n",
75+
" cur = candidate\n",
76+
" cur_val = candidate_val\n",
77+
" previous_step_size = np.abs(cur - prev)\n",
78+
" yield tuple(cur) + (cur_val,)\n",
79+
"\n",
80+
" print(\"The local minimum occurs at\", cur)"
81+
]
82+
},
83+
{
84+
"cell_type": "markdown",
85+
"metadata": {},
86+
"source": [
87+
"## Define test function\n",
88+
"\n",
89+
"Give a function to test minima finder on. Here we simply use $ f(x, y) = x^2 + y^2 $. You can also try with the surface\n",
90+
"$ f(x, y) = x^2 - y^2 $ to see the effect of an instable surface."
91+
]
92+
},
93+
{
94+
"cell_type": "code",
95+
"execution_count": null,
96+
"metadata": {},
97+
"outputs": [],
98+
"source": [
99+
"def f(x, y):\n",
100+
" return x ** 2 + y ** 2"
101+
]
102+
},
103+
{
104+
"cell_type": "markdown",
105+
"metadata": {},
106+
"source": [
107+
"## Setup figure\n",
108+
"\n",
109+
"Interrogate function on a grid in order to visualize. This uses numpy helper code to generate the grid, and evaluate the function. Note the two evalutations: One at the grid lattice points, and one in the center of each grid square!"
110+
]
111+
},
112+
{
113+
"cell_type": "code",
114+
"execution_count": null,
115+
"metadata": {},
116+
"outputs": [],
117+
"source": [
118+
"nx, ny = (20, 20) # grid resolution\n",
119+
"xmax = 1 # grid extent (+/-)\n",
120+
"x = np.linspace(-xmax, xmax, nx)\n",
121+
"y = np.linspace(-xmax, xmax, ny)\n",
122+
"step = x[1] - x[0]\n",
123+
"xx, yy = np.meshgrid(x, y)\n",
124+
"# Grid lattice values:\n",
125+
"grid_z = np.vectorize(f)(xx, yy)\n",
126+
"# Grid square center values:\n",
127+
"center_z = np.vectorize(f)(0.5 * step + xx[:-1,:-1], 0.5 * step + yy[:-1,:-1])"
128+
]
129+
},
130+
{
131+
"cell_type": "markdown",
132+
"metadata": {},
133+
"source": [
134+
"Setup code for 3D visualization with user point picking with mouse. Here we use the `SurfaceGeometry` and `SurfaceGrid` helper classes (not direct three.js classes)."
135+
]
136+
},
137+
{
138+
"cell_type": "code",
139+
"execution_count": null,
140+
"metadata": {},
141+
"outputs": [],
142+
"source": [
143+
"# Surface geometry:\n",
144+
"surf_g = SurfaceGeometry(z=list(grid_z.flat), \n",
145+
" width=2 * xmax,\n",
146+
" height=2 * xmax,\n",
147+
" width_segments=nx - 1,\n",
148+
" height_segments=ny - 1)\n",
149+
"\n",
150+
"# Surface material. Note that the map uses the center-evaluated function-values:\n",
151+
"surf = Mesh(geometry=surf_g,\n",
152+
" material=MeshLambertMaterial(map=height_texture(center_z, 'YlGnBu_r')))\n",
153+
"\n",
154+
"# Grid-lines for the surface:\n",
155+
"surfgrid = SurfaceGrid(geometry=surf_g, material=LineBasicMaterial(color='black'),\n",
156+
" position=[0, 0, 1e-2]) # Avoid overlap by lifting grid slightly"
157+
]
158+
},
159+
{
160+
"cell_type": "code",
161+
"execution_count": null,
162+
"metadata": {},
163+
"outputs": [],
164+
"source": [
165+
"# Set up scene:\n",
166+
"key_light = DirectionalLight(color='white', position=[3, 5, 1], intensity=0.4)\n",
167+
"c = PerspectiveCamera(position=[0, 3, 3], up=[0, 0, 1], aspect=view_width / view_height,\n",
168+
" children=[key_light])\n",
169+
"\n",
170+
"scene = Scene(children=[surf, c, surfgrid, AmbientLight(intensity=0.8)])"
171+
]
172+
},
173+
{
174+
"cell_type": "markdown",
175+
"metadata": {},
176+
"source": [
177+
"We will now plot our figure. Note that initially, this will not have the interactive features, but we will add the interactivity below."
178+
]
179+
},
180+
{
181+
"cell_type": "code",
182+
"execution_count": null,
183+
"metadata": {
184+
"scrolled": false
185+
},
186+
"outputs": [],
187+
"source": [
188+
"renderer = Renderer(camera=c, scene=scene,\n",
189+
" width=view_width, height=view_height,\n",
190+
" controls=[OrbitControls(controlling=c)])\n",
191+
"\n",
192+
"out = Output() # An Output for displaying captured print statements\n",
193+
"box = VBox([renderer])\n",
194+
"display(box)"
195+
]
196+
},
197+
{
198+
"cell_type": "markdown",
199+
"metadata": {},
200+
"source": [
201+
"## Adding pickers"
202+
]
203+
},
204+
{
205+
"cell_type": "markdown",
206+
"metadata": {},
207+
"source": [
208+
"First, let us add a simple position tracker. This will find what point on the surface that the mouse is hovering over. We will represent this point with a pink sphere."
209+
]
210+
},
211+
{
212+
"cell_type": "code",
213+
"execution_count": null,
214+
"metadata": {},
215+
"outputs": [],
216+
"source": [
217+
"# Picker object\n",
218+
"hover_picker = Picker(controlling=surf, event='mousemove')\n",
219+
"renderer.controls = renderer.controls + [hover_picker]\n",
220+
"\n",
221+
"# A sphere for representing the current point on the surface\n",
222+
"hover_point = Mesh(geometry=SphereGeometry(radius=0.05),\n",
223+
" material=MeshLambertMaterial(color='hotpink'))\n",
224+
"scene.add(hover_point)\n",
225+
"\n",
226+
"# Have sphere follow picker point:\n",
227+
"jslink((hover_point, 'position'), (hover_picker, 'point'))"
228+
]
229+
},
230+
{
231+
"cell_type": "markdown",
232+
"metadata": {},
233+
"source": [
234+
"Next, we will observe the changes to the hover point, and display its coordinates in a label which we add to the figure above:"
235+
]
236+
},
237+
{
238+
"cell_type": "code",
239+
"execution_count": null,
240+
"metadata": {},
241+
"outputs": [],
242+
"source": [
243+
"coord_label = HTML() # A label for showing hover picker coordinates\n",
244+
"\n",
245+
"def on_hover_change(change):\n",
246+
" coord_label.value = 'Pink point at (%.3f, %.3f, %.3f)' % tuple(change['new'])\n",
247+
"\n",
248+
"on_hover_change({'new': hover_point.position})\n",
249+
"hover_picker.observe(on_hover_change, names=['point'])\n",
250+
"box.children = (coord_label,) + box.children"
251+
]
252+
},
253+
{
254+
"cell_type": "markdown",
255+
"metadata": {},
256+
"source": [
257+
"Render the scene, and include some useful information:"
258+
]
259+
},
260+
{
261+
"cell_type": "markdown",
262+
"metadata": {},
263+
"source": [
264+
"Next, we set up a picker for when the user double clikcs on the surface. This should trigger the execution and visualization of the alogrithm."
265+
]
266+
},
267+
{
268+
"cell_type": "code",
269+
"execution_count": null,
270+
"metadata": {},
271+
"outputs": [],
272+
"source": [
273+
"# Create our picker for the double-click event (\"dblclick\")\n",
274+
"click_picker = Picker(controlling=surf, event='dblclick')\n",
275+
"\n",
276+
"def on_click(change):\n",
277+
" value = change['new']\n",
278+
" with out:\n",
279+
" print('Clicked on %s' % (value,))\n",
280+
"\n",
281+
" # Add a red sphere on the picked point\n",
282+
" point = Mesh(geometry=SphereGeometry(radius=0.05), \n",
283+
" material=MeshLambertMaterial(color='red'),\n",
284+
" position=value)\n",
285+
" scene.add(point)\n",
286+
" \n",
287+
" # Plot solution as a red line, this will start out empty\n",
288+
" points = [value]\n",
289+
" line = Line2(geometry=LineGeometry(positions=points), material=LineMaterial(color='red', linewidth=2))\n",
290+
" scene.add(line)\n",
291+
" with out: # Pick up any print statements in the algorithm\n",
292+
" for pt in find_minima(f, value, [-xmax, xmax], [-xmax, xmax]):\n",
293+
" # For each point, update the line:\n",
294+
" pt = list(pt)\n",
295+
" pt[2] += 1e-2 # offset to clear surface\n",
296+
" line.geometry = LineGeometry(positions=np.vstack([line.geometry.positions, pt]))\n",
297+
"\n",
298+
"\n",
299+
"# When the point selected by the picker changes, trigger our function:\n",
300+
"click_picker.observe(on_click, names=['point'])\n",
301+
"\n",
302+
"# Update figure:\n",
303+
"renderer.controls = renderer.controls + [click_picker]\n",
304+
"box.children = box.children + (out,)"
305+
]
306+
},
307+
{
308+
"cell_type": "code",
309+
"execution_count": null,
310+
"metadata": {},
311+
"outputs": [],
312+
"source": []
313+
}
314+
],
315+
"metadata": {
316+
"kernelspec": {
317+
"display_name": "Python 3",
318+
"language": "python",
319+
"name": "python3"
320+
},
321+
"language_info": {
322+
"codemirror_mode": {
323+
"name": "ipython",
324+
"version": 3
325+
},
326+
"file_extension": ".py",
327+
"mimetype": "text/x-python",
328+
"name": "python",
329+
"nbconvert_exporter": "python",
330+
"pygments_lexer": "ipython3",
331+
"version": "3.6.6"
332+
}
333+
},
334+
"nbformat": 4,
335+
"nbformat_minor": 2
336+
}

0 commit comments

Comments
 (0)