The goal is to better understand how humans control the velocity of their movements. Guigon 2021 proposed that motor control is based on three principles:
- optimal feedback control
- control with a receding time horizon
- task representation by a series of via-points updated at fixed frequency
With mouseReMoCo
, we want to test whether Guigon's theoretical predictions resist empirical tests:
- replicate the results (in 1D task space) that back-and-forth movements at constant speed are in fact a sequence of 120 ms sub-movements, due to the sliding horizon control mode (Guigon etal 2019).
- test the prediction that the same logic applies in a 2D task space, i.e., in circular steering at constant speed.
First public release
- bug corrections after extensive testing
For private tests before making the repo public. Updates include:
- disable press w on non-circular task
- Update command line -h
- textColor added to CLI
- print configuration in console in all cases
Fully operational version for user testing
- Updated header in output files
- Lines to match with teh beeps in
First version for user testing.
- back and forth at constant speed
- circular steering at constant speed
- new CLI keywords:
- task
- halfPeriod
- screen to mm
- tablet to mm
- tablet to screen
- display of calibration on screen (at start)
- new CLI keywords:
- screenDiagonal
- tabletSize
- circlePerimeter_mm
- interLineDistance_mm
- lineHeight_mm
LSL-Mouse is a 2019 project over which mouseReMoCo was build.
class PerformanceAtTask
computes the effective performance at each new sample
The logic is as follows:
- count starts when the move has reached the path
- count a move from point A to point B if both A and B are inside/outside
- count for all record periods : effective performance is the sum of all periods (except the moves to reach the path)
- count a move from A to B in two metrics:
- Euclidean distance (pixel) : the classical distance from A to B.
- For a same angular distance, a move that is farther from the circle centre will produce a longer path
- Angular distance (radian): the phase angle from A to B
- For a same Euclidean distance, a move that is farther from the circle centre will produce a smaller angle
- Euclidean distance (pixel) : the classical distance from A to B.
In the computations, the effective performance is updated at each new move, following the logic :
- we want the standard deviation of the distribution of the radius of the trajectory
- we compute the sum of squares (and mean) incrementally (see formula)
- we use the sum of square to get the standard deviation
- we use the standard deviation to get the effective tolerance
This is done within setEffectivePerformance
:
private void setEffectivePerformance (double radius, int X, int Y ) {
double currentMean = radius;
double currentSumS = 0 ;
int currentNbSample = nbSample + 1;
// http://datagenetics.com/blog/november22017/index.html
currentMean = radiusMean + (radius - radiusMean) / currentNbSample ;
currentSumS = radiusSumS + (radius - radiusMean) * (radius - currentMean);
radiusMean = currentMean;
radiusSumS = currentSumS;
radiusStd = stdev(currentSumS, currentNbSample);
effectiveTolerance = Math.sqrt(2 * Math.PI * Math.E) * radiusStd;
nbSample = currentNbSample;
}
One tricky part is to properly compute the angular metrics... My solution is to :
- shift (translate) X,Y to center of circle
- compute angle from horizontal using
atan
- get the unsigned difference in angle (do not care if moving clockwise or not)
- take care of 'jumps' around 0 with atan...
private double phaseAngleDistance(int cX, int cY, int pX, int pY) {
// translate current XY to center of circle
int Xc = configuration.getCenterX() - cX;
int Yc = configuration.getCenterY() - cY;
// translate previous XY to center of circle
int Xp = configuration.getCenterX() - pX;
int Yp = configuration.getCenterY() - pY;
// get angles from horizontal
double Ac = Math.atan2(Yc, Xc);
double Ap = Math.atan2(Yp, Xp);
// get the unsigned difference in angle (do not care if moving clockwise or not)
double angle = Math.abs(Ac - Ap);
// cancel angle jumps around horizontal
if (angle > Math.PI) {
angle = 2 * Math.PI - angle;
}
return angle ;
}
- 2020 Oct 30 :
- type 'w' to display the effective target width
- Phase angle is now computed and displayed
- 2020 Nov 04 :
- Effective tolerance is displayed by trial
- Central target during rest is suppressed
- Bug corrections:
- Effective tolerance is now computed with cursor radius
- 2020 Nov 08 :
- Added a histogram of radius by trial
- Refactoring:
- Added
isWithPauseTarget
in configuration - Added radius limits without cursor size in configuration
- create
radiusInternalLimit
andradiusExternalLimit
and initialization - rewrite
drawEffectiveTolerance
inDoubleCircle
using radius limits
- create
- Added
- Bug corrections:
- do not draw effective tolerance if radius was not initialized (
R <= 0
)
- do not draw effective tolerance if radius was not initialized (