Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ac01babb authored by Ytai Ben-tsvi's avatar Ytai Ben-tsvi Committed by Android (Google) Code Review
Browse files

Merge changes Ie494e03f,Ia85d5699,I7f5a39a0

* changes:
  Tune head pose processing parameters
  Force "not still" for the duration of the window after motion
  Consider the screen still until proven otherwise
parents 2ea3d2e1 cdc03062
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -42,11 +42,13 @@ class HeadTrackingProcessorImpl : public HeadTrackingProcessor {
                  .rotationalDriftTimeConstant = options.rotationalDriftTimeConstant,
          }),
          mHeadStillnessDetector(StillnessDetector::Options{
                  .defaultValue = false,
                  .windowDuration = options.autoRecenterWindowDuration,
                  .translationalThreshold = options.autoRecenterTranslationalThreshold,
                  .rotationalThreshold = options.autoRecenterRotationalThreshold,
          }),
          mScreenStillnessDetector(StillnessDetector::Options{
                  .defaultValue = true,
                  .windowDuration = options.screenStillnessWindowDuration,
                  .translationalThreshold = options.screenStillnessTranslationalThreshold,
                  .rotationalThreshold = options.screenStillnessRotationalThreshold,
+86 −41
Original line number Diff line number Diff line
@@ -28,101 +28,146 @@ using Eigen::Quaternionf;
using Eigen::Vector3f;
using Options = StillnessDetector::Options;

TEST(StillnessDetectorTest, Still) {
    StillnessDetector detector(Options{
            .windowDuration = 1000, .translationalThreshold = 1, .rotationalThreshold = 0.05});
class StillnessDetectorTest : public testing::TestWithParam<bool> {
  public:
    void SetUp() override { mDefaultValue = GetParam(); }

  protected:
    bool mDefaultValue;
};

TEST_P(StillnessDetectorTest, Still) {
    StillnessDetector detector(Options{.defaultValue = mDefaultValue,
                                       .windowDuration = 1000,
                                       .translationalThreshold = 1,
                                       .rotationalThreshold = 0.05});

    const Pose3f baseline(Vector3f{1, 2, 3}, Quaternionf::UnitRandom());
    const Pose3f withinThreshold =
            baseline * Pose3f(Vector3f(0.3, -0.3, 0), rotateX(0.01) * rotateY(-0.01));

    EXPECT_FALSE(detector.calculate(0));
    EXPECT_EQ(mDefaultValue, detector.calculate(0));
    detector.setInput(0, baseline);
    EXPECT_FALSE(detector.calculate(0));
    EXPECT_EQ(mDefaultValue, detector.calculate(0));
    detector.setInput(300, withinThreshold);
    EXPECT_FALSE(detector.calculate(300));
    EXPECT_EQ(mDefaultValue, detector.calculate(300));
    detector.setInput(600, baseline);
    EXPECT_FALSE(detector.calculate(600));
    EXPECT_EQ(mDefaultValue, detector.calculate(600));
    detector.setInput(999, withinThreshold);
    EXPECT_FALSE(detector.calculate(999));
    EXPECT_EQ(mDefaultValue, detector.calculate(999));
    detector.setInput(1000, baseline);
    EXPECT_TRUE(detector.calculate(1000));
}

TEST(StillnessDetectorTest, ZeroDuration) {
    StillnessDetector detector(Options{.windowDuration = 0});
TEST_P(StillnessDetectorTest, ZeroDuration) {
    StillnessDetector detector(Options{.defaultValue = mDefaultValue, .windowDuration = 0});
    EXPECT_TRUE(detector.calculate(0));
    EXPECT_TRUE(detector.calculate(1000));
}

TEST(StillnessDetectorTest, NotStillTranslation) {
    StillnessDetector detector(Options{
            .windowDuration = 1000, .translationalThreshold = 1, .rotationalThreshold = 0.05});
TEST_P(StillnessDetectorTest, NotStillTranslation) {
    StillnessDetector detector(Options{.defaultValue = mDefaultValue,
                                       .windowDuration = 1000,
                                       .translationalThreshold = 1,
                                       .rotationalThreshold = 0.05});

    const Pose3f baseline(Vector3f{1, 2, 3}, Quaternionf::UnitRandom());
    const Pose3f withinThreshold =
            baseline * Pose3f(Vector3f(0.3, -0.3, 0), rotateX(0.01) * rotateY(-0.01));
    const Pose3f outsideThreshold = baseline * Pose3f(Vector3f(1, 1, 0));

    EXPECT_FALSE(detector.calculate(0));
    EXPECT_EQ(mDefaultValue, detector.calculate(0));
    detector.setInput(0, baseline);
    EXPECT_FALSE(detector.calculate(0));
    EXPECT_EQ(mDefaultValue, detector.calculate(0));
    detector.setInput(300, outsideThreshold);
    EXPECT_FALSE(detector.calculate(300));
    EXPECT_EQ(mDefaultValue, detector.calculate(300));
    detector.setInput(600, baseline);
    EXPECT_FALSE(detector.calculate(600));
    EXPECT_EQ(mDefaultValue, detector.calculate(600));
    detector.setInput(900, withinThreshold);
    EXPECT_FALSE(detector.calculate(900));
    detector.setInput(1299, baseline);
    EXPECT_FALSE(detector.calculate(1299));
    EXPECT_TRUE(detector.calculate(1300));
    EXPECT_EQ(mDefaultValue, detector.calculate(900));
    detector.setInput(1300, baseline);
    EXPECT_FALSE(detector.calculate(1300));
    detector.setInput(1500, baseline);
    EXPECT_FALSE(detector.calculate(1899));
    EXPECT_TRUE(detector.calculate(1900));
}

TEST(StillnessDetectorTest, NotStillRotation) {
    StillnessDetector detector(Options{
            .windowDuration = 1000, .translationalThreshold = 1, .rotationalThreshold = 0.05});
TEST_P(StillnessDetectorTest, NotStillRotation) {
    StillnessDetector detector(Options{.defaultValue = mDefaultValue,
                                       .windowDuration = 1000,
                                       .translationalThreshold = 1,
                                       .rotationalThreshold = 0.05});

    const Pose3f baseline(Vector3f{1, 2, 3}, Quaternionf::UnitRandom());
    const Pose3f withinThreshold =
            baseline * Pose3f(Vector3f(0.3, -0.3, 0), rotateX(0.03) * rotateY(-0.03));
    const Pose3f outsideThreshold = baseline * Pose3f(rotateZ(0.06));
    EXPECT_FALSE(detector.calculate(0));

    EXPECT_EQ(mDefaultValue, detector.calculate(0));
    detector.setInput(0, baseline);
    EXPECT_FALSE(detector.calculate(0));
    EXPECT_EQ(mDefaultValue, detector.calculate(0));
    detector.setInput(300, outsideThreshold);
    EXPECT_FALSE(detector.calculate(300));
    EXPECT_EQ(mDefaultValue, detector.calculate(300));
    detector.setInput(600, baseline);
    EXPECT_FALSE(detector.calculate(600));
    EXPECT_EQ(mDefaultValue, detector.calculate(600));
    detector.setInput(900, withinThreshold);
    EXPECT_FALSE(detector.calculate(900));
    detector.setInput(1299, baseline);
    EXPECT_FALSE(detector.calculate(1299));
    EXPECT_TRUE(detector.calculate(1300));
    EXPECT_EQ(mDefaultValue, detector.calculate(900));
    detector.setInput(1300, baseline);
    EXPECT_FALSE(detector.calculate(1300));
    detector.setInput(1500, baseline);
    EXPECT_FALSE(detector.calculate(1899));
    EXPECT_TRUE(detector.calculate(1900));
}

TEST_P(StillnessDetectorTest, Suppression) {
    StillnessDetector detector(Options{.defaultValue = mDefaultValue,
                                       .windowDuration = 1000,
                                       .translationalThreshold = 1,
                                       .rotationalThreshold = 0.05});

    const Pose3f baseline(Vector3f{1, 2, 3}, Quaternionf::UnitRandom());
    const Pose3f outsideThreshold = baseline * Pose3f(Vector3f(1.1, 0, 0));
    const Pose3f middlePoint = baseline * Pose3f(Vector3f(0.55, 0, 0));

    detector.setInput(0, baseline);
    detector.setInput(1000, baseline);
    EXPECT_TRUE(detector.calculate(1000));
    detector.setInput(1100, outsideThreshold);
    EXPECT_FALSE(detector.calculate(1100));
    detector.setInput(2000, middlePoint);
    EXPECT_FALSE(detector.calculate(2000));
    EXPECT_FALSE(detector.calculate(2099));
    EXPECT_TRUE(detector.calculate(2100));
}

TEST(StillnessDetectorTest, Reset) {
    StillnessDetector detector(Options{
            .windowDuration = 1000, .translationalThreshold = 1, .rotationalThreshold = 0.05});
TEST_P(StillnessDetectorTest, Reset) {
    StillnessDetector detector(Options{.defaultValue = mDefaultValue,
                                       .windowDuration = 1000,
                                       .translationalThreshold = 1,
                                       .rotationalThreshold = 0.05});

    const Pose3f baseline(Vector3f{1, 2, 3}, Quaternionf::UnitRandom());
    const Pose3f withinThreshold =
            baseline * Pose3f(Vector3f(0.3, -0.3, 0), rotateX(0.01) * rotateY(-0.01));
    EXPECT_FALSE(detector.calculate(0));
    EXPECT_EQ(mDefaultValue, detector.calculate(0));
    detector.setInput(0, baseline);
    EXPECT_FALSE(detector.calculate(0));
    EXPECT_EQ(mDefaultValue, detector.calculate(0));
    detector.reset();
    detector.setInput(600, baseline);
    EXPECT_FALSE(detector.calculate(600));
    EXPECT_EQ(mDefaultValue, detector.calculate(600));
    detector.setInput(900, withinThreshold);
    EXPECT_FALSE(detector.calculate(900));
    EXPECT_EQ(mDefaultValue, detector.calculate(900));
    detector.setInput(1200, baseline);
    EXPECT_FALSE(detector.calculate(1200));
    EXPECT_EQ(mDefaultValue, detector.calculate(1200));
    detector.setInput(1599, withinThreshold);
    EXPECT_FALSE(detector.calculate(1599));
    EXPECT_EQ(mDefaultValue, detector.calculate(1599));
    detector.setInput(1600, baseline);
    EXPECT_TRUE(detector.calculate(1600));
}

INSTANTIATE_TEST_SUITE_P(StillnessDetectorTestParametrized, StillnessDetectorTest,
                         testing::Values(false, true));

}  // namespace
}  // namespace media
}  // namespace android
+28 −15
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ StillnessDetector::StillnessDetector(const Options& options)
void StillnessDetector::reset() {
    mFifo.clear();
    mWindowFull = false;
    mSuppressionDeadline.reset();
}

void StillnessDetector::setInput(int64_t timestamp, const Pose3f& input) {
@@ -35,27 +36,34 @@ void StillnessDetector::setInput(int64_t timestamp, const Pose3f& input) {
bool StillnessDetector::calculate(int64_t timestamp) {
    discardOld(timestamp);

    // If the window has not been full, we don't consider ourselves still.
    if (!mWindowFull) {
        return false;
    }
    // Check whether all the poses in the queue are in the proximity of the new
    // one. We want to do this before checking the overriding conditions below, in order to update
    // the suppression deadline correctly.
    bool moved = false;

    // An empty FIFO and window full is considered still (this will happen in the unlikely case when
    // the window duration is shorter than the gap between samples).
    if (mFifo.empty()) {
        return true;
    }

    // Otherwise, check whether all the poses remaining in the queue are in the proximity of the new
    // one.
    if (!mFifo.empty()) {
        for (auto iter = mFifo.begin(); iter != mFifo.end() - 1; ++iter) {
            const auto& event = *iter;
            if (!areNear(event.pose, mFifo.back().pose)) {
            return false;
                // Enable suppression for the duration of the window.
                mSuppressionDeadline = timestamp + mOptions.windowDuration;
                moved = true;
                break;
            }
        }
    }

    return true;
    // If the window has not been full, return the default value.
    if (!mWindowFull) {
        return mOptions.defaultValue;
    }

    // Force "in motion" while the suppression deadline is active.
    if (mSuppressionDeadline.has_value()) {
        return false;
    }

    return !moved;
}

void StillnessDetector::discardOld(int64_t timestamp) {
@@ -72,6 +80,11 @@ void StillnessDetector::discardOld(int64_t timestamp) {
        mWindowFull = true;
        mFifo.pop_front();
    }

    // Expire the suppression deadline.
    if (mSuppressionDeadline.has_value() && mSuppressionDeadline <= timestamp) {
        mSuppressionDeadline.reset();
    }
}

bool StillnessDetector::areNear(const Pose3f& pose1, const Pose3f& pose2) const {
+14 −4
Original line number Diff line number Diff line
@@ -36,10 +36,11 @@ namespace media {
 *    bool still = detector.calculate(timestamp);
 * }
 *
 * The stream is considered not still until a sufficient number of samples has been provided for an
 * initial fill-up of the window. In the special case of the window size being 0, this is not
 * required and the state is considered always "still". The reset() method can be used to empty the
 * window again and get back to this initial state.
 * The detection is not considered reliable until a sufficient number of samples has been provided
 * for an initial fill-up of the window. During that time, the detector will return whatever default
 * value has been configured.
 * The reset() method can be used to empty the window again and get back to this initial state.
 * In the special case of the window size being 0, the state will always be considered "still".
 */
class StillnessDetector {
  public:
@@ -47,6 +48,10 @@ class StillnessDetector {
     * Configuration options for the detector.
     */
    struct Options {
        /**
         * During the initial fill of the window, should we consider the state still?
         */
         bool defaultValue;
        /**
         * How long is the window, in ticks. The special value of 0 indicates that the stream is
         * always considered still.
@@ -87,6 +92,11 @@ class StillnessDetector {
    const float mCosHalfRotationalThreshold;
    std::deque<TimestampedPose> mFifo;
    bool mWindowFull = false;
    // As soon as motion is detected, this will be set for the time of detection + window duration,
    // and during this time we will always consider outselves in motion without checking. This is
    // used for hyteresis purposes, since because of the approximate method we use for determining
    // stillness, we may toggle back and forth at a rate faster than the window side.
    std::optional<int64_t> mSuppressionDeadline;

    bool areNear(const Pose3f& pose1, const Pose3f& pose2) const;
    void discardOld(int64_t timestamp);
+5 −5
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ namespace {
constexpr auto kMaxTranslationalVelocity = 2;

// This is how fast, in rad/s, we allow rotation angle to shift during rate-limiting.
constexpr auto kMaxRotationalVelocity = 4 * M_PI;
constexpr auto kMaxRotationalVelocity = 8;

// This should be set to the typical time scale that the translation sensors used drift in. This
// means, loosely, for how long we can trust the reading to be "accurate enough". This would
@@ -65,23 +65,23 @@ constexpr auto kPredictionDuration = 10ms;
constexpr auto kMaxLostSamples = 4;

// Auto-recenter kicks in after the head has been still for this long.
constexpr auto kAutoRecenterWindowDuration = 10s;
constexpr auto kAutoRecenterWindowDuration = 6s;

// Auto-recenter considers head not still if translated by this much (in meters, approx).
constexpr float kAutoRecenterTranslationThreshold = 0.1f;

// Auto-recenter considers head not still if rotated by this much (in radians, approx).
constexpr float kAutoRecenterRotationThreshold = 5.0f / 180 * M_PI;
constexpr float kAutoRecenterRotationThreshold = 7.0f / 180 * M_PI;

// Screen is considered to be unstable (not still) if it has moved significantly within the last
// time window of this duration.
constexpr auto kScreenStillnessWindowDuration = 10s;
constexpr auto kScreenStillnessWindowDuration = 3s;

// Screen is considered to have moved significantly if translated by this much (in meter, approx).
constexpr float kScreenStillnessTranslationThreshold = 0.1f;

// Screen is considered to have moved significantly if rotated by this much (in radians, approx).
constexpr float kScreenStillnessRotationThreshold = 5.0f / 180 * M_PI;
constexpr float kScreenStillnessRotationThreshold = 7.0f / 180 * M_PI;

// Time units for system clock ticks. This is what the Sensor Framework timestamps represent and
// what we use for pose filtering.