2

The actual problem that I have, is that - in the code I'm working on: when I try to save the plot image, I get the plot background in the image as fully transparent, so it looks like this in an image viewer (here eom, Mate's version of eog):

transparent background

I would like to have the background color to be fully opaque white, instead.

This seems to have been a problem often, e.g.:

Anyways, one problem is that I cannot reproduce this with a minimal example. In my code, essentially I have to convert the figure to an image first, then process that image, then finally save it. So I tried making a minimal example, with a conversion to image data first, which is then being saved:

import matplotlib as mpl
print(f"{mpl.__version__}")
import matplotlib.pyplot as plt
import numpy as np
import io

t = np.arange(0.0, 2.0, 0.01)
s = np.sin(2*np.pi*t)

subplotpars = dict(left = 0.05, right=0.99, top=0.89, wspace=0.1)
gss = mpl.gridspec.GridSpec(2,1, height_ratios=(2, 1), **subplotpars),

fig = plt.figure()
gs = gss[0]
ax = fig.add_subplot(gs[0,0])

ax.plot(t, s, color=(0.4, 0.2, 0.6, 0.6))

print(f"{fig.get_facecolor()=}, {fig.get_alpha()=}")
print(f"{fig.patch.get_facecolor()=}, {fig.patch.get_alpha()=}")

with io.BytesIO() as buf:
    fig.savefig(buf, dpi=120, format="png")
    buf.seek(0)
    fig_rgba = plt.imread(buf)

plt.imsave('example_io.png', fig_rgba, dpi=120)

... and this code prints this for me:

3.5.1
fig.get_facecolor()=(1.0, 1.0, 1.0, 1.0), fig.get_alpha()=None
fig.patch.get_facecolor()=(1.0, 1.0, 1.0, 1.0), fig.patch.get_alpha()=None

... and tells me that I have matplotlib 3.5.1, and that the figure background color is defined as RGBA, although with alpha channel set to 1 (opaque). Ultimately, the image that is output by this code, for me, is with fully opaque white background - so this code does not demonstrate the problem.

So, while I cannot reconstruct my problem as a minimal example - I managed to narrow down the problem in my actual code, in this snippet:

        # ... time to save as image:

        print(f"A {self.fig.get_facecolor()=}, {self.fig.get_alpha()=}")
        print(f"A {self.fig.patch.get_facecolor()=}, {self.fig.patch.get_alpha()=}")

        orig_figcol = self.fig.get_facecolor()
        orig_figpatchcol = self.fig.patch.get_facecolor()
        self.fig.set_facecolor( (1.0, 1.0, 1.0, 1.0) ) # calls patch.set_facecolor anyway
        self.fig.patch.set_facecolor( (1.0, 1.0, 1.0, 1.0) )
        self.fig.canvas.draw()

        print(f"B {self.fig.get_facecolor()=}, {self.fig.get_alpha()=}")
        print(f"B {self.fig.patch.get_facecolor()=}, {self.fig.patch.get_alpha()=}")

        with io.BytesIO() as buf:
        # ...

When this section in the code is ran, I get a printout:

A self.fig.get_facecolor()=(1.0, 1.0, 1.0, 0), self.fig.get_alpha()=None
A self.fig.patch.get_facecolor()=(1.0, 1.0, 1.0, 0), self.fig.patch.get_alpha()=0
B self.fig.get_facecolor()=(1.0, 1.0, 1.0, 0), self.fig.get_alpha()=None
B self.fig.patch.get_facecolor()=(1.0, 1.0, 1.0, 0), self.fig.patch.get_alpha()=0

So, unlike the basic example above, the starting background color of the plot, as RGBA, is (1.0, 1.0, 1.0, 0) - that is to say, alpha channel is zero (so no surprise the background is transparent).

However, I do call set_facecolor right after, with an RGBA color where alpha is 1, and I even call .draw(), to hopefully trigger a refresh/redraw -- but regardless, when try to get the same facecolor I just previously set, it still returns the old color value, where alpha was 0!

So, essentially, the way I see this is: something does not let me change the Figure background color, right before I want to save the figure as an image.

Under what circumstances would matplotlib not let me change the image background - and ultimately, how can I change the plot figure background color, right before I save the figure as image (or export it as image data, that is, pixels)?

3
  • 2
    See Matplotlib figure facecolor (background color). Matplotlib deliberately looks at the background color set in savefig, ignoring the figure's color. The suggested workaround is fig.savefig('whatever.png', facecolor=fig.get_facecolor(), edgecolor='none') Commented Feb 6, 2022 at 19:07
  • Thanks, @JohanC - as the post shows, I cannot change facecolor of fig to a color that has alpha=1, so entering facecolor=fig.get_facecolor() will not do much. Just now I also tried explicitly fig.savefig('whatever.png', facecolor=(1.0, 1.0, 1.0, 1.0)... - but that did not work either. Commented Feb 6, 2022 at 19:17
  • Thanks @JohanC - what I mean with "...but that did not work either" was that I got an image with transparent background again, even if I explicitly set an RGBA color of (1,1,1,1) which has an alpha of 1 (which should create an opaque background, not transparent). "Are you sure the resulting png file had a transparent background" - yes, I see the checkered background as in the OP image. " Which backend are you using" - for me mpl.get_backend() says 'TkAgg'. Commented Feb 6, 2022 at 20:11

1 Answer 1

1

Ok, found the solution - this required some debugging and drilling down of code; it turns out, one of the forest of libraries I include in my code, decided to change matplotlib figure properties - so here is a bit of a writeup:

So, after my attempt to use pdb to debug this failed (Inspecting a variable value in other stack frames in pdb?), I found How do you watch a variable in pdb - which recommended the watchpoints library.

So basically, after my figure init (self.fig = plt.figure(...), I did:

watch(self.fig.patch._facecolor)

... and ran the code; and finally I got something like this in the terminal printout:

====== Watchpoints Triggered ======
Call Stack (most recent call last):
...
>   figure.patch.set_alpha(0)
  set_alpha (~/venv/lib/python3.8/site-packages/matplotlib/patches.py:394):
>   self._set_facecolor(self._original_facecolor)
  to_rgba (~/venv/lib/python3.8/site-packages/matplotlib/colors.py:192):
>   return rgba
self.fig.patch._facecolor:
(1.0, 1.0, 1.0, 1.0)
->
(1.0, 1.0, 1.0, 0)

So, one of these libraries ended up calling figure.patch.set_alpha(0) - which turns out to be a bit nastier than one would deduce from the naming alone; namely, once it is set to 0 - it will prohibit entering any other alpha value, whenever you want to set the face color!!! Here is a code that reproduces that, based on the OP code:

import matplotlib as mpl
print(f"{mpl.__version__}")
import matplotlib.pyplot as plt
import numpy as np
import io

t = np.arange(0.0, 2.0, 0.01)
s = np.sin(2*np.pi*t)

subplotpars = dict(left = 0.05, right=0.99, top=0.89, wspace=0.1)
gss = mpl.gridspec.GridSpec(2,1, height_ratios=(2, 1), **subplotpars),

fig = plt.figure()
gs = gss[0]
ax = fig.add_subplot(gs[0,0])

ax.plot(t, s, color=(0.4, 0.2, 0.6, 0.6))

print(f"A {fig.get_facecolor()=}, {fig.get_alpha()=}")
print(f"A {fig.patch.get_facecolor()=}, {fig.patch.get_alpha()=}")

fig.patch.set_alpha(0)

print(f"B {fig.get_facecolor()=}, {fig.get_alpha()=}")
print(f"B {fig.patch.get_facecolor()=}, {fig.patch.get_alpha()=}")

fig.patch.set_facecolor( (0.9, 0.9, 0.9, 1.0) )

print(f"C {fig.get_facecolor()=}, {fig.get_alpha()=}")
print(f"C {fig.patch.get_facecolor()=}, {fig.patch.get_alpha()=}")

with io.BytesIO() as buf:
    fig.savefig(buf, dpi=120, format="png", facecolor=(1.0, 1.0, 1.0, 1.0))
    buf.seek(0)
    fig_rgba = plt.imread(buf)

plt.imsave('example_io.png', fig_rgba, dpi=120)

This prints:

3.5.1
A fig.get_facecolor()=(1.0, 1.0, 1.0, 1.0), fig.get_alpha()=None
A fig.patch.get_facecolor()=(1.0, 1.0, 1.0, 1.0), fig.patch.get_alpha()=None
B fig.get_facecolor()=(1.0, 1.0, 1.0, 0), fig.get_alpha()=None
B fig.patch.get_facecolor()=(1.0, 1.0, 1.0, 0), fig.patch.get_alpha()=0
C fig.get_facecolor()=(0.9, 0.9, 0.9, 0), fig.get_alpha()=None
C fig.patch.get_facecolor()=(0.9, 0.9, 0.9, 0), fig.patch.get_alpha()=0

So - with fig.patch.set_alpha(0) having ran once, even if I set fig.patch.set_facecolor( (0.9, 0.9, 0.9, 1.0) ) afterward (which explicitly sets alpha to 0) - the actual fill color that ends up in the object, still has alpha=0 (as printout C shows)!

So if you run that code, you still get transparent background in the image.

The solution turns out to be - just set fig.patch.set_alpha(1.0) right before the fig.savefig call:

# ...
with io.BytesIO() as buf:
    fig.patch.set_alpha(1.0)
    fig.savefig(buf, dpi=120, format="png", facecolor=(1.0, 1.0, 1.0, 1.0))
    # ...

Boy.... Now those were some wasted hours of my life... I wish someone would invent some machines, that you could program ... oh wait

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.