diff --git a/lib/matplotlib/cbook.py b/lib/matplotlib/cbook.py index 18ef6b793c2b..c56956c04868 100644 --- a/lib/matplotlib/cbook.py +++ b/lib/matplotlib/cbook.py @@ -2241,6 +2241,21 @@ def _reshape_2D(X): return X +def ensure_3d(arr): + """ + Return a version of arr with ndim==3, with extra dimensions added + at the end of arr.shape as needed. + """ + arr = np.asanyarray(arr) + if arr.ndim == 1: + arr = arr[:, None, None] + elif arr.ndim == 2: + arr = arr[:, :, None] + elif arr.ndim > 3 or arr.ndim < 1: + raise ValueError("cannot convert arr to 3-dimensional") + return arr + + def violin_stats(X, method, points=100): ''' Returns a list of dictionaries of data which can be used to draw a series diff --git a/lib/matplotlib/path.py b/lib/matplotlib/path.py index 2e0d86456167..ca80f4cbb6b7 100644 --- a/lib/matplotlib/path.py +++ b/lib/matplotlib/path.py @@ -24,7 +24,7 @@ from numpy import ma from matplotlib import _path -from matplotlib.cbook import simple_linear_interpolation, maxdict +from matplotlib.cbook import simple_linear_interpolation, maxdict, ensure_3d from matplotlib import rcParams @@ -988,7 +988,7 @@ def get_path_collection_extents( if len(paths) == 0: raise ValueError("No paths provided") return Bbox.from_extents(*_path.get_path_collection_extents( - master_transform, paths, np.atleast_3d(transforms), + master_transform, paths, ensure_3d(transforms), offsets, offset_transform)) diff --git a/lib/matplotlib/tests/test_cbook.py b/lib/matplotlib/tests/test_cbook.py index 2b916b08566f..b244e761cf8f 100644 --- a/lib/matplotlib/tests/test_cbook.py +++ b/lib/matplotlib/tests/test_cbook.py @@ -376,3 +376,11 @@ def test_step_fails(): np.arange(12)) assert_raises(ValueError, cbook._step_validation, np.arange(12), np.arange(3)) + + +def test_ensure_3d(): + assert_array_equal([[[1]], [[2]], [[3]]], + cbook.ensure_3d([1, 2, 3])) + assert_array_equal([[[1], [2]], [[3], [4]]], + cbook.ensure_3d([[1, 2], [3, 4]])) + assert_raises(ValueError, cbook.ensure_3d, [[[[1]]]]) diff --git a/lib/matplotlib/transforms.py b/lib/matplotlib/transforms.py index 812047910e11..1eac27fc52ee 100644 --- a/lib/matplotlib/transforms.py +++ b/lib/matplotlib/transforms.py @@ -48,6 +48,7 @@ from sets import Set as set from .path import Path +from .cbook import ensure_3d DEBUG = False # we need this later, but this is very expensive to set up @@ -667,7 +668,7 @@ def count_overlaps(self, bboxes): bboxes is a sequence of :class:`BboxBase` objects """ return count_bboxes_overlapping_bbox( - self, np.atleast_3d([np.array(x) for x in bboxes])) + self, ensure_3d([np.array(x) for x in bboxes])) def expanded(self, sw, sh): """ diff --git a/src/_backend_agg.h b/src/_backend_agg.h index ee85f00f7ad3..11abfcb98f5e 100644 --- a/src/_backend_agg.h +++ b/src/_backend_agg.h @@ -922,22 +922,6 @@ inline void RendererAgg::_draw_path_collection_generic(GCAgg &gc, typedef agg::conv_curve snapped_curve_t; typedef agg::conv_curve curve_t; - if (offsets.dim(0) != 0 && offsets.dim(1) != 2) { - throw "Offsets array must be Nx2 or empty"; - } - - if (facecolors.dim(0) != 0 && facecolors.dim(1) != 4) { - throw "Facecolors array must be a Nx4 array or empty"; - } - - if (edgecolors.dim(0) != 0 && edgecolors.dim(1) != 4) { - throw "Edgecolors array must by Nx4 or empty"; - } - - if (transforms.dim(0) != 0 && (transforms.dim(1) != 3 || transforms.dim(2) != 3)) { - throw "Transforms array must by Nx3x3 or empty"; - } - size_t Npaths = path_generator.num_paths(); size_t Noffsets = offsets.size(); size_t N = std::max(Npaths, Noffsets); @@ -1266,14 +1250,6 @@ inline void RendererAgg::draw_gouraud_triangle(GCAgg &gc, set_clipbox(gc.cliprect, theRasterizer); bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans); - if (points.dim(0) != 3 || points.dim(1) != 2) { - throw "points must be a 3x2 array"; - } - - if (colors.dim(0) != 3 || colors.dim(1) != 4) { - throw "colors must be a 3x4 array"; - } - _draw_gouraud_triangle(points, colors, trans, has_clippath); } @@ -1288,18 +1264,6 @@ inline void RendererAgg::draw_gouraud_triangles(GCAgg &gc, set_clipbox(gc.cliprect, theRasterizer); bool has_clippath = render_clippath(gc.clippath.path, gc.clippath.trans); - if (points.dim(1) != 3 || points.dim(2) != 2) { - throw "points must be a Nx3x2 array"; - } - - if (colors.dim(1) != 3 || colors.dim(2) != 4) { - throw "colors must be a Nx3x4 array"; - } - - if (points.dim(0) != colors.dim(0)) { - throw "points and colors arrays must be the same length"; - } - for (int i = 0; i < points.dim(0); ++i) { typename PointArray::sub_t point = points[i]; typename ColorArray::sub_t color = colors[i]; diff --git a/src/_backend_agg_wrapper.cpp b/src/_backend_agg_wrapper.cpp index 753afe3837d2..752a8fc9484e 100644 --- a/src/_backend_agg_wrapper.cpp +++ b/src/_backend_agg_wrapper.cpp @@ -340,15 +340,15 @@ PyRendererAgg_draw_path_collection(PyRendererAgg *self, PyObject *args, PyObject &convert_trans_affine, &master_transform, &pathobj, - &transforms.converter, + &convert_transforms, &transforms, - &offsets.converter, + &convert_points, &offsets, &convert_trans_affine, &offset_trans, - &facecolors.converter, + &convert_colors, &facecolors, - &edgecolors.converter, + &convert_colors, &edgecolors, &linewidths.converter, &linewidths, @@ -411,14 +411,14 @@ static PyObject *PyRendererAgg_draw_quad_mesh(PyRendererAgg *self, PyObject *arg &mesh_height, &coordinates.converter, &coordinates, - &offsets.converter, + &convert_points, &offsets, &convert_trans_affine, &offset_trans, - &facecolors.converter, + &convert_colors, &facecolors, &antialiased, - &edgecolors.converter, + &convert_colors, &edgecolors)) { return NULL; } @@ -459,6 +459,21 @@ PyRendererAgg_draw_gouraud_triangle(PyRendererAgg *self, PyObject *args, PyObjec return NULL; } + if (points.dim(0) != 3 || points.dim(1) != 2) { + PyErr_Format(PyExc_ValueError, + "points must be a 3x2 array, got %dx%d", + points.dim(0), points.dim(1)); + return NULL; + } + + if (colors.dim(0) != 3 || colors.dim(1) != 4) { + PyErr_Format(PyExc_ValueError, + "colors must be a 3x4 array, got %dx%d", + colors.dim(0), colors.dim(1)); + return NULL; + } + + CALL_CPP("draw_gouraud_triangle", (self->x->draw_gouraud_triangle(gc, points, colors, trans))); Py_RETURN_NONE; @@ -485,6 +500,27 @@ PyRendererAgg_draw_gouraud_triangles(PyRendererAgg *self, PyObject *args, PyObje return NULL; } + if (points.dim(1) != 3 || points.dim(2) != 2) { + PyErr_Format(PyExc_ValueError, + "points must be a Nx3x2 array, got %dx%dx%d", + points.dim(0), points.dim(1), points.dim(2)); + return NULL; + } + + if (colors.dim(1) != 3 || colors.dim(2) != 4) { + PyErr_Format(PyExc_ValueError, + "colors must be a Nx3x4 array, got %dx%dx%d", + colors.dim(0), colors.dim(1), colors.dim(2)); + return NULL; + } + + if (points.dim(0) != colors.dim(0)) { + PyErr_Format(PyExc_ValueError, + "points and colors arrays must be the same length, got %d and %d", + points.dim(0), colors.dim(0)); + return NULL; + } + CALL_CPP("draw_gouraud_triangles", self->x->draw_gouraud_triangles(gc, points, colors, trans)); Py_RETURN_NONE; diff --git a/src/_path_wrapper.cpp b/src/_path_wrapper.cpp index cd80e636dc3d..e824bf290d60 100644 --- a/src/_path_wrapper.cpp +++ b/src/_path_wrapper.cpp @@ -69,7 +69,7 @@ static PyObject *Py_points_in_path(PyObject *self, PyObject *args, PyObject *kwd if (!PyArg_ParseTuple(args, "O&dO&O&:points_in_path", - &points.converter, + &convert_points, &points, &r, &convert_path, @@ -128,7 +128,7 @@ static PyObject *Py_points_on_path(PyObject *self, PyObject *args, PyObject *kwd if (!PyArg_ParseTuple(args, "O&dO&O&:points_on_path", - &points.converter, + &convert_points, &points, &r, &convert_path, @@ -200,7 +200,10 @@ static PyObject *Py_update_path_extents(PyObject *self, PyObject *args, PyObject } if (minpos.dim(0) != 2) { - PyErr_SetString(PyExc_ValueError, "minpos must be of length 2"); + PyErr_Format(PyExc_ValueError, + "minpos must be of length 2, got %d", + minpos.dim(0)); + return NULL; } extent_limits e; @@ -263,9 +266,9 @@ static PyObject *Py_get_path_collection_extents(PyObject *self, PyObject *args, &convert_trans_affine, &master_transform, &pathsobj, - &transforms.converter, + &convert_transforms, &transforms, - &offsets.converter, + &convert_points, &offsets, &convert_trans_affine, &offset_trans)) { @@ -319,9 +322,9 @@ static PyObject *Py_point_in_path_collection(PyObject *self, PyObject *args, PyO &convert_trans_affine, &master_transform, &pathsobj, - &transforms.converter, + &convert_transforms, &transforms, - &offsets.converter, + &convert_points, &offsets, &convert_trans_affine, &offset_trans, @@ -464,7 +467,7 @@ static PyObject *Py_count_bboxes_overlapping_bbox(PyObject *self, PyObject *args "O&O&:count_bboxes_overlapping_bbox", &convert_rect, &bbox, - &bboxes.converter, + &convert_bboxes, &bboxes)) { return NULL; } diff --git a/src/py_converters.cpp b/src/py_converters.cpp index 8635d0271158..23365c71565e 100644 --- a/src/py_converters.cpp +++ b/src/py_converters.cpp @@ -518,4 +518,100 @@ int convert_face(PyObject *color, GCAgg &gc, agg::rgba *rgba) return 1; } + +int convert_points(PyObject *obj, void *pointsp) +{ + numpy::array_view *points = (numpy::array_view *)pointsp; + + if (obj == NULL || obj == Py_None) { + return 1; + } + + points->set(obj); + + if (points->dim(0) == 0) { + return 1; + } + + if (points->dim(1) != 2) { + PyErr_Format(PyExc_ValueError, + "Points must be Nx2 array, got %dx%d", + points->dim(0), points->dim(1)); + return 0; + } + + return 1; +} + +int convert_transforms(PyObject *obj, void *transp) +{ + numpy::array_view *trans = (numpy::array_view *)transp; + + if (obj == NULL || obj == Py_None) { + return 1; + } + + trans->set(obj); + + if (trans->dim(0) == 0) { + return 1; + } + + if (trans->dim(1) != 3 || trans->dim(2) != 3) { + PyErr_Format(PyExc_ValueError, + "Transforms must be Nx3x3 array, got %dx%dx%d", + trans->dim(0), trans->dim(1), trans->dim(2)); + return 0; + } + + return 1; +} + +int convert_bboxes(PyObject *obj, void *bboxp) +{ + numpy::array_view *bbox = (numpy::array_view *)bboxp; + + if (obj == NULL || obj == Py_None) { + return 1; + } + + bbox->set(obj); + + if (bbox->dim(0) == 0) { + return 1; + } + + if (bbox->dim(1) != 2 || bbox->dim(2) != 2) { + PyErr_Format(PyExc_ValueError, + "Bbox array must be Nx2x2 array, got %dx%dx%d", + bbox->dim(0), bbox->dim(1), bbox->dim(2)); + return 0; + } + + return 1; +} + +int convert_colors(PyObject *obj, void *colorsp) +{ + numpy::array_view *colors = (numpy::array_view *)colorsp; + + if (obj == NULL || obj == Py_None) { + return 1; + } + + colors->set(obj); + + if (colors->dim(0) == 0) { + return 1; + } + + if (colors->dim(1) != 4) { + PyErr_Format(PyExc_ValueError, + "Colors array must be Nx4 array, got %dx%d", + colors->dim(0), colors->dim(1)); + return 0; + } + + return 1; +} } diff --git a/src/py_converters.h b/src/py_converters.h index 90cf454e30fb..02d84affe857 100644 --- a/src/py_converters.h +++ b/src/py_converters.h @@ -38,6 +38,10 @@ int convert_snap(PyObject *obj, void *snapp); int convert_offset_position(PyObject *obj, void *offsetp); int convert_sketch_params(PyObject *obj, void *sketchp); int convert_gcagg(PyObject *pygc, void *gcp); +int convert_points(PyObject *pygc, void *pointsp); +int convert_transforms(PyObject *pygc, void *transp); +int convert_bboxes(PyObject *pygc, void *bboxp); +int convert_colors(PyObject *pygc, void *colorsp); int convert_face(PyObject *color, GCAgg &gc, agg::rgba *rgba); }