Skip to content

Commit 525e47b

Browse files
committed
Lots more graphics primitives: circle, ellipse, sphere, ellipsoid
1 parent 03535db commit 525e47b

1 file changed

Lines changed: 51 additions & 20 deletions

File tree

spatialmath/base/graphics.py

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -248,68 +248,99 @@ def plot_sphere(centre=(0,0,0), radius=1, npoints=50, ax=None, wireframe=False,
248248
ax.plot_surface(x, y, z, **kwargs)
249249

250250

251-
def ellipse(E, centre=(0,0,0), confidence=None, npoints=40, inverse=False):
251+
def ellipse(E, centre=(0,0), scale=1, confidence=None, npoints=40, inverted=False):
252+
"""[summary]
253+
254+
:param E: ellipse defined by :math:`x^T \mat{E} x = 1`
255+
:type E: ndarray(2,2)
256+
:param centre: ellipse centre, defaults to (0,0,0)
257+
:type centre: tuple, optional
258+
:param scale:
259+
:type scale:
260+
:param confidence: if E is an inverse covariance matrix plot an ellipse
261+
for this confidence interval in the range [0,1], defaults to None
262+
:type confidence: float, optional
263+
:param npoints: number of points on circumferance, defaults to 40
264+
:type npoints: int, optional
265+
:param inverted: if :math:`\mat{E}^{-1}` is provided, defaults to False
266+
:type inverted: bool, optional
267+
:raises ValueError: [description]
268+
:return: x and y coordinates
269+
:rtype: tuple of ndarray(1)
270+
271+
.. note:: In some problems we compute :math:`\mat{E}^{-1}` so to avoid
272+
inverting ``E`` twice to compute the ellipse, we flag that the inverse
273+
is provided using ``inverted``. For example:
274+
275+
- for robot manipulability
276+
:math:`\nu (\mat{J} \mat{J}^T)^{-1} \nu` i
277+
- a covariance matrix
278+
:math:`(x - \mu)^T \mat{P}^{-1} (x - \mu)`
279+
"""
252280
if E.shape != (2,2):
253281
raise ValueError('ellipse is defined by a 2x2 matrix')
254282

255-
if inverse:
256-
E = np.linalg.inv(E)
257-
258283
if confidence:
259284
# process the probability
260-
s = sqrt(chi2inv(confidence, 2))
285+
s = sqrt(chi2inv(confidence, 2)) * scale
261286
else:
262-
s = 1
287+
s = scale
263288

264289
x, y = circle() # unit circle
265-
e = sp.linalg.sqrtm(E) @ np.array([x, y])
290+
291+
if not inverted:
292+
E = np.linalg.inv(E)
293+
294+
e = s * sp.linalg.sqrtm(E) @ np.array([x, y]) + np.c_[centre]
266295
return e[0,:], e[1,:]
267296

268-
def plot_ellipse(E, centre=(0,0), confidence=None, npoints=40, inverse=False, filled=None, **kwargs):
297+
def plot_ellipse(E, centre=(0,0), scale=1, confidence=None, npoints=40, inverted=False, ax=None, filled=None, **kwargs):
269298

270299
# allow for centre[2] to plot ellipse in a plane in a 3D plot
271300

272-
x, y = ellipse(E, centre, confidence, npoints, inverse)
301+
x, y = ellipse(E, centre, scale, confidence, npoints, inverted)
273302
ax = _axes_logic(ax, 2)
274303
if filled:
275304
patch = plt.Polygon(x, y, **kwargs)
276305
ax.add_patch(patch)
277306
else:
278307
plt.plot(x, y, **kwargs)
279308

280-
def ellipsoid(E, centre=(0,0,0), confidence=None, npoints=40, inverse=False):
309+
def ellipsoid(E, centre=(0,0,0), scale=1, confidence=None, npoints=40, inverted=False):
281310

282311
if E.shape != (3,3):
283312
raise ValueError('ellipsoid is defined by a 3x3 matrix')
284313

285-
if inverse:
286-
E = np.linalg.inv(E)
287-
288314
if confidence:
289315
# process the probability
290316
from scipy.stats.distributions import chi2
291-
s = math.sqrt(chi2.ppf(s, df=2))
317+
s = math.sqrt(chi2.ppf(s, df=2)) * scale
292318
else:
293-
s = 1
319+
s = scale
320+
321+
if not inverted:
322+
E = np.linalg.inv(E)
294323

295324
x, y, z = sphere() # unit sphere
296-
e = sp.linalg.sqrtm(E) @ np.array([x.flatten(), y.flatten(), z.flatten()])
325+
e = s * sp.linalg.sqrtm(E) @ np.array([x.flatten(), y.flatten(), z.flatten()]) + np.c_[centre].T
297326
return e[0,:].reshape(x.shape), e[1,:].reshape(x.shape), e[2,:].reshape(x.shape)
298327

299-
def plot_ellipsoid(E, centre=(0,0,0), confidence=None, npoints=40, inverse=False, ax=None, wireframe=False, stride=1, **kwargs):
328+
def plot_ellipsoid(E, centre=(0,0,0), scale=1, confidence=None, npoints=40, inverted=False, ax=None, wireframe=False, stride=1, **kwargs):
300329
"""
301330
Draw an ellipsoid
302331
303332
:param E: ellipsoid
304333
:type E: ndarray(3,3)
305334
:param centre: [description], defaults to (0,0,0)
306335
:type centre: tuple, optional
336+
:param scale:
337+
:type scale:
307338
:param confidence: confidence interval, range 0 to 1
308339
:type confidence: float
309340
:param npoints: [description], defaults to 40
310341
:type npoints: int, optional
311-
:param inverse: [description], defaults to False
312-
:type inverse: bool, optional
342+
:param inverted: [description], defaults to False
343+
:type inverted: bool, optional
313344
:param ax: [description], defaults to None
314345
:type ax: [type], optional
315346
:param wireframe: [description], defaults to False
@@ -334,7 +365,7 @@ def plot_ellipsoid(E, centre=(0,0,0), confidence=None, npoints=40, inverse=False
334365
- If a confidence interval is given then ``E`` is interpretted as a covariance
335366
matrix and the ellipse size is computed using an inverse chi-squared function.
336367
"""
337-
x, y, z = ellipsoid(E, centre, confidence, npoints, inverse)
368+
x, y, z = ellipsoid(E, centre, scale, confidence, npoints, inverted)
338369
ax = _axes_logic(ax, 3)
339370
if wireframe:
340371
return ax.plot_wireframe(x, y, z, rstride=stride, cstride=stride, **kwargs)

0 commit comments

Comments
 (0)