Skip to content
Snippets Groups Projects
Commit 9bb8abe2 authored by MuijzerF's avatar MuijzerF
Browse files

Sample Rate control + Splitted recording and viewing window size

Splitted recording and viewing window size
Removed overlay checkbox
Added sample rate pull down menu
Default scaling modified to link to 16bit raw values of TCO monitor.
parent 1ae02e65
No related branches found
No related tags found
No related merge requests found
Pipeline #142446 canceled
......@@ -10,6 +10,7 @@ import json
import os
import time
from typing import Optional, List
from time import sleep
from plot_decoder.plot_decoder import PlotDecoder
from ui.combo_box import ComboBox
......@@ -30,7 +31,8 @@ class MainWindow(QMainWindow):
LINECOLORS = ['y', 'm', 'r', 'g', 'c', 'w', 'b']
FRAME_TIME = 1.0 / 60.0 # Inverse of framerate
FRAME_TIME = 1.0 / 20.0 # Inverse of framerate
LOG_TIME = 1.0 # Log data every second
def __init__(self, *args, **kwargs):
"""Constructor"""
......@@ -51,20 +53,25 @@ class MainWindow(QMainWindow):
self.data: Optional[np.array] = None # Received data, each row is a channel
self.time: Optional[np.array] = None # Timestamps of each data column
self.data_points = 0 # Number of points recorded
self.data_size = 200 # Number of points in history
self.data_size = 1920 # Number of points in history for saving
self.plot_size = 320 # Number of points in plot
self.rate = -1
self.oversample = -1
self.time_offset = None # Time offset in microseconds
# When true, all plots should be combined in one plot
self.overlay = False
self.autoscale = True # Automatic y-scaling when true
self.y_scale = [-10.0, 10.0] # Y-scale values when not automatic
self.y_scale = [-10.0, 65536.0] # Y-scale values when not automatic
# The system time of the last frame update - Used to limit framerate
self.last_update = 0.0
self.last_update_status = 0.0
# Create property stubs
self.input_port = ComboBox()
self.button_port = QPushButton('Connect')
self.input_size = QLineEdit()
self.plot_input_size = QLineEdit()
self.input_overlay = QCheckBox()
self.input_autoscale = QCheckBox()
self.input_scale = {
......@@ -74,19 +81,25 @@ class MainWindow(QMainWindow):
self.input_render = QLineEdit()
self.layout_plots = pg.GraphicsLayoutWidget()
self.button_save = QPushButton('Save')
self.button_start = QPushButton('Start')
self.button_sample_rate = QPushButton('Sample Frequency (Hz)')
self.plots: List[pg.PlotItem] = [] # Start with empty plots
self.curves: List[pg.PlotDataItem] = []
self.build_ui_elements() # Put actual GUI together
self.setWindowTitle('uScope')
self.setWindowTitle('uScope for Technical Medicine v0.7')
icon_path = os.path.realpath('images/logo.ico')
self.setWindowIcon(QIcon(icon_path))
self.show()
self.set_channels(self.channels)
# Load previous settings
self.load_settings()
......@@ -107,15 +120,32 @@ class MainWindow(QMainWindow):
layout_settings.addRow(QLabel('Serial port:'), self.input_port)
self.button_port.setCheckable(True)
self.button_port.toggled.connect(self.on_connect_toggle)
#start stop
self.button_start.setCheckable(True)
self.button_start.toggled.connect(self.on_start_toggle)
self.button_start.setDisabled(True)
self.start_str = "<start>"
self.start_str_as_byte = str.encode(self.start_str)
self.stop_str = "<stop>"
self.stop_str_as_byte = str.encode(self.stop_str)
# Data size
# Data size (recorded samples)
self.input_size.setValidator(QIntValidator(5, 1000000))
self.input_size.setText(str(self.data_size))
layout_settings.addRow(QLabel('Samples:'), self.input_size)
layout_settings.addRow(QLabel('Recorded samples:'), self.input_size)
# Plot size (displayed samples)
self.plot_input_size.setValidator(QIntValidator(5, 10000))
self.plot_input_size.setText(str(self.plot_size))
layout_settings.addRow(QLabel('Plotted samples:'), self.plot_input_size)
# Overlay
self.input_overlay.setChecked(self.overlay)
layout_settings.addRow(QLabel('Overlay channels:'), self.input_overlay)
#layout_settings.addRow(QLabel('Overlay channels:'), self.input_overlay)
# Y-Scale
layout_scaling = QHBoxLayout()
......@@ -138,6 +168,7 @@ class MainWindow(QMainWindow):
# Attach top layout
layout_top.addLayout(layout_settings)
layout_top.addWidget(self.button_port)
layout_top.addWidget(self.button_start)
layout_main.addLayout(layout_top)
......@@ -146,17 +177,44 @@ class MainWindow(QMainWindow):
layout_main.addLayout(layout_bottom)
# Buttons
layout_buttons = QHBoxLayout()
menu_save = QMenu()
menu_save.addAction('Numpy')
menu_save.addAction('CSV')
menu_save.triggered.connect(self.on_save)
self.button_save.setMenu(menu_save)
# Sample Rate
#button_sample_rate
menu_sample_rate = QMenu()
menu_sample_rate.addAction('512 Hz')
menu_sample_rate.addAction('256 Hz')
menu_sample_rate.addAction('128 Hz')
menu_sample_rate.addAction('64 Hz')
menu_sample_rate.addAction('32 Hz')
menu_sample_rate.addAction('16 Hz')
menu_sample_rate.addAction('8 Hz')
menu_sample_rate.addAction('4 Hz')
menu_sample_rate.addAction('2 Hz')
menu_sample_rate.triggered.connect(self.on_sample_rate)
self.button_sample_rate.setMenu(menu_sample_rate)
layout_buttons.addWidget(self.button_sample_rate)
self.button_sample_rate.setDisabled(True)
layout_buttons.addWidget(self.button_save)
layout_main.addLayout(layout_buttons)
# Main window widget
self.setCentralWidget(main_widget)
......@@ -173,11 +231,18 @@ class MainWindow(QMainWindow):
self.serial.setPortName(port)
# If serial opened successfully
if self.serial.open(QSerialPort.ReadOnly):
if self.serial.open(QSerialPort.ReadWrite):
self.input_port.setDisabled(True)
self.input_size.setDisabled(True)
self.plot_input_size.setDisabled(True)
self.input_overlay.setDisabled(True)
self.input_autoscale.setDisabled(True)
self.input_autoscale.setDisabled(True)
self.button_start.setDisabled(False)
self.button_sample_rate.setDisabled(False)
self.start_recording()
else:
self.button_port.setChecked(False) # Undo toggle
......@@ -188,8 +253,31 @@ class MainWindow(QMainWindow):
else:
self.input_port.setDisabled(False)
self.input_size.setDisabled(False)
self.plot_input_size.setDisabled(False)
self.input_overlay.setDisabled(False)
self.input_autoscale.setDisabled(False)
self.button_start.setDisabled(True)
self.button_sample_rate.setDisabled(True)
@pyqtSlot(bool)
def on_start_toggle(self, checked: bool):
"""When the start button is pressed"""
self.button_start.setText('Stop' if checked else 'Start')
if checked:
self.serial.write(self.start_str_as_byte)
self.button_sample_rate.setDisabled(True)
self.input_size.setDisabled(True)
self.plot_input_size.setDisabled(True)
self.input_overlay.setDisabled(True)
self.input_autoscale.setDisabled(True)
else:
self.serial.write(self.stop_str_as_byte)
self.button_sample_rate.setDisabled(False)
@pyqtSlot(bool)
def on_autoscale_toggle(self, checked: bool):
......@@ -213,6 +301,69 @@ class MainWindow(QMainWindow):
for block in self.decoder.receive_bytes(bytearray(new_bytes)):
self.update_data(*block)
@pyqtSlot(QAction)
def on_sample_rate(self, action: QAction):
self.set_sample_rate(action.text())
def set_sample_rate(self, sample_rate_id: str):
"""Set Sample Rate and Oversampling"""
if sample_rate_id == '512 Hz':
self.rate = 65536
self.oversample = 128
elif sample_rate_id == '256 Hz':
self.rate = 32768
self.oversample = 128
elif sample_rate_id == '128 Hz':
self.rate = 16384
self.oversample = 128
elif sample_rate_id == '64 Hz':
self.rate = 8192
self.oversample = 128
elif sample_rate_id == '32 Hz':
self.rate = 32
self.oversample = 1
elif sample_rate_id == '16 Hz':
self.rate = 16
self.oversample = 1
elif sample_rate_id == '8 Hz':
self.rate = 8
self.oversample = 1
elif sample_rate_id == '4 Hz':
self.rate = 4
self.oversample = 1
elif sample_rate_id == '2 Hz':
self.rate = 2
self.oversample = 1
else: #default
self.rate = -1
self.oversample = -1
#stop sampling
self.stop_str = "<stop>"
self.stop_str_as_byte = str.encode(self.stop_str)
self.serial.write(self.stop_str_as_byte)
#first disable downsampling
self.downsample_1_str = "<SetDownSampleRate001>"
self.downsample_1_str_as_byte = str.encode(self.downsample_1_str)
self.serial.write(self.downsample_1_str_as_byte)
#then set the sample rate:
self.sample_rate_str = "<SetSampleRate" + str(self.rate) + ">"
self.sample_rate_str_as_byte = str.encode(self.sample_rate_str)
self.serial.write(self.sample_rate_str_as_byte)
#then set the oversample rate:
self.downsample_str = "<SetDownSampleRate" + str(self.oversample) + ">"
self.downsample_str_as_byte = str.encode(self.downsample_str)
self.serial.write(self.downsample_str_as_byte)
print("Sample Rate: " + str(self.rate) + " Hz, Oversample: " + str(self.oversample) + "x, effective rate: " + str(self.rate / self.oversample) + " Hz")
#clear data
self.data = np.zeros((self.channels, self.data_size))
self.time = np.zeros((1, self.data_size))
@pyqtSlot(QAction)
def on_save(self, action: QAction):
self.save_data(action.text())
......@@ -246,7 +397,7 @@ class MainWindow(QMainWindow):
data = np.vstack((self.time, self.data))
header = 'time [s]'
for i in range(self.channels):
header += ', Channel {}'.format(i)
header += '; Channel {}'.format(i)
np.savetxt(filename, data.transpose(),
delimiter=';', header=header)
......@@ -260,8 +411,10 @@ class MainWindow(QMainWindow):
self.input_port.setCurrentIndex(
self.input_port.findData(settings['port'])
)
if 'size' in settings and settings['size'] > 10:
if 'size' in settings and settings['size'] > 0:
self.input_size.setText(str(settings['size']))
if 'plotsize' in settings and settings['plotsize'] > 0:
self.plot_input_size.setText(str(settings['plotsize']))
if 'overlay' in settings:
self.input_overlay.setChecked(settings['overlay'])
if 'autoscale' in settings:
......@@ -282,6 +435,7 @@ class MainWindow(QMainWindow):
settings = {
'port': self.serial.portName(),
'size': self.data_size,
'plotsize': self.plot_size,
'overlay': self.overlay,
'autoscale': self.autoscale,
'y_scale_min': self.y_scale[0],
......@@ -314,7 +468,9 @@ class MainWindow(QMainWindow):
"""Called when recording should start (e.g. when `Connect` was hit)"""
self.channels = 0 # Force an update on the next data point
self.data_points = 0
self.data_size = int(self.input_size.text())
self.data_points_old = 0
self.data_size = int(self.input_size.text()) #* int(self.rate / self.oversample)
self.plot_size = int(self.plot_input_size.text()) #* int(self.rate / self.oversample)
self.overlay = self.input_overlay.isChecked()
self.autoscale = self.input_autoscale.isChecked()
self.y_scale = [
......@@ -323,6 +479,12 @@ class MainWindow(QMainWindow):
]
self.serial.clear() # Get rid of data in buffer
if(self.rate < 0 or self.oversample < 0): #configure the sample rate if it is unknown
self.serial.write(self.start_str_as_byte)
sleep(0.1)
self.serial.write(self.stop_str_as_byte)
self.set_sample_rate('64 Hz')
def update_data(self, channels: int, micros: int, new_data: list):
"""Called when new row was received"""
......@@ -348,13 +510,22 @@ class MainWindow(QMainWindow):
self.update_plots()
self.last_update = now
if now - self.last_update_status >= self.LOG_TIME: # status update
print("Samples received: " + str(self.data_points - self.data_points_old) + ".")
self.data_points_old = self.data_points
self.last_update_status = now
def update_plots(self):
"""With data already updated, update plots"""
if self.data_points < self.data_size:
if self.data_points < self.plot_size: #just started collecting data, less than plot size available, only plot the received data
data_x = self.time[:, -self.data_points:]
data_y = self.data[:, -self.data_points:]
else:
elif self.data_points > self.plot_size: # We have more data than plot size
data_x = self.time[:, -self.plot_size:]
data_y = self.data[:, -self.plot_size:]
else: #not used
data_x = self.time
data_y = self.data
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment