Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
PGF Backend: Support interpolation='none'#6792
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
f0k commentedJul 18, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Re-added the skewing test to the demo and two more transformations. Images and dashed rectangle targets match up nicely for all transforms both in the PDF and PGF backend. Ready to merge from my side. |
Shouldn't a Actually, I rather prefer your example to what I wrote in#6673 :). Small remark: it seems that you could remove l. 33 Besides, it looks like the weird offset between the image and extent rectangle (see#6673) is still present when I plot the new version of the example: @tacaswell Should I open a dedicated issue for the latter odd behavior, as it tends to deviate from the original PR? |
f0k commentedJul 19, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
I don't know, it was removed by@mdboom in1384c7c. You tell me :)
Oh, didn't notice. Will amend my commit.
That's because the rectangle lines are centered on the left and bottom pixel boundaries rather than the pixel centers. This is consistent within matplotlib. The way the image extents are currently chosen (
I think red doesn't look nice with the new default colormap, that's why I changed it from blue (the new default color for the plot) to yellow. I'd be fine either way. |
Amended my comment to remove the unused |
@f0k Thank you for amending the example! I will then close my redundant PR#6673. About the red color: I only used it for me. I agree that blue was a bit difficult to see, and if you prefer to replace it with yellow, it's fine for me. About the offset between the extent rectangle and the image, if it's normal behavior, then everything is alright (I was just scared it might be a small bug), and indeed it's unclear to me that it's really worth the hassle to offset the rectangle by half pixel. For the PGF backend part, I apologize but I don't know enough about backends to review it. |
I retracted this, I'm not sure it should be this way, but I guess the problem lies outside the demo code. It's not the business of our PRs then.
I liked the outline showing the rotation, we could think about adding this here as well (does it make sense for the other transformations?). But probably this should still be a separate PR -- I extended the demo merely to check whether the modified PGF backend works correctly, not to make the demo more instructive.
Let's try to summon@pwuertz for this! (He's the original author of the PGF backend, not sure how much he's still involved in matplotlib.) |
Ok, reviewed the changes to the PGF backend. One small detail (just an opinion, feel free to ignore it if you don't agree): My main concern before was the half-pixel/pixel alignment, but if your extended demo indeed shows that this is a matplotlib-wide issue and not a backend-to-backend inconsistency it shouldn't hold up a merge of this PR. Better alignment is a job for another day then. In conclusion: Nice work! Thanks for tracking down the details for making this possible. |
This was not done for cleverness. If we remove lines 647--650, the translation is applied before the affine transform. This was correct for matplotlib 1.5, but is wrong for matplotlib 2.0. Integrating it in the transformation seemed easier than moving the
It's definitely consistent between backends. |
f0k commentedJul 19, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@pwuertz: Moved I can merge this into the second commit before merging if needed, I just separated it for now so@pwuertz can have a look. |
pwuertz commentedJul 19, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Ok, the code looks really good now. Unfortunately the new image handling is causing trouble on my machine. When I save the demo figure to PDF using the modified backend_pgf the images are missing completely (Xelatex from Ubuntu 16.04 Texlive). How did you set up your tests so far? Pdf, xe, lualatex? Version? Os? Tex distribution? |
f0k commentedJul 19, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Booh. Thanks for testing!
Ubuntu 14.04, pdflatex ( |
I'm seeing good results with pdflatex and lualatex, only xelatex fails. Your initial version with the translation merged into the matrix works however. Might be a bug in xelatex, but I guess it's not worth figuring it out and instead use your first approach again. |
@f0k A doc PR to write down your hard won (sorry) understanding of how the image handling works would be appreciated! |
pwuertz commentedJul 20, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@f0k This block here is based on your last commit and works with all 3 texsystems. # reference the image in the pgf picturewriteln(self.fh,r"\begin{pgfscope}")self._print_pgf_clip(gc)f=1./self.dpi# from display coords to inchwriteln(self.fh,r"\pgfsys@transformshift{%fin}{%fin}"% (x*f,y*f))iftransformisnotNone:tr1,tr2,tr3,tr4,tr5,tr6=transform.frozen().to_values()w=h=1./f# scale is already included in the transformwriteln(self.fh,r"\pgfsys@transformcm{%f}{%f}{%f}{%f}{%fin}{%fin}"% (tr1*f,tr2*f,tr3*f,tr4*f,tr5*f,tr6*f))interp=str(transformisNone).lower()# interpolation in PDF readerwriteln(self.fh,r"\pgftext[left,bottom]{\pgfimage[interpolate=%s,width=%fin,height=%fin]{%s}}"% (interp,w*f,h*f,fname_img))writeln(self.fh,r"\end{pgfscope}") |
f0k commentedJul 20, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Great, thanks for figuring it out! Shall we really use both transformshift and transformcm at the same time, or change to: # reference the image in the pgf picturewriteln(self.fh,r"\begin{pgfscope}")self._print_pgf_clip(gc)f=1./self.dpi# from display coords to inchiftransformisNone:writeln(self.fh,r"\pgfsys@transformshift{%fin}{%fin}"% (x*f,y*f))w,h=w*f,h*felse:tr1,tr2,tr3,tr4,tr5,tr6=transform.frozen().to_values()writeln(self.fh,r"\pgfsys@transformcm{%f}{%f}{%f}{%f}{%fin}{%fin}"% (tr1*f,tr2*f,tr3*f,tr4*f, (tr5+x)*f, (tr6+y)*f))w=h=1# scale is already included in the transforminterp=str(transformisNone).lower()# interpolation in PDF readerwriteln(self.fh,r"\pgftext[left,bottom]{\pgfimage[interpolate=%s,width=%fin,height=%fin]{%s}}"% (interp,w,h,fname_img))writeln(self.fh,r"\end{pgfscope}") For one, this will use either transformshift or transformcm, and secondly, it uses the case distinction to set the image height and width directly to
My knowledge is still incomplete... the PDF backend has: defget_image_magnification(self):returnself.image_dpi/72.0 This probably changes the units of the translation and the transform, or maybe just one of them. I can see if I can improve the base class docstrings with what I know, though. |
@f0k Agreed |
Amended the commits to use my latest proposed code (which uses the
Actually, the docs were not that bad. Part of the problem was that I had started with matplotlib 1.5.2, which had a different API and less documentation. |
If you want you can also take down the #TODO note in |
Did so. Wasn't sure whether the |
""" | ||
Drawthe image instance into the current axes; | ||
Drawan RGBA image. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Explanation: It's not an "image instance", and the renderer does not have any notion of axes (as far as I can see).draw_image()
is used both for axes and figure images.
f0k commentedJul 21, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Travis throws an error for Python 3.4, PYTHON_ARGS=-OO now:
Is this anything that could possibly have been caused by this PR, specifically by the last commit which changed the docstring and one argument name?Everything passed fine before. Shall we just restart Travis and hope for the best? |
@@ -508,30 +508,31 @@ def get_image_magnification(self): | |||
""" | |||
return 1.0 | |||
def draw_image(self, gc, x, y, im,trans=None): | |||
def draw_image(self, gc, x, y, im,transform=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Please do not change kwarg name, it is a (subtle) api break for anyone who is using**dd
to pass in kwargs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The point is that if anybody passestrans=
as a kwarg, it is already broken:
$ grep -re 'def draw_image' lib/matplotliblib/matplotlib/backends/backend_pgf.py: def draw_image(self, gc, x, y, im, transform=None):lib/matplotlib/backends/backend_template.py: def draw_image(self, gc, x, y, im):lib/matplotlib/backends/backend_ps.py: def draw_image(self, gc, x, y, im, transform=None):lib/matplotlib/backends/backend_svg.py: def draw_image(self, gc, x, y, im, transform=None):lib/matplotlib/backends/backend_gdk.py: def draw_image(self, gc, x, y, im):lib/matplotlib/backends/backend_pdf.py: def draw_image(self, gc, x, y, im, transform=None):lib/matplotlib/backends/backend_wx.py: def draw_image(self, gc, x, y, im):lib/matplotlib/backends/backend_cairo.py: def draw_image(self, gc, x, y, im):lib/matplotlib/backend_bases.py: def draw_image(self, gc, x, y, im, trans=None):
The base class isthe only one calling ittrans
, which leaves a trap for anybody using**dd
to pass in the keyword arguments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
That is fun.
attn@mdboom I am inclined to accept this change and an API change entry for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Agreed. Let's fix this to be consistent (transform
everywhere) and add a note inapi_changes.rst
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
and add a note in
api_changes.rst
.
If I see correctly, it's already covered byhttps://github.com/matplotlib/matplotlib/blob/master/doc/api/api_changes/2015-12-30_draw_image.rst -- if I added another note, there would be two API changes fordraw_image()
listed for matplotlib 2.0. So shall we just leave it at that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
eh, yeah there is an entry for it, but just amend it to note this particular issue, as it isn't obvious from the current item that there is this change as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Well, from the current item it isn't obvious at all what has changed:
The draw_image method implemented by backends has changed its interface.
This change is only relevant if the backend declares that it is able to transform images by returning True from option_scale_image. See the draw_image docstring for more information.
Both sentences apply to my PR as well, which just changes the changed interface. Sorry, I'm not trying to be a nuisance, but I cannot come up with a way that explicitly mentions my change without sounding silly (and confusing).
Also note that the interface change in that note belongs to#5718, which added thetrans
keyword in the first place, in an attempt to document the interface already present in the subclasses:
https://github.com/matplotlib/matplotlib/pull/5718/files#diff-504c288d675954e4e24de83a19242f4dR518
I'm just changing this to correctly call ittransform
, which has been used in subclasses even before#5718 was proposed:https://github.com/matplotlib/matplotlib/pull/5718/files#diff-2623790aa8493ba82ef737fe0e63274dL1601
So this is not actually an API change. Nobody can have usedtrans=
as a keyword argument, because it wouldn't have worked.
Is this convincing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Ah, I see your point abouttrans
. So, you are merely correcting a mistake before it reaches a release. Point taken.
@tacaswell,@mdboom, let me know if anything else is needed. The Travis failure seems unrelated. |
:meth:`option_scale_image` returns ``True``, an affine | ||
transformation *may* be passed to :meth:`draw_image`. It takes the | ||
form of a :class:`~matplotlib.transforms.Affine2DBase` instance, | ||
with translation given in pixels. Note that this transformation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I don't really understand the "translation given in pixels" part. The transform can include more than just translation. Maybe just say the transformation is applied in physical units?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The transform can include more than just translation.
Yes, but the translation is the only one that has units, everything else is unitless.I wanted to document that the translation part of the affine transform is given in pixels, not in inches.
Maybe just say the transformation is applied in physical units?
I wouldn't know what that meant. But I'd be fine with a different formulation if mine isn't clear. We should just make sure people know what to do with the transformation and thedpi
factor instead of randomly trying to multiply or divide things until the demo image matches (that's about what I had to do).
Please let me know if and how to change this part!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I am not really qualified to judge which explanation is better or more correct.@mdboom basically wrote the entire transforms system, so I have to defer to him. I do think he is right that "pixels" is the incorrect word here, and also that saying "translation" is misleading because there could be other transformations at play here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I do think he is right that "pixels" is the incorrect word here
The unit used for the translation is whatever you get when you divide inches by the givendpi
value. I thought this would be pixels, but I don't know the terms used in matplotlib -- let me know what you'd put here.
"translation" is misleading because there could be other transformations at play here
I see, it's ambiguous. I can write "the translation coordinates of which are given in <...>." to make clear the affine transformation does not only consist of a translation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I think the point here is that internally mpl has 4 coordinate systems (data, axes, figure, display) and this transform needs to be adisplay -> display
transform. In the vector backends the display units are 'points' and in the raster backends the display units are 'pixels'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
no, you get "dots", which is the "physical unit", I think (again, this part of mpl isn't my expertise, so I could be wrong here).
As for "translation" business, I really don't know (not my area of expertise).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Ah, while starting to work on this, I found where I got thepixels
from: From the description of parameterx
, which is an additional translation given todraw_image()
and uses the same units as the translation coordinates oftransform
.https://github.com/matplotlib/matplotlib/blame/22aa800/lib/matplotlib/backend_bases.py#L519
As you all seem to agree that "pixels" has always been the wrong word, I'll change both.
Looks good aside from my comments above. |
Anything left do be done? It's good to merge from my perspective. |
Did you address@mdboom's comments? I don't see any updates to api_changes.rst. |
f0k commentedAug 25, 2016 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
I commented on the comment:#6792 (comment) |
@f0k Thanks for bearing with us while we wordsmith these details. |
I've changedall three occurrences of
Thank you. While I appreciate paying attention to details, it's certainly not very motivating to iterate that long on documenting a single method. It would have helped if somebody more familiar with the internals had taken the time to make a well-informed and consistent suggestion for the docstring. Anyway, I'm thankful matplotlib exists, is in active development and accepts contributions! |
🎉 I have restarted the one failed tests (looks like one of the transient ones) and will merge as soon as that comes back green. @f0k Thanks for digging into a subtle corner of the code base. Hopefully we will hear from you again! |
ENH: PGF Backend: Support interpolation='none'
tacaswell commentedAug 27, 2016 • edited by QuLogic
Loading Uh oh!
There was an error while loading.Please reload this page.
edited by QuLogic
Uh oh!
There was an error while loading.Please reload this page.
backported to v2.x ase7d7a49 |
Great, thanks for merging! 👍 |
As discussed in#6740, the PGF backend currently doesn't support
interpolation='none'
, which means all raster graphics are interpolated prior to saving them. This adds full support for affine image transforms.In the old backend, it took me 10 minutes to implement this from what I already had in#6740:https://gist.github.com/f0k/f8c87ffd82488c2ed303989592bf4ebd/eeaa54c8b98f84fd2f80caf4305034fc1eeeb76f#file-backend_mypgf-py-L61-L62
For the new backend, things seem to have changed quite a bit, but documentation is still lacking. It took me an hour of fiddling around. Now the code produces the same result as the PDF backend forthe affine transform demo in itscurrent version, which does not include the skewing test any more.
Closes#6740.