Circular interpolation in Python -
i have 2 systems, each of has direction sensor (0-360 degrees), sensors can provide wildly different values depending on orientation of each system , linearity of each sensor. have mechanical reference can use generate table of each system pointing. yields table 3 columns:
physical systema systemb -------- ------- ------- 000.0 005.7 182.3 005.0 009.8 178.4 ... ... ...
from data shown, can see systema isn't far physical reference, systemb 180 degrees off, , goes in opposite direction (imagine mounted upside-down).
i need able map , forth between 3 values: if systema reports @ 105.7, need tell user physical direction is, tell systemb point same location. same if systemb makes initial report. , user can request both systems point desired physical direction, systema , systemb need told point.
linear interpolation isn't hard, i'm having trouble when data going in opposite directions, , modular/cyclical.
is there pythonic way these mappings?
edit: let's focus on difficult case, have 2 paired lists of values:
a b ----- ----- 0.0 182.5 10.0 172.3 20.0 161.4 ... ... 170.0 9.7 180.0 359.1 190.0 348.2 ... ... 340.0 163.6 350.0 171.8
let's lists come 2 different radars pointers aren't aligned north or else, did manually take above data moving target around , seeing each radar had point see it.
when radar says "i have target @ 123.4!", need aim radar b see it? if radar b finds target, tell radar point?
list wraps between last , first elements, list b wraps nearer middle of list. list increases monotonically, while list b decreases monotonically. notice size of degree on not same size degree on b.
is there simple interpolator wrap correctly when:
interpolating list list b.
interpolating list b list a.
it ok use 2 separate interpolator instantiations, 1 going in each direction. i'll assume linear (first-order) interpolator ok, may want use higher-order or spline interpolation in future.
some test cases:
a = 356.7, b = ?
a = 179.2, b = ?
this works me. use clean-up.
class interpolatedarray(object): """ array-like object provides interpolated values between set points. """ points = none wrap_value = none offset = none def _mod_delta(self, a, b): """ perform difference within modular domain. return value in range +/- wrap_value/2. """ limit = self.wrap_value / 2. val = - b if val < -limit: val += self.wrap_value elif val > limit: val -= self.wrap_value return val def __init__(self, points, wrap_value=none): """initialization of interpolatedarray instance. parameter 'points' list of two-element tuples, each of maps input value output value. list not need sorted. optional parameter 'wrap_value' used when domain closed, indicate both input , output domains wrap. example, table of degree values provide 'wrap_value' of 360.0. after sorting, wrapped domain's output values must monotonic in either positive or negative direction. tables don't wrap, attempts interpolate values outside input range cause valueerror exception. """ if wrap_value none: points.sort() # sort in-place on first element of each tuple else: # force values positive modular range points = sorted([(p[0]%wrap_value, p[1]%wrap_value) p in points]) # wrapped domains must monotonic, positive or negative monotonic = [points[x][1] < points[x+1][1] x in xrange(0,len(points)-1)] num_pos_steps = monotonic.count(true) num_neg_steps = monotonic.count(false) if num_pos_steps > 1 , num_neg_steps > 1: # allow 1 wrap point raise valueerror("table wrapped domains must monotonic.") self.wrap_value = wrap_value # pre-compute inter-value slopes self.x_list, self.y_list = zip(*points) if wrap_value none: intervals = zip(self.x_list, self.x_list[1:], self.y_list, self.y_list[1:]) self.slopes = [(y2 - y1)/(x2 - x1) x1, x2, y1, y2 in intervals] else: # create modular slopes, including wrap element x_rot = list(self.x_list[1:]); x_rot.append(self.x_list[0]) y_rot = list(self.y_list[1:]); y_rot.append(self.y_list[0]) intervals = zip(self.x_list, x_rot, self.y_list, y_rot) self.slopes = [self._mod_delta(y2, y1)/self._mod_delta(x2, x1) x1, x2, y1, y2 in intervals] def __getitem__(self, x): # works indexing operator [] result = none if self.wrap_value none: if x < self.x_list[0] or x > self.x_list[-1]: raise valueerror('input value out-of-range: %s'%str(x)) = bisect.bisect_left(self.x_list, x) - 1 result = self.y_list[i] + self.slopes[i] * (x - self.x_list[i]) else: x %= self.wrap_value = bisect.bisect_left(self.x_list, x) - 1 result = self.y_list[i] + self.slopes[i] * self._mod_delta(x, self.x_list[i]) result %= self.wrap_value return result
and test:
import nose def xfrange(start, stop, step=1.): """ floating point equivalent xrange().""" while start < stop: yield start start += step # test simple inverted mapping non-wrapped domain pts = [(x,-x) x in xfrange(1.,16., 1.)] = interpolatedarray(pts) in xfrange(1., 15., 0.1): nose.tools.assert_almost_equal(a[i], -i) # cause expected over/under range errors result = false # assume failure try: x = a[0.5] except valueerror: result = true assert result result = false try: x = a[15.5] except valueerror: result = true assert result # test simple wrapped domain wrap = 360. offset = 1.234 pts = [(x,((wrap/2.) - x)) x in xfrange(offset, wrap+offset, 10.)] = interpolatedarray(pts, wrap) in xfrange(0.5, wrap, 0.1): nose.tools.assert_almost_equal(a[i], (((wrap/2.) - i)%wrap))
Comments
Post a Comment