view pylearn/dataset_ops/gldataset.py @ 1496:93b8373c6735

Prefix loggers with 'pylearn.' to ensure there is no conflict when using Pylearn code within another library
author Olivier Delalleau <delallea@iro>
date Mon, 22 Aug 2011 11:28:48 -0400
parents 912be602c3ac
children
line wrap: on
line source

"""Demonstrate a complicated dynamically-generated dataset.
"""

# __init__.py

import sys, copy, logging, sys

import Image #PIL

from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from pyglew import *

from glviewer import load_texture

import numpy

import theano
from theano.compile import shared
from theano.compile import pfunc as function

_logger = logging.getLogger('gldataset')
def debug(*msg): _logger.debug(' '.join(str(m) for m in msg))
def info(*msg): _logger.info(' '.join(str(m) for m in msg))
def warn(*msg): _logger.warn(' '.join(str(m) for m in msg))
def warning(*msg): _logger.warning(' '.join(str(m) for m in msg))
def error(*msg): _logger.error(' '.join(str(m) for m in msg))

def init_GL(shape=(64,64), title='Offscreen rendering using FB0'):
    if not init_GL.done:
        w, h = shape
        init_GL.done = True
        info('initializing OpenGl subsystem')
        glutInit (sys.argv)
        glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH)
        glutInitWindowSize (w,h)
        init_GL.window = glutCreateWindow (title)
        glewInit()

        glEnable(GL_TEXTURE_2D)
        glClearColor(0.0, 0.0, 0.0, 0.0)    # This Will Clear The Background Color To Black
        glClearDepth(1.0)                                   # Enables Clearing Of The Depth Buffer
        glDepthFunc(GL_LESS)                                # The Type Of Depth Test To Do
        glEnable(GL_DEPTH_TEST)                             # Enables Depth Testing
        glShadeModel(GL_SMOOTH)                             # Enables Smooth Color Shading
            
        #glMatrixMode(GL_PROJECTION)
        #glLoadIdentity()                                    # Reset The Projection Matrix
                                                            # Calculate The Aspect Ratio Of The Window
        #gluPerspective(45.0, float(64)/float(64), 0.1, 100.0)
        glMatrixMode(GL_MODELVIEW)
init_GL.done = False

class PBufRenderer(object):
    """Render an OpenGL program to a framebuffer instead of the screen.

    The way to use this class is to enclose all the OpenGL commands you want to render between
    a call to setup() and a call to render().  So you would render a frame like this:

    .. code-block:: python
        
        p = PBufRenderer(shape)
        p.setup()
        my_display_code()
        a = p.render()
        my_display_code()
        b = p.render()

    After running this code, 'a' and 'b' will be numpy arrays of shape `shape` + (3,) containing an
    RBG rendering of your display_code.
    """
    def __init__(self, size=(128,128), upside_down=False):
        """ Offscreen rendering
        
        Save an offscreen rendering of size (w,h) to filename.
        """
        
        def round2 (n):
            """ Get nearest power of two superior to n """
            f = 1
            while f<n:
                f*= 2
            return f

        if size == None:
            size = (512,512)
        w = round2 (size[0])
        h = round2 (size[1])

        image = Image.new ("RGB", (w, h), (0, 0, 0))
        bits = image.tostring("raw", "RGBX", 0, -1)

        debug('allocating framebuffer')
        framebuffer = glGenFramebuffersEXT (1)
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer)

        debug('allocating depthbuffer')
        depthbuffer = glGenRenderbuffersEXT (1)
        glBindRenderbufferEXT (GL_RENDERBUFFER_EXT,depthbuffer)
        glRenderbufferStorageEXT (GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h)
        
        # Create texture to render to
        debug('allocating dynamic texture')
        texture = glGenTextures (1)
        glBindTexture (GL_TEXTURE_2D, texture)
        glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
        glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
        # Question: why do we need to upload a texture that we are rendering *to* ?
        glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB, w, h, 0,
                        GL_RGB, GL_UNSIGNED_BYTE, bits)

        # store variables for later use.
        self.texture = texture
        self.framebuffer = framebuffer
        self.depthbuffer = depthbuffer
        self.image = image
        self.bits = bits
        self.size = size
        self.texture_size = (w,h)
        self.upside_down = upside_down

        # set the screen as output
        glBindRenderbufferEXT (GL_RENDERBUFFER_EXT, 0)
        glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0)

    def __del__(self):
        glBindRenderbufferEXT (GL_RENDERBUFFER_EXT, 0)
        glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0)
        glDeleteTextures (1,[self.texture])
        glDeleteFramebuffersEXT (1, [self.framebuffer])
        glDeleteRenderbuffersExt (1, [self.depthbuffer])

    def setup(self):
        glBindRenderbufferEXT (GL_RENDERBUFFER_EXT, self.depthbuffer)
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,  self.framebuffer)
        glFramebufferTexture2DEXT (GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                                   GL_TEXTURE_2D,  self.texture, 0);
        glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, 
                                     GL_RENDERBUFFER_EXT,  self.depthbuffer);
                                    
        status = glCheckFramebufferStatusEXT (GL_FRAMEBUFFER_EXT);
        if status != GL_FRAMEBUFFER_COMPLETE_EXT:
            raise RuntimeError( "Error in framebuffer activation")

        # Re-orient viewport
        glViewport (0, 0, self.size[0], self.size[1])
        glMatrixMode (GL_PROJECTION)
        glLoadIdentity()
        gluPerspective (40.,self.size[0]/float(self.size[1]),1.,40.)
        glMatrixMode (GL_MODELVIEW)
        glLoadIdentity()
        gluLookAt (0,0,10, 0,0,0, 0,1,0)

    def render(self):
        # TODO: Can we get away with glFlush?
        glFinish() #renders to our framebuffer

        # read back the framebuffer to self.image
        glBindTexture (GL_TEXTURE_2D, self.texture)
        w,h = self.texture_size
        data = glReadPixels (0, 0, w, h, GL_RGB,  GL_UNSIGNED_BYTE)
        rval = numpy.fromstring(data, dtype='uint8', count=w*h*3).reshape((w,h,3))
        if self.size != self.texture_size:
            rval = rval[:self.size[0], :self.size[1],:]

        # return to default state of screen rendering
        glBindRenderbufferEXT (GL_RENDERBUFFER_EXT, 0)
        glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,  0)
        if self.upside_down:
            return rval
        else:
            return rval[::-1,:,:]

class OpenGlMovieFromImage(theano.Op):
    """Helper base class to factor code used by Ops that want to make a movie from an input
    image, using OpenGL.  The subclass specifies how to actually make the movie.
    """

    def __init__(self, width, height, upside_down=False):
        """To set up the renderer, we need to know the frame size of the images.
        Setting up the renderer for each image is much slower.
        """
        init_GL() #global initialization is no-op after first call
        self.width=width
        self.height=height
        self.upside_down=upside_down

        self.renderer = None
        # Delay construction of renderer until after merge-optimization
        #PBufRenderer((width, height), upside_down=upside_down)

        #TODO: put texture into output state as reusable resource
        self.texture = glGenTextures(1)

    def __del__(self):
        glDeleteTextures (1,[self.texture])

    def __eq__(self, other):
        return type(self) == type(other) \
                and self.width == other.width \
                and self.height == other.height \
                and self.upside_down == other.upside_down

    def __hash__(self):
        return hash(type(self)) ^ hash(self.width) ^ hash(self.height) ^ hash(self.upside_down)

    def make_node(self, x, istate):
        _x = theano.tensor.as_tensor_variable(x)
        if _x.type.dtype != 'uint8':
            raise TypeError('must be 2- or 3-tensor of uint8', x)
        if _x.type.broadcastable != (False, False) \
                and _x.type.broadcastable != (False, False, False):
            raise TypeError('must be a 2- or 3-tensor of uint8', x)
        if not isinstance(istate, theano.Variable):
            raise TypeError("variable expected", istate)
        o_type = theano.tensor.TensorType(dtype='uint8', broadcastable=[False, False, False, False])
        state_type = theano.gof.type.generic
        return theano.Apply(self, [x, istate], [o_type(), state_type()])

    def perform(self, node, (x, istate), (z_storage, ostate_storage)):
        if self.renderer is None:
            self.renderer = PBufRenderer((self.width, self.height), upside_down=self.upside_down)

        ostate = copy.deepcopy(istate)
        self.renderer.setup() 

        glBindTexture(GL_TEXTURE_2D, self.texture)   # 2d texture (x and y size)
        load_texture(x)

        z = numpy.zeros(self.z_shape, dtype='uint8')
        for i in xrange(self.n_frames):
            self.perform_set_state(istate, ostate, i)
            self.perform_display(x, ostate, i)
            di = self.renderer.render()
            z[i] = di

        # store output images 
        z_storage[0] = z

        # store next state ostate_storage
        ostate_storage[0] = ostate

class ImageOnSpinningCube(OpenGlMovieFromImage):
    def __init__(self, (n_frames, width, height), upside_down=False):
        super(ImageOnSpinningCube, self).__init__(width, height, upside_down=upside_down)
        self.n_frames = n_frames
        self.z_shape = (n_frames, width, height, 3)

    def __eq__(self, other):
        return super(ImageOnSpinningCube, self).__eq__(other) \
                and self.n_frames == other.n_frames \

    def __hash__(self):
        return super(ImageOnSpinningCube, self).__hash__() ^ hash(self.n_frames)

    def new_state(self, speed=10):
        return dict(
                rot=numpy.asarray((0.,0.,0.)),
                drot=numpy.asarray((speed,speed,speed)),
                )

    def perform_set_state(self, istate, ostate, iter):
        ostate['rot'] = istate['rot'] + istate['drot'] * iter

    def perform_display(self, x, ostate, i):
        # retrieve some state variables related to rendering
        xrot,yrot,zrot = ostate['rot']
        dxrot,dyrot,dzrot = ostate['drot']

        # load x as a texture
        glBindTexture(GL_TEXTURE_2D, self.texture)   # 2d texture (x and y size)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)      # Clear The Screen And The Depth Buffer
        glLoadIdentity()                                        # Reset The View
        glTranslatef(0.0,0.0,-5.0)                      # Move Into The Screen

        glRotatef(xrot,1.0,0.0,0.0)                     # Rotate The Cube On It's X Axis
        glRotatef(yrot,0.0,1.0,0.0)                     # Rotate The Cube On It's Y Axis
        glRotatef(zrot,0.0,0.0,1.0)                     # Rotate The Cube On It's Z Axis

        glBegin(GL_QUADS)                           # Start Drawing The Cube
        
        # Front Face (note that the texture's corners have to match the quad's corners)
        glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0,  1.0)    # Bottom Left Of The Texture and Quad
        glTexCoord2f(1.0, 0.0); glVertex3f( 1.0, -1.0,  1.0)    # Bottom Right Of The Texture and Quad
        glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0,  1.0)    # Top Right Of The Texture and Quad
        glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0,  1.0)    # Top Left Of The Texture and Quad
        
        # Back Face
        glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0, -1.0)    # Bottom Right Of The Texture and Quad
        glTexCoord2f(1.0, 1.0); glVertex3f(-1.0,  1.0, -1.0)    # Top Right Of The Texture and Quad
        glTexCoord2f(0.0, 1.0); glVertex3f( 1.0,  1.0, -1.0)    # Top Left Of The Texture and Quad
        glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0, -1.0)    # Bottom Left Of The Texture and Quad
        
        # Top Face
        glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0, -1.0)    # Top Left Of The Texture and Quad
        glTexCoord2f(0.0, 0.0); glVertex3f(-1.0,  1.0,  1.0)    # Bottom Left Of The Texture and Quad
        glTexCoord2f(1.0, 0.0); glVertex3f( 1.0,  1.0,  1.0)    # Bottom Right Of The Texture and Quad
        glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0, -1.0)    # Top Right Of The Texture and Quad
        
        # Bottom Face       
        glTexCoord2f(1.0, 1.0); glVertex3f(-1.0, -1.0, -1.0)    # Top Right Of The Texture and Quad
        glTexCoord2f(0.0, 1.0); glVertex3f( 1.0, -1.0, -1.0)    # Top Left Of The Texture and Quad
        glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0,  1.0)    # Bottom Left Of The Texture and Quad
        glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0,  1.0)    # Bottom Right Of The Texture and Quad
        
        # Right face
        glTexCoord2f(1.0, 0.0); glVertex3f( 1.0, -1.0, -1.0)    # Bottom Right Of The Texture and Quad
        glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0, -1.0)    # Top Right Of The Texture and Quad
        glTexCoord2f(0.0, 1.0); glVertex3f( 1.0,  1.0,  1.0)    # Top Left Of The Texture and Quad
        glTexCoord2f(0.0, 0.0); glVertex3f( 1.0, -1.0,  1.0)    # Bottom Left Of The Texture and Quad
        
        # Left Face
        glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0, -1.0)    # Bottom Left Of The Texture and Quad
        glTexCoord2f(1.0, 0.0); glVertex3f(-1.0, -1.0,  1.0)    # Bottom Right Of The Texture and Quad
        glTexCoord2f(1.0, 1.0); glVertex3f(-1.0,  1.0,  1.0)    # Top Right Of The Texture and Quad
        glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0, -1.0)    # Top Left Of The Texture and Quad
        
        glEnd();                                # Done Drawing The Cube

def image_on_spinning_cube(x, shape, upside_down=False):
    op = ImageOnSpinningCube(shape, upside_down=upside_down)
    istate = shared(op.new_state())
    z, ostate = op(x, istate)
    return z, {istate: ostate}

class BrownianCamera(OpenGlMovieFromImage):
    def __init__(self, (n_frames, width, height), upside_down=False):
        super(BrownianCamera, self).__init__(width, height, upside_down=upside_down)
        self.n_frames = n_frames
        self.z_shape = (n_frames, width, height, 3)

    def __eq__(self, other):
        return super(self.__class__, self).__eq__(other) \
                and self.n_frames == other.n_frames \

    def __hash__(self):
        return super(self.__class__, self).__hash__() ^ hash(self.n_frames)

    def new_state(self, pos_jitter=(.01,.01,.03), rot_jitter=(4.,4.,4.), seed=23424):
        return dict(
                pos_jitter=numpy.asarray(pos_jitter),
                rot_jitter=numpy.asarray(rot_jitter),
                pos0=numpy.asarray((0.,0.,-4.0)),
                rot0=numpy.asarray((0.,0.,0.)),
                alpha=0.1,
                # dynamic things
                pos=numpy.asarray((0.,0.,-4.0)),
                dpos=numpy.asarray((0.,0.,0.)),
                ddpos=numpy.asarray((0.,0.,0.)),
                rot=numpy.asarray((0.,0.,0.)),
                drot=numpy.asarray((0.,0.,0.)),
                ddrot=numpy.asarray((0.,0.,0.)),
                rng = numpy.random.RandomState(seed),
                )

    def perform_set_state(self, istate, ostate, iter):
        alpha = ostate['alpha']
        if iter == 0:
            ostate['pos'] = ostate['pos0'].copy()
            ostate['dpos'] *= 0
            ostate['rot'] = ostate['rot0'].copy()
            ostate['drot'] *= 0
        ostate['ddpos'] = ostate['rng'].uniform(low=-1,high=1,size=3) * ostate['pos_jitter']
        ostate['ddrot'] = ostate['rng'].uniform(low=-1,high=1,size=3) * ostate['rot_jitter']
        ostate['dpos'] += ostate['ddpos']
        ostate['drot'] += ostate['ddrot']
        ostate['pos'] = (1-alpha)*(ostate['pos'] + ostate['dpos']) + alpha * ostate['pos0']
        ostate['rot'] = (1-alpha)*(ostate['rot'] + ostate['drot']) + alpha * ostate['rot0']

    def perform_display(self, x, ostate, i):
        # retrieve some state variables related to rendering
        xrot,yrot,zrot = ostate['rot']
        xpos,ypos,zpos = ostate['pos']

        # load x as a texture
        glBindTexture(GL_TEXTURE_2D, self.texture)   # 2d texture (x and y size)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
        glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)      # Clear The Screen And The Depth Buffer
        glLoadIdentity()                                        # Reset The View
        glTranslatef(xpos,ypos,zpos)                      # Move Into The Screen

        glRotatef(xrot,1.0,0.0,0.0)                     # Rotate The Cube On It's X Axis
        glRotatef(yrot,0.0,1.0,0.0)                     # Rotate The Cube On It's Y Axis
        glRotatef(zrot,0.0,0.0,1.0)                     # Rotate The Cube On It's Z Axis

        glBegin(GL_QUADS)                           # Start Drawing The Cube
        
        # Front Face (note that the texture's corners have to match the quad's corners)
        glTexCoord2f(0.0, 0.0); glVertex3f(-1.0, -1.0,  1.0)    # Bottom Left Of The Texture and Quad
        glTexCoord2f(1.0, 0.0); glVertex3f( 1.0, -1.0,  1.0)    # Bottom Right Of The Texture and Quad
        glTexCoord2f(1.0, 1.0); glVertex3f( 1.0,  1.0,  1.0)    # Top Right Of The Texture and Quad
        glTexCoord2f(0.0, 1.0); glVertex3f(-1.0,  1.0,  1.0)    # Top Left Of The Texture and Quad
        
        glEnd();                                # Done Drawing The Cube

_brownian_camera_ops = {}
def brownian_camera(x, shape, upside_down=False, seed=8234, speed=1.0):
    if (shape, upside_down) not in _brownian_camera_ops:
        _brownian_camera_ops[(shape, upside_down)] = BrownianCamera(shape, upside_down=upside_down)
    op = _brownian_camera_ops[(shape, upside_down)]
    istate = shared(op.new_state(seed=seed))
    istate.value['pos_jitter'] *= speed
    istate.value['rot_jitter'] *= speed
    z, ostate = op(x, istate)
    return z, [(istate, ostate)]


def _dump_to_file(fn, filename='out.pkl', nexamples=1000, n_frames=10, **kwargs):
    logging.basicConfig(level=logging.INFO, stream=sys.stderr)
    import cPickle, time

    from MNIST import mnist
    i = theano.tensor.iscalar()
    z, z_updates = fn(mnist(i%50000, 'train', rasterized=False, dtype='uint8')[0], (n_frames, 28,28), **kwargs)
    f = function([i], z[:,:,:,0], updates=z_updates)

    t0 = time.time()
    rval = []
    for j in xrange(nexamples):
        if 0 == j  % 100: print >> sys.stderr, j
        rval.append(f(j))
    dt = time.time() - t0
    info('Generating ', nexamples, 'examples took', dt, 'seconds.')
    info('Generation rate:', nexamples/dt, 'examples per second.')
    info('Generated ', nexamples*n_frames, 'frames')
    info('Generation rate:', nexamples*n_frames/dt, 'frames per second.')

    cPickle.dump(rval, file(filename, 'w'), protocol=cPickle.HIGHEST_PROTOCOL)
def spinning_cube_dump(filename='spinning_cube.pkl', *args, **kwargs):
    return _dump_to_file(fn=image_on_spinning_cube, filename=filename, *args, **kwargs)
def brownian_camera_dump(filename='brownian_camera.pkl', *args, **kwargs):
    return _dump_to_file(fn=brownian_camera, filename=filename, *args, **kwargs)
def brownian_camera_dumpN(filename='brownian_cameraN.pkl', nexamples=10, n_frames=5,
        n_movies=10, img_shape=(28,28), **kwargs):
    logging.basicConfig(level=logging.INFO, stream=sys.stderr)
    import cPickle, time
    from MNIST import mnist

    s_idx = theano.tensor.iscalar()
    inputs_updates = [brownian_camera(
            x=mnist(s_idx*n_movies+i, 'train', rasterized=False, dtype='uint8')[0],
            shape=(n_frames,)+img_shape,
            seed=234234+i, **kwargs)
        for i in xrange(n_movies)]
    s_input = theano.tensor.stack(*(input for (input,update) in inputs_updates))\
            .reshape((n_movies*n_frames,)+img_shape+(3,))
    s_updates = []
    for i,u in inputs_updates:
        s_updates.extend(u)
    print s_updates
    f = function([s_idx], s_input, updates=s_updates)

    t0 = time.time()
    rval = []
    for j in xrange(nexamples):
        if 0 == j  % 1000: print >> sys.stderr, j
        rval.append(f(j))
    dt = time.time() - t0
    info('Generating ', nexamples, 'examples took', dt, 'seconds.')
    info('Generation rate:', nexamples/dt, 'examples per second.')
    info('Generated ', nexamples*n_movies*n_frames, 'frames')
    info('Generation rate:', nexamples*n_movies*n_frames/dt, 'frames per second.')

    cPickle.dump(rval, file(filename, 'w'))


def glviewer_from_file(filename='out.pkl'):
    logging.basicConfig(level=logging.DEBUG, stream=sys.stderr)
    import cPickle
    rval = cPickle.load(file(filename))
    from glviewer import GlViewer
    GlViewer(rval.__getitem__).main()