Reputation: 5384
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
):
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)?
Upvotes: 2
Views: 2593
Reputation: 5384
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
Upvotes: 1