-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
ENH: Added FuncNorm #7631
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
ENH: Added FuncNorm #7631
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
""" | ||
===================================================================== | ||
Examples of normalization using :class:`~matplotlib.colors.FuncNorm` | ||
===================================================================== | ||
|
||
This is an example on how to perform a normalization using an arbitrary | ||
function with :class:`~matplotlib.colors.FuncNorm`. A logarithm normalization | ||
and a square root normalization will be use as examples. | ||
|
||
""" | ||
|
||
import matplotlib.cm as cm | ||
import matplotlib.colors as colors | ||
import matplotlib.pyplot as plt | ||
|
||
import numpy as np | ||
|
||
norm_log = colors.FuncNorm(f='log10', vmin=0.01) | ||
# The same can be achieved with | ||
# norm_log = colors.FuncNorm(f=np.log10, | ||
# finv=lambda x: 10.**(x), vmin=0.01) | ||
|
||
norm_sqrt = colors.FuncNorm(f='sqrt', vmin=0.0) | ||
# The same can be achieved with | ||
# norm_sqrt = colors.FuncNorm(f='root{2}', vmin=0.) | ||
# or with | ||
# norm_sqrt = colors.FuncNorm(f=lambda x: x**0.5, | ||
# finv=lambda x: x**2, vmin=0.0) | ||
|
||
normalizations = [(None, 'Regular linear scale'), | ||
(norm_log, 'Log normalization'), | ||
(norm_sqrt, 'Root normalization')] | ||
|
||
# Fabricating some data | ||
x = np.linspace(0, 1, 300) | ||
y = np.linspace(-1, 1, 90) | ||
X, Y = np.meshgrid(x, y) | ||
|
||
data = np.zeros(X.shape) | ||
|
||
|
||
def gauss2d(x, y, a0, x0, y0, wx, wy): | ||
return a0 * np.exp(-(x - x0)**2 / wx**2 - (y - y0)**2 / wy**2) | ||
|
||
for x in np.linspace(0., 1, 15): | ||
data += gauss2d(X, Y, x, x, 0, 0.25 / 15, 0.25) | ||
|
||
data -= data.min() | ||
data /= data.max() | ||
|
||
# Using the custom normalizations to plot the data | ||
fig, axes = plt.subplots(3, 2, sharex='col', | ||
gridspec_kw={'width_ratios': [1, 3.5]}, | ||
figsize=plt.figaspect(0.6)) | ||
|
||
for (ax_left, ax_right), (norm, title) in zip(axes, normalizations): | ||
|
||
# Showing the normalization effect on an image | ||
cax = ax_right.imshow(data, cmap=cm.afmhot, norm=norm, aspect='auto') | ||
fig.colorbar(cax, format='%.3g', ax=ax_right) | ||
ax_right.set_title(title) | ||
ax_right.xaxis.set_ticks([]) | ||
ax_right.yaxis.set_ticks([]) | ||
|
||
# Plotting the behaviour of the normalization | ||
d_values = np.linspace(cax.norm.vmin, cax.norm.vmax, 100) | ||
cm_values = cax.norm(d_values) | ||
ax_left.plot(d_values, cm_values) | ||
ax_left.set_ylabel('Colormap values') | ||
|
||
ax_left.set_xlabel('Data values') | ||
|
||
plt.show() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -146,6 +146,63 @@ def test_BoundaryNorm(): | |
assert_true(np.all(bn(vals).mask)) | ||
|
||
|
||
class TestFuncNorm(object): | ||
def test_limits_with_string(self): | ||
norm = mcolors.FuncNorm(f='log10', vmin=0.01, vmax=2.) | ||
assert_array_equal(norm([0.01, 2]), [0, 1.0]) | ||
|
||
def test_limits_with_lambda(self): | ||
norm = mcolors.FuncNorm(f=lambda x: np.log10(x), | ||
finv=lambda x: 10.**(x), | ||
vmin=0.01, vmax=2.) | ||
assert_array_equal(norm([0.01, 2]), [0, 1.0]) | ||
|
||
def test_limits_without_vmin_vmax(self): | ||
norm = mcolors.FuncNorm(f='log10') | ||
assert_array_equal(norm([0.01, 2]), [0, 1.0]) | ||
|
||
def test_limits_without_vmin(self): | ||
norm = mcolors.FuncNorm(f='log10', vmax=2.) | ||
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. This is the same 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. Yes, but that is a test on itself :P 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. I have added added 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. Those changes should pretty much address the original comment |
||
assert_array_equal(norm([0.01, 2]), [0, 1.0]) | ||
|
||
def test_limits_without_vmax(self): | ||
norm = mcolors.FuncNorm(f='log10', vmin=0.01) | ||
assert_array_equal(norm([0.01, 2]), [0, 1.0]) | ||
|
||
def test_clip_true(self): | ||
norm = mcolors.FuncNorm(f='log10', vmin=0.01, vmax=2., | ||
clip=True) | ||
assert_array_equal(norm([0.0, 2.5]), [0.0, 1.0]) | ||
|
||
def test_clip_false(self): | ||
norm = mcolors.FuncNorm(f='log10', vmin=0.01, vmax=2., | ||
clip=False) | ||
assert_array_equal(norm([0.0, 2.5]), [-0.1, 1.1]) | ||
|
||
def test_clip_default_false(self): | ||
norm = mcolors.FuncNorm(f='log10', vmin=0.01, vmax=2.) | ||
assert_array_equal(norm([0.0, 2.5]), [-0.1, 1.1]) | ||
|
||
def test_intermediate_values(self): | ||
norm = mcolors.FuncNorm(f='log10') | ||
assert_array_almost_equal(norm([0.01, 0.5, 2]), | ||
[0, 0.73835195870437, 1.0]) | ||
|
||
def test_inverse(self): | ||
norm = mcolors.FuncNorm(f='log10', vmin=0.01, vmax=2.) | ||
x = np.linspace(0.01, 2, 10) | ||
assert_array_almost_equal(x, norm.inverse(norm(x))) | ||
|
||
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. Add tests for scalar values 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. this is now added |
||
def test_scalar(self): | ||
norm = mcolors.FuncNorm(f='linear', vmin=1., vmax=2., | ||
clip=True) | ||
assert_equal(norm(1.5), 0.5) | ||
assert_equal(norm(1.), 0.) | ||
assert_equal(norm(0.5), 0.) | ||
assert_equal(norm(2.), 1.) | ||
assert_equal(norm(2.5), 1.) | ||
|
||
|
||
def test_LogNorm(): | ||
""" | ||
LogNorm ignored clip, now it has the same | ||
|
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.
f
must be a function or a string (I don't like usingcallable
in user facing docs 'cause I think it's a little too dev 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.
I always the user of a python module will also be a developer, and callable is a keyword of python, so IMO it is more clearer than function.
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.
That's not true at all though in this case. You've got plenty of users for matplotlib in particular who are scientists but not devs who aren't gonna be familiar with any python keyword they don't use all the time (and callable is rarely in that set)
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.
I think that at least when they are native English speakers they can figure it out quickly enough from the context and the structure of the word itself, "callable" -> "call" "able" -> "something that can be called". The word "string" would be much harder to understand than "callable"--it's pure comp-sci jargon, not used anywhere else in this way, and not something that can be figured out from the word itself. We are not going to delete uses of "string" or "callable".
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.
Callable is equivalent to function.. You'd still need to mention it was a string. And string is different cause it's used in every single intro python everything, callable isn't. Honestly, callable trips me up all the time and I'm a native English speaker with a CS background.
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.
Basically, I dunno I see your point but a) I'm always wary of straight transcriptions of the if statements that triggered the exceptions being the error messages b) I sort of think their should maybe be a bigger discussion of who is matplotlib's expected audience.
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.
I would leave it callable, because I think is a more accurate term. I think anyone able to use a callable (to pass it to the function) should know the term, and if not should be able to do a 5 s google search. In any case, let´s not waste our energy discussion this, as I think it is pretty irrelevant.
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.
While I agree with you that this specific thing probably isn't worth fighting about, I feel in a general sense that it's bad practice to dismiss a usability concern as "well they should know what it's called and how to search for it" 'cause rarely are either of those statements true.