Repository: vispy/vispy

Issue:
New example: simple scientific interactive plot with vispy.gloo
This is an example illustrating the scientific plotting capabilities of vispy, using only vispy.gloo. Of course, this example will be useless in the future once we have high-level plotting interfaces. It is just illustrative to have that in the meantime.

We choose a mathematical function `x --> f(x)` and display it using a VBO. At first, we could simply use `GL_LINE_STRIP`, but  we could also use antialiased lines. Then, we can implement interactivity (panning and zooming) on the GPU (vertex shader).



Discussion:

Resolving patch:
diff --git a/examples/demo/brain.py b/examples/demo/brain.py
new file mode 100644
--- /dev/null
+++ b/examples/demo/brain.py
@@ -0,0 +1,158 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+3D brain mesh viewer.
+"""
+
+from timeit import default_timer
+import numpy as np
+
+from vispy import gloo
+from vispy import app
+from vispy.util.transforms import perspective, translate, rotate
+from vispy.util import get_data_file
+
+brain = np.load(get_data_file('brain/brain.npz'))
+data = brain['vertex_buffer']
+faces = brain['index_buffer']
+    
+VERT_SHADER = """
+#version 120
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_projection;
+uniform vec4 u_color;
+
+attribute vec3 a_position;
+attribute vec3 a_normal;
+attribute vec4 a_color;
+
+varying vec3 v_position;
+varying vec3 v_normal;
+varying vec4 v_color;
+
+void main()
+{
+    v_normal = a_normal;
+    v_position = a_position;
+    v_color = a_color * u_color;
+    gl_Position = u_projection * u_view * u_model * vec4(a_position,1.0);
+}
+"""
+
+FRAG_SHADER = """
+#version 120
+uniform mat4 u_model;
+uniform mat4 u_view;
+uniform mat4 u_normal;
+
+uniform vec3 u_light_intensity;
+uniform vec3 u_light_position;
+
+varying vec3 v_position;
+varying vec3 v_normal;
+varying vec4 v_color;
+
+void main()
+{
+    // Calculate normal in world coordinates
+    vec3 normal = normalize(u_normal * vec4(v_normal,1.0)).xyz;
+
+    // Calculate the location of this fragment (pixel) in world coordinates
+    vec3 position = vec3(u_view*u_model * vec4(v_position, 1));
+
+    // Calculate the vector from this pixels surface to the light source
+    vec3 surfaceToLight = u_light_position - position;
+
+    // Calculate the cosine of the angle of incidence (brightness)
+    float brightness = dot(normal, surfaceToLight) /
+                      (length(surfaceToLight) * length(normal));
+    brightness = max(min(brightness,1.0),0.0);
+
+    // Calculate final color of the pixel, based on:
+    // 1. The angle of incidence: brightness
+    // 2. The color/intensities of the light: light.intensities
+    // 3. The texture and texture coord: texture(tex, fragTexCoord)
+
+    // Specular lighting.
+    vec3 surfaceToCamera = vec3(0.0, 0.0, 1.0) - position;
+    vec3 K = normalize(normalize(surfaceToLight) + normalize(surfaceToCamera));
+    float specular = clamp(pow(abs(dot(normal, K)), 40.), 0.0, 1.0);
+    
+    gl_FragColor = v_color * brightness * vec4(u_light_intensity, 1);
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, close_keys='escape')
+        self.size = 800, 600
+
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        
+        self.theta, self.phi = -80, 180
+        self.translate = 3
+
+        self.faces = gloo.IndexBuffer(faces)
+        self.program.bind(gloo.VertexBuffer(data))
+        
+        self.program['u_color'] = 1, 1, 1, 1
+        self.program['u_light_position'] = (1., 1., 1.)
+        self.program['u_light_intensity'] = (1., 1., 1.)
+        
+        self._t0 = default_timer()
+        self._timer = app.Timer(1. / 60)
+        self._timer.connect(self.on_timer)
+        self._timer.start()
+        
+        self.update_matrices()
+
+    def update_matrices(self):
+        self.view = np.eye(4, dtype=np.float32)
+        self.model = np.eye(4, dtype=np.float32)
+        self.projection = np.eye(4, dtype=np.float32)
+        
+        rotate(self.model, self.theta, 1, 0, 0)
+        rotate(self.model, self.phi, 0, 1, 0)
+        
+        translate(self.view, 0, 0, -self.translate)
+        
+        self.program['u_model'] = self.model
+        self.program['u_view'] = self.view
+        self.program['u_normal'] = np.array(np.matrix(np.dot(self.view, 
+                                                             self.model)).I.T)
+        
+    def on_initialize(self, event):
+        gloo.set_state(blend=False, depth_test=True, polygon_offset_fill=True)
+
+    def on_timer(self, event):
+        elapsed = default_timer() - self._t0
+        self.phi = 180 + elapsed * 50.
+        self.update_matrices()
+        self.update()
+
+    def on_resize(self, event):
+        width, height = event.size
+        gloo.set_viewport(0, 0, width, height)
+        self.projection = perspective(45.0, width / float(height), 1.0, 20.0)
+        self.program['u_projection'] = self.projection
+
+    def on_mouse_wheel(self, event):
+        self.translate += -event.delta[1]/5.
+        self.translate = max(2, self.translate)
+        self.update_matrices()
+        self.update()
+
+    def on_draw(self, event):
+        gloo.clear()
+        self.program.draw('triangles', indices=self.faces)
+
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()
diff --git a/examples/demo/signals.py b/examples/demo/signals.py
new file mode 100644
--- /dev/null
+++ b/examples/demo/signals.py
@@ -0,0 +1,130 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vispy: gallery 2
+# Copyright (c) 2014, Vispy Development Team.
+# Distributed under the (new) BSD License. See LICENSE.txt for more info.
+
+"""
+Multiple digital signals.
+"""
+
+from vispy import gloo
+from vispy import app
+import numpy as np
+import math
+
+m = 20
+n = 25000
+x = np.tile(np.linspace(-1., 1., n), m)
+y = .1 * np.random.randn(m, n)
+y += np.arange(m).reshape((-1, 1))
+
+data = np.zeros(n*m, dtype=[
+    ('a_position', np.float32, 2),
+    ('a_color', np.float32, 3),
+    ('a_index', np.float32, 1),
+])
+
+data['a_position'] = np.zeros((n*m, 2), dtype=np.float32)
+data['a_position'][:, 0] = x
+data['a_position'][:, 1] = .9*(y.ravel()/y.max()*2-1)
+
+data['a_color'] = np.repeat(np.random.uniform(size=(m, 3), low=.5, high=.9),
+                            n, axis=0)
+
+data['a_index'] = np.repeat(np.arange(m), n)
+
+VERT_SHADER = """
+#version 120
+attribute vec2 a_position;
+attribute float a_index;
+varying float v_index;
+
+attribute vec3 a_color;
+varying vec3 v_color;
+
+uniform vec2 u_pan;
+uniform vec2 u_scale;
+
+void main() {
+    
+    vec2 position_tr = u_scale * (a_position + u_pan);
+    gl_Position = vec4(position_tr, 0.0, 1.0);
+    v_color = a_color;
+    v_index = a_index;
+}
+"""
+
+FRAG_SHADER = """
+#version 120
+varying vec3 v_color;
+varying float v_index;
+void main() {
+    gl_FragColor = vec4(v_color, 1.0);
+    if ((fract(v_index) > .00001) && (fract(v_index) < .99999))
+        gl_FragColor.a = 0.;
+}
+"""
+
+
+class Canvas(app.Canvas):
+    def __init__(self):
+        app.Canvas.__init__(self, close_keys='escape')
+        self.program = gloo.Program(VERT_SHADER, FRAG_SHADER)
+        self.program.bind(gloo.VertexBuffer(data))
+        
+        self.program['u_pan'] = (0., 0.)
+        self.program['u_scale'] = (1., 1.)
+
+    def on_initialize(self, event):
+        gloo.set_state(clear_color=(1, 1, 1, 1), blend=True, 
+                       blend_func=('src_alpha', 'one_minus_src_alpha'))
+
+    def on_resize(self, event):
+        self.width, self.height = event.size
+        gloo.set_viewport(0, 0, self.width, self.height)
+
+    def on_draw(self, event):
+        gloo.clear(color=(0.0, 0.0, 0.0, 1.0))
+        self.program.draw('line_strip')
+
+    def _normalize(self, x_y):
+        x, y = x_y
+        w, h = float(self.width), float(self.height)
+        return x/(w/2.)-1., y/(h/2.)-1.
+            
+    def on_mouse_move(self, event):
+        if event.is_dragging:
+            x0, y0 = self._normalize(event.press_event.pos)
+            x1, y1 = self._normalize(event.last_event.pos)
+            x, y = self._normalize(event.pos)
+            dx, dy = x - x1, -(y - y1)
+            button = event.press_event.button
+            
+            pan_x, pan_y = self.program['u_pan']
+            scale_x, scale_y = self.program['u_scale']
+            
+            if button == 1:
+                self.program['u_pan'] = (pan_x+dx/scale_x, pan_y+dy/scale_y)
+            elif button == 2:
+                scale_x_new, scale_y_new = (scale_x * math.exp(2.5*dx),
+                                            scale_y * math.exp(2.5*dy))
+                self.program['u_scale'] = (scale_x_new, scale_y_new)
+                self.program['u_pan'] = (pan_x - 
+                                         x0 * (1./scale_x - 1./scale_x_new), 
+                                         pan_y + 
+                                         y0 * (1./scale_y - 1./scale_y_new))
+            self.update()
+            
+    def on_mouse_wheel(self, event):
+        dx = np.sign(event.delta[1])*.05
+        scale_x, scale_y = self.program['u_scale']     
+        scale_x_new, scale_y_new = (scale_x * math.exp(2.5*dx), 
+                                    scale_y * math.exp(2.5*dx))
+        self.program['u_scale'] = (scale_x_new, scale_y_new)
+        self.update()
+        
+if __name__ == '__main__':
+    c = Canvas()
+    c.show()
+    app.run()


Test patch: