Skip to content

Changed Bbox.transformed to support more general affine transformations. #27152

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

inferentialist
Copy link

@inferentialist inferentialist commented Oct 19, 2023

PR summary

The original Bbox.transformed method does not work correctly with general affine transformations.

Relevant issue 27151

This PR closes #27151.

PR checklist

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for opening your first PR into Matplotlib!

If you have not heard from us in a week or so, please leave a new comment below and that should bring it to our attention. Most of our reviewers are volunteers and sometimes things fall through the cracks.

You can also join us on gitter for real-time discussion.

For details on testing, writing docs, and our review process, please see the developer guide

We strive to be a welcoming and open project. Please follow our Code of Conduct.

@jklymak
Copy link
Member

jklymak commented Oct 20, 2023

I think this is right, but shocking this wasn't tested before. Can you add some tests to test_transforms.py so we don't break this?

@ksunden
Copy link
Member

ksunden commented Oct 20, 2023

I think I agree that the implementation needs to be updated.

However, there has been recent discussion regarding deprecating bbox.corners (See #26805 and #26878) So I think we should perhaps resolve that before expanding its usage. (@anntzer was the one to suggest it)

Currently corners is only used internally in rotated, which itself is basically doing what this PR proposes, just creating the rotation transform on the fly. (Though that one starts from Bbox.unit(), not Bbox.null(), which works because of the ignore=True)

I suppose there is also a broader question of non-affine transforms... while for Affine transforms, I think it should be true that the corners will define the total extent, that is not generically true, but I don't think we have the ability to guarantee accuracy of this method for all transforms...

(Take, for example a geographic transform where the poles end up degenerate, and so the corners of the bounding box end up with just a sliver rather than encompasing the whole transformed shape...)

@anntzer
Copy link
Contributor

anntzer commented Oct 20, 2023

See also #12059, though.

@inferentialist
Copy link
Author

@jklymak @ksunden @anntzer, thanks for the feedback! I'm a big fan of the matplotlib package, so thanks for the work you do.

My sense is that Bbox is a intended to be a low overhead, utility class that provides a lot of syntactic sugar. I've found it useful outside of matplotlib and, in that sense, I'd be sad to see a method like Bbox.corners disappear. I'll add my two cents to the discussion of #26805 and #26878.

However, I think the most interesting thing, here, is to agree on what Bbox.transformed should do, particularly with respect to the level of generality; tests and documentation to follow.

Supporting affine transformations seems to be a reasonable line to draw. I think the argument is twofold: (1) an affine transform will map segments to segments and (2) an affine transform will preserve convexity. The implication is that you only need to consider the transformation of the corners and their corresponding extents to correctly recover the bounding box under the transformation.

Another level of generality would be to support continuous transformations. The idea here is that you want to preserve topologies: if the transformation avoids cutting or piercing the domain, then the only thing that needs consideration is the boundary of the domain. This could be addressed by inserting points along each edge of the box and transforming these newly introduced points along with the corners. One might hazard a guarantee on accuracy with a multiresolution approach. That is, if the number of points along each edge is N, and we compute something like the boundary length under the transformation; and we do the same where the number of points along each edge is 2*N; and if these numbers agree within some tolerance, then we can have some confidence that we have correctly transformed the original boundary.

I haven't given much thought to the more general cases although I would expect the complexity of a solution to increase; nor is it clear that that there's a demonstrable need for it.

Going back to the continuous case, a multiresolution solution doesn't seem well aligned with the idea that Bbox should be a low overhead, utility class. At the very least, a user might want to opt-out of such behavior. It also seems like there would probably be a significant overlap with the logic of how other patches are handled under general transformations. However, this isn't a part of the code that I've waded into.

All told, I think the affine case is a good target with one caveat: I don't want to throw any exceptions on the is_affine property. This is because I have a use case where I'm relying on composite transform that maps a rectangle to another rectangle via a non-linear intermediate step. How to best notify the user in this scenario feels like an open question.

@inferentialist
Copy link
Author

Bumping the thread for feedback on this PR.

The tl;dr proposal: support affine transforms in Bbox.transformed and include relevant tests. Will not, in general, support continuous transformations. Will remove dependency on Bbox.corners.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Needs review
Development

Successfully merging this pull request may close these issues.

[Bug]: Bbox transformed method produces unexpected results
6 participants