GitHub PullRequestReviewCommentEvent at 2014-12-03 17:50:19
Repository: matplotlib
Actor: pelson
Payload: {"action":"created","comment":{"url":"https://api.github.com/repos/matplotlib/matplotlib/pulls/comments/21249935","id":21249935,"diff_hunk":"@@ -0,0 +1,1681 @@\n+// This file contains liberal use of asserts to assist code development and\n+// debugging.  Standard matplotlib builds disable asserts so they cause no\n+// performance reduction.  To enable the asserts, you need to undefine the\n+// NDEBUG macro, which is achieved by adding the following\n+//     undef_macros=['NDEBUG']\n+// to the appropriate make_extension call in setupext.py, and then rebuilding.\n+#define NO_IMPORT_ARRAY\n+\n+#include \"src/mplutils.h\"\n+#include \"src/_contour.h\"\n+#include <algorithm>\n+\n+\n+// 'kind' codes.\n+#define MOVETO 1\n+#define LINETO 2\n+\n+// Point indices from current quad index.\n+#define POINT_SW (quad)\n+#define POINT_SE (quad+1)\n+#define POINT_NW (quad+_nx)\n+#define POINT_NE (quad+_nx+1)\n+\n+// CacheItem masks, only accessed directly to set.  To read, use accessors\n+// detailed below.  1 and 2 refer to level indices (lower and upper).\n+#define MASK_Z_LEVEL           0x0003 // Combines the following two.\n+#define MASK_Z_LEVEL_1         0x0001 // z > lower_level.\n+#define MASK_Z_LEVEL_2         0x0002 // z > upper_level.\n+#define MASK_VISITED_1         0x0004 // Algorithm has visited this quad.\n+#define MASK_VISITED_2         0x0008\n+#define MASK_SADDLE_1          0x0010 // quad is a saddle quad.\n+#define MASK_SADDLE_2          0x0020\n+#define MASK_SADDLE_LEFT_1     0x0040 // Contours turn left at saddle quad.\n+#define MASK_SADDLE_LEFT_2     0x0080\n+#define MASK_SADDLE_START_SW_1 0x0100 // Next visit starts on S or W edge.\n+#define MASK_SADDLE_START_SW_2 0x0200\n+#define MASK_BOUNDARY_S        0x0400 // S edge of quad is a boundary.\n+#define MASK_BOUNDARY_W        0x0800 // W edge of quad is a boundary.\n+// EXISTS_QUAD bit is always used, but the 4 EXISTS_CORNER are only used if\n+// _corner_mask is true.  Only one of EXISTS_QUAD or EXISTS_??_CORNER is ever\n+// set per quad, hence not using unique bits for each; care is needed when\n+// testing for these flags as they overlap.\n+#define MASK_EXISTS_QUAD       0x1000 // All of quad exists (is not masked).\n+#define MASK_EXISTS_SW_CORNER  0x2000 // SW corner exists, NE corner is masked.\n+#define MASK_EXISTS_SE_CORNER  0x3000\n+#define MASK_EXISTS_NW_CORNER  0x4000\n+#define MASK_EXISTS_NE_CORNER  0x5000\n+#define MASK_EXISTS            0x7000 // Combines all 5 EXISTS masks.\n+\n+// The following are only needed for filled contours.\n+#define MASK_VISITED_S        0x10000 // Algorithm has visited S boundary.\n+#define MASK_VISITED_W        0x20000 // Algorithm has visited W boundary.\n+#define MASK_VISITED_CORNER   0x40000 // Algorithm has visited corner edge.\n+\n+\n+// Accessors for various CacheItem masks.  li is shorthand for level_index.\n+#define Z_LEVEL(quad)            (_cache[quad] & MASK_Z_LEVEL)\n+#define Z_NE                     Z_LEVEL(POINT_NE)\n+#define Z_NW                     Z_LEVEL(POINT_NW)\n+#define Z_SE                     Z_LEVEL(POINT_SE)\n+#define Z_SW                     Z_LEVEL(POINT_SW)\n+#define VISITED(quad,li)         (_cache[quad] & (li==1 ? MASK_VISITED_1 : MASK_VISITED_2))\n+#define VISITED_S(quad)          (_cache[quad] & MASK_VISITED_S)\n+#define VISITED_W(quad)          (_cache[quad] & MASK_VISITED_W)\n+#define VISITED_CORNER(quad)     (_cache[quad] & MASK_VISITED_CORNER)\n+#define SADDLE(quad,li)          (_cache[quad] & (li==1 ? MASK_SADDLE_1 : MASK_SADDLE_2))\n+#define SADDLE_LEFT(quad,li)     (_cache[quad] & (li==1 ? MASK_SADDLE_LEFT_1 : MASK_SADDLE_LEFT_2))\n+#define SADDLE_START_SW(quad,li) (_cache[quad] & (li==1 ? MASK_SADDLE_START_SW_1 : MASK_SADDLE_START_SW_2))\n+#define BOUNDARY_S(quad)         (_cache[quad] & MASK_BOUNDARY_S)\n+#define BOUNDARY_W(quad)         (_cache[quad] & MASK_BOUNDARY_W)\n+#define BOUNDARY_N(quad)         BOUNDARY_S(quad+_nx)\n+#define BOUNDARY_E(quad)         BOUNDARY_W(quad+1)\n+#define EXISTS_QUAD(quad)        ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_QUAD)\n+#define EXISTS_NONE(quad)        ((_cache[quad] & MASK_EXISTS) == 0)\n+// The following are only used if _corner_mask is true.\n+#define EXISTS_SW_CORNER(quad)   ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_SW_CORNER)\n+#define EXISTS_SE_CORNER(quad)   ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_SE_CORNER)\n+#define EXISTS_NW_CORNER(quad)   ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_NW_CORNER)\n+#define EXISTS_NE_CORNER(quad)   ((_cache[quad] & MASK_EXISTS) == MASK_EXISTS_NE_CORNER)\n+#define EXISTS_ANY_CORNER(quad)  (!EXISTS_NONE(quad) && !EXISTS_QUAD(quad))\n+#define EXISTS_W_EDGE(quad)      (EXISTS_QUAD(quad) || EXISTS_SW_CORNER(quad) || EXISTS_NW_CORNER(quad))\n+#define EXISTS_E_EDGE(quad)      (EXISTS_QUAD(quad) || EXISTS_SE_CORNER(quad) || EXISTS_NE_CORNER(quad))\n+#define EXISTS_S_EDGE(quad)      (EXISTS_QUAD(quad) || EXISTS_SW_CORNER(quad) || EXISTS_SE_CORNER(quad))\n+#define EXISTS_N_EDGE(quad)      (EXISTS_QUAD(quad) || EXISTS_NW_CORNER(quad) || EXISTS_NE_CORNER(quad))\n+// Note that EXISTS_NE_CORNER(quad) is equivalent to BOUNDARY_SW(quad), etc.\n+\n+\n+\n+QuadEdge::QuadEdge()\n+    : quad(-1), edge(Edge_None)\n+{}\n+\n+QuadEdge::QuadEdge(long quad_, Edge edge_)\n+    : quad(quad_), edge(edge_)\n+{}\n+\n+bool QuadEdge::operator<(const QuadEdge& other) const\n+{\n+    if (quad != other.quad)\n+        return quad < other.quad;\n+    else\n+        return edge < other.edge;\n+}\n+\n+bool QuadEdge::operator==(const QuadEdge& other) const\n+{\n+    return quad == other.quad && edge == other.edge;\n+}\n+\n+bool QuadEdge::operator!=(const QuadEdge& other) const\n+{\n+    return !operator==(other);\n+}\n+\n+std::ostream& operator<<(std::ostream& os, const QuadEdge& quad_edge)\n+{\n+    return os << quad_edge.quad << ' ' << quad_edge.edge;\n+}\n+\n+\n+\n+XY::XY()\n+{}\n+\n+XY::XY(const double& x_, const double& y_)\n+    : x(x_), y(y_)\n+{}\n+\n+bool XY::operator==(const XY& other) const\n+{\n+    return x == other.x && y == other.y;\n+}\n+\n+bool XY::operator!=(const XY& other) const\n+{\n+    return x != other.x || y != other.y;\n+}\n+\n+XY XY::operator*(const double& multiplier) const\n+{\n+    return XY(x*multiplier, y*multiplier);\n+}\n+\n+const XY& XY::operator+=(const XY& other)\n+{\n+    x += other.x;\n+    y += other.y;\n+    return *this;\n+}\n+\n+const XY& XY::operator-=(const XY& other)\n+{\n+    x -= other.x;\n+    y -= other.y;\n+    return *this;\n+}\n+\n+XY XY::operator+(const XY& other) const\n+{\n+    return XY(x + other.x, y + other.y);\n+}\n+\n+XY XY::operator-(const XY& other) const\n+{\n+    return XY(x - other.x, y - other.y);\n+}\n+\n+std::ostream& operator<<(std::ostream& os, const XY& xy)\n+{\n+    return os << '(' << xy.x << ' ' << xy.y << ')';\n+}\n+\n+\n+\n+ContourLine::ContourLine(bool is_hole)\n+    : std::vector<XY>(),\n+      _is_hole(is_hole),\n+      _parent(0)\n+{}\n+\n+void ContourLine::add_child(ContourLine* child)\n+{\n+    assert(!_is_hole && \"Cannot add_child to a hole\");\n+    assert(child != 0 && \"Null child ContourLine\");\n+    _children.push_back(child);\n+}\n+\n+void ContourLine::clear_parent()\n+{\n+    assert(is_hole() && \"Cannot clear parent of non-hole\");\n+    assert(_parent != 0 && \"Null parent ContourLine\");\n+    _parent = 0;\n+}\n+\n+const ContourLine::Children& ContourLine::get_children() const\n+{\n+    assert(!_is_hole && \"Cannot get_children of a hole\");\n+    return _children;\n+}\n+\n+const ContourLine* ContourLine::get_parent() const\n+{\n+    assert(_is_hole && \"Cannot get_parent of a non-hole\");\n+    return _parent;\n+}\n+\n+ContourLine* ContourLine::get_parent()\n+{\n+    assert(_is_hole && \"Cannot get_parent of a non-hole\");\n+    return _parent;\n+}\n+\n+bool ContourLine::is_hole() const\n+{\n+    return _is_hole;\n+}\n+\n+void ContourLine::push_back(const XY& point)\n+{\n+    if (empty() || point != back())\n+        std::vector<XY>::push_back(point);\n+}\n+\n+void ContourLine::set_parent(ContourLine* parent)\n+{\n+    assert(_is_hole && \"Cannot set parent of a non-hole\");\n+    assert(parent != 0 && \"Null parent ContourLine\");\n+    _parent = parent;\n+}\n+\n+void ContourLine::write() const\n+{\n+    std::cout << \"ContourLine \" << this << \" of \" << size() << \" points:\";\n+    for (const_iterator it = begin(); it != end(); ++it)\n+        std::cout << ' ' << *it;\n+    if (is_hole())\n+        std::cout << \" hole, parent=\" << get_parent();\n+    else {\n+        std::cout << \" not hole\";\n+        if (!_children.empty()) {\n+            std::cout << \", children=\";\n+            for (Children::const_iterator it = _children.begin();\n+                 it != _children.end(); ++it)\n+                std::cout << *it << ' ';\n+        }\n+    }\n+    std::cout << std::endl;\n+}\n+\n+\n+\n+Contour::Contour()\n+{}\n+\n+Contour::~Contour()\n+{\n+    delete_contour_lines();\n+}\n+\n+void Contour::delete_contour_lines()\n+{\n+    for (iterator line_it = begin(); line_it != end(); ++line_it) {\n+        delete *line_it;\n+        *line_it = 0;\n+    }\n+    std::vector<ContourLine*>::clear();\n+}\n+\n+void Contour::write() const\n+{\n+    std::cout << \"Contour of \" << size() << \" lines.\" << std::endl;\n+    for (const_iterator it = begin(); it != end(); ++it)\n+        (*it)->write();\n+}\n+\n+\n+\n+ParentCache::ParentCache(long nx, long x_chunk_points, long y_chunk_points)\n+    : _nx(nx),\n+      _x_chunk_points(x_chunk_points),\n+      _y_chunk_points(y_chunk_points),\n+      _lines(0),  // Initialised when first needed.\n+      _istart(0),\n+      _jstart(0)\n+{\n+    assert(_x_chunk_points > 0 && _y_chunk_points > 0 &&\n+           \"Chunk sizes must be positive\");\n+}\n+\n+ContourLine* ParentCache::get_parent(long quad)\n+{\n+    long index = quad_to_index(quad);\n+    ContourLine* parent = _lines[index];\n+    while (parent == 0) {\n+        index -= _x_chunk_points;\n+        assert(index >= 0 && \"Failed to find parent in chunk ParentCache\");\n+        parent = _lines[index];\n+    }\n+    assert(parent != 0 && \"Failed to find parent in chunk ParentCache\");\n+    return parent;\n+}\n+\n+long ParentCache::quad_to_index(long quad) const\n+{\n+    long i = quad % _nx;\n+    long j = quad / _nx;\n+    long index = (i-_istart) + (j-_jstart)*_x_chunk_points;\n+\n+    assert(i >= _istart && i < _istart + _x_chunk_points &&\n+           \"i-index outside chunk\");\n+    assert(j >= _jstart && j < _jstart + _y_chunk_points &&\n+           \"j-index outside chunk\");\n+    assert(index >= 0 && index < static_cast<long>(_lines.size()) &&\n+           \"ParentCache index outside chunk\");\n+\n+    return index;\n+}\n+\n+void ParentCache::set_chunk_starts(long istart, long jstart)\n+{\n+    assert(istart >= 0 && jstart >= 0 &&\n+           \"Chunk start indices cannot be negative\");\n+    _istart = istart;\n+    _jstart = jstart;\n+    if (_lines.empty())\n+        _lines.resize(_x_chunk_points*_y_chunk_points, 0);\n+    else\n+        std::fill(_lines.begin(), _lines.end(), (ContourLine*)0);\n+}\n+\n+void ParentCache::set_parent(long quad, ContourLine& contour_line)\n+{\n+    assert(!_lines.empty() &&\n+           \"Accessing ParentCache before it has been initialised\");\n+    long index = quad_to_index(quad);\n+    if (_lines[index] == 0)\n+        _lines[index] = (contour_line.is_hole() ? contour_line.get_parent()\n+                                                : &contour_line);\n+}\n+\n+\n+\n+QuadContourGenerator::QuadContourGenerator(const CoordinateArray& x,\n+                                           const CoordinateArray& y,\n+                                           const CoordinateArray& z,\n+                                           const MaskArray& mask,\n+                                           bool corner_mask,\n+                                           long chunk_size)\n+    : _x(x),\n+      _y(y),\n+      _z(z),\n+      _nx(static_cast<long>(_x.dim(1))),\n+      _ny(static_cast<long>(_x.dim(0))),\n+      _n(_nx*_ny),\n+      _corner_mask(corner_mask),\n+      _chunk_size(chunk_size > 0 ? std::min(chunk_size, std::max(_nx, _ny)-1)\n+                                 : std::max(_nx, _ny)-1),\n+      _nxchunk(calc_chunk_count(_nx)),\n+      _nychunk(calc_chunk_count(_ny)),\n+      _chunk_count(_nxchunk*_nychunk),\n+      _cache(new CacheItem[_n]),\n+      _parent_cache(_nx,\n+                    chunk_size > 0 ? chunk_size+1 : _nx,\n+                    chunk_size > 0 ? chunk_size+1 : _ny)\n+{\n+    assert(!_x.empty() && !_y.empty() && !_z.empty() && \"Empty array\");\n+    assert(_y.dim(0) == _x.dim(0) && _y.dim(1) == _x.dim(1) &&\n+           \"Different-sized y and x arrays\");\n+    assert(_z.dim(0) == _x.dim(0) && _z.dim(1) == _x.dim(1) &&\n+           \"Different-sized z and x arrays\");\n+    assert((mask.empty() ||\n+           (mask.dim(0) == _x.dim(0) && mask.dim(1) == _x.dim(1))) &&\n+           \"Different-sized mask and x arrays\");\n+\n+    init_cache_grid(mask);\n+}\n+\n+QuadContourGenerator::~QuadContourGenerator()\n+{\n+    delete [] _cache;\n+}\n+\n+void QuadContourGenerator::append_contour_line_to_segs(\n+    ContourLine& contour_line,\n+    PyObject* segs_list) const\n+{\n+    assert(segs_list != 0 && \"Null python segs_list\");\n+\n+    // Convert ContourLine to python equivalent, and clear it.\n+    npy_intp dims[2] = {static_cast<npy_intp>(contour_line.size()), 2};\n+    numpy::array_view<double, 2> line(dims);\n+    npy_intp i = 0;\n+    for (ContourLine::const_iterator point = contour_line.begin();\n+         point != contour_line.end(); ++point, ++i) {\n+        line(i, 0) = point->x;\n+        line(i, 1) = point->y;\n+    }\n+    if (PyList_Append(segs_list, line.pyobj())) {\n+        Py_XDECREF(segs_list);\n+        throw \"Unable to add contour line to segs list\";\n+    }\n+\n+    contour_line.clear();\n+}\n+\n+void QuadContourGenerator::append_contour_to_segs_and_kinds(\n+    Contour& contour,\n+    PyObject* segs_and_kinds_list) const\n+{\n+    assert(segs_and_kinds_list != 0 && \"Null python segs_and_kinds_list\");\n+\n+    // Convert Contour to python equivalent, and clear it.\n+    for (Contour::iterator line_it = contour.begin(); line_it != contour.end();\n+         ++line_it) {\n+        ContourLine& line = **line_it;\n+        if (line.is_hole()) {\n+            // If hole has already been converted to python its parent will be\n+            // set to 0 and it can be deleted.\n+            if (line.get_parent() != 0) {\n+                delete *line_it;\n+                *line_it = 0;\n+            }\n+        }\n+        else {\n+            // Non-holes are converted to python together with their child\n+            // holes so that they are rendered correctly.\n+            ContourLine::Children::const_iterator children_it;\n+\n+            const ContourLine::Children& children = line.get_children();\n+            npy_intp npoints = static_cast<npy_intp>(line.size());\n+            for (children_it = children.begin(); children_it != children.end();\n+                 ++children_it)\n+                 npoints += static_cast<npy_intp>((*children_it)->size());\n+\n+            npy_intp segs_dims[2] = {npoints, 2};\n+            numpy::array_view<double, 2> segs(segs_dims);\n+            double* segs_ptr = segs.data();\n+\n+            npy_intp kinds_dims[1] = {npoints};\n+            numpy::array_view<unsigned char, 1> kinds(kinds_dims);\n+            unsigned char* kinds_ptr = kinds.data();\n+\n+            for (ContourLine::const_iterator point = line.begin();\n+                 point != line.end(); ++point) {\n+                *segs_ptr++ = point->x;\n+                *segs_ptr++ = point->y;\n+                *kinds_ptr++ = (point == line.begin() ? MOVETO : LINETO);\n+            }\n+            for (children_it = children.begin(); children_it != children.end();\n+                 ++children_it) {\n+                ContourLine& child = **children_it;\n+                for (ContourLine::const_iterator point = child.begin();\n+                     point != child.end(); ++point) {\n+                    *segs_ptr++ = point->x;\n+                    *segs_ptr++ = point->y;\n+                    *kinds_ptr++ = (point == child.begin() ? MOVETO : LINETO);\n+                }\n+                child.clear_parent();  // To indicate it can be deleted.\n+            }\n+\n+            if (PyList_Append(segs_and_kinds_list, segs.pyobj()) ||\n+                PyList_Append(segs_and_kinds_list, kinds.pyobj())) {\n+                Py_XDECREF(segs_and_kinds_list);\n+                contour.delete_contour_lines();\n+                throw \"Unable to add contour line to segs list\";\n+            }\n+\n+            delete *line_it;\n+            *line_it = 0;\n+        }\n+    }\n+\n+    // Delete remaining contour lines.\n+    contour.delete_contour_lines();\n+}\n+\n+long QuadContourGenerator::calc_chunk_count(long point_count) const\n+{\n+    assert(point_count > 0 && \"point count must be positive\");\n+    assert(_chunk_size > 0 && \"Chunk size must be positive\");\n+\n+    if (_chunk_size > 0) {\n+        long count = (point_count-1) / _chunk_size;\n+        if (count*_chunk_size < point_count-1)\n+            ++count;\n+\n+        assert(count >= 1 && \"Invalid chunk count\");\n+        return count;\n+    }\n+    else\n+        return 1;\n+}\n+\n+PyObject* QuadContourGenerator::create_contour(const double& level)","path":"src/_contour.cpp","position":494,"original_position":494,"commit_id":"556b4d141d4c024f2c9c8d56df40d67e565be033","original_commit_id":"556b4d141d4c024f2c9c8d56df40d67e565be033","user":{"login":"pelson","id":810663,"avatar_url":"https://avatars.githubusercontent.com/u/810663?v=3","gravatar_id":"","url":"https://api.github.com/users/pelson","html_url":"https://github.com/pelson","followers_url":"https://api.github.com/users/pelson/followers","following_url":"https://api.github.com/users/pelson/following{/other_user}","gists_url":"https://api.github.com/users/pelson/gists{/gist_id}","starred_url":"https://api.github.com/users/pelson/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pelson/subscriptions","organizations_url":"https://api.github.com/users/pelson/orgs","repos_url":"https://api.github.com/users/pelson/repos","events_url":"https://api.github.com/users/pelson/events{/privacy}","received_events_url":"https://api.github.com/users/pelson/received_events","type":"User","site_admin":false},"body":"Ah, nope, sorry, missed that.","created_at":"2014-12-03T17:50:19Z","updated_at":"2014-12-03T17:50:19Z","html_url":"https://github.com/matplotlib/matplotlib/pull/3874#discussion_r21249935","pull_request_url":"https://api.github.com/repos/matplotlib/matplotlib/pulls/3874","_links":{"self":{"href":"https://api.github.com/repos/matplotlib/matplotlib/pulls/comments/21249935"},"html":{"href":"https://github.com/matplotlib/matplotlib/pull/3874#discussion_r21249935"},"pull_request":{"href":"https://api.github.com/repos/matplotlib/matplotlib/pulls/3874"}}},"pull_request":{"url":"https://api.github.com/repos/matplotlib/matplotlib/pulls/3874","id":25376845,"html_url":"https://github.com/matplotlib/matplotlib/pull/3874","diff_url":"https://github.com/matplotlib/matplotlib/pull/3874.diff","patch_url":"https://github.com/matplotlib/matplotlib/pull/3874.patch","issue_url":"https://api.github.com/repos/matplotlib/matplotlib/issues/3874","number":3874,"state":"open","locked":false,"title":"New C++ contour code with corner_mask kwarg","user":{"login":"ianthomas23","id":580326,"avatar_url":"https://avatars.githubusercontent.com/u/580326?v=3","gravatar_id":"","url":"https://api.github.com/users/ianthomas23","html_url":"https://github.com/ianthomas23","followers_url":"https://api.github.com/users/ianthomas23/followers","following_url":"https://api.github.com/users/ianthomas23/following{/other_user}","gists_url":"https://api.github.com/users/ianthomas23/gists{/gist_id}","starred_url":"https://api.github.com/users/ianthomas23/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ianthomas23/subscriptions","organizations_url":"https://api.github.com/users/ianthomas23/orgs","repos_url":"https://api.github.com/users/ianthomas23/repos","events_url":"https://api.github.com/users/ianthomas23/events{/privacy}","received_events_url":"https://api.github.com/users/ianthomas23/received_events","type":"User","site_admin":false},"body":"This is a new C++ extension module that calculates contours to add support for different ways of dealing with masked arrays using a new `corner_mask` keyword argument.  It was originally suggested by @efiring a number of years (!) ago.  The current behaviour, which is now obtained using `corner_mask=False`, was for a single masked point to completely mask out all 4 quads touching that point.  The new behaviour, obtained using `corner_mask=True`, masks the corners of those quads touching the point but any triangular corners comprising three unmasked points on the opposite sides of those quads are contoured as usual.  The diagram below should make this clearer; red circles indicate masked points.\r\n![contour_corner_mask](https://cloud.githubusercontent.com/assets/580326/5270690/456d2508-7a65-11e4-8f13-cb503f4d9ea2.png)\r\nI intend this code to eventually replace the existing C contouring module (cntr.c), if/when it is proven to be robust enough.  I did not attempt to add the new functionality to cntr.c as it is a little too cryptic for my taste and I could not see how to shoehorn the new functionality into it.  I have kept the existing code, however, and it can be accessed using `corner_mask='legacy'`.  This allows the results of the old and new codes to be compared, and allows us to easily fall back to the old code if I have messed up.\r\n\r\nIf the `corner_mask` is not specified in a `contour` or `contourf` call, then new rcParam `contour.corner_mask` is used instead.  This defaults to `True`, on the basis that if we have improved functionality it should be the default, and is therefore an API change.\r\n\r\nI have modified the chunking behaviour (obtained using the kwarg `nchunk`).  Previously it only applied to `contourf`, in the new extension if applies to `contour` as well.  Also, previously it divided the domain into roughly nchunk-by-nchunk quads, now it is exactly nchunk-by-nchunk quads.  I prefer this as the chunks are at predictable, regularly-spaced intervals making it easier to e.g. align them with grid lines to minimise the effects of rendering artifacts that chunking can give you.  This is another API change, although not a significant one as I believe chunking is rarely used.\r\n\r\nIn the absence of chunking I have tried to make the outputs of `corner_mask=False` and `corner_mask='legacy'` as similar as possible.  I've been mostly successful as image diffs of standard situations usually turn out completely black.  But this does not always work as (1) the contoured polygons occur in a different order, and (2) the polygons' points are determined using floating-point math so will not necessarily be exactly the same.\r\n\r\nWhat I would like is for other developers/users who regularly generate contour plots (especially of masked arrays) to try out the new functionality.  Code of this complexity will invariable have some bugs in it, i.e. I won't have done a perfect job!  As well as picking holes in the implementation, there are a few obvious decisions I have made that others may question:\r\n\r\n1. Name of the `corner_mask` keyword argument.\r\n2. Default rcParam of `contour.corner_mask=True`.\r\n3. Chunking changes.\r\n\r\nFinally, I've added a couple of image tests and an example.  I have made the minimum number of changes to the python code, although I have added code to `contour.py` to remove a mask that is all `False`.  I have had to change the images generated by test_patheffects.test_collection as some default label positions have changed, presumably because the contoured polygons are in a different order.  And I've fixed a couple of typos in `_tri_wrapper.cpp` that I put in as part of the CXX removal.","created_at":"2014-12-02T21:42:32Z","updated_at":"2014-12-03T17:50:19Z","closed_at":null,"merged_at":null,"merge_commit_sha":"30ed4521d6e382d02b5fe2d6e859a7b9ded2da5b","assignee":null,"milestone":{"url":"https://api.github.com/repos/matplotlib/matplotlib/milestones/12","labels_url":"https://api.github.com/repos/matplotlib/matplotlib/milestones/12/labels","id":534144,"number":12,"title":"v1.5.x","description":null,"creator":{"login":"tacaswell","id":199813,"avatar_url":"https://avatars.githubusercontent.com/u/199813?v=3","gravatar_id":"","url":"https://api.github.com/users/tacaswell","html_url":"https://github.com/tacaswell","followers_url":"https://api.github.com/users/tacaswell/followers","following_url":"https://api.github.com/users/tacaswell/following{/other_user}","gists_url":"https://api.github.com/users/tacaswell/gists{/gist_id}","starred_url":"https://api.github.com/users/tacaswell/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tacaswell/subscriptions","organizations_url":"https://api.github.com/users/tacaswell/orgs","repos_url":"https://api.github.com/users/tacaswell/repos","events_url":"https://api.github.com/users/tacaswell/events{/privacy}","received_events_url":"https://api.github.com/users/tacaswell/received_events","type":"User","site_admin":false},"open_issues":228,"closed_issues":122,"state":"open","created_at":"2014-01-13T03:21:09Z","updated_at":"2014-12-03T15:13:56Z","due_on":null,"closed_at":null},"commits_url":"https://api.github.com/repos/matplotlib/matplotlib/pulls/3874/commits","review_comments_url":"https://api.github.com/repos/matplotlib/matplotlib/pulls/3874/comments","review_comment_url":"https://api.github.com/repos/matplotlib/matplotlib/pulls/comments/{number}","comments_url":"https://api.github.com/repos/matplotlib/matplotlib/issues/3874/comments","statuses_url":"https://api.github.com/repos/matplotlib/matplotlib/statuses/556b4d141d4c024f2c9c8d56df40d67e565be033","head":{"label":"ianthomas23:contour","ref":"contour","sha":"556b4d141d4c024f2c9c8d56df40d67e565be033","user":{"login":"ianthomas23","id":580326,"avatar_url":"https://avatars.githubusercontent.com/u/580326?v=3","gravatar_id":"","url":"https://api.github.com/users/ianthomas23","html_url":"https://github.com/ianthomas23","followers_url":"https://api.github.com/users/ianthomas23/followers","following_url":"https://api.github.com/users/ianthomas23/following{/other_user}","gists_url":"https://api.github.com/users/ianthomas23/gists{/gist_id}","starred_url":"https://api.github.com/users/ianthomas23/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ianthomas23/subscriptions","organizations_url":"https://api.github.com/users/ianthomas23/orgs","repos_url":"https://api.github.com/users/ianthomas23/repos","events_url":"https://api.github.com/users/ianthomas23/events{/privacy}","received_events_url":"https://api.github.com/users/ianthomas23/received_events","type":"User","site_admin":false},"repo":{"id":1937664,"name":"matplotlib","full_name":"ianthomas23/matplotlib","owner":{"login":"ianthomas23","id":580326,"avatar_url":"https://avatars.githubusercontent.com/u/580326?v=3","gravatar_id":"","url":"https://api.github.com/users/ianthomas23","html_url":"https://github.com/ianthomas23","followers_url":"https://api.github.com/users/ianthomas23/followers","following_url":"https://api.github.com/users/ianthomas23/following{/other_user}","gists_url":"https://api.github.com/users/ianthomas23/gists{/gist_id}","starred_url":"https://api.github.com/users/ianthomas23/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ianthomas23/subscriptions","organizations_url":"https://api.github.com/users/ianthomas23/orgs","repos_url":"https://api.github.com/users/ianthomas23/repos","events_url":"https://api.github.com/users/ianthomas23/events{/privacy}","received_events_url":"https://api.github.com/users/ianthomas23/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/ianthomas23/matplotlib","description":"","fork":true,"url":"https://api.github.com/repos/ianthomas23/matplotlib","forks_url":"https://api.github.com/repos/ianthomas23/matplotlib/forks","keys_url":"https://api.github.com/repos/ianthomas23/matplotlib/keys{/key_id}","collaborators_url":"https://api.github.com/repos/ianthomas23/matplotlib/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/ianthomas23/matplotlib/teams","hooks_url":"https://api.github.com/repos/ianthomas23/matplotlib/hooks","issue_events_url":"https://api.github.com/repos/ianthomas23/matplotlib/issues/events{/number}","events_url":"https://api.github.com/repos/ianthomas23/matplotlib/events","assignees_url":"https://api.github.com/repos/ianthomas23/matplotlib/assignees{/user}","branches_url":"https://api.github.com/repos/ianthomas23/matplotlib/branches{/branch}","tags_url":"https://api.github.com/repos/ianthomas23/matplotlib/tags","blobs_url":"https://api.github.com/repos/ianthomas23/matplotlib/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/ianthomas23/matplotlib/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/ianthomas23/matplotlib/git/refs{/sha}","trees_url":"https://api.github.com/repos/ianthomas23/matplotlib/git/trees{/sha}","statuses_url":"https://api.github.com/repos/ianthomas23/matplotlib/statuses/{sha}","languages_url":"https://api.github.com/repos/ianthomas23/matplotlib/languages","stargazers_url":"https://api.github.com/repos/ianthomas23/matplotlib/stargazers","contributors_url":"https://api.github.com/repos/ianthomas23/matplotlib/contributors","subscribers_url":"https://api.github.com/repos/ianthomas23/matplotlib/subscribers","subscription_url":"https://api.github.com/repos/ianthomas23/matplotlib/subscription","commits_url":"https://api.github.com/repos/ianthomas23/matplotlib/commits{/sha}","git_commits_url":"https://api.github.com/repos/ianthomas23/matplotlib/git/commits{/sha}","comments_url":"https://api.github.com/repos/ianthomas23/matplotlib/comments{/number}","issue_comment_url":"https://api.github.com/repos/ianthomas23/matplotlib/issues/comments/{number}","contents_url":"https://api.github.com/repos/ianthomas23/matplotlib/contents/{+path}","compare_url":"https://api.github.com/repos/ianthomas23/matplotlib/compare/{base}...{head}","merges_url":"https://api.github.com/repos/ianthomas23/matplotlib/merges","archive_url":"https://api.github.com/repos/ianthomas23/matplotlib/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/ianthomas23/matplotlib/downloads","issues_url":"https://api.github.com/repos/ianthomas23/matplotlib/issues{/number}","pulls_url":"https://api.github.com/repos/ianthomas23/matplotlib/pulls{/number}","milestones_url":"https://api.github.com/repos/ianthomas23/matplotlib/milestones{/number}","notifications_url":"https://api.github.com/repos/ianthomas23/matplotlib/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/ianthomas23/matplotlib/labels{/name}","releases_url":"https://api.github.com/repos/ianthomas23/matplotlib/releases{/id}","created_at":"2011-06-22T21:04:33Z","updated_at":"2014-12-01T21:04:19Z","pushed_at":"2014-12-02T20:12:38Z","git_url":"git://github.com/ianthomas23/matplotlib.git","ssh_url":"git@github.com:ianthomas23/matplotlib.git","clone_url":"https://github.com/ianthomas23/matplotlib.git","svn_url":"https://github.com/ianthomas23/matplotlib","homepage":"","size":199635,"stargazers_count":2,"watchers_count":2,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":2,"default_branch":"master"}},"base":{"label":"matplotlib:master","ref":"master","sha":"ca10a34e06645c35a98e9ed3db35f6a8efee70f2","user":{"login":"matplotlib","id":215947,"avatar_url":"https://avatars.githubusercontent.com/u/215947?v=3","gravatar_id":"","url":"https://api.github.com/users/matplotlib","html_url":"https://github.com/matplotlib","followers_url":"https://api.github.com/users/matplotlib/followers","following_url":"https://api.github.com/users/matplotlib/following{/other_user}","gists_url":"https://api.github.com/users/matplotlib/gists{/gist_id}","starred_url":"https://api.github.com/users/matplotlib/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/matplotlib/subscriptions","organizations_url":"https://api.github.com/users/matplotlib/orgs","repos_url":"https://api.github.com/users/matplotlib/repos","events_url":"https://api.github.com/users/matplotlib/events{/privacy}","received_events_url":"https://api.github.com/users/matplotlib/received_events","type":"Organization","site_admin":false},"repo":{"id":1385122,"name":"matplotlib","full_name":"matplotlib/matplotlib","owner":{"login":"matplotlib","id":215947,"avatar_url":"https://avatars.githubusercontent.com/u/215947?v=3","gravatar_id":"","url":"https://api.github.com/users/matplotlib","html_url":"https://github.com/matplotlib","followers_url":"https://api.github.com/users/matplotlib/followers","following_url":"https://api.github.com/users/matplotlib/following{/other_user}","gists_url":"https://api.github.com/users/matplotlib/gists{/gist_id}","starred_url":"https://api.github.com/users/matplotlib/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/matplotlib/subscriptions","organizations_url":"https://api.github.com/users/matplotlib/orgs","repos_url":"https://api.github.com/users/matplotlib/repos","events_url":"https://api.github.com/users/matplotlib/events{/privacy}","received_events_url":"https://api.github.com/users/matplotlib/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/matplotlib/matplotlib","description":"matplotlib: plotting with Python","fork":false,"url":"https://api.github.com/repos/matplotlib/matplotlib","forks_url":"https://api.github.com/repos/matplotlib/matplotlib/forks","keys_url":"https://api.github.com/repos/matplotlib/matplotlib/keys{/key_id}","collaborators_url":"https://api.github.com/repos/matplotlib/matplotlib/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/matplotlib/matplotlib/teams","hooks_url":"https://api.github.com/repos/matplotlib/matplotlib/hooks","issue_events_url":"https://api.github.com/repos/matplotlib/matplotlib/issues/events{/number}","events_url":"https://api.github.com/repos/matplotlib/matplotlib/events","assignees_url":"https://api.github.com/repos/matplotlib/matplotlib/assignees{/user}","branches_url":"https://api.github.com/repos/matplotlib/matplotlib/branches{/branch}","tags_url":"https://api.github.com/repos/matplotlib/matplotlib/tags","blobs_url":"https://api.github.com/repos/matplotlib/matplotlib/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/matplotlib/matplotlib/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/matplotlib/matplotlib/git/refs{/sha}","trees_url":"https://api.github.com/repos/matplotlib/matplotlib/git/trees{/sha}","statuses_url":"https://api.github.com/repos/matplotlib/matplotlib/statuses/{sha}","languages_url":"https://api.github.com/repos/matplotlib/matplotlib/languages","stargazers_url":"https://api.github.com/repos/matplotlib/matplotlib/stargazers","contributors_url":"https://api.github.com/repos/matplotlib/matplotlib/contributors","subscribers_url":"https://api.github.com/repos/matplotlib/matplotlib/subscribers","subscription_url":"https://api.github.com/repos/matplotlib/matplotlib/subscription","commits_url":"https://api.github.com/repos/matplotlib/matplotlib/commits{/sha}","git_commits_url":"https://api.github.com/repos/matplotlib/matplotlib/git/commits{/sha}","comments_url":"https://api.github.com/repos/matplotlib/matplotlib/comments{/number}","issue_comment_url":"https://api.github.com/repos/matplotlib/matplotlib/issues/comments/{number}","contents_url":"https://api.github.com/repos/matplotlib/matplotlib/contents/{+path}","compare_url":"https://api.github.com/repos/matplotlib/matplotlib/compare/{base}...{head}","merges_url":"https://api.github.com/repos/matplotlib/matplotlib/merges","archive_url":"https://api.github.com/repos/matplotlib/matplotlib/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/matplotlib/matplotlib/downloads","issues_url":"https://api.github.com/repos/matplotlib/matplotlib/issues{/number}","pulls_url":"https://api.github.com/repos/matplotlib/matplotlib/pulls{/number}","milestones_url":"https://api.github.com/repos/matplotlib/matplotlib/milestones{/number}","notifications_url":"https://api.github.com/repos/matplotlib/matplotlib/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/matplotlib/matplotlib/labels{/name}","releases_url":"https://api.github.com/repos/matplotlib/matplotlib/releases{/id}","created_at":"2011-02-19T03:17:12Z","updated_at":"2014-12-03T16:39:36Z","pushed_at":"2014-12-03T17:22:48Z","git_url":"git://github.com/matplotlib/matplotlib.git","ssh_url":"git@github.com:matplotlib/matplotlib.git","clone_url":"https://github.com/matplotlib/matplotlib.git","svn_url":"https://github.com/matplotlib/matplotlib","homepage":"http://matplotlib.org/","size":247748,"stargazers_count":2066,"watchers_count":2066,"language":"Python","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1002,"mirror_url":null,"open_issues_count":429,"forks":1002,"open_issues":429,"watchers":2066,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/matplotlib/matplotlib/pulls/3874"},"html":{"href":"https://github.com/matplotlib/matplotlib/pull/3874"},"issue":{"href":"https://api.github.com/repos/matplotlib/matplotlib/issues/3874"},"comments":{"href":"https://api.github.com/repos/matplotlib/matplotlib/issues/3874/comments"},"review_comments":{"href":"https://api.github.com/repos/matplotlib/matplotlib/pulls/3874/comments"},"review_comment":{"href":"https://api.github.com/repos/matplotlib/matplotlib/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/matplotlib/matplotlib/pulls/3874/commits"},"statuses":{"href":"https://api.github.com/repos/matplotlib/matplotlib/statuses/556b4d141d4c024f2c9c8d56df40d67e565be033"}}}}