Skip to content

TransformedBbox, Transform.transform_bbox, and Bbox.transformed have subtly different semantics #12059

Open
@anntzer

Description

@anntzer

TransformedBbox constructs an object that tracks changes in the original bbox and the transform (it is dynamically updated when either parent changes), wheread Transform.transform_bbox and Bbox.transformed (as well as Bbox.inverse_transformed) return a "dumb" Bbox which forgets its parents. It was recently realized (#12031) that we are constructing a lot of transform objects and indeed, looking at the codebase, there are a few places where calls to TransformedBbox could be replaced by dumb transformed bboxes as we just want to get e.g. the transformed width and throw away the result immediately after.

I was going to submit a PR for that, but then I realized that the three transformation methods have subtly different semantics. The implementations (as of 3.0rc2) are copied here for convenience:

class TransformedBbox(BboxBase):
    ...
            p = self._bbox.get_points()
            # Transform all four points, then make a new bounding box
            # from the result, taking care to make the orientation the
            # same.
            points = self._transform.transform(
                [[p[0, 0], p[0, 1]],
                 [p[1, 0], p[0, 1]],
                 [p[0, 0], p[1, 1]],
                 [p[1, 0], p[1, 1]]])
            points = np.ma.filled(points, 0.0)

            xs = min(points[:, 0]), max(points[:, 0])
            if p[0, 0] > p[1, 0]:
                xs = xs[::-1]

            ys = min(points[:, 1]), max(points[:, 1])
            if p[0, 1] > p[1, 1]:
                ys = ys[::-1]

            self._points = np.array([
                [xs[0], ys[0]],
                [xs[1], ys[1]]
            ])

...

    def transform_bbox(self, bbox):
        return Bbox(self.transform(bbox.get_points()))

...

    def transformed(self, transform):
        pts = self.get_points()
        ll, ul, lr = transform.transform(np.array([pts[0],
            [pts[0, 0], pts[1, 1]], [pts[1, 0], pts[0, 1]]]))
        return Bbox([ll, [lr[0], ul[1]]])

transform_bbox is the simplest: it applies the transform to p0 and p1 (the two corners that specify the bbox) and constructs a new Bbox keeping their order.

TransformedBbox transforms the four corners of the bbox (the two explicit ones, p0 and p1, and the two implied ones, (x0, y1) and (x1, y0)) and constructs a new bbox from the min and max of these, keeping a possible inversion between min and max.

transformed transforms three points (p0 and the two "implied" corners) and constructs the new p0 from the original p0, and the new p1 by "mixing" the results for the two "implied" corners (this change came in in #1664 by @dopplershift).


Even if there is a good reason to keep all these different semantics (not clear, but possibly for handling e.g. skewed axes as in #1664), I think at least they should be renamed in a clearer way to know exactly what to use where...

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions