Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

AddsCenteredAsinhNorm class to automatically center asinh-normaliz…#30691

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
U-C4N wants to merge1 commit intomatplotlib:main
base:main
Choose a base branch
Loading
fromU-C4N:main
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletionslib/matplotlib/colors.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3083,6 +3083,56 @@
self._scale.linear_width = value


class CenteredAsinhNorm(AsinhNorm):
def __init__(self, vcenter=0, halfrange=None, linear_width=1, clip=False):
# Initialize with AsinhNorm, vmin/vmax set via halfrange setter
super().__init__(linear_width=linear_width, vmin=None, vmax=None, clip=clip)
self._vcenter = vcenter
self.halfrange = halfrange

def autoscale(self, A):
"""Set *halfrange* to ``max(abs(A-vcenter))``, then set *vmin* and *vmax*."""
A = np.asanyarray(A)
self.halfrange = max(self._vcenter - A.min(),
A.max() - self._vcenter)

def autoscale_None(self, A):
"""Set *vmin* and *vmax* if not already set."""
A = np.asanyarray(A)
if self.halfrange is None and A.size:
self.autoscale(A)

@property
def vcenter(self):
"""The center value of the normalization."""
return self._vcenter

@vcenter.setter
def vcenter(self, vcenter):
"""Set center value and update vmin/vmax accordingly."""
if vcenter != self._vcenter:
self._vcenter = vcenter
self.halfrange = self.halfrange # Recalculate vmin/vmax
self._changed()

@property
def halfrange(self):
"""The half-range of the normalization. Returns None if vmin or vmax is not set."""

Check warning on line 3120 in lib/matplotlib/colors.py

View workflow job for this annotation

GitHub Actions/ ruff

[rdjson] reported by reviewdog 🐶Line too long (91 > 88)Raw Output:message:"Line too long (91 > 88)" location:{path:"/home/runner/work/matplotlib/matplotlib/lib/matplotlib/colors.py" range:{start:{line:3120 column:89} end:{line:3120 column:92}}} severity:WARNING source:{name:"ruff" url:"https://docs.astral.sh/ruff"} code:{value:"E501" url:"https://docs.astral.sh/ruff/rules/line-too-long"}
if self.vmin is None or self.vmax is None:
return None
return (self.vmax - self.vmin) / 2

@halfrange.setter
def halfrange(self, halfrange):
"""Set half-range and update vmin/vmax symmetrically around vcenter."""
if halfrange is None:
self.vmin = None
self.vmax = None
else:
self.vmin = self.vcenter - abs(halfrange)
self.vmax = self.vcenter + abs(halfrange)


class PowerNorm(Normalize):
r"""
Linearly map a given value to the 0-1 range and then apply
Expand Down
20 changes: 20 additions & 0 deletionslib/matplotlib/colors.pyi
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -388,6 +388,26 @@ class AsinhNorm(Normalize):
@linear_width.setter
def linear_width(self, value: float) -> None: ...

class CenteredAsinhNorm(AsinhNorm):
"""Type stub for CenteredAsinhNorm - symmetric asinh normalization."""
def __init__(
self,
vcenter: float = ...,
halfrange: float | None = ...,
linear_width: float = ...,
clip: bool = ...,
) -> None: ...
@property
def vcenter(self) -> float: ...
@vcenter.setter
def vcenter(self, vcenter: float) -> None: ...
@property
def halfrange(self) -> float | None: ...
@halfrange.setter
def halfrange(self, halfrange: float | None) -> None: ...
def autoscale(self, A: ArrayLike) -> None: ...
def autoscale_None(self, A: ArrayLike) -> None: ...

class PowerNorm(Normalize):
gamma: float
def __init__(
Expand Down
84 changes: 84 additions & 0 deletionslib/matplotlib/tests/test_colors.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -496,6 +496,90 @@ def test_CenteredNorm():
assert norm.vcenter == 1


def test_CenteredAsinhNorm():
"""Test CenteredAsinhNorm - symmetric asinh normalization."""
np.random.seed(0)

# Test 1: Basic centering - vcenter should be in middle of vmin/vmax
norm = mcolors.CenteredAsinhNorm(vcenter=0)
norm.autoscale_None([-10, 50]) # Asymmetric data
assert norm.vmax + norm.vmin == 2 * norm.vcenter
assert norm.vmin == -50 # Symmetric around 0
assert norm.vmax == 50

# Test 2: halfrange is preserved when set explicitly
norm = mcolors.CenteredAsinhNorm(vcenter=0, halfrange=10)
norm.autoscale_None([1, 3000])
assert norm.halfrange == 10
assert norm.vmin == -10
assert norm.vmax == 10

# Test 3: Different vcenter values
vcenter = 5
norm = mcolors.CenteredAsinhNorm(vcenter=vcenter)
norm.autoscale_None([1, 10])
assert norm.vmax + norm.vmin == 2 * vcenter
assert norm.halfrange == 5 # max(5-1, 10-5) = 5
assert norm.vmin == 0
assert norm.vmax == 10

# Test 4: Center maps to 0.5
norm = mcolors.CenteredAsinhNorm(vcenter=0, halfrange=10, linear_width=1)
result = norm(0)
assert np.isclose(result, 0.5), f"Center should map to 0.5, got {result}"

# Test 5: Symmetry - equal distances from center
norm = mcolors.CenteredAsinhNorm(vcenter=0, halfrange=10)
val_pos = norm(5)
val_neg = norm(-5)
assert np.isclose(val_pos, 1 - val_neg), \
f"Symmetric values should map symmetrically: {val_pos} vs {1-val_neg}"

# Test 6: vcenter setter preserves halfrange
norm = mcolors.CenteredAsinhNorm(vcenter=0, halfrange=10)
assert norm.vmin == -10
assert norm.vmax == 10
norm.vcenter = 5
assert norm.halfrange == 10
assert norm.vmin == -5
assert norm.vmax == 15

# Test 7: halfrange setter
norm = mcolors.CenteredAsinhNorm(vcenter=0)
norm.halfrange = 20
assert norm.vmin == -20
assert norm.vmax == 20
assert norm.vcenter == 0

# Test 8: linear_width parameter (inherited from AsinhNorm)
norm = mcolors.CenteredAsinhNorm(vcenter=0, halfrange=10, linear_width=2)
assert norm.linear_width == 2
norm.linear_width = 5
assert norm.linear_width == 5

# Test 9: Negative vcenter
norm = mcolors.CenteredAsinhNorm(vcenter=-10, halfrange=5)
assert norm.vmin == -15
assert norm.vmax == -5
assert norm.vcenter == -10

# Test 10: autoscale with symmetric data
norm = mcolors.CenteredAsinhNorm(vcenter=0)
data = np.array([-5, -3, 0, 3, 5])
norm.autoscale(data)
assert norm.halfrange == 5
assert norm.vmin == -5
assert norm.vmax == 5

# Test 11: Main use case from issue #30679 - diverging colormaps
norm = mcolors.CenteredAsinhNorm(vcenter=0)
data = np.array([-10, -5, 0, 5, 50]) # Asymmetric data
normalized = norm(data)
center_idx = np.where(data == 0)[0][0]
assert np.isclose(normalized[center_idx], 0.5), \
"Center value should map to 0.5 for diverging colormaps"


@pytest.mark.parametrize("vmin,vmax", [[-1, 2], [3, 1]])
def test_lognorm_invalid(vmin, vmax):
# Check that invalid limits in LogNorm error
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp