/* @file CalibAlgo.java * * @author marco corvi * @date nov 2011 * * @brief TopoDroid DistoX calibration algorithm * -------------------------------------------------------- * Copyright This sowftare is distributed under GPL-3.0 or later * See the file COPYING. * -------------------------------------------------------- * This software is adapted from TopoLinux implementation, * which, in turns, is based on PocketTopo implementation. * -------------------------------------------------------- */ package com.topodroid.DistoX; import java.lang.Math; import java.util.Locale; // used by logCoeff import android.util.Log; public class CalibAlgo { protected Matrix aG = null; protected Matrix aM = null; protected Vector bG = null; protected Vector bM = null; protected Vector[] g = null; protected Vector[] m = null; protected long[] group = null; protected float[] err = null; // errors of the data [radians] protected int idx; protected int num; protected boolean mNonLinear; protected Vector nL; protected float b0=0.0f, c0=0.0f; // bearing and clino protected float mDelta; // average data error [degrees] protected float mDelta2; // std-dev data error [degrees] protected float mMaxError = 0.0f; // max error [degrees] // ============================================================== static protected Vector scaledVector( Vector v ) { return scaledVector( v.x, v.y, v.z ); } static protected Vector scaledVector( float x, float y, float z ) { return new Vector( x/TopoDroidUtil.FV, y/TopoDroidUtil.FV, z/TopoDroidUtil.FV ); } void EnforceMax2( Vector b, Matrix a ) { double max = Math.abs( b.x ); double m; m = Math.abs( b.y ); if ( m > max ) max = m; m = Math.abs( b.z ); if ( m > max ) max = m; m = Math.abs( a.x.x ); if ( m > max ) max = m; m = Math.abs( a.x.y ); if ( m > max ) max = m; m = Math.abs( a.x.z ); if ( m > max ) max = m; m = Math.abs( a.y.x ); if ( m > max ) max = m; m = Math.abs( a.y.y ); if ( m > max ) max = m; m = Math.abs( a.y.z ); if ( m > max ) max = m; m = Math.abs( a.z.x ); if ( m > max ) max = m; m = Math.abs( a.z.y ); if ( m > max ) max = m; m = Math.abs( a.z.z ); if ( m > max ) max = m; if ( max >= 2.0 ) { float m1 = (float)(1.9999/max); TDLog.Log( TDLog.LOG_CALIB, "EnforceMax2 scale by " + m1 ); b.x *= m1; b.y *= m1; b.z *= m1; a.x.x *= m1; a.x.y *= m1; a.x.z *= m1; a.y.x *= m1; a.y.y *= m1; a.y.z *= m1; a.z.x *= m1; a.z.y *= m1; a.z.z *= m1; } } /** construct a CalibAlgo from the saved coefficients */ CalibAlgo( byte[] coeff, boolean nonLinear ) { mNonLinear = nonLinear; bG = new Vector(); bM = new Vector(); aG = new Matrix(); aM = new Matrix(); nL = new Vector(); coeffToG( coeff, bG, aG ); coeffToM( coeff, bM, aM ); coeffToNL( coeff, nL ); } // void dump() // { // Log.v("DistoX", String.format("G %8.4f %8.4f %8.4f", bG.x, bG.y, bG.z ) ); // Log.v("DistoX", "aG " + aG.x.x + " " + aG.x.y + " " + aG.x.z ); // Log.v("DistoX", " " + aG.y.x + " " + aG.y.y + " " + aG.y.z ); // Log.v("DistoX", " " + aG.z.x + " " + aG.z.y + " " + aG.z.z ); // // Log.v("DistoX", String.format("M %8.4f %8.4f %8.4f", bM.x, bM.y, bM.z ) ); // // Log.v("DistoX", "aM " + aM.x.x + " " + aM.x.y + " " + aM.x.z ); // Log.v("DistoX", " " + aM.y.x + " " + aM.y.y + " " + aM.y.z ); // Log.v("DistoX", " " + aM.z.x + " " + aM.z.y + " " + aM.z.z ); // // Log.v("DistoX", String.format("NL %8.4f %8.4f %8.4f", nL.x, nL.y, nL.z ) ); // } public CalibAlgo( int N, boolean nonLinear ) { num = 0; if ( N > 0 ) Reset( N ); mNonLinear = nonLinear; } // void setAlgorith( boolean nonLinear ) { mNonLinear = nonLinear; } public float Delta() { return mDelta; } public float Delta2() { return mDelta2; } public float Error( int k ) { return err[k]; } public float[] Errors() { return err; } public float MaxError( ) { return mMaxError; } public Matrix GetAG() { return aG; } public Matrix GetAM() { return aM; } public Vector GetBG() { return bG; } public Vector GetBM() { return bM; } public Vector GetNL() { return nL; } // public int nrCoeff() { return mNonLinear ? 52 : 48; } static protected long roundV( float x ) { long v = (long)Math.round(x * TopoDroidUtil.FV); if ( v > TopoDroidUtil.ZERO ) v = TopoDroidUtil.NEG - v; return v; } static protected long roundM( float x ) { long v = (long)Math.round(x * TopoDroidUtil.FM); if ( v > TopoDroidUtil.ZERO ) v = TopoDroidUtil.NEG - v; return v; } public byte[] GetCoeff() { if ( aG == null ) return null; byte[] coeff = new byte[ 52 ]; // FIXME nrCoeff() long v; v = roundV( bG.x ); coeff[ 0] = (byte)(v & 0xff); coeff[ 1] = (byte)((v>>8) & 0xff); v = roundM( aG.x.x ); coeff[ 2] = (byte)(v & 0xff); coeff[ 3] = (byte)((v>>8) & 0xff); v = roundM( aG.x.y ); coeff[ 4] = (byte)(v & 0xff); coeff[ 5] = (byte)((v>>8) & 0xff); v = roundM( aG.x.z ); coeff[ 6] = (byte)(v & 0xff); coeff[ 7] = (byte)((v>>8) & 0xff); v = roundV( bG.y ); coeff[ 8] = (byte)(v & 0xff); coeff[ 9] = (byte)((v>>8) & 0xff); v = roundM( aG.y.x ); coeff[10] = (byte)(v & 0xff); coeff[11] = (byte)((v>>8) & 0xff); v = roundM( aG.y.y ); coeff[12] = (byte)(v & 0xff); coeff[13] = (byte)((v>>8) & 0xff); v = roundM( aG.y.z ); coeff[14] = (byte)(v & 0xff); coeff[15] = (byte)((v>>8) & 0xff); v = roundV( bG.z ); coeff[16] = (byte)(v & 0xff); coeff[17] = (byte)((v>>8) & 0xff); v = roundM( aG.z.x ); coeff[18] = (byte)(v & 0xff); coeff[19] = (byte)((v>>8) & 0xff); v = roundM( aG.z.y ); coeff[20] = (byte)(v & 0xff); coeff[21] = (byte)((v>>8) & 0xff); v = roundM( aG.z.z ); coeff[22] = (byte)(v & 0xff); coeff[23] = (byte)((v>>8) & 0xff); v = roundV(bM.x ); coeff[24] = (byte)(v & 0xff); coeff[25] = (byte)((v>>8) & 0xff); v = roundM( aM.x.x ); coeff[26] = (byte)(v & 0xff); coeff[27] = (byte)((v>>8) & 0xff); v = roundM( aM.x.y ); coeff[28] = (byte)(v & 0xff); coeff[29] = (byte)((v>>8) & 0xff); v = roundM( aM.x.z ); coeff[30] = (byte)(v & 0xff); coeff[31] = (byte)((v>>8) & 0xff); v = roundV( bM.y ); coeff[32] = (byte)(v & 0xff); coeff[33] = (byte)((v>>8) & 0xff); v = roundM( aM.y.x ); coeff[34] = (byte)(v & 0xff); coeff[35] = (byte)((v>>8) & 0xff); v = roundM( aM.y.y ); coeff[36] = (byte)(v & 0xff); coeff[37] = (byte)((v>>8) & 0xff); v = roundM( aM.y.z ); coeff[38] = (byte)(v & 0xff); coeff[39] = (byte)((v>>8) & 0xff); v = roundV( bM.z ); coeff[40] = (byte)(v & 0xff); coeff[41] = (byte)((v>>8) & 0xff); v = roundM( aM.z.x ); coeff[42] = (byte)(v & 0xff); coeff[43] = (byte)((v>>8) & 0xff); v = roundM( aM.z.y ); coeff[44] = (byte)(v & 0xff); coeff[45] = (byte)((v>>8) & 0xff); v = roundM( aM.z.z ); coeff[46] = (byte)(v & 0xff); coeff[47] = (byte)((v>>8) & 0xff); if ( mNonLinear ) { coeff[48] = floatToByteNL( nL.x ); coeff[49] = floatToByteNL( nL.y ); coeff[50] = floatToByteNL( nL.z ); // Log.v("DistoX", "NL to coeff " + coeff[48] + " " + coeff[49] + " " + coeff[50] + " " + nL.x + " " + nL.y + " " + nL.z ); } else { coeff[48] = (byte)( 0xff ); coeff[49] = (byte)( 0xff ); coeff[50] = (byte)( 0xff ); } coeff[51] = (byte)( 0xff ); return coeff; } // N.B. the string will have the length of the coeff array static String coeffToString( byte[] coeff ) { int kk = (coeff == null)? 0 : coeff.length; StringBuilder cs = new StringBuilder( kk ); for ( int k=0; k<kk; ++k ) { cs.insert(k, (char)(coeff[k]) ); } // Log.v( "DistoX", "coeff to string " + coeff[48] + " " + coeff[49] + " " + coeff[50] + " " + coeff[51] ); return cs.toString(); } // static void logCoeff( byte[] coeff ) // DEBUG METHOD // { // Log.v( "DistoX", coeff[ 0] + " " + coeff[ 1] + " " + coeff[ 2] + " " + coeff[ 3] + " " + coeff[ 4] + " " + coeff[ 5] ); // Log.v( "DistoX", coeff[ 6] + " " + coeff[ 7] + " " + coeff[ 8] + " " + coeff[ 9] + " " + coeff[10] + " " + coeff[11] ); // Log.v( "DistoX", coeff[12] + " " + coeff[13] + " " + coeff[14] + " " + coeff[15] + " " + coeff[16] + " " + coeff[17] ); // Log.v( "DistoX", coeff[18] + " " + coeff[19] + " " + coeff[20] + " " + coeff[15] + " " + coeff[16] + " " + coeff[17] ); // Log.v( "DistoX", coeff[24] + " " + coeff[25] + " " + coeff[26] + " " + coeff[27] + " " + coeff[28] + " " + coeff[29] ); // Log.v( "DistoX", coeff[30] + " " + coeff[31] + " " + coeff[32] + " " + coeff[33] + " " + coeff[34] + " " + coeff[35] ); // Log.v( "DistoX", coeff[36] + " " + coeff[37] + " " + coeff[38] + " " + coeff[39] + " " + coeff[40] + " " + coeff[41] ); // Log.v( "DistoX", coeff[42] + " " + coeff[43] + " " + coeff[44] + " " + coeff[45] + " " + coeff[46] + " " + coeff[47] ); // Log.v( "DistoX", coeff[48] + " " + coeff[49] + " " + coeff[50] + " " + coeff[51] ); // } static byte[] stringToCoeff( String cs ) { byte[] coeff = new byte[ 52 ]; // N.B. return 52 calib coeff if ( cs == null ) { for ( int k=0; k<52; ++k ) coeff[k] = (byte)(0); } else { int kk = cs.length(); coeff[48] = (byte)( 0xff ); // default values if the string is only 48 bytes coeff[49] = (byte)( 0xff ); coeff[50] = (byte)( 0xff ); coeff[51] = (byte)( 0xff ); for ( int k=0; k<kk; ++k ) coeff[k] = (byte)( cs.charAt(k) ); // Log.v( "DistoX", "string to coeff " + coeff[48] + " " + coeff[49] + " " + coeff[50] + " " + coeff[51] ); } return coeff; } private static void coeffToBA( byte[] coeff, Vector b, Matrix a, int off ) { long v; long c0 = (int)(coeff[off+ 0]); if ( c0 < 0 ) c0 = 256+c0; long c1 = (int)(coeff[off+ 1]); if ( c1 < 0 ) c1 = 256+c1; v = c0 + (c1<<8 ); if ( v > TopoDroidUtil.ZERO ) v = v - TopoDroidUtil.NEG; b.x = v / TopoDroidUtil.FV; c0 = (int)(coeff[off+ 2]); if ( c0 < 0 ) c0 = 256+c0; c1 = (int)(coeff[off+ 3]); if ( c1 < 0 ) c1 = 256+c1; v = c0 + (c1<<8 ); if ( v > TopoDroidUtil.ZERO ) v = v - TopoDroidUtil.NEG; a.x.x = v / TopoDroidUtil.FM; c0 = (int)(coeff[off+ 4]); if ( c0 < 0 ) c0 = 256+c0; c1 = (int)(coeff[off+ 5]); if ( c1 < 0 ) c1 = 256+c1; v = c0 + (c1<<8 ); if ( v > TopoDroidUtil.ZERO ) v = v - TopoDroidUtil.NEG; a.x.y = v / TopoDroidUtil.FM; c0 = (int)(coeff[off+ 6]); if ( c0 < 0 ) c0 = 256+c0; c1 = (int)(coeff[off+ 7]); if ( c1 < 0 ) c1 = 256+c1; v = c0 + (c1<<8 ); if ( v > TopoDroidUtil.ZERO ) v = v - TopoDroidUtil.NEG; a.x.z = v / TopoDroidUtil.FM; // BY c0 = (int)(coeff[off+ 8]); if ( c0 < 0 ) c0 = 256+c0; c1 = (int)(coeff[off+ 9]); if ( c1 < 0 ) c1 = 256+c1; v = c0 + (c1<<8 ); if ( v > TopoDroidUtil.ZERO ) v = v - TopoDroidUtil.NEG; b.y = v / TopoDroidUtil.FV; c0 = (int)(coeff[off+10]); if ( c0 < 0 ) c0 = 256+c0; c1 = (int)(coeff[off+11]); if ( c1 < 0 ) c1 = 256+c1; v = c0 + (c1<<8 ); if ( v > TopoDroidUtil.ZERO ) v = v - TopoDroidUtil.NEG; a.y.x = v / TopoDroidUtil.FM; c0 = (int)(coeff[off+12]); if ( c0 < 0 ) c0 = 256+c0; c1 = (int)(coeff[off+13]); if ( c1 < 0 ) c1 = 256+c1; v = c0 + (c1<<8 ); if ( v > TopoDroidUtil.ZERO ) v = v - TopoDroidUtil.NEG; a.y.y = v / TopoDroidUtil.FM; c0 = (int)(coeff[off+14]); if ( c0 < 0 ) c0 = 256+c0; c1 = (int)(coeff[off+15]); if ( c1 < 0 ) c1 = 256+c1; v = c0 + (c1<<8 ); if ( v > TopoDroidUtil.ZERO ) v = v - TopoDroidUtil.NEG; a.y.z = v / TopoDroidUtil.FM; // BZ c0 = (int)(coeff[off+16]); if ( c0 < 0 ) c0 = 256+c0; c1 = (int)(coeff[off+17]); if ( c1 < 0 ) c1 = 256+c1; v = c0 + (c1<<8 ); if ( v > TopoDroidUtil.ZERO ) v = v - TopoDroidUtil.NEG; b.z = v / TopoDroidUtil.FV; c0 = (int)(coeff[off+18]); if ( c0 < 0 ) c0 = 256+c0; c1 = (int)(coeff[off+19]); if ( c1 < 0 ) c1 = 256+c1; v = c0 + (c1<<8 ); if ( v > TopoDroidUtil.ZERO ) v = v - TopoDroidUtil.NEG; a.z.x = v / TopoDroidUtil.FM; c0 = (int)(coeff[off+20]); if ( c0 < 0 ) c0 = 256+c0; c1 = (int)(coeff[off+21]); if ( c1 < 0 ) c1 = 256+c1; v = c0 + (c1<<8 ); if ( v > TopoDroidUtil.ZERO ) v = v - TopoDroidUtil.NEG; a.z.y = v / TopoDroidUtil.FM; c0 = (int)(coeff[off+22]); if ( c0 < 0 ) c0 = 256+c0; c1 = (int)(coeff[off+23]); if ( c1 < 0 ) c1 = 256+c1; v = c0 + (c1<<8 ); if ( v > TopoDroidUtil.ZERO ) v = v - TopoDroidUtil.NEG; a.z.z = v / TopoDroidUtil.FM; } static void coeffToG( byte[] coeff, Vector b, Matrix a ) { coeffToBA( coeff, b, a, 0 ); // Log.v("DistoX", "G " + b.x + " " + b.y + " " + b.z + " " + a.x.x + " " + a.x.y + " " + a.x.z ); } static void coeffToM( byte[] coeff, Vector b, Matrix a ) { coeffToBA( coeff, b, a, 24 ); // Log.v("DistoX", "M " + b.x + " " + b.y + " " + b.z + " " + a.x.x + " " + a.x.y + " " + a.x.z ); } // N.B. map coeff <--> NL // 0 1 // 1 2 // ... // 126 127 // 127 n.a. // 128 -127 // ... // 254 -1 // 0xff 255 0 // static float byteToFloatNL( byte b ) { int c0 = 1 + (int)(b); if ( c0 >= 128 ) c0 = c0 - 256; return c0 / TopoDroidUtil.FN; } static byte floatToByteNL( float x ) { float xf = x * TopoDroidUtil.FN; int v = (int)Math.round( xf ) - 1; if ( v <= 0 ) v = 0x100 + v; return (byte)( v & 0xff ); } // FIXME // protected static byte floatToByteV( float x ) // { // long v = (long)(x * TopoDroidUtil.FV); if ( v > TopoDroidUtil.ZERO ) v = TopoDroidUtil.NEG - v; // return (byte)(v & 0xff); // } // // protected static byte floatToByteM( float x ) // { // long v = (long)(x * TopoDroidUtil.FM); if ( v > TopoDroidUtil.ZERO ) v = TopoDroidUtil.NEG - v; // return (byte)(v & 0xff); // } static void coeffToNL( byte[] coeff, Vector nl ) { if ( coeff != null && coeff.length >= 51 ) { nl.x = byteToFloatNL( coeff[48] ); nl.y = byteToFloatNL( coeff[49] ); nl.z = byteToFloatNL( coeff[50] ); // Log.v("DistoX", "coeff to NL " + coeff[48] + " " + coeff[49] + " " + coeff[50] + " " + nl.x + " " + nl.y + " " + nl.z ); } else { nl.x = 0; nl.y = 0; nl.z = 0; } } public void AddValues( CalibCBlock b ) { // add also group-0 CBlocks to keep CBlock list and calib vectors aligned AddValues( b.gx, b.gy, b.gz, b.mx, b.my, b.mz, b.mGroup ); } public void AddValues( long gx, long gy, long gz, long mx, long my, long mz, long group0 ) { if ( idx >= num ) { return; } // g[idx] = new Vector( gx/TopoDroidUtil.FV, gy/TopoDroidUtil.FV, gz/TopoDroidUtil.FV ); // m[idx] = new Vector( mx/TopoDroidUtil.FV, my/TopoDroidUtil.FV, mz/TopoDroidUtil.FV ); g[idx] = scaledVector( gx, gy, gz ); m[idx] = scaledVector( mx, my, mz ); group[idx] = group0; if ( TDLog.LOG_CALIB ) { TDLog.Log( TDLog.LOG_CALIB, String.format("Add %d G %d %d %d M %d %d %d Grp %d", idx, gx, gy, gz, mx, my, mz, group0 ) ); } idx ++; } public int Size() { return idx; } public void Reset( int N ) { if ( N != num ) { num = N; g = new Vector[N]; m = new Vector[N]; group = new long[N]; err = new float[N]; } idx = 0; aG = null; bG = null; aM = null; bM = null; TDLog.Log( TDLog.LOG_CALIB, "Reset calibration " + N + " data"); } /* ============================================================ */ protected void LogNumber( String msg, int it ) { TDLog.Log( TDLog.LOG_CALIB, msg + " " + it ); } protected void LogMatrixVector( String msg, Matrix m1, Vector v1 ) { if ( ! TDLog.LOG_CALIB ) return; TDLog.Log( TDLog.LOG_CALIB, msg + String.format(Locale.US, " M: %8.4f %8.4f %8.4f V: %8.4f\n %8.4f %8.4f %8.4f %8.4f\n %8.4f %8.4f %8.4f %8.4f", m1.x.x, m1.x.y, m1.x.z, v1.x, m1.y.x, m1.y.y, m1.y.z, v1.y, m1.z.x, m1.z.y, m1.z.z, v1.z ) ); } protected void LogVectors( String msg, long group, Vector v1, Vector v2 ) { if ( ! TDLog.LOG_CALIB ) return; TDLog.Log( TDLog.LOG_CALIB, msg + String.format(Locale.US, " %3d V1 %8.4f %8.4f %8.4f\n V2 %8.4f %8.4f %8.4f", group, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z ) ); } protected void LogSC( String msg, float s, float c ) { if ( ! TDLog.LOG_CALIB ) return; TDLog.Log( TDLog.LOG_CALIB, msg + String.format(Locale.US, " S %8.4f C %8.4f", s, c ) ); } /* ============================================================ */ protected void checkOverflow( Vector v, Matrix m ) { float mv = v.maxAbsValue() * TopoDroidUtil.FV; float mm = m.maxAbsValue() * TopoDroidUtil.FM; if ( mv > mm ) mm = mv; if ( mm > 32768 ) { // 32768 = 256*128 = 1<<15 = 0x010000 mv = 32768 / mm; m.timesEqual( mv ); v.timesEqual( mv ); } } private float saturate( float x ) { int ix = (int)(x * TopoDroidUtil.FN); if ( ix > 127 ) { ix = 127; } else if ( ix < -127 ) { ix = -127; } return ix / TopoDroidUtil.FN; } protected void saturate( Vector nl ) { nl.x = saturate( nl.x ); nl.y = saturate( nl.y ); nl.z = saturate( nl.z ); } protected void computeBearingAndClinoRad( Vector g0, Vector m0 ) { // Vector g = g0.mult( 1.0f / TopoDroidUtil.FV ); // Vector m = m0.mult( 1.0f / TopoDroidUtil.FV ); Vector g = scaledVector( g0 ); Vector m = scaledVector( m0 ); g.normalize(); m.normalize(); Vector e = new Vector( 1.0f, 0.0f, 0.0f ); Vector y = m.cross( g ); Vector x = g.cross( y ); y.normalize(); x.normalize(); float ex = e.dot( x ); float ey = e.dot( y ); float ez = e.dot( g ); b0 = TDMath.atan2( -ey, ex ); c0 = - TDMath.atan2( ez, (float)Math.sqrt(ex*ex+ey*ey) ); // r0 = TDMath.atan2( g.y, g.z ); if ( b0 < 0.0f ) b0 += TDMath.M_2PI; // if ( r0 < 0.0f ) r0 += TDMath.M_2PI; } // ----------------------------------------------------------------------- // ERROR STATS /** compute the unit vector direction of sensor-data (g,m) */ Vector computeDirection( Vector g1, Vector m1 ) { Vector g = scaledVector( g1 ); Vector m = scaledVector( m1 ); Vector gr; Vector mr = m; if ( mNonLinear ) { Matrix gs = new Matrix(); gs.x.x = g.x * g.x - 0.5f; gs.y.y = g.y * g.y - 0.5f; gs.z.z = g.z * g.z - 0.5f; gr = bG.plus( aG.timesV( g.plus( gs.timesV( nL ) ) ) ); } else { gr = bG.plus( aG.timesV( g ) ); } computeBearingAndClinoRad( gr, mr ); return new Vector( b0, c0 ); // return new Vector( (float)Math.cos(c0) * (float)Math.cos(b0), // (float)Math.cos(c0) * (float)Math.sin(b0), // (float)Math.sin(c0) ); } // error acumulators protected int mSumCount; protected double mSumErrors; protected double mSumErrorSquared; int getStatCount() { return mSumCount; } double getStatError() { return mSumErrors; } double getStatError2() { return mSumErrorSquared; } void initErrorStats() { mSumCount = 0; mSumErrors = 0; mSumErrorSquared = 0; } // must be overridden public void addStatErrors( Vector g1[], Vector m1[], float[] errors ) { Log.v("DistoX", "calib algo add error stats"); } // must be overridden public int Calibrate() { return -1; } }