New colormap normalizations: sqrt, arcsinh #1780

Closed
wants to merge 5 commits into
from

Projects

None yet

6 participants

@keflavich

(this may be best to regard as a feature request with sample code included; I doubt my code conforms to mpl standards)

Related to #1355, #1204, and #632, I think there should be more default normalizations available in matplotlib. This pull request includes two that I've made use of: a sqrt/n'th root normalization and an arcsinh normalization. The former is good for emphasizing values near 1 (e.g., ratios of like values) and the latter is useful for high dynamic range images where the low scales are to be emphasized (e.g., images of stars with nebulosity around them); it largely serves the same purposes as SymLogNorm but with a different shape.

In part, I often want to reproduce figures made by ds9, which includes a wider range of default normalizations.

If there's an easier way to accomplish what I'm doing - e.g. just wrapping a function in some Normalize subclass - please let me know; I'm not up to date on new features of matplotlib unless they're thoroughly exampled in the gallery.

@dmcdougall
Matplotlib Developers member

A question to the other developers: In the interest of preventing a combinatorial explosion by creating a class for each elementary mathematical function, would it be a better idea to have a class that will take a callable in its constructor, and have it normalise the data with respect to the underlying callable? For example, the user could call something like CustomNorm(np.sqrt, vmin=1, vmax=10).

@keflavich
@dmcdougall
Matplotlib Developers member

@keflavich I am in agreement with you. The general 'take a callable' approach is more robust to changing requirements.

@tacaswell
Matplotlib Developers member

+1 for this (this being the callable method).

@Tillsten

While preferable, It is not simple: the normalization classes also include the inverse function, else features like colorbar won't work right.

@NelleV NelleV commented on the diff Mar 1, 2013
lib/matplotlib/colors.py
+
+class SqrtNorm(Normalize):
+ """
+ Normalize a given value to the 0-1 range on a square (or n'th) root scale
+ """
+ def __init__(self, vmin=None, vmax=None, clip=False, nthroot=2):
+ """
+ nthroot allows cube roots, fourth roots, etc.
+ """
+ self.vmin = vmin
+ self.vmax = vmax
+ self.clip = clip
+ self.nthroot = nthroot
+
+ def __call__(self, value, clip=None, midpoint=None):
+
@NelleV
NelleV Mar 1, 2013

PEP8 compliance: there should only be one blank line here.

@NelleV NelleV commented on an outdated diff Mar 1, 2013
lib/matplotlib/colors.py
+ if clip is None:
+ clip = self.clip
+
+ if cbook.iterable(value):
+ vtype = 'array'
+ val = ma.asarray(value).astype(np.float)
+ else:
+ vtype = 'scalar'
+ val = ma.array([value]).astype(np.float)
+
+ self.autoscale_None(val)
+ vmin, vmax = self.vmin, self.vmax
+
+ if vmin > vmax:
+ raise ValueError("minvalue must be less than or equal to maxvalue")
+ elif vmin==vmax:
@NelleV
NelleV Mar 1, 2013

PEP8 compliance: there should be whitespaces around '=='.

@NelleV NelleV commented on the diff Mar 1, 2013
lib/matplotlib/colors.py
+ #result = (ma.arcsinh(val)-np.arcsinh(vmin))/(np.arcsinh(vmax)-np.arcsinh(vmin))
+ result = ma.arcsinh(result/midpoint) / ma.arcsinh(1./midpoint)
+ if vtype == 'scalar':
+ result = result[0]
+ return result
+
+ def autoscale_None(self, A):
+ ' autoscale only None-valued vmin or vmax'
+ if self.vmin is None:
+ self.vmin = ma.min(A)
+ if self.vmax is None:
+ self.vmax = ma.max(A)
+ if self.vmid is None:
+ self.vmid = (self.vmax+self.vmin)/2.0
+
+
@NelleV
NelleV Mar 1, 2013

PEP compliance: there should be only 2 blank lines here.

@NelleV NelleV commented on the diff Mar 1, 2013
lib/matplotlib/colors.py
+ elif vmin==vmax:
+ return 0.0 * val
+ else:
+ if clip:
+ mask = ma.getmask(val)
+ val = ma.array(np.clip(val.filled(vmax), vmin, vmax),
+ mask=mask)
+ result = (val-vmin) * (1.0/(vmax-vmin))
+ #result = (ma.arcsinh(val)-np.arcsinh(vmin))/(np.arcsinh(vmax)-np.arcsinh(vmin))
+ result = result**(1./self.nthroot)
+ if vtype == 'scalar':
+ result = result[0]
+ return result
+
+ def autoscale_None(self, A):
+ ' autoscale only None-valued vmin or vmax'
@NelleV
NelleV Mar 1, 2013

It would be nice to have real docstrings here, ie triple double-quotes.

@pelson
Matplotlib Developers member

In principle, I think I'm in favour of this change. It definitely needs some tests though (they don't need to be pictorial).

@keflavich - do you fancy trying to get this into v1.3?

@keflavich

Yes, sounds like a good idea. I'll work on it. I guess I should start with autopep8 and then work on tests. When's the 1.3 deadline

@NelleV

@keflavich autopep8 really doesn't work well: you might have to recheck everything by hand.
On the other hand, the pep8 tool is very strict, but very useful.

@pelson
Matplotlib Developers member

Closing as the PR seems to have stalled. I love the idea of a callable in a Norm constructor though - if somebody wants to take that forwards I'd be more than happy to review.

Cheers,

@pelson pelson closed this Jan 9, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment