def test_imu_input(dt_usec, downsampling_factor, accel_x, accel_y, accel_z):
    """Make sure the acceleration is updated correctly when there is no angular
    velocity (test with and without downsampling)

    Tests random accelerations in x, y, z directions (using the hypothesis
    decorator) with different update frequencies (using pytest's parametrize
    decorator)

    """
    time_usec = 100
    delta_ang = float_array([0, 0, 0])
    delta_vel = float_array([accel_x, accel_y, accel_z]) * dt_usec / 1e6

    ekf = ecl.Ekf()
    # Run to accumulate buffer (choose sample after downsampling)
    for _ in range(20 * downsampling_factor):
        time_usec += dt_usec
        ekf.set_imu_data(time_usec, dt_usec, dt_usec, delta_ang, delta_vel)

    imu_sample = ekf.get_imu_sample_delayed()
    assert imu_sample.delta_ang_x == pytest.approx(0.0, abs=1e-3)
    assert imu_sample.delta_ang_y == pytest.approx(0.0, abs=1e-3)
    assert imu_sample.delta_ang_z == pytest.approx(0.0, abs=1e-3)
    assert imu_sample.delta_vel_x == pytest.approx(accel_x * dt_usec *
                                                   downsampling_factor / 1e6,
                                                   abs=1e-3)
    assert imu_sample.delta_vel_y == pytest.approx(accel_y * dt_usec *
                                                   downsampling_factor / 1e6,
                                                   abs=1e-3)
    assert imu_sample.delta_vel_z == pytest.approx(accel_z * dt_usec *
                                                   downsampling_factor / 1e6,
                                                   abs=1e-3)
Пример #2
0
def test_imu_sampling(dt_usec, expected_dt_usec):
    """Make sure the timing is updated correctly

    If the imu update is faster than the filter period, it should be
    downsampled, otherwise used as is.

    """
    time_usec = 0
    delta_ang = float_array([0, 0, 0])
    delta_vel = float_array([0, 0, 0])
    ekf = ecl_EKF.Ekf()
    for _ in range(100):
        time_usec += dt_usec
        ekf.set_imu_data(time_usec,
                         dt_usec,
                         dt_usec,
                         delta_ang,
                         delta_vel)

    imu_sample = ekf.get_imu_sample_delayed()
    assert imu_sample.delta_ang_dt == pytest.approx(
        expected_dt_usec / 1e6, abs=1e-5)
    assert imu_sample.delta_vel_dt == pytest.approx(
        expected_dt_usec / 1e6, abs=1e-5)
    # Make sure the timestamp of the last sample is a small positive multiple
    # of the period away from now
    assert (time_usec - imu_sample.time_us) >= 0
    assert (time_usec - imu_sample.time_us) / expected_dt_usec < 20
Пример #3
0
def test_imu_input(dt_usec, downsampling_factor, accel_x, accel_y, accel_z):
    """Make sure the acceleration is updated correctly when there is no angular
    velocity (test with and without downsampling)

    Tests random accelerations in x, y, z directions (using the hypothesis
    decorator) with different update frequencies (using pytest's parametrize
    decorator)

    """
    time_usec = 100
    delta_ang = float_array([0, 0, 0])
    delta_vel = float_array([accel_x,
                             accel_y,
                             accel_z]) * dt_usec / 1e6

    ekf = ecl_EKF.Ekf()
    # Run to accumulate buffer (choose sample after downsampling)
    for _ in range(20 * downsampling_factor):
        time_usec += dt_usec
        ekf.set_imu_data(time_usec,
                         dt_usec,
                         dt_usec,
                         delta_ang,
                         delta_vel)

    imu_sample = ekf.get_imu_sample_delayed()
    assert imu_sample.delta_ang_x == pytest.approx(0.0, abs=1e-3)
    assert imu_sample.delta_ang_y == pytest.approx(0.0, abs=1e-3)
    assert imu_sample.delta_ang_z == pytest.approx(0.0, abs=1e-3)
    assert imu_sample.delta_vel_x == pytest.approx(
        accel_x * dt_usec * downsampling_factor / 1e6, abs=1e-3)
    assert imu_sample.delta_vel_y == pytest.approx(
        accel_y * dt_usec * downsampling_factor / 1e6, abs=1e-3)
    assert imu_sample.delta_vel_z == pytest.approx(
        accel_z * dt_usec * downsampling_factor / 1e6, abs=1e-3)
Пример #4
0
def test_imu_sampling(dt_usec, expected_dt_usec):
    """Make sure the timing is updated correctly

    If the imu update is faster than the filter period, it should be
    downsampled, otherwise used as is.

    """
    time_usec = 0
    delta_ang = float_array([0, 0, 0])
    delta_vel = float_array([0, 0, 0])
    ekf = ecl_EKF.Ekf()
    for _ in range(100):
        time_usec += dt_usec
        ekf.set_imu_data(time_usec,
                         dt_usec,
                         dt_usec,
                         delta_ang,
                         delta_vel)

    imu_sample = ekf.get_imu_sample_delayed()
    assert imu_sample.delta_ang_dt == pytest.approx(
        expected_dt_usec / 1e6, abs=1e-5)
    assert imu_sample.delta_vel_dt == pytest.approx(
        expected_dt_usec / 1e6, abs=1e-5)
    # Make sure the timestamp of the last sample is a small positive multiple
    # of the period away from now
    assert (time_usec - imu_sample.time_us) >= 0
    assert (time_usec - imu_sample.time_us) / expected_dt_usec < 20
def test_accel_z_bias_converges(z_bias):
    """Make sure the accelerometer bias in z-direction is estimated correctly
    """
    ekf = ecl_EKF.Ekf()

    time_usec = 1000
    dt_usec = ecl_EKF.Ekf.FILTER_UPDATE_PERIOD_MS * 1000

    # Run for a while
    n_samples = 10000

    # Prepare data collection for plotting
    if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
        plot_data = namedtuple('PlotData', ['time', 'accel_bias', 'altitude'])
        plot_data.time = np.array(
            [i * dt_usec * 1e-6 for i in range(n_samples)])
        plot_data.accel_bias = np.zeros(n_samples)
        plot_data.altitude = np.zeros(n_samples)

    # Simulation
    for i in range(n_samples):
        update_sensors(ekf,
                       time_usec,
                       dt_usec,
                       accel=float_array([0.0, 0.0, -ecl_EKF.one_g + z_bias]))
        ekf.update()
        time_usec += dt_usec

        if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
            plot_data.altitude[i] = ekf.get_position()[2]
            plot_data.accel_bias[i] = ekf.get_accel_bias()[2]

    # Plot if necessary
    if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
        from plot_utils import figure
        with figure(params={'z_bias': z_bias},
                    subplots=(2, 1)) as (_, (ax1, ax2)):
            ax1.plot(plot_data.time,
                     plot_data.altitude,
                     label='Altitude Estimate')
            ax1.legend(loc='best')
            ax1.set_ylabel('Altitude $[m]$')
            ax2.plot(plot_data.time,
                     plot_data.accel_bias,
                     label='Accel Z Bias Estimate')
            ax2.axhline(z_bias, color='k', label='Accel Bias Value')
            ax2.legend(loc='best')
            ax2.set_ylabel('Bias $[m / s^2]$')
            ax2.set_xlabel('Time $[s]$')

    # Check assumptions
    converged_pos = ekf.get_position()
    converged_vel = ekf.get_velocity()
    converged_accel_bias = ekf.get_accel_bias()
    for i in range(3):
        assert converged_pos[i] == pytest.approx(0.0, abs=1e-2)
        assert converged_vel[i] == pytest.approx(0.0, abs=1e-2)
    for i in range(2):
        assert converged_accel_bias[i] == pytest.approx(0.0, abs=1e-3)
    assert converged_accel_bias[2] == pytest.approx(z_bias, abs=1e-2)
def test_converges_to_baro_altitude(altitude):
    """Make sure that the ekf converges to (arbitrary) baro altitudes

    Increase the altitude with a bang-bang acceleration profile to target
    altitude, then wait there for a while and make sure it converges
    """
    # Due to hypothesis not interacting with pytest, cannot use fixture here
    ekf, time_usec = initialized_ekf()

    dt_usec = ecl_EKF.Ekf.FILTER_UPDATE_PERIOD_MS * 1000

    # No samples, half are used for ramping up / down to the altitude
    n_samples = 200

    # Compute smooth acceleration profile
    rampup_accel = altitude / (((n_samples // 2 // 2) * (dt_usec / 1e6))**2)

    # Prepare data collection for plotting
    if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
        plot_data = namedtuple('PlotData',
                               ['time', 'baro', 'altitude', 'accel_z_bias'])
        plot_data.time = np.array(
            [i * dt_usec * 1e-6 for i in range(n_samples)])
        plot_data.baro = np.zeros(n_samples)
        plot_data.accel_z_bias = np.zeros(n_samples)
        plot_data.altitude = np.zeros(n_samples)

    # Simulate
    current_state = namedtuple('State', ['alt', 'vel'])
    current_state.alt = 0.0
    current_state.vel = 0.0
    for i in range(n_samples // 2):
        update_sensors(
            ekf,
            time_usec,
            dt_usec,
            accel=float_array([
                0, 0, -ecl_EKF.one_g -
                rampup_accel if i < n_samples // 4 else -ecl_EKF.one_g +
                rampup_accel
            ]),
            baro_data=current_state.alt)
        ekf.update()

        if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
            plot_data.baro[i] = current_state.alt
            plot_data.altitude[i] = -ekf.get_position()[2]
            plot_data.accel_z_bias[i] = ekf.get_accel_bias()[2]

        # Euler step
        current_state.vel += (dt_usec / 1e6) * (
            rampup_accel if i < n_samples // 4 else -rampup_accel)
        current_state.alt += current_state.vel * dt_usec / 1e6
        time_usec += dt_usec

    # Stay at altitude
    for i in range(n_samples // 2):
        update_sensors(ekf, time_usec, dt_usec, baro_data=altitude)
        ekf.update()
        time_usec += dt_usec

        if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
            plot_data.baro[n_samples // 2 + i] = altitude
            plot_data.altitude[n_samples // 2 + i] = -ekf.get_position()[2]
            plot_data.accel_z_bias[n_samples // 2 +
                                   i] = ekf.get_accel_bias()[2]

    # Plot if necessary
    if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
        from plot_utils import figure
        with figure(params={'altitude': altitude},
                    subplots=(2, 1)) as (_, (ax1, ax2)):
            ax1.plot(plot_data.time,
                     plot_data.altitude,
                     label='Altitude Estimate')
            ax1.plot(plot_data.time, plot_data.altitude, label='Baro Input')
            ax1.legend(loc='best')
            ax1.set_ylabel('Altitude $[m]$')
            ax2.plot(plot_data.time,
                     plot_data.accel_z_bias,
                     label='Accel Z Bias Estimate')
            ax2.legend(loc='best')
            ax2.set_ylabel('Bias $[m / s^2]$')
            ax2.set_xlabel('Time $[s]$')
    # plt.plot(np.array(baro_vs_pos))
    # plt.show()
    # print(ekf.get_accel_bias())

    converged_pos = ekf.get_position()
    converged_vel = ekf.get_velocity()
    assert converged_pos[0] == pytest.approx(0.0, abs=1e-6)
    assert converged_pos[1] == pytest.approx(0.0, abs=1e-6)
    # Check for 10cm level accuracy
    assert converged_pos[2] == pytest.approx(-altitude, abs=1e-1)
    assert converged_vel[0] == pytest.approx(0.0, abs=1e-3)
    assert converged_vel[1] == pytest.approx(0.0, abs=1e-3)
    assert converged_vel[2] == pytest.approx(0.0, abs=1e-1)
Пример #7
0
def test_accel_z_bias_converges(z_bias):
    """Make sure the accelerometer bias in z-direction is estimated correctly
    """
    ekf = ecl_EKF.Ekf()

    time_usec = 1000
    dt_usec = ecl_EKF.Ekf.FILTER_UPDATE_PERIOD_MS * 1000

    # Run for a while
    n_samples = 10000

    # Prepare data collection for plotting
    if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
        plot_data = namedtuple('PlotData', ['time', 'accel_bias', 'altitude'])
        plot_data.time = np.array([i * dt_usec * 1e-6
                                   for i in range(n_samples)])
        plot_data.accel_bias = np.zeros(n_samples)
        plot_data.altitude = np.zeros(n_samples)

    # Simulation
    for i in range(n_samples):
        update_sensors(ekf,
                       time_usec,
                       dt_usec,
                       accel=float_array([0.0,
                                          0.0,
                                          -ecl_EKF.one_g + z_bias]))
        ekf.update()
        time_usec += dt_usec

        if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
            plot_data.altitude[i] = ekf.get_position()[2]
            plot_data.accel_bias[i] = ekf.get_accel_bias()[2]

    # Plot if necessary
    if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
        from plot_utils import figure
        with figure(params={'z_bias': z_bias},
                    subplots=(2, 1)) as (_, (ax1, ax2)):
            ax1.plot(plot_data.time,
                     plot_data.altitude,
                     label='Altitude Estimate')
            ax1.legend(loc='best')
            ax1.set_ylabel('Altitude $[m]$')
            ax2.plot(plot_data.time,
                     plot_data.accel_bias,
                     label='Accel Z Bias Estimate')
            ax2.axhline(z_bias, color='k', label='Accel Bias Value')
            ax2.legend(loc='best')
            ax2.set_ylabel('Bias $[m / s^2]$')
            ax2.set_xlabel('Time $[s]$')

    # Check assumptions
    converged_pos = ekf.get_position()
    converged_vel = ekf.get_velocity()
    converged_accel_bias = ekf.get_accel_bias()
    for i in range(3):
        assert converged_pos[i] == pytest.approx(0.0, abs=1e-2)
        assert converged_vel[i] == pytest.approx(0.0, abs=1e-2)
    for i in range(2):
        assert converged_accel_bias[i] == pytest.approx(0.0, abs=1e-3)
    assert converged_accel_bias[2] == pytest.approx(z_bias, abs=1e-2)
Пример #8
0
def test_converges_to_baro_altitude(altitude):
    """Make sure that the ekf converges to (arbitrary) baro altitudes

    Increase the altitude with a bang-bang acceleration profile to target
    altitude, then wait there for a while and make sure it converges
    """
    # Due to hypothesis not interacting with pytest, cannot use fixture here
    ekf, time_usec = initialized_ekf()

    dt_usec = ecl_EKF.Ekf.FILTER_UPDATE_PERIOD_MS * 1000

    # No samples, half are used for ramping up / down to the altitude
    n_samples = 200

    # Compute smooth acceleration profile
    rampup_accel = altitude / (((n_samples // 2 // 2) * (dt_usec / 1e6))**2)

    # Prepare data collection for plotting
    if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
        plot_data = namedtuple('PlotData', ['time',
                                            'baro',
                                            'altitude',
                                            'accel_z_bias'])
        plot_data.time = np.array([i * dt_usec * 1e-6
                                   for i in range(n_samples)])
        plot_data.baro = np.zeros(n_samples)
        plot_data.accel_z_bias = np.zeros(n_samples)
        plot_data.altitude = np.zeros(n_samples)

    # Simulate
    current_state = namedtuple('State', ['alt', 'vel'])
    current_state.alt = 0.0
    current_state.vel = 0.0
    for i in range(n_samples // 2):
        update_sensors(ekf,
                       time_usec,
                       dt_usec,
                       accel=float_array([0,
                                          0,
                                          -ecl_EKF.one_g - rampup_accel
                                          if i < n_samples // 4
                                          else -ecl_EKF.one_g + rampup_accel]),
                       baro_data=current_state.alt)
        ekf.update()

        if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
            plot_data.baro[i] = current_state.alt
            plot_data.altitude[i] = -ekf.get_position()[2]
            plot_data.accel_z_bias[i] = ekf.get_accel_bias()[2]

        # Euler step
        current_state.vel += (dt_usec / 1e6) * (rampup_accel
                                                if i < n_samples // 4
                                                else -rampup_accel)
        current_state.alt += current_state.vel * dt_usec / 1e6
        time_usec += dt_usec

    # Stay at altitude
    for i in range(n_samples // 2):
        update_sensors(ekf, time_usec, dt_usec, baro_data=altitude)
        ekf.update()
        time_usec += dt_usec

        if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
            plot_data.baro[n_samples // 2 + i] = altitude
            plot_data.altitude[n_samples // 2 + i] = -ekf.get_position()[2]
            plot_data.accel_z_bias[
                n_samples // 2 + i] = ekf.get_accel_bias()[2]

    # Plot if necessary
    if pytest.ENABLE_PLOTTING:  # pylint: disable=no-member
        from plot_utils import figure
        with figure(params={'altitude': altitude},
                    subplots=(2, 1)) as (_, (ax1, ax2)):
            ax1.plot(plot_data.time,
                     plot_data.altitude,
                     label='Altitude Estimate')
            ax1.plot(plot_data.time,
                     plot_data.altitude,
                     label='Baro Input')
            ax1.legend(loc='best')
            ax1.set_ylabel('Altitude $[m]$')
            ax2.plot(plot_data.time,
                     plot_data.accel_z_bias,
                     label='Accel Z Bias Estimate')
            ax2.legend(loc='best')
            ax2.set_ylabel('Bias $[m / s^2]$')
            ax2.set_xlabel('Time $[s]$')
    # plt.plot(np.array(baro_vs_pos))
    # plt.show()
    # print(ekf.get_accel_bias())

    converged_pos = ekf.get_position()
    converged_vel = ekf.get_velocity()
    assert converged_pos[0] == pytest.approx(0.0, abs=1e-6)
    assert converged_pos[1] == pytest.approx(0.0, abs=1e-6)
    # Check for 10cm level accuracy
    assert converged_pos[2] == pytest.approx(-altitude, abs=1e-1)
    assert converged_vel[0] == pytest.approx(0.0, abs=1e-3)
    assert converged_vel[1] == pytest.approx(0.0, abs=1e-3)
    assert converged_vel[2] == pytest.approx(0.0, abs=1e-1)