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