import logging import os import numpy as np from PyQt5 import uic from PyQt5.QtCore import QRegExp, Qt, pyqtSignal, pyqtSlot from PyQt5.QtGui import QBrush, QColor, QPen, QRegExpValidator from PyQt5.QtWidgets import QApplication, QDialog from urh.plugins.Plugin import SignalEditorPlugin from urh.signalprocessing.IQArray import IQArray from urh.ui.painting.SceneManager import SceneManager from urh.util.Formatter import Formatter from urh.util.Logger import logger class InsertSinePlugin(SignalEditorPlugin): insert_sine_wave_clicked = pyqtSignal() INSERT_INDICATOR_COLOR = QColor(0, 255, 0, 80) def __init__(self): self.__dialog_ui = None # type: QDialog self.complex_wave = None self.__amplitude = 0.5 self.__frequency = 10 self.__phase = 0 self.__sample_rate = 1e6 self.__num_samples = int(1e6) self.original_data = None self.draw_data = None self.position = 0 super().__init__(name="InsertSine") @property def dialog_ui(self) -> QDialog: if self.__dialog_ui is None: dir_name = os.path.dirname(os.readlink(__file__)) if os.path.islink(__file__) else os.path.dirname(__file__) logging.getLogger().setLevel(logging.WARNING) self.__dialog_ui = uic.loadUi(os.path.realpath(os.path.join(dir_name, "insert_sine_dialog.ui"))) logging.getLogger().setLevel(logger.level) self.__dialog_ui.setAttribute(Qt.WA_DeleteOnClose) self.__dialog_ui.setModal(True) self.__dialog_ui.doubleSpinBoxAmplitude.setValue(self.__amplitude) self.__dialog_ui.doubleSpinBoxFrequency.setValue(self.__frequency) self.__dialog_ui.doubleSpinBoxPhase.setValue(self.__phase) self.__dialog_ui.doubleSpinBoxSampleRate.setValue(self.__sample_rate) self.__dialog_ui.doubleSpinBoxNSamples.setValue(self.__num_samples) self.__dialog_ui.lineEditTime.setValidator( QRegExpValidator(QRegExp(r"[0-9]+([nmµ]?|([\.,][0-9]{1,3}[nmµ]?))?$")) ) scene_manager = SceneManager(self.dialog_ui.graphicsViewSineWave) self.__dialog_ui.graphicsViewSineWave.scene_manager = scene_manager self.insert_indicator = scene_manager.scene.addRect(0, -2, 0, 4, QPen(QColor(Qt.transparent), 0), QBrush(self.INSERT_INDICATOR_COLOR)) self.insert_indicator.stackBefore(scene_manager.scene.selection_area) self.set_time() return self.__dialog_ui @property def amplitude(self) -> float: return self.__amplitude @amplitude.setter def amplitude(self, value: float): if value != self.amplitude: self.__amplitude = value self.draw_sine_wave() @property def frequency(self) -> float: return self.__frequency @frequency.setter def frequency(self, value: float): if value != self.frequency: self.__frequency = value self.draw_sine_wave() @property def phase(self) -> float: return self.__phase @phase.setter def phase(self, value: float): if value != self.phase: self.__phase = value self.draw_sine_wave() @property def sample_rate(self) -> float: return self.__sample_rate @sample_rate.setter def sample_rate(self, value: float): if value != self.sample_rate: self.__sample_rate = value self.set_time() self.draw_sine_wave() @property def num_samples(self) -> int: return self.__num_samples @num_samples.setter def num_samples(self, value: int): value = int(value) if value != self.num_samples: self.__num_samples = value self.set_time() self.draw_sine_wave() def create_connects(self): pass def create_dialog_connects(self): self.dialog_ui.doubleSpinBoxAmplitude.editingFinished.connect( self.on_double_spin_box_amplitude_editing_finished) self.dialog_ui.doubleSpinBoxFrequency.editingFinished.connect( self.on_double_spin_box_frequency_editing_finished) self.dialog_ui.doubleSpinBoxPhase.editingFinished.connect(self.on_double_spin_box_phase_editing_finished) self.dialog_ui.doubleSpinBoxSampleRate.editingFinished.connect( self.on_double_spin_box_sample_rate_editing_finished) self.dialog_ui.doubleSpinBoxNSamples.editingFinished.connect(self.on_spin_box_n_samples_editing_finished) self.dialog_ui.lineEditTime.editingFinished.connect(self.on_line_edit_time_editing_finished) self.dialog_ui.buttonBox.accepted.connect(self.on_button_box_accept) self.dialog_ui.buttonBox.rejected.connect(self.on_button_box_reject) self.__dialog_ui.finished.connect(self.on_dialog_finished) def get_insert_sine_dialog(self, original_data, position, sample_rate=None, num_samples=None) -> QDialog: if sample_rate is not None: self.__sample_rate = sample_rate self.dialog_ui.doubleSpinBoxSampleRate.setValue(sample_rate) if num_samples is not None: self.__num_samples = int(num_samples) self.dialog_ui.doubleSpinBoxNSamples.setValue(num_samples) self.original_data = original_data self.position = position self.set_time() self.draw_sine_wave() self.create_dialog_connects() return self.dialog_ui def draw_sine_wave(self): if self.dialog_ui.graphicsViewSineWave.scene_manager: self.dialog_ui.graphicsViewSineWave.scene_manager.clear_path() QApplication.instance().setOverrideCursor(Qt.WaitCursor) self.__set_status_of_editable_elements(enabled=False) t = np.arange(0, self.num_samples) / self.sample_rate arg = 2 * np.pi * self.frequency * t + self.phase self.complex_wave = np.empty(len(arg), dtype=np.complex64) self.complex_wave.real = np.cos(arg) self.complex_wave.imag = np.sin(arg) self.complex_wave = IQArray(self.amplitude * self.complex_wave).convert_to(self.original_data.dtype) self.draw_data = np.insert(self.original_data[:, 0], self.position, self.complex_wave[:, 0]) y, h = self.dialog_ui.graphicsViewSineWave.view_rect().y(), self.dialog_ui.graphicsViewSineWave.view_rect().height() self.insert_indicator.setRect(self.position, y - h, self.num_samples, 2 * h + abs(y)) self.__set_status_of_editable_elements(enabled=True) QApplication.instance().restoreOverrideCursor() self.dialog_ui.graphicsViewSineWave.plot_data(self.draw_data) self.dialog_ui.graphicsViewSineWave.show_full_scene() def __set_status_of_editable_elements(self, enabled: bool): for obj in ("doubleSpinBoxAmplitude", "doubleSpinBoxFrequency", "doubleSpinBoxPhase", "doubleSpinBoxSampleRate", "doubleSpinBoxNSamples", "lineEditTime", "buttonBox"): getattr(self.dialog_ui, obj).setEnabled(enabled) def set_time(self): self.dialog_ui.lineEditTime.setText(Formatter.science_time(self.num_samples / self.sample_rate, decimals=3, append_seconds=False, remove_spaces=True)) @pyqtSlot() def on_double_spin_box_amplitude_editing_finished(self): self.amplitude = self.dialog_ui.doubleSpinBoxAmplitude.value() @pyqtSlot() def on_double_spin_box_frequency_editing_finished(self): self.frequency = self.dialog_ui.doubleSpinBoxFrequency.value() @pyqtSlot() def on_double_spin_box_phase_editing_finished(self): self.phase = self.dialog_ui.doubleSpinBoxPhase.value() @pyqtSlot() def on_double_spin_box_sample_rate_editing_finished(self): self.sample_rate = self.dialog_ui.doubleSpinBoxSampleRate.value() @pyqtSlot() def on_spin_box_n_samples_editing_finished(self): self.num_samples = self.dialog_ui.doubleSpinBoxNSamples.value() @pyqtSlot() def on_line_edit_time_editing_finished(self): time_str = self.dialog_ui.lineEditTime.text().replace(",", ".") suffix = "" try: t = float(time_str) except ValueError: suffix = time_str[-1] try: t = float(time_str[:-1]) except ValueError: return factor = 10 ** -9 if suffix == "n" else 10 ** -6 if suffix == "µ" else 10 ** -3 if suffix == "m" else 1 time_val = t * factor if self.sample_rate * time_val >= 1: self.dialog_ui.doubleSpinBoxNSamples.setValue(self.sample_rate * time_val) self.dialog_ui.doubleSpinBoxNSamples.editingFinished.emit() else: self.set_time() @pyqtSlot() def on_button_box_reject(self): self.dialog_ui.reject() @pyqtSlot() def on_button_box_accept(self): self.insert_sine_wave_clicked.emit() self.dialog_ui.accept() @pyqtSlot() def on_dialog_finished(self): self.sender().graphicsViewSineWave.eliminate() self.__dialog_ui = None