HI Mario,

This is great! finally a sensible solution to the non-linear calibration.

Sometime ago I wrote a patch that does the nonlinear calibration via a
huge 2d calibration grid:

https://github.com/rainwoodman/xf86-input-wacom/tree/gridcal

The grid was an ugly huge XAtom. A parametrized approach, like your
polynomials make much more sense -- just a few bytes to
send around.

> To fix it I added an opposite distortion with a polynomial function. Each
> polynomial depend on 4 parameters. There is 4 polynomials, one per border.
> - I didn't manage to make floating point parameters

> - I added some functions to solve a linear system but I'm sure that
> including a mathematical library should be better.

Pulling in a giant mathematical library (e.g. BLAS) into a driver is
probably not a good idea.
Since solve_lu is called only when the property is set (and the
precision doesn't matter much in this context),
I think a simple function like what you have is sufficient.

> - I'm afraid that my calculus with variables of type double slows the
> execution. But I didn't manage to measure it.
>

I suspect there are already quite a few floating point operations in
the linear driver, and usually user apps add even more
floating point operations after the event is dispatched. Thus my guess
is a few more floating point operations will not make a
 noticeable difference.

> For example on my laptop I use the following parameters :
>> xinput set-prop "Wacom ISDv4 EC Pen stylus" --type=int "Wacom Tablet
>> Border Distortion Top Y" 100 0 55 68
>> xinput set-prop "Wacom ISDv4 EC Pen stylus" --type=int "Wacom Tablet
>> Border Distortion Top X" 80 0 20 26

How did you find these numbers?

Did you also write a calibration tool? Otherwise, it may be possible
to modify the calibration tool I wrote for the grid
to write out the best fit parameters for your polynomials. (at
https://github.com/rainwoodman/xf86-input-wacom/blob/gridcal/tools/calibrate.py
)


Thanks,

Yu
>
> Yours faithfully,
> Mario Geiger
>
> From efa7dbb3a0bd54e3b58fba9541d54669c1224d5e Mon Sep 17 00:00:00 2001
> From: Mario Geiger <geiger.ma...@gmail.com>
> Date: Mon, 24 Aug 2015 23:29:35 +0200
> Subject: [PATCH] distortion
>
> ---
>  include/wacom-properties.h |   6 ++
>  src/wcmCommon.c            |  52 ++++++++--
>  src/wcmXCommand.c          | 232
> +++++++++++++++++++++++++++++++++++++++++++++
>  src/xf86WacomDefs.h        |  10 ++
>  4 files changed, 293 insertions(+), 7 deletions(-)
>
> diff --git a/include/wacom-properties.h b/include/wacom-properties.h
> index b845083..e25a944 100644
> --- a/include/wacom-properties.h
> +++ b/include/wacom-properties.h
> @@ -27,6 +27,12 @@
>  /* 32 bit, 4 values, top x, top y, bottom x, bottom y */
>  #define WACOM_PROP_TABLET_AREA "Wacom Tablet Area"
>
> +/* 32 bit, 4 values, border width, border offset, point physical, point
> logical */
> +#define WACOM_PROP_TABLET_DISTORTION_TOP_X "Wacom Tablet Border Distortion
> Top X"
> +#define WACOM_PROP_TABLET_DISTORTION_TOP_Y "Wacom Tablet Border Distortion
> Top Y"
> +#define WACOM_PROP_TABLET_DISTORTION_BOTTOM_X "Wacom Tablet Border
> Distortion Bottom X"
> +#define WACOM_PROP_TABLET_DISTORTION_BOTTOM_Y "Wacom Tablet Border
> Distortion Bottom Y"
> +
>  /* 8 bit, 1 value, [0 - 3] (NONE, CW, CCW, HALF) */
>  #define WACOM_PROP_ROTATION "Wacom Rotation"
>
> diff --git a/src/wcmCommon.c b/src/wcmCommon.c
> index 9408f42..00c5dc5 100644
> --- a/src/wcmCommon.c
> +++ b/src/wcmCommon.c
> @@ -438,6 +438,21 @@ static void sendCommonEvents(InputInfoPtr pInfo, const
> WacomDeviceState* ds,
>   sendWheelStripEvents(pInfo, ds, first_val, num_vals, valuators);
>  }
>
> +static double distortionCorrectionInBorders(double coord, double border,
> double* polynomial)
> +{
> + if (coord < border) {
> + double x = coord;
> + int i;
> +
> + coord = 0.0;
> + for (i = 0; i < 5; ++i) {
> + coord *= x;
> + coord += polynomial[i];
> + }
> + }
> + return coord;
> +}
> +
>  /* rotate x and y before post X inout events */
>  void wcmRotateAndScaleCoordinates(InputInfoPtr pInfo, int* x, int* y)
>  {
> @@ -446,19 +461,42 @@ void wcmRotateAndScaleCoordinates(InputInfoPtr pInfo,
> int* x, int* y)
>   DeviceIntPtr dev = pInfo->dev;
>   AxisInfoPtr axis_x, axis_y;
>   int tmp_coord;
> + double dcoord;
>
>   /* scale into on topX/topY area */
>   axis_x = &dev->valuator->axes[0];
>   axis_y = &dev->valuator->axes[1];
>
>   /* Don't try to scale relative axes */
> - if (axis_x->max_value > axis_x->min_value)
> - *x = xf86ScaleAxis(*x, axis_x->max_value, axis_x->min_value,
> -   priv->bottomX, priv->topX);
> -
> - if (axis_y->max_value > axis_y->min_value)
> - *y = xf86ScaleAxis(*y, axis_y->max_value, axis_y->min_value,
> -   priv->bottomY, priv->topY);
> + if (axis_x->max_value > axis_x->min_value) {
> + dcoord = (*x - priv->topX) / (double)(priv->bottomX - priv->topX);
> + dcoord = distortionCorrectionInBorders(dcoord,
> priv->distortion_topX_border, priv->distortion_topX_poly);
> + dcoord = 1.0 - dcoord;
> + dcoord = distortionCorrectionInBorders(dcoord,
> priv->distortion_bottomX_border, priv->distortion_bottomX_poly);
> + dcoord = 1.0 - dcoord;
> +
> + //*x = dcoord * (priv->bottomX - priv->topX) + priv->topX;
> + //*x = xf86ScaleAxis(*x, axis_x->max_value, axis_x->min_value,
> + //   priv->bottomX, priv->topX);
> +
> + *x = round(dcoord * (axis_x->max_value - axis_x->min_value) +
> axis_x->min_value);
> + if (*x < axis_x->min_value) *x = axis_x->min_value;
> + if (*x > axis_x->max_value) *x = axis_x->max_value;
> + }
> +
> + if (axis_y->max_value > axis_y->min_value) {
> + dcoord = (*y - priv->topY) / (double)(priv->bottomY - priv->topY);
> + dcoord = distortionCorrectionInBorders(dcoord,
> priv->distortion_topY_border, priv->distortion_topY_poly);
> + dcoord = 1.0 - dcoord;
> + dcoord = distortionCorrectionInBorders(dcoord,
> priv->distortion_bottomY_border, priv->distortion_bottomY_poly);
> + dcoord = 1.0 - dcoord;
> + //*y = dcoord * (priv->bottomY - priv->topY) + priv->topY;
> + //*y = xf86ScaleAxis(*y, axis_y->max_value, axis_y->min_value,
> + //   priv->bottomY, priv->topY);
> + *y = round(dcoord * (axis_y->max_value - axis_y->min_value) +
> axis_y->min_value);
> + if (*y < axis_y->min_value) *y = axis_y->min_value;
> + if (*y > axis_y->max_value) *y = axis_y->max_value;
> + }
>
>   /* coordinates are now in the axis rage we advertise for the device */
>
> diff --git a/src/wcmXCommand.c b/src/wcmXCommand.c
> index 346ff61..2a620f0 100644
> --- a/src/wcmXCommand.c
> +++ b/src/wcmXCommand.c
> @@ -34,6 +34,8 @@
>  #endif
>
>  static void wcmBindToSerial(InputInfoPtr pInfo, unsigned int serial);
> +static int distortionCorrectionComputePolynomial(double p, double a, double
> h, double d, double* poly);
> +
>
>
> /*****************************************************************************
>  * wcmDevSwitchModeCall --
> @@ -82,6 +84,10 @@ int wcmDevSwitchMode(ClientPtr client, DeviceIntPtr dev,
> int mode)
>  static Atom prop_devnode;
>  static Atom prop_rotation;
>  static Atom prop_tablet_area;
> +static Atom prop_tablet_distortion_topX;
> +static Atom prop_tablet_distortion_topY;
> +static Atom prop_tablet_distortion_bottomX;
> +static Atom prop_tablet_distortion_bottomY;
>  static Atom prop_pressurecurve;
>  static Atom prop_serials;
>  static Atom prop_serial_binding;
> @@ -227,6 +233,18 @@ void InitWcmDeviceProperties(InputInfoPtr pInfo)
>   prop_tablet_area = InitWcmAtom(pInfo->dev, WACOM_PROP_TABLET_AREA,
> XA_INTEGER, 32, 4, values);
>   }
>
> + if (!IsPad(priv)) {
> + /* border_width border_offset point_physical point_logical */
> + values[0] = 0;
> + values[1] = 0;
> + values[2] = 0;
> + values[3] = 0;
> + prop_tablet_distortion_topX = InitWcmAtom(pInfo->dev,
> WACOM_PROP_TABLET_DISTORTION_TOP_X, XA_INTEGER, 32, 4, values);
> + prop_tablet_distortion_topY = InitWcmAtom(pInfo->dev,
> WACOM_PROP_TABLET_DISTORTION_TOP_Y, XA_INTEGER, 32, 4, values);
> + prop_tablet_distortion_bottomX = InitWcmAtom(pInfo->dev,
> WACOM_PROP_TABLET_DISTORTION_BOTTOM_X, XA_INTEGER, 32, 4, values);
> + prop_tablet_distortion_bottomY = InitWcmAtom(pInfo->dev,
> WACOM_PROP_TABLET_DISTORTION_BOTTOM_Y, XA_INTEGER, 32, 4, values);
> + }
> +
>   values[0] = common->wcmRotate;
>   if (!IsPad(priv)) {
>   prop_rotation = InitWcmAtom(pInfo->dev, WACOM_PROP_ROTATION, XA_INTEGER,
> 8, 1, values);
> @@ -717,6 +735,66 @@ int wcmSetProperty(DeviceIntPtr dev, Atom property,
> XIPropertyValuePtr prop,
>   priv->bottomX = values[2];
>   priv->bottomY = values[3];
>   }
> + } else if (property == prop_tablet_distortion_topX)
> + {
> + INT32 *values = (INT32*)prop->data;
> +
> + if (prop->size != 4 || prop->format != 32)
> + return BadValue;
> +
> + if (!checkonly)
> + {
> + /* border_width border_offset point_physical point_logical */
> + priv->distortion_topX_border = values[0]/1000.0;
> + distortionCorrectionComputePolynomial(
> + values[1]/1000.0, values[2]/1000.0, values[3]/1000.0, values[0]/1000.0,
> + priv->distortion_topX_poly);
> + }
> + } else if (property == prop_tablet_distortion_topY)
> + {
> + INT32 *values = (INT32*)prop->data;
> +
> + if (prop->size != 4 || prop->format != 32)
> + return BadValue;
> +
> + if (!checkonly)
> + {
> + /* border_width border_offset point_physical point_logical */
> + priv->distortion_topY_border = values[0]/1000.0;
> + distortionCorrectionComputePolynomial(
> + values[1]/1000.0, values[2]/1000.0, values[3]/1000.0, values[0]/1000.0,
> + priv->distortion_topY_poly);
> + }
> + } else if (property == prop_tablet_distortion_bottomX)
> + {
> + INT32 *values = (INT32*)prop->data;
> +
> + if (prop->size != 4 || prop->format != 32)
> + return BadValue;
> +
> + if (!checkonly)
> + {
> + /* border_width border_offset point_physical point_logical */
> + priv->distortion_bottomX_border = values[0]/1000.0;
> + distortionCorrectionComputePolynomial(
> + values[1]/1000.0, values[2]/1000.0, values[3]/1000.0, values[0]/1000.0,
> + priv->distortion_bottomX_poly);
> + }
> + } else if (property == prop_tablet_distortion_bottomY)
> + {
> + INT32 *values = (INT32*)prop->data;
> +
> + if (prop->size != 4 || prop->format != 32)
> + return BadValue;
> +
> + if (!checkonly)
> + {
> + /* border_width border_offset point_physical point_logical */
> + priv->distortion_bottomY_border = values[0]/1000.0;
> + distortionCorrectionComputePolynomial(
> + values[1]/1000.0, values[2]/1000.0, values[3]/1000.0, values[0]/1000.0,
> + priv->distortion_bottomY_poly);
> + }
>   } else if (property == prop_pressurecurve)
>   {
>   INT32 *pcurve;
> @@ -1073,4 +1151,158 @@ wcmBindToSerial(InputInfoPtr pInfo, unsigned int
> serial)
>
>  }
>
> +/* P A = L U
> + * P : permutation matrix
> + * L : lower matrix
> + * U : upper matrix
> +*/
> +static void
> +lu_decomposition(int n, const double* A, double* P, double* L, double* U)
> +{
> + int k,i,r;
> + double m;
> +
> + // P,L = identity
> + for (i = 0; i < n*n; ++i) P[i] = L[i] = 0.0;
> + for (i = 0; i < n*n; i += n+1) P[i] = L[i] = 1.0;
> +
> + // U = A
> + for (i = 0; i < n*n; ++i) U[i] = A[i];
> +
> + for (k = 0; k < n; ++k) {
> + // find maximum in column k
> + r = k;
> + m = U[r*n+k];
> + for (i = k; i < n; ++i) if (U[i*n+k] > m) {
> + r = i;
> + m = U[i*n+k];
> + }
> +
> + if (m > 0.0) {
> + // swap lines r,k
> + if (r != k) {
> + for (i = 0; i < n; ++i) {
> + m = U[r*n+i];
> + U[r*n+i] = U[k*n+i];
> + U[k*n+i] = m;
> + m = P[r*n+i];
> + P[r*n+i] = P[k*n+i];
> + P[k*n+i] = m;
> + }
> + for (i = 0; i < k; ++i) {
> + m = L[r*n+i];
> + L[r*n+i] = L[k*n+i];
> + L[k*n+i] = m;
> + }
> + }
> +
> + // make 0's
> + for (i = k+1; i < n; ++i) {
> + L[i*n+k] = U[i*n+k] / U[k*n+k];
> + for (r = 0; r < n; ++r) U[i*n+r] -= L[i*n+k] * U[k*n+r];
> + }
> + }
> + }
> +}
> +
> +/* LUx = b */
> +static int
> +solve_lu(int n, const double* L, const double* U, const double* b, double*
> x)
> +{
> + int k, i;
> + double s;
> + double* y = (double*)malloc(sizeof(double)*n);
> +
> + // Ly = b
> + for (k = 0; k < n; ++k) {
> + s = 0.0;
> + for (i = 0; i < k; ++i) s += L[k*n+i] * y[i];
> + y[k] = b[k] - s;
> + }
> +
> + // Ux = y
> + for (k = n-1; k >= 0; --k) {
> + s = 0.0;
> + for (i = n-1; i > k; --i) s += U[k*n+i] * x[i];
> + if (U[k*n+k] == 0.0) {
> + free(y);
> + return 1;
> + }
> + x[k] = (y[k] - s) / U[k*n+k];
> + }
> + free(y);
> + return 0;
> +}
> +
> +
> +/* Ax = b */
> +static int
> +solve_ls(int n, const double* A, const double* b, double* x)
> +{
> + int i,k;
> + double *P,*L,*U,*Pb;
> + P = (double*)malloc(sizeof(double)*n*n);
> + L = (double*)malloc(sizeof(double)*n*n);
> + U = (double*)malloc(sizeof(double)*n*n);
> + Pb = (double*)malloc(sizeof(double)*n);
> + lu_decomposition(n, A, P, L, U);
> + for (i = 0; i < n; ++i) {
> + Pb[i] = 0;
> + for (k = 0; k < n; ++k) {
> + Pb[i] += P[i*n+k] * b[k];
> + }
> + }
> + i = solve_lu(n, L, U, Pb, x);
> + free(P);
> + free(L);
> + free(U);
> + free(Pb);
> + return i;
> +}
> +
> +static int
> +distortionCorrectionComputePolynomial(double p, double a, double h, double
> d, double* poly)
> +{
> + /* p = offset at the border
> + * (a,h) = dirtortion point
> + * d = width of the border zone
> + *
> + * poly = {c4, c3... c0}
> + */
> +
> + /* F(x) = c4 x^4 + c3 x^3 + c2 x^2 + c1 x + c0
> + *
> + * Constraints => Matrix
> + * F(0) = p    => 0    0    0   0  1  : p
> + * F(d) = d    => d^4  d^3  d^2 d  1  : d
> + * F(a) = h    => a^4  a^3  a^2 a  1  : h
> + * F'(d) = 1   => 4d^3 3d^2 2d  1  0  : 1
> + * F'(a) = 1   => 4a^3 3a^2 2a  1  0  : 1
> + */
> + int r;
> +
> + const double Matrix[25] = {
> + 0, 0, 0, 0, 1,
> + d*d*d*d, d*d*d, d*d, d, 1,
> + a*a*a*a, a*a*a, a*a, a, 1,
> + 4*d*d*d, 3*d*d, 2*d, 1, 0,
> + 4*a*a*a, 3*a*a, 2*a, 1, 0
> + };
> + const double rhs[5] = {
> + p,
> + d,
> + h,
> + 1,
> + 1
> + };
> +
> + r = solve_ls(5, Matrix, rhs, poly);
> + if (r != 0) {
> + // set to F(x) = x
> + poly[0] = poly[1] = poly[2] = poly[4] = 0.0;
> + poly[3] = 1.0;
> + }
> + return r;
> +}
> +
>  /* vim: set noexpandtab tabstop=8 shiftwidth=8: */
> diff --git a/src/xf86WacomDefs.h b/src/xf86WacomDefs.h
> index 1575960..45679ec 100644
> --- a/src/xf86WacomDefs.h
> +++ b/src/xf86WacomDefs.h
> @@ -262,6 +262,16 @@ struct _WacomDeviceRec
>   unsigned int cur_serial; /* current serial in prox */
>   int cur_device_id; /* current device ID in prox */
>
> + /* distortion */
> + double distortion_topX_border;
> + double distortion_topY_border;
> + double distortion_bottomX_border;
> + double distortion_bottomY_border;
> + double distortion_topX_poly[5];
> + double distortion_topY_poly[5];
> + double distortion_bottomX_poly[5];
> + double distortion_bottomY_poly[5];
> +
>   /* button mapping information
>   *
>   * 'button' variables are indexed by physical button number (0..nbuttons)
> --
> 2.1.4
>
>
> ------------------------------------------------------------------------------
>
> _______________________________________________
> Linuxwacom-devel mailing list
> Linuxwacom-devel@lists.sourceforge.net
> https://lists.sourceforge.net/lists/listinfo/linuxwacom-devel
>

------------------------------------------------------------------------------
_______________________________________________
Linuxwacom-devel mailing list
Linuxwacom-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/linuxwacom-devel

Reply via email to