From f12ff0969dddb6f6f11384cc9234e952e22497ce Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Tue, 2 Sep 2025 18:48:09 +0200 Subject: [PATCH 1/7] Register more "clipped" color maps, use viridis_clip as default for level() --- sfs/plot2d.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/sfs/plot2d.py b/sfs/plot2d.py index 63f69966..0d71c00f 100644 --- a/sfs/plot2d.py +++ b/sfs/plot2d.py @@ -8,27 +8,26 @@ from . import util as _util -def _register_cmap_clip(name, original_cmap, alpha): +def _register_cmap_clip(name, original_name, alpha): """Create a color map with "over" and "under" values.""" - from matplotlib.colors import LinearSegmentedColormap - cdata = _plt.cm.datad[original_cmap] - if isinstance(cdata, dict): - cmap = LinearSegmentedColormap(name, cdata) - else: - cmap = LinearSegmentedColormap.from_list(name, cdata) - cmap.set_over([alpha * c + 1 - alpha for c in cmap(1.0)[:3]]) - cmap.set_under([alpha * c + 1 - alpha for c in cmap(0.0)[:3]]) + cmap = _plt.get_cmap(original_name) + cmap = cmap.with_extremes( + under=cmap.get_under() * [1, 1, 1, alpha], + over=cmap.get_over() * [1, 1, 1, alpha]) + cmap.name = name _plt.colormaps.register(cmap=cmap) +_register_cmap_clip('cividis_clip', 'cividis', 0.6) +_register_cmap_clip('cividis_r_clip', 'cividis_r', 0.6) +_register_cmap_clip('viridis_clip', 'viridis', 0.6) +_register_cmap_clip('viridis_r_clip', 'viridis_r', 0.6) + # The 'coolwarm' colormap is based on the paper # "Diverging Color Maps for Scientific Visualization" by Kenneth Moreland # http://www.sandia.gov/~kmorel/documents/ColorMaps/ -# already registered in MPL 3.9.0 -try: - _register_cmap_clip('coolwarm_clip', 'coolwarm', 0.7) -except ImportError: - pass +_register_cmap_clip('coolwarm_clip', 'coolwarm', 0.6) +_register_cmap_clip('coolwarm_r_clip', 'coolwarm_r', 0.6) def _register_cmap_transparent(name, color): @@ -313,8 +312,8 @@ def amplitude(p, grid, *, xnorm=None, cmap='coolwarm_clip', return im -def level(p, grid, *, xnorm=None, power=False, cmap=None, vmax=3, vmin=-50, - **kwargs): +def level(p, grid, *, xnorm=None, power=False, cmap='viridis_clip', + vmax=3, vmin=-50, **kwargs): """Two-dimensional plot of level (dB) of sound field. Takes the same parameters as `sfs.plot2d.amplitude()`. @@ -336,7 +335,7 @@ def level(p, grid, *, xnorm=None, power=False, cmap=None, vmax=3, vmin=-50, def particles(x, *, trim=None, ax=None, xlabel='x (m)', ylabel='y (m)', edgecolors=None, marker='.', s=15, **kwargs): """Plot particle positions as scatter plot. - + Parameters ---------- x : triple or pair of array_like From 4c18d0c78bd6ef55b866d306635c0f1326517b67 Mon Sep 17 00:00:00 2001 From: Frank Schultz Date: Wed, 3 Sep 2025 17:04:02 +0200 Subject: [PATCH 2/7] Update plot2d.py proposed alternative: - instead of having same alpha for both under and over we use individual color weights and alphas for the under/over color - by that the extreme colors can be better adapted towards the individual colormaps, for viridis and cividis to me this is not needed but - inferno, magma, plasma colormaps could then be handled much nicer, if people like to add them one day - the colorwarm URL link did not work, changed to an exisiting one --- sfs/plot2d.py | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/sfs/plot2d.py b/sfs/plot2d.py index 0d71c00f..aed37ae0 100644 --- a/sfs/plot2d.py +++ b/sfs/plot2d.py @@ -8,26 +8,32 @@ from . import util as _util -def _register_cmap_clip(name, original_name, alpha): +def _register_cmap_clip(name, original_name, col_alpha_u, col_apha_o): """Create a color map with "over" and "under" values.""" cmap = _plt.get_cmap(original_name) cmap = cmap.with_extremes( - under=cmap.get_under() * [1, 1, 1, alpha], - over=cmap.get_over() * [1, 1, 1, alpha]) + under=cmap.get_under() * col_alpha_u, + over=cmap.get_over() * col_apha_o) cmap.name = name _plt.colormaps.register(cmap=cmap) -_register_cmap_clip('cividis_clip', 'cividis', 0.6) -_register_cmap_clip('cividis_r_clip', 'cividis_r', 0.6) -_register_cmap_clip('viridis_clip', 'viridis', 0.6) -_register_cmap_clip('viridis_r_clip', 'viridis_r', 0.6) +_register_cmap_clip('cividis_clip', 'cividis', + [1, 1, 1, 0.6],[1, 1, 1, 0.1]) +_register_cmap_clip('cividis_r_clip', 'cividis_r', + [1, 1, 1, 0.1], [1, 1, 1, 0.6]) +_register_cmap_clip('viridis_clip', 'viridis', + [1, 1, 1, 0.6], [1, 1, 1, 0.1]) +_register_cmap_clip('viridis_r_clip', 'viridis_r', + [1, 1, 1, 0.1], [1, 1, 1, 0.6]) # The 'coolwarm' colormap is based on the paper # "Diverging Color Maps for Scientific Visualization" by Kenneth Moreland -# http://www.sandia.gov/~kmorel/documents/ColorMaps/ -_register_cmap_clip('coolwarm_clip', 'coolwarm', 0.6) -_register_cmap_clip('coolwarm_r_clip', 'coolwarm_r', 0.6) +# https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf +_register_cmap_clip('coolwarm_clip', 'coolwarm', + [1, 1, 1, 0.5], [1, 1, 1, 0.5]) +_register_cmap_clip('coolwarm_r_clip', 'coolwarm_r', + [1, 1, 1, 0.5], [1, 1, 1, 0.5]) def _register_cmap_transparent(name, color): @@ -176,7 +182,7 @@ def _visible_secondarysources(x0, n0, grid): """Determine secondary sources which lie within *grid*.""" x, y = _util.as_xyz_components(grid[:2]) idx = _np.where((x0[:, 0] > x.min()) & (x0[:, 0] < x.max()) & - (x0[:, 1] > y.min()) & (x0[:, 1] < x.max())) + (x0[:, 1] > y.min()) & (x0[:, 1] < x.max())) idx = _np.squeeze(idx) return x0[idx, :], n0[idx, :] From d600d26ad5ee41daca46d856f659f8e8e66a9a9b Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Wed, 3 Sep 2025 19:59:10 +0200 Subject: [PATCH 3/7] Mix dark colors with white, bright colors with black --- sfs/plot2d.py | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/sfs/plot2d.py b/sfs/plot2d.py index aed37ae0..8955f14f 100644 --- a/sfs/plot2d.py +++ b/sfs/plot2d.py @@ -8,32 +8,37 @@ from . import util as _util -def _register_cmap_clip(name, original_name, col_alpha_u, col_apha_o): +def _apply_alpha(rgba, alpha): + """Mix dark colors with white, bright colors with black.""" + from colorsys import rgb_to_hls + r, g, b, a = rgba + _, l, _ = rgb_to_hls(r, g, b) + if l > 0.5: + return [alpha * c for c in [r, g, b]] + [a] + else: + return [alpha * c + 1 - alpha for c in [r, g, b]] + [a] + + +def _register_cmap_clip(name, original_name, alpha): """Create a color map with "over" and "under" values.""" cmap = _plt.get_cmap(original_name) - cmap = cmap.with_extremes( - under=cmap.get_under() * col_alpha_u, - over=cmap.get_over() * col_apha_o) + over = _apply_alpha(cmap.get_over(), alpha) + under = _apply_alpha(cmap.get_under(), alpha) + cmap = cmap.with_extremes(under=under, over=over) cmap.name = name _plt.colormaps.register(cmap=cmap) -_register_cmap_clip('cividis_clip', 'cividis', - [1, 1, 1, 0.6],[1, 1, 1, 0.1]) -_register_cmap_clip('cividis_r_clip', 'cividis_r', - [1, 1, 1, 0.1], [1, 1, 1, 0.6]) -_register_cmap_clip('viridis_clip', 'viridis', - [1, 1, 1, 0.6], [1, 1, 1, 0.1]) -_register_cmap_clip('viridis_r_clip', 'viridis_r', - [1, 1, 1, 0.1], [1, 1, 1, 0.6]) +_register_cmap_clip('cividis_clip', 'cividis', 0.8) +_register_cmap_clip('cividis_r_clip', 'cividis_r', 0.8) +_register_cmap_clip('viridis_clip', 'viridis', 0.8) +_register_cmap_clip('viridis_r_clip', 'viridis_r', 0.8) # The 'coolwarm' colormap is based on the paper # "Diverging Color Maps for Scientific Visualization" by Kenneth Moreland # https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf -_register_cmap_clip('coolwarm_clip', 'coolwarm', - [1, 1, 1, 0.5], [1, 1, 1, 0.5]) -_register_cmap_clip('coolwarm_r_clip', 'coolwarm_r', - [1, 1, 1, 0.5], [1, 1, 1, 0.5]) +_register_cmap_clip('coolwarm_clip', 'coolwarm', 0.65) +_register_cmap_clip('coolwarm_r_clip', 'coolwarm_r', 0.65) def _register_cmap_transparent(name, color): From 3284bedcc6c47c9e245b0f3a2a69c1b0b5cf74f7 Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Thu, 4 Sep 2025 18:48:01 +0200 Subject: [PATCH 4/7] Calculate under/over values with colorspacious --- sfs/plot2d.py | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/sfs/plot2d.py b/sfs/plot2d.py index 8955f14f..5f1cd959 100644 --- a/sfs/plot2d.py +++ b/sfs/plot2d.py @@ -8,37 +8,48 @@ from . import util as _util -def _apply_alpha(rgba, alpha): - """Mix dark colors with white, bright colors with black.""" - from colorsys import rgb_to_hls - r, g, b, a = rgba - _, l, _ = rgb_to_hls(r, g, b) - if l > 0.5: - return [alpha * c for c in [r, g, b]] + [a] +def _make_extreme(rgba): + """Make bright colors darker, dark colors brighter, both less saturated.""" + # The package `colorspacious` must be installed for this to work: + from colorspacious import cspace_convert + lightness_step = 25 + chroma_factor = 0.7 + j, c, h = cspace_convert(rgba[:3], 'sRGB1', 'JCh') + if j > 50: + j -= lightness_step else: - return [alpha * c + 1 - alpha for c in [r, g, b]] + [a] + j += lightness_step + c *= chroma_factor + rgba[:3] = _np.clip(cspace_convert([j, c, h], 'JCh', 'sRGB1'), 0, 1) + return rgba -def _register_cmap_clip(name, original_name, alpha): +def _register_cmap_clip(name, original_name): """Create a color map with "over" and "under" values.""" cmap = _plt.get_cmap(original_name) - over = _apply_alpha(cmap.get_over(), alpha) - under = _apply_alpha(cmap.get_under(), alpha) - cmap = cmap.with_extremes(under=under, over=over) + cmap = cmap.with_extremes( + under=_make_extreme(cmap.get_under()), + over=_make_extreme(cmap.get_over())) cmap.name = name _plt.colormaps.register(cmap=cmap) -_register_cmap_clip('cividis_clip', 'cividis', 0.8) -_register_cmap_clip('cividis_r_clip', 'cividis_r', 0.8) -_register_cmap_clip('viridis_clip', 'viridis', 0.8) -_register_cmap_clip('viridis_r_clip', 'viridis_r', 0.8) +_register_cmap_clip('cividis_clip', 'cividis') +_register_cmap_clip('cividis_r_clip', 'cividis_r') +_register_cmap_clip('inferno_clip', 'inferno') +_register_cmap_clip('inferno_r_clip', 'inferno_r') +_register_cmap_clip('magma_clip', 'magma') +_register_cmap_clip('magma_r_clip', 'magma_r') +_register_cmap_clip('plasma_clip', 'plasma') +_register_cmap_clip('plasma_r_clip', 'plasma_r') +_register_cmap_clip('viridis_clip', 'viridis') +_register_cmap_clip('viridis_r_clip', 'viridis_r') # The 'coolwarm' colormap is based on the paper # "Diverging Color Maps for Scientific Visualization" by Kenneth Moreland # https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf -_register_cmap_clip('coolwarm_clip', 'coolwarm', 0.65) -_register_cmap_clip('coolwarm_r_clip', 'coolwarm_r', 0.65) +_register_cmap_clip('coolwarm_clip', 'coolwarm') +_register_cmap_clip('coolwarm_r_clip', 'coolwarm_r') def _register_cmap_transparent(name, color): From d5006b940ab73f8754c925c2c72a48f3f190213f Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Thu, 11 Sep 2025 18:32:33 +0200 Subject: [PATCH 5/7] Use extend='both' for color bars in sound field plots --- sfs/plot2d.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sfs/plot2d.py b/sfs/plot2d.py index 5f1cd959..7a91d2fb 100644 --- a/sfs/plot2d.py +++ b/sfs/plot2d.py @@ -206,7 +206,7 @@ def _visible_secondarysources(x0, n0, grid): def amplitude(p, grid, *, xnorm=None, cmap='coolwarm_clip', vmin=-2.0, vmax=2.0, xlabel=None, ylabel=None, - colorbar=True, colorbar_kwargs={}, ax=None, **kwargs): + colorbar=True, colorbar_kwargs=None, ax=None, **kwargs): """Two-dimensional plot of sound field (real part). Parameters @@ -330,6 +330,8 @@ def amplitude(p, grid, *, xnorm=None, cmap='coolwarm_clip', ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) if colorbar: + if colorbar_kwargs is None: + colorbar_kwargs = dict(extend='both') add_colorbar(im, **colorbar_kwargs) return im From 051ab6352bf58fc665bd91561ffad26fe22e539f Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Mon, 22 Sep 2025 19:17:14 +0200 Subject: [PATCH 6/7] Add "clipped" variant of "RdBu" colormap --- sfs/plot2d.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sfs/plot2d.py b/sfs/plot2d.py index 7a91d2fb..62bc29b0 100644 --- a/sfs/plot2d.py +++ b/sfs/plot2d.py @@ -45,6 +45,9 @@ def _register_cmap_clip(name, original_name): _register_cmap_clip('viridis_clip', 'viridis') _register_cmap_clip('viridis_r_clip', 'viridis_r') +_register_cmap_clip('RdBu_clip', 'RdBu') +_register_cmap_clip('RdBu_r_clip', 'RdBu_r') + # The 'coolwarm' colormap is based on the paper # "Diverging Color Maps for Scientific Visualization" by Kenneth Moreland # https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf From e42bc5307c09d1b5be83775fb9bb82704b5f97a2 Mon Sep 17 00:00:00 2001 From: Matthias Geier Date: Wed, 24 Sep 2025 19:51:54 +0200 Subject: [PATCH 7/7] Hard-code under/over values --- sfs/plot2d.py | 68 ++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/sfs/plot2d.py b/sfs/plot2d.py index 62bc29b0..cd51aa1c 100644 --- a/sfs/plot2d.py +++ b/sfs/plot2d.py @@ -24,35 +24,63 @@ def _make_extreme(rgba): return rgba -def _register_cmap_clip(name, original_name): - """Create a color map with "over" and "under" values.""" +def _register_cmap_with_extremes(name, original_name, **kwargs): + """Create a color map with "under" and "over" values.""" cmap = _plt.get_cmap(original_name) - cmap = cmap.with_extremes( - under=_make_extreme(cmap.get_under()), - over=_make_extreme(cmap.get_over())) + cmap = cmap.with_extremes(**kwargs) cmap.name = name _plt.colormaps.register(cmap=cmap) -_register_cmap_clip('cividis_clip', 'cividis') -_register_cmap_clip('cividis_r_clip', 'cividis_r') -_register_cmap_clip('inferno_clip', 'inferno') -_register_cmap_clip('inferno_r_clip', 'inferno_r') -_register_cmap_clip('magma_clip', 'magma') -_register_cmap_clip('magma_r_clip', 'magma_r') -_register_cmap_clip('plasma_clip', 'plasma') -_register_cmap_clip('plasma_r_clip', 'plasma_r') -_register_cmap_clip('viridis_clip', 'viridis') -_register_cmap_clip('viridis_r_clip', 'viridis_r') - -_register_cmap_clip('RdBu_clip', 'RdBu') -_register_cmap_clip('RdBu_r_clip', 'RdBu_r') +# The following under/over values have been calculated with _make_extreme(). +# They are hard-coded to avoid a dependency on the library "colorspacious". +_register_cmap_with_extremes('cividis_clip', 'cividis', + under=[0.3581123750444155, 0.4308239004832521, 0.5431626919728758, 1.0], + over=[0.748794386079359, 0.6952014568472878, 0.27380570592765713, 1.0]) +_register_cmap_with_extremes('cividis_r_clip', 'cividis_r', + under=[0.748794386079359, 0.6952014568472878, 0.27380570592765713, 1.0], + over=[0.3581123750444155, 0.4308239004832521, 0.5431626919728758, 1.0]) +_register_cmap_with_extremes('inferno_clip', 'inferno', + under=[0.3223737972210511, 0.3196564508033573, 0.3474201893768059, 1.0], + over=[0.7759331577663429, 0.7815136432099379, 0.5546145677840046, 1.0]) +_register_cmap_with_extremes('inferno_r_clip', 'inferno_r', + under=[0.7759331577663429, 0.7815136432099379, 0.5546145677840046, 1.0], + over=[0.3223737972210511, 0.3196564508033573, 0.3474201893768059, 1.0]) +_register_cmap_with_extremes('magma_clip', 'magma', + under=[0.3223737972210511, 0.3196564508033573, 0.3474201893768059, 1.0], + over=[0.7755917106347097, 0.7765145738617047, 0.621366899182334, 1.0]) +_register_cmap_with_extremes('magma_r_clip', 'magma_r', + under=[0.7755917106347097, 0.7765145738617047, 0.621366899182334, 1.0], + over=[0.3223737972210511, 0.3196564508033573, 0.3474201893768059, 1.0]) +_register_cmap_with_extremes('plasma_clip', 'plasma', + under=[0.3425913695445096, 0.42529714344969144, 0.66039452922638, 1.0], + over=[0.723897190872765, 0.7507494961114689, 0.2574503078804632, 1.0]) +_register_cmap_with_extremes('plasma_r_clip', 'plasma_r', + under=[0.723897190872765, 0.7507494961114689, 0.2574503078804632, 1.0], + over=[0.3425913695445096, 0.42529714344969144, 0.66039452922638, 1.0]) +_register_cmap_with_extremes('viridis_clip', 'viridis', + under=[0.536623905475994, 0.3775029064902613, 0.5655492658877974, 1.0], + over=[0.7453792828268919, 0.6916769483054797, 0.24219807423955453, 1.0]) +_register_cmap_with_extremes('viridis_r_clip', 'viridis_r', + under=[0.7453792828268919, 0.6916769483054797, 0.24219807423955453, 1.0], + over=[0.536623905475994, 0.3775029064902613, 0.5655492658877974, 1.0]) + +_register_cmap_with_extremes('RdBu_clip', 'RdBu', + under=[0.6931181505544421, 0.3643024937396605, 0.40355267940576434, 1.0], + over=[0.3844199587527661, 0.4705632423745359, 0.597949800233206, 1.0]) +_register_cmap_with_extremes('RdBu_r_clip', 'RdBu_r', + under=[0.3844199587527661, 0.4705632423745359, 0.597949800233206, 1.0], + over=[0.6931181505544421, 0.3643024937396605, 0.40355267940576434, 1.0]) # The 'coolwarm' colormap is based on the paper # "Diverging Color Maps for Scientific Visualization" by Kenneth Moreland # https://www.kennethmoreland.com/color-maps/ColorMapsExpanded.pdf -_register_cmap_clip('coolwarm_clip', 'coolwarm') -_register_cmap_clip('coolwarm_r_clip', 'coolwarm_r') +_register_cmap_with_extremes('coolwarm_clip', 'coolwarm', + under=[0.510515040101537, 0.5812838578665308, 0.8594377816482693, 1.0], + over=[0.9464304571740522, 0.43619922642510556, 0.4340300540797168, 1.0]) +_register_cmap_with_extremes('coolwarm_r_clip', 'coolwarm_r', + under=[0.9464304571740522, 0.43619922642510556, 0.4340300540797168, 1.0], + over=[0.510515040101537, 0.5812838578665308, 0.8594377816482693, 1.0]) def _register_cmap_transparent(name, color):