plotting a circle in log space

I have discovered, from the mailing list, the easy way to draw a circle in linear space:

cx = 700
cy = 700
r = 1000

xmin = cx - r
xmax = cx + r
ymin = cy - r
ymax = cy + r

cir = Circle( (cx,cx), radius=r,facecolor='w',edgecolor='b')
a = gca()
a.add_patch(cir)

axis([xmin,xmax,ymin,ymax])
axis('equal')

However, when trying to overplot a circle on an existing log/log plot, I get a circle section:
e = [70,1,1,12,7,185,6,3,0,1015,6,222,500,0,661,105,0,8706,0,23,131,0,0,0,6,22,1,4,0]
o = [180,2,0,15,13,3,0,0,0,20,6,2000,9748,0,38,100,0,20023,0,2,0,0,0,0,1,0,0,0,1]
f1 = figure()
loglog(o,e,'b.')
hold('on')
cx = 700
cy = 700
r = 1000

xmin = cx - r
xmax = cx + r
ymin = cy - r
ymax = cy + r

cir = Circle( (cx,cx), radius=r,facecolor='w',edgecolor='b')
a = gca()
a.add_patch(cir)

axis([xmin,xmax,ymin,ymax])
axis('equal')

How can I plot a circle in log space?

As an additional aside, I've discovered that even if I define the points that make up a circle (in linear space), I cannot plot a smooth line through them using the plot() function:
def pol2cart(th,r):
    x = r*cos(th)
    y = r*sin(th)
    return (x,y)
   def drawCircle(cx,cy,radius,np,style):
    theta = linspace(0,2*pi,np)
    rho = ones((1,np))*radius
    x,y = pol2cart(theta,rho)
    x = x + cx
    y = y + cy
    plot(x,y,style)

cx = 700
cy = 700
r = 1000
drawCircle(cx,cy,r,1000,'b')

When I look at the resulting plot, I see empty axes. If I change the plot style to 'b.', then I see the circle. Is this a bug or an undocumented feature?

Thanks,

Mike Hearne

The problem is that your circle has negative vertices since cx-r<0 and
cy-r<0. When this happens, mpl is transforming the vertices with log
coordinates and getting nans, as it should. The problem is that these
nan vertices are getting passed to the agg backend, and when the
vertex type is curve4, as it is for a circle, agg gets stuck in an
infinite recursion in the spline code. I suspect this is because the
recursion expects the comparison operator on the vertices to be well
behaved, but it is not in the presence of nans. The function in
question is agg_curve.cpp curve4_div::recursive_bezier. There is a
"maximum recursion limit" in that function, but for some reason I
don't understand, it is not breaking out of the function.

I committed a simple "fix" to the branch and the trunk to simply drop
any patch where any of the vertices are nans

        if not np.isnan(tpath.vertices).any():
            renderer.draw_path(gc, tpath, affine, rgbFace)

We might be able to do better than this -- is there a well defined way
to deal with patches where any of the transformed vertices are nans?
For simple polygons (no splines vertices), we could plot the polygon
with all the nan containing vertices removed, though in some cases
this could be a strange object -- this appears to be what was
happening by default with CirclePolygon with negative vertices but I
think this was mostly fortuitous that agg dealt with the nans
gracefully in this case. But for patches containing curve vertices,
this seems like a bad idea, since simply dropping vertices from a
spline curve is not defined.

I'm including below some sample code that shows the bug on Agg

JDH

import matplotlib
matplotlib.use('Agg')

import matplotlib.pyplot as plt
import matplotlib.patches as patches
cx = 700
cy = 700
r = 1000

fig = plt.figure()
ax = fig.add_subplot(111)

#cir = patches.CirclePolygon( (cx,cy), radius=r,facecolor='w',edgecolor='b')
cir = patches.Circle( (cx,cy), radius=r,facecolor='w',edgecolor='b')
ax.add_patch(cir)
ax.set_yscale('log')
fig.savefig('test')
plt.show()

···

On Fri, Jan 23, 2009 at 2:06 PM, Michael Hearne <mhearne@...924...> wrote:

I have discovered, from the mailing list, the easy way to draw a circle
in linear space:
...snip
cx = 700
cy = 700
r = 1000

xmin = cx - r
xmax = cx + r
ymin = cy - r
ymax = cy + r

cir = Circle( (cx,cx), radius=r,facecolor='w',edgecolor='b')
a = gca()
a.add_patch(cir)

axis([xmin,xmax,ymin,ymax])
axis('equal')

How can I plot a circle in log space?

At one point in history, the Agg backend would not do NaN removal on paths with curves -- but it looks like that's been inadvertently lost, probably in all the shuffling wrt simplification that went on, since simplification has similar restrictions. So at present, we have problems because it's passing curves with too few points to Agg, since it removes points with NaNs, but not necessarily the entire curve segment. In any case, even passing the vertices as-is (without NaNs removed) to Agg still results in an infinite loop.

The "real" fix, IMO, is to make the NaN-handling code aware of curves, which requires doing a look-ahead. That is -- if a curve segment has any NaNs at all, remove the entire curve segment. I've thought about this for a bit, but now here's some impetus to finally implement it. :slight_smile:

Mike

John Hunter wrote:

···

On Fri, Jan 23, 2009 at 2:06 PM, Michael Hearne <mhearne@...924...> wrote:
  

I have discovered, from the mailing list, the easy way to draw a circle
in linear space:
...snip
cx = 700
cy = 700
r = 1000

xmin = cx - r
xmax = cx + r
ymin = cy - r
ymax = cy + r

cir = Circle( (cx,cx), radius=r,facecolor='w',edgecolor='b')
a = gca()
a.add_patch(cir)

axis([xmin,xmax,ymin,ymax])
axis('equal')

How can I plot a circle in log space?
    
The problem is that your circle has negative vertices since cx-r<0 and
cy-r<0. When this happens, mpl is transforming the vertices with log
coordinates and getting nans, as it should. The problem is that these
nan vertices are getting passed to the agg backend, and when the
vertex type is curve4, as it is for a circle, agg gets stuck in an
infinite recursion in the spline code. I suspect this is because the
recursion expects the comparison operator on the vertices to be well
behaved, but it is not in the presence of nans. The function in
question is agg_curve.cpp curve4_div::recursive_bezier. There is a
"maximum recursion limit" in that function, but for some reason I
don't understand, it is not breaking out of the function.

I committed a simple "fix" to the branch and the trunk to simply drop
any patch where any of the vertices are nans

        if not np.isnan(tpath.vertices).any():
            renderer.draw_path(gc, tpath, affine, rgbFace)

We might be able to do better than this -- is there a well defined way
to deal with patches where any of the transformed vertices are nans?
For simple polygons (no splines vertices), we could plot the polygon
with all the nan containing vertices removed, though in some cases
this could be a strange object -- this appears to be what was
happening by default with CirclePolygon with negative vertices but I
think this was mostly fortuitous that agg dealt with the nans
gracefully in this case. But for patches containing curve vertices,
this seems like a bad idea, since simply dropping vertices from a
spline curve is not defined.

I'm including below some sample code that shows the bug on Agg

JDH

import matplotlib
matplotlib.use('Agg')

import matplotlib.pyplot as plt
import matplotlib.patches as patches
cx = 700
cy = 700
r = 1000

fig = plt.figure()
ax = fig.add_subplot(111)

#cir = patches.CirclePolygon( (cx,cy), radius=r,facecolor='w',edgecolor='b')
cir = patches.Circle( (cx,cy), radius=r,facecolor='w',edgecolor='b')
ax.add_patch(cir)
ax.set_yscale('log')
fig.savefig('test')
plt.show()
  
--
Michael Droettboom
Science Software Branch
Operations and Engineering Division
Space Telescope Science Institute
Operated by AURA for NASA

Support for handling NaNs in curves is now on the branch and trunk.

While this solves the infinite recursion problem, it still may be better in your specific case to use a CirclePolygon. All my fix does is remove an entire bezier curve when any of its elements are non-finite -- so we're talking an entire eighth-wedge at least here. By using CirclePolygon, the amount of removal could be much less.

Mike

Michael Droettboom wrote:

···

At one point in history, the Agg backend would not do NaN removal on paths with curves -- but it looks like that's been inadvertently lost, probably in all the shuffling wrt simplification that went on, since simplification has similar restrictions. So at present, we have problems because it's passing curves with too few points to Agg, since it removes points with NaNs, but not necessarily the entire curve segment. In any case, even passing the vertices as-is (without NaNs removed) to Agg still results in an infinite loop.

The "real" fix, IMO, is to make the NaN-handling code aware of curves, which requires doing a look-ahead. That is -- if a curve segment has any NaNs at all, remove the entire curve segment. I've thought about this for a bit, but now here's some impetus to finally implement it. :slight_smile:

Mike

John Hunter wrote:
  

On Fri, Jan 23, 2009 at 2:06 PM, Michael Hearne <mhearne@...924...> wrote:
  

I have discovered, from the mailing list, the easy way to draw a circle
in linear space:
...snip
cx = 700
cy = 700
r = 1000

xmin = cx - r
xmax = cx + r
ymin = cy - r
ymax = cy + r

cir = Circle( (cx,cx), radius=r,facecolor='w',edgecolor='b')
a = gca()
a.add_patch(cir)

axis([xmin,xmax,ymin,ymax])
axis('equal')

How can I plot a circle in log space?
    

The problem is that your circle has negative vertices since cx-r<0 and
cy-r<0. When this happens, mpl is transforming the vertices with log
coordinates and getting nans, as it should. The problem is that these
nan vertices are getting passed to the agg backend, and when the
vertex type is curve4, as it is for a circle, agg gets stuck in an
infinite recursion in the spline code. I suspect this is because the
recursion expects the comparison operator on the vertices to be well
behaved, but it is not in the presence of nans. The function in
question is agg_curve.cpp curve4_div::recursive_bezier. There is a
"maximum recursion limit" in that function, but for some reason I
don't understand, it is not breaking out of the function.

I committed a simple "fix" to the branch and the trunk to simply drop
any patch where any of the vertices are nans

        if not np.isnan(tpath.vertices).any():
            renderer.draw_path(gc, tpath, affine, rgbFace)

We might be able to do better than this -- is there a well defined way
to deal with patches where any of the transformed vertices are nans?
For simple polygons (no splines vertices), we could plot the polygon
with all the nan containing vertices removed, though in some cases
this could be a strange object -- this appears to be what was
happening by default with CirclePolygon with negative vertices but I
think this was mostly fortuitous that agg dealt with the nans
gracefully in this case. But for patches containing curve vertices,
this seems like a bad idea, since simply dropping vertices from a
spline curve is not defined.

I'm including below some sample code that shows the bug on Agg

JDH

import matplotlib
matplotlib.use('Agg')

import matplotlib.pyplot as plt
import matplotlib.patches as patches
cx = 700
cy = 700
r = 1000

fig = plt.figure()
ax = fig.add_subplot(111)

#cir = patches.CirclePolygon( (cx,cy), radius=r,facecolor='w',edgecolor='b')
cir = patches.Circle( (cx,cy), radius=r,facecolor='w',edgecolor='b')
ax.add_patch(cir)
ax.set_yscale('log')
fig.savefig('test')
plt.show()
  
--
Michael Droettboom
Science Software Branch
Operations and Engineering Division
Space Telescope Science Institute
Operated by AURA for NASA

While I think it's great that you fixed this with a scapel rather than
my blunt hammer of simply dropping the patch, I wonder if we should be
expecting the backends to handle nans. The general philosophy has
been to keep them as simple as possible, and nan handling is
definitely not simple. Most likely we would pay a price in efficiency
by moving the nan handling to the frontend, but would we be better off
passing a nan-filtered path to the backend?

JDH

···

On Mon, Jan 26, 2009 at 8:40 AM, Michael Droettboom <mdroe@...86...> wrote:

Support for handling NaNs in curves is now on the branch and trunk.

While this solves the infinite recursion problem, it still may be better in
your specific case to use a CirclePolygon. All my fix does is remove an
entire bezier curve when any of its elements are non-finite -- so we're
talking an entire eighth-wedge at least here. By using CirclePolygon, the
amount of removal could be much less.

John Hunter wrote:

  

Support for handling NaNs in curves is now on the branch and trunk.

While this solves the infinite recursion problem, it still may be better in
your specific case to use a CirclePolygon. All my fix does is remove an
entire bezier curve when any of its elements are non-finite -- so we're
talking an entire eighth-wedge at least here. By using CirclePolygon, the
amount of removal could be much less.
    
While I think it's great that you fixed this with a scapel rather than
my blunt hammer of simply dropping the patch, I wonder if we should be
expecting the backends to handle nans. The general philosophy has
been to keep them as simple as possible, and nan handling is
definitely not simple. Most likely we would pay a price in efficiency
by moving the nan handling to the frontend, but would we be better off
passing a nan-filtered path to the backend?
  

I don't see this as a backend/frontend issue -- I see it as a C++/Python one.

This recent change was in C++ (in PathIterator), which is generic and not specific to Agg, other than some conventions. For the Python backends, this is handled by Path.iter_vertices. This separation of NaN-handling (in C++ and in Path.iter_vertices) has been there for ages. In fact, the non-C++ backends have supported NaNs on curves for a long time (though with a small corner-case bug that's now fixed).

The Cocoa backend should probably use the common NaN-handling and simplification code, but that may require using Objective-C++, rather than just Objective-C. Alternatively, we could rewrite the NaN-handling/simplification code to use pure C, with a little shim to make it work in the highly-templatized C++ Agg world.

We could (as we do now for simplification) call out to C++ for NaN-handling as well, but I would be wary of doing the opposite.

Mike

···

On Mon, Jan 26, 2009 at 8:40 AM, Michael Droettboom <mdroe@...86...> wrote:

--
Michael Droettboom
Science Software Branch
Operations and Engineering Division
Space Telescope Science Institute
Operated by AURA for NASA