From e4ba822cc6959490868fd8868ffad1c4e9b23992 Mon Sep 17 00:00:00 2001 From: Yusuke Nojima Date: Wed, 5 Oct 2011 14:55:07 +0900 Subject: [PATCH] Promote touches in hit box according to the distance from sweet spot Change-Id: Ice0fd0514304a79aed67627c2ea3439bd5177de4 --- native/src/correction.cpp | 83 ++++++++++++++++++++--------------- native/src/correction.h | 5 +-- native/src/correction_state.h | 10 ++--- native/src/defines.h | 5 ++- native/src/proximity_info.cpp | 46 +++++++------------ native/src/proximity_info.h | 30 +++++-------- 6 files changed, 84 insertions(+), 95 deletions(-) diff --git a/native/src/correction.cpp b/native/src/correction.cpp index 9e75ffc3e..d1e866d9d 100644 --- a/native/src/correction.cpp +++ b/native/src/correction.cpp @@ -118,9 +118,8 @@ bool Correction::initProcessState(const int outputIndex) { mInputIndex = mCorrectionStates[outputIndex].mInputIndex; mNeedsToTraverseAllNodes = mCorrectionStates[outputIndex].mNeedsToTraverseAllNodes; - mEquivalentCharStrongCount = mCorrectionStates[outputIndex].mEquivalentCharStrongCount; - mEquivalentCharNormalCount = mCorrectionStates[outputIndex].mEquivalentCharNormalCount; - mEquivalentCharWeakCount = mCorrectionStates[outputIndex].mEquivalentCharWeakCount; + mSumOfDistance = mCorrectionStates[outputIndex].mSumOfDistance; + mEquivalentCharCount = mCorrectionStates[outputIndex].mEquivalentCharCount; mProximityCount = mCorrectionStates[outputIndex].mProximityCount; mTransposedCount = mCorrectionStates[outputIndex].mTransposedCount; mExcessiveCount = mCorrectionStates[outputIndex].mExcessiveCount; @@ -175,9 +174,8 @@ void Correction::incrementOutputIndex() { mCorrectionStates[mOutputIndex].mInputIndex = mInputIndex; mCorrectionStates[mOutputIndex].mNeedsToTraverseAllNodes = mNeedsToTraverseAllNodes; - mCorrectionStates[mOutputIndex].mEquivalentCharStrongCount = mEquivalentCharStrongCount; - mCorrectionStates[mOutputIndex].mEquivalentCharNormalCount = mEquivalentCharNormalCount; - mCorrectionStates[mOutputIndex].mEquivalentCharWeakCount = mEquivalentCharWeakCount; + mCorrectionStates[mOutputIndex].mSumOfDistance = mSumOfDistance; + mCorrectionStates[mOutputIndex].mEquivalentCharCount = mEquivalentCharCount; mCorrectionStates[mOutputIndex].mProximityCount = mProximityCount; mCorrectionStates[mOutputIndex].mTransposedCount = mTransposedCount; mCorrectionStates[mOutputIndex].mExcessiveCount = mExcessiveCount; @@ -220,9 +218,7 @@ Correction::CorrectionType Correction::processSkipChar( } inline bool isEquivalentChar(ProximityInfo::ProximityType type) { - // 'type ProximityInfo::EQUIVALENT_CHAR_WEAK' means that - // type == ..._WEAK or type == ..._NORMAL or type == ..._STRONG. - return type <= ProximityInfo::EQUIVALENT_CHAR_WEAK; + return type == ProximityInfo::EQUIVALENT_CHAR; } Correction::CorrectionType Correction::processCharAndCalcState( @@ -304,7 +300,7 @@ Correction::CorrectionType Correction::processCharAndCalcState( // TODO: Change the limit if we'll allow two or more proximity chars with corrections const bool checkProximityChars = noCorrectionsHappenedSoFar || mProximityCount == 0; const ProximityInfo::ProximityType matchedProximityCharId = secondTransposing - ? ProximityInfo::EQUIVALENT_CHAR_NORMAL + ? ProximityInfo::EQUIVALENT_CHAR : mProximityInfo->getMatchedProximityId(mInputIndex, c, checkProximityChars); if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId) { @@ -384,18 +380,14 @@ Correction::CorrectionType Correction::processCharAndCalcState( mMatching = true; } else if (isEquivalentChar(matchedProximityCharId)) { mMatching = true; - switch (matchedProximityCharId) { - case ProximityInfo::EQUIVALENT_CHAR_STRONG: - ++mEquivalentCharStrongCount; - break; - case ProximityInfo::EQUIVALENT_CHAR_NORMAL: - ++mEquivalentCharNormalCount; - break; - case ProximityInfo::EQUIVALENT_CHAR_WEAK: - ++mEquivalentCharWeakCount; - break; - default: - assert(false); + ++mEquivalentCharCount; + if (mSumOfDistance != NOT_A_DISTANCE) { + const int distance = mProximityInfo->getNormalizedSquaredDistance(mInputIndex); + if (distance != NOT_A_DISTANCE) { + mSumOfDistance += distance; + } else { + mSumOfDistance = NOT_A_DISTANCE; + } } } else if (ProximityInfo::NEAR_PROXIMITY_CHAR == matchedProximityCharId) { mProximityMatching = true; @@ -568,8 +560,8 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const const int transposedCount = correction->mTransposedCount / 2; const int excessiveCount = correction->mExcessiveCount + correction->mTransposedCount % 2; const int proximityMatchedCount = correction->mProximityCount; - const int equivalentCharStrongCount = correction->mEquivalentCharStrongCount; - const int equivalentCharWeakCount = correction->mEquivalentCharWeakCount; + const int mSumOfDistance = correction->mSumOfDistance; + const int mEquivalentCharCount = correction->mEquivalentCharCount; const bool lastCharExceeded = correction->mLastCharExceeded; const bool useFullEditDistance = correction->mUseFullEditDistance; const int outputLength = outputIndex + 1; @@ -679,18 +671,37 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq); } - for (int i = 0; i < equivalentCharStrongCount; ++i) { - if (DEBUG_DICT_FULL) { - LOGI("equivalent char strong"); - } - multiplyRate(WORDS_WITH_EQUIVALENT_CHAR_STRONG_PROMOTION_RATE, &finalFreq); - } - - for (int i = 0; i < equivalentCharWeakCount; ++i) { - if (DEBUG_DICT_FULL) { - LOGI("equivalent char weak"); - } - multiplyRate(WORDS_WITH_EQUIVALENT_CHAR_WEAK_DEMOTION_RATE, &finalFreq); + if (CALIBRATE_SCORE_BY_TOUCH_COORDINATES + && mEquivalentCharCount > 0 && mSumOfDistance != NOT_A_DISTANCE) { + // Let (x, y) be the coordinate of a user's touch, and let c be a key. + // Assuming users' touch distribution is gauss distribution, the conditional probability of + // the user touching (x, y) given he or she intends to hit c is: + // p(x, y | c) = exp(-(x - m_x) / (2 * s^2)) / (sqrt(2 * pi) * s) + // * exp(-(y - m_y) / (2 * s^2)) / (sqrt(2 * pi) * s) + // where (m_x, m_y) is a mean of touches of c, and s is a variance of touches of c. + // If user touches c1, c2, .., cn, the joint distribution is + // p(x1, y1 | c1) * p(x2, y2 | c2) * ... * p(xn, yn | cn) + // We consider the logarithm of this value, that is + // sum_i log p(x_i, y_i | c_i) + const + // = sum_i ((x_i - m_x)^2 + (y_i - m_y)^2) / (2 * s^2) + const + // Thus, we use the sum of squared distance as a score of the word. + static const int UPPER = WORDS_WITH_EQUIVALENT_CHAR_STRONGEST_PROMOTION_RATE; + static const int LOWER = WORDS_WITH_EQUIVALENT_CHAR_WEAKEST_DEMOTION_RATE; + static const int MIDDLE = 100; + static const int SHIFT = ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2; + const int expected = mEquivalentCharCount << SHIFT; + // factor is a function as described below: + // U\ . + // \ . + // M \ . + // \ . + // L \------- . + // 0 e + // (x-axis is mSumOfDistance, y-axis is rate, + // and e, U, M, L are expected, UPPER, MIDDLE, LOWER respectively. + const int factor = + max((UPPER * expected - (UPPER - MIDDLE) * mSumOfDistance) / expected, LOWER); + multiplyRate(factor, &finalFreq); } const int errorCount = adjustedProximityMatchedCount > 0 diff --git a/native/src/correction.h b/native/src/correction.h index a630646c1..522c65f48 100644 --- a/native/src/correction.h +++ b/native/src/correction.h @@ -127,9 +127,8 @@ private: int mOutputIndex; int mInputIndex; - int mEquivalentCharStrongCount; - int mEquivalentCharNormalCount; - int mEquivalentCharWeakCount; + int mEquivalentCharCount; + int mSumOfDistance; int mProximityCount; int mExcessiveCount; int mTransposedCount; diff --git a/native/src/correction_state.h b/native/src/correction_state.h index a8ee82acd..fff5cd578 100644 --- a/native/src/correction_state.h +++ b/native/src/correction_state.h @@ -29,9 +29,8 @@ struct CorrectionState { uint16_t mChildCount; uint8_t mInputIndex; - uint8_t mEquivalentCharStrongCount; - uint8_t mEquivalentCharNormalCount; - uint8_t mEquivalentCharWeakCount; + int32_t mSumOfDistance; + uint8_t mEquivalentCharCount; uint8_t mProximityCount; uint8_t mTransposedCount; uint8_t mExcessiveCount; @@ -66,9 +65,8 @@ inline static void initCorrectionState(CorrectionState *state, const int rootPos state->mExcessivePos = -1; state->mSkipPos = -1; - state->mEquivalentCharStrongCount = 0; - state->mEquivalentCharNormalCount = 0; - state->mEquivalentCharWeakCount = 0; + state->mSumOfDistance = 0; + state->mEquivalentCharCount = 0; state->mProximityCount = 0; state->mTransposedCount = 0; state->mExcessiveCount = 0; diff --git a/native/src/defines.h b/native/src/defines.h index 57bd9f763..19415725f 100644 --- a/native/src/defines.h +++ b/native/src/defines.h @@ -162,6 +162,7 @@ static void dumpWord(const unsigned short* word, const int length) { #define NEW_DICTIONARY_HEADER_SIZE 5 #define NOT_VALID_WORD -99 #define NOT_A_CHARACTER -1 +#define NOT_A_DISTANCE -1 #define KEYCODE_SPACE ' ' @@ -180,8 +181,8 @@ static void dumpWord(const unsigned short* word, const int length) { #define WORDS_WITH_EXCESSIVE_CHARACTER_DEMOTION_RATE 75 #define WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_DEMOTION_RATE 75 #define WORDS_WITH_TRANSPOSED_CHARACTERS_DEMOTION_RATE 60 -#define WORDS_WITH_EQUIVALENT_CHAR_STRONG_PROMOTION_RATE 105 -#define WORDS_WITH_EQUIVALENT_CHAR_WEAK_DEMOTION_RATE 95 +#define WORDS_WITH_EQUIVALENT_CHAR_STRONGEST_PROMOTION_RATE 110 +#define WORDS_WITH_EQUIVALENT_CHAR_WEAKEST_DEMOTION_RATE 90 #define FULL_MATCHED_WORDS_PROMOTION_RATE 120 #define WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE 90 #define WORDS_WITH_MATCH_SKIP_PROMOTION_RATE 105 diff --git a/native/src/proximity_info.cpp b/native/src/proximity_info.cpp index 4c21c2eb3..de3f421f3 100644 --- a/native/src/proximity_info.cpp +++ b/native/src/proximity_info.cpp @@ -115,41 +115,42 @@ void ProximityInfo::setInputParams(const int* inputCodes, const int inputLength, } mPrimaryInputWord[inputLength] = 0; for (int i = 0; i < mInputLength; ++i) { - mSweetSpotTypes[i] = calculateSweetSpotType(i); + float normalizedSquaredDistance = calculateNormalizedSquaredDistance(i); + if (normalizedSquaredDistance >= 0.0f) { + mNormalizedSquaredDistance[i] = + (int)(normalizedSquaredDistance * NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR); + } else { + mNormalizedSquaredDistance[i] = NOT_A_DISTANCE; + } } } inline float square(const float x) { return x * x; } -ProximityInfo::SweetSpotType ProximityInfo::calculateSweetSpotType(int index) const { +float ProximityInfo::calculateNormalizedSquaredDistance(int index) const { + static const float NOT_A_DISTANCE_FLOAT = -1.0f; if (KEY_COUNT == 0 || !mInputXCoordinates || !mInputYCoordinates) { // We do not have the coordinate data - return UNKNOWN; + return NOT_A_DISTANCE_FLOAT; } const int currentChar = getPrimaryCharAt(index); const unsigned short baseLowerC = Dictionary::toBaseLowerCase(currentChar); if (baseLowerC > MAX_CHAR_CODE) { - return UNKNOWN; + return NOT_A_DISTANCE_FLOAT; } const int keyIndex = mCodeToKeyIndex[baseLowerC]; if (keyIndex < 0) { - return UNKNOWN; + return NOT_A_DISTANCE_FLOAT; } const float radius = mSweetSpotRadii[keyIndex]; if (radius <= 0.0) { // When there are no calibration data for a key, // the radius of the key is assigned to zero. - return UNKNOWN; + return NOT_A_DISTANCE; } const float squaredRadius = square(radius); const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(keyIndex, index); - if (squaredDistance <= squaredRadius) { - return IN_SWEET_SPOT; - } - if (squaredDistance <= square(NEUTRAL_AREA_RADIUS_RATIO) * squaredRadius) { - return IN_NEUTRAL_AREA; - } - return OUT_OF_NEUTRAL_AREA; + return squaredDistance / squaredRadius; } float ProximityInfo::calculateSquaredDistanceFromSweetSpotCenter( @@ -213,22 +214,7 @@ ProximityInfo::ProximityType ProximityInfo::getMatchedProximityId( // The first char in the array is what user typed. If it matches right away, // that means the user typed that same char for this pos. if (firstChar == baseLowerC || firstChar == c) { - if (CALIBRATE_SCORE_BY_TOUCH_COORDINATES) { - switch (mSweetSpotTypes[index]) { - case UNKNOWN: - return EQUIVALENT_CHAR_NORMAL; - case IN_SWEET_SPOT: - return EQUIVALENT_CHAR_STRONG; - case IN_NEUTRAL_AREA: - return EQUIVALENT_CHAR_NORMAL; - case OUT_OF_NEUTRAL_AREA: - return EQUIVALENT_CHAR_WEAK; - default: - assert(false); - } - } else { - return EQUIVALENT_CHAR_NORMAL; - } + return EQUIVALENT_CHAR; } if (!checkProximityChars) return UNRELATED_CHAR; @@ -266,6 +252,8 @@ bool ProximityInfo::sameAsTyped(const unsigned short *word, int length) const { return true; } +const int ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2; +const int ProximityInfo::NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR; const int ProximityInfo::MAX_KEY_COUNT_IN_A_KEYBOARD; const int ProximityInfo::MAX_CHAR_CODE; diff --git a/native/src/proximity_info.h b/native/src/proximity_info.h index 421ca0ef6..3425efe4e 100644 --- a/native/src/proximity_info.h +++ b/native/src/proximity_info.h @@ -27,14 +27,12 @@ class Correction; class ProximityInfo { public: + static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2 = 10; + // Used as a return value for character comparison typedef enum { - // Same char, possibly with different case or accent, and in the sweet spot of the char - EQUIVALENT_CHAR_STRONG, - // Same char, possibly with different case or accent, and in the outer sweet spot - EQUIVALENT_CHAR_NORMAL, - // Same char, possibly with different case or accent, and in the hit box of the char - EQUIVALENT_CHAR_WEAK, + // Same char, possibly with different case or accent + EQUIVALENT_CHAR, // It is a char located nearby on the keyboard NEAR_PROXIMITY_CHAR, // It is an unrelated char @@ -57,31 +55,25 @@ public: bool existsAdjacentProximityChars(const int index) const; ProximityType getMatchedProximityId( const int index, const unsigned short c, const bool checkProximityChars) const; + int getNormalizedSquaredDistance(int index) const { + return mNormalizedSquaredDistance[index]; + } bool sameAsTyped(const unsigned short *word, int length) const; const unsigned short* getPrimaryInputWord() const { return mPrimaryInputWord; } private: + static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR = + 1 << NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2; // The max number of the keys in one keyboard layout static const int MAX_KEY_COUNT_IN_A_KEYBOARD = 64; // The upper limit of the char code in mCodeToKeyIndex static const int MAX_CHAR_CODE = 127; - typedef enum { - // cannot figure out the sweet spot type - UNKNOWN, - // touch position is out of neutral area of the given char - OUT_OF_NEUTRAL_AREA, - // touch position is in the neutral area of the given char - IN_NEUTRAL_AREA, - // touch position is in the sweet spot of the given char - IN_SWEET_SPOT - } SweetSpotType; - int getStartIndexFromCoordinates(const int x, const int y) const; void initializeCodeToKeyIndex(); - SweetSpotType calculateSweetSpotType(int index) const; + float calculateNormalizedSquaredDistance(int index) const; float calculateSquaredDistanceFromSweetSpotCenter(int keyIndex, int inputIndex) const; const int MAX_PROXIMITY_CHARS_SIZE; @@ -104,7 +96,7 @@ private: float mSweetSpotCenterXs[MAX_KEY_COUNT_IN_A_KEYBOARD]; float mSweetSpotCenterYs[MAX_KEY_COUNT_IN_A_KEYBOARD]; float mSweetSpotRadii[MAX_KEY_COUNT_IN_A_KEYBOARD]; - SweetSpotType mSweetSpotTypes[MAX_WORD_LENGTH_INTERNAL]; + int mNormalizedSquaredDistance[MAX_WORD_LENGTH_INTERNAL]; int mInputLength; unsigned short mPrimaryInputWord[MAX_WORD_LENGTH_INTERNAL]; int mCodeToKeyIndex[MAX_CHAR_CODE + 1];