Skip to content

Commit f7e0015

Browse files
committed
Route affine transforms through Eigen
1 parent e76eaae commit f7e0015

File tree

7 files changed

+36
-106
lines changed

7 files changed

+36
-106
lines changed

lib/matplotlib/_eigen.pyi

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ class Affine2d:
1919
def scale(self, sx: float, sy: float) -> None: ...
2020
def skew(self, xShear: float, yShear: float) -> None: ...
2121
def translate(self, tx: float, ty: float) -> None: ...
22+
def affine_transform(self, vertices: np.ndarray) -> np.ndarray: ...

lib/matplotlib/_path.pyi

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
from collections.abc import Sequence
22

3-
import numpy as np
4-
53
from .transforms import BboxBase
64

7-
def affine_transform(points: np.ndarray, trans: np.ndarray) -> np.ndarray: ...
85
def count_bboxes_overlapping_bbox(bbox: BboxBase, bboxes: Sequence[BboxBase]) -> int: ...

lib/matplotlib/tests/test_transforms.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -285,24 +285,22 @@ def test_translate_plus_other(self):
285285
assert_array_almost_equal(trans.transform(self.multiple_points),
286286
[[45, 49.75], [48.5, 51.5], [48, 48.75]])
287287

288-
def test_invalid_transform(self):
288+
def test_invalid_arguments(self):
289289
t = mtransforms.Affine2D()
290-
# There are two different exceptions, since the wrong number of
291-
# dimensions is caught when constructing an array_view, and that
292-
# raises a ValueError, and a wrong shape with a possible number
293-
# of dimensions is caught by our CALL_CPP macro, which always
294-
# raises the less precise RuntimeError.
295-
with pytest.raises(ValueError):
290+
# The wrong number of dimensions and a wrong shape with a possible number of
291+
# dimensions are both caught by pybind11, which always raises the less precise
292+
# RuntimeError.
293+
with pytest.raises(RuntimeError):
296294
t.transform(1)
297-
with pytest.raises(ValueError):
295+
with pytest.raises(RuntimeError):
298296
t.transform([[[1]]])
299297
with pytest.raises(RuntimeError):
300298
t.transform([])
301299
with pytest.raises(RuntimeError):
302300
t.transform([1])
303-
with pytest.raises(ValueError):
301+
with pytest.raises(RuntimeError):
304302
t.transform([[1]])
305-
with pytest.raises(ValueError):
303+
with pytest.raises(RuntimeError):
306304
t.transform([[1, 2, 3]])
307305

308306
def test_copy(self):

lib/matplotlib/transforms.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
from numpy.linalg import inv
4646

4747
from matplotlib import _api, _eigen
48-
from matplotlib._path import affine_transform, count_bboxes_overlapping_bbox
48+
from matplotlib._path import count_bboxes_overlapping_bbox
4949
from .path import Path
5050

5151
DEBUG = False
@@ -1798,6 +1798,15 @@ def __array__(self, *args, **kwargs):
17981798
# optimises the access of the transform matrix vs. the superclass
17991799
return self.get_matrix()
18001800

1801+
def _get_eigen_matrix(self):
1802+
if self._invalid:
1803+
mtx = self.get_matrix()
1804+
# Back-compat with external stuff.
1805+
if isinstance(eigen := getattr(self, '_mtx'), _eigen.Affine2d):
1806+
return eigen
1807+
else:
1808+
return _eigen.Affine2d(mtx)
1809+
18011810
def __eq__(self, other):
18021811
if getattr(other, "is_affine", False) and hasattr(other, "get_matrix"):
18031812
return (self.get_matrix() == other.get_matrix()).all()
@@ -1870,11 +1879,11 @@ def to_values(self):
18701879
return tuple(mtx[:2].swapaxes(0, 1).flat)
18711880

18721881
def transform_affine(self, values):
1873-
mtx = self.get_matrix()
1882+
mtx = self._get_eigen_matrix()
18741883
if isinstance(values, np.ma.MaskedArray):
1875-
tpoints = affine_transform(values.data, mtx)
1884+
tpoints = mtx.affine_transform(values.data)
18761885
return np.ma.MaskedArray(tpoints, mask=np.ma.getmask(values))
1877-
return affine_transform(values, mtx)
1886+
return mtx.affine_transform(values)
18781887

18791888
if DEBUG:
18801889
_transform_affine = transform_affine

src/_eigen.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,19 @@ PYBIND11_MODULE(_eigen, m) {
109109
self.pretranslate(Eigen::Vector2d(tx, ty));
110110
}, "tx"_a, "ty"_a,
111111
"Add a translation in place.")
112+
113+
.def("affine_transform",
114+
[](const Eigen::Affine2d& self, Eigen::Ref<const Eigen::Vector2d> vertices) {
115+
Eigen::Vector2d result = self * vertices;
116+
return result;
117+
}
118+
)
119+
.def("affine_transform",
120+
[](const Eigen::Affine2d& self, py::array_t<double> vertices_arr) {
121+
auto vertices = vertices_arr.attr("transpose")().cast<Eigen::Ref<const Eigen::Matrix2Xd>>();
122+
auto result = py::cast(self * vertices, py::return_value_policy::move);
123+
return result.attr("transpose")();
124+
}
125+
)
112126
;
113127
}

src/_path.h

Lines changed: 0 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -699,63 +699,6 @@ clip_path_to_rect(PathIterator &path, agg::rect_d &rect, bool inside, std::vecto
699699
_finalize_polygon(results, 1);
700700
}
701701

702-
template <class VerticesArray, class ResultArray>
703-
void affine_transform_2d(VerticesArray &vertices, agg::trans_affine &trans, ResultArray &result)
704-
{
705-
if (vertices.size() != 0 && vertices.shape(1) != 2) {
706-
throw std::runtime_error("Invalid vertices array.");
707-
}
708-
709-
size_t n = vertices.shape(0);
710-
double x;
711-
double y;
712-
double t0;
713-
double t1;
714-
double t;
715-
716-
for (size_t i = 0; i < n; ++i) {
717-
x = vertices(i, 0);
718-
y = vertices(i, 1);
719-
720-
t0 = trans.sx * x;
721-
t1 = trans.shx * y;
722-
t = t0 + t1 + trans.tx;
723-
result(i, 0) = t;
724-
725-
t0 = trans.shy * x;
726-
t1 = trans.sy * y;
727-
t = t0 + t1 + trans.ty;
728-
result(i, 1) = t;
729-
}
730-
}
731-
732-
template <class VerticesArray, class ResultArray>
733-
void affine_transform_1d(VerticesArray &vertices, agg::trans_affine &trans, ResultArray &result)
734-
{
735-
if (vertices.shape(0) != 2) {
736-
throw std::runtime_error("Invalid vertices array.");
737-
}
738-
739-
double x;
740-
double y;
741-
double t0;
742-
double t1;
743-
double t;
744-
745-
x = vertices(0);
746-
y = vertices(1);
747-
748-
t0 = trans.sx * x;
749-
t1 = trans.shx * y;
750-
t = t0 + t1 + trans.tx;
751-
result(0) = t;
752-
753-
t0 = trans.shy * x;
754-
t1 = trans.sy * y;
755-
t = t0 + t1 + trans.ty;
756-
result(1) = t;
757-
}
758-
759702
template <class BBoxArray>
760703
int count_bboxes_overlapping_bbox(agg::rect_d &a, BBoxArray &bboxes)
761704
{

src/_path_wrapper.cpp

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -116,36 +116,6 @@ Py_clip_path_to_rect(mpl::PathIterator path, agg::rect_d rect, bool inside)
116116
return convert_polygon_vector(result);
117117
}
118118

119-
static py::object
120-
Py_affine_transform(py::array_t<double, py::array::c_style | py::array::forcecast> vertices_arr,
121-
agg::trans_affine trans)
122-
{
123-
if (vertices_arr.ndim() == 2) {
124-
auto vertices = vertices_arr.unchecked<2>();
125-
126-
check_trailing_shape(vertices, "vertices", 2);
127-
128-
py::ssize_t dims[] = { vertices.shape(0), 2 };
129-
py::array_t<double> result(dims);
130-
auto result_mutable = result.mutable_unchecked<2>();
131-
132-
affine_transform_2d(vertices, trans, result_mutable);
133-
return result;
134-
} else if (vertices_arr.ndim() == 1) {
135-
auto vertices = vertices_arr.unchecked<1>();
136-
137-
py::ssize_t dims[] = { vertices.shape(0) };
138-
py::array_t<double> result(dims);
139-
auto result_mutable = result.mutable_unchecked<1>();
140-
141-
affine_transform_1d(vertices, trans, result_mutable);
142-
return result;
143-
} else {
144-
throw py::value_error(
145-
"vertices must be 1D or 2D, not" + std::to_string(vertices_arr.ndim()) + "D");
146-
}
147-
}
148-
149119
static int
150120
Py_count_bboxes_overlapping_bbox(agg::rect_d bbox, py::array_t<double> bboxes_obj)
151121
{
@@ -326,8 +296,6 @@ PYBIND11_MODULE(_path, m, py::mod_gil_not_used())
326296
"path_a"_a, "trans_a"_a, "path_b"_a, "trans_b"_a);
327297
m.def("clip_path_to_rect", &Py_clip_path_to_rect,
328298
"path"_a, "rect"_a, "inside"_a);
329-
m.def("affine_transform", &Py_affine_transform,
330-
"points"_a, "trans"_a);
331299
m.def("count_bboxes_overlapping_bbox", &Py_count_bboxes_overlapping_bbox,
332300
"bbox"_a, "bboxes"_a);
333301
m.def("path_intersects_path", &Py_path_intersects_path,

0 commit comments

Comments
 (0)