From: Srinath A. <sr...@fa...> - 2004-04-18 02:37:56
|
Hello, I seem to have uncovered a bug in setting/updating the axis limits. If you run the following example, you should see a straight line between (0,0) and (1,1). Pressing the "Test Zoom" button should reset the axis limits to the values depicted below. Basically, the X axis is clipped between [0.1,0.8] and the Y axis limits are either [0.516,0.517] or [0.5166,0.5167] depending on which of the two lines in the code below is commented. The following bug(s) are seen: 1. In the first case, where the y axis limit is updated to [0.516,0.517], the line should be seen as a sharply vertical line passing starting from x=0.516 and continuing sharply upwards till x=0.517. However, the line seems to start significantly _after_ x=0.52. 2. In the second case, if the ylim variable is changed to [0.5166,0.5167], then the line completely dissapears from the axes view. It seems to fall off the right edge (is my guess), whereas it should be seen as a more or less vertical line passing through x=0.5166. The test code is appended below the signature. Thanks, Srinath import matplotlib matplotlib.use('GTK') from matplotlib.backends.backend_gtk import FigureCanvasGTK from matplotlib.axes import Subplot from matplotlib.figure import Figure import gtk class MyApp: def __init__(self): self.win = gtk.Window() self.win.set_name("Embedding in GTK") self.win.connect("destroy", gtk.mainquit) self.win.set_border_width(5) self.vbox = gtk.VBox(spacing=3) self.win.add(self.vbox) self.vbox.show() self.fig = Figure(figsize=(5,4), dpi=100) self.ax = Subplot(self.fig, 111) self.ax.plot([0,1], [0,1]) self.fig.add_axis(self.ax) self.canvas = FigureCanvasGTK(self.fig) # a gtk.DrawingArea self.canvas.show() self.vbox.pack_start(self.canvas) buttonTestZoom = gtk.Button('Test Zoom') buttonTestZoom.connect('button_press_event', self.TestZoom) buttonTestZoom.show() self.vbox.pack_start(buttonTestZoom) self.win.show() def TestZoom(self, widget, event): self.ax.update_viewlim() xlim = [0.1, 0.8] # this makes the line dissapear from the axes. #ylim = [0.5166, 0.5167] # this shows the line but in the wrong location! ylim = [0.516, 0.517] self.ax.set_xlim(xlim) self.ax.set_ylim(ylim) self.canvas.draw() if __name__=='__main__': app = MyApp() gtk.mainloop() |
From: John H. <jdh...@ac...> - 2004-04-19 22:29:26
|
>>>>> "Srinath" == Srinath Avadhanula <sr...@fa...> writes: Srinath> Hello, I seem to have uncovered a bug in setting/updating Srinath> the axis limits. Hi, I have looked into this and it is indeed a bug. Here is what is going on: The transforms for x and y take your view limits and map them to display coords, where negative numbers don't make sense. It is a standard linear transformation scale = displayInterval/viewInterval return scale*(y-viewMin) + displayMin In your example, if the y axis view limits are [0.516, 0.52] and the display interval is [44.0, 360.0], then transformation of the y vector [0, 1]. scale = (360-44)/0.004 ytransformed = scale*(y-.516) + 44 which is [-40720., 38280.] Ie, when your nearest data point is much smaller than your view limits, you get negative display coords which are undefined. I am not sure this case is common enough to warrant the performance hit of detecting it and doing the linear interpolation necessary to add pseudo data points to make your plot well defined. Simply replacing the negative data with 0 is incorrect because you have to do linear interpolation to get the right location in display coords. Perhaps a warning if min(xtransformed)<0 or min(ytransformed)<0 would be appropriate and worth the performance hit. If you have some ideas on what is the best way to handle this case, let me know. JDH |
From: Srinath A. <sr...@fa...> - 2004-04-19 23:09:37
|
On Mon, 19 Apr 2004, John Hunter wrote: > >>>>> "Srinath" == Srinath Avadhanula <sr...@fa...> writes: > > Srinath> Hello, I seem to have uncovered a bug in setting/updating > Srinath> the axis limits. > > limits, you get negative display coords which are undefined. I am not > sure this case is common enough to warrant the performance hit of > detecting it and doing the linear interpolation necessary to add I have no time right now to go into a technical discussion, but IMHO this is a serious enough bug that it needs to be fixed. One can only worry about performance after doing the right thing. It doesn't make sense to give the completely wrong answer in a shorter time. [Sorry, I do not mean to sound curt or rude. Excuse me if it sounds that way.] Besides whether or not its a common enough situation is very application specific. I was planning to use matplotlib to display a complex layout. At the original scale, not much can be seen. There necessarily has to be a lot of scaling to see the details. > If you have some ideas on what is the best way to handle this case, > let me know. Could you let me know which files in matplotlib have the code in question? I'll take a look then. Srinath |
From: John H. <jdh...@ac...> - 2004-04-20 13:48:21
|
>>>>> "Srinath" == Srinath Avadhanula <sr...@fa...> writes: Srinath> Besides whether or not its a common enough situation is Srinath> very application specific. I was planning to use Srinath> matplotlib to display a complex layout. At the original Srinath> scale, not much can be seen. There necessarily has to be Srinath> a lot of scaling to see the details. Well, it's not complex scaling that causes troubles. The case where you see this failure is when you are drawing a connected line between two points where one or both of the points is far outside the view limits. Srinath> Could you let me know which files in matplotlib have the Srinath> code in question? I'll take a look then. The two relevant methods are matplotlib.transforms.Transform.positions and matplotlib.lines.Line2D._draw. I think the best place to start would be in the latter function. Let the transform do it's thing and then look for negative points. Note there are two kinds of clipping in matplotlib: data clipping and view clipping. data clipping removes data outside the view limits before transpose and plotting. view clipping is a renderer operation that prevents lines from being drawn outside the axes box. data clipping would remove a point from a connected line outside the viewport and prevent any part of that line from being drawn, even the part in the view port. view clipping would only clip the part that extends outside the viewport. The decision to use one or the other is often motivated by performance. Eg, if you want to plot a very large data set with only a small fraction in the viewport (which I often do), you often want to use data clipping to reduce the data set before transform and rendering. If you normally plot data with limits near the viewport limits you probably do not want the extra performance overhead and are happy with just view clipping which gives the "usual" result. That is why it's a configurable parameter. In earlier versions of matplotlib data clipping was on by default so data outside the view ports was removed. That is probably why we didn't see this negative transform error earlier. But experiments revealed that in the most common cases, data clipping did not provide an extra performance benefit, and occasionally surprised users in cases very similar to yours where one point of a connected line was outside the viewport and the entire line was removed. Given that negative display coords are essentially undefined (and backend dependent) at this point, I'm going to re-enable data clipping in the default matplotlibrc file, and I suggest most users do the same. There may be modulo operations on data points very far outside the data set that generate negative display values, or display values far larger than the display limits, each of which could cause artifact points. The ideal solution is yet to be found, but I'm open to suggestions and improvements. JDH |
From: Perry G. <pe...@st...> - 2004-04-20 18:39:42
|
John Hunter writes: > >>>>> "Srinath" == Srinath Avadhanula <sr...@fa...> writes: > > Srinath> Besides whether or not its a common enough situation is > Srinath> very application specific. I was planning to use > Srinath> matplotlib to display a complex layout. At the original > Srinath> scale, not much can be seen. There necessarily has to be > Srinath> a lot of scaling to see the details. > > Well, it's not complex scaling that causes troubles. The case where > you see this failure is when you are drawing a connected line between > two points where one or both of the points is far outside the view > limits. > Is the essence of the issue here whether clipping is working properly, in particular, the backend clipping? I would have thought that backend clipping should handle this properly. For which backends does it not? If it doesn't for some, perhaps the issue is to supply our own software clipping; algorithm (possibly much slower though) as an option. Perry |
From: John H. <jdh...@ac...> - 2004-04-20 20:01:21
|
>>>>> "Perry" == Perry Greenfield <pe...@st...> writes: Perry> Is the essence of the issue here whether clipping is Perry> working properly, in particular, the backend clipping? I Perry> would have thought that backend clipping should handle this Perry> properly. For which backends does it not? If it doesn't Perry> for some, perhaps the issue is to supply our own software Perry> clipping; algorithm (possibly much slower though) as an Perry> option. Backend clipping works when the points are on the canvas (the canvas is the entire figure, not just the axes). Eg, on a 100x100 canvas, with a line from (0,0) to (100,100) you can clip to the (25,25) (75,75) rectangle. No problems there. In Srinath's case, we've asked the backend to plot, for example, a line from (0,-50000), (0,100) and then clip to the (25,25) (75,75) rectangle. I don't think the average backend (GTK, Agg) knows what to do with this line since they expect bitmap/display coords; there are no backend transformations - everything happens on the front end (clearly there are pros and cons of this approach, see below). With data clipping turned, the line class throws out the (0,-50000) point as illegal. To fix this in the current framework, we have to identify line segments in the x,y arrays which intersect the view port but with one or more end points outside the viewlim, and then draw the appropriate line through view port. This issue primarily arises for connected points (line styles '-', '--', and '-.' ). For symbol lines, data clipping already does the right thing, which is to throw the point away. However, there are hypothetical wacky cases you can imagine when you feel like being mean to matplotlib, like a 'o' circle marker 5 million miles from the view limits with a 5 million and one mile radius..... For connected lines, this could be very costly to implement since we have to loop over potentially very long arrays; for markers it would be worse. Refactoring transforms to the backend would probably fix this entirely. While non-trivial, this may be the best long term solution, especially when we want to do things like line and patch collections for efficient drawing of large numbers of objects. The two major benefits of the current transform architecture are 1) it lets you specify locations like 5 points to the left of the right y axes, and have the transform automatically update in the presence of window resizes, dpi changes, etc... Very nice when drawing ticks.... and 2) backends only have to think about one coord system, display, which makes it easier to implement a backend. I'm not convinced that it is a terrible thing to have a policy that matplotlib only plots line segments and markers whose vertices are in the axes limits, but I'm open to being convinced. JDH |
From: Perry G. <pe...@st...> - 2004-04-20 21:52:33
|
John Hunter writes: > In Srinath's case, we've asked the backend to plot, for example, a > line from (0,-50000), (0,100) and then clip to the (25,25) (75,75) > rectangle. I don't think the average backend (GTK, Agg) knows what to > do with this line since they expect bitmap/display coords; there are > no backend transformations - everything happens on the front end > (clearly there are pros and cons of this approach, see below). > Hmmm, I've always figured that true clipping works with arbitrary coordinates, that it does handle exactly the problem that Srinath reports; when it doesn't then the clipping algorithm is not really fully implemented. I'm almost certain that OpenGL handles this correctly and would be surprised if postscript didn't as well. Is it true that agg will only clip positive coordinates correctly? If so I'm surprised. Some GUIs may not, but I would have thought this is standard for any graphics system. On the other hand, I admit to the possibility of being completely wrong about this. I'll look up the postscript situation. You are much more familiar with agg than I am, though (particularly since agg documentation is not easily accessible). > With data clipping turned, the line class throws out the (0,-50000) > point as illegal. > > To fix this in the current framework, we have to identify line > segments in the x,y arrays which intersect the view port but with one > or more end points outside the viewlim, and then draw the appropriate > line through view port. This issue primarily arises for connected Tell me about it :-). I wrote a Python program to do just this for a Chaco/kiva/Tk backend. Messy but doable. Clipping polygons is way messier (many complex algorithms developed for that in the literature). Performance was ok so long as the original curve didn't get fragmented into too many segments. (Worst case: 100,000 points; alternating one point out of range positive and the next out of range negative). > points (line styles '-', '--', and '-.' ). For symbol lines, data > clipping already does the right thing, which is to throw the point > away. However, there are hypothetical wacky cases you can imagine > when you feel like being mean to matplotlib, like a 'o' circle marker > 5 million miles from the view limits with a 5 million and one mile > radius..... For connected lines, this could be very costly to > implement since we have to loop over potentially very long arrays; for > markers it would be worse. > This is a bad case. This is where you do need the polygon clipping algorithm. > Refactoring transforms to the backend would probably fix this > entirely. While non-trivial, this may be the best long term solution, > especially when we want to do things like line and patch collections > for efficient drawing of large numbers of objects. The two major > benefits of the current transform architecture are 1) it lets you > specify locations like 5 points to the left of the right y axes, and > have the transform automatically update in the presence of window > resizes, dpi changes, etc... Very nice when drawing ticks.... and 2) > backends only have to think about one coord system, display, which > makes it easier to implement a backend. > I'm not sure this is needed if all the important backends (i.e., Agg-based, postscript, svg, ...) support this capability. Users are warned that clipping doesn't work well for the other backends or the backend calls call clipping functions on their data points before calling the graphics primitives. > I'm not convinced that it is a terrible thing to have a policy that > matplotlib only plots line segments and markers whose vertices are in > the axes limits, but I'm open to being convinced. > While not common, it does happen. I'm reminded of a handbook that was produced here that generated an incorrect plot because of a clipping error. We didn't write that clipping software, but it was embarrasing nonetheless. It's worth some effort to get it right I think. Before wasting more discussion over it I guess I'd like to know which backends don't support generalized clipping for lines and polygons. Perry |
From: John H. <jdh...@ac...> - 2004-04-20 22:46:37
|
>>>>> "Perry" == Perry Greenfield <pe...@st...> writes: Perry> While not common, it does happen. I'm reminded of a Perry> handbook that was produced here that generated an incorrect Perry> plot because of a clipping error. We didn't write that Perry> clipping software, but it was embarrasing nonetheless. It's Perry> worth some effort to get it right I think. Before wasting Perry> more discussion over it I guess I'd like to know which Perry> backends don't support generalized clipping for lines and Perry> polygons. I'll delve deeper into agg and find out what exactly is going. BTW, Maxim has revamped the antigrain site, added documentation, and released some new code. I'm obviously waiting until the dust settles on the next matlotlib release before upgrading (and upgrading matplotlib agg cvs will be a bit of a pain since many file names hanve changed). http://antigrain.com JDH |