Skip to content

Commit 9892ac9

Browse files
committed
Add a simple Eigen affine 2D matrix class
This currently is enough to instantiate `matplotlib.transforms.Affine2D`, and get its matrix, but can't do anything else.
1 parent 30f4ece commit 9892ac9

File tree

6 files changed

+80
-14
lines changed

6 files changed

+80
-14
lines changed

lib/matplotlib/_eigen.pyi

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from collections.abc import Sequence
2+
3+
import numpy as np
4+
5+
class Affine2d:
6+
def __init__(self, matrix: Sequence[Sequence[float]] | None = ...): ...
7+
def get_matrix(self) -> np.ndarray: ...
8+
def is_diagonal(self) -> bool: ...
9+
def __call__(self, i: int, j: int) -> float: ...

lib/matplotlib/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ typing_sources = [
8080
'py.typed',
8181
# Compiled extension types.
8282
'_c_internal_utils.pyi',
83+
'_eigen.pyi',
8384
'ft2font.pyi',
8485
'_image.pyi',
8586
'_qhull.pyi',

lib/matplotlib/transforms.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
import numpy as np
4545
from numpy.linalg import inv
4646

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

@@ -1918,20 +1918,20 @@ def __init__(self, matrix=None, **kwargs):
19181918
If *matrix* is None, initialize with the identity transform.
19191919
"""
19201920
super().__init__(**kwargs)
1921-
if matrix is None:
1922-
# A bit faster than np.identity(3).
1923-
matrix = IdentityTransform._mtx
1924-
self._mtx = matrix.copy()
1921+
if matrix is not None:
1922+
self._mtx = _eigen.Affine2d(matrix)
1923+
else:
1924+
self._mtx = _eigen.Affine2d()
19251925
self._invalid = 0
19261926

19271927
_base_str = _make_str_method("_mtx")
19281928

19291929
def __str__(self):
19301930
return (self._base_str()
1931-
if (self._mtx != np.diag(np.diag(self._mtx))).any()
1932-
else f"Affine2D().scale({self._mtx[0, 0]}, {self._mtx[1, 1]})"
1933-
if self._mtx[0, 0] != self._mtx[1, 1]
1934-
else f"Affine2D().scale({self._mtx[0, 0]})")
1931+
if not self._mtx.is_diagonal
1932+
else f"Affine2D().scale({self._mtx(0, 0)}, {self._mtx(1, 1)})"
1933+
if self._mtx(0, 0) != self._mtx(1, 1)
1934+
else f"Affine2D().scale({self._mtx(0, 0)})")
19351935

19361936
@staticmethod
19371937
def from_values(a, b, c, d, e, f):
@@ -1944,8 +1944,7 @@ def from_values(a, b, c, d, e, f):
19441944
19451945
.
19461946
"""
1947-
return Affine2D(
1948-
np.array([a, c, e, b, d, f, 0.0, 0.0, 1.0], float).reshape((3, 3)))
1947+
return Affine2D([[a, c, e], [b, d, f], [0.0, 0.0, 1.0]])
19491948

19501949
def get_matrix(self):
19511950
"""
@@ -1960,7 +1959,7 @@ def get_matrix(self):
19601959
if self._invalid:
19611960
self._inverted = None
19621961
self._invalid = 0
1963-
return self._mtx
1962+
return self._mtx.get_matrix()
19641963

19651964
def set_matrix(self, mtx):
19661965
"""
@@ -2679,8 +2678,7 @@ def __init__(self, xt, yt, scale_trans, **kwargs):
26792678
def get_matrix(self):
26802679
# docstring inherited
26812680
if self._invalid:
2682-
# A bit faster than np.identity(3).
2683-
self._mtx = IdentityTransform._mtx.copy()
2681+
self._mtx = _eigen.Affine2d()
26842682
self._mtx[:2, 2] = self._scale_trans.transform(self._t)
26852683
self._invalid = 0
26862684
self._inverted = None

meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ py_mod = import('python')
3939
py3 = py_mod.find_installation(pure: false)
4040
py3_dep = py3.dependency()
4141

42+
eigen_dep = dependency('eigen3', version: '>=3')
4243
pybind11_dep = dependency('pybind11', version: '>=2.13.2')
4344

4445
subdir('extern')

src/_eigen.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Matrix operations we need:
2+
// .copy()
3+
// check if [0, 0] != [1, 1]
4+
// re-set a, b, c, d, e, f
5+
// convert to NumPy matrix
6+
// reset to identity
7+
// rotate in place
8+
// translate in place
9+
// scale in place
10+
// skew in place
11+
// dot two matrices
12+
13+
#include <pybind11/pybind11.h>
14+
#include <pybind11/eigen.h>
15+
#include <pybind11/numpy.h>
16+
#include <pybind11/stl.h>
17+
#include <Eigen/Geometry>
18+
19+
namespace py = pybind11;
20+
using namespace pybind11::literals;
21+
22+
PYBIND11_MODULE(_eigen, m) {
23+
py::class_<Eigen::Affine2d>(m, "Affine2d")
24+
.def(py::init([]() {
25+
return Eigen::Affine2d::Identity();
26+
}))
27+
.def(py::init([](const Eigen::Matrix3d& matrix) {
28+
return Eigen::Affine2d(matrix);
29+
}),
30+
"matrix"_a.none(false))
31+
32+
.def("get_matrix", [](const Eigen::Affine2d& self) {
33+
return self.matrix();
34+
})
35+
.def_property_readonly("is_diagonal", [](const Eigen::Affine2d& self) {
36+
return self.matrix().isDiagonal();
37+
})
38+
.def("__call__", [](const Eigen::Affine2d& self, py::ssize_t i, py::ssize_t j) {
39+
if (i < 0 || i >= 3) {
40+
throw std::out_of_range(
41+
"Index i (" + std::to_string(i) + ") out of range");
42+
}
43+
if (j < 0 || j >= 3) {
44+
throw std::out_of_range(
45+
"Index j (" + std::to_string(j) + ") out of range");
46+
}
47+
return self(i, j);
48+
}, "i"_a, "j"_a)
49+
;
50+
}

src/meson.build

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ extension_data = {
4646
),
4747
'dependencies': [pybind11_dep, dl, ole32, shell32, user32],
4848
},
49+
'_eigen': {
50+
'subdir': 'matplotlib',
51+
'sources': files(
52+
'_eigen.cpp',
53+
),
54+
'dependencies': [pybind11_dep, eigen_dep],
55+
},
4956
'ft2font': {
5057
'subdir': 'matplotlib',
5158
'sources': files(

0 commit comments

Comments
 (0)