diff --git a/README.txt b/README.txt
index 85e2b24a29f9620b735fa70202c5e67d4387005e..d03c6c77b517037feaff050cbd582251290440c4 100644
--- a/README.txt
+++ b/README.txt
@@ -13,6 +13,7 @@ Contributors:
     Michael Cristopher Hogg
     Ulrich Leutner
     Felix Schill
+    Guillaume Poulin
 
 Requirements:
     PyQt 4.7+ or PySide
diff --git a/examples/GLBarGraphItem.py b/examples/GLBarGraphItem.py
new file mode 100644
index 0000000000000000000000000000000000000000..d14eba87d9a3c2dfd1479192654a9a5027d68f7f
--- /dev/null
+++ b/examples/GLBarGraphItem.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+"""
+Demonstrate use of GLLinePlotItem to draw cross-sections of a surface.
+
+"""
+## Add path to library (just for examples; you do not need this)
+import initExample
+
+from pyqtgraph.Qt import QtCore, QtGui
+import pyqtgraph.opengl as gl
+import pyqtgraph as pg
+import numpy as np
+
+app = QtGui.QApplication([])
+w = gl.GLViewWidget()
+w.opts['distance'] = 40
+w.show()
+w.setWindowTitle('pyqtgraph example: GLBarGraphItem')
+
+gx = gl.GLGridItem()
+gx.rotate(90, 0, 1, 0)
+gx.translate(-10, 0, 10)
+w.addItem(gx)
+gy = gl.GLGridItem()
+gy.rotate(90, 1, 0, 0)
+gy.translate(0, -10, 10)
+w.addItem(gy)
+gz = gl.GLGridItem()
+gz.translate(0, 0, 0)
+w.addItem(gz)
+
+# regular grid of starting positions
+pos = np.mgrid[0:10, 0:10, 0:1].reshape(3,10,10).transpose(1,2,0)
+# fixed widths, random heights
+size = np.empty((10,10,3))
+size[...,0:2] = 0.4
+size[...,2] = np.random.normal(size=(10,10))
+
+bg = gl.GLBarGraphItem(pos, size)
+w.addItem(bg)
+
+
+## Start Qt event loop unless running in interactive mode.
+if __name__ == '__main__':
+    import sys
+    if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
+        QtGui.QApplication.instance().exec_()
diff --git a/pyqtgraph/PlotData.py b/pyqtgraph/PlotData.py
index 0bf13ca87cb436d3ea0ce786b78928524b25a6cb..e5faadda028b7bf54d348f7c27b3faf79631b29d 100644
--- a/pyqtgraph/PlotData.py
+++ b/pyqtgraph/PlotData.py
@@ -15,6 +15,7 @@ class PlotData(object):
         - removal of nan/inf values
       - option for single value shared by entire column
       - cached downsampling
+      - cached min / max / hasnan / isuniform
     """
     def __init__(self):
         self.fields = {}
diff --git a/pyqtgraph/__init__.py b/pyqtgraph/__init__.py
index c1b620419a12100ebbfa9dbd0c79727715de5d44..12a4f90fb3dbeec14208330900fc9c0b5453ac84 100644
--- a/pyqtgraph/__init__.py
+++ b/pyqtgraph/__init__.py
@@ -316,7 +316,7 @@ def dbg():
     Create a console window and begin watching for exceptions.
     """
     mkQApp()
-    import console
+    from . import console
     c = console.ConsoleWidget()
     c.catchAllExceptions()
     c.show()
diff --git a/pyqtgraph/multiprocess/remoteproxy.py b/pyqtgraph/multiprocess/remoteproxy.py
index 7622b6e7b9d04518b2a8c99d1e52769c9365c763..702b10bcfd6640f973cb50f02344613fc9c1a7a8 100644
--- a/pyqtgraph/multiprocess/remoteproxy.py
+++ b/pyqtgraph/multiprocess/remoteproxy.py
@@ -205,7 +205,11 @@ class RemoteEventHandler(object):
                             fnkwds[k] = np.fromstring(byteData[ind], dtype=dtype).reshape(shape)
                 
                 if len(fnkwds) == 0:  ## need to do this because some functions do not allow keyword arguments.
-                    result = obj(*fnargs)
+                    try:
+                        result = obj(*fnargs)
+                    except:
+                        print("Failed to call object %s: %d, %s" % (obj, len(fnargs), fnargs[1:]))
+                        raise
                 else:
                     result = obj(*fnargs, **fnkwds)
                     
@@ -932,9 +936,9 @@ class ObjectProxy(object):
     def __ilshift__(self, *args):
         return self._getSpecialAttr('__ilshift__')(*args, _callSync='off')
         
-    #def __eq__(self, *args):
-    #   return self._getSpecialAttr('__eq__')(*args)
-
+    def __eq__(self, *args):
+        return self._getSpecialAttr('__eq__')(*args)
+    
     def __ne__(self, *args):
         return self._getSpecialAttr('__ne__')(*args)
         
@@ -1010,6 +1014,10 @@ class ObjectProxy(object):
     def __rmod__(self, *args):
         return self._getSpecialAttr('__rmod__')(*args)
         
+    def __hash__(self):
+        ## Required for python3 since __eq__ is defined.
+        return id(self)
+        
 class DeferredObjectProxy(ObjectProxy):
     """
     This class represents an attribute (or sub-attribute) of a proxied object.
diff --git a/pyqtgraph/opengl/GLGraphicsItem.py b/pyqtgraph/opengl/GLGraphicsItem.py
index 59bc4449b2c8dcefb218cdedd718f4795d89e54a..9680fba7e4450a90d689ef5ee8eed1c7e037708a 100644
--- a/pyqtgraph/opengl/GLGraphicsItem.py
+++ b/pyqtgraph/opengl/GLGraphicsItem.py
@@ -40,6 +40,7 @@ class GLGraphicsItem(QtCore.QObject):
         self.__glOpts = {}
         
     def setParentItem(self, item):
+        """Set this item's parent in the scenegraph hierarchy."""
         if self.__parent is not None:
             self.__parent.__children.remove(self)
         if item is not None:
@@ -98,9 +99,11 @@ class GLGraphicsItem(QtCore.QObject):
         
     
     def parentItem(self):
+        """Return a this item's parent in the scenegraph hierarchy."""
         return self.__parent
         
     def childItems(self):
+        """Return a list of this item's children in the scenegraph hierarchy."""
         return list(self.__children)
         
     def _setView(self, v):
@@ -124,10 +127,15 @@ class GLGraphicsItem(QtCore.QObject):
         return self.__depthValue
         
     def setTransform(self, tr):
+        """Set the local transform for this object.
+        Must be a :class:`Transform3D <pyqtgraph.Transform3D>` instance. This transform
+        determines how the local coordinate system of the item is mapped to the coordinate
+        system of its parent."""
         self.__transform = Transform3D(tr)
         self.update()
         
     def resetTransform(self):
+        """Reset this item's transform to an identity transformation."""
         self.__transform.setToIdentity()
         self.update()
         
@@ -148,9 +156,12 @@ class GLGraphicsItem(QtCore.QObject):
             self.setTransform(tr * self.transform())
         
     def transform(self):
+        """Return this item's transform object."""
         return self.__transform
         
     def viewTransform(self):
+        """Return the transform mapping this item's local coordinate system to the 
+        view coordinate system."""
         tr = self.__transform
         p = self
         while True:
@@ -190,16 +201,24 @@ class GLGraphicsItem(QtCore.QObject):
     
     
     def hide(self):
+        """Hide this item. 
+        This is equivalent to setVisible(False)."""
         self.setVisible(False)
         
     def show(self):
+        """Make this item visible if it was previously hidden.
+        This is equivalent to setVisible(True)."""
         self.setVisible(True)
     
     def setVisible(self, vis):
+        """Set the visibility of this item."""
         self.__visible = vis
         self.update()
         
     def visible(self):
+        """Return True if the item is currently set to be visible.
+        Note that this does not guarantee that the item actually appears in the
+        view, as it may be obscured or outside of the current view area."""
         return self.__visible
     
     
@@ -237,6 +256,10 @@ class GLGraphicsItem(QtCore.QObject):
         self.setupGLState()
         
     def update(self):
+        """
+        Indicates that this item needs to be redrawn, and schedules an update 
+        with the view it is displayed in.
+        """
         v = self.view()
         if v is None:
             return
diff --git a/pyqtgraph/opengl/MeshData.py b/pyqtgraph/opengl/MeshData.py
index 12a9b83b5eb4a3801fd9807cdcd44b70f0fb8fd5..71e566c936e47a9698521a64c89a5ada83654ff6 100644
--- a/pyqtgraph/opengl/MeshData.py
+++ b/pyqtgraph/opengl/MeshData.py
@@ -247,9 +247,9 @@ class MeshData(object):
             return self._faceNormals
         elif indexed == 'faces':
             if self._faceNormalsIndexedByFaces is None:
-                    norms = np.empty((self._faceNormals.shape[0], 3, 3))
-                    norms[:] = self._faceNormals[:,np.newaxis,:]
-                    self._faceNormalsIndexedByFaces = norms
+                norms = np.empty((self._faceNormals.shape[0], 3, 3))
+                norms[:] = self._faceNormals[:,np.newaxis,:]
+                self._faceNormalsIndexedByFaces = norms
             return self._faceNormalsIndexedByFaces
         else:
             raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
diff --git a/pyqtgraph/opengl/items/GLMeshItem.py b/pyqtgraph/opengl/items/GLMeshItem.py
index 66d543614187c77503a830f850f133b11a1b2b60..5b245e6488baa208f737cf472f4a2ff94bf020f6 100644
--- a/pyqtgraph/opengl/items/GLMeshItem.py
+++ b/pyqtgraph/opengl/items/GLMeshItem.py
@@ -69,7 +69,11 @@ class GLMeshItem(GLGraphicsItem):
         self.update()
         
     def shader(self):
-        return shaders.getShaderProgram(self.opts['shader'])
+        shader = self.opts['shader']
+        if isinstance(shader, shaders.ShaderProgram):
+            return shader
+        else:
+            return shaders.getShaderProgram(shader)
         
     def setColor(self, c):
         """Set the default color to use when no vertex or face colors are specified."""
diff --git a/pyqtgraph/opengl/shaders.py b/pyqtgraph/opengl/shaders.py
index 8f0d6e1bb076312a04ca5104f4843b60b46315e5..8922cd21434605d9a2801b478b1681fdb8304666 100644
--- a/pyqtgraph/opengl/shaders.py
+++ b/pyqtgraph/opengl/shaders.py
@@ -223,7 +223,7 @@ class Shader(object):
             try:
                 self.compiled = shaders.compileShader(self.code, self.shaderType)
             except NullFunctionError:
-                raise Exception("This OpenGL implementation does not support shader programs; many features on pyqtgraph will not work.")
+                raise Exception("This OpenGL implementation does not support shader programs; many OpenGL features in pyqtgraph will not work.")
             except RuntimeError as exc:
                 ## Format compile errors a bit more nicely
                 if len(exc.args) == 3:
diff --git a/pyqtgraph/widgets/RawImageWidget.py b/pyqtgraph/widgets/RawImageWidget.py
index a780f4633780f715b1b43d944d7e7b701d135831..517f4f99329125fdf40d273af3b0a9ddcd31f4b6 100644
--- a/pyqtgraph/widgets/RawImageWidget.py
+++ b/pyqtgraph/widgets/RawImageWidget.py
@@ -1,6 +1,7 @@
 from pyqtgraph.Qt import QtCore, QtGui
 try:
     from pyqtgraph.Qt import QtOpenGL
+    from OpenGL.GL import *
     HAVE_OPENGL = True
 except ImportError:
     HAVE_OPENGL = False
@@ -59,7 +60,6 @@ class RawImageWidget(QtGui.QWidget):
         p.end()
 
 if HAVE_OPENGL:
-    from OpenGL.GL import *
     class RawImageGLWidget(QtOpenGL.QGLWidget):
         """
         Similar to RawImageWidget, but uses a GL widget to do all drawing.