From: Phil E. <phi...@ho...> - 2012-02-03 13:40:47
|
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, |
From: Phil E. <phi...@ho...> - 2012-02-17 16:54:22
|
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, |
From: Benjamin R. <ben...@ou...> - 2012-02-17 17:27:47
|
On Fri, Feb 17, 2012 at 10:54 AM, Phil Elson <phi...@ho...>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, > > 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 |