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