diff --git a/Makefile b/Makefile index c273a3f..8e0e706 100644 --- a/Makefile +++ b/Makefile @@ -4,11 +4,19 @@ BLUE=\033[0;34m BLACK=\033[0;30m help: - @echo "$(BLUE) make dist - build dist files" + @echo "$(BLUE) make test - run the fast test suite" + @echo " make testall - run the full test suite, including slow notebook/example tests" + @echo " make dist - build dist files" @echo " make upload - upload to PyPI" @echo " make clean - remove dist and docs build files" @echo " make help - this message$(BLACK)" +test: .FORCE + pytest + +testall: .FORCE + pytest --runall + dist: .FORCE # $(MAKE) test python -m build diff --git a/RVC3/examples/imu_data.py b/RVC3/examples/imu_data.py index c7dbfdf..09beeb5 100755 --- a/RVC3/examples/imu_data.py +++ b/RVC3/examples/imu_data.py @@ -20,7 +20,7 @@ import matplotlib.pyplot as plt -def tumble(): +def IMU(): # accelerometer g0 = unitvec( [0, 0, 9.8] ).T gbias = 0.02 * np.r_[2, -2, 2].T # bias 2% of norm @@ -30,7 +30,7 @@ def tumble(): mbias = 0.02 * np.r_[-1, -1, 2] # bias 2# of norm # gyro - wbias = 0.05 * np.r_[-1, 2, -1] # bias 5% of max + wbias = 0.05 * np.r_[-1, 2, -1] # bias 10% of max ## simulation @@ -56,7 +56,7 @@ def tumble(): w0, t) # Solve for simulated sensor readings and true attitude - # 1 column per timestep + # 1 row per timestep am = np.zeros(omega.shape) mm = np.zeros(omega.shape) @@ -71,8 +71,9 @@ def tumble(): # add bias to measured wm = omega + wbias - data = namedtuple('tumble', 't omega_true attitude_true g B gyro accel magno') - return data(t, omega, truth, g0, m0, wm, am, mm) + imu = namedtuple('imu', 't dt gyro accel magno') + true = namedtuple('true', 't dt omega orientation g B') + return true(t, dt, omega, truth, g0, m0), imu(t, dt, wm, am, mm) if __name__ == "__main__": @@ -82,14 +83,14 @@ def plot(t, y, title): plt.grid(True) plt.title(title) - data = tumble() + true, imu = IMU() - print(data.attitude_true[100]) - print(data.attitude_true[100].rpy()) + print(true.orientation[100]) + print(true.orientation[100].rpy()) - plot(data.t, data.attitude_true.rpy(), 'attitude') - plot(data.t, data.gyro, 'gyro') - plot(data.t, data.accel, 'accel') - plot(data.t, data.magno, 'magno') + plot(true.t, true.orientation.rpy(), 'attitude') + plot(imu.t, imu.gyro, 'gyro') + plot(imu.t, imu.accel, 'accel') + plot(imu.t, imu.magno, 'magno') plt.show(block=True) \ No newline at end of file diff --git a/RVC3/examples/visodom.py b/RVC3/examples/visodom.py index cac2612..9e55f28 100755 --- a/RVC3/examples/visodom.py +++ b/RVC3/examples/visodom.py @@ -18,11 +18,15 @@ #!/usr/bin/env python3 # from RVC3.tools import rvcprint +import sys import numpy as np import matplotlib.pyplot as plt from machinevisiontoolbox import * import spatialmath.base as smb +# optional CLI arg: max number of frames to process, eg. `%run -m visodom 50` +max_frames = int(sys.argv[1]) if len(sys.argv) > 1 else None + # load .enpeda dataset, 12bit pixel values args = dict(mono=True, dtype="uint8", maxintval=4095, roi=[20, 750, 20, 480]) try: @@ -54,16 +58,18 @@ n_no_overlap = 0 n_optimized = 0 -for left, right in zip(lefts, rights): +for left_img, right_img in zip(lefts, rights): + if max_frames is not None and nframes >= max_frames: + break nframes += 1 - print("-----------------", left.id) + print("-----------------", left_img.id) # plt.clf() # plt.imshow(image.A, cmap='gray') # smb.plot_text((20, 420), f"frame {image.id}", color='w', backgroundcolor='k', fontsize=12) # find corner features - orbL = left.ORB(nfeatures=400, id="index") - orbR = right.ORB(nfeatures=400) + orbL = left_img.ORB(nfeatures=400, id="index") + orbR = right_img.ORB(nfeatures=400) # robustly match left and right corner features # - stereo match @@ -74,7 +80,7 @@ # too few/degenerate stereo correspondences to triangulate this # frame at all -- skip it entirely, keep the last good frame as # the temporal reference for the next one - print(f" stereo F estimation failed for frame {left.id}: {e}") + print(f" stereo F estimation failed for frame {left_img.id}: {e}") n_lr_fail += 1 continue print(matchLR) @@ -91,15 +97,17 @@ P, d = lines1.closest_to_line(lines2) print(np.nanmedian(d)) - if left.id > 0: + if left_img.id > 0: # if we have a previous frame # display two sequential stereo pairs plt.clf() - view4 = Image.Tile([left, right, left_prev, right_prev], columns=2, sep=0) + view4 = Image.Tile( + [left_img, right_img, left_prev, right_prev], columns=2, sep=0 + ) plt.imshow(view4.A, cmap="gray") - matchLR.plot_correspondence("y", offset=(left.width, 0), linewidth=0.5) + matchLR.plot_correspondence("y", offset=(left_img.width, 0), linewidth=0.5) # temporal matching matchFB = orbL.match(orbL_prev) @@ -107,18 +115,18 @@ F = matchFB.estimate(cam.points2F, method="ransac") print(matchFB) matchFB = matchFB.inliers # keep the inliers - matchFB.plot_correspondence("y", offset=(0, left.height), linewidth=0.5) + matchFB.plot_correspondence("y", offset=(0, left_img.height), linewidth=0.5) plt.pause(0.1) except ValueError as e: # too few/degenerate temporal correspondences -- same downstream # effect as zero overlapping landmarks below (no valid frame # motion estimate), but caught earlier since points2F() itself # can't even find a fundamental matrix here - print(f" temporal F estimation failed for frame {left.id}: {e}") + print(f" temporal F estimation failed for frame {left_img.id}: {e}") n_fb_fail += 1 matchFB = None - # if left.id == 10: + # if left_img.id == 10: # rvcprint.rvcprint(thicken=None) # now create a bundle adjustment problem, if we have a usable @@ -134,9 +142,7 @@ c_left = ba.add_view( SE3(), fixed=True ) # first camera at origin (current frame) - c_leftprev = ba.add_view( - SE3() - ) # initial guess, zero motion (prev frame) + c_leftprev = ba.add_view(SE3()) # initial guess, zero motion (prev frame) for k, Pk in enumerate(P.T): # for every 3D point from stereo if np.any(np.isnan(Pk)): @@ -148,9 +154,7 @@ continue landmark = ba.add_landmark(Pk) ba.add_projection(c_left, landmark, m.p1) # current left camera - ba.add_projection( - c_leftprev, landmark, m.p2 - ) # previous left camera + ba.add_projection(c_leftprev, landmark, m.p2) # previous left camera landmarks_added = True if landmarks_added: @@ -161,7 +165,7 @@ else: print( f" no overlapping stereo/temporal landmarks for frame " - f"{left.id} -- skipping bundle adjustment" + f"{left_img.id} -- skipping bundle adjustment" ) n_no_overlap += 1 displacements.append(np.full(6, np.nan)) @@ -169,8 +173,8 @@ # keep images and features for next cycle orbL_prev = orbL - left_prev = left - right_prev = right + left_prev = left_img + right_prev = right_img print() print("===== summary =====") diff --git a/RVC3/models/IBVS-holonomic.bd b/RVC3/models/IBVS-holonomic.bd index 3dd1f77..99c64d8 100644 --- a/RVC3/models/IBVS-holonomic.bd +++ b/RVC3/models/IBVS-holonomic.bd @@ -613,7 +613,7 @@ false ], [ - "args", + "fargs", [] ], [ diff --git a/RVC3/models/IBVS-nonholonomic.bd b/RVC3/models/IBVS-nonholonomic.bd index 43d9511..4fc5be3 100644 --- a/RVC3/models/IBVS-nonholonomic.bd +++ b/RVC3/models/IBVS-nonholonomic.bd @@ -299,11 +299,11 @@ false ], [ - "args", + "fargs", [] ], [ - "kwargs", + "fkwargs", {} ], [ @@ -491,11 +491,11 @@ false ], [ - "args", + "fargs", [] ], [ - "kwargs", + "fkwargs", {} ], [ @@ -572,11 +572,11 @@ true ], [ - "args", + "fargs", [] ], [ - "kwargs", + "fkwargs", {} ], [ @@ -996,7 +996,7 @@ false ], [ - "args", + "fargs", [] ], [ @@ -1162,7 +1162,7 @@ false ], [ - "args", + "fargs", [] ], [ diff --git a/RVC3/models/IBVS-quadrotor.bd b/RVC3/models/IBVS-quadrotor.bd index fd58f3a..ca14831 100644 --- a/RVC3/models/IBVS-quadrotor.bd +++ b/RVC3/models/IBVS-quadrotor.bd @@ -513,11 +513,11 @@ "=quadrotor" ], [ - "maxw", + "wmax", 1000 ], [ - "minw", + "wmin", 5 ], [ diff --git a/RVC3/models/braitenberg.py b/RVC3/models/braitenberg.py index 5883f6a..c022a65 100755 --- a/RVC3/models/braitenberg.py +++ b/RVC3/models/braitenberg.py @@ -52,7 +52,7 @@ def sensorfunc(x, offset): scale=[0, 100], size=5, shape="box", - trail=True, + path="b:", name="sensor field", init=background_graphics, ) diff --git a/RVC3/models/feedforward-main.py b/RVC3/models/feedforward-main.py index 2452012..77c5f10 100755 --- a/RVC3/models/feedforward-main.py +++ b/RVC3/models/feedforward-main.py @@ -28,5 +28,5 @@ import matplotlib.pyplot as plt -plt.plot(out.t, out.x[:, 1], out.t, out.y0[:, 1]) +plt.plot(out.t, out.x[:, 1], out.t, out.y[:, 1]) plt.show(block=True) diff --git a/RVC3/models/ploop_test.bd b/RVC3/models/ploop_test.bd new file mode 100644 index 0000000..beb5708 --- /dev/null +++ b/RVC3/models/ploop_test.bd @@ -0,0 +1,417 @@ +{ + "id": 140155287536736, + "created_by": "corkep", + "creation_time": 1632729537, + "simulation_time": 10.0, + "scene_width": 7168.000000000001, + "scene_height": 3785.6000000000004, + "blocks": [ + { + "id": 140271798589712, + "block_type": "SUBSYSTEM", + "title": "position loop", + "pos_x": -80.0, + "pos_y": -100.0, + "width": 200, + "height": 150, + "flipped": false, + "inputsNum": 3, + "outputsNum": 2, + "inputs": [ + { + "id": 140271798640800, + "index": 0, + "multi_wire": true, + "position": 1, + "socket_type": 1 + }, + { + "id": 140271798671248, + "index": 1, + "multi_wire": true, + "position": 1, + "socket_type": 1 + }, + { + "id": 140271798671728, + "index": 2, + "multi_wire": true, + "position": 1, + "socket_type": 1 + } + ], + "outputs": [ + { + "id": 140271799528944, + "index": 0, + "multi_wire": true, + "position": 3, + "socket_type": 2 + }, + { + "id": 140271798671488, + "index": 1, + "multi_wire": true, + "position": 3, + "socket_type": 2 + } + ], + "parameters": [ + [ + "subsys", + null + ], + [ + "nin", + 3 + ], + [ + "nout", + 2 + ], + [ + "blockargs", + null + ], + [ + "inport labels", + null + ], + [ + "outport labels", + null + ] + ] + }, + { + "id": 140271799528224, + "block_type": "LSPB", + "title": "trapezoidal trajectory", + "pos_x": -400.0, + "pos_y": -100.0, + "width": 100, + "height": 120.0, + "flipped": false, + "inputsNum": 0, + "outputsNum": 3, + "inputs": [], + "outputs": [ + { + "id": 140271799528320, + "index": 0, + "multi_wire": true, + "position": 3, + "socket_type": 2 + }, + { + "id": 140271799528272, + "index": 1, + "multi_wire": true, + "position": 3, + "socket_type": 2 + }, + { + "id": 140271799528512, + "index": 2, + "multi_wire": true, + "position": 3, + "socket_type": 2 + } + ], + "parameters": [ + [ + "q0", + null + ], + [ + "qf", + null + ], + [ + "T", + null + ], + [ + "blockargs", + null + ] + ] + }, + { + "id": 140271799602144, + "block_type": "CONSTANT", + "title": "0", + "pos_x": -400.0, + "pos_y": 60.0, + "width": 100, + "height": 100, + "flipped": false, + "inputsNum": 0, + "outputsNum": 1, + "inputs": [], + "outputs": [ + { + "id": 140271799602096, + "index": 0, + "multi_wire": true, + "position": 3, + "socket_type": 2 + } + ], + "parameters": [ + [ + "value", + "0" + ], + [ + "blockargs", + null + ] + ] + }, + { + "id": 140271799602336, + "block_type": "SCOPE", + "title": "q*, q", + "pos_x": 240.0, + "pos_y": -180.0, + "width": 100, + "height": 100, + "flipped": false, + "inputsNum": 2, + "outputsNum": 0, + "inputs": [ + { + "id": 140271799602432, + "index": 0, + "multi_wire": true, + "position": 1, + "socket_type": 1 + }, + { + "id": 140271799602768, + "index": 1, + "multi_wire": true, + "position": 1, + "socket_type": 1 + } + ], + "outputs": [], + "parameters": [ + [ + "nin", + 2 + ], + [ + "vector", + null + ], + [ + "styles", + null + ], + [ + "stairs", + false + ], + [ + "scale", + "auto" + ], + [ + "labels", + null + ], + [ + "grid", + true + ], + [ + "watch", + false + ], + [ + "blockargs", + null + ] + ] + }, + { + "id": 140271799619696, + "block_type": "SCOPE", + "title": "q error", + "pos_x": 240.0, + "pos_y": -20.0, + "width": 100, + "height": 100, + "flipped": false, + "inputsNum": 1, + "outputsNum": 0, + "inputs": [ + { + "id": 140271799620272, + "index": 0, + "multi_wire": true, + "position": 1, + "socket_type": 1 + } + ], + "outputs": [], + "parameters": [ + [ + "nin", + 1 + ], + [ + "vector", + null + ], + [ + "styles", + null + ], + [ + "stairs", + false + ], + [ + "scale", + "auto" + ], + [ + "labels", + null + ], + [ + "grid", + true + ], + [ + "watch", + false + ], + [ + "blockargs", + null + ] + ] + } + ], + "wires": [ + { + "id": 140271799600560, + "start_socket": 140271799528320, + "end_socket": 140271798640800, + "wire_type": 3, + "custom_routing": false, + "wire_coordinates": [] + }, + { + "id": 140271799602048, + "start_socket": 140271799528272, + "end_socket": 140271798671248, + "wire_type": 3, + "custom_routing": false, + "wire_coordinates": [] + }, + { + "id": 140271799602480, + "start_socket": 140271799602096, + "end_socket": 140271798671728, + "wire_type": 3, + "custom_routing": true, + "wire_coordinates": [ + [ + -80.0, + -20.0 + ], + [ + -120.0, + -20.0 + ], + [ + -120.0, + 100.0 + ], + [ + -300.0, + 100.0 + ] + ] + }, + { + "id": 140271799620512, + "start_socket": 140271799528944, + "end_socket": 140271799602768, + "wire_type": 3, + "custom_routing": false, + "wire_coordinates": [] + }, + { + "id": 140271799620944, + "start_socket": 140271798671488, + "end_socket": 140271799620272, + "wire_type": 3, + "custom_routing": false, + "wire_coordinates": [] + }, + { + "id": 140269547876656, + "start_socket": 140271799528320, + "end_socket": 140271799602432, + "wire_type": 3, + "custom_routing": true, + "wire_coordinates": [ + [ + 240.0, + -140.0 + ], + [ + -180.0, + -140.0 + ], + [ + -180.0, + -60.0 + ], + [ + -300.0, + -60.0 + ] + ] + } + ], + "labels": [ + { + "id": 140269547876416, + "text": "disturbance torque", + "pos_x": -280.0, + "pos_y": 100.0, + "width": 124, + "height": 24, + "fill_color": [ + 255, + 255, + 255, + 255 + ], + "styling": "\n
\ndisturbance torque
" + }, + { + "id": 140269547877472, + "text": "velocity feedforward", + "pos_x": -270.0, + "pos_y": -40.0, + "width": 133, + "height": 24, + "fill_color": [ + 255, + 255, + 255, + 255 + ], + "styling": "\n\nvelocity feedforward
" + } + ], + "grouping_boxes": [] +} \ No newline at end of file diff --git a/RVC3/models/ploop_test.py b/RVC3/models/ploop_test.py new file mode 100755 index 0000000..6dd735d --- /dev/null +++ b/RVC3/models/ploop_test.py @@ -0,0 +1,51 @@ +#! /usr/bin/env python + +""" +Creates Fig 9.13 +Robotics, Vision & Control for Python, P. Corke, Springer 2023. +Copyright (c) 2021- Peter Corke +""" + +import site +import numpy as np +import bdsim + +from roboticstoolbox import quintic_func + +site.addsitedir("bdsim") + +from ploop import ploop, G + +# test harness +sim = bdsim.BDSim() +bd = sim.blockdiagram() + +# position = bd.quintic(0) HACK + +quinticfunc = quintic_func(0, 1, 1) +trajectory = bd.FUNCTION(quinticfunc, nout=3, name="quintic") +time = bd.TIME() + +taud = bd.CONSTANT(20 / G) +# speed = bd.WAVEFORM(wave='triangle', freq=2, amplitude=25) +PLOOP = bd.SUBSYSTEM(ploop, name="PLOOP") +theta_scope = bd.SCOPE(nin=2, name=r"$\theta$", labels=["actual", "demand"]) +werr_scope = bd.SCOPE(name=r"$\omega$ error") +tau_scope = bd.SCOPE(name=r"$\tau$") +wff_scope = bd.SCOPE(name=r"$\omega_{ff}$") + +bd.connect(time, trajectory) +bd.connect(trajectory[0], PLOOP[0], theta_scope[1]) +bd.connect(trajectory[1], PLOOP[1], wff_scope) +bd.connect(taud, PLOOP[2]) +bd.connect(PLOOP[0], theta_scope[0]) + +bd.connect(PLOOP[2], werr_scope) +bd.connect(PLOOP[3], tau_scope) + +bd.compile() # check the diagram + + +if __name__ == "__main__": + sim.report(bd) + out = sim.run(bd, 1, dt=1e-3) diff --git a/RVC3/models/vloop_test.bd b/RVC3/models/vloop_test.bd new file mode 100644 index 0000000..1dc5dfa --- /dev/null +++ b/RVC3/models/vloop_test.bd @@ -0,0 +1,625 @@ +{ + "id": 140192552973888, + "created_by": "corkep", + "creation_time": 1632727013, + "scene_width": 7168.000000000001, + "scene_height": 4368.0, + "blocks": [ + { + "id": 140588467244768, + "block_type": "SUBSYSTEM", + "title": "velocity loop", + "pos_x": -160.0, + "pos_y": -120.0, + "width": 200, + "height": 150, + "flipped": false, + "inputsNum": 3, + "outputsNum": 4, + "inputs": [ + { + "id": 140588467291760, + "index": 0, + "multi_wire": true, + "position": 1, + "socket_type": 1 + }, + { + "id": 140588467322208, + "index": 1, + "multi_wire": true, + "position": 1, + "socket_type": 1 + }, + { + "id": 140588467322688, + "index": 2, + "multi_wire": true, + "position": 1, + "socket_type": 1 + } + ], + "outputs": [ + { + "id": 140588467291808, + "index": 0, + "multi_wire": true, + "position": 3, + "socket_type": 2 + }, + { + "id": 140588467322448, + "index": 1, + "multi_wire": true, + "position": 3, + "socket_type": 2 + }, + { + "id": 140588467322640, + "index": 2, + "multi_wire": true, + "position": 3, + "socket_type": 2 + }, + { + "id": 140588473625424, + "index": 3, + "multi_wire": true, + "position": 3, + "socket_type": 2 + } + ], + "parameters": [ + [ + "subsys", + null + ], + [ + "nin", + 3 + ], + [ + "nout", + 4 + ], + [ + "blockargs", + null + ], + [ + "inport labels", + null + ], + [ + "outport labels", + null + ] + ] + }, + { + "id": 140588473655152, + "block_type": "INTERPOLATE", + "title": "velocity profile", + "pos_x": -380.0, + "pos_y": -120.0, + "width": 100, + "height": 100, + "flipped": false, + "inputsNum": 1, + "outputsNum": 1, + "inputs": [ + { + "id": 140588473655248, + "index": 0, + "multi_wire": true, + "position": 1, + "socket_type": 1 + } + ], + "outputs": [ + { + "id": 140588473655200, + "index": 0, + "multi_wire": true, + "position": 3, + "socket_type": 2 + } + ], + "parameters": [ + [ + "x", + null + ], + [ + "y", + null + ], + [ + "xy", + null + ], + [ + "time", + false + ], + [ + "kind", + "linear" + ], + [ + "blockargs", + null + ] + ] + }, + { + "id": 140588473690960, + "block_type": "SCOPE", + "title": "\u03c9", + "pos_x": 160.0, + "pos_y": -240.0, + "width": 100, + "height": 100, + "flipped": false, + "inputsNum": 2, + "outputsNum": 0, + "inputs": [ + { + "id": 140588473691056, + "index": 0, + "multi_wire": true, + "position": 1, + "socket_type": 1 + }, + { + "id": 140588473717424, + "index": 1, + "multi_wire": true, + "position": 1, + "socket_type": 1 + } + ], + "outputs": [], + "parameters": [ + [ + "nin", + 2 + ], + [ + "styles", + "None" + ], + [ + "scale", + "auto" + ], + [ + "labels", + [ + "w*", + "w" + ] + ], + [ + "grid", + "true" + ], + [ + "labels", + null + ], + [ + "grid", + true + ], + [ + "watch", + false + ], + [ + "blockargs", + null + ] + ] + }, + { + "id": 140588473691200, + "block_type": "SCOPE", + "title": "\u03c9 error", + "pos_x": 160.0, + "pos_y": -100.0, + "width": 100, + "height": 100, + "flipped": false, + "inputsNum": 1, + "outputsNum": 0, + "inputs": [ + { + "id": 140588473691488, + "index": 0, + "multi_wire": true, + "position": 1, + "socket_type": 1 + } + ], + "outputs": [], + "parameters": [ + [ + "nin", + 1 + ], + [ + "styles", + "None" + ], + [ + "scale", + "auto" + ], + [ + "labels", + "none" + ], + [ + "grid", + "true" + ], + [ + "labels", + null + ], + [ + "grid", + true + ], + [ + "watch", + false + ], + [ + "blockargs", + null + ] + ] + }, + { + "id": 140588473691632, + "block_type": "SCOPE", + "title": "motor torque", + "pos_x": 160.0, + "pos_y": 40.0, + "width": 100, + "height": 100, + "flipped": false, + "inputsNum": 1, + "outputsNum": 0, + "inputs": [ + { + "id": 140588473691920, + "index": 0, + "multi_wire": true, + "position": 1, + "socket_type": 1 + } + ], + "outputs": [], + "parameters": [ + [ + "nin", + 1 + ], + [ + "styles", + null + ], + [ + "scale", + "auto" + ], + [ + "labels", + null + ], + [ + "grid", + true + ], + [ + "labels", + null + ], + [ + "grid", + true + ], + [ + "watch", + false + ], + [ + "blockargs", + null + ] + ] + }, + { + "id": 140588473692064, + "block_type": "SCOPE", + "title": "integral term", + "pos_x": 160.0, + "pos_y": 180.0, + "width": 100, + "height": 100, + "flipped": false, + "inputsNum": 1, + "outputsNum": 0, + "inputs": [ + { + "id": 140588473688128, + "index": 0, + "multi_wire": true, + "position": 1, + "socket_type": 1 + } + ], + "outputs": [], + "parameters": [ + [ + "nin", + 1 + ], + [ + "styles", + null + ], + [ + "scale", + "auto" + ], + [ + "labels", + null + ], + [ + "grid", + true + ], + [ + "labels", + null + ], + [ + "grid", + true + ], + [ + "watch", + false + ], + [ + "blockargs", + null + ] + ] + }, + { + "id": 140588473720064, + "block_type": "CONSTANT", + "title": "disturbance torque", + "pos_x": -380.0, + "pos_y": 20.0, + "width": 100, + "height": 100, + "flipped": false, + "inputsNum": 0, + "outputsNum": 1, + "inputs": [], + "outputs": [ + { + "id": 140588473719776, + "index": 0, + "multi_wire": true, + "position": 3, + "socket_type": 2 + } + ], + "parameters": [ + [ + "value", + null + ], + [ + "blockargs", + null + ] + ] + }, + { + "id": 140588473719200, + "block_type": "CONSTANT", + "title": "feedforward torque", + "pos_x": -380.0, + "pos_y": 160.0, + "width": 100, + "height": 100, + "flipped": false, + "inputsNum": 0, + "outputsNum": 1, + "inputs": [], + "outputs": [ + { + "id": 140588473719440, + "index": 0, + "multi_wire": true, + "position": 3, + "socket_type": 2 + } + ], + "parameters": [ + [ + "value", + null + ], + [ + "blockargs", + null + ] + ] + }, + { + "id": 140237887245040, + "block_type": "TIME", + "title": "time", + "pos_x": -520.0, + "pos_y": -120.0, + "width": 100, + "height": 100, + "flipped": false, + "inputsNum": 0, + "outputsNum": 1, + "inputs": [], + "outputs": [ + { + "id": 140237887245136, + "index": 0, + "multi_wire": true, + "position": 3, + "socket_type": 2 + } + ], + "parameters": [ + [ + "blockargs", + null + ] + ] + } + ], + "wires": [ + { + "id": 140588473688992, + "start_socket": 140588473655200, + "end_socket": 140588467291760, + "wire_type": 3, + "custom_routing": false, + "wire_coordinates": [] + }, + { + "id": 140588473718960, + "start_socket": 140588467291808, + "end_socket": 140588473717424, + "wire_type": 3, + "custom_routing": false, + "wire_coordinates": [] + }, + { + "id": 140588473719296, + "start_socket": 140588467322448, + "end_socket": 140588473691488, + "wire_type": 3, + "custom_routing": false, + "wire_coordinates": [] + }, + { + "id": 140588473719392, + "start_socket": 140588467322640, + "end_socket": 140588473691920, + "wire_type": 3, + "custom_routing": false, + "wire_coordinates": [] + }, + { + "id": 140588473718624, + "start_socket": 140588473719776, + "end_socket": 140588467322208, + "wire_type": 3, + "custom_routing": false, + "wire_coordinates": [] + }, + { + "id": 140237887272704, + "start_socket": 140237887245136, + "end_socket": 140588473655248, + "wire_type": 3, + "custom_routing": false, + "wire_coordinates": [] + }, + { + "id": 140192616650400, + "start_socket": 140588473655200, + "end_socket": 140588473691056, + "wire_type": 3, + "custom_routing": true, + "wire_coordinates": [ + [ + 160.0, + -200.0 + ], + [ + -220.0, + -200.0 + ], + [ + -220.0, + -80.0 + ], + [ + -280.0, + -80.0 + ] + ] + }, + { + "id": 140192616650544, + "start_socket": 140588473625424, + "end_socket": 140588473688128, + "wire_type": 3, + "custom_routing": true, + "wire_coordinates": [ + [ + 160.0, + 220.0 + ], + [ + 80.0, + 220.0 + ], + [ + 80.0, + -20.0 + ], + [ + 40.0, + -20.0 + ] + ] + }, + { + "id": 140192616650688, + "start_socket": 140588473719440, + "end_socket": 140588467322688, + "wire_type": 3, + "custom_routing": true, + "wire_coordinates": [ + [ + -160.0, + -40.0 + ], + [ + -200.0, + -40.0 + ], + [ + -200.0, + 200.0 + ], + [ + -280.0, + 200.0 + ] + ] + } + ], + "labels": [], + "grouping_boxes": [] +} \ No newline at end of file diff --git a/RVC3/models/vloop_test.py b/RVC3/models/vloop_test.py new file mode 100755 index 0000000..ecd06a7 --- /dev/null +++ b/RVC3/models/vloop_test.py @@ -0,0 +1,58 @@ +#! /usr/bin/env python + +""" +Creates Fig 9.8 +Robotics, Vision & Control for Python, P. Corke, Springer 2023. +Copyright (c) 2021- Peter Corke +""" + +import numpy as np +import bdsim + +import site +import numpy as np +import bdsim + +from vloop import vloop, B + + +# test harness for velocity loop +sim = bdsim.BDSim(graphics=True, progress=False) +bd = sim.blockdiagram() + +# parameters +disturbance = bd.CONSTANT(0, name="disturbance") +feedforward = bd.CONSTANT(0) +# speed = bd.WAVEFORM(wave='triangle', freq=2, amplitude=25) +speed = bd.INTERPOLATE( + x=(0, 0.1, 0.3, 0.4, 0.6, 1), y=(0, 0, 50, 50, 0, 0), time=True, name="demand" +) + +# import the velocity loop +VLOOP = bd.SUBSYSTEM(vloop, name="VLOOP") + +# scopes +w_scope = bd.SCOPE(nin=2, name=r"$\omega$", labels=["actual", "demand"]) +werr_scope = bd.SCOPE(name=r"$\omega_{err}$") +tau_scope = bd.SCOPE(name=r"$\tau$") +integral_scope = bd.SCOPE(name="integral") + +demand_scope = bd.SCOPE(name="demand scope") + +bd.connect(speed, demand_scope) + +bd.connect(VLOOP[0], w_scope[0]) +bd.connect(VLOOP[1], werr_scope) +bd.connect(VLOOP[2], tau_scope) +bd.connect(VLOOP[3], integral_scope) + +bd.connect(disturbance, VLOOP[1]) +bd.connect(speed, VLOOP[0], w_scope[1]) +bd.connect(feedforward, VLOOP[2]) + +bd.compile() # check the diagram + +if __name__ == "__main__": + sim.report(bd) + + out = sim.run(bd, 1, dt=1e-3) diff --git a/errata.md b/errata.md index 49f55bb..b5845a8 100644 --- a/errata.md +++ b/errata.md @@ -493,3 +493,103 @@ from roboticstoolbox.models.URDF.URDFRobot import URDF_read urdf, *_ = URDF_read("ur5") urdf ``` + +## §14.8.3 — `visodom.py` limited to 50 frames by default + +**Notebook:** `chap14.ipynb` + +**Reason:** The bridge dataset has 251 stereo frame pairs; processing all +of them (ORB matching + bundle adjustment per frame) takes several +minutes. Added an optional frame-count argument to +`RVC3/examples/visodom.py` (processes every frame if omitted) and the +notebook now passes 50 for a much faster run. Increase or drop the +argument to process more frames. + +**As printed:** +```python +%run -m visodom +``` + +**Current toolbox syntax:** +```python +%run -m visodom 50 +``` + +## §15.2.2 — bdsim clock log field renamed from `.x` to `.X` + +**Notebook:** `chap15.ipynb` + +**Reason:** bdsim's per-clock logged state array was renamed from a +lowercase `.x` attribute to `.X`, to match the uppercase convention +used for state-array fields elsewhere in a run's output struct (eg. +the top-level `out.x`/`out.xnames` pairing uses lowercase for the +continuous state but clocked/discrete state blocks use the +uppercase form). + +**As printed:** +```python +plt.plot(out.clock0.t, out.clock0.x) +``` + +**Current toolbox syntax:** +```python +plt.plot(out.clock0.t, out.clock0.X) +``` + +## §2.3.1.2 — `tripleangledemo` is now an installed CLI command + +**Notebook:** `chap2.ipynb` + +**Reason:** RTB used to ship `tripleangledemo.py` as a standalone +script; it's now `roboticstoolbox.demo.tripleangledemo`, exposed as +an installed console command (`[project.scripts]` entry point in +RTB's `pyproject.toml`) rather than a bare top-level module, so +`%run -m tripleangledemo` can no longer find it +(`'tripleangledemo' is not a valid modulename on sys.path`). Invoke +it as the installed command directly instead. + +**As printed:** +```python +%run -m tripleangledemo +``` + +**Current toolbox syntax:** +```python +!tripleangledemo +``` + +## §2.4.8 — `twistdemo` is now an installed CLI command + +**Notebook:** `chap2.ipynb` + +**Reason:** Same change as `tripleangledemo` above -- +`twistdemo.py` is now `roboticstoolbox.demo.twistdemo`, an installed +console command rather than a bare top-level module. + +**As printed:** +```python +%run -m twistdemo +``` + +**Current toolbox syntax:** +```python +!twistdemo +``` + +## §B.2.1 — `eigdemo` is now an installed CLI command + +**Notebook:** `app.ipynb` + +**Reason:** Same change as `tripleangledemo`/`twistdemo` above -- +`eigdemo.py` is now `roboticstoolbox.demo.eigdemo`, an installed +console command rather than a bare top-level module. + +**As printed:** +```python +%run -m eigdemo 1 2 3 4 +``` + +**Current toolbox syntax:** +```python +!eigdemo 1 2 3 4 +``` diff --git a/figures/code/chapter3/fig3_17.py b/figures/code/chapter3/fig3_17.py index 1730d16..eef7c58 100755 --- a/figures/code/chapter3/fig3_17.py +++ b/figures/code/chapter3/fig3_17.py @@ -10,7 +10,7 @@ from spatialmath.base import * from spatialmath.base import sym from spatialmath import SE3, SO2, SO3, UnitQuaternion -from rvcprint from RVC3.tools import rvcprint +from RVC3.tools import rvcprint # load the simulation data from imu_data import IMU diff --git a/notebooks/app.ipynb b/notebooks/app.ipynb index 8efbe43..ad300b4 100644 --- a/notebooks/app.ipynb +++ b/notebooks/app.ipynb @@ -23,7 +23,23 @@ "id": "1", "metadata": {}, "outputs": [], - "source": "try:\n from google.colab import output\n print('Running on CoLab')\n output.enable_custom_widget_manager()\n !pip install ipympl\n !pip install spatialmath-python\n COLAB = True\n SWIFT = False\nexcept ModuleNotFoundError:\n COLAB = False\n SWIFT = False\nfrom IPython.core.interactiveshell import InteractiveShell\nInteractiveShell.ast_node_interactivity = \"last_expr_or_assign\"\n\n%matplotlib widget" + "source": [ + "try:\n", + " from google.colab import output\n", + " print('Running on CoLab')\n", + " output.enable_custom_widget_manager()\n", + " !pip install ipympl\n", + " !pip install spatialmath-python\n", + " COLAB = True\n", + " SWIFT = False\n", + "except ModuleNotFoundError:\n", + " COLAB = False\n", + " SWIFT = False\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"last_expr_or_assign\"\n", + "\n", + "%matplotlib widget" + ] }, { "cell_type": "code", @@ -31,7 +47,28 @@ "id": "2", "metadata": {}, "outputs": [], - "source": "from IPython.display import HTML\n\n# add RTB examples folder to the path\nimport sys\nimport os.path\nimport roboticstoolbox as rtb\nsys.path.append(os.path.join(rtb.__path__[0], 'examples'))\n\n# standard imports\nimport numpy as np\nfrom scipy import linalg\nimport matplotlib.pyplot as plt\nimport math\nfrom math import pi\nnp.set_printoptions(\n linewidth=120, formatter={\n 'float': lambda x: f\"{0:8.4g}\" if abs(x) < 1e-10 else f\"{x:8.4g}\"})\nnp.random.seed(0)\nfrom spatialmath import *\nfrom spatialmath.base import *" + "source": [ + "from IPython.display import HTML\n", + "\n", + "# add RTB examples folder to the path\n", + "import sys\n", + "import os.path\n", + "import roboticstoolbox as rtb\n", + "sys.path.append(os.path.join(rtb.__path__[0], 'examples'))\n", + "\n", + "# standard imports\n", + "import numpy as np\n", + "from scipy import linalg\n", + "import matplotlib.pyplot as plt\n", + "import math\n", + "from math import pi\n", + "np.set_printoptions(\n", + " linewidth=120, formatter={\n", + " 'float': lambda x: f\"{0:8.4g}\" if abs(x) < 1e-10 else f\"{x:8.4g}\"})\n", + "np.random.seed(0)\n", + "from spatialmath import *\n", + "from spatialmath.base import *" + ] }, { "cell_type": "markdown", @@ -76,7 +113,7 @@ "metadata": {}, "outputs": [], "source": [ - "%run -m eigdemo 1 2 3 4" + "!eigdemo 1 2 3 4" ] }, { @@ -234,7 +271,9 @@ "id": "23", "metadata": {}, "outputs": [], - "source": "e, v = np.linalg.eigh(E)" + "source": [ + "e, v = np.linalg.eigh(E)" + ] }, { "cell_type": "markdown", @@ -825,7 +864,9 @@ "id": "79", "metadata": {}, "outputs": [], - "source": "g[1].neighbours()" + "source": [ + "g[1].neighbours()" + ] }, { "cell_type": "code", @@ -1080,4 +1121,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} diff --git a/notebooks/chap14.ipynb b/notebooks/chap14.ipynb index a86b71f..268a7da 100644 --- a/notebooks/chap14.ipynb +++ b/notebooks/chap14.ipynb @@ -23,7 +23,20 @@ "id": "1", "metadata": {}, "outputs": [], - "source": "try:\n import google.colab\n print('Running on CoLab')\n !pip install matplotlib\n !pip install machinevision-toolbox-python\n !pip install --no-deps rvc3python\n COLAB = True\nexcept:\n COLAB = False\n\nfrom IPython.core.interactiveshell import InteractiveShell\nInteractiveShell.ast_node_interactivity = \"last_expr_or_assign\"" + "source": [ + "try:\n", + " import google.colab\n", + " print('Running on CoLab')\n", + " !pip install matplotlib\n", + " !pip install machinevision-toolbox-python\n", + " !pip install --no-deps rvc3python\n", + " COLAB = True\n", + "except:\n", + " COLAB = False\n", + "\n", + "from IPython.core.interactiveshell import InteractiveShell\n", + "InteractiveShell.ast_node_interactivity = \"last_expr_or_assign\"" + ] }, { "cell_type": "code", @@ -31,7 +44,27 @@ "id": "2", "metadata": {}, "outputs": [], - "source": "from IPython.core.display import HTML\n\nimport RVC3 as rvc\nimport sys, os.path\nsys.path.append(os.path.join(rvc.__path__[0], 'examples'))\n\nimport numpy as np\nfrom scipy import linalg, stats\nimport matplotlib.pyplot as plt\nimport math\nfrom math import pi\nnp.set_printoptions(\n linewidth=120, formatter={\n 'float': lambda x: f\"{0:8.4g}\" if abs(x) < 1e-10 else f\"{x:8.4g}\"})\nnp.random.seed(0)\nfrom machinevisiontoolbox.base import *\nfrom machinevisiontoolbox import *\nfrom spatialmath.base import *\nfrom spatialmath import *" + "source": [ + "from IPython.core.display import HTML\n", + "\n", + "import RVC3 as rvc\n", + "import sys, os.path\n", + "sys.path.append(os.path.join(rvc.__path__[0], 'examples'))\n", + "\n", + "import numpy as np\n", + "from scipy import linalg, stats\n", + "import matplotlib.pyplot as plt\n", + "import math\n", + "from math import pi\n", + "np.set_printoptions(\n", + " linewidth=120, formatter={\n", + " 'float': lambda x: f\"{0:8.4g}\" if abs(x) < 1e-10 else f\"{x:8.4g}\"})\n", + "np.random.seed(0)\n", + "from machinevisiontoolbox.base import *\n", + "from machinevisiontoolbox import *\n", + "from spatialmath.base import *\n", + "from spatialmath import *" + ] }, { "cell_type": "markdown", @@ -1916,7 +1949,15 @@ "id": "181", "metadata": {}, "outputs": [], - "source": "if not COLAB:\n bunny_pcd = PointCloud.Read('bunny.ply')\n bunny_pcd.disp(block=True)\n pcd = bunny_pcd.voxel_grid(voxel_size=0.01).disp(block=True)\n pcd = bunny_pcd.downsample_voxel(voxel_size=0.01)\n pcd.normals(radius=0.1, max_nn=30)\n pcd.disp(block=True)" + "source": [ + "if not COLAB:\n", + " bunny_pcd = PointCloud.Read('bunny.ply')\n", + " bunny_pcd.disp(block=True)\n", + " pcd = bunny_pcd.voxel_grid(voxel_size=0.01).disp(block=True)\n", + " pcd = bunny_pcd.downsample_voxel(voxel_size=0.01)\n", + " pcd.normals(radius=0.1, max_nn=30)\n", + " pcd.disp(block=True)" + ] }, { "cell_type": "markdown", @@ -2142,7 +2183,10 @@ "id": "201", "metadata": {}, "outputs": [], - "source": "composite = composite.paste(images[0], (0, 0));\ncomposite.disp();" + "source": [ + "composite = composite.paste(images[0], (0, 0));\n", + "composite.disp();" + ] }, { "cell_type": "code", @@ -2184,7 +2228,10 @@ "id": "205", "metadata": {}, "outputs": [], - "source": "composite = composite.paste(tile, topleft, method=\"blend\");\ncomposite.disp();" + "source": [ + "composite = composite.paste(tile, topleft, method=\"blend\");\n", + "composite.disp();" + ] }, { "cell_type": "markdown", @@ -2237,7 +2284,13 @@ "cell_type": "markdown", "id": "210", "metadata": {}, - "source": "