Phil Elson | 3 Feb 14:40
Picon
Favicon

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,
(Continue reading)

Phil Elson | 3 Feb 17:40
Picon
Favicon

Non string projections

Some time back I asked about initialising a projection in MPL using generic
objects rather than by class name. I created a pull request associated with
this which was responded to fantastically by leejjoon which (after several
months) I have finally got around to implementing. My changes have been added
to the original pull request, which will eventually be obsoleted, but that
doesn't seem to have notified the devel mailing list, therefore I would like
to draw the list's attention to
https://github.com/matplotlib/matplotlib/pull/470#issuecomment-3743543 on
which I would greatly appreciate feedback & ultimately get onto the mpl master.

The pull request in question would pave the way for non string projections so
I thought I would play with how one might go about specifying the location of
theta_0 in a polar plot (i.e. should it be due east or due north etc.). I have
branched my changeset mentioned in the pull request above and implemented
a couple of ideas, although I am not proposing that these changes go any
further at this stage (I would be happy if someone wants to run with
them though):

Currently, one can set the theta_0 of a polar plot with:

ax = plt.axes(projection='polar')
ax.set_theta_offset(np.pi/2)
ax.plot(np.arange(100)*0.15, np.arange(100))

But internally there are some nasties going on (theta_0 is an attribute on the
axes, the transform is instantiated from within the axes and is given the axes
that is instantiating it, which is all a bit circular). I have made a branch
(https://github.com/PhilipElson/matplotlib/compare/master...polar_fun) which
alleviates the axes attribute issue and would allow something like:

(Continue reading)

Michael Droettboom | 3 Feb 19:03

Re: Non string projections

Thanks for doing this work.

On 02/03/2012 11:40 AM, Phil Elson wrote:
> Currently, one can set the theta_0 of a polar plot with:
>
> ax = plt.axes(projection='polar')
> ax.set_theta_offset(np.pi/2)
> ax.plot(np.arange(100)*0.15, np.arange(100))
>
> But internally there are some nasties going on (theta_0 is an attribute on the
> axes, the transform is instantiated from within the axes and is given the axes
> that is instantiating it, which is all a bit circular). I have made a branch
> (https://github.com/PhilipElson/matplotlib/compare/master...polar_fun) which
> alleviates the axes attribute issue and would allow something like:
>
> polar_trans = mpl.transforms.Polar.PolarTransform(theta_offset=np.pi/2)
> ax = plt.axes(projection=polar_trans)
> ax.plot(np.arange(100)*0.15, np.arange(100))

I agree that the canonical copy of theta_offset should probably live in 
the transform and not the PolarAxes.  However, an important feature of 
the current system that seems to be lost in your branch is that the user 
deals with Projections (Axes subclasses) which bring together not only 
the transformation of points from one space to another, but the axes 
shape and tick placement etc., and they also allow for changing 
everything after the fact.  The Transformation classes, as they stand 
now, are intended to be an implementation detail hidden from the user.

I'm not quite sure what the above lines are meant to do.  
matplotlib.transforms doesn't have a Polar member --  
(Continue reading)

Phil Elson | 4 Feb 18:13
Picon
Favicon

Re: Non string projections

Thanks Mike.

> I'm not quite sure what the above lines are meant to do.
> matplotlib.transforms doesn't have a Polar member --
> matplotlib.projections.polar.Polar does not have a PolarTransform member
> (on master or your polar_fun branch).  Even given that, I think the user
> should be specifying a projection, not a transformation, to create a new
> axes.  There is potential for confusion that some transformations will
> allow getting a projection out and some won't (for some it doesn't even
> really make sense).

That was meant to be
matplotlib.projections.polar.PolarAxes.PolarTransform but your right,
defining the "projection" in the transform could lead to confusion,
yet initialising an Axes as a projection seems like unnecessary
complexity. This suggests that defining a "projection" class which is
neither Transform nor Axes might make the most sense (note, what
follows is pseudo code and does not exist in the branch):

>>> polar_proj = Polar(theta0=np.pi/2)
>>> ax = plt.axes(projection=polar_proj)
>>> print ax.projection
Polar(theta0=1.57)

The PolarAxes would be initialised with the Projection instance, and
the PolarAxes can initialise the PolarTransform with a reference to
that projection. Thus changing the theta0 of the projection in the
Axes would also change the projection which is used in the Transform
instance, i.e.:

(Continue reading)

Phil Elson | 10 Feb 18:53
Picon
Favicon

Image transforms

In much the same way Basemap can take an image in a Plate Carree map
projection (e.g. blue marble) and transform it onto another projection
in a non-affine way, I would like to be able to apply a non-affine
transformation to an image, only using the proper matplotlib Transform
framework.
To me, this means that I should not have to pre-compute the projected
image before adding it to the axes, instead I should be able to pass
the source image and the Transformation stack should take care of
transforming (warping) it for me (just like I can with a Path).

As far as I can tell, there is no current matplotlib functionality to
do this (as I understand it, some backends can cope with affine image
transformations, but this has not been plumbed-in in the same style as
the Transform of paths and is done in the Image classes themselves).
(note: I am aware that there is some code to do affine transforms in
certain backends -
http://matplotlib.sourceforge.net/examples/api/demo_affine_image.html
- which is currently broken [I have a fix for this], but it doesn't
fit into the Transform framework at present.)

I have code which will do the actual warping for my particular case,
and all I need to do is hook it in nicely...

I was thinking of adding a method to the Transform class which
implements this functionality, psuedo code stubs are included:

class Transform:
...
   def transform_image(self, image):
       return self.transform_image_affine(self.transform_image_non_affine(image))
(Continue reading)

Benjamin Root | 10 Feb 19:32
Picon
Favicon

Re: Image transforms



On Fri, Feb 10, 2012 at 11:53 AM, Phil Elson <philipelson-PkbjNfxxIARBDgjK7y7TUQ@public.gmane.org> wrote:
In much the same way Basemap can take an image in a Plate Carree map
projection (e.g. blue marble) and transform it onto another projection
in a non-affine way, I would like to be able to apply a non-affine
transformation to an image, only using the proper matplotlib Transform
framework.
To me, this means that I should not have to pre-compute the projected
image before adding it to the axes, instead I should be able to pass
the source image and the Transformation stack should take care of
transforming (warping) it for me (just like I can with a Path).

As far as I can tell, there is no current matplotlib functionality to
do this (as I understand it, some backends can cope with affine image
transformations, but this has not been plumbed-in in the same style as
the Transform of paths and is done in the Image classes themselves).
(note: I am aware that there is some code to do affine transforms in
certain backends -
http://matplotlib.sourceforge.net/examples/api/demo_affine_image.html
- which is currently broken [I have a fix for this], but it doesn't
fit into the Transform framework at present.)

I have code which will do the actual warping for my particular case,
and all I need to do is hook it in nicely...

I was thinking of adding a method to the Transform class which
implements this functionality, psuedo code stubs are included:


class Transform:
...
  def transform_image(self, image):
      return self.transform_image_affine(self.transform_image_non_affine(image))

  def transform_image_non_affine(self, image):
      if not self.is_affine:
          raise NotImplementedError('This is the hard part.')
      return image
  ...
  def transform_image_affine(self, image):
       # could easily handle scale & translations (by changing the
extent), but not rotations...
       raise NotImplementedError("Need to do this. But rule out
rotations completely.")


This could then be used by the Image artist to do something like:

class Image(Artist, ...):
   ...
   def draw(self, renderer, *args, **kwargs):
       transform = self.get_transform()
       timg = transform.transform_image_non_affine(self)
       affine = transform.get_affine()
       ...
       renderer.draw_image(timg, ..., affine)


And the backends could implement:

class Renderer*...
   def draw_image(..., img, ..., transform=None):
       # transform must be an affine transform
       if transform.is_affine and i_can_handle_affines:
          ... # convert the Transform into the backend's transform form
       else:
          timage = transform.transform_image(img)



The warping mechanism itself would be fairly simple, in that it
assigns coordinate values to each pixel in the source cs (coordinate
system), transforms those points into the target cs, from which a
bounding box can be identified. The bbox is then treated as the bbox
of the target (warped) image, which is given an arbitrary resolution.
Finally the target image pixel coordinates are computed and their
associated pixel values are calculated by interpolating from the
source image (using target cs pixel values).


As mentioned, I have written the image warping code (for my higher
dimensional coordinate system case using
scipy.interpolate.NearestNDInterpolator) successfully already, so the
main motivations for this mail then, are:
 * To get a feel for whether anyone else would find this functionality
useful? Where else can it be used and in what ways?
 * To get feedback on the proposed change to the Transform class,
whether such a change would be acceptable and what pitfalls lie ahead.
 * To hear alternative approaches to solving the same problem.
 * To make sure I haven't missed a concept that already exists in the
Image module (there are 6 different "image" classes in there, 4 of
which undocumented)
 * To find out if anyone else wants to collaborate in making the
required change.

Thanks in advance for your time,


Could this mean that we could support imshow() for polar axes?  That would be nice!

Ben Root

------------------------------------------------------------------------------
Virtualization & Cloud Management Using Capacity Planning
Cloud computing makes use of virtualization - but cloud computing 
also focuses on allowing computing to be delivered as a service.
http://www.accelacomm.com/jaw/sfnl/114/51521223/
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@...
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel
Tony Yu | 14 Feb 05:31
Picon
Gravatar

Font rc-setting not updating

The title is a bit misleading: The problem is that the last font-related rc-setting seems to override all previous settings. To clarify, if I save a figure with certain font settings and *after that* change the rc-setting, the older figure appears to have the newer setting. Note that this only appears to happen with fonts---the linewidth setting, for example, shows up as expected. (See script belows)


-Tony 


import matplotlib.pyplot as plt

def test_simple_plot():
    fig, ax = plt.subplots()

    ax.plot([0, 1])
    ax.set_xlabel('x-label')
    ax.set_ylabel('y-label')
    ax.set_title('title')

    return fig

plt.rcParams['lines.linewidth'] = 10
plt.rcParams['font.family'] = 'serif'
plt.rcParams['font.size'] = 20
fig1 = test_simple_plot()

plt.rcParams['lines.linewidth'] = 1
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.size'] = 10
fig2 = test_simple_plot()

plt.show()
------------------------------------------------------------------------------
Keep Your Developer Skills Current with LearnDevNow!
The most comprehensive online learning library for Microsoft developers
is just $99.99! Visual Studio, SharePoint, SQL - plus HTML5, CSS3, MVC3,
Metro Style Apps, more. Free future releases when you subscribe now!
http://p.sf.net/sfu/learndevnow-d2d
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@...
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel
Benjamin Root | 14 Feb 05:47
Picon
Favicon

Re: Font rc-setting not updating



On Monday, February 13, 2012, Tony Yu <tsyu80-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> wrote:
> The title is a bit misleading: The problem is that the last font-related rc-setting seems to override all previous settings. To clarify, if I save a figure with certain font settings and *after that* change the rc-setting, the older figure appears to have the newer setting. Note that this only appears to happen with fonts---the linewidth setting, for example, shows up as expected. (See script belows)
>
> -Tony 
>
> import matplotlib.pyplot as plt
> def test_simple_plot():
>     fig, ax = plt.subplots()
>     ax.plot([0, 1])
>     ax.set_xlabel('x-label')
>     ax.set_ylabel('y-label')
>     ax.set_title('title')
>     return fig
> plt.rcParams['lines.linewidth'] = 10
> plt.rcParams['font.family'] = 'serif'
> plt.rcParams['font.size'] = 20
> fig1 = test_simple_plot()
> plt.rcParams['lines.linewidth'] = 1
> plt.rcParams['font.family'] = 'sans-serif'
> plt.rcParams['font.size'] = 10
> fig2 = test_simple_plot()
> plt.show()

Looks like we have an inconsistency here with how we process our None's. For most artists, properties defined as None in the constructor are then given defaults from the rcparams.  I would guess that text objects are doing if on draw(), instead. At first glacé, I would guess that would be a bug, but I would welcome other comments on this.

Ben Root
------------------------------------------------------------------------------
Keep Your Developer Skills Current with LearnDevNow!
The most comprehensive online learning library for Microsoft developers
is just $99.99! Visual Studio, SharePoint, SQL - plus HTML5, CSS3, MVC3,
Metro Style Apps, more. Free future releases when you subscribe now!
http://p.sf.net/sfu/learndevnow-d2d
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@...
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel
Benjamin Root | 17 Feb 17:14
Picon
Favicon

faulty design for Lasso widget?

Hello all,

I tracked down an annoying problem in one of applications to the Lasso widget I was using.  The widget constructor lets you specify a function to call when the lasso operation is complete.  So, when I create a Lasso, I set the canvas's widget lock to the new lasso, and the release function will unlock it when it is done.  What would occassionally happen is that the canvas wouldn't get unlocked and I wouldn't be able to use any other widget tools.

It turns out that the release function is not called if the number of vertices collected is not more than 2.  So, accidental clicks that activate the lasso never get cleaned up.  Because of this design, it would be impossible to guarantee a proper cleanup.  One could add another button_release callback to clean up if the canvas is still locked, but there is no guarantee that that callback is not called before the lasso's callback, thereby creating a race condition.

The only solution I see is to guarantee that the release callback will be called regardless of the length of the vertices array.  Does anybody see a problem with that?

Cheers!
Ben Root

------------------------------------------------------------------------------
Virtualization & Cloud Management Using Capacity Planning
Cloud computing makes use of virtualization - but cloud computing 
also focuses on allowing computing to be delivered as a service.
http://www.accelacomm.com/jaw/sfnl/114/51521223/
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@...
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel
Phil Elson | 17 Feb 17:54
Picon
Favicon

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

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,

------------------------------------------------------------------------------
Virtualization & Cloud Management Using Capacity Planning
Cloud computing makes use of virtualization - but cloud computing 
also focuses on allowing computing to be delivered as a service.
http://www.accelacomm.com/jaw/sfnl/114/51521223/

Gmane