Skip to content

Commit 178012d

Browse files
committed
Return kind codes from C++ contour and tricontour
1 parent 1b15a11 commit 178012d

File tree

7 files changed

+183
-122
lines changed

7 files changed

+183
-122
lines changed

lib/matplotlib/contour.py

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -830,9 +830,6 @@ def __init__(self, ax, *args,
830830
_api.warn_external('linewidths is ignored by contourf')
831831
# Lower and upper contour levels.
832832
lowers, uppers = self._get_lowers_and_uppers()
833-
# Ensure allkinds can be zipped below.
834-
if self.allkinds is None:
835-
self.allkinds = [None] * len(self.allsegs)
836833
# Default zorder taken from Collection
837834
self._contour_zorder = kwargs.pop('zorder', 1)
838835

@@ -858,7 +855,7 @@ def __init__(self, ax, *args,
858855

859856
self.collections[:] = [
860857
mcoll.PathCollection(
861-
self._make_paths(segs, None),
858+
self._make_paths(segs, kinds),
862859
facecolors="none",
863860
antialiaseds=aa,
864861
linewidths=width,
@@ -867,8 +864,9 @@ def __init__(self, ax, *args,
867864
transform=self.get_transform(),
868865
zorder=self._contour_zorder,
869866
label='_nolegend_')
870-
for level, width, lstyle, segs
871-
in zip(self.levels, tlinewidths, tlinestyles, self.allsegs)]
867+
for level, width, lstyle, segs, kinds
868+
in zip(self.levels, tlinewidths, tlinestyles, self.allsegs,
869+
self.allkinds)]
872870

873871
for col in self.collections:
874872
self.axes.add_collection(col, autolim=False)
@@ -1000,11 +998,23 @@ def _process_args(self, *args, **kwargs):
1000998
return kwargs
1001999

10021000
def _get_allsegs_and_allkinds(self):
1003-
"""
1004-
Override in derived classes to create and return allsegs and allkinds.
1005-
allkinds can be None.
1006-
"""
1007-
return self.allsegs, self.allkinds
1001+
"""Compute ``allsegs`` and ``allkinds`` using C extension."""
1002+
allsegs = []
1003+
allkinds = []
1004+
if self.filled:
1005+
lowers, uppers = self._get_lowers_and_uppers()
1006+
for level, level_upper in zip(lowers, uppers):
1007+
vertices, kinds = \
1008+
self._contour_generator.create_filled_contour(
1009+
level, level_upper)
1010+
allsegs.append(vertices)
1011+
allkinds.append(kinds)
1012+
else:
1013+
for level in self.levels:
1014+
vertices, kinds = self._contour_generator.create_contour(level)
1015+
allsegs.append(vertices)
1016+
allkinds.append(kinds)
1017+
return allsegs, allkinds
10081018

10091019
def _get_lowers_and_uppers(self):
10101020
"""
@@ -1022,14 +1032,7 @@ def _get_lowers_and_uppers(self):
10221032
return (lowers, uppers)
10231033

10241034
def _make_paths(self, segs, kinds):
1025-
if kinds is not None:
1026-
return [mpath.Path(seg, codes=kind)
1027-
for seg, kind in zip(segs, kinds)]
1028-
else:
1029-
# Explicit comparison of first and last points in segment is faster
1030-
# than the more elegant np.array_equal(seg[0], seg[-1]).
1031-
return [mpath.Path(seg, closed=(seg[0, 0] == seg[-1, 0]
1032-
and seg[0, 1] == seg[-1, 1])) for seg in segs]
1035+
return [mpath.Path(seg, codes=kind) for seg, kind in zip(segs, kinds)]
10331036

10341037
def changed(self):
10351038
tcolors = [(tuple(rgba),)
@@ -1396,25 +1399,6 @@ def _process_args(self, *args, corner_mask=None, **kwargs):
13961399

13971400
return kwargs
13981401

1399-
def _get_allsegs_and_allkinds(self):
1400-
"""Compute ``allsegs`` and ``allkinds`` using C extension."""
1401-
allsegs = []
1402-
if self.filled:
1403-
lowers, uppers = self._get_lowers_and_uppers()
1404-
allkinds = []
1405-
for level, level_upper in zip(lowers, uppers):
1406-
vertices, kinds = \
1407-
self._contour_generator.create_filled_contour(
1408-
level, level_upper)
1409-
allsegs.append(vertices)
1410-
allkinds.append(kinds)
1411-
else:
1412-
allkinds = None
1413-
for level in self.levels:
1414-
vertices = self._contour_generator.create_contour(level)
1415-
allsegs.append(vertices)
1416-
return allsegs, allkinds
1417-
14181402
def _contour_args(self, args, kwargs):
14191403
if self.filled:
14201404
fn = 'contourf'
Loading

lib/matplotlib/tri/tricontour.py

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def _process_args(self, *args, **kwargs):
3333
Process args and kwargs.
3434
"""
3535
if isinstance(args[0], TriContourSet):
36-
C = args[0].cppContourGenerator
36+
C = args[0]._contour_generator
3737
if self.levels is None:
3838
self.levels = args[0].levels
3939
else:
@@ -43,29 +43,9 @@ def _process_args(self, *args, **kwargs):
4343
self._mins = [tri.x.min(), tri.y.min()]
4444
self._maxs = [tri.x.max(), tri.y.max()]
4545

46-
self.cppContourGenerator = C
46+
self._contour_generator = C
4747
return kwargs
4848

49-
def _get_allsegs_and_allkinds(self):
50-
"""
51-
Create and return allsegs and allkinds by calling underlying C code.
52-
"""
53-
allsegs = []
54-
if self.filled:
55-
lowers, uppers = self._get_lowers_and_uppers()
56-
allkinds = []
57-
for lower, upper in zip(lowers, uppers):
58-
segs, kinds = self.cppContourGenerator.create_filled_contour(
59-
lower, upper)
60-
allsegs.append([segs])
61-
allkinds.append([kinds])
62-
else:
63-
allkinds = None
64-
for level in self.levels:
65-
segs = self.cppContourGenerator.create_contour(level)
66-
allsegs.append(segs)
67-
return allsegs, allkinds
68-
6949
def _contour_args(self, args, kwargs):
7050
if self.filled:
7151
fn = 'contourf'

src/_contour.cpp

Lines changed: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -381,24 +381,40 @@ QuadContourGenerator::~QuadContourGenerator()
381381
delete [] _cache;
382382
}
383383

384-
void QuadContourGenerator::append_contour_line_to_vertices(
384+
void QuadContourGenerator::append_contour_line_to_vertices_and_codes(
385385
ContourLine& contour_line,
386-
PyObject* vertices_list) const
386+
PyObject* vertices_list,
387+
PyObject* codes_list) const
387388
{
388389
assert(vertices_list != 0 && "Null python vertices_list");
390+
assert(codes_list != 0 && "Null python codes_list");
389391

390392
// Convert ContourLine to python equivalent, and clear it.
391-
npy_intp dims[2] = {static_cast<npy_intp>(contour_line.size()), 2};
392-
numpy::array_view<double, 2> line(dims);
393-
npy_intp i = 0;
393+
npy_intp npoints = static_cast<npy_intp>(contour_line.size());
394+
395+
npy_intp vertices_dims[2] = {npoints, 2};
396+
numpy::array_view<double, 2> vertices(vertices_dims);
397+
double* vertices_ptr = vertices.data();
398+
399+
npy_intp codes_dims[1] = {npoints};
400+
numpy::array_view<unsigned char, 1> codes(codes_dims);
401+
unsigned char* codes_ptr = codes.data();
402+
394403
for (ContourLine::const_iterator point = contour_line.begin();
395-
point != contour_line.end(); ++point, ++i) {
396-
line(i, 0) = point->x;
397-
line(i, 1) = point->y;
404+
point != contour_line.end(); ++point) {
405+
*vertices_ptr++ = point->x;
406+
*vertices_ptr++ = point->y;
407+
*codes_ptr++ = (point == contour_line.begin() ? MOVETO : LINETO);
398408
}
399-
if (PyList_Append(vertices_list, line.pyobj_steal())) {
409+
410+
if (contour_line.size() > 1 && contour_line.front() == contour_line.back())
411+
*(codes_ptr-1) = CLOSEPOLY;
412+
413+
if (PyList_Append(vertices_list, vertices.pyobj_steal()) ||
414+
PyList_Append(codes_list, codes.pyobj_steal())) {
400415
Py_XDECREF(vertices_list);
401-
throw std::runtime_error("Unable to add contour line to vertices_list");
416+
Py_XDECREF(codes_list);
417+
throw std::runtime_error("Unable to add contour line to vertices and codes lists");
402418
}
403419

404420
contour_line.clear();
@@ -512,6 +528,12 @@ PyObject* QuadContourGenerator::create_contour(const double& level)
512528
if (vertices_list == 0)
513529
throw std::runtime_error("Failed to create Python list");
514530

531+
PyObject* codes_list = PyList_New(0);
532+
if (codes_list == 0) {
533+
Py_XDECREF(vertices_list);
534+
throw std::runtime_error("Failed to create Python list");
535+
}
536+
515537
// Lines that start and end on boundaries.
516538
long ichunk, jchunk, istart, iend, jstart, jend;
517539
for (long ijchunk = 0; ijchunk < _chunk_count; ++ijchunk) {
@@ -523,33 +545,33 @@ PyObject* QuadContourGenerator::create_contour(const double& level)
523545
if (EXISTS_NONE(quad) || VISITED(quad,1)) continue;
524546

525547
if (BOUNDARY_S(quad) && Z_SW >= 1 && Z_SE < 1 &&
526-
start_line(vertices_list, quad, Edge_S, level)) continue;
548+
start_line(vertices_list, codes_list, quad, Edge_S, level)) continue;
527549

528550
if (BOUNDARY_W(quad) && Z_NW >= 1 && Z_SW < 1 &&
529-
start_line(vertices_list, quad, Edge_W, level)) continue;
551+
start_line(vertices_list, codes_list, quad, Edge_W, level)) continue;
530552

531553
if (BOUNDARY_N(quad) && Z_NE >= 1 && Z_NW < 1 &&
532-
start_line(vertices_list, quad, Edge_N, level)) continue;
554+
start_line(vertices_list, codes_list, quad, Edge_N, level)) continue;
533555

534556
if (BOUNDARY_E(quad) && Z_SE >= 1 && Z_NE < 1 &&
535-
start_line(vertices_list, quad, Edge_E, level)) continue;
557+
start_line(vertices_list, codes_list, quad, Edge_E, level)) continue;
536558

537559
if (_corner_mask) {
538560
// Equates to NE boundary.
539561
if (EXISTS_SW_CORNER(quad) && Z_SE >= 1 && Z_NW < 1 &&
540-
start_line(vertices_list, quad, Edge_NE, level)) continue;
562+
start_line(vertices_list, codes_list, quad, Edge_NE, level)) continue;
541563

542564
// Equates to NW boundary.
543565
if (EXISTS_SE_CORNER(quad) && Z_NE >= 1 && Z_SW < 1 &&
544-
start_line(vertices_list, quad, Edge_NW, level)) continue;
566+
start_line(vertices_list, codes_list, quad, Edge_NW, level)) continue;
545567

546568
// Equates to SE boundary.
547569
if (EXISTS_NW_CORNER(quad) && Z_SW >= 1 && Z_NE < 1 &&
548-
start_line(vertices_list, quad, Edge_SE, level)) continue;
570+
start_line(vertices_list, codes_list, quad, Edge_SE, level)) continue;
549571

550572
// Equates to SW boundary.
551573
if (EXISTS_NE_CORNER(quad) && Z_NW >= 1 && Z_SE < 1 &&
552-
start_line(vertices_list, quad, Edge_SW, level)) continue;
574+
start_line(vertices_list, codes_list, quad, Edge_SW, level)) continue;
553575
}
554576
}
555577
}
@@ -581,7 +603,9 @@ PyObject* QuadContourGenerator::create_contour(const double& level)
581603
!ignore_first, &start_quad_edge, 1, false);
582604
if (ignore_first && !contour_line.empty())
583605
contour_line.push_back(contour_line.front());
584-
append_contour_line_to_vertices(contour_line, vertices_list);
606+
607+
append_contour_line_to_vertices_and_codes(
608+
contour_line, vertices_list, codes_list);
585609

586610
// Repeat if saddle point but not visited.
587611
if (SADDLE(quad,1) && !VISITED(quad,1))
@@ -590,7 +614,18 @@ PyObject* QuadContourGenerator::create_contour(const double& level)
590614
}
591615
}
592616

593-
return vertices_list;
617+
PyObject* tuple = PyTuple_New(2);
618+
if (tuple == 0) {
619+
Py_XDECREF(vertices_list);
620+
Py_XDECREF(codes_list);
621+
throw std::runtime_error("Failed to create Python tuple");
622+
}
623+
624+
// No error checking here as filling in a brand new pre-allocated tuple.
625+
PyTuple_SET_ITEM(tuple, 0, vertices_list);
626+
PyTuple_SET_ITEM(tuple, 1, codes_list);
627+
628+
return tuple;
594629
}
595630

596631
PyObject* QuadContourGenerator::create_filled_contour(const double& lower_level,
@@ -600,13 +635,13 @@ PyObject* QuadContourGenerator::create_filled_contour(const double& lower_level,
600635

601636
Contour contour;
602637

603-
PyObject* vertices = PyList_New(0);
604-
if (vertices == 0)
638+
PyObject* vertices_list = PyList_New(0);
639+
if (vertices_list == 0)
605640
throw std::runtime_error("Failed to create Python list");
606641

607-
PyObject* codes = PyList_New(0);
608-
if (codes == 0) {
609-
Py_XDECREF(vertices);
642+
PyObject* codes_list = PyList_New(0);
643+
if (codes_list == 0) {
644+
Py_XDECREF(vertices_list);
610645
throw std::runtime_error("Failed to create Python list");
611646
}
612647

@@ -637,19 +672,19 @@ PyObject* QuadContourGenerator::create_filled_contour(const double& lower_level,
637672
}
638673

639674
// Create python objects to return for this chunk.
640-
append_contour_to_vertices_and_codes(contour, vertices, codes);
675+
append_contour_to_vertices_and_codes(contour, vertices_list, codes_list);
641676
}
642677

643678
PyObject* tuple = PyTuple_New(2);
644679
if (tuple == 0) {
645-
Py_XDECREF(vertices);
646-
Py_XDECREF(codes);
680+
Py_XDECREF(vertices_list);
681+
Py_XDECREF(codes_list);
647682
throw std::runtime_error("Failed to create Python tuple");
648683
}
649684

650685
// No error checking here as filling in a brand new pre-allocated tuple.
651-
PyTuple_SET_ITEM(tuple, 0, vertices);
652-
PyTuple_SET_ITEM(tuple, 1, codes);
686+
PyTuple_SET_ITEM(tuple, 0, vertices_list);
687+
PyTuple_SET_ITEM(tuple, 1, codes_list);
653688

654689
return tuple;
655690
}
@@ -1740,7 +1775,8 @@ ContourLine* QuadContourGenerator::start_filled(
17401775
}
17411776

17421777
bool QuadContourGenerator::start_line(
1743-
PyObject* vertices_list, long quad, Edge edge, const double& level)
1778+
PyObject* vertices_list, PyObject* codes_list, long quad, Edge edge,
1779+
const double& level)
17441780
{
17451781
assert(vertices_list != 0 && "Null python vertices list");
17461782
assert(is_edge_a_boundary(QuadEdge(quad, edge)) &&
@@ -1749,7 +1785,10 @@ bool QuadContourGenerator::start_line(
17491785
QuadEdge quad_edge(quad, edge);
17501786
ContourLine contour_line(false);
17511787
follow_interior(contour_line, quad_edge, 1, level, true, 0, 1, false);
1752-
append_contour_line_to_vertices(contour_line, vertices_list);
1788+
1789+
append_contour_line_to_vertices_and_codes(
1790+
contour_line, vertices_list, codes_list);
1791+
17531792
return VISITED(quad,1);
17541793
}
17551794

src/_contour.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,12 +318,13 @@ class QuadContourGenerator
318318
Hole
319319
} HoleOrNot;
320320

321-
// Append a C++ ContourLine to the end of a python list. Used for line
321+
// Append a C++ ContourLine to the end of two python lists. Used for line
322322
// contours where each ContourLine is converted to a separate numpy array
323323
// of (x,y) points.
324324
// Clears the ContourLine too.
325-
void append_contour_line_to_vertices(ContourLine& contour_line,
326-
PyObject* vertices_list) const;
325+
void append_contour_line_to_vertices_and_codes(ContourLine& contour_line,
326+
PyObject* vertices_list,
327+
PyObject* codes_list) const;
327328

328329
// Append a C++ Contour to the end of two python lists. Used for filled
329330
// contours where each non-hole ContourLine and its child holes are
@@ -488,12 +489,14 @@ class QuadContourGenerator
488489
// Start and complete a line contour that both starts and end on a
489490
// boundary, traversing the interior of the domain.
490491
// vertices_list: Python list that the ContourLine should be appended to.
492+
// codes_list: Python list that the kind codes should be appended to.
491493
// quad: index of quad to start ContourLine in.
492494
// edge: boundary edge to start ContourLine from.
493495
// level: contour z-value.
494496
// Returns true if the start quad does not need to be visited again, i.e.
495497
// VISITED(quad,1).
496498
bool start_line(PyObject* vertices_list,
499+
PyObject* codes_list,
497500
long quad,
498501
Edge edge,
499502
const double& level);

0 commit comments

Comments
 (0)