├── model ├── __init__.py ├── config.py ├── experiment.py ├── pftltools.py └── iostreamingdevice.py ├── view ├── __init__.py ├── config.py ├── customtheme.py └── GUI_twingo.py ├── .gitignore ├── docs └── graphics │ ├── launch_sequence.gif │ ├── example_harmonic.PNG │ ├── example_spectrum.PNG │ ├── example_spectrum2.PNG │ ├── continuous_sequence.gif │ ├── example_ timeseries.PNG │ ├── example_continuous.PNG │ ├── example_phase_scope.PNG │ ├── example_spectrogram.PNG │ └── hw_setup_soundcard.svg ├── LICENSE ├── environment.yml ├── README.md └── twingo.py /model/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /view/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | COMMANDS.txt 3 | *.docx 4 | *.pdf 5 | __pycache__/ 6 | *.db 7 | start.bat -------------------------------------------------------------------------------- /docs/graphics/launch_sequence.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjablons1/twingo/HEAD/docs/graphics/launch_sequence.gif -------------------------------------------------------------------------------- /docs/graphics/example_harmonic.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjablons1/twingo/HEAD/docs/graphics/example_harmonic.PNG -------------------------------------------------------------------------------- /docs/graphics/example_spectrum.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjablons1/twingo/HEAD/docs/graphics/example_spectrum.PNG -------------------------------------------------------------------------------- /docs/graphics/example_spectrum2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjablons1/twingo/HEAD/docs/graphics/example_spectrum2.PNG -------------------------------------------------------------------------------- /docs/graphics/continuous_sequence.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjablons1/twingo/HEAD/docs/graphics/continuous_sequence.gif -------------------------------------------------------------------------------- /docs/graphics/example_ timeseries.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjablons1/twingo/HEAD/docs/graphics/example_ timeseries.PNG -------------------------------------------------------------------------------- /docs/graphics/example_continuous.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjablons1/twingo/HEAD/docs/graphics/example_continuous.PNG -------------------------------------------------------------------------------- /docs/graphics/example_phase_scope.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjablons1/twingo/HEAD/docs/graphics/example_phase_scope.PNG -------------------------------------------------------------------------------- /docs/graphics/example_spectrogram.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjablons1/twingo/HEAD/docs/graphics/example_spectrogram.PNG -------------------------------------------------------------------------------- /model/config.py: -------------------------------------------------------------------------------- 1 | default_start_frequency = 20 2 | default_frequency = 1000 3 | default_function_type = 'sine' 4 | 5 | default_monitor_len = 2**15 6 | max_monitor_frame_len = 2**16 7 | default_finite_frame_len_sec = 1 8 | 9 | default_cm_sp_window_type = 'blackmanharris' 10 | default_cm_sp_window_size = default_monitor_len 11 | 12 | default_fm_sp_window_type = 'blackmanharris' 13 | default_fm_sp_window_size = 2**15 14 | 15 | default_cm_ph_window_size = 2**12 16 | 17 | default_fm_spg_chan = 0 18 | default_fm_spg_window_type = 'flattop' 19 | default_fm_spg_window_size = 2**11 20 | 21 | spg_window_overlap_skip_pts = 128 22 | 23 | default_pyAudio_paFloat32_level_range = [1.0, -1.0] 24 | 25 | pyaudio_read_offset_msec = 35 # Allows to manually adjust the input and output start synchronization for pyAudio dev. -------------------------------------------------------------------------------- /view/config.py: -------------------------------------------------------------------------------- 1 | PLOT_COLOR_MODIFIER_INT = 1 2 | PHASE_PLOT_COLOR_MODIFIER = 5 3 | PHASE_PLOT_HOLD_COLOR_MODIFIER = 6 4 | PHASE_PLOT_LIMIT_COLOR_MODIFIER = 'r' 5 | PHASE_PLOT_OPTIMAL_WIDTH = 2.5/7 # Classical Recording - A Practical Guide in the Decca Tradition, C.Haigh et.al. 6 | NR_OF_CHANNELS_TO_PLOT = 2 # min 1, max 2 7 | STATUS_BAR_MESSAGE_TIME_MS = 2500 8 | IO_BUFFER_INDICATOR_REFRESH_MSEC = 250 9 | FM_SP_GRAPH_MIN_dB_LIMIT = -120 10 | FM_SP_GRAPH_MAX_dB_LIMIT = 1 11 | CM_SP_GRAPH_MIN_dB_LIMIT = -120 12 | CM_SP_GRAPH_MAX_dB_LIMIT = 1 13 | MIN_OUTPUT_FREQUENCY_ALLOWED = 1 14 | MAX_COARSE_FREQ_DIAL_LIMIT = 20000 15 | SPG_FREQUENCY_LIMIT = 20000 16 | default_spg_hist_gradient = {'mode': 'rgb', 'ticks': [(1.0, (246, 255, 255, 255)), 17 | (0.8, (246, 246, 0, 255)), 18 | (0.5, (236, 0, 134, 255)), 19 | (0.2, (7, 0, 220, 255)), 20 | (0.0, (0, 0, 0, 0))]} 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Michal Jablonski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /view/customtheme.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | from PyQt5.QtGui import QPalette, QColor 3 | 4 | def apply_dark_theme(app): 5 | app.setStyle("Fusion") 6 | palette = QPalette() 7 | palette.setColor(QPalette.Window, QColor(53, 53, 53)) 8 | palette.setColor(QPalette.WindowText, QtCore.Qt.white) 9 | palette.setColor(QPalette.Base, QColor(25, 25, 25)) 10 | palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53)) 11 | palette.setColor(QPalette.ToolTipBase, QtCore.Qt.white) 12 | palette.setColor(QPalette.ToolTipText, QtCore.Qt.white) 13 | palette.setColor(QPalette.Text, QtCore.Qt.white) 14 | palette.setColor(QPalette.Button, QColor(53, 53, 53)) 15 | palette.setColor(QPalette.ButtonText, QtCore.Qt.white) 16 | palette.setColor(QPalette.BrightText, QtCore.Qt.red) 17 | palette.setColor(QPalette.Link, QColor(42, 130, 218)) 18 | palette.setColor(QPalette.Highlight, QColor(42, 130, 218)) 19 | palette.setColor(QPalette.HighlightedText, QtCore.Qt.black) 20 | palette.setColor(QPalette.Disabled, QPalette.WindowText, QtCore.Qt.gray) 21 | palette.setColor(QPalette.Disabled, QPalette.Text, QtCore.Qt.gray) 22 | palette.setColor(QPalette.Disabled, QPalette.ButtonText, QtCore.Qt.gray) 23 | app.setPalette(palette) 24 | 25 | #Thanks to: https://stackoverflow.com/questions/48256772/dark-theme-for-qt-widgets @ Michael Herrmann 26 | 27 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: twingo_conda_env 2 | channels: 3 | - anaconda 4 | - defaults 5 | - default 6 | - conda-forge 7 | dependencies: 8 | - blas=1.0=mkl 9 | - ca-certificates=2020.6.24=0 10 | - certifi=2020.6.20=py37_0 11 | - icc_rt=2019.0.0=h0cc432a_1 12 | - icu=64.2=he025d50_1 13 | - intel-openmp=2019.4=245 14 | - jpeg=9d=he774522_0 15 | - libblas=3.8.0=14_mkl 16 | - libcblas=3.8.0=14_mkl 17 | - libclang=9.0.1=default_hf44288c_0 18 | - liblapack=3.8.0=14_mkl 19 | - libpng=1.6.37=hfe6a214_1 20 | - mkl=2019.4=245 21 | - mkl-service=2.3.0=py37hfa6e2cd_0 22 | - nidaqmx-python=0.5.7=py_3 23 | - numpy=1.18.5=py37hae9e721_0 24 | - openssl=1.1.1g=he774522_0 25 | - pip=20.1.1=py_1 26 | - portaudio=19.6.0=he774522_4 27 | - pyaudio=0.2.11=py37he774522_2 28 | - pyqt=5.12.3 29 | - pyqtgraph=0.11.0=pyh9f0ad1d_0 30 | - python=3.7.6=cpython_h60c2a47_6 31 | - python_abi=3.7=1_cp37m 32 | - qt=5.12.5=h7ef1ec2_0 33 | - scipy=1.5.0=py37h9439919_0 34 | - setuptools=49.2.0=py37hc8dfbb8_0 35 | - six=1.15.0=pyh9f0ad1d_0 36 | - sqlite=3.32.3=he774522_1 37 | - vc=14.1=h869be7e_1 38 | - vs2015_runtime=14.16.27012=h30e32a0_2 39 | - wheel=0.34.2=py_1 40 | - wincertstore=0.2=py37_1003 41 | - zlib=1.2.11=h2fa13f4_1006 42 | #- pip: 43 | # - pyqt5-sip==4.19.18 44 | # - pyqt5-stubs==5.14.2.2 45 | # - pyqtchart==5.12 46 | # - pyqtwebengine==5.12.1 47 | prefix: C:\Users\Z40\Anaconda3\envs\twingo_conda_env 48 | 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twingo 2 | Twingo is a simple nidaqmx / pyAudio based, 2 channel speaker measurement application supporting continuous and finite test signals generation, acquisition and analysis. 3 | 4 | ![Twingo_demo](docs/graphics/continuous_sequence.gif) 5 | 6 | # Installation 7 | The easiest way to install will be to use Anaconda python distribution and by creating a conda environment. 8 | 9 | 1. Clone the project from GitHub 10 | 2. In Anaconda prompt navigate to the cloned project folder and setup conda environment from the YAML file: 11 | 12 | `conda env create -f environment.yml` 13 | 3. At this point you should be able to launch the application in the environment created: 14 | 15 | `conda activate twingo_conda_env` 16 | 17 | `python twingo.py` 18 | 19 | _NOTE: You will not be able to install pyAudio easily using pip because it can't handle the C binaries (whatever they are) that have to go with it. This is why you do not find the familiar requirements.txt file in this repo._ 20 | 21 | --- 22 | If you wish to set up the project in pyCharm additionally follow below steps: 23 | 24 | 1. Create new project 25 | 2. Select project folder location but _do not click_ "Create" yet 26 | 3. Instead, expand "Project Interpreter" selection 27 | - Select "Existing Interpreter" 28 | - Choose the newly created conda env (twingo_conda_env) from the drop-down 29 | 4. click "Create" 30 | You can see that you were successful by noting that in terminal the start of each line reads (twingo_conda_env) i.e. environment is activated. 31 | 32 | # Usage and capabilities 33 | There are two types of hardware that twingo should support right out of the box. 34 | 35 | 1. Your NIDAQmx USB based National instruments data acquisition card 36 | 37 | I could only test my private NI6211 M series DAQ so far but the code may well work (or require little adjustments) with any other NIDAQmx driver-based National Instruments card as long as its equipped with both analog input and output ports. 38 | 39 | ![hw_setup_NIDAQmx](docs/graphics/hw_setup_NIDAQmx.svg) 40 | 41 | 2. Your current system default sound device input and output 42 | 43 | This is later added feature and still requires much improvement but it expands the usability of the tool to any input and output sound hardware *currently set default in your system*. 44 | This way its possible to use for instance: 45 | - your sound card I/O, 46 | - external audio interfaces 47 | - Focusrite Scarlett series, e.g. 2i2 or 4i4, 48 | - Steinberg UR series, 49 | - NOTE: only first two I/O's will be functional and you may have to adjust pyaudio_read_offset_msec under model/config in order to synchronize the input and output. 50 | - external Digital/Analog converters, 51 | - bluetooth speakers. 52 | 53 | It should work likewise on Linux and Mac, let me know! 54 | 55 | ![hw_setup_NIDAQmx](docs/graphics/hw_setup_soundcard.svg) 56 | 57 | _NOTE: Your system sound drivers often have various DSP effects enabled by default that can cause strange results (most frequent offender are equalizers, echo cancellation which attempts to remove your test signal from the output since it correlates well with the input or noise cancellation that attempt to dampen white noise test) Usually though, its possible to find the sound driver config application in your system and disable these effects._ 58 | 59 | ## Example analysis: 60 | 1. High resolution harmonic distortion analysis (see demo at the top of this readme) 61 | 2. Frequency response analysis with swept or white noise signal 62 | 63 | ![example_spectrum](docs/graphics/example_spectrum.PNG) 64 | 3. Spectrogram for speaker / room ringing 65 | 66 | ![example_spectrogram](docs/graphics/example_spectrogram.PNG) 67 | 68 | 4. Phase scope to test phase matching between speaker or microphone pairs, or setup stereo image width for instrument recordings. 69 | 70 | ![example_phasescope](docs/graphics/example_phase_scope.PNG) 71 | 72 | # Note 73 | This project is my first real OOP application after several years spent just on scientific scripting once in a while, therefore i expect that a lot of my code can be improved with your support and feedback. Thanks! 74 | 75 | The ESS measurement method is still experimental and you should not trust the results too much. 76 | 77 | # Acknowledgements 78 | - ["Python For the Lab" book by Aquiles Carattino](https://www.pythonforthelab.com/books/) 79 | (without this book I'd have no clue how to structure my project) 80 | - [https://www.schlameel.com](https://www.schlameel.com/2017/06/09/interleaving-and-de-interleaving-data-with-python/) & [schlameel/interleave.py](https://gist.github.com/schlameel/2ee338c35c72bedcdda58dcb121f2786) 81 | - [Exponential Sine Sweep article from Will Fehlhaber](https://theaudioprogrammer.com/signal-analysis-ii-linear-vs-logarithmic-sine-sweep/) 82 | - [Qt Dark Theme by Michael Herrmann](https://github.com/pyqt/examples/tree/_/src/09%20Qt%20dark%20theme) 83 | - Classical Recording - A Practical Guide in the Decca Tradition, C.Haigh et.al. 84 | -------------------------------------------------------------------------------- /model/experiment.py: -------------------------------------------------------------------------------- 1 | from scipy import signal as sig 2 | import numpy as np 3 | 4 | from model import config 5 | from model.pftltools import optimal_noverlap, rescale 6 | 7 | 8 | class Experiment: 9 | def __init__(self, streaming_device): 10 | 11 | self.streaming_device = streaming_device 12 | self.streaming_device.function_gen.set_frequency(config.default_frequency) 13 | self.streaming_device.function_gen.set_start_frequency(config.default_start_frequency) 14 | self.streaming_device.function_gen.set_function(config.default_function_type) 15 | 16 | # cm experiment properties: 17 | self._cm_win = None 18 | self._cm_db_ref = None 19 | self.cm_freq_base = None 20 | self.cm_sp_window_type = None 21 | self.cm_sp_window_size = None 22 | self.cm_ph_window_size = None 23 | self.set_cm_sp_window(win_type=config.default_cm_sp_window_type, win_size=config.default_cm_sp_window_size) 24 | self.set_cm_ph_window(win_size=config.default_cm_ph_window_size) 25 | 26 | # fm experiment properties: 27 | self.optimal_noverlap = None 28 | self.fm_sp_window_type = None 29 | self.fm_sp_window_size = None 30 | self.set_fm_sp_window(win_type=config.default_fm_sp_window_type, win_size=config.default_fm_sp_window_size) 31 | 32 | self.fm_spg_chan = None 33 | self.set_fm_spg_chan(config.default_fm_spg_chan) 34 | self.fm_spg_window_type = None 35 | self.fm_spg_window_size = None 36 | self.set_fm_spg_window(win_type=config.default_fm_spg_window_type, win_size=config.default_fm_spg_window_size) 37 | 38 | # arrays for ess measurement data storage 39 | self.exp_sweep_sine_response = None 40 | self.inverse_decay_filter_response = None 41 | self.ess_time_base = None 42 | 43 | self.fm_result_x = None 44 | self.fm_result_y = None 45 | 46 | self.rot_matrix_45 = self.calculate_rotation_matrix(45) 47 | 48 | def set_cm_sp_window(self, win_type=None, win_size=None): 49 | if win_type is not None: 50 | self.cm_sp_window_type = win_type 51 | if win_size is not None: 52 | if win_size < self.streaming_device.input_frame_len: 53 | raise ValueError('>> EXCEPTION << Win size < input frame len.') 54 | self.cm_sp_window_size = win_size 55 | self.streaming_device.set_monitor(win_size) 56 | self.set_cm_freq_base() 57 | 58 | self._cm_win = sig.get_window(self.cm_sp_window_type, self.cm_sp_window_size) 59 | self._cm_db_ref = np.sum(self._cm_win) * self.streaming_device.ai_max_val 60 | 61 | def set_cm_ph_window(self, win_size=None): 62 | self.cm_ph_window_size = win_size 63 | self.streaming_device.set_monitor(win_size) 64 | 65 | def set_cm_freq_base(self): 66 | self.cm_freq_base = np.fft.rfftfreq(self.cm_sp_window_size, 67 | d=1 / self.streaming_device.ai_fs) 68 | 69 | def set_fm_sp_window(self, win_type=None, win_size=None): 70 | if win_type is not None: 71 | self.fm_sp_window_type = win_type 72 | if win_size is not None: 73 | self.fm_sp_window_size = win_size 74 | 75 | self.optimal_noverlap = optimal_noverlap(self.fm_sp_window_type, self.fm_sp_window_size) 76 | 77 | def set_fm_spg_chan(self, chan): 78 | self.fm_spg_chan = chan 79 | 80 | def set_fm_spg_window(self, win_type=None, win_size=None): 81 | if win_type is not None: 82 | self.fm_spg_window_type = win_type 83 | if win_size is not None: 84 | self.fm_spg_window_size = win_size 85 | 86 | # def set_mode_to_finite(self, meas_time_sec): 87 | # self.streaming_device.set_mode_to_finite(meas_time_sec) 88 | # self.start_experiment = self.start_fm_experiment 89 | # 90 | # def set_mode_to_continuous(self): 91 | # self.streaming_device.set_mode_to_continuous() 92 | # self.start_experiment = self.start_cm_experiment 93 | 94 | # def start_cm_experiment(self): 95 | # if self.streaming_device.get_mode() != 'continuous': 96 | # self.streaming_device.set_mode_to_continuous() 97 | # print('issue #109 track here') 98 | # 99 | # self.streaming_device.io_start() 100 | 101 | def start_fm_experiment(self): 102 | if self.streaming_device.get_mode() != 'finite': 103 | print('\nOverriding streaming device mode to finite.\n') 104 | self.streaming_device.set_mode_to_finite(self.streaming_device.finite_frame_len_sec) 105 | 106 | self.streaming_device.io_start() 107 | # TODO so what was the point of making this into a 2xN matrix 108 | self.fm_result_x = self.streaming_device.input_time_base[0] 109 | self.fm_result_y = self.streaming_device.input_frame 110 | 111 | def start_fm_ess_experiment(self): 112 | if self.streaming_device.get_mode() != 'finite': 113 | print('\nOverriding streaming device mode to finite.\n') 114 | self.streaming_device.set_mode_to_finite(self.streaming_device.finite_frame_len_sec) 115 | 116 | if self.streaming_device.function_gen.get_function() != 'ess': 117 | self.streaming_device.function_gen.set_function('ess') 118 | 119 | self.streaming_device.io_start() 120 | self.exp_sweep_sine_response = self.streaming_device.input_frame 121 | self.streaming_device.io_start() 122 | self.inverse_decay_filter_response = self.streaming_device.input_frame 123 | self.fm_result_x = np.arange(self.streaming_device.input_frame_len * 2, 124 | dtype=np.float64) / self.streaming_device.ai_fs 125 | self.fm_result_y = np.concatenate((self.exp_sweep_sine_response, 126 | self.inverse_decay_filter_response), axis=1) 127 | 128 | def calculate_cm_fft(self): 129 | input_frame = self.streaming_device.get_monitor() * self._cm_win 130 | cm_fft = np.fft.rfft(input_frame) 131 | cm_fft = np.abs(cm_fft) * 2 / self._cm_db_ref 132 | cm_db_fft = 20 * np.log10(cm_fft) 133 | 134 | return cm_db_fft 135 | 136 | def calculate_rotation_matrix(self, angle_deg): # TODO move to pftltools 137 | beta_rad = np.pi * angle_deg/180 138 | rot_matrix = np.array([[np.cos(beta_rad), -np.sin(beta_rad)], 139 | [np.sin(beta_rad), np.cos(beta_rad)]]) 140 | 141 | return rot_matrix 142 | 143 | def calculate_cm_phase(self): 144 | input_frame = self.streaming_device.get_monitor() 145 | phase_frame = np.dot(self.rot_matrix_45, np.flipud(input_frame)) 146 | return phase_frame.T 147 | 148 | def calculate_fm_fft(self, input_frame=None): 149 | if input_frame is None: 150 | input_frame = self.fm_result_y 151 | 152 | if self.fm_sp_window_size > max(self.fm_result_y.shape): 153 | raise ValueError(f'>> EXCEPTION << : FFT window {self.fm_sp_window_size}' 154 | f' pts > input frame length {self.fm_result_y.shape} pts.') 155 | 156 | fm_freq_base, fm_fft_segment_time, fm_fft = sig.stft(input_frame, 157 | fs=self.streaming_device.ai_fs, 158 | window=self.fm_sp_window_type, 159 | nperseg=self.fm_sp_window_size, 160 | noverlap=self.optimal_noverlap, 161 | padded=None, boundary=None) 162 | 163 | fm_db_fft = np.average(2 * np.abs(fm_fft), axis=2) 164 | fm_db_fft = 20 * np.log10(fm_db_fft / self.streaming_device.ai_max_val) 165 | 166 | return fm_freq_base, fm_fft_segment_time, fm_db_fft 167 | 168 | def calculate_ir_from_fm_ess(self): 169 | ess_impulse_response = sig.fftconvolve(self.exp_sweep_sine_response, 170 | self.inverse_decay_filter_response, 171 | mode='same', 172 | axes=1) 173 | ess_impulse_response = rescale(ess_impulse_response, 174 | self.streaming_device.ai_max_val) 175 | # TODO On this line relative scaling between measurements is different. We should be rescaling by some 176 | # calculated constant, not to HW limit. 177 | delta_f = self.streaming_device.function_gen.freq1 - self.streaming_device.function_gen.freq0 178 | db_ref = np.sqrt(self.streaming_device.finite_frame_len_sec / (2 * delta_f)) # TODO this is not exact 179 | ess_impulse_response /= db_ref 180 | return ess_impulse_response 181 | 182 | def calculate_fm_spectrogram(self): 183 | 184 | if self.fm_spg_window_size > max(self.fm_result_y.shape): 185 | raise ValueError(f'>> EXCEPTION << : FFT window {self.fm_sp_window_size}' 186 | f' pts > input frame length {max(self.fm_result_y.shape)} pts.') 187 | 188 | f, t, spg = sig.spectrogram(np.array(self.fm_result_y[self.fm_spg_chan]), 189 | fs=self.streaming_device.ai_fs, 190 | window=self.fm_spg_window_type, 191 | nperseg=self.fm_spg_window_size, 192 | noverlap=self.fm_spg_window_size-config.spg_window_overlap_skip_pts, 193 | scaling='spectrum', 194 | mode='magnitude', 195 | detrend=False) 196 | 197 | return f, t, spg 198 | 199 | 200 | if __name__ == "__main__": 201 | from model.iostreamingdevice import io_streaming_device_discovery 202 | from time import sleep 203 | 204 | devices_name_to_model_dict = io_streaming_device_discovery() 205 | if len(devices_name_to_model_dict) == 0: 206 | print('No I/O streaming devices were found') 207 | else: 208 | device_names = [name for name in devices_name_to_model_dict.keys()] 209 | for itr, device_name in enumerate(device_names): 210 | print('<' + str(itr) + '> ' + device_name + '\n') 211 | user_dev_index = input(" streaming device from the list.>>") 877 | device_name = device_names[int(dev_index)] 878 | daq = devices_name_to_model_dict[device_name]() 879 | 880 | # add changes to default streaming device config here: 881 | if daq.device_name == 'Dev1': 882 | daq.ai_terminal_config = 'RSE' 883 | 884 | app = pg.QtGui.QApplication(sys.argv) 885 | win = pg.GraphicsWindow(title=daq.device_name) 886 | plot_item1 = win.addPlot(title="Input frame (s)") 887 | plot_data_item_A = plot_item1.plot(pen=1) 888 | plot_data_item_B = plot_item1.plot(pen=2) 889 | win.nextRow() 890 | plot_item2 = win.addPlot(title="Monitor window (pts)") 891 | monitor_plot_data_item_A = plot_item2.plot(pen=1) 892 | monitor_plot_data_item_B = plot_item2.plot(pen=2) 893 | 894 | plot_timer_frame = QtCore.QTimer() 895 | plot_timer_frame.timeout.connect(update_time_plot) 896 | daq.monitor_ready_signal.connect(update_monitor_plot) 897 | 898 | daq.start_continuous_acq_n_gen() 899 | plot_timer_frame.start(100) 900 | app.exec_() 901 | daq.stop_continuous_acq_n_gen() 902 | -------------------------------------------------------------------------------- /twingo.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from PyQt5 import QtCore, QtWidgets 3 | import pyqtgraph as pg 4 | import numpy as np 5 | 6 | from model.experiment import Experiment 7 | from model.iostreamingdevice import io_streaming_device_discovery 8 | from model.pftltools import limit 9 | 10 | from view import config 11 | from view.customtheme import apply_dark_theme 12 | from view.GUI_twingo import Ui_MainWindow 13 | 14 | 15 | class Error(Exception): 16 | """Base class for other exceptions""" 17 | pass 18 | 19 | 20 | class NoInputData(Error): 21 | """Exception raised when ui triggers analysis before measurement data is available""" 22 | pass 23 | 24 | 25 | class LimitExceeded(Error): 26 | """Exception raised when input value exceeds expected limits""" 27 | pass 28 | 29 | 30 | class TwingoExec: 31 | 32 | def __init__(self): 33 | 34 | app = QtWidgets.QApplication(sys.argv) 35 | apply_dark_theme(app) 36 | 37 | MainWindow = QtWidgets.QMainWindow() 38 | 39 | self.mutex = QtCore.QMutex() 40 | 41 | self.ui = Ui_MainWindow() 42 | self.ui.setupUi(MainWindow) 43 | 44 | self.SETTINGS_TAB_INDEX = 0 45 | self.CONTINUOUS_MEAS_TAB_INDEX = 1 46 | self.CM_TIMESERIES_TAB_INDEX = 0 47 | self.CM_SPECTRUM_TAB_INDEX = 1 48 | self.CM_PHASE_TAB_INDEX = 2 49 | 50 | self.FINITE_MEAS_TAB_INDEX = 2 51 | self.FM_TIMESERIES_TAB_INDEX = 0 52 | self.FM_SPECTRUM_TAB_INDEX = 1 53 | self.FM_SPECTROGRAM_TAB_INDEX = 2 54 | 55 | self.ui.tabWidget_main.setCurrentIndex(self.SETTINGS_TAB_INDEX) 56 | self.ui.tabWidget_fm.setCurrentIndex(self.FM_TIMESERIES_TAB_INDEX) 57 | self.ui.tabWidget_cm.setCurrentIndex(self.CM_TIMESERIES_TAB_INDEX) 58 | 59 | self.ui.tabWidget_main.setTabEnabled(self.FINITE_MEAS_TAB_INDEX, False) 60 | self.ui.tabWidget_main.setTabEnabled(self.CONTINUOUS_MEAS_TAB_INDEX, False) 61 | 62 | if config.NR_OF_CHANNELS_TO_PLOT < 2: 63 | self.ui.tabWidget_cm.setTabEnabled(self.CM_PHASE_TAB_INDEX, False) 64 | 65 | # plot widgets: 66 | self.cm_tm_plot_widget = None 67 | self.cm_sp_plot_widget = None 68 | self.cm_ph_plot_widget = None 69 | self.fm_tm_plot_widget = None 70 | self.fm_sp_plot_widget = None 71 | self.fm_spg_widget = None 72 | self.graphics_plot = None 73 | self.img = None 74 | self.hist = None 75 | 76 | # plot hold functionality 77 | self.fm_tm_plot_data_items = [] 78 | self.fm_sp_plot_data_items = [] 79 | 80 | self.fm_tm_plot_hold_data_items = [] 81 | self.fm_sp_plot_hold_data_items = [] 82 | 83 | self.cm_tm_plot_data_items = [] 84 | self.cm_sp_plot_data_items = [] 85 | self.cm_ph_plot_data_item = None 86 | self.cm_ph_plot_box_data_item = [] 87 | 88 | self.cm_sp_plot_hold_data_items = [] 89 | self.cm_tm_plot_hold_data_items = [] 90 | self.cm_ph_plot_hold_data_items = [] 91 | 92 | self.pen_case = \ 93 | [pg.mkPen(color=chan_index + config.PLOT_COLOR_MODIFIER_INT) for chan_index in 94 | range(config.NR_OF_CHANNELS_TO_PLOT)] 95 | 96 | self.hold_pen_case = \ 97 | [pg.mkPen(color=chan_index + config.PLOT_COLOR_MODIFIER_INT, style=QtCore.Qt.DotLine) for chan_index in 98 | range(config.NR_OF_CHANNELS_TO_PLOT)] 99 | 100 | self.place_fm_tm_graph() 101 | self.place_fm_sp_graph() 102 | self.place_fm_spg_graphics() 103 | self.place_cm_tm_graph() 104 | self.place_cm_sp_graph() 105 | self.place_cm_ph_graph() 106 | self.connect_gui_signals() 107 | 108 | # paint the background of hold items with the same color as pen assigned to hold plots: 109 | hold_checkbox_style_sheet_strings = \ 110 | [f'color: rgb(0, 0, 0); background-color:rgb{self.pen_case[chan_index].color().getRgb()}' for 111 | chan_index in range(config.NR_OF_CHANNELS_TO_PLOT)] 112 | 113 | try: 114 | self.ui.checkBox_fm_tm_hold_a.setStyleSheet(hold_checkbox_style_sheet_strings[0]) 115 | self.ui.checkBox_fm_sp_hold_a.setStyleSheet(hold_checkbox_style_sheet_strings[0]) 116 | self.ui.checkBox_cm_tm_hold_a.setStyleSheet(hold_checkbox_style_sheet_strings[0]) 117 | self.ui.checkBox_cm_sp_hold_a.setStyleSheet(hold_checkbox_style_sheet_strings[0]) 118 | self.ui.checkBox_fm_tm_hold_b.setStyleSheet(hold_checkbox_style_sheet_strings[1]) 119 | self.ui.checkBox_fm_sp_hold_b.setStyleSheet(hold_checkbox_style_sheet_strings[1]) 120 | self.ui.checkBox_cm_tm_hold_b.setStyleSheet(hold_checkbox_style_sheet_strings[1]) 121 | self.ui.checkBox_cm_sp_hold_b.setStyleSheet(hold_checkbox_style_sheet_strings[1]) 122 | except IndexError as exc: 123 | print(exc) 124 | 125 | self.buffer_indicator_timer = QtCore.QTimer() 126 | self.buffer_indicator_timer.timeout.connect(self.update_output_buffer_indicator) 127 | 128 | # self.timeseries_timer = QtCore.QTimer() 129 | # self.timeseries_timer.timeout.connect(self.update_cm_timeseries_plot) 130 | 131 | self.devices_name_to_model = None 132 | self.e = None 133 | 134 | MainWindow.show() 135 | sys.exit(app.exec_()) 136 | 137 | def connect_gui_signals(self): 138 | self.ui.tabWidget_main.currentChanged.connect(self.on_tabWidget_main_changed) 139 | 140 | # settings tab: 141 | self.ui.pushButton_detectDAQ.clicked.connect(self.on_pushButton_detectDAQ_clicked) 142 | self.ui.comboBox_dev_list.activated.connect(self.on_comboBox_dev_list_activated) 143 | self.ui.comboBox_ai_a.activated.connect(self.on_comboBox_ai_ab_activated) 144 | self.ui.comboBox_ai_b.activated.connect(self.on_comboBox_ai_ab_activated) 145 | self.ui.comboBox_ao_a.activated.connect(self.on_comboBox_ao_ab_activated) 146 | self.ui.comboBox_ao_b.activated.connect(self.on_comboBox_ao_ab_activated) 147 | self.ui.comboBox_ai_terminal_cfg.activated.connect(self.on_comboBox_ai_terminal_cfg_activated) 148 | self.ui.comboBox_ai_max.activated.connect(self.on_comboBox_ai_min_max_activated) 149 | self.ui.comboBox_ai_min.activated.connect(self.on_comboBox_ai_min_max_activated) 150 | self.ui.comboBox_ao_max.activated.connect(self.on_comboBox_ao_min_max_activated) 151 | self.ui.comboBox_ao_min.activated.connect(self.on_comboBox_ao_min_max_activated) 152 | self.ui.comboBox_ai_fs.activated.connect(self.on_comboBox_ai_fs_activated) 153 | self.ui.comboBox_ao_fs.activated.connect(self.on_comboBox_ao_fs_activated) 154 | self.ui.comboBox_ao_max.activated.connect(self.on_comboBox_ao_max_activated) 155 | 156 | # common widgets 157 | self.ui.comboBox_sig_len.activated.connect(self.on_comboBox_sig_len_activated) 158 | self.ui.comboBox_out_sig_type.activated.connect(self.on_comboBox_out_sig_activated) 159 | self.ui.lineEdit_min_f.editingFinished.connect(self.on_lineEdit_min_f_editingFinished) 160 | self.ui.lineEdit_max_f.editingFinished.connect(self.on_lineEdit_max_f_editingFinished) 161 | self.ui.pushButton_start.clicked.connect(self.on_pushButton_start_clicked) 162 | self.ui.pushButton_stop.clicked.connect(self.on_pushButton_stop_clicked) 163 | 164 | # finite measurement widgets 165 | self.ui.tabWidget_fm.currentChanged.connect(self.update_current_fm_plot) 166 | # timeseries 167 | self.ui.checkBox_fm_tm_hold_a.toggled.connect(self.on_checkBox_fm_tm_hold_a_toggled) 168 | self.ui.checkBox_fm_tm_hold_b.toggled.connect(self.on_checkBox_fm_tm_hold_b_toggled) 169 | # spectrum 170 | self.ui.comboBox_fm_window_size.activated.connect(self.on_comboBox_fm_window_size_activated) 171 | self.ui.comboBox_fm_window_type.activated.connect(self.on_comboBox_fm_window_type_activated) 172 | self.ui.checkBox_fm_sp_hold_a.toggled.connect(self.on_checkBox_fm_sp_hold_a_toggled) 173 | self.ui.checkBox_fm_sp_hold_b.toggled.connect(self.on_checkBox_fm_sp_hold_b_toggled) 174 | # spectrogram 175 | self.ui.checkBox_fm_spg_log_amp.toggled.connect(self.update_fm_spg) 176 | self.ui.comboBox_fm_spg_window_size.activated.connect(self.on_comboBox_fm_spg_window_size_activated) 177 | self.ui.comboBox_fm_spg_chan.activated.connect(self.on_comboBox_fm_spg_chan_activated) 178 | self.ui.comboBox_fm_spg_window_type.activated.connect(self.on_comboBox_fm_spg_window_type_activated) 179 | 180 | # continuous measurement widgets 181 | self.ui.tabWidget_cm.currentChanged.connect(self.on_tabWidget_cm_changed) 182 | # timeseries 183 | self.ui.checkBox_cm_tm_hold_a.toggled.connect(self.on_checkBox_cm_tm_hold_a_toggled) 184 | self.ui.checkBox_cm_tm_hold_b.toggled.connect(self.on_checkBox_cm_tm_hold_b_toggled) 185 | # spectrum 186 | self.ui.comboBox_cm_window_type.activated.connect(self.on_comboBox_cm_window_type_activated) 187 | self.ui.comboBox_cm_window_size.activated.connect(self.on_comboBox_cm_window_size_activated) 188 | self.ui.checkBox_cm_sp_hold_a.toggled.connect(self.on_checkBox_cm_sp_hold_a_toggled) 189 | self.ui.checkBox_cm_sp_hold_b.toggled.connect(self.on_checkBox_cm_sp_hold_b_toggled) 190 | # phase 191 | self.ui.comboBox_cm_ph_window_size.activated.connect(self.on_comboBox_cm_ph_window_size_activated) 192 | self.ui.checkBox_cm_ph_hold_1.toggled.connect(self.on_checkBox_cm_ph_hold_1_toggled) 193 | self.ui.checkBox_cm_ph_hold_2.toggled.connect(self.on_checkBox_cm_ph_hold_2_toggled) 194 | 195 | # common 196 | self.ui.dial_cm_vfine_freq.valueChanged.connect(self.on_dial_value_changed) 197 | self.ui.dial_cm_fine_freq.valueChanged.connect(self.on_dial_value_changed) 198 | self.ui.dial_cm_coarse_freq.valueChanged.connect(self.on_dial_value_changed) 199 | 200 | def get_dials_value(self): 201 | return self.ui.dial_cm_vfine_freq.value() + \ 202 | self.ui.dial_cm_fine_freq.value() + \ 203 | self.ui.dial_cm_coarse_freq.value() 204 | 205 | def on_comboBox_sig_len_activated(self): 206 | self.e.streaming_device.set_mode_to_finite(float(self.ui.comboBox_sig_len.currentText())) 207 | 208 | def on_dial_value_changed(self): 209 | new_value = self.get_dials_value() 210 | self.e.streaming_device.function_gen.set_frequency(new_value) 211 | self.ui.lineEdit_max_f.setText(str(new_value)) 212 | self.ui.lcdNumber.display(new_value) 213 | 214 | def on_comboBox_fm_window_type_activated(self): 215 | self.e.set_fm_sp_window(win_type=self.ui.comboBox_fm_window_type.currentText()) 216 | self.update_current_fm_plot() 217 | 218 | def on_comboBox_fm_window_size_activated(self): 219 | self.e.set_fm_sp_window(win_size=int(self.ui.comboBox_fm_window_size.currentText())) 220 | self.update_current_fm_plot() 221 | 222 | def on_comboBox_cm_window_type_activated(self): 223 | self.e.set_cm_sp_window(win_type=self.ui.comboBox_cm_window_type.currentText()) 224 | 225 | def on_comboBox_cm_window_size_activated(self): 226 | self.e.set_cm_sp_window(win_size=int(self.ui.comboBox_cm_window_size.currentText())) 227 | 228 | def on_comboBox_cm_ph_window_size_activated(self): 229 | self.e.set_cm_ph_window(win_size=int(self.ui.comboBox_cm_ph_window_size.currentText())) 230 | 231 | def on_comboBox_fm_spg_chan_activated(self): 232 | self.e.set_fm_spg_chan(self.ui.comboBox_fm_spg_chan.currentIndex()) 233 | self.update_fm_spg() 234 | 235 | def on_comboBox_fm_spg_window_type_activated(self): 236 | self.e.set_fm_spg_window(win_type=self.ui.comboBox_fm_spg_window_type.currentText()) 237 | self.update_fm_spg() 238 | 239 | def on_comboBox_fm_spg_window_size_activated(self): 240 | self.e.set_fm_spg_window(win_size=int(self.ui.comboBox_fm_spg_window_size.currentText())) 241 | self.update_fm_spg() 242 | 243 | def on_comboBox_ai_ab_activated(self): 244 | # TODO some callbacks are coded in the uic generated file already and they can theoretically create race 245 | # condition with the checks below. Its probably a good idea to remove the callbacks from Designer and hard 246 | # code them here by creating separate, dedicated "on_comboBox_index_changed" callbacks for each that 247 | # contain both functionalities without allowing race. 248 | self.e.streaming_device.ai_a_name = self.ui.comboBox_ai_a.currentText() 249 | self.e.streaming_device.ai_b_name = self.ui.comboBox_ai_b.currentText() 250 | self.print_qt(f'Input channels : A:{self.e.streaming_device.ai_a_name} B:{self.e.streaming_device.ai_b_name}') 251 | 252 | def on_comboBox_ao_ab_activated(self): 253 | self.e.streaming_device.ao_a_name = self.ui.comboBox_ao_a.currentText() 254 | self.e.streaming_device.ao_b_name = self.ui.comboBox_ao_b.currentText() 255 | self.print_qt(f'Output channels : A:{self.e.streaming_device.ao_a_name} B:{self.e.streaming_device.ao_b_name}') 256 | 257 | def on_comboBox_ai_min_max_activated(self): 258 | self.e.streaming_device.ai_min_val = float(self.ui.comboBox_ai_min.currentText()) 259 | self.e.streaming_device.ai_max_val = float(self.ui.comboBox_ai_max.currentText()) 260 | self.print_qt(f'Input voltage limit min:{self.e.streaming_device.ai_min_val}V,' 261 | f' max:{self.e.streaming_device.ai_max_val}V') 262 | self.set_plot_limits() 263 | 264 | def on_comboBox_ao_min_max_activated(self): 265 | self.e.streaming_device.ao_min_val = float(self.ui.comboBox_ao_min.currentText()) 266 | self.e.streaming_device.ao_max_val = float(self.ui.comboBox_ao_max.currentText()) 267 | self.print_qt(f'Output voltage limit min:{self.e.streaming_device.ao_min_val}V,' 268 | f' max:{self.e.streaming_device.ao_max_val}V') 269 | 270 | def on_comboBox_ai_terminal_cfg_activated(self): 271 | self.e.streaming_device.ai_terminal_config = self.ui.comboBox_ai_terminal_cfg.currentText() 272 | self.print_qt(f'Ai terminal config: {self.e.streaming_device.ai_terminal_config}') 273 | 274 | def print_qt(self, message, suppress_console=True): 275 | if suppress_console is False: 276 | print(message) 277 | self.ui.statusbar.showMessage(message.__str__(), config.STATUS_BAR_MESSAGE_TIME_MS) 278 | 279 | def check_fm_data_present(self): 280 | if self.e.fm_result_y is None: 281 | raise NoInputData('No finite input data is available. Press START to run a measurement.') 282 | 283 | def toggle_plot_hold(self, hold_check_box, plot_data_item, plot_hold_data_item): 284 | """ 285 | Generic plot hold toggle functionality. If hold_check_box state isChechked() the plot_data_item X and Y data 286 | will be set to plot_hold_data_item, else plot_hold_data_item will be cleared. 287 | 288 | :param hold_check_box: checkBox qt object whose state is to be checked. 289 | :param plot_data_item: pyqtgraph data_item object with the plot to be held 290 | :param plot_hold_data_item: pyqtgraph data_item object which hold plot data is to be set or cleared 291 | :return: None 292 | """ 293 | if plot_data_item.xData is None or plot_data_item.yData is None: 294 | self.print_qt("No data to hold") 295 | return 296 | 297 | if hold_check_box.isChecked(): 298 | plot_hold_data_item.setData(plot_data_item.xData, plot_data_item.yData) 299 | else: 300 | # plot_hold_data_item.clear() # for some reason clears the continuous hold plot but not the finite one. 301 | plot_hold_data_item.setData([], []) 302 | 303 | def on_checkBox_cm_tm_hold_a_toggled(self): 304 | self.toggle_plot_hold(self.ui.checkBox_cm_tm_hold_a, 305 | self.cm_tm_plot_data_items[0], 306 | self.cm_tm_plot_hold_data_items[0]) 307 | 308 | def on_checkBox_cm_tm_hold_b_toggled(self): 309 | self.toggle_plot_hold(self.ui.checkBox_cm_tm_hold_b, 310 | self.cm_tm_plot_data_items[1], 311 | self.cm_tm_plot_hold_data_items[1]) 312 | 313 | def on_checkBox_cm_sp_hold_a_toggled(self): 314 | self.toggle_plot_hold(self.ui.checkBox_cm_sp_hold_a, 315 | self.cm_sp_plot_data_items[0], 316 | self.cm_sp_plot_hold_data_items[0]) 317 | 318 | def on_checkBox_cm_sp_hold_b_toggled(self): 319 | self.toggle_plot_hold(self.ui.checkBox_cm_sp_hold_b, 320 | self.cm_sp_plot_data_items[1], 321 | self.cm_sp_plot_hold_data_items[1]) 322 | 323 | def on_checkBox_cm_ph_hold_1_toggled(self): 324 | self.toggle_plot_hold(self.ui.checkBox_cm_ph_hold_1, 325 | self.cm_ph_plot_data_item, 326 | self.cm_ph_plot_hold_data_items[0]) 327 | 328 | def on_checkBox_cm_ph_hold_2_toggled(self): 329 | self.toggle_plot_hold(self.ui.checkBox_cm_ph_hold_2, 330 | self.cm_ph_plot_data_item, 331 | self.cm_ph_plot_hold_data_items[1]) 332 | 333 | def on_checkBox_fm_tm_hold_a_toggled(self): 334 | self.toggle_plot_hold(self.ui.checkBox_fm_tm_hold_a, 335 | self.fm_tm_plot_data_items[0], 336 | self.fm_tm_plot_hold_data_items[0]) 337 | 338 | def on_checkBox_fm_tm_hold_b_toggled(self): 339 | self.toggle_plot_hold(self.ui.checkBox_fm_tm_hold_b, 340 | self.fm_tm_plot_data_items[1], 341 | self.fm_tm_plot_hold_data_items[1]) 342 | 343 | def on_checkBox_fm_sp_hold_a_toggled(self): 344 | self.toggle_plot_hold(self.ui.checkBox_fm_sp_hold_a, 345 | self.fm_sp_plot_data_items[0], 346 | self.fm_sp_plot_hold_data_items[0]) 347 | 348 | def on_checkBox_fm_sp_hold_b_toggled(self): 349 | self.toggle_plot_hold(self.ui.checkBox_fm_sp_hold_b, 350 | self.fm_sp_plot_data_items[1], 351 | self.fm_sp_plot_hold_data_items[1]) 352 | 353 | def check_set_line_edit_number(self, line_edit_caller, casting_class, limits=None, num_on_exc=None): 354 | """ 355 | Checks, and, if necessary, corrects user QLineEdit.text() entry. 356 | 357 | :param line_edit_caller: QLineEdit object we wish to check/correct .text() of. 358 | :param casting_class: One of the numeric built-in classes (int, float ...) 359 | that will be used to test if text can be casted to a specific numeric type. 360 | :param limits: (tuple) (min, max) limits for user input. If exceeded user input will be clipped 361 | to either limit respectively. 362 | :param num_on_exc: Value which will overwrite user input in case it can not be 363 | casted to a specified numeric type. 364 | :return: numeric value corresponding to accepted/corrected QlineEdit.text(). 365 | """ 366 | # override the number to return on exception in case its supplied out of the limits required. 367 | # It can happen if the previous value is used equal to the value in lineEdit from before callback while 368 | # meantime a property determining the limits was changed (e.g. fs was reduced so max allowed freq is now 369 | # less than what is entered into freq0 field). In this case defaulting to the previous value on 370 | # exception is not ok. Therefore num_on_exc has got to be fixed to the closest limit... 371 | if limits is not None: 372 | num_on_exc = limit(num_on_exc, limits) 373 | 374 | try: 375 | num = casting_class(line_edit_caller.text()) 376 | 377 | if limits is not None: # TODO ...but it leads to code repetition which makes me wanna cry 378 | num_within_limit = limit(num, limits) 379 | if num_within_limit != num: 380 | raise LimitExceeded(f'Value {num} exceeded limits: {limits[0]}-{limits[1]}.') 381 | 382 | except ValueError as err: 383 | print(err) 384 | self.print_qt(f'Input must be numeric {casting_class}.') 385 | line_edit_caller.setText(str(num_on_exc)) 386 | return num_on_exc 387 | 388 | except LimitExceeded as err: 389 | self.print_qt(err) 390 | line_edit_caller.setText(str(num_within_limit)) 391 | return num_within_limit 392 | 393 | return num 394 | 395 | def on_comboBox_ao_fs_activated(self): 396 | self.e.streaming_device.set_ao_fs(float(self.ui.comboBox_ao_fs.currentText())) 397 | self.print_qt(f'Output sampling rate changed: {self.e.streaming_device.ao_fs}Hz.') 398 | # recalculate new max for coarse dial and realign all dials 399 | new_max_dial_rng = self.e.streaming_device.ao_fs // 2 - self.ui.dial_cm_fine_freq.maximum() - \ 400 | self.ui.dial_cm_vfine_freq.maximum() 401 | new_max_dial_rng = limit(new_max_dial_rng, (0, config.MAX_COARSE_FREQ_DIAL_LIMIT)) 402 | self.ui.dial_cm_coarse_freq.setRange(0, new_max_dial_rng) 403 | self.align_dials_position() 404 | 405 | def on_comboBox_ai_fs_activated(self): 406 | self.e.streaming_device.set_ai_fs(float(self.ui.comboBox_ai_fs.currentText())) 407 | self.print_qt(f'Input sampling rate changed: {self.e.streaming_device.ai_fs}Hz.') 408 | self.e.set_cm_freq_base() 409 | 410 | def on_lineEdit_min_f_editingFinished(self): 411 | prev_value = self.e.streaming_device.function_gen.freq0 412 | min_value = config.MIN_OUTPUT_FREQUENCY_ALLOWED 413 | max_value = self.e.streaming_device.ao_fs / 2 414 | new_value = self.check_set_line_edit_number(self.ui.lineEdit_min_f, float, 415 | limits=(min_value, max_value), 416 | num_on_exc=prev_value) 417 | if new_value == prev_value: 418 | return 419 | self.e.streaming_device.function_gen.set_start_frequency(new_value) 420 | self.print_qt(f'Start frequency changed: {self.e.streaming_device.function_gen.freq0}Hz.') 421 | 422 | def on_lineEdit_max_f_editingFinished(self): 423 | prev_value = self.e.streaming_device.function_gen.freq1 424 | min_value = config.MIN_OUTPUT_FREQUENCY_ALLOWED 425 | max_value = self.e.streaming_device.ao_fs / 2 426 | new_value = self.check_set_line_edit_number(self.ui.lineEdit_max_f, float, 427 | limits=(min_value, max_value), 428 | num_on_exc=prev_value) 429 | if new_value == prev_value: 430 | return 431 | self.e.streaming_device.function_gen.set_frequency(new_value) 432 | self.print_qt(f'Set/End frequency changed: {self.e.streaming_device.function_gen.freq1}Hz.') 433 | self.ui.lcdNumber.display(new_value) 434 | # ensure dials are in consistent position with the current set freq. and allow exploration in its vicinity 435 | self.align_dials_position() 436 | 437 | def align_dials_position(self): 438 | new_value = self.e.streaming_device.function_gen.freq1 439 | self.ui.dial_cm_vfine_freq.blockSignals(True) 440 | self.ui.dial_cm_fine_freq.blockSignals(True) 441 | self.ui.dial_cm_coarse_freq.blockSignals(True) 442 | if new_value <= self.ui.dial_cm_vfine_freq.maximum() // 2: 443 | self.ui.dial_cm_vfine_freq.setValue(new_value) 444 | self.ui.dial_cm_fine_freq.setValue(0) 445 | self.ui.dial_cm_coarse_freq.setValue(0) 446 | elif new_value <= self.ui.dial_cm_fine_freq.maximum() // 2: 447 | self.ui.dial_cm_vfine_freq.setValue(self.ui.dial_cm_vfine_freq.maximum() // 2) 448 | self.ui.dial_cm_fine_freq.setValue(new_value - self.ui.dial_cm_vfine_freq.value()) 449 | self.ui.dial_cm_coarse_freq.setValue(0) 450 | elif new_value <= self.ui.dial_cm_coarse_freq.maximum(): 451 | self.ui.dial_cm_vfine_freq.setValue(self.ui.dial_cm_vfine_freq.maximum() // 2) 452 | self.ui.dial_cm_fine_freq.setValue(self.ui.dial_cm_fine_freq.maximum() // 2) 453 | self.ui.dial_cm_coarse_freq.setValue(new_value - self.ui.dial_cm_vfine_freq.value() 454 | - self.ui.dial_cm_fine_freq.value()) 455 | elif new_value > self.ui.dial_cm_coarse_freq.maximum(): 456 | self.ui.dial_cm_vfine_freq.setValue(self.ui.dial_cm_vfine_freq.maximum() // 2) 457 | self.ui.dial_cm_fine_freq.setValue(self.ui.dial_cm_fine_freq.maximum() // 2) 458 | self.ui.dial_cm_coarse_freq.setValue(self.ui.dial_cm_coarse_freq.maximum()) 459 | 460 | self.ui.dial_cm_vfine_freq.blockSignals(False) 461 | self.ui.dial_cm_fine_freq.blockSignals(False) 462 | self.ui.dial_cm_coarse_freq.blockSignals(False) 463 | 464 | def on_comboBox_ao_max_activated(self): 465 | self.e.streaming_device.function_gen.set_amplitude(float(self.ui.comboBox_ao_max.currentText())) 466 | print(f'Output signal amplitude changed to {self.e.streaming_device.function_gen.amplitude}V.') 467 | 468 | def on_comboBox_out_sig_activated(self): 469 | self.e.streaming_device.function_gen.set_function(self.ui.comboBox_out_sig_type.currentText()) 470 | 471 | def on_tabWidget_main_changed(self): 472 | if self.ui.tabWidget_main.currentIndex() == self.FINITE_MEAS_TAB_INDEX: 473 | self.e.streaming_device.set_mode_to_finite(float(self.ui.comboBox_sig_len.currentText())) 474 | self.ui.comboBox_sig_len.setEnabled(True) 475 | elif self.ui.tabWidget_main.currentIndex() == self.CONTINUOUS_MEAS_TAB_INDEX: 476 | self.e.streaming_device.set_mode_to_continuous() 477 | self.ui.comboBox_sig_len.setEnabled(False) 478 | elif self.ui.tabWidget_main.currentIndex() == self.SETTINGS_TAB_INDEX: 479 | pass 480 | else: 481 | # reminder for future expansion of tabs 482 | self.print_qt('WARNING: This tab does not define a specific streaming device mode') 483 | 484 | def update_current_fm_plot(self): 485 | try: 486 | if self.ui.tabWidget_fm.currentIndex() == self.FM_TIMESERIES_TAB_INDEX: 487 | self.update_fm_tm_plot() 488 | elif self.ui.tabWidget_fm.currentIndex() == self.FM_SPECTRUM_TAB_INDEX: 489 | self.update_fm_sp_plot() 490 | elif self.ui.tabWidget_fm.currentIndex() == self.FM_SPECTROGRAM_TAB_INDEX: 491 | self.update_fm_spg() 492 | else: 493 | pass 494 | except NoInputData as exc: 495 | self.print_qt(exc) 496 | 497 | def on_pushButton_detectDAQ_clicked(self): 498 | self.devices_name_to_model = io_streaming_device_discovery() 499 | self.ui.comboBox_dev_list.clear() 500 | for name in self.devices_name_to_model: 501 | self.ui.comboBox_dev_list.addItem(name) 502 | 503 | self.ui.comboBox_dev_list.showPopup() 504 | 505 | if self.ui.comboBox_dev_list.count() == 1: # if only one device found use it by default 506 | self.on_comboBox_dev_list_activated() 507 | 508 | if self.ui.comboBox_dev_list.count() == 0: 509 | self.print_qt("No DAQ devices found and/or supporting packages (pyaudio / nidaqmx) are not installed.", 510 | suppress_console=False) 511 | 512 | def on_comboBox_dev_list_activated(self): 513 | device_name = self.ui.comboBox_dev_list.currentText() 514 | self.print_qt(f'Device selected: {device_name}') 515 | device_instance = self.devices_name_to_model[device_name]() 516 | self.e = Experiment(device_instance) 517 | self.configure_gui() 518 | 519 | def set_plot_limits(self): 520 | 521 | x_min = self.e.streaming_device.ai_min_val 522 | x_max = self.e.streaming_device.ai_max_val 523 | y_min = self.e.streaming_device.ai_min_val 524 | y_max = self.e.streaming_device.ai_max_val 525 | 526 | plot_items = [self.fm_tm_plot_widget.getPlotItem(), 527 | self.cm_tm_plot_widget.getPlotItem()] 528 | 529 | for plot_item in plot_items: 530 | plot_item.vb.setLimits(yMin=y_min * 1.1, yMax=y_max * 1.1) 531 | plot_item.setRange(yRange=(y_min * 1.1, y_max * 1.1), disableAutoRange=True) 532 | 533 | phase_box_x = np.array([x_min, x_max, x_max, x_min, x_min]) * config.PHASE_PLOT_OPTIMAL_WIDTH 534 | phase_box_y = np.array([y_min, y_min, y_max, y_max, y_min]) 535 | self.cm_ph_plot_box_data_item.setData(phase_box_x, phase_box_y) 536 | 537 | cm_ph_plot_item = self.cm_ph_plot_widget.getPlotItem() 538 | cm_ph_plot_item.vb.setLimits(xMin=x_min * 1.1, xMax=x_max * 1.1, yMin=y_min * 1.1, yMax=y_max * 1.1) 539 | cm_ph_plot_item.setRange(xRange=(x_min * 1.1, x_max * 1.1), yRange=(y_min * 1.1, y_max * 1.1), 540 | disableAutoRange=True) 541 | 542 | cm_ph_text_m = pg.TextItem(text='M', anchor=(0.5, 0.5)) # , color=(256, 256, 256)) 543 | cm_ph_text_a = pg.TextItem(text='A', anchor=(0.5, 0.5)) 544 | cm_ph_text_b = pg.TextItem(text='B', anchor=(0.5, 0.5)) 545 | 546 | cm_ph_text_m.setPos(0, y_max * 0.9) 547 | cm_ph_text_a.setPos(-x_max * 0.5, y_max * 0.5) 548 | cm_ph_text_b.setPos(x_max * 0.5, y_max * 0.5) 549 | 550 | cm_ph_plot_item.addItem(cm_ph_text_m) 551 | cm_ph_plot_item.addItem(cm_ph_text_a) 552 | cm_ph_plot_item.addItem(cm_ph_text_b) 553 | 554 | # TODO add limits for the spectral graphs and disable auto range 555 | 556 | def configure_gui(self): 557 | self.set_settings_page() 558 | self.ui.lineEdit_min_f.setText(str(self.e.streaming_device.function_gen.freq0)) 559 | self.ui.lineEdit_max_f.setText(str(self.e.streaming_device.function_gen.freq1)) 560 | 561 | self.ui.comboBox_out_sig_type.setCurrentText(self.e.streaming_device.function_gen.current_function) 562 | self.ui.comboBox_sig_len.setCurrentText(str(self.e.streaming_device.finite_frame_len_sec)) 563 | 564 | self.ui.comboBox_cm_window_type.setCurrentText(self.e.cm_sp_window_type) # TODO Rename to cm_sp 565 | self.ui.comboBox_cm_window_size.setCurrentText(str(self.e.cm_sp_window_size)) # TODO Rename to cm_sp 566 | 567 | self.ui.comboBox_cm_ph_window_size.setCurrentText(str(self.e.cm_ph_window_size)) 568 | 569 | self.ui.comboBox_fm_window_type.setCurrentText(self.e.fm_sp_window_type) 570 | self.ui.comboBox_fm_window_size.setCurrentText(str(self.e.fm_sp_window_size)) 571 | 572 | self.ui.comboBox_fm_spg_window_type.setCurrentText(self.e.fm_spg_window_type) 573 | self.ui.comboBox_fm_spg_window_type.setCurrentText(str(self.e.fm_spg_window_size)) 574 | self.ui.comboBox_fm_spg_chan.setCurrentText(str(self.e.fm_spg_chan)) 575 | 576 | self.align_dials_position() 577 | self.set_plot_limits() 578 | self.e.streaming_device.input_frame_ready_signal.connect(self.update_cm_tm_plot) 579 | 580 | self.ui.pushButton_start.setEnabled(True) 581 | self.ui.tabWidget_main.setTabEnabled(self.FINITE_MEAS_TAB_INDEX, True) 582 | self.ui.tabWidget_main.setTabEnabled(self.CONTINUOUS_MEAS_TAB_INDEX, True) 583 | 584 | def set_settings_page(self): 585 | self.set_comboBoxes_ai() 586 | self.set_comboBoxes_ao() 587 | self.set_comboBoxes_ai_range() 588 | self.set_comboBoxes_ao_range() 589 | self.set_comboBox_ai_config() 590 | self.set_comboBox_ai_fs() 591 | self.set_comboBox_ao_fs() 592 | self.set_comboBoxes_win_size() 593 | 594 | def set_comboBox_ai_fs(self): 595 | self.ui.comboBox_ai_fs.clear() 596 | self.ui.comboBox_ai_fs.addItems([str(item) for item in self.e.streaming_device.limits.supported_input_rates]) 597 | 598 | def set_comboBox_ao_fs(self): 599 | self.ui.comboBox_ao_fs.clear() 600 | self.ui.comboBox_ao_fs.addItems([str(item) for item in self.e.streaming_device.limits.supported_output_rates]) 601 | 602 | def set_comboBoxes_win_size(self): 603 | self.ui.comboBox_cm_window_size.clear() # TODO RENAME to cm_sp 604 | self.ui.comboBox_cm_ph_window_size.clear() 605 | self.ui.comboBox_fm_window_size.clear() 606 | self.ui.comboBox_fm_spg_window_size.clear() 607 | list_of_win_sizes = [str(item) for item in self.e.streaming_device.limits.supported_monitor_frame_lengths] 608 | self.ui.comboBox_cm_window_size.addItems(list_of_win_sizes) # TODO RENAME to cm_sp 609 | self.ui.comboBox_cm_ph_window_size.addItems(list_of_win_sizes) 610 | self.ui.comboBox_fm_window_size.addItems(list_of_win_sizes) 611 | self.ui.comboBox_fm_spg_window_size.addItems(list_of_win_sizes) 612 | 613 | def set_comboBoxes_ai(self): 614 | self.ui.comboBox_ai_a.clear() 615 | self.ui.comboBox_ai_b.clear() 616 | 617 | counter = 0 618 | for channel in self.e.streaming_device.limits.ai_physical_chans: 619 | if counter % 2 == 0: 620 | self.ui.comboBox_ai_a.addItem(channel) 621 | else: 622 | self.ui.comboBox_ai_b.addItem(channel) 623 | counter += 1 624 | 625 | def set_comboBoxes_ao(self): 626 | self.ui.comboBox_ao_a.clear() 627 | self.ui.comboBox_ao_b.clear() 628 | 629 | counter = 0 630 | for channel in self.e.streaming_device.limits.ao_physical_chans: 631 | if counter % 2 == 0: 632 | self.ui.comboBox_ao_a.addItem(channel) 633 | else: 634 | self.ui.comboBox_ao_b.addItem(channel) 635 | counter += 1 636 | 637 | def set_comboBoxes_ai_range(self): 638 | self.ui.comboBox_ai_max.clear() 639 | self.ui.comboBox_ai_min.clear() 640 | 641 | counter = 0 642 | # count backwards since ranges are listed in increasing order in the DAQ object 643 | # while we want to default to the largest input range that is the safest bet. 644 | for voltage in self.e.streaming_device.limits.ai_voltage_rngs: 645 | if counter % 2 != 1: 646 | self.ui.comboBox_ai_max.addItem(str(voltage)) 647 | else: 648 | self.ui.comboBox_ai_min.addItem(str(voltage)) 649 | counter += 1 650 | 651 | def set_comboBoxes_ao_range(self): 652 | self.ui.comboBox_ao_max.clear() 653 | self.ui.comboBox_ao_min.clear() 654 | 655 | counter = 0 656 | for voltage in self.e.streaming_device.limits.ao_voltage_rngs: 657 | if counter % 2 != 1: 658 | self.ui.comboBox_ao_max.addItem(str(voltage)) 659 | else: 660 | self.ui.comboBox_ao_min.addItem(str(voltage)) 661 | counter += 1 662 | 663 | def set_comboBox_ai_config(self): 664 | self.ui.comboBox_ai_terminal_cfg.clear() 665 | for item in self.e.streaming_device.limits.terminal_configs: 666 | self.ui.comboBox_ai_terminal_cfg.addItem(item) 667 | # its possible to use the comboBox string entries later as keywords to the enumerated type that 668 | # corresponds to them. i.e. nidaqmx.constants.TerminalConfiguration['RSE'] returns the enum same as 669 | # nidaqmx.constants.TerminalConfiguration.RSE. More documentation here: 670 | # https://docs.python.org/3/library/enum.html 671 | 672 | def on_pushButton_start_clicked(self): 673 | if self.ui.tabWidget_main.currentIndex() == self.FINITE_MEAS_TAB_INDEX: 674 | 675 | if self.ui.comboBox_out_sig_type.currentText() != 'ess': 676 | self.print_qt('>>> Finite measurement running...') 677 | self.e.start_fm_experiment() 678 | elif self.ui.comboBox_out_sig_type.currentText() == 'ess': 679 | self.print_qt('>>> Finite ESS measurement running...') 680 | self.e.start_fm_ess_experiment() 681 | self.update_current_fm_plot() 682 | 683 | elif self.ui.tabWidget_main.currentIndex() == self.CONTINUOUS_MEAS_TAB_INDEX: 684 | 685 | self.ui.pushButton_start.setEnabled(False) 686 | self.ui.tabWidget_main.setTabEnabled(self.SETTINGS_TAB_INDEX, False) 687 | self.ui.tabWidget_main.setTabEnabled(self.FINITE_MEAS_TAB_INDEX, False) 688 | self.e.streaming_device.io_start() 689 | self.print_qt('>>> Continuous measurement started.') 690 | self.ui.pushButton_stop.setEnabled(True) 691 | self.buffer_indicator_timer.start(config.IO_BUFFER_INDICATOR_REFRESH_MSEC) 692 | self.on_tabWidget_cm_changed() 693 | else: 694 | self.print_qt('Move to one of the measurement tabs to run a measurement.') 695 | 696 | def on_pushButton_stop_clicked(self): 697 | self.buffer_indicator_timer.stop() 698 | # self.timeseries_timer.stop() 699 | self.e.streaming_device.io_stop() 700 | self.ui.pushButton_stop.setEnabled(False) 701 | self.ui.pushButton_start.setEnabled(True) 702 | self.ui.tabWidget_main.setTabEnabled(self.SETTINGS_TAB_INDEX, True) 703 | self.ui.tabWidget_main.setTabEnabled(self.FINITE_MEAS_TAB_INDEX, True) 704 | self.print_qt('>>> Continuous measurement stopped.') 705 | 706 | def on_tabWidget_cm_changed(self): 707 | if self.e.streaming_device.cm_measurement_is_running: # TODO why is this needed? 708 | self.disconnect_all_drawing() 709 | if self.ui.tabWidget_cm.currentIndex() == self.CM_TIMESERIES_TAB_INDEX: 710 | self.e.streaming_device.input_frame_ready_signal.connect(self.update_cm_tm_plot) 711 | elif self.ui.tabWidget_cm.currentIndex() == self.CM_SPECTRUM_TAB_INDEX: 712 | self.e.streaming_device.set_monitor(self.e.cm_sp_window_size) 713 | self.e.streaming_device.monitor_ready_signal.connect(self.update_cm_sp_plot) 714 | elif self.ui.tabWidget_cm.currentIndex() == self.CM_PHASE_TAB_INDEX: 715 | self.e.streaming_device.set_monitor(self.e.cm_ph_window_size) 716 | self.e.streaming_device.monitor_ready_signal.connect(self.update_cm_ph_plot) 717 | else: 718 | # reminder for future expansion of tabs 719 | self.print_qt('WARNING: This tab does not define a specific streaming device configuration') 720 | 721 | def disconnect_all_drawing( 722 | self): # TODO FIND A BETTER WAY TO DO THIS BY LEARNING WHICH SIGNAL IS ACTUALLY ASSIGNED TO A GIVEN SLOT 723 | all_input_frame_draw_methods = [self.update_cm_tm_plot] 724 | all_monitor_draw_methods = [self.update_cm_sp_plot, self.update_cm_ph_plot] 725 | 726 | for method in all_input_frame_draw_methods: 727 | try: 728 | self.e.streaming_device.input_frame_ready_signal.disconnect(method) 729 | except TypeError: # This is thrown when we try to disconnect a method that isn't connected 730 | pass 731 | 732 | for method in all_monitor_draw_methods: 733 | try: 734 | self.e.streaming_device.monitor_ready_signal.disconnect(method) 735 | except TypeError: 736 | pass 737 | 738 | def place_fm_tm_graph(self): 739 | self.fm_tm_plot_widget = pg.PlotWidget(name='Timeseries') 740 | self.ui.tm_verticalLayout.addWidget(self.fm_tm_plot_widget) 741 | fm_tm_plot_item = self.fm_tm_plot_widget.getPlotItem() 742 | fm_tm_plot_item.showGrid(True, True, alpha=1) 743 | fm_tm_plot_item.setLabel('left', 'Lvl', units='V') 744 | # fm_tm_plot_item.setLabel('bottom', 'time', units='s') 745 | 746 | for chan_index in range(config.NR_OF_CHANNELS_TO_PLOT): 747 | this_plot_data_item = fm_tm_plot_item.plot(pen=self.pen_case[chan_index]) 748 | self.fm_tm_plot_data_items.append(this_plot_data_item) 749 | this_plot_data_item = fm_tm_plot_item.plot(pen=self.hold_pen_case[chan_index]) 750 | self.fm_tm_plot_hold_data_items.append(this_plot_data_item) 751 | 752 | self.fm_tm_plot_widget.show() 753 | # fm_tm_plot_item.vb.setLimits(yMin=self.streaming_device.ai_min_val, yMax=self.streaming_device.ai_max_val) 754 | 755 | def place_fm_sp_graph(self): 756 | self.fm_sp_plot_widget = pg.PlotWidget(name='Spectral Analysis') 757 | self.ui.sp_verticalLayout.addWidget(self.fm_sp_plot_widget) 758 | fm_sp_plot_item = self.fm_sp_plot_widget.getPlotItem() 759 | fm_sp_plot_item.setLogMode(True, False) 760 | fm_sp_plot_item.showGrid(True, True, alpha=1) 761 | fm_sp_plot_item.vb.setLimits(yMin=config.FM_SP_GRAPH_MIN_dB_LIMIT, yMax=config.FM_SP_GRAPH_MAX_dB_LIMIT) 762 | fm_sp_plot_item.setRange(yRange=(config.FM_SP_GRAPH_MIN_dB_LIMIT, config.FM_SP_GRAPH_MAX_dB_LIMIT), 763 | disableAutoRange=True) 764 | fm_sp_plot_item.setLabel('left', 'Lvl', units='dBFS') 765 | 766 | for chan_index in range(config.NR_OF_CHANNELS_TO_PLOT): 767 | this_plot_data_item = fm_sp_plot_item.plot(pen=self.pen_case[chan_index]) 768 | self.fm_sp_plot_data_items.append(this_plot_data_item) 769 | this_plot_data_item = fm_sp_plot_item.plot(pen=self.hold_pen_case[chan_index]) 770 | self.fm_sp_plot_hold_data_items.append(this_plot_data_item) 771 | 772 | self.fm_sp_plot_widget.show() 773 | 774 | def place_fm_spg_graphics(self): 775 | self.fm_spg_widget = pg.GraphicsLayoutWidget() 776 | self.ui.spg_verticalLayout.addWidget(self.fm_spg_widget) 777 | 778 | # view = self.ui.graphics_widget.addViewBox() 779 | self.graphics_plot = self.fm_spg_widget.addPlot() # will generate the view and axes automatically 780 | # Add labels to the axis 781 | # self.graphics_plot.setLabel('bottom', 'Time', units='s') # this way lots of space is wasted 782 | # If you include the units, Pyqtgraph automatically scales the axis and adjusts the SI prefix (in this case kHz) 783 | self.graphics_plot.setLabel('left', "Frequency", units='Hz') 784 | 785 | self.img = pg.ImageItem(border='w') 786 | self.graphics_plot.addItem(self.img) 787 | 788 | # self.ui.graphics_plot.setLogMode(True, False) 789 | 790 | self.hist = pg.HistogramLUTItem() 791 | self.hist.setImageItem(self.img) 792 | self.fm_spg_widget.addItem(self.hist) 793 | 794 | self.hist.gradient.restoreState(config.default_spg_hist_gradient) 795 | 796 | def place_cm_tm_graph(self): 797 | self.cm_tm_plot_widget = pg.PlotWidget(name='Continuous Timeseries') 798 | self.ui.cm_tm_verticalLayout.addWidget(self.cm_tm_plot_widget) 799 | cm_tm_plot_item = self.cm_tm_plot_widget.getPlotItem() 800 | # cm_tm_plot_item.vb.disableAutoRange() 801 | # cm_tm_plot_item.vb.setLimits(ymin=self.streaming_device.ai_min_val,ymax=self.streaming_device.ai_max_val) 802 | cm_tm_plot_item.showGrid(True, True, alpha=1) 803 | cm_tm_plot_item.setLabel('left', 'Lvl', units='V') 804 | 805 | for chan_index in range(config.NR_OF_CHANNELS_TO_PLOT): 806 | this_plot_data_item = cm_tm_plot_item.plot(pen=self.pen_case[chan_index]) 807 | self.cm_tm_plot_data_items.append(this_plot_data_item) 808 | this_plot_data_item = cm_tm_plot_item.plot(pen=self.hold_pen_case[chan_index]) 809 | self.cm_tm_plot_hold_data_items.append(this_plot_data_item) 810 | 811 | def place_cm_sp_graph(self): 812 | self.cm_sp_plot_widget = pg.PlotWidget(name='Continuous Spectral Analysis') 813 | self.ui.cm_sp_verticalLayout.addWidget(self.cm_sp_plot_widget) 814 | cm_sp_plot_item = self.cm_sp_plot_widget.getPlotItem() 815 | 816 | # cm_sp_plot_item.disableAutoRange() 817 | cm_sp_plot_item.setLogMode(True, False) 818 | cm_sp_plot_item.showGrid(True, True, alpha=1) 819 | cm_sp_plot_item.setLabel('left', 'Lvl', units='dBFS') 820 | 821 | for chan_index in range(config.NR_OF_CHANNELS_TO_PLOT): 822 | this_plot_data_item = cm_sp_plot_item.plot(pen=self.pen_case[chan_index]) 823 | self.cm_sp_plot_data_items.append(this_plot_data_item) 824 | this_plot_data_item = cm_sp_plot_item.plot(pen=self.hold_pen_case[chan_index]) 825 | self.cm_sp_plot_hold_data_items.append(this_plot_data_item) 826 | 827 | x_min = np.log10(config.MIN_OUTPUT_FREQUENCY_ALLOWED) 828 | x_max = np.log10(config.MAX_COARSE_FREQ_DIAL_LIMIT) 829 | y_min = config.CM_SP_GRAPH_MIN_dB_LIMIT 830 | y_max = config.CM_SP_GRAPH_MAX_dB_LIMIT 831 | 832 | cm_sp_plot_item.vb.setLimits(xMin=x_min, xMax=x_max, 833 | yMin=y_min, yMax=y_max) 834 | 835 | cm_sp_plot_item.setRange(xRange=(x_min, x_max), 836 | yRange=(y_min, y_max), 837 | disableAutoRange=True) 838 | 839 | def place_cm_ph_graph(self): 840 | self.cm_ph_plot_widget = pg.PlotWidget(name='Phase Scope') 841 | self.ui.cm_ph_horizontalLayout.addWidget(self.cm_ph_plot_widget) 842 | cm_ph_plot_item = self.cm_ph_plot_widget.getPlotItem() 843 | cm_ph_plot_item.showGrid(True, True, alpha=1) 844 | # cm_ph_plot_item.setLabel('left', 'Lvl', units='V') 845 | 846 | self.cm_ph_plot_data_item = cm_ph_plot_item.plot(pen=config.PHASE_PLOT_COLOR_MODIFIER) 847 | plot_hold_data_item1 = cm_ph_plot_item.plot(pen=config.PHASE_PLOT_HOLD_COLOR_MODIFIER) 848 | self.cm_ph_plot_hold_data_items.append(plot_hold_data_item1) 849 | plot_hold_data_item2 = cm_ph_plot_item.plot(pen=config.PHASE_PLOT_HOLD_COLOR_MODIFIER + 1) 850 | self.cm_ph_plot_hold_data_items.append(plot_hold_data_item2) 851 | self.cm_ph_plot_box_data_item = cm_ph_plot_item.plot(pen=config.PHASE_PLOT_LIMIT_COLOR_MODIFIER) 852 | 853 | # TODO GENERATE THE ROTATION MATRIX FOR 45* 854 | 855 | def update_output_buffer_indicator(self): 856 | ai_buffer_level = self.e.streaming_device.get_ai_buffer_level_prc() 857 | ao_buffer_level = self.e.streaming_device.get_ao_buffer_level_prc() 858 | 859 | self.ui.buffer_indicator_label.setText(f'ai:{ai_buffer_level}/ao:{ao_buffer_level}') 860 | 861 | def update_cm_tm_plot(self): 862 | self.e.streaming_device.read_lock.acquire() # TODO this probably isn't working 863 | input_frame = self.e.streaming_device.input_frame 864 | input_frame_timebase = self.e.streaming_device.input_time_base 865 | self.e.streaming_device.read_lock.release() 866 | 867 | for chan in range(config.NR_OF_CHANNELS_TO_PLOT): 868 | self.cm_tm_plot_data_items[chan].setData(input_frame_timebase[chan], input_frame[chan]) 869 | 870 | def update_cm_sp_plot(self): 871 | for chan in range(config.NR_OF_CHANNELS_TO_PLOT): 872 | # plot except for DC component to prevent warning with X logscale 873 | self.cm_sp_plot_data_items[chan].setData(self.e.cm_freq_base[1:], self.e.calculate_cm_fft()[chan][1:]) 874 | 875 | def update_cm_ph_plot(self): 876 | phase_frame = self.e.calculate_cm_phase() 877 | self.cm_ph_plot_data_item.setData(phase_frame) 878 | 879 | def update_fm_tm_plot(self): 880 | self.check_fm_data_present() 881 | self.print_qt('>>> Drawing...') 882 | 883 | for chan in range(config.NR_OF_CHANNELS_TO_PLOT): 884 | self.fm_tm_plot_data_items[chan].setData(self.e.fm_result_x, self.e.fm_result_y[chan]) 885 | 886 | def update_fm_sp_plot(self): 887 | self.check_fm_data_present() 888 | self.print_qt('>>> Calculating...') 889 | 890 | try: 891 | if self.e.streaming_device.function_gen.current_function != 'ess': 892 | fm_freq_base, fm_fft_segment_time, fm_db_fft = self.e.calculate_fm_fft() 893 | else: 894 | impulse_response = self.e.calculate_ir_from_fm_ess() 895 | fm_freq_base, fm_fft_segment_time, fm_db_fft = self.e.calculate_fm_fft(impulse_response) 896 | except ValueError as err: 897 | self.print_qt(err) 898 | return 899 | 900 | self.print_qt(f'{len(fm_fft_segment_time)} FFT segments average.') 901 | 902 | for chan in range(config.NR_OF_CHANNELS_TO_PLOT): 903 | self.fm_sp_plot_data_items[chan].setData(fm_freq_base[1:], fm_db_fft[chan][1:]) 904 | 905 | def update_fm_spg(self): 906 | self.check_fm_data_present() 907 | self.print_qt('>>> Calculating...') 908 | 909 | try: 910 | f, t, spg = self.e.calculate_fm_spectrogram() 911 | # axis 1 is the time axis 912 | except ValueError as err: 913 | self.print_qt(err) 914 | return 915 | except MemoryError as err: 916 | print(err) 917 | self.print_qt('>> EXCEPTION << : Out of memory. Try analyzing less data.') 918 | return 919 | 920 | # convert amplitudes to log scale if required: 921 | if self.ui.checkBox_fm_spg_log_amp.isChecked(): 922 | spg = np.log10(spg) 923 | 924 | # limit the frequency range of the analysis 925 | spg = spg[:][:int(np.size(spg, axis=0) * config.SPG_FREQUENCY_LIMIT / ( 926 | self.e.streaming_device.ai_fs / 2))] 927 | 928 | # Clip the freq axis accordingly 929 | f = f[:int(np.size(spg, axis=0))] 930 | 931 | self.hist.setLevels(np.min(spg), np.max(spg)) 932 | 933 | self.img.resetTransform() # Ensures that the previous transformation does not interfere with the current one 934 | self.img.scale(t[-1] / np.size(spg, axis=1), f[-1] / np.size(spg, axis=0)) 935 | self.img.setImage(spg.T) 936 | 937 | # Limit panning/zooming to the spectrogram 938 | self.graphics_plot.setLimits(xMin=0, xMax=t[-1], yMin=0, yMax=f[-1]) 939 | 940 | 941 | if __name__ == "__main__": 942 | TwingoExec() 943 | -------------------------------------------------------------------------------- /view/GUI_twingo.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'view\GUI_twingo.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.3 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | 10 | from PyQt5 import QtCore, QtGui, QtWidgets 11 | 12 | 13 | class Ui_MainWindow(object): 14 | def setupUi(self, MainWindow): 15 | MainWindow.setObjectName("MainWindow") 16 | MainWindow.resize(873, 564) 17 | self.centralwidget = QtWidgets.QWidget(MainWindow) 18 | self.centralwidget.setObjectName("centralwidget") 19 | self.gridLayout_3 = QtWidgets.QGridLayout(self.centralwidget) 20 | self.gridLayout_3.setObjectName("gridLayout_3") 21 | self.gridLayout = QtWidgets.QGridLayout() 22 | self.gridLayout.setObjectName("gridLayout") 23 | self.label_fm_max_f = QtWidgets.QLabel(self.centralwidget) 24 | self.label_fm_max_f.setObjectName("label_fm_max_f") 25 | self.gridLayout.addWidget(self.label_fm_max_f, 2, 2, 1, 1) 26 | self.comboBox_out_sig_type = QtWidgets.QComboBox(self.centralwidget) 27 | self.comboBox_out_sig_type.setObjectName("comboBox_out_sig_type") 28 | self.comboBox_out_sig_type.addItem("") 29 | self.comboBox_out_sig_type.addItem("") 30 | self.comboBox_out_sig_type.addItem("") 31 | self.comboBox_out_sig_type.addItem("") 32 | self.comboBox_out_sig_type.addItem("") 33 | self.comboBox_out_sig_type.addItem("") 34 | self.comboBox_out_sig_type.addItem("") 35 | self.gridLayout.addWidget(self.comboBox_out_sig_type, 1, 1, 1, 1) 36 | self.lineEdit_max_f = QtWidgets.QLineEdit(self.centralwidget) 37 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) 38 | sizePolicy.setHorizontalStretch(0) 39 | sizePolicy.setVerticalStretch(0) 40 | sizePolicy.setHeightForWidth(self.lineEdit_max_f.sizePolicy().hasHeightForWidth()) 41 | self.lineEdit_max_f.setSizePolicy(sizePolicy) 42 | self.lineEdit_max_f.setText("") 43 | self.lineEdit_max_f.setObjectName("lineEdit_max_f") 44 | self.gridLayout.addWidget(self.lineEdit_max_f, 2, 3, 1, 1) 45 | self.pushButton_stop = QtWidgets.QPushButton(self.centralwidget) 46 | self.pushButton_stop.setEnabled(False) 47 | self.pushButton_stop.setObjectName("pushButton_stop") 48 | self.gridLayout.addWidget(self.pushButton_stop, 2, 5, 1, 1) 49 | self.label_min_f = QtWidgets.QLabel(self.centralwidget) 50 | self.label_min_f.setObjectName("label_min_f") 51 | self.gridLayout.addWidget(self.label_min_f, 1, 2, 1, 1) 52 | self.lineEdit_min_f = QtWidgets.QLineEdit(self.centralwidget) 53 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed) 54 | sizePolicy.setHorizontalStretch(0) 55 | sizePolicy.setVerticalStretch(0) 56 | sizePolicy.setHeightForWidth(self.lineEdit_min_f.sizePolicy().hasHeightForWidth()) 57 | self.lineEdit_min_f.setSizePolicy(sizePolicy) 58 | self.lineEdit_min_f.setText("") 59 | self.lineEdit_min_f.setObjectName("lineEdit_min_f") 60 | self.gridLayout.addWidget(self.lineEdit_min_f, 1, 3, 1, 1) 61 | self.label_14 = QtWidgets.QLabel(self.centralwidget) 62 | self.label_14.setObjectName("label_14") 63 | self.gridLayout.addWidget(self.label_14, 1, 0, 1, 1) 64 | self.comboBox_sig_len = QtWidgets.QComboBox(self.centralwidget) 65 | self.comboBox_sig_len.setEditable(False) 66 | self.comboBox_sig_len.setObjectName("comboBox_sig_len") 67 | self.comboBox_sig_len.addItem("") 68 | self.comboBox_sig_len.addItem("") 69 | self.comboBox_sig_len.addItem("") 70 | self.comboBox_sig_len.addItem("") 71 | self.comboBox_sig_len.addItem("") 72 | self.comboBox_sig_len.addItem("") 73 | self.comboBox_sig_len.addItem("") 74 | self.comboBox_sig_len.addItem("") 75 | self.comboBox_sig_len.addItem("") 76 | self.comboBox_sig_len.addItem("") 77 | self.comboBox_sig_len.addItem("") 78 | self.comboBox_sig_len.addItem("") 79 | self.comboBox_sig_len.addItem("") 80 | self.comboBox_sig_len.addItem("") 81 | self.gridLayout.addWidget(self.comboBox_sig_len, 2, 1, 1, 1) 82 | self.label_15 = QtWidgets.QLabel(self.centralwidget) 83 | self.label_15.setObjectName("label_15") 84 | self.gridLayout.addWidget(self.label_15, 2, 0, 1, 1) 85 | self.pushButton_start = QtWidgets.QPushButton(self.centralwidget) 86 | self.pushButton_start.setEnabled(False) 87 | palette = QtGui.QPalette() 88 | brush = QtGui.QBrush(QtGui.QColor(5, 112, 0)) 89 | brush.setStyle(QtCore.Qt.SolidPattern) 90 | palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) 91 | brush = QtGui.QBrush(QtGui.QColor(5, 112, 0)) 92 | brush.setStyle(QtCore.Qt.SolidPattern) 93 | palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) 94 | brush = QtGui.QBrush(QtGui.QColor(5, 112, 0)) 95 | brush.setStyle(QtCore.Qt.SolidPattern) 96 | palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) 97 | self.pushButton_start.setPalette(palette) 98 | font = QtGui.QFont() 99 | font.setBold(True) 100 | font.setWeight(75) 101 | self.pushButton_start.setFont(font) 102 | self.pushButton_start.setAutoFillBackground(False) 103 | self.pushButton_start.setObjectName("pushButton_start") 104 | self.gridLayout.addWidget(self.pushButton_start, 1, 5, 1, 1) 105 | self.buffer_indicator_label = QtWidgets.QLabel(self.centralwidget) 106 | font = QtGui.QFont() 107 | font.setPointSize(6) 108 | self.buffer_indicator_label.setFont(font) 109 | self.buffer_indicator_label.setText("") 110 | self.buffer_indicator_label.setObjectName("buffer_indicator_label") 111 | self.gridLayout.addWidget(self.buffer_indicator_label, 1, 4, 1, 1) 112 | spacerItem = QtWidgets.QSpacerItem(65, 0, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum) 113 | self.gridLayout.addItem(spacerItem, 2, 4, 1, 1) 114 | self.gridLayout_3.addLayout(self.gridLayout, 1, 0, 1, 1) 115 | self.tabWidget_main = QtWidgets.QTabWidget(self.centralwidget) 116 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) 117 | sizePolicy.setHorizontalStretch(0) 118 | sizePolicy.setVerticalStretch(0) 119 | sizePolicy.setHeightForWidth(self.tabWidget_main.sizePolicy().hasHeightForWidth()) 120 | self.tabWidget_main.setSizePolicy(sizePolicy) 121 | self.tabWidget_main.setObjectName("tabWidget_main") 122 | self.tab_settings = QtWidgets.QWidget() 123 | self.tab_settings.setObjectName("tab_settings") 124 | self.gridLayout_4 = QtWidgets.QGridLayout(self.tab_settings) 125 | self.gridLayout_4.setObjectName("gridLayout_4") 126 | self.horizontalLayout = QtWidgets.QHBoxLayout() 127 | self.horizontalLayout.setObjectName("horizontalLayout") 128 | self.pushButton_detectDAQ = QtWidgets.QPushButton(self.tab_settings) 129 | self.pushButton_detectDAQ.setObjectName("pushButton_detectDAQ") 130 | self.horizontalLayout.addWidget(self.pushButton_detectDAQ) 131 | self.comboBox_dev_list = QtWidgets.QComboBox(self.tab_settings) 132 | self.comboBox_dev_list.setStatusTip("") 133 | self.comboBox_dev_list.setFrame(True) 134 | self.comboBox_dev_list.setObjectName("comboBox_dev_list") 135 | self.horizontalLayout.addWidget(self.comboBox_dev_list) 136 | self.gridLayout_4.addLayout(self.horizontalLayout, 1, 0, 1, 1) 137 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 138 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 139 | self.formLayout = QtWidgets.QFormLayout() 140 | self.formLayout.setHorizontalSpacing(7) 141 | self.formLayout.setObjectName("formLayout") 142 | self.label_7 = QtWidgets.QLabel(self.tab_settings) 143 | font = QtGui.QFont() 144 | font.setBold(True) 145 | font.setWeight(75) 146 | self.label_7.setFont(font) 147 | self.label_7.setObjectName("label_7") 148 | self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.label_7) 149 | self.label_4 = QtWidgets.QLabel(self.tab_settings) 150 | self.label_4.setObjectName("label_4") 151 | self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_4) 152 | self.comboBox_ai_a = QtWidgets.QComboBox(self.tab_settings) 153 | self.comboBox_ai_a.setObjectName("comboBox_ai_a") 154 | self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.comboBox_ai_a) 155 | self.label_3 = QtWidgets.QLabel(self.tab_settings) 156 | self.label_3.setObjectName("label_3") 157 | self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_3) 158 | self.comboBox_ai_b = QtWidgets.QComboBox(self.tab_settings) 159 | self.comboBox_ai_b.setObjectName("comboBox_ai_b") 160 | self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.comboBox_ai_b) 161 | self.label_5 = QtWidgets.QLabel(self.tab_settings) 162 | self.label_5.setObjectName("label_5") 163 | self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.label_5) 164 | self.comboBox_ai_terminal_cfg = QtWidgets.QComboBox(self.tab_settings) 165 | self.comboBox_ai_terminal_cfg.setObjectName("comboBox_ai_terminal_cfg") 166 | self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.comboBox_ai_terminal_cfg) 167 | self.label = QtWidgets.QLabel(self.tab_settings) 168 | self.label.setObjectName("label") 169 | self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label) 170 | self.comboBox_ai_max = QtWidgets.QComboBox(self.tab_settings) 171 | self.comboBox_ai_max.setObjectName("comboBox_ai_max") 172 | self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.comboBox_ai_max) 173 | self.label_2 = QtWidgets.QLabel(self.tab_settings) 174 | self.label_2.setObjectName("label_2") 175 | self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.label_2) 176 | self.comboBox_ai_min = QtWidgets.QComboBox(self.tab_settings) 177 | self.comboBox_ai_min.setObjectName("comboBox_ai_min") 178 | self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.comboBox_ai_min) 179 | self.label_6 = QtWidgets.QLabel(self.tab_settings) 180 | self.label_6.setObjectName("label_6") 181 | self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_6) 182 | self.comboBox_ai_fs = QtWidgets.QComboBox(self.tab_settings) 183 | self.comboBox_ai_fs.setObjectName("comboBox_ai_fs") 184 | self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.comboBox_ai_fs) 185 | self.horizontalLayout_2.addLayout(self.formLayout) 186 | self.line = QtWidgets.QFrame(self.tab_settings) 187 | self.line.setFrameShape(QtWidgets.QFrame.VLine) 188 | self.line.setFrameShadow(QtWidgets.QFrame.Sunken) 189 | self.line.setObjectName("line") 190 | self.horizontalLayout_2.addWidget(self.line) 191 | self.formLayout_2 = QtWidgets.QFormLayout() 192 | self.formLayout_2.setObjectName("formLayout_2") 193 | self.label_8 = QtWidgets.QLabel(self.tab_settings) 194 | font = QtGui.QFont() 195 | font.setBold(True) 196 | font.setWeight(75) 197 | self.label_8.setFont(font) 198 | self.label_8.setObjectName("label_8") 199 | self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.label_8) 200 | self.label_9 = QtWidgets.QLabel(self.tab_settings) 201 | self.label_9.setObjectName("label_9") 202 | self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_9) 203 | self.comboBox_ao_a = QtWidgets.QComboBox(self.tab_settings) 204 | self.comboBox_ao_a.setObjectName("comboBox_ao_a") 205 | self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.comboBox_ao_a) 206 | self.label_10 = QtWidgets.QLabel(self.tab_settings) 207 | self.label_10.setObjectName("label_10") 208 | self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.label_10) 209 | self.comboBox_ao_b = QtWidgets.QComboBox(self.tab_settings) 210 | self.comboBox_ao_b.setObjectName("comboBox_ao_b") 211 | self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.comboBox_ao_b) 212 | self.Label = QtWidgets.QLabel(self.tab_settings) 213 | self.Label.setEnabled(False) 214 | font = QtGui.QFont() 215 | font.setStrikeOut(False) 216 | self.Label.setFont(font) 217 | self.Label.setObjectName("Label") 218 | self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.Label) 219 | self.comboBox = QtWidgets.QComboBox(self.tab_settings) 220 | self.comboBox.setEnabled(False) 221 | self.comboBox.setObjectName("comboBox") 222 | self.formLayout_2.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.comboBox) 223 | self.label_11 = QtWidgets.QLabel(self.tab_settings) 224 | self.label_11.setObjectName("label_11") 225 | self.formLayout_2.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.label_11) 226 | self.comboBox_ao_max = QtWidgets.QComboBox(self.tab_settings) 227 | self.comboBox_ao_max.setObjectName("comboBox_ao_max") 228 | self.formLayout_2.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.comboBox_ao_max) 229 | self.label_12 = QtWidgets.QLabel(self.tab_settings) 230 | self.label_12.setObjectName("label_12") 231 | self.formLayout_2.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.label_12) 232 | self.comboBox_ao_min = QtWidgets.QComboBox(self.tab_settings) 233 | self.comboBox_ao_min.setObjectName("comboBox_ao_min") 234 | self.formLayout_2.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.comboBox_ao_min) 235 | self.label_13 = QtWidgets.QLabel(self.tab_settings) 236 | self.label_13.setObjectName("label_13") 237 | self.formLayout_2.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.label_13) 238 | self.comboBox_ao_fs = QtWidgets.QComboBox(self.tab_settings) 239 | self.comboBox_ao_fs.setObjectName("comboBox_ao_fs") 240 | self.formLayout_2.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.comboBox_ao_fs) 241 | self.horizontalLayout_2.addLayout(self.formLayout_2) 242 | self.gridLayout_4.addLayout(self.horizontalLayout_2, 3, 0, 1, 1) 243 | spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 244 | self.gridLayout_4.addItem(spacerItem1, 0, 0, 1, 1) 245 | self.tabWidget_main.addTab(self.tab_settings, "") 246 | self.tab_cm = QtWidgets.QWidget() 247 | self.tab_cm.setMaximumSize(QtCore.QSize(16777215, 16777215)) 248 | font = QtGui.QFont() 249 | font.setKerning(True) 250 | self.tab_cm.setFont(font) 251 | self.tab_cm.setObjectName("tab_cm") 252 | self.gridLayout_7 = QtWidgets.QGridLayout(self.tab_cm) 253 | self.gridLayout_7.setObjectName("gridLayout_7") 254 | self.groupBox = QtWidgets.QGroupBox(self.tab_cm) 255 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 256 | sizePolicy.setHorizontalStretch(0) 257 | sizePolicy.setVerticalStretch(0) 258 | sizePolicy.setHeightForWidth(self.groupBox.sizePolicy().hasHeightForWidth()) 259 | self.groupBox.setSizePolicy(sizePolicy) 260 | self.groupBox.setObjectName("groupBox") 261 | self.verticalLayout = QtWidgets.QVBoxLayout(self.groupBox) 262 | self.verticalLayout.setObjectName("verticalLayout") 263 | self.lcdNumber = QtWidgets.QLCDNumber(self.groupBox) 264 | self.lcdNumber.setProperty("value", 1000.0) 265 | self.lcdNumber.setProperty("intValue", 1000) 266 | self.lcdNumber.setObjectName("lcdNumber") 267 | self.verticalLayout.addWidget(self.lcdNumber) 268 | self.dial_cm_vfine_freq = QtWidgets.QDial(self.groupBox) 269 | self.dial_cm_vfine_freq.setMinimum(1) 270 | self.dial_cm_vfine_freq.setMaximum(100) 271 | self.dial_cm_vfine_freq.setPageStep(10) 272 | self.dial_cm_vfine_freq.setProperty("value", 50) 273 | self.dial_cm_vfine_freq.setWrapping(False) 274 | self.dial_cm_vfine_freq.setNotchTarget(2.0) 275 | self.dial_cm_vfine_freq.setNotchesVisible(True) 276 | self.dial_cm_vfine_freq.setObjectName("dial_cm_vfine_freq") 277 | self.verticalLayout.addWidget(self.dial_cm_vfine_freq) 278 | self.dial_cm_fine_freq = QtWidgets.QDial(self.groupBox) 279 | self.dial_cm_fine_freq.setMinimum(0) 280 | self.dial_cm_fine_freq.setMaximum(1000) 281 | self.dial_cm_fine_freq.setSingleStep(10) 282 | self.dial_cm_fine_freq.setPageStep(100) 283 | self.dial_cm_fine_freq.setProperty("value", 500) 284 | self.dial_cm_fine_freq.setTracking(True) 285 | self.dial_cm_fine_freq.setWrapping(False) 286 | self.dial_cm_fine_freq.setNotchTarget(5.0) 287 | self.dial_cm_fine_freq.setNotchesVisible(True) 288 | self.dial_cm_fine_freq.setObjectName("dial_cm_fine_freq") 289 | self.verticalLayout.addWidget(self.dial_cm_fine_freq) 290 | self.dial_cm_coarse_freq = QtWidgets.QDial(self.groupBox) 291 | self.dial_cm_coarse_freq.setLayoutDirection(QtCore.Qt.LeftToRight) 292 | self.dial_cm_coarse_freq.setAutoFillBackground(False) 293 | self.dial_cm_coarse_freq.setMinimum(0) 294 | self.dial_cm_coarse_freq.setMaximum(20000) 295 | self.dial_cm_coarse_freq.setSingleStep(100) 296 | self.dial_cm_coarse_freq.setPageStep(1000) 297 | self.dial_cm_coarse_freq.setProperty("value", 1000) 298 | self.dial_cm_coarse_freq.setTracking(True) 299 | self.dial_cm_coarse_freq.setInvertedAppearance(False) 300 | self.dial_cm_coarse_freq.setWrapping(False) 301 | self.dial_cm_coarse_freq.setNotchTarget(1.0) 302 | self.dial_cm_coarse_freq.setNotchesVisible(True) 303 | self.dial_cm_coarse_freq.setObjectName("dial_cm_coarse_freq") 304 | self.verticalLayout.addWidget(self.dial_cm_coarse_freq) 305 | self.gridLayout_7.addWidget(self.groupBox, 0, 1, 1, 1) 306 | self.tabWidget_cm = QtWidgets.QTabWidget(self.tab_cm) 307 | self.tabWidget_cm.setEnabled(True) 308 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) 309 | sizePolicy.setHorizontalStretch(0) 310 | sizePolicy.setVerticalStretch(0) 311 | sizePolicy.setHeightForWidth(self.tabWidget_cm.sizePolicy().hasHeightForWidth()) 312 | self.tabWidget_cm.setSizePolicy(sizePolicy) 313 | self.tabWidget_cm.setObjectName("tabWidget_cm") 314 | self.tab_cm_timeseries = QtWidgets.QWidget() 315 | self.tab_cm_timeseries.setObjectName("tab_cm_timeseries") 316 | self.gridLayout_10 = QtWidgets.QGridLayout(self.tab_cm_timeseries) 317 | self.gridLayout_10.setObjectName("gridLayout_10") 318 | self.cm_tm_verticalLayout = QtWidgets.QVBoxLayout() 319 | self.cm_tm_verticalLayout.setObjectName("cm_tm_verticalLayout") 320 | self.gridLayout_10.addLayout(self.cm_tm_verticalLayout, 0, 0, 1, 1) 321 | self.horizontalLayout_6 = QtWidgets.QHBoxLayout() 322 | self.horizontalLayout_6.setObjectName("horizontalLayout_6") 323 | spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 324 | self.horizontalLayout_6.addItem(spacerItem2) 325 | spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 326 | self.horizontalLayout_6.addItem(spacerItem3) 327 | self.label_26 = QtWidgets.QLabel(self.tab_cm_timeseries) 328 | self.label_26.setEnabled(False) 329 | self.label_26.setObjectName("label_26") 330 | self.horizontalLayout_6.addWidget(self.label_26) 331 | spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 332 | self.horizontalLayout_6.addItem(spacerItem4) 333 | self.checkBox_cm_tm_hold_a = QtWidgets.QCheckBox(self.tab_cm_timeseries) 334 | self.checkBox_cm_tm_hold_a.setEnabled(True) 335 | self.checkBox_cm_tm_hold_a.setObjectName("checkBox_cm_tm_hold_a") 336 | self.horizontalLayout_6.addWidget(self.checkBox_cm_tm_hold_a) 337 | self.checkBox_cm_tm_hold_b = QtWidgets.QCheckBox(self.tab_cm_timeseries) 338 | self.checkBox_cm_tm_hold_b.setEnabled(True) 339 | self.checkBox_cm_tm_hold_b.setObjectName("checkBox_cm_tm_hold_b") 340 | self.horizontalLayout_6.addWidget(self.checkBox_cm_tm_hold_b) 341 | self.gridLayout_10.addLayout(self.horizontalLayout_6, 1, 0, 1, 1) 342 | self.tabWidget_cm.addTab(self.tab_cm_timeseries, "") 343 | self.tab_cm_spectrum = QtWidgets.QWidget() 344 | self.tab_cm_spectrum.setObjectName("tab_cm_spectrum") 345 | self.gridLayout_11 = QtWidgets.QGridLayout(self.tab_cm_spectrum) 346 | self.gridLayout_11.setObjectName("gridLayout_11") 347 | self.cm_sp_verticalLayout = QtWidgets.QVBoxLayout() 348 | self.cm_sp_verticalLayout.setObjectName("cm_sp_verticalLayout") 349 | self.gridLayout_11.addLayout(self.cm_sp_verticalLayout, 0, 0, 1, 1) 350 | self.horizontalLayout_7 = QtWidgets.QHBoxLayout() 351 | self.horizontalLayout_7.setObjectName("horizontalLayout_7") 352 | self.label_21 = QtWidgets.QLabel(self.tab_cm_spectrum) 353 | self.label_21.setObjectName("label_21") 354 | self.horizontalLayout_7.addWidget(self.label_21) 355 | self.comboBox_cm_window_type = QtWidgets.QComboBox(self.tab_cm_spectrum) 356 | self.comboBox_cm_window_type.setObjectName("comboBox_cm_window_type") 357 | self.comboBox_cm_window_type.addItem("") 358 | self.comboBox_cm_window_type.addItem("") 359 | self.comboBox_cm_window_type.addItem("") 360 | self.comboBox_cm_window_type.addItem("") 361 | self.comboBox_cm_window_type.addItem("") 362 | self.comboBox_cm_window_type.addItem("") 363 | self.horizontalLayout_7.addWidget(self.comboBox_cm_window_type) 364 | self.label_22 = QtWidgets.QLabel(self.tab_cm_spectrum) 365 | self.label_22.setObjectName("label_22") 366 | self.horizontalLayout_7.addWidget(self.label_22) 367 | self.comboBox_cm_window_size = QtWidgets.QComboBox(self.tab_cm_spectrum) 368 | self.comboBox_cm_window_size.setCurrentText("") 369 | self.comboBox_cm_window_size.setObjectName("comboBox_cm_window_size") 370 | self.horizontalLayout_7.addWidget(self.comboBox_cm_window_size) 371 | spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 372 | self.horizontalLayout_7.addItem(spacerItem5) 373 | self.label_27 = QtWidgets.QLabel(self.tab_cm_spectrum) 374 | self.label_27.setEnabled(False) 375 | self.label_27.setObjectName("label_27") 376 | self.horizontalLayout_7.addWidget(self.label_27) 377 | spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 378 | self.horizontalLayout_7.addItem(spacerItem6) 379 | self.checkBox_cm_sp_hold_a = QtWidgets.QCheckBox(self.tab_cm_spectrum) 380 | self.checkBox_cm_sp_hold_a.setEnabled(True) 381 | self.checkBox_cm_sp_hold_a.setObjectName("checkBox_cm_sp_hold_a") 382 | self.horizontalLayout_7.addWidget(self.checkBox_cm_sp_hold_a) 383 | self.checkBox_cm_sp_hold_b = QtWidgets.QCheckBox(self.tab_cm_spectrum) 384 | self.checkBox_cm_sp_hold_b.setEnabled(True) 385 | self.checkBox_cm_sp_hold_b.setObjectName("checkBox_cm_sp_hold_b") 386 | self.horizontalLayout_7.addWidget(self.checkBox_cm_sp_hold_b) 387 | self.gridLayout_11.addLayout(self.horizontalLayout_7, 1, 0, 1, 1) 388 | self.tabWidget_cm.addTab(self.tab_cm_spectrum, "") 389 | self.tab_cm_phase = QtWidgets.QWidget() 390 | self.tab_cm_phase.setObjectName("tab_cm_phase") 391 | self.gridLayout_9 = QtWidgets.QGridLayout(self.tab_cm_phase) 392 | self.gridLayout_9.setObjectName("gridLayout_9") 393 | self.cm_ph_horizontalLayout = QtWidgets.QHBoxLayout() 394 | self.cm_ph_horizontalLayout.setObjectName("cm_ph_horizontalLayout") 395 | self.gridLayout_9.addLayout(self.cm_ph_horizontalLayout, 0, 0, 1, 1) 396 | self.horizontalLayout_8 = QtWidgets.QHBoxLayout() 397 | self.horizontalLayout_8.setObjectName("horizontalLayout_8") 398 | spacerItem7 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 399 | self.horizontalLayout_8.addItem(spacerItem7) 400 | self.label_28 = QtWidgets.QLabel(self.tab_cm_phase) 401 | self.label_28.setObjectName("label_28") 402 | self.horizontalLayout_8.addWidget(self.label_28) 403 | self.comboBox_cm_ph_window_size = QtWidgets.QComboBox(self.tab_cm_phase) 404 | self.comboBox_cm_ph_window_size.setObjectName("comboBox_cm_ph_window_size") 405 | self.horizontalLayout_8.addWidget(self.comboBox_cm_ph_window_size) 406 | spacerItem8 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 407 | self.horizontalLayout_8.addItem(spacerItem8) 408 | self.label_29 = QtWidgets.QLabel(self.tab_cm_phase) 409 | self.label_29.setEnabled(False) 410 | self.label_29.setObjectName("label_29") 411 | self.horizontalLayout_8.addWidget(self.label_29) 412 | spacerItem9 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 413 | self.horizontalLayout_8.addItem(spacerItem9) 414 | self.checkBox_cm_ph_hold_1 = QtWidgets.QCheckBox(self.tab_cm_phase) 415 | self.checkBox_cm_ph_hold_1.setObjectName("checkBox_cm_ph_hold_1") 416 | self.horizontalLayout_8.addWidget(self.checkBox_cm_ph_hold_1) 417 | self.checkBox_cm_ph_hold_2 = QtWidgets.QCheckBox(self.tab_cm_phase) 418 | self.checkBox_cm_ph_hold_2.setObjectName("checkBox_cm_ph_hold_2") 419 | self.horizontalLayout_8.addWidget(self.checkBox_cm_ph_hold_2) 420 | self.gridLayout_9.addLayout(self.horizontalLayout_8, 1, 0, 1, 1) 421 | self.tabWidget_cm.addTab(self.tab_cm_phase, "") 422 | self.gridLayout_7.addWidget(self.tabWidget_cm, 0, 0, 1, 1) 423 | self.tabWidget_main.addTab(self.tab_cm, "") 424 | self.tab_fm = QtWidgets.QWidget() 425 | self.tab_fm.setObjectName("tab_fm") 426 | self.gridLayout_2 = QtWidgets.QGridLayout(self.tab_fm) 427 | self.gridLayout_2.setObjectName("gridLayout_2") 428 | self.tabWidget_fm = QtWidgets.QTabWidget(self.tab_fm) 429 | self.tabWidget_fm.setObjectName("tabWidget_fm") 430 | self.tab_timeseries = QtWidgets.QWidget() 431 | self.tab_timeseries.setObjectName("tab_timeseries") 432 | self.gridLayout_5 = QtWidgets.QGridLayout(self.tab_timeseries) 433 | self.gridLayout_5.setObjectName("gridLayout_5") 434 | self.tm_verticalLayout = QtWidgets.QVBoxLayout() 435 | self.tm_verticalLayout.setObjectName("tm_verticalLayout") 436 | self.gridLayout_5.addLayout(self.tm_verticalLayout, 1, 0, 1, 1) 437 | self.horizontalLayout_4 = QtWidgets.QHBoxLayout() 438 | self.horizontalLayout_4.setObjectName("horizontalLayout_4") 439 | spacerItem10 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 440 | self.horizontalLayout_4.addItem(spacerItem10) 441 | spacerItem11 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 442 | self.horizontalLayout_4.addItem(spacerItem11) 443 | self.label_23 = QtWidgets.QLabel(self.tab_timeseries) 444 | self.label_23.setEnabled(False) 445 | self.label_23.setObjectName("label_23") 446 | self.horizontalLayout_4.addWidget(self.label_23) 447 | spacerItem12 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 448 | self.horizontalLayout_4.addItem(spacerItem12) 449 | self.checkBox_fm_tm_hold_a = QtWidgets.QCheckBox(self.tab_timeseries) 450 | self.checkBox_fm_tm_hold_a.setEnabled(True) 451 | self.checkBox_fm_tm_hold_a.setObjectName("checkBox_fm_tm_hold_a") 452 | self.horizontalLayout_4.addWidget(self.checkBox_fm_tm_hold_a) 453 | self.checkBox_fm_tm_hold_b = QtWidgets.QCheckBox(self.tab_timeseries) 454 | self.checkBox_fm_tm_hold_b.setEnabled(True) 455 | self.checkBox_fm_tm_hold_b.setObjectName("checkBox_fm_tm_hold_b") 456 | self.horizontalLayout_4.addWidget(self.checkBox_fm_tm_hold_b) 457 | self.gridLayout_5.addLayout(self.horizontalLayout_4, 2, 0, 1, 1) 458 | self.tabWidget_fm.addTab(self.tab_timeseries, "") 459 | self.tab_spectrum = QtWidgets.QWidget() 460 | self.tab_spectrum.setObjectName("tab_spectrum") 461 | self.gridLayout_6 = QtWidgets.QGridLayout(self.tab_spectrum) 462 | self.gridLayout_6.setObjectName("gridLayout_6") 463 | self.sp_verticalLayout = QtWidgets.QVBoxLayout() 464 | self.sp_verticalLayout.setObjectName("sp_verticalLayout") 465 | self.gridLayout_6.addLayout(self.sp_verticalLayout, 4, 0, 1, 1) 466 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout() 467 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 468 | self.label_19 = QtWidgets.QLabel(self.tab_spectrum) 469 | self.label_19.setObjectName("label_19") 470 | self.horizontalLayout_3.addWidget(self.label_19) 471 | self.comboBox_fm_window_type = QtWidgets.QComboBox(self.tab_spectrum) 472 | self.comboBox_fm_window_type.setMinimumSize(QtCore.QSize(117, 0)) 473 | self.comboBox_fm_window_type.setObjectName("comboBox_fm_window_type") 474 | self.comboBox_fm_window_type.addItem("") 475 | self.comboBox_fm_window_type.addItem("") 476 | self.comboBox_fm_window_type.addItem("") 477 | self.comboBox_fm_window_type.addItem("") 478 | self.comboBox_fm_window_type.addItem("") 479 | self.comboBox_fm_window_type.addItem("") 480 | self.horizontalLayout_3.addWidget(self.comboBox_fm_window_type) 481 | self.label_20 = QtWidgets.QLabel(self.tab_spectrum) 482 | self.label_20.setObjectName("label_20") 483 | self.horizontalLayout_3.addWidget(self.label_20) 484 | self.comboBox_fm_window_size = QtWidgets.QComboBox(self.tab_spectrum) 485 | self.comboBox_fm_window_size.setObjectName("comboBox_fm_window_size") 486 | self.horizontalLayout_3.addWidget(self.comboBox_fm_window_size) 487 | spacerItem13 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 488 | self.horizontalLayout_3.addItem(spacerItem13) 489 | self.label_24 = QtWidgets.QLabel(self.tab_spectrum) 490 | self.label_24.setEnabled(False) 491 | self.label_24.setObjectName("label_24") 492 | self.horizontalLayout_3.addWidget(self.label_24) 493 | spacerItem14 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 494 | self.horizontalLayout_3.addItem(spacerItem14) 495 | self.checkBox_fm_sp_hold_a = QtWidgets.QCheckBox(self.tab_spectrum) 496 | self.checkBox_fm_sp_hold_a.setEnabled(True) 497 | font = QtGui.QFont() 498 | font.setBold(False) 499 | font.setWeight(50) 500 | self.checkBox_fm_sp_hold_a.setFont(font) 501 | self.checkBox_fm_sp_hold_a.setAutoFillBackground(False) 502 | self.checkBox_fm_sp_hold_a.setObjectName("checkBox_fm_sp_hold_a") 503 | self.horizontalLayout_3.addWidget(self.checkBox_fm_sp_hold_a) 504 | self.checkBox_fm_sp_hold_b = QtWidgets.QCheckBox(self.tab_spectrum) 505 | self.checkBox_fm_sp_hold_b.setEnabled(True) 506 | self.checkBox_fm_sp_hold_b.setObjectName("checkBox_fm_sp_hold_b") 507 | self.horizontalLayout_3.addWidget(self.checkBox_fm_sp_hold_b) 508 | self.gridLayout_6.addLayout(self.horizontalLayout_3, 5, 0, 1, 1) 509 | self.tabWidget_fm.addTab(self.tab_spectrum, "") 510 | self.tab_spg = QtWidgets.QWidget() 511 | self.tab_spg.setObjectName("tab_spg") 512 | self.gridLayout_8 = QtWidgets.QGridLayout(self.tab_spg) 513 | self.gridLayout_8.setObjectName("gridLayout_8") 514 | self.spg_verticalLayout = QtWidgets.QVBoxLayout() 515 | self.spg_verticalLayout.setObjectName("spg_verticalLayout") 516 | self.gridLayout_8.addLayout(self.spg_verticalLayout, 1, 0, 1, 1) 517 | self.horizontalLayout_5 = QtWidgets.QHBoxLayout() 518 | self.horizontalLayout_5.setObjectName("horizontalLayout_5") 519 | self.label_17 = QtWidgets.QLabel(self.tab_spg) 520 | self.label_17.setObjectName("label_17") 521 | self.horizontalLayout_5.addWidget(self.label_17) 522 | self.comboBox_fm_spg_window_type = QtWidgets.QComboBox(self.tab_spg) 523 | self.comboBox_fm_spg_window_type.setObjectName("comboBox_fm_spg_window_type") 524 | self.comboBox_fm_spg_window_type.addItem("") 525 | self.comboBox_fm_spg_window_type.addItem("") 526 | self.comboBox_fm_spg_window_type.addItem("") 527 | self.comboBox_fm_spg_window_type.addItem("") 528 | self.comboBox_fm_spg_window_type.addItem("") 529 | self.horizontalLayout_5.addWidget(self.comboBox_fm_spg_window_type) 530 | self.label_16 = QtWidgets.QLabel(self.tab_spg) 531 | self.label_16.setObjectName("label_16") 532 | self.horizontalLayout_5.addWidget(self.label_16) 533 | self.comboBox_fm_spg_window_size = QtWidgets.QComboBox(self.tab_spg) 534 | self.comboBox_fm_spg_window_size.setObjectName("comboBox_fm_spg_window_size") 535 | self.horizontalLayout_5.addWidget(self.comboBox_fm_spg_window_size) 536 | spacerItem15 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 537 | self.horizontalLayout_5.addItem(spacerItem15) 538 | self.label_25 = QtWidgets.QLabel(self.tab_spg) 539 | self.label_25.setEnabled(False) 540 | self.label_25.setObjectName("label_25") 541 | self.horizontalLayout_5.addWidget(self.label_25) 542 | spacerItem16 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 543 | self.horizontalLayout_5.addItem(spacerItem16) 544 | self.label_18 = QtWidgets.QLabel(self.tab_spg) 545 | self.label_18.setObjectName("label_18") 546 | self.horizontalLayout_5.addWidget(self.label_18) 547 | self.comboBox_fm_spg_chan = QtWidgets.QComboBox(self.tab_spg) 548 | self.comboBox_fm_spg_chan.setObjectName("comboBox_fm_spg_chan") 549 | self.comboBox_fm_spg_chan.addItem("") 550 | self.comboBox_fm_spg_chan.addItem("") 551 | self.horizontalLayout_5.addWidget(self.comboBox_fm_spg_chan) 552 | self.checkBox_fm_spg_log_amp = QtWidgets.QCheckBox(self.tab_spg) 553 | self.checkBox_fm_spg_log_amp.setChecked(True) 554 | self.checkBox_fm_spg_log_amp.setObjectName("checkBox_fm_spg_log_amp") 555 | self.horizontalLayout_5.addWidget(self.checkBox_fm_spg_log_amp) 556 | self.checkBox_fm_log_f = QtWidgets.QCheckBox(self.tab_spg) 557 | self.checkBox_fm_log_f.setEnabled(False) 558 | self.checkBox_fm_log_f.setObjectName("checkBox_fm_log_f") 559 | self.horizontalLayout_5.addWidget(self.checkBox_fm_log_f) 560 | self.gridLayout_8.addLayout(self.horizontalLayout_5, 2, 0, 1, 1) 561 | self.tabWidget_fm.addTab(self.tab_spg, "") 562 | self.gridLayout_2.addWidget(self.tabWidget_fm, 1, 0, 1, 1) 563 | self.tabWidget_main.addTab(self.tab_fm, "") 564 | self.gridLayout_3.addWidget(self.tabWidget_main, 0, 0, 1, 1) 565 | MainWindow.setCentralWidget(self.centralwidget) 566 | self.statusbar = QtWidgets.QStatusBar(MainWindow) 567 | self.statusbar.setObjectName("statusbar") 568 | MainWindow.setStatusBar(self.statusbar) 569 | self.menubar = QtWidgets.QMenuBar(MainWindow) 570 | self.menubar.setGeometry(QtCore.QRect(0, 0, 873, 26)) 571 | self.menubar.setObjectName("menubar") 572 | self.menuHW_config = QtWidgets.QMenu(self.menubar) 573 | self.menuHW_config.setObjectName("menuHW_config") 574 | MainWindow.setMenuBar(self.menubar) 575 | self.menuHW_config.addSeparator() 576 | self.menubar.addAction(self.menuHW_config.menuAction()) 577 | 578 | self.retranslateUi(MainWindow) 579 | self.comboBox_sig_len.setCurrentIndex(2) 580 | self.tabWidget_main.setCurrentIndex(1) 581 | self.tabWidget_cm.setCurrentIndex(2) 582 | self.comboBox_cm_window_size.setCurrentIndex(-1) 583 | self.tabWidget_fm.setCurrentIndex(2) 584 | self.comboBox_fm_window_size.setCurrentIndex(-1) 585 | self.comboBox_fm_spg_window_type.setCurrentIndex(0) 586 | self.comboBox_fm_spg_window_size.setCurrentIndex(-1) 587 | self.comboBox_ai_a.currentIndexChanged['int'].connect(self.comboBox_ai_b.setCurrentIndex) 588 | self.comboBox_ai_b.currentIndexChanged['int'].connect(self.comboBox_ai_a.setCurrentIndex) 589 | self.comboBox_ao_a.currentIndexChanged['int'].connect(self.comboBox_ao_b.setCurrentIndex) 590 | self.comboBox_ao_b.currentIndexChanged['int'].connect(self.comboBox_ao_a.setCurrentIndex) 591 | self.comboBox_ai_max.currentIndexChanged['int'].connect(self.comboBox_ai_min.setCurrentIndex) 592 | self.comboBox_ai_min.currentIndexChanged['int'].connect(self.comboBox_ai_max.setCurrentIndex) 593 | self.comboBox_ao_max.currentIndexChanged['int'].connect(self.comboBox_ao_min.setCurrentIndex) 594 | self.comboBox_ao_min.currentIndexChanged['int'].connect(self.comboBox_ao_max.setCurrentIndex) 595 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 596 | 597 | def retranslateUi(self, MainWindow): 598 | _translate = QtCore.QCoreApplication.translate 599 | MainWindow.setWindowTitle(_translate("MainWindow", "Twingo")) 600 | self.label_fm_max_f.setText(_translate("MainWindow", "(max/set) f [Hz]:")) 601 | self.comboBox_out_sig_type.setToolTip(_translate("MainWindow", "select output signal and analysis type")) 602 | self.comboBox_out_sig_type.setCurrentText(_translate("MainWindow", "sine")) 603 | self.comboBox_out_sig_type.setItemText(0, _translate("MainWindow", "sine")) 604 | self.comboBox_out_sig_type.setItemText(1, _translate("MainWindow", "square")) 605 | self.comboBox_out_sig_type.setItemText(2, _translate("MainWindow", "white noise")) 606 | self.comboBox_out_sig_type.setItemText(3, _translate("MainWindow", "impulse")) 607 | self.comboBox_out_sig_type.setItemText(4, _translate("MainWindow", "sweep")) 608 | self.comboBox_out_sig_type.setItemText(5, _translate("MainWindow", "ess")) 609 | self.comboBox_out_sig_type.setItemText(6, _translate("MainWindow", "zeros")) 610 | self.lineEdit_max_f.setToolTip(_translate("MainWindow", "set/end freqency / higher cutoff of the filter")) 611 | self.pushButton_stop.setToolTip(_translate("MainWindow", "stop continuous measurement")) 612 | self.pushButton_stop.setText(_translate("MainWindow", "Stop")) 613 | self.label_min_f.setText(_translate("MainWindow", "min f [Hz]:")) 614 | self.lineEdit_min_f.setToolTip(_translate("MainWindow", "start freqency of the sweeps /lower cutoff of the filter.")) 615 | self.label_14.setText(_translate("MainWindow", "output sig.:")) 616 | self.comboBox_sig_len.setToolTip(_translate("MainWindow", "finite measurement length")) 617 | self.comboBox_sig_len.setCurrentText(_translate("MainWindow", "1")) 618 | self.comboBox_sig_len.setItemText(0, _translate("MainWindow", "0.25")) 619 | self.comboBox_sig_len.setItemText(1, _translate("MainWindow", "0.5")) 620 | self.comboBox_sig_len.setItemText(2, _translate("MainWindow", "1")) 621 | self.comboBox_sig_len.setItemText(3, _translate("MainWindow", "2")) 622 | self.comboBox_sig_len.setItemText(4, _translate("MainWindow", "3")) 623 | self.comboBox_sig_len.setItemText(5, _translate("MainWindow", "4")) 624 | self.comboBox_sig_len.setItemText(6, _translate("MainWindow", "5")) 625 | self.comboBox_sig_len.setItemText(7, _translate("MainWindow", "6")) 626 | self.comboBox_sig_len.setItemText(8, _translate("MainWindow", "7")) 627 | self.comboBox_sig_len.setItemText(9, _translate("MainWindow", "8")) 628 | self.comboBox_sig_len.setItemText(10, _translate("MainWindow", "9")) 629 | self.comboBox_sig_len.setItemText(11, _translate("MainWindow", "10")) 630 | self.comboBox_sig_len.setItemText(12, _translate("MainWindow", "15")) 631 | self.comboBox_sig_len.setItemText(13, _translate("MainWindow", "20")) 632 | self.label_15.setText(_translate("MainWindow", "sig length [s]:")) 633 | self.pushButton_start.setToolTip(_translate("MainWindow", "start measurement")) 634 | self.pushButton_start.setText(_translate("MainWindow", "START")) 635 | self.pushButton_detectDAQ.setToolTip(_translate("MainWindow", "detect data acquistion devices")) 636 | self.pushButton_detectDAQ.setText(_translate("MainWindow", "Detect DAQ")) 637 | self.comboBox_dev_list.setToolTip(_translate("MainWindow", "Select DAQ device from the list")) 638 | self.label_7.setText(_translate("MainWindow", "DAQ Input settings")) 639 | self.label_4.setText(_translate("MainWindow", "Ch.A")) 640 | self.comboBox_ai_a.setToolTip(_translate("MainWindow", "Select input channel A")) 641 | self.label_3.setText(_translate("MainWindow", "Ch.B")) 642 | self.comboBox_ai_b.setToolTip(_translate("MainWindow", "Select input channel B")) 643 | self.label_5.setText(_translate("MainWindow", "Config")) 644 | self.comboBox_ai_terminal_cfg.setToolTip(_translate("MainWindow", "Select input configuration")) 645 | self.label.setText(_translate("MainWindow", "Max limt [V]")) 646 | self.comboBox_ai_max.setToolTip(_translate("MainWindow", "Select upper input signal limit")) 647 | self.label_2.setText(_translate("MainWindow", "Min. limit [V]")) 648 | self.comboBox_ai_min.setToolTip(_translate("MainWindow", "Select lower input signal limit")) 649 | self.label_6.setText(_translate("MainWindow", "Fs [Hz]")) 650 | self.label_8.setText(_translate("MainWindow", "DAQ Output settings")) 651 | self.label_9.setText(_translate("MainWindow", "Ch.A")) 652 | self.comboBox_ao_a.setToolTip(_translate("MainWindow", "Select first output channel")) 653 | self.label_10.setText(_translate("MainWindow", "Ch.B")) 654 | self.comboBox_ao_b.setToolTip(_translate("MainWindow", "Select second output channel")) 655 | self.Label.setText(_translate("MainWindow", "Config")) 656 | self.label_11.setText(_translate("MainWindow", "Max limit [V]")) 657 | self.comboBox_ao_max.setToolTip(_translate("MainWindow", "Set upper ouput signal limit")) 658 | self.label_12.setText(_translate("MainWindow", "Min limit [V]")) 659 | self.comboBox_ao_min.setToolTip(_translate("MainWindow", "Set lower output signal limit")) 660 | self.label_13.setText(_translate("MainWindow", "Fs [Hz]")) 661 | self.tabWidget_main.setTabText(self.tabWidget_main.indexOf(self.tab_settings), _translate("MainWindow", "Settings")) 662 | self.groupBox.setTitle(_translate("MainWindow", "(max/set) f [Hz]:")) 663 | self.dial_cm_vfine_freq.setToolTip(_translate("MainWindow", "freq dial 1Hz -100 Hz")) 664 | self.dial_cm_fine_freq.setToolTip(_translate("MainWindow", "freq dial 0Hz -1kHz")) 665 | self.dial_cm_coarse_freq.setToolTip(_translate("MainWindow", "freq dial 0Hz -20kHz")) 666 | self.label_26.setText(_translate("MainWindow", "Time (s)")) 667 | self.checkBox_cm_tm_hold_a.setToolTip(_translate("MainWindow", "hold ch. A plot")) 668 | self.checkBox_cm_tm_hold_a.setText(_translate("MainWindow", "hold A")) 669 | self.checkBox_cm_tm_hold_b.setToolTip(_translate("MainWindow", "hold ch.B plot")) 670 | self.checkBox_cm_tm_hold_b.setText(_translate("MainWindow", "hold B")) 671 | self.tabWidget_cm.setTabText(self.tabWidget_cm.indexOf(self.tab_cm_timeseries), _translate("MainWindow", "timeseries")) 672 | self.label_21.setText(_translate("MainWindow", "window:")) 673 | self.comboBox_cm_window_type.setToolTip(_translate("MainWindow", "select fft window type")) 674 | self.comboBox_cm_window_type.setItemText(0, _translate("MainWindow", "blackmanharris")) 675 | self.comboBox_cm_window_type.setItemText(1, _translate("MainWindow", "hamming")) 676 | self.comboBox_cm_window_type.setItemText(2, _translate("MainWindow", "hann")) 677 | self.comboBox_cm_window_type.setItemText(3, _translate("MainWindow", "blackman")) 678 | self.comboBox_cm_window_type.setItemText(4, _translate("MainWindow", "boxcar")) 679 | self.comboBox_cm_window_type.setItemText(5, _translate("MainWindow", "flattop")) 680 | self.label_22.setText(_translate("MainWindow", "size:")) 681 | self.comboBox_cm_window_size.setToolTip(_translate("MainWindow", "select fft window length")) 682 | self.label_27.setText(_translate("MainWindow", "Frequency (Hz)")) 683 | self.checkBox_cm_sp_hold_a.setToolTip(_translate("MainWindow", "hold ch.A plot")) 684 | self.checkBox_cm_sp_hold_a.setText(_translate("MainWindow", "hold A")) 685 | self.checkBox_cm_sp_hold_b.setToolTip(_translate("MainWindow", "hold ch.B plot")) 686 | self.checkBox_cm_sp_hold_b.setText(_translate("MainWindow", "hold B")) 687 | self.tabWidget_cm.setTabText(self.tabWidget_cm.indexOf(self.tab_cm_spectrum), _translate("MainWindow", "spectrum")) 688 | self.label_28.setText(_translate("MainWindow", "size:")) 689 | self.label_29.setText(_translate("MainWindow", "Stereo Width [-]")) 690 | self.checkBox_cm_ph_hold_1.setText(_translate("MainWindow", "hold 1")) 691 | self.checkBox_cm_ph_hold_2.setText(_translate("MainWindow", "hold 2")) 692 | self.tabWidget_cm.setTabText(self.tabWidget_cm.indexOf(self.tab_cm_phase), _translate("MainWindow", "phase")) 693 | self.tabWidget_main.setTabText(self.tabWidget_main.indexOf(self.tab_cm), _translate("MainWindow", "Continuous Meas")) 694 | self.label_23.setText(_translate("MainWindow", "Time (s)")) 695 | self.checkBox_fm_tm_hold_a.setStatusTip(_translate("MainWindow", "hold ch. A plot")) 696 | self.checkBox_fm_tm_hold_a.setText(_translate("MainWindow", "hold A")) 697 | self.checkBox_fm_tm_hold_b.setStatusTip(_translate("MainWindow", "hold ch. B plot")) 698 | self.checkBox_fm_tm_hold_b.setText(_translate("MainWindow", "hold B")) 699 | self.tabWidget_fm.setTabText(self.tabWidget_fm.indexOf(self.tab_timeseries), _translate("MainWindow", "timeseries")) 700 | self.label_19.setText(_translate("MainWindow", "window:")) 701 | self.comboBox_fm_window_type.setToolTip(_translate("MainWindow", "select fft window type")) 702 | self.comboBox_fm_window_type.setItemText(0, _translate("MainWindow", "blackmanharris")) 703 | self.comboBox_fm_window_type.setItemText(1, _translate("MainWindow", "hann")) 704 | self.comboBox_fm_window_type.setItemText(2, _translate("MainWindow", "hamming")) 705 | self.comboBox_fm_window_type.setItemText(3, _translate("MainWindow", "blackman")) 706 | self.comboBox_fm_window_type.setItemText(4, _translate("MainWindow", "boxcar")) 707 | self.comboBox_fm_window_type.setItemText(5, _translate("MainWindow", "flattop")) 708 | self.label_20.setText(_translate("MainWindow", "size:")) 709 | self.comboBox_fm_window_size.setToolTip(_translate("MainWindow", "select fft window length")) 710 | self.label_24.setText(_translate("MainWindow", "Frequency (Hz)")) 711 | self.checkBox_fm_sp_hold_a.setStatusTip(_translate("MainWindow", "hold ch. A plot")) 712 | self.checkBox_fm_sp_hold_a.setText(_translate("MainWindow", "hold A")) 713 | self.checkBox_fm_sp_hold_b.setStatusTip(_translate("MainWindow", "hold ch. B plot")) 714 | self.checkBox_fm_sp_hold_b.setText(_translate("MainWindow", "hold B")) 715 | self.tabWidget_fm.setTabText(self.tabWidget_fm.indexOf(self.tab_spectrum), _translate("MainWindow", "spectrum")) 716 | self.label_17.setText(_translate("MainWindow", "window:")) 717 | self.comboBox_fm_spg_window_type.setToolTip(_translate("MainWindow", "select fft window type")) 718 | self.comboBox_fm_spg_window_type.setItemText(0, _translate("MainWindow", "hann")) 719 | self.comboBox_fm_spg_window_type.setItemText(1, _translate("MainWindow", "hamming")) 720 | self.comboBox_fm_spg_window_type.setItemText(2, _translate("MainWindow", "blackman")) 721 | self.comboBox_fm_spg_window_type.setItemText(3, _translate("MainWindow", "blackmanharris")) 722 | self.comboBox_fm_spg_window_type.setItemText(4, _translate("MainWindow", "flattop")) 723 | self.label_16.setText(_translate("MainWindow", "size:")) 724 | self.comboBox_fm_spg_window_size.setToolTip(_translate("MainWindow", "select fft window length")) 725 | self.label_25.setText(_translate("MainWindow", "Time (s)")) 726 | self.label_18.setText(_translate("MainWindow", "chan:")) 727 | self.comboBox_fm_spg_chan.setToolTip(_translate("MainWindow", "select input chanel for spectrogram")) 728 | self.comboBox_fm_spg_chan.setItemText(0, _translate("MainWindow", "A")) 729 | self.comboBox_fm_spg_chan.setItemText(1, _translate("MainWindow", "B")) 730 | self.checkBox_fm_spg_log_amp.setToolTip(_translate("MainWindow", "set color scale to logarithmic")) 731 | self.checkBox_fm_spg_log_amp.setText(_translate("MainWindow", "Log(Amp)")) 732 | self.checkBox_fm_log_f.setText(_translate("MainWindow", "Log(f)")) 733 | self.tabWidget_fm.setTabText(self.tabWidget_fm.indexOf(self.tab_spg), _translate("MainWindow", "spectrogram")) 734 | self.tabWidget_main.setTabText(self.tabWidget_main.indexOf(self.tab_fm), _translate("MainWindow", "Finite Meas")) 735 | self.menuHW_config.setTitle(_translate("MainWindow", "File")) 736 | 737 | 738 | if __name__ == "__main__": 739 | import sys 740 | app = QtWidgets.QApplication(sys.argv) 741 | MainWindow = QtWidgets.QMainWindow() 742 | ui = Ui_MainWindow() 743 | ui.setupUi(MainWindow) 744 | MainWindow.show() 745 | sys.exit(app.exec_()) 746 | --------------------------------------------------------------------------------