Caching the results of Transform.transform_path for non-affine transforms

I'm trying to understand how the TransformedPath mechanism is working
with only limited success, and was hoping someone could help.

I have a non-affine transformation defined (subclass of
matplotlib.transforms.Transform) which takes a path and applies an
intensive transformation (path curving & cutting) which can take a
little while, but am able to guarantee that this transformation is a
one off and will never change for this transform instance, therefore
there are obvious caching opportunities.
I am aware that TransformedPath is doing some caching and would really
like to hook into this rather than rolling my own caching mechanism
but can't q
uite figure out (the probably obvious!) way to do it.

To see this problem for yourself I have attached a dummy example of
what I am working on:

import matplotlib.transforms

class SlowNonAffineTransform(matplotlib.transforms.Transform):
    input_dims = 2
    output_dims = 2
    is_separable = False
    has_inverse = True

    def transform(self, points):
        return matplotlib.transforms.IdentityTransform().transform(points)

    def transform_path(self, path):
        # pretends that it is doing something clever & time consuming,
but really is just sleeping
        import time
        # take a long time to do something
        time.sleep(3)
        # return the original path
        return matplotlib.transforms.IdentityTransform().transform_path(path)

if __name__ == '__main__':
    import matplotlib.pyplot as plt

    ax = plt.axes()
    ax.plot([0, 10, 20], [1, 3, 2], transform=SlowNonAffineTransform()
+ ax.transData)
    plt.show()

When this code is run the initial "show" is slow, which is fine, but a
simple resize/zoom rect/pan/zoom will also take a long time.
How can I tell mpl that I can guarantee that my level of the transform
stack is never invalidated?

Many Thanks,

I think this feature was originally intended to work (since
TransformedPath exists)
but it wasn't working [in the way that I was expecting it to].
I made a change which now only invalidates non-affine transformations
if it is really
necessary. This change required a modification to the way
invalidation was passed through the transform stack, since certain transform
subclasses need to override the mechanism. I will try to explain the reason why
this is the case:

Suppose a TransformNode is told that it can no longer store the affine
transformed
path by its child node, then it must pass this message up to its parent nodes,
until eventually a TransformedPath instance is invalidated (triggering
a re-computation).
With Transforms this recursion can simply pass the same invalidation message up,
but for the more complex case of a CompositeTransform, which
represents the combination
of two Transforms, things get harder. I will devise a notation to help me
explain:

Let a composite transform, A, represent an affine transformation (a1)
followed by a
non affine transformation (vc2) [vc stands for very complicated] we
can write this in
the form (a1, vc2). Since non-affine Transform instances are composed of a
non-affine transformation followed by an affine one, we can write (vc2) as
(c2, a2) and the composite can now be written as (a1, c2, a2).

As a bit of background knowledge, computing the non-affine transformation of A
involves computing (a1, c2) and leaves the term (a2) as the affine
component. Additionally, a CompositeTransform which looks like (c1, a1, a2) can
be optimised such that its affine part is (a1, a2).

There are four permutations of CompositeTransforms:

A = (a1, c2, a2)
B = (c1, a1, a2)
C = (c1, a1, c2, a2)
D = (a1, a2)

When a child of a CompositeTransform tells us that its affine part is invalid,
we need to know which child it is that has told us.

This statement is best demonstrated in transform A:

If the invalid part is a1 then it follows that the non-affine part
(a1, c2) is also
invalid, hence A must inform its parent that its entire transform is invalid.

Conversely, if the invalid part is a2 then the non-affine part (a1,
c2) is unchanged and
therefore can pass on the message that only its affine part is invalid.

The changes can be found in
https://github.com/PhilipElson/matplotlib/compare/path_transform_cache
and I would really appreciate your feedback.

I can make a pull request of this if that makes in-line discussion easier.

Many Thanks,

Chances are, you have just now become the resident expert on Transforms.

A few very important questions. Does this change any existing API? If so, then changes will have to be taken very carefully. Does all current tests pass? Can you think of any additional tests to add (both for your changes and for the current behavior)? How does this impact the performance of existing code?

Maybe some demo code to help us evaluate your use-case?

Ben Root

···

On Fri, Feb 17, 2012 at 10:54 AM, Phil Elson <philipelson@…476…> wrote:

I think this feature was originally intended to work (since

TransformedPath exists)

but it wasn’t working [in the way that I was expecting it to].

I made a change which now only invalidates non-affine transformations

if it is really

necessary. This change required a modification to the way

invalidation was passed through the transform stack, since certain transform

subclasses need to override the mechanism. I will try to explain the reason why

this is the case:

Suppose a TransformNode is told that it can no longer store the affine

transformed

path by its child node, then it must pass this message up to its parent nodes,

until eventually a TransformedPath instance is invalidated (triggering

a re-computation).

With Transforms this recursion can simply pass the same invalidation message up,

but for the more complex case of a CompositeTransform, which

represents the combination

of two Transforms, things get harder. I will devise a notation to help me

explain:

Let a composite transform, A, represent an affine transformation (a1)

followed by a

non affine transformation (vc2) [vc stands for very complicated] we

can write this in

the form (a1, vc2). Since non-affine Transform instances are composed of a

non-affine transformation followed by an affine one, we can write (vc2) as

(c2, a2) and the composite can now be written as (a1, c2, a2).

As a bit of background knowledge, computing the non-affine transformation of A

involves computing (a1, c2) and leaves the term (a2) as the affine

component. Additionally, a CompositeTransform which looks like (c1, a1, a2) can

be optimised such that its affine part is (a1, a2).

There are four permutations of CompositeTransforms:

A = (a1, c2, a2)

B = (c1, a1, a2)

C = (c1, a1, c2, a2)

D = (a1, a2)

When a child of a CompositeTransform tells us that its affine part is invalid,

we need to know which child it is that has told us.

This statement is best demonstrated in transform A:

If the invalid part is a1 then it follows that the non-affine part

(a1, c2) is also

invalid, hence A must inform its parent that its entire transform is invalid.

Conversely, if the invalid part is a2 then the non-affine part (a1,

c2) is unchanged and

therefore can pass on the message that only its affine part is invalid.

The changes can be found in

https://github.com/PhilipElson/matplotlib/compare/path_transform_cache

and I would really appreciate your feedback.

I can make a pull request of this if that makes in-line discussion easier.

Many Thanks,