From b7e07c4df6965f129aaf66e791d3b361172e73fa Mon Sep 17 00:00:00 2001 From: Andrey Parfenov Date: Mon, 20 Jan 2020 16:12:11 +0100 Subject: [PATCH] add hardware test and measurement code for novaxr Signed-off-by: Andrey Parfenov --- brainflow_boards.json | 2 +- emulator/brainflow_emulator/novaxr_udp.py | 3 +- src/board_controller/openbci/novaxr.cpp | 21 ++++ tests/python/hardware_test.py | 123 ++++++++++++++++++++++ 4 files changed, 147 insertions(+), 2 deletions(-) create mode 100644 tests/python/hardware_test.py diff --git a/brainflow_boards.json b/brainflow_boards.json index fc1ea7837..400c5c3be 100644 --- a/brainflow_boards.json +++ b/brainflow_boards.json @@ -58,7 +58,7 @@ }, "3" : { "name": "NovaXR", - "sampling_rate": 1000, + "sampling_rate": 1940, "package_num_channel" : 0, "timestamp_channel" : 21, "num_rows" : 22, diff --git a/emulator/brainflow_emulator/novaxr_udp.py b/emulator/brainflow_emulator/novaxr_udp.py index 04c71a014..e420203af 100644 --- a/emulator/brainflow_emulator/novaxr_udp.py +++ b/emulator/brainflow_emulator/novaxr_udp.py @@ -78,7 +78,7 @@ def run (self): for _ in range (19): package.append (self.package_num) self.package_num = self.package_num + 1 - if self.package_num % 255 == 0: + if self.package_num % 256 == 0: self.package_num = 0 for i in range (1, self.package_size - 8): package.append (random.randint (0, 255)) @@ -89,6 +89,7 @@ def run (self): except socket.timeout: logging.info ('timeout for send') + def main (cmd_list): if not cmd_list: raise Exception ('No command to execute') diff --git a/src/board_controller/openbci/novaxr.cpp b/src/board_controller/openbci/novaxr.cpp index d05e8dfe1..f92dd9c0e 100644 --- a/src/board_controller/openbci/novaxr.cpp +++ b/src/board_controller/openbci/novaxr.cpp @@ -243,9 +243,18 @@ void NovaXR::read_thread () int res; unsigned char b[NovaXR::transaction_size]; + long counter = 0; + double recv_avg_time = 0; + double process_avg_time = 0; while (keep_alive) { + auto recv_start_time = std::chrono::high_resolution_clock::now (); res = socket->recv (b, NovaXR::transaction_size); + auto recv_stop_time = std::chrono::high_resolution_clock::now (); + auto recv_duration = + std::chrono::duration_cast (recv_stop_time - recv_start_time) + .count (); + if (res == -1) { #ifdef _WIN32 @@ -276,6 +285,7 @@ void NovaXR::read_thread () } } + auto processing_start_time = std::chrono::high_resolution_clock::now (); for (int cur_package = 0; cur_package < NovaXR::num_packages; cur_package++) { double package[NovaXR::num_channels] = {0.}; @@ -305,5 +315,16 @@ void NovaXR::read_thread () streamer->stream_data (package, NovaXR::num_channels, timestamp); db->add_data (timestamp, package); } + auto processing_stop_time = std::chrono::high_resolution_clock::now (); + auto processing_duration = std::chrono::duration_cast ( + processing_stop_time - processing_start_time) + .count (); + recv_avg_time += recv_duration / 1000.0; + process_avg_time += processing_duration / 1000.0; + counter++; } + recv_avg_time /= counter; + process_avg_time /= counter; + safe_logger (spdlog::level::trace, "recv avg time in ms {}", recv_avg_time); + safe_logger (spdlog::level::trace, "process avg time in ms {}", process_avg_time); } diff --git a/tests/python/hardware_test.py b/tests/python/hardware_test.py new file mode 100644 index 000000000..d02873e95 --- /dev/null +++ b/tests/python/hardware_test.py @@ -0,0 +1,123 @@ +import argparse +import time +import statistics + +import numpy as np +import pandas as pd +import matplotlib +import matplotlib.pyplot as plt + +import brainflow +from brainflow.board_shim import BoardShim, BrainFlowInputParams, BoardIds, LogLevels +from brainflow.data_filter import DataFilter, FilterTypes, AggOperations + + +def main (): + parser = argparse.ArgumentParser () + # use docs to check which parameters are required for specific board, e.g. for Cyton - set serial port + parser.add_argument ('--ip-port', type = int, help = 'ip port', required = False, default = 0) + parser.add_argument ('--ip-protocol', type = int, help = 'ip protocol, check IpProtocolType enum', required = False, default = 0) + parser.add_argument ('--ip-address', type = str, help = 'ip address', required = False, default = '') + parser.add_argument ('--serial-port', type = str, help = 'serial port', required = False, default = '') + parser.add_argument ('--mac-address', type = str, help = 'mac address', required = False, default = '') + parser.add_argument ('--other-info', type = str, help = 'other info', required = False, default = '') + parser.add_argument ('--streamer-params', type = str, help = 'other info', required = False, default = '') + parser.add_argument ('--board-id', type = int, help = 'board id, check docs to get a list of supported boards', required = True) + parser.add_argument ('--log', action = 'store_true') + parser.add_argument ('--run-time', type = int, help = 'run time in sec', required = True) + args = parser.parse_args () + + params = BrainFlowInputParams () + params.ip_port = args.ip_port + params.serial_port = args.serial_port + params.mac_address = args.mac_address + params.other_info = args.other_info + params.ip_address = args.ip_address + params.ip_protocol = args.ip_protocol + + if (args.log): + BoardShim.enable_dev_board_logger () + else: + BoardShim.disable_board_logger () + + # for streaming board need to use master board id + master_board_id = args.board_id + if args.board_id == BoardIds.STREAMING_BOARD.value: + master_board_id = int (params.other_info) + + board = BoardShim (args.board_id, params) + board.prepare_session () + + buffer_size = int (BoardShim.get_sampling_rate (master_board_id) * args.run_time * 1.2) # + 20% for safety + + board.start_stream (buffer_size, args.streamer_params) + time.sleep (args.run_time) + board.stop_stream () + data = board.get_board_data () + board.release_session () + + if master_board_id in (BoardIds.CYTON_BOARD.value, BoardIds.CYTON_WIFI_BOARD.value, BoardIds.GANGLION_WIFI_BOARD.value): + bytes_per_package = 33 + elif master_board_id in (BoardIds.CYTON_DAISY_BOARD, BoardIds.CYTON_DAISY_WIFI_BOARD.value): + bytes_per_package = 66 + elif master_board_id == BoardIds.SYNTHETIC_BOARD.value: + bytes_per_package = 104 + elif master_board_id == BoardIds.NOVAXR_BOARD.value: + bytes_per_package = 72 + else: + raise ValueError ('unsupported board') + + total_bytes_received = bytes_per_package * data.shape[1] + packages_per_sec = float (data.shape[1]) / float (args.run_time); + + timestamp_channel = BoardShim.get_timestamp_channel (master_board_id) + timestamp_array = data[timestamp_channel] + time_diff_array = list () + for i in range (0, timestamp_array.size - 1): + time_diff_array.append (timestamp_array[i + 1] - timestamp_array[i]) + + package_num_channel = BoardShim.get_package_num_channel (master_board_id) + package_num_array = data[package_num_channel] + lost_packages = 0 + expected = 0 + cur_id = 0 + while cur_id < package_num_array.size: + if expected == 256: + expected = 0 + if package_num_array[cur_id] != expected: + BoardShim.log_message (LogLevels.LEVEL_WARN.value, + 'package loss detected: position %d package_num value %d expected value %d' % (cur_id, package_num_array[cur_id], expected)) + lost_packages = lost_packages + 1 + else: + cur_id = cur_id + 1 + expected = expected + 1 + + package_loss = (lost_packages / data.shape[1]) * 100 + + BoardShim.log_message (LogLevels.LEVEL_INFO.value, '\nResults:\n') + BoardShim.log_message (LogLevels.LEVEL_INFO.value, 'package loss percent %f' % package_loss) + BoardShim.log_message (LogLevels.LEVEL_INFO.value, 'average time delta %f' % statistics.mean (time_diff_array)) + BoardShim.log_message (LogLevels.LEVEL_INFO.value, 'std deviation of time delta %f' % statistics.pstdev (time_diff_array)) + BoardShim.log_message (LogLevels.LEVEL_INFO.value, 'total packages received %d' % data.shape[1]) + BoardShim.log_message (LogLevels.LEVEL_INFO.value, 'packages per sec %f' % packages_per_sec) + BoardShim.log_message (LogLevels.LEVEL_INFO.value, 'total bytes received %d' % total_bytes_received) + + eeg_channels = BoardShim.get_eeg_channels (master_board_id) + emg_channels = BoardShim.get_emg_channels (master_board_id) + total_channels = eeg_channels + # for cyton/ganglion eeg_channels and emg_channels are the same array because we can not split it + # for novaxr its 2 different arrays, join them + for ch in emg_channels: + if ch not in total_channels: + total_channels.append (ch) + + df = pd.DataFrame (np.transpose (data)) + df[total_channels].to_csv ('eeg_emg_data.csv') + df.to_csv ('all_data.csv') + plt.figure () + df[total_channels].plot (subplots = True) + plt.show () + + +if __name__ == "__main__": + main ()