/*****************************************************************
BioZen
Copyright (C) 2011 The National Center for Telehealth and
Technology
Eclipse Public License 1.0 (EPL-1.0)
This library is free software; you can redistribute it and/or
modify it under the terms of the Eclipse Public License as
published by the Free Software Foundation, version 1.0 of the
License.
The Eclipse Public License is a reciprocal license, under
Section 3. REQUIREMENTS iv) states that source code for the
Program is available from such Contributor, and informs licensees
how to obtain it in a reasonable manner on or through a medium
customarily used for software exchange.
Post your updates and modifications to our GitHub or email to
t2@tee2.org.
This library is distributed WITHOUT ANY WARRANTY; without
the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the Eclipse Public License 1.0 (EPL-1.0)
for more details.
You should have received a copy of the Eclipse Public License
along with this library; if not,
visit http://www.opensource.org/licenses/EPL-1.0
*****************************************************************/
package com.t2.compassionMeditation;
import org.t2health.lib1.dsp.T2FPeriodAverageFilter;
import org.t2health.lib1.dsp.T2MovingAverageFilter;
import org.t2health.lib1.dsp.T2PeriodAverageFilter;
import android.content.Context;
import android.util.Log;
import com.oregondsp.signalProcessing.filter.iir.ChebyshevI;
import com.oregondsp.signalProcessing.filter.iir.PassbandType;
import com.t2.Constants;
import com.t2.compassionUtils.Util;
import com.t2.dataouthandler.DataOutHandler;
import com.t2.dataouthandler.DataOutHandlerException;
import com.t2.dataouthandler.DataOutHandlerTags;
import com.t2.dataouthandler.DataOutPacket;
import spine.datamodel.Data;
import spine.datamodel.Feature;
import spine.datamodel.FeatureData;
import spine.datamodel.MindsetData;
import spine.datamodel.Node;
import spine.datamodel.ShimmerData;
/**
* Does all necessary processing for raw rdata received from various biosensors
* Also handles logging of data to using DataOutHandler
*
* By having the individual activities use this class to process bio data the seperation
* is maintained between the model (biodata), and the view (activity).
*
* @author scott.coleman
*
*/
public class BioDataProcessor {
private static final String TAG = BioDataProcessor.class.getSimpleName();
private final int GSR_PERIOD_SAMPLE_SECONDS = 1;
private final int HEARTRATE_PERIOD_SAMPLE_SECONDS = 3;
private final int RESPRATE_PERIOD_SAMPLE_SECONDS = 10;
Context mContext;
private T2FPeriodAverageFilter mGsrPeriodAverage;
private T2PeriodAverageFilter mHeartRatePeriodAverage;
private T2HeartRateDetector mHeartRateDetector = new T2HeartRateDetector();
private T2MovingAverageFilter mGroundLeadFilter = new T2MovingAverageFilter(64);
private ChebyshevI mEcgBaselineFilter;
private ChebyshevI mEcgNoiseFilter;
private DataOutHandler mDataOutHandler;
public int mGsrResistance;
public double mGsrConductance;
public double mGgsrAvg;
public int mRawEcg;
public double mBaselineFiltered;
public int mShimmerHeartRate = 0;
public int mZephyrHeartRate;
public double mRespRate;
public double mSkinTempF;
/**
* @param context - Context of calling activity
*/
public BioDataProcessor(Context context) {
mContext = context;
}
/**
* Initializes all filters and processors necessary to process the biometeric data
*
* @param dataOutHandler - data output handler used to send data to output sinks
*/
public void initialize(DataOutHandler dataOutHandler) {
mDataOutHandler = dataOutHandler;
int shimmerSensorSampleRate = Integer.parseInt(SharedPref.getString(mContext, "sensor_sample_rate" ,"4"));
mGsrPeriodAverage = new T2FPeriodAverageFilter(GSR_PERIOD_SAMPLE_SECONDS * shimmerSensorSampleRate);
mHeartRatePeriodAverage = new T2PeriodAverageFilter(HEARTRATE_PERIOD_SAMPLE_SECONDS * shimmerSensorSampleRate);
mEcgBaselineFilter = new ChebyshevI( 4, 0.50885, PassbandType.HIGHPASS, 0.5, 0.5, 0.015625 ); // Fs = 64Hz, corner = .5Hz)
mEcgNoiseFilter = new ChebyshevI( 4, 0.50885, PassbandType.LOWPASS, 20, 20, 0.015625 ); // // Fs = 64Hz, corner = 20Hz)
}
/**
* Processes GSR biometeric data received from the Shimmer sensor
*
* @param shimmerData - Shimmer sensor data
* @param configuredGSRRange - GSR range configured into the sensor
*/
public void processShimmerGSRData(ShimmerData shimmerData, int configuredGSRRange) {
mGsrResistance = Util.GsrResistance(shimmerData.gsr, shimmerData.gsrRange, configuredGSRRange);
mGsrConductance = (1F / (float) mGsrResistance) * 1000000;
// Send data to output
DataOutPacket packet = new DataOutPacket();
packet.add(DataOutHandlerTags.SENSOR_TIME_STAMP, shimmerData.timestamp);
packet.add(DataOutHandlerTags.RAW_GSR, mGsrConductance, "%2.4f");
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
// e.printStackTrace();
}
// Handle logging of average GSR every 1 second
double mGgsrAvg = mGsrPeriodAverage.filter(mGsrConductance);
if (mGgsrAvg != T2FPeriodAverageFilter.AVERAGING) {
// We get here once every second
// Send data to output
packet = new DataOutPacket();
packet.add(DataOutHandlerTags.AVERAGE_GSR, mGgsrAvg, "%2.4f");
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
// e.printStackTrace();
}
}
} // End processShimmerGSR(ShimmerData shimmerData, int configuredGSRRange)
/**
* Processes EMG biometeric data received from the Shimmer sensor
*
* @param shimmerData - Shimmer sensor data
*
*/
public void processShimmerEMGData(ShimmerData shimmerData) {
// Send data to output
DataOutPacket packet = new DataOutPacket();
packet.add(DataOutHandlerTags.SENSOR_TIME_STAMP, shimmerData.timestamp);
packet.add(DataOutHandlerTags.RAW_EMG, shimmerData.emg);
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
// e.printStackTrace();
}
}
/**
* Processes ECG biometeric data received from the Shimmer sensor
* Note that this class filters the data detects heart rate using a
* Pan-Tompkins QRS detector
*
* @param shimmerData - Shimmer sensor data
*
*/
public void processShimmerECGData(ShimmerData shimmerData) {
// Account for the fact that the leads are switched in the service
int tmp = shimmerData.ecgLaLL ;
shimmerData.ecgLaLL = shimmerData.ecgRaLL;
shimmerData.ecgRaLL = tmp;
// First limit the ecg voltages to the max
if (shimmerData.ecgLaLL > 4095) shimmerData.ecgLaLL = 4096;
if (shimmerData.ecgRaLL > 4095) shimmerData.ecgRaLL = 4096;
int lead3 = mGroundLeadFilter.filter(shimmerData.ecgLaLL);
int mRawEcg = shimmerData.ecgRaLL - lead3;
double mBaselineFiltered = mEcgBaselineFilter.filter(mRawEcg);
int heartRate = mHeartRateDetector.filter((int) mBaselineFiltered, shimmerData.timestamp);
if (heartRate == -1) {
// just leave the heartrate at the last good calculated value
}
else {
// Limit the rate at which heartrate can change
int deltaHeartRate = heartRate - mShimmerHeartRate;
if (Math.abs(deltaHeartRate) < mShimmerHeartRate / 10) {
mShimmerHeartRate = heartRate;
}
else {
if (deltaHeartRate >= 0) {
mShimmerHeartRate += 1;
}
else {
mShimmerHeartRate -= 1;
}
}
}
// Send data to output
DataOutPacket packet = new DataOutPacket();
packet.add(DataOutHandlerTags.SENSOR_TIME_STAMP, shimmerData.timestamp);
packet.add(DataOutHandlerTags.RAW_ECG, mRawEcg);
packet.add(DataOutHandlerTags.FILTERED_ECG, (int) mBaselineFiltered);
packet.add(DataOutHandlerTags.RAW_HEARTRATE, mShimmerHeartRate);
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
// e.printStackTrace();
}
int hrAvg = mHeartRatePeriodAverage.filter(mShimmerHeartRate);
if (hrAvg != T2PeriodAverageFilter.AVERAGING) {
// We get here once every 3 seconds
// Send data to output
packet = new DataOutPacket();
packet.add(DataOutHandlerTags.AVERAGE_HEARTRATE, hrAvg);
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
// e.printStackTrace();
}
}
} // End processShimmerECG(ShimmerData shimmerData)
/**
* Processes HR, Skin Temp, and Resp Rate biometeric data received from the Zephyr sensor
*
* @param data - Spine data containing Zephyr data
*/
public void processZephyrData(Data data) {
Node source = data.getNode();
Feature[] feats = ((FeatureData)data).getFeatures();
Feature firsFeat = feats[0];
byte sensor = firsFeat.getSensorCode();
byte featCode = firsFeat.getFeatureCode();
int batLevel = firsFeat.getCh1Value();
mZephyrHeartRate = firsFeat.getCh2Value();
mRespRate = firsFeat.getCh3Value() / 10;
double skinTemp = firsFeat.getCh4Value() / 10;
mSkinTempF = (skinTemp * 9F / 5F) + 32F;
// Send data to output
DataOutPacket packet = new DataOutPacket();
packet.add(DataOutHandlerTags.RAW_HEARTRATE, mZephyrHeartRate);
packet.add(DataOutHandlerTags.RAW_RESP_RATE, (int) mRespRate);
packet.add(DataOutHandlerTags.RAW_SKINTEMP, mSkinTempF, "%2.1f");
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
// e.printStackTrace();
}
} // End processZephyr(ShimmerData shimmerData)
/**
* Processes Mindset (EEG) biometeric data received from the Mindset sensor
*
* @param data - Spine data containing Mindset data
* @param currentMindsetData - Structure to hold individual spectral bands
*/
public void processMindsetData(Data data, MindsetData currentMindsetData) {
Node source = data.getNode();
MindsetData mindsetData = (MindsetData) data;
if (mindsetData.exeCode == Constants.EXECODE_SPECTRAL || mindsetData.exeCode == Constants.EXECODE_RAW_ACCUM) {
if (mindsetData.exeCode == Constants.EXECODE_RAW_ACCUM)
currentMindsetData.updateRawWave(mindsetData);
currentMindsetData.updateSpectral(mindsetData); // Updates currentMindsetData with spectral data
String logDataLine = currentMindsetData.getLogDataLine();
// Send data to output
DataOutPacket packet = new DataOutPacket();
packet.add(DataOutHandlerTags.EEG_SPECTRAL, logDataLine);
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
// e.printStackTrace();
}
}
if (mindsetData.exeCode == Constants.EXECODE_POOR_SIG_QUALITY) {
currentMindsetData.poorSignalStrength = mindsetData.poorSignalStrength;
// Send data to output
DataOutPacket packet = new DataOutPacket();
packet.add(DataOutHandlerTags.EEG_SIG_STRENGTH, mindsetData.poorSignalStrength);
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
// e.printStackTrace();
}
}
if (mindsetData.exeCode == Constants.EXECODE_ATTENTION) {
currentMindsetData.attention= mindsetData.attention;
// Send data to output
DataOutPacket packet = new DataOutPacket();
packet.add(DataOutHandlerTags.EEG_ATTENTION, mindsetData.attention);
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
// e.printStackTrace();
}
}
if (mindsetData.exeCode == Constants.EXECODE_MEDITATION) {
currentMindsetData.meditation= mindsetData.meditation;
// Send data to output
DataOutPacket packet = new DataOutPacket();
packet.add(DataOutHandlerTags.EEG_MEDITATION, mindsetData.meditation);
try {
mDataOutHandler.handleDataOut(packet);
} catch (DataOutHandlerException e) {
Log.e(TAG, e.toString());
// e.printStackTrace();
}
}
} // End processMindsetData(Data data, MindsetData currentMindsetData)
}