Hello,

I have been working on a couple of interesting concoctions for matplotlib.
The first is a wrapper class called "ThinWrap" that, essentially, provides a
way to create objects that are linked to a given object.  These objects can
then be subclassed for some very interesting behaviors.  Which leads me to
my ReMap class.

The ReMap class is designed to be a wrapper around a Colormap object, and
the __call__ function is overloaded so that some other defined function can
modify the rgba values that comes from a call to the original colormap
object.  All of this is done without modifying the original colormap.  In
addition, a ReMap object can wrap another ReMap object, allowing for
stacking.  As an example, I have created a ToGrayscale() ReMap class.

For your reviewing pleasure, there are two patch files.  The first,
thinwrap.patch, adds the ThinWrap class to cbook.py.  The second,
remap.patch, creates a new file "remap.py" that holds the ReMap class and
the ToGrayscale class.  In addition, in order for things to work, a few
changes had to be made to other code:

cm.py: get_cmap() could finish the function without returning anything.  I
modified it to remove the "isinstance" check that would cause
non-Colormap/non-string objects to  fall into a black hole.  We are gonna
have to follow duck-typing here...
colors.py: The Colormap class needs to be a new-style class for ReMap to
work properly
contour.py: Commented out code that did a isinstance() check on the cmap
that would cause ReMaps on contours to fail.

I have also included an example script to demonstrate how this wrapper class
works and the ToGrayscale() class works.

Let me know what you think!
Ben Root
diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py
index 7c90867..19a078a 100644
--- a/lib/matplotlib/cbook.py
+++ b/lib/matplotlib/cbook.py
@@ -1783,6 +1783,106 @@ def is_math_text(s):
 
     return even_dollars
 
+class ThinWrap(object) :
+    """
+    The ThinWrap class is a way to produce modified interfaces
+    to an object while still maintaining the original object.
+
+    An example use case is the ReMap class in colors.py.  The ReMap
+    class takes a colormap object and intercepts its __call__() function.
+    This allows for a remapping function to modify the returned rgba values.
+    Meanwhile, any access to the attributes and functions of the original
+    colormap object through the wrapper will just get passed onto the
+    original colormap.
+
+    So long as the code honors duck-typing, you can pass this ThinWrapper
+    class down into the code and it will be none the wiser.
+    """
+    def __init__(self, origobj) :
+        """
+        Create a thin wrapper around `origobj`.
+        `origobj` must be a new style python class object (or must have
+        __getattribute__ defined.
+        """
+        # We have to use the baseclass's get and set to avoid recursion.
+        object.__setattr__(self, '_origobj', origobj)
+
+    def __getattr__(self, attr) :
+        """
+        Any attribute request/function request that can't be found
+        by normal means will come to this function.
+        Those requests will be passed along to the _origobj.
+        """
+        return object.__getattribute__(self, '_origobj').__getattribute__(attr)
+
+    
+    def __getattribute__(self, attr) :
+        """
+        Other methods of accessing an attribute uses __getattribute__.
+        In this case, we will attempt to access the attribute in this
+        object.  Upon failing, we then fall back to accessing the attribute
+        in the _origobj.
+        This is necessary for subclassed objects that have their own functions
+        that need to be used, I believe.
+        """
+        try :
+            return object.__getattribute__(self, attr)
+        except AttributeError :
+            return object.__getattribute__(self, '_origobj').__getattribute__(attr)
+        
+
+    def __setattr__(self, attr, value) :
+        """
+        Any request for setting an attribute will first be checked
+        to see if the attribute already exist in the dictionary.
+        If it isn't in there, then it will pass along the set
+        request to the _origobj.
+
+        To help understand the idea, consider the following:
+
+        >>> class Foo(object) :
+        >>>     def __init__(self, x, y) :
+        >>>         self.x = x
+        >>>         self.y = y
+        >>>
+        >>> a = Foo(22, 'bar')
+        >>> b = ThinWrap(a)
+        >>> print "a attribs - x: %d    y: %s" % (a.x, a.y)
+        'a attribs - x: 22    y: bar'
+        >>> print "b attribs - x: %d    y: %s" % (b.x, b.y)
+        'b attribs - x: 22    y: bar'
+        >>>
+        >>> b.x = 42
+        >>> print "a attribs - x: %d    y: %s" % (a.x, a.y)
+        'a attribs - x: 42    y: bar'
+        >>> print "b attribs - x: %d    y: %s" % (b.x, b.y)
+        'b attribs - x: 22    y: bar'
+        >>>
+        >>> a.y = 'foo'
+        >>> print "a attribs - x: %d    y: %s" % (a.x, a.y)
+        'a attribs - x: 22    y: foo'
+        >>> print "b attribs - x: %d    y: %s" % (b.x, b.y)
+        'b attribs - x: 22    y: foo'
+
+        Subclassed objects can have their own attributes, but must create
+        them through the __dict__.  Once those attributes that are tied
+        to the subclassed object exist in the __dict__, they can be accessed
+        in the typical manner.
+
+        >>> self.__dict__['foo'] = 'bar'
+        >>> print self.foo
+        'bar'
+
+        Unfortunately, there isn't any special handling of situations
+        where an attribute exist in both the ThinWrap object and the
+        original object. The ThinWrap object's attributes will be used.
+        """
+        if attr in object.__getattribute__(self, '__dict__') :
+            object.__setattr__(self, attr, value)
+        else :
+            object.__getattribute__(self, '_origobj').__setattr__(attr, value)
+
+
 
 if __name__=='__main__':
     assert( allequal([1,1,1]) )
diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py
index 19a078a..d1f0085 100644
--- a/lib/matplotlib/cbook.py
+++ b/lib/matplotlib/cbook.py
@@ -1788,7 +1788,7 @@ class ThinWrap(object) :
     The ThinWrap class is a way to produce modified interfaces
     to an object while still maintaining the original object.
 
-    An example use case is the ReMap class in colors.py.  The ReMap
+    An example use case is the ReMap class in remap.py.  The ReMap
     class takes a colormap object and intercepts its __call__() function.
     This allows for a remapping function to modify the returned rgba values.
     Meanwhile, any access to the attributes and functions of the original
diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py
index b246bae..6f42019 100644
--- a/lib/matplotlib/cm.py
+++ b/lib/matplotlib/cm.py
@@ -120,17 +120,20 @@ def get_cmap(name=None, lut=None):
     if name is None:
         name = mpl.rcParams['image.cmap']
 
-    if isinstance(name, colors.Colormap):
+    
+    if cbook.is_string_like(name) :
+        if name in cmap_d:
+            if lut is None:
+                return cmap_d[name]
+            elif name in datad:
+                return colors.LinearSegmentedColormap(name,  datad[name], lut)
+        
+        raise ValueError("Colormap %s is not recognized" % name)
+    else :
+        # Assume that the user knows what it wants and that the object
+        # works as a colormap.
         return name
 
-    if name in cmap_d:
-        if lut is None:
-            return cmap_d[name]
-        elif name in datad:
-            return colors.LinearSegmentedColormap(name,  datad[name], lut)
-        else:
-            raise ValueError("Colormap %s is not recognized" % name)
-
 class ScalarMappable:
     """
     This is a mixin class to support scalar -> RGBA mapping.  Handles
diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py
index eb2f445..023904b 100644
--- a/lib/matplotlib/colors.py
+++ b/lib/matplotlib/colors.py
@@ -462,7 +462,7 @@ def makeMappingArray(N, data, gamma=1.0):
     return lut
 
 
-class Colormap:
+class Colormap(object):
     """Base class for all scalar to rgb mappings
 
         Important methods:
diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py
index f5e480f..5507c93 100644
--- a/lib/matplotlib/contour.py
+++ b/lib/matplotlib/contour.py
@@ -678,7 +678,7 @@ class ContourSet(cm.ScalarMappable, ContourLabeler):
         if self.origin is not None: assert(self.origin in
                                             ['lower', 'upper', 'image'])
         if self.extent is not None: assert(len(self.extent) == 4)
-        if cmap is not None: assert(isinstance(cmap, colors.Colormap))
+        #if cmap is not None: assert(isinstance(cmap, colors.Colormap))
         if self.colors is not None and cmap is not None:
             raise ValueError('Either colors or cmap must be None')
         if self.origin == 'image': self.origin = mpl.rcParams['image.origin']
diff --git a/lib/matplotlib/remap.py b/lib/matplotlib/remap.py
new file mode 100755
index 0000000..610db9a
--- /dev/null
+++ b/lib/matplotlib/remap.py
@@ -0,0 +1,60 @@
+"""
+The remap module provides a mechanism to alter the behavior of colormaps
+without modifying the underlying Colormap object. Contained in this module
+are several useful colormap ReMappers that can be combined to achieve
+a desired effect.
+"""
+
+
+import numpy as np
+from matplotlib.colors import Colormap
+import matplotlib.cm as cm
+from cbook import ThinWrap
+
+
+class ReMap(ThinWrap) :
+    """
+    Base class for classes that modify the replies from calls to
+    a normal colormap. These calls modify the rgba values returned
+    by a call to a Colormap.
+    """
+    def __init__(self, cmap=None) :
+        """
+        Create a new ReMap wrapper around the colormap.
+
+        cmap can be a string indicating a registered
+        colormap, or a colormap object, or None to obtain
+        the default colormap.
+        """
+        cmap = cm.get_cmap(cmap)
+        ThinWrap.__init__(self, cmap)
+
+    def __call__(self, *args, **kwargs) :
+        """
+        Generic overload of the __call__ method.
+        This method will modify the returned rgba values using
+        the _remap method defined by subclasses of ThinWrap.
+        """
+        return self._remap(self.__dict__['_origobj'](*args, **kwargs))
+
+    def _remap(self, rgba) :
+        raise NotImplemented("The base ReMap class does not define _remap()")
+
+
+
+
+class ToGrayscale(ReMap) :
+    """
+    A ReMapper that will convert any color from a colormap
+    into a gray color according to the ITU-R 601-2 luma transform.
+    """
+    def _remap(self, rgba) :
+        if rgba is not None :
+            rgba = np.atleast_2d(rgba)
+            l = np.sum(rgba[:, :3] * np.array([0.299, 0.587, 0.114]), axis=1)
+            return np.squeeze(np.dstack([l, l, l, rgba[:, 3]]))
+        else :
+            # If we get a None, then return it in kind.
+            return None
+
+
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib.remap as remap



a = cm.get_cmap()
b = remap.ToGrayscale(a)


print a.name, b.name

print a((1.0, 0.8, 0.6, 0.4, 0.2, 0.0))
print b((1.0, 0.8, 0.6, 0.4, 0.2, 0.0))

a = cm.get_cmap('cool')
b = remap.ToGrayscale(a)


print a.name, b.name

print a((1.0, 0.8, 0.6, 0.4, 0.2, 0.0))
print b((1.0, 0.8, 0.6, 0.4, 0.2, 0.0))

grayMap = remap.ToGrayscale()

print grayMap.name
print grayMap((1.0, 0.8, 0.6, 0.4, 0.2, 0.0))


import numpy as np

X, Y = np.mgrid[-10:10, -10:10]
Z = np.abs(X*Y)

fig = plt.figure()
ax = fig.add_subplot(1, 2, 1)
ax.pcolor(X, Y, Z)
ax.set_title("Color")

ax = fig.add_subplot(1, 2, 2)
ax.pcolor(X, Y, Z, cmap=grayMap)
ax.set_title("Gray")


plt.show()
------------------------------------------------------------------------------
This SF.net Dev2Dev email is sponsored by:

Show off your parallel programming skills.
Enter the Intel(R) Threading Challenge 2010.
http://p.sf.net/sfu/intel-thread-sfd
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel

Reply via email to