4

2 つのシステムがあり、それぞれに方向センサー (0 ~ 360 度) がありますが、センサーは、各システムの向きと各センサーの直線性に応じて、大幅に異なる値を提供できます。各システムが実際に指している場所のテーブルを生成するために使用できる機械的基準があります。これにより、3 つの列を持つテーブルが生成されます。

Physical  SystemA  SystemB
--------  -------  -------
 000.0     005.7    182.3
 005.0     009.8    178.4
 ...       ...      ...

表示されているデータだけから、SystemA は物理的な基準から遠くないことがわかりますが、SystemB は約 180 度ずれており、反対方向に進んでいます (逆さまに取り付けられていると想像してください)。

3 つの値すべての間を行き来できるようにする必要があります。SystemA が何かが 105.7 であると報告した場合、ユーザーにその物理的な方向を伝え、次に SystemB に同じ場所を指すように伝える必要があります。SystemB が最初のレポートを作成する場合も同様です。また、ユーザーは両方のシステムが希望する物理的な方向を指すように要求できるため、SystemA と SystemB はどこを指すかを指定する必要があります。

線形補間は難しくありませんが、データが反対方向に進み、モジュラー/サイクリックである場合に問題が発生します。

これらすべてのマッピングを行う Pythonic の方法はありますか?


編集: 2 つの対になった値のリストがある、最も難しいケースに注目しましょう。

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

リストが北などに位置合わせされていないポインターを持つ 2 つの異なるレーダーから取得されたとしますが、上記のデータは手動でターゲットを動かし、各レーダーがそれを見るためにどこを指さなければならないかを確認することで取得しました。

レーダー A が「I have a target at 123.4!」と言った場合、レーダー B をどこに向ければそれを確認できますか? レーダー B がターゲットを見つけたら、レーダー A にどこを指すように指示しますか?

リスト A は最後の要素と最初の要素の間で折り返されますが、リスト B はリストの中央近くで折り返されます。リスト A は単調に増加し、リスト B は単調に減少します。通常、A の度の大きさは B の度の大きさと同じではないことに注意してください。

次の場合に正しくラップする単純な補間器はありますか:

  1. リスト A からリスト B への補間。

  2. リスト B からリスト A への補間。

各方向に 1 つずつ、2 つの個別の補間インスタンスを使用しても問題ありません。線形 (一次) 補間で問題ないと仮定しますが、将来的には高次補間またはスプライン補間を使用したいと思うかもしれません。

いくつかのテストケース:

  • A = 356.7、B = ?

  • A = 179.2、B = ?

4

4 に答える 4

1

これは私にとってうまくいくものです。おそらくいくつかのクリーンアップを使用できます。

class InterpolatedArray(object):
    """ An array-like object that provides interpolated values between set points.
    """
    points = None
    wrap_value = None
    offset = None

    def _mod_delta(self, a, b):
        """ Perform a difference within a modular domain.
            Return a value in the range +/- wrap_value/2.
        """
        limit = self.wrap_value / 2.
        val = a - 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' is a list of two-element tuples, each of which maps
        an input value to an output value.  The list does not need to be sorted.

        Optional parameter 'wrap_value' is used when the domain is closed, to
        indicate that both the input and output domains wrap.  For example, a
        table of degree values would provide a 'wrap_value' of 360.0.

        After sorting, a wrapped domain's output values must all be monotonic
        in either the positive or negative direction.

        For tables that don't wrap, attempts to interpolate values outside the
        input range cause a ValueError exception.
        """
        if wrap_value is None:
            points.sort()   # Sort in-place on first element of each tuple
        else:   # Force values to positive modular range
            points = sorted([(p[0]%wrap_value, p[1]%wrap_value) for p in points])
            # Wrapped domains must be monotonic, positive or negative
            monotonic = [points[x][1] < points[x+1][1] for x in xrange(0,len(points)-1)]
            num_pos_steps = monotonic.count(True)
            num_neg_steps = monotonic.count(False)
            if num_pos_steps > 1 and num_neg_steps > 1: # Allow 1 wrap point
                raise ValueError("Table for wrapped domains must be monotonic.")
        self.wrap_value = wrap_value
        # Pre-compute inter-value slopes
        self.x_list, self.y_list = zip(*points)
        if wrap_value is None:
            intervals = zip(self.x_list, self.x_list[1:], self.y_list, self.y_list[1:])
            self.slopes = [(y2 - y1)/(x2 - x1) for 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) for x1, x2, y1, y2 in intervals]

    def __getitem__(self, x):       # Works with indexing operator []
        result = None
        if self.wrap_value is None:
            if x < self.x_list[0] or x > self.x_list[-1]:
                raise ValueError('Input value out-of-range: %s'%str(x))
            i = 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
            i = 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

そしてテスト:

import nose

def xfrange(start, stop, step=1.):
    """ Floating point equivalent to xrange()."""
    while start < stop:
        yield start
        start += step

# Test simple inverted mapping for non-wrapped domain
pts = [(x,-x) for x in xfrange(1.,16., 1.)]
a = InterpolatedArray(pts)
for i 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)) for x in xfrange(offset, wrap+offset, 10.)]
a = InterpolatedArray(pts, wrap)
for i in xfrange(0.5, wrap, 0.1):
    nose.tools.assert_almost_equal(a[i], (((wrap/2.) - i)%wrap))
于 2013-04-10T19:46:11.643 に答える
0

ただし、線形補間を使用できます。サンプル A の値がたとえば 7.75 の場合、それは 2.5 度に似ています。サンプル B の値が 180.35 の場合、2.5 度にも似ています。トリッキーな部分は、値がオーバーフローした場合です。一連のユニットテストをセットアップして、アルゴリズムが機能するかどうかを確認するだけで、すぐに開始できます。

于 2013-04-08T21:26:24.850 に答える
0

パート 1 の答え: キャリブレーション値 + ドリフト値を含む変換テーブル。

基本的に、DialA が物理的に 0 のときに 5.7 を報告し、5 のときに 9.7 を報告する場合、ドリフト値を各読み取り位置間の距離の +/- .25 に設定して、機械的ドリフトと読み取りドリフトを考慮します。

パート 2 の答え: 予想される位置を表示しながら、両方のダイヤルで同じ値を維持します。

方向に依存しない場合は、キャリブレーション テーブルに従って正しい位置になるまで、出力ダイヤルを回転させます。

方向に依存している場合は、最後の 1 ~ 2 つの値を追跡して方向を決定する必要があります。方向を決定したら、目的の位置に到達するまで、従属ダイヤルを必要な方向に移動できます。

キャリブレーション テーブルには、方向も含める必要があります (たとえば、正または負)。

上記の 2 つの部分を念頭に置いて、回転オフセットと方向反転を補正し、正確な位置と方向を読み取ることができます。

以下は、キャリブレーション テーブルを指定して位置と方向を生成するコードです。これにより、表示の問題が解決され、従属ダイヤルがプライマリ ダイヤルと一致するようになります。

#!/usr/bin/env python

# Calibration table
# calibrations[ device ][physical position]=recorded position
calibrations={}

calibrationsdrift=1.025

calibrations["WheelA"]={}

calibrations["WheelA"]={}
calibrations["WheelA"]["000.0"]=5.7
calibrations["WheelA"]["005.0"]=9.8
calibrations["WheelA"]["010.0"]=13.9
calibrations["WheelA"]["015.0"]=18.0

calibrations["WheelB"]={}
calibrations["WheelB"]["000.0"]=182.3
calibrations["WheelB"]["005.0"]=178.4
calibrations["WheelB"]["010.0"]=174.4
calibrations["WheelB"]["015.0"]=170.3


def physicalPosition( readout , device ):
        calibration=calibrations[device]
        for physicalpos,calibratedpos in calibration.items():
                if readout < ( calibratedpos + calibrationsdrift ):
                        if readout > ( calibratedpos - calibrationsdrift ):
                                return physicalpos
        return -0


print physicalPosition( 5.8 , "WheelA")
print physicalPosition( 9.8 , "WheelA")
print physicalPosition( 10.8 , "WheelA")


def physicalDirection( lastreadout, currentreadout, device ):
        lastposition=physicalPosition( lastreadout, device)
        currentposition=physicalPosition( currentreadout, device)
        # Assumes 360 = 0, so 355 is the last position before 0
        if lastposition < currentposition:
                if lastposition == 000.0:
                        if currentposition == 355:
                                return -1
                return 1
        else:
                return -1


print physicalDirection( 5.8, 10.8, "WheelA")
print physicalDirection( 10.8, 2.8, "WheelA")

print physicalDirection( 182, 174, "WheelB")
print physicalDirection( 170, 180, "WheelB")

プログラムを実行すると、パネル/デバイス/etc に後方に取り付けられている WheelB の場合でも、方向が正しく決定されることが示されます。

$ ./dials 
000.0
005.0
005.0
1
-1
1
-1

関数に供給される「読み出し」値の一部がオフになっていることに注意してください。これは、ドリフト値によって補正されます。必要かどうかは、接続している機器によって異なります。

于 2013-04-08T21:40:49.727 に答える