Promote touches in hit box according to the distance from sweet spot

Change-Id: Ice0fd0514304a79aed67627c2ea3439bd5177de4
This commit is contained in:
Yusuke Nojima 2011-10-05 14:55:07 +09:00
parent a62a022ea7
commit e4ba822cc6
6 changed files with 84 additions and 95 deletions

View file

@ -118,9 +118,8 @@ bool Correction::initProcessState(const int outputIndex) {
mInputIndex = mCorrectionStates[outputIndex].mInputIndex; mInputIndex = mCorrectionStates[outputIndex].mInputIndex;
mNeedsToTraverseAllNodes = mCorrectionStates[outputIndex].mNeedsToTraverseAllNodes; mNeedsToTraverseAllNodes = mCorrectionStates[outputIndex].mNeedsToTraverseAllNodes;
mEquivalentCharStrongCount = mCorrectionStates[outputIndex].mEquivalentCharStrongCount; mSumOfDistance = mCorrectionStates[outputIndex].mSumOfDistance;
mEquivalentCharNormalCount = mCorrectionStates[outputIndex].mEquivalentCharNormalCount; mEquivalentCharCount = mCorrectionStates[outputIndex].mEquivalentCharCount;
mEquivalentCharWeakCount = mCorrectionStates[outputIndex].mEquivalentCharWeakCount;
mProximityCount = mCorrectionStates[outputIndex].mProximityCount; mProximityCount = mCorrectionStates[outputIndex].mProximityCount;
mTransposedCount = mCorrectionStates[outputIndex].mTransposedCount; mTransposedCount = mCorrectionStates[outputIndex].mTransposedCount;
mExcessiveCount = mCorrectionStates[outputIndex].mExcessiveCount; mExcessiveCount = mCorrectionStates[outputIndex].mExcessiveCount;
@ -175,9 +174,8 @@ void Correction::incrementOutputIndex() {
mCorrectionStates[mOutputIndex].mInputIndex = mInputIndex; mCorrectionStates[mOutputIndex].mInputIndex = mInputIndex;
mCorrectionStates[mOutputIndex].mNeedsToTraverseAllNodes = mNeedsToTraverseAllNodes; mCorrectionStates[mOutputIndex].mNeedsToTraverseAllNodes = mNeedsToTraverseAllNodes;
mCorrectionStates[mOutputIndex].mEquivalentCharStrongCount = mEquivalentCharStrongCount; mCorrectionStates[mOutputIndex].mSumOfDistance = mSumOfDistance;
mCorrectionStates[mOutputIndex].mEquivalentCharNormalCount = mEquivalentCharNormalCount; mCorrectionStates[mOutputIndex].mEquivalentCharCount = mEquivalentCharCount;
mCorrectionStates[mOutputIndex].mEquivalentCharWeakCount = mEquivalentCharWeakCount;
mCorrectionStates[mOutputIndex].mProximityCount = mProximityCount; mCorrectionStates[mOutputIndex].mProximityCount = mProximityCount;
mCorrectionStates[mOutputIndex].mTransposedCount = mTransposedCount; mCorrectionStates[mOutputIndex].mTransposedCount = mTransposedCount;
mCorrectionStates[mOutputIndex].mExcessiveCount = mExcessiveCount; mCorrectionStates[mOutputIndex].mExcessiveCount = mExcessiveCount;
@ -220,9 +218,7 @@ Correction::CorrectionType Correction::processSkipChar(
} }
inline bool isEquivalentChar(ProximityInfo::ProximityType type) { inline bool isEquivalentChar(ProximityInfo::ProximityType type) {
// 'type ProximityInfo::EQUIVALENT_CHAR_WEAK' means that return type == ProximityInfo::EQUIVALENT_CHAR;
// type == ..._WEAK or type == ..._NORMAL or type == ..._STRONG.
return type <= ProximityInfo::EQUIVALENT_CHAR_WEAK;
} }
Correction::CorrectionType Correction::processCharAndCalcState( 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 // TODO: Change the limit if we'll allow two or more proximity chars with corrections
const bool checkProximityChars = noCorrectionsHappenedSoFar || mProximityCount == 0; const bool checkProximityChars = noCorrectionsHappenedSoFar || mProximityCount == 0;
const ProximityInfo::ProximityType matchedProximityCharId = secondTransposing const ProximityInfo::ProximityType matchedProximityCharId = secondTransposing
? ProximityInfo::EQUIVALENT_CHAR_NORMAL ? ProximityInfo::EQUIVALENT_CHAR
: mProximityInfo->getMatchedProximityId(mInputIndex, c, checkProximityChars); : mProximityInfo->getMatchedProximityId(mInputIndex, c, checkProximityChars);
if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId) { if (ProximityInfo::UNRELATED_CHAR == matchedProximityCharId) {
@ -384,18 +380,14 @@ Correction::CorrectionType Correction::processCharAndCalcState(
mMatching = true; mMatching = true;
} else if (isEquivalentChar(matchedProximityCharId)) { } else if (isEquivalentChar(matchedProximityCharId)) {
mMatching = true; mMatching = true;
switch (matchedProximityCharId) { ++mEquivalentCharCount;
case ProximityInfo::EQUIVALENT_CHAR_STRONG: if (mSumOfDistance != NOT_A_DISTANCE) {
++mEquivalentCharStrongCount; const int distance = mProximityInfo->getNormalizedSquaredDistance(mInputIndex);
break; if (distance != NOT_A_DISTANCE) {
case ProximityInfo::EQUIVALENT_CHAR_NORMAL: mSumOfDistance += distance;
++mEquivalentCharNormalCount; } else {
break; mSumOfDistance = NOT_A_DISTANCE;
case ProximityInfo::EQUIVALENT_CHAR_WEAK: }
++mEquivalentCharWeakCount;
break;
default:
assert(false);
} }
} else if (ProximityInfo::NEAR_PROXIMITY_CHAR == matchedProximityCharId) { } else if (ProximityInfo::NEAR_PROXIMITY_CHAR == matchedProximityCharId) {
mProximityMatching = true; mProximityMatching = true;
@ -568,8 +560,8 @@ int Correction::RankingAlgorithm::calculateFinalFreq(const int inputIndex, const
const int transposedCount = correction->mTransposedCount / 2; const int transposedCount = correction->mTransposedCount / 2;
const int excessiveCount = correction->mExcessiveCount + correction->mTransposedCount % 2; const int excessiveCount = correction->mExcessiveCount + correction->mTransposedCount % 2;
const int proximityMatchedCount = correction->mProximityCount; const int proximityMatchedCount = correction->mProximityCount;
const int equivalentCharStrongCount = correction->mEquivalentCharStrongCount; const int mSumOfDistance = correction->mSumOfDistance;
const int equivalentCharWeakCount = correction->mEquivalentCharWeakCount; const int mEquivalentCharCount = correction->mEquivalentCharCount;
const bool lastCharExceeded = correction->mLastCharExceeded; const bool lastCharExceeded = correction->mLastCharExceeded;
const bool useFullEditDistance = correction->mUseFullEditDistance; const bool useFullEditDistance = correction->mUseFullEditDistance;
const int outputLength = outputIndex + 1; 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); multiplyRate(WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE, &finalFreq);
} }
for (int i = 0; i < equivalentCharStrongCount; ++i) { if (CALIBRATE_SCORE_BY_TOUCH_COORDINATES
if (DEBUG_DICT_FULL) { && mEquivalentCharCount > 0 && mSumOfDistance != NOT_A_DISTANCE) {
LOGI("equivalent char strong"); // 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
multiplyRate(WORDS_WITH_EQUIVALENT_CHAR_STRONG_PROMOTION_RATE, &finalFreq); // 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)
for (int i = 0; i < equivalentCharWeakCount; ++i) { // where (m_x, m_y) is a mean of touches of c, and s is a variance of touches of c.
if (DEBUG_DICT_FULL) { // If user touches c1, c2, .., cn, the joint distribution is
LOGI("equivalent char weak"); // p(x1, y1 | c1) * p(x2, y2 | c2) * ... * p(xn, yn | cn)
} // We consider the logarithm of this value, that is
multiplyRate(WORDS_WITH_EQUIVALENT_CHAR_WEAK_DEMOTION_RATE, &finalFreq); // 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 const int errorCount = adjustedProximityMatchedCount > 0

View file

@ -127,9 +127,8 @@ private:
int mOutputIndex; int mOutputIndex;
int mInputIndex; int mInputIndex;
int mEquivalentCharStrongCount; int mEquivalentCharCount;
int mEquivalentCharNormalCount; int mSumOfDistance;
int mEquivalentCharWeakCount;
int mProximityCount; int mProximityCount;
int mExcessiveCount; int mExcessiveCount;
int mTransposedCount; int mTransposedCount;

View file

@ -29,9 +29,8 @@ struct CorrectionState {
uint16_t mChildCount; uint16_t mChildCount;
uint8_t mInputIndex; uint8_t mInputIndex;
uint8_t mEquivalentCharStrongCount; int32_t mSumOfDistance;
uint8_t mEquivalentCharNormalCount; uint8_t mEquivalentCharCount;
uint8_t mEquivalentCharWeakCount;
uint8_t mProximityCount; uint8_t mProximityCount;
uint8_t mTransposedCount; uint8_t mTransposedCount;
uint8_t mExcessiveCount; uint8_t mExcessiveCount;
@ -66,9 +65,8 @@ inline static void initCorrectionState(CorrectionState *state, const int rootPos
state->mExcessivePos = -1; state->mExcessivePos = -1;
state->mSkipPos = -1; state->mSkipPos = -1;
state->mEquivalentCharStrongCount = 0; state->mSumOfDistance = 0;
state->mEquivalentCharNormalCount = 0; state->mEquivalentCharCount = 0;
state->mEquivalentCharWeakCount = 0;
state->mProximityCount = 0; state->mProximityCount = 0;
state->mTransposedCount = 0; state->mTransposedCount = 0;
state->mExcessiveCount = 0; state->mExcessiveCount = 0;

View file

@ -162,6 +162,7 @@ static void dumpWord(const unsigned short* word, const int length) {
#define NEW_DICTIONARY_HEADER_SIZE 5 #define NEW_DICTIONARY_HEADER_SIZE 5
#define NOT_VALID_WORD -99 #define NOT_VALID_WORD -99
#define NOT_A_CHARACTER -1 #define NOT_A_CHARACTER -1
#define NOT_A_DISTANCE -1
#define KEYCODE_SPACE ' ' #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_DEMOTION_RATE 75
#define WORDS_WITH_EXCESSIVE_CHARACTER_OUT_OF_PROXIMITY_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_TRANSPOSED_CHARACTERS_DEMOTION_RATE 60
#define WORDS_WITH_EQUIVALENT_CHAR_STRONG_PROMOTION_RATE 105 #define WORDS_WITH_EQUIVALENT_CHAR_STRONGEST_PROMOTION_RATE 110
#define WORDS_WITH_EQUIVALENT_CHAR_WEAK_DEMOTION_RATE 95 #define WORDS_WITH_EQUIVALENT_CHAR_WEAKEST_DEMOTION_RATE 90
#define FULL_MATCHED_WORDS_PROMOTION_RATE 120 #define FULL_MATCHED_WORDS_PROMOTION_RATE 120
#define WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE 90 #define WORDS_WITH_PROXIMITY_CHARACTER_DEMOTION_RATE 90
#define WORDS_WITH_MATCH_SKIP_PROMOTION_RATE 105 #define WORDS_WITH_MATCH_SKIP_PROMOTION_RATE 105

View file

@ -115,41 +115,42 @@ void ProximityInfo::setInputParams(const int* inputCodes, const int inputLength,
} }
mPrimaryInputWord[inputLength] = 0; mPrimaryInputWord[inputLength] = 0;
for (int i = 0; i < mInputLength; ++i) { 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; } 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) { if (KEY_COUNT == 0 || !mInputXCoordinates || !mInputYCoordinates) {
// We do not have the coordinate data // We do not have the coordinate data
return UNKNOWN; return NOT_A_DISTANCE_FLOAT;
} }
const int currentChar = getPrimaryCharAt(index); const int currentChar = getPrimaryCharAt(index);
const unsigned short baseLowerC = Dictionary::toBaseLowerCase(currentChar); const unsigned short baseLowerC = Dictionary::toBaseLowerCase(currentChar);
if (baseLowerC > MAX_CHAR_CODE) { if (baseLowerC > MAX_CHAR_CODE) {
return UNKNOWN; return NOT_A_DISTANCE_FLOAT;
} }
const int keyIndex = mCodeToKeyIndex[baseLowerC]; const int keyIndex = mCodeToKeyIndex[baseLowerC];
if (keyIndex < 0) { if (keyIndex < 0) {
return UNKNOWN; return NOT_A_DISTANCE_FLOAT;
} }
const float radius = mSweetSpotRadii[keyIndex]; const float radius = mSweetSpotRadii[keyIndex];
if (radius <= 0.0) { if (radius <= 0.0) {
// When there are no calibration data for a key, // When there are no calibration data for a key,
// the radius of the key is assigned to zero. // the radius of the key is assigned to zero.
return UNKNOWN; return NOT_A_DISTANCE;
} }
const float squaredRadius = square(radius); const float squaredRadius = square(radius);
const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(keyIndex, index); const float squaredDistance = calculateSquaredDistanceFromSweetSpotCenter(keyIndex, index);
if (squaredDistance <= squaredRadius) { return squaredDistance / squaredRadius;
return IN_SWEET_SPOT;
}
if (squaredDistance <= square(NEUTRAL_AREA_RADIUS_RATIO) * squaredRadius) {
return IN_NEUTRAL_AREA;
}
return OUT_OF_NEUTRAL_AREA;
} }
float ProximityInfo::calculateSquaredDistanceFromSweetSpotCenter( 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, // 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. // that means the user typed that same char for this pos.
if (firstChar == baseLowerC || firstChar == c) { if (firstChar == baseLowerC || firstChar == c) {
if (CALIBRATE_SCORE_BY_TOUCH_COORDINATES) { return EQUIVALENT_CHAR;
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;
}
} }
if (!checkProximityChars) return UNRELATED_CHAR; if (!checkProximityChars) return UNRELATED_CHAR;
@ -266,6 +252,8 @@ bool ProximityInfo::sameAsTyped(const unsigned short *word, int length) const {
return true; 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_KEY_COUNT_IN_A_KEYBOARD;
const int ProximityInfo::MAX_CHAR_CODE; const int ProximityInfo::MAX_CHAR_CODE;

View file

@ -27,14 +27,12 @@ class Correction;
class ProximityInfo { class ProximityInfo {
public: public:
static const int NORMALIZED_SQUARED_DISTANCE_SCALING_FACTOR_LOG_2 = 10;
// Used as a return value for character comparison // Used as a return value for character comparison
typedef enum { typedef enum {
// Same char, possibly with different case or accent, and in the sweet spot of the char // Same char, possibly with different case or accent
EQUIVALENT_CHAR_STRONG, EQUIVALENT_CHAR,
// 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,
// It is a char located nearby on the keyboard // It is a char located nearby on the keyboard
NEAR_PROXIMITY_CHAR, NEAR_PROXIMITY_CHAR,
// It is an unrelated char // It is an unrelated char
@ -57,31 +55,25 @@ public:
bool existsAdjacentProximityChars(const int index) const; bool existsAdjacentProximityChars(const int index) const;
ProximityType getMatchedProximityId( ProximityType getMatchedProximityId(
const int index, const unsigned short c, const bool checkProximityChars) const; 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; bool sameAsTyped(const unsigned short *word, int length) const;
const unsigned short* getPrimaryInputWord() const { const unsigned short* getPrimaryInputWord() const {
return mPrimaryInputWord; return mPrimaryInputWord;
} }
private: 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 // The max number of the keys in one keyboard layout
static const int MAX_KEY_COUNT_IN_A_KEYBOARD = 64; static const int MAX_KEY_COUNT_IN_A_KEYBOARD = 64;
// The upper limit of the char code in mCodeToKeyIndex // The upper limit of the char code in mCodeToKeyIndex
static const int MAX_CHAR_CODE = 127; 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; int getStartIndexFromCoordinates(const int x, const int y) const;
void initializeCodeToKeyIndex(); void initializeCodeToKeyIndex();
SweetSpotType calculateSweetSpotType(int index) const; float calculateNormalizedSquaredDistance(int index) const;
float calculateSquaredDistanceFromSweetSpotCenter(int keyIndex, int inputIndex) const; float calculateSquaredDistanceFromSweetSpotCenter(int keyIndex, int inputIndex) const;
const int MAX_PROXIMITY_CHARS_SIZE; const int MAX_PROXIMITY_CHARS_SIZE;
@ -104,7 +96,7 @@ private:
float mSweetSpotCenterXs[MAX_KEY_COUNT_IN_A_KEYBOARD]; float mSweetSpotCenterXs[MAX_KEY_COUNT_IN_A_KEYBOARD];
float mSweetSpotCenterYs[MAX_KEY_COUNT_IN_A_KEYBOARD]; float mSweetSpotCenterYs[MAX_KEY_COUNT_IN_A_KEYBOARD];
float mSweetSpotRadii[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; int mInputLength;
unsigned short mPrimaryInputWord[MAX_WORD_LENGTH_INTERNAL]; unsigned short mPrimaryInputWord[MAX_WORD_LENGTH_INTERNAL];
int mCodeToKeyIndex[MAX_CHAR_CODE + 1]; int mCodeToKeyIndex[MAX_CHAR_CODE + 1];