Android Oximeter using Image Processing and IIR filter

IIR filter

Main Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="30dp"
android:background="@color/back"
tools:context=".MainActivity">
<ImageView
android:layout_width="match_parent"
android:layout_height="500dp"
android:src="@drawable/oxy"
android:contentDescription="@string/app_name"/>
<Space
android:layout_width="match_parent"
android:layout_height="60dp"/>
<Button
android:id="@+id/startVS"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start Measuring"
android:padding="10dp"
android:backgroundTint="@color/white"
android:textColor="@color/back"
android:textStyle="bold"
android:textSize="18sp"/>
</LinearLayout>
public class MainActivity extends AppCompatActivity {    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
Button actionWrk = this.findViewById(R.id.startVS);
actionWrk.setOnClickListener(view -> {
Intent intent = new Intent(view.getContext(), OxygenProcess.class);
startActivity(intent);
finish();
});
}
}

OxygenProcess

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="40dp"
android:gravity="center"
android:background="@color/back"
tools:context=".OxygenProcess">
<SurfaceView
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/preview"/>
<ProgressBar
android:layout_width="60dp"
android:layout_height="60dp"
android:id="@+id/o2PB"
android:layout_marginTop="15dp"
android:indeterminate="false"
android:max="27"
android:progress="1"
android:progressDrawable="@drawable/cicular_progressbar"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Please wait. It will take a while"
android:textColor="@color/white"
android:textSize="15sp"/>
</LinearLayout>
public class OxygenProcess extends AppCompatActivity {    private static final String TAG = "OxygenMeasure";
private static final AtomicBoolean processing = new AtomicBoolean(false);
private Camera camera = null;
private static SurfaceHolder previewHolder = null;
private SurfaceView preview = null;
private static PowerManager.WakeLock wakeLock = null;
private Toast mainToast;
private ProgressBar Prog02;
public int ProgP = 0;
public int inc = 0;
private static long startTime = 0;
private double SamplingFreq;
private static final double RedBlueRation = 0;
double Stdr = 0;
double Stdb= 0;
double sumred = 0;
double sumblue = 0;
public int o2;
public ArrayList<Double> RedAvgList = new ArrayList<>();
public ArrayList<Double> BlueAvgList = new ArrayList<>();
public int counter = 0;
@SuppressLint("InvalidWakeLockTag")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_oxygen_process);
preview = findViewById(R.id.preview);
previewHolder = preview.getHolder();
previewHolder.addCallback(surfaceCallback);
previewHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
Prog02 = findViewById(R.id.o2PB);
Prog02.setProgress(0);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "NOTDIMSCREEN");
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);
}
@Override
protected void onResume() {
super.onResume();
wakeLock.acquire();
camera = Camera.open();
camera.setDisplayOrientation(90);
startTime = System.currentTimeMillis();
}
@Override
protected void onPause() {
super.onPause();
wakeLock.release();
camera.setPreviewCallback(null);
camera.stopPreview();
camera.release();
camera=null;
}
private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (data == null) throw new NullPointerException();
Camera.Size size = camera.getParameters().getPreviewSize();
if (size== null) throw new NullPointerException();
if (!processing.compareAndSet(false, true)) return;
int width = size.width;
int height = size.height;
double RedAvg;
double BlueAvg;
RedAvg = ImageProcessing.colordecoderRGB(data.clone(), height, width,1);
sumred+=RedAvg;
BlueAvg = ImageProcessing.colordecoderRGB(data.clone(), height, width,2);
sumblue+=BlueAvg;
RedAvgList.add(RedAvg);
BlueAvgList.add(BlueAvg);
++counter;
if (RedAvg < 200){
inc = 0;
ProgP = inc;
Prog02.setProgress(ProgP);
processing.set(false);
}
long endTime = System.currentTimeMillis();
double totleTimeInSec = (endTime - startTime)/1000d;
if (totleTimeInSec >=30){
startTime = System.currentTimeMillis();
SamplingFreq = (counter/totleTimeInSec);
Double[] Red = RedAvgList.toArray(new Double[RedAvgList.size()]);
Double[] Blue = BlueAvgList.toArray(new Double[BlueAvgList.size()]);
double HRFreq = Fft.FFT(Red, counter,SamplingFreq);
double bpm = (int) ceil(HRFreq * 60);
double meanr = sumred/counter;
double meanb = sumblue/counter;
for (int i = 0; i<counter-1; i++){
Double bufferb = Blue[i];
Stdb+=((bufferb - meanb) * (bufferb - meanb));
Double bufferr = Red[i];
Stdr+= ((bufferr - meanr) * (bufferr - meanr));
}
double varr = sqrt(Stdr/(counter -1));
double varb = sqrt(Stdb/(counter -1));
double R = (varr/meanr)/(varb/meanb);
double spo2 = 100 - 5 * R;
o2 = (int) (spo2);
if ((o2 < 80 || o2 > 99) || (bpm < 45 || bpm > 200)){
inc = 0;
ProgP = inc;
Prog02.setProgress(ProgP);
mainToast = Toast.makeText(getApplicationContext(),"Measurement Failure", Toast.LENGTH_SHORT);
mainToast.show();
startTime = System.currentTimeMillis();
counter = 0;
processing.set(false);
return;
}
} if (o2 != 0){
Intent i = new Intent(OxygenProcess.this, OxygenCalculate.class);
i.putExtra("o2r", o2);
startActivity(i);
finish();
}
if (RedAvg != 0 ){
ProgP = inc++/34;
Prog02.setProgress(ProgP);
}
processing.set(false);
}
};
private final SurfaceHolder.Callback surfaceCallback = new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
try {
camera.setPreviewDisplay(previewHolder);
camera.setPreviewCallback(previewCallback);
}catch (Throwable t){
Log.d(TAG,"surfaceCreated: ",t);
}
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder surfaceHolder, int format, int width, int height) {
Camera.Parameters parameters = camera.getParameters();
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
Camera.Size size = getSmallestPreviewSize(width, height, parameters);
if (size!=null){
parameters.setPreviewSize(size.width, size.height);
}
camera.setParameters(parameters);
camera.startPreview();
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder surfaceHolder) {
}
};
private Camera.Size getSmallestPreviewSize(int width, int height, Camera.Parameters parameters) {
Camera.Size result = null;
for (Camera.Size size : parameters.getSupportedPictureSizes()){
if (size.width <= width && size.height <= height){
if (result == null){
result = size;
}
else {
int resultArea = result.width * result.height;
int newArea = size.width * size.height;
if (newArea < resultArea) result = size;
}
}
}
return result;
}
@Override
public void onBackPressed() {
super.onBackPressed();
Intent i = new Intent(OxygenProcess.this, MainActivity.class);
startActivity(i);
finish();
}
}

Oxygen Calculation

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="40dp"
android:background="@color/back"
tools:context=".OxygenCalculate">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="YOUR OXYGEN LEVEL"
android:textAlignment="center"
android:textColor="@color/white"
android:textSize="24sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/o2r"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="XX"
android:textColor="@android:color/white"
android:textSize="30sp"
android:textStyle="bold" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:layout_marginTop="120dp">
<TextView
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="100 - 98"
android:textAlignment="textEnd"
android:textColor="@android:color/holo_blue_bright"
android:textSize="20sp"
android:textStyle="bold"
android:layout_marginEnd="10dp"
android:layout_weight="1"/>
<TextView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="Normal"
android:textAlignment="textStart"
android:textColor="@color/white"
android:textSize="20sp"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start">
<TextView
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="97 - 95"
android:textAlignment="textEnd"
android:textColor="@android:color/holo_blue_bright"
android:textSize="20sp"
android:layout_marginEnd="10dp"
android:textStyle="bold"
android:layout_weight="1"/>
<TextView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="Insufficient"
android:textAlignment="textStart"
android:textColor="@color/white"
android:textSize="20sp"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<TextView
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="94 - 90"
android:textAlignment="textEnd"
android:textColor="@android:color/holo_blue_bright"
android:textSize="20sp"
android:layout_marginEnd="10dp"
android:textStyle="bold"
android:layout_weight="1"/>
<TextView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="Decreased"
android:textAlignment="textStart"
android:textColor="@color/white"
android:textSize="20sp"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<TextView
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="below 90"
android:textAlignment="textEnd"
android:textColor="@android:color/holo_blue_bright"
android:textSize="20sp"
android:layout_marginEnd="10dp"
android:textStyle="bold"
android:layout_weight="1"/>
<TextView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="Critical"
android:textAlignment="textStart"
android:textColor="@color/white"
android:textSize="20sp"
android:layout_weight="1"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<TextView
android:layout_width="40dp"
android:layout_height="wrap_content"
android:text="below 80"
android:textAlignment="textEnd"
android:textColor="@android:color/holo_blue_bright"
android:textSize="20sp"
android:layout_marginEnd="10dp"
android:textStyle="bold"
android:layout_weight="1"/>
<TextView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="Severe hypoxia"
android:layout_weight="1"
android:textAlignment="textStart"
android:textColor="@color/white"
android:textSize="20sp"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<TextView
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="below 70"
android:layout_marginEnd="10dp"
android:textAlignment="textEnd"
android:textColor="@android:color/holo_blue_bright"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="Acute danger to life"
android:textAlignment="textStart"
android:layout_weight="1"
android:textColor="@color/white"
android:textSize="20sp"/>
</LinearLayout> <Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/sendo2"
android:text="Share your result"
android:textAllCaps="true"
android:layout_marginTop="60dp"
android:padding="12dp"
android:textSize="18sp"
android:textStyle="bold"
android:backgroundTint="@color/white"
android:textColor="@color/back" />
</LinearLayout>
public class OxygenCalculate extends AppCompatActivity {    private String Date;
DateFormat df = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
java.util.Date today = Calendar.getInstance().getTime();
int o2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_oxygen_calculate);
Date = df.format(today);
TextView Ro2 = this.findViewById(R.id.o2r);
Button So2 = this.findViewById(R.id.sendo2);
Bundle bundle = getIntent().getExtras();
if (bundle!=null){
o2 = bundle.getInt("o2r");
Ro2.setText(String.valueOf(o2));
}
So2.setOnClickListener(view -> {
Intent i = new Intent(Intent.ACTION_SEND);
i.setType("message/rfc822");
i.putExtra(Intent.EXTRA_SUBJECT, "Oxygen Meter");
i.putExtra(Intent.EXTRA_TEXT, "Oxygen Level \n"+"at "+Date+" is "+o2);
try {
startActivity(
Intent.createChooser(i,"SEND..")
);
}catch (ActivityNotFoundException e){
Toast.makeText(this, "No sharing apps installed on this device", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onBackPressed() {
super.onBackPressed();
}
}

Output:

Contact Details:

You can follow me on YouTube:

Also, visit my website for more content like this

Follow me on Instagram

Follow me on Facebook

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Golap Gunjan Barman

Golap Gunjan Barman

258 Followers

Hi everyone, myself Golap an Android app developer with UI/UX designer.