Android Oximeter using Image Processing and IIR filter

Android Oximeter using Image Processing and IIR filter

Here in this blog, we’re going to create an Oximeter using Image Processing and IIR (Infinite Impulse Response) filter, camera API, and Surface View with Power manager.

IIR filter

IIR filters are one of two primary types of digital filters used in Digital Signal Processing (DSP) applications. They have been widely deployed in audio equalization, biomedical sensor signal processing, IoT/IIoT smart sensors, and high-speed telecommunication/RF applications.

Here users can easily check their Oxygen level by putting their finger over the camera and flashlight surface. Then by using the image processing and IIR filter this app will calculate the Oxygen level.

Main Layout

activity_main.xml

<?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>

MainActivity.java

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

activity_oxygen_process.xml

<?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>

OxygenProcess.java

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

activtiy_oxygen_calculate.xml

<?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>

OxygenCalculate.java

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:

Gmail id: barmangolap15@gmail.com

WhatsApp: +91 8473855948

Instagram id: @androidapps.development.blogs

You can follow me on YouTube:

Golap Barman

Also, visit my website for more content like this

www.gbandroidblogs.com

Follow me on Instagram

Android App Developer

Follow me on Facebook

GBAndroidBlogs

--

--

--

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

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Koin Framework for Dependency Injection (Part-1) — Most Underrated Framework🙄

Automatically changing pages using the PageView widget

Winter Banner for Husky Subreddit

Provider StateManagement in Flutter

Java vs. Kotlin: Which is the Better Option for Android App Development?

News Ticker Using Navigation Component + Coroutine + Channel + Semaphore

Email Authentication with Huawei Auth Service

Koin Framework- Provide ViewModel Dependency (Part-3) - Most Underrated Framework

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

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

More from Medium

Simple custom shadow on Android

Beginning Android Development Resources

Proguard — android

Network Data Transfer Using Volley Library— Android