Commit 727ddf92 authored by Indika NK's avatar Indika NK

Added Heart rate monitor basic functionality

parent 5325cb03
import 'package:covidefender/pages/heart_rate/heart_rate_screen.dart';
import 'package:covidefender/pages/mask_detect/invoker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
......@@ -18,13 +19,10 @@ class I_guider extends StatelessWidget {
Expanded(
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('lib/assets/images/hero.gif'),
fit: BoxFit.cover
)
),
image: DecorationImage(
image: AssetImage('lib/assets/images/hero.gif'),
fit: BoxFit.cover)),
height: 300,
),
),
],
......@@ -66,7 +64,7 @@ class I_guider extends StatelessWidget {
minWidth: 100.0,
height: 150.0,
child: RaisedButton.icon(
color: Colors.red[200],
color: Colors.greenAccent,
icon: Icon(
Icons.face_unlock_sharp,
size: 60,
......@@ -78,7 +76,12 @@ class I_guider extends StatelessWidget {
),
onPressed: () {
print('face mask detection');
Navigator.pushNamed(context, '/invoker');
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => invoker())
);
// Navigator.pushNamed(context, '/invoker');
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
......@@ -98,18 +101,20 @@ class I_guider extends StatelessWidget {
minWidth: 100.0,
height: 150.0,
child: RaisedButton.icon(
icon: Icon(
Icons.notifications_active,
Icons.view_stream_outlined,
size: 40,
color: Colors.white,
),
label: Text('',
style: TextStyle(fontWeight: FontWeight.bold)),
onPressed: () {},
onPressed: () {
// action when button is pressed route to heart rate measurer
Navigator.push(context, MaterialPageRoute(builder: (context) => HeartRateScreen() ));
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10)),
color: Colors.greenAccent,
color: Colors.redAccent,
),
),
),
......@@ -140,12 +145,6 @@ class I_guider extends StatelessWidget {
)
],
),
],
),
);
......
......@@ -9,8 +9,8 @@ void main() => runApp(MaterialApp(
// initialRoute: '/guider',
routes: {
'/':(context) => HomeScreen(),
'/guider':(context) => I_guider(),
'/invoker':(context) => invoker(),
// '/guider':(context) => I_guider(),
// '/invoker':(context) => invoker(),
},
......
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart';
class Chart extends StatelessWidget {
final List<SensorValue> _data;
Chart(this._data);
@override
Widget build(BuildContext context) {
return new charts.TimeSeriesChart([
charts.Series<SensorValue, DateTime>(
id: 'Values',
colorFn: (_, __) => charts.MaterialPalette.green.shadeDefault,
domainFn: (SensorValue values, _) => values.time,
measureFn: (SensorValue values, _) => values.value,
data: _data,
)
],
animate: false,
primaryMeasureAxis: charts.NumericAxisSpec(
tickProviderSpec:
charts.BasicNumericTickProviderSpec(zeroBound: false),
renderSpec: charts.NoneRenderSpec(),
),
domainAxis: new charts.DateTimeAxisSpec(
renderSpec: new charts.NoneRenderSpec()));
}
}
class SensorValue {
final DateTime time;
final double value;
SensorValue(this.time, this.value);
}
import 'package:covidefender/pages/heart_rate/homePage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class HeartRateScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Heart Rate Detector'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset('lib/assets/images/pulse.gif'),
),
SizedBox(height: 20),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ElevatedButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (context) => HomePage(),)),
child: Text('Measure Heart Rate'),
),
],
),
],
),
);
}
}
import 'dart:async';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:wakelock/wakelock.dart';
import 'chart.dart';
class HomePage extends StatefulWidget {
@override
HomePageView createState() {
return HomePageView();
}
}
class HomePageView extends State<HomePage> with SingleTickerProviderStateMixin {
bool _toggled = false; // toggle button value
List<SensorValue> _data = List<SensorValue>(); // array to store the values
CameraController _controller;
double _alpha = 0.3; // factor for the mean value
AnimationController _animationController;
double _iconScale = 1;
int _bpm = 0; // beats per minute
int _fs = 30; // sampling frequency (fps)
int _windowLen = 30 * 6; // window length to display - 6 seconds
CameraImage _image; // store the last camera image
double _avg; // store the average value during calculation
DateTime _now; // store the now Datetime
Timer _timer; // timer for image processing
@override
void initState() {
super.initState();
_animationController =
AnimationController(duration: Duration(milliseconds: 500), vsync: this);
_animationController
..addListener(() {
setState(() {
_iconScale = 1.0 + _animationController.value * 0.4;
});
});
}
@override
void dispose() {
_timer?.cancel();
_toggled = false;
_disposeController();
Wakelock.disable();
_animationController?.stop();
_animationController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: <Widget>[
Expanded(
flex: 1,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
flex: 1,
child: Padding(
padding: EdgeInsets.all(12),
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(18),
),
child: Stack(
fit: StackFit.expand,
alignment: Alignment.center,
children: <Widget>[
_controller != null && _toggled
? AspectRatio(
aspectRatio:
_controller.value.aspectRatio,
child: CameraPreview(_controller),
)
: Container(
padding: EdgeInsets.all(12),
alignment: Alignment.center,
color: Colors.grey,
),
Container(
alignment: Alignment.center,
padding: EdgeInsets.all(4),
child: Text(
_toggled
? "Cover both the camera and the flash with your finger"
: "Camera feed will display here",
style: TextStyle(
backgroundColor: _toggled
? Colors.white
: Colors.transparent),
textAlign: TextAlign.center,
),
)
],
),
),
),
),
Expanded(
flex: 1,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
"Estimated BPM",
style: TextStyle(fontSize: 18, color: Colors.grey),
),
Text(
(_bpm > 30 && _bpm < 150 ? _bpm.toString() : "--"),
style: TextStyle(
fontSize: 32, fontWeight: FontWeight.bold),
),
],
)),
),
],
)),
Expanded(
flex: 1,
child: Center(
child: Transform.scale(
scale: _iconScale,
child: IconButton(
icon:
Icon(_toggled ? Icons.favorite : Icons.favorite_border),
color: Colors.red,
iconSize: 128,
onPressed: () {
if (_toggled) {
_untoggle();
} else {
_toggle();
}
},
),
),
),
),
Expanded(
flex: 1,
child: Container(
margin: EdgeInsets.all(12),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(18),
),
color: Colors.black),
child: Chart(_data),
),
),
],
),
),
);
}
void _clearData() {
// create array of 128 ~= 255/2
_data.clear();
int now = DateTime.now().millisecondsSinceEpoch;
for (int i = 0; i < _windowLen; i++)
_data.insert(
0,
SensorValue(
DateTime.fromMillisecondsSinceEpoch(now - i * 1000 ~/ _fs), 128));
}
void _toggle() {
_clearData();
_initController().then((onValue) {
Wakelock.enable();
_animationController?.repeat(reverse: true);
setState(() {
_toggled = true;
});
// after is toggled
_initTimer();
_updateBPM();
});
}
void _untoggle() {
_disposeController();
Wakelock.disable();
_animationController?.stop();
_animationController?.value = 0.0;
setState(() {
_toggled = false;
});
}
void _disposeController() {
_controller?.dispose();
_controller = null;
}
Future<void> _initController() async {
try {
List _cameras = await availableCameras();
_controller = CameraController(_cameras.first, ResolutionPreset.low);
await _controller.initialize();
Future.delayed(Duration(milliseconds: 100)).then((onValue) {
// _controller.flash(true);
});
_controller.startImageStream((CameraImage image) {
_image = image;
});
} catch (Exception) {
debugPrint(Exception);
}
}
void _initTimer() {
_timer = Timer.periodic(Duration(milliseconds: 1000 ~/ _fs), (timer) {
if (_toggled) {
if (_image != null) _scanImage(_image);
} else {
timer.cancel();
}
});
}
void _scanImage(CameraImage image) {
_now = DateTime.now();
_avg =
image.planes.first.bytes.reduce((value, element) => value + element) /
image.planes.first.bytes.length;
if (_data.length >= _windowLen) {
_data.removeAt(0);
}
setState(() {
_data.add(SensorValue(_now, _avg));
});
}
void _updateBPM() async {
// Bear in mind that the method used to calculate the BPM is very rudimentar
// feel free to improve it :)
// Since this function doesn't need to be so "exact" regarding the time it executes,
// I only used the a Future.delay to repeat it from time to time.
// Ofc you can also use a Timer object to time the callback of this function
List<SensorValue> _values;
double _avg;
int _n;
double _m;
double _threshold;
double _bpm;
int _counter;
int _previous;
while (_toggled) {
_values = List.from(_data); // create a copy of the current data array
_avg = 0;
_n = _values.length;
_m = 0;
_values.forEach((SensorValue value) {
_avg += value.value / _n;
if (value.value > _m) _m = value.value;
});
_threshold = (_m + _avg) / 2;
_bpm = 0;
_counter = 0;
_previous = 0;
for (int i = 1; i < _n; i++) {
if (_values[i - 1].value < _threshold &&
_values[i].value > _threshold) {
if (_previous != 0) {
_counter++;
_bpm += 60 *
1000 /
(_values[i].time.millisecondsSinceEpoch - _previous);
}
_previous = _values[i].time.millisecondsSinceEpoch;
}
}
if (_counter > 0) {
_bpm = _bpm / _counter;
print(_bpm);
setState(() {
this._bpm = ((1 - _alpha) * this._bpm + _alpha * _bpm).toInt();
});
}
await Future.delayed(Duration(
milliseconds:
1000 * _windowLen ~/ _fs)); // wait for a new set of _data values
}
}
}
......@@ -7,44 +7,37 @@ import 'cameara_service.dart';
class invoker extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Face Mask Detector'),
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset('lib/assets/images/wear.gif'),
return Scaffold(
appBar: AppBar(
title: Text('Face Mask Detector'),
),
SizedBox(height: 20),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ElevatedButton(
onPressed: ()=> invokeCamera(),
child: Text('Detect Mask'),
),
],
),
],
),
);
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset('lib/assets/images/wear.gif'),
),
SizedBox(height: 20),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ElevatedButton(
onPressed: () => invokeCamera(),
child: Text('Detect Mask'),
),
],
),
],
),
);
}
Future<void> invokeCamera() async {
WidgetsFlutterBinding.ensureInitialized();
print(await Tflite.loadModel(
model: 'lib/assets/model.tflite', labels: "lib/assets/labels.txt"));
runApp(MaskDetectingApp(
cameras: await availableCameras(),
));
WidgetsFlutterBinding.ensureInitialized();
print(await Tflite.loadModel(
model: 'lib/assets/model.tflite', labels: "lib/assets/labels.txt"));
runApp(MaskDetectingApp(
cameras: await availableCameras(),
));
}
}
......@@ -24,7 +24,7 @@ dependencies:
flutter:
sdk: flutter
flutter_svg: ^0.22.0
camera: ^0.5.8+8
# camera: ^0.5.8+8
tflite: ^1.1.1
oktoast: ^2.3.2
......@@ -34,6 +34,14 @@ dependencies:
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
#add dependencies for heart rate module
charts_flutter: ^0.9.0
wakelock: ^0.1.4+1
camera:
git:
url: http://github.com/indikaNK/plugins.git
path: packages/camera/camera
dev_dependencies:
flutter_test:
sdk: flutter
......@@ -51,6 +59,7 @@ flutter:
assets:
- lib/assets/images/top_header.jpg
- lib/assets/images/hero.gif
- lib/assets/images/mino.jpg
- lib/assets/images/boralu.jpg
- lib/assets/images/map80.png
......@@ -59,7 +68,7 @@ flutter:
- lib/assets/images/coach80.png
- lib/assets/images/time80.png
- lib/assets/images/event80.png
- lib/assets/images/
- lib/assets/images/pulse.gif
- lib/assets/model.tflite
- lib/assets/labels.txt
- assets/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment