Skip to content

fig.set_dpi() does not set the dpi correctly #11227

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
ImportanceOfBeingErnest opened this issue May 11, 2018 · 23 comments
Closed

fig.set_dpi() does not set the dpi correctly #11227

ImportanceOfBeingErnest opened this issue May 11, 2018 · 23 comments

Comments

@ImportanceOfBeingErnest
Copy link
Member

ImportanceOfBeingErnest commented May 11, 2018

Bug report

Bug summary

Changing the figure's dpi via fig.set_dpi() does not have the desired effect. There are strange things happening when saving or showing the figure as detailed below.

Bug 1 - saving

In the following a figure with dpi=200 should be saved in all 3 cases.

import matplotlib.pyplot as plt

get_img_size = lambda path: print(plt.imread(path).shape)

fig1, ax1 = plt.subplots(dpi=200)
ax1.plot([1,2])
fig1.savefig("fig1.png")
get_img_size("fig1.png")

fig2, ax2 = plt.subplots(dpi=100)
ax2.plot([1,2])
fig2.savefig("fig2.png", dpi=200)
get_img_size("fig2.png")

fig3, ax3 = plt.subplots(dpi=100)
ax3.plot([1,2])
fig3.set_dpi(200)
fig3.savefig("fig3.png", dpi="figure")
get_img_size("fig3.png")

Qt5Agg

The outcome using the Qt5Agg backend of this is

(960, 1280, 4)
(960, 1280, 4)
(480, 640, 4)

i.e. in the first two cases we get the expected 200 dpi figure. In the last case, where the figure dpi is set via fig.set_dpi it seems to be ignored and the output image has a dpi of 100.

TkAgg

Using the TkAgg backend, the saved figures are as expected ((960, 1280, 4) in all cases.)

Bug 2 - showing

In the following we would expect to get two identical figures shown, both with dpi=100 and 640x480 pixels large.

plt.close("all")

fig1, ax1 = plt.subplots(num="expected", dpi=100)
ax1.plot([1,2])

fig3, ax3 = plt.subplots(num="buggy", dpi=50)
ax3.plot([1,2])
fig3.set_dpi(100)
print(fig3.get_dpi())
fig3.savefig("fig3a.png", dpi="figure")
print(fig3.get_dpi())
plt.show()
print(fig3.get_dpi())

Qt5Agg

The output (with Qt5Agg) is the following:

100
100
50

image

The printed dpi is correctly 100 before showing the figure. After showing, it prints 50. The shown figures have both the correct size, but the figure where dpi is set via fig.set_dpi has narrower linewidth and smaller fontsize. It looks exaclty like a figure that would have been expected for dpi=50, just with the wrong pixel size.

TkAgg

Using TkAgg backend, the output is correctly printed as 100 in all cases, but the shown figures differ in size; the left (expected) figure is 640x480 pixels, while the right figure is 640x445 pixels.

image

Matplotlib version

  • Operating system: Windows 8.1
  • Matplotlib version: master (2.2.2.post1088.dev0+g9ec4b95d6)
  • Matplotlib backend: Qt5Agg & TkAgg (see text)
  • Python version: 3.6.4

Running the same with matplotlib 2.0.2 (all other versions the same) I get
Qt5Agg, saving: (960, 1280, 4), (960, 1280, 4), (480, 640, 4) - same bug as above with master.
Qt5Agg, showing 100, 100, 100 - printed dpi are all the same, shown figures are the same.
TkAgg, saving: (960, 1280, 4) in all cases. - same expected behaviour as above.
TkAgg, showing 100, 100, 100 - printed dpi are all the same, shown figures differ in same manner as above.

@jklymak
Copy link
Member

jklymak commented May 11, 2018

yes exhibit A as to why setting the figure dpi before rendering make our lives a lot harder. I had similar bug reports 6 months ago.

@ImportanceOfBeingErnest
Copy link
Member Author

Apparently they still haven't been solved? Are they duplicates?

@tacaswell
Copy link
Member

Some of this is due to the high-dpi support for Qt which effectively renders the figure at double resolution for the screen (this is due to the way that GUI frameworks deal with high-dpi, they default to using a 2x2 pixel grid for your images unless you explicitly ask it to give you access to the screen resolution). To keep the figure the correct size on the screen and the fonts at the expected size. There are a number of some what fragile latches to keep track of the 'real' and 'screen' DPI and to cope with the user moving the window from a low to a high dpi screen etc. Some of these rely on callbacks from the Qt side to fire to work correctly (see the comments in https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/tests/test_backend_qt5.py#L94 ).

You are running these as python script.py? I suspect running these interactively in IPython would give different results (due to the Qt event loop running in the background).

The Tk one looks like a bug in the Tk window resize code (we need to set the window to be the size of the plot + the size of the toolbar, looks like we are missing the correction is the case of changing the DPI)

@ImportanceOfBeingErnest
Copy link
Member Author

I tested everything via python script.py. But I observe the same when running them in Spyder via the IPython Qt console (which is how I usually work with python); I suppose that would be the same as typing runfile(script.py) into IPyton. Does that qualify as "interactively" or do you mean something else?

@ImportanceOfBeingErnest
Copy link
Member Author

The high-res stuff might explain the problems of Bug 2 I suppose. But if there is no figure shown at all, none of this should take any effect, right?

@jklymak
Copy link
Member

jklymak commented May 11, 2018

For figure:

        dpi : [ *None* | scalar > 0 | 'figure' ]
            The resolution in dots per inch.  If *None*, defaults to
            :rc:`savefig.dpi`.  If 'figure', uses the figure's dpi value.

So I think if you don't specify the dpi it'll fall back to the default, so that is your first case. Why it does something different for tk is beyond me...

EDIT oops sorry I see you specified dpi=figure. Well that is mysterious.

@jklymak
Copy link
Member

jklymak commented May 11, 2018

For plt.show the question is what do we expect to see if you have set the figure dpi to something different than your screen resolution?

@ImportanceOfBeingErnest
Copy link
Member Author

Uh, so there is even another bug? Because the first two figures are identical. I thought this to be expected because I assumed the default to be "figure", but if it isn't then the first figure should have a dpi of 100 instead of 200.
But you may simply plug dpi="figure" into the first savefig command; the complete bug report stays valid.

@tacaswell
Copy link
Member

No, the first two are correct because for the first one you are setting the figure DPI to 200 (which will render to the screen at 400) and save at 200, in the second case you are telling savefig to use 200 DPI (which it is). The third case is exposing a race condition someplace between the figure state and the GUI state.

@ImportanceOfBeingErnest
Copy link
Member Author

What the heck does "screen resolution" mean here? I'm not measuring anything with a ruler on my screen. But just because you are asking, I have a 1920x1080 pixel monitor with a diagonal of 27 inch, hence my screen resolution is sqrt(1920^2+1080^2)/27. = 82 ppi.
Nice to know - but completely unrelated to the bug report.

@ImportanceOfBeingErnest
Copy link
Member Author

ImportanceOfBeingErnest commented May 11, 2018

figure DPI to 200 (which will render to the screen at 400) and save at 200

This does not make any sense to me at all. Where is the number 400 coming from? And why should it save at 200 dpi, if - as the documentation states - the default rcParam is used, which is 100 which is "figure"?

But as I said, feel free to replace the respective line by fig1.savefig("fig1.png", dpi="figure"), the complete bug report stays valid.

@jklymak
Copy link
Member

jklymak commented May 11, 2018

Again I ask. What is the point of setting the figure dpi before print time? It’s not documented so far as I can see

@jklymak
Copy link
Member

jklymak commented May 11, 2018

OK, so on hi-dpi my machine using Qt5Agg

import matplotlib
#matplotlib.use('tkagg')
import matplotlib.pyplot as plt

plt.close("all")

fig1, ax1 = plt.subplots(num="expected", dpi=100)
ax1.plot([1,2])

fig3, ax3 = plt.subplots(num="buggy", dpi=50)
fig3.set_dpi(100)
plt.show()

print(fig3.get_dpi())

prints 100 but yields the original 50 dpi

buggy_and_expected_and_1__python_checkdpi2_py__python3_6__and_2__tmux_-cc__zsh_

If I change the original dpi to 30:

fig3, ax3 = plt.subplots(num="buggy", dpi=30)

buggy_and_expected_and_1__python_checkdpi2_py__python3_6__and_tmux_dashboard

So it makes the window 50% the size of the default window in both cases, and then makes the lines thinner according to the original figure dpi.

If I do

fig3, ax3 = plt.subplots(num="buggy", dpi=30)
fig3.set_dpi(150)

I get the bigger window, but still at the fuzzier resolution.

buggy_and_expected_and_1__python_checkdpi2_py__python3_6__and_tmux_dashboard

@tacaswell
Copy link
Member

The high-dpi GUI frameworks need a double-resolution image for painting to the screen. It is rather annoying, but it is the way it is (you can dig up quite a few comments from me about a year ago complaining vociferously about this, but I have moved on to acceptance).

The default value of `savefig.dpi' is 'figure'.

kwargs.setdefault('dpi', rcParams['savefig.dpi'])

'savefig.dpi': ['figure', validate_dpi], # DPI

which eventually falls through to backend bases

if dpi == 'figure':
dpi = getattr(self.figure, '_original_dpi', self.figure.dpi)

to resolve the string to a number. Note that this looks for an optional _original_dpi attribute on the figure which is set

figure._original_dpi = figure.dpi

and similarly in webagg. However it is not updated when set_dpi is called which is likely the source of the bug.

@jklymak Either we set the dpi at the figure level and fold it into the transform stack or we would have to pass the dpi into every single backend draw method. We also use the dpi + figure size to sort out how big to make the gui windows.

@tacaswell tacaswell added this to the v3.0 milestone May 11, 2018
@jklymak
Copy link
Member

jklymak commented May 11, 2018

...glad you moved onto acceptance, I'm still in denial 😉 Thanks for bearing w/ me!

@jklymak
Copy link
Member

jklymak commented May 11, 2018

See #11232 for what I think is a reasonable fix that shouldn't break anything....

@QuLogic QuLogic modified the milestones: v3.4.0, v3.5.0 Jan 27, 2021
@QuLogic QuLogic modified the milestones: v3.5.0, v3.6.0 Sep 25, 2021
@QuLogic QuLogic modified the milestones: v3.6.0, v3.7.0 Jul 8, 2022
@BearBearCodes
Copy link

BearBearCodes commented Dec 6, 2022

I am experiencing a similar issue on matplotlib 3.5.2 with python 3.10.4 running on WSL2 with Ubuntu 20.04.5 LTS.

EDIT: I am using TkAgg as my backend


tl;dr:

  • fig.set_dpi(150) only changes the dpi of plot shown in the Jupyter notebook but not in the saved figure
  • figure.dpi in the matplotlibrc file does not work at all
  • plt.subplots(dpi=150) changes the dpi of both the plot shown in the Jupyter notebook as well as in the saved figure

More details:

When I run the following code in a Jupyter notebook running IPython 8.1.1,

fig, ax = plt.subplots()
fig.set_dpi(150)
ax.plot([1, 2], [1, 2])
print(fig.get_dpi())
fig.savefig("test.png", dpi="figure")
plt.show()

I find that the Jupyter preview window reflects the increased DPI but the saved figure does NOT respect the changed dpi (it is still at the default 72 dpi).

Moreover, I'm finding that on my machine, the figure.dpi rcParam is not respected after I have modified it in my matplotlibrc file (located at ~/.config/matplotlib/matplotlibrc). Other changes to options like axes.prop_cycle in my matplotlibrc file are being reflected in the plots shown in the Jupyter notebook as well as in those saved via fig.savefig(), but changes to the figure.dpi option are not reflected in the plots shown/saved. Note that if I instead set the dpi via plt.subplots(dpi=150), then the plots shown/saved both have a dpi of 150, as expected.

@tacaswell
Copy link
Member

@BearBearCodes Can you please check the value of mpl.rcParams['savefig.dpi'] ?

I do not understand what you mean by "jupyter preview window" and our default dpi has been 100 from mpl2.

Can you reproduce this issues with complete stand alone script (including imports) and no mplrcparams file?

@jklymak
Copy link
Member

jklymak commented Dec 6, 2022

Actually let's close this, since DPI handling in most of the backends has changed significantly. @BearBearCodes if you can open a new issue with all the required information, that would be more clear. Thanks!

@jklymak jklymak closed this as completed Dec 6, 2022
@BearBearCodes
Copy link

Hi @tacaswell, thanks for your quick response and apologies for the confusion.

By "jupyter preview window", I just mean the output you see under the Jupyter code cell after executing the code in that cell. A screenshot of what I mean is below.
image

Also as you can see in the screenshot, the mpl.rcParams["savefig.dpi"] option is set to figure and the figure's dpi is set to 72. This is with no matplotlibrc file.

Interestingly, if I run the exact same code seen in the screenshot above in a python file rather than in a Jupyter notebook, the default figure dpi is 100. But if I change the dpi to, e.g., 150 using the following code in a standalone python file

import matplotlib as mpl
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
fig.set_dpi(150)
ax.plot([1, 2], [1, 2])
print(fig.get_dpi())
print(mpl.rcParams["figure.dpi"])
print(mpl.rcParams["savefig.dpi"])
fig.savefig("test.png", dpi="figure")
plt.show()

then the saved figure is still at 100 dpi rather than 150.

@BearBearCodes
Copy link

Actually let's close this, since DPI handling in most of the backends has changed significantly. @BearBearCodes if you can open a new issue with all the required information, that would be more clear. Thanks!

Okay I will do that. Thank you!

@jklymak
Copy link
Member

jklymak commented Dec 6, 2022

Sorry for the crossed posts. Please also be careful to use the same python environment to start Jupyter and run the standalone scripts.

@BearBearCodes
Copy link

@jklymak yep I am using running both the Jupyter notebook and the standalone python script from a conda environment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants