Mike,

In looking into the handling of inf and nan, I think I have found some complexities and inefficiencies that are easily eliminated (and I have committed some such changes; others are pending), but in the process I have also found what I am fairly sure is a bug in the path simplification code. It is illustrated by the attached modification of nan_test.py. With 128 or more points in the data set, so that the simplification is invoked, the moveto command that should jump across the gap is getting changed to a lineto. This can be seen most easily by applying the attached patch, which includes additional debugging statements to pin down the incorrect command yielded by the simplification, as well as pending changes to unify the handling of masked arrays, nans, and infs. The bug shows up with or without this patch, however. With the patch, it is also triggered by masked_demo.py, which is how I first found it. (The non-debugging, or substantive, parts of the patch are included here for your review or discussion as a separate matter.)

The middle part of the extra debugging output with the patch applied when running the nan_test.py looks like this:

2 214.726000 395.178372
3 return cmd: 2
2 218.012000 387.824331
4 skip: 2 218.012000 387.824331
1 359.310000 396.688044
3 return cmd: 2
2 362.596000 403.422341
3 return cmd: 2

The line starting with "1" is the moveto command and coordinates yielded by the c++ path iterator; the following line is showing that the corresponding command yielded by the simplification code is instead "2", and that it is being returned at a location I have called "3". All this will make sense only when you look at the patched code.

Eric
#!/usr/bin/env python
"""
Example: simple line plots with NaNs inserted.
"""
import matplotlib
#matplotlib.use('pdf')
from pylab import *
import numpy as np

t = arange(0.0, 1.0+0.01, 0.00795)
nt = len(t)

sl1 = slice(nt/2-nt/6, nt/2, 1)
sl2 = slice(nt/2, nt/2+nt/6, 1)
s = cos(2*2*pi*t)
t[sl1] = NaN
t[sl2] = np.inf
#t = np.ma.masked_invalid(t)
subplot(2,1,1)
plot(t, s, 'o', lw=4)

xlabel('time (s)')
ylabel('voltage (mV)')
title('A sine wave with a gap of NaNs between 0.4 and 0.6')
grid(True)

subplot(2,1,2)
t[0] = NaN
t[-1] = NaN
plot(t, s, '-', lw=4)
title('Also with NaN in first and last point')

xlabel('time (s)')
ylabel('more nans')
grid(True)
print 'nt = ', nt
#print t.count()
show()
Index: src/agg_py_path_iterator.h
===================================================================
--- src/agg_py_path_iterator.h	(revision 6015)
+++ src/agg_py_path_iterator.h	(working copy)
@@ -116,7 +116,7 @@
      agg::path_cmd_end_poly | agg::path_flags_close
     };
 
-#define DEBUG_SIMPLIFY 0
+#define DEBUG_SIMPLIFY 1
 
 template<class VertexSource>
 class SimplifyPath
@@ -194,6 +194,7 @@
             *y = front.y;
 #if DEBUG_SIMPLIFY
             printf((cmd == agg::path_cmd_move_to) ? "|" : "-");
+            fprintf(stderr, "1 return cmd: %d\n", cmd);
 #endif
             return cmd;
         }
@@ -218,6 +219,9 @@
         // array on each draw.
         while ((cmd = m_source->vertex(x, y)) != agg::path_cmd_stop)
         {
+#if DEBUG_SIMPLIFY
+            fprintf(stderr, "%d %f %f\n", (int) cmd, *x, *y);
+#endif
             // Do any quantization if requested
             if (m_quantize && agg::is_vertex(cmd))
             {
@@ -236,6 +240,8 @@
 #if DEBUG_SIMPLIFY
                 m_pushed++;
                 printf("|");
+                fprintf(stderr, "2 return cmd: %d\n", cmd);
+
 #endif
                 return agg::path_cmd_move_to;
             }
@@ -245,6 +251,8 @@
             {
 #if DEBUG_SIMPLIFY
                 m_skipped++;
+                fprintf(stderr, "1 skip: %d %f %f\n", (int) cmd, *x, *y);
+
 #endif
                 continue;
             }
@@ -262,6 +270,8 @@
                 m_clipped = true;
 #if DEBUG_SIMPLIFY
                 m_skipped++;
+                fprintf(stderr, "2 skip: %d %f %f\n", (int) cmd, *x, *y);
+
 #endif
                 continue;
             }
@@ -295,6 +305,8 @@
                 m_lastWrittenY = m_minY = m_lasty;
 #if DEBUG_SIMPLIFY
                 m_skipped++;
+                fprintf(stderr, "3 skip: %d %f %f\n", (int) cmd, *x, *y);
+
 #endif
                 continue;
             }
@@ -361,6 +373,8 @@
                 m_lasty = *y;
 #if DEBUG_SIMPLIFY
                 m_skipped++;
+                fprintf(stderr, "4 skip: %d %f %f\n", (int) cmd, *x, *y);
+
 #endif
                 continue;
             }
@@ -434,6 +448,8 @@
             *y = front.y;
 #if DEBUG_SIMPLIFY
             printf((cmd == agg::path_cmd_move_to) ? "|" : "-");
+            fprintf(stderr, "3 return cmd: %d\n", cmd);
+
 #endif
             return cmd;
         }
Index: lib/matplotlib/path.py
===================================================================
--- lib/matplotlib/path.py	(revision 6015)
+++ lib/matplotlib/path.py	(working copy)
@@ -84,43 +84,22 @@
         dimension.
 
         If *codes* is None, *vertices* will be treated as a series of
-        line segments.  If *vertices* contains masked values, the
-        resulting path will be compressed, with ``MOVETO`` codes
-        inserted in the correct places to jump over the masked
-        regions.
+        line segments.  If *vertices* contains masked values, they will
+        be converted to nans, which are then handled correctly by the
+        iterator and all other consumers of Path instances. Specifically,
+        ``MOVETO`` codes are used to jump over masked, nan, or inf
+        values.
         """
         if ma.isMaskedArray(vertices):
-            is_mask = True
-            mask = ma.getmask(vertices)
+            vertices = vertices.astype(np.float_).filled(np.nan)
         else:
-            is_mask = False
             vertices = np.asarray(vertices, np.float_)
-            mask = ma.nomask
 
         if codes is not None:
             codes = np.asarray(codes, self.code_type)
             assert codes.ndim == 1
             assert len(codes) == len(vertices)
 
-        # The path being passed in may have masked values.  However,
-        # the backends (and any affine transformations in matplotlib
-        # itself), are not expected to deal with masked arrays, so we
-        # must remove them from the array (using compressed), and add
-        # MOVETO commands to the codes array accordingly.
-        if is_mask:
-            if mask is not ma.nomask:
-                mask1d = np.logical_or.reduce(mask, axis=1)
-                gmask1d = np.invert(mask1d)
-                if codes is None:
-                    codes = np.empty((len(vertices)), self.code_type)
-                    codes.fill(self.LINETO)
-                    codes[0] = self.MOVETO
-                vertices = vertices[gmask1d].filled() # ndarray
-                codes[np.roll(mask1d, 1)] = self.MOVETO
-                codes = codes[gmask1d] # np.compress is much slower
-            else:
-                vertices = np.asarray(vertices, np.float_)
-
         assert vertices.ndim == 2
         assert vertices.shape[1] == 2
 
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Matplotlib-devel mailing list
Matplotlib-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-devel

Reply via email to