Improve gesture input scoring method 1.
Calculate probabilities for each points in advance. It enables to input not in the dictionary word. Change-Id: I8d84642045dc3b8ad49719d9b70dda14457995cdmain
parent
11cec73499
commit
806eba4524
|
@ -219,6 +219,8 @@ static inline void prof_out(void) {
|
||||||
#define DEBUG_CORRECTION false
|
#define DEBUG_CORRECTION false
|
||||||
#define DEBUG_CORRECTION_FREQ false
|
#define DEBUG_CORRECTION_FREQ false
|
||||||
#define DEBUG_WORDS_PRIORITY_QUEUE false
|
#define DEBUG_WORDS_PRIORITY_QUEUE false
|
||||||
|
#define DEBUG_SAMPLING_POINTS true
|
||||||
|
#define DEBUG_POINTS_PROBABILITY true
|
||||||
|
|
||||||
#ifdef FLAG_FULL_DBG
|
#ifdef FLAG_FULL_DBG
|
||||||
#define DEBUG_GEO_FULL true
|
#define DEBUG_GEO_FULL true
|
||||||
|
@ -239,6 +241,8 @@ static inline void prof_out(void) {
|
||||||
#define DEBUG_CORRECTION false
|
#define DEBUG_CORRECTION false
|
||||||
#define DEBUG_CORRECTION_FREQ false
|
#define DEBUG_CORRECTION_FREQ false
|
||||||
#define DEBUG_WORDS_PRIORITY_QUEUE false
|
#define DEBUG_WORDS_PRIORITY_QUEUE false
|
||||||
|
#define DEBUG_SAMPLING_POINTS false
|
||||||
|
#define DEBUG_POINTS_PROBABILITY false
|
||||||
|
|
||||||
#define DEBUG_GEO_FULL false
|
#define DEBUG_GEO_FULL false
|
||||||
|
|
||||||
|
|
|
@ -85,5 +85,24 @@ static inline float pointToLineSegSquaredDistanceFloat(
|
||||||
}
|
}
|
||||||
return getSquaredDistanceFloat(x, y, projectionX, projectionY);
|
return getSquaredDistanceFloat(x, y, projectionX, projectionY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normal distribution N(u, sigma^2).
|
||||||
|
struct NormalDistribution {
|
||||||
|
NormalDistribution(const float u, const float sigma)
|
||||||
|
: mU(u), mSigma(sigma),
|
||||||
|
mPreComputedNonExpPart(1.0f / sqrtf(2.0f * M_PI_F * sigma * sigma)),
|
||||||
|
mPreComputedExponentPart(-1.0f / (2.0f * sigma * sigma)) {}
|
||||||
|
|
||||||
|
float getProbabilityDensity(const float x) {
|
||||||
|
const float shiftedX = x - mU;
|
||||||
|
return mPreComputedNonExpPart * expf(mPreComputedExponentPart * SQUARE_FLOAT(shiftedX));
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
DISALLOW_IMPLICIT_CONSTRUCTORS(NormalDistribution);
|
||||||
|
float mU; // mean value
|
||||||
|
float mSigma; // standard deviation
|
||||||
|
float mPreComputedNonExpPart; // = 1 / sqrt(2 * PI * sigma^2)
|
||||||
|
float mPreComputedExponentPart; // = -1 / (2 * sigma^2)
|
||||||
|
};
|
||||||
} // namespace latinime
|
} // namespace latinime
|
||||||
#endif // LATINIME_GEOMETRY_UTILS_H
|
#endif // LATINIME_GEOMETRY_UTILS_H
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <cstring> // for memset()
|
#include <cstring> // for memset()
|
||||||
|
#include <sstream> // for debug prints
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#define LOG_TAG "LatinIME: proximity_info_state.cpp"
|
#define LOG_TAG "LatinIME: proximity_info_state.cpp"
|
||||||
|
@ -105,6 +106,7 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi
|
||||||
mDistanceCache.clear();
|
mDistanceCache.clear();
|
||||||
mNearKeysVector.clear();
|
mNearKeysVector.clear();
|
||||||
mRelativeSpeeds.clear();
|
mRelativeSpeeds.clear();
|
||||||
|
mCharProbabilities.clear();
|
||||||
}
|
}
|
||||||
if (DEBUG_GEO_FULL) {
|
if (DEBUG_GEO_FULL) {
|
||||||
AKLOGI("Init ProximityInfoState: reused points = %d, last input size = %d",
|
AKLOGI("Init ProximityInfoState: reused points = %d, last input size = %d",
|
||||||
|
@ -161,36 +163,44 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mInputSize > 0 && isGeometric) {
|
if (mInputSize > 0 && isGeometric) {
|
||||||
int sumDuration = mTimes.back() - mTimes.front();
|
const int sumDuration = mTimes.back() - mTimes.front();
|
||||||
int sumLength = mLengthCache.back() - mLengthCache.front();
|
const int sumLength = mLengthCache.back() - mLengthCache.front();
|
||||||
float averageSpeed = static_cast<float>(sumLength) / static_cast<float>(sumDuration);
|
const float averageSpeed = static_cast<float>(sumLength) / static_cast<float>(sumDuration);
|
||||||
mRelativeSpeeds.resize(mInputSize);
|
mRelativeSpeeds.resize(mInputSize);
|
||||||
for (int i = lastSavedInputSize; i < mInputSize; ++i) {
|
for (int i = lastSavedInputSize; i < mInputSize; ++i) {
|
||||||
const int index = mInputIndice[i];
|
const int index = mInputIndice[i];
|
||||||
int length = 0;
|
int length = 0;
|
||||||
int duration = 0;
|
int duration = 0;
|
||||||
if (index == 0 && index < inputSize - 1) {
|
|
||||||
length = getDistanceInt(xCoordinates[index], yCoordinates[index],
|
// Calculate velocity by using distances and durations of
|
||||||
xCoordinates[index + 1], yCoordinates[index + 1]);
|
// NUM_POINTS_FOR_SPEED_CALCULATION points for both forward and backward.
|
||||||
duration = times[index + 1] - times[index];
|
static const int NUM_POINTS_FOR_SPEED_CALCULATION = 1;
|
||||||
} else if (index == inputSize - 1 && index > 0) {
|
for (int j = index; j < min(inputSize - 1, index + NUM_POINTS_FOR_SPEED_CALCULATION);
|
||||||
length = getDistanceInt(xCoordinates[index - 1], yCoordinates[index - 1],
|
++j) {
|
||||||
xCoordinates[index], yCoordinates[index]);
|
if (i < mInputSize - 1 && j >= mInputIndice[i + 1]) {
|
||||||
duration = times[index] - times[index - 1];
|
break;
|
||||||
} else if (0 < index && index < inputSize - 1) {
|
|
||||||
length = getDistanceInt(xCoordinates[index - 1], yCoordinates[index - 1],
|
|
||||||
xCoordinates[index], yCoordinates[index])
|
|
||||||
+ getDistanceInt(xCoordinates[index], yCoordinates[index],
|
|
||||||
xCoordinates[index + 1], yCoordinates[index + 1]);
|
|
||||||
duration = times[index + 1] - times[index - 1];
|
|
||||||
} else {
|
|
||||||
length = 0;
|
|
||||||
duration = 1;
|
|
||||||
}
|
}
|
||||||
|
length += getDistanceInt(xCoordinates[j], yCoordinates[j],
|
||||||
|
xCoordinates[j + 1], yCoordinates[j + 1]);
|
||||||
|
duration += times[j + 1] - times[j];
|
||||||
|
}
|
||||||
|
for (int j = index - 1; j >= max(0, index - NUM_POINTS_FOR_SPEED_CALCULATION); --j) {
|
||||||
|
if (i > 0 && j < mInputIndice[i - 1]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
length += getDistanceInt(xCoordinates[j], yCoordinates[j],
|
||||||
|
xCoordinates[j + 1], yCoordinates[j + 1]);
|
||||||
|
duration += times[j + 1] - times[j];
|
||||||
|
}
|
||||||
|
if (duration == 0 || sumDuration == 0) {
|
||||||
|
// Cannot calculate speed; thus, it gives an average value (1.0);
|
||||||
|
mRelativeSpeeds[i] = 1.0f;
|
||||||
|
} else {
|
||||||
const float speed = static_cast<float>(length) / static_cast<float>(duration);
|
const float speed = static_cast<float>(length) / static_cast<float>(duration);
|
||||||
mRelativeSpeeds[i] = speed / averageSpeed;
|
mRelativeSpeeds[i] = speed / averageSpeed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (mInputSize > 0) {
|
if (mInputSize > 0) {
|
||||||
const int keyCount = mProximityInfo->getKeyCount();
|
const int keyCount = mProximityInfo->getKeyCount();
|
||||||
|
@ -230,6 +240,27 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (DEBUG_SAMPLING_POINTS) {
|
||||||
|
std::stringstream originalX, originalY, sampledX, sampledY;
|
||||||
|
for (int i = 0; i < inputSize; ++i) {
|
||||||
|
originalX << xCoordinates[i];
|
||||||
|
originalY << yCoordinates[i];
|
||||||
|
if (i != inputSize - 1) {
|
||||||
|
originalX << ";";
|
||||||
|
originalY << ";";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < mInputSize; ++i) {
|
||||||
|
sampledX << mInputXs[i];
|
||||||
|
sampledY << mInputYs[i];
|
||||||
|
if (i != mInputSize - 1) {
|
||||||
|
sampledX << ";";
|
||||||
|
sampledY << ";";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AKLOGI("\n%s, %s,\n%s, %s,\n", originalX.str().c_str(), originalY.str().c_str(),
|
||||||
|
sampledX.str().c_str(), sampledY.str().c_str());
|
||||||
|
}
|
||||||
// end
|
// end
|
||||||
///////////////////////
|
///////////////////////
|
||||||
|
|
||||||
|
@ -276,6 +307,10 @@ void ProximityInfoState::initInputParams(const int pointerId, const float maxPoi
|
||||||
if (DEBUG_GEO_FULL) {
|
if (DEBUG_GEO_FULL) {
|
||||||
AKLOGI("ProximityState init finished: %d points out of %d", mInputSize, inputSize);
|
AKLOGI("ProximityState init finished: %d points out of %d", mInputSize, inputSize);
|
||||||
}
|
}
|
||||||
|
if (isGeometric && mInputSize > 0) {
|
||||||
|
// updates probabilities of skipping or mapping each key for all points.
|
||||||
|
updateAlignPointProbabilities();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ProximityInfoState::checkAndReturnIsContinuationPossible(const int inputSize,
|
bool ProximityInfoState::checkAndReturnIsContinuationPossible(const int inputSize,
|
||||||
|
@ -294,7 +329,7 @@ bool ProximityInfoState::checkAndReturnIsContinuationPossible(const int inputSiz
|
||||||
// the given point and the nearest key position.
|
// the given point and the nearest key position.
|
||||||
float ProximityInfoState::updateNearKeysDistances(const int x, const int y,
|
float ProximityInfoState::updateNearKeysDistances(const int x, const int y,
|
||||||
NearKeysDistanceMap *const currentNearKeysDistances) {
|
NearKeysDistanceMap *const currentNearKeysDistances) {
|
||||||
static const float NEAR_KEY_THRESHOLD = 4.0f;
|
static const float NEAR_KEY_THRESHOLD = 1.7f;
|
||||||
|
|
||||||
currentNearKeysDistances->clear();
|
currentNearKeysDistances->clear();
|
||||||
const int keyCount = mProximityInfo->getKeyCount();
|
const int keyCount = mProximityInfo->getKeyCount();
|
||||||
|
@ -315,7 +350,7 @@ float ProximityInfoState::updateNearKeysDistances(const int x, const int y,
|
||||||
bool ProximityInfoState::isPrevLocalMin(const NearKeysDistanceMap *const currentNearKeysDistances,
|
bool ProximityInfoState::isPrevLocalMin(const NearKeysDistanceMap *const currentNearKeysDistances,
|
||||||
const NearKeysDistanceMap *const prevNearKeysDistances,
|
const NearKeysDistanceMap *const prevNearKeysDistances,
|
||||||
const NearKeysDistanceMap *const prevPrevNearKeysDistances) const {
|
const NearKeysDistanceMap *const prevPrevNearKeysDistances) const {
|
||||||
static const float MARGIN = 0.01f;
|
static const float MARGIN = 0.03f;
|
||||||
|
|
||||||
for (NearKeysDistanceMap::const_iterator it = prevNearKeysDistances->begin();
|
for (NearKeysDistanceMap::const_iterator it = prevNearKeysDistances->begin();
|
||||||
it != prevNearKeysDistances->end(); ++it) {
|
it != prevNearKeysDistances->end(); ++it) {
|
||||||
|
@ -336,18 +371,20 @@ float ProximityInfoState::getPointScore(
|
||||||
const NearKeysDistanceMap *const prevNearKeysDistances,
|
const NearKeysDistanceMap *const prevNearKeysDistances,
|
||||||
const NearKeysDistanceMap *const prevPrevNearKeysDistances) const {
|
const NearKeysDistanceMap *const prevPrevNearKeysDistances) const {
|
||||||
static const int DISTANCE_BASE_SCALE = 100;
|
static const int DISTANCE_BASE_SCALE = 100;
|
||||||
static const int SAVE_DISTANCE_SCALE = 200;
|
static const int SAVE_DISTANCE_SCALE = 500;
|
||||||
static const int SKIP_DISTANCE_SCALE = 25;
|
static const int SKIP_DISTANCE_SCALE = 10;
|
||||||
static const int CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE = 40;
|
static const float NEAR_KEY_THRESHOLD = 1.0f;
|
||||||
static const int STRAIGHT_SKIP_DISTANCE_THRESHOLD_SCALE = 50;
|
static const int CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE = 100;
|
||||||
static const int CORNER_CHECK_DISTANCE_THRESHOLD_SCALE = 27;
|
static const int STRAIGHT_SKIP_DISTANCE_THRESHOLD_SCALE = 200;
|
||||||
|
static const int CORNER_CHECK_DISTANCE_THRESHOLD_SCALE = 20;
|
||||||
static const float SAVE_DISTANCE_SCORE = 2.0f;
|
static const float SAVE_DISTANCE_SCORE = 2.0f;
|
||||||
static const float SKIP_DISTANCE_SCORE = -1.0f;
|
static const float SKIP_DISTANCE_SCORE = -1.0f;
|
||||||
static const float CHECK_LOCALMIN_DISTANCE_SCORE = -1.0f;
|
static const float NOT_LOCALMIN_DISTANCE_SCORE = -1.0f;
|
||||||
|
static const float LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE = 2.0f;
|
||||||
static const float STRAIGHT_ANGLE_THRESHOLD = M_PI_F / 36.0f;
|
static const float STRAIGHT_ANGLE_THRESHOLD = M_PI_F / 36.0f;
|
||||||
static const float STRAIGHT_SKIP_NEAREST_DISTANCE_THRESHOLD = 0.5f;
|
static const float STRAIGHT_SKIP_NEAREST_DISTANCE_THRESHOLD = 0.5f;
|
||||||
static const float STRAIGHT_SKIP_SCORE = -1.0f;
|
static const float STRAIGHT_SKIP_SCORE = -1.0f;
|
||||||
static const float CORNER_ANGLE_THRESHOLD = M_PI_F / 2.0f;
|
static const float CORNER_ANGLE_THRESHOLD = M_PI_F / 6.0f;
|
||||||
static const float CORNER_SCORE = 1.0f;
|
static const float CORNER_SCORE = 1.0f;
|
||||||
|
|
||||||
const std::size_t size = mInputXs.size();
|
const std::size_t size = mInputXs.size();
|
||||||
|
@ -373,7 +410,10 @@ float ProximityInfoState::getPointScore(
|
||||||
if (distPrev < baseSampleRate * CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE) {
|
if (distPrev < baseSampleRate * CHECK_LOCALMIN_DISTANCE_THRESHOLD_SCALE) {
|
||||||
if (!isPrevLocalMin(currentNearKeysDistances, prevNearKeysDistances,
|
if (!isPrevLocalMin(currentNearKeysDistances, prevNearKeysDistances,
|
||||||
prevPrevNearKeysDistances)) {
|
prevPrevNearKeysDistances)) {
|
||||||
score += CHECK_LOCALMIN_DISTANCE_SCORE;
|
score += NOT_LOCALMIN_DISTANCE_SCORE;
|
||||||
|
} else if (nearest < NEAR_KEY_THRESHOLD) {
|
||||||
|
// Promote points nearby keys
|
||||||
|
score += LOCALMIN_DISTANCE_AND_NEAR_TO_KEY_SCORE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Angle
|
// Angle
|
||||||
|
@ -402,7 +442,8 @@ bool ProximityInfoState::pushTouchPoint(const int inputIndex, const int nodeChar
|
||||||
NearKeysDistanceMap *const currentNearKeysDistances,
|
NearKeysDistanceMap *const currentNearKeysDistances,
|
||||||
const NearKeysDistanceMap *const prevNearKeysDistances,
|
const NearKeysDistanceMap *const prevNearKeysDistances,
|
||||||
const NearKeysDistanceMap *const prevPrevNearKeysDistances) {
|
const NearKeysDistanceMap *const prevPrevNearKeysDistances) {
|
||||||
static const float LAST_POINT_SKIP_DISTANCE_SCALE = 0.25f;
|
static const int LAST_POINT_SKIP_DISTANCE_SCALE = 4;
|
||||||
|
static const int LAST_AND_NOT_NEAREST_POINT_SKIP_DISTANCE_SCALE = 2;
|
||||||
|
|
||||||
size_t size = mInputXs.size();
|
size_t size = mInputXs.size();
|
||||||
bool popped = false;
|
bool popped = false;
|
||||||
|
@ -419,33 +460,38 @@ bool ProximityInfoState::pushTouchPoint(const int inputIndex, const int nodeChar
|
||||||
popped = false;
|
popped = false;
|
||||||
}
|
}
|
||||||
// Check if the last point should be skipped.
|
// Check if the last point should be skipped.
|
||||||
if (isLastPoint) {
|
if (isLastPoint && size > 0) {
|
||||||
if (size > 0 && getDistanceFloat(x, y, mInputXs.back(), mInputYs.back())
|
const int lastPointsDistance = getDistanceInt(x, y, mInputXs.back(), mInputYs.back());
|
||||||
< mProximityInfo->getMostCommonKeyWidth() * LAST_POINT_SKIP_DISTANCE_SCALE) {
|
if (lastPointsDistance * LAST_POINT_SKIP_DISTANCE_SCALE
|
||||||
|
< mProximityInfo->getMostCommonKeyWidth()) {
|
||||||
|
// This point is not used because it's too close to the previous point.
|
||||||
if (DEBUG_GEO_FULL) {
|
if (DEBUG_GEO_FULL) {
|
||||||
AKLOGI("p0: size = %zd, x = %d, y = %d, lx = %d, ly = %d, dist = %f, "
|
AKLOGI("p0: size = %zd, x = %d, y = %d, lx = %d, ly = %d, dist = %d, "
|
||||||
"width = %f", size, x, y, mInputXs.back(), mInputYs.back(),
|
"width = %d", size, x, y, mInputXs.back(), mInputYs.back(),
|
||||||
getDistanceFloat(x, y, mInputXs.back(), mInputYs.back()),
|
getDistanceInt(x, y, mInputXs.back(), mInputYs.back()),
|
||||||
mProximityInfo->getMostCommonKeyWidth()
|
mProximityInfo->getMostCommonKeyWidth()
|
||||||
* LAST_POINT_SKIP_DISTANCE_SCALE);
|
/ LAST_POINT_SKIP_DISTANCE_SCALE);
|
||||||
}
|
}
|
||||||
return popped;
|
return popped;
|
||||||
} else if (size > 1) {
|
} else if (lastPointsDistance * LAST_AND_NOT_NEAREST_POINT_SKIP_DISTANCE_SCALE
|
||||||
int minChar = 0;
|
< mProximityInfo->getMostCommonKeyWidth()) {
|
||||||
float minDist = mMaxPointToKeyLength;
|
int nearestChar = 0;
|
||||||
|
float nearestCharDistance = mMaxPointToKeyLength;
|
||||||
for (NearKeysDistanceMap::const_iterator it = currentNearKeysDistances->begin();
|
for (NearKeysDistanceMap::const_iterator it = currentNearKeysDistances->begin();
|
||||||
it != currentNearKeysDistances->end(); ++it) {
|
it != currentNearKeysDistances->end(); ++it) {
|
||||||
if (minDist > it->second) {
|
if (nearestCharDistance > it->second) {
|
||||||
minChar = it->first;
|
nearestChar = it->first;
|
||||||
minDist = it->second;
|
nearestCharDistance = it->second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NearKeysDistanceMap::const_iterator itPP =
|
NearKeysDistanceMap::const_iterator itPP =
|
||||||
prevNearKeysDistances->find(minChar);
|
prevNearKeysDistances->find(nearestChar);
|
||||||
if (itPP != prevNearKeysDistances->end() && minDist > itPP->second) {
|
if (itPP != prevNearKeysDistances->end() && nearestCharDistance > itPP->second) {
|
||||||
|
// The nearest key of the penultimate point is same as the nearest key of the
|
||||||
|
// last point. So, we don't need to use the last point.
|
||||||
if (DEBUG_GEO_FULL) {
|
if (DEBUG_GEO_FULL) {
|
||||||
AKLOGI("p1: char = %c, minDist = %f, prevNear key minDist = %f",
|
AKLOGI("p1: char = %c, minDist = %f, prevNear key minDist = %f",
|
||||||
minChar, itPP->second, minDist);
|
nearestChar, itPP->second, nearestCharDistance);
|
||||||
}
|
}
|
||||||
return popped;
|
return popped;
|
||||||
}
|
}
|
||||||
|
@ -503,18 +549,21 @@ int ProximityInfoState::getDuration(const int index) const {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
float ProximityInfoState::getPointToKeyLength(const int inputIndex, const int codePoint,
|
float ProximityInfoState::getPointToKeyLength(const int inputIndex, const int codePoint) const {
|
||||||
const float scale) const {
|
|
||||||
const int keyId = mProximityInfo->getKeyIndexOf(codePoint);
|
|
||||||
if (keyId != NOT_AN_INDEX) {
|
|
||||||
const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
|
|
||||||
return min(mDistanceCache[index] * scale, mMaxPointToKeyLength);
|
|
||||||
}
|
|
||||||
if (isSkippableChar(codePoint)) {
|
if (isSkippableChar(codePoint)) {
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
const int keyId = mProximityInfo->getKeyIndexOf(codePoint);
|
||||||
|
return getPointToKeyByIdLength(inputIndex, keyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
float ProximityInfoState::getPointToKeyByIdLength(const int inputIndex, const int keyId) const {
|
||||||
|
if (keyId != NOT_AN_INDEX) {
|
||||||
|
const int index = inputIndex * mProximityInfo->getKeyCount() + keyId;
|
||||||
|
return min(mDistanceCache[index], mMaxPointToKeyLength);
|
||||||
|
}
|
||||||
// If the char is not a key on the keyboard then return the max length.
|
// If the char is not a key on the keyboard then return the max length.
|
||||||
return MAX_POINT_TO_KEY_LENGTH;
|
return static_cast<float>(MAX_POINT_TO_KEY_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ProximityInfoState::getSpaceY() const {
|
int ProximityInfoState::getSpaceY() const {
|
||||||
|
@ -565,4 +614,217 @@ void ProximityInfoState::popInputData() {
|
||||||
mInputIndice.pop_back();
|
mInputIndice.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float ProximityInfoState::getPointAngle(const int index) const {
|
||||||
|
if (index <= 0 || index >= mInputSize - 1) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
const int x = mInputXs[index];
|
||||||
|
const int y = mInputYs[index];
|
||||||
|
const int nextX = mInputXs[index + 1];
|
||||||
|
const int nextY = mInputYs[index + 1];
|
||||||
|
const int previousX = mInputXs[index - 1];
|
||||||
|
const int previousY = mInputYs[index - 1];
|
||||||
|
const float previousDirection = getAngle(previousX, previousY, x, y);
|
||||||
|
const float nextDirection = getAngle(x, y, nextX, nextY);
|
||||||
|
const float directionDiff = getAngleDiff(previousDirection, nextDirection);
|
||||||
|
return directionDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
float ProximityInfoState::getPointsAngle(
|
||||||
|
const int index0, const int index1, const int index2) const {
|
||||||
|
if (index0 < 0 || index0 > mInputSize - 1) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
if (index1 < 0 || index1 > mInputSize - 1) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
if (index2 < 0 || index2 > mInputSize - 1) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
const int x0 = mInputXs[index0];
|
||||||
|
const int y0 = mInputYs[index0];
|
||||||
|
const int x1 = mInputXs[index1];
|
||||||
|
const int y1 = mInputYs[index1];
|
||||||
|
const int x2 = mInputXs[index2];
|
||||||
|
const int y2 = mInputYs[index2];
|
||||||
|
const float previousDirection = getAngle(x0, y0, x1, y1);
|
||||||
|
const float nextDirection = getAngle(x1, y1, x2, y2);
|
||||||
|
const float directionDiff = getAngleDiff(previousDirection, nextDirection);
|
||||||
|
return directionDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updates probabilities of aligning to some keys and skipping.
|
||||||
|
// Word suggestion should be based on this probabilities.
|
||||||
|
void ProximityInfoState::updateAlignPointProbabilities() {
|
||||||
|
static const float MIN_PROBABILITY = 0.00001f;
|
||||||
|
static const float SKIP_FIRST_POINT_PROBABILITY = 0.01f;
|
||||||
|
static const float SKIP_LAST_POINT_PROBABILITY = 0.1f;
|
||||||
|
static const float ANGLE_RATE = 0.8f;
|
||||||
|
static const float DEEP_CORNER_ANGLE_THRESHOLD = M_PI_F * 0.5f;
|
||||||
|
static const float SKIP_DEEP_CORNER_PROBABILITY = 0.3f;
|
||||||
|
static const float CORNER_ANGLE_THRESHOLD = M_PI_F * 35.0f / 180.0f;
|
||||||
|
static const float STRAIGHT_ANGLE_THRESHOLD = M_PI_F * 15.0f / 180.0f;
|
||||||
|
static const float SKIP_CORNER_PROBABILITY = 0.5f;
|
||||||
|
static const float SLOW_STRAIGHT_WEIGHT = 0.8f;
|
||||||
|
static const float CENTER_VALUE_OF_NORMALIZED_DISTRIBUTION = 0.0f;
|
||||||
|
|
||||||
|
mCharProbabilities.resize(mInputSize);
|
||||||
|
// Calculates probabilities of using a point as a correlated point with the character
|
||||||
|
// for each point.
|
||||||
|
for (int i = 0; i < mInputSize; ++i) {
|
||||||
|
// First, calculates skip probability. Starts form 100%.
|
||||||
|
// Note that all values that are multiplied to this probability should be in [0.0, 1.0];
|
||||||
|
float skipProbability = 1.0f;
|
||||||
|
const float speed = getRelativeSpeed(i);
|
||||||
|
|
||||||
|
// Adjusts skip probability by a rate depending on speed.
|
||||||
|
skipProbability *= min(1.0f, speed);
|
||||||
|
if (i == 0) {
|
||||||
|
skipProbability *= SKIP_FIRST_POINT_PROBABILITY;
|
||||||
|
} else if (i == mInputSize - 1) {
|
||||||
|
skipProbability *= SKIP_LAST_POINT_PROBABILITY;
|
||||||
|
} else {
|
||||||
|
const float currentAngle = getPointAngle(i);
|
||||||
|
|
||||||
|
// Adjusts skip probability by a rate depending on angle.
|
||||||
|
// ANGLE_RATE of skipProbability is adjusted by current angle.
|
||||||
|
skipProbability *= max((M_PI_F - currentAngle) / M_PI_F, 0.0f) * ANGLE_RATE +
|
||||||
|
(1.0f - ANGLE_RATE);
|
||||||
|
if (currentAngle > DEEP_CORNER_ANGLE_THRESHOLD) {
|
||||||
|
skipProbability *= SKIP_DEEP_CORNER_PROBABILITY;
|
||||||
|
}
|
||||||
|
const float prevAngle = getPointsAngle(i, i - 1, i - 2);
|
||||||
|
if (prevAngle < STRAIGHT_ANGLE_THRESHOLD && currentAngle > CORNER_ANGLE_THRESHOLD) {
|
||||||
|
skipProbability *= SKIP_CORNER_PROBABILITY;
|
||||||
|
}
|
||||||
|
if (currentAngle < STRAIGHT_ANGLE_THRESHOLD) {
|
||||||
|
// Adjusts skip probability by speed.
|
||||||
|
skipProbability *= min(1.0f, speed * SLOW_STRAIGHT_WEIGHT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// probabilities must be in [0.0, 1.0];
|
||||||
|
ASSERT(skipProbability >= 0.0f);
|
||||||
|
ASSERT(skipProbability <= 1.0f);
|
||||||
|
|
||||||
|
mCharProbabilities[i][NOT_AN_INDEX] = skipProbability;
|
||||||
|
// Second, calculates key probabilities by dividing the rest probability
|
||||||
|
// (1.0f - skipProbability).
|
||||||
|
const float inputCharProbability = 1.0f - skipProbability;
|
||||||
|
// Summing up probability densities of all near keys.
|
||||||
|
float sumOfProbabilityDensityOfNearKeys = 0.0f;
|
||||||
|
const float sigma = speed;
|
||||||
|
NormalDistribution distribution(CENTER_VALUE_OF_NORMALIZED_DISTRIBUTION, sigma);
|
||||||
|
for (int j = 0; j < mProximityInfo->getKeyCount(); ++j) {
|
||||||
|
if (mNearKeysVector[i].test(j)) {
|
||||||
|
const float distance = sqrtf(getPointToKeyByIdLength(i, j));
|
||||||
|
sumOfProbabilityDensityOfNearKeys += distribution.getProbabilityDensity(distance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int j = 0; j < mProximityInfo->getKeyCount(); ++j) {
|
||||||
|
if (mNearKeysVector[i].test(j)) {
|
||||||
|
const float distance = sqrtf(getPointToKeyByIdLength(i, j));
|
||||||
|
const float probabilityDessity = distribution.getProbabilityDensity(distance);
|
||||||
|
// inputCharProbability divided to the probability for each near key.
|
||||||
|
const float probability = inputCharProbability * probabilityDessity
|
||||||
|
/ sumOfProbabilityDensityOfNearKeys;
|
||||||
|
if (probability > MIN_PROBABILITY) {
|
||||||
|
mCharProbabilities[i][j] = probability;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrease key probabilities of points which don't have the highest probability of that key
|
||||||
|
// among nearby points. Probabilities of the first point and the last point are not suppressed.
|
||||||
|
for (int i = 1; i < mInputSize - 1; ++i) {
|
||||||
|
// forward
|
||||||
|
for (int j = i + 1; j < mInputSize; ++j) {
|
||||||
|
if (suppressCharProbabilities(i, j)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// backward
|
||||||
|
for (int j = i - 1; j >= 0; --j) {
|
||||||
|
if (suppressCharProbabilities(i, j)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DEBUG_POINTS_PROBABILITY) {
|
||||||
|
for (int i = 0; i < mInputSize; ++i) {
|
||||||
|
std::stringstream sstream;
|
||||||
|
sstream << i << ", ";
|
||||||
|
for (hash_map_compat<int, float>::iterator it = mCharProbabilities[i].begin();
|
||||||
|
it != mCharProbabilities[i].end(); ++it) {
|
||||||
|
sstream << it->first
|
||||||
|
<< "("
|
||||||
|
<< static_cast<char>(mProximityInfo->getCodePointOf(it->first))
|
||||||
|
<< "):"
|
||||||
|
<< it->second
|
||||||
|
<< ", ";
|
||||||
|
}
|
||||||
|
AKLOGI("%s", sstream.str().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decreases char probabilities of index0 by checking probabilities of a near point (index1).
|
||||||
|
bool ProximityInfoState::suppressCharProbabilities(const int index0, const int index1) {
|
||||||
|
ASSERT(0 <= index0 && index0 < mInputSize);
|
||||||
|
ASSERT(0 <= index1 && index1 < mInputSize);
|
||||||
|
static const float SUPPRESSION_LENGTH_WEIGHT = 1.5f;
|
||||||
|
const float keyWidthFloat = static_cast<float>(mProximityInfo->getMostCommonKeyWidth());
|
||||||
|
const float diff = fabsf(static_cast<float>(mLengthCache[index0] - mLengthCache[index1]));
|
||||||
|
if (diff > keyWidthFloat * SUPPRESSION_LENGTH_WEIGHT) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Summing up decreased amount of probabilities from 0%.
|
||||||
|
float sumOfAdjustedProbabilities = 0.0f;
|
||||||
|
const float suppressionRate = diff / keyWidthFloat / SUPPRESSION_LENGTH_WEIGHT;
|
||||||
|
for (hash_map_compat<int, float>::iterator it = mCharProbabilities[index0].begin();
|
||||||
|
it != mCharProbabilities[index0].end(); ++it) {
|
||||||
|
hash_map_compat<int, float>::const_iterator it2 =
|
||||||
|
mCharProbabilities[index1].find(it->first);
|
||||||
|
if (it2 != mCharProbabilities[index1].end() && it->second < it2->second) {
|
||||||
|
const float newProbability = it->second * suppressionRate;
|
||||||
|
sumOfAdjustedProbabilities += it->second - newProbability;
|
||||||
|
it->second = newProbability;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// All decreased amount of probabilities are added to the probability of skipping.
|
||||||
|
mCharProbabilities[index0][NOT_AN_INDEX] += sumOfAdjustedProbabilities;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a word that is detected by tracing highest probability sequence into charBuf and returns
|
||||||
|
// probability of generating the word.
|
||||||
|
float ProximityInfoState::getHighestProbabilitySequence(uint16_t *const charBuf) const {
|
||||||
|
int buf[mInputSize];
|
||||||
|
// Maximum probabilities of each point are multiplied to 100%.
|
||||||
|
float probability = 1.0f;
|
||||||
|
// TODO: Current implementation is greedy algorithm. DP would be efficient for many cases.
|
||||||
|
for (int i = 0; i < mInputSize; ++i) {
|
||||||
|
float maxProbability = 0.0f;
|
||||||
|
for (hash_map_compat<int, float>::const_iterator it = mCharProbabilities[i].begin();
|
||||||
|
it != mCharProbabilities[i].end(); ++it) {
|
||||||
|
if (it->second > maxProbability) {
|
||||||
|
maxProbability = it->second;
|
||||||
|
buf[i] = it->first;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
probability *= maxProbability;
|
||||||
|
}
|
||||||
|
int index = 0;
|
||||||
|
for (int i = 0; i < mInputSize && index < MAX_WORD_LENGTH_INTERNAL - 1; ++i) {
|
||||||
|
if (buf[i] != NOT_AN_INDEX) {
|
||||||
|
charBuf[index] = mProximityInfo->getCodePointOf(buf[i]);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
charBuf[index] = '\0';
|
||||||
|
return probability;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace latinime
|
} // namespace latinime
|
||||||
|
|
|
@ -55,8 +55,8 @@ class ProximityInfoState {
|
||||||
mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0), mLocaleStr(),
|
mHasTouchPositionCorrectionData(false), mMostCommonKeyWidthSquare(0), mLocaleStr(),
|
||||||
mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0),
|
mKeyCount(0), mCellHeight(0), mCellWidth(0), mGridHeight(0), mGridWidth(0),
|
||||||
mIsContinuationPossible(false), mInputXs(), mInputYs(), mTimes(), mInputIndice(),
|
mIsContinuationPossible(false), mInputXs(), mInputYs(), mTimes(), mInputIndice(),
|
||||||
mDistanceCache(), mLengthCache(), mRelativeSpeeds(), mNearKeysVector(),
|
mDistanceCache(), mLengthCache(), mRelativeSpeeds(), mCharProbabilities(),
|
||||||
mTouchPositionCorrectionEnabled(false), mInputSize(0) {
|
mNearKeysVector(), mTouchPositionCorrectionEnabled(false), mInputSize(0) {
|
||||||
memset(mInputCodes, 0, sizeof(mInputCodes));
|
memset(mInputCodes, 0, sizeof(mInputCodes));
|
||||||
memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances));
|
memset(mNormalizedSquaredDistances, 0, sizeof(mNormalizedSquaredDistances));
|
||||||
memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
|
memset(mPrimaryInputWord, 0, sizeof(mPrimaryInputWord));
|
||||||
|
@ -213,7 +213,9 @@ class ProximityInfoState {
|
||||||
return mIsContinuationPossible;
|
return mIsContinuationPossible;
|
||||||
}
|
}
|
||||||
|
|
||||||
float getPointToKeyLength(const int inputIndex, const int charCode, const float scale) const;
|
float getPointToKeyLength(const int inputIndex, const int charCode) const;
|
||||||
|
|
||||||
|
float getPointToKeyByIdLength(const int inputIndex, const int keyId) const;
|
||||||
|
|
||||||
int getSpaceY() const;
|
int getSpaceY() const;
|
||||||
|
|
||||||
|
@ -223,6 +225,12 @@ class ProximityInfoState {
|
||||||
float getRelativeSpeed(const int index) const {
|
float getRelativeSpeed(const int index) const {
|
||||||
return mRelativeSpeeds[index];
|
return mRelativeSpeeds[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float getPointAngle(const int index) const;
|
||||||
|
// Returns angle of three points. x, y, and z are indices.
|
||||||
|
float getPointsAngle(const int index0, const int index1, const int index2) const;
|
||||||
|
|
||||||
|
float getHighestProbabilitySequence(uint16_t *const charBuf) const;
|
||||||
private:
|
private:
|
||||||
DISALLOW_COPY_AND_ASSIGN(ProximityInfoState);
|
DISALLOW_COPY_AND_ASSIGN(ProximityInfoState);
|
||||||
typedef hash_map_compat<int, float> NearKeysDistanceMap;
|
typedef hash_map_compat<int, float> NearKeysDistanceMap;
|
||||||
|
@ -265,6 +273,8 @@ class ProximityInfoState {
|
||||||
bool checkAndReturnIsContinuationPossible(const int inputSize, const int *const xCoordinates,
|
bool checkAndReturnIsContinuationPossible(const int inputSize, const int *const xCoordinates,
|
||||||
const int *const yCoordinates, const int *const times);
|
const int *const yCoordinates, const int *const times);
|
||||||
void popInputData();
|
void popInputData();
|
||||||
|
void updateAlignPointProbabilities();
|
||||||
|
bool suppressCharProbabilities(const int index1, const int index2);
|
||||||
|
|
||||||
// const
|
// const
|
||||||
const ProximityInfo *mProximityInfo;
|
const ProximityInfo *mProximityInfo;
|
||||||
|
@ -286,6 +296,8 @@ class ProximityInfoState {
|
||||||
std::vector<float> mDistanceCache;
|
std::vector<float> mDistanceCache;
|
||||||
std::vector<int> mLengthCache;
|
std::vector<int> mLengthCache;
|
||||||
std::vector<float> mRelativeSpeeds;
|
std::vector<float> mRelativeSpeeds;
|
||||||
|
// probabilities of skipping or mapping to a key for each point.
|
||||||
|
std::vector<hash_map_compat<int, float> > mCharProbabilities;
|
||||||
std::vector<NearKeycodesSet> mNearKeysVector;
|
std::vector<NearKeycodesSet> mNearKeysVector;
|
||||||
bool mTouchPositionCorrectionEnabled;
|
bool mTouchPositionCorrectionEnabled;
|
||||||
int32_t mInputCodes[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL];
|
int32_t mInputCodes[MAX_PROXIMITY_CHARS_SIZE_INTERNAL * MAX_WORD_LENGTH_INTERNAL];
|
||||||
|
|
Loading…
Reference in New Issue