-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
2D Normalization & Colormapping for ScalerMappables #8738
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
201dc2a
7217536
0178730
575a244
8425637
4a5831d
85df736
8e1bc63
a9aace3
55a7a68
c2eb617
daef751
96af3d3
9285372
01b5932
28288e2
ca2b111
75b4eba
bf65847
f9dc4b1
8a985f0
8f6928d
d54cb44
4d03552
2573610
9228480
5c8dc65
21ea65f
b8d43a9
7681d14
59f56af
f98978f
75a289f
7803974
e40c87b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5141,12 +5141,30 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, | |
if not self._hold: | ||
self.cla() | ||
|
||
if norm is not None and not isinstance(norm, mcolors.Normalize): | ||
msg = "'norm' must be an instance of 'mcolors.Normalize'" | ||
isNorm = isinstance(norm, (mcolors.Normalize, mcolors.BivariateNorm)) | ||
if norm is not None and not isNorm: | ||
msg = "'norm' must be an instance of 'mcolors.Normalize' " \ | ||
"or 'mcolors.BivariateNorm'" | ||
raise ValueError(msg) | ||
|
||
if aspect is None: | ||
aspect = rcParams['image.aspect'] | ||
self.set_aspect(aspect) | ||
|
||
temp = np.asarray(X) | ||
if (temp.ndim == 3 and isinstance(norm, mcolors.BivariateNorm) or | ||
isinstance(cmap, mcolors.BivariateColormap)): | ||
if cmap is None: | ||
cmap = mcolors.BivariateColormap() | ||
if norm is None: | ||
norm = mcolors.BivariateNorm() | ||
temp = norm(temp) | ||
temp[0] = temp[0] * (cmap.N-1) | ||
temp[1] = temp[1] * (cmap.N-1) | ||
temp = temp.astype(int) | ||
X = temp[0] + cmap.N * temp[1] | ||
norm = mcolors.NoNorm() | ||
|
||
im = mimage.AxesImage(self, cmap, norm, interpolation, origin, extent, | ||
filternorm=filternorm, filterrad=filterrad, | ||
resample=resample, **kwargs) | ||
|
@@ -5173,7 +5191,6 @@ def imshow(self, X, cmap=None, norm=None, aspect=None, | |
|
||
@staticmethod | ||
def _pcolorargs(funcname, *args, **kw): | ||
# This takes one kwarg, allmatch. | ||
# If allmatch is True, then the incoming X, Y, C must | ||
# have matching dimensions, taking into account that | ||
# X and Y can be 1-D rather than 2-D. This perfect | ||
|
@@ -5186,9 +5203,25 @@ def _pcolorargs(funcname, *args, **kw): | |
# is False. | ||
|
||
allmatch = kw.pop("allmatch", False) | ||
norm = kw.pop("norm", None) | ||
cmap = kw.pop("cmap", None) | ||
|
||
if len(args) == 1: | ||
C = np.asanyarray(args[0]) | ||
|
||
if (C.ndim == 3 and isinstance(norm, mcolors.BivariateNorm) or | ||
isinstance(cmap, mcolors.BivariateColormap)): | ||
if cmap is None: | ||
cmap = mcolors.BivariateColormap() | ||
if norm is None: | ||
norm = mcolors.BivariateNorm() | ||
C = norm(C) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do this up-front? defering all of this to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check now. |
||
C[0] = C[0] * (cmap.N-1) | ||
C[1] = C[1] * (cmap.N-1) | ||
C = C.astype(int) | ||
C = C[0] + cmap.N * C[1] | ||
C = np.array(C) | ||
|
||
numRows, numCols = C.shape | ||
if allmatch: | ||
X, Y = np.meshgrid(np.arange(numCols), np.arange(numRows)) | ||
|
@@ -5200,6 +5233,18 @@ def _pcolorargs(funcname, *args, **kw): | |
|
||
if len(args) == 3: | ||
X, Y, C = [np.asanyarray(a) for a in args] | ||
if (C.ndim == 3 and isinstance(norm, mcolors.BivariateNorm) or | ||
isinstance(cmap, mcolors.BivariateColormap)): | ||
if cmap is None: | ||
cmap = mcolors.BivariateColormap() | ||
if norm is None: | ||
norm = mcolors.BivariateNorm() | ||
C = norm(C) | ||
C[0] = C[0] * (cmap.N-1) | ||
C[1] = C[1] * (cmap.N-1) | ||
C = C.astype(int) | ||
C = C[0] + cmap.N * C[1] | ||
C = np.array(C) | ||
numRows, numCols = C.shape | ||
else: | ||
raise TypeError( | ||
|
@@ -5382,9 +5427,14 @@ def pcolor(self, *args, **kwargs): | |
vmin = kwargs.pop('vmin', None) | ||
vmax = kwargs.pop('vmax', None) | ||
|
||
X, Y, C = self._pcolorargs('pcolor', *args, allmatch=False) | ||
kw = {'norm': norm, 'cmap': cmap, 'allmatch': False} | ||
X, Y, C = self._pcolorargs('pcolor', *args, **kw) | ||
Ny, Nx = X.shape | ||
|
||
if (isinstance(norm, mcolors.BivariateNorm) or | ||
isinstance(cmap, mcolors.BivariateColormap)): | ||
norm = mcolors.NoNorm() | ||
|
||
# unit conversion allows e.g. datetime objects as axis values | ||
self._process_unit_info(xdata=X, ydata=Y, kwargs=kwargs) | ||
X = self.convert_xunits(X) | ||
|
@@ -5450,9 +5500,13 @@ def pcolor(self, *args, **kwargs): | |
|
||
collection.set_alpha(alpha) | ||
collection.set_array(C) | ||
if norm is not None and not isinstance(norm, mcolors.Normalize): | ||
msg = "'norm' must be an instance of 'mcolors.Normalize'" | ||
|
||
isNorm = isinstance(norm, (mcolors.Normalize, mcolors.BivariateNorm)) | ||
if norm is not None and not isNorm: | ||
msg = "'norm' must be an instance of 'mcolors.Normalize' " \ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We have a style preference for using msg = ('...' +
'...') There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The + is not needed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
"or 'mcolors.BivariateNorm'" | ||
raise ValueError(msg) | ||
|
||
collection.set_cmap(cmap) | ||
collection.set_norm(norm) | ||
collection.set_clim(vmin, vmax) | ||
|
@@ -5582,9 +5636,14 @@ def pcolormesh(self, *args, **kwargs): | |
|
||
allmatch = (shading == 'gouraud') | ||
|
||
X, Y, C = self._pcolorargs('pcolormesh', *args, allmatch=allmatch) | ||
kw = {'norm': norm, 'cmap': cmap, 'allmatch': allmatch} | ||
X, Y, C = self._pcolorargs('pcolormesh', *args, **kw) | ||
Ny, Nx = X.shape | ||
|
||
if (isinstance(norm, mcolors.BivariateNorm) or | ||
isinstance(cmap, mcolors.BivariateColormap)): | ||
norm = mcolors.NoNorm() | ||
|
||
# unit conversion allows e.g. datetime objects as axis values | ||
self._process_unit_info(xdata=X, ydata=Y, kwargs=kwargs) | ||
X = self.convert_xunits(X) | ||
|
@@ -5723,11 +5782,28 @@ def pcolorfast(self, *args, **kwargs): | |
cmap = kwargs.pop('cmap', None) | ||
vmin = kwargs.pop('vmin', None) | ||
vmax = kwargs.pop('vmax', None) | ||
if norm is not None and not isinstance(norm, mcolors.Normalize): | ||
msg = "'norm' must be an instance of 'mcolors.Normalize'" | ||
isNorm = isinstance(norm, (mcolors.Normalize, mcolors.BivariateNorm)) | ||
if norm is not None and not isNorm: | ||
msg = "'norm' must be an instance of 'mcolors.Normalize' " \ | ||
"or 'mcolors.BivariateNorm'" | ||
raise ValueError(msg) | ||
|
||
C = args[-1] | ||
C = np.asarray(args[-1]) | ||
|
||
if (C.ndim == 3 and isinstance(norm, mcolors.BivariateNorm) or | ||
isinstance(cmap, mcolors.BivariateColormap)): | ||
if cmap is None: | ||
cmap = mcolors.BivariateColormap() | ||
if norm is None: | ||
norm = mcolors.BivariateNorm() | ||
C = norm(C) | ||
C[0] = C[0] * (cmap.N-1) | ||
C[1] = C[1] * (cmap.N-1) | ||
C = C.astype(int) | ||
C = C[0] + cmap.N * C[1] | ||
C = np.array(C) | ||
norm = mcolors.NoNorm() | ||
|
||
nr, nc = C.shape | ||
if len(args) == 1: | ||
style = "image" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -858,6 +858,32 @@ def reversed(self, name=None): | |
return ListedColormap(colors_r, name=name, N=self.N) | ||
|
||
|
||
class BivariateColormap(Colormap): | ||
def __init__(self, name, N=256): | ||
Colormap.__init__(self, name, N) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. would Colormap.init(self, name, N**2) work? |
||
|
||
def _init(self): | ||
red = np.linspace(0, 1, self.N) | ||
green = np.linspace(0, 1, self.N) | ||
red_mesh, green_mesh = np.meshgrid(red, green) | ||
blue_mesh = np.zeros_like(red_mesh) | ||
alpha_mesh = np.ones_like(red_mesh) | ||
bivariate_cmap = np.dstack((red_mesh, green_mesh, blue_mesh, alpha_mesh)) | ||
self._lut = np.vstack(bivariate_cmap) | ||
self._isinit = True | ||
self.N = self.N * self.N | ||
self._set_extremes() | ||
|
||
def _resample(self, lutsize): | ||
""" | ||
Return a new color map with *lutsize x lutsize* entries. | ||
""" | ||
return BivariateColormap(self.name, lutsize) | ||
|
||
def reversed(self, name=None): | ||
raise NotImplementedError() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No need for the parentheses. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
|
||
class Normalize(object): | ||
""" | ||
A class which, when called, can normalize data into | ||
|
@@ -1390,8 +1416,8 @@ def __call__(self, values, clip=None): | |
if clip is None: | ||
clip = [self.norm1.clip, self.norm2.clip] | ||
|
||
return [self.norm1(values[0], clip=clip[0]), | ||
self.norm2(values[1], clip=clip[1])] | ||
return np.array([self.norm1(values[0], clip=clip[0]), | ||
self.norm2(values[1], clip=clip[1])]) | ||
|
||
def inverse(self, values): | ||
""" | ||
|
@@ -1406,6 +1432,7 @@ def inverse(self, values): | |
""" | ||
return [self.norm1.inverse(values[0]), self.norm.inverse(values[1])] | ||
|
||
|
||
def rgb_to_hsv(arr): | ||
""" | ||
convert float rgb values (in the range [0, 1]), in a numpy array to hsv | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't
BivariateNorm
subclass Normalize? 'specially since now it's normalizing down to a 1d space?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BivariateNorm does not inherit anything from Normalize so I thought it should not subclass it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the new design, BivariateNorm should havel all the same classes as Normalize...and I think subclassing it so it's registered generically as a Norm is preferable to having to treat it as a special case when it doesn't need to be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be done with an
ABC
https://docs.python.org/3/library/abc.html#abc.ABCMeta.register which both Normalize and BivariateNorm register with.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but I don't see a reason why BivariateNorm shouldn't be subclassing Norm as it has the same architecture as any other norm...take data (scaler or vector)->map to 1D lut value ->get color, and color ->lut->scaler/vector.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the code paths are completely different, why force the sub-class? In general, sub-classing is only worth it when you can share significant amounts of implementation details, otherwise duck-typing is better.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My point is mostly that the code paths shouldn't end up being all that different and more to the point in mpl land there's probably a ton of code that checks if things are norms and if this code works in those instances it doesn't make sense to change all that code to cover both cases when this is still fundamentally a norm. But I think I'm on the same page as you on the solution maybe being a "Norm" metaclass.