skip to Main Content

I am putting up a colorbar tool with Python/Matplotlib and need to change the opacity at arbitrary ranges along the Y axis, instead of having the opacity value associated to the colors themselves.

Reading other posts in SO and various online tutorials, I came across posts showing pcolormesh, facecolor, edgecolor or adding the alpha value as the 4th column in the RGB color definition, but none of them actually solves the problem.

If you run my minimum sample code below (with previous help from SO, which I appreciate), you will see the opacity as defined by each color:

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

XMIN        = 0.0
XMAX        = 1.0
YMIN        = 1000.0
YMAX        = 2000.0
RATIO       = 5.0
NUM_COLORS  = 256

color_list      = ([0.2, 0.88, 0.3, 0.25], [0.33, 1.0, 0.25, 0.15], [0.7, 0.2, 0.5, 1.0], [0.0, 0.34, 0.2, 0.1], [0.5, 0.9, 0.1, 1.0])  # RGBA, 0 to 1 plus opacity
bounds          = [YMIN, 1100.0, 1400.0, 1800.0, YMAX]
color_tuples    = [(i, color) for i, color in zip(np.interp(bounds, [bounds[0], bounds[-1]], [0, 1]), color_list)]
fig, ax         = plt.subplots(1, 1, figsize = (4, 10), num = "Variable opacity by Y?")
segm_cmap       = mpl.colors.LinearSegmentedColormap.from_list("Segm_cmap", color_tuples, N = NUM_COLORS)
norm            = plt.Normalize(vmin = bounds[0], vmax = bounds[-1])
img             = ax.imshow(np.linspace(YMIN, YMAX, NUM_COLORS).reshape(-1, 1), cmap = segm_cmap, norm = norm, interpolation = "bilinear",
                        origin = "lower", extent = [XMIN, XMAX, YMIN, YMAX])
ax.set_aspect((XMAX - XMIN) / (YMAX - YMIN) * RATIO)

plt.show()

However, just like the color boundaries/gradient defined by the "bounds" list, I thought I could make a similar tuple with x,y pairs and the respective opacity and pass it as "alpha" to imshow(), since Matplotlib documentation suggests it accepts, among other things, a 2D array. Something like:

opacities = ([[XMIN to XMAX, YMIN to 1200], 1.0], [[XMIN to XMAX, 1200.1 to 1500], 0.5], [[XMIN to XMAX, 1500.1 to YMAX], 0.1])
img = ax.imshow(....., alpha = opacities)

That is, a X-range, a Y-range and an opacity value. But I couldn’t for the life of me make it work, nor could I find a few sample codes to guide me. So I said, "for justice, I must go to Don Corl…", I mean, I must seek advice from the wise guys if it is at all possible. For your reference, I have Python 3.10.12 and Matplotlib 3.5.1 (Ubuntu 22.04, if relevant).

2

Answers


  1. The code below creates the pattern for alpha (left), which is applied to a Y grid (centre), resulting in the alpha-shaded image (right).

    enter image description here

    The code creates a vector of alpha values based on the values and break points you specified. The alpha vector and the y axis vectors are then duplicated onto a 2D grid and rendered.


    import matplotlib as mpl
    import matplotlib.pyplot as plt
    import numpy as np
    
    XMIN        = 0.0
    XMAX        = 1.0
    YMIN        = 1000.0
    YMAX        = 2000.0
    RATIO       = 5.0
    NUM_COLORS  = 256
    
    
    color_list      = ([0.2, 0.88, 0.3, 0.25],
                       [0.33, 1.0, 0.25, 0.15],
                       [0.7, 0.2, 0.5, 1.0],
                       [0.0, 0.34, 0.2, 0.1],
                       [0.5, 0.9, 0.1, 1.0])  # RGBA, 0 to 1 plus opacity
    
    y_bounds        = [YMIN, 1100.0, 1400.0, 1800.0, YMAX]
    norm            = plt.Normalize(vmin=y_bounds[0], vmax=y_bounds[-1])
    
    color_tuples    = [(i, color) for i, color in zip(norm(y_bounds), color_list)]
    segm_cmap       = mpl.colors.LinearSegmentedColormap.from_list("Segm_cmap", color_tuples, N=NUM_COLORS)
    
    fig, ax         = plt.subplots(figsize = (3, 5), num = "Variable opacity by Y?")
    img             = ax.imshow(np.linspace(YMIN, YMAX, NUM_COLORS).reshape(-1, 1),
                                cmap = segm_cmap,
                                norm = norm, interpolation = "bilinear",
                                origin = "lower",
                                extent = [XMIN, XMAX, YMIN, YMAX])
    ax.set_aspect((XMAX - XMIN) / (YMAX - YMIN) * RATIO)
    plt.show()
    
    
    #
    # Implement 2D alpha
    #
    #The user-defined alpha values and break points along y
    alpha_y_breaks = [0, 1200.1, 1500.1]
    alpha_values = [1.0, 0.5, 0.1]
    
    #Create an xy grid by repeating the y axis
    # smooth gradient from YMIN to YMAX
    y_axis = np.linspace(YMIN, YMAX, num=NUM_COLORS)
    y_grid = np.tile(y_axis.reshape(-1, 1), [1, NUM_COLORS])
    
    #For each break point, find its index in y
    break_indices = [np.argwhere(y_axis >= break_pt)[0].item() for break_pt in alpha_y_breaks] + [len(y_axis)]
    #Difference the indices to get the number of elements between break points
    repeats_per_alpha = np.diff(break_indices)
    
    #Repeat each alpha value for however many repeats are needed
    alpha_vector = np.concatenate(
        [np.repeat(alpha, repeats) for alpha, repeats in zip(alpha_values, repeats_per_alpha)]
    ).reshape(-1, 1)
    
    #Broadcast the alpha vector along x
    alpha_grid = alpha_vector * np.ones_like(y_grid)
    
    #View alpha
    plt.imshow(alpha_grid, cmap='plasma')
    plt.colorbar()
    plt.gca().set(xlabel='X', ylabel='Y')
    #View Y
    plt.imshow(y_grid, cmap=segm_cmap, norm=norm, interpolation='bilinear', origin='lower')
    plt.colorbar()
    plt.gca().set(xlabel='X', ylabel='Y')
    
    #Y and alpha
    plt.imshow(y_grid, cmap = segm_cmap, norm = norm,
              interpolation = "bilinear", origin = "lower",
              alpha=alpha_grid)
    plt.gca().set(xlabel='X', ylabel='Y')
    
    Login or Signup to reply.
  2. LinearSegmentedColormap smooths out the colors (including the alpha values).
    When the alpha= parameter of imshow() is 2D, it needs to have the same number of elements as the image data. For your use case, np.where() can be used to create the alphas from the image data.

    The code below ignores the alpha given in the original color list:

    import matplotlib as mpl
    import matplotlib.pyplot as plt
    import numpy as np
    
    XMIN = 0.0
    XMAX = 1.0
    RATIO = 5.0
    NUM_COLORS = 256
    
    color_list = ([0.2, 0.88, 0.3, 0.25], [0.33, 1.0, 0.25, 0.15], [0.7, 0.2, 0.5, 1.0],
                  [0.0, 0.34, 0.2, 0.1], [0.5, 0.9, 0.1, 1.0])  # RGBA, 0 to 1 plus opacity
    color_list_rgb = [rgba[:3] for rgba in color_list]
    bounds = [1000, 1100, 1400, 1800, 2000]
    YMIN = bounds[0]
    YMAX = bounds[-1]
    
    color_tuples = [(i, color) for i, color in zip(np.interp(bounds, [bounds[0], bounds[-1]], [0, 1]), color_list_rgb)]
    
    fig, (ax, ax1, ax2) = plt.subplots(1, 3, figsize=(10, 8))
    segm_cmap = mpl.colors.LinearSegmentedColormap.from_list("Segm_cmap", color_tuples, N=NUM_COLORS)
    norm = plt.Normalize(vmin=bounds[0], vmax=bounds[-1])
    img_data = np.linspace(YMIN, YMAX, NUM_COLORS).reshape(-1, 1)
    img_alpha = np.where(img_data <= 1200, 1, np.where(img_data <= 1500, 0.5, 0.1))
    ax.imshow(img_data, cmap=segm_cmap, norm=norm, interpolation="bilinear",
              origin="lower", extent=[XMIN, XMAX, YMIN, YMAX], alpha=img_alpha)
    ax.set_aspect((XMAX - XMIN) / (YMAX - YMIN) * RATIO)
    ax.set_title('opacity via y')
    ax.set_xticks([]) # remove x ticks
    
    ax1.imshow(img_data, cmap=segm_cmap, norm=norm, interpolation="bilinear",
               origin="lower", extent=[XMIN, XMAX, YMIN, YMAX])
    ax1.set_aspect((XMAX - XMIN) / (YMAX - YMIN) * RATIO)
    ax1.set_title('without opacity')
    ax1.set_xticks([])
    
    ax2.hlines(bounds, 0, 1, lw=5, clip_on=False, colors=color_list_rgb)
    ax2.set_xlim(XMIN, XMAX)
    ax2.set_ylim(YMIN, YMAX)
    ax2.set_aspect((XMAX - XMIN) / (YMAX - YMIN) * RATIO)
    ax2.set_title('given colors')
    for sp in ['top', 'bottom']:
        ax2.spines[sp].set_visible(False)  # hide upper and lower spine
    ax2.set_xticks([])
    
    plt.show()
    

    2D alpha in imshow()

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search