Android Open Source - boogie-board-sync-android-sdk Filtering






From Project

Back to project page boogie-board-sync-android-sdk.

License

The source code is released under:

Copyright ? 2014 Kent Displays, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the...

If you think the Android project boogie-board-sync-android-sdk listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*******************************************************************************
 Copyright  2014 Kent Displays, Inc.//from  w  w  w . j a va 2  s.  co  m

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ******************************************************************************/

package com.improvelectronics.sync.android;

import java.util.ArrayList;
import java.util.List;

public class Filtering {
    public enum PathState {
        NO_POINTS, ONE_POINT, MULTIPLE_POINTS
    }

    // Switch states.
    private static final byte TSW_FLAG = 0x01;
    private static final byte RDY_FLAG = 0x01 << 2;

    // Set distance threshold for drawing a new segment (10*0.01mm = 0.1mm).
    private static final int DISTANCE_THRESHOLD_SQUARED = (10 * 10);

    private static PathState mPathState = PathState.NO_POINTS;
    private static Filter mFilter = new Filter();

    // State of line width filter.
    private static float mOldLineWidth = -1.0f;

    private static SyncCaptureReport mLastCapture;

    public static List<SyncPath> filterSyncCaptureReport(SyncCaptureReport captureReport) {
        float lineWidth;
        int distSquared;
        float velAvg, pressAvg;
        int i;
        List<SyncPath> paths = new ArrayList<SyncPath>();

        // Process based on number of points already received in current trace.
        switch (mPathState) {
            case NO_POINTS:
                if ((captureReport.getFlags() & (RDY_FLAG + TSW_FLAG)) == (RDY_FLAG + TSW_FLAG))  // Contact?
                {
                    // Have first point.
                    mPathState = PathState.ONE_POINT;

                    // Initialize the dynamic filter.
                    setFilterPosition(mFilter, captureReport);

                    // Reset filter for line width.
                    resetLineWidthFilter();
                }
                break;

            case ONE_POINT:
                if ((captureReport.getFlags() & (RDY_FLAG + TSW_FLAG)) == (RDY_FLAG + TSW_FLAG))  // Contact?
                {
                    // Apply filter and get distance**2 of filtered position from last rendered position.
                    distSquared = applyFilter(mFilter, captureReport);

                    // Render new position to PDF if sufficiently far from last rendered position.
                    if (distSquared >= DISTANCE_THRESHOLD_SQUARED) {
                        mPathState = PathState.MULTIPLE_POINTS;

                        // Compute/draw the first segment of the trace to PDF.
                        velAvg = (float) Math.sqrt(distSquared) / mFilter.time;
                        pressAvg = ((float) mFilter.last.pressure + mFilter.current.pressure) / 2;
                        lineWidth = computeLineWidth(velAvg, pressAvg);

                        paths.add(createPathWithLineWidth(lineWidth));

                        // Reset "last" point for filter.
                        setLastFilter(mFilter);
                    }
                } else  // No contact.
                {
                    mPathState = PathState.NO_POINTS;

                    // Draw the dot/period for the single point to PDF.
                    velAvg = -1.0f;
                    pressAvg = mFilter.current.pressure;
                    lineWidth = computeLineWidth(velAvg, pressAvg);

                    paths.add(createPathWithLineWidth(lineWidth));
                }
                break;

            case MULTIPLE_POINTS:
                if ((captureReport.getFlags() & (RDY_FLAG + TSW_FLAG)) == (RDY_FLAG + TSW_FLAG))  // Contact?
                {
                    // Apply filter and get distance**2 of filtered position from last rendered position.
                    distSquared = applyFilter(mFilter, captureReport);

                    // Render new position to PDF if sufficiently far from last rendered position.
                    if (distSquared >= DISTANCE_THRESHOLD_SQUARED) {
                        // Compute/draw the next trace segment to PDF.
                        velAvg = (float)Math.sqrt(distSquared) / mFilter.time;
                        pressAvg = ((float) mFilter.last.pressure + mFilter.current.pressure) / 2;
                        lineWidth = computeLineWidth(velAvg, pressAvg);

                        paths.add(createPathWithLineWidth(lineWidth));

                        // Reset "last" point for filter.
                        setLastFilter(mFilter);
                    }
                } else  // No contact.
                {
                    mPathState = PathState.NO_POINTS;

                    // Will use fixed (current) velocity to compute line width during final convergence
                    // to prevent artificial blobbing at the end of traces (due to artificial slowdown
                    // induced by repeating final digitizer coordinate).
                    velAvg = (float)Math.sqrt(mFilter.velocity.x * mFilter.velocity.x + mFilter.velocity.y * mFilter.velocity.y);

                    // Provide filter final coordinate multiple times to converge on pen up point.
                    for (i = 0; i < 4; i++) {
                        // Apply filter and get distance**2 of filtered position from last rendered position.
                        distSquared = applyFilter(mFilter, mLastCapture);

                        // Render new position to PDF if sufficiently far from last rendered position.
                        if (distSquared >= DISTANCE_THRESHOLD_SQUARED) {
                            // Compute line width.
                            pressAvg = ((float) mFilter.last.pressure + mFilter.current.pressure) / 2;
                            lineWidth = computeLineWidth(velAvg, pressAvg);

                            paths.add(createPathWithLineWidth(lineWidth));

                            // Reset "last" point for filter.
                            setLastFilter(mFilter);
                        }
                    }
                }
                break;
        }

        // Store coordinate for finalizing trace at pen up.
        mLastCapture = captureReport;

        return paths;
    }

    /**
     * Clears line width filter for start of a new trace.
     */
    private static void resetLineWidthFilter() {
        mOldLineWidth = -1.0f;
    }

    /**
     * Initializes a provided dynamic filter with the first point in a trace.
     *
     * @param f
     * @param captureReport
     */
    private static void setFilterPosition(Filter f, SyncCaptureReport captureReport) {
        f.last.x = f.current.x = (int) captureReport.getX();
        f.last.y = f.current.y = (int) captureReport.getY();
        f.last.pressure = f.current.pressure = (int) captureReport.getPressure();
        f.velocity.x = f.velocity.y = f.velocity.pressure = 0;
        f.time = 0;
    }

    /**
     * Notifies a provided dynamic filter that a new segment has been drawn.
     *
     * @param f
     */
    private static void setLastFilter(Filter f) {
        f.last.x = f.current.x;
        f.last.y = f.current.y;
        f.last.pressure = f.current.pressure;
        f.time = 0;
    }

    // Dynamic filter Proportional and Derivative controller gains
    // (includes effects of mass and sample time (K*T/mass)).
    private static final int KPP = 1229;   // 1229/8192 = 0.1500 ~0.15f
    private static final int KDD = 4915;   // 4915/8192 = 0.6000 ~0.6f

    // Updates dynamic filter state based on new reference coordinate.
    private static int applyFilter(Filter f, SyncCaptureReport captureReport) {
        int ax, ay, ap;
        int dist_sq;

        // Update delta time (samples) since last segment drawn (threshold met).
        if (f.time < 255)
            f.time++;

        // Calculate 8192 (= 2^13) x acceleration.
        ax = KPP * ((int) captureReport.getX() - f.current.x) - KDD * f.velocity.x;
        ay = KPP * ((int) captureReport.getY() - f.current.y) - KDD * f.velocity.y;
        ap = KPP * ((int) captureReport.getPressure() - f.current.pressure) - KDD * f.velocity.pressure;

        // Calculate new position.
        f.current.x += f.velocity.x;
        f.current.y += f.velocity.y;
        f.current.pressure += f.velocity.pressure;

        // Calculate new velocity.
        f.velocity.x = (((int) f.velocity.x << 13) + ax) >> 13;
        f.velocity.y = (((int) f.velocity.y << 13) + ay) >> 13;
        f.velocity.pressure = (((int) f.velocity.pressure << 13) + ap) >> 13;

        // Calculate squared distance of current point from "last" point.
        dist_sq = ((f.current.x - f.last.x) * (f.current.x - f.last.x) + (f.current.y - f.last.y) * (f.current.y - f.last.y));

        return dist_sq;
    }

    /**
     * Convert stylus pressure/speed into a line width value expressed in digitizer units. If vel < 0, the stylus was lifted after a single contact
     * point.
     *
     * @param vel        velocity expressed in digitizer units per sample interval
     * @param pressure   digitizer pressure reading
     */
    private static float computeLineWidth(float vel, float pressure) {
        int i, j;
        float dist;
        float lwa, lwb, lw;

        // Compute distance btw. successive samples in digitizer units.
        if (vel < 0)
            dist = velocityToDistance(75.0f);   // Don't know real speed if only have one point => Assume a mid-level.
        else
            dist = vel;

        // Saturate distance at range we have data for.
        if (dist < lineWidthMapArray[0].distance)
            dist = lineWidthMapArray[0].distance;
        else if (dist > lineWidthMapArray[lineWidthMapArray.length - 1].distance)
            dist = lineWidthMapArray[lineWidthMapArray.length - 1].distance;

        // Saturate pressure at range we have data for.
        if (pressure < mass[0])
            pressure = mass[0];
        else if (pressure > mass[mass.length - 1])
            pressure = mass[mass.length - 1];

        // Find the indices for distance (velocity).
        for (i = 1; i < lineWidthMapArray.length; i++) {
            if (dist <= lineWidthMapArray[i].distance)
                break;
        }

        // Find the indices for mass (pressure).
        for (j = 1; i < mass.length; j++) {
            if (pressure <= mass[j])
                break;
        }

        // Interpolate based on mass (pressure) first.
        lwa = lineWidthMapArray[i - 1].lineWidth[j - 1] + (pressure - mass[j - 1]) * (lineWidthMapArray[i - 1].lineWidth[j] - lineWidthMapArray[i - 1].lineWidth[j - 1]) / (mass[j] - mass[j - 1]);
        lwb = lineWidthMapArray[i].lineWidth[j - 1] + (pressure - mass[j - 1]) * (lineWidthMapArray[i].lineWidth[j] - lineWidthMapArray[i].lineWidth[j - 1]) / (mass[j] - mass[j - 1]);

        // Interpolate based on speed (distance) second.
        lw = lwa + (dist - lineWidthMapArray[i - 1].distance) * (lwb - lwa) / (lineWidthMapArray[i].distance - lineWidthMapArray[i - 1].distance);

        // Initialize filter if needed.
        // (The max value helps eliminate ink blobs at the start of traces due to impact pressures and/or low speeds.)
        if (mOldLineWidth < 0)
            mOldLineWidth = (lw > 45.0f ? 45.0f : lw);

        //  Filter A:  (LW changes too quickly for close samples and too slowly for far samples.)
        //  lw = (lw + 7*oldLW)/8;

        //  Filter B:
        //  if (dist <= oldLW)
        //    {
        //      lw = 0.1*lw + 0.9*oldLW;
        //    }
        //  else if (dist <= 5*oldLW)
        //    {
        //      float alpha = 0.1 + 0.9*(dist-oldLW)/(4*oldLW);
        //      lw = alpha*lw + (1 - alpha)*oldLW;
        //    }

        //  Filter C:  ** Seems to perform the best.
        lw = (2 * dist * lw + mOldLineWidth * mOldLineWidth) / (2 * dist + mOldLineWidth);

        //  Filter D:
        //  lw = (2*dist + oldLW)/(2*dist + lw)*lw;

        // Remember last linewidth for filtering.
        mOldLineWidth = lw;

        // Store the line width.
        return lw;
    }

    private static SyncPath createPathWithLineWidth(float lineWidth) {
        SyncPath path = new SyncPath();
        path.moveTo(mFilter.last.x, mFilter.last.y);
        path.setStrokeWidth(lineWidth);
        path.lineTo(mFilter.current.x, mFilter.current.y);
        return path;
    }

    // Digitizer resolution is 0.01 mm.
    private static final int TICKS_PER_MM = 100;
    // 144.425 samples per second
    private static final float MS_PER_SAMPLE = 6.924f;
    // Assuming stylus held at 30 deg angle.
    private static final float PEN_ANGLE_COS = 0.866f;
    // Scale factor for reported linewidth (to make recorded lines sharper than actual device).
    private static final float SCALE = 0.75f;

    // Array of digitizer pressure readings for which line widths are provided.
    private static final int mass[] = {massToPressure(10.0f), massToPressure(25.0f), massToPressure(50.0f), massToPressure(100.0f),
            massToPressure(150.0f), massToPressure(200.0f), massToPressure(250.0f), massToPressure(300.0f), massToPressure(350.0f),
            massToPressure(400.0f), massToPressure(450.0f), massToPressure(500.0f), massToPressure(550.0f), massToPressure(600.0f)};

    // Array of line widths vs. pressure at various velocities.
    private static final LineWidthMap lineWidthMapArray[] = new LineWidthMap[]
            {
                    //   v(mm/s)       10g*               25g*               50g               100g               150g               200g
                    //     250g               300g               350g               400g               450g               500g               550g*
                    //             600g*
                    new LineWidthMap(velocityToDistance(1.0f), new float[]{mmToDigitizer(0.720000f), mmToDigitizer(0.800000f),
                            mmToDigitizer(0.908937f), mmToDigitizer(1.108957f), mmToDigitizer(1.266351f), mmToDigitizer(1.388042f),
                            mmToDigitizer(1.462073f), mmToDigitizer(1.540000f), mmToDigitizer(1.618852f), mmToDigitizer(1.701938f),
                            mmToDigitizer(1.793265f), mmToDigitizer(1.860000f), mmToDigitizer(1.920000f), mmToDigitizer(1.954108f)}),
                    new LineWidthMap(velocityToDistance(5.0f), new float[]{mmToDigitizer(0.490000f), mmToDigitizer(0.530000f),
                            mmToDigitizer(0.614119f), mmToDigitizer(0.758321f), mmToDigitizer(0.868824f), mmToDigitizer(0.910000f),
                            mmToDigitizer(0.942034f), mmToDigitizer(1.000218f), mmToDigitizer(1.047881f), mmToDigitizer(1.083052f),
                            mmToDigitizer(1.155148f), mmToDigitizer(1.196536f), mmToDigitizer(1.250000f), mmToDigitizer(1.286546f)}),
                    new LineWidthMap(velocityToDistance(30.0f), new float[]{mmToDigitizer(0.300000f), mmToDigitizer(0.340000f),
                            mmToDigitizer(0.387672f), mmToDigitizer(0.493372f), mmToDigitizer(0.565948f), mmToDigitizer(0.620261f),
                            mmToDigitizer(0.673648f), mmToDigitizer(0.710716f), mmToDigitizer(0.746997f), mmToDigitizer(0.777846f),
                            mmToDigitizer(0.815101f), mmToDigitizer(0.837235f), mmToDigitizer(0.880000f), mmToDigitizer(0.926857f)}),
                    new LineWidthMap(velocityToDistance(75.0f), new float[]{mmToDigitizer(0.290000f), mmToDigitizer(0.295000f),
                            mmToDigitizer(0.320000f), mmToDigitizer(0.374948f), mmToDigitizer(0.422921f), mmToDigitizer(0.473530f),
                            mmToDigitizer(0.508386f), mmToDigitizer(0.541358f), mmToDigitizer(0.577623f), mmToDigitizer(0.600577f),
                            mmToDigitizer(0.621771f), mmToDigitizer(0.651861f), mmToDigitizer(0.670000f), mmToDigitizer(0.690000f)}),
                    new LineWidthMap(velocityToDistance(100.0f), new float[]{mmToDigitizer(0.280000f), mmToDigitizer(0.290000f),
                            mmToDigitizer(0.302881f), mmToDigitizer(0.338898f), mmToDigitizer(0.387231f), mmToDigitizer(0.433664f),
                            mmToDigitizer(0.452389f), mmToDigitizer(0.482745f), mmToDigitizer(0.516970f), mmToDigitizer(0.534589f),
                            mmToDigitizer(0.557370f), mmToDigitizer(0.581577f), mmToDigitizer(0.610000f), mmToDigitizer(0.620000f)}),
                    new LineWidthMap(velocityToDistance(180.0f), new float[]{mmToDigitizer(0.250000f), mmToDigitizer(0.260000f),
                            mmToDigitizer(0.280375f), mmToDigitizer(0.311056f), mmToDigitizer(0.362906f), mmToDigitizer(0.390511f),
                            mmToDigitizer(0.414745f), mmToDigitizer(0.436406f), mmToDigitizer(0.463840f), mmToDigitizer(0.478165f),
                            mmToDigitizer(0.501515f), mmToDigitizer(0.521805f), mmToDigitizer(0.540000f), mmToDigitizer(0.550000f)})
            };

    /**
     * Convert from velocity in mm/s to distance (in digitizer units) between successive samples.
     *
     * @param velocity
     * @return
     */
    private static float velocityToDistance(float velocity) {
        return ((velocity) * TICKS_PER_MM * MS_PER_SAMPLE / 1000);
    }

    /**
     * Convert from line width expressed in mm to scaled line width expressed in digitizer units.
     *
     * @param mm
     * @return
     */
    private static float mmToDigitizer(float mm) {
        return ((mm) * TICKS_PER_MM * SCALE);
    }

    /**
     * Convert from mass in grams (normal to surface) to corresponding digitizer pressure reading (along stylus).
     *
     * @param mass
     * @return
     */
    private static int massToPressure(float mass) {
        return (int) ((mass) * PEN_ANGLE_COS * 1023.0f / 600.0f + 0.5f);
    }

    private static class Filter {
        public Coordinate last;
        public Coordinate current;
        public Coordinate velocity;
        public int time;

        public Filter() {
            last = new Coordinate();
            current = new Coordinate();
            velocity = new Coordinate();
        }
    }

    private static class LineWidthMap {
        public float distance;          // In digitizer units (speed ~ distance between consecutive points).
        public float lineWidth[];     // In digitizer units.

        public LineWidthMap(float distance, float lineWidth[]) {
            this.distance = distance;
            this.lineWidth = lineWidth;
        }
    }

    private static class Coordinate {
        public int x;
        public int y;
        public int pressure;
    }
}




Java Source Code List

com.improvelectronics.sync.Config.java
com.improvelectronics.sync.android.Filtering.java
com.improvelectronics.sync.android.SyncCaptureReport.java
com.improvelectronics.sync.android.SyncFtpListener.java
com.improvelectronics.sync.android.SyncFtpService.java
com.improvelectronics.sync.android.SyncPath.java
com.improvelectronics.sync.android.SyncStreamingListener.java
com.improvelectronics.sync.android.SyncStreamingService.java
com.improvelectronics.sync.android.SyncUtilities.java
com.improvelectronics.sync.android.samples.FileBrowserAdapter.java
com.improvelectronics.sync.android.samples.FileBrowsingActivity.java
com.improvelectronics.sync.android.samples.MainActivity.java
com.improvelectronics.sync.android.samples.StreamingActivity.java
com.improvelectronics.sync.hid.HIDHandshake.java
com.improvelectronics.sync.hid.HIDInputReport.java
com.improvelectronics.sync.hid.HIDMessage.java
com.improvelectronics.sync.hid.HIDSetReport.java
com.improvelectronics.sync.hid.HIDUtilities.java
com.improvelectronics.sync.misc.CRC8.java
com.improvelectronics.sync.obex.OBEXFtpFolderListingItem.java
com.improvelectronics.sync.obex.OBEXFtpHeader.java
com.improvelectronics.sync.obex.OBEXFtpRequest.java
com.improvelectronics.sync.obex.OBEXFtpResponse.java
com.improvelectronics.sync.obex.OBEXFtpUtils.java