Вопрос

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 using the following code:

import numpy
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, aspect = "equal", axisbg = "black")
ax.add_artist(plt.Circle((0., 0., .5), color =   "white")) 

plt.xlim(-5, 5)
plt.ylim(-5, 5)

fig.savefig("test.png", dpi = 300)

plt.show()

This produces the following result:

enter image description here

Now, what I would like to do is make this image transparent. So what this means is that only the white circle should become transparent. You might already be able to see the problem arising because if I would set transparent = True. The black background automatically becomes transparent and I lose the colour black from my figure.

Another thing I tried is to not set transparent = True in savefig but to actually set the option alpha = 0. in plt.Circle. This makes the white circle actually transparent which is the end goal. However, because it is transparent I am left with an entire black background. Any ideas to solve this problem?

To summarize my goal:

I want to save a transparent version of the figure in which the white circle is transparent while the black parts are not.

I know I can use different programs such as inkscape and gimp to create what I want. However, I really need to do it within python as well due to other operations I need to perform.

Thank you!

Это было полезно?

Решение

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)

Другие советы

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top