From 38c41ee1c3e47c04fee2e7a0fb15ab51fbe55b35 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Sat, 7 Nov 2020 17:52:56 -0500 Subject: [PATCH 1/4] Added signal validation --- .../src/application/eeg_data/data_cubit.dart | 28 ++++++- .../src/application/eeg_data/data_states.dart | 17 +++- .../eeg_data/i_eeg_data_repository.dart | 4 + .../src/domain/eeg_data/signal_result.dart | 1 + .../eeg_data_repository.dart | 74 ++++++++++++++---- mobile/lib/src/infrastructure/constants.dart | 5 ++ .../navigation/routes/router.dart | 8 +- .../record_sleep/record_sleep_guide_page.dart | 5 -- .../record_sleep_recording_page.dart | 54 ++++++++++++- .../record_sleep_validate_page.dart | 78 ++++++++++++------- .../pages/record_sleep/signal_section.dart | 64 +++++++++++++++ 11 files changed, 284 insertions(+), 54 deletions(-) create mode 100644 mobile/lib/src/domain/eeg_data/signal_result.dart create mode 100644 mobile/lib/src/presentation/pages/record_sleep/signal_section.dart diff --git a/mobile/lib/src/application/eeg_data/data_cubit.dart b/mobile/lib/src/application/eeg_data/data_cubit.dart index 825ed24c..d91e34b6 100644 --- a/mobile/lib/src/application/eeg_data/data_cubit.dart +++ b/mobile/lib/src/application/eeg_data/data_cubit.dart @@ -1,10 +1,10 @@ -import 'package:polydodo/src/domain/acquisition_device/i_acquisition_device_repository.dart'; -import 'package:polydodo/src/domain/eeg_data/i_eeg_data_repository.dart'; - import 'package:bloc/bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -part './data_states.dart'; +import 'package:polydodo/src/domain/acquisition_device/i_acquisition_device_repository.dart'; +import 'package:polydodo/src/domain/eeg_data/i_eeg_data_repository.dart'; +import 'package:polydodo/src/domain/eeg_data/signal_result.dart'; +import 'package:polydodo/src/application/eeg_data/data_states.dart'; class DataCubit extends Cubit { final IAcquisitionDeviceRepository _deviceRepository; @@ -25,4 +25,24 @@ class DataCubit extends Cubit { _deviceRepository.stopDataStream(); _eegDataRepository.stopRecordingFromStream(); } + + Future testSignal() async { + _eegDataRepository.initialize(); + _eegDataRepository.testSignal( + await _deviceRepository.startDataStream(), signalCallback); + + emit(DataStateTestSignalInProgress()); + } + + void signalCallback( + SignalResult channelOneResult, SignalResult channelOneTwot, + [Exception e]) { + _eegDataRepository.stopRecordingFromStream(); + + if (e != null) { + emit(DataStateTestSignalFailure(e)); + } else { + emit(DataStateTestSignalSuccess(channelOneResult, channelOneTwot)); + } + } } diff --git a/mobile/lib/src/application/eeg_data/data_states.dart b/mobile/lib/src/application/eeg_data/data_states.dart index 239ab2e6..4c9b0653 100644 --- a/mobile/lib/src/application/eeg_data/data_states.dart +++ b/mobile/lib/src/application/eeg_data/data_states.dart @@ -1,7 +1,22 @@ -part of './data_cubit.dart'; +import 'package:polydodo/src/domain/eeg_data/signal_result.dart'; abstract class DataState {} class DataStateInitial extends DataState {} class DataStateRecording extends DataState {} + +class DataStateTestSignalInProgress extends DataState {} + +class DataStateTestSignalSuccess extends DataState { + final SignalResult channelOneResult; + final SignalResult channelTwoResult; + + DataStateTestSignalSuccess(this.channelOneResult, this.channelTwoResult); +} + +class DataStateTestSignalFailure extends DataState { + final Exception cause; + + DataStateTestSignalFailure(this.cause); +} diff --git a/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart b/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart index 12c3781c..3c4dfa3d 100644 --- a/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart +++ b/mobile/lib/src/domain/eeg_data/i_eeg_data_repository.dart @@ -1,8 +1,12 @@ +import 'package:polydodo/src/domain/eeg_data/signal_result.dart'; + abstract class IEEGDataRepository { void initialize(); void createRecordingFromStream(Stream> stream); void stopRecordingFromStream(); + void testSignal(Stream> stream, + Function(SignalResult, SignalResult, [Exception]) callback); // todo: implement export and import void importData(); void exportData(); diff --git a/mobile/lib/src/domain/eeg_data/signal_result.dart b/mobile/lib/src/domain/eeg_data/signal_result.dart new file mode 100644 index 00000000..a8ade6a3 --- /dev/null +++ b/mobile/lib/src/domain/eeg_data/signal_result.dart @@ -0,0 +1 @@ +enum SignalResult { railed, near_railed, valid, untested } diff --git a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart index 44e1f54f..e8d697c5 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart @@ -1,11 +1,13 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'dart:typed_data'; import 'package:csv/csv.dart'; import 'package:path_provider/path_provider.dart'; import 'package:polydodo/src/domain/eeg_data/eeg_data.dart'; import 'package:polydodo/src/domain/eeg_data/i_eeg_data_repository.dart'; +import 'package:polydodo/src/domain/eeg_data/signal_result.dart'; import 'package:polydodo/src/domain/unique_id.dart'; import 'package:polydodo/src/infrastructure/eeg_data_transformers/baseOpenBCITransformer.dart'; import 'package:polydodo/src/infrastructure/eeg_data_transformers/cytonTransformer.dart'; @@ -16,19 +18,19 @@ import 'package:pedantic/pedantic.dart'; import 'package:intl/intl.dart'; class EEGDataRepository implements IEEGDataRepository { - EEGData _recordingData; - BaseOpenBCITransformer, List> currentStreamTransformer; - final GanglionTransformer, List> _ganglionTransformer = GanglionTransformer, List>.broadcast(); - final CytonTransformer, List> _cytonTransformer = CytonTransformer.broadcast(); + BaseOpenBCITransformer, List> currentStreamTransformer; BaseOpenBCITransformer, List> _currentTransformer; StreamSubscription _currentTransformerStream; - StreamingSharedPreferences _preferences; + EEGData _recordingData; + double _channelOneMaxValue = 0; + double _channelTwoMaxValue = 0; + int _dataCount = 0; @override void initialize() async { @@ -57,17 +59,59 @@ class EEGDataRepository implements IEEGDataRepository { Future stopRecordingFromStream() async { // todo: move save future to another file unawaited(_currentTransformerStream.cancel()); + if (_recordingData != null) { + final directory = await getExternalStorageDirectory(); + final pathOfTheFileToWrite = + directory.path + '/' + _recordingData.fileName + '.txt'; + var file = File(pathOfTheFileToWrite); + var fileContent = [[]]; + //todo: dynamically change header when we change transformer + fileContent.addAll(OPEN_BCI_CYTON_HEADER); + fileContent.addAll(_recordingData.values); + var csv = const ListToCsvConverter().convert(fileContent); + await file.writeAsString(csv); + } + } + + @override + void testSignal(Stream> stream, + Function(SignalResult, SignalResult, [Exception]) callback) { + _dataCount = 0; + _currentTransformer.reset(); + _currentTransformerStream = stream + .asBroadcastStream() + .transform(_currentTransformer) + .listen((data) => checkSignalData(data, callback)); + } + + void checkSignalData( + List data, Function(SignalResult, SignalResult, [Exception]) callback) { + _dataCount++; + + _channelOneMaxValue = max(_channelOneMaxValue, data[1].abs()); + _channelTwoMaxValue = max(_channelTwoMaxValue, data[2].abs()); + + if (_dataCount == 1000) { + print(_channelOneMaxValue); + print(_channelTwoMaxValue); + var signalOneResult = getResult(_channelOneMaxValue); + var signalTwoResult = getResult(_channelTwoMaxValue); + + callback(signalOneResult, signalTwoResult); + } + } + + SignalResult getResult(double maxValue) { + var result = SignalResult.valid; + print(maxValue); + print(MAX_SIGNAL_VALUE * THRESHOLD_RAILED_WARN); + if (maxValue > MAX_SIGNAL_VALUE * THRESHOLD_RAILED_WARN) { + result = (maxValue > MAX_SIGNAL_VALUE * THRESHOLD_RAILED) + ? SignalResult.railed + : SignalResult.near_railed; + } - final directory = await getExternalStorageDirectory(); - final pathOfTheFileToWrite = - directory.path + '/' + _recordingData.fileName + '.txt'; - var file = File(pathOfTheFileToWrite); - var fileContent = [[]]; - //todo: dynamically change header when we change transformer - fileContent.addAll(OPEN_BCI_CYTON_HEADER); - fileContent.addAll(_recordingData.values); - var csv = const ListToCsvConverter().convert(fileContent); - await file.writeAsString(csv); + return result; } // todo: implement export and import diff --git a/mobile/lib/src/infrastructure/constants.dart b/mobile/lib/src/infrastructure/constants.dart index 98848681..58ebdd21 100644 --- a/mobile/lib/src/infrastructure/constants.dart +++ b/mobile/lib/src/infrastructure/constants.dart @@ -1,3 +1,8 @@ +const THRESHOLD_RAILED = 0.9; +const THRESHOLD_RAILED_WARN = 0.75; + +const MAX_SIGNAL_VALUE = 185000; + const START_STREAM_CHAR = 'b'; const STOP_STREAM_CHAR = 's'; diff --git a/mobile/lib/src/presentation/navigation/routes/router.dart b/mobile/lib/src/presentation/navigation/routes/router.dart index 134c94ef..a4b0f1a3 100644 --- a/mobile/lib/src/presentation/navigation/routes/router.dart +++ b/mobile/lib/src/presentation/navigation/routes/router.dart @@ -2,9 +2,12 @@ import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route_annotations.dart'; import 'package:polydodo/src/presentation/pages/bluetooth_page/bluetoothSelector_page.dart'; import 'package:polydodo/src/presentation/pages/dashboard/dashboard_page.dart'; +import 'package:polydodo/src/presentation/pages/record_sleep/record_sleep_guide_page.dart'; +import 'package:polydodo/src/presentation/pages/record_sleep/record_sleep_recording_page.dart'; +import 'package:polydodo/src/presentation/pages/record_sleep/record_sleep_validate_page.dart'; + import 'package:polydodo/src/presentation/pages/sleep_history_page/sleep_history_page.dart'; import 'package:polydodo/src/presentation/pages/night_stats_page/night_stats_page.dart'; -import 'package:polydodo/src/presentation/pages/record_sleep/record_sleep_guide_page.dart'; @MaterialAutoRouter( generateNavigationHelperExtension: true, @@ -19,6 +22,9 @@ import 'package:polydodo/src/presentation/pages/record_sleep/record_sleep_guide_ CustomRoute( page: RecordSleepValidatePage, transitionsBuilder: TransitionsBuilders.fadeIn), + CustomRoute( + page: RecordSleepRecordingPage, + transitionsBuilder: TransitionsBuilders.fadeIn), CustomRoute( page: BluetoothSelectorPage, transitionsBuilder: TransitionsBuilders.fadeIn), diff --git a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_guide_page.dart b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_guide_page.dart index bb12f70d..b5dea6e1 100644 --- a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_guide_page.dart +++ b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_guide_page.dart @@ -1,14 +1,9 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; import 'package:polydodo/src/presentation/navigation/navdrawer_tabs.dart'; import 'package:polydodo/src/presentation/navigation/navdrawer_widget.dart'; import 'package:polydodo/src/presentation/navigation/routes/router.gr.dart'; -part 'record_sleep_recording_page.dart'; -part 'record_sleep_validate_page.dart'; - class RecordSleepGuidePage extends StatelessWidget { @override Widget build(BuildContext context) { diff --git a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_recording_page.dart b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_recording_page.dart index 5490f9d3..79da7bc3 100644 --- a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_recording_page.dart +++ b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_recording_page.dart @@ -1 +1,53 @@ -part of 'record_sleep_guide_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; +import 'package:polydodo/src/application/eeg_data/data_states.dart'; +import 'package:polydodo/src/presentation/navigation/navdrawer_tabs.dart'; +import 'package:polydodo/src/presentation/navigation/navdrawer_widget.dart'; + +class RecordSleepRecordingPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Record Sleep')), + drawer: NavDrawer(activeTab: NavdrawerTab.RecordSleep), + body: BlocConsumer( + listener: (context, state) { + print(state.runtimeType); + }, + builder: (context, state) { + if (state is DataStateTestSignalSuccess) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RaisedButton( + child: Text('Start'), + onPressed: () => + BlocProvider.of(context).startStreaming(), + ), + RaisedButton( + child: Text('Test Signal'), + onPressed: () => + BlocProvider.of(context).testSignal(), + ) + ]), + ); + } else { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RaisedButton( + child: Text('Stop'), + onPressed: () => + BlocProvider.of(context).stopStreaming(), + ), + ]), + ); + } + }, + ), + ); + } +} diff --git a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_validate_page.dart b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_validate_page.dart index b4ff4ac0..6a6d7869 100644 --- a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_validate_page.dart +++ b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_validate_page.dart @@ -1,43 +1,67 @@ -part of 'record_sleep_guide_page.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; +import 'package:polydodo/src/application/eeg_data/data_states.dart'; +import 'package:polydodo/src/domain/eeg_data/signal_result.dart'; +import 'package:polydodo/src/presentation/navigation/navdrawer_tabs.dart'; +import 'package:polydodo/src/presentation/navigation/navdrawer_widget.dart'; +import 'package:polydodo/src/presentation/navigation/routes/router.gr.dart'; +import 'package:polydodo/src/presentation/pages/record_sleep/signal_section.dart'; class RecordSleepValidatePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text('Record Sleep')), + appBar: AppBar( + backgroundColor: Colors.transparent, + shadowColor: Colors.transparent, + centerTitle: true, + iconTheme: IconThemeData(color: Colors.black), + ), drawer: NavDrawer(activeTab: NavdrawerTab.RecordSleep), body: BlocConsumer( listener: (context, state) { print(state.runtimeType); }, builder: (context, state) { - if (state is DataStateInitial) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - RaisedButton( - child: Text('Start'), - onPressed: () => - BlocProvider.of(context).startStreaming(), - ), - ]), - ); - } else { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - RaisedButton( - child: Text('Stop'), - onPressed: () => - BlocProvider.of(context).stopStreaming(), - ), - ]), - ); - } + return Center( + child: Column(children: [ + _buildValidationButton(context), + buildSignalSection(state), + if (state is DataStateTestSignalSuccess && + state.channelOneResult == SignalResult.valid && + state.channelTwoResult == SignalResult.valid) + _buildNextButton(context), + ]), + ); }, ), ); } } + +Widget _buildValidationButton(var context) { + return ButtonTheme( + minWidth: 200.0, + height: 200.0, + child: RaisedButton( + onPressed: () => BlocProvider.of(context).testSignal(), + elevation: 2.0, + color: Colors.white, + child: Text('Validate Signal', + style: TextStyle(color: Colors.blue, fontSize: 20.0)), + shape: CircleBorder(), + )); +} + +Widget _buildNextButton(var context) { + return Expanded( + child: Align( + alignment: Alignment.bottomRight, + child: FlatButton( + child: Text('Next', + style: TextStyle(color: Colors.blue, fontSize: 15.0)), + onPressed: () => ExtendedNavigator.of(context) + .replace(Routes.recordSleepRecordingPage)))); +} diff --git a/mobile/lib/src/presentation/pages/record_sleep/signal_section.dart b/mobile/lib/src/presentation/pages/record_sleep/signal_section.dart new file mode 100644 index 00000000..e1c74087 --- /dev/null +++ b/mobile/lib/src/presentation/pages/record_sleep/signal_section.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:polydodo/src/application/eeg_data/data_states.dart'; +import 'package:polydodo/src/domain/eeg_data/signal_result.dart'; + +Container buildSignalSection(var state) { + var channelOneResult = SignalResult.untested; + var channelTwoResult = SignalResult.untested; + + if (state is DataStateTestSignalSuccess) { + channelOneResult = state.channelOneResult; + channelTwoResult = state.channelTwoResult; + } + + return Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + _buildSignalInformation('Fpz-Cz', channelOneResult), + _buildSignalInformation('Pz-Oz', channelTwoResult) + ], + ), + ); +} + +Container _buildSignalInformation(var signalName, var signalResult) { + return Container( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [Text(signalName), _getIcon(signalResult)], + ), + _getErrorText(signalResult) + ], + ), + ); +} + +Icon _getIcon(var signalResult) { + switch (signalResult) { + case SignalResult.near_railed: + return Icon(Icons.warning, color: Colors.yellow[800]); + case SignalResult.railed: + return Icon(Icons.error, color: Colors.red); + case SignalResult.valid: + return Icon(Icons.check, color: Colors.green); + default: + return Icon(Icons.hourglass_empty, color: Colors.blue); + } +} + +Text _getErrorText(var signalResult) { + switch (signalResult) { + case SignalResult.near_railed: + return Text('Electrode is nearly railed'); + case SignalResult.railed: + return Text('Electrode is railed'); + case SignalResult.valid: + return Text('Valid Signal'); + default: + return Text(''); + } +} From 41b240007ec6b1030e21f7804130cde62b7f274e Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Sat, 7 Nov 2020 17:59:16 -0500 Subject: [PATCH 2/4] Made some functions private and removed uncessary prints --- .../eeg_data_repository.dart | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart index e8d697c5..119467fa 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart @@ -81,10 +81,10 @@ class EEGDataRepository implements IEEGDataRepository { _currentTransformerStream = stream .asBroadcastStream() .transform(_currentTransformer) - .listen((data) => checkSignalData(data, callback)); + .listen((data) => _checkSignalData(data, callback)); } - void checkSignalData( + void _checkSignalData( List data, Function(SignalResult, SignalResult, [Exception]) callback) { _dataCount++; @@ -92,19 +92,16 @@ class EEGDataRepository implements IEEGDataRepository { _channelTwoMaxValue = max(_channelTwoMaxValue, data[2].abs()); if (_dataCount == 1000) { - print(_channelOneMaxValue); - print(_channelTwoMaxValue); - var signalOneResult = getResult(_channelOneMaxValue); - var signalTwoResult = getResult(_channelTwoMaxValue); + var signalOneResult = _getResult(_channelOneMaxValue); + var signalTwoResult = _getResult(_channelTwoMaxValue); callback(signalOneResult, signalTwoResult); } } - SignalResult getResult(double maxValue) { + SignalResult _getResult(double maxValue) { var result = SignalResult.valid; - print(maxValue); - print(MAX_SIGNAL_VALUE * THRESHOLD_RAILED_WARN); + if (maxValue > MAX_SIGNAL_VALUE * THRESHOLD_RAILED_WARN) { result = (maxValue > MAX_SIGNAL_VALUE * THRESHOLD_RAILED) ? SignalResult.railed From 6b1456b959039bdac3683ea47bc9b0f4df82f0a6 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Sun, 8 Nov 2020 13:22:14 -0500 Subject: [PATCH 3/4] Made validation continuous --- .../src/application/eeg_data/data_cubit.dart | 18 ++++--- .../src/application/eeg_data/data_states.dart | 7 ++- .../eeg_data_repository.dart | 5 ++ .../record_sleep/record_sleep_guide_page.dart | 4 ++ .../record_sleep_recording_page.dart | 5 -- .../record_sleep_validate_page.dart | 50 ++++++++++++------- .../pages/record_sleep/signal_section.dart | 15 +++--- 7 files changed, 68 insertions(+), 36 deletions(-) diff --git a/mobile/lib/src/application/eeg_data/data_cubit.dart b/mobile/lib/src/application/eeg_data/data_cubit.dart index d91e34b6..3c196cce 100644 --- a/mobile/lib/src/application/eeg_data/data_cubit.dart +++ b/mobile/lib/src/application/eeg_data/data_cubit.dart @@ -26,23 +26,29 @@ class DataCubit extends Cubit { _eegDataRepository.stopRecordingFromStream(); } - Future testSignal() async { + Future startSignalValidation() async { _eegDataRepository.initialize(); _eegDataRepository.testSignal( await _deviceRepository.startDataStream(), signalCallback); - emit(DataStateTestSignalInProgress()); + emit(DataStateTestSignalSuccess(SignalResult.valid, SignalResult.valid)); } void signalCallback( - SignalResult channelOneResult, SignalResult channelOneTwot, + SignalResult channelOneResult, SignalResult channelTwoResult, [Exception e]) { - _eegDataRepository.stopRecordingFromStream(); - if (e != null) { emit(DataStateTestSignalFailure(e)); } else { - emit(DataStateTestSignalSuccess(channelOneResult, channelOneTwot)); + (channelOneResult == SignalResult.valid && + channelTwoResult == SignalResult.valid) + ? { + _eegDataRepository.stopRecordingFromStream(), + emit(DataStateTestSignalSuccess( + channelOneResult, channelTwoResult)) + } + : emit(DataStateTestSignalInProgress( + channelOneResult, channelTwoResult)); } } } diff --git a/mobile/lib/src/application/eeg_data/data_states.dart b/mobile/lib/src/application/eeg_data/data_states.dart index 4c9b0653..48b09281 100644 --- a/mobile/lib/src/application/eeg_data/data_states.dart +++ b/mobile/lib/src/application/eeg_data/data_states.dart @@ -6,7 +6,12 @@ class DataStateInitial extends DataState {} class DataStateRecording extends DataState {} -class DataStateTestSignalInProgress extends DataState {} +class DataStateTestSignalInProgress extends DataState { + final SignalResult channelOneResult; + final SignalResult channelTwoResult; + + DataStateTestSignalInProgress(this.channelOneResult, this.channelTwoResult); +} class DataStateTestSignalSuccess extends DataState { final SignalResult channelOneResult; diff --git a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart index 119467fa..39543339 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart @@ -59,6 +59,7 @@ class EEGDataRepository implements IEEGDataRepository { Future stopRecordingFromStream() async { // todo: move save future to another file unawaited(_currentTransformerStream.cancel()); + if (_recordingData != null) { final directory = await getExternalStorageDirectory(); final pathOfTheFileToWrite = @@ -96,6 +97,10 @@ class EEGDataRepository implements IEEGDataRepository { var signalTwoResult = _getResult(_channelTwoMaxValue); callback(signalOneResult, signalTwoResult); + + _dataCount = 0; + _channelOneMaxValue = 0; + _channelTwoMaxValue = 0; } } diff --git a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_guide_page.dart b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_guide_page.dart index b5dea6e1..9514770e 100644 --- a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_guide_page.dart +++ b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_guide_page.dart @@ -1,5 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:polydodo/src/application/blocs.dart'; import 'package:polydodo/src/presentation/navigation/navdrawer_tabs.dart'; import 'package:polydodo/src/presentation/navigation/navdrawer_widget.dart'; import 'package:polydodo/src/presentation/navigation/routes/router.gr.dart'; @@ -18,6 +20,8 @@ class RecordSleepGuidePage extends StatelessWidget { ), floatingActionButton: FloatingActionButton.extended( onPressed: () { + // todo: Place start validation at last page of guide or skip guide button + BlocProvider.of(context).startSignalValidation(); ExtendedNavigator.of(context).replace(Routes.recordSleepValidatePage); }, icon: Icon(Icons.radio_button_checked), diff --git a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_recording_page.dart b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_recording_page.dart index 79da7bc3..68306b51 100644 --- a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_recording_page.dart +++ b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_recording_page.dart @@ -25,11 +25,6 @@ class RecordSleepRecordingPage extends StatelessWidget { child: Text('Start'), onPressed: () => BlocProvider.of(context).startStreaming(), - ), - RaisedButton( - child: Text('Test Signal'), - onPressed: () => - BlocProvider.of(context).testSignal(), ) ]), ); diff --git a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_validate_page.dart b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_validate_page.dart index 6a6d7869..f0a49bc5 100644 --- a/mobile/lib/src/presentation/pages/record_sleep/record_sleep_validate_page.dart +++ b/mobile/lib/src/presentation/pages/record_sleep/record_sleep_validate_page.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:polydodo/src/application/eeg_data/data_cubit.dart'; import 'package:polydodo/src/application/eeg_data/data_states.dart'; -import 'package:polydodo/src/domain/eeg_data/signal_result.dart'; import 'package:polydodo/src/presentation/navigation/navdrawer_tabs.dart'; import 'package:polydodo/src/presentation/navigation/navdrawer_widget.dart'; import 'package:polydodo/src/presentation/navigation/routes/router.gr.dart'; @@ -27,11 +26,9 @@ class RecordSleepValidatePage extends StatelessWidget { builder: (context, state) { return Center( child: Column(children: [ - _buildValidationButton(context), + _buildValidationCircle(context, state), buildSignalSection(state), - if (state is DataStateTestSignalSuccess && - state.channelOneResult == SignalResult.valid && - state.channelTwoResult == SignalResult.valid) + if (state is DataStateTestSignalSuccess) _buildNextButton(context), ]), ); @@ -41,18 +38,37 @@ class RecordSleepValidatePage extends StatelessWidget { } } -Widget _buildValidationButton(var context) { - return ButtonTheme( - minWidth: 200.0, - height: 200.0, - child: RaisedButton( - onPressed: () => BlocProvider.of(context).testSignal(), - elevation: 2.0, - color: Colors.white, - child: Text('Validate Signal', - style: TextStyle(color: Colors.blue, fontSize: 20.0)), - shape: CircleBorder(), - )); +Widget _buildValidationCircle(var context, var state) { + return Center( + child: Stack(alignment: Alignment.center, children: [ + SizedBox(width: 200, height: 200, child: _buildProgressIndicator(state)), + _buildProgressIndicatorContent(state), + ])); +} + +Widget _buildProgressIndicator(var state) { + return (state is DataStateTestSignalSuccess) + ? CircularProgressIndicator( + strokeWidth: 10, + valueColor: AlwaysStoppedAnimation(Colors.green), + value: 100, + ) + : CircularProgressIndicator( + strokeWidth: 10, + ); +} + +Widget _buildProgressIndicatorContent(var state) { + return (state is DataStateTestSignalSuccess) + ? Icon( + Icons.check, + color: Colors.green, + size: 50, + ) + : Text( + 'Validating ...', + style: TextStyle(fontWeight: FontWeight.bold), + ); } Widget _buildNextButton(var context) { diff --git a/mobile/lib/src/presentation/pages/record_sleep/signal_section.dart b/mobile/lib/src/presentation/pages/record_sleep/signal_section.dart index e1c74087..dfe20728 100644 --- a/mobile/lib/src/presentation/pages/record_sleep/signal_section.dart +++ b/mobile/lib/src/presentation/pages/record_sleep/signal_section.dart @@ -3,10 +3,11 @@ import 'package:polydodo/src/application/eeg_data/data_states.dart'; import 'package:polydodo/src/domain/eeg_data/signal_result.dart'; Container buildSignalSection(var state) { - var channelOneResult = SignalResult.untested; - var channelTwoResult = SignalResult.untested; + var channelOneResult; + var channelTwoResult; - if (state is DataStateTestSignalSuccess) { + if (state is DataStateTestSignalInProgress || + state is DataStateTestSignalSuccess) { channelOneResult = state.channelOneResult; channelTwoResult = state.channelTwoResult; } @@ -37,16 +38,16 @@ Container _buildSignalInformation(var signalName, var signalResult) { ); } -Icon _getIcon(var signalResult) { +Widget _getIcon(var signalResult) { switch (signalResult) { case SignalResult.near_railed: return Icon(Icons.warning, color: Colors.yellow[800]); case SignalResult.railed: return Icon(Icons.error, color: Colors.red); - case SignalResult.valid: - return Icon(Icons.check, color: Colors.green); - default: + case SignalResult.untested: return Icon(Icons.hourglass_empty, color: Colors.blue); + default: + return Container(); } } From 8d63236718ee0dbaff2c4ba8945479d6d79f9114 Mon Sep 17 00:00:00 2001 From: MouradLachhab Date: Mon, 9 Nov 2020 14:08:21 -0500 Subject: [PATCH 4/4] Changed channel names and initialized channel value to invalid --- .../src/application/eeg_data/data_cubit.dart | 20 ++++----- .../src/application/eeg_data/data_states.dart | 6 +-- .../src/domain/eeg_data/signal_result.dart | 2 +- .../eeg_data_repository.dart | 42 +++++++++---------- .../pages/record_sleep/signal_section.dart | 14 +++---- 5 files changed, 41 insertions(+), 43 deletions(-) diff --git a/mobile/lib/src/application/eeg_data/data_cubit.dart b/mobile/lib/src/application/eeg_data/data_cubit.dart index 3c196cce..4a7e7d53 100644 --- a/mobile/lib/src/application/eeg_data/data_cubit.dart +++ b/mobile/lib/src/application/eeg_data/data_cubit.dart @@ -31,24 +31,22 @@ class DataCubit extends Cubit { _eegDataRepository.testSignal( await _deviceRepository.startDataStream(), signalCallback); - emit(DataStateTestSignalSuccess(SignalResult.valid, SignalResult.valid)); + emit(DataStateTestSignalInProgress( + SignalResult.untested, SignalResult.untested)); } void signalCallback( - SignalResult channelOneResult, SignalResult channelTwoResult, + SignalResult fpzCzChannelResult, SignalResult pzOzChannelResult, [Exception e]) { if (e != null) { emit(DataStateTestSignalFailure(e)); + } else if (fpzCzChannelResult == SignalResult.good && + pzOzChannelResult == SignalResult.good) { + _eegDataRepository.stopRecordingFromStream(); + emit(DataStateTestSignalSuccess(fpzCzChannelResult, pzOzChannelResult)); } else { - (channelOneResult == SignalResult.valid && - channelTwoResult == SignalResult.valid) - ? { - _eegDataRepository.stopRecordingFromStream(), - emit(DataStateTestSignalSuccess( - channelOneResult, channelTwoResult)) - } - : emit(DataStateTestSignalInProgress( - channelOneResult, channelTwoResult)); + emit( + DataStateTestSignalInProgress(fpzCzChannelResult, pzOzChannelResult)); } } } diff --git a/mobile/lib/src/application/eeg_data/data_states.dart b/mobile/lib/src/application/eeg_data/data_states.dart index 48b09281..be10fc83 100644 --- a/mobile/lib/src/application/eeg_data/data_states.dart +++ b/mobile/lib/src/application/eeg_data/data_states.dart @@ -14,10 +14,10 @@ class DataStateTestSignalInProgress extends DataState { } class DataStateTestSignalSuccess extends DataState { - final SignalResult channelOneResult; - final SignalResult channelTwoResult; + final SignalResult fpzCzChannelResult; + final SignalResult pzOzChannelResult; - DataStateTestSignalSuccess(this.channelOneResult, this.channelTwoResult); + DataStateTestSignalSuccess(this.fpzCzChannelResult, this.pzOzChannelResult); } class DataStateTestSignalFailure extends DataState { diff --git a/mobile/lib/src/domain/eeg_data/signal_result.dart b/mobile/lib/src/domain/eeg_data/signal_result.dart index a8ade6a3..e7415eb3 100644 --- a/mobile/lib/src/domain/eeg_data/signal_result.dart +++ b/mobile/lib/src/domain/eeg_data/signal_result.dart @@ -1 +1 @@ -enum SignalResult { railed, near_railed, valid, untested } +enum SignalResult { railed, near_railed, good, untested, invalid } diff --git a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart index 39543339..156cd279 100644 --- a/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart +++ b/mobile/lib/src/infrastructure/connection_repositories/eeg_data_repository.dart @@ -28,8 +28,8 @@ class EEGDataRepository implements IEEGDataRepository { StreamSubscription _currentTransformerStream; StreamingSharedPreferences _preferences; EEGData _recordingData; - double _channelOneMaxValue = 0; - double _channelTwoMaxValue = 0; + double _fpzCzChannelMax = 0; + double _pzOzChannelMax = 0; int _dataCount = 0; @override @@ -60,18 +60,18 @@ class EEGDataRepository implements IEEGDataRepository { // todo: move save future to another file unawaited(_currentTransformerStream.cancel()); - if (_recordingData != null) { - final directory = await getExternalStorageDirectory(); - final pathOfTheFileToWrite = - directory.path + '/' + _recordingData.fileName + '.txt'; - var file = File(pathOfTheFileToWrite); - var fileContent = [[]]; - //todo: dynamically change header when we change transformer - fileContent.addAll(OPEN_BCI_CYTON_HEADER); - fileContent.addAll(_recordingData.values); - var csv = const ListToCsvConverter().convert(fileContent); - await file.writeAsString(csv); - } + if (_recordingData == null) return; + + final directory = await getExternalStorageDirectory(); + final pathOfTheFileToWrite = + directory.path + '/' + _recordingData.fileName + '.txt'; + var file = File(pathOfTheFileToWrite); + var fileContent = [[]]; + //todo: dynamically change header when we change transformer + fileContent.addAll(OPEN_BCI_CYTON_HEADER); + fileContent.addAll(_recordingData.values); + var csv = const ListToCsvConverter().convert(fileContent); + await file.writeAsString(csv); } @override @@ -89,23 +89,23 @@ class EEGDataRepository implements IEEGDataRepository { List data, Function(SignalResult, SignalResult, [Exception]) callback) { _dataCount++; - _channelOneMaxValue = max(_channelOneMaxValue, data[1].abs()); - _channelTwoMaxValue = max(_channelTwoMaxValue, data[2].abs()); + _fpzCzChannelMax = max(_fpzCzChannelMax, data[1].abs()); + _pzOzChannelMax = max(_pzOzChannelMax, data[2].abs()); if (_dataCount == 1000) { - var signalOneResult = _getResult(_channelOneMaxValue); - var signalTwoResult = _getResult(_channelTwoMaxValue); + var signalOneResult = _getResult(_fpzCzChannelMax); + var signalTwoResult = _getResult(_pzOzChannelMax); callback(signalOneResult, signalTwoResult); _dataCount = 0; - _channelOneMaxValue = 0; - _channelTwoMaxValue = 0; + _fpzCzChannelMax = 0; + _pzOzChannelMax = 0; } } SignalResult _getResult(double maxValue) { - var result = SignalResult.valid; + var result = SignalResult.good; if (maxValue > MAX_SIGNAL_VALUE * THRESHOLD_RAILED_WARN) { result = (maxValue > MAX_SIGNAL_VALUE * THRESHOLD_RAILED) diff --git a/mobile/lib/src/presentation/pages/record_sleep/signal_section.dart b/mobile/lib/src/presentation/pages/record_sleep/signal_section.dart index dfe20728..d4989026 100644 --- a/mobile/lib/src/presentation/pages/record_sleep/signal_section.dart +++ b/mobile/lib/src/presentation/pages/record_sleep/signal_section.dart @@ -3,21 +3,21 @@ import 'package:polydodo/src/application/eeg_data/data_states.dart'; import 'package:polydodo/src/domain/eeg_data/signal_result.dart'; Container buildSignalSection(var state) { - var channelOneResult; - var channelTwoResult; + var fpzCzChannelResult = SignalResult.invalid; + var pzOzChannelResult = SignalResult.invalid; if (state is DataStateTestSignalInProgress || state is DataStateTestSignalSuccess) { - channelOneResult = state.channelOneResult; - channelTwoResult = state.channelTwoResult; + fpzCzChannelResult = state.fpzCzChannelResult; + pzOzChannelResult = state.pzOzChannelResult; } return Container( child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - _buildSignalInformation('Fpz-Cz', channelOneResult), - _buildSignalInformation('Pz-Oz', channelTwoResult) + _buildSignalInformation('Fpz-Cz', fpzCzChannelResult), + _buildSignalInformation('Pz-Oz', pzOzChannelResult) ], ), ); @@ -57,7 +57,7 @@ Text _getErrorText(var signalResult) { return Text('Electrode is nearly railed'); case SignalResult.railed: return Text('Electrode is railed'); - case SignalResult.valid: + case SignalResult.good: return Text('Valid Signal'); default: return Text('');