Skip to content

Commit d38d523

Browse files
committed
Merge branch 'master'
2 parents 3cf96a8 + a970a48 commit d38d523

File tree

14 files changed

+378
-123
lines changed

14 files changed

+378
-123
lines changed

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,6 @@
6464

6565
packages=find_packages(exclude=["test_*", "TODO*"]),
6666

67-
install_requires=['numpy', 'scipy', 'matplotlib', 'colored', 'ansitable']
67+
install_requires=['numpy', 'scipy', 'matplotlib', 'colored', 'ansitable', 'sphinxcontrib-jsmath']
6868

6969
)

spatialmath/base/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,9 +258,12 @@
258258
"exp2jac",
259259
"rot2jac",
260260
"angvelxform",
261+
"angvelxform_dot",
261262
"trprint",
262263
"trplot",
263264
"tranimate",
265+
"tr2x",
266+
"x2tr",
264267
# spatialmath.base.transformsNd
265268
"t2r",
266269
"r2t",
@@ -325,6 +328,7 @@
325328
"isnotebook",
326329
# spatial.base.numeric
327330
"numjac",
331+
"numhess",
328332
"array2str",
329333
"bresenham",
330334
]

spatialmath/base/graphics.py

Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import math
22
from itertools import product
3+
from collections import Iterable
34
import warnings
45
import numpy as np
56
import scipy as sp
@@ -78,18 +79,16 @@ def plot_text(pos, text=None, ax=None, color=None, **kwargs):
7879
return [handle]
7980

8081

81-
def plot_point(
82-
pos, marker="bs", label=None, text=None, ax=None, textargs=None, **kwargs
83-
):
82+
def plot_point(pos, marker="bs", text=None, ax=None, textargs=None, **kwargs):
8483
"""
8584
Plot a point using matplotlib
8685
8786
:param pos: position of marker
8887
:type pos: array_like(2), ndarray(2,n), list of 2-tuples
8988
:param marker: matplotlub marker style, defaults to 'bs'
9089
:type marker: str or list of str, optional
91-
:param label: text label, defaults to None
92-
:type label: str, optional
90+
:param text: text label, defaults to None
91+
:type text: str, optional
9392
:param ax: axes to plot in, defaults to ``gca()``
9493
:type ax: Axis, optional
9594
:return: the matplotlib object
@@ -108,10 +107,10 @@ def plot_point(
108107
109108
- Multiple points can be marked if ``pos`` is a 2xn array or a list of
110109
coordinate pairs. In this case:
111-
- all points have the same label
112-
- label can include the format string {} which is susbstituted for the
110+
- all points have the same ``text`` label
111+
- ``text`` can include the format string {} which is susbstituted for the
113112
point index, starting at zero
114-
- label can be a tuple containing a format string followed by vectors
113+
- ``text`` can be a tuple containing a format string followed by vectors
115114
of shape(n). For example::
116115
117116
``("#{0} a={1:.1f}, b={2:.1f}", a, b)``
@@ -135,9 +134,6 @@ def plot_point(
135134
columns of ``p`` and label them all with successive elements of ``z``.
136135
"""
137136

138-
if text is not None:
139-
raise DeprecationWarning("use label not text")
140-
141137
if isinstance(pos, np.ndarray):
142138
if pos.ndim == 1:
143139
x = pos[0]
@@ -177,22 +173,22 @@ def plot_point(
177173
handles.append(plt.plot(x, y, m, **kwargs))
178174
else:
179175
handles.append(plt.plot(x, y, marker, **kwargs))
180-
if label is not None:
176+
if text is not None:
181177
try:
182178
xy = zip(x, y)
183179
except TypeError:
184180
xy = [(x, y)]
185-
if isinstance(label, str):
181+
if isinstance(text, str):
186182
# simple string, but might have format chars
187183
for i, (x, y) in enumerate(xy):
188-
handles.append(plt.text(x, y, " " + label.format(i), **textopts))
189-
elif isinstance(label, (tuple, list)):
184+
handles.append(plt.text(x, y, " " + text.format(i), **textopts))
185+
elif isinstance(text, (tuple, list)):
190186
for i, (x, y) in enumerate(xy):
191187
handles.append(
192188
plt.text(
193189
x,
194190
y,
195-
" " + label[0].format(i, *[d[i] for d in label[1:]]),
191+
" " + text[0].format(i, *[d[i] for d in text[1:]]),
196192
**textopts
197193
)
198194
)
@@ -251,10 +247,10 @@ def plot_homline(lines, *args, ax=None, xlim=None, ylim=None, **kwargs):
251247

252248
def plot_box(
253249
*fmt,
254-
bl=None,
255-
tl=None,
256-
br=None,
257-
tr=None,
250+
lb=None,
251+
lt=None,
252+
rb=None,
253+
rt=None,
258254
wh=None,
259255
centre=None,
260256
l=None,
@@ -265,6 +261,7 @@ def plot_box(
265261
h=None,
266262
ax=None,
267263
bbox=None,
264+
ltrb=None,
268265
filled=False,
269266
**kwargs
270267
):
@@ -277,10 +274,10 @@ def plot_box(
277274
:type tl: [array_like(2), optional
278275
:param br: bottom-right corner, defaults to None
279276
:type br: array_like(2), optional
280-
:param tr: top -ight corner, defaults to None
277+
:param tr: top-right corner, defaults to None
281278
:type tr: array_like(2), optional
282-
:param wh: width and height, defaults to None
283-
:type wh: array_like(2), optional
279+
:param wh: width and height, if both are the same provide scalar, defaults to None
280+
:type wh: scalar, array_like(2), optional
284281
:param centre: centre of box, defaults to None
285282
:type centre: array_like(2), optional
286283
:param l: left side of box, minimum x, defaults to None
@@ -313,35 +310,49 @@ def plot_box(
313310
The box can be specified in many ways:
314311
315312
- bounding box which is a 2x2 matrix [xmin, xmax; ymin, ymax]
313+
- bounding box [xmin, xmax, ymin, ymax]
314+
- alternative box [xmin, ymin, xmax, ymax]
316315
- centre and width+height
317316
- bottom-left and top-right corners
318317
- bottom-left corner and width+height
319318
- top-right corner and width+height
320319
- top-left corner and width+height
321320
321+
For plots where the y-axis is inverted (eg. for images) then top is the
322+
smaller vertical coordinate.
323+
322324
Example:
323325
324326
.. runblock:: pycon
325327
326328
>>> from spatialmath.base import plotvol2, plot_box
327329
>>> plotvol2(5)
328-
>>> plot_box('r', centre=(2,3), wh=(1,1))
330+
>>> plot_box('r', centre=(2,3), wh=1) # w=h=1
329331
>>> plot_box(tl=(1,1), br=(0,2), filled=True, color='b')
330332
"""
331333

332334
if bbox is not None:
333-
l, r, b, t = bbox
335+
if isinstance(bbox, ndarray) and bbox.ndims > 1:
336+
# case of [l r; t b]
337+
bbox = bbox.ravel()
338+
l, r, t, b = bbox
339+
elif ltrb is not None:
340+
l, t, r, b = ltrb
334341
else:
335-
if tl is not None:
336-
l, t = tl
337-
if tr is not None:
338-
r, t = tr
339-
if bl is not None:
340-
l, b = bl
341-
if br is not None:
342-
r, b = br
342+
if lt is not None:
343+
l, t = lt
344+
if rt is not None:
345+
r, t = rt
346+
if lb is not None:
347+
l, b = lb
348+
if rb is not None:
349+
r, b = rb
343350
if wh is not None:
344-
w, h = wh
351+
if isinstance(wh, Iterable):
352+
w, h = wh
353+
else:
354+
w = wh
355+
h = wh
345356
if centre is not None:
346357
cx, cy = centre
347358
if l is None:
@@ -356,17 +367,26 @@ def plot_box(
356367
pass
357368
if b is None:
358369
try:
359-
b = t - h
370+
t = b + h
360371
except:
361372
pass
362373
if b is None:
363374
try:
364-
b = cy + h / 2
375+
t = cy - h / 2
365376
except:
366377
pass
367378

368379
ax = axes_logic(ax, 2)
369380

381+
if ax.yaxis_inverted():
382+
# if y-axis is flipped, switch top and bottom
383+
t, b = b, t
384+
385+
if l >= r:
386+
raise ValueError("left must be less than right")
387+
if b >= t:
388+
raise ValueError("bottom must be less than top")
389+
370390
if filled:
371391
if w is None:
372392
try:
@@ -401,9 +421,9 @@ def plot_box(
401421
t = cy + h / 2
402422
except:
403423
pass
404-
r = plt.plot([l, l, r, r, l], [b, t, t, b, b], *fmt, **kwargs)
424+
r = plt.plot([l, l, r, r, l], [b, t, t, b, b], *fmt, **kwargs)[0]
405425

406-
return [r]
426+
return r
407427

408428

409429
def plot_poly(vertices, *fmt, close=False, **kwargs):
@@ -451,7 +471,7 @@ def circle(centre=(0, 0), radius=1, resolution=50):
451471

452472

453473
def plot_circle(
454-
radius, *fmt, centre=(0, 0), resolution=50, ax=None, filled=False, **kwargs
474+
radius, centre=(0, 0), *fmt, resolution=50, ax=None, filled=False, **kwargs
455475
):
456476
"""
457477
Plot a circle using matplotlib
@@ -633,9 +653,9 @@ def sphere(radius=1, centre=(0, 0, 0), resolution=50):
633653

634654
Phi, Theta = np.meshgrid(phi_range, theta_range)
635655

636-
x = radius * np.sin(Theta) * np.cos(Phi)
637-
y = radius * np.sin(Theta) * np.sin(Phi)
638-
z = radius * np.cos(Theta)
656+
x = radius * np.sin(Theta) * np.cos(Phi) + centre[0]
657+
y = radius * np.sin(Theta) * np.sin(Phi) + centre[1]
658+
z = radius * np.cos(Theta) + centre[2]
639659

640660
return (x, y, z)
641661

@@ -727,7 +747,7 @@ def ellipsoid(
727747
# process the probability
728748
from scipy.stats.distributions import chi2
729749

730-
s = math.sqrt(chi2.ppf(confidence, df=2)) * scale
750+
s = math.sqrt(chi2.ppf(confidence, df=3)) * scale
731751
else:
732752
s = scale
733753

@@ -1164,6 +1184,7 @@ def plotvol2(dim, ax=None, equal=True, grid=False, labels=True):
11641184
ax.set_aspect("equal")
11651185
if grid:
11661186
ax.grid(True)
1187+
ax.set_axisbelow(True)
11671188

11681189
# signal to related functions that plotvol set the axis limits
11691190
ax._plotvol = True

spatialmath/base/numeric.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,37 @@ def numjac(f, x, dx=1e-8, SO=0, SE=0):
5959

6060
return np.c_[Jcol].T
6161

62+
def numhess(J, x, dx=1e-8):
63+
r"""
64+
Numerically compute Hessian of Jacobian function
65+
66+
:param J: the Jacobian function, returns an ndarray(m,n)
67+
:type J: callable
68+
:param x: function argument
69+
:type x: ndarray(n)
70+
:param dx: the numerical perturbation, defaults to 1e-8
71+
:type dx: float, optional
72+
:return: Hessian matrix
73+
:rtype: ndarray(m,n,n)
74+
75+
Computes a numerical approximation to the Hessian for ``J(x)`` where
76+
:math:`f: \mathbb{R}^n \mapsto \mathbb{R}^{m \times n}`
77+
78+
Uses first-order difference :math:`H[:,:,i] = (J(x + dx) - J(x)) / dx`.
79+
"""
80+
81+
I = np.eye(len(x))
82+
Hcol = []
83+
J0 = J(x)
84+
for i in range(len(x)):
85+
86+
Ji = J(x + I[:,i] * dx)
87+
Hi = (Ji - J0) / dx
88+
89+
Hcol.append(Hi)
90+
91+
return np.stack(Hcol, axis=2)
92+
6293
def array2str(X, valuesep=", ", rowsep=" | ", fmt="{:.3g}",
6394
brackets=("[ ", " ]"), suppress_small=True):
6495
"""

0 commit comments

Comments
 (0)