Commit dddd6ec3 authored by indika N kumara's avatar indika N kumara

Merge branch 'origin/dev_branch' into 'master'

Origin/dev branch

See merge request !6
parents b5fc977c 0b1a3edc
......@@ -35,7 +35,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.covidefender"
minSdkVersion 16
minSdkVersion 21
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
......@@ -48,6 +48,11 @@ android {
signingConfig signingConfigs.debug
}
}
aaptOptions {
noCompress 'tflite'
noCompress 'lite'
}
}
flutter {
......
with_mask
without_mask
\ No newline at end of file
import 'dart:async';
import 'package:flutter/material.dart';
import 'i_guider.dart';
class SplashScreen extends StatefulWidget {
@override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
@override
void initState() {
super.initState();
Timer(Duration(seconds: 3), () {
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => I_guider(),
));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue[400],
body: Center(
child: Text(
"I-GUIDER",
style: TextStyle(
fontSize: 50.0,
color: Colors.white,
),
),
),
);
}
}
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
}
}
}
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:oktoast/oktoast.dart';
import 'package:tflite/tflite.dart';
import 'camera_page.dart';
class MaskDetectingApp extends StatelessWidget {
const MaskDetectingApp({
@required List<CameraDescription> cameras,
}) : assert(cameras != null),
_cameras = cameras;
final List<CameraDescription> _cameras;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Face mask detecting',
theme: ThemeData(
brightness: Brightness.dark,
),
builder: (BuildContext context, Widget widget) => OKToast(
child: widget,
),
home: CameraPage(
cameras: _cameras,
),
);
}
}
import 'dart:math';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'overlay.dart' as ol;
import 'package:tflite/tflite.dart';
class CameraPage extends StatefulWidget {
const CameraPage({
@required List<CameraDescription> cameras,
}) : assert(cameras != null),
_cameras = cameras;
final List<CameraDescription> _cameras;
@override
_CameraPageState createState() => _CameraPageState();
}
class _CameraPageState extends State<CameraPage> with WidgetsBindingObserver {
CameraController _controller;
bool _isDetecting = false;
bool _rear = false;
List<dynamic> _recognitions;
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (_controller == null || !_controller.value.isInitialized) {
return;
}
if (state == AppLifecycleState.inactive) {
_controller?.dispose();
} else if (state == AppLifecycleState.resumed) {
if (_controller != null) {
_setupCamera();
}
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_controller?.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_setupCamera();
}
void _setupCamera() {
if (widget._cameras == null || widget._cameras.isEmpty) {
print('No camera is found');
} else {
_controller = CameraController(
widget._cameras[_rear ? 0 : 1],
ResolutionPreset.max,
);
_controller.initialize().then((_) {
if (_updateCamera()) {
_readFrames();
}
});
}
}
Future<void> _switchCameraLens() async {
_rear = !_rear;
await _controller?.dispose();
_setupCamera();
}
bool _updateCamera() {
if (!mounted) {
return false;
}
setState(() {});
return true;
}
void _updateRecognitions({
List<dynamic> recognitions,
}) {
setState(() {
_recognitions = recognitions;
});
}
void _readFrames() {
_controller.startImageStream(
(CameraImage img) {
if (!_isDetecting) {
_isDetecting = true;
Tflite.runModelOnFrame(
bytesList: img.planes.map((Plane plane) {
return plane.bytes;
}).toList(),
imageWidth: img.width,
imageHeight: img.height,
numResults: 2,
).then((List<dynamic> recognitions) {
_updateRecognitions(
recognitions: recognitions,
);
_isDetecting = false;
});
}
},
);
}
@override
Widget build(BuildContext context) {
if (_controller == null || !_controller.value.isInitialized) {
return Container();
}
final Size screen = MediaQuery.of(context).size;
final double screenH = max(screen.height, screen.width);
final double screenW = min(screen.height, screen.width);
final Size previewSize = _controller.value.previewSize;
final double previewH = max(previewSize.height, previewSize.width);
final double previewW = min(previewSize.height, previewSize.width);
final double screenRatio = screenH / screenW;
final double previewRatio = previewH / previewW;
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: FloatingActionButton(
onPressed: () async => _switchCameraLens(),
child: Icon(_rear ? Icons.camera_front : Icons.camera_rear),
backgroundColor: Colors.green,
),
body: Stack(
children: <Widget>[
OverflowBox(
maxHeight: screenRatio > previewRatio
? screenH
: screenW / previewW * previewH,
maxWidth: screenRatio > previewRatio
? screenH / previewH * previewW
: screenW,
child: CameraPreview(_controller),
),
ol.Overlay(
results: _recognitions ?? <dynamic>[],
)
],
),
);
}
}
import 'package:camera/camera.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:tflite/tflite.dart';
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'),
),
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(),
));
}
}
import 'dart:async';
import 'package:flutter/material.dart';
import 'toast.dart';
import 'package:oktoast/oktoast.dart';
class Overlay extends StatefulWidget {
const Overlay({
@required List<dynamic> results,
this.threshold = 0.5,
}) : _results = results;
final List<dynamic> _results;
final double threshold;
@override
_OverlayState createState() => _OverlayState();
}
class _OverlayState extends State<Overlay> {
ToastFuture _toastFuture;
String _label;
double _confidence = 0;
set label(String value) => setState(() {
_label = value;
});
set confidence(double value) => setState(() {
_confidence = value;
});
String get label => _label;
double get confidence => _confidence;
Color _updateBorderColor(
BuildContext context,
List<dynamic> bits,
) {
if (bits == null) {
return Colors.transparent;
}
if (bits.length > 1) {
final String firstLabel = bits.first["label"] as String;
final double firstConfidence = bits.first["confidence"] as double;
final String secondLabel = bits.last["label"] as String;
final double secondConfidence = bits.last["confidence"] as double;
if (firstConfidence > secondConfidence) {
label = firstLabel;
confidence = firstConfidence;
} else {
label = secondLabel;
confidence = secondConfidence;
}
}
if (bits.length == 1) {
label = bits.first["label"] as String;
confidence = bits.first["confidence"] as double;
}
if (confidence < widget.threshold) {
scheduleMicrotask(() => _toastFuture ??= Toast.show(
context,
ToastType.error,
"Please keep the camera steady and keep a certain distance to prevent shaking.",
onDismiss: () {
_toastFuture = null;
}));
return Colors.transparent;
}
if (label == "without_mask") {
return Colors.red;
}
if (label == "with_mask") {
return Colors.greenAccent;
}
return Colors.transparent;
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
border: Border.fromBorderSide(BorderSide(
color: _updateBorderColor(
context,
widget._results,
),
width: 10,
)),
),
),
Container(
margin: const EdgeInsets.symmetric(
horizontal: 85,
),
child: Column(
children: <Widget>[
SizedBox(
height: MediaQuery.of(context).padding.top,
),
Align(
alignment: Alignment.center,
child: Text(
label == "with_mask"
? "Wearing mask ${(confidence * 100).toStringAsFixed(0)}%"
: "No mask ${(confidence * 100).toStringAsFixed(0)}%",
style: Theme.of(context).textTheme.caption.copyWith(
color: label == "with_mask"
? Colors.greenAccent
: Colors.red,
),
),
),
Align(
alignment: Alignment.center,
child: LinearProgressIndicator(
value: confidence,
valueColor: AlwaysStoppedAnimation<Color>(
label == "with_mask" ? Colors.greenAccent : Colors.red),
minHeight: 15,
),
),
],
),
),
],
);
}
}
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:oktoast/oktoast.dart';
class SlideFadeInToastAnimation extends BaseAnimationBuilder {
@override
Widget buildWidget(
BuildContext context,
Widget child,
AnimationController controller,
double percent,
) {
final double opacity = min(1.0, percent + 0.2);
final double offset = (1 + percent) * 11;
return Opacity(
opacity: opacity,
child: Transform.translate(
child: child,
offset: Offset(0, offset),
),
);
}
}
import 'package:flutter/material.dart';
import 'slide_fade_in_toast_animation.dart';
import 'package:oktoast/oktoast.dart';
class Toast {
static const Duration kToastDuration = Duration(seconds: 3);
static ToastFuture show(
BuildContext context,
ToastType type,
String text, {
VoidCallback onDismiss,
Duration duration = kToastDuration,
bool dismissEnable = true,
}) {
return showToastWidget(
_buildToast(context, type, text, dismissEnable),
context: context,
position: const ToastPosition(align: Alignment.topCenter),
duration: dismissEnable ? duration : const Duration(days: 1),
handleTouch: true,
onDismiss: onDismiss,
animationBuilder: SlideFadeInToastAnimation(),
);
}
static void dismissAll({bool animated}) {
dismissAllToast(showAnim: animated);
}
static Widget _buildOkButton(BuildContext context) {
return Row(
children: <Widget>[
const SizedBox(
width: 10.0,
),
GestureDetector(
onTap: () => dismissAllToast(showAnim: true),
child: Text(
"OK",
style: Theme.of(context).textTheme.overline.copyWith(
color: Colors.white,
),
),
)
],
);
}
static Widget _buildToast(
BuildContext context,
ToastType type,
String text,
bool dismissEnable,
) {
return SafeArea(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 10.0),
child: Material(
color: Colors.black,
borderRadius: const BorderRadius.all(Radius.circular(10.0)),
child: Container(
padding:
const EdgeInsets.symmetric(vertical: 12.0, horizontal: 16.0),
child: Row(
children: <Widget>[
if (type == ToastType.success)
const Image(
image: AssetImage('lib/assets/toast_check_mark.png'),
width: 18.0,
height: 18.0,
)
else if (type == ToastType.error)
const Image(
image: AssetImage('lib/assets/toast_error.png'),
width: 18.0,
height: 18.0,
)
else
throw Exception("Wrong toast type!"),
const SizedBox(
width: 16.0,
),
Expanded(
child: Text(
text,
overflow: TextOverflow.clip,
style: Theme.of(context)
.textTheme
.bodyText1
.copyWith(color: Colors.white),
),
),
if (dismissEnable) _buildOkButton(context),
],
),
),
),
),
);
}
}
enum ToastType {
success,
error,
}
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