Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Transforms of BoundingBox objects wrong (when backend uses dpi != 72) #16641

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
griai opened this issue Mar 3, 2020 · 5 comments
Closed

Transforms of BoundingBox objects wrong (when backend uses dpi != 72) #16641

griai opened this issue Mar 3, 2020 · 5 comments
Labels
backend: pdf status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. status: confirmed bug status: inactive Marked by the “Stale” Github Action topic: transforms and scales

Comments

@griai
Copy link

griai commented Mar 3, 2020

Bug report

Bug summary

When transforming BoundingBox objects their width and height are calculated wrong. This happens, supposedly, for backends that have a different dpi setting from 72. The issue is present, e.g., for the backends "svg", "pdf", "cairo" (pdf, ps, svg). It is not present, e.g., for the backends "ps", "png".

Code for reproduction

# Plot BoundingBox of Text object with PS backend

import matplotlib as mpl
mpl.use("PS")

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()
ax.plot([0, 1], [1, 0], "bo", ms=20)

text = ax.text(x=0.3, y=0.1, s="sample text", fontsize=20, family="Serif")

fig.savefig("bbox.ps")

bbox_display = text.get_window_extent()

bbox_data = bbox_display.inverse_transformed(ax.transData)
bbox_data_2 = ax.transData.inverted().transform_bbox(bbox_display)

print(f"bbox_display = {bbox_display}")
print(f"    width = {bbox_display.width}    height = {bbox_display.height}")
print(f"bbox_data = {bbox_data}")
print(f"bbox_data = {bbox_data_2}")
print(f"    width = {bbox_data.width}    height = {bbox_data.height}")

rect_data = mpl.patches.Rectangle(
    (bbox_data.x0, bbox_data.y0), bbox_data.width, bbox_data.height,
    fill=False, lw=0.2, color="g")
ax.add_patch(rect_data)

fig.savefig("bbox.ps")
# Try the same with PDF backend

import matplotlib as mpl
mpl.use("PDF")

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()
ax.plot([0, 1], [1, 0], "bo", ms=20)

text = ax.text(x=0.3, y=0.1, s="sample text", fontsize=20, family="Serif")

fig.savefig("bbox.pdf")

bbox_display = text.get_window_extent()

bbox_data = bbox_display.inverse_transformed(ax.transData)
bbox_data_2 = ax.transData.inverted().transform_bbox(bbox_display)

factor = 100 / 72

print(f"bbox_display = {bbox_display}")
print(f"    width = {bbox_display.width}    height = {bbox_display.height}")
print(f"bbox_data = {bbox_data}")
print(f"bbox_data = {bbox_data_2}")
print(f"    width = {bbox_data.width}    height = {bbox_data.height}")
print(f"   ~width = {bbox_data.width * factor}   ~height = {bbox_data.height * factor}")

rect_data = mpl.patches.Rectangle(
    (bbox_data.x0, bbox_data.y0), bbox_data.width, bbox_data.height,
    fill=False, lw=0.2, color="r")
ax.add_patch(rect_data)

rect_data_corrected = mpl.patches.Rectangle(
    (bbox_data.x0, bbox_data.y0), bbox_data.width * factor, bbox_data.height * factor,
    fill=False, lw=0.2, color="g", ls="--")
ax.add_patch(rect_data_corrected)

fig.savefig("bbox.pdf")

Actual outcome

PS output

bbox_ps

bbox_display = Bbox(x0=171.22909090909093, y0=70.14774999999999, x1=288.9634659090909, y1=89.50712499999999)
    width = 117.734375    height = 19.359375
bbox_data = Bbox(x0=0.30000000000000004, y0=0.08281973379629631, x1=0.6626450842853944, y1=0.1628436053240741)
bbox_data = Bbox(x0=0.30000000000000004, y0=0.08281973379629631, x1=0.6626450842853944, y1=0.1628436053240741)
    width = 0.3626450842853943    height = 0.08002387152777779

PDF output

bbox_pdf

bbox_display = Bbox(x0=237.8181818181818, y0=99.04374999999999, x1=355.5525568181818, y1=118.40312499999999)
    width = 117.734375    height = 19.359375
bbox_data = Bbox(x0=0.30000000000000004, y0=0.0876302083333334, x1=0.5611044606854839, y1=0.1452473958333334)
bbox_data = Bbox(x0=0.30000000000000004, y0=0.0876302083333334, x1=0.5611044606854839, y1=0.1452473958333334)
    width = 0.2611044606854839    height = 0.0576171875
   ~width = 0.36264508428539427   ~height = 0.08002387152777778

Expected outcome

I expect that the BoundingBoxes for both used backends have the same width and height in Axes coordinates. However, this is not the case. The BoundingBox in the PDF version can be transformed to the correct size via multiplication of its width and height by the factor 100 / 72. Therefore, I assume that the default dpi of the backends plays a role here and is not handled properly.

Matplotlib version

  • Operating system: Ubuntu 19.04
  • Matplotlib version: 3.1.1
  • Matplotlib backends: PDF (but several more are affected)
  • Python version: 3.7.4
  • Jupyter version (if applicable): 1.0.0
  • Other libraries:

Matplotlib was installed via conda.

Possibly related issues

@griai
Copy link
Author

griai commented Mar 3, 2020

One possible workaround (for the pdf backend) is to explicitly set the dpi of the figure, i.e., put this line of code directly after the figure creation with the plt.subplots() call:

fig.dpi = 72

So, this workaround confirms that the fig.dpi setting is not handled properly in the transformation of the BoundingBox.

Note that this has nothing to do with the keyword argument dpi in the call to text.get_window_extent(). Specifying this with different values changes the anchor point of the BoundingBox, thus shifting it alltogether. But we need to only change width and height.

Note further that changing the fig.dpi changes width and height of the BoundingBox for both, the PS and the PDF backend. But it does not do so for the AGG backend and PNG output. I think, I'm still missing something ...

@timhoffm
Copy link
Member

timhoffm commented Mar 4, 2020

Thanks for the clear and detailed report.

@ImportanceOfBeingErnest
Copy link
Member

I think the real problem here is that you cannot get the extention of a text without rendering it first; but rendering requires a DPI, even for backends that are pixel-independent in their output.
As you have seen you need to actually save the image first, in order to be able to use .get_window_extent(), because that requires a renderer. So with that renderer you get the bounding box of the text in pixel space. And pixel space for the pdf backend is always defined via a dpi of 72.

A workaround which avoids the dpi coming out of sync would be to use the agg backend throughout and only saving the figure via the respective vector backend:

import matplotlib as mpl
mpl.use("agg")
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot([0, 1], [1, 0], "bo", ms=20)

text = ax.text(x=0.3, y=0.1, s="sample text", fontsize=20, family="Serif")

fig.canvas.draw()
bbox_data = text.get_window_extent().inverse_transformed(ax.transData)

rect_data = mpl.patches.Rectangle(
    (bbox_data.x0, bbox_data.y0), bbox_data.width, bbox_data.height,
    fill=False, lw=1, color="r")
ax.add_patch(rect_data)

fig.savefig("bbox.png")
fig.savefig("bbox.pdf")
fig.savefig("bbox.ps")

@QuLogic
Copy link
Member

QuLogic commented Mar 19, 2020

I'm not sure what you're trying to do here, but if this is strictly what you're looking for (and not just some simplified case), a simpler workaround is to use the bbox parameter. That is:

import matplotlib as mpl
mpl.use("PDF")  # noqa

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()
ax.plot([0, 1], [1, 0], "bo", ms=20)

text = ax.text(x=0.3, y=0.1, s="sample text", fontsize=20, family="Serif",
               bbox={"fill": False, "lw": 0.2, "color": "r"})

fig.savefig("bbox2.pdf")

bbox2

The default boxstyle is square,pad=0.3 so if you want it flush, then pass a style with an explicit padding:

text = ax.text(x=0.3, y=0.1, s="sample text", fontsize=20, family="Serif",
               bbox={"fill": False, "lw": 0.2, "color": "r",
                     "boxstyle": "square,pad=0"})

bbox3

@github-actions
Copy link

This issue has been marked "inactive" because it has been 365 days since the last comment. If this issue is still present in recent Matplotlib releases, or the feature request is still wanted, please leave a comment and this label will be removed. If there are no updates in another 30 days, this issue will be automatically closed, but you are free to re-open or create a new issue if needed. We value issue reports, and this procedure is meant to help us resurface and prioritize issues that have not been addressed yet, not make them disappear. Thanks for your help!

@github-actions github-actions bot added the status: inactive Marked by the “Stale” Github Action label Jul 10, 2023
@github-actions github-actions bot added the status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. label Aug 11, 2023
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Aug 11, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend: pdf status: closed as inactive Issues closed by the "Stale" Github Action. Please comment on any you think should still be open. status: confirmed bug status: inactive Marked by the “Stale” Github Action topic: transforms and scales
Projects
None yet
Development

No branches or pull requests

4 participants