/**
*
* Funf: Open Sensing Framework
* Copyright (C) 2010-2011 Nadav Aharony, Wei Pan, Alex Pentland.
* Acknowledgments: Alan Gardner
* Contact: nadav@media.mit.edu
*
* This file is part of Funf.
*
* Funf is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Funf is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with Funf. If not, see <http://www.gnu.org/licenses/>.
*
*/
package edu.mit.media.funf.probe.builtin;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import edu.mit.media.funf.Schedule;
import edu.mit.media.funf.Schedule.DefaultSchedule;
import edu.mit.media.funf.config.Configurable;
import edu.mit.media.funf.json.IJsonObject;
import edu.mit.media.funf.math.FFT;
import edu.mit.media.funf.math.Window;
import edu.mit.media.funf.probe.Probe.Base;
import edu.mit.media.funf.probe.Probe.ContinuousProbe;
import edu.mit.media.funf.probe.Probe.RequiredFeatures;
import edu.mit.media.funf.probe.Probe.RequiredProbes;
import edu.mit.media.funf.probe.builtin.ProbeKeys.AccelerometerFeaturesKeys;
import edu.mit.media.funf.time.TimeUtil;
/**
*
* @author Max Little and Alan Gardner
*
*/
@RequiredFeatures("android.hardware.sensor.accelerometer")
@RequiredProbes(AccelerometerSensorProbe.class)
@Schedule.DefaultSchedule(interval=120, duration=15)
public class AccelerometerFeaturesProbe extends Base implements ContinuousProbe, AccelerometerFeaturesKeys {
@Configurable
private double frameDuration = 1.0;
@Configurable
private int fftSize = 128;
@Configurable
private double[] freqBandEdges = {0,1,3,6,10};
// Assumed maximum accelerometer sampling rate
private static final double SENSOR_MAX_RATE = 100.0;
private AccelerometerListener listener;
private double prevSecs;
private double prevFrameSecs;
private double frameTimer = 0;
private double[][] frameBuffer = null;
private double[] fftBufferR = null;
private double[] fftBufferI = null;
private int frameSamples = 0;
private int frameBufferSize = 0;
private FFT featureFFT = null;
private Window featureWin = null;
private static int[] freqBandIdx = null;
private class AccelerometerListener implements DataListener {
private Gson gson = getGson();
@Override
public void onDataReceived(IJsonObject completeProbeUri, IJsonObject acclerometerData) {
double currentSecs = acclerometerData.get(AccelerometerSensorProbe.TIMESTAMP).getAsDouble();
double x = acclerometerData.get(AccelerometerSensorProbe.X).getAsDouble();
double y = acclerometerData.get(AccelerometerSensorProbe.Y).getAsDouble();
double z = acclerometerData.get(AccelerometerSensorProbe.Z).getAsDouble();
if (prevSecs == 0)
{
prevSecs = currentSecs;
}
double diffSecs = currentSecs - prevSecs;
prevSecs = currentSecs;
frameBuffer[frameSamples][0] = x;
frameBuffer[frameSamples][1] = y;
frameBuffer[frameSamples][2] = z;
frameSamples ++;
frameTimer += diffSecs;
if ((frameTimer >= frameDuration) || (frameSamples == (frameBufferSize - 1))) {
JsonObject data = new JsonObject();
double fN = (double)frameSamples;
if (prevFrameSecs == 0) {
prevFrameSecs = currentSecs;
}
double diffFrameSecs = currentSecs - prevFrameSecs;
prevFrameSecs = currentSecs;
data.addProperty(TIMESTAMP, currentSecs);
data.addProperty(DIFF_FRAME_SECS, new BigDecimal(diffFrameSecs).setScale(TimeUtil.MICRO, RoundingMode.HALF_EVEN));
data.addProperty(NUM_FRAME_SAMPLES, frameSamples);
data.add(X, getFeatures(0, fN));
data.add(Y, getFeatures(1, fN));
data.add(Z, getFeatures(2, fN));
sendData(data);
// Reset frame buffer counters
frameSamples = 0;
frameTimer = 0;
// Ensure buffer is zero-padded
for (double[] row: frameBuffer) {
Arrays.fill(row, 0);
}
}
}
private JsonObject getFeatures(int i, double fN) {
JsonObject data = new JsonObject();
// Mean
double mean = 0;
for (int j = 0; j < frameSamples; j ++)
mean += frameBuffer[j][i];
mean /= fN;
data.addProperty(MEAN, mean);
double accum;
// Absolute central moment
accum = 0;
for (int j = 0; j < frameSamples; j ++)
accum += Math.abs(frameBuffer[j][i] - mean);
data.addProperty(ABSOLUTE_CENTRAL_MOMENT, accum/fN);
// Standard deviation
accum = 0;
for (int j = 0; j < frameSamples; j ++)
accum += (frameBuffer[j][i] - mean)*(frameBuffer[j][i] - mean);
data.addProperty(STANDARD_DEVIATION, Math.sqrt(accum/fN));
// Max deviation
accum = 0;
for (int j = 0; j < frameSamples; j ++)
accum = Math.max(Math.abs(frameBuffer[j][i] - mean),accum);
data.addProperty(MAX_DEVIATION, accum);
// Frequency analysis with zero-padding
Arrays.fill(fftBufferR, 0);
Arrays.fill(fftBufferI, 0);
// Drop accel. values into FFT buffer
for (int j = 0; j < frameSamples; j++)
{
fftBufferR[j] = frameBuffer[j][i] - mean;
}
// In-place windowing
featureWin.applyWindow(fftBufferR);
// In-place FFT
featureFFT.fft(fftBufferR, fftBufferI);
// Get PSD across frequency band ranges
double[] psdAcrossFrequencyBands = new double[freqBandEdges.length - 1];
for (int b = 0; b < (freqBandEdges.length - 1); b ++)
{
int j = freqBandIdx[b];
int k = freqBandIdx[b+1];
accum = 0;
for (int h = j; h < k; h ++)
{
accum += fftBufferR[h]*fftBufferR[h] + fftBufferI[h]*fftBufferI[h];
}
psdAcrossFrequencyBands[b] = accum/((double)(k - j));
}
data.add(PSD_ACROSS_FREQUENCY_BANDS, gson.toJsonTree(psdAcrossFrequencyBands));
return data;
}
@Override
public void onDataCompleted(IJsonObject completeProbeUri, JsonElement checkpoint) {
// TODO Auto-generated method stub
}
}
@Override
protected void onEnable() {
super.onEnable();
frameBufferSize = (int)Math.ceil(SENSOR_MAX_RATE/frameDuration);
frameBuffer = new double[frameBufferSize][3];
//writeLogTextLine("Accelerometer maximum frame size (samples): " + frameBufferSize);
//writeLogTextLine("Accelerometer maximum frame duation (secs): " + SENSOR_FRAME_DURATION);
//allocateFrameFeatureBuffer(STREAM_FEATURES);
featureFFT = new FFT(fftSize);
featureWin = new Window(frameBufferSize);
fftBufferR = new double[fftSize];
fftBufferI = new double[fftSize];
freqBandIdx = new int[freqBandEdges.length];
for (int i = 0; i < freqBandEdges.length; i ++) {
freqBandIdx[i] = Math.round((float)freqBandEdges[i]*((float)fftSize/(float)SENSOR_MAX_RATE));
}
listener = new AccelerometerListener();
getGson().fromJson("{}", AccelerometerSensorProbe.class).registerPassiveListener(listener);
// TODO: Register listener for accelerometer probe
}
@Override
protected void onStart() {
super.onStart();
reset();
getGson().fromJson(DEFAULT_CONFIG, AccelerometerSensorProbe.class).registerListener(listener);
}
@Override
protected void onStop() {
super.onStop();
getGson().fromJson(DEFAULT_CONFIG, AccelerometerSensorProbe.class).unregisterListener(listener);
reset();
}
@Override
protected void onDisable() {
// TODO Auto-generated method stub
super.onDisable();
}
private void reset() {
prevSecs = 0;
prevFrameSecs = 0;
frameTimer = 0;
frameSamples = 0;
// Ensure frame buffer is cleared
for (double[] row: frameBuffer)
Arrays.fill(row, 0);
}
}