Skip to content

PowerNorm should have a scale... #20355

Open

Description

@jklymak

We have a PowerNorm. We should define a PowerScale derived from a PowerTransform consistent with the log norm, etc. This will allow a proper scale for PowerNorm. I'm not exactly sure what such a scale should look like (i.e. where to put the ticks), but its probably not a linear scale.

Activity

  1. jklymak commented on Jun 3, 2021

    @jklymak
    MemberAuthor

    I'll mark as "Good first issue" because I think the example of LogNorm is relatively easy to follow and emulate down the stack. There is a bit of work to argue for the correct Locator and Formatter of the scale, but there must be examples out there.

  2. 3r3n-n commented on Jun 9, 2021

    @3r3n-n

    Hi !
    Can I try to work on this ?

  3. anntzer commented on Jun 9, 2021

    @anntzer
    Contributor

    See the discussion at #10234, #7294 (comment), #12665 regarding PowerNorm's semantics, though.

  4. yacth commented on Jun 12, 2021

    @yacth

    Hi @jklymak,

    Could you tell me more about where should I start, which files are involved in here ?
    I am willing to help for this issue.

  5. jklymak commented on Jun 12, 2021

    @jklymak
    MemberAuthor

    colors.py and look at how LogNorm is constructed...

  6. yacth commented on Jun 13, 2021

    @yacth

    Hi @jklymak could you explain me what this line does please ?

    @_make_norm_from_scale(functools.partial(scale.LogScale, nonpositive="mask"))
  7. jklymak commented on Jun 13, 2021

    @jklymak
    MemberAuthor

    It makes the norm! Note nonpositive is a kwarg for LogScale. The functools.partial passes nonpsoitive="mask" to the init of the LogScale.

    I actually don't know why @anntzer used functors.partial here rather than passing the init kwarg.

  8. anntzer commented on Jun 13, 2021

    @anntzer
    Contributor

    I don't really remember, bu I guess that may be because using the init kwarg would have made nonpositive a kwarg to LogNorm, which it wasn't before?

  9. yacth commented on Jun 13, 2021

    @yacth

    Ok I understand, thank you.
    Thus we have to add the 'PowerScale' in 'scale.py' I believe ?

    How should we define it ? @jklymak, you said that there must be an example out here.

  10. jklymak commented on Jun 13, 2021

    @jklymak
    MemberAuthor

    Again, trace what happens with LogScale...

  11. yacth commented on Jun 14, 2021

    @yacth

    Hi @jklymak, I started the implementation in the scale.py file of :

    • PowerTransform : --> How we should define the distinctions for the base ? It seems that all bases could be fine with this Transform.
    • InvertedPowerTransform --> 100% done, it was similar to the InvertedLogTransform in the code architecture.
    • PowerScale : --> Do we have to redefine this inhirited method ? set_default_locators_and_formatters(self, axis)
  12. yacth commented on Jun 25, 2021

    @yacth

    If I add this line :

    @_make_norm_from_scale(
        scale.PowerScale,
        init=lambda gamma, vmin=None, vmax=None, clip=False: None)

    just before the PowerNorm() class in the colors.py, it means I should access the kwargs for the PowerScale() in the scale.py file, am I right ?
    Because I am trying to use them but in visual studio it says for example for vmin that "vmin" is not defined.

  13. SHRISHTISHUKLA-0 commented on Jan 30, 2025

    @SHRISHTISHUKLA-0

    hello
    does this issue is still open to contribute??

  14. nixkitax commented on Aug 26, 2025

    @nixkitax

    Hello, since some time has passed, I'll try to contribute to this issue!

  15. Impaler343 commented on Aug 27, 2025

    @Impaler343
    Contributor

    In the current implementation of make_norm_from_scale the value, vmin, and vmax are normalized after transformation as: f(value) - f(v_min) / (f(v_max) - f(v_min)).
    However in case of PowerNorm, the expected behavior is normalizing before transformation as:
    f((value - vmin) / (vmax - vmin)).

    One possible solution, is to add an optional boolean argument to make_norm_from_scale that changes the order in which transformation and normalization is applied (for example, norm_before_trf).

    The new signature would look like:

    def make_norm_from_scale(scale_cls, base_norm_cls=None,*, init=None,
                             norm_before_trf=False):

    In the ScaleNorm class:

    def __call__(self, value, clip=None):
        ...
        if norm_before_trf:
            t_value = value - self.vmin
            t_value /= self.vmax - self.vmin
            t_value = self._trf.transform(t_value).reshape(np.shape(t_value))
            t_value = np.ma.masked_invalid(t_value, copy=False)
            return t_value[0] if is_scalar else t_value
        ...

    Which path should we follow? @jklymak

  16. jklymak commented on Aug 27, 2025

    @jklymak
    MemberAuthor

    However in case of PowerNorm, the expected behavior is normalizing before transformation as: f((value - vmin) / (vmax - vmin)).

    Why is PowerNorm different? Sorry, I've not kept up with power norm and have never personally used it.

  17. Impaler343 commented on Aug 27, 2025

    @Impaler343
    Contributor

    Quoting the first half of this comment

    Yes, it is equivalent to ((a - vmin) / (vmax - vmin))**gamma, so a plain Normalize is the case of gamma=1. This seems reasonable to me; we are not dealing with logs. It is consistent with the description of Gamma correction: https://en.wikipedia.org/wiki/Gamma_correction. First the data are linearly scaled to the 0-1 range based on specified vmin and vmax, and second, that 0-1 range is mapped to a new 0-1 range by raising it to a power.

    It seems that, doing normalization before transformation: ((a - vmin) / (vmax - vmin))**gamma is how PowerNorm is defined / expected to function.

    If normalization is done after transformation (like other norms), the formula changes to (a**gamma - vmin**gamma)/(vmax**gamma - vmin**gamma).

  18. rxchitrx commented on Aug 28, 2025

    @rxchitrx

    Hi! I'm interested in working on this issue. I'm new to open source contributions but have been learning Python and would like to add the scale parameter to PowerNorm to make it consistent with other normalizers.

    Based on the discussion, it looks like I need to:

    1. Add a scale parameter to PowerNorm's init method
    2. Apply the scaling in the normalization logic
    3. Add appropriate tests

    Is anyone else currently working on this? If not, I'd like to give it a try!

  19. Impaler343 commented on Aug 29, 2025

    @Impaler343
    Contributor

    @rxchitrx Im working on this and will open a PR soon

  20. rxchitrx commented on Sep 10, 2025

    @rxchitrx

    I've opened a PR to address this issue: #[30542]

    The implementation adds a scale parameter to PowerNorm that allows pre-scaling of input data before normalization. It maintains full backward compatibility (scale defaults to 1.0) and includes comprehensive tests.

    Feel free to review and provide feedback!

  21. rxchitrx commented on Sep 10, 2025

    @rxchitrx

    I see the mypy-stubtest is failing because the type stub file needs to be updated to include the new scale parameter. Should I update lib/matplotlib/colors.pyi to add the scale parameter to the PowerNorm __init__ signature, or would you prefer to handle this differently?

    Happy to make any changes you'd recommend!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions