magfit.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. #!/usr/bin/env python
  2. '''
  3. fit best estimate of magnetometer offsets
  4. '''
  5. from __future__ import print_function
  6. from builtins import range
  7. from argparse import ArgumentParser
  8. parser = ArgumentParser(description=__doc__)
  9. parser.add_argument("--no-timestamps", dest="notimestamps", action='store_true', help="Log doesn't have timestamps")
  10. parser.add_argument("--condition", default=None, help="select packets by condition")
  11. parser.add_argument("--noise", type=float, default=0, help="noise to add")
  12. parser.add_argument("--mag2", action='store_true', help="use 2nd mag from DF log")
  13. parser.add_argument("--radius", default=None, type=float, help="target radius")
  14. parser.add_argument("--plot", action='store_true', help="plot points in 3D")
  15. parser.add_argument("logs", metavar="LOG", nargs="+")
  16. args = parser.parse_args()
  17. from pymavlink import mavutil
  18. from pymavlink.rotmat import Vector3
  19. def noise():
  20. '''a noise vector'''
  21. from random import gauss
  22. v = Vector3(gauss(0, 1), gauss(0, 1), gauss(0, 1))
  23. v.normalize()
  24. return v * args.noise
  25. def select_data(data):
  26. ret = []
  27. counts = {}
  28. for d in data:
  29. mag = d
  30. key = "%u:%u:%u" % (mag.x/20,mag.y/20,mag.z/20)
  31. if key in counts:
  32. counts[key] += 1
  33. else:
  34. counts[key] = 1
  35. if counts[key] < 3:
  36. ret.append(d)
  37. print(len(data), len(ret))
  38. return ret
  39. def radius(mag, offsets):
  40. '''return radius give data point and offsets'''
  41. return (mag + offsets).length()
  42. def radius_cmp(a, b, offsets):
  43. '''return +1 or -1 for for sorting'''
  44. diff = radius(a, offsets) - radius(b, offsets)
  45. if diff > 0:
  46. return 1
  47. if diff < 0:
  48. return -1
  49. return 0
  50. def sphere_error(p, data):
  51. x,y,z,r = p
  52. if args.radius is not None:
  53. r = args.radius
  54. ofs = Vector3(x,y,z)
  55. ret = []
  56. for d in data:
  57. mag = d
  58. err = r - radius(mag, ofs)
  59. ret.append(err)
  60. return ret
  61. def fit_data(data):
  62. from scipy import optimize
  63. p0 = [0.0, 0.0, 0.0, 0.0]
  64. p1, ier = optimize.leastsq(sphere_error, p0[:], args=(data))
  65. if not ier in [1, 2, 3, 4]:
  66. raise RuntimeError("Unable to find solution")
  67. if args.radius is not None:
  68. r = args.radius
  69. else:
  70. r = p1[3]
  71. return (Vector3(p1[0], p1[1], p1[2]), r)
  72. def magfit(logfile):
  73. '''find best magnetometer offset fit to a log file'''
  74. print("Processing log %s" % filename)
  75. mlog = mavutil.mavlink_connection(filename, notimestamps=args.notimestamps)
  76. data = []
  77. last_t = 0
  78. offsets = Vector3(0,0,0)
  79. # now gather all the data
  80. while True:
  81. m = mlog.recv_match(condition=args.condition)
  82. if m is None:
  83. break
  84. if m.get_type() == "SENSOR_OFFSETS":
  85. # update current offsets
  86. offsets = Vector3(m.mag_ofs_x, m.mag_ofs_y, m.mag_ofs_z)
  87. if m.get_type() == "RAW_IMU":
  88. mag = Vector3(m.xmag, m.ymag, m.zmag)
  89. # add data point after subtracting the current offsets
  90. data.append(mag - offsets + noise())
  91. if m.get_type() == "MAG" and not args.mag2:
  92. offsets = Vector3(m.OfsX,m.OfsY,m.OfsZ)
  93. mag = Vector3(m.MagX,m.MagY,m.MagZ)
  94. data.append(mag - offsets + noise())
  95. if m.get_type() == "MAG2" and args.mag2:
  96. offsets = Vector3(m.OfsX,m.OfsY,m.OfsZ)
  97. mag = Vector3(m.MagX,m.MagY,m.MagZ)
  98. data.append(mag - offsets + noise())
  99. print("Extracted %u data points" % len(data))
  100. print("Current offsets: %s" % offsets)
  101. orig_data = data
  102. data = select_data(data)
  103. # remove initial outliers
  104. data.sort(lambda a,b : radius_cmp(a,b,offsets))
  105. data = data[len(data)/16:-len(data)/16]
  106. # do an initial fit
  107. (offsets, field_strength) = fit_data(data)
  108. for count in range(3):
  109. # sort the data by the radius
  110. data.sort(lambda a,b : radius_cmp(a,b,offsets))
  111. print("Fit %u : %s field_strength=%6.1f to %6.1f" % (
  112. count, offsets,
  113. radius(data[0], offsets), radius(data[-1], offsets)))
  114. # discard outliers, keep the middle 3/4
  115. data = data[len(data)/8:-len(data)/8]
  116. # fit again
  117. (offsets, field_strength) = fit_data(data)
  118. print("Final : %s field_strength=%6.1f to %6.1f" % (
  119. offsets,
  120. radius(data[0], offsets), radius(data[-1], offsets)))
  121. if args.plot:
  122. plot_data(orig_data, data)
  123. def plot_data(orig_data, data):
  124. '''plot data in 3D'''
  125. import matplotlib.pyplot as plt
  126. for dd, c in [(orig_data, 'r'), (data, 'b')]:
  127. fig = plt.figure()
  128. ax = fig.add_subplot(111, projection='3d')
  129. xs = [ d.x for d in dd ]
  130. ys = [ d.y for d in dd ]
  131. zs = [ d.z for d in dd ]
  132. ax.scatter(xs, ys, zs, c=c, marker='o')
  133. ax.set_xlabel('X Label')
  134. ax.set_ylabel('Y Label')
  135. ax.set_zlabel('Z Label')
  136. plt.show()
  137. total = 0.0
  138. for filename in args.logs:
  139. magfit(filename)