Lab 5: MyRuns5: Activity Recognition

This lab primarily focuses on adding a new feature to your app: the ability to automatically infer (classify) your physical activity, specifically, if you are standing, walking and running. To do this, MyRuns samples raw accelerometer data from sensors on the phone. This raw data is extracted as what are called “features”, and then you need to feed these features into a classifier trained by WEKA (Waikato Environment for Knowledge Analysis). This tool will help us to build a classifier that recognizes our current activity – standing, walking, running. In this lab, we will use the terms "classification", "recognition" and "inference" interchangeably. From the screenshot below, you can find that we have a new Input Type "Automatic" in the Start Tab. In the following sections, we will describe what we need to do to support this functionality.

TrackingService

In this lab, the TrackingService will be extended to not only listen to the location (GPS) sensor, but also the accelerometer sensor. So in the class definition, you will find that it also implements the SensorEventListener Interface. The class definition and variables declaration are given below.

        // The Tracking service will: read and process GPS data.
        public class TrackingService extends Service implements LocationListener,
                SensorEventListener {

            // A buffer list to store all GPS track points
            // It's accessed at different places
            public ArrayList<Location> mLocationList;

            // Sensor manager for accelerometer
            private SensorManager mSensorManager;
            private Sensor mAccelerometer;

            // Location manager
            private LocationManager mlocationManager;
            // Context for "this"
            private Context mContext;

            // Intents for broadcasting location/motion updates
            private Intent mLocationUpdateBroadcast;
            private Intent mActivityClassificationBroadcast;

            // A blocking queue for buffering motion sensor data
            private static ArrayBlockingQueue<Double> mAccBuffer;

            // A async task running in a different thread all the time to
            // process the motion sensor data and do classification
            private ActivityClassificationTask mActivityClassificationTask;

You can note that we add several new variables compared to lab4. These new variables will all be used for sensing, processing and classifying accelerometer activities. In onCreate() method, you need to assign values to all variables. The implementation of this method is straightforward, we will give you a suggested implementation. But feel free to implement it on your own. The ActivityClassificationTask is a new AsyncTask subclass that are designed for processing the sensor data and doing classification. We will come to it later.

In onStartCommand method, the first thing you need to do is reading mInputType. Then you need to instantiate mlocationManager like what you did in the last lab. After this, check the value of mInputType. If it's Globals.INPUT_TYPE_AUTOMATIC (which means this service will also monitor acceleromer sensor and conduct activity recognition), you need to also registering and listen to motion sensor. Specifically, you need to instantiate the private member mSensorManager and mAccelerometer. Check the following code snippets for example. After this, you need to make the service run in the foreground and create its notification object, exactly as what you did in the last lab.

    mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
    mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);
    mSensorManager.registerListener(this, mAccelerometer,SensorManager.SENSOR_DELAY_FASTEST);
    mActivityClassificationTask = new ActivityClassificationTask();

In onDestroy, besides unregistering mlocationManager you also need to unregister mSensorManager and cancel mActivityClassificationTask if the input type is automatic. The onBind and onLocationChanged method will keep the same implementation. Since the service also implements the SensorEventListener Interface, it needs to implement SensorEventListener's onSensorChanged method. The onSensorChanged method will be invoked whenever there is a new accelerometer data. What it does is to compute the magnitude of the accelerometer data and store it in mAccBuffer. If mAccBuffer is full, double its size then add the magnitude value into it. The other abstract method onAccuracyChanged can be left blank:

     public void onAccuracyChanged(Sensor sensor, int accuracy) {}

Then we introduce a new ActivityClassificationTask class which is a subclass of AsyncTask and is responsible for processing the sensor data and doing activity classification. We only need its doInBackground method for our purpose. This method keeps running in a separate thread. It maintains an infinite loop, which waits on new sensor event, processes the sensor data and uses the classifier to do activity recognition. Specifically, it takes data from the mAccBuffer variable when there is new data and store them in a double array. If the length of the array matches the a per-defined constant value, we start the process of feature extraction and classification. Finally you need to send out a broadcast about the classification result.

MapDisplayActivity

The MapDisplayActivity class is also extended to handle automatic activity recognition results. As what we did in the last lab, the MapDisplayActivity handles two tasks: New(left screenshot below) and History(right screenshot below). In this lab, when the task of map is new and the InputType is Automatic, you need to show the activity type in stats with an automatic way. You need to use the classifier to show the current type of activity.

Specifically, it adds 1) an OnMotionUpdateTask class which is a subclass of AsyncTask and is responsible for updating the new activity inference result and 2) a BroadcastReceiver to receive the motion sensor update in order to fire the OnMotionUpdateTask.

The declaration for these new member variables is given as follows:

        // A broadcast receiver to receive the motion sensor update, and do activity
        // inference in a different thread
        private IntentFilter mMotionUpdateIntentFilter;
        private BroadcastReceiver mMotionUpdateReceiver = new BroadcaseReceiver(){
            @Override  
            public void onReceive(Context context, Intent intent) {
                // Update is done in another thread so it won't block UI thread
                int val = intent.getIntExtra(Globals.KEY_CLASSIFICATION_RESULT, -1);
                Log.d(Globals.TAG, "Motion update received; " + val);
                new OnMotionUpdateTask().execute(Integer.valueOf(val));
            }
        };

We will give the implementation of onCreate method, where all member variables get instantiated and the TrackingService is started and bound. You need to implement the onResume and onPause methods. In onPause, you need to unregister the broadcast receivers (for both GPS and accelerometer if they are running right now) when the activity is about to go inactive. You will need to register them again in onResume method. The implementations of onCreateOptionsMenu and onOptionsItemSelected are the same as before.

We have two AsyncTask subclasses in this activity. OnLocationUpdateTask is the same as the last lab. As mentioned above, OnMotionUpdateTask class is responsible for inserting the new activity inference result into mEntry variable. Please implement its doInBackground method to do this. The onSaveClicked and onCancelClicked methods for the two buttons, as well as the onBackPressed method is the same as the last lab.

ExerciseEntryHelper

ExerciseEntryHelper class is also extended to handle automatic activity recognition results. In particular, we further add several new members and methods into this class. The declaration of the member variables of this class is given blow. We add an int member mCurrentInferedActivityType and an int array mInferenceCount. The comments in the declaration code indicate how they will be used. You will need to implement the setter/getter for mCurrentInferedActivityType.

    private int mCurrentInferedActivityType; // Current inferred activity type. for automatic mode 
    private int[] mInferenceCount; // Count of the inference results for voting decision

You need to extend two functions in ExerciseEntryHelper: getStatsDescription method and startLogging method to set default inferred type of activity. We have a new method updateByInference, which will be called in OnMotionUpdateTask (described above) to update current activity type based on automatic inference. This method uses these two new variables to do its task. mCurrentInferedActivityType represents the real time result, and mInferenceCount counts the occurrences of each activity during the whole exercise period. Then we need a voting scheme to smooth over the mInferenceCount we get. A voting scheme here is to select the activity that has the maximum occurrences during this period. After voting, we then can get a most reliable activity type to set mData.activityType. See comments in the code for more details.

Other classes

The WekaClassifier.java should be the decision tree you trained using the Weka GUI based on features.arff. And the provided FFT.java in package com.meapsoft is contributed by Ron Weiss. It takes sensor samples (x, y, z) in a time series of accelerometer data and transforms it to frequency domain features via Fast Fourier Transform. We will use these frequency features as the input to WekaClassifier.java.

Files explained

The source files that you have to code up breakdown into 1 activity, 1 service, 1 database helper and 1 classifier.

-- activity:
MapDisplayActivity.java:    here you need to add one BroadcastReceiver and one AsyncTask called onMotionUpdateTask.

-- service:
TrackingService.java:       add one infinite background sensing AsyncTask called ActivityClassificationTask.
    
-- database helper:
ExerciseEntryHelper.java:   extend two stats functions: getStatsDescription() and startLogging(), and add one function 
                            called updateByInference to carry out voting scheme over the set of inference results. 

-- classifier:
WekaClassifier.java:        train the decision tree using your collected data "features.arff" to generate the java code using Weka GUI.

Extra Credit

Find it in the skeleton's comment.

Submission

See webpage for submission information.