Making figure transparent with colored background

后端 未结 3 1942
隐瞒了意图╮
隐瞒了意图╮ 2021-01-12 18:09

I have a bit of a situation. What I need is a plot with a black background with several white circles drawn on top of that black background.

I managed to do this usi

相关标签:
3条回答
  • 2021-01-12 18:35

    Circles masked with colorbar

    Colormaps can have an alpha channel, so if your data is on a mesh with high vs low values showing circle vs not-circle, one set of those values can be transparent.

    This only works for me when saving the figure programmatically, with the transparent keyword; not from the Python image window.

    Starting from one of the matplotlib gallery examples (in a gimp-alike, I can cut & paste segments and the transparency is right):

    # plot transparent circles with a black background
    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.colors import LinearSegmentedColormap
    from matplotlib.cm import Greys
    
    
    dark_low = ((0., 1., 1.),
             (.3, 1., 0.),
             (1., 0., 0.))
             
    cdict = {'red':  dark_low,
    
         'green': dark_low,
    
         'blue': dark_low}
    
    cdict3 = {'red':  dark_low,
    
         'green': dark_low,
    
         'blue': dark_low,
                   
         'alpha': ((0.0, 0.0, 0.0),
                   (0.3, 0.0, 1.0),
                   (1.0, 1.0, 1.0))
        }
    
    greys = LinearSegmentedColormap('Greys', cdict)
    plt.register_cmap(cmap=greys)
        
    dropout_high = LinearSegmentedColormap('Dropout', cdict3)
    plt.register_cmap(cmap = dropout_high)
    
    # Make some illustrative fake data:
    
    x = np.arange(0, np.pi, 0.1)
    y = np.arange(0, 2*np.pi, 0.1)
    X, Y = np.meshgrid(x,y)
    Z = np.cos(X) * np.sin(Y) * 10
    
    # Make the figure:
    
    plt.figure()
    plt.subplot(1,3,1)
    plt.imshow(Z, cmap=Greys)
    plt.title('Smooth\ncolorbar')
    plt.colorbar()
    plt.subplot(1,3,2)
    plt.imshow(Z, cmap=greys)
    plt.title('Linear\ncolorbar')
    plt.colorbar()
    plt.subplot(1,3,3)
    plt.imshow(Z, cmap = dropout_high)
    plt.title('Alpha crops\n colorbar')
    plt.colorbar()
    plt.savefig('dropout_cmap', transparent=True)
    

    Adapting colorbar to a alpha-channel visual mask

    And as a layer over another image. Interesting, the colorbar with alpha channel doesn't have transparency. That seems like a bug.

    Plots with and without alpha-channel over another image

    0 讨论(0)
  • 2021-01-12 18:50

    This might not be the answer you are looking for but it gives the picture you wanted! I think you want to fill the areas outside the circle!(s) with black and leave the background transparent, rather than the other way around. It's trivial to calculate the boundaries of a single circle and use fill_between. Doing it for multiple circles might be trickier!

    import numpy as np
    import matplotlib.pyplot as plt
    
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1, aspect = "equal", )
    
    # A Circle
    xy=(1,1); r=3
    
    # more points is smoother
    xdata=np.linspace(-5,5,1001)
    ydata=np.linspace(-5,5,1001)
    
    # circle edges (top and bottom)
    c1=np.sqrt((xy[0]**2-xdata**2)+r**2)+xy[1]
    c2=-np.sqrt((xy[0]**2-xdata**2)+r**2)+xy[1]
    
    c1=np.where(np.isnan(c1),xy[0],c1)
    c2=np.where(np.isnan(c2),xy[0],c2)
    
    ax.fill_between(xdata,5,c1,color='black')
    ax.fill_between(xdata,-5,c2,color='black')
    
    plt.xlim(-5, 5)
    plt.ylim(-5, 5)
    
    fig.savefig("test.png", dpi = 300, transparent=True)
    

    Transparent circle with center 1,1 and radius 3

    0 讨论(0)
  • 2021-01-12 18:53

    Edit 3:

    It has been clarified that the underlying question is:

    how to put a 'black & transparent' mask in front of a matplotlib image produced by imshow ? The mask shall result from a matplotlib previously drawn black & white figure.

    The following code demonstrate this feature by accessing and mixing the figure rgba bitmaps:

    import numpy as np
    import matplotlib.pyplot as plt
    
    import matplotlib.cm as cm
    import matplotlib.mlab as mlab
    
    
    def get_rgba_bitmap(fig):
        fig.canvas.draw()
        tab = fig.canvas.copy_from_bbox(fig.bbox).to_string_argb()
        ncols, nrows = fig.canvas.get_width_height()
        return np.fromstring(tab, dtype=np.uint8).reshape(nrows, ncols, 4)
    
    def black_white_to_black_transpa(rgba):
        rgba[:, :, 3] = 255 - rgba[:, :, 0]
        rgba[:, :, 0:3] = 0
    
    def over(rgba1, rgba2):
        if rgba1.shape != rgba2.shape:
            raise ValueError("rgba1 and rgba2 shall have same size")
        alpha = np.expand_dims(rgba1[:, :, 3] / 255., 3)
        rgba =  np.array(rgba1 * alpha + rgba2 * (1.-alpha), dtype = np.uint8)
        return rgba[:, :, 0:3]
    
    
    # fig 1)
    fig1 = plt.figure(facecolor = "white")
    fig1.set_dpi(300)
    ax1 = fig1.add_subplot(1, 1, 1, aspect = "equal", axisbg = "black")
    ax1.add_artist(plt.Circle((0., 0., .5), color =   "white")) 
    ax1.set_xlim(-5, 5)
    ax1.set_ylim(-5, 5)
    bitmap_rgba1 = get_rgba_bitmap(fig1)
    black_white_to_black_transpa(bitmap_rgba1)
    
    # fig 2
    fig2 = plt.figure(facecolor = "white")
    fig2.set_dpi(300)
    delta = 0.025
    ax2 = fig2.add_subplot(1, 1, 1, aspect = "equal", axisbg = "black")
    ax2.set_xlim(-5, 5)
    ax2.set_ylim(-5, 5)
    x = y = np.arange(-3.0, 3.0, delta)
    X, Y = np.meshgrid(x, y)
    Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
    Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
    Z = Z2-Z1  # difference of Gaussians
    im = ax2.imshow(Z, interpolation='bilinear', cmap=cm.jet,
                    origin='lower', extent=[-5, 5, -5, 5],
                    vmax=abs(Z).max(), vmin=-abs(Z).max())
    bitmap_rgba2 = get_rgba_bitmap(fig2)    
    
    # now saving the composed figure 
    fig = plt.figure()
    fig.patch.set_alpha(0.0) 
    ax = fig.add_axes([0., 0., 1., 1.])
    ax.patch.set_alpha(0.0) 
    ax.imshow(over(bitmap_rgba1, bitmap_rgba2))
    plt.axis('off')
    fig.savefig("test_transpa.png", dpi=300)
    
    plt.show()
    

    Giving: enter image description here

    I tested with your initial photonic test case and the pic quality seems OK

    enter image description here

    Now if you want the figure background transparent too:

    • Set fig1 background to 'white' i.e. fig1 = plt.figure(facecolor='white'), as white will become transparent when passed to black_white_to_black_transpa
    • Set fig2 background to transparent fig2.patch.set_alpha(0.0) as it will be stored with no modification into bitmap_rgba2
    • Finally, take care of the alpha channel when mixing bitmap_rgba1 and bitmap_rgba2 inside over function (see below a possible modification)
    def over(rgba1, rgba2):
        if rgba1.shape != rgba2.shape:
            raise ValueError("rgba1 and rgba2 shall have same size")
        alpha1 = np.expand_dims(rgba1[:, :, 3] / 255., axis=3)
        alpha2 = np.expand_dims(rgba2[:, :, 3] / 255., axis=3)
        alpha = 1. - (1.-alpha1) * (1.-alpha2)
        C1 = rgba1[:, :, 0:3]
        C2 = rgba2[:, :, 0:3]
        C = (alpha1 * C1 + (1-alpha1) * alpha2 * C2) / alpha
        rgba =  np.empty_like(rgba1, dtype = np.uint8)
        rgba[:, :, 0:3] = C
        rgba[:, :, 3] = 255 * alpha[:, :, 0]
        return rgba
    

    last (?) edit: It seems there is an inconsistence between the array returned byto_string_argb and the one expected by imshow (order of the rgb channels). A possible solution is to change ax.imshow(over(bitmap_rgba1, bitmap_rgba2)) to:

    over_tab = over(bitmap_rgba1, bitmap_rgba2)
    over_tab[:, :, 0:3] = over_tab[:, :, ::-1][:, :, 1:4]
    ax.imshow(over_tab)
    
    0 讨论(0)
提交回复
热议问题