Skip to content

Commit 096b1d0

Browse files
authored
Merge pull request #8227 from ianthomas23/8197_contour_1x1_array
Contouring 1x1 array (issue #8197)
2 parents 5594d91 + 5ea5d43 commit 096b1d0

File tree

5 files changed

+159
-30
lines changed

5 files changed

+159
-30
lines changed

lib/matplotlib/contour.py

+4
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,8 @@ def _check_xyz(self, args, kwargs):
15421542

15431543
if z.ndim != 2:
15441544
raise TypeError("Input z must be a 2D array.")
1545+
elif z.shape[0] < 2 or z.shape[1] < 2:
1546+
raise TypeError("Input z must be at least a 2x2 array.")
15451547
else:
15461548
Ny, Nx = z.shape
15471549

@@ -1590,6 +1592,8 @@ def _initialize_x_y(self, z):
15901592
"""
15911593
if z.ndim != 2:
15921594
raise TypeError("Input must be a 2D array.")
1595+
elif z.shape[0] < 2 or z.shape[1] < 2:
1596+
raise TypeError("Input z must be at least a 2x2 array.")
15931597
else:
15941598
Ny, Nx = z.shape
15951599
if self.origin is None: # Not for image-matching.

lib/matplotlib/tests/test_contour.py

+61-30
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,9 @@ def test_contour_shape_mismatch_1():
4646
fig = plt.figure()
4747
ax = fig.add_subplot(111)
4848

49-
try:
49+
with pytest.raises(TypeError) as excinfo:
5050
ax.contour(x, y, z)
51-
except TypeError as exc:
52-
assert exc.args[0] == 'Length of x must be number of columns in z.'
51+
excinfo.match(r'Length of x must be number of columns in z.')
5352

5453

5554
def test_contour_shape_mismatch_2():
@@ -61,10 +60,9 @@ def test_contour_shape_mismatch_2():
6160
fig = plt.figure()
6261
ax = fig.add_subplot(111)
6362

64-
try:
63+
with pytest.raises(TypeError) as excinfo:
6564
ax.contour(x, y, z)
66-
except TypeError as exc:
67-
assert exc.args[0] == 'Length of y must be number of rows in z.'
65+
excinfo.match(r'Length of y must be number of rows in z.')
6866

6967

7068
def test_contour_shape_mismatch_3():
@@ -77,15 +75,13 @@ def test_contour_shape_mismatch_3():
7775
fig = plt.figure()
7876
ax = fig.add_subplot(111)
7977

80-
try:
78+
with pytest.raises(TypeError) as excinfo:
8179
ax.contour(xg, y, z)
82-
except TypeError as exc:
83-
assert exc.args[0] == 'Number of dimensions of x and y should match.'
80+
excinfo.match(r'Number of dimensions of x and y should match.')
8481

85-
try:
82+
with pytest.raises(TypeError) as excinfo:
8683
ax.contour(x, yg, z)
87-
except TypeError as exc:
88-
assert exc.args[0] == 'Number of dimensions of x and y should match.'
84+
excinfo.match(r'Number of dimensions of x and y should match.')
8985

9086

9187
def test_contour_shape_mismatch_4():
@@ -97,21 +93,15 @@ def test_contour_shape_mismatch_4():
9793
fig = plt.figure()
9894
ax = fig.add_subplot(111)
9995

100-
try:
96+
with pytest.raises(TypeError) as excinfo:
10197
ax.contour(b, g, z)
102-
except TypeError as exc:
103-
assert re.match(
104-
r'Shape of x does not match that of z: ' +
105-
r'found \(9L?, 9L?\) instead of \(9L?, 10L?\)\.',
106-
exc.args[0]) is not None, exc.args[0]
98+
excinfo.match(r'Shape of x does not match that of z: found \(9L?, 9L?\) ' +
99+
r'instead of \(9L?, 10L?\)')
107100

108-
try:
101+
with pytest.raises(TypeError) as excinfo:
109102
ax.contour(g, b, z)
110-
except TypeError as exc:
111-
assert re.match(
112-
r'Shape of y does not match that of z: ' +
113-
r'found \(9L?, 9L?\) instead of \(9L?, 10L?\)\.',
114-
exc.args[0]) is not None, exc.args[0]
103+
excinfo.match(r'Shape of y does not match that of z: found \(9L?, 9L?\) ' +
104+
r'instead of \(9L?, 10L?\)')
115105

116106

117107
def test_contour_shape_invalid_1():
@@ -123,10 +113,9 @@ def test_contour_shape_invalid_1():
123113
fig = plt.figure()
124114
ax = fig.add_subplot(111)
125115

126-
try:
116+
with pytest.raises(TypeError) as excinfo:
127117
ax.contour(x, y, z)
128-
except TypeError as exc:
129-
assert exc.args[0] == 'Inputs x and y must be 1D or 2D.'
118+
excinfo.match(r'Inputs x and y must be 1D or 2D.')
130119

131120

132121
def test_contour_shape_invalid_2():
@@ -138,10 +127,9 @@ def test_contour_shape_invalid_2():
138127
fig = plt.figure()
139128
ax = fig.add_subplot(111)
140129

141-
try:
130+
with pytest.raises(TypeError) as excinfo:
142131
ax.contour(x, y, z)
143-
except TypeError as exc:
144-
assert exc.args[0] == 'Input z must be a 2D array.'
132+
excinfo.match(r'Input z must be a 2D array.')
145133

146134

147135
@image_comparison(baseline_images=['contour_manual_labels'])
@@ -309,3 +297,46 @@ def test_contourf_symmetric_locator():
309297
locator = plt.MaxNLocator(nbins=4, symmetric=True)
310298
cs = plt.contourf(z, locator=locator)
311299
assert_array_almost_equal(cs.levels, np.linspace(-12, 12, 5))
300+
301+
302+
def test_contour_1x1_array():
303+
# github issue 8197
304+
with pytest.raises(TypeError) as excinfo:
305+
plt.contour([[0]])
306+
excinfo.match(r'Input z must be at least a 2x2 array.')
307+
308+
with pytest.raises(TypeError) as excinfo:
309+
plt.contour([0], [0], [[0]])
310+
excinfo.match(r'Input z must be at least a 2x2 array.')
311+
312+
313+
def test_internal_cpp_api():
314+
# Following github issue 8197.
315+
import matplotlib._contour as _contour
316+
317+
with pytest.raises(TypeError) as excinfo:
318+
qcg = _contour.QuadContourGenerator()
319+
excinfo.match(r'function takes exactly 6 arguments \(0 given\)')
320+
321+
with pytest.raises(ValueError) as excinfo:
322+
qcg = _contour.QuadContourGenerator(1, 2, 3, 4, 5, 6)
323+
excinfo.match(r'Expected 2-dimensional array, got 0')
324+
325+
with pytest.raises(ValueError) as excinfo:
326+
qcg = _contour.QuadContourGenerator([[0]], [[0]], [[]], None, True, 0)
327+
excinfo.match(r'x, y and z must all be 2D arrays with the same dimensions')
328+
329+
with pytest.raises(ValueError) as excinfo:
330+
qcg = _contour.QuadContourGenerator([[0]], [[0]], [[0]], None, True, 0)
331+
excinfo.match(r'x, y and z must all be at least 2x2 arrays')
332+
333+
arr = [[0, 1], [2, 3]]
334+
with pytest.raises(ValueError) as excinfo:
335+
qcg = _contour.QuadContourGenerator(arr, arr, arr, [[0]], True, 0)
336+
excinfo.match(r'If mask is set it must be a 2D array with the same ' +
337+
r'dimensions as x.')
338+
339+
qcg = _contour.QuadContourGenerator(arr, arr, arr, None, True, 0)
340+
with pytest.raises(ValueError) as excinfo:
341+
qcg.create_filled_contour(1, 0)
342+
excinfo.match(r'filled contour levels must be increasing')

lib/matplotlib/tests/test_triangulation.py

+75
Original file line numberDiff line numberDiff line change
@@ -1048,3 +1048,78 @@ def test_tricontourf_decreasing_levels():
10481048
plt.figure()
10491049
with pytest.raises(ValueError):
10501050
plt.tricontourf(x, y, z, [1.0, 0.0])
1051+
1052+
1053+
def test_internal_cpp_api():
1054+
# Following github issue 8197.
1055+
import matplotlib._tri as _tri
1056+
1057+
# C++ Triangulation.
1058+
with pytest.raises(TypeError) as excinfo:
1059+
triang = _tri.Triangulation()
1060+
excinfo.match(r'function takes exactly 7 arguments \(0 given\)')
1061+
1062+
with pytest.raises(ValueError) as excinfo:
1063+
triang = _tri.Triangulation([], [1], [[]], None, None, None, False)
1064+
excinfo.match(r'x and y must be 1D arrays of the same length')
1065+
1066+
x = [0, 1, 1]
1067+
y = [0, 0, 1]
1068+
with pytest.raises(ValueError) as excinfo:
1069+
triang = _tri.Triangulation(x, y, [[0, 1]], None, None, None, False)
1070+
excinfo.match(r'triangles must be a 2D array of shape \(\?,3\)')
1071+
1072+
tris = [[0, 1, 2]]
1073+
with pytest.raises(ValueError) as excinfo:
1074+
triang = _tri.Triangulation(x, y, tris, [0, 1], None, None, False)
1075+
excinfo.match(r'mask must be a 1D array with the same length as the ' +
1076+
r'triangles array')
1077+
1078+
with pytest.raises(ValueError) as excinfo:
1079+
triang = _tri.Triangulation(x, y, tris, None, [[1]], None, False)
1080+
excinfo.match(r'edges must be a 2D array with shape \(\?,2\)')
1081+
1082+
with pytest.raises(ValueError) as excinfo:
1083+
triang = _tri.Triangulation(x, y, tris, None, None, [[-1]], False)
1084+
excinfo.match(r'neighbors must be a 2D array with the same shape as the ' +
1085+
r'triangles array')
1086+
1087+
triang = _tri.Triangulation(x, y, tris, None, None, None, False)
1088+
1089+
with pytest.raises(ValueError) as excinfo:
1090+
triang.calculate_plane_coefficients([])
1091+
excinfo.match(r'z array must have same length as triangulation x and y ' +
1092+
r'arrays')
1093+
1094+
with pytest.raises(ValueError) as excinfo:
1095+
triang.set_mask([0, 1])
1096+
excinfo.match(r'mask must be a 1D array with the same length as the ' +
1097+
r'triangles array')
1098+
1099+
# C++ TriContourGenerator.
1100+
with pytest.raises(TypeError) as excinfo:
1101+
tcg = _tri.TriContourGenerator()
1102+
excinfo.match(r'function takes exactly 2 arguments \(0 given\)')
1103+
1104+
with pytest.raises(ValueError) as excinfo:
1105+
tcg = _tri.TriContourGenerator(triang, [1])
1106+
excinfo.match(r'z must be a 1D array with the same length as the x and ' +
1107+
r'y arrays')
1108+
1109+
z = [0, 1, 2]
1110+
tcg = _tri.TriContourGenerator(triang, z)
1111+
1112+
with pytest.raises(ValueError) as excinfo:
1113+
tcg.create_filled_contour(1, 0)
1114+
excinfo.match(r'filled contour levels must be increasing')
1115+
1116+
# C++ TrapezoidMapTriFinder.
1117+
with pytest.raises(TypeError) as excinfo:
1118+
trifinder = _tri.TrapezoidMapTriFinder()
1119+
excinfo.match(r'function takes exactly 1 argument \(0 given\)')
1120+
1121+
trifinder = _tri.TrapezoidMapTriFinder(triang)
1122+
1123+
with pytest.raises(ValueError) as excinfo:
1124+
trifinder.find_many([0], [0, 1])
1125+
excinfo.match(r'x and y must be array_like with same shape')

lib/matplotlib/tri/_tri_wrapper.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -53,31 +53,36 @@ static int PyTriangulation_init(PyTriangulation* self, PyObject* args, PyObject*
5353
if (x.empty() || y.empty() || x.dim(0) != y.dim(0)) {
5454
PyErr_SetString(PyExc_ValueError,
5555
"x and y must be 1D arrays of the same length");
56+
return -1;
5657
}
5758

5859
// triangles.
5960
if (triangles.empty() || triangles.dim(1) != 3) {
6061
PyErr_SetString(PyExc_ValueError,
6162
"triangles must be a 2D array of shape (?,3)");
63+
return -1;
6264
}
6365

6466
// Optional mask.
6567
if (!mask.empty() && mask.dim(0) != triangles.dim(0)) {
6668
PyErr_SetString(PyExc_ValueError,
6769
"mask must be a 1D array with the same length as the triangles array");
70+
return -1;
6871
}
6972

7073
// Optional edges.
7174
if (!edges.empty() && edges.dim(1) != 2) {
7275
PyErr_SetString(PyExc_ValueError,
7376
"edges must be a 2D array with shape (?,2)");
77+
return -1;
7478
}
7579

7680
// Optional neighbors.
7781
if (!neighbors.empty() && (neighbors.dim(0) != triangles.dim(0) ||
7882
neighbors.dim(1) != triangles.dim(1))) {
7983
PyErr_SetString(PyExc_ValueError,
8084
"neighbors must be a 2D array with the same shape as the triangles array");
85+
return -1;
8186
}
8287

8388
CALL_CPP_INIT("Triangulation",
@@ -109,6 +114,7 @@ static PyObject* PyTriangulation_calculate_plane_coefficients(PyTriangulation* s
109114
if (z.empty() || z.dim(0) != self->ptr->get_npoints()) {
110115
PyErr_SetString(PyExc_ValueError,
111116
"z array must have same length as triangulation x and y arrays");
117+
return NULL;
112118
}
113119

114120
Triangulation::TwoCoordinateArray result;
@@ -167,6 +173,7 @@ static PyObject* PyTriangulation_set_mask(PyTriangulation* self, PyObject* args,
167173
if (!mask.empty() && mask.dim(0) != self->ptr->get_ntri()) {
168174
PyErr_SetString(PyExc_ValueError,
169175
"mask must be a 1D array with the same length as the triangles array");
176+
return NULL;
170177
}
171178

172179
CALL_CPP("set_mask", (self->ptr->set_mask(mask)));
@@ -251,6 +258,7 @@ static int PyTriContourGenerator_init(PyTriContourGenerator* self, PyObject* arg
251258
if (z.empty() || z.dim(0) != triangulation.get_npoints()) {
252259
PyErr_SetString(PyExc_ValueError,
253260
"z must be a 1D array with the same length as the x and y arrays");
261+
return -1;
254262
}
255263

256264
CALL_CPP_INIT("TriContourGenerator",
@@ -299,6 +307,7 @@ static PyObject* PyTriContourGenerator_create_filled_contour(PyTriContourGenerat
299307
{
300308
PyErr_SetString(PyExc_ValueError,
301309
"filled contour levels must be increasing");
310+
return NULL;
302311
}
303312

304313
PyObject* result;
@@ -407,6 +416,7 @@ static PyObject* PyTrapezoidMapTriFinder_find_many(PyTrapezoidMapTriFinder* self
407416
if (x.empty() || y.empty() || x.dim(0) != y.dim(0)) {
408417
PyErr_SetString(PyExc_ValueError,
409418
"x and y must be array_like with same shape");
419+
return NULL;
410420
}
411421

412422
TrapezoidMapTriFinder::TriIndexArray result;

src/_contour_wrapper.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,20 @@ static int PyQuadContourGenerator_init(PyQuadContourGenerator* self, PyObject* a
4747
y.dim(1) != x.dim(1) || z.dim(1) != x.dim(1)) {
4848
PyErr_SetString(PyExc_ValueError,
4949
"x, y and z must all be 2D arrays with the same dimensions");
50+
return -1;
51+
}
52+
53+
if (z.dim(0) < 2 || z.dim(1) < 2) {
54+
PyErr_SetString(PyExc_ValueError,
55+
"x, y and z must all be at least 2x2 arrays");
56+
return -1;
5057
}
5158

5259
// Mask array is optional, if set must be same size as other arrays.
5360
if (!mask.empty() && (mask.dim(0) != x.dim(0) || mask.dim(1) != x.dim(1))) {
5461
PyErr_SetString(PyExc_ValueError,
5562
"If mask is set it must be a 2D array with the same dimensions as x.");
63+
return -1;
5664
}
5765

5866
CALL_CPP_INIT("QuadContourGenerator",
@@ -101,6 +109,7 @@ static PyObject* PyQuadContourGenerator_create_filled_contour(PyQuadContourGener
101109
{
102110
PyErr_SetString(PyExc_ValueError,
103111
"filled contour levels must be increasing");
112+
return NULL;
104113
}
105114

106115
PyObject* result;

0 commit comments

Comments
 (0)